package nxm.sys.lib;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.logging.ErrorManager;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.regex.Pattern;

import nxm.sys.inc.ANSIColor;
import nxm.sys.inc.Debug;
import nxm.sys.inc.IDable;
import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.MidasReference;
import nxm.sys.inc.ProvisionalUseOnly;
import nxm.sys.inc.Verify;
import nxm.sys.lib.CallStackUtil.IgnoreStackElement;

/**
  A class to define the Midas environment.

  The Midas environment for each command is initialized to that of it's
  parent and modified to reflect command specific properties.

  @author Jeff Schoen / Neon Ngo
*/
@IgnoreStackElement
public class Midas implements Cloneable, MidasReference {

  private static final int CONTEXT_PRE  =  4; // Number of source code context lines to include before error
  private static final int CONTEXT_POST =  2; // Number of source code context lines to include after error
  private static final int CODE_LIMIT   = 16; // Max number of code blocks to show

  /** Regular expression matching the simple file name of a .java file. */
  private static final Pattern JAVA_FILE = Pattern.compile("[A-Za-z0-9_$]+[.]java");

  /** Regular expression matching a class named *.nxm.[opt].[area].[ClassName]" */
  private static final Pattern NXM_CLASS = Pattern.compile("nxm[.][^.]+[.][^.]+[.][^.]+");

  /** Static flags to indicate the message severity */
  /** Equivalent to Java logger level INFO. */
  public static final int INFO=0;
  /** Equivalent to Java logger level WARNING. */
  public static final int WARNING=1;
  /** Equivalent to Java logger level SEVERE. */
  public static final int  ERROR=2;
  /** Log level */
  public static final int  DEPRECATE=3;
  /** Log level that uses logging.properties file for logging interactive commands. */
  public static final int INTERACTIVE=4;

  /** piped dataflow states */
  public static final int POFF=0, PINIT=1, PRUN=2, PPAUSE=3, PWAIT=4, PSTOP=5;
  static final String pipeModes = "Off,Init,Run,Pause,Wait,Stop";
  /** This is the offset necessary to index into the list of pipe modes ({@link #pipeModes}). */
  static final int pipeModesOffset = -1;

  /** The default logger to use for *new* Midas instances (null if java.util logging is not enabled). */
  private static Logger DEF_LOGGER = MidasToJavaLog.BOOTSTRAP_JAVALOGGER;

  /** Enable debug related to detection of Java versus macro (or command line)
   *  usage and related behavior customization */
  private static boolean javaVsMidasDebug = false;

  /** registry tables */
  public Registry registry;

  /** command dictionary */
  public Dictionary dictionary;

  /** results table */
  public Results results;

  /** pipe/queue/ramdisk system */
  public Table pipes;

  /** system message queue */
  public MessageQueue queue;

  /** current macro */
  public Macro macro;

  /** current pipe mode */
  public int pipeMode;

  /** Input/Output terminal
   *  @deprecated Since NeXtMidas 3.7.1 This is changing to package private.  Users should use the
   *  {@link #println(CharSequence)} method.
   */
  @Deprecated
  public Terminal terminal;

  /** Message INFO/WARN/ERROR file logger */
  public Logger logger;

  /** Message INFO/WARN/ERROR macro logger */
  public String msglog;

  /** file system utilities */
  public CoreIO io;

  /** verify options flag */
  public int verify;

  /** debug options flag */
  public int debug;

  /** enable ANSI colors for terminal displays */
  private static boolean useANSIColors = false;

  /** The character length of the from class:linenumber for logging */
  private static int fromLength = -1; // Default to -1 means there is nothing to cutoff.

  /** Used to determine whether to use LogUtilities or the traditional midas logger. */
  private static boolean useLogUtilities = false;

  /** timer value */
  public long timer=0, elapse=0;

  /** context status: INITIAL (-1), BOOTED (0), EXIT (-3) */
  public int status;

  /** thread polling delay */
  public double pause=0.0125;

  /** parent context for this environment */
  public Midas parent;

  /** last message */
  public String lastMessageString;

  private int timeStamp; // TimeStamp Format for messages, see Time.STD (0=Disable)

  /** Status of current option tree build (0:success 1:failure) associated with this Midas instance */
  private int buildStatus;
  /** Table containing each of the Java errors associated the build */
  private Table buildErrors;
  /** Number of Java errors associated the build */
  private int errorCount;
  /** 'ON' for pure Java behavior, 'OFF' for pure Midas, 'AUTO' for auto-detect */
  private static String nmJavaMode = Import.getEnv("NM_JAVA_MODE", "OFF");
  /** Auto-detection for Java versus Midas usage is enabled */
  private boolean autoDetectBehavior =  "AUTO".equals(nmJavaMode);
  /** Is this Java behavior, due to being forced on or auto-detection not finding this
   *  to be command line (NM prompt), macro based, or single-shot nm OS usage */
  private boolean javaBehavior = !"OFF".equals(nmJavaMode);
  /** Override NM_JAVA_MODE settings for testing */
  private static boolean forceAutoDetect = false;
  /** Override NM_JAVA_MODE settings for testing */
  private static boolean forceJavaBehaviorDefault = false;

  /** initialize a parent Midas environment from scratch */
  public Midas () {
    registry   = new Registry();
    dictionary = new Dictionary();
    pipes      = new Table();
    results    = new Results(this);
    io         = new CoreIO(this);
    queue      = new MessageQueue(this,null);
    terminal   = new Terminal(this);
    logger     = DEF_LOGGER;
    verify     = 0;
    debug      = loadDebugDefault();
    status     = -1;
    pipeMode   = POFF;
    timeStamp  = 0; // 0=Disable
    if (forceAutoDetect) autoDetectBehavior = true; // override for testing purposes
    if (forceJavaBehaviorDefault) javaBehavior = true; // override for testing purposes
    if (javaVsMidasDebug) System.out.println("#JvsM Midas Constructed with autoDetectBehavior:"
         + autoDetectBehavior + " javaBehavior defaulted to:"+javaBehavior);
    resetBuildStatus();
  }

  /** Get a clone of this context. NOTE: using same RESULTS and PIPES table.
   *  @return a clone of this context */
  public Midas cloneOf () {
    Midas midas;
    try {
      midas                   = (Midas)this.clone();
      midas.io                = new CoreIO(midas);
      midas.queue             = new MessageQueue(midas,null);
      midas.registry          = new Registry();
      midas.parent            = this;
      midas.logger            = DEF_LOGGER; // always reset to the default (usually null) logger (so sub-macros have own logger - needed by some users)
      midas.lastMessageString = null;
      midas.javaBehavior      = this.javaBehavior;
    }
    catch (CloneNotSupportedException e) {
      info("Clone not supported");
      return this;
    }
    if (javaVsMidasDebug) System.out.println("#JvsM Midas clone with autoDetectBehavior:"
        + midas.autoDetectBehavior + " javaBehavior defaulted to:"+midas.javaBehavior);
    return midas;
  }

  /** detach */
  public void detach () {
    pipeMode = POFF;
    macro    = null;
    msglog   = null;
  }

  /** Write a line of text output by sending an INTERACTIVE message.
      @param text for INTERACTIVE message
      @since NeXtMidas 2.7.1 to replace type(String) by using a more generic input type
   */
  public void type (CharSequence text) {
    message(text, INTERACTIVE);  // since 3.7.1 use message rather than terminal.writeln
   // terminal.writeln(text);
  }

  /** Write a line of text output by sending an INTERACTIVE message.
      @param text for INTERACTIVE message
      @since NeXtMidas 3.7.1 for NXM-2813
   */
  public void print (CharSequence text) {
    Object from = registry.getThreadOwner();
    message(text,INTERACTIVE,from, true);
  }

  /** Get the current path of NeXtMidas
   *  @return the comma separated list of option trees on the NeXtMidas Path
   *  @since NeXtMidas 3.9.1
   */
  public String getCurrentPathString () {
    return results.getS("OPT.PATH");
  }

  /** Get the current path of NeXtMidas
   *  @return an array of option trees on the NeXtMidas Path
   *  @since NeXtMidas 3.9.1
   */
  public String[] getCurrentPathArray () {
    return results.getS("OPT.PATH").split(",");
  }

  /** Write a line of text output by sending an INTERACTIVE message.
      @param text for INTERACTIVE message
      @since NeXtMidas 3.6.0
   */
  public void println (CharSequence text) {
    message(text, INTERACTIVE);  // since 3.7.1 use message rather than terminal.writeln
    //terminal.writeln(text);
  }

  /** Issue an informational message to indicate that a method is deprecated.
      @param msg deprecation message
      @since NeXtMidas 2.7.1 to replace deprecate(String) by using a more generic input type
   */
  public void deprecate (CharSequence msg) {
    if ((debug&Debug.DEPRECATE)!=0) message(msg, DEPRECATE);
  }

  /** Issue an informational message.
      @param text message in mare generic input type
      @since NeXtMidas 2.7.1 to replace info(String) by using a more generic input type
   */
  public void info (CharSequence text) {
    message(text, INFO);
  }

  /** issue an informational message regarding a Throwable
      @param t Throwable for info message */
  public void info (Throwable t) {
    info( t.toString() );
  }

  /** issue a warning message.
      @param text for warning
      @since NeXtMidas 2.7.1 to replace warning(String) by using a more generic input type
   */
  public void warning (CharSequence text) {
    message(text, WARNING);
  }

  /** Issue a warning message regarding a Throwable. Note that this method has been largely replaced
      by {@link #printStackTrace(Throwable)} since the {@link #printStackTrace(Throwable)} will
      include the full stack trace if the TRACE flag is set on debug. This method NEVER includes
      a stack trace.
      @param t the original Throwable
   */
  public void warning (Throwable t) {
    warning( t.toString() );
  }

  /** Throw a {@link MidasException} with the given message.
      In most cases, it is better/cleaner to throw a new MidasException verses calling this method.
      @param text the message
   */
  public void error (String text) {
    throwMidasException(text,null);
  }

  /** Throw a {@link MidasException} with the given nested exception.\
      In most cases, it is better/cleaner to throw a new MidasException verses calling this method.
      @param t the original (nested) exception
   */
  public void error (Throwable t) {
    throwMidasException(t.getMessage(),t);
  }

  /** Throw a {@link MidasException} with the given text and nested exception.
      In most cases, it is better/cleaner to throw a new MidasException verses calling this method.
      @param text the text message
      @param t    the original (nested) exception
      @since NeXtMidas 2.7.3
      @throws MidasException &nbsp;
   */
  public void error (CharSequence text, Throwable t) {
    String str = (text==null)? null : text.toString();
    throwMidasException(str, t);
  }

  /** Throw a {@link MidasException} with the given message.
      In most cases, it is better/cleaner to throw a new MidasException verses calling this method.
      verses calling this method.
      @param text text for MidasException
    */
  public void except (String text) {
    throwMidasException(text,null);
  }

  /** Throw a {@link MidasException} with the given nested exception.
      In most cases, it is better/cleaner to throw a new MidasException verses calling this method.
      @param t the original (nested) exception
   */
  public void except (Throwable t) {
    throwMidasException(t.getMessage(),t);
  }

  /** Throws a new MidasException, but deletes the top two entries on the stack that represent
      Midas.throwMidasException(..) and Midas.except(..) (or Midas.error(..)).
   */
  private void throwMidasException (String msg, Throwable t) {
    MidasException      e     = new MidasException(msg,t);
    StackTraceElement[] stack = e.getStackTrace();

    if (stack.length > 2) { // <-- Should never be less than 2, but just in case...
      // Pull the top two elements off of the stack trace. Note that the top of the
      // stack trace is stack[0], not stack[len-1].
      StackTraceElement[] stack2 = new StackTraceElement[stack.length - 2];
      System.arraycopy(stack,2,stack2,0,stack2.length);
      e.setStackTrace(stack2);
    }
    throw e;
  }

  /** Write out the entire string using the more routine.
    This is identical to <code>moreString(StringUtil.cleanSplit(text, "\n"))</code>
    @param text The text to output to the Terminal.
    @since NeXtMidas 3.7.1 for NXM-2813
  */
  public void moreString (String text) {
    moreString(StringUtil.cleanSplit(text, "\n"));
 }

  /** Write out a number of lines using the more routine.
    This is identical to <code>moreString(lines,0,lines.length-1)</code>.
    @param lines The set of lines to output.
    @since NeXtMidas 3.7.1 for NXM-2813
  */
  public final void moreString (String... lines) {
    moreString(lines,0,lines.length-1);
  }

  /** Write out a number of lines using the more routine.
    @param lines The set of lines to output.
    @param start The first line to output (inclusive).
    @param end   The last line to output (inclusive).
   @since NeXtMidas 3.7.1 for NXM-2813
  */
  public final void moreString (String[] lines, int start, int end) {
    if (lines == null) return;
    start = Math.max(start, 0);
    end   = Math.min(end, lines.length);
    Object from = registry.getThreadOwner();
    terminal.more ( Terminal.INIT );
    for (int i = start; terminal.more() && (i <= end); i++) {
      terminal.more(lines[i]);

      }
    terminal.more( Terminal.EXIT );
  }

  /** Handle the more filter open/close/test functions
    @param func The more function to perform (INIT, FLUSH, EXIT ).
    @return true if there are still available lines of output for the more filter.
    @since NeXtMidas 3.7.1 for NXM-2813
  */
  public boolean more (int func) {
    return terminal.more(func);
  }

  /** Determines if there are more lines to output with more.
    @return true if there are still available lines of output for the more filter.
    @since NeXtMidas 3.7.1 for NXM-2813
  */
  public boolean more () {
    return terminal.more();
  }

  /** Handle the more filter text output.
    @param text The text to display.
    @since NeXtMidas 3.7.1 for NXM-2813
  */
  public void more (String text) {
    terminal.more(text);
  }

  /** Handle the more filter text output for a CharSequence.
    @param text the text to display (null will be converted to "null")
    @since NeXtMidas 3.7.1 for NXM-2813
  */
  public void more (CharSequence text) {
    terminal.more(text);
  }

  /** Handle the more columnator filter text output.
    @param text        The text to display (cannot be null).
    @param columnwidth The column width to use.
    @since NeXtMidas 3.7.1 for NXM-2813
  */
  public void more (String text, int columnwidth) {
    terminal.more(text,columnwidth);
  }

  /** Issue a message with specified severity.
      @param severity The severity type of the message (e.g. {@link #DEPRECATE},
                      {@link #INFO}, {@link #WARNING}, {@link #ERROR}.
      @param text The message text
      @since NeXtMidas 2.7.1 to replace message(String, int) by using a more generic input type
   */
  public void message (CharSequence text, int severity) {
    Object from = registry.getThreadOwner();
    message(text,severity,from);
  }

  /** Issue a message with specified severity, text, and from Object.
      @param severity The severity type of the message (e.g. {@link #DEPRECATE},
                      {@link #INFO}, {@link #WARNING}, {@link #ERROR}.
      @param text The message text
      @param from The objecting sending this message
      @since NeXtMidas 2.7.1 to replace message(String, int, Object) by using a more generic input type
   */
  public void message (CharSequence text, int severity, Object from) {
    message(text, severity, from, false); // false = not partial line
//    // Prepend Type
//    String type;
//    switch (severity) {
//      case INTERACTIVE: type = "INTERACTIVE"; break;
//      case DEPRECATE:   type = "DEPRECATE"; break;
//      case INFO:        type = "INFO";      break;
//      case WARNING:     type = "WARN";      break;
//      case ERROR:       type = "ERROR";     break;
//      default:          type = "TEXT";      break;
//    }
//    lastMessageString = type+": "+text;
//
//    // Append FID
//    String fid = null;
//    if (from==null) fid = null;
//    else if (from instanceof IDable) {
//      fid = ((IDable)from).getID();
//      if (fid.length()==0 && from instanceof Command) fid = ((Command)from).name;
//    }
//    else {
//      fid = from.toString();
//      if (fid.length()>20) fid = fid.substring(0,20);
//    }
//    if (fid!=null) lastMessageString += " ["+fid+"]";
//
//    // Prepend Time Stamp
//    if (timeStamp > 0) {
//      String ts = Time.toString(Time.current(), timeStamp);
//      lastMessageString = ts + " " + lastMessageString;
//    }
//
//    if (logger!=null) {
//      logger.processMessage(this, type,text,fid);
//    }
//    if (severity != INTERACTIVE) {
//      terminal.writeln(lastMessageString);
//    }
//    if (msglog!=null) {
//      queue.put("LOG",severity,text,msglog,from);
//    }
  }

  /** Issue a message with specified severity, text, and from Object.
      @param severity The severity type of the message (e.g. {@link #DEPRECATE},
                  {@link #INFO}, {@link #WARNING}, {@link #ERROR}.
      @param text The message text
      @param from The objecting sending this message
      @param textPartialLine is this a partial line with no line feed
      @since NeXtMidas 3.7.1 to allow messages that do not have line feed
    */
  @SuppressWarnings("deprecation")  // for M.terminal
  private void message (CharSequence text, int severity, Object from, boolean textPartialLine) {
    // Prepend Type
    String type;
    Level level;
    ANSIColor ansiColor = ANSIColor.DEFAULT;
    switch (severity) {
      case INTERACTIVE: type = "INTERACTIVE"; level = Level.INFO;    ansiColor = ANSIColor.PURPLE; break;
      case DEPRECATE:   type = "DEPRECATE";   level = Level.INFO;    ansiColor = ANSIColor.PURPLE; break;
      case INFO:        type = "INFO";        level = Level.INFO;    ansiColor = ANSIColor.PURPLE; break;
      case WARNING:     type = "WARN";        level = Level.WARNING; ansiColor = ANSIColor.YELLOW; break;
      case ERROR:       type = "ERROR";       level = Level.SEVERE;  ansiColor = ANSIColor.RED; break;
      default:          type = "TEXT";        level = Level.INFO;    ansiColor = ANSIColor.PURPLE; break;
    }

    if(useLogUtilities && !(severity==INTERACTIVE)) {
      String message = text.toString();

      if(severity == DEPRECATE) {
        message = type+":"+text;
      }
      LogUtilities.log(this, level, message);
      return;
    }

    lastMessageString = type+": "+text;

    // Append FID
    String fid = null;
    if (from==null) fid = null;
    else if (from instanceof IDable) {
      fid = ((IDable)from).getID();
      if (fid.length()==0 && from instanceof Command) fid = ((Command)from).name;
    }
    else {
      fid = from.toString();
      if (fromLength>=0 && fid.length()>fromLength) fid = fid.substring(0,fromLength);
    }
    if (fid!=null) lastMessageString += " ["+fid+"]";

    // Prepend Time Stamp
    if (timeStamp > 0) {
      String ts = Time.toString(Time.current(), timeStamp);
      lastMessageString = ts + " " + lastMessageString;
    }

    if (logger!=null) {
      if (severity == INTERACTIVE) {  // INTERACTIVE messages are a means of logging interactive commands
        // INTERACTIVE commands should go to the terminal as well as the logger - NXM-3046
        if (textPartialLine) {
          terminal.write(text.toString());
        } else {
          terminal.writeln(text.toString());
        }
        int len=text.length();
        if (len>=6) {
          String test = text.subSequence(0,6).toString();
          if (test.equals("INFO: ")) { type="INFO"; text=text.subSequence(6,len); }
          if (test.equals("WARN: ")) { type="WARN"; text=text.subSequence(6,len); }
          if (test.equals("ERROR:")) { type="ERROR"; text=text.subSequence(7,len); }
        }
      }
      logger.processMessage(this,type,text,fid);
    } else if (useANSIColors){
//      // Only write the actual text when the type is INTERACTIVE
//      String textForWrite = (severity == INTERACTIVE) ? text.toString() : lastMessageString;
//      textForWrite = color(ansiColor,textForWrite);
//      Shell.writeln(textForWrite);
    }
    else {
      // Only write the actual text when the type is INTERACTIVE
      String textForWrite = (severity == INTERACTIVE) ? text.toString() : lastMessageString;
      if (useANSIColors) textForWrite = color(ansiColor,textForWrite);
      if (textPartialLine) {
        terminal.write(textForWrite);
      } else {
        terminal.writeln(textForWrite);
      }
    }
    if (msglog!=null) {
      queue.put("LOG",severity,text,msglog,from);
    }
  }

  /** Get current Logger for this Midas context
      @return current Logger
      @since  NeXtMidas 3.5.4
   */
  public Logger getLogger () {
    return logger;
  }

  /** Set/Change Logger for this Midas Context
      @param  newLogger new logger to use
      @return previous logger
      @since  NeXtMidas 3.5.4
   */
  @InternalUseOnly("Since creation of method, only used for testing")
  public Logger setLogger (Logger newLogger) {
    Logger previousLogger = logger;
    logger = newLogger;
    return previousLogger;
  }

  /** Clear the logger for this Midas Context by setting it to null */
  @InternalUseOnly("Since creation of method, only used for testing")
  public void clearLogger () {
    logger = null;
  }

  /** Set/Change this (current) Midas Context's Logger to use the MidasToJavaLog (to java.util Logger)
      with the specified java.util logging config/properties file.
      @param  javaUtilLoggingConfigUrl URL to Java util Logging configuration file (supports NeXtMidas URLs,
                 i.e. inside jar file)
      @return previous logging configuration file (null if none)
      @since  NeXtMidas 3.5.4
   */
  @InternalUseOnly("Since creation of method, only used for testing")
  public String useMidasToJavaLog (String javaUtilLoggingConfigUrl) {
    String previousConfigFile = System.getProperty("java.util.logging.config.file");
    MidasToJavaLog.setGlobalInstance(this, javaUtilLoggingConfigUrl);
    setLogger(MidasToJavaLog.getGlobalInstance());
    return previousConfigFile;
  }

  /** Set/Change "Default" Logger for *new* Midas instances.
      @param  newLogger new logger
      @return previous logger for *new* Midas context (null if none)
      @since  NeXtMidas 3.5.4
   */
  @InternalUseOnly("Since creation of method, only used for testing")
  public static Logger setDefaultLogger (Logger newLogger) {
    Logger previousLogger = DEF_LOGGER;
    DEF_LOGGER = newLogger;
    return previousLogger;
  }

  /** Get "Default" logger for *new* Midas context (null if none)
   *  @return the "Default" logger
   *  @since  NeXtMidas 3.5.4
   */
  @InternalUseOnly("Since creation of method, only used for testing")
  public static Logger getDefaultLogger () {
    return DEF_LOGGER;
  }

  /** Gets the debug flags.
      @return The mask value for the debug flags (see {@link Debug#debugList}).
   */
  public String getDebug () {
    return Parser.mask2s(Debug.debugList, debug);
  }

  /** Sets the debug flags (string mask).
      @param str The new mask value for the debug flags (see {@link Debug#debugList}).
   */
  public void setDebug (String str) {
    debug = Parser.mask(Debug.debugList, str, debug);
  }

  /** Sets the debug flags (mask).
      @param mask The new mask value for the debug flags (see {@link Debug#debugList}).
      @since 3.1.1
   */
  public void setDebug (int mask) {
    debug = mask;
  }

  /** Check to see if the given debug option is set.
   *  @param mask debug option
   *  @return true if the debug option is set
   */
  public boolean debug (int mask) {
    return (mask&debug) != 0;
  }

  /** This method loads the Midas default debug flags to be used by reading in midas_default_debug.txt.
   *  The following rules apply when using the midas_default_debug.txt file:
   *    1. The midas_default_debug.txt file can have comments appended at the top before the Midas debug flag is set.
   *    2. The Midas debug flag set in midas_default_debug.txt can be a hexadecimal or an integer.
   *    3. Whenever the "debug on ..." command is used in the nmstartup.mm file, the flags specified in the command
   *       will be prepended to what's read in from midas_default_debug.txt.
   *    4. Whenever the "debug on ..." command is not used in the nmstartup.mm file, then the Midas debug flag
   *       from midas_default_debug.txt will be set during initial startup then set to 0
   *       regardless of what's in midas_default_debug.txt.
   *
   *  @return the Midas debug flag as an integer
   *  @since NeXtMidas 4.1.1
   */
  private int loadDebugDefault() {
    InputStream in = Midas.class.getResourceAsStream("/nxm/sys/cfg/midas_default_debug.txt");
    if (in == null) return 0;

    int loadDebug = 0;
    String line = "";
    try (BufferedReader myReader = new BufferedReader(new InputStreamReader(in))){
      while ((line = myReader.readLine()) != null) {
        line = line.trim();
        if (!line.startsWith("#") && !line.startsWith("!")) {
          if (line.startsWith("0x")) loadDebug = Integer.decode(line);
          else                       loadDebug = Integer.parseInt(line);
          break;
        }
      }
    }
    catch (IOException ioex) {
      ioex.printStackTrace();
      loadDebug = 0;
    }

    return loadDebug;
  }

  /** Gets the proper instance of {@link CoreIO} to use with this Midas context.
   *  @return the proper instance of CoreIO
   *  @since NeXtMidas 3.7.1
   */
  public final CoreIO getCoreIO () {
    return io;
  }

  /** Gets the proper instance of {@link CoreIO} to use with this Midas context.
   *  This is identical to {@link #getCoreIO()}.
   *  @return the proper instance of CoreIO
   *  @since NeXtMidas 3.7.1
   */
  public final CoreIO getIO () {
    // This method only exists since many people previously used M.io and so
    // they are likely to look for M.getIO() rather than M.getCoreIO() which
    // is the more explicit usage.
    return getCoreIO();
  }

  /** Gets the verify flags.
      @return The mask value for the debug flags (see {@link Verify#verifyList}).
   */
  public String getVerify () {
    return Parser.mask2s(Verify.verifyList, verify);
  }

  /** Sets the verify flags.
      @param str The new mask value for the debug flags (see {@link Verify#verifyList}).
   */
  public void setVerify (String str) {
    verify = Parser.mask(Verify.verifyList, str, verify);
  }

  /** Check to see if the given verify option is set.
   *  @param mask verify option
   *  @return true if verify option is set
   */
  public boolean verify (int mask) {
    return (mask&verify)!=0;
  }

  /** quick check if piping
   *  @return true if piping */
  public boolean isPiped() {
    return (pipeMode != POFF && pipeMode != PSTOP);
  }

  /** Pause thread for a standard polling delay which is taken from the
      current value of the pause member.  */
  public void pause() {
    Time.sleep(pause);
  }

  /** Get the reference to the command dictionary.
   *  @return the reference to the command dictionary
   */
  public Dictionary getDictionary () {
    return dictionary;
  }

  /** Get the reference to the terminal being used by this instance.
   *  @return the reference to the terminal being used by this instance. */
  @SuppressWarnings("deprecation")  // for M.terminal - since 3.7.1 for NXM-2813
  public Terminal getTerminal() {
    return terminal;
  }

  /** Get the parent context
   *  @return the parent context */
  public Midas getParent() {
    if (javaVsMidasDebug) System.out.println("#JvsM Midas.getParent parent autoDetectBehavior:"
        + autoDetectBehavior +" javaBehavior currently:"+javaBehavior);
    return parent;
  }

  /** Get the server responsible for this instance of the Contexts shell
   * @return the server responsible for this instance of the Contexts shell */
  public String getServer() {
    Shell shell = (Shell)registry.get("SHELL");
    if (shell != null && shell.parent != null) {
      return shell.parent.getServer();
    }
    return null;
  }

  /** Get the table of active pipes and files in RAM.
   *  @return the table of active pipes and files in RAM.
   */
  public Table getPipes() {
    return pipes;
  }

  /** Get the results table.
   * @return the results table */
  public Table getResults() {
    return results;
  }

  /** Get the registry table.
   *  @return the registry table */
  public Registry getRegistry() {
    return registry;
  }

  /** Get the current macro (or null if no macro is running).
      @return the current macro */
  public Macro getMacro() {
    return macro;
  }

  /** Prints out a stack trace (as an ERROR). This is the same as <code>printStackTrace(null,err,false)</code>.
      @param err  The error/exception.
      @see #printStackTrace(CharSequence, Throwable, boolean)
      @since NeXtMidas 2.1.0
   */
  public final void printStackTrace (Throwable err) {
    printStackTrace(null, err, false);
  }

  /** Prints out a stack trace (as an ERROR). This is the same as <code>printStackTrace(null,err,forceTrace)</code>.
      @param err        The error/exception.
      @param forceTrace If false the trace will only be shown when the Debug TRACE flag is set.
      @see #printStackTrace(CharSequence, Throwable, boolean)
      @since NeXtMidas 2.1.0
   */
  public final void printStackTrace (Throwable err, boolean forceTrace) {
    printStackTrace(null, err, forceTrace);
  }

  /** Prints out a stack trace (as an ERROR). This is the same as <code>printStackTrace(msg,throwable,false)</code>.
    @param msg  An additional explanation message about the error (ignored if null).
    @param err  The error/exception.
    @see #printStackTrace(CharSequence, Throwable, boolean)
    @since NeXtMidas 2.7.1 to replace printStackTrace(String, Throwable) by using a more generic input type
  */
  public final void printStackTrace (CharSequence msg, Throwable err) {
    printStackTrace(msg, err, false);
  }

  /** Prints out a stack trace as an {@link #ERROR} severity message.
      @param msg        An additional explanation message about the error (ignored if null).
      @param err        The error/exception.
      @param forceTrace If false the trace will only be shown when the Debug TRACE flag is set.
      @since NeXtMidas 2.7.1 to replace printStackTrace(String, Throwable, boolean) by using a more generic input type
   */
  public final void printStackTrace (CharSequence msg, Throwable err, boolean forceTrace) {
    printStackTrace(msg, err, forceTrace, ERROR);
  }

  /** Prints out a stack trace as an {@link #WARNING} severity message.
   * @param msg  An additional explanation message about the exception (ignored if null).
   * @param throwable The error/exception to warn on
   * @since NeXtMidas 3.7.0
   */
  public final void warnStackTrace (CharSequence msg, Throwable throwable) {
    printStackTrace(msg, throwable, false, WARNING);
  }

  /** Prints out a stack trace as specified severity (normally {@link #WARNING} or {@link #WARNING}) message.
      @param msg        An additional explanation message about the error (ignored if null).
      @param err        The error/exception.
      @param forceTrace If false the trace will only be shown when the Debug TRACE flag is set.
      @param severity   The severity type of the message (e.g. {@link #ERROR}, {@link #WARNING}, {@link #INFO}).
      @throws IllegalArgumentException if both msg and err are null (since 3.3.4)
      @since NeXtMidas 3.3.4 (extracted from printStackTrace (CharSequence msg, Throwable err, boolean forceTrace))
   */
  public final void printStackTrace (CharSequence msg, Throwable err, boolean forceTrace, final int severity) {
    if (msg == null && err == null) { // since 3.3.4
      throw new IllegalArgumentException("Provided msg AND error/exception cannot BOTH be null.");
    }
    boolean      trace = debug(Debug.TRACE) || forceTrace;
    boolean      code  = trace && debug(Debug.SUPERTRACE);
    int       capacity = code? 4096 : (trace? 2048 : 512);
    StringBuilder sb   = new StringBuilder(capacity);    // not synchronized, more efficient

    // FORM THE ERROR MESSAGE ////////////////////////////

    if ((msg != null) && (msg.length() > 0)) {
      sb.append(msg);                                   // Include provided message
    }

    // Include the exception/error information
    if (err != null) {
      String details = null;
      if (sb.length() > 0) sb.append(": ");             // Include delimiter (if not empty - since 3.7.0)
      if (err instanceof MidasException) {
        // We threw the exception, omit the class name
        if (trace) {
          details = ((MidasException)err).getDetails();
        }
      } else {
        sb.append(Util.getClassName(err)).append(": "); // Include class name if not MidasException
      }
      String eMsg = err.getMessage();
      if (eMsg != null && !eMsg.isEmpty()) {
        sb.append(eMsg);                                // Include exception message
      }
      if ((details != null) && (details.length() > 0)) {
        sb.append(" (").append(details).append(')');    // Include details for MidasException
      }
      if (err.getCause() != null) {
        sb.append(" Cause="+err.getCause());            // Include cause of exception
      }
    }

//  message(sb, severity); // In 3.7.0 this has been move below to keep msg and stack trace together

    // since 3.7.0, append stack trace info to sb vs calling type(..) so that it says together as
    //              one block vs multiple lines (which could get interleaved with other messages)
    if (trace) {
      while (err != null) {
        StackTraceElement[] st = err.getStackTrace();
        boolean done = false;
        for (int i=0; i < st.length; i++) {
          if (!code) {
            sb.append("\n        at " + st[i]);
          }
          else if (done || (i >= CODE_LIMIT)) {
            sb.append("\n======== " + st[i]);
          }
          else {
            sb.append("\n======== " + st[i]);
            done = printSourceCodeFor(sb, i, st[i]);
          }
        }
        err = err.getCause();
        if (err != null) sb.append("\n    Caused by " + err);
      }
    }
    // PRINT MESSAGE AND STACK TRACE /////////////////////
    message(sb, severity);
  }

  /** Prints out the source code for a given stack trace element.
      @param sb StringBuilder to append output to (since 3.7.0 vs calling type(..))
      @param ele The stack trace element.
      @return true to continue printing code blocks, false if last block reached.
   */
  private final boolean printSourceCodeFor (StringBuilder sb, int level, StackTraceElement ele) {
    try {
      String fullName = ele.getClassName();
      String fileName = ele.getFileName();
      int    lineNum  = ele.getLineNumber();
      int    start    = lineNum - CONTEXT_PRE;
      int    end      = lineNum + CONTEXT_POST;
      Class<?> clazz  = Class.forName(fullName);
      Class<?> parent = clazz.getSuperclass();

      if (level > 0) {
        // Stop if we've gotten below the level of the command being run, this basically says that
        // the last thing we should see is something like myprim.open() or myprim.processMessage(..)
        // rather than the "details" that are below it in SYS.
        if (clazz  == Command.class  ) return true;  // In Command proper
        if (parent == Command.class  ) return true;  // In Primitive, GPrimitive, Macro, etc.
        if (clazz  == KeyObject.class) return false; // Don't show intermediate KeyObject methods, but do continue past them
      }

      TextFile tf = null;
      // code below commented out per BUG 1755
      /*if ((index > 0) && (lineNum > 0) && fullName.startsWith("nxm.")) {
        String root = fileName.substring(0,index);
        String ext  = fileName.substring(index+1);
        if (fullName.endsWith("."+root)) {
          tf = new TextFile(this, fullName+"."+ext);
        }
      }*/
      if ( (lineNum > 0) && JAVA_FILE.matcher(fileName).matches() && NXM_CLASS.matcher(fullName).matches() ) {
        String pkg = fullName.substring(0,fullName.lastIndexOf('.'));
        tf = new TextFile(this, new FileName(pkg+"."+fileName, true));
      }

      if ((tf != null) && tf.open(TextFile.NOABORT)) {
        for (int i = 1; i <= end; i++) {
          String line = tf.read();

               if (line == null)               break;
          else if (i == lineNum)               sb.append("\n--> " + i + ":" + line);
          else if ((i >= start) && (i <= end)) sb.append("\n    " + i + ":" + line);
        }
        tf.close();
      }
      return false;

    } catch (Exception e) {
      // ignore it
      return true;
    }
  }

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

  /** Set if this Midas instance should have Java Behavior
   *  @param isJavaBehavior true for Java, false for Midas behavior
   */
  @ProvisionalUseOnly("Beta version - API could change")
  public void setIfJavaBehavior (boolean isJavaBehavior) {
    this.javaBehavior = isJavaBehavior;
  }

  /** Get if this Midas is command line or macro based or if it is Java
   *  code based. Allows defaults to match usage.
   *
   *  @return false for Java based, true for nM prompt or Macro
   */
  @ProvisionalUseOnly("Beta version - API could change")
  public boolean isJavaBehavior () {
    return javaBehavior;
  }

  /** This Midas is a Java App and should have those defaults
   */
  @ProvisionalUseOnly("Beta version - API could change")
  public void needJavaBehavior () {
    this.javaBehavior = true;
  }

  /** Override NM_JAVA_MODE for testing
   *  @param nmJavaMode Set if forcing Java Mode
   */
  @InternalUseOnly
  public static void setForceNmJavaMode (String nmJavaMode) {
    Midas.forceAutoDetect = "AUTO".equals(nmJavaMode);
    Midas.forceJavaBehaviorDefault = !"OFF".equals(nmJavaMode);
  }

  /** Return if running in Java Mode
   *  @return true if running in Java Mode
   */
  public static String getNmJavaMode () {
    return nmJavaMode;
  }

  /** Auto-detection for Java versus Midas behavior is on
   *  @return true if auto-detection is enabled
   */
  @ProvisionalUseOnly("Beta version - API could change")
  public boolean isAutoDetectingBehavior () {
    return autoDetectBehavior;
  }

  /** Is debug enabled for Java versus Midas behavior code
   *  @return true if debug is enabled
   */
  @InternalUseOnly
  public static boolean isJavaVsMidasDebug () {
    return javaVsMidasDebug;
  }

  /** Enable or Disable debug of Java versus Midas behavior
   *  @param enable true to enable
   */
  @InternalUseOnly
  public static void enableJavaVsMidasDebug (boolean enable) {
    Midas.javaVsMidasDebug = enable;
  }

  ////////////////BUILD STATUS SUPPORT///////////////////////////////
  /** Get the status of the last build
   *  @return status of the last build
   */
  @InternalUseOnly
  public int getBuildStatus () {
    return buildStatus;
  }

  /** Set build status
   *  @param status 1 = errors, 0 = no errors
   */
  @InternalUseOnly
  public void setBuildStatus (int status) {
    buildStatus = status;
  }

  /** Get list of build errors from last build
   *  @return the build errors
   */
  @InternalUseOnly
  public Table getBuildErrors () {
    return buildErrors;
  }

  /** Reset Build Status */
  void resetBuildStatus () {
    buildStatus = Build.BUILD_SUCCESSFUL;
    buildErrors = new Table();
    errorCount = 0;
  }

  /** Add this build error to the table holding the build errors
   *  @param errorInfo First line of Java build error message
   */
  void addBuildError (String errorInfo) {
    errorCount++;
    errorInfo = errorInfo.replace('\\', '/'); // Convert any Windows backslash path delimiters to forward slashes
    buildErrors.put("ERROR_"+errorCount, errorInfo);
  }

  ////////////////Logging Support//////////////////////////////////
  /** Logs a message to either the Midas logger or the Java logger.<br>
   *  The logger will decide based on the CoreIO options.<br>
   *  <code>+MIDASLOGGERSHOULDUSELOGUTILITIES</code> - will go to the java logger<br>
   *  <code>+CLASSBASEDLOGGERSHOULDUSEMIDASLOGGER</code> - will go to the Midas Logger<br>
   *  The java logger option has the ability to provide a line number<br>
   *  The midas logger option does not have the ability to provide a line number<br>
   *  If you have the Midas logger and would like line number to be provided, please use
   *  {@link nxm.sys.lib.LogUtilities#log(Level, String)}
   *  @param text  The message to be logged
   *  @param level The level of the message (i.e. Level.INFO)
   */
  public static void log(CharSequence text, int level) {
    Shell.getSharedMidasContext().message(text, level, null);
  }

  ///////////////////////////////////////////////////////////////////////////
  //////////////// Logging Support via java.util.logging.* to Midas /////////
  private static final int LOG_ERROR   = Level.SEVERE.intValue();  // error
  private static final int LOG_WARNING = Level.WARNING.intValue(); // warning
  private static final int LOG_INFO    = Level.INFO.intValue();    // info

  /** Creates a new {@link Handler} compatible with the Java logger package that will
      log its output to this Midas context via calls to {@link #message}.
      Usage case: send java.util.logging messages to current Midas context.
      @return A new {@link Handler}
      @since NeXtMidas 2.7.1
   */
  public Handler createLogHandler () {
    return new LogHandler();
  }

  /** A simple log handler */
  private class LogHandler extends Handler {
    private boolean closed = false;

    public LogHandler () {
      setFormatter(new Formatter() {
        public String format (LogRecord record) {
          String time = Time.toString(record.getMillis()/1000 + Time.J1970TOJ1950);
          String name = (record.getLoggerName() == null)? "Anonymous Logger": record.getLoggerName();
          return name + ": " + formatMessage(record) + " [" + time + "] ";
        }
      });
    }

    public synchronized void close () {
      if (closed) return;
      flush();
      closed = true;
    }

    /** Should not need to do anything here... */
    public synchronized void flush () {
      if (closed) return;
    }

    @Override
    public boolean isLoggable (LogRecord record) {
      return !closed && super.isLoggable(record);
    }

    public synchronized void publish (LogRecord record) {
      if (closed) return;
      if (!isLoggable(record)) return;

      int lvl = record.getLevel().intValue();
      String text;

      try {
        text = getFormatter().format(record);
      } catch (Exception ex) {
        reportError(null, ex, ErrorManager.FORMAT_FAILURE);
        return;
      }

      if      (lvl >= LOG_ERROR   ) message(text, ERROR,   null);
      else if (lvl >= LOG_WARNING ) message(text, WARNING, null);
      else if (lvl >= LOG_INFO    ) message(text, INFO,    null);
      else message(text, INFO, null); // catch-all (should never be hit)
    }
  }

  /** Get the character length of the from class:linenumber for logging
   *  @return The character length of the from class:linenumber for logging
   */
  @InternalUseOnly
  public static int getFromLength () {
    return fromLength;
  }

  /** Set the character length of the from class:linenumber for logging
   *  @param fromLength The character length of the from class:linenumber for logging
   */
  @InternalUseOnly
  public static void setFromLength (int fromLength) {
    Midas.fromLength = fromLength;
  }

  /** Sets the state of using log utilities
      @param b whether or not log utilities will be used
   */
  @InternalUseOnly
  public static void setUseLogUtilities (boolean b) {
    useLogUtilities  = b;
  }

  /** Set if ANSI colors should be used for terminal displays
   *  @param useANSIColors true to use ANSI colors
   */
  public static void setUseANSIColors (boolean useANSIColors) {
    Midas.useANSIColors = useANSIColors;
  }

  private static String color (ANSIColor color, String msg) {
    return color.toString() + msg + ANSIColor.DEFAULT.toString();
  }

  ////////////////// end Logging Support Code ////////////////////
  ////////////////////////////////////////////////////////////////
}
