package nxm.sys.prim;

import nxm.sys.inc.DataTypes;
import nxm.sys.lib.BaseFile;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.Primitive;
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 files.

  @author Jeff Schoen
*/
public class fcalculator extends Primitive implements DataTypes {

  private ArgStack calc; 	// the calculator's parser/stack
  private int xfero; 		// the output transfer length
  private char type; 		// results data type
  private DataFile dfs[]; 	// the data files to use
  private int ndf=0; 		// the number of data files
  private int idf=-1; 		// the data file index value
  private boolean hasInput;	// are there any input files
  private boolean propHeader = true;  // used to back out Bug 2657 changes.

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

  public int open() {
    int     narg = MA.numberOf();
    boolean var  = MA.getState("/MON", false);

    type = MA.getU("/TYPE","F").charAt(0);
    xfer = MA.getL("/TL",1024);
    calc = new ArgStack (narg,narg,type,xfer,0);
    dfs  = new DataFile[narg];
    propHeader = MA.getState("/PROPHEADER",propHeader);  // backwards compatibility for Bug 2657

    // assume first arg is a single output label, look for inline = output syntax
    boolean useLabel=true;
    for (int i=1; i<=narg; i++) {
      if (MA.getU(i).startsWith(">")) useLabel=false;
    }

    // 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("<")) parseFileIn  (MA.getCS(i).substring(1));
      else if (s.startsWith(">")) parseFileOut (MA.getCS(i).substring(1));
      else if (calc.parseConstant(s,this,true,var)); // it is a numeric constant
      else parseFileIn(MA.getCS(i));		// else assume input file
    }

    // add output at end if necessary
    if (useLabel) parseFileOut ( MA.getU(1) );

    // check args
    calc.parseDone();

    // make sure there is at least one input file
    if (!hasInput) calc.badArg("Need at least one input file");

    return NORMAL;
  }

  public int process() {
    xfero = xfer;	// default output TL to input
    int eofs = 0;	// hit EOF in any file

    for (int i=0; i < calc.nArgs; i++) {
      Argument arg = calc.args[i];

      if (arg.kind == Argument.STACKIN) {		// Read the data in from a file
        eofs += dataIn(arg);
      }
      else if (arg.kind == Argument.STACKOUT) {	// Write the data out to a file
        dataOut(arg);
      }
      else if (arg.kind == Argument.VARIABLE) {	// Update variables
        updateVariable(arg);
      }
      else { 					// Process the argument
        if (eofs>0) {arg.size=xfer;}//overwrite size with ngot at end of file
        switch (calc.type) {
          case FLOAT:  arg.op.process (arg.fv,arg.size,arg.mode); break;
          case DOUBLE: arg.op.process (arg.dv,arg.size,arg.mode); break;
          case LONG:   arg.op.process (arg.lv,arg.size,arg.mode); break;
        }
      }
    }

    return (eofs>0)? FINISH : NORMAL;
  }

  public int close() {
    for (int i=0; i<ndf; i++) dfs[i].close();
    ndf = 0; // reset the number of data files - since 3.7.3 for NXM-3040
    return NORMAL;
  }

  /** This will prepare a file for input and add it to {@link #dfs}.  */
  private void parseFileIn (String name) {
    DataFile df = new DataFile();
    df.init(this,name,"1000,2000","S#,C#,V#",0);
    df.open(BaseFile.INPUT);
    addProgressFeed(df);
    int nxfer=xfer;
    // have the xfer size be the minimum of the # of elements or the default xfer size
    if (df.typeClass==1) {
      double numElem = df.getSize();
      if (numElem>0) {    // ensure if input file size=0 that xfer = 1024 default
        nxfer = (int)Math.ceil(Math.min(nxfer, numElem));
      }
      xfer = nxfer;
    }
    if (df.typeClass==2) { nxfer=df.getSubSize(); xfer=1; }
    df.buffer = df.getDataBuffer(xfer,(byte)type);
    Argument arg = calc.parseStackIn (name,df.buffer,nxfer);
    // mix of type 1&2 files always output the type 2
    if (xfer>1 || df.typeClass==2 || idf<0) idf=ndf;
    arg.info=ndf;
    dfs[ndf++]=df;
    hasInput = true;
  }

  /** This will prepare a file for output and add it to {@link #dfs}.  */
  private void parseFileOut (String name) {
    DataFile df = new DataFile();
    if (idf>=0) df.init(this,name,dfs[idf],0);
    else df.init(this,name,"1000","SF",0);
    df.setFormat( calc.getFormat() );
    df.open(BaseFile.OUTPUT);
    df.buffer = df.getDataBuffer(xfer,(byte)type);
    Argument arg = calc.parseStackOut (name);
    arg.info=ndf;
    dfs[ndf++]=df;
  }

  /** Reads data in from a file.

     @param arg The argument whose <code>arg.info</code> value defines the
                index in {@link #dfs} that is to be used as the input file and
                will also be the destination for the data read in.

     @return <code>1</code> if the end of the file (<code>EOF</code>) has been
             reached and <code>0</code> otherwise.
  */
  private int dataIn (Argument arg) {
    boolean reachedEnd = false;
    DataFile df = dfs[arg.info];
    int ngot = df.read (df.buffer);
    if (ngot<0) { xfero=0; return 1; }
    if (ngot<xfero) {xfero = xfer = ngot; reachedEnd=true;}
         if (type=='F') df.buffer.toArray(arg.fv[0]);
    else if (type=='D') df.buffer.toArray(arg.dv[0]);
    else if (type=='L') df.buffer.toArray(arg.lv[0]);
    return (reachedEnd)? 1:0;
  }

  /** Updates a variable on the stack. */
  private void updateVariable (Argument arg) {
    Data data = (Data)Convert.s2o(arg.name,type,this);
         if (type=='F') data.toArray(arg.fv[0]);
    else if (type=='D') data.toArray(arg.dv[0]);
    else if (type=='L') data.toArray(arg.lv[0]);
  }

  /** Writes data out to a file.

     @param arg The argument whose <code>arg.info</code> value defines the
                index in {@link #dfs} that is to be used as the output file and
                will also be the source of the data to write out.
  */
  private void dataOut (Argument arg) {
    DataFile df = dfs[arg.info];
    if (idf >=0 && propHeader) df.buffer.setHeader(dfs[idf].buffer.getHeader());   // propagate header (since 3.3.0 - Bug 2657)
    if      (type=='F') df.buffer.fromArray(arg.fv[0]);
    else if (type=='D') df.buffer.fromArray(arg.dv[0]);
    else if (type=='L') df.buffer.fromArray(arg.lv[0]);
    df.write (df.buffer,xfero);
  }

}
