/*******************************************************************************
 * Copyright 2016 Centre for Development of Advanced Computing(C-DAC), Pune
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package in.cdac.medinfo.csnotk.csnolib.icdmap;

import in.cdac.medinfo.csnotk.csnolib.agents.SNOMEDAgent;
import in.cdac.medinfo.csnotk.csnolib.model.Concept;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//Logs
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import in.cdac.medinfo.csnotk.csnolib.util.LoggerUtility;

/**
 * Process the mapping rules.
 * It has functions that checks the condition and validates the rule.
 *
 */
class RuleProcessor {

	private static final int PATTERN_TRUE = 1;
	private static final int PATTERN_FINDING_DISORDER = 2;
	private static final int PATTERN_OBSERVATION = 3;

	/**
	 * Checks the pattern for the input rule.
	 * @param rule
	 * rule for the mapping whose pattern need to be checked/matched
	 * @return
	 * integer value based on the type of pattern matched for the rule
	 */
	private int checkPattern(String rule)
	{
		if(rule.matches("(OTHERWISE )?TRUE"))
			return PATTERN_TRUE;
		if(rule.matches("IFA (\\d+) [|] (.)*\\((disorder|finding)\\) [|]"))
			return PATTERN_FINDING_DISORDER;
		else
			return PATTERN_OBSERVATION; //This means the rule matches ("IFA (\\d+) [|] (.)*\\((observable entity)\\) [|] (<|>|=)*(.)*"))
		//here it is assumed that there will not be any other pattern coming other that the above three patterns
	}

	/**
	 * Breaks down a complex rule into individual rules condition and returns result as list of rule.
	 * @param rule
	 * rule that need to be validated and broken in case of complex one
	 * @return
	 * list of rules
	 */
	List<String> splitRule(String rule){

		List<String> rules = new ArrayList<String>();
		String patterniffinding = "IFA (\\d+) [|] (.)*\\((disorder|finding)\\) [|]";
		String patternifobservation = "IFA (\\d+) [|] (.)*\\((observable entity)\\) [|] (<|>|=)*(.)*";
		String patternifboth = "(" + patterniffinding + "|" + patternifobservation + ")";

		String patternifcondition = patternifboth + " AND " + patternifboth;
		if(rule.matches(patternifcondition))
		{

			Pattern pattern = Pattern.compile(patternifboth);
			Matcher matcher = pattern.matcher(rule);

			while(matcher.find()) {
				rules.add(matcher.group(0));
			}
		}
		else
			rules.add(rule);
		return rules;
	}

	/**
	 * Gets SNOMED ID from the provided rule condition.
	 * @param rule
	 * rule from which SNOMED ID needs to be extracted
	 * @return
	 * SNOMED ID from the rule
	 */
	private String getSCTIdFromRule(String rule){

		String patternifid = "(\\d+)";
		Pattern pattern = Pattern.compile(patternifid);
		Matcher matcher = pattern.matcher(rule);

		if (matcher.find()) {
		    return matcher.group(0);
		} else {
		    return null;
		}
	}

	/**
	 * Finds age and checks it with the expression in provided rule.
	 * @param rule
	 * rule that need to be validated
	 * @param birthDate
	 * birth date of the patient
	 * @param diagnosisDate
	 * diagnosis date or current date, if not provided
	 * @return
	 * boolean - true/false based on the rule validation
	 */
	private boolean processDateRule(String rule, Date birthDate, Date diagnosisDate){
		Double diffInDays = (int) ((diagnosisDate.getTime() - birthDate.getTime()) / (1000 * 60 * 60 * 24)) / 1.0;
		Double diffInMonths = diffInDays/30;
		Double diffInYears = diffInMonths/12;

	    String patternifage = "([<>=]+) ([0-9.]+) (days|day|months|month|years|year)";
		Pattern pattern = Pattern.compile(patternifage);
		Matcher matcher = pattern.matcher(rule);

		if (matcher.find()) {
		    if (matcher.group(3).trim().equalsIgnoreCase("days") || matcher.group(3).trim().equalsIgnoreCase("day"))
		    	return operate(diffInDays, matcher.group(1).trim(), Double.parseDouble(matcher.group(2).trim()));
		    if (matcher.group(3).trim().equalsIgnoreCase("months") || matcher.group(3).trim().equalsIgnoreCase("month"))
		    	return operate(diffInMonths, matcher.group(1).trim(), Double.parseDouble(matcher.group(2).trim()));
		    if (matcher.group(3).trim().equalsIgnoreCase("years") || matcher.group(3).trim().equalsIgnoreCase("year"))
		    	return operate(diffInYears, matcher.group(1).trim(), Double.parseDouble(matcher.group(2).trim()));
		    return false;
		} else {
		    return false;
		}
	}

	/**
	 * Compares the values based on the type of operator provided to it.
	 * @param value1
	 * first value that need to be compared
	 * @param symbol
	 * symbol or the operator for which values need to be compared
	 * @param value2
	 * second value that need to be compared
	 * @return
	 * returns the result as boolean based on the comparison
	 */
	private boolean operate(Double value1, String symbol, Double value2){
		if(symbol.equalsIgnoreCase("<"))
			return (value1 < value2);
		if(symbol.equalsIgnoreCase(">"))
			return (value1 > value2);
		if(symbol.equalsIgnoreCase("="))
			return (value1.equals(value2));
		if(symbol.equalsIgnoreCase("<="))
			return (value1 <= value2);
		if(symbol.equalsIgnoreCase(">="))
			return (value1 >= value2);
		else
			return false;
	}

	/**
	 * Checks pattern for various conditions like: disorder, finding, observable entity.
	 * And for each pattern type, it carries its own set of validation based on the rule.
	 * @param record
	 * record with input details
	 * @param rule
	 * mapping rule that need to be processed and validated
	 * @return
	 * boolean - true/false based on the rule validation
	 */
	boolean processRule(Record record, String rule){

		int pattern = checkPattern(rule);

		switch (pattern)
		{
		case PATTERN_TRUE:
			return true;

		case PATTERN_FINDING_DISORDER:
			//get SCTid from the rule
			Long SCTId = Long.parseLong(getSCTIdFromRule(rule), 10);

			//process gender rule
			if (processGenderRule(record.getGender(), SCTId)==true)
				return true;

			//get all the descendants of given concept id using CSNOLib
			SNOMEDAgent snomedAgent =new SNOMEDAgent();
			Set<Concept> lstDescendants = snomedAgent.getAllDescendantConcepts(SCTId.toString());
			//get secondary sct id from the input record
			String secondaryTerm = record.getSecondarySCTID();

			//split the secondary SCT ids in to string array. Note: secondary SCT ids are + separated multiple values
			String[] secondarySnomedId = null;
			if(null != secondaryTerm && !secondaryTerm.equalsIgnoreCase("")){
				secondarySnomedId = secondaryTerm.split("\\+");

				if(null != lstDescendants)
				{
					for (Concept concept : lstDescendants)
					{
						for(int i=0; i< secondarySnomedId.length; i++)
							if (Long.parseLong(secondarySnomedId[i], 10) == SCTId || Long.parseLong(secondarySnomedId[i], 10) == Long.valueOf(concept.getId()))
								return true;
					}
				}
				else
				{
					for(int i=0; i< secondarySnomedId.length; i++)
						if (Long.parseLong(secondarySnomedId[i], 10) == SCTId)
							return true;
				}
			}
			return false;

		case PATTERN_OBSERVATION:
			return processAgeRule(record, rule);
		default:
			return false;

		}
	}

	/**
	 * Checks gender from the input record with sctid from the mapping rule.
	 * @param gender
	 * gender from the input record
	 * @param SCTId
	 * SNOMED ID from the mapping rule
	 * @return
	 * boolean - true/false based on validation
	 */
	private boolean processGenderRule(String gender, Long SCTId){

		if(null!=gender)
		{
			if((gender.equalsIgnoreCase("MALE") || gender.equalsIgnoreCase("m"))){
				if(SCTId == (long)(248153007))
					return true;
			}
			else if ((gender.equalsIgnoreCase("FEMALE") || gender.equalsIgnoreCase("f"))){
				if(SCTId == (long)(248152002))
					return true;
			}
		}
		return false;
	}

	/**
	 * Checks if rule for age comparison is validated with SNOMED id from the mapping rule.
	 * And then, finds age and checks it with expression in the provided rule.
	 * @param mapRecord
	 * record with input details
	 * @param rule
	 * mapping rule that need to be processed and validated
	 * @return
	 * boolean - true/false based on validation
	 */
	private boolean processAgeRule(Record mapRecord, String rule){
        Logger logger = LogManager.getLogger(RuleProcessor.class);
		boolean ageBoolean = false;
		Long sctId = Long.parseLong(getSCTIdFromRule(rule), 10);

		if (sctId == 424144002 || sctId == 445518008){
			Date birthDate = mapRecord.getBirthDate();
			if(null == birthDate)
				return false;
			Date diagnosisDate = mapRecord.getDiagnosisDate();
			//Add comment for justification
			if(diagnosisDate == null || diagnosisDate.toString().equals("")){
				Date current = new Date();
				diagnosisDate = current;
				try {
					DateFormat df = new SimpleDateFormat("MM-dd-yyyy");
					String newDateString = df.format(current);
					diagnosisDate = df.parse(newDateString);
				} catch (ParseException e) {
					// Log the Exception
                    logger.error(LoggerUtility.MapICDException, LoggerUtility.createErrorMessage(e));
				}
			}
			ageBoolean = processDateRule(rule, birthDate, diagnosisDate);
		}

		return ageBoolean;
	}

}
