/********************************************************************
 * Copyright © 2005 VHA. All rights reserved
 ********************************************************************/

package gov.va.med.esr.common.batchprocess;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.Transformer;
import org.apache.commons.lang.Validate;
import org.hibernate.ScrollableResults;

import gov.va.med.esr.common.model.ee.EGTProcessStatistic;
import gov.va.med.esr.common.model.ee.EGTSetting;
import gov.va.med.esr.common.model.ee.EGTSiteStatistic;
import gov.va.med.esr.common.model.lookup.EGTStatus;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.service.EGTService;
import gov.va.med.esr.service.LookupService;
import gov.va.med.fw.batchprocess.AbstractDataQueryProcess;
import gov.va.med.fw.batchprocess.DataProcessExecutionContext;
import gov.va.med.fw.batchprocess.DataQueryProcessExecutionContext;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.persistent.QueryIncrementTracker;
import gov.va.med.fw.persistent.QueryInfo;
import gov.va.med.fw.persistent.ScrollableCallback;
import gov.va.med.fw.util.CollectionUtils;

/**
 * Batch Process to trigger MFN~ZEG when the new EGT Setting is to be activated.
 * 
 * @author DNS   MANSOG
 */
public class EGTSettingProcess extends AbstractDataQueryProcess implements ScrollableCallback {

	private static final String		KEY_CURSOR_EFFECTED_ENROLLMENT_QUERY	= "cursor_queryEffectedEnrollment";

	private String					queryMfnAck								= "messageLogEntryQuery_checkMfnAck";
	private String					queryEffectedEnrollment					= "enrollmentDeterminationQuery_egtSettingEffectedPersonIds";
	private String					queryGetNotEnrolledCount				= "enrollmentDeterminationQuery_getNotEnrolledCount";
	private EGTService				egtService								= null;
	private LookupService			lookupService							= null;
	private int						enrollmentsFetchSize					= 50;

	private static final String[]	QUERY_PARAM_NAMES						= new String[] { "egtPriorityCode" };
	private static final String		PARAM_MFK_INFO							= "info";
	private static final String		PARAM_EGT_SETTING_ID					= "egtSettingId";

	/**
	 * @see gov.va.med.fw.batchprocess.AbstractDataProcess#processData(gov.va.med.fw.batchprocess.DataProcessExecutionContext,
	 *      java.util.List)
	 */
	protected void processData(DataProcessExecutionContext context,
			List acquiredData) {
		if (logger.isInfoEnabled())
			logger.info("EGTSetting batch process: Query result Size="
					+ (acquiredData == null ? 0 : acquiredData.size()));

		if (acquiredData == null || acquiredData.size() == 0)
			return;

		// There is an EGTSetting that should become current
		EGTSetting egt = (EGTSetting) acquiredData.get(0);
		try {
			if (!ackReceived(egt)) {
				if (logger.isInfoEnabled())
					logger
							.info("EGTSettingProcess: New EGTSetting exists but no MFK~ZEG ack received yet.");
				return;
			}
			if (logger.isInfoEnabled())
				logger.info("EGTSetting batch process: MFK~ZEG ack exists.");

			egt = egtService.saveAsCurrentEGTSetting(egt);
			saveProcessStatistics(getEgtProcessStatistics(egt), new Date(),
					null, EGTStatus.CODE_RUNNING);
		} catch (Exception ex) {
			context.getExceptionData().add(ex);
			throw new IllegalStateException(
					"Error while executing EGTSetting process. "
							+ ex.getMessage());
		}

		Map siteStats = Collections.synchronizedMap(new HashMap());
		context.getContextData().put("siteStats", siteStats);		
		context.getContextData().put("egt", egt);
		Integer totalRecords = null;
		try {
			QueryInfo query = new QueryInfo();
			query.setQuery(queryEffectedEnrollment);
			query.setParamValues( new Object[] { egt.getPriorityGroup().getCode() });
			query.setParamNames(QUERY_PARAM_NAMES);
            query.setFetchSize(enrollmentsFetchSize);
			totalRecords = this.getDao().scroll(this, query, (DataQueryProcessExecutionContext) context);
		} catch (Exception ex) {
			logger.error("Error while executing EGTSetting process.", ex);
			context.getExceptionData().add(ex);
		}

		try {
			EGTProcessStatistic egtProcessStatistics = getEgtProcessStatistics(egt);
			boolean isInterruptedState = isInterrupted(context);
			if (! isInterruptedState) {
				setEgtSiteStatistics(egtProcessStatistics, siteStats);
			}
			EGTStatus.Code status = isInterruptedState ? EGTStatus.CODE_STOPPED_BY_USER
					: (context.getExceptionData().size() > 0) ? EGTStatus.CODE_ERROR
							: EGTStatus.CODE_FINISHED;
			
			egtProcessStatistics.setBeneficiaryProcessedCount(totalRecords);
			egtProcessStatistics.setBeneficiaryNotEnrolledCount(getNotEnrolledCount(egt));
			saveProcessStatistics(egtProcessStatistics, null, new Date(), status);
			
		} catch (Exception ex) {
			context.getExceptionData().add(ex);
			String err = "Error while saving the EGTProcessStatistics.";
			logger.error(err, ex);
		}
		if (logger.isInfoEnabled()) {
			logger.info("EGTSetting batch process: Processing complete.");
		}
	}

	/**
	 * @param egtProcessStatistics TODO
	 * @param egt
	 * @param siteStats
	 */
	private void setEgtSiteStatistics(EGTProcessStatistic egtProcessStatistics, Map siteStats) {
		
		for (Iterator iter = siteStats.keySet().iterator(); iter.hasNext();) {
			VAFacility facility = (VAFacility) iter.next();
			EGTSiteStatistic egtSiteStats = egtProcessStatistics.getSiteStatistics(facility);
			if (egtSiteStats == null) {
				egtSiteStats = new EGTSiteStatistic();				
				egtProcessStatistics.setEGTSiteStatistic(facility, egtSiteStats);					
			}
			egtSiteStats.setBeneficiaryNotEnrolledCount((Integer)siteStats.get(facility));
		}
	}

	/**
	 * @param egt
	 * @return
	 * @throws DAOException
	 */
	private Integer getNotEnrolledCount(EGTSetting egt) throws DAOException {		
		List result = getDao().findByNamedQueryAndNamedParam(
				queryGetNotEnrolledCount, new String[] {PARAM_EGT_SETTING_ID},
				new Object[] {egt.getEntityKey().getKeyValue()});
		// Query returns count(*) so there should always be a record
		return (Integer) (result.iterator().next());
	}
	
	/**
	 * @param egt
	 * @return
	 */
	private EGTProcessStatistic getEgtProcessStatistics(EGTSetting egt) {
		EGTProcessStatistic egtStatistics = egt.getProcessStatistic();
		if (egtStatistics == null) {
			egtStatistics = new EGTProcessStatistic();
			egt.setProcessStatistic(egtStatistics);
		}
		return egtStatistics;
	}

	/**
	 * @see gov.va.med.fw.batchprocess.AbstractDataProcess#handleDataProcessCompleted(gov.va.med.fw.batchprocess.DataProcessExecutionContext)
	 */
	protected void handleDataProcessCompleted(
			DataProcessExecutionContext context) {
		super.handleDataProcessCompleted(context);
		ScrollableResults cursor = (ScrollableResults) context.getContextData()
				.get(KEY_CURSOR_EFFECTED_ENROLLMENT_QUERY);
		if (cursor != null) {
			cursor.close();
		}
	}

	/**
	 * @param egt
	 * @param code
	 */
	private void saveProcessStatistics(EGTProcessStatistic egtStatistics, 
										Date startDate, 
										Date endDate,
										EGTStatus.Code code) throws Exception {
		if (startDate != null)
			egtStatistics.setProcessStartDate(startDate);
		if (endDate != null)
			egtStatistics.setProcessEndDate(endDate);

		egtStatistics.setEgtProcessStatus(lookupService.getEGTStatusByCode(code.getCode()));

		egtService.saveEGTProcessStatistics(egtStatistics);

	}

	/**
	 * @param context
	 * @param list
	 */
	private void processEgtData(DataProcessExecutionContext context,
			List personIds, EGTSetting egt, Map siteStats) {
		try {
			egtService.updateEnrollmentWithNewEGTSetting(personIds, egt, siteStats);
			context.getProcessStatistics().incrementNumberOfSuccessfulRecords(personIds.size());
		} catch (Exception ex) {
			logger.error("Error while executing EGTSetting process.", ex);
			context.getExceptionData().add(ex);
			context.getProcessStatistics().incrementNumberOfErrorRecords(personIds.size());
		}
	}


	/**
	 * @param egt
	 * @return
	 * @throws DAOException
	 */
	private boolean ackReceived(EGTSetting egt) throws DAOException {
		List result = getDao().findByNamedQueryAndNamedParam(queryMfnAck,
				PARAM_MFK_INFO, egt.getEntityKey().getKeyValueAsString());
		// Query returns count(*) so there should always be a record
		return ((Integer) (result.iterator().next())).intValue() > 0;
	}

	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		Validate.notNull(egtService, "A EGTService is needed");
		Validate.notNull(lookupService, "A LookupService is needed");
		Validate.notNull(queryMfnAck, "A queryMfnAck (a query to check MFK ack) is needed");
		Validate.notNull(queryEffectedEnrollment, "A queryEffectedEnrollment (a query to check enrollements effected by EGTSetting) is needed");
		Validate.notNull(queryGetNotEnrolledCount, "A queryGetNotEnrolledCount name is needed");		
	}

	/**
	 * @return Returns the egtService.
	 */
	public EGTService getEgtService() {
		return egtService;
	}

	/**
	 * @param egtService
	 *            The egtService to set.
	 */
	public void setEgtService(EGTService egtService) {
		this.egtService = egtService;
	}

	/**
	 * @return Returns the lookupService.
	 */
	public LookupService getLookupService() {
		return lookupService;
	}

	/**
	 * @param lookupService
	 *            The lookupService to set.
	 */
	public void setLookupService(LookupService lookupService) {
		this.lookupService = lookupService;
	}

	/**
	 * @return Returns the queryEffectedEnrollment.
	 */
	public String getQueryEffectedEnrollment() {
		return queryEffectedEnrollment;
	}

	/**
	 * @param queryEffectedEnrollment
	 *            The queryEffectedEnrollment to set.
	 */
	public void setQueryEffectedEnrollment(String queryEffectedEnrollment) {
		this.queryEffectedEnrollment = queryEffectedEnrollment;
	}

	/**
	 * @return Returns the queryMfnAck.
	 */
	public String getQueryMfnAck() {
		return queryMfnAck;
	}

	/**
	 * @param queryMfnAckThe
	 *            queryMfnAck to set.
	 */
	public void setQueryMfnAck(String queryMfnAck) {
		this.queryMfnAck = queryMfnAck;
	}

	/**
	 * @return Returns the fetchSize.
	 */
	public int getEnrollmentsFetchSize() {
		return enrollmentsFetchSize;
	}

	/**
	 * @param fetchSize
	 *            The fetchSize to set.
	 */
	public void setEnrollmentsFetchSize(int fetchSize) {
		this.enrollmentsFetchSize = fetchSize;
	}

	/* (non-Javadoc)
	 * @see gov.va.med.fw.persistent.ScrollableCallback#handleScrolledData(gov.va.med.fw.persistent.QueryIncrementTracker)
	 */
	public void handleScrolledData(QueryIncrementTracker tracker) {
		DataProcessExecutionContext context = (DataProcessExecutionContext) tracker;
        //List personIds = new ArrayList();
        List data = tracker.getIncrementalData();
        CollectionUtils.transform(data, new Transformer() {
            public Object transform(Object arg0) {
                return (BigDecimal)((Object[])arg0)[0];
            }
        });

		processEgtData(context, data,
				(EGTSetting) context.getContextData().get("egt"),
				(Map) context.getContextData().get("siteStats"));		
	}

	/* (non-Javadoc)
	 * @see gov.va.med.fw.persistent.ScrollableCallback#continueScrolling()
	 */
	public boolean continueScrolling(QueryIncrementTracker tracker) {
		return !this.isInterrupted((DataProcessExecutionContext) tracker);
	}
}
