/*
 * 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.kuali.kfs.gl.GeneralLedgerConstants;
import org.kuali.kfs.gl.batch.service.BalancingService;
import org.kuali.kfs.gl.batch.service.PosterService;
import org.kuali.kfs.gl.batch.service.impl.BalancingServiceBaseImpl;
import org.kuali.kfs.gl.businessobject.Balance;
import org.kuali.kfs.gl.businessobject.Entry;
import org.kuali.kfs.gl.businessobject.LedgerBalanceHistory;
import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.ld.LaborConstants;
import org.kuali.kfs.module.ld.LaborKeyConstants;
import org.kuali.kfs.module.ld.batch.LaborBalancingStep;
import org.kuali.kfs.module.ld.businessobject.LaborBalanceHistory;
import org.kuali.kfs.module.ld.businessobject.LaborEntryHistory;
import org.kuali.kfs.module.ld.businessobject.LaborOriginEntry;
import org.kuali.kfs.module.ld.businessobject.LedgerBalance;
import org.kuali.kfs.module.ld.businessobject.LedgerEntry;
import org.kuali.kfs.sys.FileUtil;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.Message;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FilenameFilter;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Service implementation of BalancingService for Labor balancing
 */
@Transactional
public class LaborBalancingServiceImpl extends BalancingServiceBaseImpl<LaborEntryHistory, LaborBalanceHistory> implements BalancingService {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LaborBalancingServiceImpl.class);

    protected File laborPosterInputFile = null;
    protected File laborPosterErrorOutputFile = null;

    @Override
    public boolean runBalancing() {
        // clear out the file cache, otherwise, it won't update the history tables with the latest poster files
        // therefore, it will use the files that were first used when the balancing job was run when the JVM started, and that'll cause out of balance errors
        clearPosterFileCache();
        return super.runBalancing();
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getPosterInputFile()
     */
    @Override
    public File getPosterInputFile() {
        // avoid running scanning logic on file system
        if (laborPosterInputFile != null) {
            return laborPosterInputFile;
        }

        FilenameFilter filenameFilter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return (name.startsWith(LaborConstants.BatchFileSystem.POSTER_INPUT_FILE) &&
                    name.endsWith(GeneralLedgerConstants.BatchFileSystem.EXTENSION));
            }
        };

        laborPosterInputFile = FileUtil.getNewestFile(new File(batchFileDirectoryName), filenameFilter);

        return laborPosterInputFile;
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getPosterErrorOutputFile()
     */
    @Override
    public File getPosterErrorOutputFile() {
        // avoid running scanning logic on file system
        if (laborPosterErrorOutputFile != null) {
            return laborPosterErrorOutputFile;
        }

        FilenameFilter filenameFilter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return (name.startsWith(LaborConstants.BatchFileSystem.POSTER_ERROR_OUTPUT_FILE) &&
                    name.endsWith(GeneralLedgerConstants.BatchFileSystem.EXTENSION));
            }
        };

        laborPosterErrorOutputFile = FileUtil.getNewestFile(new File(batchFileDirectoryName), filenameFilter);

        return laborPosterErrorOutputFile;
    }


    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getPastFiscalYearsToConsider()
     */
    @Override
    public int getPastFiscalYearsToConsider() {
        return Integer.parseInt(parameterService.getParameterValueAsString(LaborBalancingStep.class, LaborConstants.Balancing.NUMBER_OF_PAST_FISCAL_YEARS_TO_INCLUDE));
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getComparisonFailuresToPrintPerReport()
     */
    @Override
    public int getComparisonFailuresToPrintPerReport() {
        return Integer.parseInt(parameterService.getParameterValueAsString(LaborBalancingStep.class, LaborConstants.Balancing.NUMBER_OF_COMPARISON_FAILURES_TO_PRINT_PER_REPORT));
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getShortTableLabel(java.lang.String)
     */
    @Override
    public String getShortTableLabel(String businessObjectName) {
        Map<String, String> names = new HashMap<String, String>();
        names.put((Entry.class).getSimpleName(), kualiConfigurationService.getPropertyValueAsString(LaborKeyConstants.Balancing.REPORT_ENTRY_LABEL));
        names.put((LaborEntryHistory.class).getSimpleName(), kualiConfigurationService.getPropertyValueAsString(LaborKeyConstants.Balancing.REPORT_ENTRY_LABEL));
        names.put((Balance.class).getSimpleName(), kualiConfigurationService.getPropertyValueAsString(LaborKeyConstants.Balancing.REPORT_BALANCE_LABEL));
        names.put((LaborBalanceHistory.class).getSimpleName(), kualiConfigurationService.getPropertyValueAsString(LaborKeyConstants.Balancing.REPORT_BALANCE_LABEL));

        return names.get(businessObjectName) == null ? kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.Balancing.REPORT_UNKNOWN_LABEL) : names.get(businessObjectName);
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getOriginEntry(java.lang.String, int)
     */
    @Override
    public OriginEntryInformation getOriginEntry(String inputLine, int lineNumber) {
        LaborOriginEntry originEntry = new LaborOriginEntry();
        originEntry.setFromTextFileForBatch(inputLine, lineNumber);

        return originEntry;
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.impl.BalancingServiceBaseImpl#updateHistoriesHelper(java.lang.Integer, java.lang.Integer, java.io.File, java.io.File)
     */
    @Override
    protected int updateHistoriesHelper(Integer postMode, Integer startUniversityFiscalYear, File inputFile, File errorFile) {
        if (postMode == PosterService.MODE_ENTRIES) {
            return super.updateHistoriesHelper(postMode, startUniversityFiscalYear, inputFile, errorFile);
        }
        return 0;
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#updateEntryHistory(org.kuali.kfs.gl.businessobject.OriginEntryInformation)
     * @see org.kuali.kfs.module.ld.batch.service.impl.LaborPosterServiceImpl#postAsLedgerEntry(org.kuali.kfs.gl.businessobject.Transaction, int, java.util.Date)
     */
    @Override
    public void updateEntryHistory(Integer postMode, OriginEntryInformation originEntry) {
        if (postMode == PosterService.MODE_ENTRIES) {
            // TODO Retrieve and update 1 by 1? Is a HashMap or cache better so that storing only occurs once at the end?
            LaborOriginEntry laborOriginEntry = (LaborOriginEntry) originEntry;
            LaborEntryHistory ledgerEntryHistory = new LaborEntryHistory(laborOriginEntry);

            LaborEntryHistory retrievedLedgerEntryHistory = (LaborEntryHistory) businessObjectService.retrieve(ledgerEntryHistory);
            if (ObjectUtils.isNotNull(retrievedLedgerEntryHistory)) {
                ledgerEntryHistory = retrievedLedgerEntryHistory;
            }

            ledgerEntryHistory.addAmount(laborOriginEntry.getTransactionLedgerEntryAmount());

            businessObjectService.save(ledgerEntryHistory);
        }
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#updateBalanceHistory(org.kuali.kfs.gl.businessobject.OriginEntryInformation)
     * @see org.kuali.kfs.module.ld.batch.service.impl.LaborPosterServiceImpl#updateLedgerBalance(org.kuali.kfs.gl.businessobject.Transaction, int, java.util.Date)
     */
    @Override
    public void updateBalanceHistory(Integer postMode, OriginEntryInformation originEntry) {
        if (postMode == PosterService.MODE_ENTRIES) {
            // TODO Retrieve and update 1 by 1? Is a HashMap or cache better so that storing only occurs once at the end?
            LaborOriginEntry laborOriginEntry = (LaborOriginEntry) originEntry;
            LaborBalanceHistory ledgerBalanceHistory = new LaborBalanceHistory(laborOriginEntry);

            LaborBalanceHistory retrievedLedgerBalanceHistory = (LaborBalanceHistory) businessObjectService.retrieve(ledgerBalanceHistory);
            if (ObjectUtils.isNotNull(retrievedLedgerBalanceHistory)) {
                ledgerBalanceHistory = retrievedLedgerBalanceHistory;
            }

            // Make sure the amount update properly recognized debit / credit logic. This is modeled after
            // LaborPosterServiceImpl#updateLedgerBalance
            KualiDecimal amount = laborOriginEntry.getTransactionLedgerEntryAmount();
            laborOriginEntry.refreshReferenceObject(KFSPropertyConstants.BALANCE_TYPE);
            laborOriginEntry.refreshReferenceObject(KFSPropertyConstants.OBJECT_TYPE);
            if (laborOriginEntry.getBalanceType().isFinancialOffsetGenerationIndicator()) {
                if (!laborOriginEntry.getTransactionDebitCreditCode().equals(laborOriginEntry.getObjectType().getFinObjectTypeDebitcreditCd())) {
                    amount = amount.negated();
                }
            }

            ledgerBalanceHistory.addAmount(laborOriginEntry.getUniversityFiscalPeriodCode(), amount);

            businessObjectService.save(ledgerBalanceHistory);
        }
    }

    /**
     * Compares entries in the Balance and BalanceHistory tables to ensure the amounts match.
     *
     * @return count is compare failures
     */
    @Override
    protected Integer compareBalanceHistory() {
        Integer countComparisionFailures = 0;

        String balanceTable = persistenceStructureService.getTableName(LedgerBalance.class);
        String historyTable = persistenceStructureService.getTableName(balanceHistoryPersistentClass);

        List<LedgerBalance> data = ledgerEntryBalanceCachingDao.compareBalanceHistory(balanceTable, historyTable, getFiscalYear());

        if (!data.isEmpty()) {
            for (Iterator<LedgerBalance> itr = data.iterator(); itr.hasNext(); ) {
                LaborBalanceHistory balance = createBalanceFromMap((Map<String, Object>) itr.next());
                countComparisionFailures++;
                if (countComparisionFailures <= this.getComparisonFailuresToPrintPerReport()) {
                    reportWriterService.writeError(balance, new Message(kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.Balancing.MESSAGE_BATCH_BALANCING_RECORD_FAILED_BALANCING), Message.TYPE_WARNING, balance.getClass().getSimpleName()));
                }
            }
        }

        return countComparisionFailures;
    }

    /**
     * Compares entries in the Entry and EntryHistory tables to ensure the amounts match.
     *
     * @return count is compare failures
     */
    @Override
    protected Integer compareEntryHistory() {
        Integer countComparisionFailures = 0;

        String entryTable = persistenceStructureService.getTableName(LedgerEntry.class);
        String historyTable = persistenceStructureService.getTableName(entryHistoryPersistentClass);

        List<LedgerEntry> data = ledgerEntryBalanceCachingDao.compareEntryHistory(entryTable, historyTable, getFiscalYear());

        if (!data.isEmpty()) {
            for (Iterator<LedgerEntry> itr = data.iterator(); itr.hasNext(); ) {
                LaborEntryHistory entry = createEntryHistoryFromMap((Map<String, Object>) itr.next());
                countComparisionFailures++;
                if (countComparisionFailures <= this.getComparisonFailuresToPrintPerReport()) {
                    reportWriterService.writeError(entry, new Message(kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.Balancing.MESSAGE_BATCH_BALANCING_RECORD_FAILED_BALANCING), Message.TYPE_WARNING, entry.getClass().getSimpleName()));
                }

            }
        }

        return countComparisionFailures;
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#clearBalanceHistory()
     */

    @Override
    public void clearHistories() {
        Map<String, Object> fieldValues = new HashMap<String, Object>();
        businessObjectService.deleteMatching(LaborEntryHistory.class, fieldValues);
        businessObjectService.deleteMatching(LaborBalanceHistory.class, fieldValues);

        reportWriterService.writeFormattedMessageLine(kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.Balancing.MESSAGE_BATCH_BALANCING_HISTORY_PURGED));
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getFilenames()
     */
    @Override
    public String getFilenames() {
        return (this.laborPosterInputFile == null ? null : this.laborPosterInputFile.getName()) + "\n"
            + (this.laborPosterErrorOutputFile == null ? null : this.laborPosterErrorOutputFile.getName());
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getBalance(org.kuali.kfs.gl.businessobject.LedgerBalanceHistory)
     */
    @Override
    public Balance getBalance(LedgerBalanceHistory ledgerBalanceHistory) {
        LedgerBalance ledgerBalance = new LedgerBalance((LaborBalanceHistory) ledgerBalanceHistory);
        return (LedgerBalance) businessObjectService.retrieve(ledgerBalance);
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#clearPosterFileCache()
     */
    @Override
    public void clearPosterFileCache() {
        this.laborPosterInputFile = null;
        this.laborPosterErrorOutputFile = null;
    }

    protected LaborBalanceHistory createBalanceFromMap(Map<String, Object> map) {
        LaborBalanceHistory balance = new LaborBalanceHistory();
        balance.setUniversityFiscalYear(((BigDecimal) (map.get(GeneralLedgerConstants.ColumnNames.UNIVERSITY_FISCAL_YEAR))).intValue());
        balance.setChartOfAccountsCode((String) map.get(GeneralLedgerConstants.ColumnNames.CHART_OF_ACCOUNTS_CODE));
        balance.setAccountNumber((String) map.get(GeneralLedgerConstants.ColumnNames.ACCOUNT_NUMBER));
        balance.setSubAccountNumber((String) map.get(GeneralLedgerConstants.ColumnNames.SUB_ACCOUNT_NUMBER));
        balance.setObjectCode((String) map.get(GeneralLedgerConstants.ColumnNames.OBJECT_CODE));
        balance.setSubObjectCode((String) map.get(GeneralLedgerConstants.ColumnNames.SUB_OBJECT_CODE));
        balance.setBalanceTypeCode((String) map.get(GeneralLedgerConstants.ColumnNames.BALANCE_TYPE_CODE));
        balance.setObjectTypeCode((String) map.get(GeneralLedgerConstants.ColumnNames.OBJECT_TYPE_CODE));
        balance.setEmplid((String) map.get(LaborConstants.ColumnNames.EMPLOYEE_IDENTIFIER));
        balance.setPositionNumber((String) map.get(LaborConstants.ColumnNames.POSITION_NUMBER));

        balance.setAccountLineAnnualBalanceAmount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.ACCOUNTING_LINE_ACTUALS_BALANCE_AMOUNT)));
        balance.setContractsGrantsBeginningBalanceAmount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.CONTRACT_AND_GRANTS_BEGINNING_BALANCE)));
        balance.setBeginningBalanceLineAmount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.BEGINNING_BALANCE)));
        balance.setMonth1Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_1_ACCT_AMT)));
        balance.setMonth2Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_2_ACCT_AMT)));
        balance.setMonth3Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_3_ACCT_AMT)));
        balance.setMonth4Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_4_ACCT_AMT)));
        balance.setMonth5Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_5_ACCT_AMT)));
        balance.setMonth6Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_6_ACCT_AMT)));
        balance.setMonth7Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_7_ACCT_AMT)));
        balance.setMonth8Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_8_ACCT_AMT)));
        balance.setMonth9Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_9_ACCT_AMT)));
        balance.setMonth10Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_10_ACCT_AMT)));
        balance.setMonth11Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_11_ACCT_AMT)));
        balance.setMonth12Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_12_ACCT_AMT)));
        balance.setMonth13Amount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.MONTH_13_ACCT_AMT)));

        return balance;

    }

    protected LaborEntryHistory createEntryHistoryFromMap(Map<String, Object> map) {
        LaborEntryHistory entry = new LaborEntryHistory();
        entry.setUniversityFiscalYear(((BigDecimal) (map.get(GeneralLedgerConstants.ColumnNames.UNIVERSITY_FISCAL_YEAR))).intValue());
        entry.setChartOfAccountsCode((String) map.get(GeneralLedgerConstants.ColumnNames.CHART_OF_ACCOUNTS_CODE));
        entry.setFinancialObjectCode((String) map.get(GeneralLedgerConstants.ColumnNames.OBJECT_CODE));
        entry.setFinancialBalanceTypeCode((String) map.get(GeneralLedgerConstants.ColumnNames.BALANCE_TYPE_CODE));
        entry.setUniversityFiscalPeriodCode((String) map.get(GeneralLedgerConstants.ColumnNames.FISCAL_PERIOD_CODE));
        entry.setTransactionDebitCreditCode((String) map.get(GeneralLedgerConstants.ColumnNames.TRANSACTION_DEBIT_CREDIT_CD));
        entry.setTransactionLedgerEntryAmount(convertBigDecimalToKualiDecimal((BigDecimal) map.get(GeneralLedgerConstants.ColumnNames.TRANSACTION_LEDGER_ENTRY_AMOUNT)));

        return entry;

    }

    protected KualiDecimal convertBigDecimalToKualiDecimal(BigDecimal biggy) {
        if (ObjectUtils.isNull(biggy)) {
            return new KualiDecimal(0);
        } else {
            return new KualiDecimal(biggy);
        }

    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getReversalInputFile()
     */
    @Override
    public File getReversalInputFile() {
        return null;
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getReversalErrorOutputFile()
     */
    @Override
    public File getReversalErrorOutputFile() {
        return null;
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getICRInputFile()
     */
    @Override
    public File getICRInputFile() {
        return null;
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getICRErrorOutputFile()
     */
    @Override
    public File getICRErrorOutputFile() {
        return null;
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getICREncumbranceInputFile()
     */
    @Override
    public File getICREncumbranceInputFile() {
        return null;
    }

    /**
     * @see org.kuali.kfs.gl.batch.service.BalancingService#getICREncumbranceErrorOutputFile()
     */
    @Override
    public File getICREncumbranceErrorOutputFile() {
        return null;
    }

}
