package nxm.sys.intr;

import java.util.regex.Pattern;

import nxm.sys.inc.Keyable;
import nxm.sys.lib.IOResource;
import nxm.sys.lib.Intrinsic;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.StringUtil;
import nxm.sys.lib.XmUtil;

/** Sets the auxiliary disk/path assignments for data files.

    @author  Jeff Schoen
 */
public class Auxiliary extends Intrinsic {
  private static final Pattern AUX_REGEX = Pattern.compile("[A-Z0-9_]+"); // Used in checkAuxTable(..)

  private boolean warn = true; // Show warnings?

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

  public int process () {
    int     np   = MA.numberOf(); // number of parameters
    boolean list = MA.getState("/LIST");
    String  waux = MA.getU("WRITE");
    String  raux = MA.getU("READ");
    String  save = MA.getU("/SAVE", "");
    String  load = MA.getU("/LOAD", ""); // <-- although this could be getS, getU matches X-Midas 4.6

    warn = MA.getZ("/WARN", true);

    if (MA.getState("/STORE") || (save.length() > 0)) {                 // store the current aux paths
      waux = MR.getS("AUX.WRITE");
      raux = MR.getS("AUX.READ");

      if (MA.getState("/STORE")) {
        MR.put("OLDAUXREAD",raux);
        MR.put("OLDAUXWRITE",waux);
      }
      else {
        MR.put(save, waux+","+raux);
      }
    }
    else if (MA.getState("/RESTORE") || (load.length() > 0)) {          // restore the current aux paths
      if (MA.getState("/RESTORE")) {
        waux = MR.getS("OLDAUXWRITE");
        raux = MR.getS("OLDAUXREAD");
      }
      else {
        String entry = MR.getS(load, "<error>");
        int    index = entry.indexOf(',');
        if (index < 0) {
          // Similar to X-Midas 4.6, this is a warning rather than an error
          M.warning("Unable to load aux data from result "+load);
          return FINISH;
        }
        waux = entry.substring(0, index);
        raux = entry.substring(index+1);
      }

      MR.put("AUX.READ", raux);
      MR.put("AUX.WRITE", waux);
    }
    else if ((waux.length()==0 && raux.length()==0 && np<=2) || list) { // display the auxes
      nxm.sys.lib.Table auxTable = MR.getTable("AUX");

      waux = auxTable.getS("WRITE");
      raux = auxTable.getS("READ");

      if (waux.startsWith("-")) {
        M.type("Write AUX     = "+waux+" (all output files will be written to "+waux+")");
      }
      else {
        M.type("Write AUX     = "+waux+" (overwrite existing files, where applicable)");
      }
      M.type("Read AUX List = "+raux);
      M.type("");
      String[] keys = auxTable.getKeys();
      for (String key : keys) {
        if (!isSpecialName(key)) {
          String path = M.io.getAuxPath(key,list);

          if (list) {
            String alias = XmUtil.getAuxAlias(key);
            if (alias == null) alias = "n/a";
            M.type(StringUtil.padRight(key,13)+" = "
                  +StringUtil.padRight(path,50)
                  +" <XmAlias="+alias+">");
          }
          else {
            String type = IOResource.getTypeString(M,path);
            M.type(StringUtil.padRight(key,13)+" = "
                  +StringUtil.padRight(path,50)
                  +" <Type="+type+">");
          }
        }
      }
      checkAuxTable(auxTable);
    }
    else {                                                              // set the read/write aux
      nxm.sys.lib.Table auxTable = MR.getTable("AUX");
      boolean ok = true;

      if (waux.length() > 0) {                                          // (1) set the write aux
        waux = XmUtil.checkAuxName(waux);                               //     Resolve X-Midas alias (e.g. 0->CWD)
        ok = checkAux(waux,true);
        MR.put("AUX.WRITE",waux);
      }
      if (raux.length() > 0) {                                          // (2) set the read aux
        raux = XmUtil.checkAuxName(raux);                               //     Resolve X-Midas alias (e.g. 0->CWD)
        raux = setAux(raux, MR.getS("AUX.READ"), true);                 //     Special handling for first entry
        for (int i=3; i<=np; i++) {
          String aux = XmUtil.checkAuxName(MA.getU(i));                 //     Resolve X-Midas alias (e.g. 0->CWD)
          raux = setAux(aux, raux, false);
        }
        MR.put("AUX.READ",raux);
      } else {
        raux = MR.getS("AUX.READ"); // required for check to see if waux is on raux
      }
      if (waux.startsWith("-")) {
        waux = waux.substring(1);
      }
      if (warn && ok && (waux.length() > 0)  && !findAux(waux, raux)) {
        // JC4: Added this warning in 2.5.0, many new users make the mistake of not including WRITE
        //      AUX on the READ path and then wonder why they can't find their files.
        M.warning("Write AUX '"+waux+"' not present on AUX.READ=\""+raux+"\", this may prevent "
                + "reading from newly-written files.");
      }
      checkAuxTable(auxTable);
    }

    return FINISH;
  }

  /** Sets an AUX, allows the +/- syntax.
      @param arg     The argument to add/set/remove.
      @param raux    The current read aux.
      @param isFirst Is it the first one that can set the entire AUX list.
   */
  private String setAux (String arg, String raux, boolean isFirst) {
    int index = arg.lastIndexOf('|');

    if (index >= 0) {
      raux    = setAux(arg.substring(0,index), raux, isFirst);
      arg     = arg.substring(index+1);
      isFirst = false;
    }
    if (arg.length() > 0) {
      boolean rem = arg.startsWith("-"); // Remove?
      boolean app = arg.startsWith("+"); // Append?
      boolean pre = arg.endsWith("+");   // Prepend?

           if (rem) arg = arg.substring(1);
      else if (app) arg = arg.substring(1);
      else if (pre) arg = arg.substring(0,arg.length()-1);

      if (arg.equals("ALL")) {
             if (rem)     { raux = setAux(allAux("-",""), raux, isFirst); }
        else if (app)     { raux = setAux(allAux("+",""), raux, isFirst); }
        else if (pre)     { raux = setAux(allAux("","+"), raux, isFirst); }
        else              { raux = setAux(allAux("",""),  raux, isFirst); }
      }
      else {
             if (rem)     {                raux = removeAux(arg,raux);    }
        else if (app)     { checkAux(arg); raux = appendAux(arg,raux);    }
        else if (pre)     { checkAux(arg); raux = prependAux(arg,raux);   }
        else if (isFirst) { checkAux(arg); raux = arg;                    }
        else              { checkAux(arg); raux = appendAux(arg,raux);    }
      }
    }
    return raux;
  }

  private boolean findAux (String aux, String list) {
    String mlist = "|"+list+"|";
    return (mlist.indexOf("|"+aux+"|") >= 0);
  }

  private String removeAux (String aux,String list) {
    String mlist = "|"+list+"|";
    mlist = mlist.replaceAll("[|]"+aux+"[|]", "|"); // Deletes all entries "|<aux>|"
    return mlist.substring(1, mlist.length()-1);
  }

  private String appendAux (String aux,String list) {
    return (list.length()==0)? aux : removeAux(aux,list)+"|"+aux;
  }

  private String prependAux (String aux,String list) {
    return (list.length()==0)? aux : aux+"|"+removeAux(aux,list);
  }

  /** Checks to see if an AUX name is valid (i.e. in the AUX table). This does NOT
      check to see of a path is valid, at this time.
      @return true if the aux is ok, false if there is an error/warning
      @throws MidasException if an error is present
   */
  private boolean checkAux (String aux) {
    return checkAux(aux,false);
  }

  /** Checks to see if an AUX name is valid (i.e. in the AUX table). This does NOT
      check to see of a path is valid, at this time.
      @return true if the aux is ok, false if there is an error/warning
      @throws MidasException if an error is present
   */
  private boolean checkAux (String aux, boolean write) {
    boolean ok = false;

    if (aux.length() == 0) {
      // ignore blank entry
    }
    else if (aux.equals("READ")  || aux.equals("WRITE") || aux.equals("ALL")) {
      M.error("AUX table entry "+aux+" is reserved for internal use, given \""+aux+"\".");
    }
    else if (!write && aux.indexOf('-') >= 0) { // duplicate +/- in READ
      M.error("Illegal character '-' in AUX table entry, given \""+aux+"\".");
    }
    else if (aux.indexOf('+') >= 0) { // found in WRITE, or duplicate +/- in READ
      M.error("Illegal character '+' in AUX table entry, given \""+aux+"\".");
    }
    else if (aux.indexOf('|') >= 0) { // can only happen with WRITE aux, READ splits on '|' in setAux(..)
      M.error("Illegal character '|' in AUX table entry, given \""+aux+"\".");
    }
    else if (warn) {
      if (aux.startsWith("-")) {
        aux = aux.substring(1);
      }
      if (M.io.getAuxPath(aux) == null) {
        if (write) {
          M.warning("Unknown AUX table entry "+aux+" used for WRITE AUX, this may cause errors when "
                + "writing files. Please add AUX table entry for "+aux+" or change WRITE AUX.");
        } else {
          M.warning("Unknown AUX table entry "+aux+" added to READ AUX, this may cause errors when "
                + "reading files. Please add AUX table entry for "+aux+" or change READ AUX.");
        }
      }
    }
    else {
      ok = true;
    }

    return ok;
  }

  /** Checks to see if the names in the AUX table are valid names.
      @return true if all entries are ok, false if there is an error/warning
      @throws MidasException if an error is present
   */
  private boolean checkAuxTable (Keyable tbl) {
    boolean ok = true;
    String[] keys = tbl.getKeys();

    for (String aux: keys) {
      if (isSpecialName(aux)) continue; // ignore special AUX entries

      if (!AUX_REGEX.matcher(name).matches()) {
        ok = false;
        M.warning("Illegal AUX name 'AUX."+name+"'. AUX names can only contain letters (A-Z), "
                + "numbers (0-9), and the underscore (_).");
      }
      else if (name.equals("READ") || name.equals("WRITE") || name.equals("ALL")) {
        ok = false;
        M.warning("Can not use AUX."+name+" in NeXtMidas. The names 'READ', 'WRITE', and 'ALL' "
                + "are reserved for use by the AUXILIARY command.");
      }
    }
    return ok;
  }

  /** Indicates if a given AUX name is used for a special internal-use entry in the AUX table. */
  private static final boolean isSpecialName (String name) {
    return name.equals("READ")  ||
           name.equals("WRITE") ||
           (name.startsWith("_") && name.endsWith("_"));
  }

  private String allAux (String prefix, String suffix) {
    StringBuilder list = new StringBuilder();
    nxm.sys.lib.Table auxes = MR.getTable("AUX");
    for (String key : auxes.getKeys()) {
           if (isSpecialName(key)) ; // Exclude READ, WRITE, _<name>_
      else if (key.equals("CWD"))  ; // exclude CWD
      else if (key.equals("DAT"))  ; // exclude DAT
      else if (key.equals("HOME")) ; // exclude HOME
      else if (key.equals("ALL"))  ; // exclude ALL <-- JC4: Prevents recursive definitions.
//    else if (!(ti.value instanceof String)); // prior to 3.1.0 (changed to below line)
      else if (auxes.getString(key) == null) ;   // exclude if NOT a string <-- JC4: Why is this here, what if it was a FileName?
      else list.append(prefix).append(key).append(suffix).append('|');
    }
    list.append(prefix).append("DAT").append(suffix);
    return list.toString();
  }

}
