/*
 * 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.cam.document.service.impl;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.ObjectCode;
import org.kuali.kfs.coa.service.ObjectCodeService;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.kns.document.MaintenanceDocument;
import org.kuali.kfs.krad.document.Document;
import org.kuali.kfs.krad.exception.ValidationException;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.cam.CamsConstants;
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.AssetGlobalDetail;
import org.kuali.kfs.module.cam.businessobject.AssetLocation;
import org.kuali.kfs.module.cam.businessobject.AssetPayment;
import org.kuali.kfs.module.cam.businessobject.AssetType;
import org.kuali.kfs.module.cam.document.service.AssetService;
import org.kuali.kfs.module.cam.document.service.PaymentSummaryService;
import org.kuali.kfs.sys.KFSConstants.DocumentStatusCodes;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.UniversityDate;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.UniversityDateService;
import org.kuali.rice.kew.api.WorkflowDocument;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class AssetServiceImpl implements AssetService {
    protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AssetServiceImpl.class);

    private ParameterService parameterService;
    private PaymentSummaryService paymentSummaryService;
    private BusinessObjectService businessObjectService;

    public ParameterService getParameterService() {
        return parameterService;
    }

    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }

    public boolean isAssetMovableCheckByAsset(Asset asset) {
        asset.refreshReferenceObject(CamsPropertyConstants.Asset.CAPITAL_ASSET_TYPE);
        return asset.getCapitalAssetType().isMovingIndicator();
    }


    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isAssetDepreciationStarted(org.kuali.kfs.module.cam.businessobject.Asset)
     */
    public boolean isAssetDepreciationStarted(Asset asset) {
        // check non-persistent field accumulatedDepreciation first since it's the sum of
        // assetPayment.accumulatedPrimaryDepreciationAmount. If it's not set yet, we'll check assetPayment one by one.
        if (ObjectUtils.isNotNull(asset.getAccumulatedDepreciation()) && asset.getAccumulatedDepreciation().isPositive()) {
            return true;
        } else {
            for (AssetPayment assetPayment : asset.getAssetPayments()) {
                if (ObjectUtils.isNotNull(assetPayment.getAccumulatedPrimaryDepreciationAmount()) && assetPayment.getAccumulatedPrimaryDepreciationAmount().isPositive()) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean isCapitalAsset(Asset asset) {
        return parameterService.getParameterValuesAsString(Asset.class, CamsConstants.Parameters.CAPITAL_ASSET_STATUS_CODES).contains(asset.getInventoryStatusCode());
    }

    public boolean isAssetRetired(Asset asset) {
        return parameterService.getParameterValuesAsString(Asset.class, CamsConstants.Parameters.RETIRED_STATUS_CODES).contains(asset.getInventoryStatusCode());
    }

    public boolean isInServiceDateChanged(Asset oldAsset, Asset newAsset) {
        return !(ObjectUtils.isNull(oldAsset.getCapitalAssetInServiceDate()) ? ObjectUtils.isNull(newAsset.getCapitalAssetInServiceDate()) : oldAsset.getCapitalAssetInServiceDate().equals(newAsset.getCapitalAssetInServiceDate()));
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isAssetFabrication(org.kuali.rice.kns.document.MaintenanceDocument)
     */
    public boolean isAssetFabrication(MaintenanceDocument maintenanceDocument) {
        return maintenanceDocument.getNewMaintainableObject().getBusinessObject() instanceof Asset && maintenanceDocument.isNew();
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isAssetLoaned(org.kuali.kfs.module.cam.businessobject.Asset)
     */
    public boolean isAssetLoaned(Asset asset) {
        return ObjectUtils.isNotNull(asset.getExpectedReturnDate()) && ObjectUtils.isNull(asset.getLoanReturnDate());
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isAssetTaggedInPriorFiscalYear(org.kuali.kfs.module.cam.businessobject.Asset)
     */
    public boolean isAssetTaggedInPriorFiscalYear(Asset asset) {
        UniversityDateService universityDateService = SpringContext.getBean(UniversityDateService.class);

        return StringUtils.isNotBlank(asset.getCampusTagNumber()) && ObjectUtils.isNotNull(asset.getFinancialDocumentPostingYear()) && !universityDateService.getCurrentFiscalYear().equals(asset.getFinancialDocumentPostingYear());
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isTagNumberCheckExclude(org.kuali.kfs.module.cam.businessobject.Asset)
     */
    public boolean isTagNumberCheckExclude(Asset asset) {
        String status = asset.getInventoryStatusCode();

        return StringUtils.equalsIgnoreCase(status, CamsConstants.InventoryStatusCode.CAPITAL_ASSET_RETIRED) || StringUtils.equalsIgnoreCase(status, CamsConstants.InventoryStatusCode.NON_CAPITAL_ASSET_RETIRED) || StringUtils.equalsIgnoreCase(asset.getCampusTagNumber(), CamsConstants.Asset.NON_TAGGABLE_ASSET);
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isOffCampusLocationEntered(org.kuali.kfs.module.cam.businessobject.Asset)
     */
    public boolean isOffCampusLocationEntered(Asset asset) {
        AssetLocation offCampus = asset.getOffCampusLocation();
        return StringUtils.isNotBlank(offCampus.getAssetLocationContactName()) || StringUtils.isNotBlank(offCampus.getAssetLocationStreetAddress()) || StringUtils.isNotBlank(offCampus.getAssetLocationCityName()) || StringUtils.isNotBlank(offCampus.getAssetLocationStateCode()) || StringUtils.isNotBlank(offCampus.getAssetLocationZipCode()) || StringUtils.isNotBlank(offCampus.getAssetLocationCountryCode());
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isFinancialObjectSubTypeCodeChanged(org.kuali.kfs.module.cam.businessobject.Asset,
     * org.kuali.kfs.module.cam.businessobject.Asset)
     */
    public boolean isFinancialObjectSubTypeCodeChanged(Asset oldAsset, Asset newAsset) {
        return !StringUtils.equalsIgnoreCase(oldAsset.getFinancialObjectSubTypeCode(), newAsset.getFinancialObjectSubTypeCode());
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isAssetTypeCodeChanged(org.kuali.kfs.module.cam.businessobject.Asset,
     * org.kuali.kfs.module.cam.businessobject.Asset)
     */
    public boolean isAssetTypeCodeChanged(Asset oldAsset, Asset newAsset) {
        return !StringUtils.equalsIgnoreCase(oldAsset.getCapitalAssetTypeCode(), newAsset.getCapitalAssetTypeCode());
    }

    public boolean isAssetDepreciableLifeLimitZero(Asset asset) {
        asset.refreshReferenceObject(CamsPropertyConstants.Asset.CAPITAL_ASSET_TYPE);
        AssetType capitalAssetType = asset.getCapitalAssetType();
        if (ObjectUtils.isNotNull(capitalAssetType)) {
            return Integer.valueOf(0).equals(capitalAssetType.getDepreciableLifeLimit());
        }
        return false;
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isCapitalAssetNumberDuplicate(java.lang.Long, java.lang.Long)
     */
    public boolean isCapitalAssetNumberDuplicate(Long capitalAssetNumber1, Long capitalAssetNumber2) {
        if (capitalAssetNumber1 != null && capitalAssetNumber2 != null && capitalAssetNumber1.compareTo(capitalAssetNumber2) == 0) {
            return true;
        }
        return false;
    }


    /**
     * This method calls the service codes to calculate the summary fields for each asset
     *
     * @param asset
     */
    public void setAssetSummaryFields(Asset asset) {
        if (ObjectUtils.isNotNull(asset)) {
            asset.setFederalContribution(paymentSummaryService.calculateFederalContribution(asset));
            asset.setAccumulatedDepreciation(paymentSummaryService.calculatePrimaryAccumulatedDepreciation(asset));
            asset.setBookValue(paymentSummaryService.calculatePrimaryBookValue(asset));
        }
    }

    public PaymentSummaryService getPaymentSummaryService() {
        return paymentSummaryService;
    }

    public void setPaymentSummaryService(PaymentSummaryService paymentSummaryService) {
        this.paymentSummaryService = paymentSummaryService;
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isAssetMovableCheckByPayment(java.lang.String)
     */
    public boolean isAssetMovableCheckByPayment(String financialObjectSubTypeCode) {
        if (ObjectUtils.isNull(financialObjectSubTypeCode)) {
            return true;
        }

        if (parameterService.getParameterValuesAsString(Asset.class, CamsConstants.Parameters.MOVABLE_EQUIPMENT_OBJECT_SUB_TYPES).contains(financialObjectSubTypeCode)) {
            return true;
        } else if (parameterService.getParameterValuesAsString(Asset.class, CamsConstants.Parameters.NON_MOVABLE_EQUIPMENT_OBJECT_SUB_TYPES).contains(financialObjectSubTypeCode)) {
            return false;
        } else {
            throw new ValidationException("Could not determine movable or non-movable for this object sub-type code " + financialObjectSubTypeCode);
        }
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isAssetMovableCheckByPayment(Asset)
     */
    public boolean isAssetMovableCheckByPayment(Asset asset) {
        String financialObjectSubTypeCode = asset.getFinancialObjectSubTypeCode();

        if (ObjectUtils.isNotNull(asset.getAssetPayments()) && !asset.getAssetPayments().isEmpty()) {
            // use the last payment, so it's more recent and less likely to hit an out-dated object code
            int size = asset.getAssetPayments().size();
            AssetPayment lastAssetPayment = asset.getAssetPayments().get(size - 1);
            lastAssetPayment.refreshReferenceObject(CamsPropertyConstants.AssetPayment.FINANCIAL_OBJECT);
            ObjectCodeService objectCodeService = (ObjectCodeService) SpringContext.getBean(ObjectCodeService.class);
            String chartCode = lastAssetPayment.getChartOfAccountsCode();
            String finObjectCode = lastAssetPayment.getFinancialObjectCode();
            ObjectCode objectCode = objectCodeService.getByPrimaryIdForCurrentYear(chartCode, finObjectCode);

            // if objectCode is null, we likely have hit an out-dated object code from some old payment
            if (ObjectUtils.isNotNull(objectCode)) {
                financialObjectSubTypeCode = objectCode.getFinancialObjectSubTypeCode();
            } else {
                LOG.warn("Possibly out-dated object code " + finObjectCode + " for chart " + chartCode + " for current fiscal year in payments for asset " + asset.getCapitalAssetNumber());
            }
        }

        return this.isAssetMovableCheckByPayment(financialObjectSubTypeCode);
    }

    public List<Asset> findActiveAssetsMatchingTagNumber(String campusTagNumber) {
        List<Asset> activeMatches = new ArrayList<Asset>();
        // find all assets matching this tag number
        Map<String, String> params = new HashMap<String, String>();
        params.put(CamsPropertyConstants.Asset.CAMPUS_TAG_NUMBER, campusTagNumber);
        Collection<Asset> tagMatches = SpringContext.getBean(BusinessObjectService.class).findMatching(Asset.class, params);
        if (tagMatches != null && !tagMatches.isEmpty()) {
            for (Asset asset : tagMatches) {
                // if found matching, check if status is not retired
                if (!isAssetRetired(asset)) {
                    activeMatches.add(asset);
                }
            }
        }
        return activeMatches;
    }


    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#findAssetsMatchingTagNumber(java.lang.String)
     */
    public Collection<Asset> findAssetsMatchingTagNumber(String campusTagNumber) {
        // find all assets matching this tag number
        Map<String, String> params = new HashMap<String, String>();
        params.put(CamsPropertyConstants.Asset.CAMPUS_TAG_NUMBER, campusTagNumber);
        Collection<Asset> tagMatches = SpringContext.getBean(BusinessObjectService.class).findMatching(Asset.class, params);

        return tagMatches;
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isObjectSubTypeCompatible(java.util.List)
     */
    public boolean isObjectSubTypeCompatible(List<String> financialObjectSubTypeCode) {
        if (financialObjectSubTypeCode == null || financialObjectSubTypeCode.size() <= 1) {
            return true;
        }

        List<String> subTypes = new ArrayList<String>(parameterService.getParameterValuesAsString(Asset.class, CamsConstants.Parameters.OBJECT_SUB_TYPE_GROUPS));
        String firstObjectSubType = (String) financialObjectSubTypeCode.get(0);
        List<String> validObjectSubTypes = new ArrayList<String>();

        // Get the set for compatible object sub type code by the first financial object sub type code
        for (String subType : subTypes) {
            if (subType.contains(firstObjectSubType)) {
                validObjectSubTypes = Arrays.asList(StringUtils.split(subType, ","));
                break;
            }
        }

        if (validObjectSubTypes.isEmpty()) {
            validObjectSubTypes.add(firstObjectSubType);
        }

        // Check in the whole list if all object sub type code are compatible.
        for (String subTypeCode : financialObjectSubTypeCode) {
            if (!validObjectSubTypes.contains(subTypeCode)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#setSeparateHistory(org.kuali.kfs.module.cam.businessobject.Asset)
     */
    public void setSeparateHistory(Asset asset) {
        // Find the asset this was separated from. It should only be one, error if more
        Map<String, String> paramsAssetGlobalDetail = new HashMap<String, String>();
        paramsAssetGlobalDetail.put(CamsPropertyConstants.AssetGlobalDetail.CAPITAL_ASSET_NUMBER, asset.getCapitalAssetNumber().toString());
        Collection<AssetGlobalDetail> assetGlobalDetails = SpringContext.getBean(BusinessObjectService.class).findMatching(AssetGlobalDetail.class, paramsAssetGlobalDetail);

        if (assetGlobalDetails.size() > 1) {
            throw new IllegalStateException("Asset #" + asset.getCapitalAssetNumber().toString() + " was created from more then one asset document.");
        } else if (assetGlobalDetails.size() == 1) {
            // Find the document associated to that
            Map<String, String> paramsAssetGlobal = new HashMap<String, String>();
            paramsAssetGlobal.put(CamsPropertyConstants.AssetGlobal.DOCUMENT_NUMBER, assetGlobalDetails.iterator().next().getDocumentNumber());
            AssetGlobal assetGlobal = (AssetGlobal) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(AssetGlobal.class, paramsAssetGlobal);

            // Only set it if it is in status approved
            if (DocumentStatusCodes.APPROVED.equals((assetGlobal.getDocumentHeader().getFinancialDocumentStatusCode()))) {
                asset.setSeparateHistory(assetGlobal);
            }
        }

        // Else no history, just return
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#getDocumentNumberThatSeparatedThisAsset(java.lang.Long)
     */
    public List<String> getDocumentNumbersThatSeparatedThisAsset(Long capitalAssetNumber) {
        Map<String, String> paramsAssetGlobal = new HashMap<String, String>();
        paramsAssetGlobal.put(CamsPropertyConstants.AssetGlobal.SEPARATE_SOURCE_CAPITAL_ASSET_NUMBER, capitalAssetNumber.toString());
        Collection<AssetGlobal> assetGlobals = SpringContext.getBean(BusinessObjectService.class).findMatching(AssetGlobal.class, paramsAssetGlobal);

        List<String> separateDocumentNumbers = new ArrayList<String>();
        for (Iterator<AssetGlobal> assetGlobalIter = assetGlobals.iterator(); assetGlobalIter.hasNext(); ) {
            AssetGlobal assetGlobal = assetGlobalIter.next();

            if (DocumentStatusCodes.APPROVED.equals(assetGlobal.getDocumentHeader().getFinancialDocumentStatusCode())) {
                separateDocumentNumbers.add(assetGlobal.getDocumentNumber());
            }
        }

        return separateDocumentNumbers;
    }

    /**
     * sets the posting year and posting month based on the asset creation date
     *
     * @param asset
     * @return none
     */
    public void setFiscalPeriod(Asset asset) {
        if (asset.getCreateDate() == null)
            return;

        Map<String, Object> primaryKeys = new HashMap<String, Object>();
        primaryKeys.put(KFSPropertyConstants.UNIVERSITY_DATE, asset.getCreateDate());
        UniversityDate universityDate = (UniversityDate) businessObjectService.findByPrimaryKey(UniversityDate.class, primaryKeys);
        if (universityDate != null) {
            asset.setFinancialDocumentPostingYear(universityDate.getUniversityFiscalYear());
            asset.setFinancialDocumentPostingPeriodCode(universityDate.getUniversityFiscalAccountingPeriod());
        }
    }

    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }


    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#getCurrentRouteLevels(org.kuali.rice.kew.api.WorkflowDocument)
     */
    public Set<String> getCurrentRouteLevels(WorkflowDocument workflowDocument) {
        return workflowDocument.getCurrentNodeNames();
    }

    /**
     * @see org.kuali.kfs.module.cam.document.service.AssetService#isMaintenanceDocumentEnroute(org.kuali.rice.kns.document.MaintenanceDocument)
     */
    public boolean isDocumentEnrouting(Document document) {
        return document.getDocumentHeader().getWorkflowDocument().isEnroute();
    }

}
