package nxm.sys.intr;

import nxm.sys.inc.DataTypes;
import nxm.sys.lib.Data;
import nxm.sys.lib.Intrinsic;
import nxm.sys.libm.ArgStack;
import nxm.sys.libm.Argument;
import nxm.sys.libm.Operator;
import nxm.sys.libm.Operators;

/**
  An HP style calculator for results parameters.

  @author Jeff Schoen
*/
public class Calculator extends Intrinsic implements DataTypes {

  private ArgStack calc;     // The stack of arguments to process
  private boolean  useLabel; // Decide if 1st arg is the sole output label
  private boolean  toScreen; // Indicates results should be printed to the screen
  private int      radix=10; // The radix to use
  private String   formatString = null;

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

  public int open() {
    int narg = MA.numberOf();
    char type = 'D';
    radix     = 10;  // default radix for decimal output
    if (MA.getState("/HEX") ) { radix=16; type='L'; }
    if (MA.getState("/BIN") ) { radix=1;  type='L'; }
    type = MA.getU("/TYPE",""+type).charAt(0);
    formatString = MA.getCS("/NFORM", formatString);

    // set up the calculator (1st time -OR- if type changed -OR- if /reuse=false (for scenario when the math operator changes))
    if (calc==null || type!=calc.type || !MA.getState("/REUSE",true)) {

      // create a new Math calculator (operator stack)
      calc = new ArgStack (narg,narg,type,1,0);

      // output results to screen ?
      toScreen = verbose || M.macro==null;

      // assume first arg is a single output label
      useLabel = true;
      // look for inline =output syntax
      for (int i=1; i<=narg; i++) {
        if (MA.getU(i).startsWith("=")) useLabel=false;
      }
      if (useLabel  && !Character.isLetter(MA.getU(1).charAt(0)))
        M.error("Calculator results label must start with a letter");
    }
    else calc.clear();

    // parse the arguments
    for (int i=1; i<=narg; i++) {
      if (useLabel && i==1) continue;
      String s = MA.getU(i);
      Operator op = Operators.get(s);

           if (op!=null)          calc.parseOperator (s,op);
      else if (s.equals("PUSH"))  calc.parsePush ();
      else if (s.equals("POP"))   calc.parsePop ();
      else if (s.equals("<>"))    calc.parseSwap ();
      else if (s.startsWith("=")) calc.parseStackOut (s.substring(1));
      else                        calc.parseConstant (s,this);
    }

    // add result at end if necessary
    if (useLabel) calc.parseStackOut( MA.getU(1) );

    // check args
    calc.parseDone();

    return NORMAL;
  }

  public int process () {
    Argument arg;
    switch (calc.type) {
     case DOUBLE:
      for (int i=0; i<calc.nArgs; i++) { arg = calc.args[i];
        if (arg.kind == Argument.STACKOUT) output( arg.name, new Data(arg.dv[0]) );
        else arg.op.process (arg.dv,arg.size,arg.mode);
      }
      break;
     case FLOAT:
      for (int i=0; i<calc.nArgs; i++) { arg = calc.args[i];
        if (arg.kind == Argument.STACKOUT) output( arg.name, new Data(arg.fv[0]) );
        else arg.op.process (arg.fv,arg.size,arg.mode);
      }
      break;
     case LONG:
      for (int i=0; i<calc.nArgs; i++) { arg = calc.args[i];
        if (arg.kind == Argument.STACKOUT) output( arg.name, new Data(arg.lv[0]) );
        else arg.op.process (arg.lv,arg.size,arg.mode);
      }
     break;
    }
    return FINISH;
  }

  private void output (String name, Data data) {
    data.resetMode();
    Object value = data;
    if (radix != 10) {
      value = data.toString(0,1,radix); // Handle /HEX, /BIN
    }
    else if (formatString != null) {
      value = data.toString(formatString); // Handle /NFORM
    }
    value = M.results.setKey(name,value); // Type cast in result name can change value
    if (toScreen) M.println("Calc: "+name+" = "+value); // since 3.7.1 using M.println instead of M.terminal.writeln to allow logging - NXM-1813
  }
}
