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

import org.apache.commons.collections.CollectionUtils;
import org.kuali.kfs.kns.document.MaintenanceDocument;
import org.kuali.kfs.krad.bo.PersistableBusinessObject;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.ar.ArConstants;
import org.kuali.kfs.module.ar.ArKeyConstants;
import org.kuali.kfs.module.ar.ArPropertyConstants;
import org.kuali.kfs.module.ar.businessobject.CostCategory;
import org.kuali.kfs.module.ar.businessobject.CostCategoryDetail;
import org.kuali.kfs.module.ar.businessobject.CostCategoryObjectCode;
import org.kuali.kfs.module.ar.businessobject.CostCategoryObjectConsolidation;
import org.kuali.kfs.module.ar.businessobject.CostCategoryObjectLevel;
import org.kuali.kfs.module.ar.service.CostCategoryService;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.validation.impl.KfsMaintenanceDocumentRuleBase;
import org.kuali.rice.core.api.util.RiceKeyConstants;

/**
 * Validates content of a <code>{@link AccountDelegate}</code> maintenance document upon triggering of a approve, save, or route
 * event.
 */
public class CostCategoryRule extends KfsMaintenanceDocumentRuleBase {
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CostCategoryRule.class);

    protected CostCategory oldCategories;
    protected CostCategory newCategories;

    private volatile static CostCategoryService costCategoryService;

    @Override
    protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {

        LOG.debug("Entering processCustomSaveDocumentBusinessRules()");
        setupConvenienceObjects(document);

        // check simple rules
        if (!isInactivation()) {
            checkSimpleRules();
        }

        return true;
    }

    @Override
    protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
        LOG.debug("Entering processCustomRouteDocumentBusinessRules()");

        setupConvenienceObjects(document);

        // check simple rules
        boolean success = true;

        if (!isInactivation()) {
            success &= checkSimpleRules();
        }

        return success;
    }

    protected void setupConvenienceObjects(MaintenanceDocument document) {
        // setup oldAccount convenience objects, make sure all possible sub-objects are populated
        oldCategories = (CostCategory) super.getOldBo();

        // setup newAccount convenience objects, make sure all possible sub-objects are populated
        newCategories = (CostCategory) super.getNewBo();
    }

    /**
     * Test if we're inactivating the cost category
     *
     * @return true if we're inactivating an entire cost category, false otherwise
     */
    protected boolean isInactivation() {
        if (!ObjectUtils.isNull(oldCategories)) {
            return oldCategories.isActive() && !newCategories.isActive();
        }
        return false;
    }

    /**
     * Checks that there are details on the cost control and that none of the details are used by other cost categories
     *
     * @return true if validations passed, false otherwise
     */
    protected boolean checkSimpleRules() {
        boolean success = true;

        // check that there is something - at least one object code or level or consolidation
        if (CollectionUtils.isEmpty(newCategories.getObjectCodes()) && CollectionUtils.isEmpty(newCategories.getObjectLevels()) && CollectionUtils.isEmpty(newCategories.getObjectConsolidations())) {
            putGlobalError(ArKeyConstants.ERROR_DOCUMENT_COST_CATEGORY_NO_DETAILS);
            success = false;
        } else {
            if (!CollectionUtils.isEmpty(newCategories.getObjectCodes())) {
                int count = 0;

                for (CostCategoryObjectCode objectCode : newCategories.getObjectCodes()) {
                    if (objectCode.isActive()) {
                        final boolean objectCodeExists = checkObjectCodeExists(objectCode);

                        if (objectCodeExists) {
                            final CostCategoryDetail conflictingDetail = getCostCategoryService().isCostCategoryObjectCodeUnique(objectCode);
                            if (!ObjectUtils.isNull(conflictingDetail)) {
                                final String conflictingDetailObjectLabel = getDataDictionaryService().getDataDictionary().getBusinessObjectEntryForConcreteClass(conflictingDetail.getClass().getName()).getObjectLabel();
                                putFieldError(ArPropertyConstants.OBJECT_CODES + "[" + count + "]." + KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, ArKeyConstants.ERROR_DOCUMENT_COST_CATEGORY_OBJECT_CODE_NOT_UNIQUE, new String[]{objectCode.getChartOfAccountsCode(), objectCode.getFinancialObjectCode(), conflictingDetailObjectLabel, conflictingDetail.getCategoryCode()});
                                success = false;
                            }
                        } else {
                            success = false;
                        }
                        count += 1;
                    }
                }
            }
            if (!CollectionUtils.isEmpty(newCategories.getObjectLevels())) {
                int count = 0;
                for (CostCategoryObjectLevel objectLevel : newCategories.getObjectLevels()) {
                    if (objectLevel.isActive()) {
                        final boolean objectLevelExists = checkObjectLevelExists(objectLevel);

                        if (objectLevelExists) {
                            final CostCategoryDetail conflictingDetail = getCostCategoryService().isCostCategoryObjectLevelUnique(objectLevel);
                            if (!ObjectUtils.isNull(conflictingDetail)) {
                                final String conflictingDetailObjectLabel = getDataDictionaryService().getDataDictionary().getBusinessObjectEntryForConcreteClass(conflictingDetail.getClass().getName()).getObjectLabel();
                                putFieldError(ArPropertyConstants.OBJECT_LEVELS + "[" + count + "]." + KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, ArKeyConstants.ERROR_DOCUMENT_COST_CATEGORY_OBJECT_LEVEL_NOT_UNIQUE, new String[]{objectLevel.getChartOfAccountsCode(), objectLevel.getFinancialObjectLevelCode(), conflictingDetailObjectLabel, conflictingDetail.getCategoryCode()});
                                success = false;
                            }
                        } else {
                            success = false;
                        }
                        count += 1;
                    }
                }
            }
            if (!CollectionUtils.isEmpty(newCategories.getObjectConsolidations())) {
                int count = 0;
                for (CostCategoryObjectConsolidation objectConsolidation : newCategories.getObjectConsolidations()) {
                    if (objectConsolidation.isActive()) {
                        final boolean objectConsolidationExists = checkObjectConsolidationExists(objectConsolidation);

                        if (objectConsolidationExists) {
                            final CostCategoryDetail conflictingDetail = getCostCategoryService().isCostCategoryObjectConsolidationUnique(objectConsolidation);
                            if (!ObjectUtils.isNull(conflictingDetail)) {
                                final String conflictingDetailObjectLabel = getDataDictionaryService().getDataDictionary().getBusinessObjectEntryForConcreteClass(conflictingDetail.getClass().getName()).getObjectLabel();
                                putFieldError(ArPropertyConstants.OBJECT_CONSOLIDATIONS + "[" + count + "]." + KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, ArKeyConstants.ERROR_DOCUMENT_COST_CATEGORY_OBJECT_CONSOLIDATION_NOT_UNIQUE, new String[]{objectConsolidation.getChartOfAccountsCode(), objectConsolidation.getFinConsolidationObjectCode(), conflictingDetailObjectLabel, conflictingDetail.getCategoryCode()});
                                success = false;
                            }
                        } else {
                            success = false;
                        }
                        count += 1;
                    }
                }
            }
        }

        return success;
    }

    @Override
    public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject line) {
        boolean success = super.processCustomAddCollectionLineBusinessRules(document, collectionName, line);

        if (line instanceof CostCategoryObjectCode) {
            CostCategoryObjectCode newObjectCode = (CostCategoryObjectCode) line;
            success &= checkObjectCodeExists(newObjectCode);

            if (success) {
                final CostCategoryDetail conflictingDetail = getCostCategoryService().isCostCategoryObjectCodeUnique(newObjectCode);
                if (!ObjectUtils.isNull(conflictingDetail)) {
                    final String conflictingDetailObjectLabel = getDataDictionaryService().getDataDictionary().getBusinessObjectEntryForConcreteClass(conflictingDetail.getClass().getName()).getObjectLabel();
                    GlobalVariables.getMessageMap().putErrorForSectionId(ArConstants.CostCategoryMaintenanceSections.EDIT_OBJECT_CODES, ArKeyConstants.ERROR_DOCUMENT_COST_CATEGORY_OBJECT_CODE_NOT_UNIQUE, new String[]{newObjectCode.getChartOfAccountsCode(), newObjectCode.getFinancialObjectCode(), conflictingDetailObjectLabel, conflictingDetail.getCategoryCode()});
                    success = false;
                }
            }
        } else if (line instanceof CostCategoryObjectLevel) {
            CostCategoryObjectLevel newObjectLevel = (CostCategoryObjectLevel) line;
            success &= checkObjectLevelExists(newObjectLevel);

            if (success) {
                final CostCategoryDetail conflictingDetail = getCostCategoryService().isCostCategoryObjectLevelUnique(newObjectLevel);
                if (!ObjectUtils.isNull(conflictingDetail)) {
                    final String conflictingDetailObjectLabel = getDataDictionaryService().getDataDictionary().getBusinessObjectEntryForConcreteClass(conflictingDetail.getClass().getName()).getObjectLabel();
                    GlobalVariables.getMessageMap().putErrorForSectionId(ArConstants.CostCategoryMaintenanceSections.EDIT_OBJECT_LEVELS, ArKeyConstants.ERROR_DOCUMENT_COST_CATEGORY_OBJECT_CODE_NOT_UNIQUE, new String[]{newObjectLevel.getChartOfAccountsCode(), newObjectLevel.getFinancialObjectLevelCode(), conflictingDetailObjectLabel, conflictingDetail.getCategoryCode()});
                    success = false;
                }
            }
        } else if (line instanceof CostCategoryObjectConsolidation) {
            CostCategoryObjectConsolidation newObjectConsolidation = (CostCategoryObjectConsolidation) line;
            success &= checkObjectConsolidationExists(newObjectConsolidation);

            if (success) {
                final CostCategoryDetail conflictingDetail = getCostCategoryService().isCostCategoryObjectConsolidationUnique(newObjectConsolidation);
                if (!ObjectUtils.isNull(conflictingDetail)) {
                    final String conflictingDetailObjectLabel = getDataDictionaryService().getDataDictionary().getBusinessObjectEntryForConcreteClass(conflictingDetail.getClass().getName()).getObjectLabel();
                    GlobalVariables.getMessageMap().putErrorForSectionId(ArConstants.CostCategoryMaintenanceSections.EDIT_OBJECT_CONSOLIDATIONS, ArKeyConstants.ERROR_DOCUMENT_COST_CATEGORY_OBJECT_CODE_NOT_UNIQUE, new String[]{newObjectConsolidation.getChartOfAccountsCode(), newObjectConsolidation.getFinConsolidationObjectCode(), conflictingDetailObjectLabel, conflictingDetail.getCategoryCode()});
                    success = false;
                }
            }
        }

        return success;
    }

    /**
     * Determines if the object code on a cost category object code actually exists
     *
     * @param objectCode the cost category object code to check
     * @return true if the object code exists, false (and an error message) otherwise
     */
    protected boolean checkObjectCodeExists(CostCategoryObjectCode objectCode) {
        boolean success = true;

        objectCode.refreshReferenceObject(KFSPropertyConstants.OBJECT_CODE_CURRENT);
        if (ObjectUtils.isNull(objectCode.getObjectCodeCurrent())) {
            final String label = getDataDictionaryService().getAttributeLabel(CostCategoryObjectCode.class, KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, RiceKeyConstants.ERROR_EXISTENCE, label);
            success = false;
        }

        return success;
    }

    /**
     * Determines if the object level on a cost category object level actually exists
     *
     * @param objectLevel the cost category object level to check
     * @return true if the object level exists, false (and an error message) otherwise
     */
    protected boolean checkObjectLevelExists(CostCategoryObjectLevel objectLevel) {
        boolean success = true;

        objectLevel.refreshReferenceObject(ArPropertyConstants.OBJECT_LEVEL);
        if (ObjectUtils.isNull(objectLevel.getObjectLevel())) {
            final String label = getDataDictionaryService().getAttributeLabel(CostCategoryObjectLevel.class, KFSPropertyConstants.FINANCIAL_OBJECT_LEVEL_CODE);
            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.FINANCIAL_OBJECT_LEVEL_CODE, RiceKeyConstants.ERROR_EXISTENCE, label);
            success = false;
        }

        return success;
    }

    /**
     * Determines if the object code on a cost category object code actually exists
     *
     * @param objectCode the cost category object code to check
     * @return true if the object code exists, false (and an error message) otherwise
     */
    protected boolean checkObjectConsolidationExists(CostCategoryObjectConsolidation objectConsolidation) {
        boolean success = true;

        objectConsolidation.refreshReferenceObject(ArPropertyConstants.OBJECT_CONSOLIDATION);
        if (ObjectUtils.isNull(objectConsolidation.getObjectConsolidation())) {
            final String label = getDataDictionaryService().getAttributeLabel(CostCategoryObjectConsolidation.class, KFSPropertyConstants.FIN_CONSOLIDATION_OBJECT_CODE);
            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.FIN_CONSOLIDATION_OBJECT_CODE, RiceKeyConstants.ERROR_EXISTENCE, label);
            success = false;
        }

        return success;
    }

    public static CostCategoryService getCostCategoryService() {
        if (costCategoryService == null) {
            costCategoryService = SpringContext.getBean(CostCategoryService.class);
        }
        return costCategoryService;
    }

}
