# Source: input/Pointer.js

    import {EventManager} from "../utils/EventManager.js";
    import {Vector2} from "../math/Vector2.js";
    import {Key} from "./Key.js";

    /**
     * Pointer object is used to called input from the user, works for booth mouse or touch screens.
     *
     * It is responsible for synchronizing user input with the render of the graphics.
     *
     * @class
     * @param {Element} domElement DOM element to create the pointer events.
     * @param {Element} canvas Canvas DOM element where the content is being drawn.
     */
    function Pointer(domElement, canvas)
    {
    	//Raw data
    	this._keys = new Array(5);
    	this._position = new Vector2(0, 0);
    	this._positionUpdated = false;
    	this._delta = new Vector2(0, 0);
    	this._wheel = 0;
    	this._wheelUpdated = false;
    	this._doubleClicked = new Array(5);

    	/**
    	 * Array with pointer buttons status.
    	 *
    	 * @type {number[]}
    	 */
    	this.keys = new Array(5);

    	/**
    	 * Pointer position inside of the window (coordinates in window space).
    	 *
    	 * This value is accumulated from multiple mouse triggered events between updated.
    	 *
    	 * @type {Vector2}
    	 */
    	this.position = new Vector2(0, 0);

    	/**
    	 * Pointer movement (coordinates in window space). Since the last update.
    	 *
    	 * This value is accumulated from multiple mouse triggered events between updated.
    	 *
    	 * @type {Vector2}
    	 */
    	this.delta = new Vector2(0, 0);

    	/**
    	 * Pointer scroll wheel movement, since the last update.
    	 *
    	 * @type {number}
    	 */
    	this.wheel = 0;

    	/**
    	 * Indicates a button of the pointer was double clicked.
    	 *
    	 * @type {boolean}
    	 */
    	this.doubleClicked = new Array(5);

    	/**
    	 * DOM element where to attach the pointer events.
    	 *
    	 * @type {Element}
    	 */
    	this.domElement = (domElement !== undefined) ? domElement : window;

    	/**
    	 * Canvas attached to this pointer instance used to calculate position and delta in element space coordinates.
    	 *
    	 * @type {Element}
    	 */
    	this.canvas = null;
    	if(canvas !== undefined)
    	{
    		this.setCanvas(canvas);
    	}

    	/**
    	 * Event manager responsible for updating the raw data variables.
    	 *
    	 * Different events are used depending on the host platform.
    	 *
    	 * When the update method is called the raw data is reset.
    	 *
    	 * @type {EventManager}
    	 */
    	this.events = new EventManager();

    	//Initialize key instances
    	for(var i = 0; i < 5; i++)
    	{
    		this._doubleClicked[i] = false;
    		this.doubleClicked[i] = false;
    		this._keys[i] = new Key();
    		this.keys[i] = new Key();
    	}

    	//Self pointer
    	var self = this;

    	//Scroll wheel
    	if(window.onmousewheel !== undefined)
    	{
    		//Chrome, edge
    		this.events.add(this.domElement, "mousewheel", function(event)
    		{
    			self._wheel = event.deltaY;
    			self._wheelUpdated = true;
    		});
    	}
    	else if(window.addEventListener !== undefined)
    	{
    		//Firefox
    		this.events.add(this.domElement, "DOMMouseScroll", function(event)
    		{
    			self._wheel = event.detail * 30;
    			self._wheelUpdated = true;
    		});
    	}
    	else
    	{
    		this.events.add(this.domElement, "wheel", function(event)
    		{
    			self._wheel = event.deltaY;
    			self._wheelUpdated = true;
    		});
    	}

    	//Touchscreen input events
    	if(window.ontouchstart !== undefined || navigator.msMaxTouchPoints > 0)
    	{
    		//Auxiliar variables to calculate touch delta
    		var lastTouch = new Vector2(0, 0);

    		//Touch start event
    		this.events.add(this.domElement, "touchstart", function(event)
    		{
    			var touch = event.touches[0];

    			self.updatePosition(touch.clientX, touch.clientY, 0, 0);
    			self.updateKey(Pointer.LEFT, Key.DOWN);

    			lastTouch.set(touch.clientX, touch.clientY);
    		});

    		//Touch end event
    		this.events.add(this.domElement, "touchend", function(event)
    		{
    			self.updateKey(Pointer.LEFT, Key.UP);
    		});

    		//Touch cancel event
    		this.events.add(this.domElement, "touchcancel", function(event)
    		{
    			self.updateKey(Pointer.LEFT, Key.UP);
    		});

    		//Touch move event
    		this.events.add(document.body, "touchmove", function(event)
    		{
    			var touch = event.touches[0];
    			self.updatePosition(touch.clientX, touch.clientY, touch.clientX - lastTouch.x, touch.clientY - lastTouch.y);
    			lastTouch.set(touch.clientX, touch.clientY);
    		});
    	}

    	//Move
    	this.events.add(this.domElement, "mousemove", function(event)
    	{
    		self.updatePosition(event.clientX, event.clientY, event.movementX, event.movementY);
    	});

    	//Button pressed
    	this.events.add(this.domElement, "mousedown", function(event)
    	{
    		self.updateKey(event.which - 1, Key.DOWN);
    	});

    	//Button released
    	this.events.add(this.domElement, "mouseup", function(event)
    	{
    		self.updateKey(event.which - 1, Key.UP);
    	});

    	//Drag start
    	this.events.add(this.domElement, "dragstart", function(event)
    	{
    		self.updateKey(event.which - 1, Key.UP);
    	});

    	//Pointer double click
    	this.events.add(this.domElement, "dblclick", function(event)
    	{
    		self._doubleClicked[event.which - 1] = true;
    	});

    	this.create();
    }

    Pointer.prototype = Pointer;
    Pointer.prototype.constructor = Pointer;

    /**
     * Left pointer button.
     *
     * @static
     * @type {number}
     */
    Pointer.LEFT = 0;

    /**
     * Middle pointer button.
     *
     * @static
     * @type {number}
     */
    Pointer.MIDDLE = 1;

    /**
     * Right pointer button.
     *
     * @static
     * @type {number}
     */
    Pointer.RIGHT = 2;

    /**
     * Back pointer navigation button.
     *
     * @static
     * @type {number}
     */
    Pointer.BACK = 3;

    /**
     * Forward pointer navigation button.
     *
     * @static
     * @type {number}
     */
    Pointer.FORWARD = 4;

    /**
     * Element to be used for coordinates calculation relative to that canvas.
     *
     * @param {DOM} element Canvas to be attached to the Pointer instance
     */
    Pointer.setCanvas = function(element)
    {
    	this.canvas = element;

    	element.pointerInside = false;

    	element.addEventListener("mouseenter", function()
    	{
    		this.pointerInside = true;
    	});

    	element.addEventListener("mouseleave", function()
    	{
    		this.pointerInside = false;
    	});
    };

    /**
     * Check if pointer is inside attached canvas (updated async).
     *
     * @return {boolean} True if pointer is currently inside the canvas
     */
    Pointer.insideCanvas = function()
    {
    	return this.canvas !== null && this.canvas.pointerInside;
    };

    /**
     * Check if pointer button is currently pressed.
     *
     * @param {Number} button Button to check status of
     * @return {boolean} True if button is currently pressed
     */
    Pointer.buttonPressed = function(button)
    {
    	return this.keys[button].pressed;
    };

    /**
     * Check if pointer button was double clicked.
     *
     * @param {Number} button Button to check status of
     * @return {boolean} True if some pointer button was just double clicked
     */
    Pointer.buttonDoubleClicked = function(button)
    {
    	return this.doubleClicked[button];
    };

    /**
     * Check if a pointer button was just pressed.
     *
     * @param {Number} button Button to check status of
     * @return {boolean} True if button was just pressed
     */
    Pointer.buttonJustPressed = function(button)
    {
    	return this.keys[button].justPressed;
    };

    /**
     * Check if a pointer button was just released.
     *
     * @param {Number} button Button to check status of
     * @return {boolean} True if button was just released
     */
    Pointer.buttonJustReleased = function(button)
    {
    	return this.keys[button].justReleased;
    };

    /**
     * Update pointer position.
     *
     * @param {Number} x
     * @param {Number} y
     * @param {Number} xDiff
     * @param {Number} yDiff
     */
    Pointer.updatePosition = function(x, y, xDiff, yDiff)
    {
    	if(this.canvas !== null)
    	{
    		var rect = this.canvas.getBoundingClientRect();
    		x -= rect.left;
    		y -= rect.top;
    	}

    	this._position.set(x, y);
    	this._delta.x += xDiff;
    	this._delta.y += yDiff;
    	this._positionUpdated = true;
    };

    /**
     * Update a pointer button.
     *
     * @param {Number} button
     * @param {Number} action
     */
    Pointer.updateKey = function(button, action)
    {
    	if(button > -1)
    	{
    		this._keys[button].update(action);
    	}
    };

    /**
     * Update pointer buttons state, position, wheel and delta synchronously.
     *
     * Should be called every frame on the update loop before reading any values from the pointer.
     */
    Pointer.update = function()
    {
    	//Update pointer keys state
    	for(var i = 0; i < 5; i++)
    	{
    		if(this._keys[i].justPressed && this.keys[i].justPressed)
    		{
    			this._keys[i].justPressed = false;
    		}
    		if(this._keys[i].justReleased && this.keys[i].justReleased)
    		{
    			this._keys[i].justReleased = false;
    		}

    		this.keys[i].set(this._keys[i].justPressed, this._keys[i].pressed, this._keys[i].justReleased);

    		//Update pointer double click
    		if(this._doubleClicked[i] === true)
    		{
    			this.doubleClicked[i] = true;
    			this._doubleClicked[i] = false;
    		}
    		else
    		{
    			this.doubleClicked[i] = false;
    		}
    	}

    	//Update pointer wheel
    	if(this._wheelUpdated)
    	{
    		this.wheel = this._wheel;
    		this._wheelUpdated = false;
    	}
    	else
    	{
    		this.wheel = 0;
    	}

    	//Update pointer Position if needed
    	if(this._positionUpdated)
    	{
    		this.delta.copy(this._delta);
    		this.position.copy(this._position);

    		this._delta.set(0,0);
    		this._positionUpdated = false;
    	}
    	else
    	{
    		this.delta.x = 0;
    		this.delta.y = 0;
    	}
    };

    /**
     * Create pointer events to collect input data.
     *
     * Should be called before using the pointer object.
     */
    Pointer.create = function()
    {
    	this.events.create();
    };

    /**
     * Dispose pointer events, should be called after the objects is no longer required.
     *
     * If not called leaves the window events created leaving a memory/code leak.
     */
    Pointer.dispose = function()
    {
    	this.events.destroy();
    };

    export {Pointer};