/*******************************************************************************
 * 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.financials.Asset;
import gov.va.med.esr.common.model.financials.Debt;
import gov.va.med.esr.common.model.financials.DependentFinancials;
import gov.va.med.esr.common.model.financials.Expense;
import gov.va.med.esr.common.model.financials.FinancialInformation;
import gov.va.med.esr.common.model.financials.FinancialStatement;
import gov.va.med.esr.common.model.financials.Income;
import gov.va.med.esr.common.model.financials.IncomeTest;
import gov.va.med.esr.common.model.financials.IncomeTestStatus;
import gov.va.med.esr.common.model.financials.IncomeThreshold;
import gov.va.med.esr.common.model.financials.RelationFinancials;
import gov.va.med.esr.common.model.financials.SpouseFinancials;
import gov.va.med.esr.common.model.lookup.AssetType;
import gov.va.med.esr.common.model.lookup.ExpenseType;
import gov.va.med.esr.common.model.lookup.IncomeTestType;
import gov.va.med.esr.common.model.lookup.IncomeType;
import gov.va.med.esr.common.model.lookup.MeansTestStatus;
import gov.va.med.esr.common.model.lookup.Relationship;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.Relation;
import gov.va.med.esr.common.model.person.SSN;
import gov.va.med.esr.common.rule.FinancialInfoInput;
import gov.va.med.esr.common.rule.data.BaseData;
import gov.va.med.esr.common.rule.data.CommsInputData;
import gov.va.med.esr.common.rule.data.EventInputData;
import gov.va.med.esr.common.rule.data.EEInputData;
import gov.va.med.esr.common.rule.data.FinancialInputData;
import gov.va.med.esr.service.FinancialsHelperService;
import gov.va.med.fw.model.lookup.AbstractCode;
import gov.va.med.fw.model.lookup.Lookup;
import gov.va.med.fw.rule.RuleDataAware;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.esr.common.util.CommonDateUtils;

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

import org.apache.commons.lang.Validate;

/**
 * Abstract input parameter class for Financials to contains common methods
 * required by all the financials including spouse and dependent financials.
 * 
 * @author Ghazenfer Mansoor
 * @version 1.0
 */
public abstract class AbstractFinancialInputParameter extends BaseParameter
        implements FinancialInfoInput {

    private static final long serialVersionUID = -1370346746640850300L;

    protected static final BigDecimal ZERO_AMOUNT = new BigDecimal(0);

    private int spousalSupportLimit = 600;

    private Relationship.Code[] childRelationships = null;

    private ExpenseType.Code[] childExpenseTypes = null;

    private ExpenseType.Code[] veteranExpenseTypes = null;

    private Integer numberOfDependentChildren = null;

    private Integer numberOfDependents = null;

    private BigDecimal calculatedTotalIncome = null;

    private BigDecimal calculatedNetIncome = null;

    private BigDecimal calculatedNetIncomeForCE = null;

    private BigDecimal calculatedDeductibleExpense = null;

    private BigDecimal calculatedNetworth = null;

    private BigDecimal calculatedIncomeExclusionThreshold = null;

    private BigDecimal calculatedPensionThreshold = null;

    private BigDecimal calculatedMedicalExpense = null;

    private BigDecimal calculatedAdjustableMedicalExpense = null;

    private FinancialsHelperService financialsHelperService = null;

    public AbstractFinancialInputParameter() {
        super();
    }

    /**
     * afterPropertiesSet
     * 
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception {
        super.afterPropertiesSet();
        Validate.notNull(financialsHelperService,
                "FinancialHelperService is required.");
        Validate.notNull(childRelationships,
                "Child Relationship array (Relationship.Code[]) is required.");
        Validate.notNull(childExpenseTypes,
                "Child Expense types array is required.");
        Validate.notNull(veteranExpenseTypes,
                "Veteran Expense types array is required.");
    }

    public void throwMTTotalIncomePresentAndFinancialStmtIncomeNullException()
            throws MTTotalIncomePresentAndFinancialStmtIncomeNullException {

        throw new MTTotalIncomePresentAndFinancialStmtIncomeNullException(
                "MT total income present and financial statement income is null.");
    }

    public BigDecimal getIncomingIncomeTestTotalIncome() {
        return this.getIncomingIncomeTest() != null ? this
                .getIncomingIncomeTest().getTotalIncome() : null;
    }

    public boolean isIncomingVeteranIncomeNull() {
        FinancialStatement incomingFS = getIncomingFinancialStatement();
        // CCR 8893: add checks for NULL
        return incomingFS == null ? true : incomingFS.getIncome().isEmpty();
    }

    public boolean isIncomingVeteranSpouseIncomeNull() {
        FinancialStatement incomingFS = getIncomingFinancialStatement();
        // CCR 8893: add checks for NULL
        if (incomingFS != null) {
            for (Iterator i = incomingFS.getSpouseFinancials().iterator(); i
                    .hasNext();) {
                SpouseFinancials sf = (SpouseFinancials) i.next();
                if (sf != null && !(sf.getIncome().isEmpty())) {
                    return false;
                }
            }
        }
        return true;
    }

    public boolean isIncomingVeteranDependantsIncomeNull() {
        FinancialStatement incomingFS = getIncomingFinancialStatement();
        // CCR 8893: add checks for null
        if (incomingFS != null) {
            for (Iterator iter = incomingFS.getDependentFinancials().iterator(); iter
                    .hasNext();) {
                DependentFinancials df = (DependentFinancials) iter.next();
                if (df != null && !(df.getIncome().isEmpty())) {
                    return false;
                }
            }
        }
        return true;
    }

    public Boolean getDisclosure(IncomeTest test) {
        if (test != null) {
            return test.getDiscloseFinancialInformation();
        }
        return null;
    }

    public Boolean getAgreeToPayIndicator(IncomeTest test) {
        if (test == null) {
            return null;
        }
        return test.getAgreesToPayDeductible();
    }

    protected void setIncomeAmount(FinancialInformation finInfo,
            IncomeType.Code typeCode, BigDecimal amount) {
        IncomeType incomeType = getIncomeType(typeCode);
        setIncomeAmount(finInfo, incomeType, amount);
    }

    protected void setAssetAmount(FinancialInformation finInfo,
            AssetType.Code typeCode, BigDecimal amount) {
        AssetType assetType = getAssetType(typeCode);
        setAssetAmount(finInfo, assetType, amount);
    }

    protected void setExpenseAmount(FinancialInformation finInfo,
            ExpenseType.Code typeCode, BigDecimal amount) {
        ExpenseType expenseType = getExpenseType(typeCode);
        setExpenseAmount(finInfo, expenseType, amount);
    }

    protected void setIncomeAmount(FinancialInformation finInfo,
            IncomeType incomeType, BigDecimal amount) {
        Validate.notNull(finInfo,
                "Can not set amount on NULL FinancialInformation Object.");
        Income income = finInfo.getIncome(incomeType);
        if (income == null) {
            income = new Income();
            finInfo.setIncome(incomeType, income);
        }
        income.setAmount(amount);
    }

    protected void setAssetAmount(FinancialInformation finInfo,
            AssetType assetType, BigDecimal amount) {
        Validate.notNull(finInfo,
                "Can not set amount on NULL FinancialInformation Object.");
        Asset asset = finInfo.getAsset(assetType);
        if (asset == null) {
            asset = new Asset();
            finInfo.setAsset(assetType, asset);
        }
        asset.setAmount(amount);
    }

    protected void setExpenseAmount(FinancialInformation finInfo,
            ExpenseType expenseType, BigDecimal amount) {
        Validate.notNull(finInfo,
                "Can not set amount on NULL FinancialInformation Object.");
        Expense expense = finInfo.getExpense(expenseType);
        if (expense == null) {
            expense = new Expense();
            finInfo.setExpense(expenseType, expense);
        }
        expense.setAmount(amount);
    }

    protected BigDecimal getIncomeAmount(FinancialInformation finInfo,
            IncomeType.Code typeCode) {
        if (finInfo == null || typeCode == null)
            return null;
        Income income = finInfo.getIncome(typeCode);
        return income != null ? income.getAmount() : null;
    }

    protected BigDecimal getExpenseAmount(FinancialInformation finInfo,
            ExpenseType.Code typeCode) {
        if (finInfo == null || typeCode == null)
            return null;

        Expense expense = finInfo.getExpense(typeCode);
        return expense != null ? expense.getAmount() : null;
    }

    protected BigDecimal getAssetAmount(FinancialInformation finInfo,
            AssetType.Code typeCode) {
        if (finInfo == null || typeCode == null)
            return null;
        Asset asset = finInfo.getAsset(typeCode);
        return asset != null ? asset.getAmount() : null;
    }

    protected ExpenseType getExpenseType(ExpenseType.Code typeCode) {
        try {
            return getLookupService().getExpenseTypeByCode(typeCode.getName());
        } catch (Exception ex) {
            throw new IllegalArgumentException(
                    "Invalid Expense type code specified");
        }
    }

    protected IncomeType getIncomeType(IncomeType.Code typeCode) {
        try {
            return getLookupService().getIncomeTypeByCode(typeCode.getName());
        } catch (Exception ex) {
            throw new IllegalArgumentException(
                    "Invalid Income type code specified");
        }
    }

    protected AssetType getAssetType(AssetType.Code typeCode) {
        try {
            return getLookupService().getAssetTypeByCode(typeCode.getName());
        } catch (Exception ex) {
            throw new IllegalArgumentException(
                    "Invalid Asset type code specified");
        }
    }

    public String getMeansTestStatusCode(IncomeTest incomeTest,
            IncomeTestType.Code testType) {
        MeansTestStatus status = getMeansTestStatus(incomeTest, testType);
        return status == null ? null : status.getCode();
    }

    public String getDeterminedStatusCode(IncomeTest incomeTest,
            IncomeTestType.Code testType) {
        MeansTestStatus status = getDeterminedStatus(incomeTest, testType);
        return status == null ? null : status.getCode();
    }

    public MeansTestStatus getDeterminedStatus(IncomeTest incomeTest,
            IncomeTestType.Code testType) {
        IncomeTestStatus status = getIncomeTestStatus(incomeTest, testType);
        return status == null ? null : status.getDeterminedStatus();
    }

    public MeansTestStatus getMeansTestStatus(IncomeTest incomeTest,
            IncomeTestType.Code testType) {
        IncomeTestStatus status = getIncomeTestStatus(incomeTest, testType);
        return status == null ? null : status.getStatus();
    }

    protected IncomeTestStatus getIncomeTestStatus(IncomeTest incomeTest,
            IncomeTestType.Code testType) {
        IncomeTestStatus status = null;
        if (incomeTest != null && testType != null) {
            status = IncomeTest.getIncomeTestStatusOfType(incomeTest
                    .getStatuses(), testType);
        }
        return status;
    }

    public Date getEffectiveTestDate(IncomeTest iTest) {
        return iTest != null ? iTest.getEffectiveDate() : null;
    }

    protected void setDebtInfo(FinancialInformation finInfo, Debt debt) {
        if (debt != null) {
            setDebtAmount(finInfo, debt.getAmount());
        }
    }

    protected void setDebtAmount(FinancialInformation finInfo, BigDecimal amount) {
        Validate.notNull(finInfo,
                "Can not set amount on NULL FinancialInformation Object.");
        Debt debt = finInfo.getDebt();
        if (debt == null) {
            debt = new Debt();
            finInfo.setDebt(debt);
        }
        debt.setAmount(amount);
    }

    protected void setAssetInfo(FinancialInformation finInfo, Collection assets) {
        if (assets != null) {
            for (Iterator iter = assets.iterator(); iter.hasNext();) {
                Asset asset = (Asset) iter.next();
                setAssetAmount(finInfo, asset.getType(), asset.getAmount());
            }
        }
    }

    protected void setIncomeInfo(FinancialInformation finInfo,
            Collection incomes) {
        if (incomes != null) {
            for (Iterator iter = incomes.iterator(); iter.hasNext();) {
                Income income = (Income) iter.next();
                setIncomeAmount(finInfo, income.getType(), income.getAmount());
            }
        }
    }

    protected void setExpenseInfo(FinancialInformation finInfo,
            Collection expenses) {
        if (expenses != null) {
            for (Iterator iter = expenses.iterator(); iter.hasNext();) {
                Expense expense = (Expense) iter.next();
                setExpenseAmount(finInfo, expense.getType(), expense
                        .getAmount());
            }
        }
    }

    protected FinancialStatement getOrCreateFinancialStatement() {
        Integer incomeYear = getIncomingFinancialStatement().getIncomeYear();
        FinancialStatement rStmt = getResultPerson().getFinancialStatement(
                incomeYear);
        if (rStmt == null) {
            rStmt = new FinancialStatement();
            getResultPerson().setFinancialStatement(incomeYear, rStmt);
        }
        return rStmt;
    }

    protected IncomeTest getOrCreateIncomeTest() {
        Integer incomeYear = getIncomingIncomeYear();
        IncomeTest rIncomeTest = getResultPerson().getIncomeTest(incomeYear);
        if (rIncomeTest == null) {
            rIncomeTest = new IncomeTest();
            rIncomeTest.setIncomeYear(incomeYear);
            getResultPerson().setIncomeTest(incomeYear, rIncomeTest);
        }
        return rIncomeTest;
    }

    public FinancialStatement getPristineFinancialStatement() {
        return getFinancialStatement(getPristinePerson(),
                getIncomingIncomeYear());
    }

    public FinancialStatement getIncomingFinancialStatement() {
        return getFinancialStatement(getIncomingPerson(),
                getIncomingIncomeYear());
    }

    public FinancialStatement getFinancialStatement(Person person,
            Integer incomeYear) {
        if (person != null && incomeYear != null) {
            return person.getFinancialStatement(incomeYear);
        }
        return null;
    }

    public IncomeTest getIncomeTest(Person person, Integer incomeYear) {
        if (person != null && incomeYear != null) {
            return person.getIncomeTest(incomeYear);
        }
        return null;
    }

    public IncomeTest getPristineIncomeTest() {
        RuleDataAware data = this.getRuleDataAware();

        // This is for context where rules need to find prior test
        // Example, if 2005 is provided, it will return immediately prior test
        // (2004)
        if (data != null && data instanceof BaseData) {
            Integer year = ((BaseData) data).getIncomeYearForCurrentTest();
            if (year != null) {
                return getPriorIncomeTest(getPristinePerson(), year);
            }
        }

        return getIncomeTest(getPristinePerson(), getIncomingIncomeYear());
    }

    public IncomeTest getIncomingIncomeTest() {
        Integer incomeYear = getIncomingIncomeYear();
        if (incomeYear != null) {
            return getIncomeTest(getIncomingPerson(), getIncomingIncomeYear());
        }
        return getHelperService().getCurrentIncomeTest(getIncomingPerson());
    }

    public Integer getIncomingIncomeYear() {
        FinancialInputData data = this.getFinancialInputData();
        if (data != null) {
            return data.getIncomingIncomeYear();
        }
        // The following should only execute when this parameter is used
        // in different context from Financial, specifically TriggerMessage.
        RuleDataAware rda = this.getRuleDataAware();
        if (rda instanceof EventInputData) {
            Integer incomeYear = ((EventInputData) rda).getIncomeYear();
            IncomeTest current = null;
            if (incomeYear != null) {
                current = this.getIncomingPerson().getIncomeTest(incomeYear);
            }
            if (current != null) {
                return current.getIncomeYear();
            }

            current = this.getHelperService().getCurrentIncomeTest(
                    this.getIncomingPerson());
            return current != null ? current.getIncomeYear() : null;
        } else if ((rda instanceof EEInputData)
                || (rda instanceof CommsInputData)) {
            IncomeTest test = getHelperService().getCurrentIncomeTest(
                    getIncomingPerson());
            if (test != null) {
                return test.getIncomeYear();
            }
        }
        return null;
    }

    protected VAFacility getSourceFacility() {
        FinancialInputData data = this.getFinancialInputData();
        if (data != null) {
            return data.getSourceFacility();
        }
        return null;
    }

    protected FinancialInputData getFinancialInputData() {
        RuleDataAware ruleDataAware = this.getRuleDataAware();
        if (ruleDataAware instanceof FinancialInputData) {
            return (FinancialInputData) ruleDataAware;
        }
        return null;
    }

    protected String getRelationGivenName(Relation relation) {
        return (relation != null && relation.getName() != null) ? relation
                .getName().getGivenName() : null;
    }

    protected String getRelationFamilyName(Relation relation) {
        return (relation != null && relation.getName() != null) ? relation
                .getName().getFamilyName() : null;
    }

    protected String getRelationSSN(Relation relation) {
        SSN ssn = relation != null ? relation.getOfficialSsn() : null;
        return ssn == null ? null : ssn.getFormattedSsnText();
    }

    protected boolean isRelationSSNUsed(RelationFinancials relFins) {
        if (relFins == null)
            return false;
        String relSSN = (relFins instanceof SpouseFinancials) ? getRelationSSN(((SpouseFinancials) relFins)
                .getReportedOn())
                : getRelationSSN(((DependentFinancials) relFins)
                        .getReportedOn());
        if (StringUtils.isEmpty(relSSN))
            return false;

        FinancialStatement finStmt = relFins.getFinancialStatement();
        if (finStmt == null)
            return false;

        // Is the SSN is matching other spouses
        for (Iterator i = finStmt.getSpouseFinancials().iterator(); i.hasNext();) {
            SpouseFinancials spouseFin = (SpouseFinancials) i.next();
            if (!spouseFin.equals(relFins)) {
                if (StringUtils.equals(relSSN, getRelationSSN(spouseFin
                        .getReportedOn()))) {
                    return true;
                }
            }
        }

        // Is the SSN is matching other dependents
        for (Iterator iter = finStmt.getDependentFinancials().iterator(); iter
                .hasNext();) {
            DependentFinancials depFin = (DependentFinancials) iter.next();
            if (!relFins.equals(depFin)) {
                if (StringUtils.equals(relSSN, getRelationSSN(depFin
                        .getReportedOn()))) {
                    return true;
                }
            }
        }
        return false;
    }

    protected Date getRelationEffectiveDate(Relation relation) {
        ImpreciseDate startImpDate = (relation != null) ? relation
                .getStartDate() : null;
        if (startImpDate != null)
            return ImpreciseDateUtils.getDateWithDefault(startImpDate);
        else
            return null;
    }

    protected Date getRelationBirthDate(Relation relation) {
        if (relation != null)
            return ImpreciseDateUtils.getDateWithDefault(relation.getDob());
        return null;
    }

    protected Date getRelationInActiveDate(Relation relation) {
        return (relation != null) ? relation.getEndDate() : null;
    }

    public boolean isEquals(Date date1, Date date2) {
        if (date1 != null && date2 != null && date1.equals(date2)) {
            return true;
        }
        return false;
    }

    public boolean isEquals(Boolean obj1, Boolean obj2) {
        return obj1 != null ? obj1.equals(obj2) : obj2 == null;
    }

    public Date getLatest(Date date1, Date date2) {
        if (date1 != null && date2 != null) {
            return date1.before(date2) ? date2 : date1;
        }
        return (date1 != null) ? date1 : date2;
    }

    public Date getEarliest(Date date1, Date date2) {
        if (date1 != null && date2 != null) {
            return date1.after(date2) ? date2 : date1;
        }
        return (date1 != null) ? date1 : date2;
    }

    public boolean isEarlier(Date date1, Date date2) {
        if (date1 != null && date2 != null) {
            return date1.before(date2);
        }
        return (date1 != null) ? true : false;
    }

    protected String getLookupCode(Lookup lookup) {
        return lookup == null ? null : lookup.getCode();
    }

    protected boolean isIncomeInRange(Collection incomes, double lower,
            double upper) {
        boolean valid = true;
        if (incomes != null && !incomes.isEmpty()) {
            for (Iterator iter = incomes.iterator(); (valid && iter.hasNext());) {
                Income income = (Income) iter.next();
                valid = (income == null) ? true : this.isInRange(income
                        .getAmount(), lower, upper);
            }
        }
        return valid;
    }

    protected boolean isExpenseInRange(Collection expenses, double lower,
            double upper) {
        boolean valid = true;
        if (expenses != null && !expenses.isEmpty()) {
            for (Iterator iter = expenses.iterator(); (valid && iter.hasNext());) {
                Expense expense = (Expense) iter.next();
                valid = (expense == null) ? true : this.isInRange(expense
                        .getAmount(), lower, upper);
            }
        }
        return valid;
    }

    protected boolean isAssetInRange(Collection assets, double lower,
            double upper) {
        boolean valid = true;
        if (assets != null && !assets.isEmpty()) {
            for (Iterator iter = assets.iterator(); (valid && iter.hasNext());) {
                Asset asset = (Asset) iter.next();
                valid = (asset == null) ? true : this.isInRange(asset
                        .getAmount(), lower, upper);
            }
        }
        return valid;
    }

    /**
     * Return the Income exclusion threshold value for the incoming income year.
     * 
     * @return
     * @throws ServiceException
     */
    public BigDecimal getCalculatedIncomeExclusionThreshold()
            throws ServiceException {
        if (calculatedIncomeExclusionThreshold == null) {
            calculatedIncomeExclusionThreshold = determineIncomeExclusionThreshold(getIncomingIncomeYear());
        }
        return calculatedIncomeExclusionThreshold;
    }

    /**
     * Return the income exclusion threshold value for a given income year.
     * 
     * @param incomeYear
     * @return the threshold value
     * @throws ServiceException
     */
    protected BigDecimal determineIncomeExclusionThreshold(Integer incomeYear)
            throws ServiceException {
        IncomeThreshold it = getLookupService().getIncomeThreshold(incomeYear);
        BigDecimal threshold = it == null ? ZERO_AMOUNT : it
                .getChildIncomeExclusion();
        return threshold == null ? ZERO_AMOUNT : threshold;
    }

    public BigDecimal getCalculatedPensionThreshold() throws ServiceException {
        if (calculatedPensionThreshold == null) {
            IncomeTest test = getIncomingIncomeTest();
            calculatedPensionThreshold = determinePensionThreshold(test
                    .getIncomeYear(), getCalculatedNumberOfDependents());
        }
        return calculatedPensionThreshold;
    }

    private BigDecimal determinePensionThreshold(Integer incomeYear,
            Integer dependentCount) throws ServiceException {
        if (incomeYear == null) {
            return ZERO_AMOUNT;
        }
        BigDecimal pensionThreshold = new BigDecimal(0);
        IncomeThreshold it = getLookupService().getIncomeThreshold(incomeYear);
        if (it != null && dependentCount != null) {
            int count = dependentCount.intValue();
            if (count == 0) {
                pensionThreshold = getNotNull(it.getPensionThreshold());
            } else if (count > 0) { // for 1st dependent
                pensionThreshold = getNotNull(it.getPension1Dependent());
            }
            // for each extra dependent in addition to 1, add to pension for 1
            // dep
            for (int i = 1; i < count; i++) {
                pensionThreshold = pensionThreshold.add(getNotNull(it
                        .getAddDependentPension()));
            }
        }
        return pensionThreshold;

    }

    public BigDecimal getCalculatedNonReimbursedMedicalExpense()
            throws ServiceException {
        if (calculatedMedicalExpense == null) {
            IncomeTest test = getIncomingIncomeTest();
            FinancialStatement stmt = getIncomingFinancialStatement();
            calculatedMedicalExpense = determineNonReimbursedMedicalExpense(
                    test, stmt);
        }
        return calculatedMedicalExpense;
    }

    public BigDecimal getCalculatedAdjustableMedicalExpense()
            throws ServiceException {
        if (calculatedAdjustableMedicalExpense == null) {
            FinancialStatement stmt = getIncomingFinancialStatement();
            calculatedAdjustableMedicalExpense = determineAdjustableMedicalExpense(stmt);
        }
        return calculatedAdjustableMedicalExpense;
    }

    private BigDecimal determineAdjustableMedicalExpense(FinancialStatement stmt)
            throws ServiceException {

        ExpenseType.Code expenseType = ExpenseType.EXPENSE_TYPE_NON_REIMBURSED_MEDICAL;

        // CCR9660 -- SUC461.35.5.1
        // If this is VOA message, then the Medical Expense field contains the
        // total Expense
        // MEDICAL EXPENSES (1.01) [veteran only] The value is the adjusted
        // medical expenses. If this is VOA then it is Total Expense.
        if (isMessageFromVOA()) {
            expenseType = ExpenseType.EXPENSE_TYPE_ADJUSTED_MEDICAL;
        }

        BigDecimal medExpClaimed = getExpenseAmount(stmt, expenseType);

        if (medExpClaimed == null) {
            return ZERO_AMOUNT;
        }
        Integer prevYear = new Integer((stmt.getIncomeYear().intValue()) - 1);
        BigDecimal pensionThreshold = determinePensionThreshold(prevYear,
                getCalculatedNumberOfDependents());
        BigDecimal deductibleAmount = pensionThreshold
                .multiply(getMedicalDeductionRate(prevYear));
        if (medExpClaimed.compareTo(deductibleAmount) > 0) {
            return medExpClaimed.subtract(deductibleAmount);
        } else {
            return ZERO_AMOUNT;
        }
    }

    private BigDecimal determineNonReimbursedMedicalExpense(
            IncomeTest incomeTest, FinancialStatement stmt)
            throws ServiceException {
    	
        BigDecimal adjustedMedExp = getExpenseAmount(stmt,
                ExpenseType.EXPENSE_TYPE_ADJUSTED_MEDICAL);
        if (adjustedMedExp == null || adjustedMedExp.intValue() == 0) {
            return ZERO_AMOUNT;
        }
        //CCR9565 fix null pointer exception
        if (incomeTest == null) {
        	return adjustedMedExp;
        }
        
        Integer prevYear = new Integer((stmt.getIncomeYear().intValue()) - 1);
        Integer dependents = getNotNull(incomeTest.getTotalNumberOfDependents());
        BigDecimal pensionThreshold = determinePensionThreshold(prevYear,
                dependents);
        BigDecimal deductibleAmount = pensionThreshold
                .multiply(getMedicalDeductionRate(prevYear));
        return adjustedMedExp.add(deductibleAmount);
    }

    /**
     * Calculate the total income of a veteran, spouse and child. Total income
     * is the summation of veteran, spouse and child's income without the
     * expense deductions.
     * 
     * @param stmt
     * @return total income as BigDecimal
     * @throws ServiceException
     */
    protected BigDecimal calculateTotalIncome(FinancialStatement stmt)
            throws ServiceException {
        // veteran income
        BigDecimal totalIncome = getTotalIncome(stmt);

        // Spouse income
        for (Iterator i = stmt.getSpouseFinancials().iterator(); i.hasNext();) {
            SpouseFinancials sf = (SpouseFinancials) i.next();
            if (isSpouseIncomeAllowed(sf)) {
                totalIncome = totalIncome.add(getTotalIncome(sf));
            }
        }

        // Child income
        for (Iterator iter = stmt.getDependentFinancials().iterator(); iter
                .hasNext();) {
            DependentFinancials df = (DependentFinancials) iter.next();
            if (isChildIncomeAllowed(df)) {
                totalIncome = totalIncome.add(getTotalIncome(df));
            }
        }
        return totalIncome;
    }

    protected BigDecimal calculateNetIncome(FinancialStatement stmt)
            throws ServiceException {
    	
    	//check null to avoid null pointer exception
    	if (stmt == null)
    		return ZERO_AMOUNT;
    	
        // Veteran net income
        BigDecimal totalIncome = calculateVeteranNetIncome(stmt);

        // Spouse net income
        for (Iterator i = stmt.getSpouseFinancials().iterator(); i.hasNext();) {
            SpouseFinancials sf = (SpouseFinancials) i.next();
            totalIncome = totalIncome.add(calculateSpouseNetIncome(sf));
        }
        // Dependent Children's net income
        for (Iterator iter = stmt.getDependentFinancials().iterator(); iter
                .hasNext();) {
            DependentFinancials df = (DependentFinancials) iter.next();
            if (isChildIncomeAllowed(df)) {
                totalIncome = totalIncome.add(calculateChildNetIncome(df));
            }
        }
        // set to zero, if expenses > income
        if (totalIncome.intValue() < 0) {
            totalIncome = ZERO_AMOUNT;
        }
        return totalIncome;
    }

    /**
     * Method strictly for continuous enrollment purposes.
     * 
     * @param test
     * @return
     * @throws ServiceException
     */
    protected BigDecimal calculateNetIncomeForCE(IncomeTest test)
            throws ServiceException {

        BigDecimal result = new BigDecimal(0);
        if (test != null) {
            BigDecimal netIncome = test.getNetIncome();
            if (netIncome != null) {
                // Hard work already done
                result = netIncome;
            } else {
                // Need to calculate on the fly using existing data.
                // Note that HL7 will always follow this path
                // since they don't provide Net Income
                BigDecimal totalIncome = test.getTotalIncome();
                BigDecimal deductableExpenses = test.getDeductibleExpenses();
                if (totalIncome != null && deductableExpenses != null) {
                    result = isGreaterThan(totalIncome, deductableExpenses) ? totalIncome
                            .subtract(deductableExpenses)
                            : new BigDecimal(0);
                } else if (totalIncome != null) {
                    result = totalIncome;
                }
                // For other cases we use default of 0
            }
        }
        return result;
    }

    /**
     * Get Total Income from the specified type CCR 6636
     * 
     * @param stmt
     * @return
     * @throws ServiceException
     */
    protected BigDecimal getTotalIncome(FinancialStatement stmt,
            IncomeType.Code incomeType) throws ServiceException {

        // Veteran net income
        BigDecimal totalIncome = new BigDecimal(0d);

        BigDecimal veteranIncome = getIncomeAmount(stmt, incomeType);
        if (veteranIncome != null)
            totalIncome = totalIncome.add(veteranIncome);

        // Spouse income
        for (Iterator i = stmt.getSpouseFinancials().iterator(); i.hasNext();) {
            SpouseFinancials sf = (SpouseFinancials) i.next();
            if (isSpouseIncomeAllowed(sf)) {
                BigDecimal spouseIncome = getIncomeAmount(sf, incomeType);
                if (spouseIncome != null)
                    totalIncome = totalIncome.add(spouseIncome);
            }
        }
        // Dependent Childrens income
        for (Iterator iter = stmt.getDependentFinancials().iterator(); iter
                .hasNext();) {
            DependentFinancials df = (DependentFinancials) iter.next();
            if (isChildIncomeAllowed(df)) {
                BigDecimal depIncome = getIncomeAmount(df, incomeType);
                if (depIncome != null)
                    totalIncome = totalIncome.add(depIncome);
            }
        }
        // set to zero, if expenses > income
        if (totalIncome.intValue() < 0) {
            totalIncome = ZERO_AMOUNT;
        }
        return totalIncome;
    }

    private BigDecimal calculateDeductibleExpense(FinancialStatement stmt)
            throws ServiceException {
        BigDecimal totalExpenses = calculateVeteranExpenses(stmt);
        for (Iterator iter = stmt.getDependentFinancials().iterator(); iter
                .hasNext();) {
            DependentFinancials df = (DependentFinancials) iter.next();
            if (isChildIncomeAllowed(df)) {
                totalExpenses = totalExpenses.add(calculateChildExpenses(df));
            }
        }
        return totalExpenses;
    }

    private BigDecimal calculateChildExpenses(DependentFinancials df)
            throws ServiceException {
        BigDecimal childExpense = new BigDecimal(0d);
        BigDecimal childEmpIncome = getTotalEmploymentIncome(df);
        BigDecimal childIncomeExcThreshold = getCalculatedIncomeExclusionThreshold();

        if (childEmpIncome.compareTo(childIncomeExcThreshold) > 0) {
            BigDecimal childEducationExpense = getTotalExpenses(df,
                    codeToStringArray(childExpenseTypes));
            BigDecimal adjustedIncome = childEmpIncome
                    .subtract(childIncomeExcThreshold);

            childExpense = childExpense.add(childIncomeExcThreshold);
            childExpense = childExpense.add(childEducationExpense
                    .compareTo(adjustedIncome) > 0 ? adjustedIncome
                    : childEducationExpense);
        } else {
            childExpense = childExpense.add(childEmpIncome);
        }
        return childExpense;
    }

    /**
     * Calculate the veteran income only. Income is calculated by adding all the
     * income minus expense total.
     * 
     * @param stmt
     * @return total income as BigDecimal
     * @throws ServiceException
     */
    private BigDecimal calculateVeteranNetIncome(FinancialStatement stmt)
            throws ServiceException {
        BigDecimal totalIncome = getTotalIncome(stmt);
        BigDecimal totalExpenses = calculateVeteranExpenses(stmt);
        return totalIncome.subtract(totalExpenses);
    }

    private BigDecimal calculateVeteranExpenses(FinancialStatement stmt)
            throws ServiceException {
        BigDecimal adjustedMedExpenses = determineAdjustableMedicalExpense(stmt);
        BigDecimal otherExpenses = getTotalExpenses(stmt,
                codeToStringArray(veteranExpenseTypes));
        return otherExpenses.add(adjustedMedExpenses);
    }

    /**
     * Calculate the spouse income if spouse is a valid dependent. If the spouse
     * is not a valid dependent, zero amount will be returned.
     * 
     * @param sf
     * @return calculated income of spouse
     */
    private BigDecimal calculateSpouseNetIncome(SpouseFinancials sf) {
        if (isSpouseIncomeAllowed(sf)) {
            return getTotalIncome(sf);
        }
        return ZERO_AMOUNT;
    }

    /**
     * Calculate the given dependent income if the child is a valid dependent
     * and income is available to the veteran. Child net income is calcualted as
     * total of child income minus educational expenses of the child.
     * 
     * @param df
     * @return calculated income of a given child
     * @throws ServiceException
     */
    private BigDecimal calculateChildNetIncome(DependentFinancials df)
            throws ServiceException {
        BigDecimal childIncome = getTotalIncome(df).subtract(
                getTotalEmploymentIncome(df));
        // this is the child's total income excluding employment income

        BigDecimal calculatedEmpIncome = calculateChildEmploymentIncome(df);
        // this is the calculated/allowed employment income

        childIncome = childIncome.add(calculatedEmpIncome);
        return childIncome;
    }

    private BigDecimal calculateChildEmploymentIncome(DependentFinancials df)
            throws ServiceException {
        BigDecimal childEmpIncome = getTotalEmploymentIncome(df);
        BigDecimal childIncomeExcThreshold = getCalculatedIncomeExclusionThreshold();
        if (childEmpIncome.compareTo(childIncomeExcThreshold) > 0) {
            BigDecimal adjustedIncome = childEmpIncome
                    .subtract(childIncomeExcThreshold);
            BigDecimal childExpenses = getTotalExpenses(df,
                    codeToStringArray(childExpenseTypes));
            if (adjustedIncome.compareTo(childExpenses) > 0) {
                return adjustedIncome.subtract(childExpenses);
            }
        }
        return ZERO_AMOUNT;
    }

    private boolean isSpouseIncomeAllowed(SpouseFinancials sf) {
        return sf != null && isSpouseValidDependent(sf);
    }

    private boolean isChildIncomeAllowed(DependentFinancials df) {
        return df != null && isDependentValidDependent(df)
                && Boolean.TRUE.equals(df.getIncomeAvailableToPatient());
    }

    /**
     * Calculate the networth for the given Financial Information. Networth is
     * calcualted as a sum of all the assets minus debts.
     * 
     * @param finInfo
     * @return net worth
     */
    protected BigDecimal getNetworth(FinancialInformation finInfo) {
        if (finInfo == null)
            return ZERO_AMOUNT;

        // VFA-SP1
        // if (CommonDateUtils.isIncomeYear2009OrLatter(finInfo.getIncomeYear()))
        //    return null;

        BigDecimal assetsAmount = getTotalAssets(finInfo);
        BigDecimal debtAmount = getTotalDebt(finInfo);

        return assetsAmount.subtract(debtAmount);
    }

    /**
     * Calculate the net worth for a given financial statement. Networth
     * calculation includes networth of veteran, spouse and all the dependents.
     * 
     * @param stmt
     * @return
     */
    protected BigDecimal calculateNetworth(FinancialStatement stmt) {
        if (stmt == null)
            return ZERO_AMOUNT;

        // VFA-SP1
        // if (CommonDateUtils.isIncomeYear2009OrLatter(stmt.getIncomeYear()))
        //    return null;

        BigDecimal networth = getNetworth(stmt); // Veteran's net worth

        // Add spouse networth
        for (Iterator i = stmt.getSpouseFinancials().iterator(); i.hasNext();) {
            SpouseFinancials sf = (SpouseFinancials) i.next();
            if (isSpouseIncomeAllowed(sf)) {
                networth = networth.add(getNetworth(sf));
            }
        }

        // Add dependents networth
        for (Iterator iter = stmt.getDependentFinancials().iterator(); iter
                .hasNext();) {
            DependentFinancials df = (DependentFinancials) iter.next();
            if (df != null && isDependentValidDependent(df)) {
                networth = networth.add(getTotalAssets(df));
            }
        }
        return networth;
    }

    /**
     * Add and return the total expenses for a given FinancialInformation and
     * expense types.
     * 
     * @param finInfo
     * @param expenseTypes
     * @return total amount of expenses as BigDecimal, never null
     */
    protected BigDecimal getTotalExpenses(FinancialInformation finInfo,
            String[] expenseTypes) {
        if (finInfo == null)
            return ZERO_AMOUNT;
        BigDecimal totalExpenses = new BigDecimal(0);
        for (Iterator iter = finInfo.getExpenses().values().iterator(); iter
                .hasNext();) {
            Expense expense = (Expense) iter.next();
            if (StringUtils.contains(expenseTypes, expense.getType().getCode())
                    && expense.getAmount() != null) {
                totalExpenses = totalExpenses.add(expense.getAmount());
            }
        }
        return totalExpenses;
    }

    /**
     * Add and return the total income amount for a given FinancialInformation.
     * 
     * @param finInfo
     * @return total amount of income as BigDecimal, never null
     */
    protected BigDecimal getTotalIncome(FinancialInformation finInfo) {
        if (finInfo == null)
            return ZERO_AMOUNT;
        BigDecimal totalIncome = new BigDecimal(0);
        for (Iterator iter = finInfo.getIncome().values().iterator(); iter
                .hasNext();) {
            Income income = (Income) iter.next();
            if (income.getAmount() != null) {
                totalIncome = totalIncome.add(income.getAmount());
            }
        }
        return totalIncome;
    }

    protected BigDecimal getTotalIncome(FinancialInformation finInfo,
            String[] incomeTypes) {
        if (finInfo == null)
            return ZERO_AMOUNT;
        BigDecimal totalIncome = new BigDecimal(0);
        for (Iterator iter = finInfo.getIncome().values().iterator(); iter
                .hasNext();) {
            Income income = (Income) iter.next();
            if (StringUtils.contains(incomeTypes, income.getType().getCode())
                    && income.getAmount() != null) {
                totalIncome = totalIncome.add(income.getAmount());
            }
        }
        return totalIncome;
    }

    /**
     * Add and return the total amount of assets for a given
     * FinancialInformation.
     * 
     * @param finInfo
     * @return total amount of assets as BigDecimal, never null
     */
    protected BigDecimal getTotalAssets(FinancialInformation finInfo) {
        if (finInfo == null)
            return ZERO_AMOUNT;

        // VFA-SP1 TODO // never null????
        // if (CommonDateUtils.isIncomeYear2009OrLatter(finInfo.getIncomeYear()))
        //    return null;

        BigDecimal totalAssets = new BigDecimal(0);
        for (Iterator iter = finInfo.getAssets().values().iterator(); iter
                .hasNext();) {
            Asset asset = (Asset) iter.next();
            if (asset.getAmount() != null) {
                totalAssets = totalAssets.add(asset.getAmount());
            }
        }
        return totalAssets;
    }

    /**
     * Return the total debt for a given FinancialInformation. If no debt
     * information exists, returns 0 value.
     * 
     * @param finInfo
     * @return debt total amount as BigDecimal, never null
     */
    protected BigDecimal getTotalDebt(FinancialInformation finInfo) {
        if (finInfo == null)
            return ZERO_AMOUNT;
        Debt debt = finInfo.getDebt();
        return debt == null || debt.getAmount() == null ? ZERO_AMOUNT : debt
                .getAmount();
    }

    /**
     * Check if the given Relation is a valid dependent or not.
     * 
     * @param rf
     * @return true if valid, false otherwise
     */
    protected boolean isValidDependent(RelationFinancials rf) {
        if (rf instanceof DependentFinancials) {
            return isDependentValidDependent((DependentFinancials) rf);
        } else if (rf instanceof SpouseFinancials) {
            return isSpouseValidDependent((SpouseFinancials) rf);
        }
        return false;
    }

    /**
     * Check if the given dependent relation is a valid dependent or not.
     * 
     * @param df
     * @return true if valid, false otherwise
     */
    public boolean isDependentValidDependent(DependentFinancials df) {
        if (df == null)
            return false;
        Relationship relationship = df.getReportedOn().getRelationship();
        String rs = relationship == null ? null : relationship.getCode();
        boolean contributed = Boolean.TRUE.equals(df.getContributedToSupport());
        return isActiveRelation(df.getReportedOn())
                && StringUtils.contains(codeToStringArray(childRelationships),
                        rs)
                && (Boolean.TRUE.equals(df.getLivedWithPatient()) || contributed);
    }

    /**
     * Check if the given spouse relation is a valid dependent or not.
     * 
     * @param sf
     * @return true if valid, false otherwise
     */
    public boolean isSpouseValidDependent(SpouseFinancials sf) {
        if (sf == null)
            return false;
        FinancialStatement stmt = sf.getFinancialStatement();
        int sfCount = stmt.getSpouseFinancials().size();
        // 5388 fix
        if (sfCount == 1
                || (sfCount > 1 && sf.getReportedOn().getEndDate() == null)) {
            
            // ES 4.2_CodeCR13338_CR_Rule Layer Modifications for Decommissioning Spousal/Dependent Support
            if (stmt.getContributedToSpouseInd() != null) {
                boolean contributed = stmt.getContributedToSpouseInd().booleanValue();
                boolean isActiveRelationFlag = isActiveRelation(sf.getReportedOn());
                Boolean livedWithPatient = sf.getLivedWithPatient();
                Boolean marriedLastCalendarYear = sf.getFinancialStatement().getMarriedLastCalendarYear();
                return isActiveRelationFlag && ((contributed && Boolean.FALSE.equals(livedWithPatient) &&
                        Boolean.TRUE.equals(marriedLastCalendarYear)) || 
                        (Boolean.TRUE.equals(marriedLastCalendarYear) && Boolean.TRUE.equals(livedWithPatient)));                
            }
            else {
                // For compatibility with old data that uses contribution amount
                boolean contributed = stmt.getContributionToSpouse() != null
                        && stmt.getContributionToSpouse().intValue() >= spousalSupportLimit;
                        return isActiveRelation(sf.getReportedOn())
                                && ((contributed
                                        && Boolean.FALSE.equals(sf.getLivedWithPatient()) && Boolean.TRUE
                                        .equals(sf.getFinancialStatement()
                                                .getMarriedLastCalendarYear())) || (Boolean.TRUE
                                                        .equals(sf.getFinancialStatement()
                                                                .getMarriedLastCalendarYear()) && Boolean.TRUE
                                                                .equals(sf.getLivedWithPatient())));
            }
        } else {
            return false;
        }
    }

    public boolean isActiveRelation(Relation relation) {
        return getFinancialsHelperService().isActiveRelation(relation,
                getIncomingIncomeYear());
    }

    protected void countDependents(FinancialStatement stmt) {
        if (stmt == null) {
            return;
        }
        int dependentCount = 0;
        Set dfSet = stmt.getDependentFinancials();
        for (Iterator iter = dfSet.iterator(); iter.hasNext();) {
            DependentFinancials df = (DependentFinancials) iter.next();
            if (isDependentValidDependent(df)) {
                dependentCount++;
            }
        }
        this.numberOfDependentChildren = new Integer(dependentCount);

        Set spSet = stmt.getSpouseFinancials();
        for (Iterator i = spSet.iterator(); i.hasNext();) {
            SpouseFinancials sf = (SpouseFinancials) i.next();
            if (isSpouseValidDependent(sf)) {
                dependentCount++;
            }
        }
        this.numberOfDependents = new Integer(dependentCount);
    }

    public Integer getCalculatedNumberOfDependentChildren() {
        if (numberOfDependentChildren == null) {
            countDependents(getIncomingFinancialStatement());
        }
        return numberOfDependentChildren;
    }

    public Integer getCalculatedNumberOfDependents() {
        if (numberOfDependents == null) {
            countDependents(getIncomingFinancialStatement());
        }
        return numberOfDependents;
    }

    public BigDecimal getCalculatedTotalIncome() throws ServiceException {
        if (calculatedTotalIncome == null) {
            calculatedTotalIncome = calculateTotalIncome(getIncomingFinancialStatement());
            
            /* CCR 11869
             * 5.11.10.	SUC2031.32.10 - Calculate Total Expense 
			 * The system calculates Total Expense. If Total Expenses are greater than Total Income 
			 * then the system sets the Total Income to zero. 
			 * The Total Computed Income returned by the Process Financial Data use case is changed to zero.
             */
            if (this.isMessageFromVOA() && 
            		this.getCalculatedDeductibleExpense() != null &&
            		this.getCalculatedDeductibleExpense().compareTo(calculatedTotalIncome) > 0)
            	calculatedTotalIncome = new BigDecimal("0"); 	
        }
        return calculatedTotalIncome;
    }

    public BigDecimal getCalculatedNetIncome() throws ServiceException {

        if (calculatedNetIncome == null) {
            calculatedNetIncome = calculateNetIncome(getIncomingFinancialStatement());
        }
        logger.debug("calculatedNetIncome: " + calculatedNetIncome);

        return calculatedNetIncome;
    }

    public BigDecimal getCalculatedNetIncomeForCE() throws ServiceException {

        if (calculatedNetIncomeForCE == null) {
            IncomeTest test = getHelperService().getCurrentIncomeTest(
                    getIncomingPerson());
            calculatedNetIncomeForCE = calculateNetIncomeForCE(test);
        }
        logger.debug("calculatedNetIncomeForCE: " + calculatedNetIncomeForCE);

        return calculatedNetIncomeForCE;
    }

    public BigDecimal getCalculatedDeductibleExpense() throws ServiceException {
        if (calculatedDeductibleExpense == null) {
            FinancialStatement stmt = getIncomingFinancialStatement();
            calculatedDeductibleExpense = calculateDeductibleExpense(stmt);
        }
        return calculatedDeductibleExpense;
    }

    public BigDecimal getCalculatedNetworth() {
        if (calculatedNetworth == null) {
            this.calculatedNetworth = calculateNetworth(getIncomingFinancialStatement());
        }
        return calculatedNetworth;
    }

    private BigDecimal getTotalEmploymentIncome(FinancialInformation finInfo) {
        Income totalEmpIncome = finInfo == null ? null : finInfo
                .getTotalEmploymentIncome();
        BigDecimal totalEmpIncomeAmount = getNotNull(totalEmpIncome == null ? null
                : totalEmpIncome.getAmount());
        return totalEmpIncomeAmount;
    }

    protected BigDecimal getNotNull(BigDecimal bd) {
        return bd == null ? new BigDecimal(0) : bd;
    }

    protected Integer getNotNull(Integer bd) {
        return bd == null ? new Integer(0) : bd;
    }

    protected BigDecimal convert(Integer intValue) {
        return intValue == null ? new BigDecimal(0) : new BigDecimal(intValue
                .intValue());
    }

    protected Boolean getNotNull(Boolean value) {
        return value == null ? Boolean.FALSE : value;
    }

    private String[] codeToStringArray(AbstractCode[] codes) {
        if (codes == null || codes.length == 0) {
            return new String[0];
        }
        String[] strArray = new String[codes.length];
        for (int i = 0; i < codes.length; i++) {
            strArray[i] = codes[i].getName();
        }
        return strArray;
    }

    /**
     * @return Returns the spousalSupportLimit.
     */
    public int getSpousalSupportLimit() {
        return spousalSupportLimit;
    }

    /**
     * @param spousalSupportLimit
     *            The spousalSupportLimit to set.
     */
    public void setSpousalSupportLimit(int spousalSupportLimit) {
        this.spousalSupportLimit = spousalSupportLimit;
    }

    /**
     * @return Returns the childRelationships.
     */
    public Relationship.Code[] getChildRelationships() {
        return childRelationships;
    }

    /**
     * @param childRelationships
     *            The childRelationships to set.
     */
    public void setChildRelationships(Relationship.Code[] childRelationships) {
        this.childRelationships = childRelationships;
    }

    /**
     * @return Returns the childExpenseTypes.
     */
    public ExpenseType.Code[] getChildExpenseTypes() {
        return childExpenseTypes;
    }

    /**
     * @param childExpenseTypes
     *            The childExpenseTypes to set.
     */
    public void setChildExpenseTypes(ExpenseType.Code[] childExpenseTypes) {
        this.childExpenseTypes = childExpenseTypes;
    }

    /**
     * @return Returns the veteranExpenseTypes.
     */
    public ExpenseType.Code[] getVeteranExpenseTypes() {
        return veteranExpenseTypes;
    }

    /**
     * @param veteranExpenseTypes
     *            The veteranExpenseTypes to set.
     */
    public void setVeteranExpenseTypes(ExpenseType.Code[] veteranExpenseTypes) {
        this.veteranExpenseTypes = veteranExpenseTypes;
    }

    /**
     * @return Returns the medicalDeductionRate.
     * @throws ServiceException
     */
    public BigDecimal getMedicalDeductionRate(Integer incomeYear)
            throws ServiceException {
        IncomeThreshold it = getLookupService().getIncomeThreshold(incomeYear);
        // CCR9565 fix null pointer exception
        if (it == null)  return ZERO_AMOUNT;
        
        BigDecimal medExpRate = getNotNull(it.getMedicalExpenseDeductible());
        if (medExpRate.doubleValue() > 0) {
            return new BigDecimal(Double
                    .toString(medExpRate.doubleValue() / 100));
        }
        return medExpRate;
    }

    public FinancialsHelperService getFinancialsHelperService() {
        return financialsHelperService;
    }

    public void setFinancialsHelperService(
            FinancialsHelperService financialsHelperService) {
        this.financialsHelperService = financialsHelperService;
    }

    private IncomeTest getPriorIncomeTest(Person person,
            Integer currentIncomeYear) {

        if (currentIncomeYear == null) {
            return null;
        }

        Map incomeTests = person.getIncomeTests();

        // Get a list of all the income years for the tests
        List incomeYears = new ArrayList(incomeTests.keySet());
        if ((incomeYears == null) || (incomeYears.isEmpty())) {
            return null;
        }

        // Loop through the income years from the most current to the oldest one
        Collections.sort(incomeYears);
        for (int i = incomeYears.size() - 1; i >= 0; i--) {
            // Get an income test
            Integer incomeYear = (Integer) incomeYears.get(i);

            if (incomeYear != null
                    && incomeYear.compareTo(currentIncomeYear) < 0) {
                return person.getIncomeTest(incomeYear);
            }
        }
        return null;
    }

    public IncomeTest getIncomingIncludingFutureIncomeTest() {
  	    //Subsequent Rejection Letter ESR 3.6_CodeCR10108
  	    //This method was cloned from getIncomingIncomeTest method.  The reason for addressing the fix in this cloned method
  	    //is to reduce the risk of impact to the rest of the ESR application as many of the ILOG rules do not have a need
  	    //to return the Future Dated Income Test.
        Integer incomeYear = getIncomingIncludingFutureIncomeYear();
        if (incomeYear != null) {
            return getIncomeTest(getIncomingPerson(), getIncomingIncludingFutureIncomeYear());
        }
        return getHelperService().getCurrentOrFutureIncomeTest(getIncomingPerson(), getPristinePerson());
    }

    public Integer getIncomingIncludingFutureIncomeYear() {
  	    //Subsequent Rejection Letter ESR 3.6_CodeCR10108
  	    //This method was cloned from getIncomingIncomeYear method.  The reason for addressing the fix in this cloned method
  	    //is to reduce the risk of impact to the rest of the ESR application as many of the ILOG rules do not have a need
  	    //to return the Future Dated Income Test.
        FinancialInputData data = this.getFinancialInputData();
        if (data != null) {
            return data.getIncomingIncomeYear();
        }
        // The following should only execute when this parameter is used
        // in different context from Financial, specifically TriggerMessage.
        RuleDataAware rda = this.getRuleDataAware();
        if (rda instanceof EventInputData) {
            Integer incomeYear = ((EventInputData) rda).getIncomeYear();
            IncomeTest current = null;
            if (incomeYear != null) {
                current = this.getIncomingPerson().getIncomeTest(incomeYear);
            }
            if (current != null) {
                return current.getIncomeYear();
            }

//            current = this.getHelperService().getCurrentIncomeTest(
//                    this.getIncomingPerson());
            current = this.getHelperService().getCurrentOrFutureIncomeTest(
                    this.getIncomingPerson(), this.getPristinePerson());
            return current != null ? current.getIncomeYear() : null;
        } else if ((rda instanceof EEInputData)
                || (rda instanceof CommsInputData)) {
            IncomeTest test = getHelperService().getCurrentOrFutureIncomeTest(
                    getIncomingPerson(), getPristinePerson());
            if (test != null) {
                return test.getIncomeYear();
            }
        }
        return null;
    }

}