/**
 * Handles desktop inputs, and passes them onto the main controller.
 * @constructor
 * @param {ascii.Controller} controller
 */
ascii.DesktopController = function(controller) {
  /** @type {ascii.Controller} */ this.controller = controller;

  /** @type {boolean} */ this.isDragging = false;

  this.installBindings();
};

/**
 * @param {number} delta
 */
ascii.DesktopController.prototype.handleZoom = function(delta) {
  var newzoom = this.controller.view.zoom * (delta > 0 ? 1.1 : 0.9);
  newzoom = Math.max(Math.min(newzoom, 5), 0.2);
  this.controller.view.setZoom(newzoom);
};

/**
 * Installs input bindings associated with keyboard controls.
 */
ascii.DesktopController.prototype.installBindings = function() {
  var canvas = this.controller.view.canvas;
  $(canvas).bind('mousewheel', function(e) {
      this.handleZoom(e.originalEvent.wheelDelta);
  }.bind(this));

  $(canvas).mousedown(function(e) {
      // Can drag by holding either the control or meta (Apple) key.
      if (e.ctrlKey || e.metaKey) {
        this.controller.startDrag(new ascii.Vector(e.clientX, e.clientY));
      } else {
        this.controller.startDraw(new ascii.Vector(e.clientX, e.clientY));
      }
  }.bind(this));

  // Pass these events through to the main controller.
  $(canvas).mouseup(function(e) {
      this.controller.endAll();
  }.bind(this));

  $(canvas).mouseleave(function(e) {
      this.controller.endAll();
  }.bind(this));

  $(canvas).mousemove(function(e) {
      this.controller.handleMove(new ascii.Vector(e.clientX, e.clientY));
  }.bind(this));
};


/**
 * Handles touch inputs, and passes them onto the main controller.
 * @constructor
 * @param {ascii.Controller} controller
 */
ascii.TouchController = function(controller) {
  /** @type {ascii.Controller} */ this.controller = controller;

  /** @type {ascii.Vector} */ this.pressVector;

  /** @type {number} */ this.originalZoom;
  /** @type {number} */ this.zoomLength;

  /** @type {number} */ this.pressTimestamp;
  /** @type {boolean} */ this.dragStarted = false;
  /** @type {boolean} */ this.zoomStarted = false;

  this.installBindings();
};

/**
 * @param {ascii.Vector} position
 */
ascii.TouchController.prototype.handlePress = function(position) {
  this.pressVector = position;
  this.pressTimestamp = $.now();
  this.dragStarted = false;

  // If a drag or zoom didn't start and if we didn't release already, then handle it as a draw.
  window.setTimeout(function() {
    if (!this.dragStarted && !this.zoomStarted && this.pressVector != null) {
      this.controller.startDraw(position);
    }
  }.bind(this), DRAG_LATENCY);
};

/**
 * The multi-touch version of handlePress.
 * @param {ascii.Vector} positionOne
 * @param {ascii.Vector} positionTwo
 */
ascii.TouchController.prototype.handlePressMulti =
    function(positionOne, positionTwo) {
  // A second finger as been placed, cancel whatever we were doing.
  this.controller.endAll();
  this.zoomStarted = true;
  this.dragStarted = false;
  this.zoomLength = positionOne.subtract(positionTwo).length();
  this.originalZoom = this.controller.view.zoom;
};

/**
 * @param {ascii.Vector} position
 */
ascii.TouchController.prototype.handleMove = function(position) {
  // Initiate a drag if we have moved enough, quickly enough.
  if (!this.dragStarted &&
      ($.now() - this.pressTimestamp) < DRAG_LATENCY &&
      position.subtract(this.pressVector).length() > DRAG_ACCURACY) {
      this.dragStarted = true;
      this.controller.startDrag(position);
  }
  // Pass on the event.
  this.controller.handleMove(position);
};

/**
 * The multi-touch version of handleMove, effectively only deals with zooming.
 * @param {ascii.Vector} positionOne
 * @param {ascii.Vector} positionTwo
 */
ascii.TouchController.prototype.handleMoveMulti =
      function(positionOne, positionTwo) {
  if (this.zoomStarted) {
    var newZoom = this.originalZoom *
        positionOne.subtract(positionTwo).length() / this.zoomLength;
    newZoom = Math.max(Math.min(newZoom, 5), 0.5);
    this.controller.view.setZoom(newZoom);
  }
};

/**
 * Ends all current actions, cleans up any state.
 */
ascii.TouchController.prototype.reset = function() {
  this.dragStarted = false;
  this.zoomStarted = false;
  this.pressVector = null;
};

/**
 * Installs input bindings associated with touch controls.
 */
ascii.TouchController.prototype.installBindings = function() {
  var canvas = this.controller.view.canvas;

  $(canvas).bind('touchstart', function(e) {
      e.preventDefault();
      if (e.originalEvent.touches.length == 1) {
        this.handlePress(new ascii.Vector(
           e.originalEvent.touches[0].pageX,
           e.originalEvent.touches[0].pageY));
      } else if (e.originalEvent.touches.length > 1) {
        this.handlePressMulti(new ascii.Vector(
           e.originalEvent.touches[0].pageX,
           e.originalEvent.touches[0].pageY),
           new ascii.Vector(
               e.originalEvent.touches[1].pageX,
               e.originalEvent.touches[1].pageY));
      }
  }.bind(this));

  $(canvas).bind('touchmove', function(e) {
      e.preventDefault();
      if (e.originalEvent.touches.length == 1) {
        this.handleMove(new ascii.Vector(
           e.originalEvent.touches[0].pageX,
           e.originalEvent.touches[0].pageY));
      } else if (e.originalEvent.touches.length > 1) {
        this.handleMoveMulti(new ascii.Vector(
           e.originalEvent.touches[0].pageX,
           e.originalEvent.touches[0].pageY),
           new ascii.Vector(
               e.originalEvent.touches[1].pageX,
               e.originalEvent.touches[1].pageY));
      }
  }.bind(this));

  // Pass through, no special handling.
  $(canvas).bind('touchend', function(e) {
      e.preventDefault();
      this.reset();
      this.controller.endAll();
  }.bind(this));
};
