/*
 * 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.fp.document;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.fp.businessobject.CapitalAccountingLines;
import org.kuali.kfs.fp.businessobject.CapitalAssetInformation;
import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLineBase;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.service.DebitDeterminerService;
import org.kuali.rice.core.api.util.type.KualiDecimal;

import java.sql.Date;
import java.util.Iterator;
import java.util.List;

/**
 * Abstract class which defines behavior common to CashReceipt-like documents.
 */
abstract public class CashReceiptFamilyBase extends CapitalAccountingLinesDocumentBase implements CapitalAssetEditable {
    protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CashReceiptFamilyBase.class);
    protected String campusLocationCode; // TODO Needs to be an actual object - also need to clarify this
    protected Date depositDate;

    /**
     * Constructs a CashReceiptFamilyBase
     */
    public CashReceiptFamilyBase() {
        setCampusLocationCode(KFSConstants.CashReceiptConstants.DEFAULT_CASH_RECEIPT_CAMPUS_LOCATION_CODE);
    }

    /**
     * Documents in the CashReceiptFamily do not perform Sufficient Funds checking
     *
     * @see org.kuali.kfs.sys.document.AccountingDocumentBase#documentPerformsSufficientFundsCheck()
     */
    @Override
    public boolean documentPerformsSufficientFundsCheck() {
        return false;
    }

    /**
     * Gets the campusLocationCode attribute.
     *
     * @return Returns the campusLocationCode.
     */
    public String getCampusLocationCode() {
        return campusLocationCode;
    }

    /**
     * Sets the campusLocationCode attribute value.
     *
     * @param campusLocationCode The campusLocationCode to set.
     */
    public void setCampusLocationCode(String campusLocationCode) {
        this.campusLocationCode = campusLocationCode;
    }

    /**
     * Gets the depositDate attribute.
     *
     * @return Returns the depositDate.
     */
    public Date getDepositDate() {
        return depositDate;
    }

    /**
     * Sets the depositDate attribute value.
     *
     * @param depositDate The depositDate to set.
     */
    public void setDepositDate(Date depositDate) {
        this.depositDate = depositDate;
    }


    /**
     * Total for a Cash Receipt according to the spec should be the sum of the amounts on accounting lines belonging to object codes
     * having the 'income' object type, less the sum of the amounts on accounting lines belonging to object codes having the
     * 'expense' object type.
     *
     * @see org.kuali.kfs.sys.document.AccountingDocument#getSourceTotal()
     */
    @Override
    public KualiDecimal getSourceTotal() {
        KualiDecimal total = KualiDecimal.ZERO;
        AccountingLineBase al = null;
        if (ObjectUtils.isNull(getSourceAccountingLines()) || getSourceAccountingLines().isEmpty()) {
            refreshReferenceObject(KFSPropertyConstants.SOURCE_ACCOUNTING_LINES);
        }
        Iterator iter = getSourceAccountingLines().iterator();
        while (iter.hasNext()) {
            al = (AccountingLineBase) iter.next();
            try {
                KualiDecimal amount = al.getAmount().abs();
                if (amount != null && amount.isNonZero()) {
                    if (isDebit(al)) {
                        total = total.subtract(amount);
                    } else { // in this context, if it's not a debit, it's a credit
                        total = total.add(amount);
                    }
                }
            } catch (Exception e) {
                // Possibly caused by accounting lines w/ bad data
                LOG.error("Error occured trying to compute Cash receipt total, returning 0", e);
                return KualiDecimal.ZERO;
            }
        }
        return total;
    }

    /**
     * Cash Receipts only have source lines, so this should always return 0.
     *
     * @see org.kuali.kfs.sys.document.AccountingDocument#getTargetTotal()
     */
    @Override
    public KualiDecimal getTargetTotal() {
        return KualiDecimal.ZERO;
    }

    /**
     * Overrides the base implementation to return an empty string.
     *
     * @see org.kuali.kfs.sys.document.AccountingDocument#getSourceAccountingLinesSectionTitle()
     */
    @Override
    public String getSourceAccountingLinesSectionTitle() {
        return KFSConstants.EMPTY_STRING;
    }

    /**
     * Overrides the base implementation to return an empty string.
     *
     * @see org.kuali.kfs.sys.document.AccountingDocument#getTargetAccountingLinesSectionTitle()
     */
    @Override
    public String getTargetAccountingLinesSectionTitle() {
        return KFSConstants.EMPTY_STRING;
    }

    /**
     * Returns true if accounting line is debit
     *
     * @param financialDocument
     * @param accountingLine
     * @param true              if accountline line
     * @see IsDebitUtils#isDebitConsideringType(FinancialDocumentRuleBase, FinancialDocument, AccountingLine)
     * @see org.kuali.rice.krad.rule.AccountingLineRule#isDebit(org.kuali.rice.krad.document.FinancialDocument,
     * org.kuali.rice.krad.bo.AccountingLine)
     */
    @Override
    public boolean isDebit(GeneralLedgerPendingEntrySourceDetail postable) {
        // error corrections are not allowed
        DebitDeterminerService isDebitUtils = SpringContext.getBean(DebitDeterminerService.class);
        //TODO ??? we now allow error correction on AD and CCR, so can't do this checking here; do it in subclasses if needed.
        //isDebitUtils.disallowErrorCorrectionDocumentCheck(this);
        return isDebitUtils.isDebitConsideringType(this, postable);
    }

    /**
     * Overrides to set the entry's description to the description from the accounting line, if a value exists.
     *
     * @param financialDocument submitted accounting document
     * @param accountingLine    accounting line in accounting document
     * @param explicitEntry     general ledger pending entry
     * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#customizeExplicitGeneralLedgerPendingEntry(org.kuali.rice.krad.document.FinancialDocument,
     * org.kuali.rice.krad.bo.AccountingLine, org.kuali.module.gl.bo.GeneralLedgerPendingEntry)
     */
    @Override
    public void customizeExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry) {
        String accountingLineDescription = postable.getFinancialDocumentLineDescription();
        if (StringUtils.isNotBlank(accountingLineDescription)) {
            explicitEntry.setTransactionLedgerEntryDescription(accountingLineDescription);
        }
    }

    /**
     * @see org.kuali.kfs.fp.document.CapitalAssetEditable#getCapitalAssetInformation()
     */
    @Override
    public List<CapitalAssetInformation> getCapitalAssetInformation() {
        return ObjectUtils.isNull(capitalAssetInformation) ? null : capitalAssetInformation;
    }

    /**
     * @see org.kuali.kfs.fp.document.CapitalAssetEditable#setCapitalAssetInformation(org.kuali.kfs.fp.businessobject.CapitalAssetInformation)
     */
    @Override
    public void setCapitalAssetInformation(List<CapitalAssetInformation> capitalAssetInformation) {
        this.capitalAssetInformation = capitalAssetInformation;
    }

    protected CapitalAssetManagementModuleService getCapitalAssetManagementModuleService() {
        return SpringContext.getBean(CapitalAssetManagementModuleService.class);
    }

    /**
     * Note: This method is only shared by subclasses which implement Correctable.
     * Upon error correction, negates amount in each capital asset accounting line,
     * and updates the documentNumber to point to the new document.
     */
    protected void correctCapitalAccountingLines() {
        for (CapitalAccountingLines capacctline : capitalAccountingLines) {
            capacctline.setDocumentNumber(documentNumber);
            capacctline.setAmount(capacctline.getAmount().negated());
        }
    }

    /**
     * Returns true if the document is error corrected.
     */
    public boolean isErrorCorrected() {
        return StringUtils.isNotEmpty(getFinancialSystemDocumentHeader().getFinancialDocumentInErrorNumber());
    }

}
