package tools;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Hashtable;

/** This is a Java version of <code>nmsf.c</code>. The translation from C to Java was done to
    improve portability and consistency across platforms. No real attempt has been made to refactor
    or optimize the program for Java.

    @author  J. Czechowski IV
    @since   NeXtMidas 1.9.2
 */
public class NmSaveFile {
  /** Prevent class instantiation. */
  private NmSaveFile() { }

  /** Looks up a value in the results table in the NMSF file and writes it out.
      @param args Any command line parameters.
      @throws Exception if an exception occurs while running.
   */
  public static synchronized void main (String[] args) throws Exception {
    String fname = System.getProperty("NMSF") + ".msf";

    if (args.length == 0) {
      if(fname.equalsIgnoreCase("null.msf")) { System.err.println("ERROR: NMSF not defined."); }
      System.out.println("NMSFname="+fname+"\n");
    } else if(fname.equalsIgnoreCase("null.msf")) {
      System.err.println("ERROR: NMSF not defined.");
      outputResults(args, null);
    } else {
      outputResults(args, findKey((String)null, args[0]));
    }
  }

  /** Prints out the results. */
  private static final void outputResults (String[] args, String value) {
    if(value == null) { value = "NotFound"; }

    if (args.length == 2) { System.out.println("set " + args[1] + "=" + value); }
    else                  { System.out.print(value); }
  }

  /** Finds a key in the NMSF file.
      @param fname The name of the NMSF file (null = use default).
      @param key   The key to find (not null).
      @since NeXtMidas 2.1.1
   */
  static String findKey (String fname, String key) throws IOException {
    Hashtable<String,Object> tab = null;

    try {
      if (fname == null) { fname = System.getProperty("NMSF"); }
      tab = readResultsTable(fname);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e.getMessage(), e);
    }

    return (tab==null)? null: findKey(tab, key);
  }

  /** Finds a key in the table. Used only by findKey(String,String). */
  @SuppressWarnings("unchecked")
  private static String findKey (Hashtable<String,Object> tab, String key) {
    key = key.toUpperCase();

    Object val = null;
    String sub = null;

    int index = key.indexOf('.');
    if (index > 0) {
      sub = key.substring(index+1);
      key = key.substring(0,index);
    }

    val = tab.get(key);

    if ((val != null) && (sub != null) && (val instanceof Hashtable)) {
      val = findKey((Hashtable<String,Object>)val, sub);
    }

    return (val == null)? null: val.toString();
  }

  /** Reads the results table from the given NMSF file. */
  private static Hashtable<String,Object> readResultsTable (String msf) throws IOException, ClassNotFoundException {
    FileInputStream fis = new FileInputStream(msf+".msf");
    ObjectInputStream ois = new ObjectInputStream(fis);
    String defTblMode  = (String)ois.readObject(); // Not used, but still must be read in.
    String defTblFlags = (String)ois.readObject(); // Not used, but still must be read in.
    byte[] resBytes    = (byte[])ois.readObject(); // Res table is in byte[] and is first entry.
    fis.close();
    return fromBytes(resBytes, 0, resBytes.length);
  }

  /** De-serialize from a byte array (specified range).
      @param bytes  the byte array
      @param index1 start index in byte array
      @param index2 end (exclusive index in byte array
      @return status =0 good <0 bad
   */
  private static Hashtable<String,Object> fromBytes (byte[] bytes, int index1, int index2) {
    // JC4: This was copied from Table.java,v 1.65. Only minor changes were made to add to a
    //      Hashtable and to not perform object conversions.
    final char LCURLY = '{';
    final char RCURLY = '}';
    final char EQUALS = '=';
    final char QUOTE  = '"';
    final char SPACE  = ' ';
    final char LPAREN = '(';
    final char RPAREN = ')';
    final char COMMA  = ',';
    final char COLON  = ':';

    Hashtable<String,Object> tbl = new Hashtable<String,Object>();

    // START COPY
    String key,value; boolean inquote; char type;
    int i,n,ik1,ik2,iv1,iv2,lv,ibr,ieq,iqu;
    i=index1; n=index2;
    if (n-i<2) return null;
    if (bytes[i]!=LCURLY || bytes[n-1]!=RCURLY) return null; // tables must be bracketed by { }
    for (;i<n-1 && bytes[i+1]<=' '; i++);	// remove white space at head
    for (;i<n-1 && bytes[n-2]<=' '; n--);	// remove white space at tail
    for (i+=1,n-=1; i<n; ) {
      for (ik1=i; i<n && bytes[i]!=EQUALS; i++); // find the key/value delimiter
      if (i>=n) break;
      ik2=i; ieq=i; iv1=i+1;
      for (;bytes[ik2-1]<=SPACE; ik2--);	// trim spaces between key and equals
      if (bytes[ik1]==QUOTE) { ik1++; ik2--; }	// trim quotes around key names
      int numStrayEquals = 0; // Count stray ='s which follow other ='s
      for (++i,ibr=0,iqu=0; i<n && (ibr>0 || iqu>0 || bytes[i]!=COMMA); i++) {
        if (bytes[i]==QUOTE) iqu = 1-iqu;
        if (iqu!=0);	// bypass counters inside quotes
        else if (bytes[i]==LCURLY) ibr++;
        else if (bytes[i]==RCURLY) ibr--;
        else if (bytes[i]==LPAREN) ibr++;
        else if (bytes[i]==RPAREN) ibr--;
        else if (bytes[i]==EQUALS && ibr==0) numStrayEquals++;
        if (ibr<0) break;
      }
      type = '_';
      if (bytes[ik1+1]==COLON) { type = (char)bytes[ik1]; ik1+=2; }
      key = new String (bytes,ik1,ik2-ik1);
      key = key.toUpperCase();
      //jph - This should eventually throw a MalformedTable exception
      if (numStrayEquals != 0) {
        //jph -- eliminated warning until I can filter valid case such as
        //   {SWITCHES=/VERBOSE=ON/STATE=10}
        //System.out.println("WARNING: Missing comma after key "+key+" in table "+toStringAbbr());
      }
      iv2=i; lv=iv2-iv1;
      if (lv<=0) {
        tbl.put(key, "");
      }
      else if (bytes[iv1]==QUOTE) {
        tbl.put(key, new String(bytes,iv1+1,iv2-iv1-2));
      }
      else if (bytes[iv1]==LCURLY) {
        //Table sub = new Table();
        //sub.fromBytes (bytes,iv1,iv2);
        //tbl.put(key, sub);
        tbl.put(key, fromBytes(bytes,iv1,iv2));
      }
      else if (lv>2 && bytes[iv1+1]==COLON) {
        String tmp = new String(bytes,iv1+2,lv-2);
        //tbl.put(key, Convert.s2o(tmp,(char)bytes[iv1],null) );
        tbl.put(key, tmp);
      }
      else {
        String tmp = new String(bytes,iv1,lv);
        //tbl.put(key, Convert.s2o(tmp,type,null) );
        tbl.put(key, tmp);
      }
      // allow {A=B,C=D}{E=F} in table syntax
      if (ibr<0 && i<n && bytes[i+1]==LCURLY) i++;
      while (i<n && bytes[++i]==SPACE);
    }
    // END COPY
    return tbl;
  }

}
