/*
 * Copyright 2012 The Toolkitchen Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

/**
 * This module contains the handlers for native platform events.
 * From here, the dispatcher is called to create unified pointer events.
 * Included are touch events (v1) and mouse events.
 */
(function(scope) {
  var dispatcher = scope.dispatcher;
  var pointermap = scope.pointermap;
  var touchMap = Array.prototype.map.call.bind(Array.prototype.map);
  // handler block for native touch events
  var touchEvents = {
    events: [
      'touchstart',
      'touchmove',
      'touchend',
      'touchcancel'
    ],
    firstTouch: null,
    isPrimaryTouch: function(inTouch) {
      return this.firstTouch === inTouch.identifier;
    },
    touchToPointer: function(inTouch) {
      var e = dispatcher.cloneEvent(inTouch);
      // Spec specifies that pointerId 1 is reserved for Mouse.
      // Touch identifiers can start at 0.
      // Add 2 to the touch identifier for compatibility.
      e.pointerId = inTouch.identifier + 2;
      e.target = this.findTarget(e);
      e.bubbles = true;
      e.cancelable = true;
      e.button = 0;
      e.buttons = 1;
      e.isPrimary = this.isPrimaryTouch(inTouch);
      e.pointerType = dispatcher.POINTER_TYPE_TOUCH;
      return e;
    },
    processTouches: function(inEvent, inFunction) {
      var tl = inEvent.changedTouches;
      var pointers = touchMap(tl, this.touchToPointer, this);
      pointers.forEach(inFunction, this);
    },
    findTarget: function(inEvent) {
      // TODO (dfreedman): support shadow.elementFromPoint here, when available
      return document.elementFromPoint(inEvent.clientX, inEvent.clientY);
    },
    touchstart: function(inEvent) {
      if (this.firstTouch === null) {
        this.firstTouch = inEvent.changedTouches[0].identifier;
      }
      this.processTouches(inEvent, this.overDown);
    },
    overDown: function(inPointer) {
      var p = pointermap.addPointer(inPointer.pointerId);
      dispatcher.over(inPointer);
      dispatcher.down(inPointer);
      p.out = inPointer;
    },
    touchmove: function(inEvent) {
      // must preventDefault first touchmove or document will scroll otherwise
      // Per Touch event spec section 5.6
      // http://www.w3.org/TR/touch-events/#the-touchmove-event
      inEvent.preventDefault();
      this.processTouches(inEvent, this.moveOverOut);
    },
    moveOverOut: function(inPointer) {
      var event = inPointer;
      var pointer = pointermap.getPointerById(event.pointerId);
      var outEvent = pointer.out;
      dispatcher.move(event);
      if (outEvent && outEvent.target !== event.target) {
        outEvent.relatedTarget = event.target;
        event.relatedTarget = outEvent.target;
        dispatcher.out(outEvent);
        dispatcher.over(event);
      }
      pointer.out = event;
    },
    touchend: function(inEvent) {
      this.processTouches(inEvent, this.upOut);
      var touch = inEvent.changedTouches[0];
      this.removeFirstTouch(touch);
    },
    upOut: function(inPointer) {
      dispatcher.up(inPointer);
      dispatcher.out(inPointer);
      pointermap.removePointer(inPointer.pointerId);
    },
    touchcancel: function(inEvent) {
      this.processTouches(inEvent, this.cancelOut);
      var touch = inEvent.changedTouches[0];
      this.removeFirstTouch(touch);
    },
    cancel: function(inPointer) {
      dispatcher.cancel(inPointer);
      dispatcher.out(inPointer);
      pointermap.removePointer(inPointer.pointerId);
    },
    removeFirstTouch: function(inTouch) {
      if (this.isPrimaryTouch(inTouch)) {
        this.firstTouch = null;
      }
    }
  };

  // handler block for native mouse events
  var mouseEvents = {
    // Mouse is required to have a pointerId of 1
    POINTER_ID: 1,
    events: [
      'mousedown',
      'mousemove',
      'mouseup',
      'mouseover',
      'mouseout'
    ],
    prepareEvent: function(inEvent) {
      var e = dispatcher.cloneEvent(inEvent);
      e.pointerId = this.POINTER_ID;
      e.isPrimary = true;
      e.pointerType = dispatcher.POINTER_TYPE_MOUSE;
      return e;
    },
    mousedown: function(inEvent) {
      if (pointermap.getPointerIndex(this.POINTER_ID) == -1) {
        var e = this.prepareEvent(inEvent);
        var p = pointermap.addPointer(this.POINTER_ID);
        p.button = inEvent.button;
        dispatcher.down(e);
      }
    },
    mousemove: function(inEvent) {
      var e = this.prepareEvent(inEvent);
      dispatcher.move(e);
    },
    mouseup: function(inEvent) {
      var p = pointermap.getPointerById(this.POINTER_ID);
      if (p && p.button === inEvent.button) {
        var e = this.prepareEvent(inEvent);
        dispatcher.up(e);
        pointermap.removePointer(this.POINTER_ID);
      }
    },
    mouseover: function(inEvent) {
      var e = this.prepareEvent(inEvent);
      dispatcher.over(e);
    },
    mouseout: function(inEvent) {
      var e = this.prepareEvent(inEvent);
      dispatcher.out(e);
    }
  };

  // only activate if this platform does not have pointer events
  if (window.navigator.pointerEnabled === undefined) {
    // We fork the initialization of dispatcher event listeners here because
    // current native touch event systems emulate mouse events. These
    // touch-emulated mouse events behave differently than normal mouse events.
    //
    // Touch-emulated mouse events will only occur if the target element has
    // either a native click handler, or the onclick attribute is set. In
    // addition, the touch-emulated mouse events fire only after the finger has
    // left the screen, negating any live-tracking ability a developer might want.
    //
    // The only way to disable mouse event emulation by native touch systems is to
    // preventDefault every touch event, which we feel is inelegant.
    //
    // Therefore we choose to only listen to native touch events if they exist.

    if ('ontouchstart' in window) {
      dispatcher.registerSource('touch', touchEvents);
    } else {
      dispatcher.registerSource('mouse', mouseEvents);
    }
    dispatcher.registerTarget(document);
    Object.defineProperty(window.navigator, 'pointerEnabled', {value: true, enumerable: true});
  }
})(window.__PointerEventShim__);

@@ -806,32 +806,165 @@
 ntifier;%0A    %7D,%0A
+    removePrimaryTouch: function(inTouch) %7B%0A      if (this.isPrimaryTouch(inTouch)) %7B%0A        this.firstTouch = null;%0A      %7D%0A    %7D,%0A
     touchToPoint
@@ -3149,89 +3149,8 @@
 t);%0A
-      var touch = inEvent.changedTouches%5B0%5D;%0A      this.removeFirstTouch(touch);%0A
     
@@ -3295,32 +3295,74 @@
 ter.pointerId);%0A
+      this.removePrimaryTouch(inPointer);%0A
     %7D,%0A    touch
@@ -3445,89 +3445,8 @@
 t);%0A
-      var touch = inEvent.changedTouches%5B0%5D;%0A      this.removeFirstTouch(touch);%0A
     
@@ -3454,24 +3454,27 @@
 ,%0A    cancel
+Out
 : function(i
@@ -3615,134 +3615,45 @@
     
-%7D,%0A    removeFirstTouch: function(inTouch) %7B%0A      if (this.isPrimaryTouch(inTouch)) %7B%0A        this.firstTouch = null;%0A      %7D
+  this.removePrimaryTouch(inPointer);
 %0A   

Clean up primary pointer tracking for touch events