

package gov.va.med.cds.filter;


import static gov.va.med.cds.exception.ErrorCodeEnum.ERROR_MARSHALLING_FILTER_XML;
import static gov.va.med.cds.exception.ErrorCodeEnum.NO_PATIENT_IDS_REQUESTED;

import gov.va.med.cds.common.person.correlation.PersonIdentifier;
import gov.va.med.cds.common.person.correlation.PersonIdentifierInterface;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.request.ValidationException;
import gov.va.med.cds.uniqueidentifier.UniqueIdentifier;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;


public class CdsFilter
    implements
        CdsFilterInterface
{
    protected static final String W3C_XML_SCHEMA_NS_URI = "http://www.w3.org/2001/XMLSchema";
    protected static final String CDS_FILTER_PATH = "classpath:gov/va/med/cds/filter/schema/Filter100.xsd";
    private String filterId = null;
    private String filterXml = null;
    private String uniqueIdentifier = null;
    private Element rootFilterElement = null;
    private Map<String, EntryFilterInterface> entryPointFilterMap = null;
    private List<PersonIdentifierInterface> queryablePersonIdentifiers = null;
    private List<Element> excludeIdentifiers = new ArrayList<Element>();
    private List<Element> resolvedIdentifiers = new ArrayList<Element>();
    private Element nationalId = null;
    private Element edipi = null;
    private static String HEALTH_ADAPTER_SINGLE_PATIENT_ALL_DATA_FILTER = "HEALTH_ADAPTER_SINGLE_PATIENT_ALL_DATA_FILTER";
    public static String EDIPI_ID_TAG = "EdiPi";
    private EntryFilterFactory entryFilterFactory = null;



    CdsFilter( Document filterDocument, String templateId, String requestId )
    {
        initFilter( filterDocument, templateId, requestId );
    }


    public Element getEdipi( )
    {
        return edipi;
    }


    CdsFilter( String filterId, Document filterDocument, String templateId, String requestId, EntryFilterFactory entryFilterFactory )
    {
        setEntryFilterFactory( entryFilterFactory );
        initFilter( filterDocument, templateId, requestId );
    }


    void initFilter( Document filterDocument, String templateId, String requestId )
    {
        setUniqueIdentifier();
        filterXml = filterDocument.asXML();
        setEntryPointFilterMap( filterDocument, templateId, requestId );
        setIdentifierElements();
    }


    private void setUniqueIdentifier( )
    {
        this.uniqueIdentifier = new UniqueIdentifier().getValue();
    }


    public String getUniqueIdentifier( )
    {
        return uniqueIdentifier;
    }


    /**
     * Hash used to return a specific EntryFilter based on its query name.<br>
     * This puts a "hidden" uniqueness constraint on the query names in a single filter.
     * 
     */
    @SuppressWarnings( "unchecked" )
    private void setEntryPointFilterMap( Document filterRequestDocument, String templateId, String requestId )
    {
        rootFilterElement = filterRequestDocument.getRootElement();
        filterId = rootFilterElement.element( "filterId" ).getTextTrim();
        List<Element> entryPointFilterElements = rootFilterElement.elements( "entryPointFilter" );
        if ( entryPointFilterElements.size() <= 0 )
        {
            // should really have never come here - but if it does
            throw new ValidationException( ErrorCodeEnum.NO_ENTRYPOINTS_IN_FILTER, filterXml );
        }

        entryPointFilterMap = new HashMap<String, EntryFilterInterface>();
        for ( Element entryFilter : entryPointFilterElements )
        {
            EntryFilterInterface entryFilterInterface = this.entryFilterFactory.createEntryFilter( filterId, templateId, entryFilter, requestId );
            String queryName = entryFilterInterface.getQueryName();
            if ( entryPointFilterMap.containsKey( queryName ) )
            {
                throw new ValidationException( ERROR_MARSHALLING_FILTER_XML, filterXml, "Entry filter " + queryName + " already exists." );
            }
            entryPointFilterMap.put( queryName, entryFilterInterface );
        }
    }


    public EntryFilterInterface getEntryFilter( String queryName )
    {
        return entryPointFilterMap.get( queryName );
    }


    public List<EntryFilterInterface> getEntryPointFilters( )
    {
        List<EntryFilterInterface> entryFilters = new ArrayList<EntryFilterInterface>();
        entryFilters.addAll( entryPointFilterMap.values() );
        return entryFilters;
    }


    public String getFilterXml( )
    {
        return filterXml;
    }


    public String getFilterId( )
    {
        return filterId;
    }


    public List<PersonIdentifierInterface> getPersonIdentifiers( )
    {
        if ( queryablePersonIdentifiers == null )
        {
            Element patientsElement = rootFilterElement.element( "patients" );
            if ( patientsElement == null )
            {
                throw new ValidationException( NO_PATIENT_IDS_REQUESTED );
            }
            Element ediPiElement = patientsElement.element( EDIPI_ID_TAG  );
            if ( ediPiElement != null && ediPiElement.elements().size() > 0 )
            {
                getEdipiIdentifier( patientsElement );
            }
            else
            {
                getResolvedPatientIdentifiers( patientsElement );
            }
        }
        return queryablePersonIdentifiers;
    }


    @SuppressWarnings( "unchecked" )
    private void getResolvedPatientIdentifiers( Element patientsElement )
    {
        List<Element> identifierList = patientsElement.elements( "resolvedIdentifiers" );

        if ( identifierList == null || identifierList.size() == 0 )
        {
            throw new ValidationException( NO_PATIENT_IDS_REQUESTED );
        }
        queryablePersonIdentifiers = new ArrayList<PersonIdentifierInterface>();
        for ( Element patientId : identifierList )
        {
            PersonIdentifierInterface patientIdentifier = new PersonIdentifier();
            patientIdentifier.setAssigningAuthority( patientId.element( "assigningAuthority" ).getTextTrim() );
            if ( patientIdentifier.getAssigningAuthority() == null || patientIdentifier.getAssigningAuthority().isEmpty() )
            {
                throw new ValidationException( ErrorCodeEnum.PATIENT_ID_MISSING_ASSIGNING_AUTHORITY );
            }

            patientIdentifier.setAssigningFacility( patientId.element( "assigningFacility" ).getTextTrim() );
            if ( patientIdentifier.getAssigningFacility() == null || patientIdentifier.getAssigningFacility().isEmpty() )
            {
                throw new ValidationException( ErrorCodeEnum.PATIENT_ID_MISSING_ASSIGNING_FACILITY );
            }

            patientIdentifier.setIdentity( patientId.element( "identity" ).getTextTrim() );
            if ( patientIdentifier.getIdentity() == null || patientIdentifier.getIdentity().isEmpty() )
            {
                throw new ValidationException( ErrorCodeEnum.PATIENT_ID_MISSING_IDENTITY );
            }

            queryablePersonIdentifiers.add( patientIdentifier );
        }
    }


    private void getEdipiIdentifier( Element patientsElement )
    {
        Element patientId = patientsElement.element( EDIPI_ID_TAG );

        if ( patientId == null )
        {
            throw new ValidationException( ErrorCodeEnum.NO_EDIPI_REQUESTED );
        }
        queryablePersonIdentifiers = new ArrayList<PersonIdentifierInterface>();

        PersonIdentifierInterface patientIdentifier = new PersonIdentifier();
        patientIdentifier.setAssigningAuthority( patientId.element( "assigningAuthority" ).getTextTrim() );
        if ( patientIdentifier.getAssigningAuthority() == null || patientIdentifier.getAssigningAuthority().isEmpty() )
        {
            throw new ValidationException( ErrorCodeEnum.EDIPI_PATIENT_ID_MISSING_ASSIGNING_AUTHORITY );
        }

        patientIdentifier.setAssigningFacility( patientId.element( "assigningFacility" ).getTextTrim() );
        if ( patientIdentifier.getAssigningFacility() == null || patientIdentifier.getAssigningFacility().isEmpty() )
        {
            throw new ValidationException( ErrorCodeEnum.EDIPI_PATIENT_ID_MISSING_ASSIGNING_FACILITY );
        }

        patientIdentifier.setIdentity( patientId.element( "identity" ).getTextTrim() );
        if ( patientIdentifier.getIdentity() == null || patientIdentifier.getIdentity().isEmpty() )
        {
            throw new ValidationException( ErrorCodeEnum.EDIPI_PATIENT_ID_MISSING_IDENTITY );
        }

        queryablePersonIdentifiers.add( patientIdentifier );

    }


    public String getVhimVersion( )
    {
        return rootFilterElement.attributeValue( "vhimVersion" );
    }


    public Set<String> getDomainEntryPoints( )
    {
        Set<String> entryPoints = new HashSet<String>();
        for ( EntryFilterInterface filter : getEntryPointFilters() )
        {
            entryPoints.add( filter.getDomainEntryPoint() );
        }

        return entryPoints;
    }


    public Element getNationalId( )
    {
        return this.nationalId;
    }


    public List<Element> getResolvedIdentifiers( )
    {
        return this.resolvedIdentifiers;
    }


    public List<Element> getExcludedIdentifiers( )
    {
        return this.excludeIdentifiers;
    }


    /**
     * Setting elements for CdsFilter to maintain for retrieval
     * Creates independent elements, unattached to existing documents that
     * can be added to
     * 
     * @param filterDocument
     */
    @SuppressWarnings( "unchecked" )
    private void setIdentifierElements( )
    {
        Element patient = rootFilterElement.element( "patients" );

        if ( patient != null )
        {
            nationalId = patient.element( "NationalId" );
            edipi = patient.element( "EdiPi" );

            List<Element> resolvedIdentifiers = patient.elements( "resolvedIdentifiers" );
            for ( Element patientIdentifier : resolvedIdentifiers )
            {
                Element resolvedIdentifier = DocumentHelper.createElement( "resultantIdentifiers" );
                resolvedIdentifier.addElement( "identity" ).setText( patientIdentifier.elementText( "identity" ) );
                resolvedIdentifier.addElement( "assigningAuthority" ).setText( patientIdentifier.elementText( "assigningAuthority" ) );
                resolvedIdentifier.addElement( "assigningFacility" ).setText( patientIdentifier.elementText( "assigningFacility" ) );
                this.resolvedIdentifiers.add( resolvedIdentifier );
            }

            List<Element> excludedIdentifiers = patient.elements( "excludeIdentifiers" );
            for ( Element excludeIdent : excludedIdentifiers )
            {
                Element excludedIdentifier = DocumentHelper.createElement( "requestedExcludedIdentifiers" );
                String identtity = excludeIdent.elementText( "identity" );
                if ( identtity != null )
                {
                    excludedIdentifier.addElement( "identity" ).setText( ( identtity ) );
                }

                excludedIdentifier.addElement( "assigningAuthority" ).setText( excludeIdent.elementText( "assigningAuthority" ) );

                String assigFacility = excludeIdent.elementText( "assigningFacility" );
                if ( assigFacility != null )
                {
                    excludedIdentifier.addElement( "assigningFacility" ).setText( assigFacility );
                }
                else
                {
                    excludedIdentifier.addElement( "assigningFacility" ).setText( "" );
                }

                this.excludeIdentifiers.add( excludedIdentifier );
            }
        }
    }


    @Override
    public boolean isPatientCentricFilter( )
    {
        String isPatientCentricStr = this.rootFilterElement.element( "entryPointFilter" ).attributeValue( "isPatientCentric" );
        if ( isPatientCentricStr == null )
        {
            return true;
        }
        return isPatientCentricStr != null && isPatientCentricStr.length() > 0 && Boolean.parseBoolean( isPatientCentricStr ) == Boolean.TRUE;
    }
    
    public void setEntryFilterFactory( EntryFilterFactory entryFilterFactory )
    {
        this.entryFilterFactory = entryFilterFactory;
    }
}
