var gBlowdart;
function InitBlowdart()
{
    var page = arguments.length > 0 ? arguments[0]:"/blowdart.cgi";
    //var page = arguments.length > 0 ? arguments[0]:"/blowdart.php";
    
    var cobj;
    if (!window.gBlowdartPages)
        window.gBlowdartPages = new Object();
    var cobj = window.gBlowdartPages[page];
    if (cobj)
        return cobj;

    // Object has not been found.  Create the new object.
    var cobj = new Object();
    window.gBlowdartPages[page] = cobj;
    if (page == "/blowdart.cgi")
        gBlowdart = cobj;
    cobj.me = "window.gBlowdartPages['"+page+"']";
    cobj.page = page;
    cobj.pool = new Array();
    cobj.free = null;
    //cobj.nextid = (new Date().getTime());
    cobj.cid = 1;
    cobj.cur = null;
    cobj.routines = new Array();

	if (!window.gDB)
		window.gDB = new Object();

    cobj.defaulttimeout = 20000;
    cobj.defaultdefer = 1000;
    cobj.intervalcmds = new Object();

    if (!window.reportError)
        window.reportError = function(e) { };

    cobj.global = window.gDB.blowdartGlobal;
    if (!cobj.global)
    {
        // The global object is needed so that we don't do things a lot of times if there are a lot of
        // blowdart objects.
        //
        cobj.global = window.gDB.blowdartGlobal = new Object();
        cobj.global.onmousemove = function( e )
        {
            this.mousetime = (new Date()).getTime();
        }
        cobj.global.mousetime = 0;
        cobj.global.onmousemove();
        if (window.eventManager)
        cobj.global.mousemoveID = eventManager.register( "onmousemove", cobj.global );
        cobj.global.uid = (new Date()).getTime();

        cobj.global.errors =
        {
            "NOERROR": 0,
            "TIMEOUT": 408,
            "BAD_REQUEST": 400,
            "JS_CONVERSION": 1000,
            "JS_MALFORMATED": 1001,
            "HREQ_ERROR": 1002
        };

        cobj.global.onload = function()  {cobj.global.loaded = true;}
	    eventManager.register( "onload", cobj.global, 500000 );
    }

    // ==============
    // Cmd management
    // ==============
    cobj.cmdarr = new Array(); // The Command array
    cobj.cmdempty = -1;
    cobj.cmdcount = 0;

    cobj.getCmd = function( id )
    {
        if (typeof id != "string")
            return null;
        var num = parseInt(id.split("-")[1]);
        var obj = this.cmdarr[num];
        return (obj && (typeof obj != "number") && (obj.id == id) ? obj:null);
    }
    cobj.makeCmd = function(cmd,cbobj)
    {
        var obj = new Object();
        if (this.cmdempty >= 0)
        {
            obj.num = this.cmdempty;
            this.cmdempty = this.cmdarr[this.cmdempty];
            this.cmdarr[obj.num] = obj;
        }
        else
        {
            obj.num = this.cmdarr.length;
            this.cmdarr.push( obj );
        }
        obj.id = "c"+(this.global.uid++)+"-"+obj.num;
        obj.cmd = cmd;
        obj.cbobj = cbobj;
        this.cmdcount++;
        return obj;
    }
    cobj.deleteCmd = function(obj)
    {
        var t = typeof obj;
        if (t == "string")
            obj = this.getCmd( obj );
        if (obj && (t == "object") && (obj.num != null))
        {
            this.cmdarr[obj.num] = this.cmdempty;
            this.cmdempty = obj.num;
            obj.num = null; // Just in case this object still has something
        }
    }
    
    cobj.makeJSONArgs = function( args )
    {
        var v=[],i = (arguments.length > 1 ? arguments[1]:0);
        for(;i<args.length;i++)
            v.push( args[i]);
        return toJSON(v);
    }
    cobj.makeJSONCmd = function( cmd,num,sendstr )
    {
        return '{"cmd":"'+cmd+'","id":'+num+',"args":'+sendstr+'}';
		//return '{cmd:"'+cmd+'",id:"'+num+'", args:"'+sendstr+'"}';
    }

    //
    cobj.makeRebolArgs = function( args ) // [startidx]
    {
        var s = "[",i = (arguments.length > 1 ? arguments[1]:0);
        for(;i<args.length;i++)
            s += toRebol( args[i] ) + " ";
        s += "]";
        return s;
    }
    cobj.makeRebolCmd = function( cmd, num, sendstr )
    {
        return '[ "'+cmd+'" '+num+' '+sendstr+']]';
    }
    
    if (page.search(".php") >= 0)
    {
        cobj.posttype = "php";
        cobj.makeArgs = cobj.makeJSONArgs;
        cobj.makeSendCmd = cobj.makeJSONCmd;
    }
    else
    {
        cobj.posttype = "rebol";
        cobj.makeArgs = cobj.makeRebolArgs;
        cobj.makeSendCmd = cobj.makeRebolCmd;
    }
    
    cobj.schedule = function() // [sendok [now]]
    {
        if (this.cursend || this.nosend) // The nosend is a hack... So that we won't get into an infinate loop on some calls.
            return;

        var now = (arguments.length > 1 ? arguments[1]:(new Date()).getTime());
        var ans,i,obj,cbobj;
        if (!this.nextsend)
        {
            // Figure out the nextsend time
            var best = null;
            for(i=0;i<this.cmdarr.length;i++)
            {
                obj = this.cmdarr[i];
                if (obj && (typeof obj == "object") && !obj.state)
                {
                    if (!best || (best < this.sendby))
                        best = obj.sendby;
                }
            }
            this.nextsend = best;
        }
        if (this.deferid != null)
        {
            window.clearTimeout(this.deferid);
            this.deferid = null;
        }
        if (!this.nextsend) // Don't need to do a send.
            return;

        if (now < this.nextsend || (arguments.length > 0 && !arguments[0]))
        {
            // Defer for a bit.
            var dt = this.nextsend - now;
            if (dt <= 0)
                dt = 1;
            this.deferid = window.setTimeout( this.me+".schedule();",dt );
            return;
        }


        this.nextsend = null;

        // Send the pending commands!
        var s = new Object();
        s.cmdarr = new Array();
        s.timeout = this.defaulttimeout;
        var sarr = [];
        for(i=0;i<this.cmdarr.length;i++)
        {
            obj = this.cmdarr[i];
            if (obj && (typeof obj == "object") && !obj.state && (!obj.slow || (now >= obj.sendby)))
            {
                if (obj.interval)
                    obj.sendby = now + obj.interval;
                ans = true;
                cbobj = obj.cbobj;
                if (cbobj && cbobj.updateCmdLine)
                {
                    // Do the callback if needed.
                    if (cbobj.obj && (typeof cbobj.updateCmdLine == "string"))
                        ans = cbobj.obj[cbobj.updateCmdLine]( cbobj );
                    else if (typeof cbobj.updateCmdLine == "function")
                        ans = cbobj.updateCmdLine( cbobj );
                    else if (typeof cbobj.updateCmdLine == "string")
                        eval( "ans = "+cbobj.callback );
                    if (ans && (typeof ans == "object") && ans.length && (obj.cmd != "nop"))
                        obj.sendstr = this.makeArgs( ans, 0 );
                }
                if (ans)
                {
                    obj.state = 1;   // Sending.
                    if (obj.cmd != "nop")
                        sarr.push(this.makeSendCmd( obj.cmd, s.cmdarr.length, obj.sendstr ));
                    s.cmdarr.push( obj.id );    // Some objects may be canced out from under it, so store the ids.
                }
            }
        }
        if (sarr.length <= 0)
        {
            // Nothing to send...do any "nop" callbacks now.
            this.process(s,null);
            return;
        }
        s.id = "s"+this.global.uid++;
        s.hreq = this.getHReq();
        if (s.hreq == null)
        {
            this.process(s,null,this.globals.errors.HREQ_ERROR);
            return;
        }
        this.cursend = s;
        s.timeoutid = setTimeout( this.me+'.timeout("'+s.sid+'");',s.timeout);
        s.hreq.onreadystatechange = new Function(this.me+'.readyStateChange("'+s.id+'");');
        s.hreq.open("POST",this.page,true);
        if (this.posttype == "php")
            s.hreq.send( '{"gPageUID":'+gPageUID+',"commands":['+sarr.join(",")+']}');
			//s.hreq.send( '{gPageUID:"'+gPageUID+'",commands:"['+sarr.join(",")+']"}');
        else
            s.hreq.send( ""+window.gPageUID+" "+sarr.join(" ") );
    }

    cobj.received = function( id, err, result ) // [now]
    {
        var obj = this.getCmd( id );
        if (!obj)
            return;
        var cbobj = obj.cbobj;
        if ((obj.state == 1) && (obj.cbobj))
            this.doCallback( obj.cbobj, err, result );

        // Now what to do with this object.
        if (obj.interval)
        {
            obj.nextsend = (arguments.length > 3 ? arguments[3]:(new Date()).getTime()) + obj.interval;
            obj.state = 0;
        }
        else
        {
            this.deleteCmd( obj );
        }
    }

    cobj.rexp_empty = /^\s*$/g;

    cobj.process = function( obj, xml ) // [error]
    {
        var err = arguments.length > 2 ? arguments[2]:this.global.errors.BAD_REQUEST;
        var now = (new Date()).getTime();
        var i,cc,c,t,data,cmd;

        var n,id,num,e,ev;
        if (xml && xml.firstChild && xml.firstChild.tagName == "group")
        {
            xml = xml.firstChild;
            for(n=xml.firstChild;n;n = n.nextSibling)
            {
                if (n.tagName == "cmd")
                {
                    data = null;
                    if ((e = n.getAttribute( "error")))
                    {
                        if (isNaN((cc = parseInt( e )))) e = cc;
                        else {if (!(e = this.global.errors[ e ])) e = this.global.errors.BAD_REQUEST;}
                    }
                    else
                        e = 0;
                    try {id = n.getAttribute( "id" );}
                    catch(ev)
                    {
                        id = -1;
                        reportError( ev );
                    }
                    if (n.firstChild)
                    {
                        try {
                            if (n.firstChild.nodeValue && !this.rexp_empty.test(n.firstChild.nodeValue))
                            {
                                eval( "data="+n.firstChild.nodeValue+";" );
                            }
                        }
                        catch(ev)
                        {
                            e = this.global.errors.JS_CONVERSION;
                            data = null;
                            reportError( ev );
                        }
                    }
                    if (!isNaN(num = parseInt( id )) && (num >= 0) && (num < obj.cmdarr.length))
                    {
                        this.received( obj.cmdarr[num],e,data, now );
                    }
                    else
                    {
                        // other commands.
                        this.doCallback( this.routines[id],e,data );
                    }
                }
            }
        }
        this.failed( obj, err, now );
        this.schedule( false );
        return;
    }

    cobj.failed = function( obj,err ) // [now]
    {
        var now = arguments.length > 2 ? arguments[2]:(new Date()).getTime();
        var i,c;
        for(i=0;i<obj.cmdarr.length;i++)
        {
            if (obj.cmdarr[i])
                this.received( obj.cmdarr[i], err, null, now );
        }
        this.closeObj( obj );
    }

    cobj.closeObj = function( obj )
    {
        if (obj.hreq)
        {
            delete obj.hreq;
            obj.hreq = null;
        }
        if (obj.timeoutid)
        {
            window.clearTimeout(obj.timeoutid)
            obj.timeoutid = null;
        }
        if (obj == this.cursend)
            this.cursend = null;
    }

    cobj.timeout = function(num)              // callback for timeouts.
    {
        if (!this.cursend || num != this.cursend.id)
            return;
        var obj = this.cursend;
        obj.timeoutid = null;
        obj.hreq.abort();
        this.failed(obj,cobj.global.errors.TIMEOUT);
        this.schedule(false);
    }


    cobj.readyStateChange = function(num)     // Callback dispatch for the comamnd.
    {
        if (!this.cursend || num != this.cursend.id)
            return;
        var obj = this.cursend;
        var id,t;
        if (obj.hreq.readyState != 4)
            return true;

        if ((new Date()).getTime() < (this.global.mousetime + 200))
        {
            // Do not process returns until the mouse is not moving.
            setTimeout( this.me+'.readyStateChange("'+num+'","mouse");',200);
            return true;
        }

        var st;
        try {st = obj.hreq.status;} catch(e) { reportError(e); st = 452; }

        // This is where all the callbacks are processed.
        if (st == 200)
            this.process( obj, obj.hreq.responseXML );
        else
            this.process( obj, null, st );
    }


    //==============================================================================
    // Blowdart.doCallback( cbobj, err, data )
    //
    // Does the callback for the cbobj.
    //==============================================================================
    cobj.doCallback = function( cbobj, err, data )
    {
        if (cbobj)
        {
            if (cbobj.obj && cbobj.func)
            {
                cbobj.obj[cbobj.func]( data,err, cbobj );
            }
            else if (cbobj.func)
                cbobj.func( data,err,cbobj );
            else if (cbobj.callback)
                eval( cbobj.callback );
        }
    }


    //==============================================================================
    // Blowdart.register( n, cbobj )
    //
    // Register or unregister a cbobj for a "pushed" blowdart command.
    //==============================================================================
    cobj.register = function(n,cbobj)
    {
        return this.routines[n] = cbobj;
    }

    //==============================================================================
    // Blowdart.getHReq()
    //
    // Returns a new hreqest object.
    //==============================================================================
    cobj.getHReq = function()
    {
        var hreq;
        try{ //to get the mozilla httprequest object
            hreq = new XMLHttpRequest();
        }catch(e){
            try{ //to get MS HTTP request object
                hreq=new ActiveXObject("Msxml2.XMLHTTP.4.0");
            }catch(e){
                try{ //to get MS HTTP request object
                    hreq=new ActiveXObject("Msxml2.XMLHTTP");
                }catch(e){
                    try{// to get the old MS HTTP request object
                        hreq = new ActiveXObject("microsoft.XMLHTTP");
                    }catch(e){hreq = null;}
                }
            }
        }
        return hreq;
    }

    //==============================================================================
    // Blowdart.send( cmd, cbobj, ... )
    //
    // Sends a command to the server.  Unless overridden by the cbobj.maxwait the defaultdefer
    // time is used if the que is empty.
    //==============================================================================
    cobj.send = function(cmd,cbobj) // [...]
    {
        var now = (new Date()).getTime()
        var obj = this.makeCmd( cmd,cbobj );
        obj.sendstr = this.makeArgs( arguments, 2 );
        obj.sendby = now+(cbobj && cbobj.maxwait ? cbobj.maxwait:this.defaultdefer);
        if (!this.nextsend || obj.sendby < this.nextsend)
            this.nextsend = obj.sendby;

        this.schedule( true,now );
        return obj.id;
    }

	if( typeof( gQuilt ) == "undefined" ) { gQuilt = false; }

    return cobj;
}


function toJSON( obj )
{
    if (!window.toJSON_init)
    {
        window.toJSON_init = true;
    
        if (!String.prototype.toJSON)
        {
            String.prototype.toJSON = function()
            {
                var s = '"' + this.replace(/(["\\])/g, '\\$1') + '"';
                s = s.replace(/(\n)/g,"\\n");
                return s;
            }
        }
        if (!Number.prototype.toJSON)
        {
            Number.prototype.toJSON = function()
            {
                return this.toString();
            }
        }
        
        if (!Boolean.prototype.toJSON)
        {
            Boolean.prototype.toJSON = function()
            {
                return this.toString();
            }
        }
        
        if (!Date.prototype.toJSON)
        {
            Date.prototype.toJSON = function()
            {
                var padd=function(s, p)
                {
                    s=p+s;
                    return s.substring(s.length - p.length);
                }
                var y = padd(this.getUTCFullYear(), "0000");
                var m = padd(this.getUTCMonth() + 1, "00");
                var d = padd(this.getUTCDate(), "00");
                var h = padd(this.getUTCHours(), "00");
                var min = padd(this.getUTCMinutes(), "00");
                var s = padd(this.getUTCSeconds(), "00");
                
                var isodate = y +  m  + d + "T" + h +  ":" + min + ":" + s;
                
                return '{"jsonclass":["sys.ISODate", ["' + isodate + '"]]}';
            }
        }
        if (!Array.prototype.toJSON)
        {
            Array.prototype.toJSON = function()
            {
                var v = [];
                for(var i=0;i<this.length;i++){
                    v.push(toJSON(escape(this[i]))) ;
                }
				//QTask, this creates the args
                return "[\"" + v.join(", ") + "\"]";
            }
        }
    }

    if(obj == null) { return "null"; }
    if(obj.toJSON) { return obj.toJSON(); }
    var v=[];
    for(var attr in obj)
    {
        if(typeof obj[attr] != "function"){
            v.push('"' + attr + '": ' + mod.marshall(obj[attr]));
        }
    }
    return "{" + v.join(", ") + "}";
}

function toRebol( v )
{
    var i,t = typeof v;
    if (v == null)
        return "#[none]";
    if (t == "string")
    {
        i = v.replace(/\^/g,"^^");
        i = i.replace(/"/g,'^"');
        i = i.replace(/\r/g,"^M");
        i = i.replace(/\n/g,"^/");
        i = i.replace(/\0/g,"^0");
        return '"'+i+'"';
    }
    if (t == "number")
        return v;
    if (t == "object")
    {
        if (v.length)
        {
            t = "[ ";
            for(i=0;i<v.length;i++)
                t += toRebol(v[i]) + " ";
            return t + "]";
        }
        else
        {
            t = "#[object! [";
            for(i in v)
                t += i + ": "+toRebol(v[i])+"\n";
            return t +"]]";
        }
    }
    if (t == "boolean")
        return "#["+v+"]";
    return "#[none]"
}
//~ ECT - added the call to make sure blowdart is initialized.
InitBlowdart();
//~ try {
//~ InitBlowdart();
//~ } catch (err)
//~ {
    //~ alert(err);
//~ }

// ECT - moved module function here so it can be used by all JS modules and not cause an not defined error.
// ================
// MODEL - include
// ================
function module(n)
{
    if (!window.gDB) window.gDB = new Object();
    if (!window.gDB.moduleDB) window.gDB.moduleDB = new Array();
    if (!window.gDB.moduleArr) window.gDB.moduleArr = new Array();
    window.gDB.moduleDB[n] = true;
    window.gDB.moduleArr.push( n );
}

