text: var Class = require('../utils/Class');
  var EventEmitter = require('eventemitter3');
  var DistanceBetween = require('../math/distance/DistanceBetween');
  var InteractiveObject = require('./InteractiveObject');
  var Circle = require('../geom/circle/Circle');
  var CircleContains = require('../geom/circle/Contains');
  var Ellipse = require('../geom/ellipse/Ellipse');
  var EllipseContains = require('../geom/ellipse/Contains');
  var Rectangle = require('../geom/rectangle/Rectangle');
  var RectangleContains = require('../geom/rectangle/Contains');
  var Triangle = require('../geom/triangle/Triangle');
  var TriangleContains = require('../geom/triangle/Contains');
  var PluginManager = require('../plugins/PluginManager');
  
  //  Drag Events
  //  https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
  //  Mouse Events
  //  https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
  
  var InputPlugin = new Class({
  
      Extends: EventEmitter,
  
      initialize:
  
      function InputPlugin (scene)
      {
          EventEmitter.call(this);
  
          //  The Scene that owns this plugin
          this.scene = scene;
  
          this.systems = scene.sys;
  
          if (!scene.sys.settings.isBooted)
          {
              scene.sys.events.once('boot', this.boot, this);
          }
  
          //  InputManager
          this.manager = scene.sys.game.input;
  
          //  A reference to this.scene.sys.displayList (set in boot)
          this.displayList;
  
          //  A reference to the this.scene.sys.cameras (set in boot)
          this.cameras;
  
          //  Proxy references available via the Scene
          this.keyboard = this.manager.keyboard;
          this.mouse = this.manager.mouse;
          this.gamepad = this.manager.gamepad;
  
          //  Only fire callbacks and events on the top-most Game Object in the display list (emulating DOM behavior)
          //  and ignore any GOs below it, or call them all?
          this.topOnly = true;
  
          //  How often should the pointer input be checked?
          //  Time given in ms
          //  Pointer will *always* be checked if it has been moved by the user.
          //  This controls how often it will be polled if it hasn't been moved.
          //  Set to 0 to poll constantly. Set to -1 to only poll on user movement.
          this.pollRate = -1;
  
          //  Internal counter
          this._pollTimer = 0;
  
          //  The distance, in pixels, the pointer has to move while being held down, before it thinks it is being dragged.
          this.dragDistanceThreshold = 0;
  
          //  The amount of time, in ms, the pointer has to be held down before it thinks it is dragging.
          this.dragTimeThreshold = 0;
  
          //  Used to temporarily store the results of the Hit Test
          this._temp = [];
  
          //  list: A list of all Game Objects that have been set to be interactive
          this._list = [];
  
          //  pendingInsertion: Objects waiting to be inserted to the list on the next call to 'begin'
          this._pendingInsertion = [];
  
          //  pendingRemoval: Objects waiting to be removed from the list on the next call to 'begin'
          this._pendingRemoval = [];
  
          //  draggable: A list of all Game Objects that have been enabled for dragging
          this._draggable = [];
  
          //  drag: A list of all Interactive Objects currently considered as being 'draggable' by any pointer, indexed by pointer ID
          this._drag = { 0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [] };
  
          //  over: A list of all Interactive Objects currently considered as being 'over' by any pointer, indexed by pointer ID
          this._over = { 0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [] };
  
          this._validTypes = [ 'onDown', 'onUp', 'onOver', 'onOut', 'onMove', 'onDragStart', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragLeave', 'onDragOver', 'onDrop' ];
      },
  
      boot: function ()
      {
          var eventEmitter = this.systems.events;
  
          eventEmitter.on('preupdate', this.preUpdate, this);
          eventEmitter.on('update', this.update, this);
          eventEmitter.on('shutdown', this.shutdown, this);
          eventEmitter.on('destroy', this.destroy, this);
  
          this.cameras = this.systems.cameras;
  
          this.displayList = this.systems.displayList;
      },
  
      preUpdate: function ()
      {
          var removeList = this._pendingRemoval;
          var insertList = this._pendingInsertion;
  
          var toRemove = removeList.length;
          var toInsert = insertList.length;
  
          if (toRemove === 0 && toInsert === 0)
          {
              //  Quick bail
              return;
          }
  
          var current = this._list;
  
          //  Delete old gameObjects
          for (var i = 0; i < toRemove; i++)
          {
              var gameObject = removeList[i];
  
              var index = current.indexOf(gameObject);
  
              if (index > -1)
              {
                  current.splice(index, 1);
  
                  //  TODO: Clear from _draggable, _drag and _over too
  
                  this.clear(gameObject);
              }
          }
  
          //  Clear the removal list
          removeList.length = 0;
  
          //  Move pendingInsertion to list (also clears pendingInsertion at the same time)
          this._list = current.concat(insertList.splice(0));
      },
  
      clear: function (gameObject)
      {
          var input = gameObject.input;
  
          input.gameObject = undefined;
          input.target = undefined;
          input.hitArea = undefined;
          input.hitAreaCallback = undefined;
          input.callbackContext = undefined;
  
          gameObject.input = null;
  
          return gameObject;
      },
  
      disable: function (gameObject)
      {
          gameObject.input.enabled = false;
      },
  
      enable: function (gameObject, shape, callback)
      {
          if (gameObject.input)
          {
              //  If it is already has an InteractiveObject then just enable it and return
              gameObject.input.enabled = true;
          }
          else
          {
              //  Create an InteractiveObject and enable it
              this.setHitArea(gameObject, shape, callback);
          }
  
          return this;
      },
  
      hitTestPointer: function (pointer)
      {
          var camera = this.cameras.getCameraBelowPointer(pointer);
  
          if (camera)
          {
              pointer.camera = camera;
  
              //  Get a list of all objects that can be seen by the camera below the pointer in the scene and store in 'output' array.
              //  All objects in this array are input enabled, as checked by the hitTest method, so we don't need to check later on as well.
              return this.manager.hitTest(pointer.x, pointer.y, this._list, camera);
          }
          else
          {
              return [];
          }
      },
  
      processDownEvents: function (pointer)
      {
          var currentlyOver = this._temp;
  
          //  Contains ALL Game Objects currently over in the array
          this.emit('pointerdown', pointer, currentlyOver);
  
          var total = 0;
  
          //  Go through all objects the pointer was over and fire their events / callbacks
          for (var i = 0; i < currentlyOver.length; i++)
          {
              var gameObject = currentlyOver[i];
  
              if (!gameObject.input)
              {
                  continue;
              }
  
              total++;
  
              gameObject.emit('pointerdown', pointer, gameObject.input.localX, gameObject.input.localY, pointer.camera);
  
              this.emit('gameobjectdown', pointer, gameObject);
          }
  
          return total;
      },
  
      processDragEvents: function (pointer, time)
      {
          if (this._draggable.length === 0)
          {
              //  There are no draggable items, so let's not even bother going further
              return 0;
          }
  
          var i;
          var c;
          var gameObject;
          var list;
          var input;
          var currentlyOver = this._temp;
  
          //  0 = Not dragging anything
          //  1 = Primary button down and objects below, so collect a draglist
          //  2 = Pointer being checked if meets drag criteria
          //  3 = Pointer meets criteria, notify the draglist
          //  4 = Pointer actively dragging the draglist and has moved
          //  5 = Pointer actively dragging but has been released, notify draglist
  
          if (pointer.dragState === 0 && pointer.primaryDown && pointer.justDown && currentlyOver.length > 0)
          {
              pointer.dragState = 1;
          }
          else if (pointer.dragState > 0 && !pointer.primaryDown && pointer.justUp)
          {
              pointer.dragState = 5;
          }
  
          //  Process the various drag states
  
          //  1 = Primary button down and objects below, so collect a draglist
          if (pointer.dragState === 1)
          {
              //  Get draggable objects, sort them, pick the top (or all) and store them somewhere
              var draglist = [];
  
              for (i = 0; i < currentlyOver.length; i++)
              {
                  gameObject = currentlyOver[i];
  
                  if (gameObject.input.draggable)
                  {
                      draglist.push(gameObject);
                  }
              }
  
              if (draglist.length === 0)
              {
                  pointer.dragState = 0;
  
                  return;
              }
              else if (draglist.length > 1)
              {
                  this.sortGameObjects(draglist);
  
                  if (this.topOnly)
                  {
                      draglist.splice(1);
                  }
              }
  
              //  draglist now contains all potential candidates for dragging
              this._drag[pointer.id] = draglist;
  
              if (this.dragDistanceThreshold === 0 && this.dragTimeThreshold === 0)
              {
                  //  No drag criteria, so snap immediately to mode 3
                  pointer.dragState = 3;
              }
              else
              {
                  //  Check the distance / time
                  pointer.dragState = 2;
              }
          }
  
          //  2 = Pointer being checked if meets drag criteria
          if (pointer.dragState === 2)
          {
              //  Has it moved far enough to be considered a drag?
              if (this.dragDistanceThreshold > 0 && DistanceBetween(pointer.x, pointer.y, pointer.downX, pointer.downY) >= this.dragDistanceThreshold)
              {
                  //  Alrighty, we've got a drag going on ...
                  pointer.dragState = 3;
              }
  
              //  Held down long enough to be considered a drag?
              if (this.dragTimeThreshold > 0 && (time >= pointer.downTime + this.dragTimeThreshold))
              {
                  //  Alrighty, we've got a drag going on ...
                  pointer.dragState = 3;
              }
          }
  
          //  3 = Pointer meets criteria and is freshly down, notify the draglist
          if (pointer.dragState === 3)
          {
              list = this._drag[pointer.id];
  
              for (i = 0; i < list.length; i++)
              {
                  gameObject = list[i];
  
                  input = gameObject.input;
  
                  input.dragState = 2;
  
                  input.dragX = pointer.x - gameObject.x;
                  input.dragY = pointer.y - gameObject.y;
  
                  input.dragStartX = gameObject.x;
                  input.dragStartY = gameObject.y;
  
                  gameObject.emit('dragstart', pointer, input.dragX, input.dragY);
  
                  this.emit('dragstart', pointer, gameObject);
              }
  
              pointer.dragState = 4;
  
              return;
          }
  
          //  4 = Pointer actively dragging the draglist and has moved
          if (pointer.dragState === 4 && pointer.justMoved)
          {
              //  Let's filter out currentlyOver for dropZones only
              var dropZones = [];
  
              for (c = 0; c < currentlyOver.length; c++)
              {
                  if (currentlyOver[c].input.dropZone)
                  {
                      dropZones.push(currentlyOver[c]);
                  }
              }
  
              list = this._drag[pointer.id];
  
              for (i = 0; i < list.length; i++)
              {
                  gameObject = list[i];
  
                  input = gameObject.input;
  
                  //  If this GO has a target then let's check it
                  if (input.target)
                  {
                      var index = dropZones.indexOf(input.target);
  
                      //  Got a target, are we still over it?
                      if (index === 0)
                      {
                          //  We're still over it, and it's still the top of the display list, phew ...
                          gameObject.emit('dragover', pointer, input.target);
  
                          this.emit('dragover', pointer, gameObject, input.target);
                      }
                      else if (index > 0)
                      {
                          //  Still over it but it's no longer top of the display list (targets must always be at the top)
                          gameObject.emit('dragleave', pointer, input.target);
  
                          this.emit('dragleave', pointer, gameObject, input.target);
  
                          input.target = dropZones[0];
  
                          gameObject.emit('dragenter', pointer, input.target);
  
                          this.emit('dragenter', pointer, gameObject, input.target);
                      }
                      else
                      {
                          //  Nope, we've moved on (or the target has!), leave the old target
                          gameObject.emit('dragleave', pointer, input.target);
  
                          this.emit('dragleave', pointer, gameObject, input.target);
  
                          //  Anything new to replace it?
                          //  Yup!
                          if (dropZones[0])
                          {
                              input.target = dropZones[0];
  
                              gameObject.emit('dragenter', pointer, input.target);
  
                              this.emit('dragenter', pointer, gameObject, input.target);
                          }
                          else
                          {
                              //  Nope
                              input.target = null;
                          }
                      }
                  }
                  else if (!input.target && dropZones[0])
                  {
                      input.target = dropZones[0];
  
                      gameObject.emit('dragenter', pointer, input.target);
  
                      this.emit('dragenter', pointer, gameObject, input.target);
                  }
  
                  var dragX = pointer.x - gameObject.input.dragX;
                  var dragY = pointer.y - gameObject.input.dragY;
  
                  gameObject.emit('drag', pointer, dragX, dragY);
  
                  this.emit('drag', pointer, gameObject, dragX, dragY);
              }
          }
  
          //  5 = Pointer actively dragging but has been released, notify draglist
          if (pointer.dragState === 5)
          {
              list = this._drag[pointer.id];
  
              for (i = 0; i < list.length; i++)
              {
                  gameObject = list[i];
  
                  input = gameObject.input;
  
                  input.dragState = 0;
  
                  input.dragX = input.localX - gameObject.displayOriginX;
                  input.dragY = input.localY - gameObject.displayOriginY;
  
                  var dropped = false;
  
                  if (input.target)
                  {
                      gameObject.emit('drop', pointer, input.target);
  
                      this.emit('drop', pointer, gameObject, input.target);
  
                      input.target = null;
  
                      dropped = true;
                  }
  
                  //  And finally the dragend event
  
                  gameObject.emit('dragend', pointer, input.dragX, input.dragY, dropped);
  
                  this.emit('dragend', pointer, gameObject, dropped);
              }
  
              pointer.dragState = 0;
          }
          
          return (pointer.dragState > 0);
      },
  
      processMoveEvents: function (pointer)
      {
          var currentlyOver = this._temp;
  
          this.emit('pointermove', pointer, currentlyOver);
  
          var total = 0;
  
          //  Go through all objects the pointer was over and fire their events / callbacks
          for (var i = 0; i < currentlyOver.length; i++)
          {
              var gameObject = currentlyOver[i];
  
              if (!gameObject.input)
              {
                  continue;
              }
  
              total++;
  
              gameObject.emit('pointermove', pointer, gameObject.input.localX, gameObject.input.localY);
  
              this.emit('gameobjectmove', pointer, gameObject);
  
              if (this.topOnly)
              {
                  break;
              }
          }
  
          return total;
      },
  
      processOverOutEvents: function (pointer)
      {
          var currentlyOver = this._temp;
  
          var i;
          var gameObject;
          var justOut = [];
          var justOver = [];
          var stillOver = [];
          var previouslyOver = this._over[pointer.id];
  
          //  Go through all objects the pointer was previously over, and see if it still is.
          //  Splits the previouslyOver array into two parts: justOut and stillOver
  
          for (i = 0; i < previouslyOver.length; i++)
          {
              gameObject = previouslyOver[i];
  
              if (currentlyOver.indexOf(gameObject) === -1)
              {
                  //  Not in the currentlyOver array, so must be outside of this object now
                  justOut.push(gameObject);
              }
              else
              {
                  //  In the currentlyOver array
                  stillOver.push(gameObject);
              }
          }
  
          //  Go through all objects the pointer is currently over (the hit test results)
          //  and if not in the previouslyOver array we know it's a new entry, so add to justOver
          for (i = 0; i < currentlyOver.length; i++)
          {
              gameObject = currentlyOver[i];
  
              //  Is this newly over?
  
              if (previouslyOver.indexOf(gameObject) === -1)
              {
                  justOver.push(gameObject);
              }
          }
  
          //  By this point the arrays are filled, so now we can process what happened...
  
          //  Process the Just Out objects
          var total = justOut.length;
  
          if (total > 0)
          {
              this.sortGameObjects(justOut);
  
              this.emit('pointerout', pointer, justOut);
  
              //  Call onOut for everything in the justOut array
              for (i = 0; i < total; i++)
              {
                  gameObject = justOut[i];
  
                  if (!gameObject.input)
                  {
                      continue;
                  }
  
                  this.emit('gameobjectout', pointer, gameObject);
  
                  gameObject.emit('pointerout', pointer);
              }
          }
  
          //  Process the Just Over objects
          total = justOver.length;
  
          if (total > 0)
          {
              this.sortGameObjects(justOver);
  
              this.emit('pointerover', pointer, justOver);
  
              //  Call onOver for everything in the justOver array
              for (i = 0; i < total; i++)
              {
                  gameObject = justOver[i];
  
                  if (!gameObject.input)
                  {
                      continue;
                  }
  
                  this.emit('gameobjectover', pointer, gameObject);
  
                  gameObject.emit('pointerover', pointer, gameObject.input.localX, gameObject.input.localY);
              }
          }
  
          //  Add the contents of justOver to the previously over array
          previouslyOver = stillOver.concat(justOver);
  
          //  Then sort it into display list order
          this._over[pointer.id] = this.sortGameObjects(previouslyOver);
  
          return previouslyOver.length;
      },
  
      processUpEvents: function (pointer)
      {
          var currentlyOver = this._temp;
  
          //  Contains ALL Game Objects currently up in the array
          this.emit('pointerup', pointer, currentlyOver);
  
          //  Go through all objects the pointer was over and fire their events / callbacks
          for (var i = 0; i < currentlyOver.length; i++)
          {
              var gameObject = currentlyOver[i];
  
              if (!gameObject.input)
              {
                  continue;
              }
  
              gameObject.emit('pointerup', pointer, gameObject.input.localX, gameObject.input.localY);
  
              this.emit('gameobjectup', pointer, gameObject);
          }
      },
  
      //  Queues a Game Object for insertion into this Input Manager on the next update.
      queueForInsertion: function (child)
      {
          if (this._pendingInsertion.indexOf(child) === -1 && this._list.indexOf(child) === -1)
          {
              this._pendingInsertion.push(child);
          }
  
          return this;
      },
  
      //  Queues a Game Object for removal from this Input Manager on the next update.
      queueForRemoval: function (child)
      {
          this._pendingRemoval.push(child);
  
          return this;
      },
  
      setDraggable: function (gameObjects, value)
      {
          if (value === undefined) { value = true; }
  
          if (!Array.isArray(gameObjects))
          {
              gameObjects = [ gameObjects ];
          }
  
          for (var i = 0; i < gameObjects.length; i++)
          {
              var gameObject = gameObjects[i];
  
              gameObject.input.draggable = value;
  
              var index = this._draggable.indexOf(gameObject);
  
              if (value && index === -1)
              {
                  this._draggable.push(gameObject);
              }
              else if (!value && index > -1)
              {
                  this._draggable.splice(index, 1);
              }
          }
  
          return this;
      },
  
      setHitArea: function (gameObjects, shape, callback)
      {
          if (shape === undefined)
          {
              return this.setHitAreaFromTexture(gameObjects);
          }
  
          if (!Array.isArray(gameObjects))
          {
              gameObjects = [ gameObjects ];
          }
  
          for (var i = 0; i < gameObjects.length; i++)
          {
              var gameObject = gameObjects[i];
  
              gameObject.input = InteractiveObject(gameObject, shape, callback);
  
              this.queueForInsertion(gameObject);
          }
  
          return this;
      },
  
      setHitAreaCircle: function (gameObjects, x, y, radius, callback)
      {
          if (callback === undefined) { callback = CircleContains; }
  
          var shape = new Circle(x, y, radius);
  
          return this.setHitArea(gameObjects, shape, callback);
      },
  
      setHitAreaEllipse: function (gameObjects, x, y, width, height, callback)
      {
          if (callback === undefined) { callback = EllipseContains; }
  
          var shape = new Ellipse(x, y, width, height);
  
          return this.setHitArea(gameObjects, shape, callback);
      },
  
      setHitAreaFromTexture: function (gameObjects, callback)
      {
          if (callback === undefined) { callback = RectangleContains; }
  
          if (!Array.isArray(gameObjects))
          {
              gameObjects = [ gameObjects ];
          }
  
          for (var i = 0; i < gameObjects.length; i++)
          {
              var gameObject = gameObjects[i];
              var frame = gameObject.frame;
  
              var width = 0;
              var height = 0;
  
              if (frame)
              {
                  width = frame.realWidth;
                  height = frame.realHeight;
              }
              else if (gameObject.width)
              {
                  width = gameObject.width;
                  height = gameObject.height;
              }
  
              if (width !== 0 && height !== 0)
              {
                  gameObject.input = InteractiveObject(gameObject, new Rectangle(0, 0, width, height), callback);
  
                  this.queueForInsertion(gameObject);
              }
          }
  
          return this;
      },
  
      setHitAreaRectangle: function (gameObjects, x, y, width, height, callback)
      {
          if (callback === undefined) { callback = RectangleContains; }
  
          var shape = new Rectangle(x, y, width, height);
  
          return this.setHitArea(gameObjects, shape, callback);
      },
  
      setHitAreaTriangle: function (gameObjects, x1, y1, x2, y2, x3, y3, callback)
      {
          if (callback === undefined) { callback = TriangleContains; }
  
          var shape = new Triangle(x1, y1, x2, y2, x3, y3);
  
          return this.setHitArea(gameObjects, shape, callback);
      },
  
      setPollAlways: function ()
      {
          this.pollRate = 0;
          this._pollTimer = 0;
  
          return this;
      },
  
      setPollOnMove: function ()
      {
          this.pollRate = -1;
          this._pollTimer = 0;
  
          return this;
      },
  
      setPollRate: function (value)
      {
          this.pollRate = value;
          this._pollTimer = 0;
  
          return this;
      },
  
      setGlobalTopOnly: function (value)
      {
          this.manager.globalTopOnly = value;
  
          return this;
      },
  
      setTopOnly: function (value)
      {
          this.topOnly = value;
  
          return this;
      },
  
      //  Given an array of Game Objects, sort the array and return it,
      //  so that the objects are in index order with the lowest at the bottom.
      sortGameObjects: function (gameObjects)
      {
          if (gameObjects.length < 2)
          {
              return gameObjects;
          }
  
          this.scene.sys.depthSort();
  
          return gameObjects.sort(this.sortHandlerGO.bind(this));
      },
  
      //  Return the child lowest down the display list (with the smallest index)
      sortHandlerGO: function (childA, childB)
      {
          //  The higher the index, the lower down the display list they are.
          //  So entry 0 will be the top-most item (visually)
          var indexA = this.displayList.getIndex(childA);
          var indexB = this.displayList.getIndex(childB);
  
          if (indexA < indexB)
          {
              return 1;
          }
          else if (indexA > indexB)
          {
              return -1;
          }
  
          //  Technically this shouldn't happen, but if the GO wasn't part of this display list then it'll
          //  have an index of -1, so in some cases it can
          return 0;
      },
  
      //  Return the child lowest down the display list (with the smallest index)
      sortHandlerIO: function (childA, childB)
      {
          //  The higher the index, the lower down the display list they are.
          //  So entry 0 will be the top-most item (visually)
          var indexA = this.displayList.getIndex(childA.gameObject);
          var indexB = this.displayList.getIndex(childB.gameObject);
  
          if (indexA < indexB)
          {
              return 1;
          }
          else if (indexA > indexB)
          {
              return -1;
          }
  
          //  Technically this shouldn't happen, but if the GO wasn't part of this display list then it'll
          //  have an index of -1, so in some cases it can
          return 0;
      },
  
      //  Given an array of Interactive Objects, sort the array and return it,
      //  so that the objects are in index order with the lowest at the bottom.
      sortInteractiveObjects: function (interactiveObjects)
      {
          if (interactiveObjects.length < 2)
          {
              return interactiveObjects;
          }
  
          this.scene.sys.depthSort();
  
          return interactiveObjects.sort(this.sortHandlerIO.bind(this));
      },
  
      stopPropagation: function ()
      {
          if (this.manager.globalTopOnly)
          {
              this.manager.ignoreEvents = true;
          }
  
          return this;
      },
  
      update: function (time, delta)
      {
          var manager = this.manager;
  
          //  Another Scene above this one has already consumed the input events
          if (manager.globalTopOnly && manager.ignoreEvents)
          {
              return;
          }
  
          var pointer = manager.activePointer;
  
          var runUpdate = (pointer.dirty || this.pollRate === 0);
  
          if (this.pollRate > -1)
          {
              this._pollTimer -= delta;
  
              if (this._pollTimer < 0)
              {
                  runUpdate = true;
  
                  //  Discard timer diff
                  this._pollTimer = this.pollRate;
              }
          }
  
          if (!runUpdate)
          {
              return;
          }
  
          this._temp = this.hitTestPointer(pointer);
  
          this.sortGameObjects(this._temp);
  
          if (this.topOnly && this._temp.length)
          {
              //  Only the top-most one counts now, so safely ignore the rest
              this._temp.splice(1);
          }
  
          var total = this.processDragEvents(pointer, time);
  
          if (!pointer.wasTouch)
          {
              total += this.processOverOutEvents(pointer);
          }
  
          if (pointer.justDown)
          {
              total += this.processDownEvents(pointer);
          }
  
          if (pointer.justUp)
          {
              this.processUpEvents(pointer);
          }
  
          if (pointer.justMoved)
          {
              total += this.processMoveEvents(pointer);
          }
  
          if (total > 0 && manager.globalTopOnly)
          {
              //  We interacted with an event in this Scene, so block any Scenes below us from doing the same this frame
              manager.ignoreEvents = true;
          }
      },
  
      //  Scene that owns this is shutting down
      shutdown: function ()
      {
          this._temp.length = 0;
          this._list.length = 0;
          this._draggable.length = 0;
          this._pendingRemoval.length = 0;
          this._pendingInsertion.length = 0;
  
          for (var i = 0; i < 10; i++)
          {
              this._drag[i] = [];
              this._over[i] = [];
          }
  
          this.removeAllListeners();
      },
  
      //  Game level nuke
      destroy: function ()
      {
          this.shutdown();
  
          this.scene = undefined;
          this.cameras = undefined;
          this.manager = undefined;
          this.events = undefined;
          this.keyboard = undefined;
          this.mouse = undefined;
          this.gamepad = undefined;
      },
  
      activePointer: {
  
          get: function ()
          {
              return this.manager.activePointer;
          }
  
      },
  
      //  The x/y coordinates of the ActivePointer based on the first camera in the camera list.
      //  This is only safe to use if your game has just 1 non-transformed camera and doesn't use multi-touch.
      x: {
  
          get: function ()
          {
              return this.manager.activePointer.x;
          }
  
      },
  
      y: {
  
          get: function ()
          {
              return this.manager.activePointer.y;
          }
  
      },
  
  });
  
  PluginManager.register('InputPlugin', InputPlugin, 'input');
  
  module.exports = InputPlugin;
  