

package gov.va.med.cds.ars.request;

import static gov.va.med.cds.exceptionframework.LoggingSeverity.FATAL;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Map;

import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import gov.va.med.cds.ars.exception.ValidationException;
import gov.va.med.cds.ars.exceptionframework.ExceptionHandlerInterface;
import gov.va.med.cds.ars.filter.ReportFilterUnmarshallerInterface;
import gov.va.med.cds.ars.model.ModelMarshallerInterface;
import gov.va.med.cds.ars.persistence.ReadableReportsPersistenceManagerInterface;
import gov.va.med.cds.ars.requestresponse.generated.ErrorSectionType;
import gov.va.med.cds.ars.requestresponse.generated.ErrorType;
import gov.va.med.cds.ars.requestresponse.generated.HTReportFilterType;
import gov.va.med.cds.ars.requestresponse.generated.HTReportType;
import gov.va.med.cds.ars.requestresponse.generated.HTResponseType;
import gov.va.med.cds.ars.requestresponse.generated.ObjectFactory;
import gov.va.med.cds.ars.requestresponse.generated.ReportDateFilterType;
import gov.va.med.cds.ars.requestresponse.generated.ReportIDType;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.exceptionframework.ExceptionInfo;
import gov.va.med.cds.exceptionframework.LoggingSeverity;



import gov.va.med.cds.saml.SamlAssertionThreadLocal;


/**
 *
 * Performs the CRUAD operations based on the client request. See gov.va.med.cds.ars.config.requestProcessorContext.xml for
 * Spring DI details.
 *
 */
public class ReportRequestProcessor
    implements
        RequestProcessorInterface
{
	// Earliest Census start date 09/10/2005 
    //public static final GregorianCalendar CENSUS_EARLIEST_START_DATE = new GregorianCalendar(2005, 8, 10);
	
	
	// Earliest Census start date 10/02/2005
	//public static final long CENSUS_EARLIEST_START_TIME = new GregorianCalendar(2005, 10, 2).getTimeInMillis();
	public static final long CENSUS_EARLIEST_START_TIME = new GregorianCalendar(2005, 9, 2).getTimeInMillis();
	
    public static final long MILLIS_IN_ONE_DAY = 24*60*60*1000;

    protected static final Log LOGGER = LogFactory.getLog( ReportRequestProcessor.class );
    
    private ReportFilterUnmarshallerInterface filterUnmarshaller;
    private ModelMarshallerInterface modelMarshaller;
	private Map<String, ReadableReportsPersistenceManagerInterface> persistenceManagerMap;
    private ExceptionHandlerInterface exceptionHandler;

    private static final String APPLICATION_NAME = "ARS";


    /**
     *
     * @see gov.va.med.cds.ars.request.RequestProcessorInterface#readAggregateData(java.lang.String, java.lang.String,
     *      java.lang.String, java.lang.String)
     */
    /**
     * Read report data
     */
	public String readAggregateData(String aReportRequestFilterXml, String aReportId, String aClientId, String aRequestId)
	{
	    if ( LOGGER.isDebugEnabled() )
	    {
	        LOGGER.debug(APPLICATION_NAME + " readAggregateData:" + aReportRequestFilterXml);
	    }

        ObjectFactory reportFactory = new ObjectFactory();
        HTReportType htReport = reportFactory.createHTReportType();
        HTResponseType reportResponse = null;
        ReportIDType reportType = null;

        try
        {
            HTReportFilterType reportRequestFilter = null;
            ReadableReportsPersistenceManagerInterface persistenceManager;

            requireInputParameters( aReportRequestFilterXml, aReportId, aClientId, aRequestId );
			// TODO Make permanent FilterUnmarshaller and tafs enable ?
            reportRequestFilter = filterUnmarshaller.unMarshalReportFilter( aReportRequestFilterXml, aReportId );

            try
            {
				reportType = ReportIDType.fromValue(aReportId);
			}
            catch ( IllegalArgumentException e )
            {
            	throw new ValidationException( ErrorCodeEnum.ARS_READ_REQUEST_REPORT_ID_NULL );
			}

			// TODO Make permanent FilterUnmarshaller and tafs enable ?
            //reportRequestFilter = filterUnmarshaller.unMarshalReportFilter( aReportRequestFilterXml, aReportId );

            htReport.setRequest( reportRequestFilter );

            if ( reportRequestFilter == null )
            {
                throw new RuntimeException( "Invalid Report Filter (unmarshalling failed) " );
            }


        	ReportDateFilterType dateFilter = null;
			switch ( reportType ) {
			
				case VR_12_STATISTICS:
					dateFilter = reportRequestFilter.getVR12ReportFilter();
					break;
	
				case DISTRIBUTION_OF_SURVEYS:
					dateFilter = reportRequestFilter.getDistributionReportFilter();
					break;
	
				case PATIENT_SATISFACTION_STATISTICS:
					dateFilter = reportRequestFilter.getPatientSatisfactionReportFilter();
					break;
	
	            case SURVEY_TRENDS:
	                dateFilter = reportRequestFilter.getSurveyTrendReportFilter();
	                break;
	
	            case DMP_UNIQUE_ICN_REPORT:
	                dateFilter = reportRequestFilter.getDMPUniqueICNReportFilter();
	                break;
	           
	            case CENSUS_WEEKLY_VENDOR_STATUS_REPORT:
	                dateFilter = reportRequestFilter.getCensusWeeklyVendorReportStatusFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	            
	            case CENSUS_POST_PROCESSING_STATUS_REPORT:
	                dateFilter = reportRequestFilter.getCensusPostProcessingStatusReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case CENSUS_TOTAL_REPORT:
	                dateFilter = reportRequestFilter.getCensusPatientCountReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case CENSUS_DMPTOTAL_REPORT:
	                dateFilter = reportRequestFilter.getCensusDMPPatientCountReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case CENSUS_MEDDEVTOTAL_REPORT:
	                dateFilter = reportRequestFilter.getCensusMedicalDevicePatientCountReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case CENSUS_IVRTOTAL_REPORT:
	                dateFilter = reportRequestFilter.getCensusIVRPatientCountReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
		        case CENSUS_BROWSERTOTAL_REPORT:
		            dateFilter = reportRequestFilter.getCensusBrowserPatientCountReportFilter();
		            validateStartDateAndEndDate(dateFilter);
		            break;
	                
	            case CENSUS_NOTASSIGNEDTOTAL_REPORT:
	                dateFilter = reportRequestFilter.getCensusNotAssignedPatientCountReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case CENSUS_MEASUREMENTTOTAL_REPORT:
	                dateFilter = reportRequestFilter.getCensusMeasurementPatientCountReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case CENSUS_VIDEOTOTAL_REPORT:
	                dateFilter = reportRequestFilter.getCensusVideoPatientCountReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case CENSUS_CELLMODEMTOTAL_REPORT:
	                dateFilter = reportRequestFilter.getCensusCellPatientCountReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case CENSUS_SATELLITETOTAL_REPORT:
	                dateFilter = reportRequestFilter.getCensusSatellitePatientCountReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	            
	            case COC_TOTALPTNIC_REPORT:
	                dateFilter = reportRequestFilter.getCoCTotalPtNICReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case COC_TOTALPTCCM_REPORT:
	                dateFilter = reportRequestFilter.getCoCTotalPtCCMReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case COC_TOTALPTACM_REPORT:
	                dateFilter = reportRequestFilter.getCoCTotalPtACMReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case COC_TOTALPTHPDP_REPORT:
	                dateFilter = reportRequestFilter.getCoCTotalPtHPDPReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	               
	            case COC_TOTALPTL_2_REPORT:
	                dateFilter = reportRequestFilter.getCoCTotalPtL2ReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break; 
	                
	            case RR_TOTALFULLRESP_REPORT:
	                dateFilter = reportRequestFilter.getRRTotalFullRespReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case RR_TOTALPARTRESP_REPORT:
	                dateFilter = reportRequestFilter.getRRTotalPartRespReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case RR_TOTALNONRESP_REPORT:
	                dateFilter = reportRequestFilter.getRRTotalNonRespReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	                
	            case RR_TOTALUNKRESP_REPORT:
	                dateFilter = reportRequestFilter.getRRTotalUnkRespReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	               
	            case RR_TOTALNONRESPGT_30_REPORT:
	                dateFilter = reportRequestFilter.getRRTotalNonRespGT30ReportFilter();
	                validateStartDateAndEndDate(dateFilter);
	                break;
	               
	            case RR_TOTALNONRESPMAX_REPORT:
	                dateFilter = reportRequestFilter.getRRTotalNonRespMaxReportFilter();
	                validateStartDateAndEndDate(dateFilter);

	                
	            default:
					break;
			}

    		if( dateFilter != null){	
    			checkForRequiredDates( dateFilter );
    		}
    		
            persistenceManager = persistenceManagerMap.get( aReportId );
            if ( persistenceManager != null )
            {
                reportResponse = persistenceManager.performReadReportAggregate( reportRequestFilter );
            }
            else
            {
                throw new RuntimeException( "No persistence manager found for report: " + aReportId );
            }
        }
        catch ( RuntimeException e )
        {
            try
            {
            	exceptionHandler.handleException( e, aReportId, aRequestId, aClientId+aRequestId, APPLICATION_NAME );
            }
            catch ( RuntimeException ex )
            {
                // ignore this top level exception since it was already logged and we want to send info back to the client
            }
        }

        htReport.setErrorSection( populateErrorSection( aRequestId, aClientId ) );
        htReport.setRequestID( aRequestId );
        htReport.setResponse( reportResponse );

        return modelMarshaller.marshalToXml( htReport );
    }


	private void checkForRequiredDates( ReportDateFilterType reportRequestFilter )
	{
		if ( reportRequestFilter.getStartDate() == null || reportRequestFilter.getEndDate() == null )
		{
			throw new RuntimeException( "Start and End Dates are required for this report type: " + reportRequestFilter.getReportID().value() );
		}
	}

	/**
     * populate the error section of the template with a list of error objects built from the exceptions in the
     * exception handler
     *
     * @param aRequestId - unique request Id provided by the client
     * @param aClientId - unique client Id provided by the client
     */
    private ErrorSectionType populateErrorSection( String aRequestId, String aClientId )
    {
    	ErrorSectionType errorSection = new ErrorSectionType();

        LoggingSeverity loggingSeverity = null;
        ErrorType errorType = null;

        // this should be a list of real exception info objects, not just 3 lists of exceptions
        for ( ExceptionInfo exceptionInfo : exceptionHandler.getExceptionInfoList(aClientId+aRequestId) )
        {
        	errorType = new ErrorType();
            errorType.setDisplayMessage( exceptionInfo.getClientMessage() );
            Throwable t = exceptionInfo.getException();
            if ( t != null )
            {
                errorType.setException( exceptionInfo.getException().getClass().getName() );
            }
            errorType.setExceptionMessage( exceptionInfo.getExceptionMessage() );
            if ( exceptionInfo.getErrorCode() != null )
            {
                errorType.setErrorCode( exceptionInfo.getErrorCode().name() );
            }
            errorType.setErrorId( aRequestId );
            loggingSeverity = exceptionInfo.getLoggingSeverity();
            if ( loggingSeverity == null )
            {
                // TODO after cleaning tests we may want to remove this
                loggingSeverity = FATAL;
                exceptionInfo.setLoggingSeverity( FATAL );
            }

            switch ( loggingSeverity )
            {
            case WARNING:
                errorSection.getWarnings().add( errorType );
                break;
            case ERROR:
                errorSection.getErrors().add( errorType );
                break;
            case FATAL:
            default:
                errorSection.getFatalErrors().add( errorType );
                break;
            }
        }
        exceptionHandler.clear(aClientId+aRequestId);

        return errorSection;
    }

    private void requireInputParameters(String aReportRequestFilterXml,
			String aReportId, String aClientId, String aRequestId)
    {
        if ( ( aReportRequestFilterXml == null ) || ( aReportRequestFilterXml.length() == 0 ) )
        {
            throw new ValidationException( ErrorCodeEnum.READ_REQUEST_FILTER_XML_NULL );
        }

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

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

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

    public void setExceptionHandler( ExceptionHandlerInterface exceptionHandler )
    {
        this.exceptionHandler = exceptionHandler;
    }

    public void setFilterUnmarshaller(ReportFilterUnmarshallerInterface filterUnmarshaller)
    {
		this.filterUnmarshaller = filterUnmarshaller;
	}

    public void setModelMarshaller(ModelMarshallerInterface modelMarshaller)
    {
		this.modelMarshaller = modelMarshaller;
	}

    public void setPersistenceManagerMap( Map<String, ReadableReportsPersistenceManagerInterface> persistenceManagerMap )
    {
        this.persistenceManagerMap = persistenceManagerMap;
    }
    
    protected void validateStartDateAndEndDate(ReportDateFilterType dateFilter) throws ValidationException{
		
    	
    	XMLGregorianCalendar xmlStartDate = dateFilter.getStartDate();
    	XMLGregorianCalendar xmlEndDate = dateFilter.getEndDate();
    	    	
    	GregorianCalendar startDate = dateFilter.getStartDate().toGregorianCalendar();
    	
    	/*
		if( Calendar.SUNDAY != startDate.get(Calendar.DAY_OF_WEEK) ){
			// start date is not Sunday
			throw new ValidationException(ErrorCodeEnum.CENSUS_REPORT_INVALID_START_DATE, xmlStartDate.toString());	
		}
		if ( startDate.getTimeInMillis() < CENSUS_EARLIEST_START_DATE.getTimeInMillis()){
			// start date precedes 09/10/2005
			throw new ValidationException(ErrorCodeEnum.CENSUS_REPORT_INVALID_START_DATE, xmlStartDate.toString());	
		}*/
    	
    	GregorianCalendar currentDate = new GregorianCalendar();
        long currentTime = currentDate.getTimeInMillis() - MILLIS_IN_ONE_DAY; 
        long startTime = startDate.getTimeInMillis();

    	
        if ( ( Calendar.SUNDAY != startDate.get( Calendar.DAY_OF_WEEK ) ) ||
                ( startTime < CENSUS_EARLIEST_START_TIME ) ||
                ( startTime > currentTime ) )
           {
                  // start date precedes 10/02/2005 or start date exceeds current date
                  throw new ValidationException(ErrorCodeEnum.CENSUS_REPORT_INVALID_START_DATE, xmlStartDate.toString());  
           }
           
		GregorianCalendar endDate = xmlEndDate.toGregorianCalendar();
		long endTime = endDate.getTimeInMillis();
		
		/*
		if( Calendar.SATURDAY != endDate.get(Calendar.DAY_OF_WEEK) ){
			// end date is not Saturday;
			throw new ValidationException(ErrorCodeEnum.CENSUS_REPORT_INVALID_END_DATE , xmlEndDate.toString());
		}
		
		
		//if( 6 != ((endDate.getTimeInMillis()-startDate.getTimeInMillis())/MILLIS_IN_ONE_DAY ) )
		if( 6 != (int)Math.ceil((double)(endDate.getTimeInMillis()-startDate.getTimeInMillis())/MILLIS_IN_ONE_DAY ) )	
		{
			// end day is not the following Saturday of start date
			throw new ValidationException(ErrorCodeEnum.CENSUS_REPORT_INVALID_TIME_PERIOD, xmlEndDate.toString(), xmlStartDate.toString());
		}
				
		GregorianCalendar now = new GregorianCalendar();
		now.setTimeInMillis(now.getTimeInMillis()-MILLIS_IN_ONE_DAY);	// substract 1 day
		
		if( endDate.getTimeInMillis() > now.getTimeInMillis()){
			// end date exceeds current date
			throw new ValidationException(ErrorCodeEnum.CENSUS_REPORT_INVALID_END_DATE, xmlEndDate.toString());
		}
		
		*/
		
        if ( ( Calendar.SATURDAY != endDate.get( Calendar.DAY_OF_WEEK ) ) ||
                ( endTime > currentTime ) )
           {
                  // end date is not Saturday;
                  throw new ValidationException( ErrorCodeEnum.CENSUS_REPORT_INVALID_END_DATE , xmlEndDate.toString() );
           }
           
      //  if ( 6 != (int)Math.ceil( (double)( endTime - startTime ) / MILLIS_IN_ONE_DAY ) ) 
        if ( 6 != (int)Math.round( (double)( endTime - startTime ) / MILLIS_IN_ONE_DAY ) ) 
        {
               // end day is not the following Saturday of start date
               throw new ValidationException(ErrorCodeEnum.CENSUS_REPORT_INVALID_TIME_PERIOD, xmlEndDate.toString(), xmlStartDate.toString());
        }

		return;
    }
    
}
