/**
 * 
 */


package gov.va.med.cds.idm.client;


import gov.va.med.cds.checksum.util.IcnChecksum;
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.exception.IdmPersonServiceException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;


/**
 * @author susarlan
 *
 */
public class IdmPersonIdentifierManagement
    implements
        PersonIdentifierCorrelationServiceInterface
{
    private static Log logger = LogFactory.getLog( IdmPersonIdentifierManagement.class );

    private static final int LENGTH_ICN_WITHOUT_CHECKSUM = 10;

    private String correspondingIdsRequestXml;
    private IdmPersonServiceInterface idmPersonService;


    /* (non-Javadoc)
     * @see gov.va.med.cds.common.person.correlation.PersonIdentifierCorrelationServiceInterface#obtainPersonIdentifiers(gov.va.med.cds.common.person.correlation.PersonIdentifierInterface)
     */
    public Collection<PersonIdentifierInterface> obtainPersonIdentifiers( PersonIdentifierInterface personIdentifier )
    {
        // add checksum to specified person identifier, if needed
        addChecksumIfNeeded( personIdentifier );

        // Construct IDM service request xml parameter from given person identifier
        String idmrequestXml = correspondingIdsRequestXml.replace( "%%REPLACE_ICN_VALUE%%", personIdentifier.getIdentity() );
        idmrequestXml = idmrequestXml.replace( "%%REPLACE_ASSIGINING_FACILITY%%", personIdentifier.getAssigningFacility() );
        idmrequestXml = idmrequestXml.replace( "%%REPLACE_ASSIGINING_AUTHORITY%%", personIdentifier.getAssigningAuthority() );

        String idmResponseXml = "";
        long sentTime = new Date().getTime();
        if ( logger.isDebugEnabled() )
        {
            logger.debug( "IDM request '" + idmrequestXml + "'" );
        }

        try
        {
            // Call the IDM service to obtain the corresponding ids for the given ICN
            idmResponseXml = idmPersonService.getCorrespondingIds( idmrequestXml );
        }
        catch ( Exception e )
        {
            throw new IdmPersonServiceException( ErrorCodeEnum.IDM_SERVICE_EXCEPTION, e, e.getMessage() );
        }

        long elapsedTime = new Date().getTime() - sentTime;
        if ( logger.isDebugEnabled() )
        {
            logger.debug( "Recieved IDM response '" + idmResponseXml + "' in " + elapsedTime + " milliseconds" );
        }

        // Construct the person identifier collection from the returned ids
        return toPersonIdentifiers( idmResponseXml, personIdentifier );
    }


    private void addChecksumIfNeeded( PersonIdentifierInterface personIdentifier )
    {
        if ( personIdentifier == null )
        {
            throw new IdmPersonServiceException( ErrorCodeEnum.IDM_NULL_PERSON_IDENTIFIER_EXCEPTION );
        }

        String identity = personIdentifier.getIdentity();
        if ( identity == null )
        {
            throw new IdmPersonServiceException( ErrorCodeEnum.IDM_NULL_PERSON_IDENTITY_EXCEPTION );
        }

        // if identity length is the length w/o checksum - add checksum to it
        if ( identity.length() == LENGTH_ICN_WITHOUT_CHECKSUM )
        {
            // should never get ICN with no checksum - keep track?
            logger.info( "ICN has no checksum - calculating and adding checksum to ICN now." );

            // append the checksum to the identity if it is not present.
            personIdentifier.setIdentity( IcnChecksum.addChecksumToIcn( identity ) );
        }
    }


    private Collection<PersonIdentifierInterface> toPersonIdentifiers( String idmResponseXml, PersonIdentifierInterface sourceIdentifier )
    {
        List<PersonIdentifierInterface> personIdentifiers = new ArrayList<PersonIdentifierInterface>();

        List<PersonIdentifierInterface> historicalPersonIdentifiers = new ArrayList<PersonIdentifierInterface>();

        if ( idmResponseXml.contains( "IDM_RESPONSE type='GET_CORRESPONDING_IDS'" ) )
        {
            InputStream is = new ByteArrayInputStream( idmResponseXml.getBytes() );
            try
            {
                // Parse the response xml from IDM
                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                //                dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
                //                dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
                dbf.setExpandEntityReferences( false );
                DocumentBuilder db = dbf.newDocumentBuilder();
                Document idmResponseDoc = db.parse( is );

                // Get all the identifiers
                NodeList identifierList = idmResponseDoc.getElementsByTagName( "IDENTIFIER" );
                for ( int i = 0; i < identifierList.getLength(); i++ )
                {
                    Node identifierNode = identifierList.item( i );
                    if ( identifierNode.getNodeType() == Node.ELEMENT_NODE )
                    {
                        PersonIdentifierInterface personIdentifier = new PersonIdentifier();

                        PersonIdentifierInterface historicalPersonIdentifier = new PersonIdentifier();

                        Element identifierElement = ( Element )identifierNode;

                        Element statusElement = ( Element )identifierElement.getElementsByTagName( "STATUS" ).item( 0 );
                        String statusValue = ( ( Node )statusElement.getChildNodes().item( 0 ) ).getNodeValue().trim();

                        Element idValueElement = ( Element )identifierElement.getElementsByTagName( "ID" ).item( 0 );
                        String idValue = ( ( Node )idValueElement.getChildNodes().item( 0 ) ).getNodeValue().trim();

                        if ( !statusValue.equals( "H" ) )
                        {
                            personIdentifier.setIdentity( idValue );
                        }
                        else
                        {
                            historicalPersonIdentifier.setIdentity( idValue );
                        }

                        if ( !identifierElement.hasAttribute( "subtype" ) || !"IDM".equals( identifierElement.getAttribute( "subtype" ) ) )
                        {
                            Element sourceValueElement = ( Element )identifierElement.getElementsByTagName( "SOURCE" ).item( 0 );
                            String sourceValue = ( ( Node )sourceValueElement.getChildNodes().item( 0 ) ).getNodeValue().trim();
                            sourceValue = ( null == sourceValue ) ? "" : sourceValue;

                            if ( !statusValue.equals( "H" ) )
                            {
                                personIdentifier.setAssigningFacility( sourceValue );
                            }
                            else
                            {
                                historicalPersonIdentifier.setAssigningFacility( sourceValue );
                            }

                            Element issuerValueElement = ( Element )identifierElement.getElementsByTagName( "ISSUER" ).item( 0 );
                            String issuerValue = ( ( Node )issuerValueElement.getChildNodes().item( 0 ) ).getNodeValue().trim();
                            issuerValue = ( null == issuerValue ) ? "" : issuerValue;

                            if ( !statusValue.equals( "H" ) )
                            {
                                personIdentifier.setAssigningAuthority( issuerValue );
                            }
                            else
                            {
                                historicalPersonIdentifier.setAssigningAuthority( issuerValue );
                            }
                        }
                        else if ( sourceIdentifier.getIdentity().equals( idValue ) )
                        {

                            if ( !statusValue.equals( "H" ) )
                            {
                                personIdentifier.setAssigningFacility( sourceIdentifier.getAssigningFacility() );
                            }
                            else
                            {
                                historicalPersonIdentifier.setAssigningFacility( sourceIdentifier.getAssigningFacility() );
                            }

                            if ( !statusValue.equals( "H" ) )
                            {
                                personIdentifier.setAssigningAuthority( sourceIdentifier.getAssigningAuthority() );
                            }
                            else
                            {
                                historicalPersonIdentifier.setAssigningAuthority( sourceIdentifier.getAssigningAuthority() );
                            }
                        }
                        else if ( identifierElement.hasAttribute( "type" ) && "NI".equals( identifierElement.getAttribute( "type" ) ) )
                        {
                            // TODO: We still need further information on how to treat NI (ICN) identifiers that 
                            // are returned by the IdM service. [BJE - 20110111]

                            if ( !statusValue.equals( "H" ) )
                            {
                                personIdentifier.setAssigningFacility( "200M" );
                                personIdentifier.setAssigningAuthority( "USVHA" );
                            }
                            else
                            {
                                historicalPersonIdentifier.setAssigningFacility( "200M" );
                                historicalPersonIdentifier.setAssigningAuthority( "USVHA" );
                            }
                        }
                        else
                        {
                            if ( !statusValue.equals( "H" ) )
                            {
                                personIdentifier.setAssigningFacility( "" );
                                personIdentifier.setAssigningAuthority( "" );
                            }
                            else
                            {
                                historicalPersonIdentifier.setAssigningFacility( "" );
                                historicalPersonIdentifier.setAssigningAuthority( "" );
                            }

                        }

                        if ( personIdentifier.getIdentity() != null )
                        {
                            personIdentifiers.add( personIdentifier );
                        }

                        if ( historicalPersonIdentifier.getIdentity() != null )
                        {
                            historicalPersonIdentifiers.add( historicalPersonIdentifier );
                        }
                    }
                }
            }
            catch ( Exception ex )
            {
                throw new IdmPersonServiceException( ErrorCodeEnum.IDM_RESPONSE_PROCESSING_EXCEPTION, ex );
            }
        }
        else if ( idmResponseXml.contains( "IDM_RESPONSE type='ERROR'" ) )
        {
            throw new IdmPersonServiceException( ErrorCodeEnum.IDM_ERROR_RESPONSE, sourceIdentifier.getIdentity(), idmResponseXml );
        }

        for ( PersonIdentifierInterface pi : historicalPersonIdentifiers )
        {
            personIdentifiers.add( pi );
        }

        return personIdentifiers;
    }


    public void setIdmPersonService( IdmPersonServiceInterface idmPersonService )
    {
        this.idmPersonService = idmPersonService;
    }


    public void setCorrespondingIdsRequestXml( String correspondingIdsRequestXml )
    {
        this.correspondingIdsRequestXml = correspondingIdsRequestXml;
    }

}
