/*
 * 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.ld.businessobject.lookup;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.gl.Constant;
import org.kuali.kfs.gl.businessobject.TransientBalanceInquiryAttributes;
import org.kuali.kfs.integration.ld.LaborLedgerObject;
import org.kuali.kfs.integration.ld.businessobject.inquiry.AbstractPositionDataDetailsInquirableImpl;
import org.kuali.kfs.kns.lookup.AbstractLookupableHelperServiceImpl;
import org.kuali.kfs.kns.lookup.HtmlData;
import org.kuali.kfs.kns.lookup.HtmlData.AnchorHtmlData;
import org.kuali.kfs.krad.lookup.CollectionIncomplete;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.util.BeanPropertyComparator;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.module.ld.LaborKeyConstants;
import org.kuali.kfs.module.ld.businessobject.AccountStatusCurrentFunds;
import org.kuali.kfs.module.ld.businessobject.July1PositionFunding;
import org.kuali.kfs.module.ld.businessobject.LaborObject;
import org.kuali.kfs.module.ld.businessobject.LedgerBalance;
import org.kuali.kfs.module.ld.businessobject.inquiry.CurrentFundsInquirableImpl;
import org.kuali.kfs.module.ld.businessobject.inquiry.PositionDataDetailsInquirableImpl;
import org.kuali.kfs.module.ld.dataaccess.LaborDao;
import org.kuali.kfs.module.ld.service.LaborInquiryOptionsService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.ObjectUtil;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.krad.bo.BusinessObject;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.apache.commons.collections.IteratorUtils.toList;

/**
 * Service implementation for the CurrentFundsLookupableHelperServiceImpl class is the front-end for all current funds balance
 * inquiry processing.
 */
public class CurrentFundsLookupableHelperServiceImpl extends AbstractLookupableHelperServiceImpl {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CurrentFundsLookupableHelperServiceImpl.class);
    private LaborDao laborDao;
    private LaborInquiryOptionsService laborInquiryOptionsService;
    private BusinessObjectService businessObjectService;

    /**
     * @see org.kuali.rice.kns.lookup.Lookupable#getInquiryUrl(org.kuali.rice.krad.bo.BusinessObject, java.lang.String)
     */
    @Override
    public HtmlData getInquiryUrl(BusinessObject bo, String propertyName) {
        if (KFSPropertyConstants.POSITION_NUMBER.equals(propertyName)) {
            LedgerBalance balance = (LedgerBalance) bo;
            AbstractPositionDataDetailsInquirableImpl positionDataDetailsInquirable = new PositionDataDetailsInquirableImpl();

            Map<String, String> fieldValues = new HashMap<String, String>();
            fieldValues.put(propertyName, balance.getPositionNumber());

            BusinessObject positionData = positionDataDetailsInquirable.getBusinessObject(fieldValues);

            return positionData == null ? new AnchorHtmlData(KFSConstants.EMPTY_STRING, KFSConstants.EMPTY_STRING) : positionDataDetailsInquirable.getInquiryUrl(positionData, propertyName);
        }
        return (new CurrentFundsInquirableImpl()).getInquiryUrl(bo, propertyName);
    }

    /**
     * Gets a list with the fields that will be displayed on page
     *
     * @param fieldValues list of fields that are used as a key to filter out data
     * @see org.kuali.rice.kns.lookup.Lookupable#getSearchResults(java.util.Map)
     * <p>
     * KRAD Conversion: Lookupable performs customization of the search results.
     * <p>
     * No uses data dictionary.
     */
    @Override
    public List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues) {
        LOG.info("getSearchResults() - Entry");

        boolean unbounded = false;
        Long actualCountIfTruncated = new Long(0);

        setBackLocation(fieldValues.get(KFSConstants.BACK_LOCATION));
        setDocFormKey(fieldValues.get(KFSConstants.DOC_FORM_KEY));

        // get the pending entry option. This method must be prior to the get search results
        String pendingEntryOption = laborInquiryOptionsService.getSelectedPendingEntryOption(fieldValues);

        // get the consolidation option
        boolean isConsolidated = laborInquiryOptionsService.isConsolidationSelected(fieldValues, getRows());

        String searchObjectCodeVal = fieldValues.get(KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
        // Check for a valid labor object code for this inquiry
        if (StringUtils.isNotBlank(searchObjectCodeVal)) {
            Map objectCodeFieldValues = new HashMap();
            objectCodeFieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, fieldValues.get(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR));
            objectCodeFieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, fieldValues.get(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE));
            objectCodeFieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, searchObjectCodeVal);

            LaborLedgerObject foundObjectCode = businessObjectService.findByPrimaryKey(LaborObject.class, objectCodeFieldValues);

            if (foundObjectCode == null) {
                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, LaborKeyConstants.ERROR_INVALID_LABOR_OBJECT_CODE, "2");
                return new CollectionIncomplete(new ArrayList(), actualCountIfTruncated);
            }
        }

        // Parse the map and call the DAO to process the inquiry
        Collection<AccountStatusCurrentFunds> searchResultsCollection = buildCurrentFundsCollection(toList(laborDao.getCurrentFunds(fieldValues, isConsolidated)), isConsolidated, pendingEntryOption);

        // update search results according to the selected pending entry option
        laborInquiryOptionsService.updateCurrentFundsByPendingLedgerEntry(searchResultsCollection, fieldValues, pendingEntryOption, isConsolidated);

        // gets the July1st budget amount column.
        Collection<July1PositionFunding> july1PositionFundings = laborDao.getJuly1(fieldValues);
        this.updateJuly1BalanceAmount(searchResultsCollection, july1PositionFundings, isConsolidated);

        // sort list if default sort column given
        List searchResults = (List) searchResultsCollection;
        List defaultSortColumns = getDefaultSortColumns();
        if (defaultSortColumns.size() > 0) {
            Collections.sort(searchResults, new BeanPropertyComparator(defaultSortColumns, true));
        }
        return new CollectionIncomplete(searchResults, actualCountIfTruncated);
    }

    /**
     * Adds the july1 budget amount to each account found in the
     *
     * @param searchResultsCollection collection with the list of current funds where the amount is added
     * @param july1PositionFundings   collection of current funds with july1st budget amounts
     * @param isConsolidated
     */
    private void updateJuly1BalanceAmount(Collection<AccountStatusCurrentFunds> searchResultsCollection, Collection<July1PositionFunding> july1PositionFundings, boolean isConsolidated) {
        for (July1PositionFunding july1PositionFunding : july1PositionFundings) {
            boolean flag = false;
            for (AccountStatusCurrentFunds accountStatus : searchResultsCollection) {
                boolean found = ObjectUtil.equals(accountStatus, july1PositionFunding, accountStatus.getKeyFieldList(isConsolidated));
                if (found) {
                    flag = true;
                    accountStatus.setJuly1BudgetAmount(accountStatus.getJuly1BudgetAmount().add(july1PositionFunding.getJuly1BudgetAmount()));
                }
            }
            if (!flag) {
                AccountStatusCurrentFunds accountStatus = new AccountStatusCurrentFunds();
                ObjectUtil.buildObject(accountStatus, july1PositionFunding, accountStatus.getKeyFieldList(isConsolidated));
                accountStatus.setJuly1BudgetAmount(july1PositionFunding.getJuly1BudgetAmount());
                accountStatus.setVariance(july1PositionFunding.getJuly1BudgetAmount()); // because YTD actual amount is 0
                if (isConsolidated) {
                    accountStatus.setSubAccountNumber("*ALL*");
                    accountStatus.setFinancialSubObjectCode("*ALL*");
                } else {
                    accountStatus.setSubAccountNumber(july1PositionFunding.getSubAccountNumber());
                    accountStatus.setSubObjectCode(july1PositionFunding.getSubObjectCode());
                }
                searchResultsCollection.add(accountStatus);
            }
        }
    }

    /**
     * Returns a list with the current funds.
     *
     * @param iterator           the iterator of search results of account status
     * @param isConsolidated     determine if the consolidated result is desired
     * @param pendingEntryOption the given pending entry option that can be no, approved or all
     * @return the current funds collection
     */
    private Collection<AccountStatusCurrentFunds> buildCurrentFundsCollection(Collection collection, boolean isConsolidated, String pendingEntryOption) {
        Collection<AccountStatusCurrentFunds> retval = null;

        if (isConsolidated) {
            retval = buildCosolidatedCurrentFundsCollection(collection, pendingEntryOption);
        } else {
            retval = buildDetailedCurrentFundsCollection(collection, pendingEntryOption);
        }
        return retval;
    }

    /**
     * Builds the current funds collection with consolidation option from an iterator
     *
     * @param iterator
     * @param pendingEntryOption the selected pending entry option
     * @return the consolidated current funds collection
     */
    private Collection<AccountStatusCurrentFunds> buildCosolidatedCurrentFundsCollection(Collection collection, String pendingEntryOption) {
        Collection<AccountStatusCurrentFunds> retval = new ArrayList<AccountStatusCurrentFunds>();
        for (Object collectionEntry : collection) {
            if (collectionEntry.getClass().isArray()) {
                int i = 0;
                Object[] array = (Object[]) collectionEntry;
                AccountStatusCurrentFunds cf = new AccountStatusCurrentFunds();
                if (LOG.isDebugEnabled()) {
                    LOG.debug("element length " + array.length);
                }
                for (Object element : array) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("I found this element " + element);
                    }
                }

                if (AccountStatusCurrentFunds.class.isAssignableFrom(getBusinessObjectClass())) {
                    try {
                        cf = (AccountStatusCurrentFunds) getBusinessObjectClass().newInstance();
                    } catch (Exception e) {
                        LOG.warn("Using " + AccountStatusCurrentFunds.class + " for results because I couldn't instantiate the " + getBusinessObjectClass());
                    }
                } else {
                    LOG.warn("Using " + AccountStatusCurrentFunds.class + " for results because I couldn't instantiate the " + getBusinessObjectClass());
                }

                cf.setUniversityFiscalYear(new Integer(array[i++].toString()));
                cf.setChartOfAccountsCode(array[i++].toString());
                cf.setAccountNumber(array[i++].toString());

                String subAccountNumber = Constant.CONSOLIDATED_SUB_ACCOUNT_NUMBER;
                cf.setSubAccountNumber(subAccountNumber);

                cf.setBalanceTypeCode(array[i++].toString());
                cf.setFinancialObjectCode(array[i++].toString());

                cf.setEmplid(array[i++].toString());
                cf.setPositionNumber(array[i++].toString());

                cf.setFinancialSubObjectCode(Constant.CONSOLIDATED_SUB_OBJECT_CODE);
                cf.setObjectTypeCode(Constant.CONSOLIDATED_OBJECT_TYPE_CODE);

                cf.setAccountLineAnnualBalanceAmount(new KualiDecimal(array[i++].toString()));
                cf.setBeginningBalanceLineAmount(new KualiDecimal(array[i++].toString()));
                cf.setContractsGrantsBeginningBalanceAmount(new KualiDecimal(array[i++].toString()));

                cf.setMonth1Amount(new KualiDecimal(array[i++].toString()));

                cf.setDummyBusinessObject(new TransientBalanceInquiryAttributes());
                cf.getDummyBusinessObject().setPendingEntryOption(pendingEntryOption);
                cf.setOutstandingEncum(getOutstandingEncum(cf));

                cf.getDummyBusinessObject().setPendingEntryOption(pendingEntryOption);
                cf.getDummyBusinessObject().setConsolidationOption(Constant.CONSOLIDATION);

                retval.add(cf);
            }
        }
        return retval;
    }

    /**
     * Builds the current funds collection with detail option from an iterator
     *
     * @param iterator           the current funds iterator
     * @param pendingEntryOption the selected pending entry option
     * @return the detailed balance collection
     */
    private Collection<AccountStatusCurrentFunds> buildDetailedCurrentFundsCollection(Collection collection, String pendingEntryOption) {
        Collection<AccountStatusCurrentFunds> retval = new ArrayList<AccountStatusCurrentFunds>();

        for (LedgerBalance balance : ((Collection<LedgerBalance>) collection)) {
            AccountStatusCurrentFunds cf = new AccountStatusCurrentFunds();
            ObjectUtil.buildObject(cf, balance);

            cf.setDummyBusinessObject(new TransientBalanceInquiryAttributes());
            cf.getDummyBusinessObject().setPendingEntryOption(pendingEntryOption);
            cf.setOutstandingEncum(getOutstandingEncum(cf));

            cf.getDummyBusinessObject().setPendingEntryOption(pendingEntryOption);
            cf.getDummyBusinessObject().setConsolidationOption(Constant.DETAIL);

            retval.add(cf);
        }
        return retval;
    }

    /**
     * Gets the outstanding encumbrance amount
     *
     * @param AccountStatusCurrentFunds
     * @param Map                       fieldValues
     */
    private KualiDecimal getOutstandingEncum(AccountStatusCurrentFunds bo) {
        Map<String, String> fieldValues = new HashMap();
        fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, bo.getUniversityFiscalYear().toString());
        fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, bo.getChartOfAccountsCode());
        fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, bo.getAccountNumber());

        if (!bo.getSubAccountNumber().equals(Constant.CONSOLIDATED_SUB_ACCOUNT_NUMBER)) {
            fieldValues.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, bo.getSubAccountNumber());
        }

        fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, bo.getFinancialObjectCode());

        if (!bo.getFinancialSubObjectCode().equals(Constant.CONSOLIDATED_SUB_OBJECT_CODE)) {
            fieldValues.put(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE, bo.getFinancialSubObjectCode());
        }
        fieldValues.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, KFSConstants.BALANCE_TYPE_INTERNAL_ENCUMBRANCE); // Encumberance
        // Balance
        // Type
        fieldValues.put(KFSPropertyConstants.EMPLID, bo.getEmplid());
        if (LOG.isDebugEnabled()) {
            LOG.debug("using " + fieldValues.values());
            LOG.debug("using " + fieldValues.keySet());
        }
        return (KualiDecimal) laborDao.getEncumbranceTotal(fieldValues);
    }

    /**
     * Sets the laborDao attribute value.
     *
     * @param laborDao The laborDao to set.
     */
    public void setLaborDao(LaborDao laborDao) {
        this.laborDao = laborDao;
    }

    /**
     * Sets the laborInquiryOptionsService attribute value.
     *
     * @param laborInquiryOptionsService The laborInquiryOptionsService to set.
     */
    public void setLaborInquiryOptionsService(LaborInquiryOptionsService laborInquiryOptionsService) {
        this.laborInquiryOptionsService = laborInquiryOptionsService;
    }

    /**
     * Sets the businessObjectService attribute value.
     *
     * @param businessObjectService The businessObjectService to set.
     */
    @Override
    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }

}
