

package gov.va.med.cds.integration;


import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import gov.va.med.cds.clinicaldata.DomainEntryPoint;
import gov.va.med.cds.junit.runners.SuiteAwareRunner;
import gov.va.med.cds.testharness.xml.XmlCompareException;
import gov.va.med.cds.uniqueidentifier.UniqueIdentifier;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;

import org.dom4j.Document;
import org.dom4j.Element;
import org.junit.runner.RunWith;
import org.xml.sax.SAXException;


/**
 *
 * EndToEndReadNoFilterTest is responsible for:
 *
 */
@RunWith( SuiteAwareRunner.class )
public abstract class AbstractEndToEndReadNoXpathFilterTest
    extends
        AbstractEndToEndTest
{
    
    protected static final String IC_PHARMACY_IDENTIFIER = "test_IC_OPP_1009";
    protected static final String IC_IDENTIFIER = "test_IC_10008";
    protected static final String ALLERGY_ASSESSMENT_IDENTIFIER = "test_AA_10004";
    protected static final String OPP_MEDICATION_IDENTIFIER = "test_OPP_1006";
    protected static final String LAB_IDENTIFIER = "test_LAB_1006";
    protected static final String VITAL_IDENTIFIER = "test_VITAL_1006";
    protected static final String DEFAULT_ASSIGNING_FAC_UNIVERSAL_ID = "VA.GOV-605";
    protected static final String DEFAULT_ASSIGNING_AUTHORITY = "USVHA";


    protected abstract String createIdentityDomainEntryPointFilter( Element personIdentifier, DomainEntryPoint... domainEntryPoint );
    protected abstract String createIdentityDomainEntryPointFilter( List<Element> patients, DomainEntryPoint... domainEntryPoint );
    protected abstract Document makeClinicalDataRecord( Element personIdentifier, String templateId )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException;

    // Read Template Ids
    protected abstract String getIntoleranceConditionReadTemplateID( );
    protected abstract String getAllergyAssessmentReadTemplateID( );
    protected abstract String getPharmacyReadTemplateID( );
    protected abstract String getLabReadTemplateID( );
    protected abstract String getVitalReadTemplateID( );
    protected abstract String getIntoleranceConditionPharmacyReadTemplateID( );

    // Create Template Ids
    protected abstract String getIntoleranceConditionCreateTemplateID( );
    protected abstract String getAllergyAssessmentCreateTemplateID( );
    protected abstract String getPharmacyCreateTemplateID( );
    protected abstract String getLabCreateTemplateID( );
    protected abstract String getVitalCreateTemplateID( );
    protected abstract ClinicalDataCount countAllPatientData( Document clinicalDataInterface )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException;

    protected abstract int countClinicalDataPatients( Document clinicalDataInterface );
    protected abstract void prepareAndCreateDataForTest( Document clinicalDataInsert )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException;

    protected abstract void createClinicalData( String templateId, String controlXml );
    
    protected String makeKeyFromPersonIdentifier( Element personIdentifier )
    {
        return personIdentifier.elementText( "identity" ) + personIdentifier.elementText( "assigningFacility" ) + personIdentifier.elementText( "assigningAuthority" );
    }
    
    protected Element createPersonIdentifier( String identity, String assigningFacility, String assigningAuthority )
    {
        return clinicalDataGenerator.createPatient( identity, assigningFacility, assigningAuthority );
    }

    protected void doReadCreateClinicalDataAndCompareRecordCounts( String readTemplateId, String createTemplateId,
                    Element personIdentifier, DomainEntryPoint... domainEntryPoint )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException
    {
        String subjectKey = makeKeyFromPersonIdentifier( personIdentifier );

        // create a filter for this subject and domainEntryPoint
        String filter = createIdentityDomainEntryPointFilter( personIdentifier, domainEntryPoint );

        // retrieve the entry point data by identifier and count the current number of records
        Map<String, Map<String, Map<DomainEntryPoint, Integer>>> beforePatientDataCount = readAndCountDomainData( readTemplateId, filter, 1 )
                        .getDataCount();

        // now add an additional record
        makeClinicalDataRecord( personIdentifier, createTemplateId );

        // again retrieve the OutpatientMedicationPromise by identifier and count the current number of records
        Map<String, Map<String, Map<DomainEntryPoint, Integer>>> afterPatientDataCount = readAndCountDomainData( readTemplateId, filter, 1 )
                        .getDataCount();

        // and get the new count of records
        compareBeforeAndAfterData( beforePatientDataCount.get( subjectKey ), afterPatientDataCount.get( subjectKey ), subjectKey, 1, domainEntryPoint );
    }


    public void testOutpatientMedicationPromiseReadCreateRead( )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException
    {
        Element sourceId = clinicalDataGenerator.createPatient( OPP_MEDICATION_IDENTIFIER, DEFAULT_ASSIGNING_FAC_UNIVERSAL_ID,
                        DEFAULT_ASSIGNING_AUTHORITY );
        doReadCreateClinicalDataAndCompareRecordCounts( getPharmacyReadTemplateID(), getPharmacyCreateTemplateID(), sourceId,
                        DomainEntryPoint.OutpatientMedicationPromise );
    }


    public void testVitalSignObservationEventReadCreateRead( )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException
    {
        Element sourceId = clinicalDataGenerator.createPatient( VITAL_IDENTIFIER, DEFAULT_ASSIGNING_FAC_UNIVERSAL_ID, DEFAULT_ASSIGNING_AUTHORITY );
        doReadCreateClinicalDataAndCompareRecordCounts( getVitalReadTemplateID(), getVitalCreateTemplateID(), sourceId,
                        DomainEntryPoint.VitalSignObservationEvent );
    }


    public void testLabTestPromiseReadCreateRead( )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException
    {
        Element sourceId = clinicalDataGenerator.createPatient( LAB_IDENTIFIER, DEFAULT_ASSIGNING_FAC_UNIVERSAL_ID, DEFAULT_ASSIGNING_AUTHORITY );
        doReadCreateClinicalDataAndCompareRecordCounts( getLabReadTemplateID(), getLabCreateTemplateID(), sourceId, DomainEntryPoint.LabTestPromise );
    }


    public void testAllergiesAssessmentReadCreateRead( )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException
    {
        Element sourceId = clinicalDataGenerator.createPatient( ALLERGY_ASSESSMENT_IDENTIFIER, DEFAULT_ASSIGNING_FAC_UNIVERSAL_ID,
                        DEFAULT_ASSIGNING_AUTHORITY );
        doReadCreateClinicalDataAndCompareRecordCounts( getAllergyAssessmentReadTemplateID(), getAllergyAssessmentCreateTemplateID(), sourceId,
                        DomainEntryPoint.AllergyAssessment );
    }


    public void testIntoleranceConditionReadCreateRead( )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException
    {
        Element sourceId = clinicalDataGenerator.createPatient( IC_IDENTIFIER, DEFAULT_ASSIGNING_FAC_UNIVERSAL_ID, DEFAULT_ASSIGNING_AUTHORITY );
        doReadCreateClinicalDataAndCompareRecordCounts( getIntoleranceConditionReadTemplateID(), getIntoleranceConditionCreateTemplateID(), sourceId,
                        DomainEntryPoint.IntoleranceCondition );
    }


    public void testMultiEntryPointRead( )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException
    {
        // make a single PersonIdentifier subject
        Element patientIdentifier = clinicalDataGenerator.createPatient( IC_PHARMACY_IDENTIFIER, DEFAULT_ASSIGNING_FAC_UNIVERSAL_ID,
                        DEFAULT_ASSIGNING_AUTHORITY );
        String key = makeKeyFromPersonIdentifier( patientIdentifier );

        // get a multi-domain template
        String templateId = getIntoleranceConditionPharmacyReadTemplateID();
        // make a multi-domain filter for single-PersonIdentifier subject
        String filter = createIdentityDomainEntryPointFilter( patientIdentifier, DomainEntryPoint.IntoleranceCondition,
                        DomainEntryPoint.OutpatientMedicationPromise );

        // retrieve the existing multi-domain data for this single-PersonIdentifier subject
        Map<String, Map<String, Map<DomainEntryPoint, Integer>>> beforePatientDataCount = readAndCountDomainData( templateId, filter, 1 )
                        .getDataCount();

        // now add an additional IntoleranceCondition and OutpatientMedicationPromise record
        makeClinicalDataRecord( patientIdentifier, getIntoleranceConditionCreateTemplateID() );
        makeClinicalDataRecord( patientIdentifier, getPharmacyCreateTemplateID() );

        // again retrieve the multi-domain data for this single-PersonIdentifier subject
        Map<String, Map<String, Map<DomainEntryPoint, Integer>>> afterPatientDataCountAB = readAndCountDomainData( templateId, filter, 1 )
                        .getDataCount();

        // now make sure we read the correct number of records
        compareBeforeAndAfterData( beforePatientDataCount.get( key ), afterPatientDataCountAB.get( key ), key, 1,
                        DomainEntryPoint.IntoleranceCondition, DomainEntryPoint.OutpatientMedicationPromise );
    }


    public void testMultiEntryPointMultiPatientWriteRead( )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException
    {
        // make a multi-PersonIdentifier subject
        Element patientIdentifierA = clinicalDataGenerator.createPatient( "test_sourceId_A", "VISN_A", DEFAULT_ASSIGNING_AUTHORITY );
        String keyA = makeKeyFromPersonIdentifier( patientIdentifierA );

        Element patientIdentifierB = clinicalDataGenerator.createPatient( "test_sourceId_B", "VISN_B", DEFAULT_ASSIGNING_AUTHORITY );
        String keyB = makeKeyFromPersonIdentifier( patientIdentifierB );

        List<Element> subjects = new ArrayList<Element>();
        subjects.add( patientIdentifierA );
        subjects.add( patientIdentifierB );

        // get a multi-domain template
        String templateId = getIntoleranceConditionPharmacyReadTemplateID();
        // make a multi-domain filter for the multi-PersonIdentifier subject1
        String filter = createIdentityDomainEntryPointFilter( subjects, DomainEntryPoint.IntoleranceCondition,
                        DomainEntryPoint.OutpatientMedicationPromise );

        // retrieve the existing multi-domain data for this multi-PersonIdentifier subject1
        Map<String, Map<String, Map<DomainEntryPoint, Integer>>> beforePatientDataCountAB = readAndCountDomainData( templateId, filter, 1 )
                        .getDataCount();

        // now add an additional IntoleranceCondition and OutpatientMedicationPromise record for identifier A
        makeClinicalDataRecord( patientIdentifierA, getIntoleranceConditionCreateTemplateID() );
        makeClinicalDataRecord( patientIdentifierA, getPharmacyCreateTemplateID() );

        // now add an additional OutpatientMedicationPromise record for identifier B
        makeClinicalDataRecord( patientIdentifierB, getPharmacyCreateTemplateID() );

        // again retrieve the multi-domain data for this multi-PersonIdentifier subject1
        Map<String, Map<String, Map<DomainEntryPoint, Integer>>> afterPatientDataCountAB = readAndCountDomainData( templateId, filter, 1 )
                        .getDataCount();

        // now we should have read the new data added and the counts should match.
        // patientIdentifierA with KeyA should have 1 new IntoleranceCondition record and 1 new OutpatientMedicationPromise record
        String subjectKey1 = keyA + keyB;
        compareBeforeAndAfterData( beforePatientDataCountAB.get( subjectKey1 ), afterPatientDataCountAB.get( subjectKey1 ), keyA, 1,
                        DomainEntryPoint.IntoleranceCondition, DomainEntryPoint.OutpatientMedicationPromise );
        // patientIdentifierB with KeyB should have 1 new OutpatientMedicationPromise record
        compareBeforeAndAfterData( beforePatientDataCountAB.get( subjectKey1 ), afterPatientDataCountAB.get( subjectKey1 ), keyB, 1,
                        DomainEntryPoint.OutpatientMedicationPromise );

        // now do all of the above for a second subject
        // make another multi-Id subject
        subjects.clear();
        Element patientIdentifierC = clinicalDataGenerator.createPatient( "test_sourceId_C", "VISN_C", DEFAULT_ASSIGNING_AUTHORITY );
        String keyC = makeKeyFromPersonIdentifier( patientIdentifierC );

        Element patientIdentifierD = clinicalDataGenerator.createPatient( "test_sourceId_D", "VISN_D", DEFAULT_ASSIGNING_AUTHORITY );
        String keyD = makeKeyFromPersonIdentifier( patientIdentifierD );

        subjects.add( patientIdentifierC );
        subjects.add(patientIdentifierD);

        // make a multi-domain filter for the multi-Id subject2
        filter = createIdentityDomainEntryPointFilter( subjects, DomainEntryPoint.IntoleranceCondition, DomainEntryPoint.OutpatientMedicationPromise );

        // retrieve the existing multi-domain data for this multi-PersonIdentifier subject2
        Map<String, Map<String, Map<DomainEntryPoint, Integer>>> beforePatientDataCountCD = readAndCountDomainData( templateId, filter, 1 )
                        .getDataCount();

        // now add an additional IntoleranceCondition record for identifier C
        makeClinicalDataRecord( patientIdentifierC, getIntoleranceConditionCreateTemplateID() );

        // now add an additional IntoleranceCondition and OutpatientMedicationPromise record for identifier D
        makeClinicalDataRecord( patientIdentifierD, getPharmacyCreateTemplateID() );
        makeClinicalDataRecord( patientIdentifierD, getIntoleranceConditionCreateTemplateID() );

        // again retrieve the multi-domain data for this multi-PersonIdentifier subject2
        Map<String, Map<String, Map<DomainEntryPoint, Integer>>> afterPatientDataCountCD = readAndCountDomainData( templateId, filter, 1 )
                        .getDataCount();

        // now we should have read the new data added and the counts should match.
        // patientIdentifierC with KeyC should have 1 new IntoleranceCondition record
        String subjectKey2 = keyC + keyD;
        compareBeforeAndAfterData( beforePatientDataCountCD.get( subjectKey2 ), afterPatientDataCountCD.get( subjectKey2 ), keyC, 1,
                        DomainEntryPoint.IntoleranceCondition );
        // patientIdentifierD with KeyD should have 1 new IntoleranceCondition record and 1 new OutpatientMedicationPromise record
        compareBeforeAndAfterData( beforePatientDataCountCD.get( subjectKey2 ), afterPatientDataCountCD.get( subjectKey2 ), keyD, 1,
                        DomainEntryPoint.IntoleranceCondition, DomainEntryPoint.OutpatientMedicationPromise );

        // Now create a multi-subject filter and read the data
        subjects.clear();
        subjects.add( patientIdentifierA );
        subjects.add( patientIdentifierB );
        subjects.add( patientIdentifierC );
        subjects.add(patientIdentifierD);
        filter = createIdentityDomainEntryPointFilter( subjects, DomainEntryPoint.IntoleranceCondition, DomainEntryPoint.OutpatientMedicationPromise );

        // retrieve the IntoleranceCondition and pharmacy by subject (multi identifiers) for second subject.
        Map<String, Map<String, Map<DomainEntryPoint, Integer>>> multiPatientDataCount = readAndCountDomainData( templateId, filter, 2 )
                        .getDataCount();

        compareBeforeAndAfterData( multiPatientDataCount.get( subjectKey1 ), afterPatientDataCountAB.get( subjectKey1 ), keyA, 0,
                        DomainEntryPoint.IntoleranceCondition, DomainEntryPoint.OutpatientMedicationPromise );
        compareBeforeAndAfterData( multiPatientDataCount.get( subjectKey1 ), afterPatientDataCountAB.get( subjectKey1 ), keyB, 0,
                        DomainEntryPoint.OutpatientMedicationPromise );
        compareBeforeAndAfterData( multiPatientDataCount.get( subjectKey2 ), afterPatientDataCountCD.get( subjectKey2 ), keyC, 0,
                        DomainEntryPoint.IntoleranceCondition );
        compareBeforeAndAfterData( multiPatientDataCount.get( subjectKey2 ), afterPatientDataCountCD.get( subjectKey2 ), keyD, 0,
                        DomainEntryPoint.IntoleranceCondition, DomainEntryPoint.OutpatientMedicationPromise );
    }


    protected ClinicalDataCount readAndCountDomainData( String templateId, String filter, int requiredPatientCount )
        throws SAXException,
            IOException,
            ParserConfigurationException,
            XmlCompareException
    {
        // read the record using the parameters passed in.
        Document clinicalDataDocument = getRequestProcessor().readClinicalData( templateId, filter, "Filter100",
                        new UniqueIdentifier().toString() );
        
        String clinicalDataResult = clinicalDataDocument.asXML();
        
        // now count result and return to caller  
        ClinicalDataCount patientDataCount = countAllPatientData( clinicalDataDocument );
        // Assert that the required number of patients was read
        assertTrue( countClinicalDataPatients( clinicalDataDocument ) == requiredPatientCount );
        
        return patientDataCount;

    }


    private void compareBeforeAndAfterData( Map<String, Map<DomainEntryPoint, Integer>> beforePatientDataCount,
                    Map<String, Map<DomainEntryPoint, Integer>> afterPatientDataCount, String aPersonIdentifier, int countDelta,
                    DomainEntryPoint... aDomainEntryPoints )
    {
        Map<DomainEntryPoint, Integer> beforeDomainDataCountMap = beforePatientDataCount.get( aPersonIdentifier );
        assertNotNull( beforeDomainDataCountMap );
        Map<DomainEntryPoint, Integer> afterDomainDataCountMap = afterPatientDataCount.get( aPersonIdentifier );
        assertNotNull( afterDomainDataCountMap );
        for ( DomainEntryPoint domainEntryPoint : aDomainEntryPoints )
        {
            assertTrue( afterDomainDataCountMap.get( domainEntryPoint ) == beforeDomainDataCountMap.get( domainEntryPoint ) + countDelta );
        }
    }
}
