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

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.krad.util.MessageMap;
import org.kuali.kfs.module.bc.BCConstants;
import org.kuali.kfs.module.bc.BCConstants.SynchronizationCheckType;
import org.kuali.kfs.module.bc.BCKeyConstants;
import org.kuali.kfs.module.bc.BCPropertyConstants;
import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding;
import org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService;
import org.kuali.kfs.module.bc.document.service.SalarySettingService;
import org.kuali.kfs.module.bc.service.HumanResourcesPayrollService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.core.api.util.type.KualiInteger;

import java.math.BigDecimal;
import java.util.List;

import static org.kuali.kfs.module.bc.BCConstants.AppointmentFundingDurationCodes.LWPA;
import static org.kuali.kfs.module.bc.BCConstants.AppointmentFundingDurationCodes.LWPF;
import static org.kuali.kfs.module.bc.BCConstants.AppointmentFundingDurationCodes.NONE;

/**
 * provide a set of rule elements for salary setting.
 */
public class SalarySettingRuleHelperServiceImpl implements SalarySettingRuleHelperService {
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SalarySettingRuleHelperServiceImpl.class);

    protected SalarySettingService salarySettingService;
    protected HumanResourcesPayrollService humanResourcesPayrollService;

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#canBeAdjusted(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding, org.kuali.rice.krad.util.MessageMap)
     */
    public boolean canBeAdjusted(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        if (appointmentFunding.getEffectiveCSFTracker() == null) {
            // These error messages should not be displayed since the reported condition is obvious and benign.
            //errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_AMOUNT, BCKeyConstants.ERROR_CANNOT_ADJUST_FUNDING_WITHOUT_EFFECTIVE_CSF_TRACKER);
            return false;
        }

        if (appointmentFunding.isAppointmentFundingDeleteIndicator()) {
            // These error messages should not be displayed since the reported condition is obvious and benign.
            //errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_AMOUNT, BCKeyConstants.ERROR_CANNOT_ADJUST_FUNDING_MARKED_AS_DELETE);
            return false;
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasActiveJob(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.rice.krad.util.MessageMap)
     */
    public boolean hasActiveJob(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap, SynchronizationCheckType synchronizationCheckType) {
        Integer fiscalYear = appointmentFunding.getUniversityFiscalYear();
        String emplid = appointmentFunding.getEmplid();
        String positionNumber = appointmentFunding.getPositionNumber();

        if (synchronizationCheckType.equals(SynchronizationCheckType.EID) && emplid.equals("VACANT")) {
            return true;
        } else {
            boolean hasActiveJob = humanResourcesPayrollService.isActiveJob(emplid, positionNumber, fiscalYear, synchronizationCheckType);
            if (!hasActiveJob) {
                errorMap.putError(KFSConstants.GLOBAL_ERRORS, BCKeyConstants.ERROR_NO_ACTIVE_JOB_FOUND, appointmentFunding.getEmplid(), appointmentFunding.getPositionNumber());
                return false;
            }

            return true;
        }
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasObjectCodeMatchingDefaultOfPosition(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.core.util.MessageMap)
     */
    public boolean hasObjectCodeMatchingDefaultOfPosition(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        String defaultObjectCode = appointmentFunding.getBudgetConstructionPosition().getIuDefaultObjectCode();
        String objectCode = appointmentFunding.getFinancialObjectCode();

        if (!StringUtils.equals(objectCode, defaultObjectCode)) {
            errorMap.putError(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, BCKeyConstants.ERROR_NOT_DEFAULT_OBJECT_CODE, defaultObjectCode);
            return false;
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasRequestedAmountZeroWhenFullYearLeave(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.core.util.MessageMap)
     */
    public boolean hasRequestedAmountZeroWhenFullYearLeave(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        String leaveDurationCode = appointmentFunding.getAppointmentFundingDurationCode();

        // Request Salary Amount must be zero because these leave codes are for full year leave without pay.
        if (StringUtils.equals(leaveDurationCode, LWPA.durationCode) || StringUtils.equals(leaveDurationCode, LWPF.durationCode)) {
            KualiInteger requestedAmount = appointmentFunding.getAppointmentRequestedAmount();

            if (!requestedAmount.isZero()) {
                errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_AMOUNT, BCKeyConstants.ERROR_REQUEST_AMOUNT_NOT_ZERO_WHEN_FULL_YEAR_LEAVE);
                return false;
            }
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasRequestedFteQuantityZeroWhenFullYearLeave(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.core.util.MessageMap)
     */
    public boolean hasRequestedFteQuantityZeroWhenFullYearLeave(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        String leaveDurationCode = appointmentFunding.getAppointmentFundingDurationCode();

        // Request Salary Amount must be zero because these leave codes are for full year leave without pay.
        if (StringUtils.equals(leaveDurationCode, LWPA.durationCode) || StringUtils.equals(leaveDurationCode, LWPF.durationCode)) {
            BigDecimal requestedFteQuantity = appointmentFunding.getAppointmentRequestedFteQuantity();

            if (requestedFteQuantity.compareTo(BigDecimal.ZERO) != 0) {
                errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_FTE_QUANTITY, BCKeyConstants.ERROR_REQUEST_FTE_NOT_ZERO_WHEN_FULL_YEAR_LEAVE);
                return false;
            }

            return true;
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasNoExistingLine(java.util.List,
     * org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding, org.kuali.rice.krad.util.MessageMap)
     */
    public boolean hasNoExistingLine(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings, PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        boolean hasNoExistingLine = salarySettingService.findAppointmentFunding(appointmentFundings, appointmentFunding) == null;
        if (!hasNoExistingLine) {
            errorMap.putError(BCPropertyConstants.NEW_BCAF_LINE, BCKeyConstants.ERROR_DUPLICATE_FUNDING_LINE);
            return false;
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasValidRequestedAmount(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.core.util.MessageMap)
     */
    public boolean hasValidRequestedAmount(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        KualiInteger requestedAmount = appointmentFunding.getAppointmentRequestedAmount();
        if (requestedAmount == null || requestedAmount.isNegative()) {
            errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_AMOUNT, BCKeyConstants.ERROR_REQUESTED_AMOUNT_NONNEGATIVE_REQUIRED);
            return false;
        }

        return true;
    }

    public boolean hasValidRequestedAmountQuickSalarySetting(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {

        if (this.hasValidRequestedAmount(appointmentFunding, errorMap)) {
            KualiInteger requestedAmount = appointmentFunding.getAppointmentRequestedAmount();
            BigDecimal requestedFteQuantity = appointmentFunding.getAppointmentRequestedFteQuantity();
            BigDecimal hourlyRate = appointmentFunding.getAppointmentRequestedPayRate();
            if (requestedAmount.isPositive() && (requestedFteQuantity != null && requestedFteQuantity.compareTo(BigDecimal.ZERO) == 0)) {
                errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_AMOUNT, BCKeyConstants.ERROR_REQUESTED_AMOUNT_NEEDS_FTE_FIRST);
                return false;
            }
            if ((hourlyRate != null && hourlyRate.compareTo(BigDecimal.ZERO) != 0) && (requestedFteQuantity != null && requestedFteQuantity.compareTo(BigDecimal.ZERO) == 0)) {
                errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_PAY_RATE, BCKeyConstants.ERROR_REQUESTED_AMOUNT_NEEDS_FTE_FIRST);
                return false;
            }
        } else {
            return false;
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasValidRequestedCsfAmount(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.core.util.MessageMap)
     */
    public boolean hasValidRequestedCsfAmount(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        KualiInteger csfAmount = appointmentFunding.getAppointmentRequestedCsfAmount();
        String leaveDurationCode = appointmentFunding.getAppointmentFundingDurationCode();

        // Requested csf amount must be greater than 0 if there is a leave
        if (!StringUtils.equals(leaveDurationCode, NONE.durationCode)) {
            if (csfAmount == null || !csfAmount.isPositive()) {
                errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_CSF_AMOUNT, BCKeyConstants.ERROR_FTE_GREATER_THAN_ZERO_REQUIRED);
                return false;
            }

            return true;
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasValidRequestedCsfTimePercent(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.core.util.MessageMap)
     */
    public boolean hasValidRequestedCsfTimePercent(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        BigDecimal csfTimePercent = appointmentFunding.getAppointmentRequestedCsfTimePercent();
        String leaveDurationCode = appointmentFunding.getAppointmentFundingDurationCode();

        // Requested csf amount must be greater than 0 if there is a leave
        if (!StringUtils.equals(leaveDurationCode, NONE.durationCode)) {
            if (csfTimePercent == null || csfTimePercent.compareTo(BigDecimal.ZERO) <= 0 || csfTimePercent.compareTo(BCConstants.ONE_HUNDRED) > 0) {
                errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_CSF_TIME_PERCENT, BCKeyConstants.ERROR_LEAVE_TIME_PERCENT_NOT_IN_RANGE, BigDecimal.ZERO.toPlainString(), BCConstants.ONE_HUNDRED.toPlainString());
                return false;
            }

            return true;
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasValidRequestedFteQuantity(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.core.util.MessageMap)
     */
    public boolean hasValidRequestedFteQuantity(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        BigDecimal requestedFteQuantity = appointmentFunding.getAppointmentRequestedFteQuantity();
        if (requestedFteQuantity == null || requestedFteQuantity.compareTo(BigDecimal.ZERO) < 0 || requestedFteQuantity.compareTo(BigDecimal.ONE) > 0) {
            errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_FTE_QUANTITY, BCKeyConstants.ERROR_FTE_QUANTITY_NOT_IN_RANGE, BigDecimal.ZERO.toPlainString(), BigDecimal.ONE.toPlainString());
            return false;
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasValidRequestedFundingMonth(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.core.util.MessageMap)
     */
    public boolean hasValidRequestedFundingMonth(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        Integer fundingMonths = appointmentFunding.getAppointmentFundingMonth();
        if (fundingMonths == null) {
            errorMap.putError(BCPropertyConstants.APPOINTMENT_FUNDING_MONTH, BCKeyConstants.ERROR_EMPTY_FUNDIN_MONTH);
            return false;
        }

        // Requested funding months must be between 0 and position normal work months if there is a leave
        String leaveDurationCode = appointmentFunding.getAppointmentFundingDurationCode();
        Integer normalWorkMonths = appointmentFunding.getBudgetConstructionPosition().getIuNormalWorkMonths();
        if (!StringUtils.equals(leaveDurationCode, NONE.durationCode)) {
            if (fundingMonths < 0 || fundingMonths > normalWorkMonths) {
                errorMap.putError(BCPropertyConstants.APPOINTMENT_FUNDING_MONTH, BCKeyConstants.ERROR_FUNDIN_MONTH_NOT_IN_RANGE, ObjectUtils.toString(fundingMonths), "0", ObjectUtils.toString(normalWorkMonths));
                return false;
            }

            return true;
        }

        // Requested funding months must equal to position normal work months if no leave
        if (!fundingMonths.equals(normalWorkMonths)) {
            errorMap.putError(BCPropertyConstants.APPOINTMENT_FUNDING_MONTH, BCKeyConstants.ERROR_NOT_EQUAL_NORMAL_WORK_MONTHS, ObjectUtils.toString(fundingMonths), ObjectUtils.toString(normalWorkMonths));
            return false;
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasValidRequestedTimePercent(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.core.util.MessageMap)
     */
    public boolean hasValidRequestedTimePercent(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        KualiInteger requestedAmount = appointmentFunding.getAppointmentRequestedAmount();
        BigDecimal requestedTimePercent = appointmentFunding.getAppointmentRequestedTimePercent();
        BigDecimal hourlyRate = appointmentFunding.getAppointmentRequestedPayRate();

        if (requestedTimePercent == null) {
            errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_TIME_PERCENT, BCKeyConstants.ERROR_EMPTY_REQUESTED_TIME_PERCENT);
            return false;
        }

        if (requestedTimePercent.compareTo(BigDecimal.ZERO) < 0 || requestedTimePercent.compareTo(BCConstants.ONE_HUNDRED) > 0) {
            errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_TIME_PERCENT, BCKeyConstants.ERROR_TIME_PERCENT_NOT_IN_RANGE, BigDecimal.ZERO.toPlainString(), BCConstants.ONE_HUNDRED.toPlainString());
            return false;
        }

        if ((requestedAmount.isPositive() || (hourlyRate != null && hourlyRate.compareTo(BigDecimal.ZERO) != 0)) && requestedTimePercent.compareTo(BigDecimal.ZERO) <= 0) {
            errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_TIME_PERCENT, BCKeyConstants.ERROR_TIME_PERCENT_GREATER_THAN_ZERO_REQUIRED);
            return false;
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasValidAdjustmentAmount(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.rice.krad.util.MessageMap)
     */
    public boolean hasValidAdjustmentAmount(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        KualiDecimal adjustmentAmount = appointmentFunding.getAdjustmentAmount();

        if (adjustmentAmount == null) {
            errorMap.putError(BCPropertyConstants.ADJUSTMENT_AMOUNT, BCKeyConstants.ERROR_ADJUSTMENT_AMOUNT_REQUIRED);
            return false;
        }

        return true;
    }

    /**
     * @see org.kuali.kfs.module.bc.document.service.SalarySettingRuleHelperService#hasValidAdjustmentAmount(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
     * org.kuali.rice.krad.util.MessageMap)
     */
    public boolean hasValidPayRateOrAnnualAmount(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
        BigDecimal payRate = appointmentFunding.getAppointmentRequestedPayRate();
        KualiInteger requestedAmount = appointmentFunding.getAppointmentRequestedAmount();

        if (requestedAmount == null && payRate == null) {
            errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_PAY_RATE, BCKeyConstants.ERROR_EMPTY_PAY_RATE_ANNUAL_AMOUNT);
            errorMap.putError(BCPropertyConstants.APPOINTMENT_REQUESTED_AMOUNT, BCKeyConstants.ERROR_EMPTY_PAY_RATE_ANNUAL_AMOUNT);
            return false;
        }

        return true;
    }

    /**
     * Sets the salarySettingService attribute value.
     *
     * @param salarySettingService The salarySettingService to set.
     */
    public void setSalarySettingService(SalarySettingService salarySettingService) {
        this.salarySettingService = salarySettingService;
    }

    /**
     * Sets the humanResourcesPayrollService attribute value.
     *
     * @param humanResourcesPayrollService The humanResourcesPayrollService to set.
     */
    public void setHumanResourcesPayrollService(HumanResourcesPayrollService humanResourcesPayrollService) {
        this.humanResourcesPayrollService = humanResourcesPayrollService;
    }
}
