package in.cdac.medinfo.csnotk.csnolib.ecl.constraint;

import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.snomed.langauges.ecl.domain.expressionconstraint.SubExpressionConstraint;
import org.snomed.langauges.ecl.domain.filter.ConceptFilterConstraint;
import org.snomed.langauges.ecl.domain.filter.DescriptionFilterConstraint;
import org.snomed.langauges.ecl.domain.filter.HistorySupplement;
import org.snomed.langauges.ecl.domain.filter.MemberFilterConstraint;
import org.snomed.langauges.ecl.domain.refinement.Operator;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

//Logs
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import in.cdac.medinfo.csnotk.csnolib.util.LoggerUtility;
//Changes
import org.apache.lucene.queryparser.classic.ParseException;
import in.cdac.medinfo.csnotk.csnolib.agents.SNOMEDAgent;
import in.cdac.medinfo.csnotk.csnolib.agents.LuceneSearchAgent;
import in.cdac.medinfo.csnotk.csnolib.model.ComponentFactory;
import in.cdac.medinfo.csnotk.csnolib.model.Concept;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumComponent;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumRelationshipType;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumDirection;
import in.cdac.medinfo.csnotk.csnolib.commons.EnumDefStatus;
import static in.cdac.medinfo.csnotk.csnolib.commons.Constants.CONCEPT_STATE;
import static in.cdac.medinfo.csnotk.csnolib.commons.Constants.ACTIVE;
import java.util.Collections;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.index.Term;
import static com.google.common.collect.Sets.newHashSet;
import in.cdac.medinfo.csnotk.csnolib.ecl.util.throwEclFeatureNotSupportedException;

public class SSubExpressionConstraint extends SubExpressionConstraint implements SConstraint {

    private LuceneSearchAgent searchAgent = new LuceneSearchAgent();
    private SNOMEDAgent snomedAgent = new SNOMEDAgent();

    public SSubExpressionConstraint(Operator operator) {
        super(operator);
    }

    @Override
    public Set<Concept> addQuery() {
        // Check features supported
        if (getMemberFieldsToReturn() != null || isReturnAllMemberFields()) {
            throw new throwEclFeatureNotSupportedException("Member fields");
        }

        HistorySupplement historySupplement = getHistorySupplement();
        if (historySupplement != null) {
            throw new throwEclFeatureNotSupportedException("History Supplement");
        } else {
            return doAddQuery();
        }
    }

    private Set<Concept> doAddQuery() {
        Logger logger = LogManager.getLogger(SSubExpressionConstraint.class);
        if (wildcard) {
            try {
                BooleanQuery.Builder builder = new BooleanQuery.Builder();
                builder.add(new TermQuery(new Term(ACTIVE, "1")),BooleanClause.Occur.MUST);
                builder.add(new TermQuery(new Term(CONCEPT_STATE, "1")),BooleanClause.Occur.MUST);
                return searchAgent.getAllConceptsForWildcard(builder);
            } catch (java.text.ParseException | ParseException | IOException  e) {
                logger.error(LoggerUtility.ConsoleWithException, LoggerUtility.createErrorMessage(e));
            }
            return Collections.emptySet();
        } else if (conceptId != null) {
            return addConstraint(conceptId);
        } else if (nestedExpressionConstraint != null) {
            return ((SConstraint) nestedExpressionConstraint).addQuery();
        }
        return Collections.emptySet();
    }

    private Set<Concept> addConstraint(String conceptId) {
        Set<Concept> finalSctIds = null;
        Set<Concept> sctIds = null;
        if (operator == null) {
            finalSctIds = new HashSet<>();
            addSingleConceptInSet(conceptId, finalSctIds);
        } else {
            switch (operator) {
                case descendantof:
                    sctIds =  snomedAgent.getAllDescendantConcepts(conceptId);
                    finalSctIds = getRefinedSet(sctIds);
                    break;
                case descendantorselfof:
                    sctIds =  snomedAgent.getAllDescendantConcepts(conceptId);
                    finalSctIds = getRefinedSet(sctIds);
                    addSingleConceptInSet(conceptId, finalSctIds);
                    break;
                case childof:
                    sctIds = snomedAgent.getRelations(conceptId, EnumRelationshipType.IS_A.getValue(), EnumDirection.DESTINATION);
                    finalSctIds = getRefinedSet(sctIds);
                    break;
                case childorselfof:
                    sctIds = snomedAgent.getRelations(conceptId, EnumRelationshipType.IS_A.getValue(), EnumDirection.DESTINATION);
                    finalSctIds = getRefinedSet(sctIds);
                    addSingleConceptInSet(conceptId, finalSctIds);
                    break;
                case ancestorof:
                    sctIds = snomedAgent.getAllAncestorConcepts(conceptId);
                    finalSctIds = getRefinedSet(sctIds);
                    break;
                case ancestororselfof:
                    sctIds = snomedAgent.getAllAncestorConcepts(conceptId);
                    finalSctIds = getRefinedSet(sctIds);
                    addSingleConceptInSet(conceptId, finalSctIds);
                    break;
                case parentof:
                    sctIds = snomedAgent.getRelations(conceptId, EnumRelationshipType.IS_A.getValue(), EnumDirection.SOURCE);
                    finalSctIds = getRefinedSet(sctIds);
                    break;
                case parentorselfof:
                    sctIds = snomedAgent.getRelations(conceptId, EnumRelationshipType.IS_A.getValue(), EnumDirection.SOURCE);
                    finalSctIds = getRefinedSet(sctIds);
                    addSingleConceptInSet(conceptId, finalSctIds);
                    break;
                case memberOf:
                    sctIds = snomedAgent.getRefsetMember(conceptId);
                    finalSctIds = getRefinedSet(sctIds);
            }
        }
        return finalSctIds;
    }

    private void addSingleConceptInSet(String conceptId, Set<Concept> finalSctIds) {
        Logger logger = LogManager.getLogger(SSubExpressionConstraint.class);
        Concept selfConcept = null;
        try {
            selfConcept = searchAgent.getConceptDetails(conceptId);
        } catch (java.text.ParseException | ParseException | IOException  e) {
            logger.error(LoggerUtility.ConsoleWithException, LoggerUtility.createErrorMessage(e));
        }
        if(selfConcept != null)
            finalSctIds.add(selfConcept);
    }

    //Only the fields needed for ECL Response
    private Set<Concept> getRefinedSet(Set<Concept> intermediateSet) {
        Set<Concept> finalSctIds = new HashSet<>();
        ComponentFactory componentFactory = ComponentFactory.getInstance();
        //Minimal object
        for( Concept conceptInSet : (Set<Concept>) intermediateSet ) {
            Concept concept = (Concept) componentFactory.getComponent(EnumComponent.CONCEPT, null);
            concept.setId(conceptInSet.getId());
            concept.setFullySpecifiedName(conceptInSet.getFullySpecifiedName());
            concept.setPreferredName(conceptInSet.getPreferredName());
            concept.setTime(conceptInSet.getTime());
            concept.setActiveStatus(conceptInSet.getActiveStatus());
            concept.setDefinitionStatusId(EnumDefStatus.getEnum(conceptInSet.getDefinitionStatusId()));
            concept.setModuleId(conceptInSet.getModuleId());
            finalSctIds.add(concept);
        }
        return finalSctIds;
    }

    @Override
    public void addConceptFilterConstraint(ConceptFilterConstraint conceptFilterConstraint) {
        throw new throwEclFeatureNotSupportedException("Concept filter");
    }

    @Override
    public void addDescriptionFilterConstraint(DescriptionFilterConstraint descriptionFilterConstraint) {
        throw new throwEclFeatureNotSupportedException("Description filter");
    }

    @Override
    public void addMemberFilterConstraint(MemberFilterConstraint memberFilterConstraint) {
        throw new throwEclFeatureNotSupportedException("Member filter");
    }

    @Override
    public Set<String> getConceptIds() {
        Set<String> conceptIds = newHashSet();
        if (conceptId != null) {
            conceptIds.add(conceptId);
        }
        if (nestedExpressionConstraint != null) {
            conceptIds.addAll(((SConstraint) nestedExpressionConstraint).getConceptIds());
        }
        return conceptIds;
    }

}

