/*******************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ******************************************************************************/
package gov.va.med.esr.common.rule.parameter;

// Java classes
import gov.va.med.esr.common.clock.Clock;
import gov.va.med.esr.common.infra.ImpreciseDate;
import gov.va.med.esr.common.infra.ImpreciseDateUtils;
import gov.va.med.esr.common.infra.PreciseDateUtils;
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.HandBookMailQueue;
import gov.va.med.esr.common.model.comms.HandBookMailStatus;
import gov.va.med.esr.common.model.comms.MailingStatusLink;
import gov.va.med.esr.common.model.ee.CatastrophicDisability;
import gov.va.med.esr.common.model.ee.CombatEpisode;
import gov.va.med.esr.common.model.ee.Eligibility;
import gov.va.med.esr.common.model.ee.EnrollmentDetermination;
import gov.va.med.esr.common.model.ee.MedicaidFactor;
import gov.va.med.esr.common.model.ee.MilitaryService;
import gov.va.med.esr.common.model.ee.MonetaryBenefit;
import gov.va.med.esr.common.model.ee.MonetaryBenefitAward;
import gov.va.med.esr.common.model.ee.POWEpisode;
import gov.va.med.esr.common.model.ee.PrisonerOfWar;
import gov.va.med.esr.common.model.ee.PurpleHeart;
import gov.va.med.esr.common.model.ee.RatedDisability;
import gov.va.med.esr.common.model.financials.Hardship;
import gov.va.med.esr.common.model.financials.IncomeTest;
import gov.va.med.esr.common.model.lookup.AddressType;
import gov.va.med.esr.common.model.lookup.BadAddressReason;
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.lookup.EligibilityType;
import gov.va.med.esr.common.model.lookup.HandBookMailStatusType;
import gov.va.med.esr.common.model.lookup.Indicator;
import gov.va.med.esr.common.model.lookup.MonetaryBenefitType;
import gov.va.med.esr.common.model.lookup.SpinalCordInjuryType;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.common.model.person.DeathRecord;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.system.SystemParameter;
import gov.va.med.esr.common.persistent.system.SystemParameterDAO;
import gov.va.med.esr.common.rule.CommunicationsInput;
import gov.va.med.esr.common.rule.data.CommsInputData;
import gov.va.med.esr.common.rule.data.PersonSignatureInputData;
import gov.va.med.esr.service.CommsLetterRequestService;
import gov.va.med.esr.service.HandBookService;
import gov.va.med.esr.service.IVMFinancialInfo;
import gov.va.med.esr.service.trigger.BulletinTrigger;
import gov.va.med.esr.service.trigger.BulletinTriggerEvent;
import gov.va.med.esr.service.trigger.LetterTrigger;
import gov.va.med.esr.service.trigger.LetterTriggerEvent;
import gov.va.med.esr.service.trigger.ProcessTriggerEvent;
import gov.va.med.fw.model.EntityKey;
import gov.va.med.fw.rule.RuleException;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.DateUtils;
import gov.va.med.fw.util.StringUtils;

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

import org.apache.commons.lang.builder.EqualsBuilder;

/**
 * Provides a facade to retrieve and process data related to communications
 *
 * @author Vu Le
 * @version 1.0
 */
public class CommunicationsInputParameter extends
		AbstractTriggerAwareInputParameter implements CommunicationsInput,
		LetterTrigger, BulletinTrigger {

	private static final long serialVersionUID = 6278256148059152581L;

	public static final String EMPTY_LINE = "                                        ";

	public static final String TAB = "\t";

	public static final String NEW_LINE = "\n";

	public static final String YES = "Yes";

	public static final String NO = "No";

	public static final String NO_DATA = "No Data";

	// Use for inserting space in bulletin text
	public static final String SPACE = " ";

	private static final String RX_COPAY_TEXT = "RX COPAY = NOT EXEMPT";

	private static final String MT_COPAY_TEXT = "MT COPAY = NOT EXEMPT";

	private static final String GMT_COPAY_TEXT = "GMT COPAY = NOT EXEMPT";

	public static final String COLON = ":";

	private static final String POW_REGISTRY_FROM_DATE = "POW Registry From Date";

	private static final String POW_REGISTRY_TO_DATE = "POW Registry To Date";

	private static final String POW_REGISTRY_CONFINEMENT_LOCATION = "POW Registry Confinement Location";

	private SystemParameterDAO systemParameterDAO = null;;

	private HandBookService handBookService=null;


	/*
	 * Aid & Attendance
	 * Housebound
	 * NSC, VA Pension
	 * Prisoner of War
	 * Purple Heart Recipient
	 * SC Less Than 50% and SC > 0%
	 * SC Less Than 50% and SC = 0% w/check amount > $0
	 * Service Connected 50% to 100%
	 */
	private static final String[]    ivmEligibilityCodeList   = {
		EligibilityType.AID_AND_ATTENDANCE.getName(),
		EligibilityType.HOUSEBOUND.getName(),
		EligibilityType.NSC_VA_PENSION.getName(),
		EligibilityType.PRISONER_OF_WAR.getName(),
		EligibilityType.PURPLE_HEART_RECIPIENT.getName(),
		EligibilityType.SC_LESS_THAN_50_PERCENT.getName(),
		EligibilityType.SERVICE_CONNECTED_50_TO_100_PERCENT.getName()};

	/**
	 * A default constructor
	 */
	public CommunicationsInputParameter() {
		super();
	}

	public SystemParameterDAO getSystemParameterDAO() {
		return systemParameterDAO;
	}

	public void setSystemParameterDAO(SystemParameterDAO systemParameterDAO) {
		this.systemParameterDAO = systemParameterDAO;
	}

	public boolean isHandbookBenefitActive() throws RuleException {
		try {
			SystemParameter sysParameter = getSystemParameterDAO().getByName(
					SystemParameter.HANDBOOK_ACTIVE);
			if (sysParameter != null && sysParameter.getValue() != null) {
				return sysParameter.getValue().equalsIgnoreCase("Y") ? true
						: false;
			}
		} catch (Exception ex) {
			throw new RuleException(
					"Error getting SystemParameter (HANDBOOK_ACTIVE) value", ex);
		}

		return false;
	}

	public boolean isIVMFinalLetterActive() throws RuleException {
		try {
			SystemParameter sysParameter = getSystemParameterDAO().getByName(
					SystemParameter.IVM_FINAL_LETTER_INDICATOR);
			if (sysParameter != null && sysParameter.getValue() != null) {
				return sysParameter.getValue().equalsIgnoreCase("Y") ? true
						: false;
			}
		} catch (Exception ex) {
			throw new RuleException(
					"Error getting SystemParameter (IVM_FINAL_LETTER_INDICATOR) value", ex);
		}

		return false;
	}

	public boolean isHandbookRollOutOver() throws RuleException {
		try {
			SystemParameter sysParameter = getSystemParameterDAO().getByName(
					SystemParameter.HANDBOOK_ROLL_OUT_OVER);
			if (sysParameter != null && sysParameter.getValue() != null) {
				return sysParameter.getValue().equalsIgnoreCase("Y") ? true
						: false;
			}
		} catch (Exception ex) {
			throw new RuleException(
					"Error getting SystemParameter (HANDBOOK_ROLL_OUT_OVER) value", ex);
		}

		return false;
	}

	public void trigger623RLetter() throws RuleException {
		this.triggerAutoLetter(this
				.getComLetterTemplateType(ComLetterTemplateType.FORM_NUMBER_623R
						.getName()));
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerLetterMailing(int)
	 */
	public void triggerLetterMailing(int letter) {
		// TODO remove after new methods are implemented
		// addTriggerEvent( new BulletinTriggerEvent( letter ) );
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerHECReviewBulletin()
	 */
	public void triggerHECReviewBulletin() {
		// This was clarified at last second. The HEC review is actually for
		// ineligible.
		triggerSendIneligibleInfoBulletin();
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerDODDeletionBulletin()
	 */
	public void triggerDODDeletionBulletin() {
		VAFacility facilitySendingMessage = this.getSendingFacility();
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_SITE_VERIFY_DOD_DELETION);
		bte.addField(
				BulletinTriggerEvent.STATION_NUMBER,
				facilitySendingMessage == null ? SPACE : facilitySendingMessage
						.getStationNumber());
		addTriggerEvent(bte);
	}


	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerBadAddressNotificationBulletin(gov.va.med.esr.common.model.lookup.BadAddressReason)
	 */
	public void triggerBadAddressNotificationBulletin(BadAddressReason reason) {
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.BAD_ADDRESS_NOTIFY);
		bte.addField(
				BulletinTriggerEvent.HecNotifyBadAddressField.BAD_ADDRESS_REASON,
				reason != null ? reason.getDescription() : SPACE);
		addTriggerEvent(bte);
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerPurpleHeartIsNoBulletin()
	 */
	public void triggerPurpleHeartIsNoBulletin() {
		PurpleHeart ph = this.getHelperService().getPurpleHeart(
				this.getResultPerson());
		String remark = (ph != null && ph.getRejectionRemark() != null) ? ph
				.getRejectionRemark().getDescription() : SPACE;
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_PURPLE_HEART_NO_DECISION);
		bte.addField(
				BulletinTriggerEvent.HecNotifyPurplseHeartNoDecision.EFFECTIVE_DATE,
				new Date());
		bte.addField(
				BulletinTriggerEvent.HecNotifyPurplseHeartNoDecision.REJECTED_REMARK,
				remark);
		addTriggerEvent(bte);
	}

	public void triggerPOWYesToNoBulletin() {
		if (logger.isDebugEnabled())
			logger.debug("+triggerPOWYesToNoBulletin");

		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_POW_NO);
		addTriggerEvent(bte);

		if (logger.isDebugEnabled())
			logger.debug("-triggerPOWYesToNoBulletin");
	}

	// CCR 11570 Use MSDS data to verify eligibility
	public void triggerHecRecord() throws RuleException {
		ProcessTriggerEvent pte = new ProcessTriggerEvent(ProcessTriggerEvent.CREATE_HEC_RECORD, this.getPersonId());
		addTriggerEvent(pte);
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerPOWNoToYesBulletin()
	 */
	public void triggerPOWNoToYesBulletin() {
		if (logger.isDebugEnabled())
			logger.debug("+triggerPOWNoToYesBulletin");

		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_POW_DISCREPANCY_NOT_POW);
		addTriggerEvent(bte);

		if (logger.isDebugEnabled())
			logger.debug("-triggerPOWNoToYesBulletin");
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerPOWDiscrepantBulletin()
	 */
	public void triggerPOWDiscrepantBulletin() {
		if (logger.isDebugEnabled())
			logger.debug("+triggerPOWDiscrepantBulletin");
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_POW_DISCREPANCY_NOT_MATCH);
		addTriggerEvent(bte);

		if (logger.isDebugEnabled())
			logger.debug("Added BulletinTrigger.DataType.HEC_NOTIFY_POW_DISCREPANCY_NOT_MATCH");

		Person pristinePderson = this.getPristinePerson();
		PrisonerOfWar pow = pristinePderson != null ? pristinePderson
				.getPrisonerOfWar() : null;
		Set powEpisodes = pow != null ? pow.getEpisodes() : new HashSet();
		StringBuffer messageBody = new StringBuffer();

		for (Iterator it = powEpisodes.iterator(); it.hasNext();) {
			POWEpisode powEpisode = (POWEpisode) it.next();
			String captureDate = (powEpisode != null && powEpisode
					.getCaptureDate() != null) ? powEpisode.getCaptureDate()
					.toString() : SPACE;
			String releaseDate = (powEpisode != null && powEpisode
					.getReleaseDate() != null) ? powEpisode.getReleaseDate()
					.toString() : SPACE;
			String confinementLocation = (powEpisode != null && powEpisode
					.getConfinementLocation() != null) ? powEpisode
					.getConfinementLocation().getDescription() : SPACE;
			messageBody.append(POW_REGISTRY_FROM_DATE).append(COLON)
					.append(captureDate);
			messageBody.append(NEW_LINE);
			messageBody.append(POW_REGISTRY_TO_DATE).append(COLON)
					.append(releaseDate);
			messageBody.append(NEW_LINE);
			messageBody.append(POW_REGISTRY_CONFINEMENT_LOCATION).append(COLON)
					.append(confinementLocation);
			messageBody.append(NEW_LINE);
			messageBody.append(NEW_LINE);
		}
		bte.addField(
				BulletinTriggerEvent.HecNotifyPowDiscrepancyNotMatch.POW_EPISODES,
				messageBody.toString());
		if (logger.isDebugEnabled())
			logger.debug("POW Episodes Info: " + messageBody);
		if (logger.isDebugEnabled())
			logger.debug("-triggerPOWDiscrepantBulletin");
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerSendIneligibleInfoBulletin()
	 */
	public void triggerSendIneligibleInfoBulletin() {

		VAFacility facilitySendingMessage = this.getSendingFacility();
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_SITE_SEND_INELIGIBLE_INFO);
		bte.addField(
				BulletinTriggerEvent.STATION_NUMBER,
				facilitySendingMessage == null ? SPACE : facilitySendingMessage
						.getStationNumber());
		addTriggerEvent(bte);
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerVeteranNeedsVerificationBulletin()
	 */
	public void triggerVeteranNeedsVerificationBulletin() {
		VAFacility preferred = this.getResultPerson().getMostRecentPreferredFacility();
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_SITE_VETERAN_VERIFICATION);
		if (preferred != null) {
			bte.addField(
					BulletinTriggerEvent.HecNotifySiteVeteranVerification.PREFERRED_FACILITY,
					preferred.getDescription());
		} else {
			bte.addField(
					BulletinTriggerEvent.HecNotifySiteVeteranVerification.PREFERRED_FACILITY,
					SPACE);
		}

		addTriggerEvent(bte);
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#trigger600CLetter()
	 */
	public void trigger600CLetter() throws RuleException {
		this.triggerAutoLetter(this
				.getComLetterTemplateType(ComLetterTemplateType.FORM_NUMBER_600C
						.getName()));
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#trigger601BLetter()
	 */
	public void trigger601BLetter() throws RuleException {
		this.triggerAutoLetter(this
				.getComLetterTemplateType(ComLetterTemplateType.FORM_NUMBER_601B
						.getName()));
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#trigger620ALetter()
	 */
	public void trigger620ALetter() throws RuleException {
		this.triggerAutoLetter(this
				.getComLetterTemplateType(ComLetterTemplateType.FORM_NUMBER_620A
						.getName()));
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerORFZ11NotMatchPersonBulletin
	 */
	public void triggerORFZ11NotMatchPersonBulletin() throws RuleException {

	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerPersonEligibilityNotVerifiedBulletin
	 */
	public void triggerPersonEligibilityNotVerifiedBulletin()
			throws RuleException {
		triggerPersonEligibilityNotVerifiedBulletin(this.getSendingFacility());
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerPersonEligibilityNotVerifiedBulletin
	 */
	public void triggerPersonEligibilityNotVerifiedBulletin(
			VAFacility facilitySendingMessage) throws RuleException {
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.ELIG_NOT_VERIFIED_CALL_HEC);

		bte.addField(
				BulletinTriggerEvent.STATION_NUMBER,
				facilitySendingMessage == null ? SPACE : facilitySendingMessage
						.getStationNumber());

		addTriggerEvent(bte);
	}


	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#hasReceivedNotificationOfIneligibleStatus()
	 */
	public boolean hasReceivedNotificationOfIneligibleStatus()
			throws RuleException {
		/**
		 * The veteran has been sent a 601B OR 601C letter had been triggered
		 * where the Communication Status is equal to any of the following:
		 * 0-send to AAC 1-sent to AAC 2-mailed by HEC 3-reject at HEC 4-reject
		 * by AAC 5- Error by AAC 6-Return by post office 7-mailed by AAC 8-Sent
		 * to HEC printer 9-address changed and mailed by AAC If the letter has
		 * been triggered and the Communication Status is one of the above THEN
		 * it indicates that an attempt was made to notify the Beneficiary that
		 * he/she is not eligible for enrollment, therefore the system shall
		 * Reject Enrollment. Exit Continuous Enrollment Processing if Rule 6
		 * has been met. ELSE If the letters were NOT triggered and the
		 * Communication Status is null or value 10-Cancel by HEC THEN it is
		 * inferred that the veteran has not received notification of his
		 * ineligible status. Continue executing the rules.
		 *
		 */
		try {
			List logs = this.getCommsLogService().findLogEntriesByPersonId(
					this.getIncomingPerson().getEntityKey()
							.getKeyValueAsString());
			if (logs != null) {
				// List logs = this.getCommsInputData().getCommsLogEntries();
				for (Iterator iter = logs.iterator(); iter.hasNext();) {
					CommsLogEntry log = (CommsLogEntry) iter.next();
					if (ComLetterTemplateType.FORM_NUMBER_601B.equals(log
							.getFormNumber())
							|| ComLetterTemplateType.FORM_NUMBER_601C
									.equals(log.getFormNumber())) {
						MailingStatusLink msl = log.getLatestMailingStatus();
						String mslCode = (msl != null && msl.getMailingStatus() != null) ? msl
								.getMailingStatus().getCode() : null;

						if (mslCode != null) {
							if (ComMailingStatusType.SEND_TO_AAC.getName()
									.equals(mslCode)
									|| ComMailingStatusType.SENT_TO_AAC
											.getName().equals(mslCode)
									|| ComMailingStatusType.SEND_TO_CMS
											.getName().equals(mslCode)
									|| ComMailingStatusType.SENT_TO_CMS
											.getName().equals(mslCode)
									|| ComMailingStatusType.MAILED_BY_HEC
											.getName().equals(mslCode)
									|| ComMailingStatusType.REJECT_AT_HEC
											.getName().equals(mslCode)
									|| ComMailingStatusType.REJECT_BY_AAC
											.getName().equals(mslCode)
									|| ComMailingStatusType.ERROR_BY_AAC
											.getName().equals(mslCode)
									|| ComMailingStatusType.RETURN_BY_POST_OFFICE
											.getName().equals(mslCode)
									|| ComMailingStatusType.SENT_TO_HEC_PRINTER
											.getName().equals(mslCode)
									|| ComMailingStatusType.ADDRESS_CHANGED_AND_MAILED_BY_AAC
											.getName().equals(mslCode)
									|| ComMailingStatusType.MAILED_BY_AAC
											.getName().equals(mslCode))
								return true;
						}
					}
				}
			}

		} catch (ServiceException e) {
			throw new RuleException("Failed to trigger VA Pension bulletin", e);
		}

		return false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getLetterMailingDate()
	 */
	public Date getLetterMailingDate() {
		// return the date that the current correspondence was originally mailed
		return this.getCommsLogEntry() != null ? this.getCommsLogEntry()
				.getMailingDate() : null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#isLetterSentToConfidentialAddress()
	 */
	public boolean isLetterSentToConfidentialAddress() {
		// determine if the letter was mailed to the confidential address of the
		// veteran
		return this.isAddressType(AddressType.CODE_CONFIDENTIAL_ADDRESS,
				this.getMailingAddress());
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#isLetterSentToPermanentAddress()
	 */
	public boolean isLetterSentToPermanentAddress() {
		// determine if the letter was mailed to the permenant address of the
		// veteran
		return this.isAddressType(AddressType.CODE_PERMANENT_ADDRESS,
				this.getMailingAddress());
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#isLetterSentToTemporaryAddress()
	 */
	public boolean isLetterSentToTemporaryAddress() {
		// determine if the letter was mailed to the temporary address of the
		// veteran
		return this.isAddressType(
				AddressType.CODE_TEMPORARY_CORRESPONDENCE_ADDRESS,
				this.getMailingAddress());
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#setLetterAddressEndDate(gov.va.med.esr.common.infra.ImpreciseDate)
	 */
	public void setLetterAddressEndDate(ImpreciseDate endDate) {
		// update the address end date of the address associated with the
		// current correspondence entry
		if (this.getMailingAddress() != null && endDate != null) {
			if (this.getMailingAddress().getStartDate() != null
					&& this.getMailingAddress().getStartDate()
							.compareTo(endDate) > 0) {
				// protects/preserves-logic against same day expiration
				this.getMailingAddress().setStartDate(endDate);
			}
			this.getMailingAddress().setEndDate(endDate);
			this.getCommsInputData().setAddressChanged(true);
		}
	}

	public void expireLetterAddress() {
		/*
		 * This logic must abide by the contract set forth in
		 * MailingAddressHelper.isValidAddress() -since today is active, must
		 * use yesterday to indicate expired
		 */

		setLetterAddressEndDate(ImpreciseDateUtils
				.createImpreciseDateWithoutTime(DateUtils.getYesterdayDate()));
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerRemailingOfLetter()
	 */
	public void triggerRemailingOfLetter() throws RuleException {
		CommsLogEntry commsLogEntry = this.getCommsLogEntry();
		if (commsLogEntry != null) {
			String form = commsLogEntry.getFormNumber();
			if (form != null) {
				ComLetterTemplateType comLetterTemplateType = this
						.getComLetterTemplateType(form);
				this.triggerRemailLetter(comLetterTemplateType);
			}
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getLetterMailedToAddress()
	 */
	public Address getLetterMailedToAddress() {
		// return address where the letter was mailed
		return this.getCommsInputData() != null ? this.getCommsInputData()
				.getMailingAddress() : null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#addCommsErrorMessage(java.lang.String)
	 */
	public void addCommsErrorMessage(String errorMessage) {
		// TODO REMOVE this
	}

	public String getTextForGMTCopayRequired() {
		return GMT_COPAY_TEXT;
	}

	public String getTextForMTCopayRequired() {
		return MT_COPAY_TEXT;
	}

	public String getTextForRXCopay() {
		return RX_COPAY_TEXT;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getLetterFileType()
	 */
	public String getLetterFileType() {
		return (this.getCommsLogEntry() != null && this.getCommsLogEntry()
				.getLetterType() != null) ? this.getCommsLogEntry()
				.getLetterType().getCode() : null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getPristineRemailStatus()
	 */
	public String getPristineRemailStatus() {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#setCommunicationStatus(java.lang.String)
	 */
	public void setCommunicationStatus(String status) throws RuleException {
		this.setCommsStatus(status);
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#setLetterFileType(java.lang.String)
	 */
	public void setLetterFileType(String fileType) throws RuleException {
		try {
			if (this.getCommsLogEntry() != null && fileType != null) {
				this.getCommsLogEntry().setLetterType(
						(ComLetterType) this.getLookupService()
								.getComLetterTypeByCode(fileType));
			}
		}

		catch (ServiceException e) {
			throw new RuleException("Failed to set comms letter type", e);
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#setRemailStatus(java.lang.String)
	 */
	public void setRemailStatus(String status) {
		// TODO not in any irl

	}


	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#updateCommunicationInformation()
	 */
	public void updateCommunicationInformation() {

		// TODO not in any irl
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getBarcodeId()
	 */
	public BigDecimal getBarcodeId() {
		return (this.getCommsLogEntry() != null && this.getCommsLogEntry()
				.getEntityKey() != null) ? new BigDecimal(this
				.getCommsLogEntry().getEntityKey().getKeyValueAsString())
				: null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getVersion()
	 */
	public Long getVersion() {
		return (this.getCommsLogEntry() != null && this.getCommsLogEntry()
				.getVersion() != null) ? new Long(this.getCommsLogEntry()
				.getVersion().longValue()) : null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getVeteranBarcodeId()
	 */
	public BigDecimal getVeteranBarcodeId() {
		CommsLogEntry cle = this.getCommsLogEntry();
		EntityKey key = cle != null ? cle.getEntityKey() : null;
		return key != null ? new BigDecimal(key.getKeyValueAsString()) : null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getVeteranFormNumber()
	 */
	public String getVeteranFormNumber() {
		return this.getCommsLogEntry() != null ? this.getCommsLogEntry()
				.getFormNumber() : null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getVeteranVersion()
	 */
	public Long getVeteranVersion() {
		return (this.getCommsLogEntry() != null && this.getCommsLogEntry()
				.getVersion() != null) ? new Long(this.getCommsLogEntry()
				.getVersion().longValue()) : null;
	}


	/**
	 *
	 */
	public String getFinalLetterCode() {
		IVMFinancialInfo info = this.getCommsInputData() != null ? this.getCommsInputData().getIvmFinancialInfo() : null;
		return info != null ? info.getIvmLetterCode() : null;
	}

	public Date getIvmCaseClosureDate() {
		return getCommsLogEntry() != null ? getCommsLogEntry().getIvmCaseClosureDate() : null;
	}

	public String getIvmCaseNumber() {
		return getCommsLogEntry() != null ? getCommsLogEntry().getIvmCaseNumber() : null;
	}

	@SuppressWarnings("unchecked")
	public boolean meetsIvmEligibilityCriteria(Person person) throws RuleException {
		if (person != null) {
			/*
			 * The person should NOT have any of these eligibilities or factors.
			 *
			 * Examine the LIST for presence:
			 * Aid & Attendance
			 * Housebound
			 * NSC, VA Pension
			 * Prisoner of War
			 * Purple Heart Recipient
			 * Service Connected 50% to 100%
			 * SC Less Than 50% and SC > 0%
			 * SC Less Than 50% and SC = 0% w/check amount > $0
			 *
			 * Have to look at person for the following:
			 * Hardship Effective Date=other than Null (below)
			 * Eligible for Medicaid=Yes (below)
			 * Date of Death Deceased (below)
			 * Catastrophically Disabled (below)
			 */
			EnrollmentDetermination ed = person.getEnrollmentDetermination();
			String code = (ed != null && ed.getPrimaryEligibility() != null) ? ed.getPrimaryEligibility().getType().getCode() : null;

			Set<Eligibility> secondaries = (ed != null)? ed.getSecondaryEligibilities() : null;

			if (code != null && StringUtils.contains(ivmEligibilityCodeList, code)) {
				if (code.equals(EligibilityType.SC_LESS_THAN_50_PERCENT.getCode())) {
					int scPercentage = this.getPercentage(person);
					BigDecimal total = this.getTotalCheckAmount(person);
					if (scPercentage == 0 && total != null && total.compareTo(new BigDecimal(0)) > 0) {
						return false;
					}
					if (scPercentage > 0) {
						return false;
					}
				}
				else
					return false;
			}
			if (secondaries != null) {
				for (Eligibility eligibility : secondaries) {
					if (StringUtils.contains(ivmEligibilityCodeList, eligibility.getType().getCode())) {
						return false;
					}
				}
			}

			// Hardship Effective Date=other than Null
			IncomeTest current = this.getHelperService().getCurrentIncomeTest(person);
			Hardship hardShip = current != null ? current.getHardship() : null;
			if (hardShip != null && hardShip.getHardshipGranted() != null && hardShip.getHardshipGranted().booleanValue()) {
				return false;
			}

			// Eligible for Medicaid=Yes
			if (this.isEligibleForMedicaid(person)) {
				return false;
			}

			// Date of Death Deceased
			DeathRecord dr = person.getDeathRecord();
			if (dr != null && dr.getDeathDate() != null) {
				return false;
			}

			// Catastrophically Disabled
			CatastrophicDisability cd = this.getCatastrophicDisability(person);
			if (cd != null && cd.getCatastrophicallyDisabled() != null && cd.getCatastrophicallyDisabled().booleanValue()) {
				return false;
			}

			return true;
		}
		return false;
	}
	private CatastrophicDisability getCatastrophicDisability(Person person) {
		return (CatastrophicDisability) this.getHelperService()
		.getClinicalDetermination(CatastrophicDisability.class, person);
	}
	private Boolean isEligibleForMedicaid(Person person) {
		MedicaidFactor ma = person.getMedicaidFactor();
		return ma != null ? getNotNull(ma.isEligibleForMedicaid()) : Boolean.FALSE;
	}
	private Boolean getNotNull(Boolean value) {
		return value == null ? Boolean.FALSE : value;
	}

	private BigDecimal getTotalCheckAmount(Person person) throws RuleException {
		MonetaryBenefitAward mba = (person != null) ? person
				.getMonetaryBenefitAward() : null;
				BigDecimal result = (mba != null) ? mba.getCheckAmount() : BigDecimal
						.valueOf(0);
				logger.debug(result);
				return result;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#isLetterSentToPOA()
	 */
	public boolean isLetterSentToPOA() {
		CommsLogEntry cle = this.getCommsLogEntry();
		return cle != null ? CommsLogEntry.RECIPIENT_POA.equals(cle
				.getRecipient()) : false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getCommsLetterFormType()
	 */
	public String getCommsLetterFormType() {
		if (this.getCommsLogEntry() != null) {
			return this.getCommsLogEntry().getFormNumber();
		}
		return (this.getCommsInputData() != null && this.getCommsInputData()
				.getFormNumber() != null) ? this.getCommsInputData()
				.getFormNumber().getCode() : null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getCommsComment()
	 */
	public String getCommsComment() {
		return this.getCommsLogEntry() != null ? this.getCommsLogEntry()
				.getActionComment() : null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getCurrentLetterForm()
	 */
	public String getCurrentLetterForm() {
		CommsLogEntry commsLogEntry = this.getCommsLogEntry();
		if (commsLogEntry != null) {
			return this.getCommsLogEntry().getFormNumber();
		}
		CommsInputData cid = this.getCommsInputData();
		return (cid != null && cid.getFormNumber() != null) ? cid
				.getFormNumber().getCode() : null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getLetterType()
	 */
	public String getLetterType() {
		if (this.getCommsInputData() != null
				&& this.getCommsInputData().getMailType() != null) {
			return this.getCommsInputData().getMailType().getName();
		}
		return null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#hasLetterBeenMailedForSameIncomeYear(java.lang.String)
	 */
	public boolean hasLetterBeenMailedForSameIncomeYear(String letterFormType)
			throws RuleException {
		// Use in Trigger Letter Desision table TBL170. We check the comms log
		// for a letter which is in the status (0-send to aac, 1-sent to aac,
		// 2-mailed by HEC,
		// 7-mailed by AAC). If a letter is found, then the method compares the
		// effective date of
		// the current income test to the date the letter was sent. If it was
		// mailed in same income year
		// effective period then return TRUE
		CommsInputData cid = this.getCommsInputData();
		String context = null;
		if (cid != null) {
			context = cid.getRuleContext();
		}

		// Don't want to evaluate this if in AAC Export process context since it
		// was already checked
		if (letterFormType == null
				|| CommsLetterRequestService.CONTEXT_AAC_EXPORT.equals(context)) {
			return false;
		}

		List logs = this.getCommsLogs(this.getIncomingPerson());

		if (logs != null) {
			for (Iterator iter = logs.iterator(); iter.hasNext();) {
				CommsLogEntry log = (CommsLogEntry) iter.next();
				if (log != null && log.getFormNumber().equals(letterFormType)) {
					MailingStatusLink msl = log.getLatestMailingStatus();
					String code = (msl != null && msl.getMailingStatus() != null) ? msl
							.getMailingStatus().getCode() : null;
					if (code != null
							&& (ComMailingStatusType.SEND_TO_AAC.getName()
									.equals(code)
									|| ComMailingStatusType.SENT_TO_AAC
											.getName().equals(code)
									|| ComMailingStatusType.SEND_TO_CMS
											.getName().equals(code)
									|| ComMailingStatusType.SENT_TO_CMS
											.getName().equals(code)
									|| ComMailingStatusType.MAILED_BY_HEC
											.getName().equals(code) || ComMailingStatusType.MAILED_BY_AAC
									.getName().equals(code))) {
						Date mailingDate = log.getMailingDate();
						Date createdDate = log.getCreatedOn();
						Date compareDate = null;
						if (mailingDate != null)
							compareDate = mailingDate;
						else
							compareDate = createdDate;
						IncomeTest test = this.getHelperService()
								.getCurrentIncomeTest(this.getIncomingPerson());
						if (test != null && compareDate != null) {
							Date incomeTestEffectiveDate = test
									.getEffectiveDate();
							if (incomeTestEffectiveDate != null) {
								GregorianCalendar daysAfter = new GregorianCalendar();
								daysAfter.setTime(incomeTestEffectiveDate);
								daysAfter.add(Calendar.DATE, 365);
								if ((compareDate.getTime() >= incomeTestEffectiveDate
										.getTime())
										&& (compareDate.getTime() < daysAfter
												.getTime().getTime())) {
									return true;
								}

							}
						}
					}
				}
			}
		}
		return false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#isDuplicateMailRequest()
	 */
	public boolean isDuplicateMailRequest() {
		// TODO REMOVE don't need to check from rules
		return false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#isLetterFormPreviouslyMailed(String)
	 */
	public boolean isLetterFormPreviouslyMailed(String letterFormType)
			throws RuleException {
		// need to check comms log for a mailing of the letter form.
		// If a letter is found in the comms log, then it must also check
		// the comms status not in (0-send to aac, 1-sent to aac).
		//
		if (letterFormType != null) {
			ComLetterTemplateType comLetterTemplateType = this
					.getComLetterTemplateType(letterFormType);
			return this
					.hasLetterFormBeenPreviouslyMailed(comLetterTemplateType);
		}
		return false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#isLetterFormPreviouslyMailedExcludeMailedStatus(java.lang.String)
	 */
	public boolean isLetterFormPreviouslyMailedExcludeMailedStatus(String type)
			throws RuleException {
		// need to check comms log for a mailing of the letter form.
		// If a letter is found in the comms log, then it must also check
		// the comms status not in (0-send to aac, 1-sent to aac, 2-mailed by
		// hec,
		// 7-mailed by aac).
		//
		if (type == null) {
			return false;
		}

		List logs = this.getCommsLogs(this.getIncomingPerson());
		if (logs != null) {
			for (Iterator iter = logs.iterator(); iter.hasNext();) {
				CommsLogEntry log = (CommsLogEntry) iter.next();
				if (type.equals(log.getFormNumber())) {
					MailingStatusLink msl = log.getLatestMailingStatus();
					String mslCode = (msl != null && msl.getMailingStatus() != null) ? msl
							.getMailingStatus().getCode() : null;
					if (mslCode != null) {
						if (!ComMailingStatusType.SEND_TO_AAC.getName().equals(
								mslCode)
								&& !ComMailingStatusType.SENT_TO_AAC.getName()
										.equals(mslCode)
								&& !ComMailingStatusType.SEND_TO_CMS.getName()
										.equals(mslCode)
								&& !ComMailingStatusType.SENT_TO_CMS.getName()
										.equals(mslCode)
								&& !ComMailingStatusType.MAILED_BY_HEC
										.getName().equals(mslCode)
								&& !ComMailingStatusType.MAILED_BY_AAC
										.getName().equals(mslCode)
								&& !ComMailingStatusType.MAILED_BY_CMS
								.getName().equals(mslCode))
							return true;
					}

				}
			}
		}
		return false;

	}

	public boolean isiMostRecent680NReturnByPostOffice() throws RuleException {
		// VFA: UC463.23 remail for 680N
		// Re-mailing: Yes, if the most recent 680N letter is status = ‘Returned
		// by Post Office’

		List logs = this.getCommsLogs(this.getIncomingPerson());

		// the log entries are sorted in decendending order of log id: most
		// recent first
		if (logs != null) {
			for (Iterator iter = logs.iterator(); iter.hasNext();) {
				CommsLogEntry log = (CommsLogEntry) iter.next();
				if (ComLetterTemplateType.FORM_NUMBER_680N.equals(log
						.getFormNumber())) {
					MailingStatusLink msl = log.getLatestMailingStatus();
					String mslCode = (msl != null && msl.getMailingStatus() != null) ? msl
							.getMailingStatus().getCode() : null;
					if (mslCode != null) {
						if (ComMailingStatusType.RETURN_BY_POST_OFFICE
								.getName().equals(mslCode))
							return true; // the most recent 680N is returned by
											// post office
						else
							return false; // the most recent 680N is not
											// returned by post office
					}
				}
			}
		}
		return false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#hasLetterBeenMailedIncludingMailedStatus(java.lang.String)
	 */
	public boolean hasLetterBeenMailedIncludingMailedStatus(
			String letterFormType) throws RuleException {
		if (letterFormType == null) {
			return false;
		}

		// CodeCR12858 different behavior for compassion letter
		if (ComLetterTemplateType.FORM_NUMBER_683A.getCode().equals(letterFormType)) {
			return this.hasCompassionLetterBeenPreviouslyMailed(letterFormType);
		}

		List logs = this.getCommsLogs(this.getIncomingPerson());
		if (logs != null) {
			for (Iterator iter = logs.iterator(); iter.hasNext();) {
				CommsLogEntry log = (CommsLogEntry) iter.next();
				if (letterFormType.equals(log.getFormNumber())) {
					MailingStatusLink msl = log.getLatestMailingStatus();
					String mslCode = (msl != null && msl.getMailingStatus() != null) ? msl
							.getMailingStatus().getCode() : null;
					if (mslCode != null) {
						if (ComMailingStatusType.SEND_TO_AAC.getName().equals(
								mslCode)
								|| ComMailingStatusType.SENT_TO_AAC.getName()
										.equals(mslCode)
								|| ComMailingStatusType.SEND_TO_CMS.getName()
										.equals(mslCode)
								|| ComMailingStatusType.SENT_TO_CMS.getName()
										.equals(mslCode)
								|| ComMailingStatusType.MAILED_BY_HEC.getName()
										.equals(mslCode)
								|| ComMailingStatusType.MAILED_BY_AAC.getName()
										.equals(mslCode)
								|| ComMailingStatusType.MAILED_BY_CMS.getName()
										.equals(mslCode))
							return true;
					}

				}
			}
		}
		return false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#isUserOverridingMailRequest()
	 */
	public boolean isUserOverridingMailRequest() {
		return this.getCommsInputData() != null ? this.getCommsInputData()
				.isOverrideRequest() : false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#setCommsComment(java.lang.String)
	 */
	public void setCommsComment(String comment) {
		if (this.getResultCommsLogEntry() != null) {
			this.getResultCommsLogEntry().setActionComment(comment);
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#setCommsStatusUpdateDate(java.util.Date)
	 */
	public void setCommsStatusUpdateDate(Date updateDate) {
		// TODO REMOVE
		// NOTE: Not needed since Hibernate sets created on date
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getCommsStatus()
	 */
	public String getCommsStatus() {
		return this.getCommsStatus(this.getCommsLogEntry());
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#setCommsStatus(java.lang.String)
	 */
	public void setCommsStatus(String communicationStatus) throws RuleException {
		if (communicationStatus == null) return;

		if (this.getResultCommsLogEntry() != null) {
			try {
				ComMailingStatusType status = this.getLookupService()
						.getComMailingStatusTypeByCode(communicationStatus);
				this.getResultCommsLogEntry().addMailingStatus(status);
			} catch (ServiceException e) {
				throw new RuleException("Failed to set comms status", e);
			}
		} else if (this.getResultHandbookMailQueue() != null) {
			// CCR11306 update to handle undelieverable handbook
			HandBookMailStatus status = this.getResultHandbookMailQueue().getHandBookMailStatus();
			if (status == null) {
				throw new RuleException("Handbook status is null. HandBookMailQueue id = " + this.getResultHandbookMailQueue().getId());
			}
			try {
				HandBookMailStatusType statusType = this.getLookupService()
						.getHandBookMailingStatusTypeByCode(communicationStatus);
				status.setStatusType(statusType);
			} catch (ServiceException e) {
				throw new RuleException("Failed to set comms status for handbook mail queue", e);
			}
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#isLetterFormPreviouslyMailedDaysAgo(java.lang.String,
	 *      int)
	 */
	public boolean isLetterFormPreviouslyMailedDaysAgo(String letterFormType,
			int daysAgo) throws RuleException {
		boolean result = false;
		if (letterFormType == null)
			return false;

		ComLetterTemplateType comLetterTemplateType = this
				.getComLetterTemplateType(letterFormType);
		if (comLetterTemplateType != null) {
			List logs = null;
			try {
				logs = this.getCommsLogService().findLogEntriesByPersonId(
						this.getIncomingPerson().getEntityKey()
								.getKeyValueAsString());
			} catch (ServiceException e) {
				throw new RuleException("Failed to get CommsLog", e);
			}

			if (logs != null && logs.size() > 0) {
				for (Iterator iter = logs.iterator(); iter.hasNext();) {
					CommsLogEntry log = (CommsLogEntry) iter.next();
					if (comLetterTemplateType.getCode().equals(
							log.getFormNumber())
							&& recipientMatches(log)) {
						MailingStatusLink msl = log.getLatestMailingStatus();
						String code = (msl != null && msl.getMailingStatus() != null) ? msl
								.getMailingStatus().getCode() : null;
						if (!ComMailingStatusType.SEND_TO_AAC.getName().equals(
								code)
								&& !ComMailingStatusType.SENT_TO_AAC.getName()
										.equals(code)
								&& !ComMailingStatusType.SEND_TO_CMS.getName()
										.equals(code)
								&& !ComMailingStatusType.SENT_TO_CMS.getName()
										.equals(code)) {
							Date mailDate = log.getMailingDate();
							if (mailDate != null) {
								Calendar to = Calendar.getInstance();
								to.setTime(new Date());
								Calendar from = Calendar.getInstance();
								from.setTime(mailDate);
								result = PreciseDateUtils.getDays(from, to)
										.compareTo(new Long(daysAgo)) >= 0;
							}
						}
					}
					if (result) {
						break;
					}
				}
			}

		}

		return result;
	}

	private boolean recipientMatches(CommsLogEntry log) {
		String recipient = log.getRecipient();
		String subject = this.getSubject();
		if (recipient != null) {
			if ((CommsLogEntry.RECIPIENT_VETERAN.equals(recipient) && AacLetterRequest.VETERAN_LETTER
					.equals(subject))
					|| (CommsLogEntry.RECIPIENT_SPOUSE.equals(recipient) && AacLetterRequest.SPOUSE_LETTER
							.equals(subject))
					|| (CommsLogEntry.RECIPIENT_DEPENDENT.equals(recipient) && AacLetterRequest.DEPENDENT_LETTER
							.equals(subject))
					|| CommsLogEntry.RECIPIENT_POA.equals(recipient)) {
				return true;
			}

		}
		return false;
	}

	/*
	 * private boolean hasLetterFormBeenPreviouslyMailed(ComLetterTemplateType
	 * type) throws RuleException { if (type == null) { return false; }
	 *
	 * return false; }
	 */
	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerLetterMailing(java.lang.String)
	 */
	public void triggerLetterMailing(String letterFormType)
			throws RuleException {
		if (letterFormType != null) {
			if (getSubject() != null
					&& (ComLetterTemplateType.FORM_NUMBER_290.getCode().equals(
							letterFormType)
							|| ComLetterTemplateType.FORM_NUMBER_291.getCode()
									.equals(letterFormType)
							|| ComLetterTemplateType.FORM_NUMBER_292.getCode()
									.equals(letterFormType) || ComLetterTemplateType.FORM_NUMBER_293
							.getCode().equals(letterFormType))) {
				this.triggerAutoLetter(
						this.getComLetterTemplateType(letterFormType),
						getSubject());
			} else {
				this.triggerAutoLetter(this
						.getComLetterTemplateType(letterFormType));
			}
		}
	}

	public void triggerLetterMailing(ComLetterTemplateType type) {
		this.triggerAutoLetter(type);
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#has30DaySSNClockExpired()
	 */
	public boolean has30DaySSNClockExpired() {
		Clock.Type fired = this.getFiredClockType();
		return fired != null ? Clock.Type.SSN_30_DAY_VALIDATION_CLOCK.getName()
				.equals(fired.getName()) : false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#start30DaySSNClock()
	 */
	public void start30DaySSNClock() throws RuleException {
		try {
			this.getScheduledTaskService().startSSNInvalidLetterClock(
					this.getResultPerson(), this.getSubject());
		} catch (ServiceException e) {
			throw new RuleException("Failed to start SSN 30 day clock", e);
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#start30DaySSNClock(java.lang.String)
	 */
	public void start30DaySSNClock(String subject) throws RuleException {
		try {
			this.getScheduledTaskService().startSSNInvalidLetterClock(
					this.getResultPerson(), subject);
		} catch (ServiceException e) {
			throw new RuleException("Failed to start SSN 30 day clock", e);
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#
	 * triggerEGTProcessingEndBulletin()
	 */
	public void triggerEGTProcessingEndBulletin() {
		// TODO Auto-generated method stub

	}

	/*
	 * (non-Javadoc)
	 *
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#
	 * triggerEGTProcessingStartBulletin()
	 */
	public void triggerEGTProcessingStartBulletin() {
		// TODO Auto-generated method stub

	}

	/*
	 * (non-Javadoc)
	 *
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#
	 * triggerEGTProcessingStoppedBulletin()
	 */
	public void triggerEGTProcessingStoppedBulletin() {
		// TODO Auto-generated method stub

	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#has30DayPseudoSSNReasonClockExpired()
	 */
	public boolean has30DayPseudoSSNReasonClockExpired() {
		Clock.Type fired = this.getFiredClockType();
		return fired != null ? Clock.Type.SSN_30_DAY_PSEUDO_SSN_REASON_CLOCK
				.getName().equals(fired.getName()) : false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#has30DayPseudoSSNVerificationClockExpired()
	 */
	public boolean has30DayPseudoSSNVerificationClockExpired() {
		Clock.Type fired = this.getFiredClockType();
		return fired != null ? Clock.Type.SSN_30_DAY_PSEUDO_SSN_VERIFICATION_CLOCK
				.getName().equals(fired.getName()) : false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#start30DayPseudoSSNReasonClock()
	 */
	public void start30DayPseudoSSNReasonClock(String subject)
			throws RuleException {
		try {
			this.getScheduledTaskService().startPseudoSSNReasonClock(
					this.getResultPerson(), subject);
		} catch (ServiceException e) {
			throw new RuleException("Failed to start Pseudo SSN 30 day clock",
					e);
		}

	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#start30DayPseudoSSNVerificationClock()
	 */
	public void start30DayPseudoSSNVerificationClock(String subject)
			throws RuleException {
		try {
			this.getScheduledTaskService().startPseudoSSNVerificationClock(
					this.getResultPerson(), subject);
		} catch (ServiceException e) {
			throw new RuleException("Failed to start Pseudo SSN 30 day clock",
					e);
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#start30DayPseudoSSNReasonClock()
	 */
	public void start30DayPseudoSSNReasonClock() throws RuleException {
		try {
			this.getScheduledTaskService().startPseudoSSNReasonClock(
					this.getResultPerson(), this.getSubject());
		} catch (ServiceException e) {
			throw new RuleException("Failed to start Pseudo SSN 30 day clock",
					e);
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#start30DayPseudoSSNVerificationClock()
	 */
	public void start30DayPseudoSSNVerificationClock() throws RuleException {
		try {
			this.getScheduledTaskService().startPseudoSSNVerificationClock(
					this.getResultPerson(), this.getSubject());
		} catch (ServiceException e) {
			throw new RuleException("Failed to start Pseudo SSN 30 day clock",
					e);
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#triggerSSARecordNotMatchBulletin()
	 */
	public void triggerSSARecordNotMatchBulletin() {
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_SSA_RECORD_NOT_MATCH_HEC_INTERNAL_ID);
		// SSNVerificationResponseInputData dataAware = null;
		// if (getBaseData() instanceof SSNVerificationResponseInputData) {
		// dataAware = (SSNVerificationResponseInputData)getBaseData();
		// }
		addTriggerEvent(bte);
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#cancel30DayPseudoSSNReasonClock()
	 */
	public void cancel30DayPseudoSSNReasonClock() throws RuleException {
		try {
			this.getScheduledTaskService().cancelClock(
					this.getIncomingPerson(),
					Clock.Type.SSN_30_DAY_PSEUDO_SSN_REASON_CLOCK,
					Clock.Group.SSN_CLOCK_GROUP, this.getSubject());
		} catch (ServiceException e) {
			throw new RuleException("Failed to cancel SSN letter clock", e);
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#cancel72HourMilServQueryClock()
	 */
	public void cancel72HourMilServQueryClock() throws RuleException {
		//no longer cancel the clock as we have other events now attached
		//to the 72 hour MSDS clock that need to be handled even if response was recieved
		/*try {
			this.getScheduledTaskService().cancelClock(
					this.getIncomingPerson(),
					Clock.Type.WF_72_HOUR_CLOCK,
					Clock.Group.WF_CLOCK_GROUP);
		} catch (ServiceException e) {
			throw new RuleException("Failed to cancel Mil Serv Query clock", e);
		}*/
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#start72HourMilServQueryClock()
	 */
	public void start72HourMilServQueryClock() throws RuleException {
		try {
			this.getScheduledTaskService().startMilServiceQueryClock(this.getIncomingPerson());
		} catch (ServiceException e) {
			throw new RuleException("Failed to start Mil Service Query Pending clock",
					e);
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#cancel30DayPseudoSSNVerificationClock()
	 */
	public void cancel30DayPseudoSSNVerificationClock() throws RuleException {
		try {
			this.getScheduledTaskService().cancelClock(
					this.getIncomingPerson(),
					Clock.Type.SSN_30_DAY_PSEUDO_SSN_VERIFICATION_CLOCK,
					Clock.Group.SSN_CLOCK_GROUP, this.getSubject());
		} catch (ServiceException e) {
			throw new RuleException("Failed to cancel SSN letter clock", e);
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getPristineCommsStatus()
	 */
	public String getPristineCommsStatus() {
		// Return comms status on file - make sure getCommsStatus
		// returns the received value.
		return this.getCommsStatus(this.getPristineCommsLogEntry());
	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#setHECRejectReasons()
	 */
	public void setHECRejectReasons() {
		// TODO REMOVE since unused
		// Captures the Comms Error messages returned
		// From the Letter Validation and ProcessLetterSend Request
		// rules.

	}

	// Private Methods

	private String getAddedRatedDisabilityCodeList() {
		Set a = this.getRatedDisabilities(this.getIncomingPerson());
		Set b = this.getRatedDisabilities(this.getPristinePerson());
		boolean aIsEmpty = (a != null && a.size() > 0) ? false : true;
		boolean bIsEmpty = (b != null && b.size() > 0) ? false : true;

		String added = SPACE;
		Set addedSet = new HashSet();

		if (aIsEmpty && bIsEmpty) {
			// No adds
			return added;
		}

		if (aIsEmpty && !bIsEmpty) {
			// No adds
			return added;
		}

		if (!aIsEmpty && bIsEmpty) {
			// Add all the incoming RD
			Iterator iter = a.iterator();
			while (iter.hasNext()) {
				RatedDisability ra = (RatedDisability) iter.next();
				addedSet.add(this.getRDCodeAndDescription(ra));
			}
			if (addedSet.size() > 0) {
				added = StringUtils.join(addedSet.toArray(), ", ");
			}
			return added;
		}

		Iterator iter = a.iterator();
		while (iter.hasNext()) {
			RatedDisability ra = (RatedDisability) iter.next();
			Iterator iterB = b.iterator();
			boolean found = false;
			while (iterB.hasNext()) {
				RatedDisability rb = (RatedDisability) iterB.next();
				if (compareRatedDisability(ra, rb)) {
					found = true;
					break;
				}
			}
			if (!found) {
				addedSet.add(this.getRDCodeAndDescription(ra));
			}
		}
		if (addedSet != null && addedSet.size() > 0) {
			added = StringUtils.join(addedSet.toArray(), ", ");
		}
		return added;
	}

	private String getRemovedRatedDisabilityCodeList() {
		Set a = this.getRatedDisabilities(this.getIncomingPerson());
		Set b = this.getRatedDisabilities(this.getPristinePerson());
		boolean aIsEmpty = (a != null && a.size() > 0) ? false : true;
		boolean bIsEmpty = (b != null && b.size() > 0) ? false : true;
		String removed = SPACE;
		Set removedSet = new HashSet();

		if (aIsEmpty && bIsEmpty) {
			// No removals
			return removed;
		}

		if (aIsEmpty && !bIsEmpty) {
			Iterator iter = b.iterator();
			while (iter.hasNext()) {
				RatedDisability rb = (RatedDisability) iter.next();
				removedSet.add(this.getRDCodeAndDescription(rb));
			}
			if (removedSet.size() > 0) {
				removed = StringUtils.join(removedSet.toArray(), ", ");
			}
			return removed;
		}

		if (!aIsEmpty && bIsEmpty) {
			// No removals
			return removed;
		}

		Iterator iter = b.iterator();
		while (iter.hasNext()) {
			RatedDisability rb = (RatedDisability) iter.next();
			Iterator iterA = a.iterator();
			boolean found = false;
			while (iterA.hasNext()) {
				RatedDisability ra = (RatedDisability) iterA.next();
				if (compareRatedDisability(ra, rb)) {
					found = true;
					break;
				}
			}
			if (!found) {
				removedSet.add(this.getRDCodeAndDescription(rb));
			}

		}

		if (removedSet.size() > 0) {
			removed = StringUtils.join(removedSet.toArray(), ", ");
		}
		return removed;
	}

	private boolean compareRatedDisability(RatedDisability a, RatedDisability b) {
		return ((a == null) && (b == null))
				|| ((a != null && b != null) && new EqualsBuilder().append(
						a.getDisability(), b.getDisability()).isEquals());
	}

	private Set getRatedDisabilities(Person person) {
		if (person != null && person.getServiceConnectionAward() != null) {
			return person.getServiceConnectionAward().getRatedDisabilities();
		}
		return null;
	}

	private String getRDCodeAndDescription(RatedDisability rd) {
		if (rd == null) {
			return SPACE;
		}
		String code = (rd.getDisability() != null) ? rd.getDisability()
				.getCode() : SPACE;
		String desc = (rd.getDisability() != null) ? rd.getDisability()
				.getName() : SPACE;
		return code + " " + desc;
	}

	public void triggerAutoLetter(ComLetterTemplateType type) {
		this.triggerLetter(type, LetterTriggerEvent.AUTO_MAIL, null);
	}

	public void triggerAutoLetter(ComLetterTemplateType type, String subject) {
		this.triggerLetter(type, LetterTriggerEvent.AUTO_MAIL, subject);
	}

	public void triggerRemailLetter(ComLetterTemplateType type) {
		this.triggerLetter(type, LetterTriggerEvent.REMAIL_MAIL, null);
	}

	private void triggerLetter(ComLetterTemplateType type,
			LetterTriggerEvent.MailType mailType, String subject) {
		// Don't trigger letter if in review mode
		if (this.getBaseData() != null && this.getBaseData().isReviewOnly()) {
			return;
		}
		LetterTriggerEvent lte = null;
		if (subject != null) {
			if (AacLetterRequest.DEPENDENT_LETTER.equals(subject))
				lte = new LetterTriggerEvent(type,
						LetterTrigger.CategoryType.DEPENDENT_LETTER);
			else if (AacLetterRequest.SPOUSE_LETTER.equals(subject))
				lte = new LetterTriggerEvent(type,
						LetterTrigger.CategoryType.SPOUSE_LETTER);
			else if (AacLetterRequest.VETERAN_LETTER.equals(subject))
				lte = new LetterTriggerEvent(type,
						LetterTrigger.CategoryType.VETERAN_LETTER);
		}

		// use default of veteran for subject
		if (lte == null) {
			lte = new LetterTriggerEvent(type);
		}
		// CodeCR12825
		if (this.isCompassionLetter(type.getCode()) || this.isIVMFinalLetter(type.getCode())) {
			this.updateLetterTriggerEventForCms(lte);
		}

		lte.setMailType(mailType);
		addTriggerEvent(lte);
	}

	private Address getMailingAddress() {
		return this.getCommsInputData() != null ? this.getCommsInputData()
				.getMailingAddress() : null;
	}

	private boolean isAddressType(AddressType.Code addressType, Address address) {
		return address != null ? address.getType().getCode()
				.equals(addressType.getName()) : false;
	}

	private CommsLogEntry getCommsLogEntry() {
		return this.getCommsInputData() != null ? this.getCommsInputData()
				.getCommsLogEntry() : null;
	}

	private CommsLogEntry getResultCommsLogEntry() {
		return this.getCommsInputData() != null ? this.getCommsInputData()
				.getResultCommsLogEntry() : null;
	}

	/**
	 * CCR11306 -- update to cover handbook
	 * @return
	 */
	private HandBookMailQueue getHandbookMailQueue() {
		return this.getCommsInputData() != null ? this.getCommsInputData()
				.getHandbookMailQueue() : null;
	}

	private HandBookMailQueue getResultHandbookMailQueue() {
		return this.getCommsInputData() != null ? this.getCommsInputData()
				.getResultHandbookMailQueue() : null;
	}

	private CommsLogEntry getPristineCommsLogEntry() {
		return this.getCommsInputData() != null ? this.getCommsInputData()
				.getPristineCommsLogEntry() : null;
	}

	private PersonSignatureInputData getPersonSignatureInputData() {
		if (this.getRuleDataAware() instanceof PersonSignatureInputData) {
			return (PersonSignatureInputData) this.getRuleDataAware();
		}
		return null;
	}

	private String getCommsStatus(CommsLogEntry entry) {
		MailingStatusLink mailStatusLink = entry.getLatestMailingStatus();
		ComMailingStatusType latestStatus = mailStatusLink != null ? mailStatusLink
				.getMailingStatus() : null;
		return latestStatus != null ? latestStatus.getCode() : null;
	}

	private boolean hasLetterFormBeenPreviouslyMailed(ComLetterTemplateType type)
			throws RuleException {
		if (type == null) {
			return false;
		}
		List logs = null;
		if (this.getCommsInputData() != null
				&& this.getCommsInputData().getCommsLogEntries() != null) {
			logs = this.getCommsInputData().getCommsLogEntries();
		} else {
			try {
				logs = this.getCommsLogService().findLogEntriesByPersonId(
						this.getIncomingPerson().getEntityKey()
								.getKeyValueAsString());
			} catch (ServiceException e) {
				throw new RuleException("Failed to get CommsLog", e);
			}
		}

		if (logs != null && logs.size() > 0) {
			for (Iterator iter = logs.iterator(); iter.hasNext();) {
				CommsLogEntry log = (CommsLogEntry) iter.next();
				if (type.getCode().equals(log.getFormNumber())) {
					MailingStatusLink msl = log.getLatestMailingStatus();
					String code = (msl != null && msl.getMailingStatus() != null) ? msl
							.getMailingStatus().getCode() : null;
					if (!ComMailingStatusType.SEND_TO_AAC.getName()
							.equals(code)
							&& !ComMailingStatusType.SENT_TO_AAC.getName()
									.equals(code)
							&& !ComMailingStatusType.SEND_TO_CMS.getName()
									.equals(code)
							&& !ComMailingStatusType.SENT_TO_CMS.getName()
									.equals(code)) {
						return true;
					}
				}
			}
		}

		return false;
	}

	/**
	 * This is only for CMS target letters at the moment.
	 * This method was based on hasLetterFormBeenPreviouslyMailed, which has
	 * a behavior that acts more like "a letter has NOT been PreviouslyMailed".
	 * In order to not break rules that need that behavior, this new method will be
	 * used to actually show "a letter has been PreviouslyMailed."
	 *
	 * This method returns true if a letter has been previously mailed and
	 * false if the letter has NOT been previously mailed.
	 *
	 * We have to limit check to Sent to CMS and Mailed by CMS otherwise the
	 * actual letter would never get sent by batch job.
	 *
	 * @param letter
	 * @return
	 * @throws RuleException
	 */
	private boolean hasCompassionLetterBeenPreviouslyMailed(String letter) throws RuleException {
		ComLetterTemplateType type = this.getComLetterTemplateType(letter);
		if (type == null || !ComLetterTemplateType.FORM_NUMBER_683A.getCode().equals(letter)) {
			return false;
		}
		List logs = null;
		if (this.getCommsInputData() != null
				&& this.getCommsInputData().getCommsLogEntries() != null) {
			logs = this.getCommsInputData().getCommsLogEntries();
		} else {
			try {
				logs = this.getCommsLogService().findLogEntriesByPersonId(this.getIncomingPerson().getEntityKey().getKeyValueAsString());
			} catch (ServiceException e) {
				throw new RuleException("Failed to get CommsLog", e);
			}
		}

		if (logs != null && logs.size() > 0) {
			for (Iterator iter = logs.iterator(); iter.hasNext();) {
				CommsLogEntry log = (CommsLogEntry) iter.next();
				if (type.getCode().equals(log.getFormNumber())) {
					MailingStatusLink msl = log.getLatestMailingStatus();
					String code = (msl != null && msl.getMailingStatus() != null) ? msl.getMailingStatus().getCode() : null;
					if (ComMailingStatusType.MAILED_BY_CMS.getName().equals(code) ||
							ComMailingStatusType.SENT_TO_CMS.getName().equals(code)) {
						return true;
					}
				}
			}
		}

		return false;
	}

	private ComLetterTemplateType getComLetterTemplateType(String type)
			throws RuleException {
		if (type != null) {
			try {

				return this.getLookupService().getComLetterTemplateTypeByCode(
						type);
			} catch (ServiceException e) {
				throw new RuleException(
						"Failed to get com letter template type", e);
			}
		}
		return null;
	}

	public void trigger290SSNValidationLetter() throws RuleException {
		this.triggerAutoLetter(this
				.getComLetterTemplateType(ComLetterTemplateType.FORM_NUMBER_290
						.getName()), this.getSubject());
	}

	public void triggerInconsistantOEFOIFCombatEpisodeData(
			VAFacility facilitySendingMessage) throws RuleException {
		triggerInconsistantOEFOIFCombatEpisodeData("", facilitySendingMessage);
	}

	/**
	 * Trigger email when inconsitant data is received in HL7 message for
	 * oef/oif episodes
	 */
	public void triggerInconsistantOEFOIFCombatEpisodeData(String dfn,
			VAFacility facilitySendingMessage) throws RuleException {
		Person pristine = getResultPerson();
		MilitaryService ms = pristine.getMilitaryService();
		Set combatEpisodes = ms.getCombatEpisodes();

		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.INCONS_CONFLICT_DATA_FROM_SITE);
		bte.addField(
				BulletinTriggerEvent.STATION_NUMBER,
				facilitySendingMessage == null ? SPACE : facilitySendingMessage
						.getStationNumber());

		// Build episodes information
		StringBuffer messageBody = new StringBuffer();

		if (combatEpisodes != null && combatEpisodes.size() > 0) {
			// create the table
			for (Iterator i = combatEpisodes.iterator(); i.hasNext();) {
				CombatEpisode episode = (CombatEpisode) i.next();
				String location = episode.getConflictLocation() == null ? ""
						: episode.getConflictLocation().getName();
				String startDate = formatImpreciseDate(episode.getStartDate());
				String endDate = formatImpreciseDate(episode.getEndDate());
				String source = episode.getOEFOIFSource() == null ? SPACE
						: episode.getOEFOIFSource().getName();
				messageBody.append(formatString(location, 30));
				messageBody.append(formatString(startDate, 20));
				messageBody.append(formatString(endDate, 20));
				messageBody.append(formatString(source, 30));
				messageBody.append(NEW_LINE);
				// Facility is not included here
			}
		} else {
			messageBody.append("None");// empty line
			messageBody.append(NEW_LINE);// empty line
		}
		bte.addField(
				BulletinTriggerEvent.InconsistentConflictDatafromSite.OEFOIF_EPISODES,
				messageBody.toString());

		addTriggerEvent(bte);
	}

	public void triggerAppointmentConversionCompleteBulletin()
			throws RuleException {
		// TODO Auto-generated method stub

	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#getMailingAddressTypeCode()
	 */
	public String getMailingAddressTypeCode() {
		Address address = this.getMailingAddress();
		return (address != null && address.getType() != null) ? address
				.getType().getCode() : null;
	}

	private String formatString(String str, int length) {
		if (str == null || str.length() == 0) {
			return EMPTY_LINE.substring(0, length);
		} else if (str.length() >= length) {
			return str;
		} else {
			return str + EMPTY_LINE.substring(0, length - str.length());
		}
	}

	private String formatImpreciseDate(ImpreciseDate date) {
		if (date == null)
			return SPACE;
		if (date.isPrecise())
			return DateUtils.format(date.getDate(), DateUtils.MMDDYYYY);
		return date.toStandardFormat();
	}

	/*
	 * private String getVisits(Person person) { String result = " "; if (person
	 * != null) { Map visits = person.getPatientVisitSummaries(); Set
	 * descriptions = new HashSet(); if (visits != null && visits.size() > 0) {
	 * for (Iterator iter = visits.keySet().iterator(); iter.hasNext();) {
	 * SiteYear year = (SiteYear) iter.next(); PatientVisitSummary pvs =
	 * person.getPatientVisitSummary( year.getYear(), year.getFacility()); if
	 * (pvs != null) { VAFacility fac = pvs.getFacilityVisited(); if (fac !=
	 * null) descriptions.add(fac.getDescription()); } }
	 *
	 * if (descriptions.size() > 0) { result =
	 * StringUtils.join(descriptions.toArray(), "; "); } } } return result; }
	 */

	private List getCommsLogs(Person person) throws RuleException {
		List logs = null;
		if (this.getCommsInputData() != null
				&& this.getCommsInputData().getCommsLogEntries() != null) {
			logs = this.getCommsInputData().getCommsLogEntries();
		} else {
			if (person != null) {
				try {
					logs = this.getCommsLogService().findLogEntriesByPersonId(
							person.getEntityKey().getKeyValueAsString());
				} catch (ServiceException e) {
					throw new RuleException("Failed to get CommsLog", e);
				}
			}
		}
		return logs;
	}

	// The following are enhancements to ESR 3.1 VOA, please see
	// SUC_[676] Send Update Message (Z11)

	/**
	 * SUC510.15.12 - Spinal Cord Injury Bulletin If the Veteran's Online
	 * Application indicates Spinal Cord Injury and the Proxy Preferred Facility
	 * Patient Record has been created send the Veteran's Online Application
	 * Spinal Cord Injury Bulletin to the Proxy Preferred Facility. The system
	 * sends the bulletin to the G.DGEN Eligibility Alert @(site).med.DNS   .
	 */
	public void triggerVOASpinalCordBulletin() throws RuleException {

		if (getIncomingPerson().getSpinalCordInjury() != null && getIncomingPerson().getSpinalCordInjury().getSpinalCordInjuryType() != null &&
				!getIncomingPerson().getSpinalCordInjury().getSpinalCordInjuryType().getCode().equals(SpinalCordInjuryType.CODE_NOT_APPLICABLE.getCode())) {

			BulletinTriggerEvent bte = new BulletinTriggerEvent(
					BulletinTrigger.DataType.VOA_SPINAL_CORD_INJURY);
			bte.addField(BulletinTriggerEvent.VOASpinalCordFields.VPID,
					getIncomingPerson().getVPIDEntityKey().getKeyValueAsString());
			if (getDfn() != null) {
				bte.addField(BulletinTriggerEvent.VOASpinalCordFields.DFN, getDfn());
			}
			bte.addField(BulletinTriggerEvent.VOASpinalCordFields.STATION_ID,
					VAFacility.CODE_MHV.getCode());

			// Preferred facility or Proxy preferred facility!!??
			bte.addField(
					BulletinTriggerEvent.VOASpinalCordFields.PREFERRED_FACILITY,
					getIncomingPerson().getMostRecentPreferredFacility().getCode());
			addTriggerEvent(bte);
		}

	}

	public void triggerHandBookMail(String type)throws RuleException {
		// Don't trigger letter if in review mode
		if (this.getBaseData() != null && this.getBaseData().isReviewOnly()) {
			return;
		}
		ComLetterTemplateType tmeplate=this.getComLetterTemplateType(type);
		LetterTriggerEvent lte = null;
		if (lte == null) {
			lte = new LetterTriggerEvent(tmeplate);
		}

		lte.setMailType(LetterTriggerEvent.AUTO_MAIL);
		addTriggerEvent(lte);

	}

	/**
	 * @see gov.va.med.esr.common.rule.CommunicationsInput#hasReceivedNotificationOfIneligibleStatus()
	 */
	public boolean has400HSendBefore(Person person)
			throws RuleException {

		try {
			List logs = this.getHandBookService().findHandbookEntriesInSendSatusByPersonId(new BigDecimal(person.getEntityKey().getKeyValueAsString()),null);
            if(logs.size()>0){
            	return true;
            }
		} catch (ServiceException e) {
			logger.error("error in finding if 400Hsend before:"+e.getStackTrace());
			throw new RuleException("error in finding if 400Hsend before:", e);

		}

		return false;
	}
	public boolean hasAny400HTriggerdBefore(Person person)
	throws RuleException {

		try {
			List logs = this.getHandBookService().findAnyHandbookSentOrMailedForPersonId(person.getEntityKey().getKeyValueAsString());
			if(logs.size()>0){
				return true;
			}
		} catch (ServiceException e) {
			logger.error("error in finding if any 400H triggered before:"+e.getStackTrace());
			throw new RuleException("error in finding if any 400H triggered before:", e);

		}

		return false;
	}

	public boolean isLastHandBookReturnPost(Person person) throws RuleException {

		try {
			List logs = this.getHandBookService().lastHandbookReturnPostOffice(
					person.getPersonEntityKey().getKeyValueAsString());
			if (logs.size() > 0) {
				return true;
			}
		} catch (ServiceException e) {
			//System.out.print(e.getStackTrace());
			throw new RuleException("Failed to find last handbook return by post office", e);
			// System.out.print(e.getStackTrace());
		}

		return false;
	}

	public HandBookService getHandBookService() {
		return handBookService;
	}

	public void setHandBookService(HandBookService handBookService) {
		this.handBookService = handBookService;
	}

	public boolean isPristinePensionInd(){

		return getPensionInd(this.getPristinePerson());
	}

	public boolean isUpdatedPensionInd(){

		return getPensionInd(this.getResultPerson());
	}

	public boolean isUpdatedAAInd(){

		return getAAInd(this.getResultPerson());
	}

	public boolean isUpdatedHBInd(){

		return getHBInd(this.getResultPerson());
	}

	private boolean getPensionInd(Person person){
		boolean pensionInd = false;
		MonetaryBenefitAward mba = person.getMonetaryBenefitAward();
		MonetaryBenefit mb = mba != null?person.getMonetaryBenefitAward().getMonetaryBenefitByType(MonetaryBenefitType.CODE_VA_PENSION): null;
		if(mb != null && mb.getMonetaryBenefitIndicator() != null){
			if(mb.getMonetaryBenefitIndicator().getCode().equals(Indicator.YES.getCode())){
				pensionInd = true;
			}

		}
		return pensionInd;

	}

	private boolean getAAInd(Person person){
		boolean aaInd = false;
		MonetaryBenefitAward mba = person.getMonetaryBenefitAward();
		MonetaryBenefit mb = mba != null?person.getMonetaryBenefitAward().getMonetaryBenefitByType(MonetaryBenefitType.CODE_AID_AND_ATTENDANCE): null;
		if(mb != null && mb.getMonetaryBenefitIndicator() != null){
			if(mb.getMonetaryBenefitIndicator().getCode().equals(Indicator.YES.getCode())){
				aaInd = true;
			}

		}
		return aaInd;

	}

	private boolean getHBInd(Person person){
		boolean hbInd = false;
		MonetaryBenefitAward mba = person.getMonetaryBenefitAward();
		MonetaryBenefit mb = mba != null?person.getMonetaryBenefitAward().getMonetaryBenefitByType(MonetaryBenefitType.CODE_HOUSEBOUND): null;
		if(mb != null && mb.getMonetaryBenefitIndicator() != null){
			if(mb.getMonetaryBenefitIndicator().getCode().equals(Indicator.YES.getCode())){
				hbInd = true;
			}

		}
		return hbInd;

	}

	private boolean isPristinePrimEligCodeEqualsType(String eligType){
		boolean eligCodeIsEqual = false;
		EligibilityType type = this.getPristinePerson().getEnrollmentDetermination().getPrimaryEligibility().getType();
        String code = (type == null) ? null : type.getCode();

		if(code != null && code.equals(eligType)){
			eligCodeIsEqual = true;
		}

		return eligCodeIsEqual;

	}

	public boolean isPristinePrimEligCodeNSC(){
		if(this.getPristinePerson().getEnrollmentDetermination() != null &&
				this.getPristinePerson().getEnrollmentDetermination().getPrimaryEligibility() != null){
			return isPristinePrimEligCodeEqualsType(EligibilityType.NSC.getCode());

		}
		else{
			return false;
		}
	}

	public boolean isPristinePrimEligCodeNSCVAPension(){
		if(this.getPristinePerson().getEnrollmentDetermination() != null &&
				this.getPristinePerson().getEnrollmentDetermination().getPrimaryEligibility() != null){
			return isPristinePrimEligCodeEqualsType(EligibilityType.NSC_VA_PENSION.getCode());

		}
		else{
			return false;
		}
	}

	public boolean isPristinePrimEligCodeLess100(){
		if(this.getPristinePerson().getEnrollmentDetermination() != null &&
				this.getPristinePerson().getEnrollmentDetermination().getPrimaryEligibility() != null){
			if(isPristinePrimEligCodeEqualsType(EligibilityType.SC_LESS_THAN_50_PERCENT.getCode()) || isPristinePrimEligCodeEqualsType(EligibilityType.SERVICE_CONNECTED_50_TO_100_PERCENT.getCode())){
				return true;
			}
		}
		return false;
	}

	public boolean isPristinePrimEligCodeHB(){
		if(this.getPristinePerson().getEnrollmentDetermination() != null &&
				this.getPristinePerson().getEnrollmentDetermination().getPrimaryEligibility() != null){
			return isPristinePrimEligCodeEqualsType(EligibilityType.HOUSEBOUND.getCode());

		}
		else{
			return false;
		}
	}

	public boolean isPristinePrimEligCodeAA(){
		if(this.getPristinePerson().getEnrollmentDetermination() != null &&
				this.getPristinePerson().getEnrollmentDetermination().getPrimaryEligibility() != null){
			return isPristinePrimEligCodeEqualsType(EligibilityType.AID_AND_ATTENDANCE.getCode());

		}
		else{
			return false;
		}
	}

	public void triggerVAPensionToAABulletin() {
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_VA_PENSION_TO_AA);
		addTriggerEvent(bte);
	}

	public void triggerSCVAPensionTerminateBulletin() {
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_SC_VA_PENSION_TERMINATE);
		addTriggerEvent(bte);
		bte.addField(
				BulletinTriggerEvent.HecNotificationOfTerminatedVAPensionSC.SC_PERCENTAGE,
				getPercentage(getResultPerson()));
	}

	public void triggerNSCVAPensionTerminateBulletin() {
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_NSC_VA_PENSION_TERMINATE);
		addTriggerEvent(bte);
	}

	public void triggerVAPensionToHBBulletin() {
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_VA_PENSION_TO_HB);
		addTriggerEvent(bte);
	}

	public void triggerSCToVAPensionBulletin() {
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_SC_TO_VA_PENSION);
		addTriggerEvent(bte);
	}

	public void triggerNSCToVAPensionBulletin() {
		BulletinTriggerEvent bte = new BulletinTriggerEvent(
				BulletinTrigger.DataType.HEC_NOTIFY_NSC_TO_VA_PENSION);
		addTriggerEvent(bte);
	}

	private int getPercentage(Person person){
		int percent = 0;

		if(person.getServiceConnectionAward() != null && person.getServiceConnectionAward().getServiceConnectedPercentage() != null ){
			percent = person.getServiceConnectionAward().getServiceConnectedPercentage();
		}

		return percent;

	}

	public boolean isProcessingHandbook() {
		return getCommsInputData().isProcessingHandbook();
	}

	/**
	 * 	CCR11323
		Check handbook_mail_queue table for any entries for the person id under processing in the status “send to cms” with form_type_id = 400B or 400F or 400H .

		If the original request is for 400H and there is a 400B or 400F in handbook_mail_queue table from the query above, set the status for those entries in the handbook_mail_queue table  to “Cancel by HEC”
		Call the method to trigger a new handbook (type 400H) with the latest default release control number
		If the original request is for 400H and there is a 400H in handbook_mail_queue table from the query above, don’t do anything

		If the original request is for 400B and there is a 400B in handbook_mail_queue table from the query above, don’t do anything
		If the original request is for 400B and there is a 400F in handbook_mail_queue table
		Call the method to trigger a new handbook insert(type 400B) with the latest default release control number
		If the original request is 400B  and there is a 400H in the in handbook_mail_queue table from the query above, don’t do anything

		If the original request is for 400F and there is a 400B in handbook_mail_queue table from the query above ,
		Call the method to trigger a new handbook insert(Type 400F) with the latest default release control number
		If the original request is for 400F and there is a 400F in handbook_mail_queue table from the query above, don’t do anything
		If the original request is 400F and there is a 400H in the in handbook_mail_queue table from the query above, don’t do anything
	 */
	public void retriggerHandbookForReturnedOrRejected() throws RuleException
	{
		try {
			// another 400H is in send state, so don't do anything
			if (hasHandBookInSendStatus(ComLetterTemplateType.FORM_NUMBER_400H.getCode()))
				return;

			// get the original request template
			CommsTemplate origRequestTemplate = getHandbookMailQueue().getTemplate();
			String origFormNumber = null;
			if (origRequestTemplate != null) {
				origFormNumber = origRequestTemplate.getCommsTemplateFormNumber();
			}

			if (ComLetterTemplateType.FORM_NUMBER_400H.getCode().equals(origFormNumber)) {
				retrigger400H();
			} else if (ComLetterTemplateType.FORM_NUMBER_400B.getCode().equals(origFormNumber)) {
				retrigger400B();
			} else if (ComLetterTemplateType.FORM_NUMBER_400F.getCode().equals(origFormNumber)) {
				retrigger400F();
			}
		} catch (ServiceException e) {
			logger.error("Error in retriggerHandbookForReturnedOrRejected:"+e.getStackTrace());
			throw new RuleException("Error in retriggerHandbookForReturnedOrRejected:", e);
		}
	}

	public void updateMVIOfDODChanges() throws ServiceException, RuleException {
		sendDodUpdateToMVI();
	}

	public void updateMVIOfVerifiedDODChanges() throws ServiceException, RuleException {
		if (getIncomingPerson().getDeathRecord().getDodVerifiedInd() != null
				&& getIncomingPerson().getDeathRecord().getDodVerifiedInd()) {
			sendDodUpdateToMVI();
		}
	}

	private void sendDodUpdateToMVI() throws RuleException {
		ProcessTriggerEvent pte = new ProcessTriggerEvent(ProcessTriggerEvent.UPDATE_MVI_DOD, this.getPersonId());
		addTriggerEvent(pte);
	}

	/**
	 * If the original request is for 400H and there is a 400H in handbook_mail_queue table from the query above, don’t do anything
	 * @return
	 * @throws ServiceException
	 */
	private boolean hasHandBookInSendStatus(String formNumber) throws ServiceException {
		ComLetterTemplateType templateType = this.getLookupService()
		.getComLetterTemplateTypeByCode(formNumber);

		List pastHandbookList = handBookService.findHandbookEntriesInSendToCMSByPersonByTemp(this.getIncomingPerson(), templateType);

		return (pastHandbookList != null && !pastHandbookList.isEmpty());
	}

	/**
	 *	If the original request is for 400H and there is a 400B or 400F in handbook_mail_queue table from the query above, set the status for those entries in the handbook_mail_queue table  to “Cancel by HEC”
	 *	Call the method to trigger a new handbook (type 400H) with the latest default release control number
	 */
	private void retrigger400H() throws ServiceException, RuleException {

		cancelHandbookInSendStatus(ComLetterTemplateType.FORM_NUMBER_400B.getCode());
		cancelHandbookInSendStatus(ComLetterTemplateType.FORM_NUMBER_400F.getCode());

		this.triggerHandBookMail(ComLetterTemplateType.FORM_NUMBER_400H.getName());
	}

	/**
	 * Cancel any existing 400B or 400F handbook in send_to_cms status
	 * @param formNumber
	 */
	private void cancelHandbookInSendStatus(String formNumber) throws ServiceException {
		ComLetterTemplateType templateType = this.getLookupService().getComLetterTemplateTypeByCode(formNumber);

		List pastHandBookList = handBookService.findHandbookEntriesInSendToCMSByPersonByTemp(this.getIncomingPerson(), templateType);
		if (pastHandBookList != null && !pastHandBookList.isEmpty()) {
			// should be only one
			HandBookMailQueue mailQueue = (HandBookMailQueue) pastHandBookList.iterator().next();
			HandBookMailStatusType cancelByHEC = (HandBookMailStatusType)this.getLookupService().getByCode(HandBookMailStatusType.class, HandBookMailStatusType.CANCEL_BY_HEC);
			this.handBookService.saveHandbookMailStatus(mailQueue, cancelByHEC);
		}
	}

	/**
	 *	If the original request is for 400B and there is a 400B in handbook_mail_queue table from the query above, don’t do anything
	 *	If the original request is for 400B and there is a 400F in handbook_mail_queue table
	 *	Call the method to trigger a new handbook insert(type 400B) with the latest default release control number
	 *
	 * @throws ServiceException
	 */
	private void retrigger400B() throws ServiceException, RuleException {
		if (hasHandBookInSendStatus(ComLetterTemplateType.FORM_NUMBER_400B.getCode())) {
			return;
		} else {
			this.triggerHandBookMail(ComLetterTemplateType.FORM_NUMBER_400B.getName());
		}
	}

	/**
	 *	If the original request is for 400F and there is a 400B in handbook_mail_queue table from the query above ,
	 *	Call the method to trigger a new handbook insert(Type 400F) with the latest default release control number
	 *  If the original request is for 400F and there is a 400F in handbook_mail_queue table from the query above, don’t do anything
	 *
	 * @throws ServiceException
	 * @throws RuleException
	 */
	private void retrigger400F() throws ServiceException, RuleException {
		if (hasHandBookInSendStatus(ComLetterTemplateType.FORM_NUMBER_400F.getCode())) {
			return;
		} else {
			this.triggerHandBookMail(ComLetterTemplateType.FORM_NUMBER_400F.getName());
		}
	}

	// CodeCR12825
	private boolean isIVMFinalLetter(String form) {
		if (form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_400.getCode()) ||
				form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_450.getCode()) ||
				form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_451.getCode()) ||
				form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_452.getCode()) ||
				form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_453.getCode()) ||
				form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_454.getCode()) ||
				form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_455.getCode())) {
			return true;
		}
		return false;
	}
	// CodeCR12825
	private boolean isCompassionLetter(String form) {
		if (form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_683A.getCode())) {
			return true;
		}
		return false;
	}
	// CodeCR12825
	private void updateLetterTriggerEventForCms(LetterTriggerEvent lte) {
		IVMFinancialInfo info = this.getCommsInputData() != null ? this.getCommsInputData().getIvmFinancialInfo() : null;
		if (info != null) {
			lte.setIvmCaseClosureDate(info.getIvmCaseClosureDate());
			lte.setIvmCaseNumber(info.getIvmCaseNumber());
		}
	}

}