/*******************************************************************************
 * 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 java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import in.cdac.medinfo.csnotk.csnolib.icdmap.ICDDBManager.queryConstants;
import in.cdac.medinfo.csnotk.csnolib.util.PropertyReader;
//Logs
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import in.cdac.medinfo.csnotk.csnolib.util.LoggerUtility;
/**
 * Maps SNOMED CT ID (conceptId) to ICD-10 code.
 * @see Record
 * @see MapResult
 */
public class ICDMapAgent{

	private RuleProcessor ruleProcessor = new RuleProcessor();
	private ICDDBManager icdDbManager = new ICDDBManager();
	private LuceneICDMapAgent luceneMapAgent=new LuceneICDMapAgent();
	/**
	 * Maps SNOMED CT ID of provided record to ICD-10 code.
	 * For each record, check if primary SNOMED CT ID can be mapped or not and then carries out process for it.
	 * And returns MapResult having mapped ICD-10 code with mapping details.
	 * @param mapInput
	 * record providing diagnosis SNOMED CT ID that need to be mapped to ICD-10 code with other input fields
	 * @return
	 * MapResult for the input record SNOMED CT ID
	 */
	public MapResult map(Record mapInput){
		if (null!=mapInput){
			PropertyReader.loadSystemProperties();
			// check if valid SNOMED code is found, then only map it
			MapResult mapResult=getMap(mapInput);
			 if(mapResult!=null && !("MappingConstants.NO_MAP_FOUND".equalsIgnoreCase(mapResult.getMapStatus())))
				return mapResult;
			else{
				// else put the result message - No Map Found. It will be present in
				// the download-able report.
				mapResult = new MapResult();
				mapResult.setConceptId(mapInput.getPrimarySCTID());
				List<MapGroup> mapGroups = new ArrayList<MapGroup>();
				mapResult.setMapGroup(mapGroups);
				mapResult.setMapStatus(MappingConstants.NO_MAP_FOUND);
				return mapResult;
				}
		}
		return null;
	}

	/**
	 * Maps SNOMED CT ID by processing rules of groups.
	 * It inputs a record providing diagnosis SNOMED CT ID that need to be mapped to ICD-10 code.
	 * And returns MapResult having mapped ICD-10 code with mapping details.
	 * @param record
	 * provide diagnosis SNOMED CT ID that need to be mapped to ICD-10 code with other input fields.
	 * @return
	 * list of mapGroup for the input record SNOMED CT ID
	 */
	private MapResult getMap(Record record){
		PropertyReader.loadSystemProperties();
		MapResult mapResult = new MapResult();
		mapResult.setConceptId(record.getPrimarySCTID());
		int resultCount = 0;
		List<MapGroup> mapGroups = new ArrayList<MapGroup>();
		List<MappingComponent> listMappingComponents = luceneMapAgent.getICDMap(record.getPrimarySCTID()+"");
		if(listMappingComponents==null){
			return null;
		}

		//use a custom comparator to get non unique keys for repeatable group and priority
		TreeMap<MapKey, MappingComponent> objMapGroup  = new TreeMap<MapKey, MappingComponent>(new MapKeyComparator());
		/*add the mapping component list to TreeMap to get sorted element. Sorting is done on Key. This is
		This is required because the rules should be processed in order of group and priority.
		*/
		for (MappingComponent mappingComponent : listMappingComponents)
			objMapGroup.put(new MapKey(mappingComponent.getMapGroup(), mappingComponent.getMapPriority()), mappingComponent);
		int iGroup=0;
		MapGroup mapGroup = null;
		for(Map.Entry<MapKey,MappingComponent> entry : objMapGroup.entrySet()){
			if(iGroup<entry.getKey().getGroup()){
				if(null !=mapGroup)
					mapGroups.add(mapGroup);
				mapGroup = new MapGroup();
				iGroup = entry.getKey().getGroup();
			}
			List<String> rules;
			//This method returns multiple rules if the map rule is complex. For a simple map a single value will be returned in the list
			rules = ruleProcessor.splitRule(entry.getValue().getMapRule());
			boolean flag  = true;
				for(int i=0; i<rules.size(); i++){
					//process a rule and iterate through look till the return status is true for all set of rules.
					flag = flag & ruleProcessor.processRule(record, rules.get(i));
					if(flag==false)
						break; //break if any one rule is not applicable
			}
			if(flag==true &&  (mapGroup.getMappedICDCode()=="" || mapGroup.getMappedICDCode().equals("")) ){
				mapGroup.setMappedICDCode(entry.getValue().getMapTarget());
				resultCount++;
			}

			MapAdvice mapAdvice = new MapAdvice();
			mapAdvice.setAdvice(entry.getValue().getMapAdvice());
			mapAdvice.setTarget(entry.getValue().getMapTarget());
			if(icdDbManager.getICDDBSatus())
				mapAdvice.setTerm(icdDbManager.getICDTerm("'"+entry.getValue().getMapTarget()+"'") );
			mapGroup.addMapAdvice(mapAdvice);
		}
		mapGroups.add(mapGroup);
		mapResult.setMapGroup(mapGroups);
		switch(resultCount){
		case 0: mapResult.setMapStatus(MappingConstants.NO_MAP_FOUND); break;
		case 1: mapResult.setMapStatus(MappingConstants.MAP_FOUND); break;
		default: mapResult.setMapStatus(MappingConstants.INDETERMINATE_MAP); break;
		}
		return mapResult;
	}


	/**
	 * This method creates database for SNOMED CT to ICD map
	 * @param snomedIntDirPath Directory path for SNOMED CT International Release file path example: G:\SnomedCT_InternationalRF2_PRODUCTION_20170731T150000Z\Snapshot
	 * @param overridedb will override database and index if true. It should be false after database creation process
	 */
	public boolean createICDMapdb(String snomedIntDirPath, boolean overridedb){
        Logger logger = LogManager.getLogger(ICDMapAgent.class);

		try{

		if(!overridedb)
		return false;

		String refsetICDMap=null;
		File snomedIntDir = new File(snomedIntDirPath);
		if(!snomedIntDir.exists())
		{
			logger.error(LoggerUtility.ConsoleWithException, "Wrong directory provided for SNOMED CT release files");
			return false;
		}
		File[] files_refset = new File(snomedIntDirPath+File.separator+"Refset"+File.separator+"Map").listFiles();
		for (File file : files_refset) {
			if(file.getName().matches("der2_iisssccRefset_ExtendedMap(.*)"))
				refsetICDMap=file.getAbsolutePath();
		}
		if(refsetICDMap==null)
		{
			logger.error(LoggerUtility.ConsoleWithException, "Wrong directory provided for SNOMED CT release files");
			return false;
		}

		logger.info(LoggerUtility.CONSOLE, "Please wait for SNOMED CT to ICD-10 map db creation.");
		QueryManager qm = new QueryManager();
		qm.createICDMapdb(refsetICDMap);

		if(luceneMapAgent.deleteIndex(MappingConstants.MAP_INDEX))
			return luceneMapAgent.createIndex(MappingConstants.MAP_INDEX);
		else return false;

		}catch(Exception e){
            logger.error(LoggerUtility.MapICDException, LoggerUtility.createErrorMessage(e));
			return false;
		}
		}


	/**
	 * This method initialize the icd database properties to show terms along with icd code
	 * @param jdbc_url_icd jdbc url for icd database
	 * @param icd_db_username username for icd database
	 * @param icd_db_password password for icd database
	 * @param icd_jdbc_driver_class jdbc driver class for icd database
	 * @param icd_jar_name name of jdbc connector jar for icd database
	 * @param icd_query sql select query to get term from icd database table for  a code
	 */
	public void initICDDB(String jdbc_url_icd, String icd_db_username, String icd_db_password, String  icd_jdbc_driver_class, String icd_jar_name, String icd_query) {
        Logger logger = LogManager.getLogger(ICDMapAgent.class);
		if(null == jdbc_url_icd || null ==icd_db_username || null==icd_db_password || null ==icd_jdbc_driver_class || null== icd_jar_name || null==icd_query){
			String message = String.format("ICD database properties might be not available "
					+ "jdbc_url_icd :%s ,icd_db_username :%s ,icd_jdbc_driver_class :%s ,icd_jar_name :%s ,icd_query :%s"
					, jdbc_url_icd,icd_db_username,icd_jdbc_driver_class,icd_jar_name,icd_query);
			logger.error(LoggerUtility.MapICDException, message);
			return;
		}
		PropertyReader.loadSystemProperties();
		PropertyReader.setProperty(queryConstants.JDBC_URL_ICD, jdbc_url_icd);
		PropertyReader.setProperty(queryConstants.ICDUSERNAME, icd_db_username);
		PropertyReader.setProperty(queryConstants.ICDPASSWORD, icd_db_password);
		PropertyReader.setProperty(queryConstants.ICDDRIVERNAME, icd_jdbc_driver_class);
		PropertyReader.setProperty(queryConstants.ICDJARNAME, icd_jar_name);
		PropertyReader.setProperty(queryConstants.ICDQUERY, icd_query);
		ICDDBManager.setupICDDB();
	}

}
