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

import org.apache.commons.collections.CollectionUtils;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.ar.businessobject.CollectionEvent;
import org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument;
import org.kuali.kfs.module.ar.report.ContractsGrantsAgingReportDetailDataHolder;
import org.kuali.kfs.module.ar.report.ContractsGrantsReportDataHolder;
import org.kuali.kfs.module.ar.report.service.ContractsGrantsAgingReportService;
import org.kuali.kfs.module.ar.report.service.ContractsGrantsReportDataBuilderService;
import org.kuali.kfs.module.ar.report.service.ContractsGrantsReportHelperService;
import org.kuali.kfs.sys.report.ReportInfo;
import org.kuali.kfs.sys.util.KfsDateUtils;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.krad.bo.BusinessObject;

import java.math.BigDecimal;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Builds the DataHolder for the Contracts & Grants Invoice Aging report
 */
public class ContractsGrantsAgingReportBuilderServiceImpl implements ContractsGrantsReportDataBuilderService {
    protected ReportInfo reportInfo;
    protected ContractsGrantsReportHelperService contractsGrantsReportHelperService;
    protected ContractsGrantsAgingReportService contractsGrantsAgingReportService;

    /**
     * @see org.kuali.kfs.module.ar.report.service.ContractsGrantsReportDataBuilderService#buildReportDataHolder(java.util.List, java.lang.String)
     */
    @Override
    public ContractsGrantsReportDataHolder buildReportDataHolder(List<? extends BusinessObject> displayList, String sortPropertyName) {
        // check field is valid for subtotal
        boolean isFieldSubtotalRequired = true;
        Map<String, List<KualiDecimal>> subTotalMap = new HashMap<String, List<KualiDecimal>>();

        if (isFieldSubtotalRequired) {
            subTotalMap = buildSubTotalMap((List<ContractsGrantsInvoiceDocument>) displayList, sortPropertyName);
        }

        BigDecimal invoiceTotal = BigDecimal.ZERO;
        BigDecimal paymentTotal = BigDecimal.ZERO;
        BigDecimal remainingTotal = BigDecimal.ZERO;

        // build report
        ContractsGrantsReportDataHolder cgInvoiceReportDataHolder = new ContractsGrantsReportDataHolder();
        List<ContractsGrantsAgingReportDetailDataHolder> details = cgInvoiceReportDataHolder.getDetails();

        for (ContractsGrantsInvoiceDocument cgInvoiceEntry : (List<ContractsGrantsInvoiceDocument>) displayList) {
            ContractsGrantsAgingReportDetailDataHolder reportDetail = new ContractsGrantsAgingReportDetailDataHolder();
            // set report data
            setReportData(cgInvoiceEntry, reportDetail);

            if (isFieldSubtotalRequired) {
                // set sortedFieldValue for grouping in the report
                reportDetail.setSortedFieldValue(getContractsGrantsReportHelperService().getPropertyValue(cgInvoiceEntry, sortPropertyName));
                reportDetail.setDisplaySubtotal(true);
                // set subTotal from subTotalMap
                reportDetail.setInvoiceSubTotal(subTotalMap.get(getContractsGrantsReportHelperService().getPropertyValue(cgInvoiceEntry, sortPropertyName)).get(0).bigDecimalValue());
                reportDetail.setPaymentSubTotal(subTotalMap.get(getContractsGrantsReportHelperService().getPropertyValue(cgInvoiceEntry, sortPropertyName)).get(1).bigDecimalValue());
                reportDetail.setRemainingSubTotal(subTotalMap.get(getContractsGrantsReportHelperService().getPropertyValue(cgInvoiceEntry, sortPropertyName)).get(2).bigDecimalValue());


            } else {
                // set this to empty string for not displaying subtotal
                reportDetail.setDisplaySubtotal(false);
            }

            invoiceTotal = invoiceTotal.add(reportDetail.getInvoiceAmount());
            paymentTotal = paymentTotal.add(reportDetail.getPaymentAmount());
            remainingTotal = remainingTotal.add(reportDetail.getRemainingAmount());
            details.add(reportDetail);
        }

        // set total field
        ContractsGrantsAgingReportDetailDataHolder reportDetail = new ContractsGrantsAgingReportDetailDataHolder();
        reportDetail.setDisplayTotal(true);
        reportDetail.setInvoiceTotal(invoiceTotal);
        reportDetail.setPaymentTotal(paymentTotal);
        reportDetail.setRemainingTotal(remainingTotal);

        details.add(reportDetail);

        cgInvoiceReportDataHolder.setDetails(details);
        return cgInvoiceReportDataHolder;
    }

    /**
     * Returns the class of ContractsGrantsInvoiceDocument
     *
     * @see org.kuali.kfs.module.ar.report.service.ContractsGrantsReportDataBuilderService#getDetailsClass()
     */
    @Override
    public Class<? extends BusinessObject> getDetailsClass() {
        return ContractsGrantsInvoiceDocument.class;
    }

    /**
     * @param cgInvoiceReportEntry
     * @param reportDetail
     */
    protected void setReportData(ContractsGrantsInvoiceDocument cgInvoiceReportEntry, ContractsGrantsAgingReportDetailDataHolder reportDetail) {
        java.util.Date today = new java.util.Date();
        Date sqlToday = new java.sql.Date(today.getTime());
        reportDetail.setAgencyNumber(cgInvoiceReportEntry.getInvoiceGeneralDetail().getAward().getAgency().getAgencyNumber());
        reportDetail.setAgencyName(cgInvoiceReportEntry.getInvoiceGeneralDetail().getAward().getAgency().getReportingName());
        reportDetail.setCustomerNumber(cgInvoiceReportEntry.getCustomerNumber());
        reportDetail.setProposalNumber(cgInvoiceReportEntry.getInvoiceGeneralDetail().getProposalNumber().toString());
        reportDetail.setAwardEndDate(cgInvoiceReportEntry.getInvoiceGeneralDetail().getAward().getAwardEndingDate());
        reportDetail.setDocumentNumber(cgInvoiceReportEntry.getDocumentNumber());

        WorkflowDocument workflowDocument = cgInvoiceReportEntry.getDocumentHeader().getWorkflowDocument();
        Date docCreateDate = new Date(workflowDocument.getDateCreated().toDate().getTime());
        reportDetail.setInvoiceDate(docCreateDate);

        // last event date
        List<CollectionEvent> events = cgInvoiceReportEntry.getCollectionEvents();
        if (ObjectUtils.isNotNull(events) && CollectionUtils.isNotEmpty(events)) {
            Collections.sort(events, new Comparator<CollectionEvent>() {

                @Override
                public int compare(CollectionEvent o1, CollectionEvent o2) {
                    if (KfsDateUtils.isSameDay(o1.getActivityDate(), o2.getActivityDate())) {
                        return 0;
                    }
                    return o2.getActivityDate().compareTo(o1.getActivityDate());
                }
            });
            reportDetail.setLastEventDate(events.get(0).getActivityDate());
        }

        // calculate ageInDays : current date - created date
        final long MILLSECS_PER_DAY = 24 * 60 * 60 * 1000;
        reportDetail.setAgeInDays((sqlToday.getTime() - docCreateDate.getTime()) / MILLSECS_PER_DAY);

        BigDecimal invoiceAmount = cgInvoiceReportEntry.getTotalDollarAmount().bigDecimalValue();
        reportDetail.setInvoiceAmount(invoiceAmount);

        BigDecimal paymentAmount = cgInvoiceReportEntry.getPaymentAmount().bigDecimalValue();
        reportDetail.setPaymentAmount(paymentAmount);

        BigDecimal remainingAmount = invoiceAmount.subtract(paymentAmount);
        reportDetail.setRemainingAmount(remainingAmount);
    }

    /**
     * @see org.kuali.kfs.module.ar.report.service.ContractsGrantsAgingReportService#buildSubTotalMap(java.util.List, java.lang.String)
     */
    protected Map<String, List<KualiDecimal>> buildSubTotalMap(List<ContractsGrantsInvoiceDocument> displayList, String sortPropertyName) {
        Map<String, List<KualiDecimal>> returnSubTotalMap = new HashMap<String, List<KualiDecimal>>();
        // get list of sort fields
        List<String> valuesOfsortProperty = getContractsGrantsReportHelperService().getListOfValuesSortedProperties(displayList, sortPropertyName);

        // calculate sub_total and build subTotalMap
        for (String value : valuesOfsortProperty) {
            KualiDecimal invoiceSubTotal = KualiDecimal.ZERO;
            KualiDecimal paymentSubTotal = KualiDecimal.ZERO;
            KualiDecimal remainingSubTotal = KualiDecimal.ZERO;

            for (ContractsGrantsInvoiceDocument cgInvoiceReportEntry : displayList) {
                // set fieldValue as "" when it is null
                if (value.equals(getContractsGrantsReportHelperService().getPropertyValue(cgInvoiceReportEntry, sortPropertyName))) {
                    KualiDecimal sourceTotal = cgInvoiceReportEntry.getSourceTotal();
                    KualiDecimal paymentAmount = cgInvoiceReportEntry.getPaymentAmount();
                    invoiceSubTotal = invoiceSubTotal.add(sourceTotal);
                    paymentSubTotal = paymentSubTotal.add(paymentAmount);
                    remainingSubTotal = remainingSubTotal.add(sourceTotal.subtract(paymentSubTotal));
                }
            }
            List<KualiDecimal> allSubTotal = new ArrayList<KualiDecimal>();
            allSubTotal.add(0, invoiceSubTotal);
            allSubTotal.add(1, paymentSubTotal);
            allSubTotal.add(2, remainingSubTotal);

            returnSubTotalMap.put(value, allSubTotal);
        }
        return returnSubTotalMap;
    }

    @Override
    public ReportInfo getReportInfo() {
        return reportInfo;
    }

    public void setReportInfo(ReportInfo reportInfo) {
        this.reportInfo = reportInfo;
    }

    public ContractsGrantsReportHelperService getContractsGrantsReportHelperService() {
        return contractsGrantsReportHelperService;
    }

    public void setContractsGrantsReportHelperService(ContractsGrantsReportHelperService contractsGrantsReportHelperService) {
        this.contractsGrantsReportHelperService = contractsGrantsReportHelperService;
    }

    public ContractsGrantsAgingReportService getContractsGrantsAgingReportService() {
        return contractsGrantsAgingReportService;
    }

    public void setContractsGrantsAgingReportService(ContractsGrantsAgingReportService contractsGrantsAgingReportService) {
        this.contractsGrantsAgingReportService = contractsGrantsAgingReportService;
    }
}
