

package gov.va.med.cds.filter;


import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.internal.ClinicalDataServiceSynchronousInternalInterface;
import gov.va.med.cds.request.ValidationException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;


/***
 * MultipleEntryPointFilterHelper helps convert filter with multiple clinical data domains and multiple entry points, into filters of single clinical data domain 
 *  
 */
public class MultipleEntryPointFilterHelper
{
    private final static String XPATH_DOMAIN_ENTRY_POINT = "/filter:filter/entryPointFilter//domainEntryPoint";
    private Log logger = LogFactory.getLog( MultipleEntryPointFilterHelper.class );

    // Contains Filter Id , List of domain entry points for that Filter
    private Map<String, List<String>> multipleEntryPointFilterId;

    // Maps Domain Entry point of Filter to read result Template Id  
    private Map<String, String> filterDomainEntryPointToTemplateId;

    // Maps Filter Id to List containing Entry Points  
    private Map<String, List<ClinicalDataServiceSynchronousInternalInterface>> filterEntryPointDelegateMap;

    
    public String getTemplateId( String domainEntryPoint )
    {
        return filterDomainEntryPointToTemplateId.get( domainEntryPoint );
    }


    /***
     * 
     * @param afilterRequest is the multiple clinical data domain filter
     * @param aFilterId is the filter Id of the filter
     * @return a list of single clinical data domain filters
     */
    @SuppressWarnings( "unchecked" )
    public List<SingleEntryPointFilterInfo> convertMultipleEntryPointFilterToSingleEntryPointFilter( String afilterRequest, String aFilterId )
    {
        Document filterDocument = getDocument( afilterRequest );

        List<SingleEntryPointFilterInfo> singleEntryPointFilterInfoList = new ArrayList<SingleEntryPointFilterInfo>();

        // Get the list of domain entry points
        List<String> entryPoints = multipleEntryPointFilterId.get( aFilterId );

        Element rootElement = filterDocument.getRootElement();

        List<Node> array[] = new LinkedList[entryPoints.size()];

        for ( int loopCounter = 0; loopCounter < entryPoints.size(); )
        {
            array[loopCounter] = new LinkedList<Node>();
            loopCounter++ ;
        }

        // Create a list of domain entry points for each clinical data domain
        for ( int loopCounter = 0; loopCounter < entryPoints.size(); loopCounter++ )
        {
            for ( Iterator<Element> i = rootElement.elementIterator(); i.hasNext(); )
            {
                Element element = ( Element )i.next();

                if ( element.getName().equals( "entryPointFilter" ) )
                {
                    for ( String entryPoint : entryPoints )
                    {
                        if ( element.selectSingleNode( XPATH_DOMAIN_ENTRY_POINT ).getText().equals( entryPoint ) )
                        {
                            array[loopCounter].add( element.detach() );
                            break;
                        }
                    }
                    break;
                }
            }
        }

        // create single clinical data domain filter
        for ( int loopCounter = 0; loopCounter < entryPoints.size(); loopCounter++ )
        {
            SingleEntryPointFilterInfo singleEntryPointFilterInfo = new SingleEntryPointFilterInfo();

            // Handles multiple domain filters without all domains   
            if ( array[loopCounter].size() == 0 )
            {
                continue;
            }

            filterDocument = createSingleEntryPointFilter( array[loopCounter], filterDocument, singleEntryPointFilterInfo );

            singleEntryPointFilterInfo.setSingleEntryPointFilter( prettyPrint( filterDocument ) );

            singleEntryPointFilterInfoList.add( singleEntryPointFilterInfo );

            filterDocument = removeNodesFromDocument( array[loopCounter], filterDocument );
        }

        return singleEntryPointFilterInfoList;
    }


    private String prettyPrint( Document aFilterDocument )
    {
        OutputFormat outputFormat = OutputFormat.createPrettyPrint();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        try
        {
            XMLWriter xmlWriter = new XMLWriter( byteArrayOutputStream, outputFormat );
            xmlWriter.write( aFilterDocument );
        }
        catch ( UnsupportedEncodingException e )
        {
            throw new ValidationException( ErrorCodeEnum.FILTER_PARSER_DOM_EXCEPTION );
        }
        catch ( IOException e )
        {
            throw new ValidationException( ErrorCodeEnum.FILTER_PARSER_DOM_EXCEPTION );
        }

        return byteArrayOutputStream.toString();
    }


    private Document createSingleEntryPointFilter( List<Node> aNodeList, Document aFilterDocument,
                    SingleEntryPointFilterInfo aSingleEntryPointFilterInfo )
    {

        for ( Node node : aNodeList )
        {
            aFilterDocument.getRootElement().add( node );
        }

        // save entry point info so that the filter need not be parsed to get it during READ call
        aSingleEntryPointFilterInfo.setDomainEntryPoint( aFilterDocument.selectSingleNode( XPATH_DOMAIN_ENTRY_POINT ).getText() );

        return aFilterDocument;
    }


    public List<ClinicalDataServiceSynchronousInternalInterface> getClinicalDataSynchronousServiceFromEntryPointFilterMap( String entryPoint )
    {
        if ( logger.isDebugEnabled() )
        {
            logger.debug( "Obtaining service delegates for entry point: " + entryPoint );
        }

        if ( filterEntryPointDelegateMap.get( entryPoint ) == null )
        {
            throw new ValidationException( ErrorCodeEnum.CANT_MATCH_TEMPLATE_TO_DELEGATE, entryPoint );
        }

        return filterEntryPointDelegateMap.get( entryPoint );
    }


    private Document removeNodesFromDocument( List<Node> aNodeList, Document aFilterDocument )
    {
        for ( Node node : aNodeList )
        {
            aFilterDocument.getRootElement().remove( node );
        }

        return aFilterDocument;
    }


    private String getEntryPoint( Document aFilterDocument )
    {
        return aFilterDocument.selectSingleNode( XPATH_DOMAIN_ENTRY_POINT ).getText();
    }


    public String getEntryPoint( String aFilterRequest )
    {
        return getEntryPoint( getDocument( aFilterRequest ) );
    }


    public Document getDocument( String aFilterRequest )
        throws ValidationException
    {
        if ( aFilterRequest == null || aFilterRequest.length() == 0 )
        {
            throw new IllegalArgumentException();
        }

        try
        {
            return DocumentHelper.parseText( aFilterRequest );
        }
        catch ( DocumentException e )
        {
            throw new ValidationException( ErrorCodeEnum.FILTER_PARSER_DOM_EXCEPTION );
        }
    }


    public void setLogger( Log logger )
    {
        this.logger = logger;
    }


    public void setMultipleEntryPointFilterId( Map<String, List<String>> multipleEntryPointFilterId )
    {
        this.multipleEntryPointFilterId = multipleEntryPointFilterId;
    }


    public void setFilterDomainEntryPointToTemplateId( Map<String, String> filterDomainEntryPointToTemplateId )
    {
        this.filterDomainEntryPointToTemplateId = filterDomainEntryPointToTemplateId;
    }


    public void setFilterEntryPointDelegateMap( Map<String, List<ClinicalDataServiceSynchronousInternalInterface>> filterEntryPointDelegateMap )
    {
        this.filterEntryPointDelegateMap = filterEntryPointDelegateMap;
    }

}
