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

/*
 * Created on Apr 17, 2006
 *
 */
package gov.va.med.esr.service.impl;

import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.esr.common.model.ivmdm.IVMClearLoadErrorStatistics;
import gov.va.med.esr.common.model.ivmdm.IVMDMExtractBatch;
import gov.va.med.esr.common.model.ivmdm.IVMMigration;
import gov.va.med.esr.common.model.ivmdm.IVMMigrationArchive;
import gov.va.med.esr.common.model.lookup.MessageStatus;
import gov.va.med.esr.common.model.comms.CommsLogEntry;
import gov.va.med.esr.common.model.comms.MailingStatusLink;
import gov.va.med.esr.common.model.person.SSN;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.common.model.person.id.VPIDEntityKey;
import gov.va.med.esr.common.model.system.SystemParameter;
import gov.va.med.esr.service.IVMDMService;
import gov.va.med.esr.service.CommsLogService;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.PSDelegateService;
import gov.va.med.esr.service.PersonIdentityTraits;
import gov.va.med.esr.service.PersonService;
import gov.va.med.esr.service.SystemParameterService;
import gov.va.med.esr.service.external.person.IVMCandidateInfo;
import gov.va.med.esr.service.external.person.IvmLetterCandidateInfo;
import gov.va.med.esr.service.external.person.IvmLetterStatusInfo;
import gov.va.med.esr.service.external.person.collections.IvmLetterStatusCollection;
import gov.va.med.fw.model.EntityKey;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.persistent.DAOOperations;
import gov.va.med.fw.service.AbstractComponent;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.StringUtils;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Iterator;

import org.apache.commons.lang.Validate;

/**
 * @author Yi He
 * @author DNS   MANSOG
 */
public class IVMDMServiceImpl extends AbstractComponent implements IVMDMService {

	private static final long serialVersionUID = 63420326746244993L;
	private static final String QUERY_FIND_CONVERSION_BASE = "ivmReversalQuery_findConversionBase";
	private static final String QUERY_GET_IVM_MIGRATION_BY_PERSON_INCOME_YEAR = "ivmDMQuery_GetIVMMigrationByPersonAndIncomeYear";
	// CCR 12540 support explicit IVM delete
	private static final String QUERY_GET_IVM_MIGRATION_MARKED_FOR_DELETE = "ivmDMQuery_GetIVMMigrationMarkedForDelete";

	private static final String QUERY_GET_IVM_MIGRATION_BY_PERSON_INCOME_YEAR_STATUS = "ivmDMQuery_GetIVMMigrationByPersonAndIncomeYearAndStatus";

	private static final String QUERY_GET_LATEST_MIGRATED_YEAR_BY_PERSON = "ivmDMQuery_GetLastedMigratedYearByPerson";

	private static final String QUERY_GET_MOST_RECENT_MIGRATED_YEAR_BY_PERSON = "ivmDMQuery_GetMostRecentMigratedYearByPerson";

	private static final String QUERY_CHECK_SELECTION_CRITERIA = "ivmDMQuery_checkSelectionCriteria";

	private static final String QUERY_GET_IVM_MIGRATION_BY_ICN_INCOME_YEAR = "ivmDMQuery_GetIVMMigrationByICNAndIncomeYear";

	private static final String QUERY_GET_IVM_MIGRATION_BY_IDENTIFIER = "ivmMigrationQuery_Identifier";

	private static final String QUERY_GET_IVM_CANDIDATES  = "ivmDMQuery_GetIVMCandidatesObj";

	private static final String QUERY_GET_IVM_LETTER_CANDIDATES  = "ivmLetterStatusQuery_GetIVMLetterCandidatesObj";

	private static final String QUERY_GET_IVM_DATA_FOR_ADVICE  = "ivmDMQuery_GetIVMMigrationsWithCompleteOrPendingStatus";

	private static final String ICN_PARAM_NAME = "icn";

	private static final String PERSON_ID_PARAM_NAME = "personId";

	private static final String INCOME_YEAR_PARAM_NAME = "incomeYear";

	private static final String IDENTIFIER_PARAM_NAME = "identifier";

	private static final String IVM_DATE_PARAM_NAME = "ivmdate";

	private static final String STATUS_PARAM_NAME = "status";

	private static final String IVM_LIMIT_PARAM_NAME = "ivmlimit";

	private static final String STATUS_COMPLETE = "Complete";

	private DAOOperations genericDAO;

	private PSDelegateService psDelegateService;

	private SystemParameterService systemParamService;

	private LookupService lookupService;

	private PersonService personService;

	private CommsLogService commsLogService;

	private int ivmCandidateLimit;

	/**
	 * @see gov.va.med.fw.service.AbstractComponent#afterPropertiesSet()
	 */
	public void afterPropertiesSet() throws Exception {
		super.afterPropertiesSet();
		Validate.notNull(genericDAO, "DAOOperations is required.");
		Validate.notNull(systemParamService,
				"SystemParameterService is required.");
		Validate.notNull(lookupService, "LookupService is required.");
		Validate.notNull(psDelegateService, "PSDelegateService is required.");
	}

	public IVMMigration findIVMMigration(PersonEntityKey person,
			Integer incomeYear) throws ServiceException {
		Validate.notNull(person, "A person must not be null");
		Validate.notNull(incomeYear, "Income year must not be null");
		try {
			String[] paramNames = new String[] { PERSON_ID_PARAM_NAME,
					INCOME_YEAR_PARAM_NAME };
			Object[] paramValues = new Object[] {
					new BigDecimal(person.getKeyValueAsString()), incomeYear };
			List list = genericDAO.findByNamedQueryAndNamedParam(
					QUERY_GET_IVM_MIGRATION_BY_PERSON_INCOME_YEAR, paramNames,
					paramValues);

			return list.isEmpty() ? null : (IVMMigration) list.get(0);
		}
		catch (DAOException e) {
			throw new ServiceException(
					"Failed to get a list of IVM Migrations for person "
							+ person.getKeyValue(), e);
		}
	}

	public IVMMigration findIVMMigrationMarkedForDelete(PersonEntityKey person, Integer incomeYear) throws ServiceException {
		Validate.notNull(person, "A person must not be null");
		Validate.notNull(incomeYear, "Income year must not be null");
		try {
			String[] paramNames = new String[] { PERSON_ID_PARAM_NAME,
					INCOME_YEAR_PARAM_NAME };
			Object[] paramValues = new Object[] {
					new BigDecimal(person.getKeyValueAsString()), incomeYear };
			List list = genericDAO.findByNamedQueryAndNamedParam(
					QUERY_GET_IVM_MIGRATION_MARKED_FOR_DELETE, paramNames,
					paramValues);

			return list.isEmpty() ? null : (IVMMigration) list.get(0);
		}
		catch (DAOException e) {
			throw new ServiceException(
					"Failed to get a list of IVM Migrations for person "
							+ person.getKeyValue(), e);
		}
	}

	public IVMMigration findIVMMigration(String icn, Integer incomeYear)
			throws ServiceException {
		Validate.notNull(icn, "A ICN must not be null");
		Validate.notNull(incomeYear, "Income year must not be null");
		try {
			String[] paramNames = new String[] { ICN_PARAM_NAME,
					INCOME_YEAR_PARAM_NAME };
			Object[] paramValues = new Object[] { icn, incomeYear };
			List list = genericDAO.findByNamedQueryAndNamedParam(
					QUERY_GET_IVM_MIGRATION_BY_ICN_INCOME_YEAR, paramNames,
					paramValues);
			return (list != null && list.size() > 0) ? (IVMMigration) list.get(0)
					: null;
		}
		catch (DAOException e) {
			throw new ServiceException(
					"Failed to get a list of IVM Migrations for icn " + icn, e);
		}
	}

	public IVMMigration findIVMMigration(BigDecimal identifier)
	throws ServiceException {
		Validate.notNull(identifier, "An Identifier must not be null");

		try {
			String[] paramNames = new String[] { IDENTIFIER_PARAM_NAME };
			Object[] paramValues = new Object[] { identifier };
			List list = genericDAO.findByNamedQueryAndNamedParam(
					QUERY_GET_IVM_MIGRATION_BY_IDENTIFIER, paramNames,
					paramValues);
			return (list != null && list.size() > 0) ? (IVMMigration) list.get(0)
					: null;
		}
		catch (DAOException e) {
			throw new ServiceException(
					"Failed to get a list of IVM Migrations for identifier " + identifier, e);
		}
	}

	public void saveClearLoadErrorStatistics(
			IVMClearLoadErrorStatistics ivmCLEStats) throws ServiceException {
		Validate.notNull(ivmCLEStats,
				"A IVMClearLoadErrorStatistics must not be null");
		try {
			genericDAO.saveObject(ivmCLEStats);
		}
		catch (DAOException e) {
			throw new ServiceException(
					"Failed to save a IVM Clear Load Error Statistics  ", e);
		}
	}

	public void saveIVMMigration(IVMMigration ivmMigration)
			throws ServiceException {
		Validate.notNull(ivmMigration, "A IVM migration must not be null");
		try {
			genericDAO.saveObject(ivmMigration);
		}
		catch (DAOException e) {
			throw new ServiceException("Failed to save a IVM migration ", e);
		}
	}

	public void saveIVMMigration(IVMMigration ivmMigration, MessageStatus.Code status) throws ServiceException {
		Validate.notNull(ivmMigration, "A IVM migration must not be null");
		Validate.notNull(status, "IVM Migration Status must not be null");

		ivmMigration.setStatus(this.lookupService.getMessageStatusByCode(status));

		try {
			genericDAO.saveObject(ivmMigration);
		}
		catch (DAOException e) {
			throw new ServiceException("Failed to save a IVM migration ", e);
		}
	}

	public void saveIVMMigration(Date extractStartTime,
			PersonEntityKey personKey, String icn, Integer year)
			throws ServiceException {
		IVMMigration ivmMigration = findIVMMigration(personKey, year);
		if (ivmMigration == null) {
			ivmMigration = new IVMMigration();
		}
		ivmMigration.setMigrationDate(extractStartTime);
		ivmMigration.setIcn(icn);
		ivmMigration.setIncomeYear(year);
		ivmMigration.setPersonEntityKey(personKey);
		ivmMigration.setLoadFailureReason(null);
		ivmMigration.setRemigrate(false);
		try {
			genericDAO.saveObject(ivmMigration);
		}
		catch (DAOException e) {
			throw new ServiceException("Failed to save a IVM migration ", e);
		}
	}

	public void saveIVMDMExtractBatch(IVMDMExtractBatch ivmdmExtract)
			throws ServiceException {
		Validate.notNull(ivmdmExtract, "A IVM DM Extract batch must not be null");
		try {
			genericDAO.saveObject(ivmdmExtract);
		}
		catch (DAOException e) {
			throw new ServiceException(
					"Failed to save a list of IVM DM Extract batch ", e);
		}
	}

	/**
	 * @see gov.va.med.esr.service.IVMDMService#findExtractIncomeYears(gov.va.med.esr.common.model.person.id.PersonEntityKey,
	 *      java.lang.Integer,
	 *      gov.va.med.esr.common.model.person.id.VPIDEntityKey)
	 */
	public Set findExtractIncomeYears(PersonEntityKey personKey,
			Integer incomeYear, VPIDEntityKey vpidKey) throws ServiceException {
		Integer currentYear = new Integer(getCurrentIY());
		Integer nextYear = new Integer(currentYear.intValue() + 1);

		if (incomeYear != null) {
			return handleIncomeYearSpecificChange(personKey, incomeYear, vpidKey,
					currentYear, nextYear);
		}
		else {
			return handleNonIncomeYearSpecificChange(personKey, vpidKey,
					currentYear, nextYear);
		}
	}


	/**
	 * CCR 11892 IVM
	 */
	public int getCurrentActiveIVMIncomeYear() throws ServiceException {
		return this.getCurrentIY();
	}


	public int getNextVMIncomeYear() throws ServiceException {
		int currentYear = getCurrentIY();
		return currentYear + 1;
	}

	// CCR 11892
	public Set findIVMMigrationData(Date date) throws ServiceException {
		Set ivmMigrationData = new HashSet();
		Validate.notNull(date, "A date must not be null");

		try {
			if(logger.isInfoEnabled())
				logger.info("Going to get environment property for limit data ");
			long limit = (long) this.getIvmCandidateLimit();
			if(logger.isInfoEnabled())
				logger.info("Completed get environment property for limit data: ");

			//CCR12827, remove date dependency from retrieve query, only send limit param
			String[] paramNames = {IVM_LIMIT_PARAM_NAME};
			Object[] paramValues = {limit};
			if(logger.isInfoEnabled())
				logger.info("Going to execute retrieve candidate query ");
			List list = genericDAO.findByNamedQueryAndNamedParam(
					QUERY_GET_IVM_CANDIDATES, paramNames, paramValues);
			if(logger.isInfoEnabled())
				logger.info("Completed executing query and received list of candidates ");
			if(logger.isInfoEnabled())
				logger.info("Going to check if list is empty ");
			if (list.isEmpty()) {
				if(logger.isInfoEnabled())
					logger.info("List is EMPTY");
				return ivmMigrationData;
			}
			if(logger.isInfoEnabled())
				logger.info("Completed check for empty list ");
			if(logger.isInfoEnabled())
				logger.info("Going to loop through list and add to ivm migration data set for returning to IVM ");
			for (Iterator i = list.iterator(); i.hasNext();) {
				IVMMigration ivm = (IVMMigration) i.next();
				ivmMigrationData.add(ivm);
			}
			if(logger.isInfoEnabled())
				logger.info("Completed looping ");
		} catch (DAOException e) {
			throw new ServiceException(
					"Failed to get a list of IVM Candidates", e);
		}
		if(logger.isInfoEnabled())
			logger.info("Going to return set to IVM ");
		return ivmMigrationData;
	}


	public Set findIVMLetterStatusData() throws ServiceException {
		Set ivmLetterStatusData = new HashSet();
		try {
			List list = genericDAO.find(QUERY_GET_IVM_LETTER_CANDIDATES);
			if (list.isEmpty()) {
				return ivmLetterStatusData;
			}
			for (Iterator i = list.iterator(); i.hasNext();) {
				CommsLogEntry entry = (CommsLogEntry) i.next();
				ivmLetterStatusData.add(entry);
			}
			if(logger.isInfoEnabled())
				logger.info("Completed looping ");
		} catch (DAOException e) {
			throw new ServiceException(
					"Failed to get a list of IVM Candidates", e);
		}
		if(logger.isInfoEnabled())
			logger.info("Going to return set to IVM ");
		return ivmLetterStatusData;

	}

	private Set handleNonIncomeYearSpecificChange(PersonEntityKey personKey,
			VPIDEntityKey vpidKey, Integer currentYear, Integer nextYear)
			throws ServiceException {
		Set incomeYears = getNonIYSpecificChangeYears(personKey, vpidKey,
				currentYear, nextYear);

		// Now remove the years from this list who has a load error reason in the
		// database
		Integer[] yearsArray = (Integer[]) incomeYears.toArray(new Integer[0]);
		for (int i = 0; i < yearsArray.length; i++) {
			Integer year = yearsArray[i];
			IVMMigration ivmMigration = this.findIVMMigration(personKey, year);
			if (ivmMigration != null
					&& ivmMigration.getLoadFailureReason() != null) {
				incomeYears.remove(year);
			}
		}
		return incomeYears;
	}

	private Set getNonIYSpecificChangeYears(PersonEntityKey personKey,
			VPIDEntityKey vpidKey, Integer currentYear, Integer nextYear)
			throws ServiceException {
		Set incomeYears = new HashSet();

		// non IY specific change
		// If the veteran does meet the IVM Selection Criteria for
		// the Current DM IY and/or the next IY, then a record is migrated
		// for each of these years the veteran meets the IVM Selection
		// Criteria.
		boolean meetCurrentIY = runSelectionCriteria(personKey, currentYear,
				vpidKey);
		boolean meetNextIY = runSelectionCriteria(personKey, nextYear, vpidKey);
		if (meetCurrentIY) {
			incomeYears.add(currentYear);
		}
		if (meetNextIY) {
			incomeYears.add(nextYear);
		}
		// If the veteran does not meet the IVM Selection Criteria for the
		// Current DM IY
		// and/or next IY, then the most recent IY, for which the veteran
		// has been
		// previously migrated without an EDB Load Failure Reason,
		// if the veteran has been previously migrated, will be sent as a DM
		// Update.
		if (!meetCurrentIY && !meetNextIY) {
			Integer latestMigratedYear = getLatestMigratedYear(personKey);
			if (latestMigratedYear != null) {
				incomeYears.add(latestMigratedYear);
			}
		}
		return incomeYears;
	}

	private Set handleIncomeYearSpecificChange(PersonEntityKey personKey,
			Integer incomeYear, VPIDEntityKey vpidKey, Integer currentYear,
			Integer nextYear) throws ServiceException {
		Set incomeYears = new HashSet();

		IVMMigration ivmMigration = this.findIVMMigration(personKey, incomeYear);
		if (ivmMigration != null) {
			// has been migrated for the IY
			if (ivmMigration.getMigrationDate() != null
					&& ivmMigration.getLoadFailureReason() == null) {
				// has been migrated for the IY successfully
				incomeYears.add(incomeYear);
			}

		}
		else {
			// has not been migrated for the IY, check if meet selection
			// criteria for the IY
			if (incomeYear.intValue() == currentYear.intValue()
					|| incomeYear.intValue() == nextYear.intValue()) {
				if (runSelectionCriteria(personKey, incomeYear, vpidKey)) {
					incomeYears.add(incomeYear);
				}
			}
		}
		return incomeYears;
	}

	public boolean meetSelectionCriteria(PersonEntityKey personKey, String icn,
			Integer incomeYear) throws ServiceException {
		VPIDEntityKey vpidKey = CommonEntityKeyFactory.createVPIDEntityKey(icn);
		return runSelectionCriteria(personKey, incomeYear, vpidKey);
	}

	// 1. SSN Verification Status = Verified.
	// 2. Date of Death Not Populated.
	// 3. Primary Income Test for the Income Year
	// • Means Test Status = MT Copay Exempt
	// • Test Determined Status = MT Copay Exempt
	// • Hardship Effective Date = Null
	// 4. Eligible for Medicaid = No or Null
	// 5. Check veteran eligibility data:
	// • The Primary Eligibility Code = NSC
	// OR
	// • (The Primary Eligibility Code = SC Less than 50% AND SC% = 0 or Null
	// AND Total Check Amount = Null or 0.)
	private boolean runSelectionCriteria(PersonEntityKey personKey,
			Integer incomeYear, VPIDEntityKey vpidKey) throws ServiceException {
		try {
			boolean allowed = meetPartialSelectionCriteria(personKey, incomeYear);
			return allowed && isSsnStatusVerified(vpidKey);
		}
		catch (Exception ex) {
			throw new ServiceException("Failed to check selection criteria", ex);
		}
	}

	private boolean meetPartialSelectionCriteria(EntityKey entityKey,
			Integer incomeYear) throws DAOException {
		String[] paramNames = { PERSON_ID_PARAM_NAME, INCOME_YEAR_PARAM_NAME };
		Object[] paramValues = { entityKey.getKeyValue(), incomeYear };
		List result = genericDAO.findByNamedQueryAndNamedParam(
				QUERY_CHECK_SELECTION_CRITERIA, paramNames, paramValues);
		return ((Integer) result.get(0)).intValue() > 0;
	}

	private boolean isSsnStatusVerified(VPIDEntityKey vpidEntityKey)
			throws Exception {
		PersonIdentityTraits iTraits = psDelegateService
				.getIdentityTraits(vpidEntityKey);
		SSN ssn = iTraits.getSsn();
		return ssn == null ? false : ssn.isSsaVerificationStatusVerified();
	}

	private int getCurrentIY() throws ServiceException {
		// Prior to the Cut-Off Date within a year, the Current DM Income Year
		// is equal to
		// the year - 2. After the Cut-Off Date within a year, the Current DM
		// Income
		// Year is equal to the year - 1.
		String incomeYearCutOff = systemParamService.getByName(
				SystemParameter.IVM_DM_IY_CUTOFF_DATE).getValue();
		int cutOffMonth = Integer.parseInt(incomeYearCutOff.substring(0, 2)
				.trim());
		int cutOffDay = Integer.parseInt(incomeYearCutOff.substring(2).trim());

		Calendar calendar = Calendar.getInstance();
		int currentYear = calendar.get(Calendar.YEAR);
		int currentMonth = calendar.get(Calendar.MONTH);
		int currentDay = calendar.get(Calendar.DAY_OF_MONTH);

		boolean afterCutOff = currentMonth >= cutOffMonth
				&& currentDay >= cutOffDay;
		return afterCutOff ? currentYear - 1 : currentYear - 2;
	}

	// CCR 11892 IVM
	public Integer getMostRecentIVMMigratedYear(PersonEntityKey person) throws ServiceException {
		Serializable param = new BigDecimal(person.getKeyValueAsString());
		try {
			List list = genericDAO.findByNamedQueryAndNamedParam(
					QUERY_GET_MOST_RECENT_MIGRATED_YEAR_BY_PERSON, PERSON_ID_PARAM_NAME,
					param);
			return list.isEmpty() ? null : (Integer) list.get(0);
		}
		catch (DAOException e) {
			throw new ServiceException(
					"Failed to get most recent migrated year for person " + param, e);
		}
	}


	public IVMMigration findIVMMigration(PersonEntityKey personKey, Integer incomeYear, MessageStatus.Code status) throws ServiceException {
		Validate.notNull(personKey, "A person key must not be null");
		Validate.notNull(incomeYear, "Income year must not be null");
		Validate.notNull(status, "status must not be null");
		try {
			String[] paramNames = new String[] {PERSON_ID_PARAM_NAME,
					INCOME_YEAR_PARAM_NAME, STATUS_PARAM_NAME};
			Object[] paramValues = new Object[] {
					new BigDecimal(personKey.getKeyValueAsString()), incomeYear, status.getCode() };
			List list = genericDAO.findByNamedQueryAndNamedParam(
					QUERY_GET_IVM_MIGRATION_BY_PERSON_INCOME_YEAR_STATUS, paramNames,
					paramValues);

			return list.isEmpty() ? null : (IVMMigration) list.get(0);
		}
		catch (DAOException e) {
			throw new ServiceException(
					"Failed to get a list of IVM Migrations for person "
							+ personKey.getKeyValue(), e);
		}
	}

	public Integer getLatestHECLegacyYear(PersonEntityKey person)
			throws ServiceException {
		return getLatestMigratedYear(person);
	}


	public Set findIVMMigrationsWithCompleteOrPendingStatus(PersonEntityKey person) throws ServiceException {
		Set data =  new HashSet();
		Validate.notNull(person, "A person key must not be null");

		try {
			String[] paramNames = new String[] { PERSON_ID_PARAM_NAME };
			Object[] paramValues = new Object[] {
					new BigDecimal(person.getKeyValueAsString())};
			List list = genericDAO.findByNamedQueryAndNamedParam(
					QUERY_GET_IVM_DATA_FOR_ADVICE, paramNames,
					paramValues);
			if (list.isEmpty()) return data;

	        for (Iterator i=list.iterator(); i.hasNext();) {
	        	IVMMigration ivm = (IVMMigration)i.next();
	        	data.add(ivm);
	        }
		}
		catch (DAOException e) {
			throw new ServiceException(
					"Failed to get a list of IVM migration records", e);
		}
		return data;
	}

	/**
	 * @param person
	 * @return The latest income year for which the person has been migrated
	 *         successfully.
	 * @throws ServiceException
	 */
	private Integer getLatestMigratedYear(PersonEntityKey person)
			throws ServiceException {
		Serializable param = new BigDecimal(person.getKeyValueAsString());
		try {
			List list = genericDAO.findByNamedQueryAndNamedParam(
					QUERY_GET_LATEST_MIGRATED_YEAR_BY_PERSON, PERSON_ID_PARAM_NAME,
					param);
			return list.isEmpty() ? null : (Integer) list.get(0);
		}
		catch (DAOException e) {
			throw new ServiceException(
					"Failed to get lastest migrated year for person " + param, e);
		}
	}

	/**
	 * Update the sync error reason data in the database.
	 *
	 * @param incomeYear -
	 *           The income year
	 * @param icn -
	 *           Unique ICN number for the person
	 * @param date -
	 *           Date string
	 * @param errorReason -
	 *           Error reason
	 * @return true if data is updated successfully, false otherwise.
	 * @throws ServiceException -
	 *            if there is any error saving the IVM Migration
	 */
	public boolean updateErrorReasonData(Integer incomeYear, String icn,
			String date, String errorReason) throws ServiceException {
		IVMMigration ivmMigration = findIVMMigration(icn, incomeYear);
		if (ivmMigration != null) {
			if (StringUtils.isNotEmpty(errorReason))
				ivmMigration.setLoadFailureReason(errorReason);
			if (StringUtils.isEmpty(date))
				ivmMigration.setMigrationDate(null);

			ivmMigration.setRemigrate(false);

			try {
				genericDAO.saveObject(ivmMigration);
			}
			catch (DAOException e) {
				throw new ServiceException("Failed to save a IVM Migration for "
						+ ivmMigration.getEntityKey().getKeyValueAsString(), e);
			}
			return true;
		}
		return false;
	}

	/**
	 * Update the clear load error process data (clear error reasons previously
	 * loaded) in the database.
	 *
	 * @param icn -
	 *           Unique ICN of the person
	 * @param incomeYear -
	 *           The income year
	 * @param loadErrorStats -
	 *           IVMClearLoadErrorStatistics to update the statistics.
	 * @throws ServiceException -
	 *            if there is any error saving the IVM Migration
	 */
	public void updateClearLoadError(String icn, String incomeYear,
			IVMClearLoadErrorStatistics loadErrorStats) throws ServiceException {
		IVMMigration ivmMigration = findIVMMigration(icn, new Integer(incomeYear));
		if (!isErrorReasonExists(ivmMigration)) {
			loadErrorStats.incrementNoErrorReasonsExistsCount();
		}
		else {
			ivmMigration.setLoadFailureReason(null);
			if (shouldRemigrate(ivmMigration)) {
				ivmMigration.setRemigrate(true);
			}
			else {
				loadErrorStats.incrementNoLongerMeetingCriteriaCount();
				ivmMigration.setRemigrate(false);
			}

			try {
				genericDAO.saveObject(ivmMigration);
			}
			catch (DAOException e) {
				throw new ServiceException("Failed to save a IVM Migration for "
						+ ivmMigration.getEntityKey().getKeyValueAsString(), e);
			}
		}
	}

	public IVMCandidateInfo[] updateIVMCandidateRetrieval(Set candidateSet)
			throws ServiceException {

		IVMMigration ivmCandidate;

		IVMCandidateInfo[] eeIVMCandidateInfo = new IVMCandidateInfo[candidateSet
				.size()];

		Iterator itr = candidateSet != null ? candidateSet.iterator() : null;
		int i = 0;
		while (itr != null && itr.hasNext()) {
			IVMCandidateInfo ivmCandidateInfo = new IVMCandidateInfo();
			// ivmData = (BigDecimal) itr.next();
			// ivmCandidate = findIVMMigration(ivmData);
			ivmCandidate = (IVMMigration) itr.next();

			if (ivmCandidate != null) {
				if (logger.isInfoEnabled())
					logger.info("Going to person service for: " + ivmCandidate);
				// Used person service to convert person id to vpid
				ivmCandidateInfo.setVpid(this.getPersonService()
						.getVPIDByPersonId(ivmCandidate.getPersonEntityKey())
						.getVPID());
				if (logger.isInfoEnabled())
					logger.info("Completed person service call for: "
							+ ivmCandidate);
				if (logger.isInfoEnabled())
					logger
							.info("Going to set income year for: "
									+ ivmCandidate);
				ivmCandidateInfo.setIncomeYear(ivmCandidate.getIncomeYear());
				if (logger.isInfoEnabled())
					logger.info("Completed set income year for: "
							+ ivmCandidate);
				if (logger.isInfoEnabled())
					logger
							.info("Going to set transaction retrieving entity key: "
									+ ivmCandidate);
				ivmCandidateInfo.setTransactionId(ivmCandidate.getEntityKey()
						.getKeyValueAsString());
				if (logger.isInfoEnabled())
					logger
							.info("Completed set transaction retrieving entity key: "
									+ ivmCandidate);
				if (logger.isInfoEnabled())
					logger.info("Going to decide on saving " + ivmCandidate);
				if (!MessageStatus.AWAITING_ACKNOWLEDGEMENT.getName().equals(
						ivmCandidate.getStatus().getCode())) {
					if (logger.isInfoEnabled())
						logger.info("Going to save ivm migration "
								+ ivmCandidate);
					saveIVMMigration(ivmCandidate,
							MessageStatus.AWAITING_ACKNOWLEDGEMENT);
					if (logger.isInfoEnabled())
						logger.info("Completed save ivm migration "
								+ ivmCandidate);
				}
				logger.info("Ended task within loop for =" + ivmCandidate);
			}

			eeIVMCandidateInfo[i] = ivmCandidateInfo;
			i++;
		}
		if (logger.isInfoEnabled())
			logger
					.info("Outside while loop - Returning candidate info array to caller ="
							+ eeIVMCandidateInfo.length);
		return eeIVMCandidateInfo;
	}

	@SuppressWarnings("unchecked")
	public IvmLetterCandidateInfo[] updateIVMLetterCandidateRetrieval(Set letterCandidates)
			throws ServiceException {
		IvmLetterCandidateInfo[] ivmLetterCandidateInfos = new IvmLetterCandidateInfo[letterCandidates.size()];

		Iterator itr = letterCandidates != null ? letterCandidates.iterator() : null;
		int i = 0;
		while (itr != null && itr.hasNext()) {
			IvmLetterCandidateInfo ivmLetterCandidateInfo = new IvmLetterCandidateInfo();
			CommsLogEntry entry = (CommsLogEntry) itr.next();

			if (entry != null) {
				ivmLetterCandidateInfo.setVpid(this.getPersonService().getVPIDByPersonId(entry.getPersonIdEntityKey()).getVPID());

				ivmLetterCandidateInfo.setIvmCaseNumber(entry.getIvmCaseNumber());
				ivmLetterCandidateInfo.setIvmLetterCode(entry.getFormNumber());
				ivmLetterCandidateInfo.setIvmLetterMailedDate(entry.getMailingDate());
				List<MailingStatusLink> links = entry.getMailingStatusLinks();
				IvmLetterStatusInfo[] ivmLetterStatusInfos = links != null ? new IvmLetterStatusInfo[links.size()] : new IvmLetterStatusInfo[0];
				if (links != null) {
					int j = 0;
					for (MailingStatusLink link: links) {
						IvmLetterStatusInfo ivmLetterStatusInfo = new IvmLetterStatusInfo();
						ivmLetterStatusInfo.setCommunicationId(entry.getCommsLogIdString());
						// Use created on date since the modified dates are always the same.
						ivmLetterStatusInfo.setRecordModifiedDate(link.getCreatedOn());
						ivmLetterStatusInfo.setLetterCommunicationStatus(link.getMailingStatus().getCode());
						ivmLetterStatusInfos[j] = ivmLetterStatusInfo;
						j++;
					}
				}

				IvmLetterStatusCollection ivmLetterStatusCollection = new IvmLetterStatusCollection(ivmLetterStatusInfos);
				ivmLetterCandidateInfo.setIvmLetterStatuses(ivmLetterStatusCollection);
			}

			ivmLetterCandidateInfos[i] = ivmLetterCandidateInfo;
			i++;
		}

		return ivmLetterCandidateInfos;
	}

	@SuppressWarnings("unchecked")
	public void updateCommLogEntriesSentToIVMFlag(Set letterCandidates)
			throws ServiceException {
		IvmLetterCandidateInfo[] ivmLetterCandidateInfos = new IvmLetterCandidateInfo[letterCandidates.size()];
		Iterator itr = letterCandidates != null ? letterCandidates.iterator() : null;

		while (itr != null && itr.hasNext()) {
			CommsLogEntry entry = (CommsLogEntry) itr.next();
			entry.setSentToIVM(Boolean.FALSE);
			this.getCommsLogService().update(entry);
		}
	}

	public boolean updateIVMStatus(BigDecimal transactionId, String status) throws ServiceException{

		IVMMigration ivmRecord = findIVMMigration(transactionId);

		//CCR13439 - IVM sending massive error messages more than we can store
		String errMsg = status;
		if (status != null && status.length() > 1500) {
			errMsg = status.substring(0, 1500);
		}

		if(logger.isInfoEnabled())
			logger.info("IVM Record found = " + "record id: "+ivmRecord.getEntityKey() + " , uncoming transation id: " + transactionId);

		boolean updated = false;
		if(ivmRecord !=null){
			updated = true;
			if(status == null || status.equals("")){
				ivmRecord.setMigrationDate(new Date());
				ivmRecord.setLoadFailureReason(status);
				saveIVMMigration(ivmRecord, MessageStatus.COMPLETE);
				//CCR12713
				archivePreviousMigrations(ivmRecord);
				if(logger.isInfoEnabled())
					logger.info("Saved Status to complete for transaction id:  = " + transactionId);
			}
			else{
				ivmRecord.setLoadFailureReason(errMsg);
				saveIVMMigration(ivmRecord, MessageStatus.ERROR);
				if(logger.isInfoEnabled())
					logger.info("Saved Status to Error for transaction id:  = " + transactionId);
			}
		}
		if(logger.isInfoEnabled())
			logger.info("Completed archiving/updating for transaction id:  = " + transactionId );
		return updated;

	}

	//CCR12713, archive previous completed migrations for same IY upon record completion
	public void archivePreviousMigrations(IVMMigration ivmRecord) throws ServiceException{

		//get all current migration records for this person
		Set previousMigrations = findIVMMigrationsWithCompleteOrPendingStatus(ivmRecord.getPersonEntityKey());
		Iterator iter = previousMigrations.iterator();

		while (iter.hasNext()) {
			IVMMigration prev =  (IVMMigration) iter.next();
			//find all the completed status for same IY
			if (prev.getIncomeYear().equals(ivmRecord.getIncomeYear()) && prev.getStatus().getDescription().equals(STATUS_COMPLETE)) {
				try {
					if (!prev.getEntityKey().equals(ivmRecord.getEntityKey())) {
						IVMMigrationArchive ar = new IVMMigrationArchive(prev);
						genericDAO.saveObject(ar);
						genericDAO.removeObject(prev.getEntityKey());
						if (logger.isDebugEnabled()) {
							logger.debug("Archived IVM MIGRATE_ID:="+ prev.getEntityKey().getKeyValueAsString());
						}
					}
				} catch (DAOException e) {
					throw new ServiceException("Failed to ARCHIVE a completed IVM migration: " + ivmRecord.getEntityKey().getKeyValueAsString(), e);
				}
			}
		}
	}

	private boolean shouldRemigrate(IVMMigration ivmMigration)
			throws ServiceException {
		Validate.notNull(ivmMigration, "IVMMigration Object must not be null.");

		if (ivmMigration.getMigrationDate() != null) {
			return true;
		}
		else {
			return meetSelectionCriteria(ivmMigration.getPersonEntityKey(),
					ivmMigration.getIcn(), ivmMigration.getIncomeYear());
		}
	}

	private boolean isErrorReasonExists(IVMMigration ivmMigration) {
		return ivmMigration != null
				&& ivmMigration.getLoadFailureReason() != null;
	}

	public DAOOperations getGenericDAO() {
		return this.genericDAO;
	}

	public void setGenericDAO(DAOOperations dao) {
		this.genericDAO = dao;
	}

	public void setSystemParameterService(
			SystemParameterService systemParamService) {
		this.systemParamService = systemParamService;
	}

	public void setLookupService(LookupService lookupService) {
		this.lookupService = lookupService;
	}

	/**
	 * @param psDelegateService
	 *           The psDelegateService to set.
	 */
	public void setPsDelegateService(PSDelegateService psDelegateService) {
		this.psDelegateService = psDelegateService;
	}

	public PersonService getPersonService() {
		return personService;
	}

	public void setPersonService(PersonService personService) {
		this.personService = personService;
	}

	public int getIvmCandidateLimit() {
		return ivmCandidateLimit;
	}

	public void setIvmCandidateLimit(int ivmCandidateLimit) {
		this.ivmCandidateLimit = ivmCandidateLimit;
	}

	public CommsLogService getCommsLogService() {
		return commsLogService;
	}

	public void setCommsLogService(CommsLogService commsLogService) {
		this.commsLogService = commsLogService;
	}

}
