/*******************************************************************************
 * Copyright 2014 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.agents;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import in.cdac.medinfo.csnotk.csnolib.api.ISNOMEDAgent;
import in.cdac.medinfo.csnotk.csnolib.api.ISearchAgent;
import in.cdac.medinfo.csnotk.csnolib.commons.ConceptDetails;
import in.cdac.medinfo.csnotk.csnolib.commons.Constants;
import in.cdac.medinfo.csnotk.csnolib.commons.DBSchemaMapping;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumAcceptability;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumComponent;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumDirection;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumLangRefset;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumProperties;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumRelationshipType;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumSemanticTag;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumState;
import in.cdac.medinfo.csnotk.csnolib.db.QueryManager;
import in.cdac.medinfo.csnotk.csnolib.model.ComponentFactory;
import in.cdac.medinfo.csnotk.csnolib.model.CompositeDescription;
import in.cdac.medinfo.csnotk.csnolib.model.Concept;
import in.cdac.medinfo.csnotk.csnolib.model.Description;
import in.cdac.medinfo.csnotk.csnolib.model.Refset;
import in.cdac.medinfo.csnotk.csnolib.model.Relationship;
import in.cdac.medinfo.csnotk.csnolib.model.RelationshipTC;
import in.cdac.medinfo.csnotk.csnolib.model.SimpleMapRefsetResult;
import in.cdac.medinfo.csnotk.csnolib.util.KeyBasedComparator;
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;
//ECL
import in.cdac.medinfo.csnotk.csnolib.ecl.ExpressionConstraintLanguageService;
import in.cdac.medinfo.csnotk.csnolib.ecl.constraint.SConstraint;
import org.snomed.langauges.ecl.ECLException;


/**
 * A concrete implementation of {@link ISNOMEDAgent}
 *
 * Library interface for application. It provides the basic methods to
 * query the SNOMED CT repository for finding suggestions, searching, and
 * retrieving {@link Concept}s and their {@link Description}s for known concept
 * identifiers.
 *
 * @see Concept
 * @see Description
 * @see CompositeDescription
 * @see Relationship
 * @see EnumSemanticTag
 * @see EnumState
 * @see EnumAcceptability
 */
public class SNOMEDAgent implements ISNOMEDAgent {
	private static final long serialVersionUID = 1L;
	private static final String RANGE= "[0-9]*";
	private static final String REFSET = "Refset";
	private static final String SNOMED_RT = "snomed rt";
	private static final String ENUM_STATE = "enumState :";
	private static final String TERMINOLOGY = "Terminology";
	private static final String DER2_C_REFSET = "x?(der|rel)2_[sci]*Refset_(.*)";
	private static final String ENUM_ACCEPTABILITY = "enumAcceptability :";
	private static final String ENUM_SEMANTIC_TAG = "enumSemanticTag :";
	private static final String INVALID_PARAMETER = "INVALID PARAMETER";
	private static final String PLEASE_WAIT_FOR_DB_CREATION = "Please wait for db creation...";
	private static final String DATABASE_CREATED_SUCCESSFULLY = "Database created successfully.";
	private static final String WRONG_PATH_PROVIDED_FOR_REFSET_FILE = "Wrong path provided for refset file :";
	private static final String WRONG_PATH_PROVIDED_FOR_SIMPLE_MAP_REFSET_FILE = "Wrong path provided for Simple Map Refset file :";
	private static final String WRONG_DIRECTORY_PROVIDED_FOR_SNOMED_CT_RELEASE_FILES = "Wrong directory provided for SNOMED CT release files";
	private static final String WRONG_DIRECTORY_PROVIDED_FOR_SNOMED_CT_EXTENSION_RELEASE_FILES = "Wrong directory provided for SNOMED CT Extension release files";
	private static final String ALL_INDEX_GENERATION_FINISHED = "Index generation finished.";
	static boolean updateSematicTag=false;
	static boolean updateRelationType=false;
	private static Set<String> semanticTagSet=new HashSet<>();
	private static List<String> relationTypeSet=new ArrayList<>();
	private ISearchAgent searchAgent = new LuceneSearchAgent();
	private static List<List<String>> langRefset= new ArrayList<>();

	/**
	 * Checks whether RF2 file contains records or not
	 * @param file File object pointing to RF2 file
	 */
	private boolean RF2FileHasRecords( File file ) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		boolean fileHasRecords=false;
		try(BufferedReader br = new BufferedReader(new FileReader(file))) {
			br.readLine(); // skip first line of file
			String line = br.readLine(); // reading second line of file
			if(line!=null){ // if second line not null then it has data
				fileHasRecords = true;
			}
		} catch (FileNotFoundException e) {
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		} catch (IOException e) {
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		}
		return fileHasRecords;
	}

	/**
	 * Returns the list of descriptions including Fully specified names (FSN).
	 * @param conceptId SNOMED CT concept identifier.
	 * @param enumLangRefset {@link EnumLangRefset} Specifies English language reference set for US or UK.
	 * @return List of {@link CompositeDescription} for given concept id.
	 */
	public List<CompositeDescription> getAllDescriptions(String conceptId,EnumLangRefset enumLangRefset){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		if (null == conceptId || !conceptId.matches(RANGE))
			return Collections.emptyList();
		else {
			PropertyReader.loadSystemProperties();
			HashMap<String, List<String>> queryParams = new HashMap<>();
			queryParams.put(Constants.SEARCH_TERM, Arrays.asList(conceptId));
			queryParams.put(Constants.MAX_RECORDS,Arrays.asList(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT)));
			queryParams.put(DBSchemaMapping.REFSETLANG_REFSET_ID ,Arrays.asList(enumLangRefset.getValue()));
			List<CompositeDescription> output = new ArrayList<>();
			try {
				output.addAll(searchAgent.search(queryParams,true));
			} catch (Exception e) {
				// Log the Exception
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}
			return output;
		}
	}

	/**
	 * Searches given concept id in the SNOMED CT repository based on provided relationship type, direction.
	 * @param
	 *  conceptId - {@link String} Concept ID to be searched in SNOMED CT repository.<br>
	 * 	@param enumRelationshipType - {@link EnumRelationshipType} Type of relationship.<br>
	 *            If enumRelationshipType is,<br>
	 *  		  is a - It will return only "is a" relation type Components.<br>
	 * 			  all - It will return only "all" relation type Components.<br>
	 * 			  finding site - It will return only "finding site" relation type Components.<br>
	 *  @param direction - {@link EnumDirection} Relationship direction i.e whether the specified concept Id is the source or destination
	 * 				Possible values for directions is,<br>
	 * 				source - concept id is a source<br>
	 * 				destination - concept id is a destination
	 *  @return Set of {@link Concept} matching the given parameter.<br>
	 */
	//created for lucene to get related concepts for lucene indexes
	@Override
	public Set<Concept> getRelations(String conceptId,String relationshipType, EnumDirection direction)
	{
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		PropertyReader.loadSystemProperties();
		List<Relationship> lstrelationships = null;
		Set<Concept> relConcepts=new HashSet<>();
		relationTypeSet =getRelationShipType();
		if(relationTypeSet.contains(relationshipType.trim().toLowerCase()) || relationshipType.toLowerCase().equals(EnumRelationshipType.ALL.getValue())||relationshipType.toLowerCase().equals(EnumRelationshipType.IS_A.getValue())) {
			try
			{
				if(direction==(EnumDirection.DESTINATION)){
					lstrelationships =searchAgent.getRelationships(conceptId, relationshipType, direction);
					for (Relationship rel : lstrelationships) {
						relConcepts.add(rel.getSourceConcept());
					}
				}
				else if(direction==EnumDirection.SOURCE){
					lstrelationships =searchAgent.getRelationships(conceptId, relationshipType, direction);
					for (Relationship rel : lstrelationships) {
						relConcepts.add(rel.getDestinationConcept());
					}
				}
			}
			catch (Exception e){
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}
			return relConcepts;
		}
		else {
			logger.error(LoggerUtility.Exception, "Invalid relationship type : {}",relationshipType);
			return Collections.emptySet();
		}
	}



	/**
	 * Searches given conceptId in the SNOMED CT repository<br>
	 * @param conceptId String it will search on the basis of given conceptId and also use for checking
	 * the entered conceptId is valid or not<br>
	 * @return Object of {@link Concept} respected to given conceptId
	 *
	 */
	@Override
	public Concept search(final String conceptId){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		if(conceptId!=null){
			try{
				return searchAgent.search(conceptId);
			}catch (ParseException | IOException e){
				//Log the Exception
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}catch (Exception e){
				//Log the Exception
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}
		}
		return null;

	}

	/**
	 * Searches given term/text or concept id in SNOMED CT repository. Result includes active as well as inactive terms.
	 * @param matchTerm {@link String} term/text or Concept ID to be searched.
	 * @param groupByConcept boolean value, if true, then method will return set of {@link CompositeDescription} which will contain one
	 *  			description from one concept.
	 * @param refsetId Identifier of Refset if search is to be done from a specific Refset and null if search is to be done from whole SNOMED CT Terminology.
	 * @param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @return Set of {@link CompositeDescription} matching the given term/text or Concept ID.
	 */
	@Override
	public Set<CompositeDescription> search(String matchTerm, boolean groupByConcept,String refsetId, List<String> parentIdList,boolean excludeConcept) {
		PropertyReader.loadSystemProperties();
		HashMap<String, List<String>> queryParams = new HashMap<>();
		queryParams.put(Constants.SEARCH_TERM, Arrays.asList(matchTerm));
		queryParams.put(Constants.MAX_RECORDS,Arrays.asList(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT)));
		queryParams.put(Constants.REFSET_ID ,Arrays.asList(refsetId));

		Set<CompositeDescription> searchedDescriptions = searchAgent.search(queryParams,true);
		if(searchedDescriptions.isEmpty()){
			queryParams.put(Constants.MAX_RECORDS,Arrays.asList("0"));
			searchedDescriptions = searchAgent.search(queryParams,true);
		}



		if(parentIdList!=null && !parentIdList.isEmpty() )
		{
			searchedDescriptions=processForParentIdSearch(searchedDescriptions, parentIdList,excludeConcept);
		}
		if(groupByConcept)
		{
			return processForGroupByConcept(searchedDescriptions);
		}
		return searchedDescriptions;
	}

	/**
	 * Searches limited best matching terms for given term/text or concept iD in SNOMED CT repository.
	 * @param matchTerm {@link String} term/text or Concept ID to be searched in SNOMED CT repository.
	 * @param returnLimit Maximum number of matching terms to be fetched.	 *
	 * @param groupByConcept boolean value, if true, then method will return set of {@link CompositeDescription} which will contain one
	 *  			description from one concept.
	 *@param refsetId Identifier of Refset if search is to be done from a specific Refset and null if search is to be done from whole SNOMED CT Terminology.
	 *@param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @return Set of {@link CompositeDescription} matching the given term/text or Concept ID.
	 */
	@Override
	public Set<CompositeDescription> search(String matchTerm, int returnLimit, boolean groupByConcept,String refsetId, List<String> parentIdList,boolean excludeConcept
			) {

		Set<CompositeDescription> output = new LinkedHashSet<>();

		int returnlimit = -1;

		PropertyReader.loadSystemProperties();
		returnlimit = Integer.parseInt(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT));

		Set<CompositeDescription> searchedDescriptions = search(matchTerm, groupByConcept,refsetId,parentIdList,false);
		if (-1 >= returnLimit || returnlimit <= returnLimit) {
			return searchedDescriptions;
		}
		if (null != searchedDescriptions) {
			for (CompositeDescription description : searchedDescriptions) {
				if (output.size() >= returnLimit) {
					break;
				}
				output.add(description);
			}
		}

		if(parentIdList!=null && !parentIdList.isEmpty() && searchedDescriptions != null)
		{
			searchedDescriptions = processForParentIdSearch(searchedDescriptions, parentIdList,excludeConcept);
		}
		if(groupByConcept)
		{
			return processForGroupByConcept(searchedDescriptions);
		}
		return output;
	}



	/**
	 * Searches given term/text or concept id in the SNOMED CT repository based on provided component state (active, inactive, both).
	 * by the user.
	 * @param matchTerm {@link String} term/text or Concept ID to be searched in SNOMED CT repository.
	 * @param enumState {@link EnumState} State of a component.<br>
	 *            If enumState is,<br>
	 *  		  active - It will return only active Components.<br>
	 * 			  inactive - It will return only inactive Components.<br>
	 * 			  both - It will return both active and inactive Components.<br>
	 * @param returnLimit Maximum number of matching terms to be fetched.
	 * @param groupByConcept boolean value, if true, then method will return set of {@link CompositeDescription} which will contain one
	 *  			description from one concept.
	 *  @param refsetId Identifier of Refset if search is to be done from a specific Refset and null if search is to be done from whole SNOMED CT Terminology.
	 *  @param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @return Set of {@link CompositeDescription} matching the given term/text or Concept ID.
	 */
	@Override
	public Set<CompositeDescription> search(String matchTerm, EnumState enumState,
			int returnLimit, boolean groupByConcept,String refsetId, List<String> parentIdList,boolean excludeConcept) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);

		Set<CompositeDescription> output = new LinkedHashSet<>();
		if (enumState == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_STATE + enumState;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		}

		if (EnumState.BOTH == enumState) {
			return search(matchTerm, returnLimit, groupByConcept,refsetId,parentIdList,excludeConcept);
		}

		int returnlimit = -1;

		PropertyReader.loadSystemProperties();
		returnlimit = Integer.parseInt(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT));

		HashMap<String, List<String>> queryParams = new HashMap<>();
		queryParams.put(Constants.SEARCH_TERM, Arrays.asList(matchTerm));
		queryParams.put(Constants.STATE, Arrays.asList(enumState.name()));
		queryParams.put(Constants.MAX_RECORDS,Arrays.asList(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT)));
		queryParams.put(Constants.REFSET_ID ,Arrays.asList(refsetId));


		Set<CompositeDescription> searchedDescriptions = searchAgent.search(queryParams,true);

		if(parentIdList!=null && !parentIdList.isEmpty())
		{
			searchedDescriptions=processForParentIdSearch(searchedDescriptions, parentIdList,excludeConcept);
		}
		if(groupByConcept)
		{
			searchedDescriptions = processForGroupByConcept(searchedDescriptions);
		}

		if (-1 >= returnLimit || returnlimit <= returnLimit) {
			return searchedDescriptions;
		}

		if (null != searchedDescriptions) {
			for (CompositeDescription description : searchedDescriptions) {

				if (output.size() >= returnLimit) {
					break;
				}
				output.add(description);

			}
		}

		return output;
	}

	/**
	 * Searches given term/text or concept id in the SNOMED CT repository based on provided component state, semantic tag.
	 * @param matchTerm {@link String} term/text or Concept ID to be searched in SNOMED CT repository.
	 * @param enumState {@link EnumState} State of a component.<br>
	 *            If enumState is,<br>
	 *  		  active - It will return only active Components.<br>
	 * 			  inactive - It will return only inactive Components.<br>
	 * 			  both - It will return both active and inactive Components.<br>
	 * @param enumSemanticTagList
	 *            {@link List EnumSemanticTag} Refers to the List of Semantic Tags in which SNOMED CT term/text or concept id will be searched. <br>
	 *            If enumSemanticTag is,<br>
	 *            all - It will search SNOMED CT term in all the Semantic Tags.<br>
	 *            For other possible values please refer {@link EnumSemanticTag}.<br>
	 *            A list of {@link EnumSemanticTag} can also be provide in which search/suggest is to be perform.<br>
	 * @param returnLimit Maximum number of matching terms to be fetched.
	 * @param groupByConcept boolean value, if true, then method will return set of {@link CompositeDescription} which will contain one
	 *  			description from one concept.
	 *  @param refsetId Identifier of Refset if search is to be done from a specific Refset and null if search is to be done from whole SNOMED CT Terminology.
	 *  @param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @return Set of {@link CompositeDescription} matching the given term/text or Concept ID.
	 */
	@Override
	public Set<CompositeDescription> search(String matchTerm, EnumState enumState,
			List<String> semanticTagList, int returnLimit, boolean groupByConcept,String refsetId, List<String> parentIdList,boolean excludeConcept) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);

		Set<CompositeDescription> output = new LinkedHashSet<>();
		if (enumState == null){
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_STATE + enumState;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		} else if (semanticTagList == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_SEMANTIC_TAG + semanticTagList;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		}

		if (semanticTagList.contains("all")||semanticTagList.contains("ALL")) {
			return search(matchTerm, enumState, returnLimit, groupByConcept,refsetId,parentIdList,excludeConcept);
		}

		int returnlimit = -1;

		PropertyReader.loadSystemProperties();
		returnlimit = Integer.parseInt(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT));

		semanticTagList=this.validateSemanticTag(semanticTagList);
		if(semanticTagList==null)
			return output;

		HashMap<String, List<String>> queryParams = new HashMap<>();
		queryParams.put(Constants.SEARCH_TERM, Arrays.asList(matchTerm));
		queryParams.put(Constants.STATE, Arrays.asList(enumState.name()));
		queryParams.put(Constants.SEMANTIC_TAG,semanticTagList);
		queryParams.put(Constants.MAX_RECORDS,Arrays.asList(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT)));
		queryParams.put(Constants.REFSET_ID ,Arrays.asList(refsetId));

		Set<CompositeDescription> searchedDescriptions = searchAgent.search(queryParams,true);
		if(parentIdList!=null && !parentIdList.isEmpty())
		{
			searchedDescriptions=processForParentIdSearch(searchedDescriptions, parentIdList,excludeConcept);
		}
		if(groupByConcept)
		{
			searchedDescriptions = processForGroupByConcept(searchedDescriptions);
		}

		if (-1 >= returnLimit || returnlimit <= returnLimit) {
			return searchedDescriptions;
		}

		if (null != searchedDescriptions) {
			for (CompositeDescription description : searchedDescriptions) {

				if (output.size() >= returnLimit) {
					break;
				}
				output.add(description);

			}
		}

		return output;
	}

	@Override
	public Set<CompositeDescription> search(String matchTerm, EnumState enumState,
		List<String> semanticTagList, int returnLimit, boolean groupByConcept,String refsetId, List<String> parentIdList,boolean fullConcept,boolean excludeConcept) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);

		Set<CompositeDescription> output = new LinkedHashSet<>();
		if (enumState == null){
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_STATE + enumState;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		} else if (semanticTagList == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_SEMANTIC_TAG + semanticTagList;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		}

		if (semanticTagList.contains("all")||semanticTagList.contains("ALL")) {
			return search(matchTerm, enumState, returnLimit, groupByConcept,refsetId,parentIdList,excludeConcept);
		}

		int returnlimit = -1;

		PropertyReader.loadSystemProperties();
		returnlimit = Integer.parseInt(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT));

		semanticTagList=this.validateSemanticTag(semanticTagList);
		if(semanticTagList==null)
			return output;

		HashMap<String, List<String>> queryParams = new HashMap<>();
		queryParams.put(Constants.SEARCH_TERM, Arrays.asList(matchTerm));
		queryParams.put(Constants.STATE, Arrays.asList(enumState.name()));
		queryParams.put(Constants.SEMANTIC_TAG,semanticTagList);
		queryParams.put(Constants.MAX_RECORDS,Arrays.asList(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT)));
		queryParams.put(Constants.REFSET_ID ,Arrays.asList(refsetId));

		Set<CompositeDescription> searchedDescriptions = searchAgent.search(queryParams,fullConcept);
		if(parentIdList!=null && !parentIdList.isEmpty())
		{
			searchedDescriptions=processForParentIdSearch(searchedDescriptions, parentIdList,excludeConcept);
		}
		if(groupByConcept)
		{
			searchedDescriptions = processForGroupByConcept(searchedDescriptions);
		}

		if (-1 >= returnLimit || returnlimit <= returnLimit) {
			return searchedDescriptions;
		}

		if (null != searchedDescriptions) {
			for (CompositeDescription description : searchedDescriptions) {

				if (output.size() >= returnLimit) {
					break;
				}
				output.add(description);

			}
		}

		return output;
	}

	/**
	 * Searches given term/text or concept id in the SNOMED CT repository based on provided component state, semantic tag, acceptability.
	 * @param matchTerm {@link String} term/text or Concept ID to be searched in SNOMED CT repository.
	 * @param enumState {@link EnumState} State of a component.<br>
	 *            If enumState is,<br>
	 *  		  active - It will return only active Components.<br>
	 * 			  inactive - It will return only inactive Components.<br>
	 * 			  both - It will return both active and inactive Components.<br>
	 * @param enumSemanticTagList
	 *            {@link EnumSemanticTag} Refers to the Semantic Tag in which SNOMED CT term/text or concept id will be searched. <br>
	 *            If enumSemanticTag is,<br>
	 *            all - It will search SNOMED CT term in all the Semantic Tags.<br>
	 *            For other possible values please refer {@link EnumSemanticTag}.<br>
	 *            A list of {@link EnumSemanticTag} can also be provide in which search/suggest is to be perform.<br>
	 * @param enumAcceptability
	 *            {@link EnumAcceptability} Represents the type of description i.e. whether the search results contains preferred terms or
	 *            preferred terms other than FSN, only acceptable terms or synonyms.<br>
	 *            If enumAcceptability is, <br>
	 *            preferred - refers to all preferred description for a concept (including FSN). <br>
	 *  		  preferredexcludingfsn - refers to SNOMEDCT description for preferred terms excluding FSN.<br>
	 *  		  synonyms - refers to all description for a concept (excluding FSN).<br>
	 * 		  	  acceptable- refers to all description for a concept excluding preferred.<br>
	 *  		  all - refers to all description for a concept (both preferred and acceptable).<br>
	 * @param returnLimit Maximum number of matching terms to be fetched.
	 * @param groupByConcept boolean value, if true, then method will return set of {@link CompositeDescription} which will contain one
	 *  			description from one concept.
	 *  @param refsetId Identifier of Refset if search is to be done from a specific Refset and null if search is to be done from whole SNOMED CT Terminology.
	 *  @param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @return Set of {@link CompositeDescription} matching the given term/text or Concept ID.
	 */
	@Override
	public Set<CompositeDescription> search(String matchTerm, EnumState enumState,
			List<String> semanticTagList, EnumAcceptability enumAcceptability, int returnLimit, boolean groupByConcept, String refsetId, List<String> parentIdList,boolean excludeConcept) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		Set<CompositeDescription> output = new LinkedHashSet<>();
		if (enumState == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_STATE + enumState;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		} else if (semanticTagList == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_SEMANTIC_TAG + semanticTagList;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		} else if (enumAcceptability == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_ACCEPTABILITY
					+ enumAcceptability;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		}

		if (EnumAcceptability.ALL == enumAcceptability) {
			return search(matchTerm, enumState, semanticTagList, returnLimit, groupByConcept,refsetId,parentIdList,excludeConcept);
		}
		PropertyReader.loadSystemProperties();
		int returnlimit = -1;


		semanticTagList=this.validateSemanticTag(semanticTagList);
		if(semanticTagList==null)
			return output;

		returnlimit = Integer.parseInt(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT));
		HashMap<String, List<String>> queryParams = new HashMap<>();
		queryParams.put(Constants.SEARCH_TERM, Arrays.asList(matchTerm));
		queryParams.put(Constants.STATE, Arrays.asList(enumState.name()));
		queryParams.put(Constants.SEMANTIC_TAG, semanticTagList);
		queryParams.put(Constants.ACCEPTABILITY,Arrays.asList(enumAcceptability.name()));
		queryParams.put(Constants.MAX_RECORDS,Arrays.asList(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT)));
		queryParams.put(Constants.REFSET_ID ,Arrays.asList(refsetId));


		Set<CompositeDescription> searchedDescriptions = searchAgent.search(queryParams,true);
		if(parentIdList!=null && !parentIdList.isEmpty())
		{
			searchedDescriptions=processForParentIdSearch(searchedDescriptions, parentIdList,excludeConcept);
		}
		if(groupByConcept)
		{
			searchedDescriptions = processForGroupByConcept(searchedDescriptions);
		}

		if (-1 >= returnLimit || returnlimit <= returnLimit) {
			return searchedDescriptions;
		}

		if (null != searchedDescriptions) {
			for (CompositeDescription description : searchedDescriptions) {

				if (output.size() >= returnLimit) {
					break;
				}
				output.add(description);

			}
		}

		return output;
	}

	/**
	 * Returns concept details associated for a given concept identifier (corresponding descriptions and relationships are also listed)
	 * @param id {@link String} identifier of the SNOMED CT {@link Concept}.
	 * @param enumLangRefsetId {@link EnumLangRefset} Specifies English language reference set for US or UK.	 *
	 * @return {@link ConceptDetails} that contains the details corresponding to a particular SNOMED CT Concept
	 * (including descriptions and relationships related to that particular concept).
	 */
	@Override
	public ConceptDetails getConceptDetails(String id,EnumLangRefset enumLangRefsetId) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		if (null == id || !id.matches(RANGE))
			return null;

		ConceptDetails conceptDetails=null;
		Concept concept =null;
		List<CompositeDescription> descriptions=null;
		List<Relationship> relationships=new ArrayList<>();
		try{
			descriptions = new ArrayList<>(this.getAllDescriptions(id,enumLangRefsetId));
			relationships=searchAgent.getRelationships(id,"all",EnumDirection.getEnum("source"));
			boolean active=false;
			if(relationships.isEmpty())
				concept=searchAgent.search(id);
			else if(relationships.get(0)!=null)
				concept=relationships.get(0).getSourceConcept();
			if(concept!=null && 1==concept.getActiveStatus())
				active=true;
			if(null == concept)
				return null;
			else
				conceptDetails = new ConceptDetails(descriptions,relationships,concept.getFullySpecifiedName(),id,concept.getDefinitionStatusId(), active,
						concept.getTime(), concept.getModuleId());
		}
		catch(Exception e){
			// Log the Exception
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		}
		return conceptDetails;
	}


	/**
	 *  Returns all concept associated for a given attribute name
	 * @param attributeName
	 * @return  {@link Concept} that contains the list of corresponding to a particular SNOMED CT Concept attribute name and attribute value
	 */
	@Override
	public List<Relationship> getConcepts(String attribute){
		return this.getConcepts(attribute, null, null, false, null, false);
	}

	/**
	 * Returns all concept associated for a given attribute name and semantic tags
	 * @param attributeName
	 * @param semanticTagList
	 * @return  {@link Concept} that contains the list of corresponding to a particular SNOMED CT Concept attribute name
	 */
	@Override
	public List<Relationship> getConcepts(String attribute,List<String> semanticTagList){
		return this.getConcepts(attribute,semanticTagList,null,false,null,false);
	}

	/**
	 * Returns all concept associated for a given attribute name and destination id (attribute value)
	 * @param attributeName
	 * @param destinationId
	 * @return  {@link Concept} that contains the list of corresponding to a particular SNOMED CT Concept attribute name and attribute value
	 */
	@Override
	public List<Relationship> getConcepts(String attribute,String destinationId){
		return this.getConcepts(attribute,null,destinationId,false,null,false);
	}

	/**
	 * Returns all concept associated for a given attribute name, semantic tags and destination id (attribute value)
	 * @param attributeName
	 * @param semanticTagList
	 * @param destinationId
	 * @return  {@link Concept} that contains the list of corresponding to a particular SNOMED CT Concept attribute name and attribute value
	 */
	@Override
	public List<Relationship> getConcepts(String attribute,List<String> semanticTagList,String destinationId){
		return this.getConcepts(attribute,semanticTagList,destinationId,false,null,false);
	}

	/**
	 *  Returns all concept associated for a given attribute name and other parameters
	 * @param attributeName
	 * @param semanticTagList
	 * @param destinationId
	 * @param subsumption
	 * @param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @param excludeConcept
	 * @return  {@link Relationship} that contains the list of Relationships corresponding to a particular SNOMED CT attribute value
	 */
	@Override
	public List<Relationship> getConcepts(String attribute, List<String> semanticTagList, String destinationId,
			boolean subsumption,List<String> parentIdList,boolean excludeConcept){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		HashMap<String, List<String>> queryParam = null;
		List<Relationship> resultList = null;
		PropertyReader.loadSystemProperties();
		relationTypeSet=getRelationShipType();

		try {
			if(attribute!=null){
				//initialization if and only if satisfies criteria
				queryParam = new HashMap<>();
				attribute=attribute.trim().toLowerCase();
				if(attribute.isEmpty())
					throw new IllegalArgumentException("Attribute type can't be empty");
				else if(!attribute.matches("^[A-Za-z\\s\\-]*$"))
					throw new IllegalArgumentException("Invalid attribute type: "+attribute);
				else if(!relationTypeSet.contains(attribute.trim().toLowerCase()) && !attribute.toLowerCase().equals(EnumRelationshipType.IS_A.getValue())) //null==EnumRelationshipType.getEnumForValue(attribute))
					throw new IllegalArgumentException("Invalid attribute type: "+attribute);
				else
					queryParam.put(Constants.TYPE+Constants.CONCEPTID, Arrays.asList(attribute));
			}else
				throw new IllegalArgumentException("Attribute type can't be null");

			if(null != semanticTagList){
				//remove empty elements and convert others to lowercase
				Iterator<String> semanticTagIterator = semanticTagList.iterator();
				while (semanticTagIterator.hasNext()){
					if(semanticTagIterator.next().isEmpty())
						semanticTagIterator.remove();
					else
						semanticTagIterator.toString().trim().toLowerCase();
				}
				//get indexed semantic tag from sem index dir
				semanticTagSet=this.getSemanticTags();

				//replace snomed rt ctv3 to snomed rt+ctv3
				if(semanticTagList.contains(SNOMED_RT) && semanticTagList.contains("ctv3")){
					semanticTagList.remove(SNOMED_RT);
					semanticTagList.remove("ctv3");
					semanticTagList.add("snomed rt+ctv3");
				}
				//validate semantic tags
				if(!semanticTagSet.containsAll(semanticTagList)){
					//identify wrong semantic tag
					for(String semanticTag:semanticTagList){
						if(!semanticTagSet.contains(semanticTag)){
							if(!semanticTag.equalsIgnoreCase(EnumSemanticTag.ALL.getValue()))
								throw new IllegalArgumentException("Invalid semantic tag: "+semanticTag);
						}
					}

				}
				queryParam.put(Constants.SEMANTIC_TAG, semanticTagList);
			}

			if(destinationId!=null){
				if(destinationId.isEmpty())
					throw new IllegalArgumentException("ERROR:Destination id can't be empty");
				else if(!destinationId.matches("^[\\d]*$") || destinationId.length()>16 || destinationId.charAt(destinationId.length()-2)!='0')
					throw new IllegalArgumentException("ERROR:Invalid Destination id");
				else {
					//For destinationId
					queryParam.put(Constants.DESTINATION+Constants.CONCEPTID, Arrays.asList(destinationId));
					resultList = searchAgent.getConcepts(queryParam);
					if(subsumption) {
						//For Children
						Set<Concept> allDescendantsSet = getAllDescendantConcepts(destinationId);
						if( allDescendantsSet.size() > 0 ) {
							for (Concept cpt : allDescendantsSet) {
								queryParam.put(Constants.DESTINATION+Constants.CONCEPTID, Arrays.asList(cpt.getId()));
								resultList.addAll(searchAgent.getConcepts(queryParam));
							}
						}
					}
				}
			}

			//Parent Id processing
			if(parentIdList!=null && !parentIdList.isEmpty()) {
				resultList=processForParentIdSearchForRelationship(resultList, parentIdList, excludeConcept);
			}


			return resultList;
		}catch (ParseException | IOException | IllegalArgumentException e){
			//Log the Exception
			if(e instanceof IllegalArgumentException)
				logger.info(LoggerUtility.CONSOLE, "Error: {}",e.getMessage());
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		}catch (Exception e){
			//Log the Exception
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		}
		return Collections.emptyList();
	}




	/**
	 * Returns a SNOMED CT {@link Concept} associated for a given {@link Concept} identifier.
	 * @param id {@link String} identifier of the SNOMED CT {@link Concept}.
	 * @return {@link Concept} SNOMED CT concept.
	 */
	@Override
	public Concept getConcept(String id) {
		if (null == id || !id.matches(RANGE))
			return null;
		ComponentFactory componentFactory = ComponentFactory.getInstance();
		Concept concept = null;
		concept = (Concept) componentFactory.getComponent(EnumComponent.CONCEPT, id);
		return concept;
	}


	/**
	 * Returns {@link List} of SNOMED CT {@link Concept} associated for a given {@link Concept} identifier.
	 * @param ids {@link List} {@link String} identifier of the SNOMED CT {@link Concept}.
	 * @return {@link List} {@link Concept} SNOMED CT concept
	 */
	@Override
	public List<Concept> getConcept(List<String> ids) {
		List<Concept> concepts= new ArrayList<>();
		if (null == ids)
			return Collections.emptyList();
		for (String id : ids) {
			ComponentFactory componentFactory = ComponentFactory.getInstance();
			Concept concept = null;
			concept = (Concept) componentFactory.getComponent(EnumComponent.CONCEPT, id);
			concepts.add(concept);
		}
		return concepts;
	}

	/**
	 * Returns most matching suggestions for given term/text in SNOMED CT repository.
	 * @param matchTerm {@link String} term/text to be searched.
	 * @param refsetId Identifier of Refset if search is to be done from a specific Refset and null if search is to be done from whole SNOMED CT Terminology.
	 * @param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @return Set of {@link String} suggestions matching the given term/text.
	 */
	@Override
	public Set<String> getSuggestions(String matchTerm,String refsetId,List<String> parentIdList,boolean excludeConcept) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		Set<String> output = new LinkedHashSet<>();
		// This block added for not returning any suggestions if ConceptID/Desc
		// ID is input by user
		/**
		 * JAVA SCRIPT LONG LIMIT: +/-9007199254740992 JAVA LONG LIMIT: +/-
		 * 9223372036854775807 Sample SNOMED ID: 900000000000441000
		 */
		try {
			if (null != matchTerm) {
				matchTerm = matchTerm.trim();
				if (!matchTerm.isEmpty()) {
					Long.parseLong(matchTerm);
					return output;
				}

			}
		} catch (NumberFormatException e) {
			// Log the Exception
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		}

		int topReturnLimit = -1;

		PropertyReader.loadSystemProperties();
		topReturnLimit = Integer.parseInt(PropertyReader
				.getProperty(Constants.TOP_SUGGEST_RETURNLIMIT));

		HashMap<String, List<String>> queryParams = new HashMap<>();
		queryParams.put(Constants.SEARCH_TERM, Arrays.asList(matchTerm));
		queryParams.put(Constants.MAX_RECORDS,Arrays.asList(PropertyReader.getProperty(Constants.SUGGEST_RETURNLIMIT)));
		queryParams.put(Constants.REFSET_ID ,Arrays.asList(refsetId));

		Set<CompositeDescription> descriptions = searchAgent.search(queryParams,true);

		if(parentIdList!=null && !parentIdList.isEmpty())
		{
			descriptions=processForParentIdSearch(descriptions, parentIdList,excludeConcept);
		}

		List<CompositeDescription> descriptionList=new ArrayList<>();

		descriptionList.addAll(descriptions);
		Collections.sort(descriptionList, new KeyBasedComparator(matchTerm));

		if (null != descriptionList) {
			for (Description description : descriptionList) {
				if (output.size() >= topReturnLimit) {
					break;
				}
				output.add(description.getTerm());
			}
		}
		return output;
	}

	/**
	 * Returns limited best matching suggestions for given term/text in SNOMED CT repository.
	 * @param matchTerm {@link String} term/text to be searched in SNOMED CT repository.
	 * @param returnLimit Maximum number of matching terms to be fetched.
	 * @param refsetId Identifier of Refset if search is to be done from a specific Refset and null if search is to be done from whole SNOMED CT Terminology.
	 * @param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @return Set of {@link String} matching the given term/text.
	 */
	@Override
	public Set<String> getSuggestions(String matchTerm, int returnLimit,String refsetId, List<String> parentIdList,boolean excludeConcept) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		Set<String> output = new LinkedHashSet<>();
		// This block added for not returning any suggestions if ConceptID/Desc
		// ID is input by user
		/**
		 * JAVA SCRIPT LONG LIMIT: +/-9007199254740992 JAVA LONG LIMIT: +/-
		 * 9223372036854775807 Sample SNOMED ID: 900000000000441000
		 */
		try {
			if (null != matchTerm) {
				matchTerm = matchTerm.trim();
				if (!matchTerm.isEmpty()) {
					Long.parseLong(matchTerm);
					return output;
				}

			}
		} catch (NumberFormatException e) {
			// Log the Exception
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		}
		int allReturnLimit = -1;
		int topReturnLimit = 10;

		PropertyReader.loadSystemProperties();
		topReturnLimit = Integer.parseInt(PropertyReader
				.getProperty(Constants.TOP_SUGGEST_RETURNLIMIT));
		allReturnLimit = Integer.parseInt(PropertyReader
				.getProperty(Constants.SUGGEST_RETURNLIMIT));

		Set<CompositeDescription> searchedDescriptions = search(matchTerm, allReturnLimit, false, refsetId,parentIdList,excludeConcept); //check

		List<CompositeDescription> descriptionList=new ArrayList<>();

		descriptionList.addAll(searchedDescriptions);
		Collections.sort(descriptionList, new KeyBasedComparator(matchTerm));

		if (-1 >= returnLimit || topReturnLimit < returnLimit) {
			returnLimit = topReturnLimit;
		}

		if (null != descriptionList) {
			for (Description description : descriptionList) {
				if (output.size() >= returnLimit) {
					break;
				}
				output.add(description.getTerm());
			}
		}

		return output;
	}

	/**
	 * Returns most matching suggestions for term/text in the SNOMED CT repository based on provided component state (active, inactive, both).
	 * by the user.
	 * @param matchTerm {@link String} term/text to be searched in SNOMED CT repository.
	 * @param enumState {@link EnumState} State of a component.<br>
	 *            If enumState is,<br>
	 *  		  active - It will return only active Components.<br>
	 * 			  inactive - It will return only inactive Components.<br>
	 * 			  both - It will return both active and inactive Components.<br>
	 * @param returnLimit Maximum number of matching terms to be fetched.
	 * @param refsetId Identifier of Refset if search is to be done from a specific Refset and null if search is to be done from whole SNOMED CT Terminology.
	 * @param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @return Set of {@link String} matching the given term/text.
	 */
	@Override
	public Set<String> getSuggestions(String matchTerm, EnumState enumState,
			int returnLimit,String refsetId,List<String> parentIdList,boolean excludeConcept) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);

		Set<String> output = new LinkedHashSet<>();

		if (enumState == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_STATE + enumState;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		}
		if (EnumState.BOTH == enumState) {
			return getSuggestions(matchTerm, returnLimit,refsetId,parentIdList,excludeConcept);
		}


		// This block added for not returning any suggestions if ConceptID/Desc
		// ID is input by user
		/**
		 * JAVA SCRIPT LONG LIMIT: +/-9007199254740992 JAVA LONG LIMIT: +/-
		 * 9223372036854775807 Sample SNOMED ID: 900000000000441000
		 */
		try {
			if (null != matchTerm) {
				matchTerm = matchTerm.trim();
				if (!matchTerm.isEmpty()) {
					Long.parseLong(matchTerm);
					return output;
				}
			}
		} catch (NumberFormatException e) {
			// Log the Exception
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		}

		int topReturnLimit = 10;

		PropertyReader.loadSystemProperties();
		topReturnLimit = Integer.parseInt(PropertyReader
				.getProperty(Constants.TOP_SUGGEST_RETURNLIMIT));


		HashMap<String, List<String>> queryParams = new HashMap<>();
		queryParams.put(Constants.SEARCH_TERM, Arrays.asList(matchTerm));
		queryParams.put(Constants.STATE, Arrays.asList(enumState.name()));
		queryParams.put(Constants.MAX_RECORDS,Arrays.asList(PropertyReader.getProperty(Constants.SUGGEST_RETURNLIMIT)));
		queryParams.put(Constants.REFSET_ID ,Arrays.asList(refsetId));

		Set<CompositeDescription> searchedDescriptions = searchAgent
				.search(queryParams,true);

		if(parentIdList!=null && !parentIdList.isEmpty())
		{
			searchedDescriptions=processForParentIdSearch(searchedDescriptions, parentIdList,excludeConcept);
		}
		/**
		 * Modified to sort suggestions based on keyword
		 */
		List<CompositeDescription> descriptionList=new ArrayList<>();

		descriptionList.addAll(searchedDescriptions);
		Collections.sort(descriptionList, new KeyBasedComparator(matchTerm));

		if (-1 >= returnLimit || topReturnLimit < returnLimit) {
			returnLimit = topReturnLimit;
		}
		if (null != descriptionList) {
			for (CompositeDescription description : descriptionList) {
				if (output.size() >= returnLimit) {
					break;
				}
				output.add(description.getTerm());

			}
		}

		return output;
	}

	/**
	 * Returns most matching suggestions for given term/text or concept id in the SNOMED CT repository based on provided component state, semantic tag.
	 * @param matchTerm {@link String} term/text or Concept ID to be searched in SNOMED CT repository.
	 * @param enumState {@link EnumState} State of a component.<br>
	 *            If enumState is,<br>
	 *  		  active - It will return only active Components.<br>
	 * 			  inactive - It will return only inactive Components.<br>
	 * 			  both - It will return both active and inactive Components.<br>
	 * @param enumSemanticTagList
	 *            {@link EnumSemanticTag} Refers to the Semantic Tag in which SNOMED CT term/text or concept id will be searched. <br>
	 *            If enumSemanticTag is,<br>
	 *            all - It will search SNOMED CT term in all the Semantic Tags.<br>
	 *            For other possible values please refer {@link EnumSemanticTag}.<br>
	 *            A list of {@link EnumSemanticTag} can also be provide in which search/suggest is to be perform.<br>
	 * @param returnLimit Maximum number of matching terms to be fetched.
	 * @param refsetId Identifier of Refset if search is to be done from a specific Refset and null if search is to be done from whole SNOMED CT Terminology.
	 * @param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @return Set of {@link String} matching the given term/text or Concept ID.
	 */
	@Override
	public Set<String> getSuggestions(String matchTerm, EnumState enumState,
			List<String> semanticTagList, int returnLimit,String refsetId, List<String> parentIdList,boolean excludeConcept) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);

		Set<String> output = new LinkedHashSet<>();
		if (enumState == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_STATE + enumState;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		} else if (semanticTagList == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_SEMANTIC_TAG + semanticTagList;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		}

		semanticTagList=this.validateSemanticTag(semanticTagList);
		if(semanticTagList==null)
			return output;

		// This block added for not returning any suggestions if ConceptID/Desc
		// ID is input by user
		/**
		 * JAVA SCRIPT LONG LIMIT: +/-9007199254740992 JAVA LONG LIMIT: +/-
		 * 9223372036854775807 Sample SNOMED ID: 900000000000441000
		 */
		try {
			if (null != matchTerm) {
				matchTerm = matchTerm.trim();
				if (!matchTerm.isEmpty()) {
					Long.parseLong(matchTerm);
					return output;
				}
			}
		} catch (NumberFormatException e) {
			// Log the Exception
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		}

		int topReturnLimit = 10;

		PropertyReader.loadSystemProperties();
		topReturnLimit = Integer.parseInt(PropertyReader
				.getProperty(Constants.TOP_SUGGEST_RETURNLIMIT));

		HashMap<String, List<String>> queryParams = new HashMap<>();
		queryParams.put(Constants.SEARCH_TERM, Arrays.asList(matchTerm));
		queryParams.put(Constants.STATE, Arrays.asList(enumState.name()));
		if(!semanticTagList.contains(EnumSemanticTag.ALL.getValue()))
			queryParams.put(Constants.SEMANTIC_TAG, semanticTagList);
		queryParams.put(Constants.MAX_RECORDS,Arrays.asList(PropertyReader.getProperty(Constants.SUGGEST_RETURNLIMIT)));
		queryParams.put(Constants.REFSET_ID ,Arrays.asList(refsetId));

		Set<CompositeDescription> searchedDescriptions = searchAgent
				.search(queryParams,true);

		if(parentIdList!=null && !parentIdList.isEmpty())
		{
			searchedDescriptions=processForParentIdSearch(searchedDescriptions, parentIdList,excludeConcept);
		}

		List<CompositeDescription> descriptionList=new ArrayList<>();

		descriptionList.addAll(searchedDescriptions);
		Collections.sort(descriptionList, new KeyBasedComparator(matchTerm));

		if (-1 >= returnLimit || topReturnLimit < returnLimit) {
			returnLimit = topReturnLimit;
		}

		if (null != descriptionList) {
			for (Description description : descriptionList) {

				if (output.size() >= returnLimit) {
					break;
				}
				output.add(description.getTerm());

			}
		}

		return output;

	}

	/**
	 * Returns most matching suggestions for given term/text or concept id in the SNOMED CT repository based on provided component state, semantic tag, acceptability.
	 * @param matchTerm {@link String} term/text or Concept ID to be searched in SNOMED CT repository.
	 * @param enumState {@link EnumState} State of a component.<br>
	 *            If enumState is,<br>
	 *  		  active - It will return only active Components.<br>
	 * 			  inactive - It will return only inactive Components.<br>
	 * 			  both - It will return both active and inactive Components.<br>
	 * @param enumSemanticTagList
	 *            {@link EnumSemanticTag} Refers to the Semantic Tag in which SNOMED CT term/text or concept id will be searched. <br>
	 *            If enumSemanticTag is,<br>
	 *            all - It will search SNOMED CT term in all the Semantic Tags.<br>
	 *            For other possible values please refer {@link EnumSemanticTag}.<br>
	 *            A list of {@link EnumSemanticTag} can also be provide in which search/suggest is to be perform.<br>
	 * @param enumAcceptability
	 *            {@link EnumAcceptability} Represents the type of description i.e. whether the search results contains preferred terms or
	 *            preferred terms other than FSN or only acceptable terms and so on.<br>
	 *            If enumAcceptability is, <br>
	 *            preferred - refers to all preferred description for a concept (including FSN). <br>
	 *  		  preferredexcludingfsn - refers to SNOMEDCT description for preferred terms excluding FSN.<br>
	 *  		  synonyms - refers to all description for a concept (excluding FSN).<br>
	 *  		  acceptable- refers to all description for a concept excluding preferred.<br>
	 *  		  all - refers to all description for a concept (both preferred and acceptable).<br>
	 * @param returnLimit Maximum number of matching terms to be fetched.
	 * @param refsetId Identifier of Refset if search is to be done from a specific Refset and null if search is to be done from whole SNOMED CT Terminology.
	 * @param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @return Set of {@link String} matching the given term/text or Concept ID.
	 */
	@Override
	public Set<String> getSuggestions(String matchTerm, EnumState enumState,
			List<String> semanticTagList, EnumAcceptability enumAcceptability, int returnLimit,String refsetId,List<String> parentIdList,boolean excludeConcept) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		Set<String> output = new LinkedHashSet<>();

		if (enumState == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_STATE + enumState;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		} else if (semanticTagList == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_SEMANTIC_TAG + semanticTagList;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		} else if (enumAcceptability == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_ACCEPTABILITY
					+ enumAcceptability;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		}

		if (EnumAcceptability.ALL == enumAcceptability) {
			return getSuggestions(matchTerm, enumState, semanticTagList, returnLimit,refsetId,parentIdList,excludeConcept);
		}

		semanticTagList=this.validateSemanticTag(semanticTagList);
		if(semanticTagList==null)
			return output;

		// This block added for not returning any suggestions if ConceptID/Desc
		// ID is input by user

		if (matchTerm.matches(RANGE)) {
			return output;
		}

		int topReturnLimit = 10;
		PropertyReader.loadSystemProperties();
		topReturnLimit = Integer.parseInt(PropertyReader
				.getProperty(Constants.TOP_SUGGEST_RETURNLIMIT));


		HashMap<String, List<String>> queryParams = new HashMap<>();
		queryParams.put(Constants.SEARCH_TERM, Arrays.asList(matchTerm));
		queryParams.put(Constants.STATE, Arrays.asList(enumState.name()));
		if(!semanticTagList.contains(EnumSemanticTag.ALL.getValue()))
			queryParams.put(Constants.SEMANTIC_TAG,semanticTagList);
		queryParams.put(Constants.ACCEPTABILITY, Arrays.asList(enumAcceptability.name()));
		queryParams.put(Constants.MAX_RECORDS,Arrays.asList(PropertyReader.getProperty(Constants.SUGGEST_RETURNLIMIT)));
		queryParams.put(Constants.REFSET_ID ,Arrays.asList(refsetId));

		Set<CompositeDescription> descriptions = searchAgent.search(queryParams,true);

		if(parentIdList!=null && !parentIdList.isEmpty())
		{
			descriptions=processForParentIdSearch(descriptions, parentIdList,excludeConcept);
		}

		List<CompositeDescription> descriptionList=new ArrayList<>();

		descriptionList.addAll(descriptions);
		Collections.sort(descriptionList, new KeyBasedComparator(matchTerm));

		if (-1 >= returnLimit || topReturnLimit < returnLimit) {
			returnLimit = topReturnLimit;
		}

		if (null != descriptionList) {
			for (Description description : descriptionList) {

				if (output.size() >= returnLimit) {
					break;
				}
				output.add(description.getTerm());

			}
		}

		return output;
	}
	/**
	 * Retrieves the Semantic Tag for a particular Concept identifier.
	 * @param conceptId {@link String} value for Concept identifier for which hierarchy is to be identified.
	 * @param typeId type of the description.
	 * @param term the description for that concept.
	 * @return the hierarchy to which the concept belongs.
	 * @throws SQLException if error occurs while accessing database.
	 */
	public String getHierarchy(String conceptId, String typeId, String term)
			throws SQLException {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		if (null == conceptId)
			return null;
		else {
			try {
				QueryManager qm = new QueryManager();
				return qm.getHierarchy(conceptId, typeId, term);
			} catch (SQLException e) {
				// Log the Exception
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}catch (Exception e) {
				// Log the Exception
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}
		}
		return null;
	}

	/**
	 * Returns a {@link Set} of all Descendants {@link Concept}s for given concept identifier.
	 * @param id SNOMED CT concept identifier.
	 * @return Set of {@link Concept}s.
	 */
	@Override
	public Set<Concept> getAllDescendantConcepts(String id) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		if (null == id || !id.matches(RANGE))
			return Collections.emptySet();
		else {
			try {
				return searchAgent.searchTC(id);
			} catch (ParseException e) {
				// Log the Exception
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}catch (Exception e) {
				// Log the Exception
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}
		}

		return Collections.emptySet();
	}


	/**
	 * The method will return Set of Composite Descriptions such that one Description per Semantic tag for one Concept id.
	 * @param setCompositeDescription LinkedHashSet of {@link CompositeDescription} of matched terms.
	 * @return LinkedHashSet of {@link CompositeDescription} one Description per Semantic tag for one Concept id.
	 */
	private Set<CompositeDescription> processForGroupByConcept(Set<CompositeDescription> setCompositeDescription)
	{
		Set<CompositeDescription> outputGroupByConcept = new LinkedHashSet<>();
		ArrayList<String> lstUniqueConcept= new ArrayList<>();

		HashMap<String, CompositeDescription> hm = new HashMap<>();
		for (CompositeDescription cd : setCompositeDescription) {
			if(!lstUniqueConcept.contains(cd.getConceptId()))
			{
				outputGroupByConcept.add(cd);
				lstUniqueConcept.add(cd.getConceptId());
				hm.put(cd.getConceptId(), cd);
			}
			else if(!cd.getHierarchy().equalsIgnoreCase((hm.get(cd.getConceptId()).getHierarchy())))
			{
				outputGroupByConcept.add(cd);
			}
		}

		return outputGroupByConcept;
	}

	/**
	 * The method will return Set of Composite Descriptions such that one Description per Semantic tag for one Concept id.
	 * @param setCompositeDescription LinkedHashSet of {@link CompositeDescription} of matched terms.
	 * @param matchTerm String value of term to be searched.
	 * @param excludeConcept {boolean true/false} true= searching the concept/terms  other then respective  parent id ; false = searching the concept/terms within a respective parent id  the
	 * @return LinkedHashSet of {@link CompositeDescription} one Description per Semantic tag for one Concept id.
	 */
	private Set<CompositeDescription> processForParentIdSearch(Set<CompositeDescription> searchResultCompositeDescription, List<String> parentIdList,boolean excludeConcept)
	{

		if(parentIdList!=null && !parentIdList.isEmpty())
		{
			Set<Concept> allDescendantsSet = new LinkedHashSet<>();
			for (String parentId : parentIdList)
			{
				allDescendantsSet.addAll(getAllDescendantConcepts(parentId));
			}
			Set<String> allDescendantsConceptIdSet = new LinkedHashSet<>();
			for (Concept cpt : allDescendantsSet) {
				allDescendantsConceptIdSet.add(cpt.getId());
			}
			Iterator<CompositeDescription> itr = searchResultCompositeDescription.iterator();
			while(itr.hasNext()) {
				String sctid = itr.next().getConceptId();
				if(!allDescendantsConceptIdSet.contains(sctid) && excludeConcept == false)
				{
					itr.remove();
				} else if(allDescendantsConceptIdSet.contains(sctid) && excludeConcept == true) {
					itr.remove();
				}
			}
			return searchResultCompositeDescription;
		}
		else
			return Collections.emptySet();
	}

	/**
	 * The method will return List of Relationships with respect to parent(s).
	 * @param searchResultRelationship LinkedHashSet of {@link Relationship} Relationships.
	 * @param parentIdList List of Identifier of Concepts if search is to be done from it's descendants only and null if search is to be done from whole SNOMED CT Terminology.
	 * @param excludeConcept {boolean true/false} true= searching the relationship other than respective parent id ; false = searching the relationships within a respective parent id
	 * @return List of {@link Relationship}
	 */
	private List<Relationship> processForParentIdSearchForRelationship(List<Relationship> searchResultRelationship, List<String> parentIdList,boolean excludeConcept)
	{

		if(parentIdList!=null && !parentIdList.isEmpty())
		{
			Set<Concept> allDescendantsSet = new LinkedHashSet<>();
			for (String parentId : parentIdList)
			{
				allDescendantsSet.addAll(getAllDescendantConcepts(parentId));
			}
			Set<String> allDescendantsConceptIdSet = new LinkedHashSet<>();
			for (Concept cpt : allDescendantsSet) {
				allDescendantsConceptIdSet.add(cpt.getId());
			}
			Iterator<Relationship> itr = searchResultRelationship.iterator();
			while(itr.hasNext()) {
				String sctid = itr.next().getSourceConcept().getId();
				if(!allDescendantsConceptIdSet.contains(sctid) && excludeConcept == false)
				{
					itr.remove();
				} else if(allDescendantsConceptIdSet.contains(sctid) && excludeConcept == true) {
					itr.remove();
				}
			}
			return searchResultRelationship;
		}
		else
			return Collections.emptyList();
	}

	/**
	 * The method will configure the properties to default values
	 * */
	@Override
	public void init()
	{

		init(EnumProperties.DATA_PATH.getValue(),EnumProperties.ERROR_LOG_PATH.getValue(),Integer.parseInt(EnumProperties.SEARCH_RETURNLIMIT_VAL.getValue()),Integer.parseInt(EnumProperties.SUGGEST_RETURNLIMIT_VAL.getValue()),Integer.parseInt(EnumProperties.TOP_SUGGEST_RETURNLIMIT_VAL.getValue()));

	}

	/**
	 * The method will configure properties by values provided in parameters
	 * @param dataDir location for database and lucene index.
	 * @param errorLogDir location for logs.
	 * @param searchReturnlimit maximum number record from which search should be perform example: 10000
	 * @param suggestReturnlimit maximum  number of record from which suggest should be perform example: 500
	 * @param topSuggestReturnlimit maximum number of top suggestions that would be return in suggestion list example: 50
	 */
	@Override
	public void init(String dataDir, String errorLogDir, Integer searchReturnlimit , Integer suggestReturnlimit, Integer topSuggestReturnlimit)
	{
		PropertyReader.loadSystemProperties();

		if(null==dataDir)
			dataDir=EnumProperties.DATA_PATH.getValue();

		File file=new File(dataDir);
		if(!file.exists()){
			dataDir=file.getAbsolutePath();
		}
		PropertyReader.setProperty(Constants.DATA_DIR, dataDir);

		if(null==errorLogDir)
			errorLogDir=EnumProperties.ERROR_LOG_PATH.getValue();

		file=new File(errorLogDir);
		if(!file.exists()){
			errorLogDir=file.getAbsolutePath();
		}
		PropertyReader.setProperty(Constants.ERROR_LOG_DIR, errorLogDir);
		//Setting up system variable 'error_log_dir' for log4j
		System.setProperty("error_log_dir", errorLogDir);



		if(null==searchReturnlimit)
			PropertyReader.setProperty(Constants.SEARCH_RETURNLIMIT , EnumProperties.SEARCH_RETURNLIMIT_VAL.getValue());
		else
			PropertyReader.setProperty(Constants.SEARCH_RETURNLIMIT , Integer.toString(searchReturnlimit));


		if(null==suggestReturnlimit)
			PropertyReader.setProperty(Constants.SUGGEST_RETURNLIMIT , EnumProperties.SUGGEST_RETURNLIMIT_VAL.getValue());
		else
			PropertyReader.setProperty(Constants.SUGGEST_RETURNLIMIT , Integer.toString(suggestReturnlimit));

		if(null==topSuggestReturnlimit)
			PropertyReader.setProperty(Constants.TOP_SUGGEST_RETURNLIMIT , EnumProperties.TOP_SUGGEST_RETURNLIMIT_VAL.getValue());
		else
			PropertyReader.setProperty(Constants.TOP_SUGGEST_RETURNLIMIT , Integer.toString(topSuggestReturnlimit));


	}
	/**
	 * This method creates SNOMED CT database
	 * @param snomedIntDirPath Directory path for SNOMED CT International Release directory 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
	 */
	@Override
	public boolean createSNOMEDdb(String snomedIntDirPath, boolean overridedb){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		if(!overridedb)
		{
			return false;
		}
		LuceneSearchAgent searchAgent=(LuceneSearchAgent)this.searchAgent;
		String conceptFile=null;
		String descriptionFile=null;
		String relationshipFile=null;
		String refsetLangFile=null;
		String textdefinitionFile=null;
		String concreteRelationshipFile=null;
		PropertyReader.loadSystemProperties();
		File snomedIntDir = new File(snomedIntDirPath);
		if(!snomedIntDir.exists())
		{
			logger.error(LoggerUtility.ConsoleWithException, WRONG_DIRECTORY_PROVIDED_FOR_SNOMED_CT_RELEASE_FILES);
			return false;
		}

		File[] filesTerminology = new File(snomedIntDirPath+File.separator+TERMINOLOGY).listFiles();
		if(filesTerminology==null){
			logger.error(LoggerUtility.ConsoleWithException, WRONG_DIRECTORY_PROVIDED_FOR_SNOMED_CT_RELEASE_FILES);
			return false;
		}
		else{
			for (File file : filesTerminology) {


				if(file.getName().matches("sct2_Concept(.*)"))
				{
					conceptFile=file.getAbsolutePath();
				}
				if(file.getName().matches("sct2_Description(.*)"))
				{
					descriptionFile=file.getAbsolutePath();
				}
				if(file.getName().matches("sct2_RelationshipConcreteValues(.*)"))
				{
					if(RF2FileHasRecords( file ))
						concreteRelationshipFile=file.getAbsolutePath();

					continue;
				}
				if(file.getName().matches("sct2_Relationship(.*)"))
				{
					relationshipFile=file.getAbsolutePath();
				}
				if(file.getName().matches("sct2_TextDefinition(.*)"))
				{
					textdefinitionFile=file.getAbsolutePath();
				}
			}

			File[] filesRefset = new File(snomedIntDirPath+File.separator+REFSET+File.separator+"Language").listFiles();
			for (File file : filesRefset) {
				if(file.getName().matches(DER2_C_REFSET))
					refsetLangFile=file.getAbsolutePath();
			}
			if(conceptFile==null || descriptionFile==null || relationshipFile==null || refsetLangFile==null){

				logger.error(LoggerUtility.ConsoleWithException, WRONG_DIRECTORY_PROVIDED_FOR_SNOMED_CT_RELEASE_FILES);
				return false;
			}

			logger.info(LoggerUtility.ConsoleWithLog, PLEASE_WAIT_FOR_DB_CREATION);
			QueryManager qm = new QueryManager();
			try {
				qm.createSNOMEDdb(conceptFile, descriptionFile, relationshipFile, refsetLangFile,textdefinitionFile,concreteRelationshipFile);
				ClosureBuilderAgent generator = new ClosureBuilderAgent();
				generator.setrelFile(relationshipFile);
				generator.buildClosureTable(false);
				logger.info(LoggerUtility.ConsoleWithLog, DATABASE_CREATED_SUCCESSFULLY);

				if(searchAgent.deleteDataDirectory(PropertyReader.getProperty(Constants.DATA_DIR))){
					boolean indexGenerated = false;
					if(concreteRelationshipFile==null) {
						indexGenerated = searchAgent.createIndex(null,null,true,false);
					}
					else {
						indexGenerated = searchAgent.createIndex(null,null,true,true);
					}

					if( indexGenerated )
						logger.info(LoggerUtility.ConsoleWithLog, ALL_INDEX_GENERATION_FINISHED);

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


	/**
	 * This method creates database for given refsets
	 * @param refsetFilePath file path for refset example: G:/SnomedCT_GPFPICPC2_PRODUCTION_20171018T120000Z/Snapshot/Refset/Content/der2_Refset_SimpleSnapshot_INT_20170731.txt
	 * @param overridedb will override database and index if true. It should be false after database creation process
	 */
	@Override
	public boolean createRefsetdb(String refsetFilePath, boolean overridedb)
	{
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		if(!overridedb)
			return false;
		File refFile;
		String refsetId = null;
		Set<String> moduleIdSet=new LinkedHashSet<>();
		LuceneSearchAgent searchAgent=(LuceneSearchAgent)this.searchAgent;
		refFile= new File(refsetFilePath);
		if(!refFile.exists())
		{
			logger.error(LoggerUtility.ConsoleWithException, "{}{}", WRONG_PATH_PROVIDED_FOR_REFSET_FILE,refsetFilePath);
			return false;
		}
		if(refFile.getName().matches(DER2_C_REFSET)){
			return false;
		}
		QueryManager qm =new QueryManager();

		try {
			BufferedReader in = new BufferedReader(new FileReader(refFile));
			in.readLine();
			String[] tokens = in.readLine().split("\t");
			refsetId=tokens[4];
			String line=null;
			for(int i=0;i<15;i++){
				line=in.readLine();
				if(line!=null)
					moduleIdSet.add(line.split("\t")[3]);
				else
					break;
			}
			in.close();
			logger.info(LoggerUtility.ConsoleWithLog, "Please wait for refset db creation : {}",refsetId);
			qm.createRefsetdb(refsetId, refFile.getAbsolutePath(),moduleIdSet);
			logger.info(LoggerUtility.ConsoleWithLog, "Refset database created successfully.");

			searchAgent.checkIndex(refsetId);
			boolean indexGenerated = searchAgent.createIndex(refsetId,null,true,false);
			if( indexGenerated )
				logger.info(LoggerUtility.ConsoleWithLog, ALL_INDEX_GENERATION_FINISHED);

			return indexGenerated;

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

	/**
	 * This method creates database for given refsets
	 * @param refsetFilePath list of file path for refset example: G:/SnomedCT_GPFPICPC2_PRODUCTION_20171018T120000Z/Snapshot/Refset/Content/der2_Refset_SimpleSnapshot_INT_20170731.txt
	 * @param moduleIdSet list of modules
	 * @param forCRefset creates table for crefset if true, else for refset
	 */
	private boolean createRefsetdbAndModuleSet(List<String> refsetContent, Set<String> moduleIdSet, boolean forCRefset){
		// TO DO : Improve logic here
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		File refFile;
		String refsetId = null;
		QueryManager qm = new QueryManager();
		if(refsetContent.isEmpty())
			return false;
		try {
			// get moduleid
			BufferedReader in = new BufferedReader(new FileReader(refsetContent.get(0)));
			in.readLine();
			String line=null;
			for(int i=0;i<15;i++){
				line=in.readLine();
				if(line!=null)
					moduleIdSet.add(line.split("\t")[3]);
				else
					break;
			}
			in.close();
		} catch (Exception e) {
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			return false;
		}
		for(String refsetFilePath:refsetContent){
			refFile= new File(refsetFilePath);
			if(!refFile.exists())
			{
				logger.error(LoggerUtility.ConsoleWithException,"{}{}", WRONG_PATH_PROVIDED_FOR_REFSET_FILE,refsetFilePath);
				return false;
			}
			try{
				// get refset id
				BufferedReader in = new BufferedReader(new FileReader(refFile));
				in.readLine();
				String[] tokens = in.readLine().split("\t");
				refsetId=tokens[4];
				in.close();
				if(!forCRefset)
					qm.createRefsetdb(refsetId, refFile.getAbsolutePath(),moduleIdSet);  // creating reset id db
				else
					qm.createcRefsetdb(refsetId, refFile.getAbsolutePath(),moduleIdSet);

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


	/**
	 * This method creates SNOMED CT extension database
	 * @param snomedExtDirPath Directory path for SNOMED CT extension Release directory 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
	 */
	@Override
	public boolean createExtensiondb(String snomedExtDirPath, boolean overridedb){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		if(!overridedb){
			return false;
		}

		LuceneSearchAgent searchAgent=(LuceneSearchAgent)this.searchAgent;
		Set<String> moduleIdSet=new LinkedHashSet<>();
		String conceptFile=null;
		String descriptionFile=null;
		String concreteRelationshipFile=null;
		String relationshipFile=null;
		String refsetLangFile=null;

		PropertyReader.loadSystemProperties();
		File snomedExtDir = new File(snomedExtDirPath);
		if(!snomedExtDir.exists()){
			logger.error(LoggerUtility.ConsoleWithException, WRONG_DIRECTORY_PROVIDED_FOR_SNOMED_CT_EXTENSION_RELEASE_FILES);
			return false;
		}

		File[] filesTerminology = new File(snomedExtDirPath+File.separator+TERMINOLOGY).listFiles();
		for (File file : filesTerminology) {
			if(file.getName().matches(".*sct2_Concept(.*)")){
				conceptFile=file.getAbsolutePath();
			}
			if(file.getName().matches(".*sct2_Description(.*)")){
				descriptionFile=file.getAbsolutePath();
			}
			if(file.getName().matches(".*sct2_RelationshipConcreteValues(.*)")){
				if(RF2FileHasRecords(file))
					concreteRelationshipFile=file.getAbsolutePath();

				continue;
			}
			if(file.getName().matches(".*sct2_Relationship(.*)")){
				relationshipFile=file.getAbsolutePath();
			}


		}

		File[] filesRefset = new File(snomedExtDirPath+File.separator+REFSET+File.separator+"Language").listFiles();
		for (File file : filesRefset) {
			if(file.getName().matches(DER2_C_REFSET))
				refsetLangFile=file.getAbsolutePath();
		}
		if(conceptFile==null || descriptionFile==null || relationshipFile==null || refsetLangFile==null){
			logger.error(LoggerUtility.ConsoleWithException, WRONG_DIRECTORY_PROVIDED_FOR_SNOMED_CT_EXTENSION_RELEASE_FILES);
			return false;
		}
		// To get ModuleID of extension
		try {
			BufferedReader in = new BufferedReader(new FileReader(conceptFile));
			in.readLine();
			String line=null;
			for(int i=0;i<15;i++){
				line=in.readLine();
				if(line!=null)
					moduleIdSet.add(line.split("\t")[3]);
				else
					break;
			}
			in.close();
		} catch (Exception e) {
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			return false;
		}
		logger.info(LoggerUtility.ConsoleWithLog, PLEASE_WAIT_FOR_DB_CREATION);
		QueryManager qm = new QueryManager();
		try {
			qm.createExtensiondb(moduleIdSet, conceptFile, descriptionFile, relationshipFile, refsetLangFile, concreteRelationshipFile);

		} catch (Exception e) {
			// Log the Exception
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			return false;
		}
		List <String> refsetContent=new ArrayList<>();
		List <String>  cRefsetContent=new ArrayList<>();
		List<String> skipRefsetContent=new ArrayList<>();
		Set<String> rmoduleIdSet=new LinkedHashSet<>();
		Set<String> crmoduleIdSet=new LinkedHashSet<>();
		File[] filesRefset_content = new File(snomedExtDirPath+File.separator+REFSET+File.separator+"Content").listFiles();
		try{
			for (File file : filesRefset_content) {
				if((file.getName().matches(".*der2_Refset_(.*)"))){
					if(RF2FileHasRecords(file)){
						refsetContent.add(file.getAbsolutePath());
					}
					else
						skipRefsetContent.add(file.getName());
				}
				else if((file.getName().matches(DER2_C_REFSET))){
					if(RF2FileHasRecords(file)){
						cRefsetContent.add(file.getAbsolutePath());
					}
					else
						skipRefsetContent.add(file.getName());

				}

			}
			if(skipRefsetContent!=null){
				for(String f:skipRefsetContent)
					logger.info(LoggerUtility.ConsoleWithLog, "Skipped RefestFile : {}",f);
			}

			//To get ModuleID of Refset
			if(!(refsetContent.isEmpty())&&refsetContent!=null)
				createRefsetdbAndModuleSet(refsetContent,rmoduleIdSet,false);

			if(!cRefsetContent.isEmpty())
				createRefsetdbAndModuleSet(cRefsetContent,crmoduleIdSet,true);

			ClosureBuilderAgent generator = new ClosureBuilderAgent();
			//Create temporary relationship file and fetch its path
			String filePath = qm.exportRelationshipTableAndDeleteTCTable();
			if( filePath.equals("Error") ) {
				logger.error(LoggerUtility.Exception, "Error creating temporary file");
				return false;
			}
			generator.setrelFile(filePath);
			generator.buildClosureTable(true);
			//Delete temporary file
			qm.deleteRelationshipTempFile(filePath);

			logger.info(LoggerUtility.ConsoleWithLog, DATABASE_CREATED_SUCCESSFULLY);

		} catch (Exception e) {
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		}
		if(searchAgent.checkIndex(Constants.SNOINDEX)&&searchAgent.checkIndex(Constants.RELATION_INDEX)&&searchAgent.checkIndex(Constants.TCINDEX)){
			boolean indexGenerated = true;
			if(concreteRelationshipFile==null)
				indexGenerated = searchAgent.createIndex(null,moduleIdSet,true,false);
			else
				indexGenerated = searchAgent.createIndex(null,moduleIdSet,true,true);
			if(! cRefsetContent.isEmpty())
				indexGenerated = indexGenerated && searchAgent.createIndex(DBSchemaMapping.cREFSET,crmoduleIdSet,false,false);
			if(!refsetContent.isEmpty())
				indexGenerated = indexGenerated && searchAgent.createIndex(DBSchemaMapping.REFSET, rmoduleIdSet,false,false);

			if( indexGenerated ) {
				searchAgent.addExtensionDetailsForIndexing( moduleIdSet );
				logger.info(LoggerUtility.ConsoleWithLog, ALL_INDEX_GENERATION_FINISHED);
			}

			return indexGenerated;
		}
		else
			return false;
	}

	@Override
	public Set<String> getSemanticTags(){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		try {
			if(updateSematicTag){
				semanticTagSet=searchAgent.getSemanticTags();
				updateSematicTag=false;
			}else if(null==semanticTagSet || semanticTagSet.isEmpty())
				semanticTagSet=searchAgent.getSemanticTags();
		}catch (IOException | org.apache.lucene.queryparser.classic.ParseException e){
			// Log the Exception
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			return Collections.emptySet();
		}catch(Exception e){
			// Log the Exception
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			return Collections.emptySet();
		}
		return semanticTagSet;
	}

	@Override
	public List<String> getRelationShipType(){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		try {
			relationTypeSet=searchAgent.getRelationType();
		} catch (IOException | org.apache.lucene.queryparser.classic.ParseException e) {
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			return Collections.emptyList();
		}
		return relationTypeSet;
	}

	/**
	 * validateSemanticTag method used for validating semantic tags for any mistake in it
	 * @param semanticTagList
	 * @return List of {@link String} having semantic tag init for any wrong semantic tag it will return null
	 */
	private List<String> validateSemanticTag(List<String> semanticTagList){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		PropertyReader.loadSystemProperties();
		//remove empty elements and convert others to lowercase
		Iterator<String> semanticTagIterator = semanticTagList.iterator();
		while (semanticTagIterator.hasNext()){
			if(semanticTagIterator.next().isEmpty())
				semanticTagIterator.remove();
			else
				semanticTagIterator.toString().trim().toLowerCase();
		}

		//get indexed semantic tag from sem index dir
		Set<String> indexedSemanticTags=this.getSemanticTags();

		//replace snomed rt ctv3 to snomed rt+ctv3
		if(semanticTagList.contains(SNOMED_RT) && semanticTagList.contains("ctv3")){
			semanticTagList.remove(SNOMED_RT);
			semanticTagList.remove("ctv3");
			semanticTagList.add("snomed rt+ctv3");
		}

		//validate semantic tags
		if(!indexedSemanticTags.containsAll(semanticTagList)){
			//identify wrong semantic tag
			for(String semanticTag:semanticTagList){
				if(!indexedSemanticTags.contains(semanticTag)&&!semanticTag.equalsIgnoreCase("all")){
					String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
							+ Constants.CLASS_NAME + this.getClass().getName()
							+ Constants.NEW_LINE + "semantictag:" + semanticTag;
					logger.error(LoggerUtility.Exception, strMessage);
					return Collections.emptyList();
				}
			}

		}
		return semanticTagList;
	}


	/**
	 * This method used for retrieving language refset values from LangRefsetEnum
	 * @return List of List {@link String} containing language refset values in it
	 */
	public List<List<String>> getLangRefset(){
		if(SNOMEDAgent.langRefset.isEmpty()){
			List<String> langrefsetEnumElementList=null;
			for(EnumLangRefset element:EnumLangRefset.values()){
				langrefsetEnumElementList=new ArrayList<>();
				langrefsetEnumElementList.add(element.getId());
				langrefsetEnumElementList.add(element.getValue());
				SNOMEDAgent.langRefset.add(langrefsetEnumElementList);
			}
		}
		return SNOMEDAgent.langRefset;
	}

	@Override
	public boolean createSimpleMapdb(String snomedIntDirPath, boolean overridedb) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);

		if(!overridedb)
			return false;
		String mapfile=null;
		File snapshot;
		Set<String> moduleIdSet = new LinkedHashSet<>();
		String moduleId=null;
		LuceneSearchAgent searchAgent=(LuceneSearchAgent)this.searchAgent;
		snapshot= new File(snomedIntDirPath);
		if(!snapshot.exists())
		{
			logger.error(LoggerUtility.ConsoleWithException, "{}{}",WRONG_PATH_PROVIDED_FOR_SIMPLE_MAP_REFSET_FILE,snomedIntDirPath);
			return false;
		}
		if(snapshot.isDirectory())
		{
			File[] filesRefset = new File(snapshot + File.separator + REFSET + File.separator + "Map").listFiles();
			if (filesRefset == null) {
				logger.error(LoggerUtility.ConsoleWithException, "{}{}",WRONG_PATH_PROVIDED_FOR_SIMPLE_MAP_REFSET_FILE , snomedIntDirPath);
				return false;
			}
			for (File file : filesRefset) {
				if (file.getName().matches(".*der2_sRefset_SimpleMap(.*)"))
					mapfile = file.getAbsolutePath();
			}
		}
		else if(snapshot.isFile())
		{
			if (snapshot.getName().matches(".*der2_sRefset_SimpleMap(.*)"))
				mapfile = snapshot.getAbsolutePath();
		}
		QueryManager qm =new QueryManager();
		if(mapfile==null ){
			logger.error(LoggerUtility.ConsoleWithException, "{}{}",WRONG_PATH_PROVIDED_FOR_SIMPLE_MAP_REFSET_FILE , snomedIntDirPath);
			return false;
		}

		try {
			/* To get ModuleID of extension */
			try {
				BufferedReader in = new BufferedReader(new FileReader(mapfile));
				in.readLine();
				String data=in.readLine();
				in.close();
				if(data!=null)
				{
					moduleIdSet.add(data.split("\t")[3]);
					moduleId=data.split("\t")[3];
				}
				else
				{
					logger.error(LoggerUtility.ConsoleWithException, "Simple Map Refset file is empty");
					return false;
				}

			} catch (Exception e) {
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
				return false;
			}
			logger.info(LoggerUtility.ConsoleWithLog, "Please wait for simple map db creation... " );


			if (!qm.createSimpleMapdb(moduleId, mapfile)) {
				String descriptionFile = null;
				String descTermForExtension = null;
				File[] fileRefsets = new File(snapshot + File.separator + TERMINOLOGY).listFiles();
				for (File file : fileRefsets) {
					if (file.getName().matches("sct2_Description(.*)")) {
						descriptionFile = file.getAbsolutePath();
					}
				}
				if (descriptionFile != null) {
					String conceptId = null;

					try {
						BufferedReader in = new BufferedReader(new FileReader(descriptionFile));
						in.readLine();
						String line = null;
						while ((line = in.readLine()) != null) {
							conceptId = line.split("\t")[4];
							if (conceptId.equals(moduleId)) {
								descTermForExtension = line.split("\t")[7];
								break;
							}
						}

						in.close();
					} catch (Exception e) {
						logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
					}
				}
				String errorMsg = "SNOMED CT Extension database was not being created, please configure respective Extension then configure SimpleMapRefset ";
				if (descTermForExtension != null)
					errorMsg = errorMsg.concat(", Extension name to be configure : " + descTermForExtension);
				logger.error(LoggerUtility.ConsoleWithException, errorMsg);
				throw new Exception(errorMsg);

			}
			logger.info(LoggerUtility.ConsoleWithLog, "simple map database created successfully.");

			searchAgent.checkIndex(Constants.SIMPLE_MAP_INDEX);
			boolean indexGenerated = searchAgent.createIndex(DBSchemaMapping.SIMPLEMAP, moduleIdSet,true,false);
			if( indexGenerated )
				logger.info(LoggerUtility.ConsoleWithLog, ALL_INDEX_GENERATION_FINISHED);

			return indexGenerated;

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

	}
	/**
	 * Searches the simpleMapTarget of refsetComponentId provided .
	 * @param queryParams
	 * @return  SimpleMapRefsetResult mapped to the given Refset Component ID.
	 * @throws IOException
	 * @throws ParseException
	 * @throws org.apache.lucene.queryparser.classic.ParseException
	 */
	@Override
	public SimpleMapRefsetResult searchSimpleMap(String refsetComponentId ) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		if(refsetComponentId!=null){
			try{
				SimpleMapRefsetResult simpleMapRefsetResult=  searchAgent.searchSimpleMap(refsetComponentId);
				if(simpleMapRefsetResult!=null && !("NO_MAP_FOUND".equalsIgnoreCase(simpleMapRefsetResult.getMapStatus())))
					return simpleMapRefsetResult;
				else{
					// else put the result message - No Map Found. It will be present in
					// the download-able report.
					simpleMapRefsetResult = new SimpleMapRefsetResult();
					simpleMapRefsetResult.setConceptId(Long.parseLong(refsetComponentId));
					simpleMapRefsetResult.setMapStatus("NO_MAP_FOUND");
					return simpleMapRefsetResult;
				}
			}catch (ParseException | IOException e){
				//Log the Exception
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}catch (Exception e){
				//Log the Exception
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}
		}
		return null;


	}

	/**
	 * Searches for the immediate children descriptions.
	 *@param conceptId
	 *				Concept Id whose immediate children to search
	 * @return Set of {@link RelationshipTC} matching the given Concept ID.<br>
	 * @throws IOException if any error occurs while processing.
	 * @throws java.text.ParseException
	 */
	@Override
	public Set<RelationshipTC> getIsARelationshipsWithCount(String conceptId)
	{
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		PropertyReader.loadSystemProperties();
		Set<RelationshipTC> lstrelationships = null;
		if (null == conceptId || !conceptId.matches(RANGE))
			return Collections.emptySet();
		else {
			try
			{

				lstrelationships =searchAgent.getIsARelationshipsWithCount(conceptId) ;

			}
			catch (Exception e) {
				// Log the Exception
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}
			return lstrelationships;
		}
	}

	/**
	 * Returns a {@link Set} of all Descendants {@link Refset}s for given concept identifier.	 *
	 * @return Set of {@link Refset}s.
	 */

	public Set<Refset> getAllRefset(){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		try{
			return searchAgent.getAllRefset();
		}catch (ParseException | IOException | org.apache.lucene.queryparser.classic.ParseException e){
			//Log the Exception
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		}
		return Collections.emptySet();
	}

	/**
	 * Returns a {@link Set} of all Extensions {@link Refset}s.	 *
	 * @return Set of {@link Refset}s.
	 */

	public Set<Refset> getAllExtension(){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		try{
			return searchAgent.getAllExtension();
		}catch (ParseException | IOException | org.apache.lucene.queryparser.classic.ParseException e){
			//Log the Exception
			logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
		}
		return Collections.emptySet();
	}

	/**
	 * Returns a {@link Set} of all Refset Members {@link Concept}s for given concept refset identifier.
	 * @param id SNOMED CT concept identifier.
	 * @return Set of {@link Concept}s.
	 */
	public Set<Concept> getRefsetMember(String refsetId){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		try {
			return searchAgent.getRefsetMember(refsetId);
		} catch (org.apache.lucene.queryparser.classic.ParseException | IOException | ParseException e) {
			logger.error(LoggerUtility.Exception, "{}{}",LoggerUtility.createErrorMessage(e),e.getMessage());
		}
		return Collections.emptySet();
	}

	/**
	 * Returns a {@link Set} of all Extension Members {@link Concept}s for given concept extension identifier.
	 * @param id SNOMED CT concept identifier.
	 * @param pageNumber records to load for a specific page (pagination).
	 * @return Set of {@link Concept}s.
	 */
	public Set<Concept> getExtensionMember(String moduleId, int pageNumber){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		try {
			return searchAgent.getExtensionMember(moduleId, pageNumber);
		} catch (org.apache.lucene.queryparser.classic.ParseException | IOException | ParseException e) {
			logger.error(LoggerUtility.Exception, "{}{}",LoggerUtility.createErrorMessage(e),e.getMessage());
		}
		return Collections.emptySet();
	}


	@Override
	public Set<CompositeDescription> search(String matchTerm, EnumState enumState, List<String> semanticTagList,
			EnumAcceptability enumAcceptability, int returnLimit, boolean groupByConcept, String refsetId,
			List<String> parentIdList, boolean fullConcept,boolean excludeConcept) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		Set<CompositeDescription> output = new LinkedHashSet<>();
		if (enumState == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_STATE + enumState;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		} else if (semanticTagList == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_SEMANTIC_TAG + semanticTagList;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		} else if (enumAcceptability == null) {
			String strMessage = INVALID_PARAMETER + Constants.NEW_LINE
					+ Constants.CLASS_NAME + this.getClass().getName()
					+ Constants.NEW_LINE + ENUM_ACCEPTABILITY
					+ enumAcceptability;
			logger.error(LoggerUtility.Exception, strMessage);
			return output;
		}

		if (EnumAcceptability.ALL == enumAcceptability) {
			return search(matchTerm, enumState, semanticTagList, returnLimit, groupByConcept,refsetId,parentIdList,excludeConcept);
		}
		PropertyReader.loadSystemProperties();
		int returnlimit = -1;


		semanticTagList=this.validateSemanticTag(semanticTagList);
		if(semanticTagList==null)
			return output;

		returnlimit = Integer.parseInt(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT));
		HashMap<String, List<String>> queryParams = new HashMap<>();
		queryParams.put(Constants.SEARCH_TERM, Arrays.asList(matchTerm));
		queryParams.put(Constants.STATE, Arrays.asList(enumState.name()));
		queryParams.put(Constants.SEMANTIC_TAG, semanticTagList);
		queryParams.put(Constants.ACCEPTABILITY,Arrays.asList(enumAcceptability.name()));
		queryParams.put(Constants.MAX_RECORDS,Arrays.asList(PropertyReader.getProperty(Constants.SEARCH_RETURNLIMIT)));
		queryParams.put(Constants.REFSET_ID ,Arrays.asList(refsetId));


		Set<CompositeDescription> searchedDescriptions = searchAgent.search(queryParams,fullConcept);
		if(parentIdList!=null && !parentIdList.isEmpty())
		{
			searchedDescriptions=processForParentIdSearch(searchedDescriptions, parentIdList,excludeConcept);
		}
		if(groupByConcept)
		{
			searchedDescriptions = processForGroupByConcept(searchedDescriptions);
		}

		if (-1 >= returnLimit || returnlimit <= returnLimit) {
			return searchedDescriptions;
		}

		if (null != searchedDescriptions) {
			for (CompositeDescription description : searchedDescriptions) {

				if (output.size() >= returnLimit) {
					break;
				}
				output.add(description);

			}
		}

		return output;

	}



	/**
	 * This method creates SNOMED CT Refset database
	 * @param snomedIntDirPath Directory path for SNOMED CT International Release directory 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
	 */
	@Override
	public boolean createSNOMEDRefsetdb(String snomedIntDirPath,boolean overridedb){
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		if(!overridedb)
		{
			return false;
		}
		LuceneSearchAgent searchAgent=(LuceneSearchAgent)this.searchAgent;
		PropertyReader.loadSystemProperties();
		File snomedIntDir = new File(snomedIntDirPath);
		if(!snomedIntDir.exists())
		{
			logger.error(LoggerUtility.ConsoleWithException, WRONG_DIRECTORY_PROVIDED_FOR_SNOMED_CT_RELEASE_FILES);
			return false;
		}

		List <String> refsetContent=new ArrayList<>();
		List <String>  cRefsetContent=new ArrayList<>();
		List<String> skipRefsetContent=new ArrayList<>();
		Set<String> moduleIdSet=new LinkedHashSet<>();
		Set<String> crmoduleIdSet=new LinkedHashSet<>();
		File[] filesRefsetContent = new File(snomedIntDirPath+File.separator+REFSET+File.separator+"Content").listFiles();
		for (File file : filesRefsetContent) {
			if((file.getName().matches(".*der2_Refset_(.*)"))){
				if(RF2FileHasRecords(file)){
					refsetContent.add(file.getAbsolutePath());

				}
				else
					skipRefsetContent.add(file.getName());
			}
			else if((file.getName().matches(DER2_C_REFSET))){
				if(RF2FileHasRecords(file)){
					cRefsetContent.add(file.getAbsolutePath());
				}
				else
					skipRefsetContent.add(file.getName());
			}

		}
		if(skipRefsetContent!=null){
			for(String f:skipRefsetContent)
				logger.info(LoggerUtility.ConsoleWithLog, "Skipped RefestFile : {}",f);
		}

		try{

			if( cRefsetContent!=null && !( cRefsetContent.isEmpty())){
				logger.info(LoggerUtility.ConsoleWithLog, "Creating cRefset table");
				createRefsetdbAndModuleSet(cRefsetContent,crmoduleIdSet,true);
			}
			if(!refsetContent.isEmpty()){
				logger.info(LoggerUtility.ConsoleWithLog, "Creating Refset table");
				createRefsetdbAndModuleSet(refsetContent,moduleIdSet,false);
			}
			if(searchAgent.checkIndex(Constants.SNOINDEX)&&searchAgent.checkIndex(Constants.RELATION_INDEX)&&searchAgent.checkIndex(Constants.TCINDEX)){
				//Since && returns true if both statements are true
				boolean indexGenerated = true;
				if(!( cRefsetContent.isEmpty())&&  cRefsetContent!=null)
					indexGenerated = indexGenerated && searchAgent.createIndex(DBSchemaMapping.cREFSET,crmoduleIdSet,false,false);
				if(!(refsetContent.isEmpty()))
					indexGenerated = indexGenerated && searchAgent.createIndex(DBSchemaMapping.REFSET, moduleIdSet,false,false);

				if( indexGenerated )
					logger.info(LoggerUtility.ConsoleWithLog, ALL_INDEX_GENERATION_FINISHED);

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

	/**
	 * Returns a {@link Set} of all Ancester {@link Concept}s for given concept identifier.
	 * @param id SNOMED CT concept identifier.
	 * @return Set of {@link Concept}s.
	 */
	Set<Concept>ancestorSet=new HashSet<>();
	public Set<Concept> getAllAncestorConcepts(String id) {
		Logger logger = LogManager.getLogger(SNOMEDAgent.class);
		boolean flag=true;
		if (null == id || !id.matches(RANGE))
			return Collections.emptySet();
		else {
			Set<Concept>iteratorSet=new HashSet<>();
			Set<Concept>subSet=new HashSet<>();

			try {
				if(ancestorSet.isEmpty()) {
					ancestorSet=getRelations(id,EnumRelationshipType.IS_A.getValue(), EnumDirection.SOURCE);
					iteratorSet.addAll(ancestorSet);
				}
				if(!iteratorSet.isEmpty()){
					while(subSet!=null) {
						for(Concept conceptId:iteratorSet) {
							subSet.addAll(getRelations(conceptId.getId(),EnumRelationshipType.IS_A.getValue(), EnumDirection.SOURCE));
							ancestorSet.addAll(subSet);
						}
						iteratorSet=subSet;
						for(Concept c:subSet) {
							if(c.getId().contains("138875005")) {
								flag=false;
								subSet=null;
								break;
							}
						}
						if(flag)
							subSet=new HashSet<>();
					}
				}

			}catch (Exception e) {
				// Log the Exception
				logger.error(LoggerUtility.Exception, LoggerUtility.createErrorMessage(e));
			}
		}
		for(Concept c:ancestorSet) {
			if(c.getId().equals("138875005"))
			{ancestorSet.remove(c);
			break;}
		}
		return ancestorSet;
	}

	/**
	 * Checks validity of concept IDs (must be present in Index and active) in ECL object.
	 * @param expressionConstraint {@link SConstraint} Object form of ECL query
	 */
	private void validateConceptIds(SConstraint expressionConstraint) {
		Set<String> conceptIds = expressionConstraint.getConceptIds();
		//Searching for invalid IDs
		List<String> invalidConceptIds = new ArrayList<>();
		for (String conceptId : conceptIds) {
			//Based on Validate API
			Set<CompositeDescription> compositDescription = search(conceptId, EnumState.getEnum("active"),
					Arrays.asList("all"), EnumAcceptability.getEnum("all"), -1, false, null, null,
					Boolean.parseBoolean("true"), false);
			if (compositDescription.isEmpty()) {
				invalidConceptIds.add(conceptId);
			}
		}
		//Invalidity check
		if (!invalidConceptIds.isEmpty()) {
			//Based on Snowstorm's error handling
			throw new IllegalArgumentException(String.format("Concepts in the ECL request do not exist or are inactive : %s.",
					invalidConceptIds));
		}
	}

	/**
	 * Checks if term is present in concept ID or term (preferredName or FSN).
	 * @param Set of {@link Concept}s.
	 * @param term String to match.
	 * @return Set of {@link Concept}s.
	 */
	private Set<Concept> filterEclResultByTerm(Set<Concept> eclResult, String term) {
		Set<Concept> filteredResult = new HashSet<>();
		//To make search case insensitive
		term = term.toLowerCase();
		for (Concept concept : eclResult) {
			if( concept.getId().equalsIgnoreCase(term) ||
					concept.getPreferredName().toLowerCase().contains(term) ||
					concept.getFullySpecifiedName().toLowerCase().contains(term)) {
				filteredResult.add(concept);
			}
		}
		return filteredResult;
	}

	/**
	 * Parses ECL query and fetches result.
	 * @param ecl {@link String} ECL query.
	 * @param term Filter result based on given {@link String}.
	 * @return Set of {@link Concept}s.
	 */
	public Set<Concept> executeEcl(String ecl, String term){
		//Step 1 : String to Object (Parse)
		ExpressionConstraintLanguageService eclAgent = new ExpressionConstraintLanguageService();
		//Step 2 : Validate
		try {
			validateConceptIds(eclAgent.doGetConstraint(ecl));
		} catch (ECLException eclException) {
			throw new IllegalArgumentException(String.format("ECL syntax error. %s", eclException.getMessage()));
		}
		//Step 3 : Parsing ECL
		Set<Concept> eclResponse = null;
		eclResponse = eclAgent.getEclConstraints(ecl);
		term = term.trim();
		if ( term != null && !term.equalsIgnoreCase("")) {
			eclResponse = filterEclResultByTerm(eclResponse, term);
		}
		return eclResponse;
	}

}


