package commons.script;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import basics.unexpected.Failure;
import basics.utl.SUtl;
import basics.utl.SysUtl;
import bsh.Interpreter;
import commons.application.ctx.Context;
import commons.utl.CtxUtl;

public class Evaluator<T>
{
Interpreter         bsh                  = new Interpreter();
boolean             internalErrorOccured = false;
Throwable           error                = null;
String              RETURNVAR            = "result";
List<String>        constantNames;
Map<String, String> constants;
Context             ctx;

public Evaluator(Context ctx, List<String> constantNames, Map<String, String> constants)
{
   this.ctx = ctx;
   this.constantNames = constantNames;
   this.constants = constants;
}

public void rethrow()
{
   if(internalErrorOccured)
      throw new Failure(error);
}

public boolean hadError()
{
   return internalErrorOccured;
}

public T eval(String expression, T defaultvalue)
{
   String resolvedExpression = Processor.replaceConstants(expression, constantNames, constants);
   resolvedExpression = SUtl.ltrim(CtxUtl.resolveVariables(ctx, resolvedExpression));
   SysUtl.trace("{%}==>{%}", expression, resolvedExpression);
   StringBuilder sb = new StringBuilder();
   sb.append(RETURNVAR);
   sb.append(" = ");
   sb.append(resolvedExpression);
   try
   {
      bsh.eval(sb.toString());
      return (T)bsh.get(RETURNVAR);
   }
   catch(Throwable t)
   {
      if(!internalErrorOccured)
      {
         internalErrorOccured = true;
         error = t;
      }
      SysUtl.trace(t);
      return defaultvalue;
   }
}

public T eval(String expression)
{
   String resolvedExpression = Processor.replaceConstants(expression, constantNames, constants);
//   SysUtl.trace("{" + expression + "}==>{" + resolvedExpression + "}");
   resolvedExpression = SUtl.ltrim(CtxUtl.resolveVariables(ctx, resolvedExpression));
   StringBuilder sb = new StringBuilder();
   sb.append(RETURNVAR);
   sb.append(" = ");
   sb.append(resolvedExpression);
   try
   {
      bsh.eval(sb.toString());
      return (T)bsh.get(RETURNVAR);
   }
   catch(Throwable t)
   {
      throw new Failure(expression + ". " + SysUtl.recognizeError(t));
   }
}

public void setFromCtx(String var, Context ctx)
{
   try
   {
      bsh.set(var, ctx.getr(var));
   }
   catch(Throwable t)
   {
      if(!internalErrorOccured)
      {
         internalErrorOccured = true;
         error = t;
      }
      SysUtl.trace(t);
   }
}

public synchronized void setAllFromCtx(Context ctx)
{
   try
   {
      for(Iterator<String> iter = ctx.keyIterator(); iter.hasNext();)
      {
         String key = iter.next();
         bsh.set(key, ctx.getr(key));
      }
   }
   catch(Throwable t)
   {
      if(!internalErrorOccured)
      {
         internalErrorOccured = true;
         error = t;
      }
      SysUtl.trace(t);
   }
}

public synchronized String setAllFromClause(String clause, Context ctx)
{
//   SysUtl.trace(clause);
   String key = "";
   try
   {
      List<String> list = SUtl.listTaggedToken(clause, "%", "%");
      if(list == null)
      {
         SysUtl.trace("No tags to replace: %", clause);
      }
      else
         for(int i = 0; i < list.size(); i++)
         {
            key = list.get(i);
            Object val = ctx.getr(key);
            clause = SUtl.replaceAll(clause, SUtl.tag("%", key, "%"), key);
            setValueGuessType(key, val);
         }
   }
   catch(Throwable t)
   {
      t.printStackTrace();
      if(!internalErrorOccured)
      {
         internalErrorOccured = true;
         error = t;
      }
      SysUtl.terror(t, key);
   }
   return clause;
}

public void setValueGuessType(String key, Object value)
{
   try
   {
      if(value == null)
      {
         set(key, (String)null);
         return;
      }
      String sval = value.toString();
      if(SUtl.isDigitsOnly(sval))
      {
         set(key, SUtl.toDouble(sval, -1));
         return;
      }
      if(sval.equalsIgnoreCase("true") || sval.equalsIgnoreCase("false"))
      {
         set(key, SUtl.toBoolean(sval, false));
         return;
      }
      set(key, sval);
   }
   catch(Throwable t )
   {
      SysUtl.terror(t, "Current key: " + key, value);
   }
}

public void set(String var, T value)
{
   try
   {
      bsh.set(var, value);
   }
   catch(Throwable t)
   {
      if(!internalErrorOccured)
      {
         internalErrorOccured = true;
         error = t;
      }
      SysUtl.trace(t);
   }
}

public void set(String var, double value)
{
   try
   {
      bsh.set(var, value);
   }
   catch(Throwable t)
   {
      if(!internalErrorOccured)
      {
         internalErrorOccured = true;
         error = t;
      }
      SysUtl.trace(t);
   }
}

public void set(String var, long value)
{
   try
   {
      bsh.set(var, value);
   }
   catch(Throwable t)
   {
      if(!internalErrorOccured)
      {
         internalErrorOccured = true;
         error = t;
      }
      SysUtl.trace(t);
   }
}

public void set(String var, boolean value)
{
   try
   {
      bsh.set(var, value);
   }
   catch(Throwable t)
   {
      if(!internalErrorOccured)
      {
         internalErrorOccured = true;
         error = t;
      }
      SysUtl.trace(t);
   }
}

public void set(String var, String value)
{
   try
   {
      bsh.set(var, value);
   }
   catch(Throwable t)
   {
      if(!internalErrorOccured)
      {
         internalErrorOccured = true;
         error = t;
      }
      SysUtl.trace(t);
   }
}

public static void unittest()
{
   Evaluator e = new Evaluator(new Context(), new ArrayList<String>(),
      new HashMap<String, String>());
   e.set("a", true);
   e.set("b", "true");
   SysUtl.trace("a?" + e.eval("a == true"));
   SysUtl.trace("b?" + e.eval("b == true"));
}

public static void main(String [] args)
{
   unittest();
}
}
/*

   Interpeter bsh = new Interpreter();
   // Evaluate statements and expressions
   bsh.eval("foo=Math.sin(0.5)");
   bsh.eval("bar=foo*5; bar=Math.cos(bar);");
   bsh.eval("for(i=0; i<10; i++) { print(\"hello\"); }");
   // same as above using java syntax and apis only
   bsh.eval("for(int i=0; i<10; i++) { System.out.println(\"hello\"); }");
   // Source from files or streams
   bsh.source("myscript.bsh"); // or bsh.eval("source(\"myscript.bsh\")");
   // Use set() and get() to pass objects in and out of variables
   bsh.set("date", new Date());
   Date date = (Date)bsh.get("date");
   // This would also work:
   Date date = (Date)bsh.eval("date");
   bsh.eval("year = date.getYear()");
   Integer year = (Integer)bsh.get("year"); // primitives use wrappers
   // With Java1.3+ scripts can implement arbitrary interfaces...
   // Script an awt event handler (or source it from a file, more likely)
   bsh.eval("actionPerformed( e ) { print( e ); }");
   // Get a reference to the script object (implementing the interface)
   ActionListener scriptedHandler = (ActionListener)bsh.eval("return (ActionListener)this");
   // Use the scripted event handler normally...
   new JButton.addActionListener(script);
   */
