

package gov.va.med.cds.response.sequencer;


import gov.va.med.cds.clinicaldata.TemplateMetaDataInterface;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.template.TemplateManager;
import gov.va.med.cds.xml.schema.SchemaHelper;
import gov.va.med.cds.xml.schema.SchemaHelperInterface;

import java.io.StringReader;
import java.sql.Blob;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Validator;

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


public class OutpatientPharmacyResponseSchemaSequencer
    implements
        ResponseSequencerInterface
{
    private TemplateManager templateManager;
    private final Map<String, Blob> TEMPLATE_SCHEMA_BLOB_CACHE = new Hashtable<String, Blob>();


    @Override
    public Document sequence( String templateId, String sourceXml, boolean validateReadResponse )
    {
        if ( sourceXml == null || sourceXml.isEmpty() )
        {
            throw new SequencerException( ErrorCodeEnum.SCHEMA_VALIDATION_FAILED, "Source document is null" );
        }

        Document sourceDoc = null;
        try
        {
            sourceDoc = DocumentHelper.parseText( sourceXml );
        }
        catch ( DocumentException dex )
        {
            throw new SequencerException( ErrorCodeEnum.SCHEMA_VALIDATION_FAILED, dex, dex.getMessage() );
        }

        return sequence( templateId, sourceDoc, validateReadResponse );
    }


    @Override
    public Document sequence( String templateId, Document sourceDoc, boolean validateReadResponse )
    {
        if ( templateId == null || templateId.isEmpty() )
        {
            throw new SequencerException( ErrorCodeEnum.READ_REQUEST_TEMPLATE_ID_NULL );
        }

        if ( sourceDoc == null || !sourceDoc.hasContent() )
        {
            throw new SequencerException( ErrorCodeEnum.SCHEMA_XML_NULL );
        }

        SchemaHelperInterface schemaHelper = new SchemaHelper( getBlobJar( templateId ), templateId );
        Map<String, List<String>> elementMap = schemaHelper.getSymbolMap();
        Element rootElement = sourceDoc.getRootElement();
        Element rootElementUpdated = updateRootElement( rootElement );
        List<String> elementOrder = elementMap.get( "CLINICALDATA.CLINICALDATA" );
        reorderElementsInElement( rootElementUpdated, elementOrder, elementMap );

        if ( validateReadResponse )
        {
            try
            {
                Validator validator = templateManager.getTemplateValidator( templateId );
                validator.validate( new StreamSource( new StringReader( sourceDoc.asXML() ) ) );
            }
            catch ( Exception e )
            {
                throw new SequencerException( ErrorCodeEnum.SCHEMA_VALIDATION_FAILED, e, e.getMessage() );
            }
        }

        return sourceDoc;
    }


    private Element updateRootElement( Element rootElement )
    {
        List<Element> outpatientMedicationPromisesList = rootElement.selectNodes( "//outpatientMedicationPromises" );
        for ( Element outpatientMedicationElement : outpatientMedicationPromisesList )
        {
            // Handle treatmentFactor
            List<Element> treatmentFactorList = outpatientMedicationElement.selectNodes( "treatmentFactors/treatmentFactor" );
            for ( Element element : treatmentFactorList )
            {
                element.detach();
                outpatientMedicationElement.add( element );
            }
            Element treatmentFactors = ( Element )outpatientMedicationElement.selectSingleNode( "treatmentFactors" );
            if ( treatmentFactors != null )
                treatmentFactors.detach();

            // Handle cancel/comments
            List<Element> cancelCommentsList = outpatientMedicationElement.selectNodes( "comments" );
            Element cancelElement = ( Element )outpatientMedicationElement.selectSingleNode( "cancel" );
            for ( Element element : cancelCommentsList )
            {
                if ( cancelElement != null )
                {
                    element.detach();
                    cancelElement.add( element );
                }
            }
        }
        return rootElement;
    }


    private void reorderElementsInElement( Element element, List<String> elementOrder, Map<String, List<String>> elementMap )
    {
        List<Element> orderedElementList = new ArrayList<Element>();
        for ( String xsdElementDetail : elementOrder )
        {
            int indexOfSlash = xsdElementDetail.indexOf( '/' );

            String elementName = xsdElementDetail.substring( 0, indexOfSlash );
            String elementType = xsdElementDetail.substring( indexOfSlash + 1 );

            for ( Element e : ( List<Element> )element.elements( elementName ) )
            {
                if ( elementType.contains( "." ) )
                {
                    reorderElementsInElement( e, elementMap.get( elementType ), elementMap );
                }
                orderedElementList.add( e );
            }
        }

        element.setContent( orderedElementList );
    }


    private Blob getBlobJar( String templateId )
    {
        Blob schemaJar = TEMPLATE_SCHEMA_BLOB_CACHE.get( templateId );

        if ( schemaJar == null )
        {
            try
            {
                TemplateMetaDataInterface templateMetaData = templateManager.getTemplateMetaData( templateId );
                schemaJar = new javax.sql.rowset.serial.SerialBlob( templateMetaData.getTemplateJar() );
                TEMPLATE_SCHEMA_BLOB_CACHE.put( templateId, schemaJar );
            }
            catch ( Exception e )
            {
                throw new SequencerException( ErrorCodeEnum.READ_REQUEST_TEMPLATE_ID_INVALID, templateId, e.getMessage() );
            }

        }

        return schemaJar;
    }


    @Resource
    public void setTemplateManager( TemplateManager templateManager )
    {
        this.templateManager = templateManager;
    }

}
