/**
 * Ajax
 */
Ajax = {

    requestId : 1,

    get : function(url, params, callbackFunc, callbackObj) {
        var ajax = new AjaxRequest();
        ajax.addCallback(callbackFunc, callbackObj);
        ajax.get(url, params);
    },

    post : function(url, params, callbackFunc, callbackObj) {
        var ajax = new AjaxRequest();
        ajax.addCallback(callbackFunc, callbackObj);
        ajax.post(url, params);
    },

    getForm : function(form, callbackFunc, callbackObj) {
        var ajax = new AjaxRequest();
        ajax.addCallback(callbackFunc, callbackObj);
        ajax.getForm(form);
    },

    postForm : function(form, callbackFunc, callbackObj) {
        var ajax = new AjaxRequest();
        ajax.addCallback(callbackFunc, callbackObj);
        ajax.postForm(form);
    }
};

function AjaxRequest() { 
    this.callbacks = [];
}

AjaxRequest.prototype.addCallback = function(func, obj) {
    this.callbacks.push({func:func, obj:(obj ? obj : null)});
}

AjaxRequest.prototype.setPassthrough = function(passthrough) {
    this.passthrough = passthrough;
}

AjaxRequest.prototype.get = function(url, params) {
    this.request('GET', url, params);
}

AjaxRequest.prototype.post = function(url, params) {
    this.request('POST', url, params);
}

AjaxRequest.prototype.getForm = function(form) {
    this.requestForm('GET', form);
}

AjaxRequest.prototype.postForm = function(form) {
    this.requestForm('POST', form);
}

//TODO - test emtpy form action post
AjaxRequest.prototype.requestForm = function(method, form) {
    var form = Form.getForm(form);
    var params = Form.getKeyValues(form);
    this.request(method, form.action ? form.action : '', params);
}

AjaxRequest.prototype.request = function(method, url, params) {
    var req = this.getRequestObj();
    if(method.toUpperCase() == 'GET') {
        url += '?' + this.serializeParams(params);
        req.open('GET', url, true);
        req.send(null);
    } else if(method.toUpperCase() == 'POST') {
        req.open('POST', url, true);
        contentType = 'application/x-www-form-urlencoded';
        req.setRequestHeader('Content-Type', contentType);
        req.send(this.serializeParams(params));
    }
}

AjaxRequest.prototype.serializeParams = function(params) {
    if(!params) params = {};
    params.AjaxRequestId = Ajax.requestId++;
    var keyValues = [];
    for(var i in params) {
        var k = encodeURIComponent(i);
        if(typeof(params[i]) == 'object') {
           for(var e in params[i]) {
               keyValues.push(k + '[]=' + encodeURIComponent(params[i][e]))
           } 
        } else {
           keyValues.push(k + '=' + encodeURIComponent(params[i]));
        }
    }
    return keyValues.join('&');
}

AjaxRequest.prototype.getRequestObj = function() {
    var req = undefined;
    if(window.XMLHttpRequest)
        try { req = new XMLHttpRequest(); } catch(e) { return; }
    else if(window.ActiveXObject) {
        try { req = new ActiveXObject("Msxml2.XMLHTTP"); }
        catch(e) {
            try { req = new ActiveXObject("Microsoft.XMLHTTP"); }
            catch(e) { return; }
        }
    }
    var ajaxRequest = this;
    req.onreadystatechange = function() {
        ajaxRequest.readyStateChange(req); 
    }
    return req;
}

AjaxRequest.prototype.readyStateChange = function(req) {
    if(req.readyState == 4) {
        var response = {
            success : req.status == 200 ? true : false,
            failure : req.status == 200 ? false : true,
            status : req.status,
            text : req.responseText,
            xml : req.responseXML,
            json : Json.decode(req.responseText),
            passthrough : this.passthrough,
            request : this
        };
        for(var i in this.callbacks) {
            var c = this.callbacks[i];
            c.func.call(c.obj, response, this.arg);
        }
    }
}

Json = {

    encode : function(x) {
        return Json.types[typeof x](x);
    },

    // defaults to untrusted
    decode : function(x, trusted) {
        if(!trusted) trusted = false;
        try {

            if(x === undefined || x === '')
                return undefined;

            if(trusted) return eval('(' + x + ')');

            var testRegex = /[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/;
            var replaceRegex = /"(\\.|[^"\\])*"/g;
            if(!testRegex.test(x.replace(replaceRegex, '')))
                return eval('(' + x + ')');

        } catch(ex) {}
        return undefined;
    },

    characters : {
        '\b': '\\b',
        '\t': '\\t',
        '\n': '\\n',
        '\f': '\\f',
        '\r': '\\r',
        '"' : '\\"',
        '\\': '\\\\'
    },

    types : {
        'array' : function(x) {
            var a = ['['], b, f, i, l = x.length, v;
            for (i = 0; i < l; i += 1) {
                v = x[i];
                f = Json.types[typeof v];
                if(f) {
                    v = f(v);
                    if(typeof v == 'string') {
                        if(b)
                            a[a.length] = ',';
                        a[a.length] = v;
                        b = true;
                    }
                }
            }
            a[a.length] = ']';
            return a.join('');
        },

        'boolean' : function(x) {
            return Json.types.string(x);
        },

        'null' : function(x) {
            return 'null';
        },

        'number' : function(x) {
            return isFinite(x) ? Json.types.string(x) : 'null';
        },

        'object' : function(x) {
            if(x) {
                if(x instanceof Array)
                    return Json.types.array(x);
                var a = ['{'], b, f, i, v;
                for(i in x) {
                    v = x[i];
                    f = Json.types[typeof v];
                    if(f) {
                        v = f(v);
                        if(typeof v == 'string') {
                            if(b)
                                a[a.length] = ',';
                            a.push(Json.types.string(i), ':', v);
                            b = true;
                        }
                    }
                }
                a[a.length] = '}';
                return a.join('');
            }
            return 'null';
        },

        'string' : function (x) {
            if(/["\\\x00-\x1f]/.test(x)) {
                x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                    var c = Json.characters[b];
                    if (c)
                        return c;
                    c = b.charCodeAt();
                    return '\\u00' + Math.floor(c / 16).toString(16) +
                        (c % 16).toString(16);
                });
            }
            return '"' + x + '"';
        }
    }        
};

Page = {

    go : function(url) {
        document.location = url;
    },

    refresh : function() {
        url = '' + document.location;  
        document.location = url.substring(0, url.lastIndexOf('#'));
    },

    //TODO - make sure this function gets called at page load
    fixExternalLinks : function() {
        if(!document.getElementsByTagName) return;
        var anchors = document.getElementsByTagName("a");
        for(var i = 0; i < anchors.length; i++) {
            if(anchors[i].getAttribute("href") &&
                anchors[i].getAttribute("rel") == "external")
            {
                anchors[i].target = "_blank";
            }
        }
    }
};

Table = {
    addRow : function(table, cells) {
        table = Element.getElement(table);
        var row = table.insertRow(table.rows.length);
        for(var i in cells) {
            var cell = document.createElement('td');
            cell.appendChild(cells[i]);
            row.appendChild(cell);
        }
        return row;
    }
};


/**
 * Form
 */
Form = {

    disableAll : function() {
        for(var i = 0; i < document.forms.length; ++i)
            Form.disable(document.forms[i]);
    },

    enableAll : function() {
        for(var i = 0; i < document.forms.length; ++i)
            Form.enable(document.forms[i]);
    },

    disable : function(form) {
        form = this.getForm(form);
        for(var i = 0; i < form.elements.length; ++i)
            form.elements[i].disabled = true;
    },

    enable : function(form) {
        form = this.getForm(form);
        for(var i = 0; i < form.elements.length; ++i)
            form.elements[i].disabled = false;
    },

    hide : function(form) {
        form = this.getForm(form);
        form.style.display = 'none';
    },

    show : function(form) {
        form = this.getForm(form);
        form.style.display = 'block';
    },

    getKeyValues : function(form) {
        var form = this.getForm(form);
        var keyValues = {};
        for(var i in form.elements) {
            var el = form.elements[i];
            try { if(!el.name) continue; }
            catch(ex) { continue; }
            switch(el.type) {
                case 'select-multiple':
                    keyValues[el.name] =  el.value;
                    break;
                case 'radio':
                case 'checkbox':
                    if(el.checked) {
                        if(keyValues[el.name] == undefined)
                            keyValues[el.name] = new Array();
                        keyValues[el.name].push(el.value);
                    }
                    break;
                case 'file':
                case undefined:
                    break;
                default:
                    keyValues[el.name] =  el.value;
                    break;
            }
        }
        return keyValues;
    },

    reset : function(form) {
        form = this.getForm(form);
        form.reset();
    },

    // if nothing is passed, it returns the first form on the page
    // if a form name or number is passed the assoc form is returned
    // if a form object is passed, that same form obj is returned
    getForm : function(form) {
        if(form === undefined )
            return document.forms[0];
        var type = typeof form;
        if(type == 'string' || type == 'number')
            return document.forms[form];
        else if(type == 'object')
            return form;
    }
};

/**
 * Message Box Class
 */
function MessageBox(boxId) {

    // accepts ids or elements
    this.boxId = boxId;
    this.box = undefined;
    this.foo = 'message box class';

    this.fadeInDuration = .5;
    this.fadeOutDuration = 1;
    this.duration = 5;
}

MessageBox.prototype.add = function(text, classNames) {
    this.addTemp(text, classNames, false);
}

MessageBox.prototype.addTemp = function(text, classNames, remove) {

    if(!this.box);
        this.box = Element.getElement(this.boxId);

    classNames = classNames || [];
    classNames.push('message');

    var p = document.createElement('p');
    p.className = classNames.join(' ');
    p.appendChild(document.createTextNode(text));

    var div = document.createElement('div');
    div.className = 'message';
    div.appendChild(p);

    this.box.appendChild(div);

    var anim = new YAHOO.util.Anim(div);
    anim.attributes.opacity = { from : 0, to : 1 };
    anim.duration = this.fadeInDuration / 2;
    anim.animate();

    var anim = new YAHOO.util.Anim(div);
    anim.attributes.height = {  from : 0, to : 40 };
    anim.duration = this.fadeInDuration;
    anim.animate();

    if(remove === false)
        return;

    // delayed hide
    milliseconds = (this.duration + this.fadeInDuration) * 1000;
    var duration = this.fadeOutDuration;
    setTimeout(function() {
        var anim = new YAHOO.util.Anim(div);
        anim.attributes.opacity = { to : 0 };
        anim.attributes.height = { to : 0 };
        anim.duration = duration;
        anim.animate();
        delete(duration);
        delete(div);
    }, milliseconds);

    // delayed remove from page
    // (500 adds a small 1/2 second buffer)
    setTimeout(function() {
        Element.remove(div);
        delete(div);
    }, milliseconds + 500 + (this.fadeOutDuration * 1000));
}

Element = {

    setText : function(el, text, classNames) {

        el = Element.getElement(el);

        if(!text) {
            el.style.display = 'none';
            return
        }
        el.style.display = 'block';
        if(!classNames) classNames = [];
        else if(typeof classNames == 'string')
            classNames = [classNames];
        el.className = classNames.join(' ');

        if(typeof text == 'string')
            el.innerHTML = text;
        else
            el.appendChild(text);
    },

    setErrorText: function(el, text, classNames) {
        el = Element.getElement(el);
        if(!classNames) classNames = [];
        if(typeof classNames == 'string')
            classNames = [classNames, 'error'];
        else
            classNames.push('error');
        Element.setText(el, text, classNames);
    },

    offset : function(el) {
        el = Element.getElement(el);
        var o = { left:el.offsetLeft, top:el.offsetTop };
        while((el = el.offsetParent) != null) {
            o.left += el.offsetLeft;
            o.top += el.offsetTop;
        }
        return o;
    },

    opacity : function(el, opacity) {
        if(opacity > 1) opacity == 1;
        else if(opacity < 0) opacity == 0;
        el.style.opacity = opacity;
        el.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
    },

    getInnerText : function(el) {
        el = Element.getElement(el);
        if(typeof el == 'string')
            return el;
        else if(typeof el == undefined)
            return el;
        else if(el.innerText)
            return el.innerText;
        var str = '';
        var length = el.childNodes.length;
        for(var i = 0; i < length; ++i) {
            if(el.childNodes[i].nodeType == 1)
                str += this.getInnerText(el.childNodes[i]);
            else if(el.childNodes[i].nodeType == 3)
                str += el.childNodes[i].nodeValue;
        }
        return str;
    },

    remove : function(el) {
        el = Element.getElement(el);
        el.parentNode.removeChild(el);
        return el;
    },

    removeAllChildren : function(el) {
        el = Element.getElement(el);
        while(el.childNodes.length > 0)
            el.removeChild(el.childNodes[0]);
    },

    getElement : function(el) {
        if(typeof el != 'object')
            return document.getElementById(el);
        return el;
    }
};

Radio = {
    getCheckedValue : function(radioList) {

        if(!radioList.length) {
            if(radioList.checked)
                return radioList.value;
            else
                return undefined;
        }
            
        for(var i in radioList)
            if(radioList[i].checked)
                return radioList[i].value;
        return undefined;
    }
};


/*
 * TODO LIST -- Bug Fixes
 *  - IE fails to post select/option elements that do not have a value
 *    attribute associated with the option, check for these
 *  - allow for optionally cache'able request
 *  - fix setArgument passthrough method for correct reuse
 */
