package nxm.sys.test.junit.lib;

import static nxm.sys.test.junit.IsDataEqual.equalToDataObj;
import static org.junit.Assert.assertThat;

import java.util.Collection;
import java.util.Map;

import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.FileUtil;
import nxm.sys.lib.Keywords;
import nxm.sys.lib.Message;
import nxm.sys.lib.MessageQueue;
import nxm.sys.lib.Midas;
import nxm.sys.lib.NeXtMidas;
import nxm.sys.lib.Table;
import nxm.sys.lib.Util;
import nxm.sys.test.junit.NMTestCase;

/** This will test the Time class.
    @author  J. Czechowski IV / Neon Ngo
    @since   NeXtMidas 3.3.0
 */
public final class KeywordsTest extends NMTestCase {

  /** public constructor to allow running this test case directly with JUnit. */
  public KeywordsTest (String methodName) {
    this(methodName, NeXtMidas.getGlobalInstance().getMidas()); // testPutMain() requires a valid Midas context, so use global instance
  }

  /** Creates a new instance of the class.
      @param methodName The name of the method being called.
      @param midas      The midas context testTo use when running the tests.
   */
  public KeywordsTest (String methodName, Midas midas) {
    super(methodName, midas);
  }

  /** Added in NeXtMidas 3.9.1 to test deleteScope when extended header buf not initialized */
  public void testDeleteScope () {
    Midas M = getMidasContext();
    boolean successful = false;
    DataFile origDataFile = new DataFile(M, urlDat+"test1000.prm");
    DataFile newDataFile = new DataFile(M,"test_keywords_deletescope.tmp{AUX=RAM}",origDataFile,0);
    Keywords newDataFileKW = newDataFile.getKeywordsObject();
    try {
      info("!!!!! It is ok to see an info below for 'Keywords: No EXT keywords deleted ...' !!!!!");
      newDataFileKW.deleteScope("EXT");
      successful = true;
    }
    catch (NullPointerException npe) {
      fail("Keywords.deleteScope threw and NPE because the Keywords.buf is uninitialized");
    }
    finally {
      newDataFile.close();
    }
    assertTrue("Keywords.deleteScope handled case where Keywords.buf is uninitialized", successful);
  }

  /** Added in NeXtMidas 3.9.1 to test that getAll can now be called with "EXTENDED" */
  public void testGetAll () {
    Midas M = getMidasContext();
    DataFile dataFileExtHeader = new DataFile(M,urlDat+"test2000_tcprec.prm");
    dataFileExtHeader.open();
    Collection<Map.Entry<String,Object>> extHeaderKW = dataFileExtHeader.getKeywordsObject().getAll("EXTENDED",true);
    // Expecting 2 extended header keywords in this file. If this file is updated, this test will need updating
    assertTrue("Keywords.getAll", extHeaderKW.size() == 2);
    dataFileExtHeader.close();
  }

  public void testPutMain () {
    String LONG_VALUE = "0123456789012345678901234567890123456789"
                      + "0123456789012345678901234567890123456789"; // 80 chars

    Object[] TEST_CASES = {
      // KEYWORD        VALUE            WARN?
      "CREATOR",        "NXM3.3.0",     false,
      "IO",             "NeXtMidas",    false,
      "PACKET",         "--junk--",     false,
      "PKT_BYTE_COUNT", "--junk--",     false,
      "TC_PREC",        "--junk--",     false,
      "VER",            "1.1",          false,

      "ACQDATE",        "2012.123",     false,
      "ACQTIME",        "12:00:00",     false,
      "COMMENT",        "Main comment", false,

      "JUNK",           "123",          true,
      "COMMENT1",       "1st comment",  true,
      "COMMENT2",       "2nd comment",  true,
      "COMMENT3",       "3rd comment",  true,
    };

    Midas m = getMidasContext().cloneOf();
    // since 3.5.4 - next three lines setup to capture warn messages to a "LOG" message(s) via the Midas.msglog mechanism
    MessageQueue mq = new MessageQueue(m, this);
    String msgid = Util.getClassName(getClass()).toUpperCase();
    msgid = m.registry.putInstance(msgid, mq);
    m.msglog = msgid;

    DataFile df = new DataFile(m, "ram:keywords_test");
    Keywords kw = df.getKeywordsObject();

    info("!!!!! It is ok to see ~9 warnings below for 'Keywords: ...' !!!!!");
    assertContainsWarnMsg(mq, "No warnings on opening file", null);

    for (int i = 0; i < TEST_CASES.length; i+=3) {
      String  key  = (String )TEST_CASES[i ];
      String  val  = (String )TEST_CASES[i+1];
      boolean warn = (Boolean)TEST_CASES[i+2];
      String  contains1 = (warn)? key : null;
      String  contains2 = contains1; // default is to warn
      String  contains3 = null;

      kw.putMain(key, val       );
      assertContainsWarnMsg(mq, "Warning when inserting "+key+" [warn=null]", contains1);
      kw.deleteMain(key);

      kw.putMain(key, val, true );
      assertContainsWarnMsg(mq, "Warning when inserting "+key+" [warn=null]", contains2);
      kw.deleteMain(key);

      kw.putMain(key, val, false);
      assertContainsWarnMsg(mq, "Warning when inserting "+key+" [warn=false]", contains3);
      kw.deleteMain(key);
    }

    kw.putMain("COMMENT", LONG_VALUE);
    assertContainsWarnMsg(mq, "Warning when inserting long COMMENT", null);

    kw.putMain("TC_PREC", "1.23456e-14");
    assertContainsWarnMsg(mq, "Warning when inserting TC_PREC", "COMMENT");

    df.close();
  }

  /**
      @param mq The message queue that got the message were routed to
      @param assertMessage  A description of the test.
      @param contains "Some" of the text expected in the warning message.
      @since NeXtMidas 3.5.4
   */
  private void assertContainsWarnMsg (MessageQueue mq, final String assertMessage, final String contains) {
    Message msg = mq.get();
    while (msg != null) {
      if ("LOG".equals(msg.getName()) && msg.getInfo() == Midas.WARNING) {
        if (contains == null) { // should not get any warning messages
          fail(assertMessage);
        }
        Object msgData = msg.getData();
        if (! (msgData != null && msgData.toString().contains(contains))) {
          fail(assertMessage + " contains=" + contains + " actual=" + msgData);
        }
      }
      msg = mq.get();
    }
    assertTrue(assertMessage, true);
  }

  /**
   * @since NeXtMidas 3.7.0
   */
  public void testPut () {
    Midas M = getMidas();
    // begin byte order test for BUG 3009
    String[] byteOrders = {"IEEE", "EEEI" };
    Data[] testdata = { new Data( (short) 1),        // 1. SI
                        new Data("CF", 1, 2.0),      // 2. CF
                        new Data("VD", 1, 3.0),      // 3. VD
                        new Data("QD", 1, 4.0),      // 4. QD
                        new Data(5, 6, 7, 8, 9),     // 5. SL x5
//                      new Data(6L, 7L),            // 6. SX x2 TODO: skipped since format becomes CX x1
//                      new Data(7.0, 8.0, 9.0),     // 7. SD x3 TODO: skipped since format becomes VD x1
//                      new Data("CF", 4, 8.0f),     // 8. CF x4 TODO: skipped since format becomes SF x8
                      };

    for (String byteOrder : byteOrders) {
      DataFile df = new DataFile(M, "ram:keywords_byte_order_" + byteOrder);
      df.open(DataFile.OUTPUT);       // create file in current machine's byte endianess
      df.convertHeaderRep(byteOrder); // convert main header/extend header to desired byte order

      Keywords kw = df.getKeywordsObject();
      int ii = 1;
      for (Data expectedData: testdata) {
        String key = "KEYWORD_" + ii;
        // Note: need to get a copy of Data, b/c if the byte order is swapped, it would touch original Data object
        kw.put(key, expectedData.copy());
        Object val = kw.get(key);
        assertThat("DataFile (HR="+ df.getHeadRep() + ") " + key + " value - " + expectedData.getFormat(), val, equalToDataObj(expectedData));
        ii++;
      }

      df.close();
      df.erase(true); // cleanup
    }
    // end byte order tests for BUG 3009
  }

  public void testMakeTable () {

    Midas m = getMidasContext().cloneOf();
    String inputFN = FileUtil.getCanonicalPath(urlSysTest+"testplat4plat2blue_cov.cdif");
    DataFile df = new DataFile(m, inputFN);
    df.open(DataFile.INPUT);   // open input file as INPUT - corrected in 3.7.1
    DataFile output = new DataFile(m,"ram:testplatkeyword_put");
    output.open(DataFile.OUTPUT);

    Table tbl = df.keywords.makeTable(false);  // create the table

    // This test compares the values as strings.  This test does not compare value types e.g D, L, etc
    for (Map.Entry<String,Object> kwd : df.keywords.getAll("EXT", false)) {
      assertEquals("testMakeTable: Input DataFile key value equals new Table key value ",
          kwd.getValue().toString(), tbl.getTable("EXTENDED").getS(kwd.getKey().replace(".", Keywords.getDotSubstitute())));
    }
    // Put extended table key into DataFile 'output' where "@@" will be modified to "."
    String[] st = tbl.getTable("EXTENDED").getKeyList().split( ",");
    for (int i=0; i<st.length; i++) {
      output.getKeywordsObject().put(st[i], tbl.getTable("EXTENDED").getS(st[i]));
    }

    // Test to ensure key values were modified in set/put
    for (Map.Entry<String, Object> okw : output.keywords.getAll("EXT", false)) {
      assertEquals("testMakeTable: Output DataFile key value equals Table key value. ",
          tbl.getTable("EXTENDED").getS(okw.getKey().replace(".", Keywords.getDotSubstitute())), okw.getValue().toString());
    }

    df.close();
    output.close();
    output.erase(true);
   }

  /**
   *  This tests that setting the dotted keywords works correctly.
   */
  public void testMakeTableWithDottedKeywords () {

    Midas m = getMidasContext().cloneOf();
    String origDotSubstitute = Keywords.getDotSubstitute();
    String substituteValue = "***dot***";
    Keywords.setDotSubstitute(substituteValue);
    String inputFN = FileUtil.getCanonicalPath(urlSysTest+"testplat4plat2blue_cov.cdif");
    DataFile df = new DataFile(m, inputFN);
    df.open(DataFile.INPUT);   // open input file as INPUT - corrected in 3.7.1
    DataFile output = new DataFile(m,"ram:testplatkeyword_put");
    output.open(DataFile.OUTPUT);

    Table tbl = df.keywords.makeTable(false);  // create the table

    // This test compares the values as strings.  This test does not compare value types e.g D, L, etc
    for (Map.Entry<String,Object> kwd : df.keywords.getAll("EXT", false)) {
      assertEquals("testMakeTable: Input DataFile key value equals new Table key value ",
          kwd.getValue().toString(), tbl.getTable("EXTENDED").getS(kwd.getKey().replace(".", substituteValue)));
    }
    // Put extended table key into DataFile 'output' where "@@" will be modified to "."
    String[] st = tbl.getTable("EXTENDED").getKeyList().split( ",");
    for (int i=0; i<st.length; i++) {
      output.getKeywordsObject().put(st[i], tbl.getTable("EXTENDED").getS(st[i]));
    }

    // Test to ensure key values were modified in set/put
    for (Map.Entry<String, Object> okw : output.keywords.getAll("EXT", false)) {
      assertEquals("testMakeTable: Output DataFile key value equals Table key value. ",
          tbl.getTable("EXTENDED").getS(okw.getKey().replace(".", substituteValue)), okw.getValue().toString());
    }

    df.close();
    output.close();
    output.erase(true);

    Keywords.setDotSubstitute(origDotSubstitute);
   }

  /**
   * Test the Keywords.putAll method
   *
   * Specifically tests setting keywords from a table
   * that contains values that are Objects, where the
   * Objects are of subclass of Number and the type is
   * not directly specified in the key
   *
   *  NXM-3007 Keywords: Putting keywords fails
   *
   *  @since NeXtMidas 3.7.2
   */
  public void testPutAll() {
    Midas m = getMidasContext().cloneOf();
    DataFile output = new DataFile(m,"ram:testSetkeyword");
    output.open(DataFile.OUTPUT);
    Keywords kw = output.getKeywordsObject();
    Table tbl = kw.makeTable(false);  // create the table
    Integer one = 1;
    Double two = 2.0;
    tbl.put("ONE", one);  // Put Integer Object in table without declaring type
    tbl.put("TWO", two);  // Put in Double Object
    kw.putAll(tbl);
    assertEquals("testPutAll: Keyword set correctly from table Integer value. ",
        kw.getKey("ONE"),new Data(1));
    assertEquals("testPutAll: Keyword set correctly from table Double value. ",
        kw.getKey("TWO"), new Data(2.0));
    output.close();
    output.erase(true);
  }

  public void testSet () {} // tested in testPut, testMakeTable
  public void testGet () {} // tested in testPut
}