/**
 * 
 */


package gov.va.med.cds.response;


import gov.va.med.cds.clinicaldata.vhim400.ClinicalDataResponseInterface;
import gov.va.med.cds.exception.ErrorCodeEnum;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;


/**
 * @author DNS   egberb
 *
 */
public class ParameterizedLabResponseAggregator
    implements
        ResponseAggregatorInterface
{

    private ClinicalDataResponseInterface clinicalDataResponse;

    private Map<String, List<LabAggregatorNodeSpecification>> aggregatedChildMap = new Hashtable<String, List<LabAggregatorNodeSpecification>>();


    public ParameterizedLabResponseAggregator( ClinicalDataResponseInterface clinicalDataResponse, Map<String, List<LabAggregatorNodeSpecification>> aggregatedChildMap )
    {
        this.clinicalDataResponse = clinicalDataResponse;
        this.aggregatedChildMap = aggregatedChildMap;
    }


    @SuppressWarnings( "unchecked" )
    @Override
    public Document aggregateResponses( List<Document> readResponseDocuments )
        throws ResponseAggregatorException
    {
        try
        {
            // If the list of read results to aggregate is null or empty, throw an exception
            // becuase we shouldn't be in here.
            if ( readResponseDocuments == null || readResponseDocuments.isEmpty() )
            {
                throw new ResponseAggregatorException( ErrorCodeEnum.XML_AGGREGATION_ZERO_INPUT_SOURCES );
            }

            // If the list of results only includes one document, there is no need to aggregate; 
            // just return the document.
            if ( readResponseDocuments.size() == 1 )
            {
                return readResponseDocuments.get( 0 );
            }

            Document aggregatedResponseDocument = buildBaseResponseDocument( readResponseDocuments );
            Map<String, List<Element>> aggregated = new Hashtable<String, List<Element>>();
            List<Element> aggregatedElements = null;

            // for each read response document
            for ( Document responseDocument : readResponseDocuments )
            {
                // iterate over the list of elements that have been defined as 
                // aggregate-able in the spring context file.
                for ( String childName : aggregatedChildMap.keySet() )
                {
                    // use the clinical data response object to get the child to 
                    // aggregate from the document
                    Element childToAggregate = clinicalDataResponse.getChildElement( responseDocument, childName );

                    if ( childToAggregate != null )
                    {
                        // for each of the elements defined in the map, aggregate the nodes
                        for ( LabAggregatorNodeSpecification nodeSpec : aggregatedChildMap.get( childName ) )
                        {
                            aggregatedElements = aggregated.get( childName );
                            if ( aggregatedElements == null )
                            {
                                aggregatedElements = new ArrayList<Element>();
                                aggregated.put( childName, aggregatedElements );
                            }

                            if ( nodeSpec.isModifyNode() )
                            {
                                modifyNode( childToAggregate, nodeSpec );
                            }

                            List<Element> children = childToAggregate.elements( nodeSpec.getNodeName() );

                            for ( Element child : children )
                            {
                                addToElements( aggregatedElements, child, nodeSpec.consolodate() );
                            }
                        }
                    }
                }
            }

            // Merge the aggregated Elements into the response document
            for ( String childName : aggregated.keySet() )
            {
                Element parent = clinicalDataResponse.getChildElement( aggregatedResponseDocument, childName, true );

                for ( Element child : aggregated.get( childName ) )
                {
                    parent.add( child );
                }
            }

            return aggregatedResponseDocument;
        }
        catch ( Throwable t )
        {
            throw new ResponseAggregatorException( ErrorCodeEnum.XML_AGGREGATION_FAILED, t );
        }
    }


    private void modifyNode( Element childToAggregate, LabAggregatorNodeSpecification nodeSpec )
    {

        String childToAggregateXML = childToAggregate.asXML();

        Map<String, String> nodeChangeList = nodeSpec.getFromNodetoNode();

        List<Node> labTestPromisesList = new ArrayList<Node>();

        for ( Object key : nodeChangeList.keySet() )
        {
            for ( Object tempElement : childToAggregate.selectNodes( String.valueOf( key ) ) )
            {
                labTestPromisesList.add( ( ( Node )tempElement ).detach() );
            }

            for ( Node labTestPromisesNode : labTestPromisesList )
            {
                String labTestPromisesNodeXML = labTestPromisesNode.asXML();

                String labTestsOuterNodeXML = labTestPromisesNode.selectSingleNode( nodeChangeList.get( String.valueOf( key ) ) ).asXML();

                Node labTestOuterNode = labTestPromisesNode.selectSingleNode( nodeChangeList.get( String.valueOf( key ) ) ).detach();

                labTestPromisesNodeXML = labTestPromisesNode.asXML();

                for ( Object labTestInnerElement : ( ( Element )labTestOuterNode ).elements() )
                {
                    String labTestInnerElementXML = ( ( Element )labTestInnerElement ).asXML();

                    ( ( Element )labTestPromisesNode ).add( ( ( Element )labTestInnerElement ).detach() );
                }

            }

            for ( Node labTestsNode : labTestPromisesList )
            {
                childToAggregate.add( labTestsNode );
            }

        }

    }


    private Document buildBaseResponseDocument( List<Document> documents )
    {
        String templateId = null;
        String requestId = null;
        String nationalId = null;

        for ( Document d : documents )
        {
            // get the template id from the document. break out of look if template id, request id, and national have been found.
            if ( templateId == null || templateId.equals( this.clinicalDataResponse.getErrorResponseTemplateId() ) )
            {
                Element templateIdElement = this.clinicalDataResponse.getTemplateId( d );
                templateId = templateIdElement.getText();
                if ( templateId != null && !templateId.equals( clinicalDataResponse.getErrorResponseTemplateId() ) && requestId != null && nationalId != null )
                {
                    break;
                }
            }

            // get the request id from the document. break out of look if template id, request id, and national have been found.
            if ( requestId == null )
            {
                Element requestIdElement = clinicalDataResponse.getRequestId( d );
                if ( requestIdElement != null )
                {
                    requestId = requestIdElement.getText();
                }
                if ( templateId != null && !templateId.equals( clinicalDataResponse.getErrorResponseTemplateId() ) && requestId != null && nationalId != null )
                {
                    break;
                }
            }

            // TODO: Figure out a way to make this happen through aggregation.
            // get the national id from the document. break out of look if template id, request id, and national have been found.
            if ( nationalId == null )
            {
                Element requestedNationalIdElement = clinicalDataResponse.getRequestedNationalId( d );
                if ( requestedNationalIdElement != null )
                {
                    nationalId = requestedNationalIdElement.getText();
                }
                if ( templateId != null && !templateId.equals( clinicalDataResponse.getErrorResponseTemplateId() ) && requestId != null && nationalId != null )
                {
                    break;
                }
            }

        }

        Document response = this.clinicalDataResponse.buildEmptyClinicalDocumentWithPatient( templateId, requestId );

        if ( nationalId != null )
        {
            this.clinicalDataResponse.addRequestedNationalId( response, nationalId );
        }

        return response;

    }


    @SuppressWarnings( "unchecked" )
    private void addToElements( List<Element> aggregated, Element element, boolean consolodate )
    {
        if ( consolodate )
        {
            Element consolodated = findElementByName( aggregated, element.getName() );
            if ( consolodated == null )
            {
                // nothing to consolodate to. use the existing element as 
                // the base for all future consolodations of this element type.
                aggregated.add( ( Element )element.detach() );
            }
            else
            {
                // consolodate the children of the incoming element to the 
                // element already found in the list.
                for ( Element child : ( List<Element> )element.elements() )
                {
                    consolodated.add( child.detach() );
                }
            }

        }
        else
        {
            aggregated.add( ( Element )element.detach() );
        }
    }


    private Element findElementByName( List<Element> elements, String name )
    {
        for ( Element e : elements )
        {
            if ( e.getName().equals( name ) )
            {
                return e;
            }
        }

        return null;
    }
}
