package routines;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/*
 * user specification: the function's comment should contain keys as follows: 1. write about the function's comment.but
 * it must be before the "{talendTypes}" key.
 * 
 * 2. {talendTypes} 's value must be talend Type, it is required . its value should be one of: String, char | Character,
 * long | Long, int | Integer, boolean | Boolean, byte | Byte, Date, double | Double, float | Float, Object, short |
 * Short
 * 
 * 3. {Category} define a category for the Function. it is required. its value is user-defined .
 * 
 * 4. {param} 's format is: {param} <type>[(<default value or closed list values>)] <name>[ : <comment>]
 * 
 * <type> 's value should be one of: string, int, list, double, object, boolean, long, char, date. <name>'s value is the
 * Function's parameter name. the {param} is optional. so if you the Function without the parameters. the {param} don't
 * added. you can have many parameters for the Function.
 * 
 * 5. {example} gives a example for the Function. it is optional.
 */
public class LcagGeneral {
	public static final String IN_SYNC_POLICY_IGNORE_CASE = "ignore-case";
	public static final String IN_SYNC_POLICY_CASE_SENSITIVE = "case-sensitive";
	public static final String COMPARE_SPACES_DIFFER = "_spacesDiffer_";
	public static final String COMPARE_PART_OF = "_partOf_";
	public static final String COMPARE_CONTAINS = "_contains_";
	public static final String COMPARE_ENDS_WITH = "_endsWith_";
	public static final String COMPARE_STARTS_WITH = "_startsWith_";
	public static final String COMPARE_ERROR = "?error?";
	public static final String COMPARE_EQUAL_EXCEPT_CASE = "_equalExceptCase_";
	public static final String COMPARE_EQUALS = "_equal_";
	public static final String COMPARE_NOT_AVAILABLE = "_n/a_";
	private static LcagLogger log = LcagLogger.getLogger(LcagGeneral.class);

	public LcagGeneral() {
	}

	{
	}

	/**
	 * Sleep some milli seconds
	 * 
	 * {talendTypes} Void
	 * 
	 * {Category} LCAG
	 * 
	 * {param} String("milliSeconds") input: number of milliseconds to sleep
	 * 
	 */
	public static void sleep(long milliSeconds) {
		try {
			Thread.sleep(milliSeconds, 0);
		} catch (Exception e) {
			;
		}
	}

	/**
	 * incrementCounter: Create a new named counter in a global HashMap (usually
	 * globalMap) and initialize to 0. Increments the counter if it already exists.
	 * 
	 * {talendTypes} int
	 * 
	 * {Category} LCAG
	 * 
	 * {param} Map("aMap") input: Map to use to store counter value (usually
	 * globalMap) {param} String("aCounter") input: Name of counter
	 * 
	 */
	public static int incrementCounter(Map<String, Object> aMap, String aCounter) {
		Integer n = (Integer) aMap.getOrDefault(aCounter, 0);

		n += 1;
		if (n == 1)
			log.debug("incrementCounter: Created new counter %s = %d", aCounter, n);

		aMap.put(aCounter, n);

		return n;
	}

	/**
	 * firstValue: Return the first parameter that is not null and not an empty
	 * String (or blanks-only).
	 * 
	 * @param valueList variable number of parameter, all of them must have the same
	 *                  type
	 * @return first parameter that is not null (and for String not empty) or null
	 *         if all parameters are null
	 * 
	 *         {talendTypes} Object
	 * 
	 *         {Category} LCAG
	 * 
	 *         {param} Object("valueList") valueList...: variable number of
	 *         parameter, all of them must have the same type
	 * 
	 *         {examples} Assume String variables s1 = null, s2 = "", s3 = " ", s4 =
	 *         "Hi friends" ->> firstValue(s1, s2, s3, s4, "Hello World") returns
	 *         "Hi friends" ->> firstValue(s1, "Hello World") returns "Hello World"
	 *         ->> firstValue(s1, s2) returns null
	 */
	public static <T> T firstValue(T... valueList) {
		T result = null;

		// get the first non-null value into aValue
		for (T obj : valueList) {
			if (obj != null) {
				if (obj instanceof String && obj.toString().trim().equals("")) {
					continue;
				}
				result = obj;
				break;
			}
		}

		return result;
	}

	/**
	 * inSync: Used to check if a field in MDM is in-sync with another object. The
	 * second parameter is in-sync with the first one, if * first one is null
	 * (in-sync because it is safe to update that field) * or both are equal
	 * 
	 * @param inMDM_Obj    - first field to compare to (usually a MDM object field)
	 * @param withOtherObj - field to compare to first parameter
	 * 
	 * @return true if first parameter is null or if both are equal (deeply checked)
	 * 
	 *         {talendTypes} Object
	 * 
	 *         {Category} LCAG
	 * 
	 *         {param} Object(inMDM_Obj) inMDM_Obj: first field to compare to
	 *         (usually a MDM object field) {param} Object(withOtherObj)
	 *         withOtherObj: field to compare to first parameter
	 * 
	 */
	public static boolean inSync(Object inMDM_Obj, Object withOtherObj) {
		boolean result = (inMDM_Obj == null) || java.util.Objects.deepEquals(inMDM_Obj, withOtherObj);

		return result;
	}

	/**
	 * inSyncIgnoreCase: Used to check if a String field in MDM is in-sync with
	 * another. The second parameter is in-sync with the first one, if * first one
	 * is null (in-sync because it is safe to update that field) * or both are equal
	 * ignoring case
	 * 
	 * @param inMDM_Obj    - first String to compare to (usually a MDM object field)
	 * @param withOtherObj - String to compare to first parameter
	 * 
	 * @return true if first parameter is null or if both are equal
	 * 
	 *         {talendTypes} Object
	 * 
	 *         {Category} LCAG
	 * 
	 *         {param} String(inMDM_Obj) inMDM_Obj: first String to compare to
	 *         (usually a MDM object field) {param} String(withOtherObj)
	 *         withOtherObj: String to compare to first parameterr
	 * 
	 */
	public static boolean inSyncIgnoreCase(String inMDM_Obj, String withOtherObj) {
		boolean result = (inMDM_Obj == null) || inMDM_Obj.equalsIgnoreCase(withOtherObj);

		return result;
	}

	private static int countChararcters(CharSequence s, String charList) {
		int result = 0;

		if (s == null || charList == null)
			return 0;

		int n = s.length();

		for (int i = 0; i < n; i++) {
			CharSequence c = s.subSequence(i, i + 1);

			if (charList.contains(c))
				result++;
		}

		return result;
	}

	/**
	 * Check if string s1 and s2 are equal except white spaces in between. The
	 * result string indicates how to modify s1 to get s2
	 * 
	 * @param s1
	 * @param s2
	 * @return String s2 if both differ excluding spaces. If both are equal
	 *         excluding spaces: Return s2 where spaces are replaced by ' ' (if
	 *         space is in s1 and s2) or '+' (space must be added to s2) and '-'
	 *         (space must be deleted from s2)
	 */
	private static String compareWithSpaces(String s1, String s2, boolean ignoreCase) {
		String result = "";
		CharSequence s1Temp;
		CharSequence s2Temp;

		s1Temp = ignoreCase ? s1.toLowerCase() : s1;
		s2Temp = ignoreCase ? s2.toLowerCase() : s2;

		String s1NoBlancs = s1Temp.toString().replaceAll("[ \t\n]", "");
		String s2NoBlancs = s2Temp.toString().replaceAll("[ \t\n]", "");

		if (!s1NoBlancs.equals(s2NoBlancs)) {
			result = s2;
		} else {
			// both strings excluding spaces are equal

			int n1 = s1.length();
			int n2 = s2.length();
			int nMin = Math.min(n1, n2);

			int i = 0;
			int j = 0;

			while (i < nMin && j < nMin) {
				char c1 = s1Temp.charAt(i);
				char c2 = s2Temp.charAt(j);

				if (c1 == c2) {
					result += s1.charAt(i);
					i++;
					j++;
				} else if (c1 == ' ' && c2 != ' ') {
					result += '+';
					i++;
				} else if (c2 == ' ' && c1 != ' ') {
					result += '-';
					j++;
				} else {
					break;
				}
			}
		}

		return result;
	}

	/**
	 * compareEqual: Used to check if two parameters are equal. Returns * second
	 * parameter as string if not equal to the first one * "_n/a_" is the second is
	 * null or empty * "_equal_" if both are equal (deeply checked) *
	 * "_equalExceptCase_" if both are Strings that are equal except upper/lower
	 * case differences
	 * 
	 * @param aValue       - first field to compare to (usually a MDM object field)
	 * @param withOtherObj - field to compare to first parameter
	 * 
	 * @return see above
	 * 
	 *         {talendTypes} Object
	 * 
	 *         {Category} LCAG
	 * 
	 *         {param} Object(inMDM_Obj) aValue: first field to compare to (usually
	 *         a MDM object field) {param} Object(withOtherObj) withOtherObj: field
	 *         to compare to first parameter
	 * 
	 */
	public static String compareEqual(Object aValue, Object withOtherObj) {
		String result = COMPARE_ERROR;
		SimpleDateFormat df = LcagDateTime.MDM_DATE_FORMAT;

		String s1 = (aValue == null) ? "" : aValue.toString().trim();
		String s2 = (withOtherObj == null) ? "" : withOtherObj.toString().trim();

		// Convert dates to mdm standard format:
		if (aValue != null && (aValue instanceof Date))
			s1 = df.format((Date) aValue);
		if (withOtherObj != null && (withOtherObj instanceof Date))
			s2 = df.format((Date) withOtherObj);

		if (!"".equals(s1) && "".equals(s2)) {
			result = COMPARE_NOT_AVAILABLE;
		} else if ("".equals(s1) && !"".equals(s2)) {
			result = s2;
		} else if (s1.equals(s2)) {
			result = COMPARE_EQUALS;
		} else if (s1.equalsIgnoreCase(s2)) {
			result = COMPARE_EQUAL_EXCEPT_CASE;
		} else {
			result = s2;
		}

		return result;
	}

	/**
	 * compareValue: Used to check if two parameters are equal. Returns * second
	 * parameter as string if not equal to the first one * "_n/a_" is the second is
	 * null or empty * "_equal_" if both are equal (deeply checked) *
	 * "_equalExceptCase_" if both are Strings that are equal except upper/lower
	 * case differences * "_startsWith_*" if the first string starts with the second
	 * * "_startsWith_*" if the first string starts with the second * "_contains_*"
	 * if the first string contains the second * "_partOf_*" if the second string
	 * contains the first
	 * 
	 * @param aValue       - first field to compare to (usually a MDM object field)
	 * @param withOtherObj - field to compare to first parameter
	 * 
	 * @return see above
	 * 
	 *         {talendTypes} Object
	 * 
	 *         {Category} LCAG
	 * 
	 *         {param} Object(inMDM_Obj) aValue: first field to compare to (usually
	 *         a MDM object field) {param} Object(withOtherObj) withOtherObj: field
	 *         to compare to first parameter
	 * 
	 */
	public static String compareValue(Object aValue, Object withOtherObj) {
		String result = COMPARE_ERROR;
		SimpleDateFormat df = LcagDateTime.MDM_DATE_FORMAT;

		String s1 = (aValue == null) ? "" : aValue.toString().trim();
		String s2 = (withOtherObj == null) ? "" : withOtherObj.toString().trim();

		// Convert dates to mdm standard format:
		if (aValue != null && (aValue instanceof Date))
			s1 = df.format((Date) aValue);
		if (withOtherObj != null && (withOtherObj instanceof Date))
			s2 = df.format((Date) withOtherObj);

		if (!"".equals(s1) && "".equals(s2)) {
			result = COMPARE_NOT_AVAILABLE;
		} else if ("".equals(s1) && !"".equals(s2)) {
			result = s2;
		} else if (s1.equals(s2)) {
			result = COMPARE_EQUALS;
		} else if (s1.equalsIgnoreCase(s2)) {
			result = COMPARE_EQUAL_EXCEPT_CASE;
		} else {
			result = s2;

			if (s1.startsWith(s2)) {
				result = COMPARE_STARTS_WITH + s2;
			} else if (s1.endsWith(s2)) {
				result = COMPARE_ENDS_WITH + s2;
			} else if (s1.contains(s2)) {
				result = COMPARE_CONTAINS + s2;
			} else if (s2.contains(s1)) {
				result = COMPARE_PART_OF + s2;
			} else {
				String correctionAdvise = LcagGeneral.compareWithSpaces(s1, s2, true);

				if (correctionAdvise.equals(s2)) {
					result = s2;
				} else {
					int n = countChararcters(s2, "+-");
					int nc = countChararcters(correctionAdvise, "+-");

					if (nc > n || correctionAdvise.length() < s2.length()) {
						result = COMPARE_SPACES_DIFFER + correctionAdvise;
					}
				}
			}
		}

		return result;
	}

	/**
	 * inSyncByPolicy: Used to check if a String field in MDM is in-sync with
	 * another with respect to a given compare policy. The second parameter is
	 * in-sync with the first one for * enriching policy (policy starts with '+'):
	 * if inMDM_Obj is null, otherwise the following rules apply * "case-sensitive"
	 * policy: both objects are equal in all values * "ignore-case" policy: the
	 * string representation of both objects is equal disregarding case of letters *
	 * "Levenstein" policy: not yet supported, throws exception
	 * 
	 * @param inMDM_Obj    - first String to compare to (usually a MDM object field)
	 * @param withOtherObj - String to compare to first parameter
	 * @param policy       - Compare policy to use
	 * 
	 * @return true if first two parameter are in-sync with respect to the policy
	 *         given.
	 * 
	 *         {talendTypes} boolean
	 * 
	 *         {Category} LCAG
	 * 
	 *         {param} String(inMDM_Obj) inMDM_Obj: first String to compare to
	 *         (usually a MDM object field) {param} String(withOtherObj)
	 *         withOtherObj: String to compare to first parameter {param}
	 *         String("policy") input: compare policy to use (case-sensitive,
	 *         ignore-case, Levenstein (not yet supported))
	 * 
	 */
	public static boolean inSyncByPolicy(Object inMDM_Obj, Object withOtherObj, String policy) {
		if (policy == null)
			throw new IllegalArgumentException("Error: policy is not set (null)");
		if (inMDM_Obj == null && withOtherObj == null)
			return true;

		// Check enriching rule: If policy starts with '+' withOtherObject can safely
		// set the inMDM_Obj if that one is null
		if ((inMDM_Obj == null || "".equals(inMDM_Obj)) && policy.startsWith("+"))
			return true;

		if (withOtherObj == null || inMDM_Obj == null)
			return false;

		// after here, remove leading + from sync-rule
		policy = policy.startsWith("+") ? policy.substring(1) : policy;

		boolean result = false;

		if (IN_SYNC_POLICY_CASE_SENSITIVE.equals(policy)) {
			result = java.util.Objects.deepEquals(inMDM_Obj, withOtherObj);
		} else if (IN_SYNC_POLICY_IGNORE_CASE.equals(policy)) {
			String inMDM = inMDM_Obj.toString();
			String withOther = withOtherObj.toString();

			result = inMDM.equalsIgnoreCase(withOther);
		} else {
			throw new IllegalArgumentException(String.format("Error: policy '%s' is not supported", policy));
		}

		return result;
	}
}
