/**
 * @summary Adds all needed event listeners
 * @private
 */
PhotoSphereViewer.prototype._bindEvents = function() {
  window.addEventListener('resize', this);

  // all interation events are binded to the HUD only
  if (this.config.mousemove) {
    this.hud.container.style.cursor = 'move';
    this.hud.container.addEventListener('mousedown', this);
    this.hud.container.addEventListener('touchstart', this);
    window.addEventListener('mouseup', this);
    window.addEventListener('touchend', this);
    this.hud.container.addEventListener('mousemove', this);
    this.hud.container.addEventListener('touchmove', this);
  }

  if (PhotoSphereViewer.SYSTEM.fullscreenEvent) {
    document.addEventListener(PhotoSphereViewer.SYSTEM.fullscreenEvent, this);
  }

  if (this.config.mousewheel) {
    this.hud.container.addEventListener(PhotoSphereViewer.SYSTEM.mouseWheelEvent, this);
  }

  this.on('_side-reached', function(side) {
    if (this.isAutorotateEnabled()) {
      if (side === 'left' || side === 'right') {
        this._reverseAutorotate();
      }
    }
  });
};

/**
 * @summary Handles events
 * @param {Event} evt
 * @private
 */
PhotoSphereViewer.prototype.handleEvent = function(evt) {
  switch (evt.type) {
    // @formatter:off
    case 'resize': PSVUtils.throttle(this._onResize(), 50); break;
    case 'keydown':     this._onKeyDown(evt);     break;
    case 'mousedown':   this._onMouseDown(evt);   break;
    case 'touchstart':  this._onTouchStart(evt);  break;
    case 'mouseup':     this._onMouseUp(evt);     break;
    case 'touchend':    this._onTouchEnd(evt);    break;
    case 'mousemove':   this._onMouseMove(evt);   break;
    case 'touchmove':   this._onTouchMove(evt);   break;
    case PhotoSphereViewer.SYSTEM.fullscreenEvent:  this._fullscreenToggled();  break;
    case PhotoSphereViewer.SYSTEM.mouseWheelEvent:  this._onMouseWheel(evt);    break;
    // @formatter:on
  }
};

/**
 * @summary Resizes the canvas when the window is resized
 * @fires PhotoSphereViewer.size-updated
 * @private
 */
PhotoSphereViewer.prototype._onResize = function() {
  if (this.container.clientWidth != this.prop.size.width || this.container.clientHeight != this.prop.size.height) {
    this.prop.size.width = parseInt(this.container.clientWidth);
    this.prop.size.height = parseInt(this.container.clientHeight);
    this.prop.aspect = this.prop.size.width / this.prop.size.height;

    if (this.renderer) {
      this.renderer.setSize(this.prop.size.width, this.prop.size.height);
      if (this.composer) {
        this.composer.reset(new THREE.WebGLRenderTarget(this.prop.size.width, this.prop.size.height));
      }
      this.render();
    }

    /**
     * @event size-updated
     * @memberof PhotoSphereViewer
     * @summary Triggered when the viewer size changes
     * @param {PhotoSphereViewer.Size} size
     */
    this.trigger('size-updated', this.getSize());
  }
};

/**
 * @summary Handles keyboard events
 * @param {KeyboardEvent} evt
 * @private
 */
PhotoSphereViewer.prototype._onKeyDown = function(evt) {
  var dLong = 0;
  var dLat = 0;
  var dZoom = 0;

  var key = evt.key || PhotoSphereViewer.KEYMAP[evt.keyCode || evt.which];

  switch (key) {
    // @formatter:off
    case 'ArrowUp': dLat = 0.01; break;
    case 'ArrowDown': dLat = -0.01; break;
    case 'ArrowRight': dLong = 0.01; break;
    case 'ArrowLeft': dLong = -0.01; break;
    case 'PageUp':case '+': dZoom = 1; break;
    case 'PageDown':case '-': dZoom = -1; break;
    // @formatter:on
  }

  if (dZoom !== 0) {
    this.zoom(this.prop.zoom_lvl + dZoom);
  }
  else if (dLat !== 0 || dLong !== 0) {
    this.rotate({
      longitude: this.prop.longitude + dLong * this.prop.move_speed * this.prop.hFov,
      latitude: this.prop.latitude + dLat * this.prop.move_speed * this.prop.vFov
    });
  }
};

/**
 * @summary Handles mouse button events
 * @param {MouseEvent} evt
 * @private
 */
PhotoSphereViewer.prototype._onMouseDown = function(evt) {
  this._startMove(evt);
};

/**
 * @summary Handles mouse buttons events
 * @param {MouseEvent} evt
 * @private
 */
PhotoSphereViewer.prototype._onMouseUp = function(evt) {
  this._stopMove(evt);
};

/**
 * @summary Handles mouse move events
 * @param {MouseEvent} evt
 * @private
 */
PhotoSphereViewer.prototype._onMouseMove = function(evt) {
  if (evt.buttons !== 0) {
    evt.preventDefault();
    this._move(evt);
  }
};

/**
 * @summary Handles touch events
 * @param {TouchEvent} evt
 * @private
 */
PhotoSphereViewer.prototype._onTouchStart = function(evt) {
  if (evt.touches.length === 1) {
    this._startMove(evt.touches[0]);
  }
  else if (evt.touches.length === 2) {
    this._startZoom(evt);
  }
};

/**
 * @summary Handles touch events
 * @param {TouchEvent} evt
 * @private
 */
PhotoSphereViewer.prototype._onTouchEnd = function(evt) {
  this._stopMove(evt.changedTouches[0]);
};

/**
 * @summary Handles touch move events
 * @param {TouchEvent} evt
 * @private
 */
PhotoSphereViewer.prototype._onTouchMove = function(evt) {
  if (evt.touches.length === 1) {
    evt.preventDefault();
    this._move(evt.touches[0]);
  }
  else if (evt.touches.length === 2) {
    evt.preventDefault();
    this._zoom(evt);
  }
};

/**
 * @summary Initializes the movement
 * @param {MouseEvent|Touch} evt
 * @private
 */
PhotoSphereViewer.prototype._startMove = function(evt) {
  if (this.isGyroscopeEnabled()) {
    return;
  }

  this._stopAll();

  this.prop.mouse_x = this.prop.start_mouse_x = parseInt(evt.clientX);
  this.prop.mouse_y = this.prop.start_mouse_y = parseInt(evt.clientY);
  this.prop.moving = true;
  this.prop.zooming = false;

  this.prop.mouse_history.length = 0;
  this._logMouseMove(evt);
};

/**
 * @summary Initializes the zoom
 * @param {TouchEvent} evt
 * @private
 */
PhotoSphereViewer.prototype._startZoom = function(evt) {
  var t = [
    { x: parseInt(evt.touches[0].clientX), y: parseInt(evt.touches[0].clientY) },
    { x: parseInt(evt.touches[1].clientX), y: parseInt(evt.touches[1].clientY) }
  ];

  this.prop.pinch_dist = Math.sqrt(Math.pow(t[0].x - t[1].x, 2) + Math.pow(t[0].y - t[1].y, 2));
  this.prop.moving = false;
  this.prop.zooming = true;
};

/**
 * @summary Stops the movement
 * @description If the move threshold was not reached a click event is triggered, otherwise an animation is launched to simulate inertia
 * @param {MouseEvent|Touch} evt
 * @private
 */
PhotoSphereViewer.prototype._stopMove = function(evt) {
  if (this.isGyroscopeEnabled()) {
    this._click(evt);
    return;
  }

  if (this.prop.moving) {
    // move threshold to trigger a click
    if (Math.abs(evt.clientX - this.prop.start_mouse_x) < PhotoSphereViewer.MOVE_THRESHOLD && Math.abs(evt.clientY - this.prop.start_mouse_y) < PhotoSphereViewer.MOVE_THRESHOLD) {
      this._click(evt);
      this.prop.moving = false;
    }
    // inertia animation
    else if (this.config.move_inertia) {
      this._logMouseMove(evt);
      this._stopMoveInertia(evt);
    }
    else {
      this.prop.moving = false;
    }
  }

  this.prop.mouse_history.length = 0;
  this.prop.zooming = false;
};

/**
 * @summary Performs an animation to simulate inertia when the movement stops
 * @param {MouseEvent|Touch} evt
 * @private
 */
PhotoSphereViewer.prototype._stopMoveInertia = function(evt) {
  var self = this;

  var direction = {
    x: evt.clientX - this.prop.mouse_history[0][1],
    y: evt.clientY - this.prop.mouse_history[0][2]
  };

  var norm = Math.sqrt(direction.x * direction.x + direction.y * direction.y);

  this.prop.animation_promise = PSVUtils.animation({
    properties: {
      clientX: { start: evt.clientX, end: evt.clientX + direction.x },
      clientY: { start: evt.clientY, end: evt.clientY + direction.y }
    },
    duration: norm * PhotoSphereViewer.INERTIA_WINDOW / 100,
    easing: 'outCirc',
    onTick: function(properties) {
      self._move(properties, false);
    }
  })
    .ensure(function() {
      self.prop.moving = false;
    });
};

/**
 * @summary Triggers an event with all coordinates when a simple click is performed
 * @param {MouseEvent|Touch} evt
 * @fires PhotoSphereViewer.click
 * @fires PhotoSphereViewer.dblclick
 * @private
 */
PhotoSphereViewer.prototype._click = function(evt) {
  var boundingRect = this.container.getBoundingClientRect();

  var data = {
    target: evt.target,
    client_x: evt.clientX,
    client_y: evt.clientY,
    viewer_x: parseInt(evt.clientX - boundingRect.left),
    viewer_y: parseInt(evt.clientY - boundingRect.top)
  };

  var intersect = this.viewerCoordsToVector3({ x: data.viewer_x, y: data.viewer_y });

  if (intersect) {
    var sphericalCoords = this.vector3ToSphericalCoords(intersect);
    data.longitude = sphericalCoords.longitude;
    data.latitude = sphericalCoords.latitude;

    // TODO: for cubemap, computes texture's index and coordinates
    if (!this.prop.isCubemap) {
      var textureCoords = this.sphericalCoordsToTextureCoords({ longitude: data.longitude, latitude: data.latitude });
      data.texture_x = textureCoords.x;
      data.texture_y = textureCoords.y;
    }

    if (!this.prop.dblclick_timeout) {
      /**
       * @event click
       * @memberof PhotoSphereViewer
       * @summary Triggered when the user clicks on the viewer (everywhere excluding the navbar and the side panel)
       * @param {PhotoSphereViewer.ClickData} data
       */
      this.trigger('click', data);

      this.prop.dblclick_data = PSVUtils.clone(data);
      this.prop.dblclick_timeout = setTimeout(function() {
        this.prop.dblclick_timeout = null;
        this.prop.dblclick_data = null;
      }.bind(this), PhotoSphereViewer.DBLCLICK_DELAY);
    }
    else {
      if (Math.abs(this.prop.dblclick_data.client_x - data.client_x) < PhotoSphereViewer.MOVE_THRESHOLD &&
        Math.abs(this.prop.dblclick_data.client_y - data.client_y) < PhotoSphereViewer.MOVE_THRESHOLD) {
        /**
         * @event dblclick
         * @memberof PhotoSphereViewer
         * @summary Triggered when the user double clicks on the viewer. The simple `click` event is always fired before `dblclick`
         * @param {PhotoSphereViewer.ClickData} data
         */
        this.trigger('dblclick', this.prop.dblclick_data);
      }

      clearTimeout(this.prop.dblclick_timeout);
      this.prop.dblclick_timeout = null;
      this.prop.dblclick_data = null;
    }
  }
};

/**
 * @summary Performs movement
 * @param {MouseEvent|Touch} evt
 * @param {boolean} [log=true]
 * @private
 */
PhotoSphereViewer.prototype._move = function(evt, log) {
  if (this.prop.moving) {
    var x = parseInt(evt.clientX);
    var y = parseInt(evt.clientY);

    this.rotate({
      longitude: this.prop.longitude - (x - this.prop.mouse_x) / this.prop.size.width * this.prop.move_speed * this.prop.hFov,
      latitude: this.prop.latitude + (y - this.prop.mouse_y) / this.prop.size.height * this.prop.move_speed * this.prop.vFov
    });

    this.prop.mouse_x = x;
    this.prop.mouse_y = y;

    if (log !== false) {
      this._logMouseMove(evt);
    }
  }
};

/**
 * @summary Perfoms zoom
 * @param {TouchEvent} evt
 * @private
 */
PhotoSphereViewer.prototype._zoom = function(evt) {
  if (this.prop.zooming) {
    var t = [
      { x: parseInt(evt.touches[0].clientX), y: parseInt(evt.touches[0].clientY) },
      { x: parseInt(evt.touches[1].clientX), y: parseInt(evt.touches[1].clientY) }
    ];

    var p = Math.sqrt(Math.pow(t[0].x - t[1].x, 2) + Math.pow(t[0].y - t[1].y, 2));
    var delta = 80 * (p - this.prop.pinch_dist) / this.prop.size.width;

    this.zoom(this.prop.zoom_lvl + delta);

    this.prop.pinch_dist = p;
  }
};

/**
 * @summary Handles mouse wheel events
 * @param {MouseWheelEvent} evt
 * @private
 */
PhotoSphereViewer.prototype._onMouseWheel = function(evt) {
  evt.preventDefault();
  evt.stopPropagation();

  var delta = evt.deltaY !== undefined ? -evt.deltaY : (evt.wheelDelta !== undefined ? evt.wheelDelta : -evt.detail);

  if (delta !== 0) {
    var direction = parseInt(delta / Math.abs(delta));
    this.zoom(this.prop.zoom_lvl + direction);
  }
};

/**
 * @summary Handles fullscreen events
 * @fires PhotoSphereViewer.fullscreen-updated
 * @private
 */
PhotoSphereViewer.prototype._fullscreenToggled = function() {
  var enabled = this.isFullscreenEnabled();

  if (this.config.keyboard) {
    if (enabled) {
      this.startKeyboardControl();
    }
    else {
      this.stopKeyboardControl();
    }
  }

  /**
   * @event fullscreen-updated
   * @memberof PhotoSphereViewer
   * @summary Triggered when the fullscreen mode is enabled/disabled
   * @param {boolean} enabled
   */
  this.trigger('fullscreen-updated', enabled);
};

/**
 * @summary Stores each mouse position during a mouse move
 * @description Positions older than "INERTIA_WINDOW" are removed<br>
 *     Positions before a pause of "INERTIA_WINDOW" / 10 are removed
 * @param {MouseEvent|Touch} evt
 * @private
 */
PhotoSphereViewer.prototype._logMouseMove = function(evt) {
  var now = Date.now();
  this.prop.mouse_history.push([now, evt.clientX, evt.clientY]);

  var previous = null;

  for (var i = 0; i < this.prop.mouse_history.length;) {
    if (this.prop.mouse_history[0][i] < now - PhotoSphereViewer.INERTIA_WINDOW) {
      this.prop.mouse_history.splice(i, 1);
    }
    else if (previous && this.prop.mouse_history[0][i] - previous > PhotoSphereViewer.INERTIA_WINDOW / 10) {
      this.prop.mouse_history.splice(0, i);
      i = 0;
      previous = this.prop.mouse_history[0][i];
    }
    else {
      i++;
      previous = this.prop.mouse_history[0][i];
    }
  }
};