/* Con is a console singleton for logging errors. */
var Con = (function(){
    return window.console ||
            {
                data: [],
                log: function log(x){
                    this.data.push(x);
                }
            };
}());

/* Xhr is a singleton for sync & async ajax requests. */
var Xhr = (function(){
    var _new = window.XMLHttpRequest ?
                function(){
                    return new XMLHttpRequest();
                } :
                function(){
                    return new ActiveXObject('Msxml2.XMLHTTP')
                            || new ActiveXObject('Microsoft.XMLHTTP');
                };
    return {
        cache: {},
        readCache: function rch(url) {
            return this.cache[url];
        },
        writeCache: function wch(url, data){
            this.cache[url] = data;
        },
        get: function get(url, async, succ, fail){
            // First lookup cache.
            var data = this.readCache(url);
            if (data) {
                if (succ) {
                    succ(data);
                }
                return;
            }

            // Data not cached, must do HTTP request.
            // Get a handle to a new XHR instance.
            var x = _new(),
                me = this;

            // Wire up callbacks for success and failure.
            x.onreadystatechange = function() {
                if (this.readyState !== 4) {
                    // Still in progress, keep waiting.
                    return;
                }
                var s = this.status;
                if (s === 200 || s === 0) {
                    // HTTP OK or FILE OK
                    // Write data to cache for future re-use.
                    me.writeCache(url, x.responseText);
                    // Notify callback.
                    if (succ) {
                        succ(x.responseText);
                    }
                } else {
                    // Notify callback.
                    if (fail) {
                        fail(x);
                    }
                }
                x = null;
            };

            // Send the request.
            x.open('GET', url, !!async);
            x.send(null);
        }
    };
    
}());

/*  Util is a singleton with common utility functions. */
var Util = {
    IsIE: !!document.all,
    copy: function cpy(a,b){
        if (a) {
            var k;
            b = b || {};
            for (k in a){
                b[k] = a[k];
            }
            return b;
        }
    },
    trim: function trm(s){
        return s.replace(/^[\s]+/, '').replace(/[\s]+$/, '');
    },
    tpl: function tpl(s, h) {
        return s.replace(
            /\{\@([^\}]+)\}/gm,         /* matches: "{@...}" */
            function _val(x, n) {
                return (h[n] != null) ? h[n] : "";  /* if h[n] is not null AND not undefined */
            }
        );
    },
    domNode: function dn(me, n){
        n = n || 'domNode';
        var el = me[n];
        if (typeof(el) === 'string') {
            el = me[n] = (me.hWin || top).document.getElementById(el);
        }
        return el;
    }
};

/*  Anim is an instantiable class representing an action
    repeated at a fixed interval.
*/

var Anim = (function(){

    function Anim(cfg){
        Util.copy(cfg, this);
    }
    Anim.prototype = {
        tmr: null,
        dur: 500,
        rate: 100,
        target: null,
        style: true,
        prop: null,
        fromVal: null,
        toVal: null,
        suffix: 'px',
        start: function str(delay){
            var me = this;
            if (delay) {
                window.setTimeout(
                    function(){
                        me.start();
                        me = null;
                    },
                    delay);
                return;
            }
            
            this.total = parseInt(this.dur/this.rate,10);
            this.count = 0;
            this.obj = this.target;
            if (typeof(this.obj) === 'string') {
                this.obj = document.getElementById(this.obj);
            }
            if (this.style) {
                this.obj = this.obj.style;
            }
            if (this.doStep()) {
                this.tmr = window.setInterval(
                    function(){
                        if (!me.doStep()) {
                            me.stop();
                        }
                    },
                    this.rate);
            }
        },
        stop: function stp(){
            if (this.tmr) {
                window.clearInterval(this.tmr);
                delete this.tmr;
            }
            if (this.complete) {
                this.complete();
            }
        },
        doStep: function doSt(){
            var k = ++this.count,
                val = this.fromVal + (this.toVal-this.fromVal)*this.count/this.total;
            this.obj.style[this.prop] = val + this.suffix;
            return (k < this.total);
        }
    };
    return Anim;
}());

var Fade = (function(){

    function Fade(cfg){
        Util.copy(cfg, this);
    }
    Fade.prototype = new Anim();
    Fade.prototype.style = true;
    Fade.prototype.doStep = function doSt(){
        var tot = this.total,
            k = ++this.count,
            tween = parseInt(
                        (this.fromVal + (this.toVal-this.fromVal)*k/tot) * 100,
                        10);
                        
        if (Util.IsIE) {
            this.obj.filter = 'progid:DXImageTransform.Microsoft.Alpha(Opacity='+tween+')';
        } else {
            this.obj.opacity = tween/100;
        }
        return (k < tot);
    };
    return Fade;
        
}());

/*  Menu is an instantiable class representing a list
    driven by a config file.
*/
var List = (function(){

    function List(cfg){
        Util.copy(cfg, this);
    }        
    List.prototype = {
        hWin: null,
        domNode: null,
        items: null,
        url: null,
        tpl: '',
        load: function load(){
            /* Loads list config file, then renders menu. */
            var me = this;
            Xhr.get(
                this.url,
                false,
                function(data){
                    me.parse(data);
                    me.render();
                });
        },
        parse: function prs(txt){
            var its = this.items = [],
                a = (txt || '').split('\r\n'),
                l = a.length,
                i, 
                index = 0, 
                field = 1,
                item = {};

            function _eol() {
                if (field > 1){
                    item.index = index;
                    its[index] = item;
                    index++;
                    item = {};
                    field = 1;
                }
            }
            
            for (i=0; i<l; i++){
                var s = a[i];
                if (!s || !s.match(/\S/)) {
                    // Blank- or all-whitespace- line indicates end of item. Add item to list.
                    _eol();
                } else {
                    item[field++] = Util.trim(s);
                }
            }
            // Last line indicates end of item. Add item to list.
            _eol();
        },
        render: function rndr(){
            var el = Util.domNode(this);
            if (el) {
                var out = [],
                    a = this.items,
                    l = (a && a.length) || 0,
                    s = this.tpl,
                    f  = this.itemRenderer || Util.tpl,
                    i;
                for (i=0; i<l; i++){
                    out.push(f(s, a[i]));
                }
                el.innerHTML = out.join('');
            }
        }
    };
    return List;    
}());

function ClassItemRenderer(tpl, item){
    /* A non-templated item renderer function, used for rendering classes info data. */
    var out = [],
        i, j;
    if (item[1]) {
        out.push('<div><div class="title">' + item[1] + '</div><div class="details">');
        for (i=2; item[i]; i++) {
            out.push('<pre>'+item[i]+'</pre>');
        }
        out.push('</div></div>');
    }
    return out.join('');
}

var LightBox = (function(){
    function LB(cfg){
        Util.copy(cfg, this);
    }
    LB.prototype = {
        url: null,
        hWin: null,
        domNode: null,
        artNode: null,
        tpl: '',
        hide: function hd(){
            var el = Util.domNode(this);
            if (el) {
                new Fade({
                    target: el,
                    fromVal: 1,
                    toVal: 0,
                    complete: function(){
                                el.style.display = 'none';
                    }
                }).start();
            }
        },        
        show: function sw(item){
            var el = Util.domNode(this),
                el2 = Util.domNode(this, 'artNode');
            if (el && el2) {
                var tmp = Util.tpl(this.tpl, item);
                el2.innerHTML = tmp;
                new Fade({
                    target: el,
                    fromVal: 0,
                    toVal: 1
                }).start();
                el.style.display = 'block';
            }
        }
    };
    return LB;
}());

