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

import org.apache.commons.io.filefilter.RegexFileFilter;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.gl.GeneralLedgerConstants;
import org.kuali.kfs.gl.batch.PosterSummaryReportStep;
import org.kuali.kfs.gl.businessobject.GlSummary;
import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
import org.kuali.kfs.gl.report.PosterOutputSummaryReport;
import org.kuali.kfs.module.ld.LaborConstants;
import org.kuali.kfs.module.ld.batch.service.LaborBalanceSummaryReportService;
import org.kuali.kfs.module.ld.businessobject.LaborBalanceSummary;
import org.kuali.kfs.module.ld.service.LaborLedgerBalanceService;
import org.kuali.kfs.module.ld.util.LaborOriginEntryFileIterator;
import org.kuali.kfs.sys.FileUtil;
import org.kuali.kfs.sys.batch.service.WrappingBatchService;
import org.kuali.kfs.sys.businessobject.SystemOptions;
import org.kuali.kfs.sys.service.FiscalYearAwareReportWriterService;
import org.kuali.kfs.sys.service.OptionsService;
import org.kuali.kfs.sys.service.ReportWriterService;
import org.kuali.rice.core.api.datetime.DateTimeService;

import java.io.File;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import static org.kuali.kfs.gl.GeneralLedgerConstants.GlSummaryReport.CURRENT_AND_LAST_YEAR;
import static org.kuali.kfs.gl.GeneralLedgerConstants.GlSummaryReport.CURRENT_YEAR_LOWER;
import static org.kuali.kfs.gl.GeneralLedgerConstants.GlSummaryReport.CURRENT_YEAR_UPPER;

/**
 * Implements a set of methods that can generate labor balance summary reports
 */
public class LaborBalanceSummaryReportServiceImpl implements LaborBalanceSummaryReportService {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LaborBalanceSummaryReportServiceImpl.class);

    private DateTimeService dateTimeService;
    private OptionsService optionsService;
    private ParameterService parameterService;

    private LaborLedgerBalanceService laborLedgerBalanceService;
    private ReportWriterService laborPosterOutputSummaryReportWriterService;
    private FiscalYearAwareReportWriterService laborActualBalanceSummaryReportWriterService;
    private FiscalYearAwareReportWriterService laborBudgetBalanceSummaryReportWriterService;
    private FiscalYearAwareReportWriterService laborEncumbranceSummaryReportWriterService;

    private String batchFileDirectoryName;

    /**
     * @see org.kuali.kfs.module.ld.batch.service.LaborBalanceSummaryReportService#generateBalanceSummaryReports()
     */
    public void generateBalanceSummaryReports() {
        LOG.debug("generateBalanceSummaryReports() started");

        Date runDate = dateTimeService.getCurrentSqlDate();
        this.generatePosterOutputSummaryReport(runDate);
        this.generateBalanceSummaryReports(runDate);
    }

    /**
     * @see org.kuali.kfs.module.ld.batch.service.LaborBalanceSummaryReportService#generateBalanceSummaryReports(java.sql.Date)
     */
    public void generateBalanceSummaryReports(Date runDate) {
        LOG.debug("generateBalanceSummaryReports(Date) started");

        String yearEndPeriodLowerBound = parameterService.getParameterValueAsString(PosterSummaryReportStep.class, CURRENT_YEAR_LOWER);
        String lastDayOfFiscalYear = parameterService.getParameterValueAsString(PosterSummaryReportStep.class, CURRENT_AND_LAST_YEAR);
        String yearEndPeriodUpperBound = parameterService.getParameterValueAsString(PosterSummaryReportStep.class, CURRENT_YEAR_UPPER);

        Integer currentYear = optionsService.getCurrentYearOptions().getUniversityFiscalYear();
        this.generateBalanceSummaryReports(currentYear, runDate);

        // if today is within the lower bound of the year end period, then generate reports for the next fiscal year
        if (this.isInYearEndLowerBound(runDate, yearEndPeriodLowerBound, lastDayOfFiscalYear)) {
            this.generateBalanceSummaryReports(currentYear + 1, runDate);
        }

        // if today is within the upper bound of the year end period, then generate reports for the last fiscal year
        if (this.isInYearEndUpperBound(runDate, yearEndPeriodUpperBound, lastDayOfFiscalYear)) {
            this.generateBalanceSummaryReports(currentYear - 1, runDate);
        }
    }

    // generate a set of balance summary reports for actual, budget and encumbrance balances
    protected void generateBalanceSummaryReports(Integer fiscalYear, Date runDate) {
        SystemOptions options = optionsService.getOptions(fiscalYear);
        if (options == null) {
            LOG.fatal("The data for " + fiscalYear + "have NOT been setup.");
            return;
        }

        List<String> actualsBalanceTypes = this.getActualBalanceTypes(fiscalYear);
        this.writeSummaryReport(fiscalYear, actualsBalanceTypes, laborActualBalanceSummaryReportWriterService);

        List<String> budgetBalanceTypes = this.getBudgetBalanceTypes(fiscalYear);
        this.writeSummaryReport(fiscalYear, budgetBalanceTypes, laborBudgetBalanceSummaryReportWriterService);

        List<String> encumbranceBalanceTypes = this.getEncumbranceBalanceTypes(fiscalYear);
        this.writeSummaryReport(fiscalYear, encumbranceBalanceTypes, laborEncumbranceSummaryReportWriterService);
    }

    protected void writeSummaryReport(Integer fiscalYear, List<String> balanceTypes, FiscalYearAwareReportWriterService reportWriterService) {
        List<LaborBalanceSummary> balanceSummary = laborLedgerBalanceService.findBalanceSummary(fiscalYear, balanceTypes);
        List<GlSummary> summaryList = new ArrayList<GlSummary>(balanceSummary);

        GlSummary totals = new LaborBalanceSummary();
        for (GlSummary summaryLine : summaryList) {
            totals.add(summaryLine);
        }
        totals.setFundGroup("Total");

        try {
            reportWriterService.setFiscalYear(fiscalYear);
            ((WrappingBatchService) reportWriterService).initialize();
            reportWriterService.writeSubTitle("Balance Type of " + balanceTypes + " for Fiscal Year " + fiscalYear);
            reportWriterService.writeNewLines(1);

            reportWriterService.writeTableRowSeparationLine(totals);
            reportWriterService.writeTable(summaryList, true, false);

            reportWriterService.writeTableRowSeparationLine(totals);
            reportWriterService.writeTableRow(totals);
        } finally {
            ((WrappingBatchService) reportWriterService).destroy();
        }
    }

    /**
     * Generates reports about the output of a poster run.
     *
     * @param runDate the date the poster was run.
     */
    protected void generatePosterOutputSummaryReport(Date runDate) {
        PosterOutputSummaryReport posterOutputSummaryReport = new PosterOutputSummaryReport();

        // summarize all the entries for the main poster
        File mainPosterFile = FileUtil.getNewestFile(new File(batchFileDirectoryName), new RegexFileFilter((LaborConstants.BatchFileSystem.POSTER_INPUT_FILE + "\\.[0-9_\\-]+\\" + GeneralLedgerConstants.BatchFileSystem.EXTENSION)));
        if (mainPosterFile != null && mainPosterFile.exists()) {
            LaborOriginEntryFileIterator mainPosterIterator = new LaborOriginEntryFileIterator(mainPosterFile);
            while (mainPosterIterator.hasNext()) {
                OriginEntryInformation originEntry = mainPosterIterator.next();
                posterOutputSummaryReport.summarize(originEntry);
            }
        } else {
            LOG.warn("Could not Main Poster Input file, " + LaborConstants.BatchFileSystem.POSTER_INPUT_FILE + ", for tabulation in the Poster Output Summary Report");
        }

        posterOutputSummaryReport.writeReport(laborPosterOutputSummaryReportWriterService);
    }


    /**
     * get the encumbrance balance type codes for the given fiscal year
     *
     * @param fiscalYear the given fiscal year
     * @return the encumbrance balance type codes for the given fiscal year
     */
    protected List<String> getEncumbranceBalanceTypes(Integer fiscalYear) {
        SystemOptions options = optionsService.getOptions(fiscalYear);

        List<String> balanceTypes = new ArrayList<String>();
        balanceTypes.add(options.getExtrnlEncumFinBalanceTypCd());
        balanceTypes.add(options.getIntrnlEncumFinBalanceTypCd());
        balanceTypes.add(options.getPreencumbranceFinBalTypeCd());
        balanceTypes.add(options.getCostShareEncumbranceBalanceTypeCd());
        return balanceTypes;
    }

    /**
     * get the actual balance type codes for the given fiscal year
     *
     * @param fiscalYear the given fiscal year
     * @return the actual balance type codes for the given fiscal year
     */
    protected List<String> getActualBalanceTypes(Integer fiscalYear) {
        SystemOptions options = optionsService.getOptions(fiscalYear);

        List<String> balanceTypes = new ArrayList<String>();
        balanceTypes.add(options.getActualFinancialBalanceTypeCd());
        return balanceTypes;
    }

    /**
     * get the budget balance type codes for the given fiscal year
     *
     * @param fiscalYear the given fiscal year
     * @return the budget balance type codes for the given fiscal year
     */
    protected List<String> getBudgetBalanceTypes(Integer fiscalYear) {
        SystemOptions options = optionsService.getOptions(fiscalYear);

        List<String> balanceTypes = new ArrayList<String>();
        balanceTypes.add(options.getBudgetCheckingBalanceTypeCd());
        balanceTypes.add(options.getBaseBudgetFinancialBalanceTypeCd());
        balanceTypes.add(options.getMonthlyBudgetFinancialBalanceTypeCd());
        return balanceTypes;
    }

    /**
     * determine if the given date is within the year end period
     *
     * @param runDate                 the given date
     * @param yearEndPeriodLowerBound the lower bound date of year end period
     * @param yearEndPeriodUpperBound the upper bound date of year end period
     * @param lastDayOfFiscalYear     the last day of the current fiscal year
     * @return true if the given date is within the lower bound of year end period; otherwise, false
     */
    protected boolean isInYearEndPeriod(Date runDate, String yearEndPeriodLowerBound, String yearEndPeriodUpperBound, String lastDayOfFiscalYear) {
        return isInYearEndLowerBound(runDate, yearEndPeriodLowerBound, lastDayOfFiscalYear) || isInYearEndUpperBound(runDate, yearEndPeriodUpperBound, lastDayOfFiscalYear);
    }

    /**
     * determine if the given date is within the lower bound of year end period
     *
     * @param runDate                 the given date
     * @param yearEndPeriodLowerBound the lower bound date of year end period
     * @param lastDayOfFiscalYear     the last day of the current fiscal year
     * @return true if the given date is within the lower bound of year end period; otherwise, false
     */
    protected boolean isInYearEndLowerBound(Date runDate, String yearEndPeriodLowerBound, String lastDayOfFiscalYear) {
        SimpleDateFormat sdf = new SimpleDateFormat("MMdd");
        String today = sdf.format(runDate);
        return today.compareTo(yearEndPeriodLowerBound) >= 0 && today.compareTo(lastDayOfFiscalYear) <= 0;
    }

    /**
     * determine if the given date is within the upper bound of year end period
     *
     * @param runDate                 the given date
     * @param yearEndPeriodUpperBound the upper bound date of year end period
     * @param lastDayOfFiscalYear     the last day of the current fiscal year
     * @return true if the given date is within the upper bound of year end period; otherwise, false
     */
    protected boolean isInYearEndUpperBound(Date runDate, String yearEndPeriodUpperBound, String lastDayOfFiscalYear) {
        SimpleDateFormat sdf = new SimpleDateFormat("MMdd");
        String today = sdf.format(runDate);

        String month = StringUtils.mid(lastDayOfFiscalYear, 0, 2);
        String date = StringUtils.mid(lastDayOfFiscalYear, 2, 2);

        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.MONTH, Integer.parseInt(month) - 1);
        calendar.set(Calendar.DATE, Integer.parseInt(date));
        calendar.add(Calendar.DATE, 1);
        String firstDayOfNewFiscalYear = sdf.format(calendar.getTime());

        return today.compareTo(yearEndPeriodUpperBound) <= 0 && today.compareTo(firstDayOfNewFiscalYear) >= 0;
    }

    /**
     * Sets the dateTimeService attribute value.
     *
     * @param dateTimeService The dateTimeService to set.
     */
    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    /**
     * Sets the optionsService attribute value.
     *
     * @param optionsService The optionsService to set.
     */
    public void setOptionsService(OptionsService optionsService) {
        this.optionsService = optionsService;
    }

    /**
     * Sets the parameterService attribute value.
     *
     * @param parameterService The parameterService to set.
     */
    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }

    /**
     * Sets the laborLedgerBalanceService attribute value.
     *
     * @param laborLedgerBalanceService The laborLedgerBalanceService to set.
     */
    public void setLaborLedgerBalanceService(LaborLedgerBalanceService laborLedgerBalanceService) {
        this.laborLedgerBalanceService = laborLedgerBalanceService;
    }

    /**
     * Sets the laborActualBalanceSummaryReportWriterService attribute value.
     *
     * @param laborActualBalanceSummaryReportWriterService The laborActualBalanceSummaryReportWriterService to set.
     */
    public void setLaborActualBalanceSummaryReportWriterService(FiscalYearAwareReportWriterService laborActualBalanceSummaryReportWriterService) {
        this.laborActualBalanceSummaryReportWriterService = laborActualBalanceSummaryReportWriterService;
    }

    /**
     * Sets the laborBudgetBalanceSummaryReportWriterService attribute value.
     *
     * @param laborBudgetBalanceSummaryReportWriterService The laborBudgetBalanceSummaryReportWriterService to set.
     */
    public void setLaborBudgetBalanceSummaryReportWriterService(FiscalYearAwareReportWriterService laborBudgetBalanceSummaryReportWriterService) {
        this.laborBudgetBalanceSummaryReportWriterService = laborBudgetBalanceSummaryReportWriterService;
    }

    /**
     * Sets the laborEncumbranceSummaryReportWriterService attribute value.
     *
     * @param laborEncumbranceSummaryReportWriterService The laborEncumbranceSummaryReportWriterService to set.
     */
    public void setLaborEncumbranceSummaryReportWriterService(FiscalYearAwareReportWriterService laborEncumbranceSummaryReportWriterService) {
        this.laborEncumbranceSummaryReportWriterService = laborEncumbranceSummaryReportWriterService;
    }

    /**
     * Sets the laborPosterOutputSummaryReportWriterService attribute value.
     *
     * @param laborPosterOutputSummaryReportWriterService The laborPosterOutputSummaryReportWriterService to set.
     */
    public void setLaborPosterOutputSummaryReportWriterService(ReportWriterService laborPosterOutputSummaryReportWriterService) {
        this.laborPosterOutputSummaryReportWriterService = laborPosterOutputSummaryReportWriterService;
    }

    /**
     * Sets the batchFileDirectoryName attribute value.
     *
     * @param batchFileDirectoryName The batchFileDirectoryName to set.
     */
    public void setBatchFileDirectoryName(String batchFileDirectoryName) {
        this.batchFileDirectoryName = batchFileDirectoryName;
    }


}
