package commons.script;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import basics.application.Args;
import basics.application.Config;
import basics.application.user.AppUser;
import basics.application.user.User;
import basics.collections.TypedValueMap;
import basics.utl.Empty;
import basics.utl.ObjectUtl;
import basics.utl.SUtl;
import basics.utl.SysUtl;
import commons.application.GLOBAL;
import commons.application.Application;
import commons.application.ctx.Context;
import commons.script.lang.Clause;
import commons.script.lang.operations.CMD;
import commons.script.lang.operations.Call;
import commons.script.lang.operations.Interrupt;
import commons.script.lang.operations.MakroDef;
import commons.utl.CtxUtl;

public class Processor
{
Context                            ctx           = null;
Analyzer                           analyzer      = new Analyzer();
XNode                              root          = new XNode();
ScriptLoader                       scriptLoader  = null;
boolean                            suspended     = false;
boolean                            stop          = false;
boolean                            exit          = false;
Map<String, String>                constants     = null;
List<String>                       constantNames = null;
private Collection<Callable<Void>> tasks         = null;
private String                     docid         = null;

public Processor(Context ctx, List<String> constantNames, Map<String, String> constants)
{
   this.constantNames = constantNames;
   this.constants = constants;
   this.ctx = ctx;
   this.scriptLoader = new ScriptLoader(analyzer, ctx);
}

public boolean isSuspended()
{
   return suspended;
}

public boolean isStop()
{
   return stop;
}

public boolean isExit()
{
   return exit;
}

public void process(String input, Collection<Callable<Void>> tasks, final String docid)
{
   root = new XNode();
   this.tasks = tasks;
   this.docid = docid;
   TokenizedLines lines = new TokenizedLines();
   List<String> tokens = InputTokenizer.parse(input);
   lines.add(0, tokens);
   XNode node = analyzer.analyzeSyntax(ctx, root, lines);
   execute(node, ctx, scriptLoader);
}

private void checkIfCallToExpand(XNode node)
{
   if(node instanceof Call && !node.expanded)
   {
//      SysUtl.trace("EXPAND %", node);
      resolveAndCopy(((Call)node).getArgs().getArgumentMap(), ctx);
      node = scriptLoader.load(node, ((Call)node).getScript());
      node.expanded = true;
   }
}

private boolean isCmd(XNode node)
{
   if(!(node instanceof CMD))
      return false;
   if(!suspended)
   {
      resolveAndCopy(((CMD)node).getArgs().getArgumentMap(), ctx);
//      SysUtl.trace("RUN % ", ((CMD)node).getCmd() + SUtl.join(ctx.values()));
      if(Application.engine() != null)
         Application.engine().callBack(((CMD)node).getCmd(), ctx, tasks, docid);
   }
   return true;
}

public static String replaceConstants(String text, List<String> constantNames,
   Map<String, String> constants)
{
   //   SysUtl.trace(">>text:'%' constantNames:%", text, constantNames.toString());
   for(String name : constantNames)
   {
      String token = InputTokenizer.TOKEN_MAKRO_DEF + name;
      if(text.indexOf(token) > -1)
         text = SUtl.replaceAll(text, token, constants.get(name));
   }
   //   SysUtl.trace("<<text:'%'", text);
   return text;
}

private void resolveAndCopy(TypedValueMap map, Context ctx)
{
   for(String key : map.keySet())
   {
      String value = map.get(key, "");
//      SysUtl.trace("key(" + key + ") val(" + value + ")");
      {
         String resolvedValue = replaceConstants(value, constantNames, constants);
         //         SysUtl.trace(value +"-->"+ resolvedValue);
         resolvedValue = SUtl.ltrim(CtxUtl.resolveVariables(ctx, resolvedValue));
         //         SysUtl.trace(value +"-->"+ resolvedValue);
         ctx.put(key, resolvedValue);
      }
   }
}

private boolean isMakroDef(XNode node)
{
   if(!(node instanceof MakroDef))
      return false;
   if(!suspended)
   {
      MakroDef md = ((MakroDef)node);
      //      ctx.env().put(md.getKey(), md.getVal());
      if(Empty.is(md.getVal()))
      {
         constantNames.remove(md.getKey());
         constants.remove(md.getVal());
      }
      else
      {
         constants.put(md.getKey(), md.getVal());
         if(!constantNames.contains(md.getKey()))
            constantNames.add(md.getKey());
      }
//      SysUtl.trace("DEF %:%", md.getKey(), md.getVal());
   }
   return true;
}

private boolean evaluatesTrue(XNode node, Context ctx)
{
   if(!(node instanceof Clause))
      return true;
   String clause = ((Clause)node).getClause(); // evaluate clause...
   Evaluator<Boolean> e = new Evaluator<Boolean>(ctx, constantNames, constants);
   clause = e.setAllFromClause(clause, ctx);
   boolean b = e.eval(clause);
//   SysUtl.trace("CLAUSE: " + clause + " in node: " + node + ": " + b);
   return b;
}

private void execute(XNode node, Context ctx, ScriptLoader scriptLoader)
{
   if(stop || exit)
      return;
   do
      if(interrupt(node))
         return;
      else
         if(!isMakroDef(node))
            if(!isCmd(node))
            {
               checkIfCallToExpand(node);
               if(evaluatesTrue(node, ctx))
               {
                  if(node.straightNodes != null)
                     for(int i = 0; i < node.straightNodes.size(); i++)
                        execute(node.straightNodes.get(i), ctx, scriptLoader);
               }
               else
               {
                  if(node.altNodes != null)
                     for(int i = 0; i < node.altNodes.size(); i++)
                        execute(node.altNodes.get(i), ctx, scriptLoader);
                  return;
               }
            }
   while(node.repeat);
}

private boolean interrupt(XNode node)
{
   if(!(node instanceof Interrupt))
      return false;
//   SysUtl.trace("INTTR %", node);
   String t = ((Interrupt)node).getValue();
   if(t.equals(InputTokenizer.TOKEN_STOP))
      stop = true;
   else
      if(t.equals(InputTokenizer.TOKEN_RESUME))
         suspended = false;
      else
         if(t.equals(InputTokenizer.TOKEN_SUSPEND))
            suspended = true;
         else
            return false;
   return true;
}

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

public static void unittest()
{
   SysUtl.trace("term % of % in %, %", 1, 2, "getDocId()", "ObjectUtl.getId(this)");
//   Context ctx = new Context();
//   Args prgArgs = new Args(new String [] { });
//   CtxUtl.addPropertiesToEnv(Manager.ctx(), new Config(GLOBAL.KEY_CONFIG_APPLICATION)
//      .getProperties());
//   ctx.env().put(GLOBAL.CTX_ENVKEY_BASEDIR,
//      prgArgs.getOption("basedir", ctx.env().get(GLOBAL.CTX_ENVKEY_BASEDIR, "home")));
//   String uid = prgArgs.getOption("userid", ctx.env().consume("userid", "."));
//   //   if(!Empty.is(uid))
//   ctx.env().put(GLOBAL.CTX_ENVKEY_USER, AppUser.create(uid, null, null, User.ROLE.ADMIN.name()));
//   SysUtl.trace("user  : " + ctx.env().get(GLOBAL.CTX_ENVKEY_USER, ""));
//   SysUtl.trace("home  : " + new File(CtxUtl.userHome(ctx)).getAbsolutePath());
//   Processor p = new Processor(ctx, new ArrayList<String>(), new HashMap<String, String>());
//   p.process("call demo3", null, null);
}
}
