

package gov.va.med.cds.persistence.hibernate;


import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.filter.EntryFilterInterface;
import gov.va.med.cds.persistence.QueryAssociationInterface;
import gov.va.med.cds.persistence.QueryWorkInterface;

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

import java.util.List;
import java.util.Map;


public class LabHibernateDomModelAssembler
    extends
        HibernateDomModelAssembler
    implements
        ModelAssemblerInterface
{
    protected List<String> emptyLeafElementList;
    private Map<String, String> emptyElementsToBeRemovedMap;


    public void setEmptyLeafElementList( List<String> emptyLeafElementList )
    {
        this.emptyLeafElementList = emptyLeafElementList;
    }


    public void setEmptyElementsToBeRemovedMap( Map<String, String> emptyElementsToBeRemovedMap )
    {
        this.emptyElementsToBeRemovedMap = emptyElementsToBeRemovedMap;
    }


    @SuppressWarnings( "unchecked" )
    @Override
    public Document assembleModel( EntryFilterInterface entryFilter, List<QueryWorkInterface> queryWork )
    {
        Document clinicalDataDocument = clinicalDataResponse.buildEmptyClinicalDocumentWithPatient( entryFilter.getTemplateId(),
                        entryFilter.getRequestId() );
        Element clinicalRootElement = clinicalDataResponse.getClinicalRoot( clinicalDataDocument );

        // iterate over the query work assembling its results into the model
        for ( QueryWorkInterface work : queryWork )
        {
            QueryAssociationInterface queryAssociation = work.getQueryAssociation();
            String parentKey = queryAssociation.getParentKey();

            Element results = ( Element )work.getResults();
            if ( results != null && results.hasContent() )
            {
                // iterate over each result assembling it into the model
                for ( Element childElement : ( List<Element> )results.elements() )
                {
                    // build an xpath for locating the parent element of this result
                    String parentKeyValue = ( parentKey == null ) ? null : childElement.elementText( parentKey );
                 
                    String xpath = null;

                    if ( parentKeyValue != null )
                    {
                        // if its an association with an identifier, build the xpath with a predicate clause.
                        //                        xpath = String.format( "//%s[%s='%s']", queryAssociation.getAssociationParent(), queryAssociation.getKey(), parentKeyValue );
                        xpath = String.format( "%s/%s[%s='%s']", clinicalRootElement.getUniquePath(), queryAssociation.getAssociationParent(),
                                        queryAssociation.getKey(), parentKeyValue );
                    }
                    else
                    {
                        // if its an association without an identifier (i.e. patient -> entry_point), build the xpath without a predicate.
                        //                        xpath = String.format( "//%s", queryAssociation.getAssociationParent() );
                        xpath = clinicalRootElement.getUniquePath();
                    }

                    // select the parent element from the model using the xpath
                    List<Node> nodeList = clinicalRootElement.selectNodes( xpath );

                    if ( nodeList !=null && nodeList.size() == 0 )
                    {
                        break;
                    }

                    if ( nodeList !=null && nodeList.size() > 1 )
                    {
                        for ( Node node : nodeList )
                        {
                            if ( node != null && node instanceof Element )
                            {
                                Element parent = ( Element )node;
                                if ( queryAssociation.isCollapsable() )
                                {
                                    // check to see if the element already exists in the parent.
                                    Element collapseToElement = parent.element( childElement.getName() );
                                    if ( collapseToElement != null )
                                    {
                                        for ( Node n : ( ( List<Node> )childElement.elements() ) )
                                        {
                                            collapseToElement.add( ( Element )n.clone() );
                                        }

                                        childElement = collapseToElement;
                                    }
                                    else
                                    {
                                        parent.add( ( Element )childElement.clone() );
                                    }
                                }
                                else
                                {
                                    parent.add( ( Element )childElement.clone() );
                                }
                            }
                            else
                            {
                                // for some reason the query result cannot be assembled into the model.
                                throw new ModelAssemblerException( ErrorCodeEnum.MODEL_ASSEMBLER_EXCEPTION_NOT_ALL_RESULTS_ASSEMBLED,
                                                entryFilter.getDomainEntryPoint(), work.getQueryAssociation().getAssociationParent() );
                            }
                        }
                    }
                    else
                    {
                        Node node = null;

                        if ( null != nodeList )
                        {
                            node = nodeList.get( 0 );
                        }

                        if ( node != null && node instanceof Element )
                        {
                            Element parent = ( Element )node;
                            if ( queryAssociation.isCollapsable() )
                            {
                                // check to see if the element already exists in the parent.
                                Element collapseToElement = parent.element( childElement.getName() );
                                if ( collapseToElement != null )
                                {
                                    for ( Node n : ( ( List<Node> )childElement.elements() ) )
                                    {
                                        collapseToElement.add( n.detach() );
                                    }

                                    childElement = collapseToElement;
                                }
                                else
                                {
                                    parent.add( childElement.detach() );
                                }
                            }
                            else
                            {
                                parent.add( ( Element )childElement.detach() );
                            }
                        }
                        else
                        {
                            // for some reason the query result cannot be assembled into the model.
                            throw new ModelAssemblerException( ErrorCodeEnum.MODEL_ASSEMBLER_EXCEPTION_NOT_ALL_RESULTS_ASSEMBLED,
                                            entryFilter.getDomainEntryPoint(), work.getQueryAssociation().getAssociationParent() );
                        }
                    }

                }
            }
        }

        // finally, clean up the model by removing all of the extraneous 
        // id elements from it.
        removeElements( clinicalRootElement );

        // remove empty elements
        if ( emptyElementList != null )
        {
            removeEmptyElements( clinicalRootElement );
        }

        if ( emptyLeafElementList != null )
        {
            removeEmptyLeafElements( clinicalRootElement );
        }

        if ( emptyElementsToBeRemovedMap != null )
        {
            removeParentElement( clinicalRootElement );
        }

        return clinicalDataDocument;
    }


    private void removeEmptyLeafElements( Element parent )
    {
        for ( String xpath : emptyLeafElementList )
        {
            List<Element> elements = parent.selectNodes( xpath );
            if ( elements != null && elements.size() > 0 )
            {
                for ( Element element : elements )
                {
                    if ( ( element.getText() == null || element.getText().equals( "" ) ) && element.elements().size() == 0 )
                    {
                        element.detach();
                    }
                }
            }
        }
    }


    @SuppressWarnings( "unchecked" )
    private void removeParentElement( Element root )
    {
        for ( String key : emptyElementsToBeRemovedMap.keySet() )
        {
            int levelOfDepth = Integer.valueOf( emptyElementsToBeRemovedMap.get( key ) );

            List<Element> elements = root.selectNodes( key );
            if ( elements != null && elements.size() > 0 )
            {
                for ( Element element : elements )
                {
                    if ( ( element.getText() == null || element.getText().equals( "" ) ) && element.elements().size() == 0 )
                    {
                        for ( int i = 0; i < levelOfDepth; i++ )
                        {
                            element.getParent().detach();
                        }
                    }
                }
            }
        }
    }

}
