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

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.gl.businessobject.LedgerBalanceTypeSummaryTotalLine;
import org.kuali.kfs.gl.businessobject.LedgerSummaryDetailLine;
import org.kuali.kfs.gl.businessobject.LedgerSummaryTotalLine;
import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
import org.kuali.kfs.sys.DynamicCollectionComparator;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.service.ReportWriterService;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Helper class which can summarize entries by balance type and then print out a ledger summary report
 */
public class LedgerSummaryReport {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LedgerSummaryReport.class);

    private LedgerSummaryTotalLine ledgerTotalLine;
    private Map<String, LedgerBalanceTypeSummaryTotalLine> balanceTypeTotals;
    Map<String, LedgerSummaryDetailLine> details;

    /**
     * Constructs a LedgerSummaryReport
     */
    public LedgerSummaryReport() {
        ledgerTotalLine = new LedgerSummaryTotalLine();
        balanceTypeTotals = new LinkedHashMap<String, LedgerBalanceTypeSummaryTotalLine>();
        details = new LinkedHashMap<String, LedgerSummaryDetailLine>();
    }

    /**
     * Summarizes an entry into the various totals which this report is keeping
     *
     * @param entry an entry to summarize
     */
    public void summarizeEntry(OriginEntryInformation entry) {
        LedgerBalanceTypeSummaryTotalLine balanceTypeTotal = getBalanceTypeSummaryTotalLine(entry, balanceTypeTotals);
        LedgerSummaryDetailLine detailLine = getDetailLine(entry, details);
        addEntryToLedgerSummaries(entry, ledgerTotalLine, balanceTypeTotal, detailLine);
    }

    /**
     * Retrieves the proper balance type summarizer from the given map, or creates a new summarizer and puts it in the Map if it doesn't already exist
     *
     * @param entry             the origin entry to find a balance type summarizer for
     * @param balanceTypeTotals the Map of balance type summarizers
     * @return the proper balance type summarizer
     */
    protected LedgerBalanceTypeSummaryTotalLine getBalanceTypeSummaryTotalLine(OriginEntryInformation entry, Map<String, LedgerBalanceTypeSummaryTotalLine> balanceTypeTotals) {
        final String balanceTypeCode = (StringUtils.isBlank(entry.getFinancialBalanceTypeCode())) ? " " : entry.getFinancialBalanceTypeCode();
        LedgerBalanceTypeSummaryTotalLine balanceTypeTotal = balanceTypeTotals.get(balanceTypeCode);
        if (balanceTypeTotal == null) {
            balanceTypeTotal = new LedgerBalanceTypeSummaryTotalLine(balanceTypeCode);
            balanceTypeTotals.put(balanceTypeCode, balanceTypeTotal);
        }
        return balanceTypeTotal;
    }

    /**
     * Retrieves the proper detail line summarizer from the given map, or creates a new summarizer and adds it to the map if needed
     *
     * @param entry       the origin entry to find a detail line summarizer for
     * @param detailLines a Map of detail line summarizers
     * @return the proper detail line summarizer
     */
    protected LedgerSummaryDetailLine getDetailLine(OriginEntryInformation entry, Map<String, LedgerSummaryDetailLine> detailLines) {
        final String key = LedgerSummaryDetailLine.getKeyString(entry);
        LedgerSummaryDetailLine detailLine = detailLines.get(key);
        if (detailLine == null) {
            detailLine = new LedgerSummaryDetailLine(entry.getFinancialBalanceTypeCode(), entry.getFinancialSystemOriginationCode(), entry.getUniversityFiscalYear(), entry.getUniversityFiscalPeriodCode());
            detailLines.put(detailLine.getKey(), detailLine);
        }
        return detailLine;
    }

    /**
     * Adds the amount of the origin entry into the appropriate total - debit, credit, or budget - on the various ledger summarizers
     *
     * @param originEntry      the origin entry to add the total from
     * @param totalLine        a complete total to add the amount to
     * @param balanceTypeTotal the total for the entries with the same balance type as the origin entry to add the amount to
     * @param detailLine       the proper detail amount to add the amoun tto
     */
    protected void addEntryToLedgerSummaries(OriginEntryInformation originEntry, LedgerSummaryTotalLine totalLine, LedgerBalanceTypeSummaryTotalLine balanceTypeTotal, LedgerSummaryDetailLine detailLine) {
        if (originEntry.getTransactionDebitCreditCode() != null && originEntry.getTransactionDebitCreditCode().equals(KFSConstants.GL_DEBIT_CODE)) {
            totalLine.addDebitAmount(originEntry.getTransactionLedgerEntryAmount());
            balanceTypeTotal.addDebitAmount(originEntry.getTransactionLedgerEntryAmount());
            detailLine.addDebitAmount(originEntry.getTransactionLedgerEntryAmount());
        } else if (originEntry.getTransactionDebitCreditCode() != null && originEntry.getTransactionDebitCreditCode().equals(KFSConstants.GL_CREDIT_CODE)) {
            totalLine.addCreditAmount(originEntry.getTransactionLedgerEntryAmount());
            balanceTypeTotal.addCreditAmount(originEntry.getTransactionLedgerEntryAmount());
            detailLine.addCreditAmount(originEntry.getTransactionLedgerEntryAmount());
        } else {
            totalLine.addBudgetAmount(originEntry.getTransactionLedgerEntryAmount());
            balanceTypeTotal.addBudgetAmount(originEntry.getTransactionLedgerEntryAmount());
            detailLine.addBudgetAmount(originEntry.getTransactionLedgerEntryAmount());
        }
    }

    /**
     * Writes the report of totals to the given reportWriterService
     *
     * @param reportWriterService a report writer service to write the ledger summary report to
     */
    public void writeReport(ReportWriterService reportWriterService) {
        if (details.size() > 0) {
            List<LedgerSummaryDetailLine> detailList = new ArrayList<LedgerSummaryDetailLine>(details.values());
            DynamicCollectionComparator.sort(detailList, LedgerSummaryDetailLine.keyFields);

            reportWriterService.writeTableHeader(detailList.get(0));
            String currentBalanceType = detailList.get(0).getFinancialBalanceTypeCode();
            currentBalanceType = StringUtils.isBlank(currentBalanceType) ? " " : currentBalanceType;

            for (LedgerSummaryDetailLine detailLine : detailList) {
                String detailLineBalanceType = (StringUtils.isBlank(detailLine.getFinancialBalanceTypeCode())) ? " " : detailLine.getFinancialBalanceTypeCode();
                if (!detailLineBalanceType.equals(currentBalanceType)) {
                    LedgerBalanceTypeSummaryTotalLine subTitleLine = balanceTypeTotals.get(currentBalanceType);

                    if (subTitleLine != null) {
                        reportWriterService.writeTableRow(subTitleLine);
                        reportWriterService.writeTableRowSeparationLine(subTitleLine);
                    }
                    currentBalanceType = detailLineBalanceType;
                }

                reportWriterService.writeTableRow(detailLine);
            }
            final LedgerBalanceTypeSummaryTotalLine lastLine = balanceTypeTotals.get(detailList.get(detailList.size() - 1).getFinancialBalanceTypeCode());
            if (lastLine != null) {
                reportWriterService.writeTableRow(lastLine);
            }

            reportWriterService.writeTableRowSeparationLine(ledgerTotalLine);
            reportWriterService.writeTableRow(ledgerTotalLine);
        }
    }

}
