/**
 * 
 */


package gov.va.med.cds.filter.vhim400;


import gov.va.med.cds.common.person.correlation.PersonIdentifier;
import gov.va.med.cds.common.person.correlation.PersonIdentifierCorrelationServiceInterface;
import gov.va.med.cds.common.person.correlation.PersonIdentifierInterface;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.filter.CdsFilter;
import gov.va.med.cds.filter.FilterPatientResolverException;
import gov.va.med.cds.filter.FilterPatientResolverInterface;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;


/**
 * @author susarlan
 * 
 * Class used for resolving unresolved national identifier in a CDS filter.
 * This class expects the information for patients to be specified in an XML element as follows.
 * Any change to the following structure requires corresponding changes to this class.
 * 
 * <xs:element name="patients" type="filter:patient"/>
 * where type filter:patient and sub-types used by the 'patient' type are as follows:
 * 
 * <xs:complexType name="patient">
 *   <xs:choice>
 *     <xs:element name="resolvedIdentifiers" type="filter:PersonIdentifier" maxOccurs="unbounded"/>
 *     <xs:sequence>
 *       <xs:element name="NationalId" type="xs:string"/>
 *       <xs:element name="excludeIdentifiers" type="filter:ExcludeIdentifier" minOccurs="0" maxOccurs="unbounded"/>
 *     </xs:sequence>
 *   </xs:choice>
 * </xs:complexType>
 * 
 * <xs:complexType name="PersonIdentifier">
 *   <xs:sequence>
 *     <xs:element name="assigningAuthority" type="xs:string"/>
 *     <xs:element name="assigningFacility" type="xs:string"/>
 *     <xs:element name="identity" type="xs:string"/>
 *   </xs:sequence>
 * </xs:complexType>
 * 
 * <xs:complexType name="ExcludeIdentifier">
 *   <xs:sequence>
 *     <xs:element name="assigningAuthority" type="xs:string"/>
 *     <xs:choice minOccurs="0">
 *       <xs:sequence>
 *         <xs:element name="assigningFacility" type="xs:string"/>
 *           <xs:choice minOccurs="0">
 *             <xs:element name="identity" type="xs:string"/>
 *           </xs:choice>
 *       </xs:sequence>
 *     </xs:choice>
 *   </xs:sequence>
 * </xs:complexType>
 *
 */
/**
 * @author susarlan
 *
 */
public class SinglePatientResolver
    implements
        FilterPatientResolverInterface
{
    private static Log logger = LogFactory.getLog( SinglePatientResolver.class );

    // Constant Values used in code
    private static String NATIONAL_IDENTIFIER_ASSIGNING_FACILITY = "200M";
    private static String NATIONAL_IDENTIFIER_ASSIGNING_AUTHORITY = "USVHA";
    private static String HEALTH_ADAPTER_SINGLE_PATIENT_ALL_DATA_FILTER = "HEALTH_ADAPTER_SINGLE_PATIENT_ALL_DATA_FILTER";

    // XML Tags from filter used in code
    private static String PATIENTS_TAG = "patients";
    private static String NATIONAL_ID_TAG = "NationalId";
    private static String RESOLVED_IDENTIFIERS_TAG = "resolvedIdentifiers";
    private static String EXCLUDE_IDENTIFIERS_TAG = "excludeIdentifiers";
    private static String ASSIGNING_AUTHORITY_TAG = "assigningAuthority";
    private static String ASSIGNING_FACILITY_TAG = "assigningFacility";
    private static String IDENTITY_TAG = "identity";

    private PersonIdentifierCorrelationServiceInterface patientIdentifierCorrelationService;


    // Dom4j does not support generics
    @SuppressWarnings( "unchecked" )
    public Document resolveIdentifiersInFilter( String filterId, Document filterRequestDocument )
    {
        //        if ( logger.isDebugEnabled() )
        //        {
        //            logger.debug( "resolveIdentifiersInFilter for filterId: " + filterId + " with filter xml:\n" + filterRequest );
        //        }

        //        Document filterRequestDocument = getDocument( filterRequest );

        if ( filterRequestDocument == null )
        {
            throw new FilterPatientResolverException( ErrorCodeEnum.FILTER_PARSER_DOM_EXCEPTION );
        }

        Element rootElement = filterRequestDocument.getRootElement();

        // Get the patients element from the filter
        Element patients = rootElement.element( PATIENTS_TAG );
        if ( patients == null )
        {
            throw new FilterPatientResolverException( ErrorCodeEnum.NO_PATIENT_IDS_REQUESTED );
        }

        // Get the National ID from the patients
        @SuppressWarnings( "rawtypes" )
        List identifierList = patients.elements( NATIONAL_ID_TAG );
        int numIds = identifierList.size();

        if ( logger.isDebugEnabled() )
        {
            logger.debug( "Number of national ids in filter: " + numIds );
        }

        if ( numIds == 1 )
        {
            // 1 national id in filter - obtain corresponding identifiers and remove national id element from patients
            Collection<PersonIdentifierInterface> correspondingIdentifiers = resolveNationaIdentifier( ( Element )identifierList.get( 0 ) );

            // exclude identifiers specified in filter from patients node & list of corresponding identifiers 
            excludeIdentifiers( patients, correspondingIdentifiers );

            // add resolved identifiers to patients
            addResolvedIdentifiers( patients, correspondingIdentifiers );

            if ( logger.isDebugEnabled() )
            {
                logger.debug( "Final set of resolved identifiers after exclusions:\n" + correspondingIdentifiers );
            }
        }
        else if ( numIds == 0 )
        {
            identifierList = patients.elements( CdsFilter.EDIPI_ID_TAG );
            if ( identifierList == null || identifierList.size() <= 0 )
            {
                identifierList = patients.elements( RESOLVED_IDENTIFIERS_TAG );
                if ( identifierList.size() <= 0 )
                {
                    throw new FilterPatientResolverException( ErrorCodeEnum.NO_PATIENT_IDS_REQUESTED );
                }
                if ( logger.isDebugEnabled() )
                {
                    logger.debug( "Filter has resolved identifiers - not national id - returning original filter w/o calling IDM service." );
                }
            }
            else
            {
                if ( logger.isDebugEnabled() )
                {
                    logger.debug( "Filter has EDIPI - not national id - returning original filter w/o calling IDM service." );
                }
            }
        }
        else
        {
            // can have utmost 1 national id in filter
            throw new FilterPatientResolverException( ErrorCodeEnum.MULTIPLE_NATIONAL_IDS_IN_FILTER, filterRequestDocument.asXML() );
        }

        if ( logger.isDebugEnabled() )
        {
            logger.debug( "resolveIdentifiersInFilter returning modified filter below:\n" + filterRequestDocument.asXML() );
        }

        return filterRequestDocument;
    }


    private Collection<PersonIdentifierInterface> resolveNationaIdentifier( Element nationalIdElement )
    {
        String nationalId = nationalIdElement.getTextTrim();

        // remove the NationalId node from the patients
        //nationalIdElement.detach();

        return getCorrespondingIdentifiers( nationalId );
    }


    private Collection<PersonIdentifierInterface> getCorrespondingIdentifiers( String nationalId )
    {
        Collection<PersonIdentifierInterface> correspondingIdentifiers = null;

        PersonIdentifierInterface unResolvedPersonIdentifier = new PersonIdentifier();
        unResolvedPersonIdentifier.setIdentity( nationalId );
        unResolvedPersonIdentifier.setAssigningFacility( NATIONAL_IDENTIFIER_ASSIGNING_FACILITY );
        unResolvedPersonIdentifier.setAssigningAuthority( NATIONAL_IDENTIFIER_ASSIGNING_AUTHORITY );

        if ( patientIdentifierCorrelationService == null )
        {
            throw new FilterPatientResolverException( ErrorCodeEnum.PATIENT_ID_CORRELATION_SERVICE_NULL );
        }

        correspondingIdentifiers = patientIdentifierCorrelationService.obtainPersonIdentifiers( unResolvedPersonIdentifier );
        if ( logger.isDebugEnabled() )
        {
            logger.debug( "National id '" + nationalId + "' resolved to following identifiers:\n" + correspondingIdentifiers );
        }

        if ( correspondingIdentifiers == null || correspondingIdentifiers.isEmpty() )
        {
            throw new FilterPatientResolverException( ErrorCodeEnum.NO_PATIENT_IDS_RESOLVED );
        }

        return correspondingIdentifiers;
    }


    // Dom4j does not support generics
    @SuppressWarnings( "unchecked" )
    private void excludeIdentifiers( Element patients, Collection<PersonIdentifierInterface> correspondingIdentifiers )
    {
        Collection<PersonIdentifierInterface> identifiersToExclude = new ArrayList<PersonIdentifierInterface>();

        @SuppressWarnings( "rawtypes" )
        List excludeIdentifiers = patients.elements( EXCLUDE_IDENTIFIERS_TAG );
        for ( Object excludeIdentifierObject : excludeIdentifiers )
        {
            Element excludeIdentifierElement = ( Element )excludeIdentifierObject;
            // assigning authority is always specified in an excluded identifier
            String assigningAuthority = excludeIdentifierElement.element( ASSIGNING_AUTHORITY_TAG ).getTextTrim();

            // check if assigning facility is specified
            Element facilityElement = excludeIdentifierElement.element( ASSIGNING_FACILITY_TAG );
            String assigningFacility = null;
            if ( facilityElement != null )
            {
                assigningFacility = facilityElement.getTextTrim();
            }

            // Check if identity is specified
            Element identityElement = excludeIdentifierElement.element( IDENTITY_TAG );
            if ( identityElement != null )
            {
                String identity = identityElement.getTextTrim();

                PersonIdentifierInterface excludePersonIdentifier = new PersonIdentifier();
                excludePersonIdentifier.setAssigningAuthority( assigningAuthority );
                excludePersonIdentifier.setAssigningFacility( assigningFacility );
                excludePersonIdentifier.setIdentity( identity );
                identifiersToExclude.add( excludePersonIdentifier );
            }
            else
            {
                for ( PersonIdentifierInterface pi : correspondingIdentifiers )
                {
                    if ( pi.getAssigningAuthority().equals( assigningAuthority ) )
                    {
                        if ( assigningFacility == null || assigningFacility.equals( pi.getAssigningFacility() ) )
                        {
                            identifiersToExclude.add( pi );
                        }
                    }
                }
            }

            // remove exclude identifier node from the patients list
            //excludeIdentifierElement.detach();

        }

        if ( logger.isDebugEnabled() )
        {
            logger.debug( "Following identifiers are being excluded from resolved identifier list:\n" + identifiersToExclude );
        }

        // remove the identifiers to exclude from the corresponding identifiers list
        correspondingIdentifiers.removeAll( identifiersToExclude );
        if ( correspondingIdentifiers.isEmpty() )
        {
            throw new FilterPatientResolverException( ErrorCodeEnum.ALL_PATIENT_IDS_EXCLUDED );
        }
    }


    private void addResolvedIdentifiers( Element patients, Collection<PersonIdentifierInterface> resolvedIdentifiers )
    {
        try
        {
            for ( PersonIdentifierInterface pi : resolvedIdentifiers )
            {
                Element resolvedId = patients.addElement( RESOLVED_IDENTIFIERS_TAG );

                resolvedId.addElement( ASSIGNING_AUTHORITY_TAG ).addText( pi.getAssigningAuthority() );
                resolvedId.addElement( ASSIGNING_FACILITY_TAG ).addText( pi.getAssigningFacility() );
                resolvedId.addElement( IDENTITY_TAG ).addText( pi.getIdentity() );
            }
        }
        catch ( Exception e )
        {
            throw new FilterPatientResolverException( ErrorCodeEnum.ERROR_ADDING_RESOLVEDIDENTIFIERS_TO_FILTER, e );
        }
    }


    public void setPatientIdentifierCorrelationService( PersonIdentifierCorrelationServiceInterface patientIdentifierCorrelationService )
    {
        this.patientIdentifierCorrelationService = patientIdentifierCorrelationService;
    }
}
