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

import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.ar.ArConstants;
import org.kuali.kfs.module.ar.ArKeyConstants;
import org.kuali.kfs.module.ar.ArPropertyConstants;
import org.kuali.kfs.module.ar.document.CustomerInvoiceDocument;
import org.kuali.kfs.sys.document.validation.GenericValidation;
import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
import org.kuali.rice.core.api.datetime.DateTimeService;

import java.sql.Timestamp;

import static org.kuali.kfs.sys.document.validation.impl.AccountingDocumentRuleBaseConstants.ERROR_PATH.DOCUMENT_ERROR_PREFIX;

public class CustomerInvoiceDueDateValidation extends GenericValidation {

    private CustomerInvoiceDocument customerInvoiceDocument;
    private DateTimeService dateTimeService;
    private ParameterService parameterService;

    @Override
    public boolean validate(AttributedDocumentEvent event) {

        Timestamp dueDateTimestamp = new Timestamp(customerInvoiceDocument.getInvoiceDueDate().getTime());
        Timestamp billingDateTimestamp = new Timestamp(dateTimeService.getCurrentDate().getTime());
        // test only for initial state and not for correction
        if (ObjectUtils.isNull((customerInvoiceDocument.getFinancialSystemDocumentHeader().getFinancialDocumentInErrorNumber())) &&
            (dueDateTimestamp.before(billingDateTimestamp) || dueDateTimestamp.equals(billingDateTimestamp))) {
            GlobalVariables.getMessageMap().putError(DOCUMENT_ERROR_PREFIX + ArPropertyConstants.CustomerInvoiceDocumentFields.INVOICE_DUE_DATE, ArKeyConstants.ERROR_CUSTOMER_INVOICE_DOCUMENT_INVALID_INVOICE_DUE_DATE_BEFORE_OR_EQUAL_TO_BILLING_DATE);
            return false;
        } else {
            long diffInDays = getDifferenceInDays(billingDateTimestamp, dueDateTimestamp); //KfsDateUtils.getDifferenceInDays(billingDateTimestamp, dueDateTimestamp);
            int maxNumOfDaysAfterCurrentDateForInvoiceDueDate = Integer.parseInt(parameterService.getParameterValueAsString(CustomerInvoiceDocument.class, ArConstants.MAXIMUM_NUMBER_OF_DAYS_AFTER_CURRENT_DATE_FOR_INVOICE_DUE_DATE));
            if (diffInDays >= maxNumOfDaysAfterCurrentDateForInvoiceDueDate) {
                GlobalVariables.getMessageMap().putError(DOCUMENT_ERROR_PREFIX + ArPropertyConstants.CustomerInvoiceDocumentFields.INVOICE_DUE_DATE, ArKeyConstants.ERROR_CUSTOMER_INVOICE_DOCUMENT_INVALID_INVOICE_DUE_DATE_MORE_THAN_X_DAYS, maxNumOfDaysAfterCurrentDateForInvoiceDueDate + "");
                return false;
            }

        }

        return true;
    }

    /**
     * This method calculates the difference in days between the two timestamps provided.
     * <p>
     * This method is used instead of KfsDateUtils.getDifferenceInDays() because there is a rounding issue within the timestamp that exists which must be dealt with to
     * prevent improper calculations.  This issue is similar to the problems that exist with adding and subtracting doubles and the inherently bad way that Java handles
     * numbers.
     * <p>
     * The approach used within KfsDateUtils does not offer enough accuracy to calculate the difference consistently and accurately.
     *
     * @param t1
     * @param t2
     * @return The difference in days between the two given timestamps.
     */
    public static long getDifferenceInDays(Timestamp t1, Timestamp t2) {
        // Make sure the result is always > 0
        if (t1.compareTo(t2) < 0) {
            Timestamp tmp = t1;
            t1 = t2;
            t2 = tmp;
        }

        // Timestamps mix milli and nanoseconds in the API, so we have to separate the two
        long diffSeconds = (t1.getTime() / 1000) - (t2.getTime() / 1000);
        // For normals dates, we have millisecond precision
        int nano1 = ((int) t1.getTime() % 1000) * 1000000;
        nano1 = t1.getNanos();
        int nano2 = ((int) t2.getTime() % 1000) * 1000000;
        nano2 = t2.getNanos();

        int diffNanos = nano1 - nano2;
        if (diffNanos < 0) {
            // Borrow one second
            diffSeconds--;
            diffNanos += 1000000000;
        }

        // mix nanos and millis again
        Timestamp result = new Timestamp((diffSeconds * 1000) + (diffNanos / 1000000));
        // setNanos() with a value of in the millisecond range doesn't affect the value of the time field
        // while milliseconds in the time field will modify nanos! Damn, this API is a *mess*
        result.setNanos(diffNanos);
        return result.getDate();
    }

    public CustomerInvoiceDocument getCustomerInvoiceDocument() {
        return customerInvoiceDocument;
    }

    public void setCustomerInvoiceDocument(CustomerInvoiceDocument customerInvoiceDocument) {
        this.customerInvoiceDocument = customerInvoiceDocument;
    }

    public DateTimeService getDateTimeService() {
        return dateTimeService;
    }

    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    public ParameterService getParameterService() {
        return parameterService;
    }

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