goog.provide('lime.events.EventDispatcher');

goog.require('lime.events.Event');

/**
 * EventDispatcher object. Deals with event handlers
 * @param {lime.Director} director Director object.
 * @constructor
 */
lime.events.EventDispatcher = function(director) {
    this.director = director;
    this.handlers = {};
    this.swallows = {};

};

/**
 * Register the event listener for node
 * @param {lime.Node} node Node that responds to events.
 * @param {string} eventType type of event to listen.
 */
lime.events.EventDispatcher.prototype.register = function(node, eventType) {
    if (!goog.isDef(this.handlers[eventType])) {
        this.handlers[eventType] = [node];
        this.handlers[eventType].sorted = true;
        //base element switch here because safari fires touchend on
        //dom tree changes otherwise
        goog.events.listen(eventType.substring(0, 5) == 'touch' && node!=this.director ?
            document : (eventType.substring(0, 3) == 'key' ?
            window : this.director.domElement.parentNode), eventType,
            this, false, this);
    }
    else {
        if (!goog.array.contains(this.handlers[eventType], node)) {
            this.handlers[eventType].push(node);
            this.handlers[eventType].sorted = false;
        }
    }
};

/**
 * Release the event listener for node
 * @param {lime.Node} node Node that responds to events.
 * @param {string} eventType type of event to release.
 */
lime.events.EventDispatcher.prototype.release = function(node, eventType) {
    if (goog.isDef(this.handlers[eventType])) {
         goog.array.remove(this.handlers[eventType], node);
         if (!this.handlers[eventType].length) {
             goog.events.unlisten(this.director.domElement.parentNode,
                 eventType, this, false, this);
            delete this.handlers[eventType];
         }
    }
};

/**
 * Update order of handler nodes. Called on tree changes.
 * @param {lime.Node} node Node that has changed.
 */
lime.events.EventDispatcher.prototype.updateDispatchOrder = function(node){
    for(var eventType in this.handlers){
        var handlers = this.handlers[eventType];
        if(goog.array.contains(handlers,node)){
            handlers.sorted = false;
        }
    }
}

/**
 * Setup swallow rule for an event. Swallow means that next events from
 * same interaction will go straight to the handler
 * @param {lime.events.Event} e Event.
 * @param {string} type Event type.
 * @param {function(lime.events.Event)} handler Function to call.
 */
lime.events.EventDispatcher.prototype.swallow = function(e, type, handler) {
   /*
   // don't remember why this check was needed
   if (e.type != 'mousedown' && e.type != 'touchstart' &&
        e.type != 'touchmove' && e.type != 'keydown') return;*/
    var id = e.identifier;
    if (!goog.isDef(this.swallows[id])) {
        this.swallows[id] = [];
    }
    this.swallows[id].push([e.targetObject, type, handler]);
    //console.log('listen');
    goog.events.listen(e.targetObject, type, goog.nullFunction);
};

/**
 * Handle DOM event
 * @param {Event} e Event.
 */
lime.events.EventDispatcher.prototype.handleEvent = function(e) {

    if (!goog.isDef(this.handlers[e.type])) return;

    if (!this.handlers[e.type].sorted) {
        this.handlers[e.type].sort(lime.Node.compareNode);
        this.handlers[e.type].sorted = true;
    }

    var handlers = this.handlers[e.type].slice(), didhandle = false;

    var touchIndex = 0, doBreak = 0;

    while (!doBreak) {

    var ee = new lime.events.Event(this);
    ee.type = e.type;
    ee.event = e;

    if (e.type.substring(0, 5) == 'touch') {
        var touch = e.getBrowserEvent().changedTouches[touchIndex];
        ee.screenPosition = new goog.math.Coordinate(touch.pageX, touch.pageY);
        ee.identifier = touch.identifier;
        touchIndex++;

        if (touchIndex >= e.getBrowserEvent().changedTouches.length) {
            doBreak = 1;
        }
    }
    else {
        ee.screenPosition = new goog.math.Coordinate(
            e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,
            e.clientY + document.body.scrollTop + document.documentElement.scrollTop
        );
        doBreak = 1;
    }

    if (goog.isDef(this.swallows[ee.identifier])) {
        var s = this.swallows[ee.identifier];

        for (var i = 0; i < s.length; i++) {
            if (s[i][1] == e.type || (goog.isArray(s[i][1]) &&
                    goog.array.contains(s[i][1], e.type))) {

                var handler = s[i][0];
                ee.targetObject = handler;
                ee.position = handler.screenToLocal(ee.screenPosition);
                s[i][2].call(handler, ee);
                didhandle = true;
            }
        }
        //handler.dispatchEvent(ee);

        if (e.type == 'touchend' || e.type == 'touchcancel' ||
            e.type == 'mouseup' || e.type == 'keyup') {
            delete ee.targetObject;
            ee.release();
        }
    }
    else {
        for (var i = 0; i < handlers.length; i++) {

            var handler = handlers[i];

            if (this.director.getCurrentScene() != handler.getScene() &&
                handler != this.director) continue;

            if (handler.getHidden() || !handler.inTree_) continue;

            ee.targetObject = handler;

            if (handler.hitTest(ee) || e.type.substring(0, 3) == 'key') {

                ee.targetObject = handler;
                handler.dispatchEvent(ee);
                didhandle = true;

                if (ee.event.propagationStopped_) break;
            }

        }

    }

    }

    if (didhandle && this.shouldPreventDefault(e.type)) {
        e.preventDefault();
    }
};

/**
 * If an event of the given type should be allowed to have default behavior.
 */
lime.events.EventDispatcher.prototype.shouldPreventDefault = function(type) {
    if (goog.string.startsWith(type, "key")) {
        return false;
    }
    return true;
};
