package nxm.sys.intr;

import java.util.Arrays;

import nxm.sys.lib.Convert;
import nxm.sys.lib.Intrinsic;
import nxm.sys.lib.KeyObject;
import nxm.sys.lib.Parser;

/** Handles the invocation of a method on an object in the ResultsTable, Registry, or a
    fully-qualified class name.
    @see New
    @author Jeff Schoen / Jay Hallam / Neon Ngo
*/
public class Invoke extends Intrinsic {
  protected boolean moreVerbose = false;
  protected boolean debug       = false;

  /** default constructor */
  public Invoke () {}

  public int open () {
    if (verbose) { moreVerbose = MA.getState("/VERBOSE"); } // Unlike the verbose var this defaults to false.
    debug = MA.getState("/DEBUG");
    return NORMAL;
  }

  public int process () {
    String label = MA.getU("LABEL");
    String func  = MA.getQ("FUNC");
    String firstKey = MA.getKey(1);

    boolean oldSchool = (!isEmpty(label) && !isEmpty(func)) ||
                        (MA.getNArgs() == 2 && (firstKey.equals("LABEL") || firstKey.equals("P1")) );
    if ( oldSchool ) {
      // Legacy 2 args of the form... invoke LABEL=<res> FUNC=nxm  or ... invoke <res> <func>
      label = MA.getU(1);
      func  = MA.getQ(2);
      Object value = doInvoke(func, false);
      outputResults(label, value);

    } else {
      String key;
      int argCount = 0;
      while (argCount < MA.getNArgs()) {
        key = MA.getKey(++argCount);
        label = key;
        func = MA.getQ(key);
        Object value = doInvoke(func, false);
        outputResults(label, value);
      }
    }
    return FINISH;
  }

  private boolean isEmpty (String str) {
    return ((str == null) || (str.length() == 0));
  }

  /* Puts results in results table and/or to the screen */
  private void outputResults (String label, Object value) {
    if (!isEmpty(label)) MR.put(label,value);
    if (verbose) {
      if (isEmpty(label)) {
        M.println("Invoke: ANSWER = "+value);    // since 3.7.1 using M.println instead of M.terminal.writeln to allow logging - NXM-1813
      } else if (moreVerbose) {
        M.println("Invoke: "+label+" = "+value);
      }
    }
  }

  /** Does the "dirty work" for invoke. This has been separated out so that NEW can use this
      functionality.
      @param func            The function to invoke.
      @param assumeClassName Indicates if the function should be assumed to be a class name.
   */
  protected Object doInvoke (String func, boolean assumeClassName) {
    int funcLen = func.length();
    int iLparen = func.indexOf('(');       // index of left parenthesis
    if (iLparen<0) iLparen = funcLen;
    else { // otherwise have left (e.g. opening) parenthesis
      // Bug 1640: more informative error message (when have extra chars after ')'
      int iRparen = func.lastIndexOf(')'); // index of right most parenthesis
      if (iRparen<funcLen-1) {
        M.error("Missing closing right parenthesis ')' or extra character(s) after it.");
      }
    }
    int i = func.substring(0,iLparen).lastIndexOf('.');
    if (i<=0) M.error ("Malformed function string: '"+func+"'");

    String   name  = func.substring(0,i);
    Object   root  = null;
    Object[] args  = null;

    // find the root object
    if(!assumeClassName) {
      if (name.equals(""));                       // no arg
      else if ((root=MR.get(name.toUpperCase()))!=null);	// try result
      else if ((root=MR.get("REG."+name.toUpperCase()))!=null);	// try registry entry
      else assumeClassName = true;
    }

    if (assumeClassName) {
      try {                                  // try class name
        ClassLoader loader = this.getClass().getClassLoader();
        if (debug) M.info("Loading "+ name +" using ClassLoader="+loader);
        Class<?> c = loader.loadClass(name);
        //root = c.newInstance();
        root = c;
      } catch (Exception e) {
        // it probably was not intended to be a class name
      }
    }

    if (root==null) M.error ("Could not find Object named: "+name);

    // resolve any arguments
    if (iLparen<funcLen) {
      args = parseArgs(func.substring(iLparen+1, func.length()-1));
      func = func.substring(i+1,iLparen);
    } else {
      args = null;
      func = func.substring(i+1);
    }

    if (moreVerbose) {
      M.type("doInvoke() calling KeyObject.getKey() root="+root+" func="+func+" args[]="+Arrays.toString(args));
    }
    // execute the function
    return KeyObject.getKey(root,func,args,this);
  }

  /** Parses an argument string into objects (this will attempt to resolve any names as objects).
      This method is ONLY used by doInvoke(..) and should probably be a static object in Parser
      that takes in the string and a reference (for the Convert.s2o).
   */
  private Object[] parseArgs(String str) {
    Parser parser = new Parser(str);
    parser.clean(false);
    Object[] objs = new Object[parser.elements()];

    for (int i = 0; i < objs.length; i++) {
      objs[i] = Convert.s2o(parser.get(i+1),'_',this);
    }
    return objs;
  }
}
