/*
 * 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.gl.batch.service.impl;

import org.kuali.kfs.gl.GeneralLedgerConstants;
import org.kuali.kfs.gl.batch.service.AccountingCycleCachingService;
import org.kuali.kfs.gl.batch.service.PostTransaction;
import org.kuali.kfs.gl.businessobject.SufficientFundBalances;
import org.kuali.kfs.gl.businessobject.Transaction;
import org.kuali.kfs.krad.service.PersistenceStructureService;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.service.ReportWriterService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
 * An implementation of PostTransaction which posts a transaction to the appropriate sufficient funds record
 */
@Transactional
public class PostSufficientFundBalances implements PostTransaction {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PostSufficientFundBalances.class);

    private AccountingCycleCachingService accountingCycleCachingService;
    private PersistenceStructureService persistenceStructureService;

    /**
     * Constructs a PostSufficientFundBalances instance
     */
    public PostSufficientFundBalances() {
        super();
    }

    /**
     * Posts the transaction to the appropriate sufficient funds records
     *
     * @param t                         the transaction which is being posted
     * @param mode                      the mode the poster is currently running in
     * @param postDate                  the date this transaction should post to
     * @param posterReportWriterService the writer service where the poster is writing its report
     * @return the accomplished post type
     * @see org.kuali.kfs.gl.batch.service.PostTransaction#post(org.kuali.kfs.gl.businessobject.Transaction, int, java.util.Date)
     */
    public String post(Transaction t, int mode, Date postDate, ReportWriterService posterReportWriterService) {
        LOG.debug("post() started");

        String returnCode = GeneralLedgerConstants.UPDATE_CODE;

        if (KFSConstants.SF_TYPE_NO_CHECKING.equals(t.getAccount().getAccountSufficientFundsCode())) {
            // Don't need to post
            return GeneralLedgerConstants.EMPTY_CODE;
        }

        // Get the Sufficient funds code
        // Sufficient Funds Code
        String sufficientFundsObjectCode = null;
        if (KFSConstants.SF_TYPE_OBJECT.equals(t.getAccount().getAccountSufficientFundsCode())) {
            sufficientFundsObjectCode = t.getFinancialObjectCode();
        } else if (KFSConstants.SF_TYPE_LEVEL.equals(t.getAccount().getAccountSufficientFundsCode())) {
            if (ObjectUtils.isNull(t.getFinancialObject())) {
                return "E:Could not find sufficient funds object code for " + t.toString();
            }
            sufficientFundsObjectCode = t.getFinancialObject().getFinancialObjectLevelCode();
        } else if (KFSConstants.SF_TYPE_CONSOLIDATION.equals(t.getAccount().getAccountSufficientFundsCode())) {
            //sufficientFundsObjectCode = t.getFinancialObject().getFinancialObjectLevel().getFinancialConsolidationObjectCode();
            if (ObjectUtils.isNull(t.getFinancialObject())) {
                return "E:Could not find sufficient funds object code for " + t.toString();
            }
            sufficientFundsObjectCode = accountingCycleCachingService.getObjectLevel(t.getFinancialObject().getChartOfAccountsCode(), t.getFinancialObject().getFinancialObjectLevelCode()).getFinancialConsolidationObjectCode();
        } else if (KFSConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(t.getAccount().getAccountSufficientFundsCode()) || KFSConstants.SF_TYPE_ACCOUNT.equals(t.getAccount().getAccountSufficientFundsCode())) {
            sufficientFundsObjectCode = GeneralLedgerConstants.getSpaceFinancialObjectCode();
        } else {
            return "E:Invalid sufficient funds code (" + t.getAccount().getAccountSufficientFundsCode() + ")";
        }

        // Look to see if there is a sufficient funds record for this
        SufficientFundBalances sfBalance = accountingCycleCachingService.getSufficientFundBalances(t.getUniversityFiscalYear(), t.getChartOfAccountsCode(), t.getAccountNumber(), sufficientFundsObjectCode);
        if (sfBalance == null) {
            returnCode = GeneralLedgerConstants.INSERT_CODE;
            sfBalance = new SufficientFundBalances();
            sfBalance.setUniversityFiscalYear(t.getUniversityFiscalYear());
            sfBalance.setChartOfAccountsCode(t.getChartOfAccountsCode());
            sfBalance.setAccountNumber(t.getAccountNumber());
            sfBalance.setFinancialObjectCode(sufficientFundsObjectCode);
            sfBalance.setAccountActualExpenditureAmt(KualiDecimal.ZERO);
            sfBalance.setAccountEncumbranceAmount(KualiDecimal.ZERO);
            sfBalance.setCurrentBudgetBalanceAmount(KualiDecimal.ZERO);
            sfBalance.setAccountSufficientFundsCode(t.getAccount().getAccountSufficientFundsCode());
        }

        if (KFSConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(t.getAccount().getAccountSufficientFundsCode())) {
            // 2640-PROCESS-CASH
            if (t.getFinancialBalanceTypeCode().equals(t.getOption().getActualFinancialBalanceTypeCd())) {
                if (t.getFinancialObjectCode().equals(t.getChart().getFinancialCashObjectCode()) || t.getFinancialObjectCode().equals(t.getChart().getFinAccountsPayableObjectCode())) {
                    // 2641-PROCESS-CASH-ACTUAL
                    updateBudgetAmount(t.getTransactionDebitCreditCode(), sfBalance, t.getTransactionLedgerEntryAmount());
                } else {
                    // No need to post this
                    return GeneralLedgerConstants.EMPTY_CODE;
                }
            } else if (t.getFinancialBalanceTypeCode().equals(t.getOption().getExtrnlEncumFinBalanceTypCd()) || t.getFinancialBalanceTypeCode().equals(t.getOption().getIntrnlEncumFinBalanceTypCd()) || t.getFinancialBalanceTypeCode().equals(t.getOption().getPreencumbranceFinBalTypeCd()) || t.getOption().getCostShareEncumbranceBalanceTypeCd().equals(t.getFinancialBalanceTypeCode())) {
                if (t.getFinancialObjectTypeCode().equals(t.getOption().getFinObjTypeExpenditureexpCd()) || t.getFinancialObjectTypeCode().equals(t.getOption().getFinObjTypeExpendNotExpCode()) || t.getOption().getFinancialObjectTypeTransferExpenseCd().equals(t.getFinancialObjectTypeCode()) || t.getOption().getFinObjTypeExpNotExpendCode().equals(t.getFinancialObjectTypeCode())) {
                    // 2462-PROCESS-CASH-ENCUMBRANCE
                    updateEncumbranceAmount(t.getTransactionDebitCreditCode(), sfBalance, t.getTransactionLedgerEntryAmount());
                } else {
                    // No need to post this
                    return GeneralLedgerConstants.EMPTY_CODE;
                }
            } else {
                // No need to post this
                return GeneralLedgerConstants.EMPTY_CODE;
            }
        } else {
            // 2630-PROCESS-OBJECT-OR-ACCOUNT
            if (t.getFinancialObjectTypeCode().equals(t.getOption().getFinObjTypeExpenditureexpCd()) || t.getFinancialObjectTypeCode().equals(t.getOption().getFinObjTypeExpendNotExpCode()) || t.getOption().getFinancialObjectTypeTransferExpenseCd().equals(t.getFinancialObjectTypeCode()) || t.getOption().getFinObjTypeExpNotExpendCode().equals(t.getFinancialObjectTypeCode())) {
                if (t.getFinancialBalanceTypeCode().equals(t.getOption().getActualFinancialBalanceTypeCd())) {
                    // 2631-PROCESS-OBJTACCT-ACTUAL
                    updateExpendedAmount(t.getTransactionDebitCreditCode(), sfBalance, t.getTransactionLedgerEntryAmount());
                } else if (t.getFinancialBalanceTypeCode().equals(t.getOption().getExtrnlEncumFinBalanceTypCd()) || t.getFinancialBalanceTypeCode().equals(t.getOption().getIntrnlEncumFinBalanceTypCd()) || t.getFinancialBalanceTypeCode().equals(t.getOption().getPreencumbranceFinBalTypeCd()) || t.getFinancialBalanceTypeCode().equals(t.getOption().getCostShareEncumbranceBalanceTypeCd())) {
                    // 2632-PROCESS-OBJTACCT-ENCMBRNC
                    updateEncumbranceAmount(t.getTransactionDebitCreditCode(), sfBalance, t.getTransactionLedgerEntryAmount());
                } else if (t.getFinancialBalanceTypeCode().equals(t.getOption().getBudgetCheckingBalanceTypeCd())) {
                    sfBalance.setCurrentBudgetBalanceAmount(sfBalance.getCurrentBudgetBalanceAmount().add(t.getTransactionLedgerEntryAmount()));
                } else {
                    // No need to post this
                    return GeneralLedgerConstants.EMPTY_CODE;
                }
            } else {
                // No need to post this
                return GeneralLedgerConstants.EMPTY_CODE;
            }
        }

        // If we get here, we need to save the balance entry
        if (returnCode.equals(GeneralLedgerConstants.INSERT_CODE)) {
            accountingCycleCachingService.insertSufficientFundBalances(sfBalance);
        } else {
            accountingCycleCachingService.updateSufficientFundBalances(sfBalance);
        }


        return returnCode;
    }

    /**
     * Updates the expenditure amount of a given sufficient funds balance record
     *
     * @param debitCreditCode whether the the amount should be debited or credited to the SF balance
     * @param bal             a sufficient funds balance to update
     * @param amount          the amount to debit or credit
     */
    protected void updateExpendedAmount(String debitCreditCode, SufficientFundBalances bal, KualiDecimal amount) {
        if (KFSConstants.GL_CREDIT_CODE.equals(debitCreditCode)) {
            bal.setAccountActualExpenditureAmt(bal.getAccountActualExpenditureAmt().subtract(amount));
        } else if (KFSConstants.GL_DEBIT_CODE.equals(debitCreditCode) || KFSConstants.GL_BUDGET_CODE.equals(debitCreditCode)) {
            bal.setAccountActualExpenditureAmt(bal.getAccountActualExpenditureAmt().add(amount));
        }
    }

    /**
     * Updates the encumbrance amount of a given sufficient funds balance record
     *
     * @param debitCreditCode whether the the amount should be debited or credited to the SF balance
     * @param bal             a sufficient funds balance to update
     * @param amount          the amount to debit or credit
     */
    protected void updateEncumbranceAmount(String debitCreditCode, SufficientFundBalances bal, KualiDecimal amount) {
        if (KFSConstants.GL_CREDIT_CODE.equals(debitCreditCode)) {
            bal.setAccountEncumbranceAmount(bal.getAccountEncumbranceAmount().subtract(amount));
        } else if (KFSConstants.GL_DEBIT_CODE.equals(debitCreditCode) || KFSConstants.GL_BUDGET_CODE.equals(debitCreditCode)) {
            bal.setAccountEncumbranceAmount(bal.getAccountEncumbranceAmount().add(amount));
        }
    }

    /**
     * Updates the budget amount of a given sufficient funds balance record
     *
     * @param debitCreditCode whether the the amount should be debited or credited to the SF balance
     * @param bal             a sufficient funds balance to update
     * @param amount          the amount to debit or credit
     */
    protected void updateBudgetAmount(String debitCreditCode, SufficientFundBalances bal, KualiDecimal amount) {
        if (KFSConstants.GL_CREDIT_CODE.equals(debitCreditCode)) {
            bal.setCurrentBudgetBalanceAmount(bal.getCurrentBudgetBalanceAmount().subtract(amount));
        } else if (KFSConstants.GL_DEBIT_CODE.equals(debitCreditCode) || KFSConstants.GL_BUDGET_CODE.equals(debitCreditCode)) {
            bal.setCurrentBudgetBalanceAmount(bal.getCurrentBudgetBalanceAmount().add(amount));
        }
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.PostTransaction#getDestinationName()
     */
    public String getDestinationName() {
        return persistenceStructureService.getTableName(SufficientFundBalances.class);
    }

    public void setAccountingCycleCachingService(AccountingCycleCachingService accountingCycleCachingService) {
        this.accountingCycleCachingService = accountingCycleCachingService;
    }

    public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
        this.persistenceStructureService = persistenceStructureService;
    }
}
