

package gov.va.med.cds.request;


import gov.va.med.cds.clinicaldata.DomainEntryPoint;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.exceptionframework.ExceptionHandler;
import gov.va.med.cds.filter.FilterValidatorInterface;
import gov.va.med.cds.internal.ClinicalDataServiceSynchronousInternalInterface;
import gov.va.med.cds.template.TemplateHelperInterface;

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

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.springframework.util.StringUtils;


/**
 * 
 * Performs the CRUAD operations based on the client request. See
 * gov.va.med.cds.config.requestProcessorContext.xml for Spring DI details.
 * 
 */
public class TemplateRequestProcessor
    implements
        RequestProcessorInterface,
        ResponseValidatorMBeanInterface
{
    private static final int MAX_TEMPLATE_ID_LENGTH = 128;
    private static final int MAX_CUAD_REQUEST_LENGTH = 1000000;
    private static final int MAX_FILTER_REQUEST_LENGTH = 10000;
    private static final int MAX_FILTER_ID_LENGTH = 128;
    private static final int MAX_REQUEST_ID_LENGTH = 256;
    
    private static final String TEMPLATE_ID_PARAMETER_NAME = "TemplateId ";
    private static final String CUAD_REQUEST_PARAMETER_NAME = "CUADRequestXML ";
    private static final String FILTER_REQUEST_PARAMETER_NAME = "FilterRequestXML ";
    private static final String FILTER_ID_PARAMETER_NAME = "FilterId ";
    private static final String REQUEST_ID_PARAMETER_NAME = "RequestId ";
    
    private ClinicalDataServiceSynchronousInternalInterface delegatingClinicalDataServiceSynchronousInternal = null;
    private TemplateHelperInterface templateHelper;
    private FilterValidatorInterface filterManager;
    private boolean validateReadResponseXML = false;
    private List<String> vistaCreateTemplateIdsList;


    /**
     * 
     * @see gov.va.med.cds.request.RequestProcessorInterface#readClinicalData(java.lang.String,
     *      java.lang.String, java.lang.String, java.lang.String)
     */
    /**
     * Read data
     */
    public Document readClinicalData( String aTemplateId, String aFilterRequest, String aFilterId, String aRequestId )
    {
        Document responseDocument = null;
        
        try
        {
            checkForNullReadParameters( aTemplateId, aFilterId, aFilterRequest, aRequestId );
            checkReadParameterLengths( aTemplateId, aFilterId, aFilterRequest, aRequestId );

            responseDocument = DocumentHelper.createDocument();

            filterManager.validateFilterXml( aFilterId, aFilterRequest );

            List<Document> readResponses = delegatingClinicalDataServiceSynchronousInternal.readClinicalData( aFilterRequest, aFilterId, aTemplateId, aRequestId );
            responseDocument = templateHelper.getResponseAggregator( aTemplateId ).aggregateResponses( readResponses );
            responseDocument = templateHelper.getResponseSequencer( aTemplateId ).sequence( aTemplateId, responseDocument, validateReadResponseXML );
            responseDocument = ( templateHelper.getResponseOrganizer( aTemplateId ) != null )
                    ? templateHelper.getResponseOrganizer( aTemplateId ).organize( aFilterRequest, responseDocument )
                    : responseDocument;
        }
        catch ( Exception e )
        {
            responseDocument = processException( e, aTemplateId, aFilterRequest, aFilterId, aRequestId,
                    this.templateHelper.getApplicationName( aTemplateId ) );
        }

        return responseDocument;
    }


    private void checkForNullReadParameters( String aTemplateId, String aFilterId, String aFilterRequest, String aRequestId )
    {
        StringBuffer nullParams = new StringBuffer();

        if ( !StringUtils.hasText( aTemplateId ) )
        {
            nullParams.append( TEMPLATE_ID_PARAMETER_NAME );
        }

        if ( !StringUtils.hasText( aFilterId ) )
        {
            nullParams.append( FILTER_ID_PARAMETER_NAME );
        }

        if ( !StringUtils.hasText( aFilterRequest ) )
        {
            nullParams.append( FILTER_REQUEST_PARAMETER_NAME );
        }

        if ( !StringUtils.hasText( aRequestId ) )
        {
            nullParams.append( REQUEST_ID_PARAMETER_NAME );
        }

        if ( nullParams.length() > 0 )
        {
            throw new ValidationException( ErrorCodeEnum.READ_REQUEST_INPUT_PARAMETERS_NULL, nullParams.toString() );
        }

    }


    private void checkForNullCUADParameters( String aCUADRequest, String aTemplateId, String aRequestId )
    {
        StringBuffer nullParams = new StringBuffer();

        if ( !StringUtils.hasText( aCUADRequest ) )
        {
            nullParams.append( CUAD_REQUEST_PARAMETER_NAME );
        }

        if ( !StringUtils.hasText( aTemplateId ) )
        {
            nullParams.append( TEMPLATE_ID_PARAMETER_NAME );
        }

        if ( !StringUtils.hasText( aRequestId ) )
        {
            nullParams.append( REQUEST_ID_PARAMETER_NAME );
        }

        if ( nullParams.length() > 0 )
        {
            throw new ValidationException( ErrorCodeEnum.WRITE_REQUEST_INPUT_PARAMETERS_NULL, nullParams.toString() );
        }
    }


    private void checkReadParameterLengths( String aTemplateId, String aFilterId, String aFilterRequest, String aRequestId )
    {
        StringBuffer nullParams = new StringBuffer();

        if ( aTemplateId.length() > MAX_TEMPLATE_ID_LENGTH ) 
        {
            nullParams.append( TEMPLATE_ID_PARAMETER_NAME );
        }

        if ( aFilterId.length() > MAX_FILTER_ID_LENGTH ) 
        {
            nullParams.append( FILTER_ID_PARAMETER_NAME );
        }

        if ( aFilterRequest.length() > MAX_FILTER_REQUEST_LENGTH ) 
        {
            nullParams.append( FILTER_REQUEST_PARAMETER_NAME );
        }

        if ( aRequestId.length() > MAX_REQUEST_ID_LENGTH )
        {
            nullParams.append( REQUEST_ID_PARAMETER_NAME );
        }

        if ( nullParams.length() > 0 )
        {
            throw new ValidationException( ErrorCodeEnum.READ_REQUEST_PARAMETERS_LENGTH_INVALID, nullParams.toString() );
        }
    }


    private void checkCUADParameterLengths( String aCUADRequest, String aTemplateId, String aRequestId )
    {
        StringBuffer nullParams = new StringBuffer();

        if ( aCUADRequest.length() > MAX_CUAD_REQUEST_LENGTH ) 
        {
            nullParams.append( CUAD_REQUEST_PARAMETER_NAME );
        }

        if ( aTemplateId.length() > MAX_TEMPLATE_ID_LENGTH ) 
        {
            nullParams.append( TEMPLATE_ID_PARAMETER_NAME );
        }

        if ( aRequestId.length() > MAX_REQUEST_ID_LENGTH )
        {
            nullParams.append( REQUEST_ID_PARAMETER_NAME );
        }

        if ( nullParams.length() > 0 )
        {
            throw new ValidationException( ErrorCodeEnum.WRITE_REQUEST_PARAMETERS_LENGTH_INVALID, nullParams.toString() );
        }
    }


    private Document processException( Exception anException, String aTemplateId, String aRequestId, String anAppName )
    {
        Document errorResponseDocument = null;
        
        try
        {
            errorResponseDocument = ExceptionHandler.handleException( anException, aTemplateId, aRequestId, anAppName );
        }
        catch ( Exception ex )
        {
            // ignore this top level exception since it was already logged and
            // we want to send info back to the
            // client
        }
        
        return errorResponseDocument;
    }


    private Document processException( Exception anException, String aTemplateId, String aFilterRequest, String aFilterId, String aRequestId, String anAppName )
    {
        Document errorResponseDocument = null;
        
        try
        {
            errorResponseDocument = ExceptionHandler.handleException( anException, aTemplateId, aFilterRequest, aFilterId, aRequestId, anAppName );
        }
        catch ( Exception ex )
        {
            // ignore this top level exception since it was already logged and
            // we want to send info back to the
            // client
        }
        
        return errorResponseDocument;
    }


    /**
     * 
     * @see gov.va.med.cds.request.RequestProcessorInterface#createClinicalData(java.lang.String,
     *      java.lang.String, java.lang.String)
     */
    public Document createClinicalData( String aCreateRequest, String aTemplateId, String aRequestId )
    {
        Document writeResponse = null;
        
        try
        {
            checkForNullCUADParameters( aCreateRequest, aTemplateId, aRequestId );
            checkCUADParameterLengths( aCreateRequest, aTemplateId, aRequestId );
            
            if ( vistaCreateTemplateIdsList.contains( aTemplateId ) )
            {
                writeResponse = writeClinicalData( aCreateRequest, aTemplateId, aRequestId );
            }
            else
            {

                writeResponse = delegatingClinicalDataServiceSynchronousInternal.createClinicalData( aCreateRequest, aTemplateId, aRequestId );
            }
        }
        catch ( Exception e )
        {
            writeResponse = processException( e, aTemplateId, aRequestId, this.templateHelper.getApplicationName( aTemplateId ) );
        }
        
        return writeResponse;
    }


    private Document writeClinicalData( String aWritRequest, String aTemplateId, String aRequestId )
    {
        Document responseDocument = null;

        responseDocument = DocumentHelper.createDocument();

        List<Document> createResponses = delegatingClinicalDataServiceSynchronousInternal.writeClinicalData( aWritRequest, aTemplateId, aRequestId );
        responseDocument = templateHelper.getResponseAggregator( aTemplateId ).aggregateResponses( createResponses );

        return responseDocument;
    }


    /**
     * Create or Update. This method is a place holder method for work that
     * could be done to handle a create or update where we don't know the order
     * of records received. This method would need to resolve if the call was a
     * create or an update and if the data was the most recent data. Currently
     * this logic is being done in the database by triggers.
     * 
     * @param aCreateOrUpdateRequest
     *            request xml
     * @param aTemplateId
     *            template id
     * @param aRequestId
     *            request id
     * @return cds response object
     */
    public Document createOrUpdateClinicalData( String aCreateOrUpdateRequest, String aTemplateId, String aRequestId )
    {
        Document writeResponse = null;
        
        try
        {
            checkForNullCUADParameters( aCreateOrUpdateRequest, aTemplateId, aRequestId );
            checkCUADParameterLengths( aCreateOrUpdateRequest, aTemplateId, aRequestId );

            writeResponse = delegatingClinicalDataServiceSynchronousInternal.createClinicalData( aCreateOrUpdateRequest, aTemplateId, aRequestId );
        }
        catch ( Exception e )
        {
            writeResponse = processException( e, aTemplateId, aRequestId, this.templateHelper.getApplicationName( aTemplateId ) );
        }
        
        return writeResponse;
    }


    /**
     * 
     * @see gov.va.med.cds.request.RequestProcessorInterface#updateClinicalData(java.lang.String,
     *      java.lang.String, java.lang.String)
     */
    public Document updateClinicalData( String anUpdateRequest, String aTemplateId, String aRequestId )
    {
        Document writeResponse = null;
        
        try
        {
            checkForNullCUADParameters( anUpdateRequest, aTemplateId, aRequestId );
            checkCUADParameterLengths( anUpdateRequest, aTemplateId, aRequestId );
            
            writeResponse = delegatingClinicalDataServiceSynchronousInternal.updateClinicalData( anUpdateRequest, aTemplateId, aRequestId );
        }
        catch ( Exception e )
        {
            writeResponse = processException( e, aTemplateId, aRequestId, this.templateHelper.getApplicationName( aTemplateId ) );
        }
        
        return writeResponse;
    }


    /**
     * 
     * @see gov.va.med.cds.request.RequestProcessorInterface#appendClinicalData(java.lang.String,
     *      java.lang.String, java.lang.String)
     */
    public Document appendClinicalData( String anAppendRequest, String aTemplateId, String aRequestId )
    {
        Document writeResponse = null;
        
        try
        {
            checkForNullCUADParameters( anAppendRequest, aTemplateId, aRequestId );
            checkCUADParameterLengths( anAppendRequest, aTemplateId, aRequestId );
            
            writeResponse = delegatingClinicalDataServiceSynchronousInternal.appendClinicalData( anAppendRequest, aTemplateId, aRequestId );
        }
        catch ( Exception e )
        {
            writeResponse = processException( e, aTemplateId, aRequestId, this.templateHelper.getApplicationName( aTemplateId ) );
        }
        
        return writeResponse;
    }


    /**
     * 
     * @see gov.va.med.cds.request.RequestProcessorInterface#deleteClinicalData(java.lang.String,
     *      java.lang.String, java.lang.String)
     */
    public Document deleteClinicalData( String aDeleteRequest, String aTemplateId, String aRequestId )
    {
        Document writeResponse = null;
        
        try
        {
            checkForNullCUADParameters( aDeleteRequest, aTemplateId, aRequestId );
            checkCUADParameterLengths( aDeleteRequest, aTemplateId, aRequestId );
            
            writeResponse = delegatingClinicalDataServiceSynchronousInternal.deleteClinicalData( aDeleteRequest, aTemplateId, aRequestId );
        }
        catch ( Exception e )
        {
            writeResponse = processException( e, aTemplateId, aRequestId, this.templateHelper.getApplicationName( aTemplateId ) );
        }
        
        return writeResponse;
    }


    public void setFilterManager( FilterValidatorInterface aFilterManager )
    {
        this.filterManager = aFilterManager;
    }


    public void setDelegatingClinicalDataServiceSynchronousInternal(
            ClinicalDataServiceSynchronousInternalInterface aDelegatingClinicalDataServiceSynchronousInternal )
    {
        this.delegatingClinicalDataServiceSynchronousInternal = aDelegatingClinicalDataServiceSynchronousInternal;
    }


    public void setTemplateHelper( TemplateHelperInterface aTemplateHelper )
    {
        this.templateHelper = aTemplateHelper;
    }


    public boolean isValidateReadResponseXML( )
    {
        return validateReadResponseXML;
    }


    public void setValidateReadResponseXML( boolean aValidateReadResponseXML )
    {
        this.validateReadResponseXML = aValidateReadResponseXML;
    }


    public void setVistaCreateTemplateIdsList( List<String> aVistaCreateTemplateIdsList )
    {
        this.vistaCreateTemplateIdsList = aVistaCreateTemplateIdsList;
    }

}
