/**
 * 
 */
package gov.va.med.cds.response;

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

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

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


/**
 * @author vhaislegberb
 *
 */
public class ParameterizedResponseAggregator
    implements
        ResponseAggregatorInterface
{
    
    private ClinicalDataResponseInterface clinicalDataResponse;
    
    private Map<String, List<AggregatorNodeSpecification>> aggregatedChildMap = new Hashtable<String, List<AggregatorNodeSpecification>>();
    
    
    public ParameterizedResponseAggregator( ClinicalDataResponseInterface clinicalDataResponse, Map<String, List<AggregatorNodeSpecification>> 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 );
            }
            
//this logic is wrong!if response is a single pathways document it won't build baseResponseDocument and change namespace from clinicaldata to a pathways namesapce document
//removed this if statement - all tests pass - if SQA doesn't expose any issues after testing 3.14 release July 2016, this block can be removed and brokentest in test class as well
            // 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(AggregatorNodeSpecification nodeSpec : aggregatedChildMap.get( childName ))
                        {
                            aggregatedElements = aggregated.get( childName );
                            if(aggregatedElements == null)
                            {
                                aggregatedElements = new ArrayList<Element>();
                                aggregated.put( childName, aggregatedElements );
                            }
                            
                            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 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 = null;
        if ( clinicalDataResponse instanceof ConfigurablePathwaysDataResponse )
        {

            response = ( ( ConfigurablePathwaysDataResponse )this.clinicalDataResponse ).buildEmptyPathwaysDocumentWithPatient( templateId, requestId );
        }
        else
        {
            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;
    }
}
