// Dr. JECL - Javascript Event Compatiblity Layer
// 
// Copyright (C) 2005 Nikolas Coukouma and Fujitsu Laboratories of America, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

// This file provides one class and a couple functions:
// applyEmulation( obj ) - adds addEventListener and removeEventListener,
//                    and dispatchEvent if they DO NOT exist. Note that IE
//                    passes by copy,so you should assign the reurn value:
//                      obj = applyEmulation( obj )
// applyEmulationForce( obj ) - adds addEventListener and removeEventListener,
//                    and dispatchEvent even if they DO exist. Note that IE
//                    passes by copy,so you should assign the reurn value:
//                      obj = applyEmulationForce( obj )
//
// In addition to providing listening functions, if your handler
// takes an argument (an event), the following things will be handled
// 1) event will be the actual event
// 2) event.type              - a string describing the event
// 3) event.target            - the object where the event occurred
// 4) event.cur               - the object currently being handled
// 5) event.pageX,event.pageY - coordinates relative to the top left of the page
// 6) event.emulationOn       - will be set to true if emualtion is on
// Note that event.button is NOT handled
//
// INTERNALS:
// 
// Set - A quick implementation of a set. Create a new set with:
//       aSet = new Set
// Set.add( obj ) - add an element to the set
// Set.remove( obj ) - remove an element from the set
// Set.length() - returns the length of the set
// Set.item( index ) - returns the item at the index, or undefined if it's
//                     out of bounds 
// Set.map( func ) - applies func to each element of the set
//                   func( item, index )
// 
// The following functions are meant to be used internally
//
// capture( event ) - recursively propogates an event and calls capturing handlers
// bubble( event ) - similar to capture, but propogation is done in slightly
//            different order
// addEventListener_traditional( name, handler, will_capture )
//    - emulates addEventListener
// removeEventListener_traditional( name, handler, will_capture )
//    - emulates removeEventListener
// dispatchEvent_traditional( name )
//    - emulates dispatchEvent

function Set() {
        this.arr = new Array();
        this.length = function () { return this.arr.length };
        this.item = function ( i ) {
                if( i >= 0 && i < this.arr.length ) {
                        return this.arr[ i ];
                } else {
                        return undefined;
                }
        }
        this.remove = function ( val ) {
               var i;
                for( i = 0; i < this.arr.length; i++ ) {
                        if( this.arr[ i ] === key ) {
                                this.arr.splice( i, 1 );
                                return true;
                        }
                }
                return false;
        }

        this.add = function ( val ) {
                if( val in this ) {
                        return false;
                } else {
                        this.arr.push( val );
                        return true;
                }
        }
        this.map = function( func ) {
                for( i = 0; i < this.arr.length; i++ ) {
                        func( this.arr[ i ], i );
                }
        }
}

function capture( event ) {
        var i;
        var cur = event.cur;
        event.cur = event.cur.parentNode;
        // recurse
        if( this.parentNode != undefined ) {
                if( this.parentNode.capture == undefined ) {
                        this.parentNode.capture = capture;
                }
                this.parentNode.capture( event );
        }
        // calls all capturing handlers
        event.cur = cur;
        
        // lookup the type
        if( this.emu_capture ) {
                arr = this.emu_capture[ event.type ];
                if( arr != undefined ) {
                        // actually call the handlers
                        for( i = 0; i < arr.length(); i++ ) {
                                func = arr.item( i );
                                func( event );
                        }
                }
        }
}

function bubble( event ) {
        var i;
        // calls all bubbleing handlers
        // lookup the type
        if( this.emu_bubble != undefined ) {
                arr = this.emu_bubble[ event.type ];
                if( arr != undefined ) {
                        // actually call the handlers
                        for( i = 0; i < arr.length(); i++ ) {
                                func = arr.item( i );
                                func( event );
                        }
                }
        }
        
        // recurse
        event.cur = event.cur.parentNode;
        if( this.parentNode != undefined ) {
                if( this.parentNode.bubble == undefined ) {
                        this.parentNode.bubble = bubble;
                }
                this.parentNode.bubble( event );
        }
}

function addEventListener_traditional( event, fun, will_capture ) {
        if( will_capture ) {
                // this handler will use emulated capturing
                // lookup the type
                var arr;
                if( this.emu_capture ) {
                        if( this.emu_capture[ event ] ) {
                                arr = this.emu_capture[ event ];
                        } else {
                                arr = this.emu_capture[ event ] = new Set();
                        }
                } else {
                        this.emu_capture = new Object();
                        arr = this.emu_capture[ event ] = new Set();
                }
                arr.add( fun );
        } else {
                var arr;
                // this handler will use emulated capturing
                // lookup the type
                if( this.emu_bubble ) {
                        arr = this.emu_bubble[ event ];
                } else {
                        this.emu_bubble = new Object();
                        arr = this.emu_bubble[ event ] = new Set();
                }
                arr.add( fun );
        }
        this[ 'on'+event ] = function( e ) {
                if( e == undefined ) {
                        e = window.event;
                }
                e.emulationOn = true;
                if( e.target == undefined ) {
                        e.target = e.srcElement;
                }
                if( e.target == this ) {
                        if( ( e.clientX || e.clientY ) &&
                            !( e.pageX ) && !( e.pageY ) ) {
                                e.pageX = e.clientX + document.body.scrollLeft;
                                e.pageY = e.clientY + document.body.scrollTop;
                        }
                        e.cur = e.target;
                        if( this.capture == undefined ) {
                                this.capture = capture;
                        }
                        this.capture( e );
                        if( this.bubble == undefined ) {
                                this.bubble = bubble;
                        }
                        e.cur = e.target;
                        this.bubble( e );
                }
        }
}

function removeEventListener_traditional( event, fun, will_capture ) {
        if( will_capture ) {
                // lookup the type
                if( this.emu_capture ) {
                        arr = this.emu_capture[ event ];
                        arr.remove( fun );
                }
        } else {
                // this handler will use emulated capturing
                // lookup the type
                if( this.emu_bubble ) {
                        arr = this.emu_bubble[ event ];
                        arr.add( fun );
                }
        }
}

function dispatchEvent_traditional( event ) {
        if( this["on"+event] ) {
                var fire = this["on"+event];
                if( typeof fire == 'function' ) {
                        fire();
                }
        }
}

function applyEmulation( o ) {
        if( typeof o == 'object' ) {
                if( o.addEventListener == undefined ) {
                        applyEmulationForce( o );
                }
        }
        return o;
}

function applyEmulationForce( o ) {
        if( typeof o == 'object' ) {
                o.addEventListener = addEventListener_traditional;
                o.removeEventListener = removeEventListener_traditional;
                o.dispatchEvent = dispatchEvent_traditional;
        }
        return o;
}