/*
Development Library - Copyright 2005 Six Apart
$Id: devel.js 23025 2006-02-07 01:49:33Z ydnar $
*/


/* benchmarking */

benchmark = function( callback, iterations ) {
    var start = new Date();
    for( var i = 0; i < iterations; i++ )
        callback();
    var end = new Date();
    return (end.getSeconds() - start.getSeconds()) +
        (end.getMilliseconds() - start.getMilliseconds()) / 1000;
}


inspect = function( object, allProperties, noBreaks ) {
    var out = "";
    for( var property in object ) {
        try {
            if( !allProperties && !object.hasOwnProperty( property ) )
                continue;
            out += property + ": " + object[ property ] +
                (noBreaks ? "\n" : "<br />");
        } catch( e ) {}
    }
    return out;
}

shellCommands = {
    
        load : function load(url) {
	        var sc = Logger.singleton._win.document.createElement("script");
	        sc.type = "text/javascript";
	        sc.src = url;
	        Logger._win.document.getElementsByTagName("head")[0].appendChild( sc );
            Logger.singleton.logDebug("Loading " + url + "...");
	    },
	
	    print : function print(txt) {
            log(txt);
        },
	
	    pr : function pr(txt)  {
	        shellCommands.print(txt);
	        return txt;
	    },
	
	    props : function props(el) {
	        var ns = ["Methods", "Fields", "Unreachables"];
	        var as = [[], [], []]; // array of (empty) arrays of arrays!
	        var p, j, i; // loop variables, several used multiple times
	
	        var protoLevels = 0;
	
	        for (p = el; p; p = p.__proto__) {
	            for (i=0; i<ns.length; ++i) as[i][protoLevels] = [];
	            ++protoLevels;
	        }
	
	        for(var a in el) {
	            // Shortcoming: doesn't check that VALUES are the same in object and prototype.'
	
	            var protoLevel = -1;
	            try {
	                for (p = el; p && (a in p); p = p.__proto__)
	                    ++protoLevel;
	            } catch(e) {
	                protoLevel = 0;
	            }
	
	            var type = 1;
	            try {
	                if ((typeof el[a]) == "function") type = 0;
	            } catch (e) { type = 2; }
	
	            as[type][protoLevel].push(a);
	        }
	
	        function times(x, n) { return n ? x + times(x, n-1) : ""; }
	
	        for ( j = 0; j < protoLevels; ++j )
	            for ( i = 0; i < ns.length; ++i )
	                if ( as[i][j].length )
	                    Logger.singleton.logDebug( ns[i] + times(" of prototype", j), as[i][j].join(", ") );
	    },
	
	    blink : function blink( node ) {
	        if ( !node )                      throw("blink: argument is null or undefined.");
	        if ( node.nodeType == null )      throw("blink: argument must be a node.");
	        if ( node.nodeType == 3 )         throw("blink: argument must not be a text node");
	        if ( node.documentElement )       throw("blink: argument must not be the document object");
	
	        function setOutline( o ) { 
	            return function() {
	                if ( node.style.outline != node.style.bogusProperty ) {
	                    // browser supports outline (Firefox 1.1 and newer, CSS3, Opera 8).
	                    node.style.outline = o;
	                } else if ( node.style.MozOutline != node.style.bogusProperty ) {
	                    // browser supports MozOutline (Firefox 1.0.x and older)
	                    node.style.MozOutline = o;
	                } else {
	                    // browser only supports border (IE). border is a fallback because it moves things around.
	                    node.style.border = o;
	                }
	            }
	        } 
	  
	        function focusIt( a ) {
	            return function() {
	                a.focus();
	            }
	        }
	
	        if ( node.ownerDocument ) {
	            var windowToFocusNow = ( node.ownerDocument.defaultView || node.ownerDocument.parentWindow );
	            if ( windowToFocusNow )
	                setTimeout( focusIt( windowToFocusNow.top ), 0);
	        }
	
	        for(var i=1;i<7;++i)
	            setTimeout(setOutline((i%2) ? '3px solid red' : 'none'), i*100);
	
	        setTimeout(focusIt(window), 800);
	        setTimeout(focusIt(Logger.singleton._in), 810);
	        setTimeout(Logger.singleton.refocus, 820);
	    },
	
	    scope : function scope(sc) {
	        if (!sc) sc = {};
	        Logger.singleton._scope = sc;
	        Logger.singleton.logWarn("Scope is now " + sc + ".  If a variable is not found in this scope, window will also be searched.  New variables will still go on window.", "message");
	    },
	
	    mathHelp : function mathHelp() {
	        Logger.singleton.logDebug("Math constants", "E, LN2, LN10, LOG2E, LOG10E, PI, SQRT1_2, SQRT2", "propList");
	        Logger.singleton.logDebug("Math methods", "abs, acos, asin, atan, atan2, ceil, cos, exp, floor, log, max, min, pow, random, round, sin, sqrt, tan", "propList");
	    },
	
	    ans : undefined
	};

/* logging, alert override */

Logger = new Class( Object, {
    width: 420,
    height: 240,
    windowName: "log",
    colors: {
        INFO: "#000000",
        ERROR: "#f00000",
        DEBUG: "#005f16",
        WARN: "#2634cf"
    },
    count: 0,


    log: function() {
        this.logMessages.call( this, "INFO", arguments );
    },
    
    
    logError: function() {
        this.logMessages.call( this, "ERROR", arguments );
    },
    
    
    logWarn: function() {
        this.logMessages.call( this, "WARN", arguments );
    },


    logDebug: function() {
        this.logMessages.call( this, "DEBUG", arguments );
    },
    
    
    logMessages: function( type, messages ) {
        try {
            var message = "";
            for( var i = 0; i < messages.length; i++ )
                if ( messages[ i ].name && messages[ i ].message )
                    message += messages[ i ].name + ": " + messages[ i ].message;
                else
                    message += messages[ i ];

            // create window
            this.createWindow();

            // check for no window
            if( !this.window ) {
                confirm( "Logger popup window blocked. Using confirm() instead.\n\n" + msg );
                return true;
            }

            // create div
            var div = this.window.document.createElement( "div" );
            div.className = type;
            div.style.color = this.colors[ type ] || "#000";
            div.style.backgroundColor = (this.count % 2) ? "#bfbfbf" : "#ffffff";
            div.style.width = "auto";
            div.style.padding = "3px";
            div.innerHTML = message;

            // append to window
            if ( !this.content )
                this.content = this.window.document.getElementById( "content" );
            this.content.appendChild( div );
            this.content.scrollTop = this.content.scrollHeight;
            return true;
        } catch( e ) {}
    },
    

    createWindow: function() {
        if( this.window && this.window.document )
            return;
        
        // create window
        var x = "auto";
        var y = "auto";
        var attr = "resizable=yes, menubar=no, location=no, directories=no, scrollbars=yes, status=no, " +
            "width=" + this.width + ", height=" + this.height + 
            "screenX=" + x + ", screenY=" + y + ", " +
            "left=" + x + ", top=" + y + ", "; 
        // 2006-01-19 cmb For WebKit debugging, change "" below to "/logger".
        this.window = window.open( "", this.windowName, attr );
        
        // check for blocked popup
        if( !this.window )
            return;
        
        // for safari
        window.top.focus();
        
        var instance;
        try {
            instance = this.window.__Logger;
        } catch( e ) {
            this.window.location.replace( "about:blank" );
        }
        
        // check for pre-existing instance
        if( instance ) {
            // create divider div
            var div = this.window.document.createElement( "div" );
            div.style.backgroundColor = "#ffffff"
            div.style.width = "auto";
            div.style.height = "2px";
            div.style.fontSize = "0.1px";
            div.style.lineHeight = "0.1px";
            if ( !this.content )
                this.content = this.window.document.getElementById( "content" );
            this.content.appendChild( div );
        } else {
            // write body
            var doc = this.window.document;
            doc.open( "text/html", "replace" );
            doc.write( "<html><head><title>JavaScript Log</title><script type=\"text/javascript\">var classes = {};function swapClass(e,c){var o=document.getElementById('content');if (e.checked) {for(b in classes) {if(classes[b] == -1 && b == c) delete classes[b];}} else { classes[c] = -1; } var cs=[]; for(b in classes) {if(classes[b] == -1)cs.push(b);} o.className = cs.length ? cs.join(' ') : '' }" );
            doc.write( "</script></head><body onload=\"try{ document.getElementById('command-area').focus(); }catch(e){};\">" );
            doc.write( "<style><!-- .tdebug .debug { display: none; } .twarn .warn { display: none; } .tinfo .info { display: none; } .terror .error { display: none; } --></style>" );
            doc.write( "<div id=\"toolbar\" style=\"position: absolute; left: 0px; top: 0px; width:100%; height: 25px; background-color: #CCCCCC;\">" );
            doc.write( "<input type=\"checkbox\" id=\"INFO\" checked onchange=\"swapClass(this,'tinfo');\"> INFO&nbsp;<input type=\"checkbox\" id=\"DEBUG\" checked onchange=\"swapClass(this,'tdebug');\">" );
            doc.write( "DEBUG&nbsp;<input type=\"checkbox\" id=\"WARN\" checked onchange=\"swapClass(this,'twarn');\"> WARN&nbsp;" );
            doc.write( "<input type=\"checkbox\" id=\"ERROR\" checked onchange=\"swapClass(this,'terror');\"> ERROR" );
            doc.write( "<button onclick=\"document.getElementById('content').innerHTML='';\">Clear</button>" );
            doc.write( "</div><div id=\"content\" style=\"position: absolute; top: 26px; width:100%; height: expression( document.body.clientHeight - toolbar.clientHeight - 73 ); bottom: 73px; overflow: auto;\"></div>" );
            doc.write( "<div id=\"bottom-bar\" style=\"position: absolute; left: 0px; bottom: 0px; width:100%; height: 72px; background-color: #CCCCCC;\"><textarea cols=\"50\" rows=\"3\" id=\"command-area\" style=\"width: 100%; height: 70px\"></textarea></div></body></html>" );
            doc.close();
            
            // setup style
            this.window.title = "JavaScript Logger";
            this.window.document.body.style.margin = "0";
            this.window.document.body.style.padding = "0";
            this.window.document.body.style.fontFamily = "verdana, 'lucida grande', geneva, arial, helvetica, sans-serif";
            this.window.document.body.style.fontSize = "10px";
        }
        
        // get previous instance and attach new instance
        this.prev = instance;
        this.window.__Logger = this;
        
        if ( this.prev ) {
            // copy history
            this.histList = this.prev.histList || [ "" ];
            this.histPos = this.prev.histPos || 0;
            
            // copy message count
            this.count = this.prev.count;
            
            // dereference previous previous
            this.prev.prev = null;
        }
            
        this._in = this.window.document.getElementById( "command-area" );

        if ( this.window.opener && !this.window.opener.closed )
            this._win = this.window.opener;
        else
            this._win = this.window;
        
        var _this = this;
        this._win.onerror = function( error, url, line ) {
            
            var trace = "----ERROR----<br/>\n";
            var haveStackTrace = false;
            var numErrors = 0;
            if ( error.constructor.toString().match( /^function Array/ ) == null) {
              	if ( defined( error.message ) )
              	  trace += "Error Message: " + error.message;
                  
              	if ( defined( error.description ) )
          	      trace += "<br/>\nError Description: " + error.description;
              	
              	if ( defined( error.name ) )
      	          trace += "<br/>\nError Name: " + error.name;
              	
                if ( defined( error.fileName ) )
              	  trace += "<br/>\nFile Name: " + error.fileName;
              	
                if ( defined( error.lineNumber ) )
              	  trace += "<br/>\nLine Number: " + error.lineNumber;
              	
                if ( defined( error.stack ) ) {
              	  trace += "<br/>\nStack Trace: " + error.stack;
      	          haveStackTrace = true;
              	} else
              	  trace += error.toString() + "\n<br/>";
            }
            
            if ( url )
                trace += "<br/>File Name: " + url;

            if ( line )
                trace += "<br/>Line Num: " + line;
      
            if ( !haveStackTrace ) {
            
                _this._showStackTrace_init(arguments);
            
                do {
                  callingMethod = _this._showStackTrace_getCaller(currentMethod);
                  if ( callingMethod ) {
                    trace += callingMethod.toString().match( /function.*/ )[ 0 ].replace( /{.*/, "" ) + "<br/>\n";
                    currentMethod = _this._showStackTrace_getCurrent( callingMethod.arguments );
                  }
                  numErrors++;
                } while ( defined( currentMethod.caller ) && currentMethod.caller != null && numErrors < maxNumErrors );
            }
            
            trace += "<br/>\n-------------";

            _this.logError( trace );
            
            return true;
        };

        this.window.document.onclick = function( e ) {
            if (!e) e = event || _this.window.event;
            
            try {
            
            if ( !e.target )
                e.target = e.srcElement
            
            var g = e.target;
  
            while ( !g.tagName )
                g = g.parentNode;
            var t = g.tagName.toUpperCase();
            if ( t == "A" || t == "INPUT" )
                return;
    
            if ( window.getSelection ) {
                // Mozilla
                if ( String( window.getSelection() ) ) return;
            } else if ( document.getSelection ) {
                // Opera? Netscape 4?
                if ( document.getSelection() ) return;
            } else {
                // IE
                if ( document.selection.createRange().text ) return;
            }
            _this.refocus(); 
            } catch(er) {
                _this.logError( er );
            };
        };
        
        this._in.onkeydown = function( e ) {
            if ( !e ) e = event || _this.window.event;
            
            try {
            //_this.logDebug(e.keyCode + " = " + e.keycode);

            if ( e.shiftKey && e.keyCode == 13 ) {
                // enter and shift-enter
    		} else if ( e.keyCode == 13 ) { // enter
    		    try { _this.handleCommand(); } catch( er ) { _this.logError( er ); };
    		    setTimeout(function() { _this._in.value = ""; }, 0);
    		} else if ( e.keyCode == 38 ) { // up
    		    // go up in history if at top or ctrl-up
	    	    if ( e.ctrlKey || _this._in.selectionStart == null || _this._in.selectionStart == 0 )
                    _this.hist( true );
    		} else if ( e.keyCode == 40 ) { // down
	    	    // go down in history if at end or ctrl-down
    		    if ( e.ctrlKey || _this._in.selectionStart == null || _this._in.selectionEnd == _this._in.textLength )
                    _this.hist(false);
    		} else if ( e.keyCode == 9 ) { // tab
    		    _this.tabComplete();
    		    setTimeout( function() { _this.refocus(); }, 0 ); // refocus because tab was hit
    		} else { }
		
            } catch( er ) {
                _this.logError( er );
            };
            return true;
        };
        
        this.initTarget();
        this.refocus();
    },

    _showStackTrace_init: function( arg ) {
        currentMethod = this._showStackTrace_getCurrent( arg );
    },


    /**
     * This method gets the current function/method in the call stack.  
     * Works on browsers that use the ECMA standard calls and on IE 5+.
     * @param arg <code>Object</code>  An <code>arguments</code> object from a function call.
     */ 
    _showStackTrace_getCurrent: function( arg ) {
        return typeof ( ( arg.callee != "undefined" ) && arg.callee != null ) ? arg.callee : arg.caller;
    },


    /**
     * This method gets the next function/method up the call stack.  
     * Works on browsers that use the ECMA standard calls and on IE 5+.
     * @param current <code>function</code>  The current function in the call stack being walked.
     */ 
    _showStackTrace_getCaller: function(current) {
        return typeof ( ( current.caller != "undefined" ) && current.caller != null ) ? current.caller : current.callee;
    },


    _scope: {},
    _win: null,
    _in: null,
    tooManyMatches: null,
    question: null,

    initTarget: function() {
        this._win.Shell = window;
        this._win.print = shellCommands.print;
    },
    
    histList: [ "" ],
    histPos: 0,
    
    hist: function( up ) {
        // histList[0] = first command entered, [1] = second, etc.
        // type something, press up --> thing typed is now in "limbo"
        // (last item in histList) and should be reachable by pressing 
        // down again.

        var L = this.histList.length;

        if ( L == 1 ) return;

        if ( up ) {
            if ( this.histPos == L-1 ) {
                // Save this entry in case the user hits the down key.
                this.histList[ this.histPos ] = this._in.value;
            }
            if ( this.histPos > 0 ) {
                this.histPos--;
                // Use a timeout to prevent up from moving cursor within new text
                // Set to nothing first for the same reason
                var _this = this;
                setTimeout(function() {
                    _this._in.value = '';
                    _this._in.value = _this.histList[ _this.histPos ]; 
                    if ( _this._in.setSelectionRange ) 
                        _this._in.setSelectionRange( 0, 0 );
                }, 0);
            }
        } else {
            // down
            if ( this.histPos < L-1 ) {
                this.histPos++;
                this._in.value = this.histList[ this.histPos ];
            } else if ( this.histPos == L-1 ) {
                // Already on the current entry: clear but save
                if ( this._in.value ) {
                    this.histList[ this.histPos ] = this._in.value;
                    ++this.histPos;
                    this._in.value = "";
                }
            }
        }
    },
   

    handleCommand: function( s ) {
        this._in.value = this.question = s ? s : this._in.value;

        if ( this.question == "" ) return;

        this.histList[ this.histList.length-1 ] = this.question;
        this.histList[ this.histList.length ] = "";
        this.histPos = this.histList.length - 1;
  
        // Unfortunately, this has to happen *before* the JavaScript is run, so that 
        // print() output will go in the right place.
        this._in.value = '';
        this.logDebug('> '+this.question);

        if ( this._win.closed ) {
            this.logError("Target window has been closed.");
            return;
        }
        
        try {
            ("Shell" in this._win)
        } catch(e) {
            this.logError("The Debug Shell cannot access variables in the target window: ",e);
            return;
        }
        
        if (("Shell" in this._win)) {
            this.initTarget(); // silent
            
            // Evaluate Shell.question using _win's eval (this is why eval isn't in the |with|, IIRC).
            this._win.location.href = "javascript:try{ var _this = window.__Logger; if(!_this) { _this = Logger.singleton; } _this.logAnswer(eval('with(_this._scope) with(Shell.shellCommands) {' + _this.question + String.fromCharCode(10) + '}')); } catch(er) { _this.logError(er); }; _this.refocus(); void 0";
        }
    },
   
    
    logAnswer: function(ans) {
        if (ans !== undefined) {
            this.logDebug(ans);
            shellCommands.ans = ans;
        }
    },
    
    refocus: function() {
       this._in.blur();
       this._in.focus();
    },
    
    tabComplete: function() {
        /*
         * Working backwards from s[from], find the spot
         * where this expression starts.  It will scan
         * until it hits a mismatched ( or a space,
         * but it skips over quoted strings.
         * If stopAtDot is true, stop at a '.'
         */
        function findbeginning(b, from, stopAtDot) {
            /*
             *  Complicated function.
             *
             *  Return true if s[i] == q BUT ONLY IF
             *  s[i-1] is not a backslash.
             */
            function equalButNotEscaped(b,i,q) {
                if(b.charAt(i) != q) // not equal go no further
                    return false;

                if(i==0) // beginning of string
                    return true;

                if(b.charAt(i-1) == '\\') // escaped?
                    return false;

                return true;
            }

            var nparens = 0;
            var i;
            for(i=from; i>=0; i--) {
                if(b.charAt(i) == ' ') break;

                if(stopAtDot && b.charAt(i) == '.') break;
        
                if(b.charAt(i) == ')') {
                    nparens++;
                } else if(b.charAt(i) == '(') {
                    nparens--;
                }

                if(nparens < 0) break;

                // skip quoted strings
                if(b.charAt(i) == '\'' || b.charAt(i) == '\"') {
                    //this.logDebug("skipping quoted chars: ");
                    var quot = b.charAt(i);
                    i--;
                    while(i >= 0 && !equalButNotEscaped(s,i,quot)) {
                        //this.logDebug(s.charAt(i));
                        i--;
                    }
                    //this.logDebug("\n");
                }
            }
            return i;
        }

        function getcaretpos(inp) {
            if(inp.selectionEnd)
              return inp.selectionEnd;

            if(inp.createTextRange) {
                //this.logDebug('using createTextRange\n');
                var docrange = this._win.Shell.document.selection.createRange();
                var inprange = inp.createTextRange();
                inprange.setEndPoint('EndToStart', docrange);
                return inprange.text.length;
            }

            return inp.value.length; // sucks, punt
        }

        function setselectionto(inp,pos) {
            if(inp.selectionStart) {
                inp.selectionStart = inp.selectionEnd = pos;
            } else if(inp.createTextRange) {
                var docrange = this._win.Shell.document.selection.createRange();
                var inprange = inp.createTextRange();
                inprange.move('character',pos);
                inprange.select();
            } else {
                /*
                inp.select();
                if(this._win.Shell.document.getSelection())
                    this._win.Shell.document.getSelection() = "";
                */
            }
        }
        
        // get position of cursor within the input box
        var caret = getcaretpos(this._in);

        if (caret) {
            //this.logDebug("----\n");
            var dotpos, spacepos, complete, obj;
            //this.logDebug("caret pos: " + caret + "\n");
            // see if there's a dot before here'
            dotpos = findbeginning(this._in.value, caret-1, true);
            //this.logDebug("dot pos: " + dotpos + "\n");
            if(dotpos == -1 || this._in.value.charAt(dotpos) != '.') {
                dotpos = caret;
                //this.logDebug("changed dot pos: " + dotpos + "\n");
            }

            // look backwards for a non-variable-name character
            spacepos = findbeginning(this._in.value, dotpos-1, false);
            //this.logDebug("space pos: " + spacepos + "\n");
            // get the object we're trying to complete on'
            if(spacepos == dotpos || spacepos+1 == dotpos || dotpos == caret) {
               // try completing function args
                if(this._in.value.charAt(dotpos) == '(' ||
                    (this._in.value.charAt(spacepos) == '(' && (spacepos+1) == dotpos)) {
                    var fn,fname;
                    var from = (this._in.value.charAt(dotpos) == '(') ? dotpos : spacepos;
                    spacepos = findbeginning(this._in.value, from-1, false);

                    fname = this._in.value.substr(spacepos+1,from-(spacepos+1));
                    //this.logDebug("fname: " + fname + "\n");
                    var _win = this._win;
                    try {
                        // with(_win.Shell._scope)
                        with(this._scope)
                            with(_win)
                                with(Shell.shellCommands)
                                    fn = eval(fname);
                    } catch(e) {
                        this.logDebug('fn is not a valid object',e);
                        return;
                    }
                    if(fn == undefined) {
                       //this.logDebug('fn is undefined');
                       return;
                    }
                    if (fn instanceof Function) {
                        // Print function definition, including argument names, but not function body
                        if(!fn.toString().match(/function .+?\(\) +\{\n +\[native code\]\n\}/))
                            this.logDebug(fn.toString().match(/function .+?\(.*?\)/));
                    }

                    return;
                } else {
                    obj = this._win;
                }
            } else {
                var objname = this._in.value.substr(spacepos+1,dotpos-(spacepos+1));
                //this.logDebug("objname: |" + objname + "|\n");
                var _win = this._win;
                try {
                    //with(_win.Shell._scope)
                    with(this._scope)
                        with(_win)
                            obj = eval(objname);
                } catch(e) {
                    this.logError(e); 
                    return;
                }
                if( obj == undefined )
                    return;
            }
            
            //this.logDebug("obj: " + obj + "\n");
            // get the thing we're trying to complete'
            if(dotpos == caret) {
                if(spacepos+1 == dotpos || spacepos == dotpos) {
                    // nothing to complete
                    //this.logDebug("nothing to complete\n");
                    return;
                }

                complete = this._in.value.substr(spacepos+1,dotpos-(spacepos+1));
            } else {
                complete = this._in.value.substr(dotpos+1,caret-(dotpos+1));
            }
            
            //this.logDebug("complete: " + complete + "\n");
            // ok, now look at all the props/methods of this obj
            // and find ones starting with 'complete'
            var matches = [];
            var bestmatch = null;

            for(var a in obj) {
                //a = a.toString();
                //XXX: making it lowercase could help some cases,
                // but screws up my general logic.
                if(a.substr(0,complete.length) == complete) {
                    matches.push(a);
                    ////this.logDebug("match: " + a + "\n");
                    // if no best match, this is the best match
                    if(bestmatch == null) {
                        bestmatch = a;
                    } else {
                        // the best match is the longest common string
                        function min(a,b){ return ((a<b)?a:b); }
                        var i;
                        for(i=0; i< min(bestmatch.length, a.length); i++) {
                            if(bestmatch.charAt(i) != a.charAt(i))
                                break;
                        }
                        bestmatch = bestmatch.substr(0,i);
                        ////this.logDebug("bestmatch len: " + i + "\n");
                    }
                    ////this.logDebug("bestmatch: " + bestmatch + "\n");
                }
            }
            
            bestmatch = (bestmatch || "");
            ////this.logDebug("matches: " + matches + "\n");
            var objAndComplete = (objname || obj) + "." + bestmatch;
            //this.logDebug("matches.length: " + matches.length + ", tooManyMatches: " + this.tooManyMatches + ", objAndComplete: " + objAndComplete + "\n");
            
            if (matches.length > 1 && (this.tooManyMatches == objAndComplete || matches.length <= 10)) {
                this.logDebug("Matched: ", matches.join(', '));
                this.tooManyMatches = null;
            } else if(matches.length > 10) {
                this.logDebug(matches.length + " matches.  Press tab again to see them.");
                this.tooManyMatches = objAndComplete;
            } else {
                this.tooManyMatches = null;
            }
            
            if(bestmatch != "") {
                var sstart;
                if(dotpos == caret) {
                    sstart = spacepos+1;
                } else {
                    sstart = dotpos+1;
                }
                this._in.value = this._in.value.substr(0, sstart)
                    + bestmatch
                    + this._in.value.substr(caret);
                setselectionto(this._in,caret + (bestmatch.length - complete.length));
            }
        }
    }
    
} );

Logger.override( {
    initSingleton: function() {
        if( Logger.singleton )
            return;
        return Class.initSingleton.apply( Logger, arguments );
    },


    log: function() {
        return Logger.logMessages.call( this, "INFO", arguments );
    },
    
    
    logError: function() {
        return Logger.logMessages.call( this, "ERROR", arguments );
    },
    
    
    logWarn: function() {
        return Logger.logMessages.call( this, "WARN", arguments );
    },


    logDebug: function() {
        return Logger.logMessages.call( this, "DEBUG", arguments );
    },
    
    
    logMessages: function( type, messages ) {
        Logger.initSingleton();
        return Logger.singleton.logMessages( type, messages );
    }
} );


log = Logger.log;
log.error = Logger.logError;
log.warn = Logger.logWarn;
log.debug = Logger.logDebug;

