

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.SchemaHelperCache;
import gov.va.med.cds.xml.schema.SchemaHelperInterface;

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

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;


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

    private Map<String, String> entryPointForElementMap;

    private SchemaHelperCache schemaHelperCache;


    public void setSchemaHelperCache( SchemaHelperCache schemaHelperCache )
    {
        this.schemaHelperCache = schemaHelperCache;
    }


    public void setEntryPointForElementMap( Map<String, String> entryPointForElementMap )
    {
        this.entryPointForElementMap = entryPointForElementMap;
    }


    @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 )
    {
        SchemaHelperInterface schemaHelper = null;

        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 );
        }

        schemaHelper = schemaHelperCache.getSchemaHelper( templateId );

        if ( schemaHelper == null )
        {

            schemaHelper = new SchemaHelper( getBlobJar( templateId ), templateId );

            schemaHelperCache.putSchemaHelper( templateId, schemaHelper );

        }

        Map<String, List<String>> elementMap = schemaHelper.getSymbolMap();
        Element rootElement = sourceDoc.getRootElement();

        List<String> elementOrder = null;

        if ( entryPointForElementMap.get( templateId ) == null )
        {
            if ( elementMap.get( "CLINICALDATA.CLINICALDATA" ) != null )
            {
                elementOrder = elementMap.get( "CLINICALDATA.CLINICALDATA" );
            }
        }
        else
        {
            elementOrder = elementMap.get( entryPointForElementMap.get( templateId ) );
        }

        reorderElementsInElement( rootElement, 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 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;
    }

}
