/*******************************************************************************
 * Copyright © 2004-2006 VHA. All rights reserved
 ******************************************************************************/
package gov.va.med.esr.service.impl;

// Java Classes
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;

import gov.va.med.esr.common.model.comms.AacLetterRequest;
import gov.va.med.esr.common.model.comms.CommsLogEntry;
import gov.va.med.esr.common.model.comms.CommsTemplate;
import gov.va.med.esr.common.model.comms.MailingStatusLink;
import gov.va.med.esr.common.model.lookup.ComLetterTemplateType;
import gov.va.med.esr.common.model.lookup.ComLetterType;
import gov.va.med.esr.common.model.lookup.ComMailingStatusType;
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.persistent.comms.AacLetterRequestDAO;
import gov.va.med.esr.common.persistent.comms.CommsLogEntryDAO;
import gov.va.med.esr.common.persistent.comms.CommsTemplateDAO;
import gov.va.med.esr.common.persistent.comms.PrintRequestDuplicationException;
import gov.va.med.esr.common.util.LetterFormatHelper;
import gov.va.med.esr.service.CommsLetterRequestService;
import gov.va.med.esr.service.DemographicService;
import gov.va.med.esr.service.DuplicatePrintRequestServiceException;
import gov.va.med.esr.service.trigger.LetterTrigger;
import gov.va.med.esr.service.trigger.LetterTriggerEvent;
import gov.va.med.esr.service.trigger.LetterTriggerIdentity;
import gov.va.med.esr.service.trigger.LetterTriggerEvent.MailType;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.validation.ValidationMessage;
import gov.va.med.fw.validation.ValidationMessages;



/**
 * Provides Comms Letter Request Services. Project: Comms</br> Created on:
 * 11:59:26 AM </br>
 *
 * @author The ESR Team
 */
public class CommsLetterRequestServiceImpl extends AbstractRuleAwareServiceImpl
		implements Serializable, CommsLetterRequestService {
	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = -1259201288293567373L;

	// The list of available comms templates that are available for mailing
	private List manualMailingLst;

	private CommsLogEntryDAO commsLogDAO = null;

	private AacLetterRequestDAO aacRequestDAO = null;

	private CommsTemplateDAO templDAO = null;

	/**
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	public void afterPropertiesSet() throws Exception {
		super.afterPropertiesSet();

		Validate.notNull(this.commsLogDAO,
				"A Comms Log Entry DAO must be configured");
		Validate.notNull(this.aacRequestDAO,
				"An AAC Letter Request DAO must be configured");
		Validate.notNull(this.templDAO,
				"An Comms Letter Template DAO must be configured");
	}

	/**
	 * Request an AAC letter to AAC export letter batch. This can be called by
	 * UI synchronously or can be called by JMS consumer process asynchronously.
	 *
	 * @param person
	 *            the person this letter is sending to
	 * @param letterType
	 *            Details about the target letter
	 * @param workflowCaseId
	 *            the workflow case Id for the person, if applicable (not used
	 *            in I3)
	 * @param mailType
	 *            LetterTriggerEvent.MANUAL_MAIL; LetterTriggerEvent.REMAIL;
	 *            LetterTriggerEvent.HISTORIC_MAIL;
	 *            LetterTriggerEvent.AUTO_MAIL; Please see
	 *            gov.va.med.esr.service.trigger.LetterTriggerEvent for types
	 * @param override
	 *            indicate if this request is a result of user override, null
	 *            means false
	 *
	 * @return a list of two lists of error messages. The first list is the
	 *         required fields error messages. The second list is the condition
	 *         to send error messages. Empty list if no error messages.
	 * @throws ServiceException
	 */
	public List requestAacLetter(Person person,
			LetterTriggerIdentity letterType, String workflowCaseId,
			LetterTriggerEvent.MailType mailType, Boolean override)
			throws ServiceException {
		return doRequestLetter(person, letterType, workflowCaseId, mailType,
				override, null, null);
	}

	/**
	 * Request an AAC letter to AAC export letter batch. This can be called by
	 * UI synchronously or can be called by JMS consumer process asynchronously.
	 *
	 * @param person
	 *            the person this letter is sending to
	 * @param letterType
	 *            Details about the target letter
	 * @param workflowCaseId
	 *            the workflow case Id for the person, if applicable (not used
	 *            in I3)
	 * @param mailType
	 *            LetterTriggerEvent.MANUAL_MAIL; LetterTriggerEvent.REMAIL;
	 *            LetterTriggerEvent.HISTORIC_MAIL;
	 *            LetterTriggerEvent.AUTO_MAIL; Please see
	 *            gov.va.med.esr.service.trigger.LetterTriggerEvent for types
	 * @param override
	 *            indicate if this request is a result of user override, null
	 *            means false
	 * @param actionComment
	 *            user comment
	 *
	 * @return a list of two lists of error messages. The first list is the
	 *         required fields error messages. The second list is the condition
	 *         to send error messages. Empty list if no error messages.
	 * @throws ServiceException
	 */
	public List requestAacLetter(Person person,
			LetterTriggerIdentity letterType, String workflowCaseId,
			LetterTriggerEvent.MailType mailType, Boolean override,
			String actionComment) throws ServiceException {
		return this.doRequestLetter(person, letterType, workflowCaseId, mailType, override, actionComment, null);
	}

    /**
     * Request an AAC or CMS letter to AAC/CMS export letter batch.
     * This can be called by JMS consumer process asynchronously.
     *
     * @param person
     * @param letterType
     * @return
     * @throws ServiceException
     */
	public List requestLetter(Person person,
			LetterTriggerIdentity letterType, LetterTriggerEvent letterTriggerEvent) throws ServiceException {
		return this.doRequestLetter(person, letterType, letterTriggerEvent.getWorkflowCaseId(),
				letterTriggerEvent.getMailType(), null, null, letterTriggerEvent);
	}

	private List doRequestLetter(Person person,
			LetterTriggerIdentity letterType, String workflowCaseId,
			LetterTriggerEvent.MailType mailType, Boolean override,
			String actionComment, LetterTriggerEvent letterTriggerEvent) throws ServiceException {
		ComLetterTemplateType formNum = letterType.getDataType();
		if (formNum == null || formNum.getName() == null) {
			ServiceException ex = new ServiceException(
					"Can not send a letter with an empty form number!!!");
			logger.error(
					"Error empty form number while requesting an AAC letter",
					ex);
			throw ex;
		}

		// get CommsTemplate from form number
		String formNumberStr = formNum.getCode();
		CommsTemplate templ = null;
		try {
			templ = (CommsTemplate) this.templDAO.findTemplateListByFormNumber(
					formNumberStr).get(0);
		} catch (DAOException dex) {
			logger.error("ERROR to find a Comms Template with form number ", dex);
			throw new ServiceException(
					"Excption when find a Comms Template with form number="
							+ formNumberStr + ". Error Message: "
							+ dex.getMessage(), dex);
		}

		String personId = person.getEntityKey().getKeyValueAsString();
		String code = this.createUniqueCode(personId, letterType);
		try {
			AacLetterRequest curr = aacRequestDAO
					.findAacLetterRequestByUniqueCode(code);
			// No reason to continue since an identical request is already in
			// queue but has
			// not been processed yet by batch job.
			if (curr != null) {
				// did not find any caller that expects non-null response
				return null;
			}
		} catch (DAOException dex) {
			logger.error("ERROR finding a print request with unique code ", dex);
			throw new ServiceException(
					"ERROR finding a print request with unique code = " + code
							+ "  Error Message: " + dex.getMessage(), dex);
		}

		// get the address for mailing
		DemographicService demoSrv = (DemographicService) this
				.getComponent("demographicService");
		Address mailingAddress = demoSrv.getLetterAddress(person);
		mailingAddress.setCounty(StringUtils.remove(mailingAddress.getCounty(), "AMP;"));
		mailingAddress.setCity(StringUtils.remove(mailingAddress.getCity(), "AMP;"));
		mailingAddress.setLine1(StringUtils.remove(mailingAddress.getLine1(), "AMP;"));
		mailingAddress.setLine2(StringUtils.remove(mailingAddress.getLine2(), "AMP;"));
		mailingAddress.setLine3(StringUtils.remove(mailingAddress.getLine3(), "AMP;"));

		// create a log entry
		CommsLogEntry logEntry = null;
		List errMsgList = null;

		try {
			logEntry = this.createLogEntry(person, templ, workflowCaseId,
					mailType, letterType.getCategoryType(), override,
					mailingAddress, actionComment, letterTriggerEvent);

			// make sure the letter pass the rules by ILog Rule Engine and
			// log are updated with the correct status and error reasons if any
			errMsgList = processLetterRules(person, formNum, workflowCaseId,
					mailType, override, logEntry,
					letterType.getCategoryType() != null ? letterType
							.getCategoryType().getName() : null);

			// insert a new log entry
			commsLogDAO.insert(logEntry);
		} catch (Exception dex) {
			logger.error(
					"ERROR to insert a new log entry with status \"Send to AAC\" while requesting an AAC letter",
					dex);
			throw new ServiceException("Excption when inserting a log entry!"
					+ dex.getMessage(), dex);
		}

		// insert a print request
		// NOTE: even the one fails the rules has to be inserted because
		// collecting statistics
		// of "Reject At HEC" is a requirement.
		AacLetterRequest req = createAacLetterRequest(letterType, logEntry,
				person, errMsgList);

		try {
			aacRequestDAO.insert(req);
		} catch (PrintRequestDuplicationException dupEx) {
			throw new DuplicatePrintRequestServiceException(
					"Duplicate Print Reqeust Entry Found!" + dupEx.getMessage(),
					dupEx);
		} catch (DAOException dex) {
			logger.error(
					"ERROR to insert a new AAC letter request while requesting an AAC letter",
					dex);
			throw new ServiceException(
					"Data Access Object: AacLetterRequestDAO.insert() error: "
							+ dex.getMessage(), dex);
		}

		return errMsgList;
	}

	/**
	 * Check letter rules and update mailing log for an AAC letter request. This
	 * can be used at letter request time
	 * (CommsLetterRequestService.requestAacLetter()) or at letter batch process
	 * time (AacExportCommand.execute()).
	 *
	 * @param person
	 *            the person this letter is sending to
	 * @param formNum
	 *            the form number of this letter
	 * @param workflowCaseId
	 *            the workflow case Id for the person, if applicable (not used
	 *            in I3)
	 * @param mailType
	 *            LetterTriggerEvent.MANUAL_MAIL; LetterTriggerEvent.REMAIL;
	 *            LetterTriggerEvent.HISTORIC_MAIL;
	 *            LetterTriggerEvent.AUTO_MAIL; Please see
	 *            gov.va.med.esr.service.trigger.LetterTriggerEvent for types
	 * @param override
	 *            indicate if this request is a result of user override, null
	 *            means false
	 * @param logEntry
	 *            the log entry to be inserted (new, created at request time) or
	 *            to be updated (existing log entry, at batch time)
	 *
	 * @param categoryType
	 *            the category or subject of the letter
	 *
	 * @return a list of two lists of error messages. The first list is the
	 *         required fields error messages. The second list is the condition
	 *         to send error messages. Empty list if no error messages.
	 * @throws ServiceException
	 */
	public List processLetterRules(Person person,
			ComLetterTemplateType formNum, String workflowCaseId,
			MailType mailType, Boolean override, CommsLogEntry logEntry,
			String categoryType) throws ServiceException {
		return doProcessLetterRules(person, formNum, workflowCaseId, mailType,
				override, logEntry, categoryType,
				CommsLetterRequestService.CONTEXT_REQUEST_LETTER);
	}

	/**
	 * @see gov.va.med.esr.service.CommsLetterRequestService#processLetterRulesForAACExport(gov.va.med.esr.common.model.person.Person,
	 *      gov.va.med.esr.common.model.lookup.ComLetterTemplateType,
	 *      java.lang.String,
	 *      gov.va.med.esr.service.trigger.LetterTriggerEvent.MailType,
	 *      java.lang.Boolean, gov.va.med.esr.common.model.comms.CommsLogEntry,
	 *      java.lang.String)
	 */
	public List processLetterRulesForAACExport(Person person,
			ComLetterTemplateType formNum, String workflowCaseId,
			MailType mailType, Boolean override, CommsLogEntry logEntry,
			String categoryType) throws ServiceException {
		return doProcessLetterRules(person, formNum, workflowCaseId, mailType,
				override, logEntry, categoryType,
				CommsLetterRequestService.CONTEXT_AAC_EXPORT);
	}

	private List doProcessLetterRules(Person person,
			ComLetterTemplateType formNum, String workflowCaseId,
			MailType mailType, Boolean override, CommsLogEntry logEntry,
			String categoryType, String context) throws ServiceException {
		// get log list for the person, for rules that require letter history
		List logs = null;
		try {
			logs = this.commsLogDAO.findCommsLogListByPersonId(person
					.getEntityKey().getKeyValueAsString());
		} catch (DAOException dex) {
			logger.error("ERROR to find comms log list with this person" + dex);
			throw new ServiceException(
					"Excption when find Comms log list for a person"
							+ dex.getMessage(), dex);
		}

		// make sure the letter pass the rules by ILog Rule Engine
		// always check the required data element
		ValidationMessages errMsgs1 = this.getRuleValidationService()
				.validateLetterMailing(logEntry, person);
		List errMsgLst1 = convertErrorMessages(errMsgs1);

		// if not overriden, check the conditions to send rules next
		ValidationMessages errMsgs2 = null;
		if (override == null || override.equals(Boolean.FALSE))
			errMsgs2 = this.getRuleValidationService()
					.validateLetterSendRequest(person, formNum, workflowCaseId,
							mailType, override, logs, categoryType, context);
		List errMsgLst2 = convertErrorMessages(errMsgs2);

		List errMsgList = new java.util.ArrayList();
		errMsgList.add(errMsgLst1);
		errMsgList.add(errMsgLst2);

		// update the mailing status and reject reasons if appropreate
		updateLogEntryInMemory(errMsgList, logEntry);

		return errMsgList;
	}

	/**
	 * Cancel an existing AAC letter request before it is sent to AAC. This is
	 * valid only if the mailing status is 'Send to AAC' or 'Send to BPR' (BR
	 * 5271)
	 *
	 * @param commsLogId
	 *            the log id
	 *
	 * @return a Boolean to indicate if the cancel operation is successful.
	 * @throws ServiceException
	 */
	public Boolean cancelLetterRequest(CommsLogEntry commsLogEntry)
			throws ServiceException {
		Validate.notNull(commsLogEntry, "commsLogEntry is required");

		Boolean result = Boolean.FALSE;
		try {
			boolean validScenario = false;
			MailingStatusLink latestMailingStatus = commsLogEntry
					.getLatestMailingStatus();
			if (latestMailingStatus == null
					|| ComMailingStatusType.SEND_TO_AAC.getCode().equals(
							latestMailingStatus.getMailingStatus().getCode())
					|| ComMailingStatusType.SEND_TO_CMS.getCode().equals(
							latestMailingStatus.getMailingStatus().getCode())) {
				validScenario = true;
				commsLogEntry.addMailingStatus(getLookupService()
						.getComMailingStatusTypeByCode(
								ComMailingStatusType.CANCEL_BY_HEC.getCode()));
				this.setSentToIVM(commsLogEntry);

			} else if (ComMailingStatusType.CANCEL_BY_HEC.getCode().equals(
					latestMailingStatus.getMailingStatus().getCode())) {
				validScenario = true;
				// already marked Cancel, no need to change it
			}

			if (validScenario) {
				result = Boolean.TRUE;
				commsLogDAO.update(commsLogEntry);

				// make sure to cancel the print request(s) as well
				String commsLogEntryId = commsLogEntry.getEntityKey()
						.getKeyValueAsString();
				List reqs = aacRequestDAO
						.findAacLetterRequestsByLogId(new BigDecimal(
								commsLogEntryId));

				if (reqs != null && !reqs.isEmpty()) {
					AacLetterRequest req = null;
					String dupChk = null;
					for (int i = 0; i < reqs.size(); i++) {
						req = (AacLetterRequest) reqs.get(i);
						dupChk = req.getCommsPrintRequestDupeCheck();
						if ((dupChk == null)
								|| (!dupChk.equals(commsLogEntryId))) {
							req.setCommsPrintRequestDupeCheck(commsLogEntryId); // reset
																				// this
							aacRequestDAO.saveObject(req);
						}
						// else, this was rejected at the HEC so nothing to do
					}
				}
			}
		} catch (DAOException dex) {
			logger.error("ERROR to cancel a AAC letter request", dex);
			throw new ServiceException(
					"Exception trying to cancel a AAC letter request: "
							+ dex.getMessage(), dex);
		}
		return result;
	}

	/**
	 * Get a list of form numbers that are available for manually mailing to
	 * AAC.
	 *
	 * @return a list of form numbers
	 * @throws ServiceException
	 */
	public synchronized List getAvailableLettersForMailing()
			throws ServiceException {
		if (manualMailingLst == null) {
			try {
				return initTemplateList();
			} catch (DAOException e) {
				throw new ServiceException(
						"Unable to retrieve static list of letters for manual mailing",
						e);
			}
		}
		return manualMailingLst;
	}

	/**
	 * @return Returns the aacRequestDAO.
	 */
	public AacLetterRequestDAO getAacRequestDAO() {
		return aacRequestDAO;
	}

	/**
	 * @param aacRequestDAO
	 *            The aacRequestDAO to set.
	 */
	public void setAacRequestDAO(AacLetterRequestDAO aacRequestDAO) {
		this.aacRequestDAO = aacRequestDAO;
	}

	/**
	 * @return Returns the commsLogDAO.
	 */
	public CommsLogEntryDAO getCommsLogDAO() {
		return commsLogDAO;
	}

	/**
	 * @param commsLogDAO
	 *            The commsLogDAO to set.
	 */
	public void setCommsLogDAO(CommsLogEntryDAO commsLogDAO) {
		this.commsLogDAO = commsLogDAO;
	}

	/**
	 * @return Returns the templDAO.
	 */
	public CommsTemplateDAO getTemplDAO() {
		return templDAO;
	}

	/**
	 * @param templDAO
	 *            The templDAO to set.
	 */
	public void setTemplDAO(CommsTemplateDAO templDAO) {
		this.templDAO = templDAO;
	}

	private List convertErrorMessages(ValidationMessages errMsgs) {
		List lst = new ArrayList();
		if (errMsgs == null || errMsgs.isEmpty())
			return lst;

		Iterator it = errMsgs.get();
		while (it.hasNext())
			lst.add(((ValidationMessage) it.next()).getKey());

		return lst;

	}

	private AacLetterRequest createAacLetterRequest(
			LetterTriggerIdentity letterType, CommsLogEntry logEntry,
			Person person, List errMsgList) {
		AacLetterRequest req = new AacLetterRequest();
		req.setCommsLogEntry(logEntry);
		req.setCommsTemplateFormNumber(letterType.getDataType().getCode());
		String personId = person.getEntityKey().getKeyValueAsString();

		// NOTE: even a letter fails the rules has to be inserted because
		// collecting statistics
		// of "Reject At HEC" is a requirement.

		// if reject at hec, insert unique log id as dup check, bucause logid is
		// unique and a log can not be rejected twice
		if (isRejectAtHec(errMsgList))
			req.setCommsPrintRequestDupeCheck(logEntry.getCommsLogIdString());
		else { // duplicate: same veteran, same form, in the same batch, create
				// a combination key of person id and form number
			/*
			 * StringBuffer buf = new StringBuffer();
			 * buf.append(LetterFormatHelper.padLeft(personId, 21, '0')).append(
			 * AacLetterRequest.DUPE_CHECK_DELIMITER);
			 * buf.append(letterType.getDataType().getCode()).append(
			 * AacLetterRequest.DUPE_CHECK_DELIMITER);
			 * buf.append(letterType.getCategoryType().getName());
			 */
			req.setCommsPrintRequestDupeCheck(createUniqueCode(personId,
					letterType));
		}

		return req;
	}

	private String createUniqueCode(String personId,
			LetterTriggerIdentity letterType) {
		StringBuffer buf = new StringBuffer();
		buf.append(LetterFormatHelper.padLeft(personId, 21, '0')).append(
				AacLetterRequest.DUPE_CHECK_DELIMITER);
		buf.append(letterType.getDataType().getCode()).append(
				AacLetterRequest.DUPE_CHECK_DELIMITER);
		buf.append(letterType.getCategoryType().getName());
		return buf.toString();
	}

	private CommsLogEntry createLogEntry(Person person, CommsTemplate templ,
			String workflowCaseId, LetterTriggerEvent.MailType mailType,
			LetterTrigger.CategoryType categoryType, Boolean override,
			Address mailingAddress, String actionComment, LetterTriggerEvent letterTriggerEvent) throws Exception {

		CommsLogEntry logEntry = new CommsLogEntry();

		// based on form and LetterTrigger, determine recipient
		String formNumber = templ.getCommsTemplateFormNumber();
		String recipient = CommsLogEntry.RECIPIENT_VETERAN;
		if (formNumber.equals(ComLetterTemplateType.FORM_NUMBER_298.getCode())
				|| formNumber.equals(ComLetterTemplateType.FORM_NUMBER_299
						.getCode())
				|| LetterTrigger.CategoryType.SPOUSE_LETTER.getName().equals(
						categoryType.getName())) {
			recipient = CommsLogEntry.RECIPIENT_SPOUSE;
		} else if (LetterTrigger.CategoryType.DEPENDENT_LETTER.getName()
				.equals(categoryType.getName())) {
			recipient = CommsLogEntry.RECIPIENT_DEPENDENT;
		}

		logEntry.setPersonId(new BigDecimal(person.getPersonEntityKey()
				.getKeyValueAsString()));
		logEntry.setRecipient(recipient);
		logEntry.setTemplate(templ);
		logEntry.setLetterType(templ.getLetterType());
		logEntry.setAddress(mailingAddress);
		logEntry.setOverrideIndicator(override);

		logEntry.setName(person.getLegalName());
		logEntry.setSsn(person.getOfficialSsn());
		logEntry.addActionComment(actionComment);

		if (mailType.equals(LetterTriggerEvent.REMAIL_MAIL))
			logEntry.setRemailIndicator(CommsLogEntry.REMAIL_INDICATOR_RESEND);

		// store the trigger type
		logEntry.setComMailingTriggerType(getLookupService()
				.getComMailingTriggerTypeByCode(mailType.getName()));

		if (workflowCaseId != null && workflowCaseId.length() > 0)
			logEntry.setWorkflowCaseId(new BigDecimal(workflowCaseId));

		// Auto letter scenario
		if (letterTriggerEvent != null && ComLetterType.CODE_IVM.getCode().equals(templ.getLetterType().getCode())) {
			logEntry.setIvmCaseClosureDate(letterTriggerEvent.getIvmCaseClosureDate());
			logEntry.setIvmCaseNumber(letterTriggerEvent.getIvmCaseNumber());
		}
		// Re-mail or manual letter scenario
		else if (letterTriggerEvent == null && ComLetterType.CODE_IVM.getCode().equals(templ.getLetterType().getCode()))  {
			if (!ComLetterTemplateType.FORM_NUMBER_683A.getCode().equals(formNumber)) {
				CommsLogEntry mostRecent =  getMostRecentCommsLogByFormWithCompleteIvmData(person, formNumber);
				if (mostRecent != null) {
					logEntry.setIvmCaseClosureDate(mostRecent.getIvmCaseClosureDate());
					logEntry.setIvmCaseNumber(mostRecent.getIvmCaseNumber());
				}
			}
		}
		this.setSentToIVM(logEntry);

		return logEntry;
	}

	private CommsLogEntry getMostRecentCommsLogByFormWithCompleteIvmData(Person person, String form) throws ServiceException {
		if (form == null) {
			return null;
		}
		List<CommsLogEntry> list = null;
		try {
			list = this.commsLogDAO.findCommsLogListByPersonId(person
					.getEntityKey().getKeyValueAsString());
		} catch (DAOException dex) {
			logger.error("ERROR to find comms log list with this person" + dex);
			throw new ServiceException(
					"Excption when find Comms log list for a person"
					+ dex.getMessage(), dex);
		}

		List<CommsLogEntry> filteredList = new ArrayList<CommsLogEntry>();
		for (CommsLogEntry entry : list) {
			if (form.equals(entry.getFormNumber()) &&
					entry.getIvmCaseClosureDate() != null &&
					entry.getIvmCaseNumber() != null) {
				filteredList.add(entry);
			}
		}
		Comparator comparator = new Comparator() {
			public int compare(Object pObject1, Object pObject2)
			{
				CommsLogEntry entry1 = (CommsLogEntry)pObject1;
				CommsLogEntry entry2 = (CommsLogEntry)pObject2;
				if (entry1 == null || entry1.getModifiedOn() == null) return -1;
				if (entry2 == null || entry2.getModifiedOn() == null) return 1;
				return (entry2.getModifiedOn().compareTo(entry1.getModifiedOn()));
			}
		};

		List<CommsLogEntry> orderedEntries = new ArrayList<CommsLogEntry>(filteredList);

		Collections.sort(orderedEntries, comparator);
		CommsLogEntry recent = null;
		if (orderedEntries.size() > 0) {
			recent = (CommsLogEntry)orderedEntries.get(0);
		}
		return recent;
	}

	private void updateLogEntryInMemory(List errMsgs, CommsLogEntry logEntry)
			throws ServiceException {

		// always Arraylist of two arraylist
		if (isRejectAtHec(errMsgs)) {
			// Reject at HEC, set mailing status and hec reject reasons
			logEntry.addMailingStatus(getLookupService()
					.getComMailingStatusTypeByCode(
							ComMailingStatusType.REJECT_AT_HEC.getCode()));

			List msgLst = null;

			// pupulate the two lists of error messages
			for (int j = 0; j < 2; j++) {
				msgLst = (List) errMsgs.get(j);

				for (int i = 0; i < msgLst.size(); i++)
					logEntry.addHecRejectReason((String) msgLst.get(i));
			}
		} else {
			if (logEntry.getEntityKey() == null) {
				/* new log at request time */

				/* default */
				ComMailingStatusType.Code type = ComMailingStatusType.SEND_TO_AAC;
				if (logEntry.getTemplate().getLetterType().getCode()
						.equals(ComLetterType.CODE_HANDBOOK.getCode()))
					type = ComMailingStatusType.SEND_TO_CMS;
				else if ((logEntry.getTemplate().getLetterType().getCode()
						.equals(ComLetterType.CODE_IVM.getCode())))
					type = ComMailingStatusType.SEND_TO_CMS;

				logEntry.addMailingStatus(getLookupService()
						.getComMailingStatusTypeByCode(type.getCode()));
			} else {
				/* existing log at batch time */

				/* default */
				ComMailingStatusType.Code type = ComMailingStatusType.SENT_TO_AAC;
				if (logEntry.getTemplate().getLetterType().getCode()
						.equals(ComLetterType.CODE_HANDBOOK.getCode()))
					type = ComMailingStatusType.SENT_TO_CMS;
				else if ((logEntry.getTemplate().getLetterType().getCode()
						.equals(ComLetterType.CODE_IVM.getCode())))
					type = ComMailingStatusType.SENT_TO_CMS;

				logEntry.addMailingStatus(getLookupService()
						.getComMailingStatusTypeByCode(type.getCode()));
			}
		}
		this.setSentToIVM(logEntry);
	}

	private boolean isRejectAtHec(List errMsgs) {
		// always Arraylist of two arraylists
		return !(((List) errMsgs.get(0)).isEmpty() && ((List) errMsgs.get(1))
				.isEmpty());
	}

	/**
	 * Initializes the list of templates
	 *
	 * @throws DAOException
	 *             if any problems were encountered reading the list of
	 *             templates.
	 */
	protected List initTemplateList() throws DAOException {
		manualMailingLst = new ArrayList();
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_290.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_291.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_292.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_293.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_600C.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_600D.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_620A.getCode()).get(0));

		//Subsequent Rejection Letter ESR 3.6_CodeCR10108
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_623C.getCode()).get(0));

		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_623D.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_640B.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_640C.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_640V.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_640D.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_640G.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_640H.getCode()).get(0));
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_640I.getCode()).get(0));

		//Public Law ESR 3.5_CodeCR10680
		manualMailingLst.add(templDAO.findTemplateListByFormNumber(
				ComLetterTemplateType.FORM_NUMBER_640K.getCode()).get(0));

		return manualMailingLst;
	}

	private void setSentToIVM(CommsLogEntry logEntry) {
		if (ComLetterType.CODE_IVM.getCode().equals(logEntry.getLetterType().getCode())) {
			logEntry.setSentToIVM(Boolean.TRUE);
		}
	}
}
