package nxm.sys.libm;

import nxm.sys.inc.DataTypes;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.MidasException;

/**
  Helper class for implementing a calculator using Reverse Polish Notation.

  This class helps parse arguments to a calculator function.
  A stack of working arrays is maintained internally.

  This is used by CALC, FCALC, and inline CALC().

  @author Jeff Schoen
*/
public class ArgStack extends Object implements DataTypes {

  private int iArg, maxArg;
  private int iStk, maxStk;
  /** number of Arguments */
  public int nArgs;
  private String rawArgs;

  private Stack[] stks;
  /** array of Arguments */
  public Argument[] args;

  private int flags;
  /** Midas DataType (please refer to {@link nxm.sys.inc.DataTypes})*/
  public char type;
  /** the transfer length */
  public int xfer;

  /** initialize the type, transfer length and max extent of the stack
   *  @param maxArg maximum number of Arguments
   *  @param maxStk maximum stack size
   *  @param type   Midas type
   *  @param xfer   the transfer length
   *  @param flags  flags
   */
  public ArgStack (int maxArg, int maxStk, char type, int xfer, int flags) {
    this.maxArg = maxArg;
    this.maxStk = maxStk;
    this.type = type;
    this.xfer = xfer;
    this.flags = flags;
    args = new Argument[maxArg];
    stks = new Stack[maxStk];
    clear();
  }

  /** clear the stack and parser */
  public void clear() {
    iStk=iArg=0;
    rawArgs=null;
  }

  /** set the current transfer or array length
   *  @param xfer current transfer or array length */
  public void setXfer (int xfer) { this.xfer = xfer; }

  /** current number of values on stack
   *  @return current number of values on stack */
  public int getStackValues() { return iStk; }

  /** example parse next argument loop
   *  @param s   String containing argument
   *  @param ref The reference to use when resolving results parameters.
   *  @return true if successful */
  public boolean parseNext (String s, Object ref) {
    Operator op = Operators.get(s);
    boolean ok=false;
         if (op!=null)          ok = parseOperator(s,op);
    else if (s.equals("PUSH"))  ok = parsePush();
    else if (s.equals("POP"))   ok = parsePop();
    else if (s.equals("<>"))    ok = parseSwap();
    else if (s.startsWith("="))      parseStackOut(s.substring(1));
    else                        ok = parseConstant(s, ref, false);
    return ok;
  }

  /** parse (and perform) the stack push operator
   *  @return true if successful */
  public boolean parsePush() {
    addArg("PUSH");
    stks[iStk] = stks[iStk-1];
    iStk++;
    return true;
  }

  /** parse (and perform) the stack pop operator
   *  @return true if successful */
  public boolean parsePop() {
    addArg("POP");
    if (iStk<=0) return false;
    iStk--;
    return true;
  }

  /** parse (and perform) the stack X&lt;&gt;Y operator
   *  @return true if successful */
  public boolean parseSwap() {
    addArg("<>");
    if (iStk<2) return false;
    Stack tstk = stks[iStk-1];
    stks[iStk-1] = stks[iStk-2];
    stks[iStk-2] = tstk;
    return true;
  }

  /** parse an operator
      @param s The operator string
      @param op The Argument operator
      @return true if successful
  */
  public boolean parseOperator (String s, Operator op) {
    addArg(s);
    Argument arg = new Argument(Argument.OPERATOR,s); arg.op = op;
    int ins=arg.op.getInputs(), outs=arg.op.getOutputs();
    if (iStk<ins) badArg ("Insufficient parameters on stack for operator "+arg.name);
    int omode=0,mode=0,isize,osize;
    // loop through possible output modes for a valid op
    for (omode=1; omode<=3; omode++) {
      mode=osize=0;
      if (ins==0) isize = xfer;
      else isize = stks[iStk-ins].size;
      // find minimum length of all array inputs
      for (int i=1; i<ins; i++) {
        int insize = stks[iStk-ins+i].size;
        if (insize<=1) continue; // scalar parameter
        if (isize<=1 || insize<isize) isize = insize;
      }
      // this sould be a while on number of ins
      for (int i=0; i<ins; i++) mode |= (stks[iStk-ins+i].mode << (i*4));
      for (int i=0; i<outs; i++) mode |= (omode << ((ins+i)*4));
      mode |= arg.op.flags;
      osize = arg.op.validate (type,isize,mode);
      if (osize==Operator.NOSUPPORT) continue;
      int ios = ins+outs;
      arg.mode = mode;
      // load the stack for this operation
      iStk -= ins;
      if (type==DOUBLE) {
        arg.dv = new double[ios][];
        for (int i=0; i<ins; i++) arg.dv[i]=stks[iStk+i].d;
        for (int i=0; i<outs; i++) {
          Stack stk=getStack(omode,osize,null);
          stks[iStk++]=stk;
          arg.dv[ins+i]=stk.d;
        }
      }
      else if (type==FLOAT) {
        arg.fv = new float[ios][];
        for (int i=0; i<ins; i++) arg.fv[i]=stks[iStk+i].f;
        for (int i=0; i<outs; i++) {
          Stack stk=getStack(omode,osize,null);
          stks[iStk++]=stk;
          arg.fv[ins+i]=stk.f;
        }
      }
      else if (type==LONG) {
        arg.lv = new int[ios][];
        for (int i=0; i<ins; i++) arg.lv[i]=stks[iStk+i].l;
        for (int i=0; i<outs; i++) {
          Stack stk=getStack(omode,osize,null);
          stks[iStk++]=stk;
          arg.lv[ins+i]=stk.l;
        }
      }
      arg.size = isize;
      args[iArg++] = arg;
      return true;
    }
    badArg ("Invalid parameters on stack for operator "+arg.name);
    return false;
  }

  /** parse a numeric constant or an expression that evaluates to a constant
      @param s The number, result or expression.
      @param ref The reference.  Usually Midas or this.
      @return true if able to convert to a constant, false otherwise.
  */
  public boolean parseConstant (String s, Object ref) {
    return parseConstant(s,ref,false);
  }

  /** Parse a numeric constant or an expression that evaluates to a constant. This is the same
      as <code>parseConstant(s,ref,soft,false)</code>.
      @param s    The expression (e.g. "4", "X", "X+2").
      @param ref  The reference to use when resolving results parameters.
      @param soft Use soft validation, just return false if unable to convert to a constant.
      @return true if able to convert to a constant, false otherwise.
   */
  public boolean parseConstant (String s, Object ref, boolean soft) {
    return parseConstant(s,ref,soft,false);
  }

  /** Parse a numeric constant/variable or an expression that evaluates to a constant or variable.
      This method is "smart" in that it will evaluate constant expressions (e.g. "2+2") as constants
      even if <code>var</code> is true.
      @param s    The expression (e.g. "4", "X", "X+2").
      @param ref  The reference to use when resolving results parameters.
      @param soft Use soft validation, just return false if unable to convert to a constant.
      @param var  Assume that any results are variables that could later change during processing
                  (note: they must be defined, but are not assumed to be constant)?
      @return true if able to convert to a constant/variable, false otherwise.
      @since NeXtMidas 2.5.3
   */
  public boolean parseConstant (String s, Object ref, boolean soft, boolean var) {
    if (!soft) addArg(s);
    if (iStk>=maxStk) badArg ("Stack too small for constant "+s);
    Object obj = s2o(s,ref);
    if (var && (obj != null)) {
      var = (s2o(s,null) == null); // if s2o(s,null) returns a number it is a numeric constant rather than a variable
    }
    if (obj instanceof Data);
    else if (soft) return false;
    else badArg ("Could not convert argument "+s+" to constant");
    if (soft) addArg(s);// down here to make soft mode not add extra arg
    Data data = (Data)obj;
    Stack stk = getStack (data.spa,1,data);
    stks[iStk++] = stk;
    if (var) {
      Argument arg = new Argument(Argument.VARIABLE,s);
           if (type==DOUBLE) { arg.dv = new double[1][]; arg.dv[0]=stk.d; }
      else if (type==FLOAT)  { arg.fv = new float[1][];  arg.fv[0]=stk.f; }
      else if (type==LONG)   { arg.lv = new int[1][];    arg.lv[0]=stk.l; }
      args[iArg++] = arg;
    }
    return true;
  }

  /** Quick s2o conversion used by parseConstant, returns null if not successful. */
  private Object s2o(String s, Object ref) {
    Object obj = null;
    try { obj = Convert.s2o(s,type,ref); } catch (MidasException e) { }
    return obj;
  }

  /** @deprecated Since NeXtMidas 2.5.3: Use <code>parseConstant(s,ref,soft,true)</code>.
   *  @param s   The raw argument string
   *  @param ref Reference object (often a Midas context or a Command)
   *  @return true if successful
   */
  @Deprecated public boolean parseVariable (String s, Object ref) {
    addArg(s);
    Argument arg = new Argument(Argument.VARIABLE,s);
    if (iStk>=maxStk) badArg ("Stack too small for constant "+arg.name);
    Data data = (Data)Convert.s2o(s,type,ref);
    Stack stk = getStack (data.spa,1,data);
         if (type==DOUBLE) { arg.dv = new double[1][]; arg.dv[0]=stk.d; }
    else if (type==FLOAT) { arg.fv = new float[1][]; arg.fv[0]=stk.f; }
    else if (type==LONG) { arg.lv = new int[1][]; arg.lv[0]=stk.l; }
    stks[iStk++] = stk;
    args[iArg++] = arg;
    return true;
  }

  /** parse a stack input argument (result or file)
      @param s  The result or file string
      @param data The Data
      @param size The size of the data
      @return stack output argument
  */
  public Argument parseStackIn (String s, Data data, int size) {
    addArg(s);
    Argument arg = new Argument(Argument.STACKIN,s);
    if (iStk>=maxStk) badArg ("Stack too small for argument "+arg.name);
    Stack stk = getStack (data.spa,size,data);
         if (type==DOUBLE) { arg.dv = new double[1][]; arg.dv[0]=stk.d; }
    else if (type==FLOAT) { arg.fv = new float[1][]; arg.fv[0]=stk.f; }
    else if (type==LONG) { arg.lv = new int[1][]; arg.lv[0]=stk.l; }
    stks[iStk++] = stk;
    args[iArg++] = arg;
    return arg;
  }

  /** parse a stack output argument (result or file)
      @param s  The result or file string
      @return stack output argument
  */
  public Argument parseStackOut (String s) {
    addArg("="+s);
    Argument arg = new Argument(Argument.STACKOUT,s);
    if (iStk<=0) badArg ("Nothing on Stack for argument "+arg.name);
    Stack stk = stks[--iStk];
         if (type==DOUBLE) { arg.dv = new double[1][]; arg.dv[0]=stk.d; }
    else if (type==FLOAT) { arg.fv = new float[1][]; arg.fv[0]=stk.f; }
    else if (type==LONG) { arg.lv = new int[1][]; arg.lv[0]=stk.l; }
    args[iArg++] = arg;
    return arg;
  }

  private Stack getStack (int mode, int size, Data data) {
    Stack stk = new Stack();
    stk.mode = mode;
    stk.size = size;
         if (type==DOUBLE) { stk.d = new double[size*mode]; if (data!=null) data.toArray(stk.d); }
    else if (type==FLOAT) { stk.f = new float[size*mode]; if (data!=null) data.toArray(stk.f); }
    else if (type==LONG) { stk.l = new int[size*mode]; if (data!=null) data.toArray(stk.l); }
    return stk;
  }

  /** finish parsing and check arg count
   *  @return 0 if successful, otherwise the number of arguments left on the stack */
  public int parseDone () {
    nArgs = iArg;
    if (iStk!=0) badArg ("Invalid number of arguments parsed, "+iStk+" left on stack");
    return iStk;
  }

  /** get the format at the current top of stack
   *  @return the format at the current top of stack */
  public String getFormat() {
    int mode = stks[iStk-1].mode;
    String fm = "S";
    if (mode==2) fm = "C";
    if (mode==3) fm = "V";
    return fm+type;
  }

  /** example process loop, does not work with variables or files on the stack.
   *  @return number of Arguments */
  public int process() {
    int stat;
    for (iArg=0; iArg<nArgs; iArg++) {
      stat=0;
      Argument arg = args[iArg];
      if (arg.kind==Argument.STACKIN);
      else if (arg.kind==Argument.STACKOUT);
      else if (type==DOUBLE) stat = arg.op.process(arg.dv,arg.size,arg.mode);
      else if (type==FLOAT) stat = arg.op.process(arg.fv,arg.size,arg.mode);
      else if (type==LONG) stat = arg.op.process(arg.lv,arg.size,arg.mode);
      if (stat<0) return stat;
    }
    return nArgs;
  }

  /** standard single result
   *  @return standard single result */
  public double[] result() {
    return args[nArgs-1].dv[0];
  }

  private class Stack {
    int mode,size;
    double[] d;
    float[] f;
    int[] l;
  }

  private void addArg (String arg) {
    if (rawArgs==null) rawArgs = arg;
    else rawArgs += ","+arg;
  }

  /** Throws a MidasException due to bad arguments
      @param reason reason for the bad arguments
   */
  public void badArg (String reason) {
    if (nArgs==0) rawArgs += ",...";
    throw new MidasException (reason+" in ("+rawArgs+")");
  }

}
