/*
 * 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.coa.document.validation.impl;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.A21IndirectCostRecoveryAccount;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.Chart;
import org.kuali.kfs.coa.businessobject.IndirectCostRecoveryAccount;
import org.kuali.kfs.kns.document.MaintenanceDocument;
import org.kuali.kfs.krad.bo.PersistableBusinessObject;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.validation.impl.KfsMaintenanceDocumentRuleBase;

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

/**
 * Business rule(s) applicable to AccountMaintenance documents.
 */
abstract public class IndirectCostRecoveryAccountsRule extends KfsMaintenanceDocumentRuleBase {

    protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(IndirectCostRecoveryAccountsRule.class);

    protected static final BigDecimal BD100 = new BigDecimal(100);
    private List<? extends IndirectCostRecoveryAccount> activeIndirectCostRecoveryAccountList;
    protected String boFieldPath;

    /**
     * Custom processing for adding collection lines
     *
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument, java.lang.String, org.kuali.rice.krad.bo.PersistableBusinessObject)
     */
    public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) {
        boolean success = true;

        // this incoming bo needs to be refreshed because it doesn't have its subobjects setup
        bo.refreshNonUpdateableReferences();

        if (bo instanceof IndirectCostRecoveryAccount || bo instanceof A21IndirectCostRecoveryAccount) {
            IndirectCostRecoveryAccount account = (IndirectCostRecoveryAccount) bo;
            success &= checkIndirectCostRecoveryAccount(account);
        }
        return success;
    }

    /**
     * This method calls the rule: KFSPropertyConstants.INDIRECT_COST_RECOVERY_ACCOUNT
     *
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
     */
    protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
        boolean success = true;
        return success = checkIndirectCostRecoveryAccountDistributions();
    }

    /**
     * This method checks if the ICR collection should or should not be filled
     * error message is not handled in the function
     *
     * @param expectFilled
     * @return
     */
    protected boolean checkICRCollectionExist(boolean expectFilled) {
        boolean success = true;

        success = expectFilled != activeIndirectCostRecoveryAccountList.isEmpty();

        //double check each of the account/coa codes are not blank
        if (!success && expectFilled) {
            for (IndirectCostRecoveryAccount account : activeIndirectCostRecoveryAccountList) {
                success &= StringUtils.isNotBlank(account.getIndirectCostRecoveryAccountNumber())
                    && StringUtils.isNotBlank(account.getIndirectCostRecoveryFinCoaCode());
            }
        }

        return success;
    }


    /**
     * This method checks if the ICR collection should or should not be filled
     * add error message if validation is not successful
     *
     * @param expectFilled
     * @param errorMessage
     * @param args
     * @return
     */
    protected boolean checkICRCollectionExistWithErrorMessage(boolean expectFilled, String errorMessage, String... args) {
        boolean success = true;
        success = checkICRCollectionExist(expectFilled);
        if (!success) {
            putFieldError(boFieldPath, errorMessage, args);
        }
        return success;
    }

    /**
     * Check valid IndirectCostRecovery Account
     *
     * @return
     */
    protected boolean checkIndirectCostRecoveryAccount(IndirectCostRecoveryAccount icrAccount) {

        boolean success = true;

        //check for empty values on the ICR account

        // The chart and account  must exist in the database.
        String chartOfAccountsCode = icrAccount.getIndirectCostRecoveryFinCoaCode();
        String accountNumber = icrAccount.getIndirectCostRecoveryAccountNumber();
        BigDecimal icraAccountLinePercentage = ObjectUtils.isNotNull(icrAccount.getAccountLinePercent()) ? icrAccount.getAccountLinePercent() : BigDecimal.ZERO;
        return checkIndirectCostRecoveryAccount(chartOfAccountsCode, accountNumber, icraAccountLinePercentage);
    }

    protected boolean checkIndirectCostRecoveryAccount(String chartOfAccountsCode, String accountNumber, BigDecimal icraAccountLinePercentage) {
        boolean success = true;
        if (StringUtils.isBlank(chartOfAccountsCode)) {
            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ICR_CHART_OF_ACCOUNTS_CODE, KFSKeyConstants.ERROR_REQUIRED,
                getDDAttributeLabel(KFSPropertyConstants.ICR_CHART_OF_ACCOUNTS_CODE));
            success &= false;
        }

        if (StringUtils.isBlank(accountNumber)) {
            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ICR_ACCOUNT_NUMBER, KFSKeyConstants.ERROR_REQUIRED,
                getDDAttributeLabel(KFSPropertyConstants.ICR_ACCOUNT_NUMBER));
            success &= false;
        }

        if (StringUtils.isNotBlank(chartOfAccountsCode) && StringUtils.isNotBlank(accountNumber)) {
            Map<String, String> chartAccountMap = new HashMap<String, String>();
            chartAccountMap.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode);
            if (SpringContext.getBean(BusinessObjectService.class).countMatching(Chart.class, chartAccountMap) < 1) {
                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ICR_CHART_OF_ACCOUNTS_CODE, KFSKeyConstants.ERROR_EXISTENCE, chartOfAccountsCode);
                success &= false;
            }
            chartAccountMap.put(KFSPropertyConstants.ACCOUNT_NUMBER, accountNumber);
            if (SpringContext.getBean(BusinessObjectService.class).countMatching(Account.class, chartAccountMap) < 1) {
                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ICR_ACCOUNT_NUMBER, KFSKeyConstants.ERROR_EXISTENCE, chartOfAccountsCode + "-" + accountNumber);
                success &= false;
            }
        }


        //check the percent line
        if (icraAccountLinePercentage.compareTo(BigDecimal.ZERO) <= 0 || icraAccountLinePercentage.compareTo(BD100) == 1) {
            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ICR_ACCOUNT_LINE_PERCENT,
                KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_ACCOUNT_INVALID_LINE_PERCENT);
            success &= false;
        }

        return success;
    }

    /**
     * Check the collection list of indirect cost recovery account
     * <p>
     * 1. Check each account with rule: checkIndirectCostRecoveryAccount
     * 2. Total distributions from all the account should be 100
     *
     * @param activeIndirectCostRecoveryAccountList
     * @param document
     * @return
     */
    protected boolean checkIndirectCostRecoveryAccountDistributions() {
        boolean result = true;
        if (ObjectUtils.isNull(activeIndirectCostRecoveryAccountList) || (activeIndirectCostRecoveryAccountList.size() == 0)) {
            return result;
        }

        int i = 0;
        BigDecimal totalDistribution = BigDecimal.ZERO;

        for (IndirectCostRecoveryAccount icra : activeIndirectCostRecoveryAccountList) {
            String errorPath = MAINTAINABLE_ERROR_PREFIX + boFieldPath + "[" + i++ + "]";
            GlobalVariables.getMessageMap().addToErrorPath(errorPath);
            checkIndirectCostRecoveryAccount(icra);
            GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);

            if (!ObjectUtils.isNull(icra.getAccountLinePercent())) {
                totalDistribution = totalDistribution.add(icra.getAccountLinePercent());
            }
        }

        //check the total distribution is 100
        if (totalDistribution.compareTo(BD100) != 0) {
            putFieldError(boFieldPath, KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_ACCOUNT_TOTAL_NOT_100_PERCENT);
            result &= false;
        }

        return result;
    }

    /**
     * Get the attribute label from DataDictionary
     *
     * @param attribute
     * @return
     */
    protected String getDDAttributeLabel(String attribute) {
        return ddService.getAttributeLabel(IndirectCostRecoveryAccount.class, attribute);
    }

    public List<? extends IndirectCostRecoveryAccount> getActiveIndirectCostRecoveryAccountList() {
        return activeIndirectCostRecoveryAccountList;
    }

    public void setActiveIndirectCostRecoveryAccountList(List<? extends IndirectCostRecoveryAccount> indirectCostRecoveryAccountList) {
        this.activeIndirectCostRecoveryAccountList = indirectCostRecoveryAccountList;
    }

    public String getBoFieldPath() {
        return boFieldPath;
    }

    public void setBoFieldPath(String boFieldPath) {
        this.boFieldPath = boFieldPath;
    }

}

