package com.github.lightcopy;

import java.util.HashSet;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Thread-safe configuration for application, uses concurrent hashmap internally, therefore only
 * non-null keys and values are allowed. All keys and values are trimmed to remove whitespaces. When
 * fetching with default value, exception is not propagated, but default value is returned instead.
 */
public class Configuration {
  private static Logger LOG = LoggerFactory.getLogger(Configuration.class);

  // Available options and default parameters
  // Web server settings (host, port, scheme)
  public static final String HTTP_HOST_KEY = "http.host";
  public static final String HTTP_HOST_DEFAULT = "0.0.0.0";
  public static final String HTTP_PORT_KEY = "http.port";
  public static final int HTTP_PORT_DEFAULT = 8080;
  public static final String HTTP_SCHEME = "http";
  // HDFS settings (host, port)
  public static final String HDFS_HOST = "hdfs.host";
  public static final String HDFS_HOST_DEFAULT = "localhost";
  public static final String HDFS_PORT = "hdfs.port";
  public static final int HDFS_PORT_DEFAULT = 8020;

  // Keep all keys above registered in the set, used to extract relevant entries from properties
  public static final HashSet<String> REGISTERED_KEYS = new HashSet<String>();
  static {
    REGISTERED_KEYS.add(HTTP_HOST_KEY);
    REGISTERED_KEYS.add(HTTP_PORT_KEY);
  }

  private ConcurrentHashMap<String, String> options;

  public Configuration(Properties props) {
    this.options = new ConcurrentHashMap<String, String>();
    for (String key : props.stringPropertyNames()) {
      if (REGISTERED_KEYS.contains(key)) {
        String value = props.getProperty(key);
        LOG.debug("Insert entry " + key + "->" + value);
        options.put(key, value);
      }
    }
  }

  public Configuration() {
    this(new Properties());
  }

  /** Clean up key, check for null and trim whitespaces */
  private String cleanupKey(String key) {
    String orig = (key == null) ? key : key.trim();
    if (orig == null || orig.length() == 0) {
      throw new IllegalArgumentException("Invalid key '" + key + "', non empty key is expected");
    }
    return orig;
  }

  /** Clean up value, check for null and trim whitespaces */
  private String cleanupValue(String value) {
    if (value == null) {
      throw new IllegalArgumentException(
        "Invalid value '" + value + "', non-null value is expected");
    }
    return value.trim();
  }

  /** Whether or not key exists in configuration */
  private boolean containsKey(String key) {
    return this.options.containsKey(key);
  }

  /** Assert that key exists in configuration */
  private void assertKey(String key) {
    if (!containsKey(key)) {
      throw new IllegalArgumentException("Key '" + key + "' is not found in options");
    }
  }

  /** Set value for key */
  public void set(String key, String value) {
    this.options.put(cleanupKey(key), cleanupValue(value));
  }

  /** Set integer value for key */
  public void set(String key, int value) {
    set(key, Integer.toString(value));
  }

  /** Get value for key */
  public String get(String key) {
    String orig = cleanupKey(key);
    assertKey(orig);
    return this.options.get(orig);
  }

  /** Get value for key, use default if key does not exist */
  public String get(String key, String defaultValue) {
    String orig = cleanupKey(key);
    if (containsKey(orig)) {
      return this.options.get(orig);
    } else {
      return defaultValue;
    }
  }

  /** Get base-10 integer for key */
  public int getInt(String key) {
    String orig = cleanupKey(key);
    assertKey(orig);
    try {
      return Integer.parseInt(this.options.get(orig));
    } catch (NumberFormatException err) {
      throw new IllegalArgumentException("Could not parse value for key '" + key + "'", err);
    }
  }

  /** Get base-10 integer for key, use default if key does not exist, of parsing fails */
  public int getInt(String key, int defaultValue) {
    String orig = cleanupKey(key);
    if (!containsKey(orig)) return defaultValue;
    try {
      return Integer.parseInt(this.options.get(orig));
    } catch (NumberFormatException err) {
      LOG.warn("Failed to parse value for key " + key + ", " + err);
      return defaultValue;
    }
  }

  /** Configuration methods **/

  public String httpHost() {
    return get(HTTP_HOST_KEY, HTTP_HOST_DEFAULT);
  }

  public int httpPort() {
    return getInt(HTTP_PORT_KEY, HTTP_PORT_DEFAULT);
  }

  public String scheme() {
    return HTTP_SCHEME;
  }

  public String hdfsHost() {
    return get(HDFS_HOST, HDFS_HOST_DEFAULT);
  }

  public int hdfsPort() {
    return getInt(HDFS_PORT, HDFS_PORT_DEFAULT);
  }
}

@@ -1038,16 +1038,20 @@
 DFS_HOST
+_KEY
  = %22hdfs
@@ -1158,16 +1158,20 @@
 DFS_PORT
+_KEY
  = %22hdfs
@@ -1498,16 +1498,96 @@
 T_KEY);%0A
+    REGISTERED_KEYS.add(HDFS_HOST_KEY);%0A    REGISTERED_KEYS.add(HDFS_PORT_KEY);%0A
   %7D%0A%0A  p
@@ -4790,16 +4790,20 @@
 DFS_HOST
+_KEY
 , HDFS_H
@@ -4874,16 +4874,20 @@
 DFS_PORT
+_KEY
 , HDFS_P

update conf