/* ELEvalFunction.java

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

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

*/

package org.zkoss.zssex.formula;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;

import org.zkoss.poi.ss.formula.eval.BoolEval;
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.OperandResolver;
import org.zkoss.poi.ss.formula.eval.StringEval;
import org.zkoss.poi.ss.formula.eval.ValueEval;
import org.zkoss.poi.ss.formula.functions.FreeRefFunction;
import org.zkoss.poi.ss.formula.functions.Function;
import org.zkoss.poi.ss.formula.OperationEvaluationContext;
import org.zkoss.poi.ss.formula.eval.NotImplementedException;
import org.zkoss.poi.ss.usermodel.DateUtil;
import org.zkoss.util.logging.Log;
import org.zkoss.xel.XelContext;

/**
 * This the default function that delegate the POI function call to EL tld function.
 * @author henrichen
 * @author dennischen
 */
public class ELEvalFunction implements Function, FreeRefFunction, Serializable {
	private static final long serialVersionUID = 8370499997232730816L;
	
	private final String _functionName;
	private static final Log logger = Log.lookup(ELEvalFunction.class.getName());

	public ELEvalFunction(String name) {
		_functionName = name;
	}
	
	/**
	 * Check if the mapped function is exist
	 */
	//NOTE this method is accessed by zpoi 3.9 OverridableFunction by reflection
	public boolean hasFunction(){
		final XelContext ctx = XelContextHolder.getXelContext();
		if (ctx != null) {
			final String tldFuncname = _functionName.replace('.',  '$'); //ZSS-1210
			org.zkoss.xel.Function fn = ctx.getFunctionMapper().resolveFunction("zss", tldFuncname); //ZSS-1210
			return fn!=null;
		}
		return false;
	}
	
	
	@Override
	public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
		final XelContext ctx = XelContextHolder.getXelContext();
		if(ctx == null)
		 throw new NotImplementedException(_functionName+", not in zk context");
		else {
			final String tldFuncname = _functionName.replace('.', '$'); //ZSS-1210
			org.zkoss.xel.Function fn = ctx.getFunctionMapper().resolveFunction("zss", tldFuncname); //ZSS-1210
			if (fn != null) {
				final Class<?> retType = fn.getReturnType();
				final Class<?>[] paramTypes = fn.getParameterTypes();
				try {
					//guess if it want to evaluate arguments by the function itself
					//(is fn(ValueEval[] args, int srcRowIndex, int srcColumnIndex):ValueEval;_  
					//then pass args directly as a Function
					if (ValueEval.class.isAssignableFrom(retType)
						&& paramTypes.length == 3
						&& ValueEval[].class.isAssignableFrom(paramTypes[0])
						&& int.class.equals(paramTypes[1])
						&& int.class.equals(paramTypes[2])){
						return (ValueEval) fn.invoke(null, new Object[] {args, srcRowIndex, srcColumnIndex});
					}
					
					//auto evaluate arguments
					Object[] params = new Object[args.length];
					for(int j = 0; j < args.length; ++j) {
						final Class<?> paramType = paramTypes[j];
						final ValueEval ev = OperandResolver.getSingleValue(args[j], srcRowIndex, srcColumnIndex);
						final Object param = coerceToObject(paramType, ev);
						params[j] = param;
					}
					final Object retValue = fn.invoke(null, params);
					return coerceToValueEval(retType, retValue);
				} catch (Exception e) {
					if (e instanceof EvaluationException) {
						return ((EvaluationException) e).getErrorEval();
					}
					if (e instanceof InvocationTargetException) {
						final Throwable te = ((InvocationTargetException)e).getTargetException();
						if (te instanceof EvaluationException) {
							return ((EvaluationException) te).getErrorEval();
						}else{
							logger.warning("cannot invoke "+_functionName+" for "+te);
							//it might be missing required arguments 
							return ErrorEval.VALUE_INVALID;
						}
					} else {
						return ErrorEval.VALUE_INVALID;
					}
				}
			} else {
				return ErrorEval.NAME_INVALID;
			}
		}
	}
	private Object coerceToObject(Class<?> type, ValueEval value) 
	throws EvaluationException {
		if (ValueEval.class.isAssignableFrom(type)) {
			return value;
		} else if (String.class.isAssignableFrom(type)) {
			return OperandResolver.coerceValueToString(value);
		} else if (int.class.equals(type) || Integer.class.isAssignableFrom(type)) {
			return new Integer(OperandResolver.coerceValueToInt(value));
		} else if (short.class.equals(type) || Short.class.isAssignableFrom(type)) {
			return new Short((short)OperandResolver.coerceValueToInt(value));
		} else if (long.class.equals(type) || Long.class.isAssignableFrom(type)) {
			return new Long(OperandResolver.coerceValueToLong(value));
		} else if (byte.class.equals(type) || Byte.class.isAssignableFrom(type)) {
			return new Byte((byte)OperandResolver.coerceValueToInt(value));
		} else if (double.class.equals(type) || Double.class.isAssignableFrom(type)) {
			return new Double(OperandResolver.coerceValueToDouble(value));
		} else if (float.class.equals(type) || Float.class.isAssignableFrom(type)) {
			return new Float(OperandResolver.coerceValueToDouble(value));
		} else if (BigDecimal.class.isAssignableFrom(type)) {
			return new BigDecimal(OperandResolver.coerceValueToDouble(value));
		} else if (BigInteger.class.isAssignableFrom(type)) {
			return new BigInteger(""+OperandResolver.coerceValueToLong(value));
		} else if (Date.class.isAssignableFrom(type)) {
			return DateUtil.getJavaDate(OperandResolver.coerceValueToDouble(value));
		}
		throw new EvaluationException(ErrorEval.VALUE_INVALID);
	}
	private ValueEval coerceToValueEval(Class<?> type, Object value)
	throws EvaluationException {
		if (String.class.isAssignableFrom(type)) {
			return new StringEval((String)value);
		} else if (Number.class.isAssignableFrom(type)
				|| int.class.equals(type)
				|| double.class.equals(type)
				|| float.class.equals(type)
				|| long.class.equals(type)
				|| short.class.equals(type)
				|| byte.class.equals(type)) {
			return new NumberEval(((Number)value).doubleValue());
		} else if (Boolean.class.isAssignableFrom(type)) {
			return BoolEval.valueOf(((Boolean)value).booleanValue());
		} else if (value instanceof ValueEval) {
			return (ValueEval) value;
		}
		throw new EvaluationException(ErrorEval.VALUE_INVALID);
	}
	@Override
	public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
		return evaluate(args, ec.getRowIndex(), ec.getColumnIndex()); 
	}
}
