// Copyright 2015 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.

'use strict';

/**
 * The period of time in milliseconds to wait between updating the viewport
 * position by the scroll velocity.
 *
 * @private
 */
ViewportScroller.DRAG_TIMER_INTERVAL_MS_ = 100;

/**
 * The maximum drag scroll distance per DRAG_TIMER_INTERVAL in pixels.
 *
 * @private
 */
ViewportScroller.MAX_DRAG_SCROLL_DISTANCE_ = 100;

/**
 * Creates a new ViewportScroller.
 * A ViewportScroller scrolls the page in response to drag selection with the
 * mouse.
 *
 * @param {Object} viewport The viewport info of the page.
 * @param {Object} plugin The PDF plugin element.
 * @param {Object} window The window containing the viewer.
 * @constructor
 */
function ViewportScroller(viewport, plugin, window) {
  this.viewport_ = viewport;
  this.plugin_ = plugin;
  this.window_ = window;
  this.mousemoveCallback_ = null;
  this.timerId_ = null;
  this.scrollVelocity_ = null;
  this.lastFrameTime_ = 0;
}

ViewportScroller.prototype = {
  /**
   * Start scrolling the page by |scrollVelocity_| every
   * |DRAG_TIMER_INTERVAL_MS_|.
   *
   * @private
   */
  startDragScrollTimer_: function() {
    if (this.timerId_ === null) {
      this.timerId_ = this.window_.setInterval(
          this.dragScrollPage_.bind(this),
          ViewportScroller.DRAG_TIMER_INTERVAL_MS_);
      this.lastFrameTime_ = Date.now();
    }
  },

  /**
   * Stops the drag scroll timer if it is active.
   *
   * @private
   */
  stopDragScrollTimer_: function() {
    if (this.timerId_ !== null) {
      this.window_.clearInterval(this.timerId_);
      this.timerId_ = null;
      this.lastFrameTime_ = 0;
    }
  },

  /**
   * Scrolls the viewport by the current scroll velocity.
   *
   * @private
   */
  dragScrollPage_: function() {
    const position = this.viewport_.position;
    const currentFrameTime = Date.now();
    const timeAdjustment = (currentFrameTime - this.lastFrameTime_) /
        ViewportScroller.DRAG_TIMER_INTERVAL_MS_;
    position.y += (this.scrollVelocity_.y * timeAdjustment);
    position.x += (this.scrollVelocity_.x * timeAdjustment);
    this.viewport_.position = position;
    this.lastFrameTime_ = currentFrameTime;
  },

  /**
   * Calculate the velocity to scroll while dragging using the distance of the
   * cursor outside the viewport.
   *
   * @param {Object} event The mousemove event.
   * @return {Object} Object with x and y direction scroll velocity.
   * @private
   */
  calculateVelocity_: function(event) {
    const x =
        Math.min(
            Math.max(
                -event.offsetX, event.offsetX - this.plugin_.offsetWidth, 0),
            ViewportScroller.MAX_DRAG_SCROLL_DISTANCE_) *
        Math.sign(event.offsetX);
    const y =
        Math.min(
            Math.max(
                -event.offsetY, event.offsetY - this.plugin_.offsetHeight, 0),
            ViewportScroller.MAX_DRAG_SCROLL_DISTANCE_) *
        Math.sign(event.offsetY);
    return {x: x, y: y};
  },

  /**
   * Handles mousemove events. It updates the scroll velocity and starts and
   * stops timer based on scroll velocity.
   *
   * @param {Object} event The mousemove event.
   * @private
   */
  onMousemove_: function(event) {
    this.scrollVelocity_ = this.calculateVelocity_(event);
    if (!this.scrollVelocity_.x && !this.scrollVelocity_.y) {
      this.stopDragScrollTimer_();
    } else if (!this.timerId_) {
      this.startDragScrollTimer_();
    }
  },

  /**
   * Sets whether to scroll the viewport when the mouse is outside the
   * viewport.
   *
   * @param {boolean} isSelecting Represents selection status.
   */
  setEnableScrolling: function(isSelecting) {
    if (isSelecting) {
      if (!this.mousemoveCallback_) {
        this.mousemoveCallback_ = this.onMousemove_.bind(this);
      }
      this.plugin_.addEventListener(
          'mousemove', this.mousemoveCallback_, false);
    } else {
      this.stopDragScrollTimer_();
      if (this.mousemoveCallback_) {
        this.plugin_.removeEventListener(
            'mousemove', this.mousemoveCallback_, false);
      }
    }
  }
};
