/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
 */
package com.ericsson.ssa.sip.timer;


/**
 * Component that calculates how to reschedule a servlet timer.
 *
 *
 * @author qmaghes
 * @since 2007 nov 29
 *
 */
public class RescheduleAlgorithm {
    // Timer to be resceduled
    ServletTimerImpl timer;
    long timeNow;

    /**
     * Constructor
     *
     * @param timer     The timer to be resceduled.
     * @param timeNow   The present time. Typically called with System.currentTimeMillis().
     */
    public RescheduleAlgorithm(ServletTimerImpl timer, long timeNow) {
        this.timer = timer;
        this.timeNow = timeNow;
    }

    /**
     * Should this timer be called at once without being part of a new schedule?
     *
     * @return
     */
    public boolean fireAtOnceSeparately() {
        return fireAtOnce() &&
        !(timer.isRepeatingTimer() && timer.isFixedDelay());
    }

    /**
     * Helper to check if this timer should fire at once.
     *
     * @return
     */
    private boolean fireAtOnce() {
        return timer.scheduledExecutionTime() <= timeNow;
    }

    /**
     * Does this timer need to be resceduled?
     * All recurring timers need rescedulation.
     * Non-recurring timers only if they are not fired at once.
     *
     * If the timers needs reschedule, the initial delay is calculated
     * with the <code>delayUntilNextExecution</code>.
     *
     * @return
     */
    public boolean needReschedule() {
        if (timer.isRepeatingTimer()) {
            return true;
        }

        if (fireAtOnce()) {
            return false;
        }

        return true;
    }

    /**
     * Delay until next execution. Applicable for timers that need rescedule.
     *
     * @return
     */
    public long delayUntilNextExecution() {
        if (!needReschedule()) {
            return -1;
        }

        // Non repeating timer => time until it expires
        if (!timer.isRepeatingTimer()) {
            return timer.scheduledExecutionTime() - timeNow;
        }

        // Recurring timer
        if (timer.isFixedDelay()) {
            if (fireAtOnce()) {
                // For fixed delay, if it should fire at once, the initial delay is 0 at the first execution is part
                // of the new reschedule. (We don't fire at once separately)
                return 0;
            } else {
                // No missed execution => time until it expires
                return timer.scheduledExecutionTime() - timeNow;
            }
        } else { // fixed rate 
                 // For fixed rate, it might fire at ones as a separate execution
                 // The remainig times it should fire at a multiple of period
                 // from the next execution time.

            long oneScheduledExecution = timer.scheduledExecutionTime();
            long period = timer.getPFieldPeriod();

            // Next execution time (relative) will be 
            // (oneScheduledExecution - timeNow) + n*period
            // but will fit in interval [0,period[
            return positiveModulo(oneScheduledExecution - timeNow, period);
        }
    }

    /**
     * Division with remainder that always return a number in [0,remainder[
     *
     * Example -3%5 => -3
     *         positiveModulo(-3,5) => 2
     *
     * @param arg
     * @param remainder
     * @return
     */
    private long positiveModulo(long arg, long remainder) {
        if (arg >= 0) {
            return arg % remainder;
        }

        return remainder - (-arg % remainder);
    }
}
