function ErrorHandler()
{
    if (!ErrorHandler._instance)
    {
        ErrorHandler._instance = this;
    }
    else
    {
        return ErrorHandler._instance;
    }
}

ErrorHandler.prototype.send = function(err, type)
{
    var debug_out = getEl('debug_out');

    if (!debug_out)
    {
        return;
    }

    var msg = '<div style="border:1px solid #000">';

    if (typeof err == 'object')
    {
        for (p in err)
        {
            msg = msg + '<b>' + p + '</b>: ' + err[p] + '<br />';
        }
    }
    else
    {
        msg = msg + err + '<br />';
    }
    msg = msg + '<pre style="background-color:#e2e2e2">' + ErrorHandler.prototype.send.caller + '</pre>';
    msg = msg + '</div><br />';
    debug_out.innerHTML = msg + debug_out.innerHTML;

//    // notice
//    if (type < E_WARNING) {
//
//    // warning
//    } else if (type < E_FATAL) {
//
//    // fatal
//    } else {
//    }
}
function EventRegistry()
{
    if (!EventRegistry._instance)
    {
        EventRegistry._instance = this;
        this.triggers = {};
    }
    else
    {
        return EventRegistry._instance;
    }
}

EventRegistry.prototype.register = function(group, label, event_name, code)
{
    if (!this.triggers[group])
    {
        this.triggers[group] = new Array();
    }
    if (!this.triggers[group][label])
    {
        this.triggers[group][label] = new Array();
    }
    if (!this.triggers[group][label][event_name])
    {
        this.triggers[group][label][event_name] = new Array();
    }

    this.triggers[group][label][event_name].push(code);
}

// er.execute(getEvent(), 'some_id', 'click');
EventRegistry.prototype.execute = function(evt, group, label, event_name)
{
    // is this event registered?
    if (!this.triggers[group] || !this.triggers[group][label] || !this.triggers[group][label][event_name])
    {
        return false;
    }

    for (var i = 0, len = this.triggers[group][label][event_name].length; i < len; i++)
    {
        this.triggers[group][label][event_name][i](evt);
    }
    return true;
}

// er.clear('some_id'); clear all actions for this group & label
EventRegistry.prototype.clear = function(group, label)
{
    delete this.triggers[group][label];
}
function JsonControl()
{
    this.xmlhttp = false;

    try
    {
        this.xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch (e)
    {

        try
        {
            this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        catch (E)
        {
            this.xmlhttp = false;
        }
    }

    if (!this.xmlhttp && typeof XMLHttpRequest != 'undefined')
    {
        this.xmlhttp = new XMLHttpRequest();
    }
}

JsonControl.prototype.toString = function()
{
    string =  'JsonControl: .call: ' + this.call;
    return string;
}

JsonControl.prototype.call = function(call)
{
    this.call = call;
}

// set the entire data string at once
// overwrites anything previously set
JsonControl.prototype.setData = function(data)
{
    this.data = data;
}

// appends a key => value pair to the
// existing data
JsonControl.prototype.setParam = function(k, v)
{
    if (this.data)
    {
        this.data += '&' + k + '=' + v;
    }
    else
    {
        this.data = k + '=' + v;
    }
}

JsonControl.prototype.accept = function(f)
{
    this.accept_function = f;
}

JsonControl.prototype.execute = function()
{
    // LOG
//    out('\nJsonControl sending | ' + this.call);
    // LOG


    if (!this.data)
    {
        this.xmlhttp.open("GET", this.call, true);
    }
    else
    {
        this.xmlhttp.open("POST", this.call, true);
    }

    this.xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    // unsubstantiated reports of moz/firefox freezing if return data is
    // not xml, unless this is set
    //this.xmlhttp.overrideMimeType('text/xml');

    if (this.data)
    {
        this.xmlhttp.setRequestHeader('Content-Length', this.data.length);

        // LOG
//        out('\nJsonControl data    | ' + this.data);
        // LOG

    }
    this.xmlhttp.setRequestHeader('Connection','Close');

    var xmlhttp = this.xmlhttp;
    var accept_function = this.accept_function
        ? this.accept_function
        : false;

    this.xmlhttp.onreadystatechange = function()
    {
        if (xmlhttp.readyState == 4)
        {
            if(xmlhttp.status == '200')
            {
                // LOG
                //out('JsonControl received\n\n' + xmlhttp.responseText);
                // LOG

                if (accept_function)
                {
                    try
                    {
                        eval('var rs = ' + xmlhttp.responseText);
//                        out('evaled');
//                        out(var_dump(rs));
                    }
                    catch (e)
                    {
                        var eh = new ErrorHandler();
                        eh.send('JSON Parse Error:<br />' + xmlhttp.responseText, E_FATAL);
                    }
                    // TODO check for null and error codes and do something appropriate
                    // 401, 404
                    if (rs.error)
                    {
                        alert('Error ' + rs.error);
                        return false;
                    }
                    accept_function(rs);
                }
            }
        }
    }
    this.xmlhttp.send(this.data);
}
function PageController()
{
    if (!PageController._instance)
    {
        PageController._instance = this;
        this.er = new EventRegistry();
    }
    else
    {
        return PageController._instance;
    }
}

PageController.prototype.main = function(evt)
{
    var err_handler = new ErrorHandler();

    try
    {
        var evt = getEvent(evt);
    }
    catch (e)
    {
        err_handler.send(e, E_FATAL);
    }

    var el = getEventTarget(evt);

    try
    {
        // only need to try this if the element had an id
        if (el.id)
        {
            this.er.execute(evt, 'id', el.id, evt.type);

            // only check expando properties if the element had an id
            if (el.getAttribute)
            {
                var trigger = el.getAttribute('trigger');
                if (trigger)
                {
                    this.er.execute(evt, 'trigger', trigger, evt.type);
                }
            }
        }

    }
    catch (e)
    {
        err_handler.send(e, E_FATAL);
    }
}
function ParseGet()
{
    if (!ParseGet._instance)
	{
        ParseGet._instance = this;
        this.get = {};
        this.parseGet();
    }
	else
	{
        return ParseGet._instance;
    }
}

ParseGet.prototype.parseGet = function()
{
	// get query string
    var query = location.search.substring(1);
    var pairs = query.split('&');

    for (var i = 0; i < pairs.length; i++)
	{
		// find name=value
        var pos = pairs[i].indexOf('=');
		// not there? skip...
        if (pos == -1)
		{
			continue;
		}
        var argname = pairs[i].substring(0,pos);
        var value = pairs[i].substring(pos+1);
        this.get[argname] = unescape(value);
    }
}

/* popup windows */

// convenience wrapper function
// type is optional. defaults to standard browser window
function popWin(url, x, y, type, name)
{
    type = type ? type : '';
    name = name ? name : '';

    var win = new PopUpWindow();
    win.create(url, x, y, type, name);
}

function PopUpWindow()
{
    PopUpWindow.pop_win = null;
}

// type is optional. defaults to standard browser window
PopUpWindow.prototype.create = function(url, x, y, type, name)
{
    type = type ? type : '';
    name = name ? name : '';

    if (PopUpWindow.pop_win != null && !PopUpWindow.pop_win.closed)
    {
        PopUpWindow.pop_win.close();
    }

    var opts = '';
    if (type == 'console')
    {
        opts = 'scrollbars,resizable,width=' + x + ',height=' + y;
    }
    else if (type == 'menu')
    {
        opts = 'menubar,scrollbars,resizable,width=' + x + ',height=' + y;
    }
    else
    {
        opts = '';
    }

    PopUpWindow.pop_win = window.open(url, name, opts);
    setTimeout('PopUpWindow.prototype.focus()', 1500);
}

PopUpWindow.prototype.focus = function()
{
    PopUpWindow.pop_win.focus();
}

