/*
 * 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.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.AccountingPeriod;
import org.kuali.kfs.coa.businessobject.SubAccount;
import org.kuali.kfs.coa.service.AccountService;
import org.kuali.kfs.coa.service.BalanceTypeService;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.gl.batch.ScrubberStep;
import org.kuali.kfs.gl.batch.service.AccountingCycleCachingService;
import org.kuali.kfs.gl.businessobject.OriginEntryFull;
import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
import org.kuali.kfs.gl.service.ScrubberValidator;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.PersistenceService;
import org.kuali.kfs.krad.service.PersistenceStructureService;
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.LaborScrubberStep;
import org.kuali.kfs.module.ld.batch.service.LaborAccountingCycleCachingService;
import org.kuali.kfs.module.ld.businessobject.LaborObject;
import org.kuali.kfs.module.ld.businessobject.LaborOriginEntry;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.Message;
import org.kuali.kfs.sys.MessageBuilder;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
import org.kuali.kfs.sys.businessobject.SystemOptions;
import org.kuali.kfs.sys.businessobject.UniversityDate;
import org.kuali.kfs.sys.service.OptionsService;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.rice.core.api.config.property.ConfigurationService;

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

/**
 * Service implementation of ScrubberValidator.
 */
public class ScrubberValidatorImpl implements ScrubberValidator {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ScrubberValidatorImpl.class);

    private ConfigurationService kualiConfigurationService;
    private BusinessObjectService businessObjectService;
    private ParameterService parameterService;
    private AccountService accountService;
    private BalanceTypeService balanceTypService;
    private OptionsService optionsService;

    private PersistenceService persistenceService;
    private ScrubberValidator scrubberValidator;
    private PersistenceStructureService persistenceStructureService;
    private boolean continuationAccountIndicator;


    /**
     * @see org.kuali.module.labor.service.LaborScrubberValidator#validateTransaction(owrg.kuali.module.labor.bo.LaborOriginEntry,
     * org.kuali.kfs.module.ld.businessobject.LaborOriginEntry, org.kuali.kfs.gl.businessobject.UniversityDate)
     */
    public List<Message> validateTransaction(OriginEntryInformation originEntry, OriginEntryInformation scrubbedEntry, UniversityDate universityRunDate, boolean laborIndicator, AccountingCycleCachingService laborAccountingCycleCachingService) {
        LOG.debug("validateTransaction() started");
        List<Message> errors = new ArrayList<Message>();
        continuationAccountIndicator = false;

        LaborOriginEntry laborOriginEntry = (LaborOriginEntry) originEntry;
        LaborOriginEntry laborScrubbedEntry = (LaborOriginEntry) scrubbedEntry;

        // gl scrubber validation
        errors = scrubberValidator.validateTransaction(laborOriginEntry, laborScrubbedEntry, universityRunDate, laborIndicator, laborAccountingCycleCachingService);

        refreshOriginEntryReferences(laborOriginEntry);
        refreshOriginEntryReferences(laborScrubbedEntry);

        if (StringUtils.isBlank(laborOriginEntry.getEmplid())) {
            laborScrubbedEntry.setEmplid(LaborConstants.getDashEmplId());
        }

        if (StringUtils.isBlank(laborOriginEntry.getPositionNumber())) {
            laborScrubbedEntry.setPositionNumber(LaborConstants.getDashPositionNumber());
        }

        Message err = null;

        //this validation is duplicated.  This is in ScrubberValidatorImpl under GL
//        err = this.validateClosedPeriodCode(laborOriginEntry, laborScrubbedEntry);
//        if (err != null) {
//            errors.add(err);
//        }

        err = validatePayrollEndFiscalYear(laborOriginEntry, laborScrubbedEntry, universityRunDate, (LaborAccountingCycleCachingService) laborAccountingCycleCachingService);
        if (err != null) {
            errors.add(err);
        }

        err = validatePayrollEndFiscalPeriodCode(laborOriginEntry, laborScrubbedEntry, universityRunDate, (LaborAccountingCycleCachingService) laborAccountingCycleCachingService);
        if (err != null) {
            errors.add(err);
        }

        err = validateAccount(laborOriginEntry, laborScrubbedEntry, universityRunDate, (LaborAccountingCycleCachingService) laborAccountingCycleCachingService);
        if (err != null) {
            errors.add(err);
        }

        err = validateSubAccount(laborOriginEntry, laborScrubbedEntry, (LaborAccountingCycleCachingService) laborAccountingCycleCachingService);
        if (err != null) {
            errors.add(err);
        }

        return errors;
    }

    /**
     * This method is for refreshing References of Origin Entry
     */
    protected void refreshOriginEntryReferences(OriginEntryFull originEntry) {
        Map<String, Class> referenceClasses = persistenceStructureService.listReferenceObjectFields(originEntry.getClass());
        for (String reference : referenceClasses.keySet()) {
            if (KFSPropertyConstants.PROJECT.equals(reference)) {
                if (KFSConstants.getDashProjectCode().equals(originEntry.getProjectCode())) {
                    originEntry.setProject(null);
                } else {
                    persistenceService.retrieveReferenceObject(originEntry, reference);
                }
            } else if (KFSPropertyConstants.FINANCIAL_SUB_OBJECT.equals(reference)) {
                if (KFSConstants.getDashFinancialSubObjectCode().equals(originEntry.getFinancialSubObjectCode())) {
                    originEntry.setFinancialSubObject(null);
                } else {
                    persistenceService.retrieveReferenceObject(originEntry, reference);
                }
            } else if (KFSPropertyConstants.SUB_ACCOUNT.equals(reference)) {
                if (KFSConstants.getDashSubAccountNumber().equals(originEntry.getSubAccountNumber())) {
                    originEntry.setSubAccount(null);
                } else {
                    persistenceService.retrieveReferenceObject(originEntry, reference);
                }
            } else {
                persistenceService.retrieveReferenceObject(originEntry, reference);
            }
        }
    }

    /**
     * Validates the closed period code of the origin entry. Scrubber accepts closed fiscal periods for the specified balance type.
     *
     * @param originEntry the origin entry being scrubbed
     * @param workingEntry the scrubbed version of the origin entry
     * @return a Message if an error was encountered, otherwise null
     */
    //this validation is duplicated.  This is in ScrubberValidatorImpl under GL
//    protected Message validateClosedPeriodCode(LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry) {
//        LOG.debug("validateClosedPeriodCode() started");
//
//        String periodCode = laborOriginEntry.getUniversityFiscalPeriodCode();
//        if (StringUtils.isBlank(periodCode)) {
//            return null;
//        }
//
//        // Scrubber accepts closed fiscal periods for A21 Balance
//        AccountingPeriod accountingPeriod = referenceLookup.get().getAccountingPeriod(laborOriginEntry);
//        if (ObjectUtils.isNotNull(accountingPeriod) && !accountingPeriod.isActive()) {
//            String bypassBalanceType = parameterService.getParameterValueAsString(LaborScrubberStep.class, LaborConstants.Scrubber.CLOSED_FISCAL_PERIOD_BYPASS_BALANCE_TYPES);
//
//            if (!laborWorkingEntry.getFinancialBalanceTypeCode().equals(bypassBalanceType)) {
//                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_FISCAL_PERIOD_CLOSED, periodCode, Message.TYPE_FATAL);
//            }
//
//            laborWorkingEntry.setUniversityFiscalPeriodCode(periodCode);
//        }
//
//        return null;
//    }

    /**
     * This method is for validation of payrollEndFiscalYear
     */
    protected Message validatePayrollEndFiscalYear(LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, UniversityDate universityRunDate, LaborAccountingCycleCachingService laborAccountingCycleCachingService) {
        LOG.debug("validatePayrollEndFiscalYear() started");
        SystemOptions scrubbedEntryOption = null;
        if (laborOriginEntry.getPayrollEndDateFiscalYear() != null) {
            scrubbedEntryOption = laborAccountingCycleCachingService.getSystemOptions(laborOriginEntry.getPayrollEndDateFiscalYear());

            if (scrubbedEntryOption == null) {
                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_PAYROLL_END_DATE_FISCAL_YEAR, "" + laborOriginEntry.getPayrollEndDateFiscalYear(), Message.TYPE_FATAL);
            }

        }

        return null;
    }

    /**
     * This method is for validation of PayrollEndFiscalPeriodCode
     */
    protected Message validatePayrollEndFiscalPeriodCode(LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, UniversityDate universityRunDate, LaborAccountingCycleCachingService laborAccountingCycleCachingService) {
        LOG.debug("validateUniversityFiscalPeriodCode() started");

        AccountingPeriod accountingPeriod = null;
        Integer tempPayrollFiscalYear = 0;
        if (laborOriginEntry.getPayrollEndDateFiscalYear() == null) {
            tempPayrollFiscalYear = universityRunDate.getUniversityFiscalYear();
        } else {
            tempPayrollFiscalYear = laborOriginEntry.getPayrollEndDateFiscalYear();
        }

        if (!laborOriginEntry.getPayrollEndDateFiscalPeriodCode().equals("")) {
            accountingPeriod = laborAccountingCycleCachingService.getAccountingPeriod(tempPayrollFiscalYear, laborOriginEntry.getPayrollEndDateFiscalPeriodCode());
            if (accountingPeriod == null) {
                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_PAYROLL_END_DATE_FISCAL_PERIOD, laborOriginEntry.getPayrollEndDateFiscalPeriodCode(), Message.TYPE_FATAL);
            }
        }


        return null;
    }

    /**
     * Performs Account Validation.
     */
    protected Message validateAccount(LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, UniversityDate universityRunDate, LaborAccountingCycleCachingService laborAccountingCycleCachingService) {
        LOG.debug("validateAccount() started");

        Account account = laborOriginEntry.getAccount();
        boolean suspenseAccountLogicInd = parameterService.getParameterValueAsBoolean(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_ACCOUNT_LOGIC_PARAMETER);
        if (ObjectUtils.isNull(account)) {
            if (suspenseAccountLogicInd) {
                return useSuspenseAccount(laborWorkingEntry);
            }
            return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ACCOUNT_NOT_FOUND, laborOriginEntry.getChartOfAccountsCode() + "-" + laborOriginEntry.getAccountNumber(), Message.TYPE_FATAL);
        }

        // default
        laborWorkingEntry.setAccount(account);
        laborWorkingEntry.setChartOfAccountsCode(account.getChartOfAccountsCode());
        laborWorkingEntry.setAccountNumber(account.getAccountNumber());

        // no further validation for gl annual doc type
        String glAnnualClosingType = parameterService.getParameterValueAsString(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ANNUAL_CLOSING_DOC_TYPE);
        if (glAnnualClosingType.equals(laborOriginEntry.getFinancialDocumentTypeCode())) {
            return null;
        }

        // Sub-Fund Wage Exclusion
        String orginationCode = laborOriginEntry.getFinancialSystemOriginationCode();
        List<String> nonWageSubfundBypassOriginationCodes = new ArrayList<String>(parameterService.getParameterValuesAsString(LaborScrubberStep.class, LaborConstants.Scrubber.NON_WAGE_SUB_FUND_BYPASS_ORIGINATIONS));
        boolean subfundWageExclusionInd = parameterService.getParameterValueAsBoolean(LaborScrubberStep.class, LaborConstants.Scrubber.SUBFUND_WAGE_EXCLUSION_PARAMETER);

        if (subfundWageExclusionInd && !account.getSubFundGroup().isSubFundGroupWagesIndicator() && !nonWageSubfundBypassOriginationCodes.contains(orginationCode)) {
            if (suspenseAccountLogicInd) {
                return useSuspenseAccount(laborWorkingEntry);
            }

            return MessageBuilder.buildMessage(LaborKeyConstants.ERROR_SUN_FUND_NOT_ACCEPT_WAGES, Message.TYPE_FATAL);
        }

        // Account Fringe Validation
        List<String> nonFringeAccountBypassOriginationCodes = new ArrayList<String>(parameterService.getParameterValuesAsString(LaborScrubberStep.class, LaborConstants.Scrubber.NON_FRINGE_ACCOUNT_BYPASS_ORIGINATIONS));
        boolean accountFringeExclusionInd = parameterService.getParameterValueAsBoolean(LaborScrubberStep.class, LaborConstants.Scrubber.ACCOUNT_FRINGE_EXCLUSION_PARAMETER);

        if (accountFringeExclusionInd && !nonFringeAccountBypassOriginationCodes.contains(orginationCode)) {
            return checkAccountFringeIndicator(laborOriginEntry, laborWorkingEntry, account, universityRunDate, laborAccountingCycleCachingService);
        }

        // Expired/Closed Validation
        return handleExpiredClosedAccount(laborOriginEntry.getAccount(), laborOriginEntry, laborWorkingEntry, universityRunDate);
    }

    /**
     * Checks the continuation account system indicator. If on checks whether the account is expired or closed, and if so calls the
     * contination logic.
     */
    protected Message handleExpiredClosedAccount(Account account, LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, UniversityDate universityRunDate) {
        List<String> continuationAccountBypassBalanceTypeCodes = balanceTypService.getContinuationAccountBypassBalanceTypeCodes(universityRunDate.getUniversityFiscalYear());
        List<String> continuationAccountBypassOriginationCodes = new ArrayList<String>(parameterService.getParameterValuesAsString(LaborScrubberStep.class, LaborConstants.Scrubber.CONTINUATION_ACCOUNT_BYPASS_ORIGINATION_CODES));
        List<String> continuationAccountBypassDocumentTypeCodes = new ArrayList<String>(parameterService.getParameterValuesAsString(LaborScrubberStep.class, LaborConstants.Scrubber.CONTINUATION_ACCOUNT_BYPASS_DOCUMENT_TYPE_CODES));

        Calendar today = Calendar.getInstance();
        today.setTime(universityRunDate.getUniversityDate());

        long offsetAccountExpirationTime = getAdjustedAccountExpirationDate(account);
        boolean isAccountExpiredOrClosed = (account.getAccountExpirationDate() != null && isAccountExpired(account, universityRunDate)) || !account.isActive();
        boolean continuationAccountLogicInd = parameterService.getParameterValueAsBoolean(LaborScrubberStep.class, LaborConstants.Scrubber.CONTINUATION_ACCOUNT_LOGIC_PARAMETER);

        if (continuationAccountLogicInd && isAccountExpiredOrClosed) {
            // special checks for origination codes that have override ability
            boolean isOverrideOriginCode = continuationAccountBypassOriginationCodes.contains(laborOriginEntry.getFinancialSystemOriginationCode());
            if (isOverrideOriginCode && !account.isActive()) {
                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ORIGIN_CODE_CANNOT_HAVE_CLOSED_ACCOUNT, laborOriginEntry.getChartOfAccountsCode() + "-" + laborOriginEntry.getAccountNumber(), Message.TYPE_FATAL);
            }

            boolean canBypass = isOverrideOriginCode || continuationAccountBypassBalanceTypeCodes.contains(laborOriginEntry.getFinancialBalanceTypeCode()) || continuationAccountBypassDocumentTypeCodes.contains(laborOriginEntry.getFinancialDocumentTypeCode().trim());
            if (account.isActive() && canBypass) {
                return null;
            }

            return continuationAccountLogic(account, laborOriginEntry, laborWorkingEntry, universityRunDate);
        }

        return null;
    }

    /**
     * Loops through continuation accounts for 10 tries or until it finds an account that is not expired.
     */
    protected Message continuationAccountLogic(Account expiredClosedAccount, LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, UniversityDate universityRunDate) {
        String chartCode = expiredClosedAccount.getContinuationFinChrtOfAcctCd();
        String accountNumber = expiredClosedAccount.getContinuationAccountNumber();

        List<String> checkedAccountNumbers = new ArrayList<String>();
        for (int i = 0; i < 10; ++i) {
            if (checkedAccountNumbers.contains(chartCode + accountNumber)) {
                // Something is really wrong with the data because this account has already been evaluated.
                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CIRCULAR_DEPENDENCY_IN_CONTINUATION_ACCOUNT_LOGIC, Message.TYPE_FATAL);
            }

            checkedAccountNumbers.add(chartCode + accountNumber);

            if (chartCode == null || accountNumber == null) {
                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CONTINUATION_ACCOUNT_NOT_FOUND, Message.TYPE_FATAL);
            }

            // Lookup the account
            Account account = accountService.getByPrimaryId(chartCode, accountNumber);
            if (ObjectUtils.isNull(account)) {
                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CONTINUATION_ACCOUNT_NOT_FOUND, Message.TYPE_FATAL);
            }

            // check account expiration
            long offsetAccountExpirationTime = getAdjustedAccountExpirationDate(account);
            if (ObjectUtils.isNotNull(account.getAccountExpirationDate()) && isAccountExpired(account, universityRunDate)) {
                chartCode = account.getContinuationFinChrtOfAcctCd();
                accountNumber = account.getContinuationAccountNumber();
            } else {

                // set continuationAccountLogicIndi
                continuationAccountIndicator = true;

                laborWorkingEntry.setAccount(account);
                laborWorkingEntry.setAccountNumber(accountNumber);
                laborWorkingEntry.setChartOfAccountsCode(chartCode);
                laborWorkingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
                laborWorkingEntry.setTransactionLedgerEntryDescription(kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.MSG_AUTO_FORWARD) + " " + expiredClosedAccount.getChartOfAccountsCode() + expiredClosedAccount.getAccountNumber() + laborOriginEntry.getTransactionLedgerEntryDescription());

                return MessageBuilder.buildMessage(KFSKeyConstants.MSG_ACCOUNT_CLOSED_TO, laborWorkingEntry.getChartOfAccountsCode() + "-" + laborWorkingEntry.getAccountNumber(), Message.TYPE_WARNING);
            }
        }

        // We failed to find a valid continuation account.
        boolean suspenseAccountLogicInd = parameterService.getParameterValueAsBoolean(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_ACCOUNT_LOGIC_PARAMETER);
        if (suspenseAccountLogicInd) {
            return useSuspenseAccount(laborWorkingEntry);
        } else {
            return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CONTINUATION_ACCOUNT_LIMIT_REACHED, Message.TYPE_FATAL);
        }
    }

    /**
     * For fringe transaction types checks if the account accepts fringe benefits. If not, retrieves the alternative account, then
     * calls expiration checking on either the alternative account or the account passed in.
     */
    protected Message checkAccountFringeIndicator(LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, Account account, UniversityDate universityRunDate, LaborAccountingCycleCachingService laborAccountingCycleCachingService) {
        // check for fringe tranaction type
        //LaborObject laborObject = (LaborObject) businessObjectService.findByPrimaryKey(LaborObject.class, fieldValues);
        LaborObject laborObject = laborAccountingCycleCachingService.getLaborObject(laborOriginEntry.getUniversityFiscalYear(), laborOriginEntry.getChartOfAccountsCode(), laborOriginEntry.getFinancialObjectCode());
        boolean isFringeTransaction = laborObject != null && org.apache.commons.lang.StringUtils.equals(LaborConstants.BenefitExpenseTransfer.LABOR_LEDGER_BENEFIT_CODE, laborObject.getFinancialObjectFringeOrSalaryCode());

        // alternative account handling for non fringe accounts
        if (isFringeTransaction && !account.isAccountsFringesBnftIndicator()) {
            Account altAccount = accountService.getByPrimaryId(laborOriginEntry.getAccount().getReportsToChartOfAccountsCode(), laborOriginEntry.getAccount().getReportsToAccountNumber());
            if (ObjectUtils.isNotNull(altAccount)) {
                laborWorkingEntry.setAccount(altAccount);
                laborWorkingEntry.setAccountNumber(altAccount.getAccountNumber());
                laborWorkingEntry.setChartOfAccountsCode(altAccount.getChartOfAccountsCode());
                Message err = handleExpiredClosedAccount(altAccount, laborOriginEntry, laborWorkingEntry, universityRunDate);
                if (err == null) {
                    err = MessageBuilder.buildMessageWithPlaceHolder(LaborKeyConstants.MESSAGE_FRINGES_MOVED_TO, Message.TYPE_WARNING, new Object[]{altAccount.getAccountNumber()});
                }
                return err;
            }

            // no alt acct, use suspense acct if active
            boolean suspenseAccountLogicInd = parameterService.getParameterValueAsBoolean(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_ACCOUNT_LOGIC_PARAMETER);
            if (suspenseAccountLogicInd) {
                return useSuspenseAccount(laborWorkingEntry);
            }

            return MessageBuilder.buildMessage(LaborKeyConstants.ERROR_NON_FRINGE_ACCOUNT_ALTERNATIVE_NOT_FOUND, Message.TYPE_FATAL);
        }

        return handleExpiredClosedAccount(account, laborOriginEntry, laborWorkingEntry, universityRunDate);
    }

    /**
     * Adjustment of Account if it is contracts and grants
     */
    protected long getAdjustedAccountExpirationDate(Account account) {
        long offsetAccountExpirationTime = 0;

        if (account.getAccountExpirationDate() != null) {
            offsetAccountExpirationTime = account.getAccountExpirationDate().getTime();

            if (account.isForContractsAndGrants() && account.isActive()) {
                String daysOffset = parameterService.getParameterValueAsString(ScrubberStep.class, KFSConstants.SystemGroupParameterNames.GL_SCRUBBER_VALIDATION_DAYS_OFFSET);
                int daysOffsetInt = 0; // default to 0

                if (!org.apache.commons.lang.StringUtils.isBlank(daysOffset)) {
                    daysOffsetInt = new Integer(daysOffset).intValue();
                }

                Calendar tempCal = Calendar.getInstance();
                tempCal.setTimeInMillis(offsetAccountExpirationTime);
                tempCal.add(Calendar.DAY_OF_MONTH, daysOffsetInt);
                offsetAccountExpirationTime = tempCal.getTimeInMillis();
            }
        }

        return offsetAccountExpirationTime;
    }

    /**
     * This method changes account to suspenseAccount
     */
    protected Message useSuspenseAccount(LaborOriginEntry workingEntry) {
        String suspenseAccountNumber = parameterService.getParameterValueAsString(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_ACCOUNT);
        String suspenseCOAcode = parameterService.getParameterValueAsString(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_CHART);
        String suspenseSubAccountNumber = parameterService.getParameterValueAsString(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_SUB_ACCOUNT);

        Account account = accountService.getByPrimaryId(suspenseCOAcode, suspenseAccountNumber);

        if (ObjectUtils.isNull(account)) {
            return MessageBuilder.buildMessage(LaborKeyConstants.ERROR_INVALID_SUSPENSE_ACCOUNT, Message.TYPE_FATAL);
        }

        workingEntry.setAccount(account);
        workingEntry.setAccountNumber(suspenseAccountNumber);
        workingEntry.setChartOfAccountsCode(suspenseCOAcode);
        workingEntry.setSubAccountNumber(suspenseSubAccountNumber);

        return MessageBuilder.buildMessageWithPlaceHolder(LaborKeyConstants.MESSAGE_SUSPENSE_ACCOUNT_APPLIED, Message.TYPE_WARNING, new Object[]{suspenseCOAcode, suspenseAccountNumber, suspenseSubAccountNumber});
    }

    /**
     * Validates the sub account of the origin entry
     *
     * @param originEntry  the origin entry being scrubbed
     * @param workingEntry the scrubbed version of the origin entry
     * @return a Message if an error was encountered, otherwise null
     */

    protected Message validateSubAccount(LaborOriginEntry originEntry, LaborOriginEntry workingEntry, LaborAccountingCycleCachingService laborAccountingCycleCachingService) {
        LOG.debug("validateSubAccount() started");

        // when continuationAccount used, the subAccountNumber should be changed to dashes and skip validation subAccount process
        if (continuationAccountIndicator) {
            workingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
            return null;
        }

        // If the sub account number is empty, set it to dashes.
        // Otherwise set the workingEntry sub account number to the
        // sub account number of the input origin entry.
        if (org.springframework.util.StringUtils.hasText(originEntry.getSubAccountNumber())) {
            // sub account IS specified
            if (!KFSConstants.getDashSubAccountNumber().equals(originEntry.getSubAccountNumber())) {
                SubAccount originEntrySubAccount = laborAccountingCycleCachingService.getSubAccount(originEntry.getChartOfAccountsCode(), originEntry.getAccountNumber(), originEntry.getSubAccountNumber());
                //SubAccount originEntrySubAccount = getSubAccount(originEntry);
                if (originEntrySubAccount == null) {
                    // sub account is not valid
                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SUB_ACCOUNT_NOT_FOUND, originEntry.getChartOfAccountsCode() + "-" + originEntry.getAccountNumber() + "-" + originEntry.getSubAccountNumber(), Message.TYPE_FATAL);
                } else {
                    // sub account IS valid
                    if (originEntrySubAccount.isActive()) {
                        // sub account IS active
                        workingEntry.setSubAccountNumber(originEntry.getSubAccountNumber());
                    } else {
                        // sub account IS NOT active
                        if (parameterService.getParameterValueAsString(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ANNUAL_CLOSING_DOC_TYPE).equals(originEntry.getFinancialDocumentTypeCode())) {
                            // document IS annual closing
                            workingEntry.setSubAccountNumber(originEntry.getSubAccountNumber());
                        } else {
                            // document is NOT annual closing
                            return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SUB_ACCOUNT_NOT_ACTIVE, originEntry.getChartOfAccountsCode() + "-" + originEntry.getAccountNumber() + "-" + originEntry.getSubAccountNumber(), Message.TYPE_FATAL);
                        }
                    }
                }
            } else {
                // the sub account is dashes
                workingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
            }
        } else {
            // No sub account is specified.
            workingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
        }


        return null;

    }


    /**
     * @see org.kuali.kfs.gl.service.ScrubberValidator#isAccountExpired(org.kuali.kfs.coa.businessobject.Account, org.kuali.kfs.sys.businessobject.UniversityDate)
     */
    public boolean isAccountExpired(Account account, UniversityDate universityRunDate) {
        return scrubberValidator.isAccountExpired(account, universityRunDate);
    }

    public void validateForInquiry(GeneralLedgerPendingEntry entry) {
    }

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

    /**
     * Sets the kualiConfigurationService attribute value.
     *
     * @param service The kualiConfigurationService to set.
     */
    public void setConfigurationService(ConfigurationService service) {
        kualiConfigurationService = service;
    }

    /**
     * Sets the accountService attribute value.
     *
     * @param as The accountService to set.
     */
    public void setAccountService(AccountService as) {
        accountService = as;
    }

    /**
     * Sets the persistenceService attribute value.
     *
     * @param ps The persistenceService to set.
     */
    public void setPersistenceService(PersistenceService ps) {
        persistenceService = ps;
    }

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

    /**
     * Sets the balanceTypService attribute value.
     *
     * @param balanceTypService The balanceTypService to set.
     */
    public void setBalanceTypService(BalanceTypeService balanceTypService) {
        this.balanceTypService = balanceTypService;
    }

    /**
     * Sets the scrubberValidator attribute value.
     *
     * @param sv The scrubberValidator to set.
     */
    public void setScrubberValidator(ScrubberValidator sv) {
        scrubberValidator = sv;
    }

    /**
     * Sets the persistenceStructureService attribute value.
     *
     * @param persistenceStructureService The persistenceStructureService to set.
     */
    public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
        this.persistenceStructureService = persistenceStructureService;
    }

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


}
