package nxm.sys.intr;

import java.util.regex.Pattern;

import nxm.sys.lib.CommandLine;
import nxm.sys.lib.Intrinsic;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Shell;
import nxm.sys.lib.Table;
import nxm.sys.lib.Terminal;
import nxm.sys.lib.TextFile;

/** Updates the commands.cnf file for a specified option tree.

    @author Jay Hallam / Neon Ngo
*/
public class Update extends Intrinsic {
   // Regular expression for command names (pre-compiled for better performance)
  private static final Pattern CMD_NAME_REGEX = Pattern.compile("[A-Z_][A-Z0-9_]*([*][A-Z0-9_]*)?");

  CommandLine OldCmdLine;  // Command line as it currently appears in dictionary
  CommandLine CurCmdLine;  // New or replacement command line
  String path;
  boolean done;

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

  public int process () {
    done  = false;
    boolean delete      = MA.getState("/DELETE");
    boolean autoUpdate  = MA.getState("/AUTO") || delete;
    String  commandName = MA.getS("NAME");
    String  support     = MA.getS("SUPPORT");
    String  sargs       = MA.getS("NARGS");
    String  opt         = "";

    commandName = getCommandName(commandName);  // check to see if this is an abbreviation

    int iSemi = commandName.indexOf(";");
    if (iSemi >= 0) {
      opt = commandName.substring(iSemi+1);
      commandName = commandName.substring(0, iSemi);
    }
    if (commandName.length() == 0) {
      commandName = promptUser("Command name", "", null);
      if ((commandName == null) || (commandName.length() == 0)) {
        throw new MidasException("UPDATE: No command name specified.");
      }
      commandName = commandName.toUpperCase();
    }

    if (commandName.endsWith(".MM") || commandName.endsWith(".JAVA") || commandName.endsWith(".FOR")) {
      throw new MidasException("UPDATE: Illegal command name '"+commandName+"'. Do not include file "
                             + "extension when calling UPDATE.");
    }
    else if (!CMD_NAME_REGEX.matcher(commandName).matches()) {
      throw new MidasException("UPDATE: Illegal command name '"+commandName+"'. Command names must "
                             + "start with a letter and may only include letters (A-Z) numbers (0-9) "
                             + "and the underscore (_).");
    }

    String savedOpt = opt;
    opt = MA.getS("OPT",opt);
    if (!savedOpt.equals("") && !savedOpt.equalsIgnoreCase(opt)) {
      M.warning("Option '"+savedOpt+"' on command name overridden by OPT="+opt);
    }

    // In auto-update mode?
    path = ""+M.results.get("OPT.PATH");
    if (!autoUpdate) {
      if (opt != null && !opt.equals("")) { //jph
        path = opt;
      }
    }

    OldCmdLine = new CommandLine(commandName, path);

    // Initialize the new command line with the old and set any parameters on command line
    if (OldCmdLine.isInCommandsFile(path)) {
      CurCmdLine = OldCmdLine;
    } else {
      CurCmdLine = new CommandLine(commandName,opt,support,sargs,null); // new command
    }

    // Override any current command line parameters that may have been found in
    // the COMMANDS.CNF with all that are included on this command line
    if (opt != null && !opt.equals("")) { CurCmdLine.setOption (opt); }
    if (MA.isPresent("SUPPORT")) { CurCmdLine.setSupport (support); }
    if (MA.isPresent("NARGS"  )) { CurCmdLine.setNargsString (sargs); }

    // if this command has been modified in the dictionary, use the new # of args
    int entry = M.dictionary.findMatch(commandName);
    if (entry != -1) {
      Table tbl = M.dictionary.getEntry(entry);
      if (tbl != null && tbl.containsKey("NARGS"))
        CurCmdLine.setNargsString(tbl.getS("NARGS"));
    }
    // Set the default argument values, if they are present
    int nargs = CurCmdLine.getNargs();
    String defs  = ",";
    boolean anyDefaultsPresent = false;
    for (int i = 1; i <= nargs; i++) {
      String defStr = MA.getS("DEF"+i);
      if ( defStr != null && defStr != "") {
        anyDefaultsPresent = true;
        defs = defs.concat(defStr+",");
      } else {
        defs = defs.concat(CurCmdLine.getDefault(i)+",");
      }
    }
    if (anyDefaultsPresent) {
      CurCmdLine.setDefaults(defs);
    }

    // Prompt if we are not in auto-update mode
    if (!autoUpdate && !done) {
      prompt(commandName);
    }

    if (!done) {
      updateCommand(delete);
      Shell.runCommand(M,"PATH,REDO");
    }

    return FINISH;
  }

  /** Gets the full name if an abbreviation of an existing dictionary
      command is used, otherwise returns the input
      @since NeXtMidas 2.7.3
   */
  private String getCommandName (String cmdName) {
    Parser p;
    p = M.dictionary.findParser(cmdName);
    if (p != null) {
      String tmp = p.get(1);
      int i = tmp.indexOf(';');
      return tmp.substring(0,i);
    }
    return cmdName;  // not an abb,
  }

  /** Prompt for various inputs
   *  @param commandName command name */
  public void prompt (String commandName) {
    // Get the option tree name
    if ( !MA.isPresent ("OPT") ) {
      String opt = CurCmdLine.getOption();
      // Bug 1764: if optname is blank (default to first option in path)
      if (opt.equals("") && path != null) {
         int index = path.indexOf(",");
         if (index != -1) { opt = path.substring(0,index); }
         else             { opt = path; } // only one option tree (SYS) in path
      }
      opt = promptUser("Option tree (USER, SITE, UCL, etc):",
                       opt, "Invalid option tree entered");
      if (opt == null || opt == "") {
        done = true;
      } else {
        CurCmdLine.setOption (opt);
      }
    }
    if (path.toUpperCase().indexOf(CurCmdLine.getOption().toUpperCase()) < 0) {
      M.error(CurCmdLine.getOption()+" is not in path "+path);
    }

    // Get the command abbreviation
    String nameAbbrev = CurCmdLine.getAbbrevName();
    String nameFull = CurCmdLine.getName();
    if (commandName.indexOf("*") <= 0) {
      nameAbbrev = promptUser(nameFull+" Abbreviation:",
                              nameAbbrev, "Invalid name abbreviation entered");
    }
    CurCmdLine.setAbbrevName(nameAbbrev);

    // Get the support
    if ( !MA.isPresent ("SUPPORT") ) {
      String support = CurCmdLine.getSupport();
      if (support == null || support == "") {
        support = CommandLine.DEFAULT_SUPPORT;
      }
      support = promptUser("Support (Any of: "+CommandLine.VALID_SUPPORTS+")",
                           support,"Invalid support entered");
      if (support == null || support == "") {
        done = true;
      } else {
        support = support.trim().toUpperCase();
        if (!CommandLine.isValidSupport(support)) {
          M.error("Invalid support '"+support+"' entered. Valid supports are "
                 +CommandLine.getValidSupports());
        }
        CurCmdLine.setSupport(support);
      }
    }

    if ( !MA.isPresent ("NARGS") ) {
      // Get the number of arguments
      String sargs = CurCmdLine.getNargsString();
      sargs = promptUser("Number of arguments",sargs,"Invalid NARGS entered");
      if (sargs == null || sargs == "") {
        done = true;
      } else {
        CurCmdLine.setNargsString(sargs);
      }
    }

    // Get the default argument values
    int nargs = CurCmdLine.getNargs();
    String defs  = ",";
    for (int i = 1; i <= nargs; i++) {
      String token = CurCmdLine.getDefault(i);
      if ( !MA.isPresent ("DEF"+i) ) {
        if (token == null) { token = ""; }
        token = promptUser("Default (TAG=value or - to remove) "+i,
                           token, "Invalid default value entered");
      }
      if (token.equals("-")) {
        // Remove the TAG= parameter
        defs = defs.concat(",");
      } else {
        defs = defs.concat(token+",");
      }
    }
    CurCmdLine.setDefaults(defs);
  }

  /** Prompt the user for input. */
  @SuppressWarnings("deprecation")  // for M.terminal - since 3.7.1 for NXM-2813
  private String promptUser (String prompt, String def, String warnMsg) {
    if (def != null) {
      prompt += " ["+def+"] -> ";
    }

    String strInput = M.terminal.getInput(prompt,"",Terminal.INPUT_NO_ESC);

    // JWB - BEGIN BUG 1948 FIX (Prevent escape characters and up/down arrows)
    if (strInput.startsWith("!") || strInput.startsWith("[") || strInput.startsWith("^")) {
      Shell.warning("Invalid input. Setting to default, if applicable");
      strInput = "";
    }
    // END BUG 1948 FIX

    if (strInput.length()==0 ) {
      strInput = def;
      if (def == null) { M.warning(warnMsg); }
    }
    return strInput;
  }

  /** Gets the template commands.cnf file. */
  private String[] getTemplate () {
    String   fname  = M.io.name ("CFG","commands","template","SYS");
    TextFile infile = new TextFile(M,fname);
    String[] lines  = null;

    if (infile.open(TextFile.NOABORT)) {
      lines = infile.readAllLines();
    } else {
      lines = new String[0]; // Use blank.
    }
    infile.close();
    return lines;
  }

  /** Update a command in the dictionary */
  private void updateCommand (boolean delete) {
    boolean  updated = false;
    String   fname   = M.io.name ("CFG","commands","cnf",CurCmdLine.getOption());
    TextFile infile  = new TextFile(M,fname);
    TextFile outfile = new TextFile(M,fname);
    String   cmdName = CurCmdLine.getName();
    String[] lines   = null; // All of the lines in the existing file.

    if (!infile.exists()) {
      lines = getTemplate();
    } else {
      infile.open(TextFile.INPUT|TextFile.FORCEABORT);
      lines = infile.readAllLines();
    }
    infile.close();

    outfile.open(TextFile.OUTPUT|TextFile.FORCEABORT);

    for (String line : lines) {
      CommandLine tempCL = new CommandLine(line);
      String curname = tempCL.getName();
      int comparison = curname.compareTo(cmdName);

      if ((comparison >= 0) && !updated) {
        if (delete) {
        } else {
          writeCommand(outfile);
        }
        if (comparison > 0) {
          outfile.writeln(line);
        }
        updated = true;
      } else {
        outfile.writeln(line);
      }
    }
    if (!updated && !delete) {
      writeCommand(outfile); // Add to end of file.
    }
    outfile.close();
  }

  /** Only used by updateCommand(..). */
  private void writeCommand (TextFile outfile) {
    outfile.writeln(CurCmdLine.getCommandLine());
  }
}
