﻿if (!self.INDIGO) (function () {
    self.INDIGO = {};

    //simple warn function for testing, requires a div with id warning
    self.warn = function (str, clear) {
        var div = $('warning');
        if (div) {
            if (clear)
                empty(div);
            div.innerHTML += str + '<br />';
        }
    };
    //trim //found this in a case study of several trim techniques, this was generally the fastest one for trimming on small/normal strings
    String.prototype.trim = function (str) {
        str = str || '\s';
        str = '\\' + str;
        //return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        return this.replace(new RegExp('^' + str + str + '*'), '').replace(new RegExp(str + str + '*$'), '');
    };

    //capitalize strings... clever!
    String.prototype.capitalize = function(limit){ //v1.0
        return this.replace(limit ? /\w+/ : /\w+/g, function(a) {
            return a.charAt(0).toUpperCase() + a.substr(1).toLowerCase();
        }); 
    };
    String.prototype.capitalise = String.prototype.capitalize; //what's the convention here anyway? Behaviour.js is a known script...

    self.getSign = function (i) {
        return (!i && 1) || (i / Math.abs(i));
    };

    //Tracking... BETA!
    /*
    //Used to track 'invalid' set properties //browsers tested so far: IE/FF/O/S
    function read(d) {
        var invalid = [];
        if (d.nodeType === 1 && d.nodeName !== "APPLET") {
            var a, i, l, x, c = document.createElement(d.nodeName);
            for (var x in d) {
                if (!collInvalid[x] && d[x] !== c[x]) {
                    try {
                        d[x] = d[x];
                    } catch (err) {
                        invalid.push(x + ":true");
                    }
                }
            }
            a = d.childNodes;
            if (a) {
                l = a.length;
                for (i = 0; i < l; i += 1) {
                    read(d.childNodes[i]);
                }
            }
        }
        warn(invalid);
    }
    */

    self.collInvalid = {parentNode:true,childNodes:true,firstChild:true,lastChild:true,previousSibling:true,nextSibling:true,
    attributes:true,cellIndex:true,offsetTop:true,offsetLeft:true,offsetWidth:true,offsetHeight:true,offsetParent:true,
    scrollHeight:true,scrollWidth:true,clientHeight:true,clientWidth:true,style:true,children:true,outerText:true,
    currentStyle:true,document:true,parentTextEdit:true,parentElement:true,runtimeStyle:true,filters:true,clientTop:true,
    clientLeft:true,behaviorUrns:true,all:true,sourceIndex:true,innerHTML:true,outerHTML:true,textContent:true,host:true,
    hostname:true,pathname:true,search:true,port:true,hash:true,selectionStart:true,selectionEnd:true,
    sectionRowIndex:true,naturalHeight:true,naturalWidth:true,x:true,y:true,text:true,rows:true,
    tBodies:true,rowIndex:true,cells:true,textLength:true,controllers:true,
    innerText:true,fileModifiedDate:true,fileCreatedDate:true,mimeType:true,href:true,protocol:true,complete:true,
    nameProp:true,isMultiLine:true,readyState:true,height:true,forms:true,labels:true,validity:true,forms:true,
    labels:true,validity:true,forms:true,labels:true,validity:true,boxObject:true,elements:true,contentDocument:true,
    contentWindow:true,areas:true,outerDiv:true,innerDiv:true,width:true,options:true,canHaveHTML:true,
    canHaveChildren:true,length:true,isindex:true,form:true,isContentEditable:true,templateElements:true,
    selectedOptions:true,parentRule:true};

    self.events = {
        maxEvents: 10,
        step: 0,
        collection: [],
        add: function (e) {
            this.collection[this.step++] = e;
            while (this.collection.length > this.maxEvents) {
                this.collection.shift();
                this.step--;
            }
        },
        undo: function () {
            if (this.step > 0) {
                this.collection[--this.step].undo();
            }
        },
        redo: function () {
            if (this.step < this.history && this.collection[this.step] && this.collection[this.step++].redo) {
                this.collection[this.step - 1].redo();
            }
        },
        cEvent: function (undo, redo) {
            return {
                undo: undo,
                redo: redo/*,
                track: function () {};
                capture: function () {};
                */
            }
        },
        Event: function () {
            var past = [];
            var future = [];
            var captured = false;
            this.track = function (o, deep) {
                recordElement(o, deep);
            };

            function recordElement(o, deep/*object [, deep/level][, object[, deep/level]]...*/) {
                //deep/level can be either true, or a number
                //analyze arguments, assume single object+deep/level pair for now
                function save(curO, level) {
                //check if we're allowed to add
                    if (!(captured && future.length === past.length)) {
                        level = level || 0;
                        var history = captured ? future : past;
                        var record = {};
                        record.element = curO;
                        record.childOrder = [];
                        record.cssText = "";
                        record.level = level;
                        record.deep = deep;
                        history.push(record);
                        if (curO.nodeType === 1 && curO.nodeName !== 'APPLET') {
                            record.properties = {};
                            record.attributes = {};
                            var a, i, l, x, attr, c;
                            c = document.createElement(curO.nodeName);
                            //Record properties
                            for (x in curO) {
                                if (!collInvalid[x] && curO[x] !== c[x]) {
                                    //warn("Storing property " + x + " with value: " + curO[x]);
                                    record.properties[x] = curO[x];
                                }
                            }
                            //Record attributes
                            if (!document.all || window.opera) {
                            attr = curO.attributes;
                                if (attr) {
                                l = attr.length;
                                    for (i = 0; i < l; i ++) {
                                        if (attr[i].name) {
                                            //alert("attr: " + attr[i].name.toLowerCase());
                                            if (record.properties[attr[i].name.toLowerCase()]) {
                                                //alert("Duplicate event handler detected: " + attr[i].name);
                                            }
                                            //warn("Storing attribute " + attr[i].name + " with value: " + attr[i].value);
                                            record.attributes[attr[i].name] = attr[i].value;
                                        }
                                    }
                                }
                            }
                            //Record style
                            record.cssText = curO.style.cssText;
                            if (level < deep || deep === true) {
                                a = curO.childNodes;
                                if (a) {
                                    l = a.length;
                                    for (i = 0; i < l; i++) {
                                        //save(a[i], level + 1); =
                                        arguments.callee(a[i], level + 1);
                                        record.childOrder.push(a[i]);
                                    }
                                }
                            }
                        } else if (curO.nodeType === 3) {
                            record.nodeValue = curO.nodeValue;
                        }
                    }
                }
                if (o) {
                    save(o);
                }
            }

            this.capture = function () {
                captured = true;
                var i, l, record;
                l = past.length;
                for (i = 0; i < l; i++) {
                    record = past[i];
                        if (record.level === 0) {
                            recordElement(record.element, record.deep);
                    }
                }
            };

            function restore(history) {
                var x, i, l, record, element, j, m;
                l = history.length;
                //Restore attributes, properties, style and childnodes
                for (i = 0; i < l; i++) {
                    record = history[i];
                    element = record.element;
                    //warn("Restoring element " + element);
                    //RESTORE ATTRIBUTES BEFORE PROPERTIES -> PROPERTIES WILL OVERRIDE ATTRIBUTES IN BEHAVIOUR
                    for (x in record.attributes) {
                        //warn("Restoring attribute " + x + " with value " + record.attributes[x]);
                        element.setAttribute(x, record.attributes[x]);
                    }
                    for (x in record.properties) {
                        //warn("Restoring property " + x + " with value " + record.properties[x]);
                        element[x] = record.properties[x];
                    }
                    if (record.nodeValue) {
                        //warn("Restoring nodeValue " + record.nodeValue);
                        element.nodeValue = record.nodeValue;
                    }
                    //Restore style
                    if (element.style) {
                        element.style.cssText = record.cssText;
                    }
                    //Restore childnodes positions

                    //purge existing elements
                    while (element.firstChild) {
                        element.removeChild(element.firstChild);
                    }
                    //restore
                    m = record.childOrder.length;
                    if (m > 0) {
                        for (j = 0; j < m; j++) {
                            element.appendChild(record.childOrder[j]);
                        }
                    }
                }
            };

            this.redo = function () {
                restore(future);
            };

            this.undo = function () {
                restore(past);
            };
        }
    };
    ////End tracking


    //style functions
    //get computed style
    //FLOAT CASE: FF: float (even though it's style.cssFloat) //IE: styleFloat (also style.styleFloat)
    //MARGIN/PADDING CASE: top/right/bottom/left
    //BORDER-x CASE: top/right/bottom/left | width/style/color
    //BORDER CASE: returns undefined when borders don't match
    self.getStyle = function (el, cssRule){ //non-border cssRule can be in either style: camelCase or hyphen-ated //handles float
        var types, i, result, check;
        if (cssRule === 'margin' || cssRule === 'padding') {
            types = ['top', 'right', 'bottom', 'left'];
            result = '';
            for (i = 0; i < 4; i++) {
                result += " " + arguments.callee(el, cssRule + '-' + types[i]);
            }
            return result.replace(/( .+?)\1\1\1/, '$1').trim(); //reduce to only 1 element if all 4 sides are identical
        }
        if (cssRule === 'border') {
            types = ['top', 'right', 'bottom', 'left'];
            check = arguments.callee(el, 'border-' + types[0]);
            for (i = 1; i < 4; i++) {
                result = arguments.callee(el, 'border-' + types[i]);
                if (result !== check)
                    return undefined;
            }
            return result;
        }
        if (cssRule.match(/^border-(width|style|color)$/)) {
            types = ['top', 'right', 'bottom', 'left'];
            result = '';
            for (i = 0; i < 4; i++) {
                result += ' ' + arguments.callee(el, cssRule.replace(/-(.*)/, '-' + types[i] + '-$1'));
            }
            return result.replace(/( .+?)\1\1\1/, '$1').trim(); //reduce to only 1 element if all 4 sides are identical (IE style style retrieval, differs with FF, good, bad? hm)
        }
        if (cssRule.match(/^border-(top|right|bottom|left)$/)) {
            types = ['width', 'style', 'color'];
            result = '';
            for (i = 0; i < 3; i++) {
                result += ' ' + arguments.callee(el, cssRule + "-" + types[i]);
            }
            return result.trim();
        }
        //Opera has both 'currentStyle' (IE) as defaultView.getComputedStyle (FF), but only currentStyle returns correct/useful/consistent values, so check for that first
        if (el.currentStyle) {
            if (window.opera && cssRule === 'width' && el.currentStyle[cssRule] !== 'auto') //Opera's width detection is off
                return (getDimensions(el)[0] - parseInt('0' + getStyle(el, 'padding-left')) - parseInt('0' + getStyle(el, 'padding-right')) - parseInt('0' + getStyle(el, 'border-left')) - parseInt('0' + getStyle(el, 'border-right'))) + 'px';
            return el.currentStyle[cssRule.replace(/-(\w)/g, function (match, p1) {
                return p1.toUpperCase();
            }).replace(/^(float|cssFloat)$/, 'styleFloat')];
        } else if (document.defaultView && document.defaultView.getComputedStyle) {
            return document.defaultView.getComputedStyle(el, '').getPropertyValue(cssRule.replace(/^(styleFloat|cssFloat)$/, 'float').replace(/([A-Z])/g, function (match, p1) {
                return "-" + p1.toLowerCase();
            }));
        }
        return undefined;
    };

    self.copyStyle = function (item) {
        var style = {};
        for (var x in item.style)
            if (item.style.hasOwnProperty(x))
                style[x] = getStyle(item, x);
        return style;
    };

    self.applyStyle = function (item, style) { //apply styles in object
        if (typeof style === 'string')
            return addClass(item, style);
        for (var x in style)
            if (style.hasOwnProperty(x))
                item.style[x] = style[x];
        return item;
    };

    self.removeStyle = function (item, style) { //remove styles in object
        if (typeof style === 'string')
            return removeClass(item, style);
        for (var x in style)
            if (style.hasOwnProperty(x))
                item.style[x] = '';
        return item;
    };

    self.getOPos = function (obj) { //Object position relative to document
        var curLeft = 0, curTop = 0;
        if (obj.offsetParent) {
            curLeft = obj.offsetLeft;
            curTop = obj.offsetTop;
            while (obj = obj.offsetParent) {
                curLeft += obj.offsetLeft;
                curTop += obj.offsetTop;
            }
        }
        return [curLeft, curTop];
    };

    self.getFPos = function (obj) { //Object position in the flow
    var position, coords, orCoords = [obj.style.left, obj.style.top];
        if (obj.parentNode !== obj.offsetParent) {
            position = obj.parentNode.style.position;
            obj.parentNode.style.position = 'relative';
            coords = [obj.offsetLeft, obj.offsetTop];
            obj.parentNode.style.position = position;
        } else
            coords = [obj.offsetLeft, obj.offsetTop];
        return coords;
    };

    self.getOFPos = function (obj) { //Original relative position
        var coords, orCoords = [getStyle(obj, 'left'), getStyle(obj, 'top')];
        obj.style.left = '0px';
        obj.style.top = '0px';
        coords = getFPos(obj);
        obj.style.left = orCoords[0];
        obj.style.top = orCoords[1];
        return coords;
    };

    self.getMPos = function (e) { //Mouse position of event
        e = e || window.event;
        var x, y;
        if (e.pageX) {
            x = e.pageX;
            y = e.pageY;
        } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6/7 Strict Mode
            x = e.clientX + document.documentElement.scrollLeft;
            y = e.clientY + document.documentElement.scrollTop;
        } else if (e) {
            x = e.clientX + document.body.scrollLeft;
            y = e.clientY + document.body.scrollTop;
        }
        return [x, y];
    };

    //Viewport dimensions 
    self.getVDims = function () {
        var x, y;
        if (self.innerHeight) { // all except Explorer
            x = self.innerWidth;
            y = self.innerHeight;
        } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6/7 Strict Mode
            x = document.documentElement.clientWidth;
            y = document.documentElement.clientHeight;
        } else if (document.body) { // other Explorers
            x = document.body.clientWidth;
            y = document.body.clientHeight;
        }
        return [x, y];
    };

    //Scrolling dimensions 
    self.getSDims = function () {
        var x, y;
        if (self.pageYOffset) { // all except Explorer
            x = self.pageXOffset;
            y = self.pageYOffset;
        } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6/7 Strict Mode
            x = document.documentElement.scrollLeft;
            y = document.documentElement.scrollTop;
        } else if (document.body) { // all other Explorers
            x = document.body.scrollLeft;
            y = document.body.scrollTop;
        }
        return [x, y];
    };
    
    self.getPDims = function () { //page dimensions
        var x, y;
	    if (window.innerHeight && typeof(window.scrollMaxY) === 'number') {// Firefox
		    y = window.innerHeight + (window.scrollMaxY || 0);
		    x = window.innerWidth + (window.scrollMaxX || 0);
	    } else if (document.body.scrollHeight > document.body.offsetHeight){ // Safari
		    y = document.body.scrollHeight;
		    x = document.body.scrollWidth;
	    } else { // works in Explorer 6 Strict, Mozilla (not FF)
		    y = document.body.offsetHeight;
		    x = document.body.offsetWidth;
  	    }
	    return [x, y];
    };

    self.getDimensions = function (el) { //returns dimensions of element [width, height]
        el = $(el); 
        var width, height;
        if (getStyle(el, 'display') == 'none') {
            el.style.display = 'block';
            width = el.offsetWidth;
            height = el.offsetHeight;
            el.style.display = 'none';
        } else {
            width = el.offsetWidth;
            height = el.offsetHeight;
        }
        return [width, height];
    };

    //Copy event... allow for editing and passing on (used by TSwap)
    //outdated anyway, use built-in event constructors
    self.copyEvent = function (e) {
        e = e || window.event || {}; //...
        return {
            target: e.target || e.srcElement,
            relatedTarget: e.relatedTarget || e.toElement,
            pageX: e.pageX || e.clientX + document.body.scrollLeft,
            pageY: e.pageY || e.clientY + document.body.scrollTop,
            returnValue: e.returnValue, //useless, has to be set on real event
            cancelBubble: e.cancelBubble, //useless, has to be set on real event
            type: e.type,
            button: e.button,
            altKey: e.altKey,
            shiftKey: e.shiftKey,
            ctrlKey: e.ctrlKey,
            preventDefault: e.preventDefault && function () {
                e.preventDefault();
            },
            stopPropagation: e.stopPropagation && function () {
                e.stopPropagation();
            },
            isCopy: true
        }
    };

    self.getHZ = function () { //get highest Z-index
        var hZ = 0, zi;
        getAll().match(function (el) {
            return true || (getStyle(el, 'position') !== 'static');
        }).forEach(function (el) {
            zi = getStyle(el, 'zIndex');
            if (zi)
                if (parseInt(zi, 10) > hZ)
                    hZ = zi;
        });
        return parseInt('0' + hZ, 10);
    };

    //revise "indicator"... attribute/property ok, include class?
    //indicator values? not compatible with classes
    //check for an 'attribute' set as either attribute, classname or property
    self.hasIndicator = function (el, ind, val) {
        val = val || ind;
        var reg = new RegExp('(^|\\s)' + ind + '(\\s|$)');                                                          //too restrictive
        return !!((el.className && reg.test(el.className)) || (el.getAttribute && el.getAttribute(ind.toLowerCase())/* === val*/) || el[ind]);
    };

    self.hasParentIndicator = function (el, ind, val) {
        do {
            if (hasIndicator(el, ind, val))
                return true;
        } while (el = el.parentNode);
        return false;
    };

    self.addIndicator = function (el, ind, val) {
        val = val || ind;
        addClass(el, ind);
        el.setAttribute(ind, val);
        el[ind] = true;
        return el;
    };

    self.removeIndicator = function (el, ind, val) {
        removeClass(el, ind);
        el[ind] = undefined;
        el.removeAttribute(ind);
        return el;
    };

    self.getIndicator = function (el, ind) {
        return el.getAttribute(ind.toLowerCase()) || el[ind];
    };

    self.getIndicatedParent = function (el, ind, val) { //return first occuring parentnode (or node itself) with given indicator
        do
            if (hasIndicator(el, ind, val))
                return el;
        while (el = el.parentNode);
        return null;
    };

    self.hasParent = function (el, parent) {
        do {
            if (el === parent)
                return true;
        } while (el = el.parentNode);
        return false;
    };

    //...remove element safely
    self.remove = function (el) {
        if (el.parentNode) {
            purge(el);
            el.parentNode.removeChild(el);
        }
        return el;
    };
    
    //remove all child nodes 
    self.empty = function (el) {
        while (el.firstChild)
            remove(el.firstChild);
        return el;
    };

    //remove classname
    self.removeClass = function (el, name) {
        if (el.className)
            el.className = el.className.replace(new RegExp('(^|\\s)' + name + '(\\s|$)', 'g'), ' ').trim();
        return el;
    };

    //add classname
    self.addClass = function (el, name) {
        if (!new RegExp('(^|\\s)' + name + '(\\s|$)').exec(el.className))
            el.className = (el.className + ' ' + name).trim();
        return el;
    };

    //get first occuring attribute of parentnodes
    self.getParentAttribute = function (el, attr) {
        if (el.hasAttribute && el.hasAttribute(attr))
            return el.getAttribute(attr);
        if (el.parentNode)
            return arguments.callee(el.parentNode, attr);
        return null;
    };

    //get first occuring non-empty style property of parentnodes (replace by a generic getStyle on the parentnode? hm
    self.getSetParentStyle = function (el, attr) {
        if (el.style && el.style[attr])
            return el.style[attr];
        if (el.parentNode)
            return arguments.callee(el.parentNode, attr);
        return null;
    };

    //...
    self.stopEvent = function (e) {
        if (e.preventDefault) {
            e.preventDefault();
            e.stopPropagation();
        }
        e.returnValue = false;
        e.cancelBubble = true;
        return false;
    };

    //walk every element of the DOM and pass it to the function provided //return an array of all elements the function returned true for
    self.walk = function (func, o) {
        o = o || document.body;
        var match = [];
        if (func(o)) {
            match.push(o);
        }
        if (o.hasChildNodes) {
            (function (o) {
                o = o.firstChild;
                if (o) {
                    do {
                        if (func(o)) {
                            match.push(o);
                        }
                        if (o.hasChildNodes) {
                            arguments.callee(o);
                        }
                    } while(o = o.nextSibling);
                }
            })(o);
        }
        return match;
    };

    //Converts an array-like object to a real array
    self.toArray = function (arO) {
        var i, l = arO.length, ar = [];
        for (i = 0; i < l; i++) {
            ar[i] = arO[i];
        }
            return ar;
        }

        //Get all document/body elements
        self.getAll = function (o, incParent) {
        if (incParent) {
            o = document;
        }
        o = o || document.body;
        var coll = o.getElementsByTagName('*');
        return toArray((coll && (typeof coll.length === 'number')) ? coll : o.all).concat(incParent ? [o] : []); //prevent returning an unrecognised collection
    };

    self.isArray = function (o) {
        return Object.prototype.toString.apply(o) === '[object Array]';
    };

    //Common Array augmentations:
    if (!Array.prototype.forEach) {
        //Apply function to each element in an array
        Array.prototype.forEach = function (fun, scope) {
            var i, l = this.length, scope = scope || self;
            for (i = 0; i < l; i++) {
                fun.call(scope, this[i], i, this);
            }
        };
        //Apply function to each element and return results in a new array
        Array.prototype.map = function (fun, scope) {
            var i, l = this.length, map = [], scope = scope || self;
            for (i = 0; i < l; i++) {
                map[i] = fun.call(scope, this[i], i, this);
            }
            return map;
        };
        //Return index of first occuring given value
        Array.prototype.indexOf = function (val, start) {
            var i, l = this.lenght, start = start || 0;
            for (i = start; i < l; i++) {
                if (this[i] === val) {
                    return i;
                }
            }
            return -1;
        };
    }


    //Array method that returns index if a given value was found in it 
    Array.prototype.inArray = function (value) {
        var i, l;
        l = this.length;
        for (i = 0; i < l; i++) {
            if (this[i] === value) {
                return true;
            }
        }
        return false;
    };

    //Return array of all elements for which the function returned a truthy value (gap/mutation-guarded)
    Array.prototype.match = function (fun, scope) {
        var i, val, l = this.length, match = [], scope = scope || self;
        for (i = 0; i < l; i++)
            if (i in this && ((val = this[i]) || true) && fun.call(scope, this[i], i, this))
                match.push(val);
        return match;
    };

    //Shuffles array (yay for obscurity?)
    Array.prototype.shuffle = function () {
        for(var j, x, i = this.length; i; j = parseInt(Math.random() * i), x = this[--i], this[i] = this[j], this[j] = x);
    };
    
    //returns copy of array
    Array.prototype.copy = function () {
        return toArray(this);
    };

    //remove element if present
    Array.prototype.remove = function (item) {
        while (this.inArray(item))
            this.splice(this.indexOf(item), 1);
        return item;
    };
    
    function chain(fun) {
        return function () {
            return fun.apply(this, arguments);
        }
    }

    INDIGO.chains = {
        getElementsByAttribute: function (attr, attrVal) {
            return getElementsByAttribute(this, attr, attrVal);
        },
        getElementByAttribute: function (attr, attrVal) {
            return getElementByAttribute(this, attr, attrVal);
        }
    };

    //gets elements that match the attribute + value given //IE/FF class name difference compatability
    self.getElementsByAttribute = function (/*o, attr, attrVal*/) {
        var o, attr, attrVal, reg, arMatch = [];
        if (typeof arguments[0] === 'object') {
            o = arguments[0];
            attr = arguments[1];
            attrVal = arguments[2];
        } else {
            o = document.body;
            attr = arguments[0];
            attrVal = arguments[1];
        }

        if (attr === 'class' || attr === 'className')
            attr = (document.all && !window.opera) ? 'className' : 'class';

        if (attrVal)
            reg = new RegExp('(^|\\s)' + attrVal + '(\\s|$)');

        function matchAttr(el) {
            var elAttr = el.getAttribute && el.getAttribute(attr);
            return !!((elAttr && reg && reg.test(elAttr)) || (elAttr && !reg));
        } 

        //return walk(matchAttr, o); //use the new getAll/forEach (faster);
        if (o instanceof Array)
            arMatch = o.match(matchAttr);
        else
            arMatch = getAll(o).match(matchAttr);

        //chaining
        arMatch.getElementsByAttribute = INDIGO.chains.getElementsByAttribute;
        arMatch.getESBA = arMatch.getElementsByAttribute;
        arMatch.getElementByAttribute = INDIGO.chains.getElementByAttribute;
        arMatch.getEBA = arMatch.getElementByAttribute;

        return arMatch;
    };

    self.getESBA = getElementsByAttribute; //alias

    self.getElementByAttribute = function (o, attr, attrVal) { //single select variant, a la getElementById
        return getElementsByAttribute(o, attr, attrVal)[0];
    };

    self.getEBA = getElementByAttribute; //alias

    self.getElementsByClassName = function (/*o, classVal*/) {
        var o, classVal;
        if (typeof arguments[0] === 'object') {
            o = arguments[0];
            classVal = arguments[1];
        } else {
            o = document.body;
            classVal = arguments[0];
        }
        return getElementsByAttribute(o, 'class', classVal);
    };

    self.$ = function () {
        var element, elements = [];
        for (var i = 0, l = arguments.length; i < l; i++) {
            element = arguments[i];
            if (typeof element === 'string')
                element = document.getElementById(element);
            if (l === 1)
                return element;
            elements[i] = element;
        }
        return elements;
    };
    
    self.create = function (name) {
        return document.createElement(name);
    };
    
    //Clear version of the loadScript and getXHR functions with onload script loading feature 
    self.getXHR = function () {
        var XHR, constructor, param;
        constructor = XMLHttpRequest || ActiveXObject;
        param = ((XHR = new constructor(true)) && true) || ((XHR = new constructor('Msxml2.XMLHTTP')) && 'Msxml2.XMLHTTP') || ((XHR = new constructor('Microsoft.XMLHTTP')) && 'Microsoft.XMLHTTP');
        getXHR = function () {return new constructor(param);};
        return XHR;
    };

    self.loadScript = function (url, async, delay) {
        var http = getXHR();
        url += ((url.indexOf('?') + 1) ? '&' : '?') + ('crndid=' + new Date().getTime()); //randomize url with a crndid param
        http.open('GET', url, !!async);
        if (http.overrideMimeType)
            http.overrideMimeType('text/javascript');
        if (delay) {
            window.addEvent('load', function () {
                if (!INDIGO[delay]) {
                    http.send(null);
                    eval.call(self, http.responseText);
                }
            });
        } else {
            http.send(null);
            eval.call(self, http.responseText);
        }
    };

    self.xCall = function (url, callback, xml, sync) {
        var http = getXHR();
        url += ((url.indexOf('?') + 1) ? '&' : '?') + ('crndid=' + Math.random()/*new Date().getTime()*/); //randomize url with a crndid param //math.random is slightly faster
        http.open('GET', url, !sync);
        if (callback) {
            http.onreadystatechange = function () {
                if (http.readyState == 4 && http.status == 200)
                    if (xml)
                        callback(http.responseXML, http);
                    else
                        callback(http.responseText, http);
            };
            http.send(null);
        }  else {
            http.send(null);
            return http;
        }
    };
    
    //Shortened version of the loadScript+getHttp functions for use in sub-files
    /*
    self.loadScript = function (url, async) {
        var http = (self.XMLHttpRequest && new XMLHttpRequest()) ||
        (self.ActiveXObject && (new ActiveXObject("Msxml2.XMLHTTP") || new ActiveXObject("Microsoft.XMLHTTP")));
        http.open("GET", url + ((url.indexOf("?") + 1) ? "&" : "?") + ("crndid=" + new Date().getTime()), !!async);
        if (http.overrideMimeType) http.overrideMimeType("text/javascript");
        http.send(null);
        eval.call(self, http.responseText);
    }
    */
    
    self.randInt = function (h, l) {
        l = l || 0;
        return parseInt((h - l + 1) * Math.random() + l, 10);
    };

    self.toInt = function (strInt) {
        return parseInt(strInt, 10);
    }

    self.setCookie = function (c_name, value, expiredays) {
    	var exdate = new Date();
    	exdate.setDate(exdate.getDate() + expiredays);
    	document.cookie = c_name + '=' + escape(value) + ((expiredays == null) ? '' : ';expires=' + exdate.toGMTString());
    };

    self.getCookie = function (c_name) {
    	if (document.cookie.length > 0) {
	        c_start = document.cookie.indexOf(c_name + '=');
		    if (c_start !== -1) {
                c_start = c_start + c_name.length+1;
                c_end = document.cookie.indexOf(';', c_start);
                if (c_end === -1) {
                    c_end = document.cookie.length;
                }
	    	    return unescape(document.cookie.substring(c_start, c_end));
	        }
    	}
    	return '';
    };

    //event attachment abstraction
    self.addEvent = function() {
        if (window.addEventListener) {
            return function(el, type, fn, trickle) {
                if (!(typeof el === 'object')) {
                    trickle = fn;
                    fn = type;
                    type = el;
                    el = window;
                }
                el.addEventListener(type, fn, trickle);
            };
        } else if (window.attachEvent) {
            return function(el, type, fn) {
                if (!(typeof el === 'object')) {
                    trickle = fn;
                    fn = type;
                    type = el;
                    el = window;
                }
                //var f = function() {
                // fn.call(el, window.event);
                //};
                var f = fn; //else it becomes 'impossible' to detach events
                el.attachEvent('on' + type, f);
            };
        }
    }();

    self.removeEvent = function() {
        if (window.removeEventListener) {
            return function(el, type, fn, trickle) {
                if (!(typeof el === 'object')) {
                    trickle = fn; 
                    fn = type;
                    type = el;
                    el = window;
                }
                el.removeEventListener(type, fn, trickle);
            };
        } else if (window.detachEvent) {
            return function(el, type, fn) {
                if (!(typeof el === 'object')) {
                    fn = type;
                    type = el;
                    el = window;
                }
                el.detachEvent('on' + type, fn);
            };
        }
    }();

    if (!document.addEvent) {
        document.addEvent = function (ev, func, trickle) {
            addEvent(this, ev, func, trickle);
        };
        document.removeEvent = function (ev, func, trickle) {
            removeEvent(this, ev, func, trickle);
        };
    }

    //call before removing a dom item to avoid memory leak 
    self.purge = function (d) {
        d = d || document.body;
        var a = d.attributes, i, l, n, x;
        if (a) {
            l = a.length;
            for (i = 0; i < l; i += 1) {
                n = a[i].name;
                if (typeof d[n] === 'function') {
                    d[n] = null;
                }
            }
            for (x in a)
                if (a.hasOwnProperty(x))
                    a[x] = null;
        }
        a = d.childNodes;
        if (a) {
            l = a.length;
            for (i = 0; i < l; i += 1) {
                purge(d.childNodes[i]);
            }
        }
    };
    
    self.object = function (o) {
        function F() {};
        F.prototype = o;
        return new F();
    };
    
    window.addEvent('unload', purge);
})();

//COOL TRICK
/*
javascript:R=0; x1=.1; y1=.05; x2=.25; y2=.24; x3=1.6; y3=.24; x4=300; y4=200; x5=300; y5=200; DI= document.images; DIL=DI.length; function A(){for(i=0; i*/
