/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.module.bc.document.service.impl;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.bc.BCConstants;
import org.kuali.kfs.module.bc.BCKeyConstants;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionAdministrativePost;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionAppointmentFundingReason;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionAppointmentFundingReasonCode;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionCalculatedSalaryFoundationTracker;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionIntendedIncumbent;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionOrgReasonSummaryReport;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionOrgReasonSummaryReportTotal;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionPosition;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionReportThresholdSettings;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionSalaryFunding;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionSalarySocialSecurityNumber;
import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding;
import org.kuali.kfs.module.bc.document.dataaccess.BudgetConstructionSalarySummaryReportDao;
import org.kuali.kfs.module.bc.document.service.BudgetConstructionOrganizationReportsService;
import org.kuali.kfs.module.bc.document.service.BudgetConstructionReasonSummaryReportService;
import org.kuali.kfs.module.bc.document.service.BudgetConstructionReportsServiceHelper;
import org.kuali.kfs.module.bc.report.BudgetConstructionReportHelper;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.core.api.util.type.KualiInteger;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Service implementation of BudgetConstructionReasonSummaryReportService.
 */
@Transactional
public class BudgetConstructionReasonSummaryReportServiceImpl implements BudgetConstructionReasonSummaryReportService {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BudgetConstructionReasonSummaryReportServiceImpl.class);

    protected BudgetConstructionSalarySummaryReportDao budgetConstructionSalarySummaryReportDao;
    protected BudgetConstructionOrganizationReportsService budgetConstructionOrganizationReportsService;
    protected BudgetConstructionReportsServiceHelper budgetConstructionReportsServiceHelper;
    protected ConfigurationService kualiConfigurationService;
    protected BusinessObjectService businessObjectService;

    /**
     * @see org.kuali.kfs.module.bc.document.service.BudgetConstructionReasonSummaryReportService#updateReasonSummaryReport(java.lang.String,
     * java.lang.Integer, org.kuali.kfs.module.bc.businessobject.BudgetConstructionReportThresholdSettings)
     */
    @Override
    public void updateReasonSummaryReport(String principalId, Integer universityFiscalYear, BudgetConstructionReportThresholdSettings budgetConstructionReportThresholdSettings) {
        boolean selectOnlyGreaterThanOrEqualToThreshold = budgetConstructionReportThresholdSettings.isUseGreaterThanOperator();
        KualiDecimal thresholdPercent = budgetConstructionReportThresholdSettings.getThresholdPercent();

        boolean applyAThreshold = budgetConstructionReportThresholdSettings.isUseThreshold();
        if (applyAThreshold) {
            budgetConstructionSalarySummaryReportDao.updateSalaryAndReasonSummaryReportsWithThreshold(principalId, universityFiscalYear - 1, selectOnlyGreaterThanOrEqualToThreshold, thresholdPercent);
        } else {
            budgetConstructionSalarySummaryReportDao.updateSalaryAndReasonSummaryReportsWithoutThreshold(principalId, true);
        }
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.BudgetConstructionReasonSummaryReportService#buildReports(java.lang.Integer,
     * java.lang.String, org.kuali.kfs.module.bc.businessobject.BudgetConstructionReportThresholdSettings)
     */
    @Override
    public Collection<BudgetConstructionOrgReasonSummaryReport> buildReports(Integer universityFiscalYear, String principalId, BudgetConstructionReportThresholdSettings reportThresholdSettings) {
        Collection<BudgetConstructionOrgReasonSummaryReport> reportSet = new ArrayList<BudgetConstructionOrgReasonSummaryReport>();

        BudgetConstructionOrgReasonSummaryReport reasonSummaryReport;
        Collection<BudgetConstructionSalarySocialSecurityNumber> bcSalarySsnList = budgetConstructionReportsServiceHelper.getDataForBuildingReports(BudgetConstructionSalarySocialSecurityNumber.class, principalId, buildOrderByList());

        Map salaryFundingMap = new HashMap();
        for (BudgetConstructionSalarySocialSecurityNumber ssnEntry : bcSalarySsnList) {
            Collection<BudgetConstructionSalaryFunding> salaryFundingList = budgetConstructionReportsServiceHelper.getSalaryFunding(principalId, ssnEntry.getEmplid());
            salaryFundingMap.put(ssnEntry, salaryFundingList);
        }

        List<BudgetConstructionSalarySocialSecurityNumber> listForCalculateTotalPerson = deleteDuplicated((List) bcSalarySsnList, 1);
        List<BudgetConstructionSalarySocialSecurityNumber> listForCalculateTotalOrg = deleteDuplicated((List) bcSalarySsnList, 2);

        // Calculate Total Section
        Collection<BudgetConstructionOrgReasonSummaryReportTotal> reasonSummaryTotalPerson = calculatePersonTotal(universityFiscalYear, bcSalarySsnList, listForCalculateTotalPerson, salaryFundingMap);
        Collection<BudgetConstructionOrgReasonSummaryReportTotal> reasonSummaryTotalOrg = calculateOrgTotal(reasonSummaryTotalPerson, listForCalculateTotalOrg, salaryFundingMap);

        // get object codes
        String objectCodes = budgetConstructionReportsServiceHelper.getSelectedObjectCodes(principalId);

        // get reason codes
        String reasonCodes = budgetConstructionReportsServiceHelper.getSelectedReasonCodes(principalId);

        for (BudgetConstructionSalarySocialSecurityNumber ssnEntry : bcSalarySsnList) {
            Collection<BudgetConstructionSalaryFunding> salaryFundingList = (Collection) salaryFundingMap.get(ssnEntry);

            for (BudgetConstructionSalaryFunding salaryFundingEntry : salaryFundingList) {
                reasonSummaryReport = new BudgetConstructionOrgReasonSummaryReport();
                buildReportsHeader(universityFiscalYear, objectCodes, reasonCodes, reasonSummaryReport, salaryFundingEntry, ssnEntry, reportThresholdSettings);
                buildReportsBody(universityFiscalYear, reasonSummaryReport, salaryFundingEntry, ssnEntry);
                buildReportsTotal(reasonSummaryReport, ssnEntry, reasonSummaryTotalPerson, reasonSummaryTotalOrg);
                reportSet.add(reasonSummaryReport);
            }
        }

        return reportSet;
    }

    /**
     * builds report Header
     */
    public void buildReportsHeader(Integer universityFiscalYear, String objectCodes, String reasonCodes, BudgetConstructionOrgReasonSummaryReport reasonSummaryReport, BudgetConstructionSalaryFunding salaryFundingEntry, BudgetConstructionSalarySocialSecurityNumber bcSSN, BudgetConstructionReportThresholdSettings budgetConstructionReportThresholdSettings) {
        Integer prevFiscalyear = universityFiscalYear - 1;
        reasonSummaryReport.setFiscalYear(prevFiscalyear + "-" + universityFiscalYear.toString().substring(2, 4));

        reasonSummaryReport.setOrganizationCode(bcSSN.getOrganizationCode());
        String organizationName = bcSSN.getOrganization().getOrganizationName();
        if (organizationName == null) {
            String wrongOrganizationName = kualiConfigurationService.getPropertyValueAsString(BCKeyConstants.ERROR_REPORT_GETTING_ORGANIZATION_NAME);
            reasonSummaryReport.setOrganizationName(wrongOrganizationName);
        } else {
            reasonSummaryReport.setOrganizationName(organizationName);
        }

        reasonSummaryReport.setOrgChartOfAccountsCode(bcSSN.getOrganizationChartOfAccountsCode());
        String chartDescription = bcSSN.getOrganizationChartOfAccounts().getFinChartOfAccountDescription();
        if (chartDescription == null) {
            String wrongChartDescription = kualiConfigurationService.getPropertyValueAsString(BCKeyConstants.ERROR_REPORT_GETTING_CHART_DESCRIPTION);
            reasonSummaryReport.setOrgChartOfAccountDescription(wrongChartDescription);
        } else {
            reasonSummaryReport.setOrgChartOfAccountDescription(chartDescription);
        }

        Integer prevPrevFiscalyear = prevFiscalyear - 1;
        reasonSummaryReport.setReqFy(prevFiscalyear + "-" + universityFiscalYear.toString().substring(2, 4));
        reasonSummaryReport.setFinancialObjectCode(salaryFundingEntry.getFinancialObjectCode());

        reasonSummaryReport.setObjectCodes(objectCodes);

        if (budgetConstructionReportThresholdSettings.isUseThreshold()) {
            if (budgetConstructionReportThresholdSettings.isUseGreaterThanOperator()) {
                reasonSummaryReport.setThresholdOrReason(BCConstants.Report.THRESHOLD + BCConstants.Report.THRESHOLD_GREATER + budgetConstructionReportThresholdSettings.getThresholdPercent().toString() + BCConstants.Report.PERCENT);
            } else {
                reasonSummaryReport.setThresholdOrReason(BCConstants.Report.THRESHOLD + BCConstants.Report.THRESHOLD_LESS + budgetConstructionReportThresholdSettings.getThresholdPercent().toString() + BCConstants.Report.PERCENT);
            }
        } else {
            reasonSummaryReport.setThresholdOrReason(BCConstants.Report.SELECTED_REASONS + reasonCodes);
        }

        // reason, amt, desc
        List<BudgetConstructionAppointmentFundingReason> appointmentFundingReasonList = salaryFundingEntry.getPendingAppointmentFunding().getBudgetConstructionAppointmentFundingReason();
        if (ObjectUtils.isNotNull(appointmentFundingReasonList) && !appointmentFundingReasonList.isEmpty()) {
            BudgetConstructionAppointmentFundingReason appointmentFundingReason = appointmentFundingReasonList.get(0);

            Integer reasonAmount = BudgetConstructionReportHelper.convertKualiInteger(appointmentFundingReason.getAppointmentFundingReasonAmount());
            reasonSummaryReport.setAppointmentFundingReasonAmount(reasonAmount);

            BudgetConstructionAppointmentFundingReasonCode reasonCode = appointmentFundingReason.getAppointmentFundingReason();
            if (ObjectUtils.isNotNull(reasonCode)) {
                reasonSummaryReport.setAppointmentFundingReasonDescription(reasonCode.getAppointmentFundingReasonDescription());
            }
        }
    }

    /**
     * builds report body
     */
    public void buildReportsBody(Integer fiscalYear, BudgetConstructionOrgReasonSummaryReport reasonSummaryReport, BudgetConstructionSalaryFunding salaryFundingEntry, BudgetConstructionSalarySocialSecurityNumber salarySSN) {
        int curToInt = -1;
        double curFteInt = -1.00;

        PendingBudgetConstructionAppointmentFunding appointmentFunding = salaryFundingEntry.getPendingAppointmentFunding();
        BudgetConstructionIntendedIncumbent intendedIncumbent = budgetConstructionReportsServiceHelper.getBudgetConstructionIntendedIncumbent(appointmentFunding);
        if (intendedIncumbent != null) {
            reasonSummaryReport.setIuClassificationLevel(intendedIncumbent.getIuClassificationLevel());
        }

        int nameLength = salarySSN.getName().length();
        reasonSummaryReport.setName(salarySSN.getName().substring(0, (nameLength > 35) ? 35 : nameLength));

        BudgetConstructionAdministrativePost administrativePost = budgetConstructionReportsServiceHelper.getBudgetConstructionAdministrativePost(appointmentFunding);
        BudgetConstructionPosition budgetConstructionPosition = budgetConstructionReportsServiceHelper.getBudgetConstructionPosition(fiscalYear, appointmentFunding);

        // set report body
        reasonSummaryReport.setChartOfAccountsCode(salaryFundingEntry.getChartOfAccountsCode());
        reasonSummaryReport.setAccountNumber(salaryFundingEntry.getAccountNumber());
        reasonSummaryReport.setSubAccountNumber(salaryFundingEntry.getSubAccountNumber());
        reasonSummaryReport.setFinancialSubObjectCode(salaryFundingEntry.getFinancialSubObjectCode());

        if (administrativePost != null) {
            reasonSummaryReport.setAdministrativePost(administrativePost.getAdministrativePost());
        }

        if (budgetConstructionPosition != null) {
            reasonSummaryReport.setPositionNumber(budgetConstructionPosition.getPositionNumber());
            reasonSummaryReport.setNormalWorkMonthsAndiuPayMonths(budgetConstructionPosition.getIuNormalWorkMonths() + "/" + budgetConstructionPosition.getIuPayMonths());
            reasonSummaryReport.setPositionFte(BudgetConstructionReportHelper.setDecimalDigit(budgetConstructionPosition.getPositionFullTimeEquivalency(), 5, false));
            reasonSummaryReport.setPositionSalaryPlanDefault(budgetConstructionPosition.getPositionSalaryPlanDefault());
            reasonSummaryReport.setPositionGradeDefault(budgetConstructionPosition.getPositionGradeDefault());
        }

        BudgetConstructionCalculatedSalaryFoundationTracker csfTracker = appointmentFunding.getEffectiveCSFTracker();
        if (ObjectUtils.isNotNull(csfTracker)) {
            reasonSummaryReport.setCsfTimePercent(BudgetConstructionReportHelper.setDecimalDigit(csfTracker.getCsfTimePercent(), 2, false));
            reasonSummaryReport.setCsfAmount(csfTracker.getCsfAmount().intValue());

            // calculate amountChange and percentChange
            if (appointmentFunding.getAppointmentRequestedFteQuantity().equals(csfTracker.getCsfFullTimeEmploymentQuantity())) {
                Integer amountChange = appointmentFunding.getAppointmentRequestedAmount().subtract(csfTracker.getCsfAmount()).intValue();
                reasonSummaryReport.setAmountChange(amountChange);

                BigDecimal percentChange = BudgetConstructionReportHelper.calculatePercent(new BigDecimal(amountChange), csfTracker.getCsfAmount().bigDecimalValue());
                reasonSummaryReport.setPercentChange(percentChange);
            }
        }

        if (StringUtils.equals(appointmentFunding.getFinancialSubObjectCode(), KFSConstants.getDashFinancialSubObjectCode())) {
            reasonSummaryReport.setFinancialSubObjectCode(BCConstants.Report.BLANK);
        } else {
            reasonSummaryReport.setFinancialSubObjectCode(appointmentFunding.getFinancialSubObjectCode());
        }

        reasonSummaryReport.setEmplid(appointmentFunding.getEmplid());
        reasonSummaryReport.setAppointmentFundingDurationCode(appointmentFunding.getAppointmentFundingDurationCode());
        reasonSummaryReport.setAppointmentTotalIntendedAmount(appointmentFunding.getAppointmentTotalIntendedAmount().intValue());

        BigDecimal totalIntendedFteQuantity = BudgetConstructionReportHelper.setDecimalDigit(appointmentFunding.getAppointmentTotalIntendedFteQuantity(), 5, false);
        reasonSummaryReport.setAppointmentTotalIntendedFteQuantity(totalIntendedFteQuantity);

        if (StringUtils.equals(appointmentFunding.getAppointmentFundingDurationCode(), BCConstants.Report.NONE)) {
            reasonSummaryReport.setSalaryAmount(appointmentFunding.getAppointmentRequestedAmount().intValue());
            reasonSummaryReport.setPercentAmount(appointmentFunding.getAppointmentRequestedTimePercent());
            reasonSummaryReport.setSalaryMonths(appointmentFunding.getAppointmentFundingMonth());
        } else {
            reasonSummaryReport.setSalaryAmount(appointmentFunding.getAppointmentRequestedCsfAmount().intValue());
            reasonSummaryReport.setPercentAmount(appointmentFunding.getAppointmentRequestedCsfTimePercent());

            if (budgetConstructionPosition != null) {
                reasonSummaryReport.setSalaryMonths(budgetConstructionPosition.getIuNormalWorkMonths());
            }
        }

        if (appointmentFunding.isAppointmentFundingDeleteIndicator()) {
            reasonSummaryReport.setDeleteBox(BCConstants.Report.DELETE_MARK);
        } else {
            reasonSummaryReport.setDeleteBox(BCConstants.Report.BLANK);
        }

        // set tiFlag
        if (appointmentFunding.isAppointmentFundingDeleteIndicator()) {
            if (curToInt == -1) {
                curToInt = appointmentFunding.getAppointmentTotalIntendedAmount().intValue();
            } else if (curToInt != appointmentFunding.getAppointmentTotalIntendedAmount().intValue()) {
                reasonSummaryReport.setTiFlag(BCConstants.Report.PLUS);
            }

            if (curFteInt == -1.00) {
                curFteInt = appointmentFunding.getAppointmentTotalIntendedFteQuantity().doubleValue();
            } else if (curFteInt != appointmentFunding.getAppointmentTotalIntendedFteQuantity().doubleValue()) {
                reasonSummaryReport.setTiFlag(BCConstants.Report.PLUS);
            }
        }
    }

    /**
     * build the total sections for the report
     */
    public void buildReportsTotal(BudgetConstructionOrgReasonSummaryReport reasonSummaryReportEntry, BudgetConstructionSalarySocialSecurityNumber ssnEntry, Collection<BudgetConstructionOrgReasonSummaryReportTotal> reasonSummaryTotalPerson, Collection<BudgetConstructionOrgReasonSummaryReportTotal> reasonSummaryTotalOrg) {

        for (BudgetConstructionOrgReasonSummaryReportTotal totalPersonEntry : reasonSummaryTotalPerson) {
            if (isSameSsnEntryForTotalPerson(totalPersonEntry.getBudgetConstructionSalarySocialSecurityNumber(), ssnEntry)) {
                reasonSummaryReportEntry.setPersonPositionNumber(totalPersonEntry.getPersonPositionNumber());
                reasonSummaryReportEntry.setPersonFiscalYearTag(totalPersonEntry.getPersonFiscalYearTag());
                reasonSummaryReportEntry.setPersonNormalMonthsAndPayMonths(totalPersonEntry.getPersonCsfNormalMonths().toString() + "/" + totalPersonEntry.getPersonCsfPayMonths().toString());
                reasonSummaryReportEntry.setPersonCsfAmount(totalPersonEntry.getPersonCsfAmount());
                reasonSummaryReportEntry.setPersonCsfPercent(totalPersonEntry.getPersonCsfPercent());
                reasonSummaryReportEntry.setPersonSalaryNormalMonths(totalPersonEntry.getPersonSalaryNormalMonths());
                reasonSummaryReportEntry.setPersonSalaryAmount(totalPersonEntry.getPersonSalaryAmount());
                reasonSummaryReportEntry.setPersonSalaryPercent(totalPersonEntry.getPersonSalaryPercent());
                reasonSummaryReportEntry.setPersonSalaryFte(totalPersonEntry.getPersonSalaryFte());
                reasonSummaryReportEntry.setPersonTiFlag(totalPersonEntry.getPersonTiFlag());
                reasonSummaryReportEntry.setPersonAmountChange(totalPersonEntry.getPersonAmountChange());
                reasonSummaryReportEntry.setPersonPercentChange(totalPersonEntry.getPersonPercentChange());
            }
            for (BudgetConstructionOrgReasonSummaryReportTotal totalOrgEntry : reasonSummaryTotalOrg) {
                if (isSameSsnEntryForTotalOrg(totalOrgEntry.getBudgetConstructionSalarySocialSecurityNumber(), ssnEntry)) {
                    reasonSummaryReportEntry.setNewFte(totalOrgEntry.getNewFte());
                    reasonSummaryReportEntry.setNewTotalAmount(totalOrgEntry.getNewTotalAmount());
                    reasonSummaryReportEntry.setConTotalBaseAmount(totalOrgEntry.getConTotalBaseAmount());
                    reasonSummaryReportEntry.setConFte(totalOrgEntry.getConFte());
                    reasonSummaryReportEntry.setConTotalRequestAmount(totalOrgEntry.getConTotalRequestAmount());
                    reasonSummaryReportEntry.setNewAverageAmount(totalOrgEntry.getNewAverageAmount());
                    reasonSummaryReportEntry.setConAverageBaseAmount(totalOrgEntry.getConAverageBaseAmount());
                    reasonSummaryReportEntry.setConAverageRequestAmount(totalOrgEntry.getConAverageRequestAmount());
                    reasonSummaryReportEntry.setConAveragechange(totalOrgEntry.getConAveragechange());
                    reasonSummaryReportEntry.setConPercentChange(totalOrgEntry.getConPercentChange());
                }
            }
        }
    }

    // calculate the totals for the given person
    protected Collection<BudgetConstructionOrgReasonSummaryReportTotal> calculatePersonTotal(Integer universityFiscalYear, Collection<BudgetConstructionSalarySocialSecurityNumber> bcSalarySsnList, List<BudgetConstructionSalarySocialSecurityNumber> listForCalculateTotalPerson, Map salaryFundingMap) {
        Collection<BudgetConstructionOrgReasonSummaryReportTotal> returnCollection = new ArrayList<BudgetConstructionOrgReasonSummaryReportTotal>();

        for (BudgetConstructionSalarySocialSecurityNumber personEntry : listForCalculateTotalPerson) {
            PersonTotalHolder totalsHolder = new PersonTotalHolder();
            totalsHolder.emplid = personEntry.getEmplid();

            for (BudgetConstructionSalarySocialSecurityNumber salaryFundingEntry : bcSalarySsnList) {
                if (isSameSsnEntryForTotalPerson(personEntry, salaryFundingEntry)) {
                    Collection<BudgetConstructionSalaryFunding> salaryFundings = (Collection<BudgetConstructionSalaryFunding>) salaryFundingMap.get(personEntry);
                    this.collectPersonTotal(universityFiscalYear, salaryFundings, totalsHolder);
                }
            }

            this.adjustPersonTotal(totalsHolder);
            returnCollection.add(this.createReportTotal(personEntry, totalsHolder));
        }

        return returnCollection;
    }

    // adjust the total amount that just is held by the given holder
    protected void adjustPersonTotal(PersonTotalHolder totalsHolder) {
        Integer restatementCsfAmount = 0;
        if (totalsHolder.salaryPayMonth == 0 || totalsHolder.csfPayMonths == 0 || BigDecimal.ZERO.compareTo(totalsHolder.csfPercent) == 0 || totalsHolder.csfNormalMonths == 0) {
            restatementCsfAmount = 0;
        } else {
            BigDecimal salaryMonthPercent = new BigDecimal(totalsHolder.salaryNormalMonths * 1.0 / totalsHolder.salaryPayMonth);
            BigDecimal salaryFteQuantity = totalsHolder.salaryPercent.multiply(salaryMonthPercent);

            BigDecimal csfMonthpercent = new BigDecimal(totalsHolder.csfNormalMonths * 1.0 / totalsHolder.csfPayMonths);
            BigDecimal csfFteQuantity = totalsHolder.csfPercent.multiply(csfMonthpercent);

            BigDecimal restatementCsfPercent = salaryFteQuantity.divide(csfFteQuantity, 6, BigDecimal.ROUND_HALF_UP);
            BigDecimal csfAmount = new BigDecimal(totalsHolder.csfAmount);
            restatementCsfAmount = csfAmount.multiply(restatementCsfPercent).setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
        }

        if (totalsHolder.salaryPayMonth == 0) {
            totalsHolder.salaryFte = BigDecimal.ZERO;
        } else {
            BigDecimal salaryFte = totalsHolder.salaryPercent.multiply(new BigDecimal(totalsHolder.salaryNormalMonths * 1.0 / (totalsHolder.salaryPayMonth * 100.0)));
            totalsHolder.salaryFte = BudgetConstructionReportHelper.setDecimalDigit(salaryFte, 5, false);
        }

        if (totalsHolder.salaryPayMonth != totalsHolder.csfPayMonths) {
            if (totalsHolder.csfPayMonths == 0) {
                restatementCsfAmount = 0;
            } else {
                BigDecimal amount = new BigDecimal(restatementCsfAmount * totalsHolder.salaryPayMonth * 1.0 / totalsHolder.csfPayMonths);
                restatementCsfAmount = BudgetConstructionReportHelper.setDecimalDigit(amount, 0, false).intValue();
            }
        }

        totalsHolder.csfAmount = restatementCsfAmount;
        totalsHolder.amountChange = totalsHolder.salaryAmount - totalsHolder.csfAmount;

        if (totalsHolder.csfAmount != 0) {
            totalsHolder.percentChange = BudgetConstructionReportHelper.calculatePercent(totalsHolder.amountChange, totalsHolder.csfAmount);
        } else {
            totalsHolder.percentChange = BigDecimal.ZERO;
        }

        if (totalsHolder.curToInt != 0 && totalsHolder.curToInt != -1 && totalsHolder.curToInt != totalsHolder.salaryAmount.intValue() || totalsHolder.curFteInt != 0 && totalsHolder.curFteInt != -1.00 && totalsHolder.curFteInt != totalsHolder.salaryFte.doubleValue()) {
            totalsHolder.tiFlag = BCConstants.Report.PLUS;
        } else {
            totalsHolder.tiFlag = BCConstants.Report.BLANK;
        }
    }

    // collect the total amounts for a single person and save the totals in the given holder
    protected void collectPersonTotal(Integer universityFiscalYear, Collection<BudgetConstructionSalaryFunding> salaryFundings, PersonTotalHolder totalsHolder) {
        int maxSalaryAmount = 0;
        int maxCsfAmount = 0;

        for (BudgetConstructionSalaryFunding salaryFunding : salaryFundings) {
            PendingBudgetConstructionAppointmentFunding appointmentFunding = salaryFunding.getPendingAppointmentFunding();
            BudgetConstructionPosition budgetConstructionPosition = budgetConstructionReportsServiceHelper.getBudgetConstructionPosition(universityFiscalYear, appointmentFunding);

            int salaryAmount = 0;
            Integer salaryMonths = 0;
            BigDecimal salaryPercent = BigDecimal.ZERO;
            String durationCode = appointmentFunding.getAppointmentFundingDurationCode();

            if (StringUtils.equals(durationCode, BCConstants.Report.NONE)) {
                salaryAmount = appointmentFunding.getAppointmentRequestedAmount().intValue();
                salaryMonths = appointmentFunding.getAppointmentFundingMonth();
                salaryPercent = appointmentFunding.getAppointmentRequestedTimePercent();
            } else {
                salaryAmount = appointmentFunding.getAppointmentRequestedCsfAmount().intValue();
                salaryMonths = budgetConstructionPosition.getIuNormalWorkMonths();

                boolean hasRequestedCsfTimePercent = appointmentFunding.getAppointmentRequestedCsfTimePercent() != null;
                salaryPercent = hasRequestedCsfTimePercent ? appointmentFunding.getAppointmentRequestedCsfTimePercent() : BigDecimal.ZERO;
            }

            if (salaryAmount > maxSalaryAmount) {
                maxSalaryAmount = totalsHolder.salaryAmount;
                totalsHolder.salaryPayMonth = budgetConstructionPosition.getIuPayMonths();
                totalsHolder.salaryNormalMonths = salaryMonths;
            }

            totalsHolder.salaryAmount += salaryAmount;
            totalsHolder.salaryPercent = totalsHolder.salaryPercent.add(salaryPercent);

            BudgetConstructionCalculatedSalaryFoundationTracker csfTracker = appointmentFunding.getEffectiveCSFTracker();
            if (csfTracker == null) {
                continue;
            }

            KualiInteger effectiveCsfAmount = csfTracker.getCsfAmount();
            if (effectiveCsfAmount == null || effectiveCsfAmount.isZero()) {
                continue;
            }

            if (effectiveCsfAmount.intValue() > maxCsfAmount) {
                maxCsfAmount = effectiveCsfAmount.intValue();
            }

            totalsHolder.csfAmount += effectiveCsfAmount.intValue();
            totalsHolder.csfPercent = totalsHolder.csfPercent.add(csfTracker.getCsfTimePercent());

            // data for previous year, position table has two data, one is for current year and another is for previous year.
            Integer previousFiscalYear = universityFiscalYear - 1;
            BudgetConstructionPosition previousYearBudgetConstructionPosition = budgetConstructionReportsServiceHelper.getBudgetConstructionPosition(previousFiscalYear, appointmentFunding);

            totalsHolder.csfPayMonths = previousYearBudgetConstructionPosition.getIuPayMonths();
            totalsHolder.csfNormalMonths = previousYearBudgetConstructionPosition.getIuNormalWorkMonths();

            totalsHolder.positionNumber = budgetConstructionPosition.getPositionNumber();
            totalsHolder.fiscalYearTag = previousFiscalYear.toString() + ":";

            if (!appointmentFunding.isAppointmentFundingDeleteIndicator()) {
                if (totalsHolder.curToInt <= -1) {
                    totalsHolder.curToInt = appointmentFunding.getAppointmentTotalIntendedAmount().intValue();
                }

                if (totalsHolder.curFteInt <= -1.00) {
                    totalsHolder.curFteInt = appointmentFunding.getAppointmentTotalIntendedFteQuantity().doubleValue();
                }
            }
        }
    }

    // calculate the totals for the given organization
    protected Collection<BudgetConstructionOrgReasonSummaryReportTotal> calculateOrgTotal(Collection<BudgetConstructionOrgReasonSummaryReportTotal> reasonSummaryTotalPerson, List<BudgetConstructionSalarySocialSecurityNumber> listForCalculateTotalOrg, Map salaryFundingMap) {
        Collection<BudgetConstructionOrgReasonSummaryReportTotal> returnCollection = new ArrayList<BudgetConstructionOrgReasonSummaryReportTotal>();

        for (BudgetConstructionSalarySocialSecurityNumber totalOrgEntry : listForCalculateTotalOrg) {
            OrganizationTotalHolder totalsHolder = new OrganizationTotalHolder();

            for (BudgetConstructionOrgReasonSummaryReportTotal reportTotalPersonEntry : reasonSummaryTotalPerson) {
                if (isSameSsnEntryForTotalOrg(totalOrgEntry, reportTotalPersonEntry.getBudgetConstructionSalarySocialSecurityNumber())) {
                    if (reportTotalPersonEntry.getPersonCsfAmount() == 0) {
                        totalsHolder.newFte = totalsHolder.newFte.add(reportTotalPersonEntry.getPersonSalaryFte());
                        totalsHolder.newTotalAmount += reportTotalPersonEntry.getPersonSalaryAmount();
                    } else {
                        totalsHolder.conTotalBaseAmount += reportTotalPersonEntry.getPersonCsfAmount();
                        totalsHolder.conFte = totalsHolder.conFte.add(reportTotalPersonEntry.getPersonSalaryFte());
                        totalsHolder.conTotalRequestAmount += reportTotalPersonEntry.getPersonSalaryAmount();
                    }
                }
            }

            // calculate average and change
            if (BigDecimal.ZERO.compareTo(totalsHolder.newFte) != 0) {
                BigDecimal averageAmount = BudgetConstructionReportHelper.calculateDivide(new BigDecimal(totalsHolder.newTotalAmount), totalsHolder.newFte);
                totalsHolder.newAverageAmount = BudgetConstructionReportHelper.setDecimalDigit(averageAmount, 0, false).intValue();
            }

            if (BigDecimal.ZERO.compareTo(totalsHolder.conFte) != 0) {
                BigDecimal averageAmount = BudgetConstructionReportHelper.calculateDivide(new BigDecimal(totalsHolder.conTotalBaseAmount), totalsHolder.conFte);
                totalsHolder.conAverageBaseAmount = BudgetConstructionReportHelper.setDecimalDigit(averageAmount, 0, false).intValue();

                BigDecimal averageRequestAmount = BudgetConstructionReportHelper.calculateDivide(new BigDecimal(totalsHolder.conTotalRequestAmount), totalsHolder.conFte);
                totalsHolder.conAverageRequestAmount = BudgetConstructionReportHelper.setDecimalDigit(averageRequestAmount, 0, false).intValue();
            }

            totalsHolder.conAveragechange = totalsHolder.conAverageRequestAmount - totalsHolder.conAverageBaseAmount;

            if (totalsHolder.conAverageBaseAmount != 0) {
                totalsHolder.conPercentChange = BudgetConstructionReportHelper.calculatePercent(totalsHolder.conAveragechange, totalsHolder.conAverageBaseAmount);
            }

            returnCollection.add(this.createReportTotal(totalOrgEntry, totalsHolder));
        }

        return returnCollection;
    }

    // create a report total for the given organization with the values in the given total holder
    protected BudgetConstructionOrgReasonSummaryReportTotal createReportTotal(BudgetConstructionSalarySocialSecurityNumber totalOrgEntry, OrganizationTotalHolder totalsHolder) {
        BudgetConstructionOrgReasonSummaryReportTotal reportTotal = new BudgetConstructionOrgReasonSummaryReportTotal();

        reportTotal.setBudgetConstructionSalarySocialSecurityNumber(totalOrgEntry);
        reportTotal.setNewFte(totalsHolder.newFte);
        reportTotal.setNewTotalAmount(totalsHolder.newTotalAmount);
        reportTotal.setConTotalBaseAmount(totalsHolder.conTotalBaseAmount);
        reportTotal.setConFte(totalsHolder.conFte);
        reportTotal.setConTotalRequestAmount(totalsHolder.conTotalRequestAmount);
        reportTotal.setNewAverageAmount(totalsHolder.newAverageAmount);
        reportTotal.setConAverageBaseAmount(totalsHolder.conAverageBaseAmount);
        reportTotal.setConAverageRequestAmount(totalsHolder.conAverageRequestAmount);
        reportTotal.setConAveragechange(totalsHolder.conAveragechange);
        reportTotal.setConPercentChange(totalsHolder.conPercentChange);

        return reportTotal;
    }


    // create a report total for the given person with the values in the given total holder
    protected BudgetConstructionOrgReasonSummaryReportTotal createReportTotal(BudgetConstructionSalarySocialSecurityNumber totalPersonEntry, PersonTotalHolder totalsHolder) {
        BudgetConstructionOrgReasonSummaryReportTotal reportTotal = new BudgetConstructionOrgReasonSummaryReportTotal();

        reportTotal.setBudgetConstructionSalarySocialSecurityNumber(totalPersonEntry);
        reportTotal.setPersonPositionNumber(totalsHolder.positionNumber);
        reportTotal.setPersonFiscalYearTag(totalsHolder.fiscalYearTag);
        reportTotal.setPersonCsfNormalMonths(totalsHolder.csfNormalMonths);
        reportTotal.setPersonCsfPayMonths(totalsHolder.csfPayMonths);
        reportTotal.setPersonCsfAmount(totalsHolder.csfAmount);
        reportTotal.setPersonCsfPercent(totalsHolder.csfPercent);
        reportTotal.setPersonSalaryNormalMonths(totalsHolder.salaryNormalMonths);
        reportTotal.setPersonSalaryAmount(totalsHolder.salaryAmount);
        reportTotal.setPersonSalaryPercent(totalsHolder.salaryPercent);
        reportTotal.setPersonSalaryFte(BudgetConstructionReportHelper.setDecimalDigit(totalsHolder.salaryFte, 5, false));
        reportTotal.setPersonTiFlag(totalsHolder.tiFlag);
        reportTotal.setPersonAmountChange(totalsHolder.amountChange);
        reportTotal.setPersonPercentChange(totalsHolder.percentChange);

        return reportTotal;
    }


    // a total holder that contains the totals for a single person
    protected class PersonTotalHolder {
        String emplid = StringUtils.EMPTY;
        String positionNumber = StringUtils.EMPTY;
        String fiscalYearTag = StringUtils.EMPTY;
        String tiFlag = StringUtils.EMPTY;

        Integer csfNormalMonths = 0;
        Integer csfPayMonths = 0;
        Integer csfAmount = 0;
        BigDecimal csfPercent = BigDecimal.ZERO;

        Integer salaryNormalMonths = 0;
        Integer salaryPayMonth = 0;
        Integer salaryAmount = 0;
        BigDecimal salaryPercent = BigDecimal.ZERO;
        BigDecimal salaryFte = BigDecimal.ZERO;

        Integer amountChange = 0;
        BigDecimal percentChange = BigDecimal.ZERO;

        int curToInt = -1;
        double curFteInt = -1.00;
    }

    // a total holder that contains the totals for an organization
    protected class OrganizationTotalHolder {
        BigDecimal newFte = BigDecimal.ZERO;
        Integer newTotalAmount = 0;
        Integer newAverageAmount = 0;
        BigDecimal conFte = BigDecimal.ZERO;
        Integer conTotalBaseAmount = 0;
        Integer conTotalRequestAmount = 0;
        Integer conAverageBaseAmount = 0;
        Integer conAverageRequestAmount = 0;
        Integer conAveragechange = 0;
        BigDecimal conPercentChange = BigDecimal.ZERO;
    }

    /**
     * builds orderByList for sort order.
     *
     * @return returnList
     */
    public List<String> buildOrderByList() {
        List<String> returnList = new ArrayList<String>();
        returnList.add(KFSPropertyConstants.ORGANIZATION_CHART_OF_ACCOUNTS_CODE);
        returnList.add(KFSPropertyConstants.ORGANIZATION_CODE);
        returnList.add(KFSPropertyConstants.PERSON_NAME);
        returnList.add(KFSPropertyConstants.EMPLID);

        return returnList;
    }

    /**
     * Deletes duplicated entry from list
     *
     * @param List list
     * @return a list that all duplicated entries were deleted
     */
    protected List deleteDuplicated(List list, int mode) {
        // mode 1 is for getting a list of total object
        // mode 2 is for getting a list of total account
        int count = 0;
        BudgetConstructionSalarySocialSecurityNumber ssnEntry = null;
        BudgetConstructionSalarySocialSecurityNumber ssnEntryAux = null;
        List returnList = new ArrayList();
        if ((list != null) && (list.size() > 0)) {
            ssnEntry = (BudgetConstructionSalarySocialSecurityNumber) list.get(count);
            ssnEntryAux = (BudgetConstructionSalarySocialSecurityNumber) list.get(count);
            returnList.add(ssnEntry);
            count++;
            while (count < list.size()) {
                ssnEntry = (BudgetConstructionSalarySocialSecurityNumber) list.get(count);
                switch (mode) {
                    case 1: {
                        if (!isSameSsnEntryForTotalPerson(ssnEntry, ssnEntryAux)) {
                            returnList.add(ssnEntry);
                            ssnEntryAux = ssnEntry;
                        }
                    }
                    case 2: {
                        if (!isSameSsnEntryForTotalOrg(ssnEntry, ssnEntryAux)) {
                            returnList.add(ssnEntry);
                            ssnEntryAux = ssnEntry;
                        }
                    }
                }
                count++;
            }
        }
        return returnList;
    }

    protected boolean isSameSsnEntryForTotalPerson(BudgetConstructionSalarySocialSecurityNumber firstSsn, BudgetConstructionSalarySocialSecurityNumber secondSsn) {
        if (firstSsn.getOrganizationChartOfAccountsCode().equals(secondSsn.getOrganizationChartOfAccountsCode()) && firstSsn.getOrganizationCode().equals(secondSsn.getOrganizationCode()) && firstSsn.getEmplid().equals(secondSsn.getEmplid())) {
            return true;
        } else {
            return false;
        }
    }

    protected boolean isSameSsnEntryForTotalOrg(BudgetConstructionSalarySocialSecurityNumber firstSsn, BudgetConstructionSalarySocialSecurityNumber secondSsn) {
        if (firstSsn.getOrganizationChartOfAccountsCode().equals(secondSsn.getOrganizationChartOfAccountsCode()) && firstSsn.getOrganizationCode().equals(secondSsn.getOrganizationCode())) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Sets the budgetConstructionSalarySummaryReportDao attribute value.
     *
     * @param budgetConstructionSalarySummaryReportDao The budgetConstructionSalarySummaryReportDao to set.
     */
    public void setBudgetConstructionSalarySummaryReportDao(BudgetConstructionSalarySummaryReportDao budgetConstructionSalarySummaryReportDao) {
        this.budgetConstructionSalarySummaryReportDao = budgetConstructionSalarySummaryReportDao;
    }

    /**
     * Sets the budgetConstructionOrganizationReportsService attribute value.
     *
     * @param budgetConstructionOrganizationReportsService The budgetConstructionOrganizationReportsService to set.
     */
    public void setBudgetConstructionOrganizationReportsService(BudgetConstructionOrganizationReportsService budgetConstructionOrganizationReportsService) {
        this.budgetConstructionOrganizationReportsService = budgetConstructionOrganizationReportsService;
    }

    /**
     * Sets the budgetConstructionReportsServiceHelper attribute value.
     *
     * @param budgetConstructionReportsServiceHelper The budgetConstructionReportsServiceHelper to set.
     */
    public void setBudgetConstructionReportsServiceHelper(BudgetConstructionReportsServiceHelper budgetConstructionReportsServiceHelper) {
        this.budgetConstructionReportsServiceHelper = budgetConstructionReportsServiceHelper;
    }

    /**
     * Sets the kualiConfigurationService attribute value.
     *
     * @param kualiConfigurationService The kualiConfigurationService to set.
     */
    public void setConfigurationService(ConfigurationService kualiConfigurationService) {
        this.kualiConfigurationService = kualiConfigurationService;
    }

    /**
     * Sets the businessObjectService attribute value.
     *
     * @param businessObjectService The businessObjectService to set.
     */
    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }
}
