<commit_before>/*
 * Copyright 2008 The Kuali Foundation.
 * 
 * Licensed under the Educational Community License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.opensource.org/licenses/ecl1.php
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.kfs.module.cab.service.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.ObjectCode;
import org.kuali.kfs.integration.cab.CapitalAssetBuilderAssetTransactionType;
import org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService;
import org.kuali.kfs.integration.cam.CapitalAssetManagementAsset;
import org.kuali.kfs.integration.purap.CapitalAssetLocation;
import org.kuali.kfs.integration.purap.CapitalAssetSystem;
import org.kuali.kfs.integration.purap.ItemCapitalAsset;
import org.kuali.kfs.module.cab.CabConstants;
import org.kuali.kfs.module.cab.CabKeyConstants;
import org.kuali.kfs.module.cab.CabParameterConstants;
import org.kuali.kfs.module.cab.CabPropertyConstants;
import org.kuali.kfs.module.cab.businessobject.AssetTransactionType;
import org.kuali.kfs.module.cam.CamsConstants;
import org.kuali.kfs.module.cam.CamsKeyConstants;
import org.kuali.kfs.module.cam.CamsPropertyConstants;
import org.kuali.kfs.module.cam.businessobject.Asset;
import org.kuali.kfs.module.cam.businessobject.AssetGlobal;
import org.kuali.kfs.module.cam.businessobject.AssetType;
import org.kuali.kfs.module.cam.document.service.AssetService;
import org.kuali.kfs.module.purap.PurapConstants;
import org.kuali.kfs.module.purap.PurapPropertyConstants;
import org.kuali.kfs.module.purap.businessobject.AvailabilityMatrix;
import org.kuali.kfs.module.purap.businessobject.CapitalAssetTransactionTypeRule;
import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
import org.kuali.kfs.module.purap.businessobject.PurApItem;
import org.kuali.kfs.module.purap.businessobject.PurchasingCapitalAssetItem;
import org.kuali.kfs.module.purap.businessobject.PurchasingItemBase;
import org.kuali.kfs.module.purap.businessobject.RecurringPaymentType;
import org.kuali.kfs.module.purap.document.service.PurapService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.Building;
import org.kuali.kfs.sys.businessobject.Room;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.ParameterService;
import org.kuali.kfs.sys.service.impl.ParameterConstants;
import org.kuali.rice.kns.bo.Campus;
import org.kuali.rice.kns.bo.Parameter;
import org.kuali.rice.kns.document.Document;
import org.kuali.rice.kns.service.BusinessObjectService;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.kuali.rice.kns.service.KualiConfigurationService;
import org.kuali.rice.kns.service.LookupService;
import org.kuali.rice.kns.util.GlobalVariables;
import org.kuali.rice.kns.util.KualiDecimal;
import org.kuali.rice.kns.util.ObjectUtils;


public class CapitalAssetBuilderModuleServiceImpl implements CapitalAssetBuilderModuleService {

    private ParameterService parameterService;
    private BusinessObjectService businessObjectService;
    private DataDictionaryService dataDictionaryService;
    private AssetService assetService;
    
    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }
    
    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }
    
    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
        this.dataDictionaryService = dataDictionaryService;
    }
    
    public void setAssetService(AssetService assetService) {
        this.assetService = assetService;
    }
    
    public boolean validateIndividualCapitalAssetSystemFromPurchasing(String systemState, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType) {
        //For Individual Asset system type, the List of CapitalAssetSystems in the input parameter for validateAllFieldRequirementsByChart
        //should be null. So we'll pass in a null here.
        boolean valid = validateAllFieldRequirementsByChart(systemState, null, capitalAssetItems, chartCode, documentType, PurapConstants.CapitalAssetSystemTypes.INDIVIDUAL);
        valid &= validateQuantityOnLocationsEqualsQuantityOnItem(capitalAssetItems);
        //TODO : add all the other cams validations according to the specs in here whenever applicable, or, according to the jira : potential validation '
        //against CAMS data (for example, asset # exist in CAMS) 
        valid &= validateIndividualSystemPurchasingTransactionTypesAllowingAssetNumbers(capitalAssetItems);
        return valid;
    }
    
    public boolean validateOneSystemCapitalAssetSystemFromPurchasing(String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType) {
        boolean valid = validateAllFieldRequirementsByChart(systemState, capitalAssetSystems, capitalAssetItems, chartCode, documentType, PurapConstants.CapitalAssetSystemTypes.ONE_SYSTEM);
        //TODO : add all the other cams validations according to the specs in here whenever applicable, or, according to the jira : potential validation '
        //against CAMS data (for example, asset # exist in CAMS) 
        String capitalAssetTransactionType = capitalAssetItems.get(0).getCapitalAssetTransactionTypeCode();
        valid &= validatePurchasingTransactionTypesAllowingAssetNumbers(capitalAssetSystems.get(0), capitalAssetTransactionType);
        return valid;
    }

    public boolean validateMultipleSystemsCapitalAssetSystemFromPurchasing(String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType) {
        boolean valid = validateAllFieldRequirementsByChart(systemState, capitalAssetSystems, capitalAssetItems, chartCode, documentType, PurapConstants.CapitalAssetSystemTypes.MULTIPLE);
        //TODO : add all the other cams validations according to the specs in here whenever applicable, or, according to the jira : potential validation '
        //against CAMS data (for example, asset # exist in CAMS) 
        return valid;
    }
    
    /**
     * @see org.kuali.kfs.integration.service.CapitalAssetBuilderModuleService#doesDocumentExceedThreshold(org.kuali.rice.kns.util.KualiDecimal)
     */
    public boolean doesDocumentExceedThreshold(KualiDecimal docTotal) {
        String parameterThreshold = parameterService.getParameterValue(AssetGlobal.class, CamsConstants.Parameters.CAPITALIZATION_LIMIT_AMOUNT);
        if (docTotal.compareTo(new KualiDecimal(parameterThreshold)) > 0) {
            return true;
        }
        return false;
    }

    /**
     * @see org.kuali.kfs.integration.service.CapitalAssetBuilderModuleService#validateAccounts(java.util.List, java.lang.String)
     */
    public boolean validateAccounts(List<SourceAccountingLine> accountingLines, String transactionType) {
        // TODO Auto-generated method stub
        return false;
    }

    private boolean validateAllFieldRequirementsByChart(String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType, String systemType) {
        boolean valid = true;
        Map<String, String> fieldValues = new HashMap<String, String>();
        fieldValues.put(CabPropertyConstants.Parameter.PARAMETER_NAMESPACE_CODE, "KFS-CAB");
        fieldValues.put(CabPropertyConstants.Parameter.PARAMETER_DETAIL_TYPE_CODE, "Document");
        String name = "CHARTS_REQUIRING%" + documentType;
        fieldValues.put(CabPropertyConstants.Parameter.PARAMETER_NAME, name);
        //TODO: KULPURAP-2837, Find a more permanent home for the codes for handling "LIKE" criterias. This works fine for now
        //to serve its purpose, although this is probably not the permanent location (ask Chris).
        List<Parameter> results = SpringContext.getBean(PurapService.class).getParametersGivenLikeCriteria(fieldValues);

        for (Parameter parameter : results) {
            if (systemType.equals(PurapConstants.CapitalAssetSystemTypes.INDIVIDUAL)) {
                valid &= validateFieldRequirementByChartForIndividualSystemType(systemState, capitalAssetItems, chartCode, parameter.getParameterName(), parameter.getParameterValue());
            }
            else {
                valid &= validateFieldRequirementByChartForOneOrMultipleSystemType(systemType, systemState, capitalAssetSystems, capitalAssetItems, chartCode, parameter.getParameterName(), parameter.getParameterValue());
            }
        }
        return valid;
    }

    private boolean validateFieldRequirementByChartForOneOrMultipleSystemType(String systemType, String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String parameterName, String parameterValueString) {
        boolean valid = true;        
        boolean needValidation = (parameterService.getParameterEvaluator(ParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, parameterName, chartCode).evaluationSucceeds());
        
        if (needValidation) {
            String mappedName = PurapConstants.CAMS_REQUIREDNESS_FIELDS.REQUIREDNESS_FIELDS_BY_PARAMETER_NAMES.get(parameterName);
            
            if (mappedName != null) {
                //Check the availability matrix here, if this field doesn't exist according to the avail. matrix, then no need
                //to validate any further.
                String availableValue = getValueFromAvailabilityMatrix(mappedName, systemType, systemState);
                if (availableValue.equals(PurapConstants.CapitalAssetAvailability.NONE)) {
                    return true;
                }
                
                //capitalAssetTransactionType field is off the item
                if (mappedName.equals("capitalAssetTransactionType")) {
                    String[] mappedNames = {"purchasingCapitalAssetItems", mappedName};
                    for (PurchasingCapitalAssetItem item : capitalAssetItems) {
                        valid &= validateFieldRequirementByChartHelper (item, ArrayUtils.subarray(mappedNames, 1, mappedNames.length));
                    }
                }
                //all the other fields are off the system.
                else {
                    List<String> mappedNamesList = new ArrayList<String>();
                    mappedNamesList.add("purchasingCapitalAssetSystems");
                    if (mappedName.indexOf(".") < 0) {
                        mappedNamesList.add(mappedName);     
                    }
                    else {
                        mappedNamesList.addAll(mappedNameSplitter(mappedName));
                    }
                    //For One system type, we would only have 1 CapitalAssetSystem, however, for multiple we may have more than
                    //one systems (ask Chris). Either way, this for loop should allow both the one system and multiple system
                    //types to work fine.
                    for (CapitalAssetSystem system : capitalAssetSystems) {
                        valid &= validateFieldRequirementByChartHelper (system, ArrayUtils.subarray(mappedNamesList.toArray(), 1, mappedNamesList.size()));
                    }
                }
            }
        }
        return valid;
    }
    
    private boolean validateFieldRequirementByChartForIndividualSystemType(String systemState, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String parameterName, String parameterValueString) {
        boolean valid = true;        
        boolean needValidation = (parameterService.getParameterEvaluator(ParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, parameterName, chartCode).evaluationSucceeds());
        
        if (needValidation) {
            String mappedName = PurapConstants.CAMS_REQUIREDNESS_FIELDS.REQUIREDNESS_FIELDS_BY_PARAMETER_NAMES.get(parameterName);
            if (mappedName != null) {
                //Check the availability matrix here, if this field doesn't exist according to the avail. matrix, then no need
                //to validate any further.
                String availableValue = getValueFromAvailabilityMatrix(mappedName, PurapConstants.CapitalAssetSystemTypes.INDIVIDUAL, systemState);
                if (availableValue.equals(PurapConstants.CapitalAssetAvailability.NONE)) {
                    return true;
                }
                //capitalAssetTransactionType field is off the item
                List<String> mappedNamesList = new ArrayList<String>();
                
                if (mappedName.equals("capitalAssetTransactionType")) {
                    mappedNamesList.add("purchasingCapitalAssetItems");
                    mappedNamesList.add(mappedName);
     
                }
                //all the other fields are off the system which is off the item
                else {
                    mappedNamesList.add("purchasingCapitalAssetItems");
                    mappedNamesList.add("purchasingCapitalAssetSystem");
                    if (mappedName.indexOf(".") < 0) {
                        mappedNamesList.add(mappedName);     
                    }
                    else {
                        mappedNamesList.addAll(mappedNameSplitter(mappedName));
                    }
                }
                //For Individual system type, we'll always iterate through the item, then if the field is off the system, we'll get it through
                //the purchasingCapitalAssetSystem of the item.
                for (PurchasingCapitalAssetItem item : capitalAssetItems) {
                    valid &= validateFieldRequirementByChartHelper (item, ArrayUtils.subarray(mappedNamesList.toArray(), 1, mappedNamesList.size()));
                }
            }
        }
        return valid;
    }
    
    private List<String> mappedNameSplitter(String mappedName) {
        List<String> result = new ArrayList<String>();    
        String[] mappedNamesArray = mappedName.split("\\.");
        for (int i=0; i<mappedNamesArray.length; i++) {
            result.add(mappedNamesArray[i]);
        }
        return result;
    }
    
    private boolean validateFieldRequirementByChartHelper(Object bean, Object[] mappedNames) {
        boolean valid = true;
        Object value = ObjectUtils.getPropertyValue(bean, (String)mappedNames[0]);
        if (ObjectUtils.isNull(value)) {
            return false;
        }
        else if (value instanceof Collection) {
            if (((Collection)value).isEmpty()) {
                //if this collection doesn't contain anything, when it's supposed to contain some strings with values, return false.
                return false;
            }
            for (Iterator iter = ((Collection)value).iterator(); iter.hasNext();) {
                valid &= validateFieldRequirementByChartHelper(iter.next(), ArrayUtils.subarray(mappedNames, 1, mappedNames.length));
            }
            return valid;
        }
        else if (StringUtils.isBlank(value.toString())) {
            return false;
        }
        else if (mappedNames.length > 1) {
            //this means we have not reached the end of a single field to be traversed yet, so continue with the recursion.
            valid &= validateFieldRequirementByChartHelper(value, ArrayUtils.subarray(mappedNames, 1, mappedNames.length));
            return valid;
        }
        else {
            return true;
        }
    }
    
    public List<CapitalAssetBuilderAssetTransactionType> getAllAssetTransactionTypes() {
        List<CapitalAssetBuilderAssetTransactionType> tranTypes = new ArrayList<CapitalAssetBuilderAssetTransactionType>();
        tranTypes = (List<CapitalAssetBuilderAssetTransactionType>)businessObjectService.findAll(AssetTransactionType.class);
        return tranTypes;
    }
    
    public String getValueFromAvailabilityMatrix(String fieldName, String systemType, String systemState) {
        for (AvailabilityMatrix am : PurapConstants.CAMS_AVAILABILITY_MATRIX.MATRIX_LIST) {   
            if (am.fieldName.equals(fieldName) && am.systemState.equals(systemState) && am.systemType.equals(systemType)) {
                return am.availableValue;        
            }
        }
        //if we can't find any matching from availability matrix, return null for now.
        return null;
    }

    /**
     * Validates that the total quantity on all locations equals to the quantity on the line item.
     * This is only used for IND system type.
     * 
     * @param capitalAssetItems
     * @return true if the total quantity on all locations equals to the quantity on the line item.
     */
    private boolean validateQuantityOnLocationsEqualsQuantityOnItem(List<PurchasingCapitalAssetItem> capitalAssetItems) {
        boolean valid = true;
        for (PurchasingCapitalAssetItem item : capitalAssetItems) {
            KualiDecimal total = new KualiDecimal(0);
            for (CapitalAssetLocation location : item.getPurchasingCapitalAssetSystem().getCapitalAssetLocations()) {
                total = total.add(location.getItemQuantity());
            }
            if (!item.getPurchasingItem().getItemQuantity().equals(total)) {
                valid = false;
            }
        }
        
        return valid;
    }
    
    /**
     * Validates for the individual system type that for each of the items, the capitalAssetTransactionTypeCode 
     * matches the system parameter PURCHASING_ASSET_TRANSACTION_TYPES_ALLOWING_ASSET_NUMBERS, the
     * method will return true but if it doesn't match the system parameter, then loop through each of the itemCapitalAssets.
     * If there is any non-null capitalAssetNumber then return false.
     * 
     * @param capitalAssetItems the List of PurchasingCapitalAssetItems on the document to be validated
     * @return false if the capital asset transaction type does not match the system parameter that allows asset numbers
     *         but the itemCapitalAsset contains at least one asset numbers.
     */
    private boolean validateIndividualSystemPurchasingTransactionTypesAllowingAssetNumbers(List<PurchasingCapitalAssetItem> capitalAssetItems) {
        boolean valid = true;
        for (PurchasingCapitalAssetItem capitalAssetItem : capitalAssetItems) {
            valid &= validatePurchasingTransactionTypesAllowingAssetNumbers(capitalAssetItem.getPurchasingCapitalAssetSystem(), capitalAssetItem.getCapitalAssetTransactionTypeCode());
        }
        return valid;
    }

    /**
     * Generic validation that if the capitalAssetTransactionTypeCode does not match the system parameter
     * PURCHASING_ASSET_TRANSACTION_TYPES_ALLOWING_ASSET_NUMBERS and at least one of the itemCapitalAssets
     * contain a non-null capitalAssetNumber then return false.
     * This method is used by one system and multiple system types as well as being used as a helper method
     * for validateIndividualSystemPurchasingTransactionTypesAllowingAssetNumbers method.
     * 
     * @param capitalAssetSystem the capitalAssetSystem containing a List of itemCapitalAssets to be validated.
     * @param capitalAssetTransactionType the capitalAssetTransactionTypeCode containing asset numbers to be validated.
     * @return false if the capital asset transaction type does not match the system parameter that allows asset numbers
     *         but the itemCapitalAsset contains at least one asset numbers. 
     */
    private boolean validatePurchasingTransactionTypesAllowingAssetNumbers(CapitalAssetSystem capitalAssetSystem, String capitalAssetTransactionType) {
        String parameterName = CabParameterConstants.CapitalAsset.PURCHASING_ASSET_TRANSACTION_TYPES_ALLOWING_ASSET_NUMBERS;
        boolean allowedAssetNumbers = (parameterService.getParameterEvaluator(ParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, parameterName, capitalAssetTransactionType).evaluationSucceeds());
        if (allowedAssetNumbers) {
            //If this is a transaction type that allows asset numbers, we don't need to validate anymore, just return true here.
            return true;
        }
        else {
            for (ItemCapitalAsset asset : capitalAssetSystem.getItemCapitalAssets()) {
                if (asset.getCapitalAssetNumber() != null) {
                    return false;
                }
            }
        }
        return true;
    }
    
    //-------- KULPURAP 2795 methods start here.

    /**
     * Wrapper to do Capital Asset validations, generating errors instead of warnings. Makes sure that 
     * the given item's data relevant to its later possible classification as a Capital Asset is internally consistent, 
     * by marshaling and calling the methods marked as Capital Asset validations. This implementation assumes that 
     * all object codes are valid (real) object codes.
     * 
     * @param item                      A PurchasingItemBase object
     * @param recurringPaymentType      The item's document's RecurringPaymentType
     * @param itemIdentifier            The item number (String)
     * @param apoCheck                  True if this check is for APO purposes
     * @return True if the item passes all Capital Asset validations
     */
    public boolean validateItemCapitalAssetWithErrors(RecurringPaymentType recurringPaymentType, PurApItem item, boolean apoCheck) {
        PurchasingItemBase purchasingItem = (PurchasingItemBase)item;
        return validateItemCapitalAsset(recurringPaymentType, purchasingItem, false);
    }<commit_msg>KULPURAP-2854 and some of the changes for KULPURAP-2768 and KULPURAP-2769<commit_after>/*
 * Copyright 2008 The Kuali Foundation.
 * 
 * Licensed under the Educational Community License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.opensource.org/licenses/ecl1.php
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.kfs.module.cab.service.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.ObjectCode;
import org.kuali.kfs.integration.cab.CapitalAssetBuilderAssetTransactionType;
import org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService;
import org.kuali.kfs.integration.cam.CapitalAssetManagementAsset;
import org.kuali.kfs.integration.purap.CapitalAssetLocation;
import org.kuali.kfs.integration.purap.CapitalAssetSystem;
import org.kuali.kfs.integration.purap.ItemCapitalAsset;
import org.kuali.kfs.module.cab.CabConstants;
import org.kuali.kfs.module.cab.CabKeyConstants;
import org.kuali.kfs.module.cab.CabParameterConstants;
import org.kuali.kfs.module.cab.CabPropertyConstants;
import org.kuali.kfs.module.cab.businessobject.AssetTransactionType;
import org.kuali.kfs.module.cam.CamsConstants;
import org.kuali.kfs.module.cam.CamsKeyConstants;
import org.kuali.kfs.module.cam.CamsPropertyConstants;
import org.kuali.kfs.module.cam.businessobject.Asset;
import org.kuali.kfs.module.cam.businessobject.AssetGlobal;
import org.kuali.kfs.module.cam.businessobject.AssetType;
import org.kuali.kfs.module.cam.document.service.AssetService;
import org.kuali.kfs.module.purap.PurapConstants;
import org.kuali.kfs.module.purap.PurapPropertyConstants;
import org.kuali.kfs.module.purap.businessobject.AvailabilityMatrix;
import org.kuali.kfs.module.purap.businessobject.CapitalAssetTransactionTypeRule;
import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
import org.kuali.kfs.module.purap.businessobject.PurApItem;
import org.kuali.kfs.module.purap.businessobject.PurchasingCapitalAssetItem;
import org.kuali.kfs.module.purap.businessobject.PurchasingItemBase;
import org.kuali.kfs.module.purap.businessobject.RecurringPaymentType;
import org.kuali.kfs.module.purap.document.service.PurapService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.Building;
import org.kuali.kfs.sys.businessobject.Room;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.ParameterService;
import org.kuali.kfs.sys.service.impl.ParameterConstants;
import org.kuali.rice.kns.bo.Campus;
import org.kuali.rice.kns.bo.Parameter;
import org.kuali.rice.kns.document.Document;
import org.kuali.rice.kns.service.BusinessObjectService;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.kuali.rice.kns.service.KualiConfigurationService;
import org.kuali.rice.kns.service.LookupService;
import org.kuali.rice.kns.util.GlobalVariables;
import org.kuali.rice.kns.util.KualiDecimal;
import org.kuali.rice.kns.util.ObjectUtils;


public class CapitalAssetBuilderModuleServiceImpl implements CapitalAssetBuilderModuleService {

    private ParameterService parameterService;
    private BusinessObjectService businessObjectService;
    private DataDictionaryService dataDictionaryService;
    private AssetService assetService;
    
    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }
    
    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }
    
    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
        this.dataDictionaryService = dataDictionaryService;
    }
    
    public void setAssetService(AssetService assetService) {
        this.assetService = assetService;
    }
    
    public boolean validateIndividualCapitalAssetSystemFromPurchasing(String systemState, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType) {
        //For Individual Asset system type, the List of CapitalAssetSystems in the input parameter for validateAllFieldRequirementsByChart
        //should be null. So we'll pass in a null here.
        boolean valid = validateAllFieldRequirementsByChart(systemState, null, capitalAssetItems, chartCode, documentType, PurapConstants.CapitalAssetSystemTypes.INDIVIDUAL);
        valid &= validateQuantityOnLocationsEqualsQuantityOnItem(capitalAssetItems);
        //TODO : add all the other cams validations according to the specs in here whenever applicable, or, according to the jira : potential validation '
        //against CAMS data (for example, asset # exist in CAMS) 
        valid &= validateIndividualSystemPurchasingTransactionTypesAllowingAssetNumbers(capitalAssetItems);
        valid &= validateNonQuantityDrivenAllowedIndicator(capitalAssetItems);
        return valid;
    }
    
    public boolean validateOneSystemCapitalAssetSystemFromPurchasing(String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType) {
        boolean valid = validateAllFieldRequirementsByChart(systemState, capitalAssetSystems, capitalAssetItems, chartCode, documentType, PurapConstants.CapitalAssetSystemTypes.ONE_SYSTEM);
        //TODO : add all the other cams validations according to the specs in here whenever applicable, or, according to the jira : potential validation '
        //against CAMS data (for example, asset # exist in CAMS) 
        String capitalAssetTransactionType = capitalAssetItems.get(0).getCapitalAssetTransactionTypeCode();
        valid &= validatePurchasingTransactionTypesAllowingAssetNumbers(capitalAssetSystems.get(0), capitalAssetTransactionType);
        valid &= validateNonQuantityDrivenAllowedIndicator(capitalAssetItems);
        return valid;
    }

    public boolean validateMultipleSystemsCapitalAssetSystemFromPurchasing(String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType) {
        boolean valid = validateAllFieldRequirementsByChart(systemState, capitalAssetSystems, capitalAssetItems, chartCode, documentType, PurapConstants.CapitalAssetSystemTypes.MULTIPLE);
        //TODO : add all the other cams validations according to the specs in here whenever applicable, or, according to the jira : potential validation '
        //against CAMS data (for example, asset # exist in CAMS) 
        return valid;
    }
    
    /**
     * @see org.kuali.kfs.integration.service.CapitalAssetBuilderModuleService#doesDocumentExceedThreshold(org.kuali.rice.kns.util.KualiDecimal)
     */
    public boolean doesDocumentExceedThreshold(KualiDecimal docTotal) {
        String parameterThreshold = parameterService.getParameterValue(AssetGlobal.class, CamsConstants.Parameters.CAPITALIZATION_LIMIT_AMOUNT);
        if (docTotal.compareTo(new KualiDecimal(parameterThreshold)) > 0) {
            return true;
        }
        return false;
    }

    /**
     * @see org.kuali.kfs.integration.service.CapitalAssetBuilderModuleService#validateAccounts(java.util.List, java.lang.String)
     */
    public boolean validateAccounts(List<SourceAccountingLine> accountingLines, String transactionType) {
        // TODO Auto-generated method stub
        return false;
    }

    private boolean validateAllFieldRequirementsByChart(String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType, String systemType) {
        boolean valid = true;
        Map<String, String> fieldValues = new HashMap<String, String>();
        fieldValues.put(CabPropertyConstants.Parameter.PARAMETER_NAMESPACE_CODE, "KFS-CAB");
        fieldValues.put(CabPropertyConstants.Parameter.PARAMETER_DETAIL_TYPE_CODE, "Document");
        String name = "CHARTS_REQUIRING%" + documentType;
        fieldValues.put(CabPropertyConstants.Parameter.PARAMETER_NAME, name);
        //TODO: KULPURAP-2837, Find a more permanent home for the codes for handling "LIKE" criterias. This works fine for now
        //to serve its purpose, although this is probably not the permanent location (ask Chris).
        List<Parameter> results = SpringContext.getBean(PurapService.class).getParametersGivenLikeCriteria(fieldValues);

        for (Parameter parameter : results) {
            if (systemType.equals(PurapConstants.CapitalAssetSystemTypes.INDIVIDUAL)) {
                valid &= validateFieldRequirementByChartForIndividualSystemType(systemState, capitalAssetItems, chartCode, parameter.getParameterName(), parameter.getParameterValue());
            }
            else {
                valid &= validateFieldRequirementByChartForOneOrMultipleSystemType(systemType, systemState, capitalAssetSystems, capitalAssetItems, chartCode, parameter.getParameterName(), parameter.getParameterValue());
            }
        }
        return valid;
    }

    private boolean validateFieldRequirementByChartForOneOrMultipleSystemType(String systemType, String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String parameterName, String parameterValueString) {
        boolean valid = true;        
        boolean needValidation = (parameterService.getParameterEvaluator(ParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, parameterName, chartCode).evaluationSucceeds());
        
        if (needValidation) {
            String mappedName = PurapConstants.CAMS_REQUIREDNESS_FIELDS.REQUIREDNESS_FIELDS_BY_PARAMETER_NAMES.get(parameterName);
            
            if (mappedName != null) {
                //Check the availability matrix here, if this field doesn't exist according to the avail. matrix, then no need
                //to validate any further.
                String availableValue = getValueFromAvailabilityMatrix(mappedName, systemType, systemState);
                if (availableValue.equals(PurapConstants.CapitalAssetAvailability.NONE)) {
                    return true;
                }
                
                //capitalAssetTransactionType field is off the item
                if (mappedName.equals("capitalAssetTransactionType")) {
                    String[] mappedNames = {"purchasingCapitalAssetItems", mappedName};
                    for (PurchasingCapitalAssetItem item : capitalAssetItems) {
                        valid &= validateFieldRequirementByChartHelper (item, ArrayUtils.subarray(mappedNames, 1, mappedNames.length));
                    }
                }
                //all the other fields are off the system.
                else {
                    List<String> mappedNamesList = new ArrayList<String>();
                    mappedNamesList.add("purchasingCapitalAssetSystems");
                    if (mappedName.indexOf(".") < 0) {
                        mappedNamesList.add(mappedName);     
                    }
                    else {
                        mappedNamesList.addAll(mappedNameSplitter(mappedName));
                    }
                    //For One system type, we would only have 1 CapitalAssetSystem, however, for multiple we may have more than
                    //one systems (ask Chris). Either way, this for loop should allow both the one system and multiple system
                    //types to work fine.
                    for (CapitalAssetSystem system : capitalAssetSystems) {
                        valid &= validateFieldRequirementByChartHelper (system, ArrayUtils.subarray(mappedNamesList.toArray(), 1, mappedNamesList.size()));
                    }
                }
            }
        }
        return valid;
    }
    
    private boolean validateFieldRequirementByChartForIndividualSystemType(String systemState, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String parameterName, String parameterValueString) {
        boolean valid = true;        
        boolean needValidation = (parameterService.getParameterEvaluator(ParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, parameterName, chartCode).evaluationSucceeds());
        
        if (needValidation) {
            String mappedName = PurapConstants.CAMS_REQUIREDNESS_FIELDS.REQUIREDNESS_FIELDS_BY_PARAMETER_NAMES.get(parameterName);
            if (mappedName != null) {
                //Check the availability matrix here, if this field doesn't exist according to the avail. matrix, then no need
                //to validate any further.
                String availableValue = getValueFromAvailabilityMatrix(mappedName, PurapConstants.CapitalAssetSystemTypes.INDIVIDUAL, systemState);
                if (availableValue.equals(PurapConstants.CapitalAssetAvailability.NONE)) {
                    return true;
                }
                //capitalAssetTransactionType field is off the item
                List<String> mappedNamesList = new ArrayList<String>();
                
                if (mappedName.equals("capitalAssetTransactionType")) {
                    mappedNamesList.add("purchasingCapitalAssetItems");
                    mappedNamesList.add(mappedName);
     
                }
                //all the other fields are off the system which is off the item
                else {
                    mappedNamesList.add("purchasingCapitalAssetItems");
                    mappedNamesList.add("purchasingCapitalAssetSystem");
                    if (mappedName.indexOf(".") < 0) {
                        mappedNamesList.add(mappedName);     
                    }
                    else {
                        mappedNamesList.addAll(mappedNameSplitter(mappedName));
                    }
                }
                //For Individual system type, we'll always iterate through the item, then if the field is off the system, we'll get it through
                //the purchasingCapitalAssetSystem of the item.
                for (PurchasingCapitalAssetItem item : capitalAssetItems) {
                    valid &= validateFieldRequirementByChartHelper (item, ArrayUtils.subarray(mappedNamesList.toArray(), 1, mappedNamesList.size()));
                }
            }
        }
        return valid;
    }
    
    private List<String> mappedNameSplitter(String mappedName) {
        List<String> result = new ArrayList<String>();    
        String[] mappedNamesArray = mappedName.split("\\.");
        for (int i=0; i<mappedNamesArray.length; i++) {
            result.add(mappedNamesArray[i]);
        }
        return result;
    }
    
    private boolean validateFieldRequirementByChartHelper(Object bean, Object[] mappedNames) {
        boolean valid = true;
        Object value = ObjectUtils.getPropertyValue(bean, (String)mappedNames[0]);
        if (ObjectUtils.isNull(value)) {
            return false;
        }
        else if (value instanceof Collection) {
            if (((Collection)value).isEmpty()) {
                //if this collection doesn't contain anything, when it's supposed to contain some strings with values, return false.
                return false;
            }
            for (Iterator iter = ((Collection)value).iterator(); iter.hasNext();) {
                valid &= validateFieldRequirementByChartHelper(iter.next(), ArrayUtils.subarray(mappedNames, 1, mappedNames.length));
            }
            return valid;
        }
        else if (StringUtils.isBlank(value.toString())) {
            return false;
        }
        else if (mappedNames.length > 1) {
            //this means we have not reached the end of a single field to be traversed yet, so continue with the recursion.
            valid &= validateFieldRequirementByChartHelper(value, ArrayUtils.subarray(mappedNames, 1, mappedNames.length));
            return valid;
        }
        else {
            return true;
        }
    }
    
    public List<CapitalAssetBuilderAssetTransactionType> getAllAssetTransactionTypes() {
        List<CapitalAssetBuilderAssetTransactionType> tranTypes = new ArrayList<CapitalAssetBuilderAssetTransactionType>();
        tranTypes = (List<CapitalAssetBuilderAssetTransactionType>)businessObjectService.findAll(AssetTransactionType.class);
        return tranTypes;
    }
    
    public String getValueFromAvailabilityMatrix(String fieldName, String systemType, String systemState) {
        for (AvailabilityMatrix am : PurapConstants.CAMS_AVAILABILITY_MATRIX.MATRIX_LIST) {   
            if (am.fieldName.equals(fieldName) && am.systemState.equals(systemState) && am.systemType.equals(systemType)) {
                return am.availableValue;        
            }
        }
        //if we can't find any matching from availability matrix, return null for now.
        return null;
    }

    /**
     * Validates that the total quantity on all locations equals to the quantity on the line item.
     * This is only used for IND system type.
     * 
     * @param capitalAssetItems
     * @return true if the total quantity on all locations equals to the quantity on the line item.
     */
    private boolean validateQuantityOnLocationsEqualsQuantityOnItem(List<PurchasingCapitalAssetItem> capitalAssetItems) {
        boolean valid = true;
        for (PurchasingCapitalAssetItem item : capitalAssetItems) {
            KualiDecimal total = new KualiDecimal(0);
            for (CapitalAssetLocation location : item.getPurchasingCapitalAssetSystem().getCapitalAssetLocations()) {
                total = total.add(location.getItemQuantity());
            }
            if (!item.getPurchasingItem().getItemQuantity().equals(total)) {
                valid = false;
            }
        }
        
        return valid;
    }
    
    /**
     * Validates for the individual system type that for each of the items, the capitalAssetTransactionTypeCode 
     * matches the system parameter PURCHASING_ASSET_TRANSACTION_TYPES_ALLOWING_ASSET_NUMBERS, the
     * method will return true but if it doesn't match the system parameter, then loop through each of the itemCapitalAssets.
     * If there is any non-null capitalAssetNumber then return false.
     * 
     * @param capitalAssetItems the List of PurchasingCapitalAssetItems on the document to be validated
     * @return false if the capital asset transaction type does not match the system parameter that allows asset numbers
     *         but the itemCapitalAsset contains at least one asset numbers.
     */
    private boolean validateIndividualSystemPurchasingTransactionTypesAllowingAssetNumbers(List<PurchasingCapitalAssetItem> capitalAssetItems) {
        boolean valid = true;
        for (PurchasingCapitalAssetItem capitalAssetItem : capitalAssetItems) {
            valid &= validatePurchasingTransactionTypesAllowingAssetNumbers(capitalAssetItem.getPurchasingCapitalAssetSystem(), capitalAssetItem.getCapitalAssetTransactionTypeCode());
        }
        return valid;
    }

    /**
     * Generic validation that if the capitalAssetTransactionTypeCode does not match the system parameter
     * PURCHASING_ASSET_TRANSACTION_TYPES_ALLOWING_ASSET_NUMBERS and at least one of the itemCapitalAssets
     * contain a non-null capitalAssetNumber then return false.
     * This method is used by one system and multiple system types as well as being used as a helper method
     * for validateIndividualSystemPurchasingTransactionTypesAllowingAssetNumbers method.
     * 
     * @param capitalAssetSystem the capitalAssetSystem containing a List of itemCapitalAssets to be validated.
     * @param capitalAssetTransactionType the capitalAssetTransactionTypeCode containing asset numbers to be validated.
     * @return false if the capital asset transaction type does not match the system parameter that allows asset numbers
     *         but the itemCapitalAsset contains at least one asset numbers. 
     */
    private boolean validatePurchasingTransactionTypesAllowingAssetNumbers(CapitalAssetSystem capitalAssetSystem, String capitalAssetTransactionType) {
        String parameterName = CabParameterConstants.CapitalAsset.PURCHASING_ASSET_TRANSACTION_TYPES_ALLOWING_ASSET_NUMBERS;
        boolean allowedAssetNumbers = (parameterService.getParameterEvaluator(ParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, parameterName, capitalAssetTransactionType).evaluationSucceeds());
        if (allowedAssetNumbers) {
            //If this is a transaction type that allows asset numbers, we don't need to validate anymore, just return true here.
            return true;
        }
        else {
            for (ItemCapitalAsset asset : capitalAssetSystem.getItemCapitalAssets()) {
                if (asset.getCapitalAssetNumber() != null) {
                    return false;
                }
            }
        }
        return true;
    }
    
    /**
     * Validates that if the non quantity drive allowed indicator on the
     * capital asset transaction type is false and the quantity on any of the
     * line items is either null or 0 then return false.
     * 
     * @param capitalAssetItems The List of PurchasingCapitalAssetItem to be validated.
     * @return false if the indicator is false and there is at least one non quantity 
     *         items on the list.
     */
    private boolean validateNonQuantityDrivenAllowedIndicator(List<PurchasingCapitalAssetItem> capitalAssetItems) {
        boolean valid = true;        
        for (PurchasingCapitalAssetItem capitalAssetItem : capitalAssetItems) {
            if (!capitalAssetItem.getCapitalAssetTransactionType().getCapitalAssetNonquantityDrivenAllowIndicator()) {
                if (capitalAssetItem.getPurchasingItem().getItemQuantity() == null || capitalAssetItem.getPurchasingItem().getItemQuantity().equals(new KualiDecimal(0))) {
                    valid &= false;
                }
            }
        }
        return valid;
    }
    
    //-------- KULPURAP 2795 methods start here.

    /**
     * Wrapper to do Capital Asset validations, generating errors instead of warnings. Makes sure that 
     * the given item's data relevant to its later possible classification as a Capital Asset is internally consistent, 
     * by marshaling and calling the methods marked as Capital Asset validations. This implementation assumes that 
     * all object codes are valid (real) object codes.
     * 
     * @param item                      A PurchasingItemBase object
     * @param recurringPaymentType      The item's document's RecurringPaymentType
     * @param itemIdentifier            The item number (String)
     * @param apoCheck                  True if this check is for APO purposes
     * @return True if the item passes all Capital Asset validations
     */
    public boolean validateItemCapitalAssetWithErrors(RecurringPaymentType recurringPaymentType, PurApItem item, boolean apoCheck) {
        PurchasingItemBase purchasingItem = (PurchasingItemBase)item;
        return validateItemCapitalAsset(recurringPaymentType, purchasingItem, false);
    }
{'fraction_non_alphanumeric': 0.0710518952194408, 'fraction_numerical': 0.0015928022557523838, 'mean_word_length': 3.19011544011544, 'pattern_counts': {'":': 0, '<': 64, '<?xml version=': 0, '>': 62, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5281667', 'n_tokens_mistral': 12165, 'n_tokens_neox': 11743, 'n_words': 2979}