/*****************************************************************************
 *
 * Copyright (c) 2004-2005 Guido Wesdorp. All rights reserved.
 *
 * This software is distributed under the terms of the JSBase
 * License. See LICENSE.txt for license text.
 *
 *****************************************************************************/

var global = this;
global.misclib = new function() {
    /* This lib mostly contains fixes for common problems in JS, additions to
        the core global functions and wrappers for fixing browser issues.

        See the comments in the code for further explanations.
    */

    // reference to the lib to reach it from the functions/classes
    var misclib = this;

    var Timer = function() {
        /* Wrapper around window.setTimeout, to solve number of problems:
        
            * setTimeout gets a string rather than a function reference as an
                argument

            * setTimeout can not handle arguments to the callable properly, 
                since it's evaluated in the window namespace, not in the current
                scope

            Usage:

                // call the global 'schedule' function to register a function
                // called 'foo' on the current (imaginary) object, passing in
                // a variable called 'bar' as an argument, so it gets called 
                // after 1000 msecs
                window.misclib.schedule(this, this.foo,  1000, bar);

            Note that if you don't care about 'this' inside the function 
            you register, you can just pass in a ref to the 'window' object
            as the first argument. No lookup is done in the namespace, the
            first argument is only used as the 'this' variable inside the
            function.
        */
        this.lastid = 0;
        this.functions = {};
        
        this.registerCallable = function(object, func, timeout) {
            /* register a function to be called with a timeout

                args: 
                    object - the context in which to call the function ('this')
                    func - the function
                    timeout - timeout in millisecs
                    
                all other args will be passed 1:1 to the function when called
            */
            var args = new Array();
            for (var i=3; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            var id = this._createUniqueId();
            this.functions[id] = new Array(object, func, args);
            // setTimeout will be called in the module namespace, where the 
            // timer_instance variable also resides (see below)
            setTimeout("window.misclib.timer_instance." +
                        "_handleFunction(" + id + ")", timeout);
        };

        this._handleFunction = function(id) {
            /* private method that does the actual function call */
            var obj = this.functions[id][0];
            var func = this.functions[id][1];
            var args = this.functions[id][2];
            this.functions[id] = null;
            func.apply(obj, args);
        };

        this._createUniqueId = function() {
            /* create a unique id to store the function by */
            while (this.lastid in this.functions && 
                        this.functions[this.lastid]) {
                this.lastid++;
                if (this.lastid > 100000) {
                    this.lastid = 0;
                }
            }
            return this.lastid;
        };
    };

    // create a timer instance in the module namespace
    var timer_instance = this.timer_instance = new Timer();

    // this is the function that should be called to register callables
    this.schedule = function() {
            timer_instance.registerCallable.apply(timer_instance, arguments)};

    // cross-browser event registration
    var events_supported = true;
    try {
        window;
    } catch(e) {
        events_supported = false;
    };
    
    if (events_supported) {
        // first some privates
        
        // a registry for events so they can be unregistered on unload of the 
        // body
        var event_registry = {};

        // an iterator for unique ids
        var currid = 0;

        // just to make some guy on irc happy, store references to 
        // browser-specific wrappers so we don't have to find out the type of
        // browser on every registration (yeah, yeah, i admit it was quite a 
        // good tip... ;)
        var reg_handler = null;
        var unreg_handler = null;
        if (window.addEventListener) {
            reg_handler = function(element, eventname, handler) {
                // XXX not sure if anyone ever uses the last argument...
                element.addEventListener(eventname, handler, false);
            };
            unreg_handler = function(element, eventname, handler) {
                element.removeEventListener(eventname, handler, false);
            };
        } else if (window.attachEvent) {
            reg_handler = function(element, eventname, handler) {
                element.attachEvent('on' + eventname, handler);
            };
            dereg_handler = function(element, eventname, handler) {
                element.detachEvent('on' + eventname, handler);
            };
        } else {
            reg_handler = function(element, eventname, handler) {
                var message = 'Event registration not supported or not ' +
                                'understood on this platform';
                if (global.exception) {
                    throw(new exception.NotSupported(message));
                } else {
                    throw(message);
                };
            };
        };

        this.addEventHandler = function(element, eventname, handler, context) {
            /* Method to register an event handler

                Works in standards compliant browsers and IE, and solves a
                number of different problems. Most obviously it makes that 
                there's only a single function to use and memorize, but also 
                it makes that 'this' inside the function points to the context 
                (usually you'll want to pass in the object the method is 
                defined on), and it fixes memory leaks (IE is infamous for 
                leaking memory, which can lead to problems if you register a 
                lot of event handlers, especially since the memory leakage 
                doesn't disappear on page reloads, see e.g. 
                http://www.bazon.net/mishoo/articles.epl?art_id=824).
            
                Arguments:
                
                    * element - the object to register the event on
                    
                    * eventname - a string describing the event (Mozilla style, 
                        so without the 'on')
                    
                    * handler - a reference to the function to be called when 
                        the event occurs

                    * context - the 'this' variable inside the function

                The arguments passed to the handler:

                    * event - a reference to the event fired

                    * all arguments passed in to this function besides the ones
                        described
                        
            */
            var args = new Array();
            for (var i=4; i < arguments.length; i++) {
                args.push(arguments[i]);
            };
            var wrapper = function(event) {
                testing.debug('adding event handler');
                args = [event].concat(args);
                handler.apply(context, args)
                args.shift();
            };
            currid++;
            event_registry[currid] = [element, eventname, wrapper];
            reg_handler(element, eventname, wrapper);
            // return the wrapper so the event can be unregistered later on
            return currid;
        };

        this.removeEventHandler = function(regid) {
            /* method to remove an event handler for both IE and Mozilla 
            
                pass in the id returned by addEventHandler
            */
            // remove the entry from the event_registry
            var args = event_registry[regid];
            if (!args) {
                var message = 'removeEventListener called with ' +
                                'unregistered id';
                if (global.exception) {
                    throw((new exception.NotFound(message)));
                } else {
                    throw(message);
                };
            };
            delete event_registry[regid];
            unreg_handler(args[0], args[1], args[2]);
        };

        this.removeAllEventHandlers = function() {
            for (var id in event_registry) {
                try {
                    misclib.removeEventHandler(id);
                } catch(e) {
                    // element must have been discarded or something...
                };
            };
        };
    };

    // string representation of objects
    this.safe_repr = function(o) {
        var visited = {}; 
        var ret = misclib._repr_helper(o, visited);
        delete visited;
        return ret;
    };

    this.repr = function(o, dontfallback) {
        try {
            return misclib._repr_helper(o);
        } catch(e) {
            // when something fails here, we assume it's because of recursion
            // and fall back to safe_repr()
            if (dontfallback) {
                throw(e);
            };
            return misclib.safe_repr(o);
        };
    };

    this._repr_helper = function(o, visited) {
        var newid = 0;
        if (visited) {
            // XXX oomph... :|
            for (var attr in visited) {
                if (visited[attr] === o) {
                    return '#' + attr + '#';
                };
                newid++;
            };
        };
        var ret = 'unknown?';
        var type = typeof o;
        switch (type) {
            case 'undefined':
                ret = 'undefined'
                break;
            case 'string':
                ret = '"' + string.escape(o) + '"';
                break;
            case 'object':
                if (o instanceof Array) {
                    if (visited) {
                        visited[newid] = o;
                    };
                    var r = [];
                    for (var i=0; i < o.length; i++) {
                        var newo = o[i];
                        r.push(misclib._repr_helper(newo, visited));
                    };
                    ret = ''
                    if (visited) {
                        ret += '#' + newid + '=';
                    };
                    ret += '[' + r.join(', ') + ']';
                } else if (o instanceof Date) {
                    ret = '(new Date(' + o.valueOf() + '))';
                } else {
                    if (visited) {
                        visited[newid] = o;
                    };
                    var r = [];
                    for (var attr in o) {
                        var newo = o[attr];
                        r.push(attr + ': ' + misclib._repr_helper(newo, 
                                                                visited));
                    };
                    ret = '';
                    if (visited) {
                        ret += '#' + newid + '=';
                    };
                    ret += '{' + r.join(', ') + '}';
                };
                break;
            default:
                ret = o.toString();
                break;
        };
        return ret;
    };

    this.dir = function(obj) {
        /* list all the attributes of an object */
        var ret = [];
        for (var attr in obj) {
            ret.push(attr);
        };
        return ret;
    };

    this.print = function(message, win) {
        /* write output in a way that the user can see it

            creates a div in browsers, prints to stdout in spidermonkey

            this is here not only as a convenient way to print output, but also
            so that it's possible to override, that way customizing prints
            from code
        */
        if (win === undefined) {
            win = window;
        };
        message = '' + message;
        var p = null;
        try {
          win.foo;
        } catch(e) {
          // no window, so we guess we're inside some JS console, assuming it
          // has a print() function... if there are situations in which this
          // doesn't suffice, please mail me (johnny@johnnydebris.net)
          print(message);
          return;
        };
        var div = document.createElement('div');
        div.className = 'printed';
        div.appendChild(document.createTextNode(message));
        document.getElementsByTagName('body')[0].appendChild(div);
    };
}();
