package hc.adl;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

/**
 * parse a single statement
 * 
 * @author hc
 * 
 */
public class EquationParser {
	protected Map<String, EFunction> mapTable;

	protected Map<String, Object> mVariables;

	protected IFunctionCallback iCallback;

	public EquationParser() {
		mVariables = new HashMap<String, Object>();

		mapTable = new HashMap<String, EFunction>();
		mapTable.put("format", EFunction.Format);
		mapTable.put("log", EFunction.Log);
		mapTable.put("deref", EFunction.Deref);
		mapTable.put("isnull", EFunction.IsNull);

		/*
		 * add booleans
		 */
		mVariables.put("true", true);
		mVariables.put("false", false);
	}

	/**
	 * allow an external variable map to be set
	 * 
	 * @param map
	 *            Variable map
	 */
	public void SetVariableMap(Map<String, Object> map) {
		mVariables = map;

		mVariables.put("true", true);
		mVariables.put("false", false);
		mVariables.put("null", null);
	}

	/**
	 * set the callback function
	 * 
	 * @param nCB
	 *            Function callback function
	 */
	public void SetCallback(IFunctionCallback nCB) {
		iCallback = nCB;
	}

	/**
	 * Get function call parameters
	 * 
	 * @param s
	 *            parameter string, params must be separated by commas.
	 * @return param string array
	 */
	public static Vector<String> GetParams(String s) {
		int i;
		boolean bInString;
		int iBR;
		boolean bFound;
		Vector<String> sParams;

		sParams = new Vector<String>();

		// iterate linewise
		while (s.length() != 0) {
			bInString = false;
			iBR = 0;
			bFound = false;
			for (i = 0; i < s.length(); i++) {
				switch (s.charAt(i)) {
				case '"':
					bInString = !bInString;
					break;
				case '(':
				case '[':
				case '{':
					iBR++;
					break;
				case ')':
				case ']':
				case '}':
					iBR--;
					break;
				case ',':
					if ((!bInString) && (iBR == 0)) {
						sParams.add(s.substring(0, i));
						s = s.substring(i + 1);
						bFound = true;
					}
					break;
				default:
					break;
				}

				if (bFound)
					break;
			}

			if (!bFound) {
				sParams.add(s);
				s = "";
			}
		}

		// return the result
		return sParams;
	}

	/**
	 * Call a function using the set callback interface
	 * 
	 * @param sFuncName
	 *            Function name
	 * @param vParams
	 *            Function parameters
	 * @return Function result
	 */
	public Object CallFunction(String sFuncName, Map<String, Object> locals,
			Vector<Object> vParams) {
		String[] funcParts = sFuncName.split("\\.");
		IFunctionCallback callback = iCallback;
		if (funcParts.length == 2) {
			if (locals.containsKey(funcParts[0])) {
				try {
					callback = (IFunctionCallback) locals.get(funcParts[0]);
				} catch (ClassCastException e) {
					System.err.println(e);
					return null;
				}
			} else {
				try {
					callback = (IFunctionCallback) mVariables.get(funcParts[0]);
				} catch (ClassCastException e2) {
					System.err.println(e2);
					return null;
				}
			}
		} else if (funcParts.length > 2) {
			System.err
					.println("Error: currently only single object references allowed.");
			return null;
		}

		String funcname = funcParts[funcParts.length - 1];
		EFunction builtin = mapTable.get(funcname);
		if (builtin != null) {
			return ADLFunctions.CallFunction(this, locals, builtin, vParams);
		}

		if (callback == null) {
			return null;
		} else {
			return callback.FunctionCallback(funcname, vParams);
		}
	}

	/**
	 * Evaulate a function string
	 * 
	 * @param s
	 *            String to evaluate
	 * @return Evaluation result
	 */
	public Object Evaluate(String s, Map<String, Object> locals)
			throws ADLException {
		int iPos;
		char cAT;
		String sFuncName;
		Vector<String> sParams;
		Iterator<String> iParams;
		Vector<Object> vParams;

		if (s.length() == 0)
			return 0;

		cAT = s.charAt(0);
		if (cAT == '(') {
			iPos = s.lastIndexOf(')');
			assert (iPos != -1);

			return Parse(s.substring(1, iPos), locals);
		} else if (cAT == '"') {
			iPos = s.lastIndexOf('"');
			assert (iPos != -1);

			return s.substring(1, iPos);
		}

		else if ((cAT >= '0') && (cAT <= '9')) {
			if (s.indexOf('.') == -1)
				return Integer.parseInt(s.trim());
			else
				return Double.parseDouble(s.trim());
		} else {
			iPos = s.indexOf('(');
			sFuncName = s;
			if (iPos != -1) {
				sFuncName = s.substring(0, iPos);
				s = s.substring(iPos);

				iPos = s.lastIndexOf(')');
				assert (iPos != -1);

				sParams = GetParams(s.substring(1, iPos));
				iParams = sParams.iterator();
				vParams = new Vector<Object>();

				while (iParams.hasNext()) {
					vParams.add(Parse(iParams.next(), locals));
				}

				return CallFunction(sFuncName, locals, vParams);
			} else {
				String sVarName = sFuncName.trim();
				if (locals.containsKey(sVarName)) {
					return escape(locals.get(sVarName));
				}
				return escape(mVariables.get(sFuncName.trim()));
			}
		}
	}

	private Object escape(Object object) {
		if (object instanceof Integer) {
			return makeInteger(object);
		} else if (object instanceof String) {
			return AutomatCreator.makeString(object);
		} else {
			return object;
		}
	}

	private Object makeInteger(Object object) {
		return (int) (Integer.valueOf((Integer) object));
	}

	/**
	 * The Parser.
	 * 
	 * @param s
	 *            String to parse
	 * @return Parsing result
	 * @throws ADLException
	 *             On shit
	 */
	@SuppressWarnings("unchecked")
	public Object Parse(String s, Map<String, Object> locals)
			throws ADLException {
		char elem;
		char operator;
		int i;
		int iLen;
		int iBrackets;
		Boolean bFound;
		String sLeft;
		String sRight;
		Object evalRes;

		boolean bNegate = false;

		Object eORight;
		Object eIRight;

		s = s.trim();
		iLen = s.length();

		if (iLen == 0)
			return 0;

		if (s.charAt(0) == '"') {
			return Evaluate(s, locals);
		} else if (s.charAt(0) == '-') {
			bNegate = true;
			s = s.substring(1);
			iLen--;
			if (iLen < 1)
				throw new RuntimeException("Error: invalid expression!");
		}

		bFound = false;
		iBrackets = 0;
		for (i = 0; i < iLen; i++) {
			elem = s.charAt(i);

			switch (elem) {
			case '*':
			case '/':
			case '+':
			case '-':
			case '<':
			case '>':
			case '&':
			case '|':
			case '=':
			case '?':
			case ':':
			case '!':
			case '~':
				if (iBrackets == 0) {
					bFound = true;
				}

				break;
			case '(':
				iBrackets++;
				break;
			case ')':
				iBrackets--;
				break;
			}

			if (bFound)
				break;
		}

		assert (iBrackets == 0);
		sLeft = s.substring(0, i).trim();
		if (sLeft.length() == 0)
			evalRes = 0;
		else {
			evalRes = Evaluate(sLeft, locals);
		}
		if (bNegate) {
			evalRes = Negate(evalRes);
		}
		if (bFound) {
			operator = s.charAt(i);
			if (operator == '&') {
				if (((Boolean) evalRes).booleanValue() == false) {
					return false;
				}
			}

			if (operator == '|') {
				if (((Boolean) evalRes).booleanValue() == true) {
					return true;
				}
			}

			sRight = s.substring(i + 1);
			eORight = Parse(sRight, locals);
			if (eORight != null) {
				Comparable cLeft, cRight;
				eIRight = eORight;

				switch (operator) {
				case '+': {
					if (evalRes.getClass() == Integer.class) {
						return (Integer) evalRes + (Integer) eIRight;
					} else if (evalRes.getClass() == Double.class) {
						return (Double) evalRes + (Double) eIRight;
					} else if (evalRes.getClass() == String.class) {
						return (String) evalRes + (String) eIRight;
					}
				}
					// substraction
				case '-':
					if (evalRes.getClass() == Integer.class) {
						return (Integer) evalRes - (Integer) eIRight;
					} else if (evalRes.getClass() == Double.class) {
						return (Double) evalRes - (Double) eIRight;
					}
					// division
				case '/':
					if (evalRes.getClass() == Integer.class) {
						return (Integer) evalRes / (Integer) eIRight;
					} else if (evalRes.getClass() == Double.class) {
						return (Double) evalRes / (Double) eIRight;
					}
					// multiplication
				case '*':
					if (evalRes.getClass() == Integer.class) {
						return (Integer) evalRes * (Integer) eIRight;
					} else if (evalRes.getClass() == Double.class) {
						return (Double) evalRes * (Double) eIRight;
					}
					// comparision: smaller than
				case '<':
					cLeft = (Comparable) evalRes;
					cRight = (Comparable) eIRight;
					return cLeft.compareTo(cRight) < 0;
					// comparision: greater than
				case '>':
					cLeft = (Comparable) evalRes;
					cRight = (Comparable) eIRight;
					return cLeft.compareTo(cRight) > 0;
				case ':':
					// assignment
					locals.put(sLeft.trim(), eIRight);
					break;
				// comparision: equals
				case '=':
					return evalRes.equals(eIRight);
					// equals not
				case '!':
					return !evalRes.equals(eIRight);
					// and
				case '&':
					return ((Boolean) evalRes && (Boolean) eIRight);
					// or
				case '|':
					return ((Boolean) evalRes || (Boolean) eIRight);
					// someone screwed up
				case '?':
					return handleQM(evalRes, (String) eIRight);
				case '~':
					return StringTools.convertToNL((String) evalRes,
							(String) eIRight);
				default:
					System.out.format("Operator %c unknown\n", operator);
				}
			}
		}

		return evalRes;
	}

	@SuppressWarnings("unchecked")
	private Object handleQM(Object map, String string) {
		if (map == null) {
			map = new HashMap<String, Object>();
		}
		try {
			return Parse(string, (Map<String, Object>) map);
		} catch (ADLException e) {
			return e;
		} catch (ClassCastException e) {
			return string;
		}
	}

	protected Object Negate(Object evalRes) {
		if (evalRes.getClass() == Integer.class)
			return 0 - (Integer) evalRes;
		else if (evalRes.getClass() == Double.class)
			return 0 - (Double) evalRes;
		else
			throw new RuntimeException(String.format(
					"Error: can't negate a %s object!\n", evalRes.getClass()
							.toString()));
	}

	public void SetVariable(String key, Object value) {
		this.mVariables.put(key, value);
	}

	public Object GetVariable(String key) {
		return this.mVariables.get(key);
	}
}
