/*******************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ******************************************************************************/
package gov.va.med.esr.common.rule.parameter;
// Java classes
import gov.va.med.esr.common.infra.ImpreciseDate;
import gov.va.med.esr.common.infra.ImpreciseDateUtils;
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.ee.MilitaryService;
import gov.va.med.esr.common.model.ee.MilitaryServiceEpisode;
import gov.va.med.esr.common.model.ee.MilitaryServiceSiteRecord;
import gov.va.med.esr.common.model.financials.DependentFinancials;
import gov.va.med.esr.common.model.financials.FinancialStatement;
import gov.va.med.esr.common.model.financials.PatientVisitSummary;
import gov.va.med.esr.common.model.financials.SpouseFinancials;
import gov.va.med.esr.common.model.lookup.AssociationType;
import gov.va.med.esr.common.model.lookup.CollectionMethod;
import gov.va.med.esr.common.model.lookup.DeathDataSource;
import gov.va.med.esr.common.model.lookup.PseudoSSNReason;
import gov.va.med.esr.common.model.lookup.SSAVerificationStatus;
import gov.va.med.esr.common.model.lookup.SSNType;
import gov.va.med.esr.common.model.lookup.SourceDesignation;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.person.Association;
import gov.va.med.esr.common.model.person.DeathRecord;
import gov.va.med.esr.common.model.person.Name;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.PreferredFacility;
import gov.va.med.esr.common.model.person.PreferredLanguage;
import gov.va.med.esr.common.model.person.SSN;
import gov.va.med.esr.common.model.system.SystemParameter;
import gov.va.med.esr.common.rule.DemographicInput;
import gov.va.med.esr.common.rule.data.CommsInputData;
import gov.va.med.esr.common.rule.data.ExternalSystemsInputData;
import gov.va.med.esr.common.rule.data.PersonInputData;
import gov.va.med.esr.service.PSDelegateService;
import gov.va.med.esr.service.PersonService;
import gov.va.med.esr.service.PreferredFacilityService;
import gov.va.med.esr.service.external.demographics.DemographicsChangeType;
import gov.va.med.esr.service.trigger.LetterTriggerEvent;
import gov.va.med.esr.service.trigger.ProcessTriggerEvent;
import gov.va.med.fw.rule.RuleException;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.StringUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This class is a facade for accessing person's demographic data.
 *
 * @author Carlos Ruiz
 * @version 1.0
 */
public class DemographicInputParameter extends BaseParameter implements
		DemographicInput {

	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = -750695891690076382L;

	private Date dateOfBirth = null;

	private Date dateOfDeath = null;
	
	private Date preferredLanguageEntryDate = null;

	private Date dateOfDeathReported = null;

	private String genderCode = null;

	private String ssn = null;

	private String patientName = null;

	private VAFacility preferredFacility = null;

	private static final String PSEUDO_SSN_REASON = "R";

	private static final String SSN_INVALID = "I";

	private static final Character KNOWN = '1';

	private PSDelegateService psDelegateService = null;

	private PreferredFacilityService preferredFacilityService = null;

	private PersonService personService = null;

	/**
	 * Default constructor
	 */
	public DemographicInputParameter() {
		super();
	}

	public DeathRecord getDeathRecord()
	{
	    return this.getDeathRecord(this.getIncomingPerson());
	}

	public Date getPristineDateOfDeath()
	{
		return this.getDateOfDeath(this.getPristinePerson());
	}

	/**
	 * @see gov.va.med.esr.common.rule.parameter.Demographic#getDateOfBirth()
	 */
	public Date getDateOfBirth() {
		if ( dateOfBirth == null ) {
			this.dateOfBirth = this.getDateOfBirth(this.getIncomingPerson());
		}
		return this.dateOfBirth;
	}

	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#getDateOfDeath()
	 */
	public Date getDateOfDeath() {
		if ( dateOfDeath == null ) {
			this.dateOfDeath = this.getDateOfDeath(this.getIncomingPerson());
		}
		return this.dateOfDeath;
	}
	
    public Date getPreferredLanguageEntryDate() {
        if (preferredLanguageEntryDate == null) {           
            if (this.getIncomingPerson() != null && this.getIncomingPerson().getPreferredLanguage() != null){
                this.preferredLanguageEntryDate = this.getIncomingPerson().getPreferredLanguage().getLanguageEntryDate();
            }         
        }
        return this.preferredLanguageEntryDate;
    }

	public String getPristineSiteOfNotification() {
	    DeathRecord deathRecord = this.getDeathRecord(this.getPristinePerson());
		return (deathRecord != null && deathRecord.getFacilityReceived() != null) ? deathRecord
				.getFacilityReceived().getName()
				: null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#getDateOfDeathReported()
	 */
	public Date getDateOfDeathReported() {
		if ( dateOfDeathReported == null ) {
			this.dateOfDeathReported = this.getDateOfDeathReported(this
					.getIncomingPerson());
		}
		return this.dateOfDeathReported;
	}

	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#getSourceOfNotification()
	 */
	public String getSourceOfNotification() {
		DeathRecord deathRecord = this.getDeathRecord(this.getIncomingPerson());
		return (deathRecord != null && deathRecord.getDataSource() != null) ? deathRecord.getDataSource().getCode()	: null;
	}


    public void setSourceOfNotification(String deathSource) {
        DeathRecord resultDeathRecord = getDeathRecord(getResultPerson());
        if(resultDeathRecord != null && StringUtils.isNotEmpty(deathSource)) {
            try {
                DeathDataSource dataSource = (DeathDataSource)getLookupService().getByCode(DeathDataSource.class,deathSource);
                resultDeathRecord.setDataSource(dataSource);
            } catch(Exception ex) {
                throw new RuntimeException("Failed to get VBA DeathDataSource", ex);
            }
        }
    }

	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#getSiteOfNotification()
	 */
	public String getSiteOfNotification() {
		DeathRecord deathRecord = this.getDeathRecord(this.getIncomingPerson());
		return (deathRecord != null && deathRecord.getFacilityReceived() != null) ? deathRecord
				.getFacilityReceived().getName()
				: null;
	}

	public boolean isDOBPlus15AfterMilitaryServiceServiceEntryDate()
	{
	    Date dobPlus15 = getDateOf15thBirthday();
	    if(dobPlus15 == null) return false;

	    MilitaryService militaryService = this.getIncomingPerson().getMilitaryService();
	    if(militaryService == null || militaryService.getHECMilitaryServiceSiteRecord() == null) return false;

	    Set MSEs = militaryService.getHECMilitaryServiceSiteRecord().getMilitaryServiceEpisodes();
	    for(Iterator iter = MSEs.iterator(); iter.hasNext();)
	    {
	        MilitaryServiceEpisode mse = (MilitaryServiceEpisode)iter.next();
	        Date mseStartDate = (mse.getStartDate() != null) ? ImpreciseDateUtils.getDateWithDefault(mse.getStartDate()) : null;
	        if(isAfter(dobPlus15,mseStartDate)) return true;
	    }
	    return false;
	}

	public boolean isDODBeforeMilitaryServiceServiceEntryDate()
	{
	    Date dod = getDateOfDeath();
	    MilitaryService militaryService = this.getIncomingPerson().getMilitaryService();
	    Set siteRecords = (militaryService != null) ? militaryService.getMilitaryServiceSiteRecords() : null;
	    if(dod == null || siteRecords == null || siteRecords.isEmpty()) return false;

	    for(Iterator siteRecordsIter = siteRecords.iterator(); siteRecordsIter.hasNext();)
	    {
	        MilitaryServiceSiteRecord siteRecord = (MilitaryServiceSiteRecord)siteRecordsIter.next();
	        if(siteRecord != null)
	        {
	            Set MSEs = siteRecord.getMilitaryServiceEpisodes();
	            for(Iterator mseIter = MSEs.iterator(); mseIter.hasNext();)
	            {
	                MilitaryServiceEpisode mse = (MilitaryServiceEpisode)mseIter.next();
	                Date mseStartDate = (mse.getStartDate() != null) ? ImpreciseDateUtils.getDateWithDefault(mse.getStartDate()) : null;
	                if(isBefore(dod,mseStartDate)) return true;
	            }
	        }
	    }
	    return false;
	}

	public boolean isDODBeforeMilitaryServiceServiceSeparationDate()
	{
	    Date dod = getDateOfDeath();
	    MilitaryService militaryService = this.getIncomingPerson().getMilitaryService();
	    Set siteRecords = (militaryService != null) ? militaryService.getMilitaryServiceSiteRecords() : null;
	    if(dod == null || siteRecords == null || siteRecords.isEmpty()) return false;

	    for(Iterator siteRecordsIter = siteRecords.iterator(); siteRecordsIter.hasNext();)
	    {
	        MilitaryServiceSiteRecord siteRecord = (MilitaryServiceSiteRecord)siteRecordsIter.next();
	        if(siteRecord != null)
	        {
	            Set MSEs = siteRecord.getMilitaryServiceEpisodes();
	            for(Iterator mseIter = MSEs.iterator(); mseIter.hasNext();)
	            {
	                MilitaryServiceEpisode mse = (MilitaryServiceEpisode)mseIter.next();
	                Date mseEndDate = (mse.getEndDate() != null) ? ImpreciseDateUtils.getDateWithDefault(mse.getEndDate()) : null;
	                if(isBefore(dod,mseEndDate)) return true;
	            }
	        }
	    }
	    return false;
	}

	public void updateDeathRecord(DeathRecord deathRecord) throws RuleException
	{
	    if(deathRecord == null)
	    {
	        this.removeDeathRecord();
	        return;
	    }

        try
        {
            //If the Date of Death present, remove Lazarus date.
            if(deathRecord.getDeathDate() != null) {
                deathRecord.setLazarusDate(null);
            }
            DeathRecord resultDeathRecord = getDeathRecord(getResultPerson());
            if(resultDeathRecord == null) {
                resultDeathRecord = new DeathRecord();
                setDeathRecord(resultDeathRecord);
            }
            this.getMergeRuleService().mergeDeathRecord(deathRecord,resultDeathRecord);
        }
        catch (ServiceException e)
        {
			if (this.logger.isDebugEnabled())
			{
				this.logger.debug("Failed to merge death record", e);
			}
			throw new RuntimeException("Failed to merge death record", e);
		}
	}

	public void setDateOfDeathReported(Date date)
	{
	    DeathRecord resultDeathRecord = getDeathRecord(getResultPerson());
	    if(resultDeathRecord != null)
	    {
	        resultDeathRecord.setDeathReportDate(date);
	    }
	}

	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#getGenderCode()
	 */
	public String getGenderCode() {
		if ( genderCode == null ) {
			this.genderCode = this.getGenderCode(this.getIncomingPerson());
		}
		return this.genderCode;
	}
	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#getGenderCode()
	 * ccr11147. changeing this method to return always PristineGenderCode()
	 */
	public String getPristineGenderCode() {
				 return getGenderCode(this.getPristinePerson());
	}
	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#getLastName()
	 */
	public String getLastName() {
		Name legalName = this.getLegalName(this.getIncomingPerson());
		return legalName != null ? legalName.getFamilyName() : null;
	}

	public Association getNextOfKin() {
		Association primaryNok = Association.getAssociationOfType(this.getIncomingPerson().getAssociations(),
				AssociationType.CODE_PRIMARY_NEXT_OF_KIN.getCode());
		return primaryNok;
	}

	public String getNokFirstName() {
		Association nok = this.getNextOfKin();
		return nok != null ? nok.getFirstName() : null;
	}

	public String getNokLastName() {
		Association nok = this.getNextOfKin();
		return nok != null ? nok.getLastName() : null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#getSSN()
	 */
	public String getSSN() {
		if ( this.ssn == null ) {
			this.ssn = this.getSSN(this.getIncomingPerson());
		}
		return this.ssn;
	}

	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#isDateInFuture(java.util.Date)
	 */
	public boolean isDateInFuture(Date adate) {
		Date today = Calendar.getInstance().getTime();
		return isAfter(adate, today);
	}

	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#getDateOf15thBirthday()
	 */
	public Date getDateOf15thBirthday() {
		Date birthDate = getDateOfBirth(this.getIncomingPerson());
		Date fifteenthBirthday = null;
		if (birthDate != null) {
			Calendar calendar = Calendar.getInstance();
			calendar.setTime(birthDate);
			int year = calendar.get(Calendar.YEAR);
			year += 15;
			calendar.set(Calendar.YEAR, year);
			fifteenthBirthday = calendar.getTime();
		}
		return fifteenthBirthday;
	}

	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#isDeathRecordDeleted()
	 */
	public boolean isDeathRecordDeleted() {
		if ((getDateOfDeath(getPristinePerson()) != null) &&
				(getDateOfDeath(getIncomingPerson()) == null)) {
			return true;
		}
		return false;
	}

	/**
	 * CCR12879_AJ_VOA - Apply PF processing logic when VOA submitted PF
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#isVOAApplicantKnownToES()
	 */
	public boolean isVOAApplicantKnownToES() {

		//VOA indicator == null is a special case that is known to ES too
		if (this.getIncomingPerson().getVOAIndicator() == null || KNOWN.equals(this.getIncomingPerson().getVOAIndicator())){
			return true;
		}
		return false;
	}

	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#removeDeathRecord()
	 */
	public void removeDeathRecord() throws RuleException {
		DeathRecord deathRecord = this.getResultPerson().getDeathRecord();
		if (deathRecord != null) {
			deathRecord.setDeathDate(null);
			deathRecord.setDataSource(null);
			deathRecord.setDeathReportDate(null);
			deathRecord.setFacilityReceived(null);
			deathRecord.setLazarusDate(new ImpreciseDate(this.getCurrentDate()));
			if (isMVIDODServiceActive()) {
				if (this.isUpdateFromGUI()) {
					deathRecord.setDeathReportedBy("ES_USER");
				} else {					
					deathRecord.setDeathReportedBy(this.getIncomingPerson()
							.getModifiedBy().getName());
				}
			}
		}
	}
	
	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#restorePristineDeathRecord()
	 */
	public void restorePristineDeathRecord() {
		DeathRecord pristineDeathRecord = this.getPristinePerson().getDeathRecord();		
		DeathRecord incomingDeathRecord = this.getIncomingPerson().getDeathRecord();
		
		if(pristineDeathRecord == null){			
			this.getIncomingPerson().setDeathRecord(null);			
		}
		else{
			if(incomingDeathRecord == null){
				incomingDeathRecord = new DeathRecord();				
			}
			incomingDeathRecord.setDeathDate(pristineDeathRecord.getDeathDate());
			incomingDeathRecord.setDataSource(pristineDeathRecord.getDataSource());
			incomingDeathRecord.setDeathReportDate(pristineDeathRecord.getDeathReportDate());
			incomingDeathRecord.setDeathReportedBy(pristineDeathRecord.getDeathReportedBy());
			incomingDeathRecord.setDodVerifiedInd(pristineDeathRecord.getDodVerifiedInd());
			incomingDeathRecord.setFacilityReceived(pristineDeathRecord.getFacilityReceived());
			incomingDeathRecord.setLazarusDate(pristineDeathRecord.getLazarusDate());
		}
	}

	public String getDODReceivedFrom() {
		DeathRecord deathRecord = this.getIncomingPerson().getDeathRecord();
		String stationNumber = (deathRecord != null && deathRecord
				.getFacilityReceived() != null) ? deathRecord.getFacilityReceived()
				.getStationNumber() : null;
		return stationNumber;
	}

	public boolean isDateOfDeathUpdatedByVAMC() {
		DeathRecord deathRecord = this.getIncomingPerson().getDeathRecord();
		String stationNumber = (deathRecord != null && deathRecord
				.getFacilityReceived() != null) ? deathRecord.getFacilityReceived()
				.getStationNumber() : null;
		return (stationNumber != null) ? !stationNumber
				.equals(VAFacility.CODE_HEC.getName()) : false;
	}
	
	public boolean isDateOfDeathUpdatedByMVI() {		
		return this.isUpdateFromMVI();
	}

	public String getPristineFirstName() {
		Name legalName = this.getLegalName(this.getPristinePerson());
		return legalName != null ? legalName.getGivenName() : null;
	}

	public String getPristineLastName() {
		Name legalName = this.getLegalName(this.getPristinePerson());
		return legalName != null ? legalName.getFamilyName() : null;
	}

	public String getPristineMiddleName() {
		Name legalName = this.getLegalName(this.getPristinePerson());
		return legalName != null ? legalName.getMiddleName() : null;
	}

	public String getMiddleName() {
		Name legalName = this.getLegalName(this.getIncomingPerson());
		return legalName != null ? legalName.getMiddleName() : null;
	}

	public String getFirstName() {
		Name legalName = this.getLegalName(this.getIncomingPerson());
		return legalName != null ? legalName.getGivenName() : null;
	}

	/**
	 * @see gov.va.med.esr.common.rule.parameter.DemographicInput#getPatientName()
	 */
	public String getPatientName() {

		if( this.patientName == null ) {
			StringBuffer info = new StringBuffer();
			info.append( this.getPristineLastName() )
				 .append( ", " )
				 .append( this.getPristineFirstName() );

			this.patientName = info.toString();
		}

		return this.patientName;
	}

    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#getSensitiveIndicator()
     */
    public Boolean getSensitiveIndicator() {
        return this.getSensitivityRecordIndicator( this.getIncomingPerson() );
    }

    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#getPristineSensitiveIndicator()
     */
    public Boolean getPristineSensitiveIndicator() {
        return this.getSensitivityRecordIndicator( this.getPristinePerson() );
    }

	public boolean isSensitivityChanged() {
		return !(isEqual(getPristinePerson().getSensitiveRecord(),getIncomingPerson().getSensitiveRecord()));
	}

    public Date getSensitivityChangeDate() {
        return this.getSensitivityChangeDate(this.getIncomingPerson());
    }

    public Date getPristineSensitivityChangeDate() {
        return this.getSensitivityChangeDate(this.getPristinePerson());
    }

	/**
	 * Gets the calulated the preferred facility.
	 *
	 * 1732[UC34.3.5.2.2]
	 * If Preferred Facility is still NULL or the data did not pass constraint checks
	 * THEN
	 * The system shall compute the Preferred Facility for the enrollment record.
	 *
	 * The algorithm for computing preferred facility is documented in the ESR Supplementary
	 * Specification document.
	 *
	 * 6	Preferred Facility Computation
	 * The following steps are to be applied when it becomes necessary to derive the Preferred
	 * Facility value:
	 *
	 * If the veteran has only visited one facility in the past two years, assign that facility as the
	 * Preferred Facility.
	 * If this does not result in the identification of a preferred facility, THEN identify the
	 * geographically closest facility within the boundaries of the VISN in which the veteran
	 * resides. The veteran’s zip code is used as a basis.
	 * Otherwise, if no zip code upon which to base the preferred facility is available, assign the
	 * most recent facility providing care as the preferred facility.
	 *
	 * If no preferred facility value could be determined through the above steps, the institution ID
	 * of the last site to send HEC a message for that veteran is used as the preferred facility.
	 */
	public VAFacility getCalculatedPreferredFacility() {
	    if( preferredFacility == null ) {
		    Map PVS = this.getIncomingPerson().getPatientVisitSummaries();
		    List sortedPatientVisitSummaries = sortLastVisitDateRecentToOld(PVS.values());

		    // If the veteran has only visited one facility in the past two years, assign that facility as the
		    // Preferred Facility.
		    this.preferredFacility = this.getPreferredFacilityPast2Years(sortedPatientVisitSummaries);

		    // If this does not result in the identification of a preferred facility, THEN identify the
		    // geographically closest facility within the boundaries of the VISN in which the veteran
		    // resides. The veteran’s zip code is used as a basis.
		    if(this.preferredFacility == null) {
		        this.preferredFacility = getPreferredFacilityByLocation();
		    }

		    // Otherwise, if no zip code upon which to base the preferred facility is available, assign the
		    // most recent facility providing care as the preferred facility.
		    if(this.preferredFacility == null) {
		        this.preferredFacility = getPreferredFacilityRecentVisit(sortedPatientVisitSummaries);
		    }

		    // If no preferred facility value could be determined through the above steps, the institution ID
		    // of the last site to send HEC a message for that veteran is used as the preferred facility.
		    if(this.preferredFacility == null) {
		        this.preferredFacility = getPreferredFacilityBySiteSendingMessage();
		    }
	    }
	    return this.preferredFacility;
	}

	/**
	 * For backward compatibility, used in setCalculatedPreferredFacility only
	 *
	 */
	public void setPreferredFacility(VAFacility facility) throws ServiceException {
		PreferredFacility pf = new PreferredFacility();
		pf.setAssignmentDate(new Date());
		pf.setFacility(facility);
		pf.setSourceDesignation(this.getLookupService().getSourceDesignationByCode(SourceDesignation.CODE_ESR.getCode()));
	    this.getResultPerson().addPreferredFacility(pf);
	}

    public void updateSensitivityInformation() {

        Person incomingPerson = getIncomingPerson();
        Person resultPerson = getResultPerson();

        resultPerson.setSensitiveRecord(incomingPerson.getSensitiveRecord());
        resultPerson.setSensitivityChangeDate(incomingPerson.getSensitivityChangeDate());
        resultPerson.setSensitivityChangeSite(incomingPerson.getSensitivityChangeSite());
        resultPerson.setSensitivityChangeSource(incomingPerson.getSensitivityChangeSource());
    }

	/**
	 * This method updates the result person object with demographic data of
	 * incoming person.
	 *
	 * @see MergeRuleService.mergeDemographic() method for more details.
	 */
	public void updateDemographicInformation() {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("updateDemographicInformation()");
		}
		try {
            Person incomingPerson = getIncomingPerson();

            if (isUpdateFromGUI()) {
                // If the sensitivity flag was updated, update the change date with the transaction timestamp
                if (isSensitivityChanged()) {
                    incomingPerson.setSensitivityChangeDate(getCurrentDate());
                }
                this.updateSensitivityInformation();
                // CCR 11088 - set default for collection method on Ethnicity
                if (this.getIncomingPerson().getEthnicity() != null &&
                		this.getIncomingPerson().getEthnicity().getCollectionMethod() == null) {
                	this.getIncomingPerson().getEthnicity().setCollectionMethod(
                			this.getLookupService().getCollectionMethodByCode(CollectionMethod.CODE_SLF.getCode()));
                }
             }

            this.getMergeRuleService().mergeDemographic(incomingPerson,this.getResultPerson());

            // Defect 2060 required that we remove the claim folder attributes from the copy service includes.
            // Now we must copy these separately, but only when UI is the origin.
            if (isUpdateFromGUI()) {
                this.getResultPerson().setClaimFolderLocation(this.getIncomingPerson().getClaimFolderLocation());
                this.getResultPerson().setClaimFolderNumber(this.getIncomingPerson().getClaimFolderNumber());
                /* CCR10640 -- preferred facility.  Now support multiple PFs instead of a single one. PF data must now
                   processed and calculated instead of directly accepting input data.
                this.getResultPerson().setPreferredFacility(this.getIncomingPerson().getPreferredFacility());*/
                this.getResultPerson().setUserEnrolleeValidThrough(this.getIncomingPerson().getUserEnrolleeValidThrough());
                this.getResultPerson().setUserEnrolleeSite(this.getIncomingPerson().getUserEnrolleeSite());
                this.getResultPerson().setAppointmentRequestDate(this.getIncomingPerson().getAppointmentRequestDate());
                this.getResultPerson().setAppointmentRequestResponse(this.getIncomingPerson().getAppointmentRequestResponse());
            }
            else {
                // Need to copy gender, name, birth record for messages only since these
                // were removed from copy service when copying person using mergeDemographics
                this.getMergeRuleService().mergeIdentityTraits(incomingPerson,this.getResultPerson());

                // Requirement 4159 and 4158
                //For an existing person if Enrollee Valid Through Date
                //is further in the future than the EDB Enrollee Valid Through
                // Date then accept the HL7 data and update in place.
                Integer existing = (this.getPristinePerson() != null) ? this.getPristinePerson().getUserEnrolleeValidThrough() : null;
                Integer incoming = (this.getIncomingPerson() != null) ? this.getIncomingPerson().getUserEnrolleeValidThrough() : null;
                if ((existing != null && incoming != null && incoming.compareTo(existing) > 0) ||
                        existing == null) {
                    //  1452[UC39.3.1.1.5] User Enrollee Site is required if User Enrollee Valid Through (date) is supplied.
                    if (incoming != null && this.getIncomingPerson().getUserEnrolleeSite() != null) {
                        this.getResultPerson().setUserEnrolleeValidThrough(this.getIncomingPerson().getUserEnrolleeValidThrough());
                        this.getResultPerson().setUserEnrolleeSite(this.getIncomingPerson().getUserEnrolleeSite());
                    }
                }

                //Requirement 3846, 3847
                //If the EDB Appointment Request Response and Appointment Request Date are null then accept what is received on the HL7 from VistA.
                //If the EDB Appointment Request Response and Appointment Request Date are not null then ignore what is received on the HL7 from VistA and do not change the EDB values
                Date appointmentRequestDate = (this.getPristinePerson() != null) ? this.getPristinePerson().getAppointmentRequestDate():null;
                Boolean appointmentRequestResponse = (this.getPristinePerson() != null) ? this.getPristinePerson().getAppointmentRequestResponse():null;
                //CCR12958
                if(appointmentRequestDate == null || appointmentRequestResponse == null)
                {
                    this.getResultPerson().setAppointmentRequestDate(this.getIncomingPerson().getAppointmentRequestDate());
                    this.getResultPerson().setAppointmentRequestResponse(this.getIncomingPerson().getAppointmentRequestResponse());
                }

                // Requirement 3846
                // If this is a Veteran's Online App Only Record and this
                // is a Veteran's Online App Z07 and the Hl7 Appointment Request Date is
                // not null then accept what is received on the HL7 from the Veteran's
                // Online Application

                if(appointmentRequestDate != null ){
                    this.getResultPerson().setAppointmentRequestDate(this.getIncomingPerson().getAppointmentRequestDate());
                }
            }
		}
		catch (ServiceException e) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Failed to merge person", e);
			}
			throw new RuntimeException("Failed to merge person", e);
		}
	}

    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#updateIdentityTraitsInformation()
     */
    public void updateIdentityTraitsInformation() {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("updateIdentityTraitsInformation()");
        }
        try {
            Person incomingPerson = getIncomingPerson();
            this.getMergeRuleService().mergeIdentityTraits(incomingPerson,this.getResultPerson());
        }
        catch (ServiceException e) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Failed to merge person", e);
            }
            throw new RuntimeException("Failed to merge person", e);
        }
    }

    /**
     * @see gov.va.med.esr.common.rule.ContactInformationInput#updateEmergencyResponseIndicator()
     */
    public void updateEmergencyResponseIndicator() throws RuleException {
        try {
            Person incomingPerson = this.getIncomingPerson();
            Person resultPerson = this.getResultPerson();
            this.getMergeRuleService().mergeEmergencyResponseIndicator(incomingPerson,resultPerson);
        } catch(Exception ex) {
            throw new RuleException("Error while merging EmergencyResponseIndicator",ex);
        }
    }

    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#getNamePrefix()
     */
    public String getNamePrefix() {
        Name legalName = this.getIncomingPerson().getLegalName();
        return legalName != null ? legalName.getPrefix() : null;
    }

    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#getPOAFirstName()
     */
    public String getPOAFirstName() {
        Name name = this.getNameFromLetter();
        return name != null ? name.getGivenName() : null;
    }

    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#getPOALastName()
     */
    public String getPOALastName() {
        Name name = this.getNameFromLetter();
        return name != null ? name.getFamilyName() : null;
    }

    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#getRequestedChangeType()
     */
    public DemographicsChangeType getRequestedChangeType() {
        ExternalSystemsInputData externalSystemsInputData = this.getExternalSystemsInputData();
        if (externalSystemsInputData != null) {
            return externalSystemsInputData.getDemographicsChangeEvent() != null ?
                    externalSystemsInputData.getDemographicsChangeEvent().getChangeType() : null;
        }
        return null;
    }

    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#getPristinePseudoSSNReason()
     */
    public String getPristinePseudoSSNReason() {
        SSN ssn = this.getPristinePerson().getOfficialSsn();
        return (ssn != null && ssn.getPseudoSSNReason() != null)? ssn.getPseudoSSNReason().getCode() : null;
    }

    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#getPseudoSSNReason()
     */
    public String getPseudoSSNReason() {
        SSN ssn = this.getIncomingPerson().getOfficialSsn();
        return (ssn != null && ssn.getPseudoSSNReason() != null)? ssn.getPseudoSSNReason().getCode() : null;
    }

    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#getPristineSSNVerificationStatus()
     */
    public String getPristineSSNVerificationStatus() {
        // concept of pristine does not apply in comms context
        SSN ssn = this.getIncomingPerson().getOfficialSsn();
        return (ssn != null && ssn.getSsaVerificationStatus() != null)? ssn.getSsaVerificationStatus().getCode() : null;
    }


    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#getSSNVerificationStatus()
     */
    public String getSSNVerificationStatus() {
        SSN ssn = this.getIncomingPerson().getOfficialSsn();
        return (ssn != null && ssn.getSsaVerificationStatus() != null)? ssn.getSsaVerificationStatus().getCode() : null;
    }


    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#hasInvalidSSNVerificationStatusForVetOrRelation(gov.va.med.esr.common.model.person.Person)
     */
    public boolean hasInvalidSSNVerificationStatusForVetOrRelation(Person person) throws RuleException {
        return this.satisfiesSSNCriteria(SSN_INVALID, person);
    }

    /**
     * @see gov.va.med.esr.common.rule.DemographicInput#hasPseudoSSNReasonOfFollowup()
     */
    public boolean hasPseudoSSNReasonOfFollowup() throws RuleException {
        return this.satisfiesSSNCriteria(PSEUDO_SSN_REASON, this.getIncomingPerson());
    }

	// Private Methods


    private boolean satisfiesSSNCriteria(String ssnCriteria, Person person) {
        if (ssnCriteria == null)return false;
        if (person == null) return false;

        boolean result = false;
        SSN ssn = null;
        String subject = getSubject();
        if (subject == null || AacLetterRequest.VETERAN_LETTER.equals(subject)) {
            ssn = ssnCriteria.equals(SSN_INVALID)?
//                  SSN.getSSNOfType(person.getSsns(), SSNType.CODE_ACTIVE.getCode()) :
                  person.getOfficialSsn() :
                        SSN.getSSNOfType(person.getSsns(), SSNType.CODE_PSEUDO.getCode());
            result = ssnCriteria.equals(SSN_INVALID) ?
                    this.hasInvalidSSNVerificationStatus(ssn) :
                        this.hasPseudoReasonOfFollowup(ssn);
        }

        if (!result) {
            // keep looking
            Map statements = person.getFinancialStatements();

            for (Iterator i= statements.keySet().iterator(); i.hasNext();) {
                Integer year = (Integer)i.next();
                FinancialStatement statement = person.getFinancialStatement(year);
                if (subject == null || AacLetterRequest.SPOUSE_LETTER.equals(subject)) {
                    Set spouses = statement.getSpouseFinancials();

                    for (Iterator ii = spouses.iterator(); ii.hasNext();){
                        SpouseFinancials sf = (SpouseFinancials)ii.next();
                        if (sf.getReportedOn() != null) {
                            SSN spouseSSN = ssnCriteria.equals(SSN_INVALID)?
                                    sf.getReportedOn().getOfficialSsn() :
                                        sf.getReportedOn().getPseudoSsn();

                            result = ssnCriteria.equals(SSN_INVALID) ?
                                    this.hasInvalidSSNVerificationStatus(spouseSSN) :
                                        this.hasPseudoReasonOfFollowup(spouseSSN);
                        }

                        if (result)
                            break;
                    }
                }

                if (!result) {
                    if (subject == null || AacLetterRequest.DEPENDENT_LETTER.equals(subject)) {
                        Set dependents = statement.getDependentFinancials();
                        for (Iterator iter = dependents.iterator(); iter.hasNext();) {
                            DependentFinancials df = (DependentFinancials) iter.next();
                            if (df.getReportedOn() != null) {
                                SSN dependentSSN = ssnCriteria.equals(SSN_INVALID)?
                                        df.getReportedOn().getOfficialSsn() :
                                            df.getReportedOn().getPseudoSsn();

                                result = ssnCriteria.equals(SSN_INVALID) ?
                                        this.hasInvalidSSNVerificationStatus(dependentSSN) :
                                            this.hasPseudoReasonOfFollowup(dependentSSN);
                            }
                            if (result)
                                break;
                        }
                    }
                }
                if (result)
                    break;
            }
        }
        return result;
    }


    private boolean hasInvalidSSNVerificationStatus(SSN ssn) {
        if (ssn == null) {
            return false;
        }

        //CCR# 6984. ESR passes Invalid by SSA status to PSD sync and PSD passes to MPI asyc.
        //Thus the status of onFile copy is not updated promptly by PSD.
        //The automatically triggered 290 letter has to pass the trigger condition before passed to
        //the rule ConditionToSend here. For a auto triggered letter, before the issue between PSD and MPI can be solved,
        //the rule can be loosen here by skipping the condition to send since it was checked at the triggering time

        CommsInputData cid = this.getCommsInputData();
        if (cid.getRuleContext() != null &&
            cid.getMailType() != null &&
            cid.getRuleContext().equals(gov.va.med.esr.service.CommsLetterRequestService.CONTEXT_REQUEST_LETTER) &&
            cid.getMailType().equals(LetterTriggerEvent.AUTO_MAIL))
        {
            return true;
        }

        if (ssn.getSsnText() != null
                && ssn.getSsaVerificationStatus() != null) {

            if (SSAVerificationStatus.INVALID_PER_SSA.getCode().equals(ssn.getSsaVerificationStatus().getCode())) {
                return true;
            }
        }
        return false;
    }

    private boolean hasPseudoReasonOfFollowup(SSN ssn) {
        if (ssn == null) {
            return false;
        }

        if (ssn.getPseudoSSNReason() != null) {

            if (PseudoSSNReason.CODE_FOLLOWUP_REQD.getCode().equals(ssn.getPseudoSSNReason().getCode())) {
                return true;
            }
        }
        return false;
    }


    private Boolean getSensitivityRecordIndicator( Person person ) {
        return person != null ? person.getSensitiveRecord() : null;
    }

    private Date getSensitivityChangeDate(Person person) {
        return (person != null) ? person.getSensitivityChangeDate() : null;
    }

	/**
	 * If the veteran has only visited one facility in the past two years, assign that facility as the
	 * Preferred Facility.
	 */
	private VAFacility getPreferredFacilityPast2Years(Collection PVSs)
	{
	    PatientVisitSummary preferredPVS = null;
	    if(PVSs != null) {
	        Date currentDateMinus2Years = getCurrentDateMinus2Years();
	        List PVSsPast2Years = new ArrayList();
	        for(Iterator iter=PVSs.iterator(); iter.hasNext();) {
	            PatientVisitSummary PVS = (PatientVisitSummary)iter.next();
	            Date lastVisitedDate = (PVS != null) ? PVS.getLastVisitDate() : null;
	            if(this.isAfter(lastVisitedDate,currentDateMinus2Years)) {
	                PVSsPast2Years.add(PVS);
	            }
	        }
	        preferredPVS = (PVSsPast2Years.size() == 1) ? (PatientVisitSummary)PVSsPast2Years.get(0) : null;
	    }
	    return (preferredPVS != null) ? preferredPVS.getFacilityVisited() : null;
	}

	/**
	 * If this does not result in the identification of a preferred facility, THEN identify the
	 * geographically closest facility within the boundaries of the VISN in which the veteran
	 * resides. The veteran’s zip code is used as a basis.
	 */
	private VAFacility getPreferredFacilityByLocation()
	{
	    //This method is intentionallt blank until further requirement clarifications.
	    return null;
	}

	/**
	 * If no zip code upon which to base the preferred facility is available, assign the
	 * most recent facility providing care as the preferred facility.
	 * @param sortedPatientVisitSummaries
	 */
	private VAFacility getPreferredFacilityRecentVisit(Collection sortedPatientVisitSummaries)
	{
	    PatientVisitSummary PVS = null;
	    if(sortedPatientVisitSummaries != null && !sortedPatientVisitSummaries.isEmpty()) {
	        PVS = (PatientVisitSummary)sortedPatientVisitSummaries.iterator().next();
	    }
	    return (PVS != null) ? PVS.getFacilityVisited() : null;
	}

	/**
	 * If no preferred facility value could be determined through the above steps, the institution ID
	 * of the last site to send HEC a message for that veteran is used as the preferred facility.
	 */
	private VAFacility getPreferredFacilityBySiteSendingMessage()
	{
	    PersonInputData inputData = (this.getRuleDataAware() instanceof PersonInputData) ? (PersonInputData)this.getRuleDataAware() : null;
	    return (inputData != null) ? inputData.getSendingFacility() : null;
	}

	/**
	 * Gets the current date minus 2 yaers.
	 */
	private Date getCurrentDateMinus2Years() {
	    Calendar calendar = this.toCalendar(this.getCurrentDate());
		if( calendar != null ) {
			calendar.add(Calendar.YEAR,-2);
		}
		return (calendar != null) ? calendar.getTime() : null;
	}

	/**
	 * Sorts the PatientVisitSummary on last visit date from recent to old.
	 * @param patientVisitSummaries PatientVisitSummary objects
	 * @return sorted PatientVisitSummary on last visit date from recent to old.
	 */
	private List sortLastVisitDateRecentToOld(Collection patientVisitSummaries)
    {
	    List sortedPatientVisitSummaries = new ArrayList();
        if(patientVisitSummaries != null && !patientVisitSummaries.isEmpty()) {
            sortedPatientVisitSummaries.addAll(patientVisitSummaries);
            Comparator comparator = new Comparator() {
                public int compare(Object pObject1, Object pObject2) {
                    Date date1 = (pObject1 instanceof PatientVisitSummary) ? ((PatientVisitSummary)pObject1).getLastVisitDate() : null;
                    Date date2 = (pObject2 instanceof PatientVisitSummary) ? ((PatientVisitSummary)pObject2).getLastVisitDate() : null;
                    return (date1 != null && date2 != null) ? (-date1.compareTo(date2)) : 0;
                }
            };
            Collections.sort(sortedPatientVisitSummaries,comparator);
        }
        return sortedPatientVisitSummaries;
    }

	private DeathRecord getDeathRecord(Person person) {
		return person != null ? person.getDeathRecord() : null;
	}

	private Date getDateOfDeath(Person person) {
		DeathRecord deathRecord = this.getDeathRecord(person);
		ImpreciseDate impreciseDate = (deathRecord != null) ? deathRecord
				.getDeathDate() : null;
		return (impreciseDate != null) ? ImpreciseDateUtils
				.getDateWithDefault(impreciseDate) : null;
	}

	
	
	private Date getDateOfDeathReported(Person person) {
		DeathRecord deathRecord = this.getDeathRecord(person);
		return (deathRecord != null) ? deathRecord.getDeathReportDate() : null;
	}

	private String getGenderCode(Person person) {
		return (person != null && person.getGender() != null) ? person
				.getGender().getCode() : null;
	}

	private Name getLegalName(Person person) {
		return (this.getIncomingPerson().getLegalName() != null) ? this
				.getIncomingPerson().getLegalName() : null;
	}

	private String getSSN(Person person) {
		return (person != null && person.getOfficialSsn() != null) ? person.getOfficialSsn().getFormattedSsnText() : null;
	}

	private void setDeathRecord(DeathRecord deathRecord) {
		getResultPerson().setDeathRecord(deathRecord);
	}

    private Name getNameFromLetter() {
        CommsInputData commsInputData = this.getCommsInputData();
        CommsLogEntry commsLogEntry = (commsInputData != null)?
                commsInputData.getCommsLogEntry() : null;
        return commsLogEntry != null ? commsLogEntry.getName() : null;
    }

	public PSDelegateService getPsDelegateService() {
		return psDelegateService;
	}

	public void setPsDelegateService(PSDelegateService psDelegateService) {
		this.psDelegateService = psDelegateService;
	}

    public PreferredFacilityService getPreferredFacilityService() {
		return preferredFacilityService;
	}

	public void setPreferredFacilityService(PreferredFacilityService preferredFacilityService) {
		this.preferredFacilityService = preferredFacilityService;
	}

	//The following are enhancements to ESR 3.1 VOA, please see
    //SUC_[676] Send Update Message (Z11)
	public boolean isTreatingFacilityExist() throws ServiceException
    {
    	Set facilities = this.getMessagingService().findSitesOfRecord(this.getPristinePerson());
    	return facilities == null ? false : facilities.size()>0;
    }
    public Set findSitesOfRecord() throws ServiceException
    {
    	return this.getMessagingService().findSitesOfRecord(this.getIncomingPerson());
    }

	public String getIDState() throws ServiceException {
        //CCR 11403: for Vista inbound messages, check enrollment records, add 200ESRCorreltion if necessary
		this.getPersonService().checkAndAddESRCorrelation(this.getPristinePerson());

		return getPsDelegateService().getIDState(
				this.getIncomingPerson().getVPIDEntityKey());
	}

	public boolean isRequestIDStateChangeError() throws ServiceException
	{
		try{
			this.getPsDelegateService().requestIDStateChange(
					getIncomingPerson().getVPIDEntityKey(),
					getPristinePerson().getEnrollmentDetermination().getEnrollmentStatus());
		} catch (ServiceException ex)
		{
			this.logger.error("Failed to Request IDState Change", ex);
			return true;
		}
		return false;
	}

	public boolean isCreatePreferFacilityError() throws ServiceException
	{
		try{
			this.getPersonService().addPreferredFacilityCorrelation(this.getIncomingPerson());
		} catch (ServiceException ex)
		{
			this.logger.error("Failed to Add Preferred Facility", ex);
			return true;
		}
		return false;
	}

	public boolean hasIncomingPreferredFacility() {
    	return !this.getIncomingPerson().getPreferredFacilities().isEmpty();
    }

    public boolean hasResultPreferredFacility() {
    	return !this.getResultPerson().getPreferredFacilities().isEmpty();
    }

   /**
    * Returns the preferred facility sent via the inbound Z07 from VistA.  Since VistA only has
    * one preferred facility, we take only the first one.
    */
    public PreferredFacility getPreferredFacility() {
    	Iterator iter = this.getIncomingPerson().getPreferredFacilities().iterator();
    	return (iter.hasNext()? (PreferredFacility)iter.next() : null);
    }

	/**
	 * R-12: Modify ESR to ignore PF value sent from VistA on the ZEN-8 segment of the Z07 for the following condition:
	 * 1.	The most recent (using the date/time stamp) PF on file has a “Source Designation of “PCP Active or “ESR”.
	 * @return
	 * @throws ServiceException
	 */
	public boolean shouldIgnoreIncomingPreferredFacility() throws ServiceException
	{
		return getPreferredFacilityService().hasPCPActiveOrESRPF(this.getPristinePerson());
	}

	/**
	 * CCR12879_AJ_VOA - Apply PF processing logic when VOA submitted PF does:
	 * Ignore PF value sent from VOA for the following condition:
	 * 1.	The most recent (using the date/time stamp) PF on file has a “Source Designation of “PCP Active.
	 * @return
	 * @throws ServiceException
	 */
	public boolean shouldIgnoreIncomingVOAPreferredFacility() throws ServiceException
	{
		return getPreferredFacilityService().hasPCPActivePF(this.getPristinePerson());
	}

	/**
	 * R-44: Modify ESR to always upload the PF sent from VistA and assign the “SD” of “VistA” if the Veteran does not exist in ESR.
	 * R-45: Modify ESR to upload the PF received from VistA when ESR’s most recent (using the   date/time stamp) “SD” is equal to “VistA” or “PCP Inactive” or if ESR does not have a PF on file (Newly registered Veteran).
	 *
	 * Note:  ESR will assign a “Source Designation of “VistA” upon upload since VistA will not share the “SD” via Z07.
	 */
	 public boolean shouldAddIncomingPreferredFacility() throws ServiceException
	{
		return ! this.getPreferredFacilityService().hasActivePreferredFacility(this.getPristinePerson().getPreferredFacilities());
	}

	/**
	 * ES 4.0_CodeCR12879 - Modify ESR to always upload the PF sent from VOA and assign the “SD” of “ESR” if the Veteran does not exist in ESR.
	 * VOA applicant is known to ES:

	 * If there is a PF in ADR table with source designation as "PCP Active" do not add the PF from VOA form
	 * But share data with VistA site selected by the user depending on the person's authentication level. The preferred facility selected in the form is stored in VOA.VOA_ENROLLMENT_APPLICATION table.

	 * If there is a PF in ADR table with source designation as "PCP Inactive", add the PF from VOA form to ADR.Preferred facility table
	 * Share data to VistA depending on the person's authentication level

	 * If there is a PF in ADR table with source designation as "VistA", add the PF from VOA form to ADR.Preferred facility table
	 * Share data to VistA depending on the person's authentication level

	 * If there is a PF in ADR table with source designation as "ESR", update the PF to one from the VOA form to ADR.Preferred facility table
	 * Share data to VistA depending on the person's authentication level . Data will also be shared with old Preferred facility.
	 * Note:  Any preferred facility added from VOA form will carry the source designation of "ESR"
	 */
	 public boolean shouldAddIncomingVOAPreferredFacility() throws ServiceException
	{
		 return getPreferredFacilityService().hasVistaOrPCPInactivePF(this.getPristinePerson());
	}

	/**
	 * Add a new preferred facility from VistA or ESR. Trigger Z11.
	 */
	public void addIncomingPreferredFacility(PreferredFacility incoming, String sdCode) throws ServiceException
	{
		this.getPreferredFacilityService().addPreferredFacility(incoming, this.getResultPerson(), sdCode);

		// Update the calculated mostRecentPreferredFacility on Person
        this.getPreferredFacilityService().setCalculatedMostRecentPreferredFacility(getResultPerson());

	}

	/**
	 * If source designation is "VistA", update the existing preferred facility record by: deactivating the existing VistA
	 * record; and add or update the incoming facility's record
	 */
	public void updatePreferredFacilityFromVistA(PreferredFacility incoming) throws ServiceException
	{
		// look for existing preferred facility with source designation as VistA first, deactivate it first
		PreferredFacility vistaPf = getPreferredFacilityService().getPreferredFacilityFromVista(this.getResultPerson());
		if (vistaPf != null) {
			if (incoming.getFacility().getStationNumber().equals(vistaPf.getFacility().getStationNumber())) {
				// if existing preferred facility with source designation is already for the same facility, do nothing
				return;
			}
			else {
				// retire the existing VistA record, followed by adding the new one in:
				vistaPf.setUnassignmentDate(new Date());
			}
		}
		this.getPreferredFacilityService().addPreferredFacility(incoming, this.getResultPerson(), SourceDesignation.CODE_VISTA.getCode());

		// Update the calculated mostRecentPreferredFacility on Person
        this.getPreferredFacilityService().setCalculatedMostRecentPreferredFacility(getResultPerson());
	}

	/**
	 * CCR12879_AJ_VOA - Apply PF processing logic when VOA submitted PF
	 * If source designation is "ESR", update the existing preferred facility record by: deactivating the existing ESR
	 * record; and add or update the incoming facility's record
	 */
	public void updatePreferredFacilityFromVOA(PreferredFacility incoming) throws ServiceException
	{
		// look for existing preferred facility with source designation as ESR first, deactivate it first
		PreferredFacility esPf = getPreferredFacilityService().getPreferredFacilityFromES(this.getResultPerson());
		if (esPf != null) {
			if (incoming.getFacility().getStationNumber().equals(esPf.getFacility().getStationNumber())) {
				// if existing preferred facility with source designation is already for the same facility, do nothing
				return;
			}
			else {
				// retire the existing ESR record, followed by adding the new one in:
				esPf.setUnassignmentDate(new Date());
			}
		}
		this.getPreferredFacilityService().addPreferredFacility(incoming, this.getResultPerson(), SourceDesignation.CODE_ESR.getCode());

		// Update the calculated mostRecentPreferredFacility on Person
        this.getPreferredFacilityService().setCalculatedMostRecentPreferredFacility(getResultPerson());
	}

	/**
	 * Receive updated preferred facility data from UI, and update the result person data per UI update rules
	 * R-28:  Modify ESR to allow a user to only update Preferred Facility field manually  if veteran PF “Source Designation” is “PCP Inactive” or “ESR” or “Vista” or “NULL”
	 * Trigger Z11 if data is changed.
	 */
	public void updatePreferredFacilitiesFromUI() throws ServiceException
	{
	       Set incomingPFs = getIncomingPerson().getPreferredFacilities();

	        for(Iterator iter = incomingPFs.iterator(); iter.hasNext();) {
	            PreferredFacility incomingPF = (PreferredFacility)iter.next();

	            // if a new preferred facility, add it as "ESR"
	            if(incomingPF.getEntityKey() == null) {
	        		this.getPreferredFacilityService().addPreferredFacility(incomingPF, this.getResultPerson(), SourceDesignation.CODE_ESR.getCode());
	            } else {
	            	// if an existing entry is updated, inactivate onFile preferred facility; and insert the changed data as a new one w/ SD="ESR"
	            	// Modify ESR to allow a user to only update Preferred Facility field manually  if veteran PF “Source Designation” is “PCP Inactive” or “ESR” or “Vista” or “NULL”

	            	PreferredFacility onFilePF = (PreferredFacility)getResultPerson().getPreferredFacilityByEntityKey(incomingPF.getEntityKey());

	            	if (onFilePF != null &&
	            		!onFilePF.getFacility().getStationNumber().equals(incomingPF.getFacility().getStationNumber()) &&
	            		!onFilePF.getSourceDesignation().getCode().equals(SourceDesignation.CODE_PCP_ACTIVE.getCode()))
	            	{
	                	onFilePF.setUnassignmentDate(new Date());
		        		this.getPreferredFacilityService().addPreferredFacility(incomingPF, this.getResultPerson(), SourceDesignation.CODE_ESR.getCode());
	                }
	            }
	        }

	        // Update the calculated mostRecentPreferredFacility on Person
	        this.getPreferredFacilityService().setCalculatedMostRecentPreferredFacility(getResultPerson());
	}

   /**
    * Returns true if the parent sending site of the preferred facility changed
    */
	public boolean isPreferredFacilityChanged() throws ServiceException {
		boolean anyPFChanges = false;

		//CCR11668 Modify handbook PF insert trigger to trigger only when a PF is added, or
		//an existing PF is replaced by a different PF
		//VAFacility resultPF = getResultPerson().getMostRecentPreferredFacility();
		//VAFacility pristinePF = getPristinePerson().getMostRecentPreferredFacility();

		Set resultPFs = this.getPreferredFacilityService().getPFSites(getResultPerson());
		Set pristinePFs = this.getPreferredFacilityService().getPFSites(getPristinePerson());

		//VAFacility resultParent = getParentSite(resultPF);
		//VAFacility pristineParent = getParentSite(pristinePF);

		/*return ! (resultParent != null ?
				(pristineParent != null && resultParent.getCode().equals(pristineParent.getCode())) :
					pristineParent == null ); */

		for (Iterator iter = resultPFs.iterator(); iter.hasNext(); ) {
			if (pristinePFs.contains (iter.next())) {
				continue;
			} else {
				return true;
			}
		}
		return anyPFChanges;
	}
	
	public boolean isMVIDODServiceActive() throws RuleException {		
		try {
			SystemParameter sysParameter = getSystemParameterService().getByName(
					SystemParameter.DOD_INDICATOR);
			if (sysParameter != null && sysParameter.getValue() != null) {
				return sysParameter.getValue().equalsIgnoreCase("Y") ? true
						: false;
			}
		} catch (Exception ex) {
			throw new RuleException(
					"Error getting SystemParameter (DOD_INDICATOR) value", ex);
		}
		
		return false;
		
	}
	
	public boolean wasDODVerified() {		
		if ((this.getPristinePerson().getDeathRecord() != null)
				&& (this.getPristinePerson().getDeathRecord()
						.getDodVerifiedInd() != null)
				&& this.getPristinePerson().getDeathRecord()
						.getDodVerifiedInd()) {
			return true;
		}

		return false;
	}
	
	public void setDoDModifiedBy() {		
		DeathRecord resultDeathRecord = getDeathRecord(getResultPerson());
		if(resultDeathRecord != null){			
			if(this.isUpdateFromGUI()){
				resultDeathRecord.setDeathReportedBy("ES_USER");   
        	}
        	else{ 
        		if(this.getIncomingPerson().getModifiedBy() != null && !(StringUtils.isBlank(this.getIncomingPerson().getModifiedBy().getName()) )){
        			resultDeathRecord.setDeathReportedBy(this.getIncomingPerson().getModifiedBy().getName());        			
        		}
        		else{
        			resultDeathRecord.setDeathReportedBy("MVI Date of Death Process");
        		}        		
        	} 			
		}
	}
	
	public boolean isDODVerified() {		
		if ((this.getDeathRecord() != null)
				&& (this.getDeathRecord()
						.getDodVerifiedInd() != null)
				&& (this.getDeathRecord()
						.getDodVerifiedInd())) {
			return true;
		}

		return false;
	}

	public PersonService getPersonService() {
		return personService;
	}

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