// Dstl (c) Crown Copyright 2017
package uk.gov.dstl.baleen.uima.utils;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Properties;
import java.util.TimeZone;

import uk.gov.dstl.baleen.exceptions.InvalidParameterException;

/** Convert a string into an object. Used by LegacyMongoConsumer - considered legacy code! */
public class StringToObject {

  private static final String CONFIG_ALLOW_DATES = "allowDates";
  private static final String CONFIG_PRECEDING_ZERO_ISNT_NUMBER = "precedingZeroIsntNumber";

  private StringToObject() {
    // Singleton
  }

  /**
   * Convert a string to a Java object of the correct type with the same value (e.g. "1" &rarr; 1).
   *
   * <p>If a number has a preceding 0, it will be assumed not to be a number as it is likely to
   * represent a phone number
   *
   * <p>This method accepts no configuration, and so uses default values
   *
   * @param s String to convert
   * @return A Java object of the correct type
   */
  public static Object convertStringToObject(String s) {
    return convertStringToObject(s, new Properties());
  }

  /**
   * Convert a string to a Java object of the correct type with the same value (e.g. "1" &rarr; 1).
   *
   * <p>If a number has a preceding 0, it will be assumed not to be a number as it is likely to
   * represent a phone number
   *
   * <p>The following configuration keys can be set:
   *
   * <ul>
   *   <li><b>allowDates</b> - true (default) or false
   *   <li><b>precedingZeroIsntNumber</b> - true (default) or false
   * </ul>
   *
   * @param s String to convert
   * @param config Configuration values
   * @return A Java object of the correct type
   */
  public static Object convertStringToObject(String s, Properties config) {
    Boolean precedingZeroIsntNumber = getConfigPrecedingZero(config);

    if (s == null) {
      return null;
    } else if (tryNumber(s, precedingZeroIsntNumber)) {
      try {
        return parseNumber(s);
      } catch (InvalidParameterException e) {
        // Ignore
      }
    }

    if ("true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)) {
      return Boolean.parseBoolean(s);
    }

    return convertToDate(s, config);
  }

  private static boolean getConfigPrecedingZero(Properties config) {
    Boolean precedingZeroIsntNumber = true;

    if (config.containsKey(CONFIG_PRECEDING_ZERO_ISNT_NUMBER)) {
      Object o = config.get(CONFIG_PRECEDING_ZERO_ISNT_NUMBER);
      if (o instanceof Boolean) {
        precedingZeroIsntNumber = (Boolean) o;
      } else {
        precedingZeroIsntNumber = Boolean.valueOf(o.toString());
      }
    }

    return precedingZeroIsntNumber;
  }

  private static Number parseNumber(String s) throws InvalidParameterException {
    try {
      return Integer.parseInt(s);
    } catch (NumberFormatException e) {
      // Ignore
    }

    try {
      return Double.parseDouble(s);
    } catch (NumberFormatException e) {
      // Ignore
    }

    throw new InvalidParameterException("Couldn't parse number");
  }

  /**
   * Test whether we should attempt to covert a string to a number, based on the current
   * configuration
   */
  private static boolean tryNumber(String s, boolean precedingZeroIsntNumber) {
    if (s.startsWith("0.")) {
      return true;
    }
    if (precedingZeroIsntNumber && !s.startsWith("0")) {
      return true;
    }
    if (!precedingZeroIsntNumber) {
      return true;
    }

    return false;
  }

  private static Object convertToDate(String s, Properties config) {
    Object allowDates = config.get(CONFIG_ALLOW_DATES);
    if (allowDates == null || "true".equalsIgnoreCase(allowDates.toString())) {
      try {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
        df.setTimeZone(TimeZone.getTimeZone("UTC"));
        return df.parse(s);
      } catch (ParseException e) {
        // Ignore
      }

      try {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        return df.parse(s);
      } catch (ParseException e) {
        // Ignore
      }

      try {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        df.setTimeZone(TimeZone.getTimeZone("UTC"));
        return df.parse(s);
      } catch (ParseException e) {
        // Ignore
      }
    }

    return s;
  }
}
