import {
    Services
} from '#service';

/**
 * Pass class helps to manage single-pass process. A pass is scheduled using
 * delay method. Only one pass can be pending at a time. If no pass is pending
 * the process is considered to be "idle".
 */
export class Pass {
    /**
     * Creates a new Pass instance.
     * @param {!Window} win
     * @param {function()} handler Handler to be executed when pass is triggered.
     * @param {number=} opt_defaultDelay Default delay to be used when schedule
     *   is called without one.
     */
    constructor(win, handler, opt_defaultDelay) {
        this.timer_ = Services.timerFor(win);

        /** @private @const {function()} */
        this.handler_ = handler;

        /** @private @const {number} */
        this.defaultDelay_ = opt_defaultDelay || 0;

        /** @private {number|string} */
        this.scheduled_ = -1;

        /** @private {number} */
        this.nextTime_ = 0;

        /** @private {boolean} */
        this.running_ = false;

        /**
         * @private
         * @const {function()}
         */
        this.boundPass_ = () => {
            this.pass_();
        };
    }

    /**
     * Whether or not a pass is currently pending.
     * @return {boolean}
     */
    isPending() {
        return this.scheduled_ != -1;
    }

    /**
     * Tries to schedule a new pass optionally with specified delay. If the new
     * requested pass is requested before the pending pass, the pending pass is
     * canceled. If the new pass is requested after the pending pass, the newly
     * requested pass is ignored.
     *
     * Returns {@code true} if the pass has been scheduled and {@code false} if
     * ignored.
     *
     * @param {number=} opt_delay Delay to schedule the pass. If not specified
     *   the default delay is used, falling back to 0.
     * @return {boolean}
     */
    schedule(opt_delay) {
        let delay = opt_delay || this.defaultDelay_;
        if (this.running_ && delay < 10) {
            // If we get called recursively, wait at least 10ms for the next
            // execution.
            delay = 10;
        }

        const nextTime = Date.now() + delay;
        // Schedule anew if nothing is scheduled currently or if the new time is
        // sooner then previously requested.
        if (!this.isPending() || nextTime - this.nextTime_ < -10) {
            this.cancel();
            this.nextTime_ = nextTime;
            this.scheduled_ = this.timer_.delay(this.boundPass_, delay);

            return true;
        }

        return false;
    }

    /**
     *
     */
    pass_() {
        this.scheduled_ = -1;
        this.nextTime_ = 0;
        this.running_ = true;
        this.handler_();
        this.running_ = false;
    }

    /**
     * Cancels the pending pass if any.
     */
    cancel() {
        if (this.isPending()) {
            this.timer_.cancel(this.scheduled_);
            this.scheduled_ = -1;
        }
    }
}