/**
 * 
 */


package gov.va.med.cds.internal;


import java.util.ArrayList;
import java.util.Collection;
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 gov.va.med.cds.clinicaldata.Operation;
import gov.va.med.cds.clinicaldata.TemplateMetaDataInterface;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.filter.CdsFilterFactoryInterface;
import gov.va.med.cds.filter.CdsFilterInterface;
import gov.va.med.cds.filter.FilterPatientResolverInterface;
import gov.va.med.cds.request.ValidationException;
import gov.va.med.cds.request.WriteRequestInterface;
import gov.va.med.cds.response.WriteResponseGeneratorInterface;
import gov.va.med.cds.template.TemplateHelperInterface;
import gov.va.med.cds.template.TemplateManager;
import gov.va.med.cds.template.TemplateWriteRequest;
import gov.va.med.cds.tfs.util.TemplateMetaDataHelper;
import gov.va.med.cds.transaction.TransactionManagerInterface;
import gov.va.med.cds.util.NestedElementWrapperUtil;


/**
 * @author susarlan
 *
 */
public class ClinicalDataServiceSynchronousInternal
    implements
        ClinicalDataServiceSynchronousInternalInterface
{
    private Log logger = LogFactory.getLog( ClinicalDataServiceSynchronousInternal.class );
    private static final String START_TAG = "<";
    private static final String END_TAG = "</";
    
    private TransactionManagerInterface transactionManager;
    private CdsFilterFactoryInterface filterFactory;
    private FilterPatientResolverInterface filterPatientResolver;
    private TemplateManager templateManager;
    private TemplateHelperInterface templateHelper;
    private boolean addPatientIdentifiers = true;
    private String cdsAppName;
    private Map<String, String> namespacesMap;
    private NestedElementWrapperUtil nestedElementWrapperUtil;
    private Map<String, List<String>> emptyElementListMap;


    public List<Document> readClinicalData( String filterRequest, String filterId, String templateId, String requestId )
    {
        if ( logger.isDebugEnabled() )
        {
            logger.debug( "Processing read request - filterid: " + filterId + ", templateId: " + templateId + ", requestId: " + requestId );
        }

        if ( filterRequest == null || filterRequest.length() == 0 )
        {
            throw new ValidationException( ErrorCodeEnum.READ_REQUEST_FILTER_XML_NULL );
        }

        // Parse the input filter request XML
        Document filterRequestDocument = this.getDocument( filterRequest );
        if ( isPatientCentricRequest( filterRequestDocument ) )
        {
            filterRequestDocument = filterPatientResolver.resolveIdentifiersInFilter( filterId, filterRequestDocument );
        }

        CdsFilterInterface cdsFilter = filterFactory.create( filterId, filterRequestDocument, templateId, requestId );

        validateReadRequest( templateId, requestId, cdsFilter );

        List<Document> results = transactionManager.performReadOnClinicalData( templateId, requestId, cdsFilter );

        //adding filter patient identifier data into results to be aggregated into response if needed (siteCentric filter will have only one entryPointFilter
        if ( addPatientIdentifiers && !cdsFilter.getEntryPointFilters().get(0).isSiteCentricFilter())
        {
            Document patientIdentifierDocument = templateHelper.getPatientIdentifierDocumentBuilder( templateId )
                            .createPatientWithIdentifiersDocument( cdsFilter, templateId, requestId );
            results.add( 0, patientIdentifierDocument );
        }

        return results;
    }


    @SuppressWarnings( "unused" )
    private void validateReadRequest( String templateId, String requestId, CdsFilterInterface cdsFilter )
    {
        TemplateMetaDataInterface templateMetaData = templateManager.validateReadTemplateId( templateId );
        String vhimVersion = ( cdsFilter != null ) ? cdsFilter.getVhimVersion() : null;
        Collection<String> templateEntryPoints = TemplateMetaDataHelper.getEntryPoints( templateMetaData.getDomainEntryPoints() );
        Collection<String> domainEntryPoints = cdsFilter.getDomainEntryPoints();

        // TODO: We may need additional validation of whether CDS can process the incoming filter
        // request and return the specified template (templateId) [BJE]

        // Ensure templateEntryPoints is a subset of domainEntryPoints
        for ( String domainEntryPoint : domainEntryPoints )
        {
            if ( !templateEntryPoints.contains( domainEntryPoint.trim() ) )
            {
                throw new ValidationException( ErrorCodeEnum.REQUEST_TYPE_MISMATCH_TEMPLATE_ENTRYPOINT_FILTER_ENTRYPOINT, new String[] {
                                templateEntryPoints.toString(), domainEntryPoints.toString() } );
            }
        }
    }


    public Document createClinicalData( String createRequest, String templateId, String requestId )
    {
        return doCUAD( Operation.Create, createRequest, templateId, requestId );
    }


    public Document updateClinicalData( String updateRequest, String templateId, String requestId )
    {
        return doCUAD( Operation.Update, updateRequest, templateId, requestId );
    }


    public Document deleteClinicalData( String deleteRequest, String templateId, String requestId )
    {
        return doCUAD( Operation.Delete, deleteRequest, templateId, requestId );
    }


    public Document appendClinicalData( String appendRequest, String templateId, String requestId )
    {
        return doCUAD( Operation.Append, appendRequest, templateId, requestId );
    }

    public List<Document> writeClinicalData( String createRequest, String templateId, String requestId )
    {
    	return doVistaCreate( Operation.Create, createRequest, templateId, requestId );
    }
    
    public Document doCUAD( Operation operation, String clinicalData, String templateId, String requestId )
    {
        
    	Document writeResultDocument = null;
        Document clinicalDataDocument = null;
        /*     
         * Using stored procedures to handle segments updates        
        boolean isCensusCreate = (Operation.Create.equals(operation) && templateId.contains("Census")) ? true : false;
    	boolean hasBadSegment = false;
    	CensusMasterIngest censusMasterIngest = new CensusMasterIngest();
    	*/    	        
    	WriteResponseGeneratorInterface writeResponseGenerator = templateHelper.getWriteResponseGenerator( templateId );

        try
        {
            if ( logger.isDebugEnabled() )
            {
                logger.debug( "Processing request " + operation + " for - templateId: " + templateId + ", requestId: " + requestId + ", request: "
                                + clinicalData );
            }
			/*     
			 * Using stored procedures to handle segments updates        
            if( isCensusCreate){
            	// if bad segment is found, it will throw PersistenceException so that the catch will register the error and generate the error response.
            	censusMasterIngest.setTemplateId(templateId);
            	censusMasterIngest.setIngestStatus(CensusSurveyConstant.INGEST_FAIL);
            	hasBadSegment =  hasBadSegmentOrDuplicate(censusMasterIngest, clinicalData);
        		if( hasBadSegment ){
        			CensusException ce = new CensusException("Census segment may be duplicate or bad segment was found with vendor: " 
        					+ censusMasterIngest.getVendorName() + ", Datetime: " + censusMasterIngest.getSegmentDatetime());
        			return writeResponseGenerator.generateWriteErrorResponse( templateId, requestId, ce, clinicalDataDocument, cdsAppName );
        		}
        		pupulateSegmentInformation(censusMasterIngest, clinicalData);
        	}
			 */        	
            validateCUADRequest( clinicalData, templateId, requestId, operation );

			clinicalData = removeUnwantedNameSpaces( clinicalData, templateId );

			clinicalDataDocument = DocumentHelper.parseText( clinicalData );

			clinicalDataDocument = nestedElementWrapperUtil.addNestedElements( clinicalDataDocument, templateId );
				
			clinicalDataDocument = removeEmptyElements( clinicalDataDocument, templateId );
        }
        catch ( Exception e )
        {
        	 writeResultDocument = writeResponseGenerator.generateWriteErrorResponse( templateId, requestId, e, clinicalDataDocument, cdsAppName );
 			/*     
 			 * Using stored procedures to handle segments updates        
        	 if ( isCensusCreate)
        	 {
        		 
        		 // will populate necessary data to aCensusMetaData for updating census_master_ingest and census_master_segment tables.
        		censusMasterIngest.setIngestStatus(CensusSurveyConstant.INGEST_FAIL); 
        		censusMasterIngest.setCurrentSegmentProcessStatus(CensusSurveyConstant.STATUS_VALIDATION_FAIL);
        		transactionManager.performCUADOnCensusMetaData(Operation.Create, censusMasterIngest);
        	 }
        	 */
        	 return writeResultDocument;
        }
		try
		{	
            List<Element> createdRecordIdentifiers = transactionManager.performCUADOnClinicalData( operation, clinicalDataDocument, templateId,
                            requestId );

            writeResultDocument = writeResponseGenerator.generateSuccessfulWriteResponse( templateId, requestId, createdRecordIdentifiers );

        } 
		catch ( Exception e )
		{
        	// since persist Census master and census segment table will be done under same transaction as persist census data. 
        	// so if persist census data failed, then persist census master and census segment failed as well
            writeResultDocument = writeResponseGenerator.generateWriteErrorResponse( templateId, requestId, e, clinicalDataDocument, cdsAppName );
        }
		
        return writeResultDocument;
    }

    private List<Document> doVistaCreate( Operation operation, String clinicalData, String templateId, String requestId )
    {
        
    	Document writeResultDocument = null;
        Document clinicalDataDocument = null;
        List<Document> results = null;
          	        
    	WriteResponseGeneratorInterface writeResponseGenerator = templateHelper.getWriteResponseGenerator( templateId );

        try
        {
            if ( logger.isDebugEnabled() )
            {
                logger.debug( "Processing request " + operation + " for - templateId: " + templateId + ", requestId: " + requestId + ", request: "
                                + clinicalData );
            }
      	
            validateCUADRequest( clinicalData, templateId, requestId, operation );

			clinicalData = removeUnwantedNameSpaces( clinicalData, templateId );

			clinicalDataDocument = DocumentHelper.parseText( clinicalData );

			clinicalDataDocument = nestedElementWrapperUtil.addNestedElements( clinicalDataDocument, templateId );
				
			clinicalDataDocument = removeEmptyElements( clinicalDataDocument, templateId );
        }
        catch ( Exception e )
        {
        	 writeResultDocument = writeResponseGenerator.generateWriteErrorResponse( templateId, requestId, e, clinicalDataDocument, cdsAppName );
 			 results = new ArrayList<Document>();
        	 results.add(writeResultDocument);
        	 return results;
        }
		try
		{	
	        results = transactionManager.performCUADVistAData( operation, clinicalDataDocument, templateId,
                    requestId );
        } 
		catch ( Exception e )
		{
            writeResultDocument = writeResponseGenerator.generateWriteErrorResponse( templateId, requestId, e, clinicalDataDocument, cdsAppName );
            if(results == null)
            {
            	results = new ArrayList<Document>();
            }
       	 	results.add(writeResultDocument);       	
        }
		
        return results;
    }


    private void validateCUADRequest( String clinicalData, String templateId, String requestId, Operation operation )
    {
        if ( operation == null )
        {
            // "The method {0} is not supported." 
            throw new ValidationException( ErrorCodeEnum.OPERATION_NOT_SUPPORTED, "null" );
        }

        WriteRequestInterface cuadRequest = new TemplateWriteRequest( clinicalData, operation );
        cuadRequest.setRequestId( requestId );
        cuadRequest.setTemplateId( templateId );

        templateManager.validateWriteRequest( cuadRequest );
    }


    private Document getDocument( String filterRequest )
    {
        // Parse the input filter request XML
        try
        {
            return DocumentHelper.parseText( filterRequest );
        }
        catch ( DocumentException e )
        {
            throw new ValidationException( ErrorCodeEnum.FILTER_PARSER_DOM_EXCEPTION, e, filterRequest, e.getMessage() );
        }
    }


    private boolean isPatientCentricRequest( Document filterRequestDocument )
    {
        Element rootElement = filterRequestDocument.getRootElement();
        Element entryPointFilterElement = rootElement.element( "entryPointFilter" );
        String isPatientCentricStr = entryPointFilterElement.attributeValue( "isPatientCentric" );
        if( isPatientCentricStr == null ) {
            return true;
        }
        
        return isPatientCentricStr != null && isPatientCentricStr.length() > 0 && Boolean.parseBoolean( isPatientCentricStr ) == Boolean.TRUE;
    }
    
    private String removeUnwantedNameSpaces( String clinicalData, String templateId )
    {
        String namespaceValue = null;
        if ( namespacesMap.containsKey( templateId ) )
        {
            namespaceValue = namespacesMap.get( templateId );
            clinicalData = clinicalData.replace( START_TAG + namespaceValue, START_TAG );
            clinicalData = clinicalData.replace( END_TAG + namespaceValue, END_TAG );
        }
        
        return clinicalData;
    }

    
    @SuppressWarnings( "unchecked" )
    private Document removeEmptyElements( Document clinicalDataDocument, String templateId ) throws DocumentException
    {
        List<String> emptyElementList = emptyElementListMap.get( templateId );
        
        if ( emptyElementList != null )
        {
            for ( String xpath : emptyElementList )
            {
                List<Element> elements = clinicalDataDocument.selectNodes( xpath );
                if ( elements != null && elements.size() > 0 )
                {
                    for ( Element element : elements )
                    {
                        if ( element.getText() == null || element.getText().equals( "" ) )
                        {
                            element.detach();
                        }
                    }
                }
            }
        }
        
        return clinicalDataDocument;
    }

    
    public void setTransactionManager( TransactionManagerInterface transactionManager )
    {
        this.transactionManager = transactionManager;
    }


    public void setFilterFactory( CdsFilterFactoryInterface filterFactory )
    {
        this.filterFactory = filterFactory;
    }


    public void setFilterPatientResolver( FilterPatientResolverInterface filterPatientResolver )
    {
        this.filterPatientResolver = filterPatientResolver;
    }


    public void setTemplateManager( TemplateManager templateManager )
    {
        this.templateManager = templateManager;
    }


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


    public void setAddPatientIdentifiers( boolean addPatientIdentifiers )
    {
        this.addPatientIdentifiers = addPatientIdentifiers;
    }


    public void setCdsAppName( String cdsAppName )
    {
        this.cdsAppName = cdsAppName;
    }


    public void setNamespacesMap( Map<String, String> namespacesMap )
    {
        this.namespacesMap = namespacesMap;
    }
    
    
    public void setNestedElementWrapperUtil( NestedElementWrapperUtil nestedElementWrapperUtil )
    {
        this.nestedElementWrapperUtil = nestedElementWrapperUtil;
    }
    

    public void setEmptyElementListMap( Map<String, List<String>> emptyElementListMap )
    {
        this.emptyElementListMap = emptyElementListMap;
    }
    
    /*
    private boolean hasBadSegmentOrDuplicate(CensusMasterIngest censusMasterIngest, String clinicalData) throws Exception {
    	// find vendor name
    	
    	int beginIndex = clinicalData.indexOf("<vendor>");
    	beginIndex = clinicalData.indexOf("<name>", beginIndex);
		int endIndex = clinicalData.indexOf("</name>", beginIndex);
		String vendorName = clinicalData.substring(beginIndex+6, endIndex);
		vendorName = vendorName.trim();
		censusMasterIngest.setVendorName(vendorName);
    	// Find report start date
    	beginIndex = clinicalData.indexOf("<datetime>");
    	endIndex = clinicalData.indexOf("</datetime>", beginIndex);
    	    		
    	String datetime = clinicalData.substring(beginIndex+10, endIndex);
    	
    	censusMasterIngest.setSegmentDatetime(datetime);
        	
    	try {
			List<CensusMasterIngest> results = transactionManager.performReadOnCensusMetaData(censusMasterIngest);
			if( results.size() > 0){
				for(Iterator<CensusMasterIngest> it= results.iterator(); it.hasNext(); ){
					CensusMasterIngest em = it.next();
					String name= em.getIngestStatus();	
					if( !name.equals("PENDING"))
						return true;
				}
				
			}
		} catch (Exception e) {
			CensusException ce = new CensusException("Query Census master and Segment table Exception: " + e.getMessage());
			throw ce;
		}
    	return false;
    }
    
    private void pupulateSegmentInformation(CensusMasterIngest censusMasterIngest, String clinicalData)throws Exception{
    	    	
    	int beginIndex = clinicalData.indexOf("<datetime>");
    	int endIndex = clinicalData.indexOf("</datetime>", beginIndex);
    	if( beginIndex == -1 || endIndex == -1)
    		throw new IndexOutOfBoundsException();
    	
    	String segmentDatetime = clinicalData.substring(beginIndex+10, endIndex);
    	censusMasterIngest.setSegmentDatetime(segmentDatetime);
    	
    	beginIndex = clinicalData.indexOf("<current>", endIndex);
    	endIndex = clinicalData.indexOf("</current>", beginIndex);
    	
    	if( beginIndex == -1 || endIndex == -1)
    		throw new IndexOutOfBoundsException();
    	
    	String currentSegment = clinicalData.substring(beginIndex+9, endIndex);
    	currentSegment = currentSegment.trim();
        censusMasterIngest.setCurrentSegmentNo(new Integer(currentSegment));
        
        beginIndex = clinicalData.indexOf("<last>", endIndex);
        endIndex = clinicalData.indexOf("</last>", beginIndex);
        if( beginIndex == -1 || endIndex == -1)
    		throw new IndexOutOfBoundsException();
    	
        String lastSegment = clinicalData.substring(beginIndex+6, endIndex);
        censusMasterIngest.setCensusExpectedCount(new Integer(lastSegment));
    	
    	beginIndex = clinicalData.indexOf("<number>", endIndex);
    	endIndex = clinicalData.indexOf("</number>", beginIndex);
    	
    	if( beginIndex == -1 || endIndex == -1)
    		throw new IndexOutOfBoundsException();
    	
    	String vendorFacility = clinicalData.substring(beginIndex+8, endIndex);
    	censusMasterIngest.setVendorFacility(vendorFacility);
    	
    	beginIndex = clinicalData.indexOf("<reportStartDate>", endIndex);
    	endIndex = clinicalData.indexOf("</reportStartDate>", beginIndex);
    	if( beginIndex == -1 || endIndex == -1)
    		throw new IndexOutOfBoundsException();
    	
    	String reportStartDate = clinicalData.substring(beginIndex+17, endIndex);
    	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    	Date reportStartDt = sdf.parse(reportStartDate);
    	censusMasterIngest.setReportStartDatetime(reportStartDt);
    	
    }*/
}
