// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('cr.ui', function() {
  /**
   * Creates a new button element. The repeating button behaves like a
   * keyboard button, which auto-repeats if held. This button is designed
   * for use with controls such as brightness and volume adjustment buttons.
   * @constructor
   * @extends {HTMLButtonElement}
   */
  var RepeatingButton = cr.ui.define('button');

  /**
   * DOM Events that may be fired by the Repeating button.
   */
  RepeatingButton.Event = {
    BUTTON_HELD: 'buttonHeld'
  };

  RepeatingButton.prototype = {
    __proto__: HTMLButtonElement.prototype,

    /**
     * Delay in milliseconds before the first repeat trigger of the button
     * held action.
     * @type {number}
     * @private
     */
    holdDelayTime_: 500,

    /**
     * Delay in milliseconds between triggers of the button held action.
     * @type {number}
     * @private
     */
    holdRepeatIntervalTime_: 50,

    /**
     * Callback function ID when repeated intervals trigger. Initialized when
     * the button is held for an initial delay period and cleared when the
     * button is released.
     * @type {number|undefined}
     * @private
     */
    intervalCallbackId_: undefined,

    /**
     * Callback function to arm the repeat timer. Initialized when the button
     * is pressed and cleared when the interval timer is set or the button is
     * released.
     * @type {number|undefined}
     * @private
     */
    armRepeaterCallbackId_: undefined,

    /**
     * Initializes the button.
     */
    decorate: function() {
      this.addEventListener('mousedown', this.buttonDown_.bind(this));
      this.addEventListener('mouseup', this.buttonUp_.bind(this));
      this.addEventListener('mouseout', this.buttonUp_.bind(this));
      this.addEventListener('touchstart', this.touchStart_.bind(this));
      this.addEventListener('touchend', this.buttonUp_.bind(this));
      this.addEventListener('touchcancel', this.buttonUp_.bind(this));
    },

    /**
     * Called when the user initiates a touch gesture.
     * @param {!Event} e The triggered event.
     * @private
     */
    touchStart_: function(e) {
      // Block system level gestures to prevent double tap to zoom. Also,
      // block following mouse event to prevent double firing of the button
      // held action in the case of a tap. Otherwise, a single tap action in
      // webkit generates the following event sequence: touchstart, touchend,
      // mouseover, mousemove, mousedown, mouseup and click.
      e.preventDefault();
      this.buttonDown_(e);
    },

    /**
     * Called when the user presses this button.
     * @param {!Event} e The triggered event.
     * @private
     */
    buttonDown_: function(e) {
      this.clearTimeout_();
      // Trigger the button held action immediately, after an initial delay and
      // then repeated based on a fixed time increment. The time intervals are
      // in agreement with the defaults for the ChromeOS keyboard and virtual
      // keyboard.
      // TODO(kevers): Consider adding a common location for picking up the
      //               initial delay and repeat interval.
      this.buttonHeld_();
      var self = this;
      var armRepeaterCallback = function() {
        // In the event of a click/tap operation, this button has already been
        // released by the time this timeout triggers. Test to ensure that the
        // button is still being held (i.e. clearTimeout has not been called).
        if (typeof self.armRepeaterCallbackId_ != 'undefined') {
          self.armRepeaterCallbackId_ = undefined;
          self.buttonHeld_();
          self.intervalCallbackId_ = setInterval(self.buttonHeld_.bind(self),
                                                 self.holdRepeatIntervalTime_);
        }
      };
      this.armRepeaterCallbackId_ = setTimeout(armRepeaterCallback,
                                               this.holdDelayTime_);
    },

    /**
     * Called when the user releases this button.
     * @param {!Event} e The triggered event.
     * @private
     */
    buttonUp_: function(e) {
      this.clearTimeout_();
    },

    /**
     * Resets the interval callback.
     * @private
     */
    clearTimeout_: function() {
      if (typeof this.armRepeaterCallbackId_ != 'undefined') {
        clearTimeout(this.armRepeaterCallbackId_);
        this.armRepeaterCallbackId_ = undefined;
      }
      if (typeof this.intervalCallbackId_ != 'undefined') {
        clearInterval(this.intervalCallbackId_);
        this.intervalCallbackId_ = undefined;
      }
    },

    /**
     * Dispatches the action associated with keeping this button pressed.
     * @private
     */
    buttonHeld_: function() {
      cr.dispatchSimpleEvent(this, RepeatingButton.Event.BUTTON_HELD);
    },

    /**
     * Getter for the initial delay before repeating.
     * @type {number} The delay in milliseconds.
     */
    get repeatDelay() {
      return this.holdDelayTime_;
    },

    /**
     * Setter for the initial delay before repeating.
     * @type {number} The delay in milliseconds.
     */
    set repeatDelay(delay) {
      this.holdDelayTime_ = delay;
    },

    /**
     * Getter for the repeat interval.
     * @type {number} The repeat interval in milliseconds.
     */
    get repeatInterval() {
      return this.holdRepeatIntervalTime_;
    },

    /**
     * Setter for the repeat interval.
     * @type {number} The interval in milliseconds.
     */
   set repeatInterval(delay) {
     this.holdRepeatIntervalTime_ = delay;
   }
  };

  return {
    RepeatingButton: RepeatingButton
  };
});

