package net.minecraft.src.weasel;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.nfunk.jep.JEP;

import net.minecraft.src.weasel.exception.WeaselRuntimeException;
import net.minecraft.src.weasel.obj.WeaselBoolean;
import net.minecraft.src.weasel.obj.WeaselInteger;


/**
 * Expression evaluation class
 * 
 * @author MightyPork
 * @copy (c) 2012
 */
public class Calculator {
	//private static ScriptEngineManager engineFactory = new ScriptEngineManager();
	//private static ScriptEngine jsEngine = engineFactory.getEngineByName("JavaScript");
	private static Pattern variablePattern = Pattern.compile("([a-zA-Z_]{1}[a-zA-Z_0-9.]*?)(?:[^a-zA-Z_0-9.(]|$)");

	/**
	 * Format an integer (plain or wrapped in WeaselInteger, or a boolean, or
	 * long) using given radix, and output as string.
	 * 
	 * @param obj the integer
	 * @param radix radix. 2=binary,8=octal,16=hex,10=decimal.
	 * @return the formatted integer
	 */
	public static String formatInteger(Object obj, int radix) {
		Integer i = 0;
		if (obj instanceof Integer) i = (Integer) obj;
		if (obj instanceof Long) i = (Integer) obj;
		if (obj instanceof Float) i = Math.round((Float) obj);
		if (obj instanceof Double) i = (int) Math.round((Double) obj);
		if (obj instanceof Boolean) i = (Boolean) obj ? 1 : 0;
		if (obj instanceof WeaselInteger) i = ((WeaselInteger) obj).get();
		if (obj instanceof WeaselBoolean) i = ((WeaselBoolean) obj).get() ? 1 : 0;

		switch (radix) {
			case 2:
				return "0b" + Integer.toBinaryString(i);
			case 8:
				return "0c" + Integer.toOctalString(i);
			case 16:
				return "0x" + Integer.toHexString(i);
			case 10:
				return "0d" + Integer.toString(i);
			default:
				return "0d" + i;
		}
	}

//	/**
//	 * @param engine the script engine
//	 * @param javascript script to evaluate
//	 * @return result object
//	 * @throws CalcException when evaluation fails.
//	 */
//	public static Object eval(ScriptEngine engine, String javascript) throws CalcException {
//		try {
//			return engine.eval(javascript);
//		} catch (ScriptException e) {
//			throw new CalcException(e.getMessage());
//		}
//	}

	/**
	 * Evaluate an expression with variables from VM.
	 * 
	 * @param expression expression, without semicolon. eg. (14+a)/2;
	 * @param variableContainer weasel variable container (primary ENGINE, but sometimes also VariableMap).
	 * @return result of the expression.
	 * @throws CalcException when evaluation fails.
	 */
	public static Object eval(String expression, IVariableContainer variableContainer) throws CalcException {
		
		if(expression == null || expression.length()==0) return null;

		if (expression.contains(";")) {
			throw new CalcException("Semicolon in a numeric expression. Possible injection attack.");
		}
		
		expression = expression.replaceAll("\\s", "");
		
		// List of variables needed to evaluate this expression
		List<String> varsNeeded = new ArrayList<String>();		
		
		// List of renamed variables (. -> __)
		Map<String,String> rename = new HashMap<String,String>();
		
		JEP jep = new JEP();
		jep.setAllowAssignment(true);
		
		Matcher matcher = variablePattern.matcher(expression);
		
		while(matcher.find()){
			
		    String name = matcher.group(1);
		    String real = name;
		    varsNeeded.add(name);
		    
		    //if name contains dot, replace it by __ to prevent "no such object" errors in JS
		    if(name.contains(".")) {
		    	real = name;
		    	name = name.replaceAll("\\.", "__");
		    	rename.put(real, name);
		    	expression = expression.replace(real, name);
		    }		    
		    
		    //add the variable into JS engine
		    try {
		    	jep.addVariable(name, variableContainer.getVariable(real).get());
		    	//jsEngine.put(name, variableContainer.getVariable(real).get());
		    }catch(NullPointerException npe) {
		    	throw new WeaselRuntimeException("Variable "+real + " not set in this scope.");
		    }
		}
		
		jep.parseExpression(expression);

		// execute JS
		Object out = jep.getValueAsObject(); //eval(jsEngine, expression);
		
		if(jep.hasError()) {
			throw new CalcException(jep.getErrorInfo());
		}

		// put variables back into Variable Map in Engine
		for (String varname : varsNeeded) {			
			String real = varname;
			if(rename.containsKey(varname)) real = rename.get(varname);
			
			variableContainer.getVariable(real).set(jep.getVarValue(varname));	
		}

		return out;
		
		
//		if(expression == null || expression.length()==0) return null;
//
//		if (expression.contains(";")) {
//			throw new CalcException("Semicolon in a numeric expression. Possible injection attack.");
//		}
//		
//		expression = expression.replaceAll("\\s", "");
//		
//		// List of variables needed to evaluate this expression
//		List<String> varsNeeded = new ArrayList<String>();		
//		
//		// List of renamed variables (. -> __)
//		Map<String,String> rename = new HashMap<String,String>();
//		
//		
//		Matcher matcher = variablePattern.matcher(expression);
//		
//		while(matcher.find()){
//			
//		    String name = matcher.group(1);
//		    String real = name;
//		    varsNeeded.add(name);
//		    
//		    //if name contains dot, replace it by __ to prevent "no such object" errors in JS
//		    if(name.contains(".")) {
//		    	real = name;
//		    	name = name.replaceAll("\\.", "__");
//		    	rename.put(real, name);
//		    	expression = expression.replace(real, name);
//		    }		    
//		    
//		    //add the variable into JS engine
//		    try {
//		    	jsEngine.put(name, variableContainer.getVariable(real).get());
//		    }catch(NullPointerException npe) {
//		    	throw new WeaselRuntimeException("Variable "+real + " not set in this scope.");
//		    }
//		}
//
//		// execute JS
//		Object out;
//		try {
//			out = eval(jsEngine, expression);
//		}catch(RuntimeException re) {
//			throw new CalcException("Expression evaluation failed: "+re.getMessage());
//		}
//
//		// put variables back into Variable Map in Engine
//		for (String varname : varsNeeded) {			
//			String real = varname;
//			if(rename.containsKey(varname)) real = rename.get(varname);
//			
//			variableContainer.getVariable(varname).set(jsEngine.get(real));	
//		}
//
//		return out;

	}

	/**
	 * Exception during evaluating expression
	 * 
	 * @author MightyPork
	 * @copy (c) 2012
	 */
	public static class CalcException extends WeaselRuntimeException {

		/** Description why the evaluation failed. */
		public String cause;

		/**
		 * @param e causing expression.
		 */
		public CalcException(RuntimeException e) {
			super(e);
		}

		/**
		 * @param cause cause description
		 */
		public CalcException(String cause) {
			super(cause);
		}
	}
}
