/*
 * 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.tem.document.validation.impl;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.module.tem.TemConstants.TravelParameters;
import org.kuali.kfs.module.tem.TemKeyConstants;
import org.kuali.kfs.module.tem.TemParameterConstants;
import org.kuali.kfs.module.tem.TemPropertyConstants;
import org.kuali.kfs.module.tem.businessobject.ActualExpense;
import org.kuali.kfs.module.tem.businessobject.PerDiem;
import org.kuali.kfs.module.tem.businessobject.PerDiemExpense;
import org.kuali.kfs.module.tem.document.TravelDocument;
import org.kuali.kfs.module.tem.document.TravelDocumentBase;
import org.kuali.kfs.module.tem.document.service.TravelDocumentService;
import org.kuali.kfs.module.tem.service.PerDiemService;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.document.validation.GenericValidation;
import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
import org.kuali.kfs.sys.util.KfsDateUtils;
import org.kuali.rice.core.api.util.type.KualiDecimal;

import java.text.SimpleDateFormat;

public class TravelAuthTripDetailMealsAndIncidentalsValidation extends GenericValidation {

    protected TravelDocumentService travelDocumentService;
    protected ParameterService parameterService;
    protected PerDiemService perDiemService;

    @Override
    public boolean validate(AttributedDocumentEvent event) {
        boolean rulePassed = true;
        TravelDocumentBase document = (TravelDocumentBase) event.getDocument();
        SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");

        //determine if per diem rule should be daily or per meal
        final boolean checkDailyPerDiem = parameterService.getParameterValueAsBoolean(TemParameterConstants.TEM_DOCUMENT.class, TravelParameters.VALIDATE_DAILY_PER_DIEM_AND_INCIDENTALS_IND);

        int count = 0;
        for (PerDiemExpense estimate : document.getPerDiemExpenses()) {
            final boolean prorated = !KfsDateUtils.isSameDay(document.getTripBegin(), document.getTripEnd()) && (KfsDateUtils.isSameDay(estimate.getMileageDate(), document.getTripBegin()) || KfsDateUtils.isSameDay(estimate.getMileageDate(), document.getTripEnd()));
            if (prorated && !estimate.isProrated()) {
                estimate.setProrated(true); // we lost the prorated and we need it back
            }

            Integer perDiemPercent = travelDocumentService.calculateProratePercentage(estimate, document.getTripType().getPerDiemCalcMethod(), document.getTripEnd());
            String expenseDate = sdf.format(estimate.getMileageDate());

            if (estimate != null) {
                final PerDiem estimatePerDiem = getPerDiemService().getPerDiem(estimate.getPrimaryDestinationId(), estimate.getMileageDate(), document.getEffectiveDateForPerDiem(estimate));
                if (estimatePerDiem != null && perDiemPercent != null) {

                    //check daily per diem instead of validation in each meal
                    if (!checkDailyPerDiem) {

                        // check for invalid breakfast amounts
                        KualiDecimal defaultBreakfast = PerDiemExpense.calculateMealsAndIncidentalsProrated(estimatePerDiem.getBreakfast(), perDiemPercent);
                        if (defaultBreakfast.isGreaterThan(KualiDecimal.ZERO) && defaultBreakfast.isLessThan(estimate.getBreakfastValue())) {
                            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.DOCUMENT + "." + TemPropertyConstants.PER_DIEM_EXPENSES + "[" + count + "]." + TemPropertyConstants.UNFILTERED_BREAKFAST_VALUE, TemKeyConstants.ERROR_TA_MEAL_AND_INC_NOT_VALID, new String[]{"Breakfast - " + expenseDate, estimate.getBreakfastValue().toString(), defaultBreakfast.toString()});
                            rulePassed = false;
                        }

                        // check for invalid lunch amounts
                        KualiDecimal defaultLunch = PerDiemExpense.calculateMealsAndIncidentalsProrated(estimatePerDiem.getLunch(), perDiemPercent);
                        if (defaultLunch.isGreaterThan(KualiDecimal.ZERO) && defaultLunch.isLessThan(estimate.getLunchValue())) {
                            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.DOCUMENT + "." + TemPropertyConstants.PER_DIEM_EXPENSES + "[" + count + "]." + TemPropertyConstants.UNFILTERED_LUNCH_VALUE, TemKeyConstants.ERROR_TA_MEAL_AND_INC_NOT_VALID, new String[]{"Lunch - " + expenseDate, estimate.getLunchValue().toString(), defaultLunch.toString()});
                            rulePassed = false;
                        }

                        // check for invalid dinner amounts
                        KualiDecimal defaultDinner = PerDiemExpense.calculateMealsAndIncidentalsProrated(estimatePerDiem.getDinner(), perDiemPercent);
                        if (defaultDinner.isGreaterThan(KualiDecimal.ZERO) && defaultDinner.isLessThan(estimate.getDinnerValue())) {
                            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.DOCUMENT + "." + TemPropertyConstants.PER_DIEM_EXPENSES + "[" + count + "]." + TemPropertyConstants.UNFILTERED_DINNER_VALUE, TemKeyConstants.ERROR_TA_MEAL_AND_INC_NOT_VALID, new String[]{"Dinner - " + expenseDate, estimate.getDinnerValue().toString(), defaultDinner.toString()});
                            rulePassed = false;
                        }

                        // check for invalid incidentals amounts
                        KualiDecimal defaultIncidentals = PerDiemExpense.calculateMealsAndIncidentalsProrated(estimatePerDiem.getIncidentals(), perDiemPercent);
                        if (defaultIncidentals.isGreaterThan(KualiDecimal.ZERO) && defaultIncidentals.isLessThan(estimate.getIncidentalsValue())) {
                            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.DOCUMENT + "." + TemPropertyConstants.PER_DIEM_EXPENSES + "[" + count + "]." + TemPropertyConstants.UNFILTERED_INCIDENTALS_VALUE, TemKeyConstants.ERROR_TA_MEAL_AND_INC_NOT_VALID, new String[]{"Incidentals - " + expenseDate, estimate.getIncidentalsValue().toString(), defaultIncidentals.toString()});
                            rulePassed = false;
                        }
                    } else {

                        KualiDecimal dailyPerDiem = PerDiemExpense.calculateMealsAndIncidentalsProrated(estimatePerDiem.getMealsAndIncidentals(), perDiemPercent);
                        if (dailyPerDiem.isGreaterThan(KualiDecimal.ZERO) && dailyPerDiem.isLessThan(estimate.getMealsAndIncidentals())) {
                            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.DOCUMENT + "." + TemPropertyConstants.PER_DIEM_EXPENSES, TemKeyConstants.ERROR_TA_MEAL_AND_INC_NOT_VALID, new String[]{"Daily PerDiem - " + expenseDate, estimate.getMealsAndIncidentals().toString(), dailyPerDiem.toString()});
                            rulePassed = false;
                            break;
                        }

                    }
                }
            }
            count += 1;
        }

        if (StringUtils.isBlank(document.getMealWithoutLodgingReason()) && document.isMealsWithoutLodging()) {
            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.DOCUMENT + "." + TemPropertyConstants.MEAL_WITHOUT_LODGING_REASON, TemKeyConstants.ERROR_TRVL_MEALS_NO_LODGING_REQUIRES_JUSTIFICATION);
            rulePassed = false;
        }

        return rulePassed;
    }

    /**
     * Determines whether the given document has any actual expenses associated which are lodging expenses
     *
     * @param document the document to look for lodging expenses on
     * @return true if there are lodging expenses on the document, false otherwise
     */
    protected boolean hasLodgingActualExpense(TravelDocument document) {
        for (ActualExpense expense : document.getActualExpenses()) {
            if (expense.isLodging() || expense.isLodgingAllowance()) {
                return true;
            }
        }
        return false;
    }

    public void setTravelDocumentService(TravelDocumentService travelDocumentService) {
        this.travelDocumentService = travelDocumentService;
    }

    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }

    public PerDiemService getPerDiemService() {
        return perDiemService;
    }

    public void setPerDiemService(PerDiemService perDiemService) {
        this.perDiemService = perDiemService;
    }

}
