'use strict';
// General-purpose JavaScript modernisation. Ensure features are available in
// older browsers, whilst using fast native implementations in newer browsers.


// Add ECMA262-5 method binding if not supported natively
//
if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        if (arguments.length<=1) {
            return function() {
                return that.apply(owner, arguments);
            };
        } else {
            var args= Array.prototype.slice.call(arguments, 1);
            return function() {
                return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
            };
        }
    };
}

Function.identity= function(o) { return o; };

// Add ECMA262-5 string trim if not supported natively, and Prototype/MS-AJAX
// string comparators if not already added.
//
if (!('trim' in String.prototype)) {
    String.prototype.trim= function() {
        return this.replace(/^\s+/, '').replace(/\s+$/, '');
    };
}
if (!('startsWith' in String.prototype)) {
    String.prototype.startsWith= function(s) {
        return this.lastIndexOf(s, 0)===0;
    };
}
if (!('endsWith' in String.prototype)) {
    String.prototype.endsWith= function(s) {
        var i= this.length-s.length;
        return i>=0 && this.indexOf(s, i)===i;
    };
}
if (!('padLeft' in String.prototype)) {
    String.prototype.padLeft = function (length, character) { 
         return new Array(length - this.length + 1).join(character || ' ') + this;
    };
}


// Add ECMA262-5 Array methods if not supported natively
//
if (!('indexOf' in Array.prototype)) {
    Array.prototype.indexOf= function(find, i /*opt*/) {
        if (i===undefined) i= 0;
        if (i<0) i+= this.length;
        if (i<0) i= 0;
        for (var n= this.length; i<n; i++)
            if (i in this && this[i]===find)
                return i;
        return -1;
    };
}
if (!('lastIndexOf' in Array.prototype)) {
    Array.prototype.lastIndexOf= function(find, i /*opt*/) { /* care: i is inclusive */
        if (i===undefined) i= this.length-1;
        if (i<0) i+= this.length;
        if (i>this.length-1) i= this.length-1;
        for (i++; i-->0;)
            if (i in this && this[i]===find)
                return i;
        return -1;
    };
}
if (!('forEach' in Array.prototype)) {
    Array.prototype.forEach= function(action, that /*opt*/) {
        for (var i= 0, n= this.length; i<n; i++)
            if (i in this)
                action.call(that, this[i], i, this);
    };
}
if (!('map' in Array.prototype)) {
    Array.prototype.map= function(mapper, that /*opt*/) {
        var other= new Array(this.length);
        for (var i= 0, n= this.length; i<n; i++)
            if (i in this)
                other[i]= mapper.call(that, this[i], i, this);
        return other;
    };
}
if (!('filter' in Array.prototype)) {
    Array.prototype.filter= function(filter, that /*opt*/) {
        var other= [], v;
        for (var i=0, n= this.length; i<n; i++)
            if (i in this && filter.call(that, v= this[i], i, this))
                other.push(v);
        return other;
    };
}
if (!('every' in Array.prototype)) {
    Array.prototype.every= function(tester, that /*opt*/) {
        for (var i= 0, n= this.length; i<n; i++)
            if (i in this && !tester.call(that, this[i], i, this))
                return false;
        return true;
    };
}
if (!('some' in Array.prototype)) {
    Array.prototype.some= function(tester, that /*opt*/) {
        for (var i= 0, n= this.length; i<n; i++)
            if (i in this && tester.call(that, this[i], i, this))
                return true;
        return false;
    };
}

// Make Array from Array-like. Non-standard. Like Array slice(), but slice
// isn't guaranteed to work on host objects like NodeList.
//
Array.fromList= function(list, alwayscopy) {
    if (alwayscopy===undefined) alwayscopy= true;
    if (list instanceof Array && !alwayscopy)
        return list;
    var array= new Array(list.length);
    for (var i= 0, n= list.length; i<n; i++)
        array[i]= list[i];
    return array;
}


// Lightweight class/instances. Non-standard.
//
Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw 'Constructor function requires new operator';
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    if (this!==Object) {
        Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
        Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    }
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};


// Selectors-API-style class name finder
//
function Node_getElementsByClassName(node, classnames, taghint) {
    if ('getElementsByClassName' in node)
        return node.getElementsByClassName(classnames);

    var exps= classnames.split(/\s+/).map(function(name) {
        name= name.replace(/([\/\\\^$*+?.()|\[\]{}])/g, '\\$1');
        return new RegExp('(^|\\s)'+name+'(\\s|$)');
    });
    var els= node.getElementsByTagName(taghint || '*');
    var matches= [];
    for (var i= 0, n= els.length; i<n; i++) {
        var el= els[i];
        if (exps.every(function(exp) {
            return exp.test(el.className);
        }))
            matches.push(el);
    }
    return matches;
}

// ElementTraversal spec methods
//
function Element_nextElementSibling(element) {
    if ('nextElementSibling' in element)
        return element.nextElementSibling;
    do
        element= element.nextSibling;
    while (element!==null && element.nodeType!==1);
    return element;
}
function Element_previousElementSibling(element) {
    if ('previousElementSibling' in element)
        return element.previousElementSibling;
    do
        element= element.previousSibling;
    while (element!==null && element.nodeType!==1);
    return element;
}
function Element_firstElementChild(element) {
    if ('firstElementChild' in element)
        return element.firstElementChild;
    var child= element.firstChild;
    while (child!==null && child.nodeType!==1)
        child= child.nextSibling;
    return child;
}
function Element_lastElementChild(element) {
    if ('lastElementChild' in element)
        return element.lastElementChild;
    var child= element.lastChild;
    while (child!==null && child.nodeType!==1)
        child= child.previousSibling;
    return child;
}
function Element_childElementCount(element) {
    if ('childElementCount' in element)
        return element.childElementCount;
    var count= 0;
    for (var i= element.childNodes.length; i-->0;)
        if (element.childNodes[i].nodeType===1)
            count++;
    return count;
}


// HTML5 classList-style methods
//
var mod_DOMTokenList= Object.makeSubclass();
mod_DOMTokenList.prototype._init= function(element) {
    this.element= element;
};
mod_DOMTokenList.prototype.item= function(ix) {
    return this.element.className.trim().split(/\s+/)[ix];
};
mod_DOMTokenList.prototype.contains= function(name) {
    var classes= this.element.className.trim().split(/\s+/);
    return classes.indexOf(name)!==-1;
};
mod_DOMTokenList.prototype.add= function(name) {
    var classes= this.element.className.trim().split(/\s+/);
    if (classes.indexOf(name)===-1) {
        classes.push(name);
        this.element.className= classes.join(' ');
    }
};
mod_DOMTokenList.prototype.remove= function(name) {
    var classes= this.element.className.trim().split(/\s+/);
    var ix= classes.indexOf(name);
    if (ix!==-1) {
        classes.splice(ix, 1);
        this.element.className= classes.join(' ');
    }
};
mod_DOMTokenList.prototype.toggle= function(name) {
    var classes= this.element.className.trim().split(/\s+/);
    var ix= classes.indexOf(name);
    if (ix!==-1)
        classes.splice(ix, 1);
    else
        classes.push(name);
    this.element.className= classes.join(' ');
};

function Element_classList(element) {
    return 'classList' in element? element.classList : new mod_DOMTokenList(element);
}

// Use classNames to receive arbitrary data
//
function Element_getClassArgument(element, name) {
    var classes= element.className.trim().split(/\s+/);
    for (var i= 0, n=classes.length; i<n; i++)
        if (classes[i].startsWith(name+'-'))
            return decodeURIComponent(classes[i].substring(name.length+1));
    return null;
}


// Event binding with IE compatibility, and decoupling layer to prevent IE6-7
// memory leaks
//
function EventTarget_addEventListener(target, event, listener) {
    if ('addEventListener' in target) {
        target.addEventListener(event, listener, false);
    } else if ('attachEvent' in target) {
        target.attachEvent('on'+event, listener.bind(target));
    }
}

// ECMA262-5 JSON.parse (non-compliant fallback to fast eval with no callback)
//
function JSON_parse(s) {
    if ('JSON' in window)
        return JSON.parse(s);
    return eval('('+s+')');
}

// XMLHttpRequest fallback for IE6
// Note, cannot use `in` test for XMLHttpRequest because when native XHR is disabled
// in IE7+, the property is actually present but set to `null`.
//
if (!window.XMLHttpRequest && 'ActiveXObject' in window) {
    window.XMLHttpRequest= function() {
        return new ActiveXObject('MSXML2.XMLHttp');
    };
}

// Element creation convenience function
//
function Element_create(tag, attrs, content) {
    var el= document.createElement(tag);
    if (attrs!==undefined)
        for (var k in attrs)
            if (attrs.hasOwnProperty(k))
                el[k]= attrs[k];
    if (content!==undefined) {
        if (typeof(content)==='string')
            el.appendChild(document.createTextNode(content));
        else
            for (var i= 0; i<content.length; i++)
                el.appendChild(content[i]);
    }
    return el;
};

// Cookie IO. This uses its own date formatting, because the Cookie-spec date
// format does not quite match JavaScript's own.
//
function getCookie(name) {
    name= encodeURIComponent(name);
    var cookies= document.cookie.split(';');
    for (var i= cookies.length; i-->0;)
        if (cookies[i].trim().startsWith(name+'='))
            return decodeURIComponent(cookies[i].trim().substring(name.length+1));
    return null;
}

function setCookie(name, value /* opt */, duration /* opt */) {
    var expires= '';
    if (value===undefined) {
        value= '_';
        expires= '; expires=Sat, 01-Jan-2000 00:00:00 GMT';
    } else if (duration!==undefined) {
        var x= new Date(new Date().getTime()+duration);
        var wn= ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][x.getUTCDay()];
        var mn= ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][x.getUTCMonth()];
        var d= (''+x.getUTCDate()).padLeft(2, '0');
        var y= (''+x.getUTCFullYear()).padLeft(4, '0');
        var h= (''+x.getUTCHours()).padLeft(2, '0');
        var m= (''+x.getUTCMinutes()).padLeft(2, '0');
        var s= (''+x.getUTCSeconds()).padLeft(2, '0');
        expires= '; expires='+wn+', '+d+'-'+mn+'-'+y+' '+h+':'+m+':'+s+' GMT';
    }
    document.cookie= encodeURIComponent(name)+'='+encodeURIComponent(value)+'; path=/'+expires;
}


// Abstract base for looped animations that can be stopped and started
//
var PeriodicAnimation= Object.makeSubclass();
PeriodicAnimation.prototype._init= function(period) {
    this.period= period;
    this.interval= null;
    this.epoch= 0;
    this.paused= null;
};
PeriodicAnimation.prototype.start= function() {
    if (this.interval===null) {
        this.interval= window.setInterval(this.animate.bind(this), 100);
        if (this.paused===null)
            this.epoch= new Date().getTime();
        else
            this.epoch+= new Date().getTime()-this.paused;
    }
};
PeriodicAnimation.prototype.stop= function() {
    if (this.interval!==null) {
        window.clearInterval(this.interval);
        this.interval= null;
        this.paused= new Date().getTime();
    }
};
PeriodicAnimation.prototype.animate= function() {
    var t= new Date().getTime()-this.epoch;
    this.set(t/this.period%1);
};
PeriodicAnimation.prototype.set= function() {};


// Abstract base for animated linear sliding switch between 0 and 1
//
var SwitchAnimation= Object.makeSubclass();
SwitchAnimation.prototype._init= function(period, initial) {
    this.period= period;
    this.interval= null;
    this.aim= initial || 0;
    this.t= 0;
}
SwitchAnimation.prototype.set= function(aim) {
    if (aim===this.aim)
        return;
    this.aim= aim;
    var now= new Date().getTime();
    if (this.interval===null) {
        this.t= now;
        this.interval= window.setInterval(this.update.bind(this), 50);
    } else {
        this.t= now-this.t-this.period+now
        this.update();
    }
};
SwitchAnimation.prototype.update= function() {
    var now= new Date().getTime();
    var x= Math.min((now-this.t)/this.period, 1);
    this.show(this.aim===0? 1-x : x);
    if (x===1) {
        window.clearInterval(this.interval);
        this.interval= null;
    }
};
SwitchAnimation.prototype.show= function() {};


