package nxm.sys.lib;

import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Properties;
import java.util.logging.Level;

import nxm.sys.inc.CommandParent;
import nxm.sys.inc.Commandable;
import nxm.sys.inc.Debug;
import nxm.sys.inc.IDable;
import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.KeyObjectNames;
import nxm.sys.inc.MessageHandler;
import nxm.sys.inc.MidasReference;
import nxm.sys.inc.Verify;

/**
  Base class for all commands. <br>
  <br>
  It contains methods and fields for initializing and maintaining the command
  environment.  This class currently contains the default sequencer for the
  open/process/close methods.  These three methods should be over-ridden by
  the subclasses.
  <br>
  NOTE: any public fields should be considered for <b>read-only use</b> by callers.
  Changing any of the public fields are only for internal-use only within the baseline.
  @author Jeff Schoen
*/
public class Command extends Object implements Commandable, Runnable, MessageHandler, IDable, MidasReference {

  /** global flags mask */
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  public static String flagsList = "Open,Process,Close,Checkin,Checkout,ProcessException,ProcessMessage,RunEnter,RunSingle,RunExit";

  /** This is the command parent to use.
      If this is <code>null</code> (normal operation) then just perform the default action.
      @since NeXtMidas 2.1.0
   */
  private CommandParent cmdParent = null;

  /** Midas context. Please use the {@link #getContext()} method to retrieve
      this parameter instead of directly accessing it.
      @see #getContext()
   */
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  @KeyObjectNames() // hide to disambiguate with the "M" acronym of {@link Macro#getMidas()}
  public Midas M;

  /** The "external" message queue. i.e. to send messages to any target destination.
      @see #queue
      @see #getMQ()
   */
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  public MessageQueue MQ;

  /** The "internal" message queue reference for class use (i.e. this Command's message queue)
      @see #MQ
   */
  @InternalUseOnly("since 3.5.4, this has always been meant for internal use only")
  public MessageQueue queue; // TODO: deprecate use MQ instead? original use case was in macro before Object supported invoking getQueue() for this.queue?

  /** Command line arguments.
      @see #args
   */
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  public Args MA;

  /** Command line arguments reference for class use.
      @see #MA
   */
  @InternalUseOnly("since 3.5.4, this has always been meant for internal use only")
  public Args args; // TODO: deprecate, use MA instead? original use use case was in macro before KeyObject supported invoking getArgs() for this.args?

  /** The results table. */
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  public Results MR;

  /** The user I/O terminal. */
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  public Terminal MT;

  /** Data Operations handle.
   * @since NeXtMidas 3.9.2 this has been changed to private. If you use it, please let the nextmidas team know.
   * */
  @InternalUseOnly("since 3.5.4, this has always been meant for internal use only")
  private DataOp MD;

  /** The execution thread. */
  @InternalUseOnly("since 3.5.4, this has always been meant for internal use only")
  public Thread thread;

  /** The current execution state. */
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  public int state;

  /** The current command state. */
  private int setState;

  /** The command name. */ // TODO deprecate? use getName() instead
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  public String name;

  /** The command support. */
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  public char support;

  /** The command option tree. */
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  public String option;

  /** The process engine to run on */
  private Object engine;

  /** The command registry ID. */
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  public String id;

  /** Indicates if verbose output should be emitted. */
  protected boolean verbose; // 2011-06-01 changed to protected in 2.9.2

  /** Indicates if this is running as a pipe. */
  @InternalUseOnly("since 3.5.4, this has always been meant for read-only use by callers")
  public boolean isPiped=false;

  /** Any command properties. */
  private Properties properties=null;

  /** The delay to use for the poll loop. */
  protected double pollTime=0.0125;

  /** The message handler to use. */
  private MessageHandler msgHandler = null;

  /** The progress meter handles to use. */
  private DataFile progressDF = null;
  private String progressText = null;
  private double progressMax = 1;
  // Changed to Object vs GProgress to break the libg dependency. Reflection is used to call and GProgress
  // methods if the libg package is available. Since 3.9.2
  private Object gpw;
  private String gpr;

  private static final Class<?> GPROGRESS_CLASS;
  private static final Constructor<?> GPROGRESS_CONST;
  private static final Method GPROGRESS_SET_LABEL;
  private static final Method GPROGRESS_SET_PROGRESS;
  private static final Method GPROGRESS_CLOSE;

  /** is this a command issued from the nM prompt */
  private boolean nmPromptCmd;

  // This static block will load up the GProgress class and methods needed to add a progress bar.
  static {
    Class<?> gprogressClass;
    Class<?> gwidgetClass;
    Constructor<?> cons;
    Method setLabel;
    Method setProgress;
    Method close;
    try {
      gprogressClass = Class.forName("nxm.sys.libg.GProgress");
      gwidgetClass = Class.forName("nxm.sys.libg.GWidget");
      cons = gprogressClass.getConstructor(Object.class,String.class,String.class,String.class,int.class,int.class,int.class,MessageHandler.class);
      setLabel = gwidgetClass.getDeclaredMethod("setLabel", String.class);
      setProgress = gprogressClass.getDeclaredMethod("setProgress", double.class);
      close = gwidgetClass.getDeclaredMethod("close");
    }
    catch (Exception e) {
      gprogressClass=null;
      cons=null;
      setLabel=null;
      setProgress=null;
      close=null;
    }
    GPROGRESS_CLASS = gprogressClass;
    GPROGRESS_CONST = cons;
    GPROGRESS_SET_LABEL = setLabel;
    GPROGRESS_SET_PROGRESS = setProgress;
    GPROGRESS_CLOSE = close;
  }

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

  /** Attaches any command-line arguments to this command.
      @param args The command line arguments.
   */
  public void setArgs (Args args) {
    if (cmdParent != null) {
      // Preprocess the args if a command parent is available.
      args = cmdParent.setArgs(this, args);
    }
    args.cmd = this;
    MA = this.args = args;
    name = args.name;
    option = args.option;
    support = args.support;
    id = args.getU("/ID");
    if (id.indexOf('^')>=0) id=null;
  }

  /** Modifies one of the command-line arguments. This can be done just prior to a restart to alter
      the command-line arguments as seen by the command when it re-enters the {@link #open()}
      method following a restart. From a Macro this can be used as follows:
      <pre>
        myprimitive/id=myid IN=_mypipe FS=1k   ! This is inside the pipe on... pipe off

        set reg.myid.arg("IN") _pipe2
        set reg.myid.arg("FS") 1K
        set reg.myid.state RESTART
      </pre>
      @param key   The tag name of the argument.
      @param value The new value for the argument.
   */
  public void setArg (String key, String value) {
    MA.put(key, value);
  }

  /** Initialize the command context.
      @param M The Midas context to use.
      @return This returns {@link #INIT} by default.
      @see #checkout()
   */
  @SuppressWarnings("deprecation") // for M.terminal - since 3.7.1 for NXM-2813
  public synchronized int checkin (Midas M) {
    this.M = M;
    MR = M.results;
    MT = M.terminal;
    MD = new DataOp();
    MQ = queue = getQueue();
    verbose = MA.getState("/VERBOSE",(M.macro==null));
    pollTime = args.getD("/POLL",M.pause);
    return INIT;
  }

  /** Initialize any switches. This must always called before open(). */
  private void initSwitches () {
    // Note that /ID, /POLL, and /VERBOSE are not here for historical reasons. Also,
    // /BG is handled in Command.java as part of lunching the command.
    setMsgID(MA.getU("/MSGID"), true);
  }

  /** Initialize any switches. This must always called after open(). */
  private void openSwitches () {
    if (MA.find("/GPW")) { // graphical progress widget
      if (progressText == null) progressText = MA.getRaw();
      // Changed here to use reflection in order to break dependency on libg. This class can now be run without libg. since 3.9.2
      if (GPROGRESS_CLASS != null) {
        try {
          gpw = GPROGRESS_CONST.newInstance(this, "Progress", "Run,Pause,Abort", progressText, 0, 0, 0, this);
          GPROGRESS_SET_LABEL.invoke(gpw, "PROGRESS");
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
            | InvocationTargetException e) {
          LogUtilities.log(Level.WARNING, "Error instantiating GProgress", e);
        }
      }
    }
    gpr = MA.getU("/GPR", null); // graphical progress result
  }

  /** Free up any resources.
      @see #checkin(Midas)
   */
  public synchronized void checkout () {
    M  = null;
    MR = null;
    MT = null;
    MQ = null;
    MD = null;
  }

  /** Return reference to the Midas context of this command.
   *  @return The Midas context of this command
   */
  public final Midas getContext() { return M; }

  /** Get a message queue that can be used for this command. */
  protected MessageQueue getQueue() { return new MessageQueue (M,this); }

  public int open ()    { return NORMAL; }
  public int process () { return NOOP;   }
  public int close ()   { return NORMAL; }

  /** Sets the message handler to use.
      @param mh The message handler to use.
      @since NeXtMidas 2.7.1
      @see #setMsgID(String)
   */
  public synchronized void setMessageHandler (MessageHandler mh) {
    msgHandler = mh;
    MA.remove("/MSGID"); // Keep this MessageHandler following a restart.
  }

  /** Gets the message handler being used.
      @return The message handler to use.
      @since NeXtMidas 2.7.1
      @see #getMsgID()
   */
  public MessageHandler getMessageHandler () {
    return msgHandler;
  }

  /** Set if this command is coming from the nM prompt
   *  @param nmPromptCmd true if command came directly from the nM prompt
   */
  public void setNmPromptCmd (boolean nmPromptCmd) {
    this.nmPromptCmd = nmPromptCmd;
  }

  /** Is this command one that was issued from the nM prompt
   *  @return true if is nM prompt command
   */
  public boolean isNmPromptCmd () {
    return this.nmPromptCmd;
  }

  /** Sets the message handler to use.
      @param regName The registry name of the {@link MessageHandler} to send messages to.
      @since NeXtMidas 2.7.1
      @see #setMessageHandler(MessageHandler)
   */
  public void setMsgID (String regName) {
    setMsgID(regName, false);
  }

  /** <b>Internal use only:</b> Actual set for /MSGID= */
  private synchronized void setMsgID (String regName, boolean init) {
    if (StringUtil.isNull(regName) || (regName.length() == 0)) {
      msgHandler = null;
      MA.remove("/MSGID");
    }
    else {
      msgHandler = M.registry.getMessageHandler(regName, 5);
      if (msgHandler != null) {
        setArg("/MSGID", regName); // Save for use following a restart.
      }
      else if (init) {
        // JC4: Use warning rather than error to be backwards-compatible with 2.7.0
        M.warning("Can not use /MSGID='"+regName+"', REG."+regName+" is not a Command or an "
                + "instance of MessageHandler.");
      }
      else {
        M.error("Can not use setMsgID('"+regName+"'), REG."+regName+" is not a Command or an "
              + "instance of MessageHandler.");
      }
    }
  }

  /** Gets the registry name (i.e. ID) of the message handler being used.
      @return The registry name of the {@link MessageHandler} to send messages to. Returns null if
              there is no message handler or "&lt;unknown&gt;" if there is a message handler but the
              corresponding registry name is unknown.
      @since NeXtMidas 2.7.1
      @see #getMessageHandler()
   */
  public synchronized String getMsgID () {
    // String regName = MA.getS("/MSGID"); // prior to 3.3.1
    String regName = MA.getU("/MSGID"); // since 3.3.1: to be consistent with initSwitches()
    return ((regName == null) && (msgHandler != null))? "<unknown>": regName;
  }

  /** Sends a message to the message handler. This is a convenience method that constructs a message
      and passes it to {@link #sendMessage(Message)}.
      @param name The name of the Message.
      @param info The value of the info field.
      @param data The value of the data field.
      @since NeXtMidas 2.7.1
   */
  protected void sendMessage (String name, int info, Object data) {
    Message msg = new Message(name, info, data);
    sendMessage(msg);
  }

  /** Sends a message to the message handler. Note that this does nothing if {@link #getMessageHandler()}
      is null or if <code>msg.from</code> is the same as {@link #getMessageHandler()}. This automatically
      sets the following before sending the message:
      <pre>
        msg.to   = {@link #getMessageHandler()};
        msg.from = this;
      </pre>
      @param msg The message to send.
      @since NeXtMidas 2.7.1
   */
  protected void sendMessage (Message msg) {
    MessageHandler mh = getMessageHandler(); // use a pointer to avoid synchronization issues
    if ((mh != null) && (mh != msg.from)) {
      msg.to   = mh;
      msg.from = this;
      mh.processMessage(msg);
    }
  }

  /** Processes a message.
      @param msg The message.
      @return {@link #NORMAL} after processing the message or {@link #NOOP} if
              the message is not processed.
      @see #processMessage(String,int,Object)
   */
  public synchronized int processMessage (Message msg) {
    Table t; String key;
    if (msg.name.equals("SET")) {
      t = (Table)msg.data;
      for (Table.Iterator ti=t.iterator(); ti.getNext();) {
        KeyObject.setKey(this,ti.key,ti.value);
      }
    }
    else if (msg.name.equals("GET")) {
      t = (Table)msg.data;
      for (Table.Iterator ti=t.iterator(); ti.getNext();) {
        t.put(ti.key,KeyObject.getKey(this,ti.key));
      }
      msg.name = "RET";
      MQ.put (msg,msg.from);
    }
    else if (msg.name.startsWith("SET.")) {
      key = msg.name.substring(4);
      KeyObject.setKey (this,key,msg.data);
    }
    else if (msg.name.startsWith("GET.")) {
      key = msg.name.substring(4);
      msg.name = "RET."+key;
      msg.data = KeyObject.getKey (this,key);
      MQ.put (msg,msg.from);
    }
    else if (msg.name.equals("PROGRESS")) {
      String text = (String)msg.data;
           if (text.equals("RUN"))   setState(RESUME);
      else if (text.equals("PAUSE")) setState(PAUSE);
      else if (text.equals("ABORT")) setState(FINISH);
      else if (text.equals("EXIT"))  setState(FINISH);
      return NORMAL;
    }
    else if (msg.name.equals("NULL")) { // NULL is typically sent when SENDW is used
      return NORMAL;
    }
    else if (msg.name.equals("EXIT")) {
      setState(FINISH);
    }
    else return NOOP;
    return NORMAL;
  }

  /** Forms and processes a message. This is the same as using:
      <pre>processMessage(new Message(name,info,data))</pre>
      @param name The name of the message.
      @param info Any flags indicating the message's information.
      @param data Any associated data to accompany the message.
      @return {@link #NORMAL} after processing the message or {@link #NOOP} if
              the message is not processed.
      @see #processMessage(Message)
   */
  public int processMessage (String name, int info, Object data) {
    return processMessage ( new Message(name,info,data) );
  }

  /** Processes messages from the message queue. The only methods external to {@link Command} that
      should ever call this are those that implement {@link CommandParent}.
      @param maxmsg The maximum number of messages to process from the queue or -1 to process all
                    available messages.
      @return The status returned from processing the last message with
              {@link #processMessage(Message)} or {@link #NOOP} if no messages are available.
      @see #processMessage(Message)
      @since NeXtMidas 2.1.0
   */
  public int processMessages (int maxmsg) {
    Message msg;
    int status = NOOP;
    if (queue == null) return status;
    while ((maxmsg-- != 0) && ((msg=queue.get()) != null)) {
      try {
        status = processMessage(msg);
      } catch (Exception e) {
         processMessageException(msg, e);
      }
    }
    return status;
  }

  /** Handles exceptions in processMessage(..). Unlike processException(..) this does not abort
      the primitive. It merely reports the bad message (which, hopefully, is an outlier) and
      continues on.
   */
  private void processMessageException (Message m, Exception e) {
    if ((M.macro!=null) && !(m instanceof ErrorMessage)) {
      Message msg = new ErrorMessage("ERROR",0,e,M.macro,this);
      M.macro.processMessage(msg);
    }
    else {
      M.printStackTrace("Error processing message "+m, e);
    }
  }

  /** This will process a exception caught by the command. If this command is
      running in a macro (i.e. <code>this.M.macro&nbsp;!=&nbsp;null</code>) then
      just pass it as a message to the macro using:
      <pre>
        this.M.macro.processMessage(new Message("ERROR",0,e,this.M.macro,this))
      </pre>
      If this is not running in a macro then the exception is output to the user
      (a stack trace will be printed to {@link System#err} if
      <code>this.M.debug</code> includes the {@link Debug#TRACE} flag).

      @param e The exception to process.
*/
  public void processException (Exception e) {
    processException((Throwable) e);
  }

  /** This will process any throwable caught by the command. If this command is
      running in a macro (i.e. <code>this.M.macro&nbsp;!=&nbsp;null</code>) then
      just pass it as a message to the macro using:
      <pre>
        this.M.macro.processMessage(new Message("ERROR",0,e,this.M.macro,this))
      </pre>
      If this is not running in a macro then the exception is output to the user
      (a stack trace will be printed to {@link System#err} if
      <code>this.M.debug</code> includes the {@link Debug#TRACE} flag).

      @param e The exception to process.
      @since NeXtMidas 3.5.0
   */
  void processException (Throwable e) {
    if (M != null && M.macro != null) {      // 1. let the macro decide
      Message msg = new ErrorMessage("ERROR",0,e,M.macro,this);
      M.macro.processMessage(msg);
    } else if (M != null) {                  // 2. use Midas context to print stack trace
      M.printStackTrace(e);
    } else {                                 // 3. else just print out stack trace
      e.printStackTrace();
    }
    // clean up the command
    try {
      state=ABORT; close(); state=EXIT;
    } catch (Exception ec) {
      // ignore
    }
  }

  public void run() {
    runEnter();
    try {  // process the command
      while (state!=DONE && state!=EXIT) {
        int status = (cmdParent==null)? runSingle() : cmdParent.runSingle (this,state,setState);
        if (status==NOOP) Time.sleep (pollTime);
        else if (status==SLEEP) Time.sleep(0.1);
      }
    } catch (Exception e) {
      processException(e);
    // BEGIN BUG 2817 CHANGES
    // Here we want to specifically catch the NoClassDefFoundError thrown when an invalid display
    // environment variable is set. Specifically, we want to catch:
    //                   Could not initialize class sun.awt.X11.XToolKit
    //               OR
    //                   Could not initialize class sun.awt.X11GraphicsEnvironment
    // Any other instances of NoClassDefFoundError we want to re-throw.
//    } catch (NoClassDefFoundError err) {
//      if (err.getMessage().startsWith("Could not initialize class sun.awt.X11")) {
//        processException(err);
//      } else {
//        throw err;
//      }
    }
    // END BUG 2817 CHANGES    ADW: This section was removed with an update to the DR from the user.
    runExit();
    checkout();
  }

  /** Performs any tasks associated with entering the {@link #run()} method.
      This should always be called at the beginning of the {@link #run()} method.
      @see #run()
      @see #runExit()
   */
  protected void runEnter() {
    if (cmdParent != null) cmdParent.runEnter(this);
    if (M.timer>0) M.timer = System.currentTimeMillis();
    if (M.verify!=0) verify();
    state=INIT;
    if (!putInRegistry()) return;  // bail here if intrinsic, foreign
    if (id==null) id = MA.getU("/ID");
    if (id.length()>0) {
      Object old = M.getRegistry().get(id);
      if (old != null) {
        // JC4: We will change the following to an error after two stable releases.
        M.warning("Duplicate entries for /ID="+id+" found in registry; overriding "+old+" with "+this+". "
                + "This behavior is deprecated as of NeXtMidas 2.5.0 and will be an ERROR in future releases.");
      }
      M.getRegistry().put(id,this);
    } else {
      id = M.getRegistry().putInstance(name,this);
    }
    // handle initializations
    if (M.macro!=null && M.macro.isTxt()) M.macro.prepTxtCommand(this);
    setState = MA.getSelectionIndex("/STATE",stateList,START,stateListOffset);
  }

  /** Performs any tasks associated with maintaining the {@link #run()} method. This should be
      called repeatedly after runEnter until generating an exit status.
      @return The updated status.
      @see #run()
   */
  @SuppressWarnings("deprecation") // for M.terminal - since 3.7.1 for NXM-2813
  public int runSingle() {
    int status = NOOP;

    // CHECK PIPE MODE
    if (isPiped) {
           if (M.pipeMode==Midas.PRUN) { } // no change
      else if (M.pipeMode==Midas.PINIT) status = SLEEP;
      else if (M.pipeMode==Midas.PSTOP) status = FINISH;
      else if (M.pipeMode==Midas.POFF)  status = FINISH;
    }

    // INITIAL PROCESSING (check setState, process messages, then call process())
    if (setState!=0) {
      if (cmdParent != null) cmdParent.processMessages (this, -1);
      else processMessages(-1);     // dont leave old stuff on queue
      status=setState; setState=0;  // use status to cause setState() change
    }
    else if (status!=NOOP) { }
    else if (state==PROCESS) {
      if (cmdParent != null) status = cmdParent.processMessages (this, 1);
      else if (queue!=null) status = processMessages(1);
      if (status!=NOOP) { }
      else if (M.pipeMode==Midas.PPAUSE) { }
      else if (engine==null) status = process();
      else {
        int maxloop = processReady();
        for (int i=0; i<maxloop; i++) {
          status = process();
          if (status!=NORMAL) break;
        }
      }
          if (gpw != null) {
            try {
              // Changed here to use reflection in order to break dependency on libg. This class can now be run without libg. since 3.9.2
              GPROGRESS_SET_PROGRESS.invoke(gpw, getProgress());
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
              LogUtilities.log(Level.WARNING, "Caught an exception setting the progress", e);
            }
          }
      if (gpr!=null) MR.put(gpr,getProgress());
    }
    else if (state==SUSPEND) status = SLEEP;
    else if (state==INIT) status = SLEEP;

    //////////// BEGIN BUG 2150 CHANGES //////////////////////////
    //if (M.pipeMode==Midas.POFF && M.terminal.getInterrupt()!=0) status = FINISH;
    if (finishing()) status = FINISH;
    if (getInterrupt()) {
      int interruptCount = M.terminal.getInterrupt();
      //System.out.println("Command.runSingle() state="+getState()+" isPiped="+isPiped+" pipemode="+M.pipeMode);
      if (M.macro != null) {
        int pm = M.pipeMode;
        if  (pm==Midas.PINIT || pm==Midas.PRUN || pm==Midas.PPAUSE || pm==Midas.PWAIT) {
          M.macro.setPipeMode(Midas.PSTOP);
          M.pipeMode = Midas.PSTOP;
        }
        else { status = FINISH; }
      }
      else {
        // Since 3.7.1 for NXM-2626 If panel is not run in the background then finish.
        if (!args.cmd.args.getState("/BG")) {
          M.terminal.setInterrupt(0);
          status = FINISH;
        }
      }
      // 2011-03-09 NTN: short pause when interrupted to allow user to double ctrl+c out of piped sub-macros in sequence
      if (interruptCount > 0) { Time.sleep(0.25); }
    }
    //////////// END BUG 2150 CHANGES ////////////////////////////

    // FINAL PROCESSING (return status from process() controls what to do next)
         if (status==NORMAL ) { } // READY FOR NEXT PASS
    else if (status==NOOP   ) { }
    else if (status==INIT   ) state=INIT;
    else if (status==SUSPEND) state=SUSPEND;
    else if (status==PAUSE  ) state=SUSPEND;
    else if (status==RESUME ) state=PROCESS;
    else if (status==ABORT  ) {
      state=EXIT;
    }
    else if (status==RESTART) {
      state=RESTART; status=restart();
      if (status==FINISH) setState=FINISH;
      else if (status==ABORT) state=EXIT;
      else state=PROCESS;
    }
    else if (status==START) {
      // clear the message queue in case second pass
      if (queue!=null && isPiped && M.pipeMode==Midas.PWAIT) queue.clear();
      state=OPEN;
      initSwitches();
      status=open();
      openSwitches();
      if (status==FINISH) setState=FINISH;
      else if (status==ABORT) state=EXIT;
      else state=PROCESS;
    }
    else if (status==STOP && state!=INIT) {
      state=RESTART; close();
      state=INIT;
    }
    else if (status==FINISH && setState==0) {
      if (state!=INIT) { state=CLOSE; close(); }
      state=DONE;
    }
    return status;
  }

  /** Performs any tasks associated with exiting the {@link #run()} method.
      This should always be called at the end of the {@link #run()}
      method (usually just before a call to {@link #checkout()}.
      @see #run()
      @see #runEnter()
      @see #checkout()
   */
  protected void runExit () {
    if ((M.verify&Verify.BEEP)!=0) Shell.beep();
    if (M.timer>0) {
      double time = 1.e-3 * (double)(System.currentTimeMillis()-M.timer);
      M.println("Command took "+time+" seconds");  // since 3.7.1 using M.print to allow logging of interactive commands NXM-2813
    }
    if (putInRegistry()) M.registry.remove (id); // remove from registry
    if (cmdParent != null) cmdParent.runExit(this);
    if (gpw != null) {
      try {
        // Changed here to use reflection in order to break dependency on libg. This class can now be run without libg. since 3.9.2
        GPROGRESS_CLOSE.invoke(gpw);
      } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
        LogUtilities.log(Level.WARNING, "Caught an exception closing GProgress", e);

      }
    }
  }


  /** Print the parsed command line (if enabled). */
  public void verify () {
    if (M.verify==0) return;
    if (M.pipeMode==Midas.PWAIT) {
     if ((M.verify&Verify.CONTROLS)==0) return;
    } else {
     if ((M.verify&Verify.COMMANDS)==0) return;
    }
    M.println( MA.toString() );  // since 3.7.1 using M.print to allow logging of interactive commands NXM-2813
  }

  /** This will convert a state number to its string representation.
      @param state The state identifier.
      @see #stateList
      @return the String representation of the input state number
   */
  public static String stateToString (int state) {
    return Parser.get(stateList,state,stateListOffset);
  }

  /** Sets the command state. A warning will be issued if attempting to set the
      state to a state that would be prior to {@link #START}.
      @param value This should be one of the Command Sequence Directives.
      @see #stateList
   */
  public void setState (int value) {
    if (value>=START) {
      setState = value;
    } else {
      M.warning("Cannot set state to "+stateToString(value));
    }
  }

  /** Sets the command state by name.
      @param value The new state (see {@link #stateList}).
   */
  public void setState (String value) {
    setState( Parser.find(stateList,value,0,stateListOffset) );
  }

  /** Indicates if the state of the command was changed while inside the current process call.
      This allows the command to check to see if it should abort any (normally) blocking read
      calls, due to a change in the command's state.
      @since NeXtMidas 2.5.0
      @return true if state command was changed, false otherwise.
   */
  public final boolean isStateChanged() {
    return (setState != 0);
  }

  /** This will attempt to restart the command. This should not be called directly; it should only
      be called by the {@link #run()} method. To initiate a restart either set the state to
      {@link #RESTART}.
      @see #close()
      @see #open()
      @return 0 if completed successfully, non-zero if an error occurred.
   */
  public int restart() {
    state = RESTART;
    close();
    initSwitches();
    return open();
  }

  /** Return the parsed command line.
      @return the parsed command line.
   */
  public String toString() { return MA.toString(); }

  /** Test if this command is pipeable.
      @return <code>true</code> if this command supports the pipe mode and
              <code>false</code> otherwise.
   */
  public boolean isPipable() {
    return false;
  }

  /** test whether this method is being run by the Command thread
      @return true if this method is being run by the Command thread, false otherwise.
   */
  public boolean thisIsMe() {
    return ( Thread.currentThread() == thread );
  }

  /** Print the name of this thread to {@link System#out}.
      @param text Any text to prefix the name of the current thread with.
   */
  @InternalUseOnly("since 3.5.4, this has always been meant for internal use only")
  public void whoIsThis (String text) {
    System.out.println(text+" "+Thread.currentThread().getName() );
  }

  /** Set the poll time.
      @param seconds The new poll time (in seconds).
      @see #getPollTime()
   */
  public void setPollTime (double seconds) { pollTime = seconds; }

  /** Get the poll time.
      @return The poll time (in seconds).
      @see #setPollTime(double)
   */
  public double getPollTime() { return pollTime; }

  /** Get the command's arguments.
      Note: This will return {@link #MA} and not {@link #args}.
      @return {@link #MA}
   */
  public Args getArgs() { return MA; }

  /** Get the current command state as a string.
   *  @return the current command state as a String.
   */
  public String getState() {
    return Parser.get(stateList,state,stateListOffset);
  }

  public String getID() { return id; }


  /** Obtains the properties file associated with a Command (Intrinsic or
      Primitive).  The properties file must be located in the option path's
      cfg directory and be named <code>&lt;command_name&gt;.properties</code>.
      Within the command code, call <code>getProperties()</code> to get the
      command's property file. If none exists, the properties object will be empty.

      @return The property file associated with command
              (this will be empty if no properties are found).
   */
  protected Properties getProperties() {
    Properties props = properties;
    if (props == null) props = properties = new Properties();
    try {
      String fname = M.io.name("CFG", name, "PROPERTIES", option);
      if (M.io.exists(fname)) {
        FileInputStream in = new FileInputStream(fname);
        props.load(in);
        in.close();
      }
    } catch (Exception e) {
      if (M != null) M.printStackTrace("Unable to get/load properties file for command", e, false, Midas.WARNING);
    }
    return (Properties) props.clone();
  }

  /** This will get a specific property.
      @param name The name of the property to get.
      @return The value associated with the property named <code><i>name</i></code>.
      @see #getProperties()
   */
  protected Object getProperty (String name) {
    Properties props = properties;
    if (props == null) props = getProperties();
    if (props == null) return null;
    return props.get(name);
  }

  /** Get the command's message queue.
      Note: This will return {@link #MQ} and not {@link #queue}.
      @return {@link #MQ}
   */
  public MessageQueue getMQ() {
    if (queue != MQ) {
      Shell.warning("ERROR: Command.MQ and Command.queue are out of synch.");
    }
    return MQ;
  }

  /** Gets the command parent.
      @return The command parent (<code>null</code> if not applicable).
   */
  public CommandParent getCmdParent() {
    return cmdParent;
  }

  /** Sets the command parent. <br>
      This method should ONLY be called from <code>Shell.loadCommand(..)</code>.
      @param cmdParent The command parent (<code>null</code> if not applicable).
   */
  public void setCmdParent (CommandParent cmdParent) {
    this.cmdParent = cmdParent;
  }

  /** Sets the verbose print output mode.
      @param verbose True or false.
   */
  public void setVerbose (boolean verbose) {
    this.verbose = verbose;
  }

  /** Gets the verbose print output mode.
   *  @return true if {@link #verbose} is set, false otherwise.
   *  @since NeXtMidas 2.7.4
   */
  public boolean getVerbose () { return verbose; }

  /** Gets the command name.
   *  @return {@link #name}
   */
  public String getName() {
    return name;
  }

  /** Sets the command processing engine.
      @param engine The processing engine to use.
      @param thread The thread to use.
   */
  void setCmdProcEngine(Object engine, Thread thread) {
    this.engine = engine;
    this.thread = thread;
  }

  /** Gets the command processing engine (null if none in use). */
  Object getCmdProcEngine() {
    return engine;
  }

  /** Returns whether the command is ready to process data
   *  @return -1
   */
  public int processReady() { return -1; }

  /** This allows us to flag error messages we send to prevent infinite loops. */
  private static final class ErrorMessage extends Message {
    ErrorMessage (String name, int info, Object data, Object to, Object from) {
      super(name,info,data,to,from);
    }
  }

  /** Should this command be inserted into the registry? */
  private boolean putInRegistry () {
    return (support == 'P') || (support == 'H') || (support == 'M');
  }

  /** Indicates the command has been interrupted either by a CTRL+C in the terminal
      or the closing of the graphical window attached to the pipe section
      @since NeXtMidas 2.9.0
      @return true if an interrupt has occurred via CTRL+C or the closing of the graphical window attached to the pipe section.
   */
  @SuppressWarnings("deprecation") // for M.terminal - since 3.7.1 for NXM-2813
  public boolean getInterrupt () {
    return   (M.terminal.getInterrupt() != 0) || (isPiped && (M.pipeMode == Midas.POFF));
  }

  /** Indicates the state or setState of the Command is {@link Commandable#FINISH} or
      {@link Commandable#ABORT} or {@link Commandable#DONE}
      @since NeXtMidas 3.4.0
      @return {@link Commandable#FINISH} OR {@link Commandable#ABORT} OR {@link Commandable#DONE}
   */
  public boolean finishing () {
    // added for Bug 2686 to indicate Command is done and break process loop.
    return   ((setState == FINISH || setState == DONE  || setState == ABORT) ||
             (state     == FINISH || state    == DONE  || state    == ABORT));
  }

  /** Sets the DataFile to monitor for progress
   *  @param df The {@link DataFile} to be used.
      @param text The text to use for the progress meter.
   */
  public void setProgressFeed (DataFile df, String text) {
    progressDF = df;
    progressText = text;
  }
  /** returns the {@code 0.0 -> 1.0} progress based on input file.
   *  @return a double value between 0 and 1 indicating the current progress, or -2 if an error has occurred.
   */
  public double getProgress() {
    DataFile df = progressDF;
    if (df==null) return -2;
    double prog = progressMax * (df.seek()/df.getSize());
    return prog;
  }

  /** add progressive feed
   *  @param df DataFile
   */
  public void addProgressFeed (DataFile df) {
    if (progressDF==null) progressDF = df;
  }

  /** Get Midas
   *  @return Midas
   *  @since NeXtMidas 3.5.1 */
  @Override
  public Midas getMidas () {
    return M;
  }

  /** Get the current command status
   *  @return the current command status as a String.
   *  @see #getState()
   *  @since NeXtMidas 3.7.0
   */
  public String getStatus () {
    return getState();
  }

  /** Gets a list of commands from the command dictionary. <br>
      <br>
      <code>"*"</code>, <code>"ALL"</code> or <code>null</code> may be passed
      in for <code>names</code>, <code>supports</code>, or <code>options</code>
      to indicate that no filtering should be done based on that charactersitic.
      <br>
      <br>
      Additionally <code>names</code> may use the <code>'*'</code> and
      <code>'?'</code> wildcards.

      @param midas       The midas context to use.
      @param names       The names of the commands.
      @param supports    The command support (must be an the allowable support
                         identifier: <code>'P'</code>, <code>'H'</code>, etc.).
      @param options     The option tree.
      @param showOption  Indicates if the option should be shown for options NOT
                         in the <code>SYS</code> tree.
      @param showSupport Indicates if the command's support should be shown.

      @return A list of the commands from the command dictionary that match the
              specified list of commands.
      @since NeXtMidas 3.9.2 moved here
   */
  public static String[] getMenuCommands (Midas midas, String names, String supports,
      String options, boolean showOption, boolean showSupport) {
    final ArrayList<String> commands = new ArrayList<>();

    names = (names == null) ? "*" : names.toUpperCase();
    supports = (supports == null) ? "*" : supports.toUpperCase();
    options = (options == null) ? "*" : options.toUpperCase();

    if (names.equals("ALL")) {
      names = "*";
    }
    if (supports.equals("ALL")) {
      supports = "*";
    }
    if (options.equals("ALL")) {
      options = "*";
    }

    supports = supports.substring(0, 1); /* Translate "PRIM" to "P", etc. */

    for (int i = 0; i < midas.dictionary.getSize(); i++) {
      Parser command = new Parser(midas.dictionary.get(i));
      String name = command.get(1);
      // At this point name looks like...COMMAND;OPT:n,
      int lname = name.indexOf(';');
      int lendName = name.indexOf(':');
      if (lendName < 0) lendName = name.length();
      String option = name.substring(lname + 1, lendName);

      name = name.substring(0, lname);
      String support = command.get(2);

      if (!supports.equals("*") && !StringUtil.containsCommonCharacter(supports, support)) continue;
      if (!options.equals("*") && !options.equals(option)) continue;
      if (!StringUtil.isWildcardMatch(names + "*", name)) continue;

      String cmd = name;
      if (!option.equalsIgnoreCase("SYS") && showOption) {
        cmd += ";" + option;
      }
      if (showSupport) {
        cmd += "-" + support;
      }

      commands.add(cmd);
    }
    return commands.toArray(new String[commands.size()]);
  }


}
