/**
 * 
 */


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


import gov.va.med.cds.clinicaldata.DomainEntryPoint;
import gov.va.med.cds.clinicaldata.vhim400.ClinicalDataResponse400;
import gov.va.med.cds.common.person.correlation.PersonIdentifierInterface;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.filter.EntryFilterInterface;
import gov.va.med.cds.persistence.PersistenceException;
import gov.va.med.cds.persistence.QueryStrategyInterface;
import gov.va.med.cds.persistence.QueryWorkInterface;
import gov.va.med.cds.persistence.ReadPersistenceManagerInterface;
import gov.va.med.cds.rules.BooleanRuleInterface;
import gov.va.med.cds.template.TemplateHelperInterface;
import gov.va.med.cds.template.generated.JaxBMarshallerUnmarshallerInterface;
import gov.va.med.cds.transaction.WorkManagerInterface;
import gov.va.med.cds.util.TimeoutUtil;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import gov.va.med.cds.saml.SamlAssertionThreadLocal;

import commonj.work.WorkItem;

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


public abstract class AbstractReadHibernatePersistenceManager
    extends
        AbstractHibernatePersistenceManager
    implements
        ReadPersistenceManagerInterface
{
    protected static final Log LOGGER = LogFactory.getLog( AbstractReadHibernatePersistenceManager.class );
    private static final String HDR = "HDRII-R";

    TemplateHelperInterface templateHelper;

    private List<BooleanRuleInterface> rules;

    protected Map<String, QueryStrategyInterface> templateQueryStrategyMap;

    protected WorkManagerInterface queryThreadWorkManager;

    protected Map<String, String> templateTimeoutMap = null;

    protected Class<?> pointInTimeUserType;

    protected String siteIdentifier = null;

    protected List<String> vistaExcludedTemplateIdsList;

    protected List<String> hdrExcludedTemplateIdsList;

    protected ClinicalDataResponse400 defaultCDSResponse;
    
   
	public boolean isApplicable( EntryFilterInterface entryFilter, List<PersonIdentifierInterface> personIdentifiers )
        throws PersistenceException
    {
        boolean isApplicable = false;
        DomainEntryPoint domainEntryPoint = DomainEntryPoint.valueOf( entryFilter.getDomainEntryPoint() );

        for ( BooleanRuleInterface r : rules )
        {
            isApplicable = ( r.evaluate( domainEntryPoint ) );
            if ( isApplicable )
            {
                break;
            }
        }

        return isApplicable;
    }


    protected QueryStrategyInterface getQueryStrategyFromMap( EntryFilterInterface entryFilter )
    {
        QueryStrategyInterface qs = this.templateQueryStrategyMap.get( String.format( "%s-%s-%s", entryFilter.getTemplateId(), entryFilter.getQueryName(), entryFilter.getDomainEntryPoint() ) );
        if ( qs == null )
        {
            qs = this.templateQueryStrategyMap.get( String.format( "%s-%s", entryFilter.getTemplateId(), entryFilter.getDomainEntryPoint() ) );
        }
        return qs;
    }


    @Override
    public Document performReadOnClinicalData( EntryFilterInterface entryFilter, List<PersonIdentifierInterface> personIdentifiers, String applicationName )
        throws PersistenceException
    {
        Document result = null;
        String personIdentity = null;

        /**
         * Use Patient information from personIdentifiers to create response XML with <Error> section in case of exception during READ 
         */
        if ( personIdentifiers != null )
        {
            // Find the Patient associated with the VistA Site
            if ( this.siteIdentifier != null ) //siteIdentifier is null for HDR queries
            {
                for ( PersonIdentifierInterface personIdentifier : personIdentifiers )
                {
                    if ( personIdentifier.getAssigningFacility().equals( this.siteIdentifier ) )
                    {
                        personIdentity = personIdentifier.getIdentity();
                        break;
                    }
                }
            }
        }

        try
        {
            QueryStrategyInterface queryStrategy = getQueryStrategyFromMap( entryFilter );

            if ( queryStrategy != null )
            {
                // Create and execute the query work.
                List<QueryWorkInterface> queryWork = createQueryWork( queryStrategy, entryFilter, personIdentifiers, pointInTimeUserType, siteIdentifier, marshallerUnmarshaller );
                executeQueryWork( queryWork, entryFilter, personIdentity, applicationName  );

                // Assemble the model from the query pieces.
                ModelAssemblerInterface modelAssembler = queryStrategy.getModelAssembler();
                result = modelAssembler.assembleModel( entryFilter, queryWork );
            }
            else
            {
                //TODO - replace this line if ( this.managerIdentifier.equals( HDR ) )
                if ( this.managerIdentifier.contains( HDR ) )
                {
                    result = chkExcludedTemplateIdsList( hdrExcludedTemplateIdsList, entryFilter, result );
                }
                else
                {
                    result = chkExcludedTemplateIdsList( vistaExcludedTemplateIdsList, entryFilter, result );
                }

            }
        }
        catch ( PersistenceException pe )
        {
            throw pe;
        }
        catch ( Exception e )
        {
            String msg = e.getMessage();
            if ( e.getCause() != null )
                msg = String.format( "%s%s%s", e.getMessage(), ": ", e.getCause().getMessage() );
           
            throw new PersistenceException( ErrorCodeEnum.READ_REQUEST_DATA_SOURCE_FAILURE, this.siteIdentifier, personIdentity, entryFilter.getDomainEntryPoint(), this.managerIdentifier, msg );
        }

        return result;
    }


    protected Document chkExcludedTemplateIdsList( List<String> excludedTemplateIdsList, EntryFilterInterface entryFilter, Document result )
    {
        if ( !excludedTemplateIdsList.contains( String.format( "%s-%s", entryFilter.getTemplateId(), entryFilter.getDomainEntryPoint() ) ) )
        {
            throw new PersistenceException( ErrorCodeEnum.READ_QUERY_STRATEGY_NULL_FAILURE, entryFilter.getTemplateId(), entryFilter.getDomainEntryPoint() );
        }
        else
        {
            result = this.defaultCDSResponse.buildEmptyClinicalDocument( entryFilter.getTemplateId(), entryFilter.getRequestId() );
        }
        return result;
    }


    protected abstract List<QueryWorkInterface> createQueryWork( QueryStrategyInterface queryStrategy, EntryFilterInterface entryFilter, List<PersonIdentifierInterface> personIdentifiers,
                    Class<?> pointInTimeUserType, String siteId, JaxBMarshallerUnmarshallerInterface marshallerUnmarshaller )
        throws PersistenceException;


    /**
     * Executes the query work provided as a parameter concurrently.
     * 
     * @param queryWork The query work to execute.
     * @param entryFilter The entry filter used in case of exceptions.
     * @param personIdentity The person identity used in case of exceptions.
     */
    protected void executeQueryWork( List<QueryWorkInterface> queryWork, EntryFilterInterface entryFilter, String personIdentity, String applicationName )
        throws Exception
    {
        List<WorkItem> work = new ArrayList<WorkItem>();

        if ( queryWork != null && queryWork.size() > 0 )
        {
            for ( QueryWorkInterface w : queryWork )
            {
                try
                {
                    work.add( this.queryThreadWorkManager.schedule( w ) );
                }
                catch ( Exception e )
                {
                    w.addException( e, applicationName );
                }
            }

            try
            {

                long timeout = TimeoutUtil.processTimeout( templateTimeoutMap, entryFilter, applicationName ) * 1000;

                // true if all WorkItems have completed, false if the timeout has expired. 
                boolean timedOut = this.queryThreadWorkManager.waitForAll( work, timeout );

                if ( !timedOut )
                {
                    throw new PersistenceException( ErrorCodeEnum.READ_REQUEST_DATA_SOURCE_TIMEOUT, this.siteIdentifier, personIdentity, entryFilter.getDomainEntryPoint(), this.managerIdentifier,
                                    ": The query timed out with timeout value of: " + timeout + " milliseconds" );
                }
            }
            catch ( Exception e )
            {
                LOGGER.error( "Timeout Exception while querying for: " + entryFilter.getDomainEntryPoint() );
                // A timeout exception will NOT result in each work item containing an exception.  Must
                // stop execution here.
                throw e;
            }
            finally
            {
                // The sessions must be closed here since they are created
                // using SessionFactory.openSession. The transaction manager
                // will not release these sessions.
                for ( QueryWorkInterface qw : queryWork )
                {
                    //force the release of connections by closing the hibernate session...
                    qw.release();
                }
            }

            // If there are any errors with any of the query work, throw an exception.
            for ( QueryWorkInterface w : queryWork )
            {
                if ( w.getExceptions().size() > 0 )
                {
                    // Report the first QueryWork exception message
                    Exception ex = w.getExceptions().get( 0 );
                    String rootCauseMessage = ExceptionUtils.getRootCause( ex ) == null ? ex.getMessage() : ExceptionUtils.getRootCause( ex ).getMessage();

                    throw new PersistenceException( ErrorCodeEnum.READ_REQUEST_DATA_SOURCE_FAILURE, ex, this.siteIdentifier, personIdentity, entryFilter.getDomainEntryPoint(), this.managerIdentifier,
                                    String.format( "%s%s%s", ErrorCodeEnum.READ_REQUEST_DATA_SOURCE_FAILURE.name(), ": ", rootCauseMessage ) );
                }
            }
        }
    }


    /**
     * Sets the rules that govern the behavior of the isApplicable method.
     * 
     * @param rules The rules to evaluate.
     */
    public void setRules( List<BooleanRuleInterface> rules )
    {
        this.rules = rules;
    }


    /**
     * 
     * @param templateQueryStrategyMap
     */
    public void setTemplateQueryStrategyMap( Map<String, QueryStrategyInterface> templateQueryStrategyMap )
    {
        this.templateQueryStrategyMap = templateQueryStrategyMap;
    }


    public void setQueryThreadWorkManager( WorkManagerInterface queryThreadWorkManager )
    {
        this.queryThreadWorkManager = queryThreadWorkManager;
    }


    public void setPointInTimeUserType( Class<?> pointInTimeUserType )
        throws ClassNotFoundException
    {
        this.pointInTimeUserType = pointInTimeUserType;
    }


    /**
     * The site identifier for this persistence manager applies.
     * 
     * @param siteIdentifier The site identifier.
     */
    public void setSiteIdentifier( String siteIdentifier )
    {
        this.siteIdentifier = siteIdentifier;
    }


    public String getSiteIdentifier( )
    {
        return siteIdentifier;
    }


    public void setTemplateTimeoutMap( Map<String, String> templateTimeoutMap )
    {
        this.templateTimeoutMap = templateTimeoutMap;
    }


    public void setVistaExcludedTemplateIdsList( List<String> vistaExcludedTemplateIdsList )
    {
        this.vistaExcludedTemplateIdsList = vistaExcludedTemplateIdsList;
    }


    public void setHdrExcludedTemplateIdsList( List<String> hdrExcludedTemplateIdsList )
    {
        this.hdrExcludedTemplateIdsList = hdrExcludedTemplateIdsList;
    }


    public void setDefaultCDSResponse( ClinicalDataResponse400 defaultCDSResponse )
    {
        this.defaultCDSResponse = defaultCDSResponse;
    }

}
