package nxm.sys.lib;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import nxm.sys.inc.InternalUseOnly;

/** A variant of {@link nxm.sys.lib.Logger} that uses the normal Java logging utilities
    ({@link java.util.logging.Logger}) for logging, facilitating use of NeXtMidas (as a
    JAR file) within a larger application. E.g. this sends Midas log messages to Java util logger (JUL).<br>
    <br>
    Logging can be enabled by adding the following line to the <code>logging.properties</code>
    file pointed to by the Java system property <code>java.util.logging.config.file</code>:
    <pre>
      <span style="color:blue">nxm.sys.lib.NeXtMidas.enable=true</span>
    </pre>
    The logging level can be controlled by adding one of the following: <br>
    <pre>
      Java Logging Property                    # NeXtMidas Message Types
      --------------------------------------------------------------------------------<span style="color:blue">
      nxm.sys.lib.NeXtMidas.level=SEVERE       # ERROR
      nxm.sys.lib.NeXtMidas.level=WARNING      # ERROR + WARNING
      nxm.sys.lib.NeXtMidas.level=INFO         # ERROR + WARNING + INFO
      nxm.sys.lib.NeXtMidas.level=CONFIG       # ERROR + WARNING + INFO + DEPRECATE</span>
    </pre>
    Note that unlike the normal Java logging, all of the standard NeXtMidas log messages
    will show up as coming from the {@link NeXtMidas} class (see {@link #LOG_CLASS}) since
    NeXtMidas doesn't differentiate the origin of the log message the same way. If a log
    message originates from a running primitive and NeXtMidas is able to identify the ID
    of that primitive (e.g. where a normal NeXtMidas message would print with "[id]" at
    the end), the ID will be reported as the method of origin for the Java log message. <br>
    <br>
    Sample <code>logging.properties</code>:
    <pre>
      handlers=java.util.logging.ConsoleHandler
      .level=WARNING

      java.util.logging.ConsoleHandler.level=ALL
      java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter

      # com.example.level=FINE
      # com.example.handler=java.util.logging.ConsoleHandler
      <span style="color:blue">
      nxm.sys.lib.NeXtMidas.enable=true
      nxm.sys.lib.NeXtMidas.level=CONFIG
      </span>
    </pre>
    Sample Usage: <br>
    <pre>
      Normal NeXtMidas Logging:
        {@code nM> fft ram:DoesNotExist }
        <span style="color:red">ERROR</span>: <span style="color:blue">Could not open ram:DoesNotExist: Could not find...</span> [<span style="color:purple">FFT</span>]

      With Java Logging <i>(config file shown above)</i>:
        {@code nM> fft ram:DoesNotExist }
        Dec 16, 2014 5:06:39 PM nxm.sys.lib.NeXtMidas <span style="color:purple">FFT</span>
        <span style="color:red">SEVERE</span>: <span style="color:blue">Could not open ram:DoesNotExist: Could not find...</span>
    </pre>
    Note that since this passes everything to the Java logging functions, this class does not
    directly manage the log files. All of the file-specific methods will either throw an
    exception or act as a NOOP.

    @author  J. Czechowski IV
    @since   NeXtMidas 3.5.4
 */
@InternalUseOnly("Instantiated by Midas class based on logging properties, should not be accessed directly")
public class MidasToJavaLog extends nxm.sys.lib.Logger {
  /** The "class" used when logging NeXtMidas messages (points to {@link NeXtMidas}). */
  public static final String LOG_CLASS = NeXtMidas.class.getName();

  /** The logger used when logging NeXtMidas messages. */
  public static final Logger LOG = Logger.getLogger(LOG_CLASS);

  /** The "bootstrap" logger used when logging NeXtMidas messages, null if not in use. */
  static final MidasToJavaLog BOOTSTRAP_JAVALOGGER = getBootstrapLogger();

  /** The "Global" (singleton) logger used when logging NeXtMidas messages, null if not in use. */
  private static MidasToJavaLog LOGGER = BOOTSTRAP_JAVALOGGER;

  /** Creates a new instance. */
  MidasToJavaLog () {
    this(null);
  }

  /** Creates a new instance.
      @param midas The associated Midas context (currently unused).
   */
  MidasToJavaLog (Midas midas) {
    super(midas);
  }

  @Override
  @SuppressWarnings("deprecation")  // for M.terminal
  public void processMessage (Midas midas, CharSequence type, CharSequence text, CharSequence from) {
    String _type = (type == null)? "TEXT" : type.toString(); // matches Logger.processMessage(..)
    String _text = (text == null)? ""     : text.toString(); // matches Logger.processMessage(..)
    String _from = (from == null)? null   : from.toString(); // keep as null since we need that for logp(..)

    if ((_from != null) && _from.equals("UNKNOWN")) {
      _from = null; // "UNKNOWN" can be set by Logger.processMessage(..)
    }

    Level level;
    if  (_type.equals("DEPRECATE") || _type.equals("CONFIG")) {
      level = Level.CONFIG;
    }
    // both "WARN" and "WARNING" are the same (matches Logger.processMessage(..))
    else if (_type.equals("WARNING") || _type.equals("WARN")) {
      level = Level.WARNING;
    }
    else if (_type.equals("ERROR")) {
      level = Level.SEVERE;
    }
    else {
      // if "INFO", "TEXT", "UNKNOWN" or default, use level INFO
      // "UNKNOWN" is same as "TEXT" (matches Logger.processMessage(..))
      // default is same as "TEXT" (matches Midas.message(..))
      level = Level.INFO;
    }
    // Since 3.7.2 for NXM-3046, let Midas.message handle sending INTERACTIVE
    // commands to the terminal
//    if (_type.equals("INTERACTIVE")) {
//      // SEND TO CONSOLE AT REQUEST OF USER (This may result in duplicate console output.)
//      if (midas == null) {
//        // Midas can be null following a bootstrap load using no argument constructor
//        midas = Shell.getMidasContext();
//      }
//      midas.terminal.writeln(_text);
//    }
    LOG.logp(level, LOG_CLASS, _from, _text);
  }


  /** This method is used when initializing {@link Midas#DEF_LOGGER} and must be careful since the normal
      Midas context and/or error handling is not initialized at the point where this method is called.
      @return The "bootstrap" (default) logger or null if n/a.
   */
  private static MidasToJavaLog getBootstrapLogger () {
    try {
      LogManager mgr = LogManager.getLogManager();
      if (mgr == null) return null;
      String enable = mgr.getProperty("nxm.sys.lib.NeXtMidas.enable");

      // Don't use Convert.o2z(..) here since we have not initialized the error handling it will need to use
      if ((enable != null) && StringUtil.isTrue(enable)) {
        // Use no-argument version since we don't have a Midas at this point
        return new MidasToJavaLog();
      }
    }
    catch (Throwable t) {
      // Must use System.err since we have not initialized our normal M.error(..) functions
      // by the time this method is called.
      System.err.println("Unable to initialize logging: "+t);
      t.printStackTrace();
    }
    return null;
  }

  /** Re-initialize the "Global" (singleton) MidasToJavaLog instance with specified Midas context
      and java.util logging config properties file.
      @param midas Midas context.
      @param javaUtilLoggingConfigUrl path to the java util logging configuration file (supports
               NeXtMidas IOResource URLs, i.e. inside jar file).
      @return The previous "global" logger or null if n/a.
   */
  static MidasToJavaLog setGlobalInstance (Midas midas, String javaUtilLoggingConfigUrl) {
    final MidasToJavaLog previous = LOGGER;
/* TODO: in the future notify previous "global" Logger that it has been replaced
    if (previous != null) {
      try {
        // get calling method for "from" information
        StackTraceElement[] straceElements = Thread.currentThread().getStackTrace();
        String from = null;
        // [0]=getStackTrace(), [1]=close(), [2]=is calling method
        if (straceElements.length >= 3) {
          from = straceElements[2].toString();
        }
        // Send message informing user/app that previous global Midas to Java Logger is being replaced
        previous.processMessage("CONFIG", "Global MidasToJavaLog is being replaced with Midas=" + midas + " configUrl=" + javaUtilLoggingConfigUrl, from);
      } catch (Throwable t) {
        // Pass: ignore, since we don't want a problem with sending CONFIG msg to prevent changing of the global instance
      }
    }
*/
    try {
      System.setProperty("java.util.logging.config.file", javaUtilLoggingConfigUrl);
      LogManager mgr = LogManager.getLogManager();
      // using IOResource so that logging config file can be loaded from inside a jar file, etc.
      IOResource iores = midas.io.findResource(javaUtilLoggingConfigUrl, IOResource.DIR_INPUT);
      if (iores == null || !iores.exists()) {
        throw new FileNotFoundException();
      }
      if (!iores.open()) {
        throw new IOException("Unable to open logging config file (for read)");
      }
      mgr.readConfiguration(iores.getInputStream());
      iores.close();

      String enable = mgr.getProperty("nxm.sys.lib.NeXtMidas.enable");

      // Don't use Convert.o2z(..) here since we may not have initialized the error handling it will need to use
      if ((enable != null) && StringUtil.isTrue(enable)) {
         // Use argument version since we probably have a Midas at this point
         LOGGER = new MidasToJavaLog(midas);
      }
      return previous;
    }
    catch (IOException io) {
      throw new MidasException("Unable to read logging config file at " + javaUtilLoggingConfigUrl, io);
    }
  }

  /** Get the active "global" Java Logger
      @return the active "global" JavaLogger (null if none)
   */
  public static MidasToJavaLog getGlobalInstance () {
    return LOGGER;
  }

  // File handling is n/a...
  @Override public void open (Object fn) { throw new UnsupportedOperationException(); }
  @Override public void flush () { }
  @Override public void close () { } // <- called when any Macro closes and it's (M.logger != null) which is true when Midas to java logging is enabled
  @Override public void saveOff () { }
  @Override public int  getSize () { return -1; }
}
