/**
 *  Event Mixins
 *  (c) 2006 Seth Dillingham <seth.dillingham@gmail.com>
 *
 *  This software is hereby released into the public domain. Do with it as
 *  you please, but with the understanding that it is provided "AS IS" and 
 *  without any warranty of any kind.
 *  
 *  (But I'd love to be told about where and how this code is being used.)
 **/

/**
 *  Description:
 *    add support (to any object or class) by mixing this class into your own
 *  
 *  Requires prototype.js
 *  
 *  Usage:
 *    To publish custom events:
 *      1. mix this class with your own via
 *         Object.extend( [your class or prototype], Event.Publisher )
 *      2. post events by calling
 *         this.dispatchEvent( [event name], [data for event] )
 *   
 *    To activate and deactivate the event-tracing feature, just call 
 *      this.toggleEventsTrace()
 **/

Event.Publisher = Class.create();
Object.extend(Event.Publisher, {
    _ls_event_targets: null,

    _event_source_id: null,

    _fl_trace_events: false,

    getEventSourceId: function () {
        if (typeof this._event_source_id == 'function') return this._event_source_id();
        else return this._event_source_id;
    },

    getEventTarget: function (event_name) {
        if (!this._ls_event_targets) this._ls_event_targets = new Array();

        if (!this._ls_event_targets[event_name]) document.body.appendChild(
        this._ls_event_targets[event_name] = document.createElement('A'));

        return this._ls_event_targets[event_name];
    },

    addEventListener: function (event_name, callback_func, capturing) {
        var targ = this.getEventTarget(event_name);

        Event.observe(targ, 'click', callback_func, capturing);

        if (this._fl_trace_events) {
            var data = {
                publisher: this.getEventSourceId(),
                event_name: event_name,
                listener: callback_func,
                capturing: capturing,
                event_source_proxy: targ
            };

            this.dispatchEvent('eventListenerAdded', data, true, true);
        }
    },

    removeEventListener: function (event_name, callback_func, capturing) {
        var targ = this.getEventTarget(event_name);

        Event.stopObserving(targ, 'click', callback_func, capturing);

        if (this._fl_trace_events) {
            var data = {
                publisher: this.getEventSourceId(),
                event_name: event_name,
                listener: callback_func,
                capturing: capturing,
                event_source_proxy: targ
            };

            this.dispatchEvent('eventListenerRemoved', data, true, true);
        }
    },

    dispatchEvent: function (event_name, data, can_bubble, cancelable) {
        var targ = this.getEventTarget(event_name);
        var event_data = {
            event_name: event_name,
            event_target: this,
            data: data ? data : null
        };

        if (!can_bubble) can_bubble = false;
        if (!cancelable) cancelable = false;

        var event = Event.create(event_data, can_bubble, cancelable, true, targ);

        if (this._fl_trace_events) {
            if (event_name.match(/event(?:ListenerAdded|ListenerRemoved|Dispatched|Received)/)) return;

            var data = {
                publisher: this.getEventSourceId(),
                event_name: event_name,
                event_data: event_data,
                can_bubble: can_bubble,
                cancelable: cancelable,
                event_source_proxy: targ,
                result: event
            };

            this.dispatchEvent('eventDispatched', data, true, true);
        }
    },

    toggleEventsTrace: function () {
        var trace = Event.Tracer.findTracer();

        if (!trace || !this._fl_trace_events) {
            this._fl_trace_events = true;

            trace = Event.Tracer.startTrace();

            trace.registerPublisher(this);
        }
        else {
            this._fl_trace_events = false;

            if (trace) trace.unregisterPublisher(this);
        }

        return this._fl_trace_events;
    },

    isEventsTraceActive: function () {
        return this._fl_trace_events;
    }
});

/**
 *  MIX IN: Event.Listener
 *  
 *  Description:
 *    easily add support for receiving totally custom events
 *    (to any object or class) by mixing this class into
 *    your own
 *  
 *  Usage:
 *	   To receive custom events:
 *      1. mix this class with your own via
 *         Object.extend( [your class or prototype], EventListener )
 *      2. listen for events by calling (from your object)
 *         this.listen()
 *         (see params for this.listen, below)
 **/
Event.Listener = Class.create();
Object.extend(Event.Listener, {
    _listens: null,

    getEventHandlerName: function (event_name) {
        var onEvent_name = event_name.split(/[ _]/).join('-').camelize();

        return "on" + onEvent_name.charAt(0).toUpperCase() + onEvent_name.substr(1);
    },

    /**
     *	 Params:
     *    event_source [object]:
     *      the object which will generate the events, and which implements (or
     *      mixes in) the Event.Publisher interface (we need addEventListener)
     *    event_name [string]:
     *      the name of the event for which your object will listen
     *    use_capture [boolean]:
     *      standard DOM Event API param
     *    onEvent_name [string]:
     *      the name of the method in your object which will be called when the
     *      event is received if you omit this param, listen will look for a
     *      function named with the CapitalizedCamelCased name of the event with
     *      "on" at the front. So, if the event is named "message_received",
     *      we'll look for a function named "onMessageReceived" You can override
     *      this behavior by overriding getEventHandlerName in your object.
     **/
    listenForEvent: function (event_source, event_name, use_capture, onEvent_name) {
        if (!onEvent_name) onEvent_name = this.getEventHandlerName(event_name);

        if (!this._listens) this._listens = new Array();

        //added this in to allow for anonymous function handling of an event
        var eventHandler = this[onEvent_name];

        if (typeof(onEvent_name) == 'function') {
            eventHandler = onEvent_name;
        }

        var cb = eventHandler.bindAsEventListener(this);
        this._listens.push([event_source, event_name, use_capture, onEvent_name, cb])

        event_source.addEventListener(event_name, cb, use_capture);
    },

    stopListeningForEvent: function (event_source, event_name, use_capture, onEvent_name) {
        if (!this._listens) return false;

        if (!onEvent_name) onEvent_name = this.getEventHandlerName(event_name);

        var ix_item = -1;
        var ls = this._listens.detect(function (val, ix) {
            if ((val[0] == event_source) && (val[1] == event_name) && (val[2] == use_capture) && (val[3] == onEvent_name)) {
                ix_item = ix;
                return true;
            }
        });

        if (ix_item >= 0) {
            this._listens.splice(ix_item, 1);

            event_source.removeEventListener(event_name, ls[4], use_capture);

            return true;
        }

        return false;
    }
});

/**
 *  Extensions to Prototype's Event object,
 *  for cleanly creating and dispatching custom events
 *  
 *  Called from Event.Publisher
 **/
Object.extend(Event, {
    create: function (event_data, can_bubble, cancelable, fl_dispatch, target) {
        var event;

        if (document.createEvent) { // gecko, safari
            if (!can_bubble) can_bubble = false;
            if (!cancelable) cancelable = false;

            if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
                event = document.createEvent('HTMLEvents')

                event.initEvent('click', can_bubble, cancelable);
            }
            else { // gecko uses MouseEvents
                event = document.createEvent('MouseEvents')

                event.initMouseEvent("click", can_bubble, cancelable, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
            }
        }
        else { // msie
            event = document.createEventObject();
            event.event_type = 'onclick';
        }

        event.event_data = event_data;

        if (fl_dispatch) Event.dispatch(target, event);

        return event;
    },

    dispatch: function (target, event) {
        if (document.createEvent) return target.dispatchEvent(event);
        else return target.fireEvent((typeof(event.event_type) != "undefined") ? event.event_type : 'onclick', event);
    }
});