/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
 * http://scratchdisk.com/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name Emitter
 * @namespace
 * @private
 */
var Emitter = {
    on: function(type, func) {
        // If an object literal is passed, attach all callbacks defined in it
        if (typeof type !== 'string') {
            Base.each(type, function(value, key) {
                this.on(key, value);
            }, this);
        } else {
            var entry = this._eventTypes[type];
            if (entry) {
                var handlers = this._callbacks = this._callbacks || {};
                handlers = handlers[type] = handlers[type] || [];
                if (handlers.indexOf(func) === -1) { // Not added yet, add now.
                    handlers.push(func);
                    // See if this is the first handler that we're attaching,
                    // and call install if defined.
                    if (entry.install && handlers.length == 1)
                        entry.install.call(this, type);
                }
            }
        }
        return this;
    },

    off: function(type, func) {
        // If an object literal is passed, detach all callbacks defined in it
        if (typeof type !== 'string') {
            Base.each(type, function(value, key) {
                this.off(key, value);
            }, this);
            return;
        }
        var entry = this._eventTypes[type],
            handlers = this._callbacks && this._callbacks[type],
            index;
        if (entry && handlers) {
            // See if this is the last handler that we're detaching (or if we
            // are detaching all handlers), and call uninstall if defined.
            if (!func || (index = handlers.indexOf(func)) !== -1
                    && handlers.length === 1) {
                if (entry.uninstall)
                    entry.uninstall.call(this, type);
                delete this._callbacks[type];
            } else if (index !== -1) {
                // Just remove this one handler
                handlers.splice(index, 1);
            }
        }
        return this;
    },

    once: function(type, func) {
        return this.on(type, function() {
            func.apply(this, arguments);
            this.off(type, func);
        });
    },

    emit: function(type, event) {
        // Returns true if fired, false otherwise
        var handlers = this._callbacks && this._callbacks[type];
        if (!handlers)
            return false;
        var args = [].slice.call(arguments, 1);
        for (var i = 0, l = handlers.length; i < l; i++) {
            // When the handler function returns false, prevent the default
            // behaviour and stop propagation of the event by calling stop()
            if (handlers[i].apply(this, args) === false
                    && event && event.stop) {
                event.stop();
                break;
            }
        }
        return true;
    },

    responds: function(type) {
        return !!(this._callbacks && this._callbacks[type]);
    },

    // Keep deprecated methods around from previous Callback interface.
    attach: '#on',
    detach: '#off',
    fire: '#emit',

    _installEvents: function(install) {
        var handlers = this._callbacks,
            key = install ? 'install' : 'uninstall';
        for (var type in handlers) {
            if (handlers[type].length > 0) {
                var entry = this._eventTypes[type],
                    func = entry[key];
                if (func)
                    func.call(this, type);
            }
        }
    },

    statics: {
        // Override inject() so that sub-classes automatically add the accessors
        // for the event handler functions (e.g. #onMouseDown) for each property
        // NOTE: This needs to be defined in the first injection scope, as for
        // simplicity, we don't loop through all of them here.
        inject: function inject(src) {
            var events = src._events;
            if (events) {
                // events can either be an object literal or an array of
                // strings describing the on*-names.
                // We need to map lowercased event types to the event
                // entries represented by these on*-names in _events.
                var types = {};
                Base.each(events, function(entry, key) {
                    var isString = typeof entry === 'string',
                        name = isString ? entry : key,
                        part = Base.capitalize(name),
                        type = name.substring(2).toLowerCase();
                    // Map the event type name to the event entry.
                    types[type] = isString ? {} : entry;
                    // Create getters and setters for the property
                    // with the on*-name name:
                    name = '_' + name;
                    src['get' + part] = function() {
                        return this[name];
                    };
                    src['set' + part] = function(func) {
                        // Detach the previous event, if there was one.
                        var prev = this[name];
                        if (prev)
                            this.off(type, prev);
                        if (func)
                            this.on(type, func);
                        this[name] = func;
                    };
                });
                src._eventTypes = types;
            }
            return inject.base.apply(this, arguments);
        }
    }
};
