package nxm.sys.test.junit.lib;

import java.util.Arrays;

import nxm.sys.lib.*;
import nxm.sys.test.junit.NMTestCase;

/** This will test the KeyVector class.

    @author  J. Czechowski IV
    @since   NeXtMidas 2.1.0
 */
public final class KeyVectorTest extends NMTestCase {
  /** public constructor to allow running this test case directly with JUnit. */
  public KeyVectorTest (String methodName) { this(methodName, null); }

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

  //////////////////////////////////////////////////////////////////////////////////////////////////
  private static final Integer ZERO   = Integer.valueOf(0);
  private static final Integer ONE    = Integer.valueOf(1);
  private static final Integer TWO    = Integer.valueOf(2);
  private static final Integer THREE  = Integer.valueOf(3);
  private static final Integer FOUR   = Integer.valueOf(4);
  private static final Integer TEN    = Integer.valueOf(10);
  private static final Integer TWENTY = Integer.valueOf(20);

  private static final int[]    TEST_INDEX = { 0, 1, 2, 3, 4, -1, -1 };
  private static final String[] TEST_LIST  = { "ZERO", "ONE", "TWO", "THREE", "FOUR", "JUNK", "FoUr" };
  private static final Object[] TEST_VALUE = { ZERO, ONE, TWO, THREE, FOUR, null, null };

  /** Creates a sample table to use.
      @return A sample table to use.
   */
  private KeyVector makeSampleKeyVector(boolean duplicates) {
    KeyVector kv = new KeyVector();

    for (int i = 0; i < TEST_LIST.length; i++) {
      if (TEST_VALUE[i] != null) {
        kv.put(TEST_LIST[i], TEST_VALUE[i]);
      }
    }
    if (duplicates) {
      kv.add("ZERO",  "ZERO");
      kv.add("TWO",   "TWO");
      kv.add("FOUR",  "FOUR");
    }
    return kv;
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // TEST METHODS
  //////////////////////////////////////////////////////////////////////////////////////////////////
  public void testDump ()   { } // No applicable tests
  public void testExpand () { } // Deprecated internal method, no tests

  private void bug873_Test() {
    KeyVector kv = makeSampleKeyVector(true);

    kv.add("THREE", "THREE");
    assertEquals("get('THREE') is 3", THREE, kv.get("THREE"));
  }

  public void testAdd() {
    bug873_Test();

    KeyVector kv = new KeyVector();
    kv.add("ZERO",  ZERO);
    kv.add("ONE",   ONE);
    kv.add("TWO",   TWO);
    kv.add("THREE", THREE);
    assertEquals("get(0) is 0", ZERO,  kv.get(0));
    assertEquals("get(1) is 1", ONE,   kv.get(1));
    assertEquals("get(2) is 2", TWO,   kv.get(2));
    assertEquals("get(3) is 3", THREE, kv.get(3));
  }

  public void testGetKeyList() {
    KeyVector kv    = makeSampleKeyVector(false);
    String    lkv   = "ZERO,ONE,TWO,THREE,FOUR";
    String    lkv13 = "ONE,TWO";
    KeyVector kvd   = makeSampleKeyVector(true);
    String    lkvd  = "ZERO,ONE,TWO,THREE,FOUR,ZERO,TWO,FOUR";

    assertEquals("getKeyList() is '"+lkv+"'",      lkv,    kv.getKeyList());
    assertEquals("getKeyList() is '"+lkvd+"'",     lkvd,   kvd.getKeyList());
    assertEquals("getKeyList(1,3) is '"+lkv13+"'", lkv13,  kv.getKeyList(1,3));
  }

  public void testGetKeys() {
    KeyVector kv    = makeSampleKeyVector(false);
    String[]  keys  = kv.getKeys();
    int       count = 0;
    boolean   found = false;
    Arrays.sort(keys);

    for (int i = 0; i < TEST_LIST.length; i++) {
      if (TEST_VALUE[i] != null) {
        count++;
        found = (Arrays.binarySearch(keys,TEST_LIST[i]) >= 0);
        assertTrue("getKeys() -> contains "+TEST_LIST[i], found);
      }
    }

    assertEquals("getKeys() -> number of keys", count, keys.length);
  }

  public void testSortByKey() {
    KeyVector kv        = makeSampleKeyVector(false);
    String    unsorted  = "ZERO,ONE,TWO,THREE,FOUR";
    String    sorted    = "FOUR,ONE,THREE,TWO,ZERO";
    String    sortedRev = "ZERO,TWO,THREE,ONE,FOUR";

                         assertEquals("sortByKey(false) initial",unsorted,  kv.getKeyList());
    kv.sortByKey(false); assertEquals("sortByKey(false) sorted", sorted,    kv.getKeyList());
    kv.sortByKey(true);  assertEquals("sortByKey(true)  sorted", sortedRev, kv.getKeyList());


    // Bug 1371 - Sort broken (with duplicate keys)
    String[]  keys   = { "ZERO","ONE","TWO","THREE","FOUR","ONE","TWO"};
    Integer[] values = {  ZERO,  ONE,  TWO,  THREE,  FOUR,  TEN,  TWENTY };
    kv.clear();
    for (int i = 0; i < keys.length; i++) {
      kv.add(keys[i], values[i]);
    }
    String unsorted2  = StringUtil.join(keys,",");
    String sorted2    = "FOUR,ONE,ONE,THREE,TWO,TWO,ZERO";
    String sortedRev2 = "ZERO,TWO,TWO,THREE,ONE,ONE,FOUR";

    checkKV("sortByKey(false) initial (duplicates)", kv, unsorted2, keys, values);
    kv.sortByKey(false);
    checkKV("sortByKey(false) sorted  (duplicates)", kv, sorted2, keys, values);
    kv.sortByKey(true);
    checkKV("sortByKey(true)  sorted  (duplicates)", kv, sortedRev2, keys, values);
  }

  private void checkKV(String descr, KeyVector kv, String list, String[] keys, Object[] values) {
    assertEquals(descr, list, kv.getKeyList());

    for (int i = 0; i < values.length; i++) {
      Object val = values[i];
      String key = keys[i];
      int    idx = kv.findValue(val);

      assertTrue(descr+" found value "+val, (idx >= 0));
      assertEquals(descr+" value "+val+" with key "+key, key, kv.getKey(idx));
    }
  }

  public void testPut() {
    KeyVector kv = makeSampleKeyVector(false);

    kv.put(1,"TEN",TEN);
    assertEquals("put(1,'TEN', 10) -> get(1) is 10",       TEN,  kv.get(1));
    assertEquals("put(1,'TEN', 10) -> get('TEN') is 10",   TEN,  kv.get("TEN"));
    assertEquals("put(1,'TEN', 10) -> get('ONE') is null", null, kv.get("ONE"));

    kv.put("TWO",TEN);
    assertEquals("put('TWO', 10) -> get(2) is 10",       TEN,  kv.get(2));
    assertEquals("put('TWO', 10) -> get('TWO') is 10",   TEN,  kv.get("TWO"));
  }

  public void testSet() {
    KeyVector kv = makeSampleKeyVector(false);

    kv.set(1,"TEN",TEN);
    assertEquals("set(1,'TEN', 10) -> get(1) is 10",       TEN,  kv.get(1));
    assertEquals("set(1,'TEN', 10) -> get('TEN') is 10",   TEN,  kv.get("TEN"));
    assertEquals("set(1,'TEN', 10) -> get('ONE') is null", null, kv.get("ONE"));

    kv.set("TWO",TEN);
    assertEquals("set('TWO', 10) -> get(2) is 10",       TEN,  kv.get(2));
    assertEquals("set('TWO', 10) -> get('TWO') is 10",   TEN,  kv.get("TWO"));
  }

  public void testGet() {
    KeyVector kv = makeSampleKeyVector(false);

    for (int i = 0; i < TEST_VALUE.length; i++) {
      assertSame("get("+i+")",               TEST_VALUE[i], kv.get(i));
      assertSame("get("+TEST_LIST[i]+")",    TEST_VALUE[i], kv.get(TEST_LIST[i]));
      assertSame("getKey("+TEST_LIST[i]+")", TEST_VALUE[i], kv.getKey(TEST_LIST[i]));
    }
  }

  public void testRename() {
    KeyVector kv = makeSampleKeyVector(false);

    kv.rename("TWO", "TEN");
    for (int i = 0; i < TEST_VALUE.length; i++) {
      String key = TEST_LIST[i];

      assertSame("rename('TWO,'TEN') --> get("+i+")",   TEST_VALUE[i], kv.get(i));
      if (!key.equals("TWO")) {
        assertSame("rename('TWO,'TEN') --> get("+key+")", TEST_VALUE[i], kv.get(key));
      } else {
        assertSame("rename('TWO,'TEN') --> get('TWO')", null,          kv.get("TWO"));
        assertSame("rename('TWO,'TEN') --> get('TEN')", TEST_VALUE[i], kv.get("TEN"));
      }
    }
  }

  public void testGetKey() {
    // Mostly tested in testGet
    KeyVector kv         = makeSampleKeyVector(false);
    String    size       = Integer.toString(kv.size());
    String    keyList    = kv.getKeyList();
    String[]  keys       = kv.getKeys();
    String    keysSize   = Integer.toString(keys.length);
    String[]  passedKeys = (String[]) kv.getKey("GETKEYS");
    String    passedSize = Integer.toString(passedKeys.length);
    boolean   found      = false;

    Arrays.sort(keys);

    for (int i = 0; i < keys.length; i++) {
      if (keys[i] != null) {
        found = (Arrays.binarySearch(keys,passedKeys[i]) >= 0);
        assertTrue("getKey('GETKEYS') --> contains "+passedKeys[i], found);
      }
    }

    assertEquals("getKey('GETKEYS') count", keysSize, passedSize);
    assertNull  ("getKey('GETPREV')",       kv.getKey("GETPREV"));
    assertEquals("getKey('GETSIZE')",       size,    kv.getKey("GETSIZE").toString());
    assertEquals("getKey('GETLIST')",       keyList, kv.getKey("GETLIST").toString());
  }

  public void testClear() {
    KeyVector kv = makeSampleKeyVector(false);

    kv.clear();
    assertEquals("clear() -> size() is 0",         0,    kv.size());
    for (int i = 0; i < TEST_VALUE.length; i++) {
      assertSame("get("+i+")",            null,  kv.get(i));
      assertSame("get("+TEST_LIST[i]+")", null,  kv.get(TEST_LIST[i]));
    }
  }

  public void testSetKey() {
    KeyVector kv = makeSampleKeyVector(false);
    int i = kv.size();

    kv.setKey("TEN",TEN);
    assertEquals("setKey('TEN', 10) -> get("+i+")",   TEN,  kv.get(i));
    assertEquals("setKey('TEN', 10) -> get('TEN')",   TEN,  kv.get("TEN"));

    kv.setKey("TWO",TEN);
    assertEquals("setKey('TWO', 10) -> get(2)",       TEN,  kv.get(2));
    assertEquals("setKey('TWO', 10) -> get('TWO')",   TEN,  kv.get("TWO"));
  }

  public void testSize() {
    KeyVector kv = new KeyVector();

    assertEquals("size() is 0", 0, kv.size());
    for (int i = 0; i < 10; i++) {
      String str = "F"+i;
      int size = i+1;

      kv.put(str,str);
      assertEquals("put('"+str+"',str) -> size()", size, kv.size());
      assertEquals("put('"+str+"',str) -> getSize()", kv.size(), kv.getSize());
    }
  }

  public void testGetSize() { } // tested in testSize()

  public void testRemove() {
    KeyVector kv = new KeyVector();

    assertEquals("size() is 0", 0, kv.size());
    for (int i = 0; i < 10; i++) {
      String str = "F"+i;
      kv.put(str,str);
    }

    for (int i = 0; i < 10; i++) {
      String str = "F"+i;
      int size = 9-i;
      kv.remove(str);
      assertEquals("remove('"+str+"') -> size()",        size, kv.size());
      assertEquals("remove('"+str+"') -> get('"+str+"')", null, kv.get(str));
    }
  }

  public void testCopy() {
    KeyVector kv1 = makeSampleKeyVector(false);
    KeyVector kv2 = kv1.copy();

    assertEquals("copy() -> size() matches", kv1.size(), kv2.size());
    for (int i = 0; i < TEST_LIST.length; i++) {
      assertSame("copy() -> getKey('"+TEST_LIST[i]+"')", kv1.get(TEST_LIST[i]), kv2.get(TEST_LIST[i]));
    }
  }

  public void testFind() {
    KeyVector kv = makeSampleKeyVector(false);

    for (int i = 0; i < TEST_LIST.length; i++) {
      assertEquals("find('"+TEST_LIST[i]+"')", TEST_INDEX[i], kv.find(TEST_LIST[i]));
      assertEquals("findKey('"+TEST_LIST[i]+"')", TEST_INDEX[i], kv.findKey(TEST_LIST[i]));
    }
  }

  public void testFindKey() { } // tested in testFind()

  public void testFindValue() {
    KeyVector kv = makeSampleKeyVector(false);

    for (int i = 0; i < TEST_VALUE.length; i++) {
      Object val = (TEST_VALUE[i] != null)? TEST_VALUE[i]: "JUNK"+i;
      assertEquals("findValue('"+val+"') is "+i, TEST_INDEX[i], kv.findValue(val));
    }
  }

  public void testMerge () {
    final String[] KEYS = { "ZERO", "ONE", "TWO", "THREE", "FOUR", "ZERO", "TEN", "TWENTY" };
    final Object[] VALS = {  ZERO,   ONE,   TWO,   THREE,   FOUR,  "ZERO", "TEN", "TWENTY" };

    KeyVector kv1 = new KeyVector();
    KeyVector kv2 = new KeyVector();
    KeyVector k   = new KeyVector();
    Table     t   = new Table();

    for (int i = 0; i < 5; i++) {
      kv1.add(KEYS[i], VALS[i]);
      kv2.add(KEYS[i], VALS[i]);
    }
    for (int i = 5; i < KEYS.length; i++) {
      k.add(KEYS[i], VALS[i]);
      t.put(KEYS[i], VALS[i]);
    }
    kv1.merge(k);
    kv2.merge(t);

    for (int i = 0; i < KEYS.length; i++) {
      assertEquals("merge() with KeyVector (key at "+i+")", KEYS[i], kv1.getKey(i));
      assertEquals("merge() with KeyVector (val at "+i+")", VALS[i], kv1.get(i));
      assertEquals("merge() with Table (key at "+i+")",     KEYS[i], kv2.getKey(i));
      assertEquals("merge() with Table (val at "+i+")",     VALS[i], kv2.get(i));
    }
  }

  // Since 3.5.3 Bug 2876 2883, toString uses default Table flags +
  // DisableEscapeSequences, to give an option to serialize Time objects as T:...
  public void testToString() {
    Time t = new Time("T:2015:03:04::12:34:56.789");
    int defaultTableFlags = Table.getDefaultFlagsInt();

    // Create KeyVector with Time object
    KeyVector kv = new KeyVector();
    kv.add("KEYD",  12.34);
    kv.add("TVAL", t);

    // Test toString with Table default flag of SerializeTimeAsTColon as true
    // (and illustrate that DisableEscapeSecquences flag is not used)
    Table.setDefaultFlags("+SerializeTimeAsTColon");
    // illustrate that DisableEscapeSecquences flag is not used be ensuring disable is false
    Table.setDefaultFlags("-DisableEscapeSequences");
    String keyVectorString_TC = kv.toString();
    assertEquals("KeyVector.toString with Time object serialized as T:<FULL_STD>",
        "{KEYD=12.34,TVAL=T:2015:03:04::12:34:56.789000000000}",keyVectorString_TC);

    // Test toString with Table default flag of SerializeTimeAsTColon as true
    Table.setDefaultFlags("-SerializeTimeAsTColon");
    // Also check with DisableEscapeSecquences flag true to make sure masking logic correct
    Table.setDefaultFlags("+DisableEscapeSequences");
    String keyVectorString = kv.toString();
    assertEquals("KeyVector.toString with Time object serialized as STD Time String",
        "{KEYD=12.34,TVAL=\"2015:03:04::12:34:56.789\"}",keyVectorString);

    // restore default Table flags
    Table.setDefaultFlags(defaultTableFlags);
  }
}
