/* EngineeringFunctionImpl.java

	Purpose:
		
	Description:
		
	History:
		Apr 19, 2010 2:23:06 PM, Created by ashish

Copyright (C) 2010 Potix Corporation. All Rights Reserved.

*/
package org.zkoss.zssex.formula.fn;

import org.apache.commons.math.MathException;
import org.apache.commons.math.special.Erf;
import org.zkoss.poi.ss.formula.eval.ErrorEval;
import org.zkoss.poi.ss.formula.eval.EvaluationException;
import org.zkoss.poi.ss.formula.eval.NumberEval;
import org.zkoss.poi.ss.formula.eval.StringEval;
import org.zkoss.poi.ss.formula.eval.ValueEval;
import org.zkoss.poi.ss.formula.functions.Function;
import org.zkoss.poi.ss.formula.functions.NumericFunction;
import org.zkoss.poi.ss.formula.functions.NumericFunctionHelper;
import org.zkoss.poi.ss.formula.functions.TextFunction;
import org.zkoss.poi.ss.formula.functions.TextFunctionHelper;
import org.zkoss.poi.ss.formula.functions.Complex;
import org.zkoss.poi.ss.formula.functions.UtilFns;


/**
 * @author ashish
 *
 */
public class EngineeringFunctionImpl {

	//the parameters to decide to use Bessel function's approximation formula or use recursion method.
	final private static double THRESHOLD = 30.0;
	final private static double minE = 1.0E-10d;
	final private static long iterateMax = 100;
	private static long fac(long n){
		long result = 1;
		if(n <= 1)
		{
			return 1;
		}
		else
		{
			while( n >= 2)
			{
				result *= n;
				n--;
			}
			return result;
		}		
	}

	public static final Function BESSELI = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			double x = NumericFunction.singleOperandEvaluate(args[0], srcRowIndex, srcColumnIndex);
			int n = (int) NumericFunction.singleOperandEvaluate(args[1], srcRowIndex, srcColumnIndex);
			return NumericFunctionHelper.checkValue(engineeringBesseli(x, n));
		}
	};
	public static final Function BESSELJ = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			double x = NumericFunction.singleOperandEvaluate(args[0], srcRowIndex, srcColumnIndex);
			int n = (int) NumericFunction.singleOperandEvaluate(args[1], srcRowIndex, srcColumnIndex);
			return NumericFunctionHelper.checkValue(engineeringBesselj(x, n));
		}

		/**
		 * @param x
		 * @param n
		 * @return
		 * @throws EvaluationException
		 */
	};

	private static double engineeringBesselj(double x, int n)
			throws EvaluationException {
		/*
		 * inf (-1)^k (x/2)^(n+2k) Jn(x) = SUM TERM(n,k) with TERM(n,k) ==
		 * --------------------- k=0 k! (n+k)!
		 * 
		 * and the Approximation for this BESSEL function Jn(x):
		 * 
		 * Jn(x) ~ sqrt( 2 / (PI x) ) cos( x - n PI/2 - PI/4 ) for x>=0.
		 * 
		 * but it's only accurate when x is big enough.
		 */
		double absX, result = 0;
		int k = 1;
		// get |x| for finding which way to calculate Jn(x)
		absX = Math.abs(x);
		if (n < 0) {
			throw new EvaluationException(ErrorEval.NUM_ERROR);
		}

		// use iteration method.
		if (absX < THRESHOLD) {
			/*
			 * Start the iteration, first find TERM(n,0)
			 * 
			 * TERM(n,0) = (x/2)^n / n!
			 */
			double Term = Math.pow(x / 2, n) / fac((long) n);
			result = Term;
			double upTerm = -(Math.pow(x, 2) / 4);
			do {
				/*
				 * For getting TERM(n,k) from TERM(n,k-1): (math reduced result)
				 * 
				 * -(x^2/4) TERM(n,k) = ---------- TERM(n,k-1) k(n+k)
				 */
				Term = (upTerm / (k * (n + k))) * Term;

				// add Terms to sum up the result for many times until it's
				// enough!
				result += Term;
			} while ((Math.abs(Term) > minE) && (++k) < iterateMax);
		}
		// use approximated formula.
		else {
			/*
			 * Approximation for the BESSEL function Jn(x):
			 * 
			 * Jn(x) ~ sqrt( 2 / (PI x) ) cos( x - n PI/2 - PI/4 ) for x >= 0.
			 * 
			 * When n IN {0,2,4,...} is axially symmetric at x=0, which is Jn(x)
			 * = Jn(-x). So the approximation for x < 0 is:
			 * 
			 * Jn(x) = Jn(|x|) for x < 0 and n IN {0,2,4,...}.
			 * 
			 * When n IN {1,3,5,...} is point-symmetric at x=0, which is J_n(x)
			 * = -J_n(-x). So the approximation for x < 0 is:
			 * 
			 * Jn(x) = -Jn(|x|) for x < 0 and n IN {1,3,5,...}.
			 */

			result = Math.sqrt(2 / ((Math.PI) * absX))
					* Math.cos(absX - (double) n * Math.PI / 2 - Math.PI / 4);

			// x < 0 and n IN {1,3,5,...} , Jn(x) = -Jn(|x|)
			if (x < 0 && (n % 2) == 1)
				result *= -1;
		}
		return result;
	}
	
	//for find the value of Kn(x), we must first find K0(x) & K1(x)
	private static double BesselK0(double x) throws EvaluationException {
		double	result;

		if( x <= 2.0 )
		{
			double	x2 = x * 0.5;
			double	y = x2 * x2;
			
			Object para[] = new Double[2];
			para[0] = new Double(x);
			para[1] = new Double(0);
			
			//don't know why and how it works, maybe another approximate formula.
			result = -1 * Math.log(x2) * engineeringBesseli(x, 0) + ( -0.57721566 + y * ( 0.42278420 + y * ( 0.23069756 + y * ( 0.3488590e-1 +y * ( 0.262698e-2 + y * ( 0.10750e-3 + y * 0.74e-5 ) ) ) ) ) );
		}
		else
		{
			double	y = 2.0 / x;
			//don't know why and how it works, maybe another approximate formula.
			result = Math.exp( -1 * x) / Math.sqrt( x) * ( 1.25331414 + y * ( -0.7832358e-1 + y * ( 0.2189568e-1 + y * ( -0.1062446e-1 + y * ( 0.587872e-2 + y * ( -0.251540e-2 + y * 0.53208e-3 ) ) ) ) ) );
		}

		return result;
	}
	private static double BesselK1(double x) throws EvaluationException {
		double	result;

		if( x <= 2.0 )
		{
			double	x2 = x * 0.5;
			double	y = x2 * x2;
			Object para_for_i[] = new Double[2];
			para_for_i[0] = new Double(x);
			para_for_i[1] = new Double(1);
			
			//don't know why and how it works, maybe another approximate formula.
	        result = Math.log( x2 ) * engineeringBesseli(x, 1) + ( 1.0 + y * ( 0.15443144 + y * ( -0.67278579 + y * ( -0.18156897 + y * ( -0.1919402e-1 + y * ( -0.110404e-2 + y * ( -0.4686e-4 ) ) ) ) ) ) ) / x;
		}
		else
		{
			double	y = 2.0 / x;
			
			//don't know why and how it works, maybe another approximate formula.
			result = Math.exp( -1 * x ) / Math.sqrt( x ) * ( 1.25331414 + y * ( 0.23498619 + y * ( -0.3655620e-1 + y * ( 0.1504268e-1 + y * ( -0.780353e-2 + y * ( 0.325614e-2 + y * ( -0.68245e-3 ) ) ) ) ) ) );
		}

		return result;
	}

	/**
	 * @param x 
	 * @param n 
	 * @return Bessel function result
	 * @throws EvaluationException
	 */
	public static double engineeringBesseli(double x, int n) throws EvaluationException {
		/* 
		 *           inf                                   (x/2)^(n+2k)
		 * In(x)  =  SUM   TERM(n,k)   with   TERM(n,k) == -------------
		 *           k=0                                    k! (n+k)!
		 *            
		 * and the Approximation for the BESSEL function In(x) for great x:
		 *
		 * In(x)  ~  e^x / sqrt( 2 * PI * x )  for  x>=0.
		 */
		double absX, result = 0;
		if (n < 0) {
			throw new EvaluationException(ErrorEval.NUM_ERROR);
		}
		//get |x| for finding which way to calculate In(x)
		absX = Math.abs(x);
		
		//use iteration method.
		if(absX < THRESHOLD) {
			/* Start the iteration, first TERM(n,0)
			 *
		     * TERM(n,0) = (x/2)^n / n!
			 */
			double Term = Math.pow( x/2, n)/fac((long)n);
			result = Term;
			double upTerm = Math.pow( x, 2)/4;
			int k=1;
			do
			{
				/* For getting TERM(n,k) from TERM(n,k-1): (math reduced result)
				 * 
				 *               x^2/4
				 * TERM(n,k) =  -------- TERM(n,k-1) 
				 *               k(n+k)
				 */
				Term = (upTerm/(k*(n+k))) * Term;
				
				//add Terms to sum up the result for many times until it's enough!
				result += Term;
			}while( (Math.abs(Term) > minE) && (++k) < iterateMax );
		}
		
		//use approximated formula.
		else
		{
		/* Approximation for the BESSEL function In(x):
		 *
		 * In(x)  ~  e^x / sqrt( 2 PI x )  for  x>=0.
		 *
		 * When n IN {0,2,4,...} is axially symmetric at x=0, which is In(x) = In(-x).
		 * So the approximation for x < 0 is:
		 *
		 * In(x)  =  In(|x|)  for  x < 0  and  n IN {0,2,4,...}.
		 *
		 * When n IN {1,3,5,...} is point-symmetric at x=0, which is In(x) = -In(-x).
		 * So the approximation for x < 0 is:
		 *
		 *  In(x)  =  -In(|x|)  for  x < 0  and  n IN {1,3,5,...}.
		 */
			result = Math.exp(x) / Math.sqrt(2 * Math.PI * x);
			//x < 0  and  n IN {1,3,5,...} , In(x) = -In(|x|)
			if( x < 0 && ( n % 2 ) == 1 )
				result *= -1;
			
		}
		return result;
	}

	public static final Function BESSELK = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			double x, result;
			int n;
			x = NumericFunction.singleOperandEvaluate(args[0], srcRowIndex, srcColumnIndex);
			n = (int) NumericFunction.singleOperandEvaluate(args[1], srcRowIndex, srcColumnIndex);
			if (n < 0) {
				throw new EvaluationException(ErrorEval.NUM_ERROR);
			}
			//if x is small enough, use approximate formula to get result.
			if( x == 0) result = BesselK0(x);
			else if ( x == 1) result = BesselK1(x);
			else
			{
				double	temp;
				double	term = BesselK0( x );
				result = BesselK1( x );
				for(int i = 1 ; i < n ; i++ )
				{
					temp = term + i * 2 / x * result;
					term = result;
					result = temp;
				}
			}
			return NumericFunctionHelper.checkValue(result);
		}
	};

	//for find the value of Yn(x), we must first find Y0(x) & Y1(x)
	private static double BesselY0(double x) throws EvaluationException {
		
		double	result;

		if( x < 8.0 )
		{
			//magic variables??
			double	y = x * x;

			double	f1 = -2957821389.0 + y * ( 7062834065.0 + y * ( -512359803.6 +
						y * ( 10879881.29 + y * ( -86327.92757 + y * 228.4622733 ) ) ) );

			double	f2 = 40076544269.0 + y * ( 745249964.8 + y * ( 7189466.438 +
						y * ( 47447.26470 + y * ( 226.1030244 + y ) ) ) );

			Object para[] = new Double[2];
			para[0] = new Double(x);
			para[1] = new Double(0);
			
			//magic formula???
	        result = f1 / f2 + 0.636619772 * engineeringBesselj(x, 0) * Math.log( x );
		}
		else
		{
			//magic variables??
			double	z = 8.0 / x;
			double	y = z * z;
			double	x2 = x - 0.785398164;

			double	f1 = 1.0 + y * ( -0.1098628627e-2 + y * ( 0.2734510407e-4 +
							y * ( -0.2073370639e-5 + y * 0.2093887211e-6 ) ) );

			double	f2 = -0.1562499995e-1 + y * ( 0.1430488765e-3 +
							y * ( -0.6911147651e-5 + y * ( 0.7621095161e-6 +
							y * ( -0.934945152e-7 ) ) ) );
			
			//magic formula???
			result = Math.sqrt( 0.636619772 / x ) * ( Math.sin( x2 ) * f1 + z * Math.cos( x2 ) * f2 );
		}

		return result;
	}
	
	private static double BesselY1(double x) throws EvaluationException {
		double	result;

		if( x < 8.0 )
		{
			//magic variables??
			double	y = x * x;

			double	f1 = x * ( -0.4900604943e13 + y * ( 0.1275274390e13 +
							y * ( -0.5153438139e11 + y * ( 0.7349264551e9 +
							y * ( -0.4237922726e7 + y * 0.8511937935e4 ) ) ) ) );

			double	f2 = 0.2499580570e14 + y * ( 0.4244419664e12 +
							y * ( 0.3733650367e10 + y * ( 0.2245904002e8 +
							y * ( 0.1020426050e6 + y * ( 0.3549632885e3 + y ) ) ) ) );

			Object para[] = new Double[2];
			para[0] = new Double(x);
			para[1] = new Double(1);
			
			//magic formula???
	        result = f1 / f2 + 0.636619772 * ( engineeringBesselj(x, 1) * Math.log( x ) - 1.0 / x );
		}
		else
		{
			//magic formula??? I need someone who is familiar with math to explain this!!!
			result = Math.sqrt( 0.636619772 / x ) * Math.sin( x - 2.356194491 );
		}
		return result;
	}

	public static final Function BESSELY = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			double x, result;
			int n;
			x = NumericFunction.singleOperandEvaluate(args[0], srcRowIndex, srcColumnIndex);
			n = (int) NumericFunction.singleOperandEvaluate(args[1], srcRowIndex, srcColumnIndex);
			if (n < 0) {
				throw new EvaluationException(ErrorEval.NUM_ERROR);
			}
			//if x is small enough, use approximate formula to get result.
			if( x == 0) result = BesselY0(x);
			else if ( x == 1) result = BesselY1(x);
			else
			{
				double temp;
				double term = BesselY0( x );
				result = BesselY1( x );

				for( int i = 1 ; i < n ; i++ )
				{
					temp = i * 2 / x * result - term;
					term = result;
					result = temp;
				}
			}
			return NumericFunctionHelper.checkValue(result);
		}
	};

	/* return decimal */
	private static int binToDec(String bin) throws EvaluationException {
		int places = bin.length();
		if(places > 10) {
			throw new EvaluationException(ErrorEval.NUM_ERROR);
		} else {
			for(int i = 0; i < places; i++) {
				if(bin.charAt(i)!='0' && bin.charAt(i)!='1') {
					throw new EvaluationException(ErrorEval.NUM_ERROR);
				}
			}
			Integer result = Integer.valueOf(bin, 2);
			if(places == 10) {
				result = Integer.valueOf(result.intValue() - 1024);
			}
			return result.intValue();
		}
	}

	
	public static final Function BIN2DEC = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			int num = (int) NumericFunction.singleOperandEvaluate(args[0], srcRowIndex, srcColumnIndex);
			return binToDec(Integer.toString(num));
		}
	};

	public static final Function BIN2HEX = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String bin = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			int places = 0;
			if(args.length==2) {
				places = TextFunctionHelper.evaluateIntArg(args[1], srcCellRow, srcCellCol);
			}
			return new StringEval(decToHex(binToDec(bin),places));
		}
	};

	public static final Function BIN2OCT = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String bin = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			int places = 0;
			if(args.length==2) {
				places = TextFunctionHelper.evaluateIntArg(args[1], srcCellRow, srcCellCol);
			}
			return new StringEval(decToOct(binToDec(bin), places));
		}
	};

	public static final Function COMPLEX = new TextFunction() {

		@Override
		protected ValueEval evaluateFunc(ValueEval[] args, int srcCellRow,
				int srcCellCol) throws EvaluationException {
			double real = TextFunctionHelper.evaluateDoubleArg(args[0], srcCellRow, srcCellCol);
			double imaginary = TextFunctionHelper.evaluateDoubleArg(args[1], srcCellRow, srcCellCol);
			String suffix = "i";
			if(args.length==3) {
				suffix = TextFunctionHelper.evaluateStringArg(args[2], srcCellRow, srcCellCol);
			}
			if(Double.isNaN(real) || Double.isNaN(imaginary) || (!"i".equals(suffix) && !"j".equals(suffix))) {
				throw new EvaluationException(ErrorEval.VALUE_INVALID);
			}			
			return new StringEval(UtilFns.format(new Complex(real, imaginary, suffix)));
		}
	};
	
	private static String decToBin(int dec, int places) throws EvaluationException {
		if(dec < -512 || dec > 511) {
			throw new EvaluationException(ErrorEval.NUM_ERROR);
		} else {
			String result = Integer.toBinaryString(dec);
			if(dec < 0){
				result = result.substring(result.length()-10, result.length());
			}
			if(places != 0 && result.length() < 10) {
		
				result = UtilFns.padZero(result, places);
			}
			return result;			
		}
	}

	public static final Function DEC2BIN = new TextFunction() {

		@Override
		protected ValueEval evaluateFunc(ValueEval[] args, int srcCellRow,
				int srcCellCol) throws EvaluationException {
			int dec = TextFunctionHelper.evaluateIntArg(args[0], srcCellRow, srcCellCol);
			int places = 0;
			if(args.length==2) {
				places = TextFunctionHelper.evaluateIntArg(args[1], srcCellRow, srcCellCol);
			}
			return new StringEval(decToBin(dec,places));
		}
	};
	
	/* return hexadecimal */
	private static String decToHex(int dec, int places) throws EvaluationException {
		String result = Integer.toHexString(dec).toUpperCase();
		if(dec < 0){
			StringBuffer sf = new StringBuffer("FFFFFFFFFF");
			result = sf.replace(10 - result.length(), 10, result).toString();
		}
		if(places != 0 && result.length() < 10) {
			result = UtilFns.padZero(result, places);
		}
		return result;
	}

	public static final Function DEC2HEX = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			int dec = TextFunctionHelper.evaluateIntArg(args[0], srcCellRow, srcCellCol);
			int places = 0;
			if(args.length==2) {
				places = TextFunctionHelper.evaluateIntArg(args[1], srcCellRow, srcCellCol);
			}
			 
			return new StringEval(decToHex(dec, places));
		}
	};
	/* return octal */
	private static String decToOct(int dec, int places) throws EvaluationException {
		if(dec < -536870912 || dec > 536870911) {
			throw new EvaluationException(ErrorEval.NUM_ERROR);
		} else {
			String result = Integer.toOctalString(dec);
			if(dec < 0){
				result = result.substring(result.length()-10, result.length());
			}
			if(places != 0 && result.length() < 10) {
				result = UtilFns.padZero(result, places);
			}
			return result;
		}
	}

	public static final Function DEC2OCT = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			int dec = TextFunctionHelper.evaluateIntArg(args[0], srcCellRow, srcCellCol);
			int places = 0;
			if(args.length==2) {
				places = TextFunctionHelper.evaluateIntArg(args[1], srcCellRow, srcCellCol);
			}
			return new StringEval(decToOct(dec, places));
		}
	};
	/* return true if String is hexdecimal */
	private static boolean isHex(String hex) {
		boolean result = true;
		for(int i = 0; i < hex.length(); i++) {
			if(!Character.isDigit(hex.charAt(i)) && !hex.substring(i, i+1).matches("[a-fA-F]")) {
				result = false;
			}
		}
		return result;
	}
	/* return decimal */
	private static long hexToDec(String hex) throws EvaluationException {
		int places = hex.length();
		if(places > 10) {
			throw new EvaluationException(ErrorEval.NUM_ERROR);
		} else {
			if(!isHex(hex)) {
				throw new EvaluationException(ErrorEval.NUM_ERROR);
			}
			Long result = Long.valueOf(hex, 16);
			if(result.longValue() > Long.valueOf("7fffffffff", 16).longValue()) {
				result = Long.valueOf(result.longValue()-Long.valueOf("10000000000", 16).longValue());
			}
			return result.longValue();
		}
	}

	public static final Function HEX2DEC = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String hex = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			return new NumberEval(hexToDec(hex));
		}
	};

	public static final Function HEX2BIN = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String hex = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			int places = 0;
			if(args.length==2) {
				places = TextFunctionHelper.evaluateIntArg(args[1], srcCellRow, srcCellCol);
			}
			return new StringEval(decToBin((int)hexToDec(hex),places));
		}
	};

	public static final Function HEX2OCT = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String hex = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			int places = 0;
			if(args.length==2) {
				places = TextFunctionHelper.evaluateIntArg(args[1], srcCellRow, srcCellCol);
			}
			return new StringEval(decToOct((int)hexToDec(hex),places));
		}
	};

	public static final Function DELTA = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			double n1 = NumericFunction.singleOperandEvaluate(args[0], srcRowIndex, srcColumnIndex);
			double n2 = NumericFunction.singleOperandEvaluate(args[1], srcRowIndex, srcColumnIndex);
			int result = 0;
			if(n1 == n2) {
				result = 1;
			}
			return result;
		}
	};

	public static final Function ERF = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			double lowerLimit = NumericFunction.singleOperandEvaluate(args[0], srcRowIndex, srcColumnIndex);
			double upperLimit = 0.0;
			if(lowerLimit < 0 || upperLimit < 0) {
				throw new EvaluationException(ErrorEval.NUM_ERROR);
			}
			try {
				double erf = Erf.erf(lowerLimit);
				if(args.length == 2) {
					upperLimit = NumericFunction.singleOperandEvaluate(args[1], srcRowIndex, srcColumnIndex);
					erf = Erf.erf(upperLimit) - erf;
				}
				return erf;
			} catch (MathException e) {
				throw new EvaluationException(ErrorEval.NUM_ERROR);
			}
		}
	};
	
	public static final Function ERFC = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			double x = NumericFunction.singleOperandEvaluate(args[0], srcRowIndex, srcColumnIndex);
			if(x < 0) {
				throw new EvaluationException(ErrorEval.NUM_ERROR);
			}
			try {
				double erfc = 1 - Erf.erf(x);
				return erfc;
			} catch (MathException e) {
				throw new EvaluationException(ErrorEval.NUM_ERROR);
			}
		}
	};

	public static final Function GESTEP = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			double num = NumericFunction.singleOperandEvaluate(args[0], srcRowIndex, srcColumnIndex);
			double step = NumericFunction.singleOperandEvaluate(args[1], srcRowIndex, srcColumnIndex);
			
			return num >= step ? 1 : 0;
		}
	};

	public static final Function IMABS = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcRowIndex, srcColumnIndex);
			return UtilFns.validateComplex(inum).abs();
		}
	};

	public static final Function IMAGINARY = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcRowIndex, srcColumnIndex);
			return UtilFns.validateComplex(inum).getImaginary();
		}
	};

	public static final Function IMARGUMENT = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcRowIndex, srcColumnIndex);
			Complex c = UtilFns.validateComplex(inum);
			double real = c.getReal();
			double img = c.getImaginary();
			/* original math formula:
			 * theta = arc tangent(img/real)
			 * but since MSFT IMARGUMENT() return value is (-pi, pi]
			 *           java.lang.math atan() return value is (-pi/2, pi/2]
			 * so instead use atan2()
			 */
			if(img == 0 && real == 0) //What is the theta of "0+0i"? Should return #DIV/0! 
			{
				throw new EvaluationException(ErrorEval.DIV_ZERO);
			} else {
				return Math.atan2(img, real);
			}
		}
	};
	public static final Function IMCONJUGATE = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			Complex c = UtilFns.validateComplex(inum);
			return new StringEval(UtilFns.format(UtilFns.cToComplex(c.conjugate(), c.getSuffix())));
		}
	};

	public static final Function IMCOS = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			Complex c = UtilFns.validateComplex(inum);
			return new StringEval(UtilFns.format(UtilFns.cToComplex(c.cos(), c.getSuffix())));
		}
	};

	public static final Function IMDIV = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String inum1 = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			String inum2 = TextFunctionHelper.evaluateStringArg(args[1], srcCellRow, srcCellCol);
			Complex c1 = UtilFns.validateComplex(inum1);
			Complex c2 = UtilFns.validateComplex(inum2);
			return new StringEval(UtilFns.format(UtilFns.cToComplex(c1.divide(c2), c1.getSuffix())));
		}
	};
	public static final Function IMEXP = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			Complex c = UtilFns.validateComplex(inum);
			String suffix = c.getSuffix();
			return new StringEval(UtilFns.format(UtilFns.cToComplex(new Complex(Math.E, 0, suffix).pow(c), suffix)));
		}
	};

	public static final Function IMLN = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			Complex c = UtilFns.validateComplex(inum);
			return new StringEval(UtilFns.format(UtilFns.cToComplex(c.log(), c.getSuffix())));
		}
	};

	public static final Function IMLOG10 = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			Complex c = UtilFns.validateComplex(inum);
			String suffix = c.getSuffix();
			return new StringEval(UtilFns.format(UtilFns.cToComplex(c.log().divide(new Complex(10, 0, suffix).log()), suffix)));
		}
	};

	public static final Function IMLOG2 = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			Complex c = UtilFns.validateComplex(inum);
			String suffix = c.getSuffix();
			return new StringEval(UtilFns.format(UtilFns.cToComplex(c.log().divide(new Complex(2, 0, suffix).log()), suffix)));
		}
	};

	public static final Function IMPOWER = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String inum1 = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			String inum2 = TextFunctionHelper.evaluateStringArg(args[1], srcCellRow, srcCellCol);
			Complex c1 = UtilFns.validateComplex(inum1);
			Complex c2 = UtilFns.validateComplex(inum2);
			return new StringEval(UtilFns.format(UtilFns.cToComplex(c1.pow(c2), c1.getSuffix())));
		}
	};
	/**
	 * Convert an object array to a complex array
	 * @param args ValueEval array
	 * @param srcCellRow row of source cell
	 * @param srcCellCol column of source cell
	 * @return complex array
	 * @throws EvaluationException 
	 */
	public static Complex[] toComplexArray(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
		Complex[] complex = new Complex[args.length];
		for(int i = 0; i < args.length; i++) {
			if (args[i] instanceof ErrorEval) {
				throw new EvaluationException((ErrorEval) args[i]);
			}else{
				complex[i] = UtilFns.validateComplex(TextFunctionHelper.evaluateStringArg(args[i], srcCellRow, srcCellCol));
				if(!complex[i].getSuffix().equals("k")) {
					for(int j = 0; j < i; j++) {
						if(!complex[j].getSuffix().equals("k") && !complex[i].getSuffix().equals(complex[j].getSuffix())) {
							throw new EvaluationException(ErrorEval.NUM_ERROR);
						}											
					}
				}
			}
		}
		return complex;
	}

	public static final Function IMPRODUCT = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			Complex[] c = toComplexArray(args, srcCellRow, srcCellCol);
			org.apache.commons.math.complex.Complex product = null;
			for(int i = 0; i < c.length-1; i++) {
				 product = c[i].multiply(c[i+1]);
			}
			return new StringEval(UtilFns.format(UtilFns.cToComplex(product, UtilFns.validateComplex(TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol)).getSuffix())));
		}
	};

	public static final Function IMREAL = new NumericFunction() {

		@Override
		public double eval(ValueEval[] args, int srcRowIndex, int srcColumnIndex) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcRowIndex, srcColumnIndex);
			return UtilFns.validateComplex(inum).getReal();
		}
	};

	public static final Function IMSIN = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			Complex c = UtilFns.validateComplex(inum);
			return new StringEval(UtilFns.format(UtilFns.cToComplex(c.sin(), c.getSuffix())));
		}
	};

	public static final Function IMSQRT = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String inum = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			Complex c = UtilFns.validateComplex(inum);
			return new StringEval(UtilFns.format(UtilFns.cToComplex(c.sqrt(), c.getSuffix())));
		}
	};

	public static final Function IMSUB = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			Complex[] complex = toComplexArray(args, srcCellRow, srcCellCol);
			return new StringEval(UtilFns.format(UtilFns.cToComplex(complex[0].subtract(complex[1]), complex[0].getSuffix())));
		}
	};

	public static final Function IMSUM = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			Complex[] c = toComplexArray(args, srcCellRow, srcCellCol);
			org.apache.commons.math.complex.Complex sum = null;
			for(int i = 0; i < c.length-1; i++) {
				sum = c[i].add(c[i+1]);
			}
			return new StringEval(UtilFns.format(UtilFns.cToComplex(sum, UtilFns.validateComplex(TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol)).getSuffix())));
		}
	};

	/* return true if String is octal */
	private static boolean isOct(String oct) {
		boolean result = true;
		for(int i = 0; i < oct.length(); i++) {
			if(!oct.substring(i, i+1).matches("[0-7]")) {
				result = false;
			}
		}
		return result;
	}
	/* return decimal */
	private static int octToDec(String oct) throws EvaluationException {
		int places = oct.length();
		if(places > 10) {
			throw new EvaluationException(ErrorEval.NUM_ERROR);
		} else {
			if(!isOct(oct)) {
				throw new EvaluationException(ErrorEval.NUM_ERROR);
			}
			Integer result = Integer.valueOf(oct, 8);
			if(result.intValue() > 536870911) {
				result = Integer.valueOf(result.intValue() - 1073741824);
			}
			return result.intValue();
		}
	}
	public static final Function OCT2BIN = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String oct = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			int places = 0;
			if(args.length==2) {
				places = TextFunctionHelper.evaluateIntArg(args[1], srcCellRow, srcCellCol);
			}
			return new StringEval(decToBin((int)octToDec(oct),places));
		}
	};
	
	public static final Function OCT2DEC = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String oct = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			return new NumberEval(octToDec(oct));
		}
	};
	public static final Function OCT2HEX = new TextFunction() {

		@Override
		public ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException {
			String oct = TextFunctionHelper.evaluateStringArg(args[0], srcCellRow, srcCellCol);
			int places = 0;
			if(args.length==2) {
				places = TextFunctionHelper.evaluateIntArg(args[1], srcCellRow, srcCellCol);
			}
			return new StringEval(decToHex((int)octToDec(oct),places));
		}
	};

}
