/*****************
*   Type-Tests   *
*****************/
function isString(obj)    { return typeof(obj) == 'string'; }
function isNumber(obj)    { return typeof(obj) == 'number'; }
function isBoolean(obj)   { return typeof(obj) == 'boolean'; }
function isFunction(obj)  { return typeof(obj) == 'function'; }
function isObject(obj)    { return typeof(obj) == 'object' || isFunction(obj); }

function isArray(obj)     { return isObject(obj) && obj instanceof Array; }
function isDate(obj)      { return isObject(obj) && obj instanceof Date; }
function isError(obj)     { return isObject(obj) && obj instanceof Error; }

function isUndefined(obj) { return typeof(obj) == 'undefined'; }
function isNull(obj)      { return obj === null; }
function isNone(obj)      { return isUndefined(obj) || isNull(obj); }
function isSet(obj)       { return !isNone(obj); }

function isTrue(obj)      { return isSet(obj) && !!obj; }
function isFalse(obj)     { return isSet(obj) &&  !obj; }

function isEmpty(obj) {
    switch (typeof(obj)) {
        case 'undefined': return true;
        case 'string':    return obj == '';
        case 'number':    return obj == 0;
        case 'boolean':   return obj == false;
        case 'function':
            for (var i in obj) { if (!(isSet(Function.prototype[i]))) return false; }
            return obj.toString() == (function(){}).toString();
        case 'object':
            if (obj === null) return true;
            var pt = Object.prototype;
            if (isArray(obj)) { pt = Array.prototype; }
            if (isError(obj)) { pt = Error.prototype; }
            for (var i in obj) { if (!(isSet(pt[i]))) return false; }
            return true;
        default:
            return false;
    }
}

/***********************
*   Browser Abilites   *
***********************/
var UserAgent = new function() {
    this.name        = null;
    this.version     = null;
    this.os          = null;
    this.osId        = null;
    this.engine      = null;
    this.description = navigator.userAgent;
    this.toString    = function() { return '${name} ${version} (${osId}) -- ${engine}'.inReplace(this); }
    
    // inName(): Checks if something is in the user-agent's name
    this.inName = function(part, caseSensitive) {
        if (isSet(caseSensitive) && caseSensitive) {
            return !!(navigator.userAgent.indexOf(part)+1)
        }
        return !!(navigator.userAgent.toLowerCase().indexOf(part.toLowerCase())+1)
    }
    
    // Checks for functionality:
    this.knowsDom  = isSet(document.getElementById);
    this.knowsAjax = this.knowsDom && isSet(document.createElement) && (isSet(window.XMLHttpRequest)||isSet(window.ActiveXObject));
    
    // General user-agent-object
    function AgentObject() {
        this.version = null;
        this.getVersion = function() {
            if (isSet(this.version)) { return this.version; }
            var version = 0;
            for (var i in this) {
                if (isSet(Object.prototype[i]))   { continue; }
                if (isNaN(i) || isFalse(this[i])) { continue; }
                if (i > version) { version = i; }
            }
            this.version = version ? version : null;
            return this.version;
        }
        this.lt  = function(v) { return this.getVersion() <  v; }
        this.lte = function(v) { return this.getVersion() <= v; }
        this.gt  = function(v) { return this.getVersion() >  v; }
        this.gte = function(v) { return this.getVersion() >= v; }
    }
    String.prototype.lt  =
    String.prototype.lte =
    String.prototype.gt  =
    String.prototype.gte = function() { return false; }
    
    // Check for Opera
    this.getOpera = function() {
        if (isNone(window.opera)) { return ''; }
        var o = new AgentObject();
        o['7.x'] = this.inName('Opera 7.')   || this.inName('Opera/7.');
        o[7.2]   = this.inName('Opera 7.2')  || this.inName('Opera/7.2');
        o[7.23]  = this.inName('Opera 7.23') || this.inName('Opera/7.23');
        o[7.5]   = this.inName('Opera 7.5')  || this.inName('Opera/7.5');
        o[7.54]  = this.inName('Opera 7.54') || this.inName('Opera/7.54');
        o['8.x'] = this.inName('Opera 8.')   || this.inName('Opera/8.');
        o[8]     = this.inName('Opera 8.0')  || this.inName('Opera/8.0');
        o[8.5]   = this.inName('Opera 8.5')  || this.inName('Opera/8.5');
        o[8.54]  = this.inName('Opera 8.54') || this.inName('Opera/8.54');
        o['9.x'] = this.inName('Opera 9.')   || this.inName('Opera/9.');
        o[9]     = this.inName('Opera 9.0')  || this.inName('Opera/9.0');
        this.name    = 'Opera';
        this.version = o.getVersion();
        this.engine  = 'Presto';
        return o;
    }
    this.isOpera = this.getOpera();
    
    // Check for Internet Explorer
    this.getIe = function() {
        if (isNone(document.all) || this.isOpera) { return ''; }
        var o = new AgentObject();
        o['4.x'] = this.inName('MSIE 4.');
        o[4]     = this.inName('MSIE 4.0');
        o['5.x'] = this.inName('MSIE 5.');
        o[5]     = this.inName('MSIE 5.0');
        o[5.1]   = this.inName('MSIE 5.1');
        o[5.2]   = this.inName('MSIE 5.2');
        o[5.5]   = this.inName('MSIE 5.5');
        o[6]     = this.inName('MSIE 6.0');
        o[7]     = this.inName('MSIE 7.0');
        this.name    = 'MSIE';
        this.version = o.getVersion();
        this.engine  = 'Trident'; // Might be corrected below in this.getMac()!
        return o;
    }
    this.isIe = this.getIe();
    
    // Check for Safari and Konqueror
    this.isKhtml = this.knowsDom && this.inName('khtml');
    this.getSafari = function() {
        if (!this.isKhtml)          { return ''; }
        if (!this.inName('Safari')) { return ''; }
        var o = new AgentObject();
        o['1.x'] = this.inName('Safari/31'); // Versions: see http://www.apfelwiki.de/Main/Safari#toc4
        o[1.3]   = this.inName('Safari/31');
        o['2.x'] = this.inName('Safari/41');
        o[2]     = this.inName('Safari/41');
        o['3.x'] = this.inName('Safari/52');
        o[3]     = this.inName('Safari/52');
        this.name    = 'Safari';
        this.version = o.getVersion();
        this.engine  = 'KHTML';
        return o;
    }
    this.isSafari = this.getSafari();
    this.getKonqueror = function() {
        if (!this.isKhtml) { return ''; }
        if (this.isSafari) { return ''; }
        var o = new AgentObject();
        o['2.x'] = this.inName('Konqueror/2.');
        o[2]     = this.inName('Konqueror/2.0');
        o[2.1]   = this.inName('Konqueror/2.1');
        o[2.2]   = this.inName('Konqueror/2.2');
        o['3.x'] = this.inName('Konqueror/3');
        o[3]     = this.inName('Konqueror/3.0') || this.inName('Konqueror/3;');
        o[3.3]   = this.inName('Konqueror/3.3');
        o[3.4]   = this.inName('Konqueror/3.4');
        o[3.5]   = this.inName('Konqueror/3.5');
        o['4.x'] = this.inName('Konqueror/4.');
        o[4]     = this.inName('Konqueror/4.0');
        o[4.1]   = this.inName('Konqueror/4.1');
        o[4.2]   = this.inName('Konqueror/4.2');
        this.name    = 'Konqueror';
        this.version = o.getVersion();
        this.engine  = 'KHTML';
        return o;
    }
    this.isKonqueror = this.getKonqueror();
    
    // Check for Firefox and Mozilla
    this.isGecko = this.knowsDom && !this.isIe && !this.isOpera && !this.isKhtml;
    this.getFirefox = function() {
        if (!this.isGecko)           { return ''; }
        if (!this.inName('Firefox')) { return ''; }
        var o = new AgentObject();
        o['1.x'] = this.inName('Firefox/1.');
        o[1]     = this.inName('Firefox/1.0');
        o[1.5]   = this.inName('Firefox/1.5');
        o['2.x'] = this.inName('Firefox/2.');
        o[2]     = this.inName('Firefox/2.0');
        this.name    = 'Firefox';
        this.version = o.getVersion();
        this.engine  = 'Gecko';
        return o;
    }
    this.isFirefox = this.getFirefox();
    this.getMozilla = function() {
        if (!this.isGecko)  { return ''; }
        if (this.isFirefox) { return ''; }
        var o = new AgentObject();
        o['1.x'] = this.inName('rv:1.');
        o[1]     = this.inName('rv:1.0');
        o[1.1]   = this.inName('rv:1.1');
        o[1.2]   = this.inName('rv:1.2');
        o[1.3]   = this.inName('rv:1.3');
        o[1.4]   = this.inName('rv:1.4');
        o[1.5]   = this.inName('rv:1.5');
        o[1.6]   = this.inName('rv:1.6');
        o[1.7]   = this.inName('rv:1.7');
        o[1.8]   = this.inName('rv:1.8');
        this.name    = 'Mozilla';
        this.version = o.getVersion();
        this.engine  = 'Gecko';
        return o;
    }
    this.isMozilla = this.getMozilla();
    
    // Check for Windows
    this.getWin = function() {
        if (!this.inName('win')) { return ''; }
        var o = new Object();
        o['95']     = this.inName('Windows 95');
        o['98']     = this.inName('Windows 98');
        o['ce']     = this.inName('Windows CE');
        o['me']     = this.inName('Windows ME');
        o['nt']     = this.inName('Windows NT;')    || this.inName('Windows NT 4.0') || this.inName('Windows NT)');
        o['2000']   = this.inName('Windows NT 5.0') || this.inName('Windows 2000');
        o['xp']     = this.inName('Windows NT 5.1') || this.inName('Windows XP');
        o['xp-sp2'] = this.inName('Windows NT 5.1') && this.inName('SV1');
        o['s-2003'] = this.inName('Windows NT 5.2');
        o['vista']  = this.inName('Windows NT 6')   || this.inName('Vista');
        this.os   = 'Windows';
        this.osId = o['95']     ? 'Win95'
                  : o['98']     ? 'Win98'
                  : o['2000']   ? 'Win2000'
                  : o['ce']     ? 'WinCE'
                  : o['me']     ? 'WinME'
                  : o['nt']     ? 'WinNT'
                  : o['xp-sp2'] ? 'WinXP-SP2'
                  : o['xp']     ? 'WinXP'
                  : o['s-2003'] ? 'WinServer2003'
                  : o['vista']  ? 'WinVista'
                  : 'Win';
        return o;
    }
    this.isWin = this.getWin();
    
    // Check for Mac
    this.getMac = function() {
        if (!this.inName('Mac')) { return ''; }
        if (this.isWin)          { return ''; }
        var o = new Object();
        o['ppc'] = this.inName('PowerPC') || this.inName('PPC');
        o['x']   = this.inName('Mac OS X');
        this.os   = 'MacOS';
        this.osId = o['x']   ? 'MacOSX'
                  : o['ppc'] ? 'MacPPC'
                  : 'MacOS';
        if (this.isIe) { this.engine = 'Tasman'; }
        return o;
    }
    this.isMac = this.getMac();
    
    // Check for Linux
    this.getLinux = function() {
        if (!this.inName('X11') && !this.inName('Linux')) { return ''; }
        if (this.isWin || this.isMac)                     { return ''; }
        var o = new Object();
        o['suse']  = this.inName('SuSE');
        o['sunos'] = this.inName('SunOS');
        this.os   = 'Linux';
        this.osId = o['suse']  ? 'SuseLinux'
                  : o['sunos'] ? 'SunOS'
                  : 'Linux';
        return o;
    }
    this.isLinux = this.getLinux();
}


/**************************
*   Old Browser Support   *
**************************/
function encodeUtf8(value) {
    var c, s;
    var result = '';
    var i = 0;
    while (i < value.length) {
        c = value.charCodeAt(i++);
        if (c >= 0xDC00 && c < 0xE000) continue;
        if (c >= 0xD800 && c < 0xDC00) {
            if (i >= value.length) continue;
            s = value.charCodeAt(i++);
            if (s < 0xDC00 || c >= 0xDE00) continue;
            c = ((c-0xD800)<<10)+(s-0xDC00)+0x10000;
        }
        if      (c < 0x80)    result += String.fromCharCode(c);
        else if (c < 0x800)   result += String.fromCharCode(0xC0+(c>>6),  0x80+(c&0x3F));
        else if (c < 0x10000) result += String.fromCharCode(0xE0+(c>>12), 0x80+(c>>6&0x3F),  0x80+(c&0x3F));
        else                  result += String.fromCharCode(0xF0+(c>>18), 0x80+(c>>12&0x3F), 0x80+(c>>6&0x3F), 0x80+(c&0x3F));
    }
    return result;
}
function encodeCharCode(charCode) {
    return '%' + charCode.toString(16).toUpperCase();
}
if (!isFunction(encodeURIComponent)) {
    function encodeURIComponent(value) {
        var allowedUriChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-';
        var result = '';
        value = encodeUtf8(value);
        for (var i=0; i<value.length; i++) {
            result += allowedUriChars.indexOf(value.charAt(i)) < 0
                    ? encodeCharCode(value.charCodeAt(i))
                    : value.charAt(i);
        }
        return result;
    }
}

/***********************
*   Global Functions   *
***********************/

function getAbsoluteUrl(uri, baseUri) {
    if (isUndefined(baseUri)) { baseUri = location.href; }
    function getUriParts(uri) {
        /*  The following Regular Expression splits a URI into its parts, which are:
            0 = complete URL
            1 = protocol (without '://')
            2 = hostport
            3 = path     (including leading '/')
            4 = query    (without '?')
            5 = fragment (without '#')
            NOTE: Because of elderly browsers we did without (?:), so the correct indices have to be readjusted.
        */
        var re     = new RegExp('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$');
        var result = uri.match(re);
        var parts  = {
            '1': result[2], 'protocol': result[2],
            '2': result[4], 'hostport': result[4],
            '3': result[5], 'path':     result[5],
            '4': result[7], 'query':    result[7],
            '5': result[9], 'fragment': result[9],
            '0': result[0], 'complete': result[0]
            
        };
        return parts;
    }
    var baseParts = getUriParts(baseUri);
    var newParts  = getUriParts(uri);
    var start = 1;
    for (start=1; start<=5; start++) {
        if (!isEmpty(newParts[start])) { break; }
    }
    for (var i=5; i>=start; i--) {
        if (i==3) {
            if (newParts[i].substring(0, 1) == '/') { baseParts[i] = ''; }
            basePath = baseParts[i].split('/');
            newPath  = newParts[i].split('/');
            basePath.pop();
            for (var j=0; j<newPath.length; j++) {
                if (newPath[j] == '.')  { continue; }
                if (newPath[j] == '..') { basePath.pop(); continue; }
                basePath.push(newPath[j]);
            }
            baseParts[i] = basePath.join('/');
        } else {
            baseParts[i] = newParts[i];
        }
    }
    baseParts[4] = isEmpty(baseParts[4]) ? '' : '?'+baseParts[4];
    baseParts[5] = isEmpty(baseParts[5]) ? '' : '#'+baseParts[5];
    return '${1}://${2}${3}${4}${5}'.inReplace(baseParts);
}


/**********************************
*   Global Prototype Extensions   *
**********************************/
Array.prototype.hasValue = function(value) {
    for (var i=0; i<this.length; i++) {
        if (this[i] == value) { return true; }
    }
    return false;
}

String.prototype.startsWith = function(start) {
    return this.toString().substring(0, start.length) == start;
}

String.prototype.inReplace = function(values) {
    var text = this.toString();
    for (var i in values) {
        var value = values[i];
        if (isSet(Object.prototype[i]) || (isSet(value) && !isString(value) && !isFunction(value.toString))) { continue; }
        var re = new RegExp('(\\$\\{'+i+'\\})|(\\$'+i+'\\b)', 'g');
        text = text.replace(re, isFunction(value) ? function(/* Needed for Opera */) { return value(); } : value);
    }
    return text;
}

/*********************
*   Dynamic Loader   *
*********************/
if (UserAgent.knowsDom) {
    var _loadedJsResources = new Array();
    var _runOnAllLoaded    = new Array();
    function loadJs(uri) {
        if (   document.createElement
            && document.getElementsByTagName
            && document.getElementsByTagName('head')
            && document.getElementsByTagName('head')[0]
        ) {
            var js = document.createElement('script');
            js.setAttribute('type', 'text/javascript');
            js.setAttribute('src',  uri);
            var head = document.getElementsByTagName('head')[0];
            head.insertBefore(js, head.firstChild);
        }
    }
    function jsLoaded(name) {
        if (!isLoaded(name)) {
            _loadedJsResources.push(name);
        }
        //alert('Loaded: '+name);
        for (var i=0; i<_runOnAllLoaded.length; i++) {
            var item = _runOnAllLoaded[i];
            for (var j=0; j<item.length; j++) {
                if (isString(item[j]) && !isLoaded(item[j])) { break; }
                if (isFunction(item[j])) {
                    var temp = item[j];
                    _runOnAllLoaded[i] = new Array();
                    temp();
                }
            }
        }
    }
    function isLoaded(name) {
        return _loadedJsResources.hasValue(name);
    }
    
    function requires() {
        var allLoaded = true;
        for (var i=0; i<arguments.length; i++) {
            var item = arguments[i];
            if (isString(item)) {
                if (!isLoaded(item)) {
                    //alert('Now loading: '+item);
                    allLoaded = false;
                    loadJs(getAbsoluteUrl(item, baseScriptUri));
                }
            }
        }
        if (allLoaded) {
            arguments[arguments.length-1]();
        } else {
            _runOnAllLoaded.push(arguments);
            // Opera somehow is _too_ synchronous, so we enforce
            // a what-needs-to-be-executed-check:
            jsLoaded('_');
        }
    }
    
    var baseScriptUri = location.href;
    var scripts = document.getElementsByTagName('script');
    for (var i=scripts.length-1; i>=0; i--) {
        if (new RegExp(/\/base.js$/).test('/'+scripts[i].src)) {
            baseScriptUri = getAbsoluteUrl(scripts[i].src);
            break;
        }
    }
    
    jsLoaded('base.js');
}
//alert(!!UserAgent.isIe);
//alert(UserAgent+"\n"+UserAgent.description);
