package nxm.sys.libm;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
  Code generation utility for Operator subclasses.

  @author Jeff Schoen
  @author Neon Ngo - 2011-07-20 Added support for TEXT directive in config file (for short description of Operator)
                                Fixed potential NPE in parseConfig(..) method
                                Updated/added docs for some methods and most fields
*/
public class Generate {
  /** output debug messages? */
  private static boolean debug = false;

  /** path to "Operators.cnf" file (this will also be output directory of generated file(s) */
  String path;
  /** parsed fields of current Operator's: number of inputs [arg 3] and number of outputs [arg 4] (from OPERATOR directive in config) */
  int inputs, outputs;
  /** current Operator's: name [arg0], class [arg1], symbol [arg2], flags [arg5], override name class javadocs (text) [arg6] (from OPERATOR directive in config) */
  String name, clazz, symbol, flags, text;
  /** short description for current Operator (from TEXT directive in config) */
  String description;

  /** output verbose output messages? */
  private boolean verbose;
  /** does current operator use scale factor? */
  private boolean scaled;
  private int     ranks;

  /** max lines to read from config file */
  static final int maxLines = 1024;
  /** number of lines in config file */
  int nlines = 0;
  /** entire config file text (commented out lines are NOT included) */
  String[] lines = new String[maxLines];

  /** max number of operators allowed */
  static final int maxOps = 1024;
  /** number of operators from config file */
  int nops = 0;
  /** list of all operators */
  int[] ops = new int[maxOps];

  /** max coding lines for current Operator */
  static final int maxCodes = 32;
  /** number of coding lines for current Operator */
  int ncodes = 0;
  /** java loop code for current Operator (from LOOPCODE directive in config) */
  String[] codes = new String[maxCodes];
  /** java pre loop code for current Operator (from INITCODE directive in config) */
  String[] precodes = new String[maxCodes];
  /** java post loop code for current Operator (from EXITCODE directive in config) */
  String[] postcodes = new String[maxCodes];
  /** java declaration code for current Operator (from VARIABLE directive in config) */
  String declarations;

  /** Prevent others from creating a new instance with this private constructor. */
  private Generate() { }

  /** Driver for Generate (see {@link #doOpers(String, String, boolean)})
      @param args path and function arguments (respectively) for Operator
   */
  public static void main (String[] args) {
    doOpers(args[0], args[1], false);
    System.exit(0);
  }

  private FileWriter getFileWriter (String name) throws IOException {
    return new FileWriter(path+name);
  }

  /** Generate the specified operator(s).
      @param path path to "Operators.cnf" file (this will also be output directory of generated file(s)
      @param func operator (or ALL) to generate code for
      @param verbose true to turn on verbose output messages
   */
  public static void doOpers (String path, String func, boolean verbose) {
    try {
      (new Generate()).run(path, func, verbose);
    } catch (IOException e) {
      System.out.println("Generate.doOpers(...) IO Problem: "+e);
    }
  }

  private void run (String path, String func, boolean verbose) throws IOException {
    this.path = path;
    this.verbose = verbose;
    doOpers (func);
  }

  private void doOpers (String func) throws IOException {

    parseConfig("Operators.cnf");

    // Generate Operator classes
    int did = 0;
    boolean all = func.equals("ALL");
    for (int iop=0; iop<nops; iop++) {
      parseOperator(iop);
      if (all || name.equals(func)) { doOper(); did++; }
    }
    if (did==0) System.out.println("Generate.doOpers(.) No func="+func+" found in config table");
    if (!all) return;

    // Generate the Operator enumeration index
    FileWriter tf = getFileWriter("OpId.java");
    writeHeader (tf);
    tf.write("/**\n"
           + "   Operator enumeration lookup index. <br>\n"
           + "    See {@link Operator} for a description of the available I/O Modes.\n"
           + "   @author  Auto-Generated by the {@link Generate} class.\n"
           + "*/\n");
    tf.write("public interface OpId { \n\n");

    for (int i=0; i<nops; i++) {
      String id    = getS(lines[ops[i]],0);
      String clazz = getS(lines[ops[i]],1);

      tf.write("  /** Identifier symbol for the "+clazz+" operator.\n"
             + "      @see "+clazz+"\n"
             + "   */\n"
             + "  public static final int "+id+" = "+(i+1)+";\n\n");
    }

    tf.write("  /** The total number of operators. */\n"
           + "  public static final int MAXOPS = "+nops+";\n\n");
    tf.write("}\n");

    tf.close();

    // Generate the Operator parser and class finder
    tf = getFileWriter("Operators.java");
    if (verbose) System.out.println("Generating Operators.java");
    writeHeader (tf);
    tf.write("import nxm.sys.inc.*;\n\n");
    tf.write("/**\n"
           + "   Operator class resolver. <br>\n"
           + "   @author  Auto-Generated by the {@link Generate} class.\n"
           + "   @see OpId\n"
           + "*/\n"
           + "public class Operators implements OpId,Constants {\n\n"
           + "  /** Get an operator based on its identifier symbol.\n"
           + "      @param id The identifier symbol for the operator.\n"
           + "                (See {@link OpId} for the set of operator symbol definitions.)\n"
           + "      @return A new instance of the operator identified by the identifier symbol provided,\n"
           + "              or <code>null</code> if the identifier provided does not match one of the valid operators.\n"
           + "   */ \n"
           + "  public static Operator get (int id) {\n"
           + "    Operator op=null;\n"
           + "    if (id==0) op=null;\n");
    for (int i=0; i<nops; i++) {
      String line = lines[ops[i]];
      tf.write("    else if (id=="+getS(line,0)+") op = new "+getS(line,1)+"();\n");
    }
    tf.write( "    return op;\n"
            + "  }\n\n\n"
            + "  /** Get an operator based on its identifier symbol.\n"
            + "      @param id The identifier for the operator.\n"
            + "                (See <code>Operators.cnf</code> for the definitions of the identifiers.)\n"
            + "      @return A new instance of the operator identified by the identifier symbol provided,\n"
            + "              or <code>null</code> if the identifier provided does not match one of the valid operators.\n"
            + "   */ \n"
            + "  public static Operator get (String id) {\n"
            + "    Operator op=null;\n"
            + "    if (id==null||id.length()==0) op=null;\n");
    for (int i=0; i<nlines; i++) {
      String line = lines[i];
      boolean oper = line.startsWith("OPERATOR");
      boolean alias = line.startsWith("ALIAS");
      if (!oper && !alias) continue;
      String key = getS(line,0).trim(); // since 3.3.0: trim key (operator name) and sym
      String sym = getS(line,2).trim(); // symbol alias for operator
// 2008-02-28: Since 2.5.4 for Bug 1297 --  Problem:
//   nM> calc,,SCALE 2 *
// where SCALE is a results parameter, did not work.
// Issue: Is that has been set above and is not the value for this line
//        in the cnf file for this command.
// Solution: Reparse the line
//      if (getFlag("NOCALC")) continue;

      String myFlagsString = getS(line,5);
      if (getFlag(myFlagsString, "NOCALC")) continue;
      tf.write("    else if (id.equals(\""+key+"\")");
      if (sym!=null && sym.length()>0) tf.write(" || id.equals(\""+sym+"\")");
      if (oper) tf.write(") op = new "+getS(line,1)+"();\n");
      if (alias) tf.write(") op = new "+getS(line,1)+";\n");
    }
    tf.write( "    return op;\n"
            + "  }\n"
            + "}\n");
    tf.close();

    // Generate a list of auto generated files for cleanup
    writeFileList("GeneratedFiles.lst");
    writeFileList(".gitignore");
  }

  private void writeHeader (FileWriter tf) throws IOException {
    tf.write("/***********************************/\n");
    tf.write("/* THIS IS AN AUTO-GENERATED CLASS */\n");
    tf.write("/*       !!! DO NOT EDIT !!!       */\n");
    tf.write("/***********************************/\n");
    tf.write("package nxm.sys.libm;\n\n");
  }

  /** Writes the list of generated files to a file.
      @param fname The name of the output file.
   */
  private void writeFileList (String fname) throws IOException {
    FileWriter tf = getFileWriter(fname);
    tf.write("OpId.java\nOperators.java\n");
    for (int iop=0; iop<nops; iop++) {
      parseOperator(iop);
      if (!getFlag("NOGEN")) tf.write(clazz+".java\n");
    }
    tf.write("GeneratedFiles.lst\n");
    tf.write(".gitignore\n");
    tf.close();
  }

  private void doOper() throws IOException {

    if (text==null || text.length()<=0) {
      text = "Performs the "+clazz+" operation on arrays. "; // NTN: DO NOT remove the last period followed by space
    }
    if (getFlag("NOGEN")) {
      if (verbose) System.out.println("Generate=N class "+clazz+" in "+path);
      return;
    }
    if (verbose) System.out.println("Generating class "+clazz+" in "+path);

    if (text.indexOf(". ") == -1) text += ". "; // append period and space to end of text if it doesn't have it.

    String shortDescription; // since 3.1.0 for BUG 2440
    if (description == null) shortDescription = "";
    else shortDescription = "   " + description + " <br>\n";

    FileWriter tfo = getFileWriter(clazz+".java");
    writeHeader (tfo);
    tfo.write("import nxm.sys.inc.*;\n\n"
            + "/**\n"
            + "   "+text+"<br>\n"
            + shortDescription
            + "   See {@link Operator} for a description of the available I/O Modes.\n"
            + "   @author  Auto-Generated by the {@link Generate} class.\n"
            + "   @see     Operator\n"
            + "*/\n"
            + "@SuppressWarnings(\"cast\")\n" // since 3.1.1: suppress redundant cast warnings
            + "public final class "+clazz+" extends Operator implements Constants {\n\n");
    makeOperator(tfo);
    tfo.write("}\n");
    tfo.close();
  }

  /** generate code for a subclass */
  private int makeOperator (FileWriter tf) throws IOException {
    int ok=0;
    ranks = -1;
    makeDeclarations (tf);
    makeConstructors (tf,clazz);
    makeValidate (tf);
    ok += makeProcessMethod1  (tf,"double",0);
    ok += makeProcessMethod1  (tf,"double",1);
    ok += makeOperatorMethods (tf,"double");
    ok += makeProcessMethod1  (tf,"float",0);
    ok += makeProcessMethod1  (tf,"float",1);
    ok += makeOperatorMethods (tf,"float");
    ok += makeProcessMethod1  (tf,"int",0);
    ok += makeProcessMethod1  (tf,"int",1);
    ok += makeOperatorMethods (tf,"int");
    makeGetMethods (tf,ranks);
    return ok;
  }

  private int makeValidate (FileWriter tf) throws IOException {
    int ok=0;
    tf.write("  public final int validate (char type, int n, int mode) {\n");
    tf.write("    switch (mode) {\n");
    for (int ic=0; ic<ncodes; ic++) {
      String mode = getS(codes[ic],0);
      int dupe=0;
      for (int i=0; i<ic; i++) { if (mode.equals(getS(codes[i],0))) dupe++; }
      if (dupe==0) tf.write("      case "+mode+": return n;\n");
    }
    tf.write("      default: return NOSUPPORT;\n"
           + "    }\n"
           + "  }\n\n");
    return ok;
  }

  /** generate code for a subclass */
  private int makeProcessMethod1 (FileWriter tf, String type, int scale) throws IOException {
    int ok=0; String scl;
    if (scale==1) {
      tf.write("  public final int process ("+type+"[][] v, int n, int mode, double scale) {\n");
    } else {
      tf.write("  public final int process ("+type+"[][] v, int n, int mode) {\n");
    }
    tf.write("    switch (mode) { \n");

    for (int ic=0; ic<ncodes; ic++) { String line = codes[ic];
      if (line.indexOf("CODE_D ")>=0 && !type.equals("double")) continue;
      if (line.indexOf("CODE_F ")>=0 && !type.equals("float")) continue;
      if (line.indexOf("CODE_L ")>=0 && !type.equals("int")) continue;
      String mode = getS(line,0);
      int rank = mode.length();
      String equ = getS(line,1);
      scl = (scaled)? ", scale" : "";
      switch (rank) {
        case 1: tf.write("      case "+mode+": "+mode+"(v[0],n"+scl+"); return n;\n"); break;
        case 2: tf.write("      case "+mode+": "+mode+"(v[0],v[1],n"+scl+"); return n;\n"); break;
        case 3: tf.write("      case "+mode+": "+mode+"(v[0],v[1],v[2],n"+scl+"); return n;\n"); break;
        case 4: tf.write("      case "+mode+": "+mode+"(v[0],v[1],v[2],v[3],n"+scl+"); return n;\n"); break;
      }
      ok++;
      if (ranks==-1) ranks=rank;
      else if (rank!=ranks) ranks=-2;
    }
    tf.write("      default:  return NOSUPPORT;\n");
    tf.write("    }\n");
    tf.write("  }\n\n");
    return ok;
  }

  /** generate code for a subclass */
  private int makeProcessMethod3 (FileWriter tf, String type) throws IOException {
    int ok=0;
    String scl;
    tf.write("  public final int process ("+type+"[] a, "+type+"[] b, "+type+"[] c, "+type+"[] d, int n, int mode) {\n");
    tf.write("    switch (mode) { \n");

    for (int ic=0; ic<ncodes; ic++) { String line = codes[ic];
      if (line.indexOf("CODE_D ")>=0 && !type.equals("double")) continue;
      if (line.indexOf("CODE_F ")>=0 && !type.equals("float")) continue;
      if (line.indexOf("CODE_L ")>=0 && !type.equals("int")) continue;
      String mode = getS(line,0);
      int rank = mode.length();
      String equ = getS(line,1);
      scl = (scaled)? ", scale" : "";
      switch (rank) {
        case 1: tf.write("      case "+mode+": "+mode+"(a,n"+scl+"); return n;\n"); break;
        case 2: tf.write("      case "+mode+": "+mode+"(a,b,n"+scl+"); return n;\n"); break;
        case 3: tf.write("      case "+mode+": "+mode+"(a,b,c,n"+scl+"); return n;\n"); break;
        case 4: tf.write("      case "+mode+": "+mode+"(a,b,c,d,n"+scl+"); return n;\n"); break;
      }
      ok++;
    }
    tf.write("      default:   return NOSUPPORT;\n");
    tf.write("    }\n");
    tf.write("  }\n\n");
    return ok;
  }


  /** generate code for a subclass */
  private int makeOperatorMethods (FileWriter tf, String type) throws IOException {
    int ok=0;
    for (int ic=0; ic<ncodes; ic++) {
      String line = codes[ic];
      if (line.indexOf("CODE_D ")>=0 && !type.equals("double")) continue;
      if (line.indexOf("CODE_F ")>=0 && !type.equals("float")) continue;
      if (line.indexOf("CODE_L ")>=0 && !type.equals("int")) continue;
      String mode = getS(line,0);
      int rank = mode.length();
      String equ = getS(line,1);

      final String[] vname = new String[] { "a", "b", "c", "d" };
      final String[] vpos = new String[] { "first", "second", "third", "fourth" };

      // Method JavaDoc Comment
      tf.write("  /** Apply this operator to mode {@link Operator#"+mode+"} data.\n");

      for (int i=0; i<rank; i++) tf.write("      @param "+vname[i]+"     The "+vpos[i]+"  operand.\n");

      tf.write("      @param n     The number of elements to operate on.\n");

      if (scaled) {
        tf.write("      @param scale The scale factor.\n");
        tf.write("      @see #process(" + type + "[][],int,int,double)\n");
      } else {
        tf.write("      @see #process(" + type + "[][],int,int)\n");
      }
      tf.write("   */\n");

      // Method Signature
      if (declarations!=null) {
        tf.write("  public final void "+mode+" (");
      } else {
        tf.write("  public static final void "+mode+" (");
      }

      for (int i=0; i<rank; i++) tf.write(type+"[] "+vname[i]+", ");

      if (scaled) {
        tf.write("int n, double scale) {\n");
      } else {
        tf.write("int n) {\n");
      }

      // Method Body
      boolean hasS = mode.indexOf('S')>=0;
      boolean hasC = mode.indexOf('C')>=0 || mode.indexOf('G')>=0;
      boolean hasV = mode.indexOf('V')>=0;
      boolean hasSC = mode.equals("SC");
      boolean oconst = getFlag("OSE");
      int pconst = oconst? rank : -1;
      inputs = rank-outputs;
      boolean hasInputSSS = mode.startsWith("SSS") && (inputs == 3);

      if (precodes[ic]!=null) {
        tf.write("    " + doSubs(getS(precodes[ic],1),type,"abcdefg",mode.length(),pconst) + "\n");
      }

      //
      // Handle Special case of 3 inputs allowing 2 of the 3 to be constants
      //
      if (inputs == 3){
        for (int i=inputs; i>=0; i--) {
          for (int secondConst = i-1; secondConst>=0;secondConst--){

            if (inputs <= 1);
            else if (i == 0)     tf.write("    } else {\n");
            else if (i < inputs || secondConst < i-1) tf.write("    } else ");
            else                 tf.write("    ");

            switch(i) {
              case 1: tf.write("if (a.length == 1) {\n"); break;
              case 2:
                switch (secondConst){
                  case 0: tf.write("if (b.length  == 1) {\n"); break;
                  case 1: tf.write("if ((b.length  == 1) && (a.length == 1)) {\n"); break;
                }
                break;
              case 3:
                switch (secondConst){
                  case 0: tf.write("if (c.length == 1) {\n"); break;
                  case 1: tf.write("if ((c.length == 1) && (a.length == 1)) {\n"); break;
                  case 2: tf.write("if ((c.length == 1) && (b.length == 1)) {\n"); break;
                }
                break;
            }

            if (hasSC) tf.write("      for (int i=n-1,j=i+i; i>=0; i--,j-=2) {\n");
            else if (hasC)  tf.write("      for (int i=0,j=0; i<n; i++,j+=2) {\n");
            else if (hasV)  tf.write("      for (int i=0,k=0; i<n; i++,k+=3) {\n");
            else            tf.write("      for (int i=0; i<n; i++) {\n");

            tf.write("        ");
            tf.write(doSubs(equ,type,"abcdefg",mode.length(),i,secondConst));
            tf.write("\n      }\n");

            if ((inputs > 1) && (i==0)) tf.write("    }\n");
          }
          if (i == 0) {
            tf.write("    } else {\n");

            if (hasSC) tf.write("      for (int i=n-1,j=i+i; i>=0; i--,j-=2) {\n");
            else if (hasC)  tf.write("      for (int i=0,j=0; i<n; i++,j+=2) {\n");
            else if (hasV)  tf.write("      for (int i=0,k=0; i<n; i++,k+=3) {\n");
            else            tf.write("      for (int i=0; i<n; i++) {\n");

            tf.write("        ");
            tf.write(doSubs(equ,type,"abcdefg",mode.length(),i));
            tf.write("\n      }\n");

            tf.write("    }\n");
          }
        }
        if (postcodes[ic]!=null) {
          tf.write("    " + doSubs(getS(postcodes[ic],1),type,"abcdefg",mode.length(),pconst) + "\n");
        }

      }
      //
      // Normal case (2 inputs or less)
      //
      else {
        for (int i=inputs; i>=0; i--) {
          if (inputs==1 && i==1) continue;

          if (inputs <= 1);
          else if (i == 0)     tf.write("    } else {\n");
          else if (i < inputs) tf.write("    } else ");
          else                 tf.write("    ");

          switch(i) {
            case 1: tf.write("if (a.length == " + getModeLength(mode,i) + ") {\n"); break;
            case 2: tf.write("if (b.length == " + getModeLength(mode,i) + ") {\n"); break;
            case 3: tf.write("if (c.length == " + getModeLength(mode,i) + ") {\n"); break;
          }

               if (hasSC) tf.write("      for (int i=n-1,j=i+i; i>=0; i--,j-=2) {\n");
          else if (hasC)  tf.write("      for (int i=0,j=0; i<n; i++,j+=2) {\n");
          else if (hasV)  tf.write("      for (int i=0,k=0; i<n; i++,k+=3) {\n");
          else            tf.write("      for (int i=0; i<n; i++) {\n");

          tf.write("        ");
          tf.write(doSubs(equ,type,"abcdefg",mode.length(),oconst?rank:i));
          tf.write("\n      }\n");

          if ((inputs > 1) && (i==0)) tf.write("    }\n");
        }

        if (postcodes[ic]!=null) {
          tf.write("    " + doSubs(getS(postcodes[ic],1),type,"abcdefg",mode.length(),pconst) + "\n");
        }
      }
      // Close Method
      tf.write("  }\n\n");
      ok++;
    }
    return ok;
  }

  private int getModeLength (String modes, int i) {
    char m = modes.charAt(i-1);
    if (m=='C'||m=='G') return 2;
    if (m=='V') return 3;
    return 1;
  }

  /** do substitutions on equation, of type, for N variables, with constant input */
  private String doSubs (String equ, String type, String vs, int ns, int iconst, int secondConst) {
    for (int i=0; i<ns; i++) {
     char v = vs.charAt(i);
     if ((i+1 == iconst) || (i+1 == secondConst)) {
      equ = doSub(equ,v+"",v+"[0]");
      equ = doSub(equ,v+"r",v+"[0]");
      equ = doSub(equ,v+"i",v+"[1]");
      equ = doSub(equ,v+"x",v+"[0]");
      equ = doSub(equ,v+"y",v+"[1]");
      equ = doSub(equ,v+"z",v+"[2]");
     } else {
      equ = doSub(equ,v+"",v+"[i]");
      equ = doSub(equ,v+"r",v+"[j]");
      equ = doSub(equ,v+"i",v+"[j+1]");
      equ = doSub(equ,v+"x",v+"[k]");
      equ = doSub(equ,v+"y",v+"[k+1]");
      equ = doSub(equ,v+"z",v+"[k+2]");
     }
    }
    equ = doSub(equ,"type",type);
    if (type.equals("int")) type = "Integer";
    if (type.equals("float")) type = "Float";
    if (type.equals("double")) type = "Double";
    equ = doSub(equ,"Type",type);
    return equ;
  }

  /** do substitutions on equation, of type, for N variables, with constant input */
  private String doSubs (String equ, String type, String vs, int ns, int iconst) {
    for (int i=0; i<ns; i++) {
     char v = vs.charAt(i);
     if (i+1 == iconst) {
      equ = doSub(equ,v+"",v+"[0]");
      equ = doSub(equ,v+"r",v+"[0]");
      equ = doSub(equ,v+"i",v+"[1]");
      equ = doSub(equ,v+"x",v+"[0]");
      equ = doSub(equ,v+"y",v+"[1]");
      equ = doSub(equ,v+"z",v+"[2]");
     } else {
      equ = doSub(equ,v+"",v+"[i]");
      equ = doSub(equ,v+"r",v+"[j]");
      equ = doSub(equ,v+"i",v+"[j+1]");
      equ = doSub(equ,v+"x",v+"[k]");
      equ = doSub(equ,v+"y",v+"[k+1]");
      equ = doSub(equ,v+"z",v+"[k+2]");
     }
    }
    equ = doSub(equ,"type",type);
    if (type.equals("int")) type = "Integer";
    if (type.equals("float")) type = "Float";
    if (type.equals("double")) type = "Double";
    equ = doSub(equ,"Type",type);
    return equ;
  }

  private String doSub (String equ, String s1, String s2) {
    char c; String delimiters = " ()[]+-*/;:?<>#$=,|&!~^%.";
    for (int i=0; (i=equ.indexOf(s1,i))>=0; i++) {
      if (i==0) c=' '; else c=equ.charAt(i-1);
      if (delimiters.indexOf(c)<0) continue;
      if (i+s1.length()==equ.length()) c=' '; else c=equ.charAt(i+s1.length());
      if (delimiters.indexOf(c)<0) continue;
      equ = equ.substring(0,i)+s2+equ.substring(i+s1.length());
    }
    return equ;
  }

  private void makeGetMethods (FileWriter tf, int rank) throws IOException {
    if (rank>0 && inputs<0) inputs = rank-outputs;
    tf.write("  public int getInputs() { return "+inputs+"; }\n");
    tf.write("  public int getOutputs() { return "+outputs+"; }\n");
  }

  private void makeDeclarations (FileWriter tf) throws IOException {
    if (declarations != null) {
      tf.write("  /* Parameter declarations. */\n"
             + "  " + declarations + "\n\n");
    }
  }

  private void makeConstructors (FileWriter tf, String name) throws IOException {
    tf.write("  /** Create a new instance. */\n"
           + "  public "+name+"() {\n"
           + "    super();\n"
           + "  }\n"
           + "\n"
           + "  /** Create a new instance.\n"
           + "      @param flags The processing flags to use.\n"
           + "   */\n"
           + "  public "+name+" (int flags) {\n"
           + "    super(flags);\n"
           + "  }\n"
           + "\n"
           + "  /** Create a new instance.\n"
           + "      @param scale The scale factor to use.\n"
           + "   */\n"
           + "  public "+name+" (double scale) {\n"
           + "    super(scale);\n"
           + "  }\n"
           + "\n"
           + "  /** Create a new instance.\n"
           + "      @param flags The processing flags to use.\n"
           + "      @param scale The scale factor to use.\n"
           + "   */\n"
           + "  public "+name+" (int flags, double scale) {\n"
           + "    super(flags,scale);\n"
           + "  }\n"
           + "\n");
  }

  private void parseConfig (String name) throws IOException {
    BufferedReader br = new BufferedReader( new FileReader(path+name) );
    while (true) {
      String line = br.readLine();
      if (line==null) break;
      line = line.trim();
      if (line.length()==0) continue;
      if (line.startsWith("!")) continue;
      if (line.startsWith("OPERATOR")) ops[nops++]=nlines;
      while (line.endsWith("\\")) {
        line = line.substring(0,line.length()-1) + "\n";
        String nextLine = br.readLine();
        if (nextLine==null) break; // prevent NPE when on last line of config
        line += "        "+nextLine.trim();
      }
      lines[nlines++] = line+",,,,,,,";
    }
    ops[nops]=nlines;
    br.close();
  }

  private void parseOperator (int iop) {
    String line = lines[ops[iop]];
    name = getS(line,0);
    clazz = getS(line,1);
    symbol = getS(line,2);
    inputs = getL(line,3,-1);
    outputs = getL(line,4,1);
    flags = getS(line,5);
    text = getS(line,6);
    ncodes = 0;
    scaled = false;
    declarations = null;
    description  = null;
    for (int i=0; i<maxCodes; i++) {
      codes[i] = precodes[i] = postcodes[i] = null; // reset state
    }
    for (int i=ops[iop]; i<ops[iop+1]; i++) {
      String curLine = lines[i];
      if (debug) System.out.println("processing line for ["+name+"]: "+ curLine);
      if (curLine.startsWith("INITCODE")) precodes[ncodes]    = curLine;
      if (curLine.startsWith("LOOPCODE")) codes[ncodes++]     = curLine;
      if (curLine.startsWith("EXITCODE")) postcodes[ncodes-1] = curLine;
      if (curLine.startsWith("TEXT"))     description  = getS(curLine,-1).trim(); // since 3.1.0 for BUG 2440
      if (curLine.startsWith("VARIABLE")) declarations = getS(curLine,-1);
      else if (curLine.indexOf("scale")>=0) scaled=true;
    }
  }

  /** Grabs the specified index argument from specified line string (after directive (first word in line)).
      @param line  String to grab specified argument from
      @param index argument index to grab from line (zero based); use -1 to grab all args
   */
  private String getS (String line, int index) {
    int inQuote=0, inParen=0;
    int iSpace = line.indexOf(" ");
    int iEqual = line.indexOf("=");
    if (index==0) return line.substring(iSpace+1,iEqual);
    if (index==-1) { index=1; iEqual=iSpace; }
    for (int i=iEqual+1,j=iEqual,k=0; i<line.length(); i++) {
      char c = line.charAt(i);
      if (c=='"') inQuote = 1-inQuote;
      else if (c=='(') inParen++;
      else if (c==')') inParen--;
      if (inQuote>0 || inParen>0 || c!=',');
      else if (++k<index) j=i;
      else if (line.charAt(j+1)=='"') return line.substring(j+2,i-1);
      else return line.substring(j+1,i);
    }
    return "";
  }

  private int getL (String line, int index, int def) {
    String s = getS(line,index);
    if (s==null || s.length()==0) return def;
    char c = s.charAt(0);
         if (c=='0') return 0;
    else if (c=='1') return 1;
    else if (c=='2') return 2;
    else if (c=='3') return 3;
    else if (c=='4') return 4;
    else return def;
  }

//jph --28feb08 -- problem calc,,SCALE 2 * would not work where SCALE is a results parameter
  private boolean getFlag (String flagsString, String flag) {
    return (flagsString!=null && flagsString.indexOf(flag) >= 0);
  }
//jph -- 28feb08 -- end
  private boolean getFlag (String flag) {
    return (flags!=null && flags.indexOf(flag) >= 0);
  }

  /** Gets whether or not NeXtMidas debugging is enabled
      @return state of NeXtMidas debugging
   */
  public static boolean getDebug ()         { return debug; }
  /** Toggles the state of NeXtMidas debugging
      @param val new state for NeXtMidas debugging
   */
  public static void setDebug (boolean val) { debug = val;  }
}
