/*******************************************************************************
 * 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.model;

import java.io.Serializable;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import in.cdac.medinfo.csnotk.csnolib.commons.EnumDefStatus;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumDirection;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumLangRefset;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumSemanticTag;
import in.cdac.medinfo.csnotk.csnolib.db.QueryManager;
import in.cdac.medinfo.csnotk.csnolib.util.SemanticTagBuilder;
//Logs
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import in.cdac.medinfo.csnotk.csnolib.util.LoggerUtility;

/**
 * {@link Concept} is a clinical idea associated with a unique identifier . A {@link Concept} consists
 * of an identifier, a fully specified name, a semantic tag of type {@link EnumSemanticTag} to which a
 * concept belongs and a {@link List} of {@link Description}s which are the synonyms describing the {@link Concept}.
 *
 */

public class Concept extends Component implements Serializable, Comparable<Concept>
{
    private static final Logger logger = LogManager.getLogger(Concept.class);
	private static final long serialVersionUID = 1L;

	/**
	 * An identifier that tells whether the concept is fully Defined or primitive.
	 */
	private EnumDefStatus definitionStatusId;

	/**
	 * Default constructor
	 */
	protected Concept()
	{
	}

	/**
	 * Parameterized constructor
	 * @param id
	 * 			 represents the Concept Id
	 * @throws SQLException
	 * @throws ParseException
	 */
	protected Concept(String id)throws SQLException, ParseException
	{
		loadConcept(id);
	}

	/**
	 * Parameterized constructor
	 * @param id
	 * 			 		 represents the Concept Id.
	 * @param defStatusId
	 * 					 represents the definition status Id of that particular concept.
	 * @param effTime
	 * 					 represents the effective time.
	 * @param status
	 * 					 represents the status of that concept (1 for active and 0 for inactive).
	 * @param modId
	 * 					 represents the module Id associated with the concept.
	 * @throws SQLException
	 * @throws ParseException
	 */

	protected Concept(EnumDefStatus defStatusId,String id,Date effTime,Byte status,String modId) throws SQLException, ParseException
	{
		super(id,effTime,status,modId);
		this.definitionStatusId=defStatusId;
		loadConcept(id);
	}

	/**
	 * A human readable name for the concept.
	 */

	private String fullySpecifiedName;

	/**
	 * The semantic tag type of this concept
	 */

	private String semantictag;

	/**
	 * All available descriptions of the this concept
	 */

	private List<Description> descriptions;

	/*
	 * prefferd name of term in us dialect
	 *
	 */
	private String preferredName;


	/**
	 * Loads the attribute values for a particular concept.
	 * @param id
	 * 			 represents the SNOMED CT Concept Identifier.
	 * @param qm
	 * 			 represents object of {@link QueryManager} class.
	 * @throws SQLException
	 * @throws ParseException
	 */
	private void loadConcept(String id) throws SQLException, ParseException
	{
		QueryManager qm = new QueryManager();
		qm.populateConcept(id,this);
	}

	/**
	 * Returns the {@link Concept} identifier.
	 * @return id of that particular Concept.
	 */
	public String getId()
	{
		return super.getId();
	}

	/**
	 * Sets the Concept identifier.
	 * @param id
	 * 			 represents Concept identifier.
	 */
	public void setId(String id)
	{
		super.setId(id);
	}

	/**
	 * Returns the Concept FSN.
	 * @return FSN (Fully Specified Name) of that concept.
	 */
	public String getFullySpecifiedName()
	{
		return fullySpecifiedName;
	}

	/**
	 * Sets the fully specified name associated with the
	 * Concept.
	 * @param fullySpecifiedName
	 * 							 represents FSN of a concept.
	 *
	 */

	public void setFullySpecifiedName(String fullySpecifiedName)
	{
		this.fullySpecifiedName = fullySpecifiedName;
	}

	/**
	 * Returns the definition status.
	 * @return definitionStatus Id of that concept.
	 */

	public String getDefinitionStatusId() {
		return this.definitionStatusId.getValue();
	}

	/**
	 * Sets the definition status Id of the Concept.
	 * @param definitionStatusId
	 * 							 represents definition status Id of a concept.
	 *
	 */

	public void setDefinitionStatusId(EnumDefStatus definitionStatusId) {
		this.definitionStatusId = definitionStatusId;
	}

	@Override
	public String toString() {
		return "Concept [id=" + id + ", fullySpecifiedName="
				+ fullySpecifiedName +", preferredName="+preferredName+ ", activeStatus=" + activeStatus
				+ ", semantictag=" + semantictag + ", definitionStatusId="
				+ definitionStatusId.getValue() + ", descriptions=" + descriptions + "]";
	}
	/**
	 * Returns the active status of the Concept.
	 * @return activeStatus of that concept.
	 */
	public Byte getActiveStatus()
	{
		return super.getActiveStatus();
	}
	/**
	 * Sets the active status of the Concept.
	 * @param activeStatus
	 * 					refers to the active status of the Concept.
	 */
	public void setActiveStatus(Byte activeStatus)
	{
		super.setActiveStatus(activeStatus);
	}
	/**
	 * Returns the module Id.
	 * @return moduleId of that concept.
	 */

	public String getModuleId()
	 {
		 return super.getModuleId();
	 }
	/**
	 * Sets the module Id associated with the Concept.
	 * @param modId
	 * 				 represents Module Id of the concept
	 * sets moduleId of a concept.
	 */
	 public void setModuleId(String modId)
	 {
		 super.setModuleId(modId);
	 }
	 /**
	  * Returns the effective time
	  * @return effective time for a concept.
	  */
	 public Date getTime()
	 {
		 return super.getTime();
	 }
	 /**
	  *
	  * Sets the effective time of a concept.
	  */
	 public void setTime(Date date)
	 {
		 super.setTime(date);
	 }

	/**
	 * Sets the list of {@link Description} corresponding to the SNOMED CT concept.
	 * @param descriptions
	 * 						 List of {@link Description}.
	 */
	public void setDescriptions(List<Description> descriptions)
	{
		this.descriptions = descriptions;
	}

	/**
	 * Returns all {@link Description}s for this {@link Concept} other than its FSN.
	 * @return List of {@link Description}s for a concept.
	 * @throws SQLException if any error occurs while accessing database.
	 * @throws ParseException
	 */
	public List<Description> getDescriptions() throws SQLException, ParseException
	{
		if(null == this.descriptions)
		{
			QueryManager qm = new QueryManager();
			descriptions = 	qm.getDescriptions(this.id);

		}
		return this.descriptions;
	}

	/**
	 *  @return {@link String} value of preferred name
	 * */
	public String getPreferredName() {
		return preferredName;
	}

	/**
	 * Returns the preferred name for a concept.
	 * @return {@link String} value that corresponds to the preferred name
	 * associated with the Concept.
	 * @param EnumLangRefset
	 * @throws SQLException if any error occurs while accessing database.
	 */
	public String getPreferredName(EnumLangRefset langRefset) throws SQLException
	{
		if(null ==  this.preferredName)
		{
			QueryManager qm = new QueryManager();
			preferredName = 	qm.getPreferredName(this.id,langRefset);
		}

		return this.preferredName;

	}

	/**
	 * this method sets the preferred name of a term
	 *  @param string preferredName
	 *  @return void
	 * */
	public void setPreferredName(String preferredName) {
		this.preferredName = preferredName;
	}

	/**
	 * Returns list of SNOMED CT identifier of children or parents for a particular {@link Concept} for a particular
	 *  relationship type based on direction.
	 * @param enumRelationshipType
	 * 				 Type of the relationships.
	 * @param direction
	 * 					Direction(source/destination) in which relationship is to be retrieved.
	 * @return Set of {@link Relationship}.
	 */
	public Set<Concept> getRelations(String  relationshipType, EnumDirection direction)
	{
		QueryManager qm = new QueryManager();
		List<Relationship> lstrelationships = null;
		Set<Concept> relConcepts=new HashSet<Concept>();

		try
		{
			if(direction==(EnumDirection.DESTINATION)){
				lstrelationships = qm.getRelationships(this.id, relationshipType, direction);
				for (Relationship rel : lstrelationships) {
					relConcepts.add(rel.getSourceConcept());
				}
			}
			else if(direction==EnumDirection.SOURCE){
				lstrelationships = qm.getRelationships(this.id, relationshipType, direction);
				for (Relationship rel : lstrelationships) {
					relConcepts.add(rel.getDestinationConcept());
				}
			}

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

		return relConcepts;
	}

	/**
	 * Returns the semantic tag.
	 * @return semantic tag based on the FSN specified.
	 * @throws Exception if any error occurs while processing.
	 */
	public String getSemanticTag() throws IllegalArgumentException
	{
		if(this.semantictag == null)
		{
			String semantictag = SemanticTagBuilder.buildSemanticTagFromString(this.fullySpecifiedName);
			if(null == semantictag){
				this.semantictag = EnumSemanticTag.SITUATION_WITH_EXPLICIT_CONTEXT.getValue();
			}
			else
				throw new IllegalArgumentException("semantictag is null");
		}
		return this.semantictag;
	}

	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Concept other = (Concept) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}
@Override
	public int compareTo(Concept concept)
	{
		return this.getId().compareTo(concept.getId());
	}

}
