/*
 * 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.AccountGlobalDetail;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.document.validation.impl.KfsMaintenanceDocumentRuleBase;

import java.util.List;

/**
 * This class contains common Business Rule functionality for Global Documents.
 */
public class GlobalDocumentRuleBase extends KfsMaintenanceDocumentRuleBase {

    /**
     * Constructs a GlobalDocumentRuleBase.java.
     */
    public GlobalDocumentRuleBase() {
        super();
    }

    /**
     * This method checks whether the set of Account Change Detail records on this document all are under the same Chart of
     * Accounts. It will set the appropriate field error if it did fail, and return the result.
     *
     * @param accountGlobalDetails
     * @return True if the test passed with no errors, False if any errors occurred.
     */
    protected boolean checkOnlyOneChartErrorWrapper(List<AccountGlobalDetail> accountGlobalDetails) {
        CheckOnlyOneChartResult result = checkOnlyOneChart(accountGlobalDetails);
        if (!result.success) {
            putFieldError("accountGlobalDetails[" + result.firstLineNumber + "].chartOfAccountsCode", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_ONE_CHART_ONLY);
            putFieldError("accountGlobalDetails[" + result.failedLineNumber + "].chartOfAccountsCode", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_ONE_CHART_ONLY);
        }
        return result.success;
    }

    /**
     * This method checks whether the set of Account Change Detail records on this document all are under the same Chart of
     * Accounts. It will return a failed CheckOnlyOneChartResult if so. Note that this method doesnt actually set any errors, it
     * just returns whether or not the test succeeded, and where it failed if it failed.
     *
     * @param accountGlobalDetails The popualted accountGlobalDocument to test.
     * @return A populated CheckOnlyOneChartResult object. This will contain whether the test succeeded or failed, and if failed,
     * what lines the failures occurred on.
     */
    protected CheckOnlyOneChartResult checkOnlyOneChart(List<AccountGlobalDetail> accountGlobalDetails) {
        // if there is not enough information to do the test, then exit happily with no failure
        if (accountGlobalDetails == null) {
            return new CheckOnlyOneChartResult(true);
        }
        if (accountGlobalDetails.isEmpty()) {
            return new CheckOnlyOneChartResult(true);
        }

        // test to see if there is more than one chart listed, ignores blank chartcodes
        int compareLineNumber = 0;
        int firstChartLineNumber = 0;
        String firstChart = "";
        for (AccountGlobalDetail account : accountGlobalDetails) {
            if (StringUtils.isBlank(firstChart)) {
                if (StringUtils.isNotBlank(account.getChartOfAccountsCode())) {
                    firstChart = account.getChartOfAccountsCode();
                    firstChartLineNumber = compareLineNumber;
                }
            } else {
                if (StringUtils.isNotBlank(account.getChartOfAccountsCode())) {
                    if (!firstChart.equalsIgnoreCase(account.getChartOfAccountsCode())) {
                        return new CheckOnlyOneChartResult(false, firstChartLineNumber, compareLineNumber);
                    }
                }
            }
            compareLineNumber++;
        }
        return new CheckOnlyOneChartResult(true);
    }

    /**
     * This class is used internally to represent the result of the CheckOnlyOneChart method.
     */
    protected class CheckOnlyOneChartResult {

        public int firstLineNumber;
        public int failedLineNumber;
        public boolean success;

        /**
         * Constructs a CheckOnlyOneChartResult
         */
        public CheckOnlyOneChartResult() {
            firstLineNumber = -1;
            failedLineNumber = -1;
            success = true;
        }

        /**
         * Constructs a CheckOnlyOneChartResult
         *
         * @param success
         */
        public CheckOnlyOneChartResult(boolean success) {
            this();
            this.success = success;
        }

        /**
         * Constructs a CheckOnlyOneChartResult
         *
         * @param success
         * @param firstLineNumber
         * @param failedLineNumber
         */
        public CheckOnlyOneChartResult(boolean success, int firstLineNumber, int failedLineNumber) {
            this.success = success;
            this.firstLineNumber = firstLineNumber;
            this.failedLineNumber = failedLineNumber;
        }

    }

    /**
     * This method tests whether the line being added has a different Chart of Accounts Code from any of the existing lines. It will
     * set an Error and return false if this is the case.
     *
     * @param newAccountLine
     * @param accountGlobalDetails
     * @return True if the line being added has the exact same chart as all the existing lines, False if not.
     */
    protected boolean checkOnlyOneChartAddLineErrorWrapper(AccountGlobalDetail newAccountLine, List<AccountGlobalDetail> accountGlobalDetails) {
        boolean success = checkOnlyOneChartAddLine(newAccountLine, accountGlobalDetails);
        if (!success) {
            // putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_ONE_CHART_ONLY_ADDNEW);
            // TODO: KULCOA-1091 Need to add this error to the add line, but this doesn't work right, as the
            // error message comes out at the bottom, and the field doesn't get highlighted.
            // putFieldError("newAccountGlobalDetail.chartOfAccountsCode",
            // KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_ONE_CHART_ONLY);

            // added to prevent error from showing at the top of the document, but rather in the Account Detail Edit section
            GlobalVariables.getMessageMap().putError("chartOfAccountsCode", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_ONE_CHART_ONLY_ADDNEW);
        }
        return success;
    }

    /**
     * This method tests whether a new line can be added, based on the rule that says all the accounts being used must belong to the
     * same chart. If the line being added differs from any existing line's Chart code, it will return false. Note that this
     * document does not actually set any errors, it just reports success or failure.
     *
     * @param newAccountLine
     * @param accountGlobalDetails
     * @return True if no errors are found, False if the line being added has a different Chart code than any of the existing lines.
     */
    protected boolean checkOnlyOneChartAddLine(AccountGlobalDetail newAccountLine, List<AccountGlobalDetail> accountGlobalDetails) {
        if (newAccountLine == null || accountGlobalDetails == null) {
            return true;
        }
        if (accountGlobalDetails.isEmpty()) {
            return true;
        }
        String newChart = newAccountLine.getChartOfAccountsCode();
        if (StringUtils.isBlank(newChart)) {
            return true;
        }
        for (AccountGlobalDetail account : accountGlobalDetails) {
            if (StringUtils.isNotBlank(account.getChartOfAccountsCode())) {
                if (!newChart.equalsIgnoreCase(account.getChartOfAccountsCode())) {
                    return false;
                }
            }
        }
        return true;
    }

}
