package routines;

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

/*
 * 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 {
	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 = "?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 = "_n/a_";
    	} else if ("".equals(s1) && ! "".equals(s2)) {
    		result = s2;
     	} else if ( s1.equals(s2) ) {
     		result = "_equal_";
     	} else if (s1.equalsIgnoreCase(s2)) {
    		result = "_equalExceptCase_";
    	} 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 = "?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 = "_n/a_";
    	} else if ("".equals(s1) && ! "".equals(s2)) {
    		result = s2;
     	} else if ( s1.equals(s2) ) {
     		result = "_equal_";
     	} else if (s1.equalsIgnoreCase(s2)) {
    		result = "_equalExceptCase_";
    	} else {
			result = s2;
			
			if (s1.startsWith(s2)) {
				result = "_startsWith_" + s2;
			} else if (s1.endsWith(s2)) {
				result = "_endsWith_" + s2;    				
			} else if (s1.contains(s2)) {
				result = "_contains_" + s2;
			} else if (s2.contains(s1)) {
				result = "_partOf_" + 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 = "_spacesDiffer_" + 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 ("case-sensitive".equals(policy)) {
    		result = java.util.Objects.deepEquals(inMDM_Obj, withOtherObj);
    	} else if ("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;
    }
}
