package nxm.sys.test.junit.lib;

import static nxm.sys.lib.MidasToJavaLog.LOG;
import static nxm.sys.lib.MidasToJavaLog.LOG_CLASS;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.FileHandler;
import java.util.logging.Filter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;

import nxm.sys.inc.Debug;
import nxm.sys.lib.BaseFile;
import nxm.sys.lib.FileName;
import nxm.sys.lib.Logger;
import nxm.sys.lib.Midas;
import nxm.sys.lib.MidasToJavaLog;
import nxm.sys.lib.Shell;
import nxm.sys.test.junit.NMTestCase;

/** This will test the MidasToJavaLog class.
    @author  J. Czechowski IV / Jeanette McCall
    @since NeXtMidas 3.5.4
 */
public final class MidasToJavaLogTest extends NMTestCase {

  private Midas M;
  private Logger midasToJavaLogger;
  private Logger originalLoggerBeforeTest;
  private Logger originalDefaultLoggerBeforeTest;

  /** Public constructor to allow running this test case directly with JUnit.
      @param name The name to use.
   */
  public MidasToJavaLogTest (String name) {
    this(name, null);
  }

  /** Creates a new instance of the class.
      This constructor is required by NeXtMidas Unit Testing framework.
      @param methodName The name of the method being called.
      @param midas      The midas context testTo use when running the tests.
   */
  public MidasToJavaLogTest (String methodName, Midas midas) {
    super(methodName, midas);
  }

  private static final Object[] testCases = {
    Midas.INTERACTIVE, Level.INFO,  "Sample INTERACTIVE message",   "InteractiveTest",
    Midas.ERROR,     Level.SEVERE,  "Sample ERROR message",        "ErrorTest",
    Midas.WARNING,   Level.WARNING, "Sample WARNING message",      "WarningTest",
    Midas.INFO,      Level.INFO,    "Sample INFO message",         "InfoTest",
    Midas.DEPRECATE, Level.CONFIG,  "Sample DEPRECATE message",    "DeprecateTest",
    -999,            Level.INFO,    "Sample message without type", "GenericTest",
  };

  /** Programmatically use Midas to Java util Logger to use our test java util logging config/properties file. */
  @Override
  public void setUp () {
    M = getMidas();
    assertNotNull("Midas context must not be null", M);
    String javaUtilLoggingConfigFile = urlSysTest+"logging.properties";
    originalLoggerBeforeTest = M.getLogger();
    originalDefaultLoggerBeforeTest = Midas.getDefaultLogger();
    M.useMidasToJavaLog(javaUtilLoggingConfigFile);
    midasToJavaLogger = M.getLogger();
    assertNotNull("MidasToJavaLog must not be null", midasToJavaLogger);
    assertEquals(MidasToJavaLog.class, midasToJavaLogger.getClass());
    Midas.setDefaultLogger(midasToJavaLogger);
    assertTrue("Java Logger Enabled", M.logger instanceof MidasToJavaLog);
  }

  @Override
  public void tearDown () {
    M.setLogger(originalLoggerBeforeTest);
    Midas.setDefaultLogger(originalDefaultLoggerBeforeTest);
  }

  public void testGetGlobalInstance () {
    MidasToJavaLog globalInstance = MidasToJavaLog.getGlobalInstance();
    assertEquals(midasToJavaLogger, globalInstance);
  }

  public void testProcessMessage () {

    if (Shell.isGradleBuild()) {
      info("Skipping testing testProcessMessage when running from Gradle Build, as it involves creating a log file");
      return;
    }

    // Get information to also log to file in AUX.WRITE
    FileName fn = FileName.getFileName("AUX", "WRITE", "nxmlogs_" + Shell.getNmPID(), "txt", null);
    String logFilePath = fn.toString();
    Handler     fileHandler        = null;

    TempHandler tempLogHandler     = new TempHandler();
    Filter      saveFilter         = LOG.getFilter();
    Handler[]   saveHandlers       = LOG.getHandlers();
    Level       saveLevel          = LOG.getLevel();
    boolean     saveParentHandlers = LOG.getUseParentHandlers();

    LOG.setFilter(null);
    for (Handler h : saveHandlers) LOG.removeHandler(h); // remove all other handlers
    LOG.addHandler(tempLogHandler); // add temp Log Handler for testing
    LOG.setLevel(Level.ALL);
    LOG.setUseParentHandlers(false);

    try {
      // also write to file
      fileHandler = new FileHandler(logFilePath);
      LOG.addHandler(fileHandler);

      for (int i = 0; i < testCases.length; i+=4) {
        int    levelMidas = (Integer)testCases[i+0];
        Level  levelJava  = (Level  )testCases[i+1];
        String text       = (String )testCases[i+2];
        String from       = (String )testCases[i+3];

        int expectedNumMessages = 3;
        // 1st message
        switch (levelMidas) {
          case Midas.INTERACTIVE: M.message(text, levelMidas); break;
          case Midas.ERROR:     M.message(text, levelMidas); break; // don't use M.error(..) since it throws exception!
          case Midas.WARNING:   M.warning(text);             break;
          case Midas.INFO:      M.info(text);                break;
          case Midas.DEPRECATE:
            M.deprecate(text);
            if (! M.debug(Debug.DEPRECATE)) {
              expectedNumMessages--; // deprecation is turned off, so decrement expected # msgs
            }
            break;
          default:              M.message(text, levelMidas); break; // no matching M.xxx(..) function
        }
        M.message(text, levelMidas);       // 2nd message
        M.message(text, levelMidas, from); // 3rd message
        assertEquals("Got correct number of log messages for severity=" + levelMidas + " javaLevel=" + levelJava,
            expectedNumMessages, tempLogHandler.records.size());

        for (int j = 0; j < tempLogHandler.records.size(); j++) {
          LogRecord r = tempLogHandler.records.get(j);

          assertEquals("Correct level", levelJava, r.getLevel());
          assertEquals("Correct text",  text,      r.getMessage());
          assertEquals("Correct class", LOG_CLASS, r.getSourceClassName());
          if (j == 2) {
            // Don't check other forms with the 'method' since they will vary based on how
            // this unittest is called (e.g. when run from CONFIDENCE it will be "CONFIDENCE")
            assertEquals("Correct method", from, r.getSourceMethodName());
          }
        }
        tempLogHandler.records.clear();
      }
      checkLogFile(logFilePath); // Check the log file
    }
    catch (IOException io) {
      fail("Problems opening log file: " + logFilePath);
    }
    finally {
      LOG.setFilter(saveFilter);
      for (Handler h : LOG.getHandlers()) LOG.removeHandler(h);
      for (Handler h : saveHandlers     ) LOG.addHandler(h);
      LOG.setLevel(saveLevel);
      LOG.setUseParentHandlers(saveParentHandlers);
      if (fileHandler != null) {
        fileHandler.close();
      }
      BaseFile.delete(logFilePath);
    }
  }

  /** Check that Log File contains the different test messages
      @param filePath complete file path
   */
  private void checkLogFile (String filePath) {
    BufferedReader br = null;
    try {
      br = new BufferedReader(new FileReader(filePath));
      StringBuilder sb = new StringBuilder();
      String line = br.readLine();

      while (line != null) {
        sb.append(line);
        sb.append("/n");
        line = br.readLine();
      }
      String everything = sb.toString(); // get entire log file as string

      // Check that each message text is present
      for (int i = 0; i < testCases.length; i+=4) {
        String text = (String )testCases[i+2];
        boolean found = (everything.indexOf(text) > -1);
        assertTrue("Log File contains:"+ text, found);
      }
    } catch (IOException io) {
      io.printStackTrace();
      fail("Error reading log file: " + io);
    } finally {
      try {
        br.close();
      } catch (IOException e) {
        fail("Error closing log file: " + e);
      }
    }
  }

  public void testGetSize () {
    assertEquals("Log size", -1, midasToJavaLogger.getSize());
  }

  public void testClose () {
    midasToJavaLogger.close();
    assertTrue("No errors calling close()", true);
  }

  public void testFlush () {
    midasToJavaLogger.flush();
    assertTrue("No errors calling flush()", true);
  }

  public void testSaveOff () {
    midasToJavaLogger.saveOff();
    assertTrue("No errors calling saveOff()", true);
  }

  public void testOpen () {
    assertException("UnsupportedOperationException calling open(..)", UnsupportedOperationException.class, new Runnable() {
      @Override
      public void run () {
        midasToJavaLogger.open("abc123");
      }
    });
  }

  /** A temporary log handler that stores messages in a list. */
  private static final class TempHandler extends Handler {
    ArrayList<LogRecord> records = new ArrayList<>(); // the collected log records

    /** Creates a new instance. */
    TempHandler () {
      super.setLevel(Level.ALL);
    }

    @Override public void close () { }
    @Override public void flush () { }

    @Override
    public void publish (LogRecord record) {
      records.add(record);
    }
  }
}