L3D.ObjectClicker = (function() {

function pointerCheck(camera) {
    return ((camera instanceof L3D.PointerCamera || camera instanceof TutoCamera) && camera.pointerLocked);
}

/**
 * Class to allow easy object clicking
 * @constructor
 * @memberof L3D
 * @param renderer {THREE.Renderer} the renderer
 * @param camera {THREE.Camera} the camera to use
 * @param objects {Object[]} the objects on which the collision is possible
 * @param onHover {function} callback called when the mouse hovers an object
 * @param onClick {function} callback called when the user clicks an object
 * @param [domElement=document] {Element} element to which ObjectClicker listens to
 */
var ObjectClicker = function(renderer, camera, objects, onHover, onClick, domElement) {

    /**
     * Renderer used
     * @type {THREE.Renderer}
     */
    this.renderer = renderer;

    /**
     * Objects on which the collision is possible
     * @type {Object[]}
     */
    this.objects = objects;

    /**
     * Function called when the mouse hovers an object
     * @type {function}
     */
    this.onHover = onHover;

    /**
     * Function called when the user clicks on an object
     * @type {function}
     */
    this.onClick = onClick;

    /**
     * Camera used for ray casting
     * @type {THREE.Camera}
     */
    this.camera = camera;

    /**
     * Position of the mouse
     * @type {Object}
     */
    this.mouse = {x: null, y: null};

    /**
     * Element on which we will listen
     * @type {Element}
     */
    this.domElement = domElement || document;

    /**
     * Element currently hovered
     * @type {Object}
     */
    this.hoveredElement = null;

    /**
     * Raycaster used for finding the objects
     * @private
     * @type {THREE.Raycaster}
     */
    this.raycaster = new THREE.Raycaster();

    /**
     * Currently pointed object
     * @type {Object}
     */
    this.currentPointedObject = null;

    /**
     * Previously pointed object
     * @type {Object}
     */
    this.previousPointedObject = null;

    // Add event listeners
    var self = this;
    this.domElement.addEventListener('mousemove', function(event) { self.update(event); });

    if (navigator.userAgent.indexOf("Firefox") > 0) {
        // If firefox
        this.domElement.addEventListener('mousedown', function(event) {self.click(event); });
    } else {
        // If chrome
        this.domElement.addEventListener('click', function(event) { self.click(event); });
        this.domElement.addEventListener('contextmenu', function(event) { event.button = 2; self.click(event); });
    }

};

/**
 * Returns the object pointed by the mouse
 */
ObjectClicker.prototype.getPointedObject = function() {

    // Compute x and y for unprojection
    var x = ( this.mouse.x / this.renderer.domElement.width ) * 2 - 1;
    var y = - (this.mouse.y / this.renderer.domElement.height) * 2 + 1;

    if (pointerCheck(this.camera)) {

        x = 0;
        y = 0;

    }

    var vector = new THREE.Vector3(x,y,0.5);
    vector.unproject(this.camera);

    // Set raycaster
    this.raycaster.set(this.camera.position, vector.sub(this.camera.position).normalize());

    // Compute intersections
    var intersects = this.raycaster.intersectObjects(this.objects, false);

    // Avoid non-raycastable objects
    for (var i = 0; i < intersects.length && !intersects[i].object.raycastable; i++){}

    // Objects are sorted by distance in intersects, the best is the first
    return intersects[i];

};

/**
 * Updates the position of the mouse and the hovered object and calls onHover
 * @param event {Event} the event received by 'mousemove'
 */
ObjectClicker.prototype.update = function(event) {

    // Set mouse position
    if (event !== undefined) {

        this.mouse.x = event.offsetX || event.layerX;
        this.mouse.y = event.offsetY || event.layerY;

    }

    // Update current pointed object
    this.previousPointedObject = this.currentPointedObject;
    this.currentPointedObject = this.getPointedObject();

    // If those two objects are different, call onHover
    if (this.previousPointedObject !== this.currentPointedObject) {
        var p = pointerCheck(this.camera);
        this.onHover(
            this.currentPointedObject,
            p ? this.renderer.domElement.width  / 2 : this.mouse.x,
            p ? this.renderer.domElement.height / 2 : this.mouse.y
        );

    }

};

/**
 * Calls onClick on the current pointed element
 */
ObjectClicker.prototype.click = function(event) {

    this.onClick(this.currentPointedObject, this.mouse.x, this.mouse.y, event);

};

return ObjectClicker;

})();
