package nxm.sys.test.junit.lib;

import java.util.Map;
import java.util.Properties;

import nxm.sys.inc.MapKBT;
import nxm.sys.lib.LogUtilities;
import nxm.sys.lib.Midas;
import nxm.sys.lib.PropsUtilities;
import nxm.sys.lib.Shell;
import nxm.sys.test.junit.NMTestCase;

public class PropsUtilitiesTest extends NMTestCase {

  /** public constructor to allow running this test case directly with JUnit. */
  public PropsUtilitiesTest (String methodName) {
    this(methodName, Shell.getSharedMidasContext());
  }

  /**
   * 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 PropsUtilitiesTest (String methodName, Midas midas) {
    super(methodName, midas);
  }

  public void testLoadProps () {
    String propsFile = "nxm/sys/test/PropsUtilitiesTestFile.properties";
    String[] keys = new String[] { "String.class", "Double.class", "Float.class", "Long.class", "Integer.class",
        "Short.class", "Byte.class", "Boolean.class", "Character.class", "testuser.home", "testuser.name" , "person", "valuewithkey"};
    Class<?>[] classes = new Class<?>[] { String.class, Double.class, Float.class, Long.class, Integer.class,
        Short.class, Byte.class, Boolean.class, Character.class, String.class, String.class, String.class, String.class};
    String[] values = new String[] { "String", "2.0", "3.0", "21474836470", "2147483647", "100", "1", "true", "c", "/home/user", "username", "John", "Hello John"};

    // There should be an equal number of keys to properties size.
    Properties properties = PropsUtilities.loadProps(propsFile);

    assertEquals(keys.length, properties.size());
    for (int i = 0; i < keys.length; i++) {
      assertEquals("key "+keys[i],values[i], PropsUtilities.getPropValue(properties, keys[i], classes[i]).toString());
    }

    // Loading from a MapKBT with the same values as in the properties file
    // should give the same Properties values
    // 1) get original properties as map
    Map<String,String> mapFromProps = LogUtilities.propsAsMap(properties);
    // 2) for expected purposes, substitute in the value with key value
    String person = properties.get("person").toString(); // need to manually substitute person value for MapKBT
    String valueWithKeyValue = properties.get("valuewithkey").toString().replace("${person}", person);
    properties.put("valuewithkey", valueWithKeyValue);
    // 3) Create a MapKBT from the Map created from the untouched properties
    MapKBT kbtProps = MapKBT.wrapMap(mapFromProps);
    // 4) Use the new loadProps API to create a Properties object from a MapKBT
    Properties propertiesKBT = PropsUtilities.loadProps(kbtProps);
    // 5) Compare expected with actual
    assertEquals("loadProps(MapKBT) values equal",properties, propertiesKBT);

    properties = PropsUtilities.loadProps(propsFile, PropsUtilities.SYSTEM_PROPS);
    System.setProperties(properties); // this will change the current system.properties to be the one we are using.

    assertEquals(keys.length, properties.size());
    for (int i = 0; i < keys.length; i++) {
      assertEquals(values[i], PropsUtilities.getPropValue(properties, keys[i], classes[i]).toString());
    }

    // Set the system level properties
    // create object from system properties
    Properties systemProperties = PropsUtilities.loadProps(PropsUtilities.SYSTEM_PROPS);
    properties = PropsUtilities.loadProps(propsFile, systemProperties);

    // Make sure that all the properties are equal
    for(int i = 0; i < keys.length; i++) {
      assertEquals(PropsUtilities.getPropValue(properties, keys[i], classes[i]), PropsUtilities.getPropValue(systemProperties, keys[i], classes[i]));
    }
    // TODO May need to change these asserts. Is there a better way? Maybe find the logic that defines the values and use it to get it? Maybe mock the result?
    // Get the values that are defined by default - they do not need to be defined in the properties file
    assertNotNull("can we do a better assert here",PropsUtilities.getPropValue(systemProperties, "HOST", String.class));
    assertNotNull("can we do a better assert here",PropsUtilities.getPropValue(systemProperties, "HOSTNAME", String.class));
    assertNotNull("can we do a better assert here",PropsUtilities.getPropValue(systemProperties, "PID", Integer.class));
    assertNotNull("can we do a better assert here",PropsUtilities.getPropValue(systemProperties, "USER", String.class));
    assertNotNull("can we do a better assert here",PropsUtilities.getPropValue(systemProperties, "HOME", String.class));

    // Load a properties that only grabs key's that start with a certain prefix
    Properties prefixprops = PropsUtilities.extractProps(properties, "testuser.", false);
    assertEquals("there should be 2 values in the properties", 2, prefixprops.size());
    assertEquals(PropsUtilities.getPropValue(properties, "testuser.name", String.class), PropsUtilities.getPropValue(prefixprops, "name", String.class));
    assertEquals(PropsUtilities.getPropValue(properties, "testuser.home", String.class), PropsUtilities.getPropValue(prefixprops, "home", String.class));


    // Return a default value if the value is not in the Properties object
    String defValue = "this is the default";
    assertEquals(defValue, PropsUtilities.getPropValue(properties, "getDefault", defValue));

    // Set the default value if it is not in the properties object
    PropsUtilities.usePropValue(properties, "addDefault", defValue);
    assertEquals(defValue,PropsUtilities.getPropValue(properties, "addDefault", String.class));

    // Test that we throw the exception in the given situation
    // Trying to get a value that is a string to be returned as an Integer.
    try {
      PropsUtilities.getPropValue(properties, "testuser.home", Integer.class);
      fail("Should not get here as an exception should have been thrown");
    }
    catch (IllegalArgumentException e) {}

    // Test that we throw the exception in the given situation
    // Trying to get a value that is not defined and sending in the def of Requried String
    try {
      PropsUtilities.usePropValue(properties, "notdefined", PropsUtilities.REQUIRED_STRING);
      fail("Should not get here as an exception should have been thrown");
    }
    catch (NullPointerException e) {}
  }
}
