// Classes. Define a new class with var C= Object.subclass(),
// add class members to C.prototype,
// provide optional C.prototype._init() method to initialise from constructor args,
// call base class methods using Base.prototype.call(this, ...).
//
Function.prototype.subclass= function() {
    var c= new Function(
        'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+
        'if (arguments[0]!==Function.prototype.subclass._FLAG && this._init) this._init.apply(this, arguments); '
    );
    if (this!==Object)
        c.prototype= new this(Function.prototype.subclass._FLAG);
    return c;
};
Function.prototype.subclass._FLAG= {};


// Add ECMA5 method binding if not supported natively.
// This uses a closure so will leak memory in IE6 if bound methods are attached
// to DOM objects - consider adding lookup map for this case in future.
//
if (!Function.prototype.bind) {
    Function.prototype.bind= function(owner/* {, args} */) {
        var method= this;
        var args= Array_fromSequence(arguments).slice(1);
        return function() {
            var allargs= args.length==0? arguments : arguments.length==0? args : Array_fromSequence(args, arguments);
            return method.apply(owner, allargs);
        };
    };
}

function Function_return(value) {
    return function() { return value; }
};

// Add some ECMA5 array features if not supported natively
//
if (![].indexOf) {
    Array.prototype.indexOf= function(find) {
        for (var i= 0; i<this.length; i++)
            if (this[i]===find)
                return i;
        return -1;
    };
}
if (![].map) {
    Array.prototype.map= function(f, that) {
        var a= new Array(this.length);
        for (var i= 0; i<this.length; i++) if (this[i]!==undefined)
            a[i]= f.call(that, this[i], i, this);
        return a
    };
}
if (![].filter) {
    Array.prototype.filter= function(f, that) {
        var a= [];
        for (var i= 0; i<this.length; i++) if (this[i]!==undefined)
            if (f.call(that, this[i], i, this))
                a.push(this[i]);
        return a
    };
}
if (![].some) {
    Array.prototype.some= function(f, that) {
        for (var i= 0; i<this.length; i++) if (this[i]!==undefined)
            if (f.call(that, this[i], i, this))
                return true
        return false;
    };
}
if (![].every) {
    Array.prototype.every= function(f, that) {
        for (var i= 0; i<this.length; i++) if (this[i]!==undefined)
            if (!f.call(that, this[i], i, this))
                return false
        return true;
    };
}

// Compile an Array from one or more Array-like sequences (arguments, NodeList...)
//
function Array_fromSequence() {
    var arr= [];
    for (var argi= 0; argi<arguments.length; argi++)
        for (var itemi= 0; itemi<arguments[argi].length; itemi++)
            arr.push(arguments[argi][itemi]);
    return arr;
}


// Add Mozilla/WHAT getElementsByClassName method if not supported natively.
// Optionally pass in a tagname that all objects of that class will be, for efficiency; this
// is not used by the native getElementsByClassName so don't rely on it.
//
if (!document.getElementsByClassName) {
    document.getElementsByClassName= function(classnames, taghint) {
        if (taghint===undefined)
            taghint= '*';
        var classes= classnames.split(' ');
        var elements= Array_fromSequence(document.getElementsByTagName(taghint));
        return elements.filter(function(element) {
            var elcls= element.className.split(' ');
            return classes.every(function(cls) {
                return elcls.indexOf(cls)!=-1;
            });
        });
    };
}


// Add mock JSON parser if not supported natively
//
if (!window.JSON) {
    var JSON= {};
    JSON.parse= function(s) {
        return eval('('+s+')');
    }
}

// Add JavaScript 1.8 trimming if not supported natively (or by another library)
//
String.prototype.trim= function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
};

// Not added to prototype as might clash with Prototype/MSAJAX
// libraries, which do not support multiple prefix/suffix arguments.
//
function String_startsWith(s) {
    for (var i= arguments.length; i-->1;)
        if (s.substring(0, arguments[i].length)==arguments[i])
            return true;
    return false;
}
function String_endsWith(s) {
    for (var i= arguments.length; i-->1;)
        if (s.substring(s.length-arguments[i].length)==arguments[i])
            return true;
    return false;
}


// DOM Utilities

// Space-separated class name handling
//
function Element_hasClass(element, classname) {
    return element.className.split(' ').indexOf(classname)!=-1;
}
function Element_setClass(element, classname, active) {
    var classes= element.className.split(' ');
    var ix= classes.indexOf(classname);
    if ((ix!=-1)==active)
        return;
    if (active)
        classes.push(classname);
    else
        classes.splice(ix, 1);
    element.className= classes.join(' ');
}

function Element_getClassArg(el, classname) {
    var prefix= classname+'-';
    var classes= el.className.split(' ');
    for (var i= classes.length; i-->0;)
        if (classes[i].substring(0, prefix.length)==prefix)
            return classes[i].substring(prefix.length);
    return null;
}


// Remove all content in a node
//
function Node_clearChildren(node) {
    while (node.firstChild)
        node.removeChild(node.firstChild);
}


// IE fixes
//
var IE= false;
var IE7= false;
/*@cc_on
@if (@_win32)
    IE= true;
@end @*/
var IE7= IE && document.documentElement.style.maxHeight!==window.undefined;
var IE8= IE && document.documentMode>=8;

// IE7 is deranged and doesn't let site use window.prompt() any more.
// Simulate its action, returning to a supplied callback when done.
//
var ErsatzPrompt= Object.subclass();
ErsatzPrompt.prototype._init= function(question, initial) {
    this.question= question;
    this.initial= initial;
};
ErsatzPrompt.prototype.callback= function(callback) {
    this.callback= callback;
    if (!IE7) return this.callback(window.prompt(this.question));

    this.shade= document.createElement('div');
    this.shade.style.position= 'fixed'; this.shade.style.zIndex= '100';
    this.shade.style.left= '0'; this.shade.style.top= '0';
    this.shade.style.width= '100%'; this.shade.style.height= '100%';
    this.shade.style.backgroundColor= 'black'; this.shade.style.filter= 'alpha(opacity=50)';
    this.form= document.createElement('form');
    this.form.style.position= 'fixed'; this.form.style.zIndex= '101';
    this.form.style.left= '30%'; this.form.style.top= '25%';
    this.form.style.width= '40%'; this.form.style.backgroundColor= 'ThreeDFace';
    this.form.style.border= 'outset white 2px'; this.form.style.padding= '8px 14px 8px 8px';
    this.form.onsubmit= this.submitted.bind(this);

    var heading= document.createElement('p');
    heading.style.color= 'ButtonText';
    heading.appendChild(document.createTextNode(this.question));
    this.form.appendChild(heading);
    this.form.appendChild(document.createElement('p'));
    this.field= document.createElement('input');
    this.field.type= 'text'; this.field.value= this.initial;
    this.field.style.width= '100%';
    this.form.lastChild.appendChild(this.field);
    this.form.appendChild(document.createElement('p'));
    var button= document.createElement('input');
    button.type= 'submit'; button.className='default'; button.value= 'OK';
    this.form.lastChild.appendChild(button);
    var button= document.createElement('input');
    button.type= 'submit'; button.value= 'Cancel'; button.style.marginLeft= '8px';
    button.onclick= this.cancelled.bind(this);
    this.form.lastChild.appendChild(button);

    document.body.insertBefore(this.form, document.body.firstChild);
    document.body.insertBefore(this.shade, document.body.firstChild);
    if (this.field.createTextRange!==window.undefined) {
        var range= this.field.createTextRange();
        range.setEndPoint('StartToEnd', range);
        range.select();
    } else {
        this.field.focus();
    }
};
ErsatzPrompt.prototype.submitted= function() {
    var response= this.field.value;
    this.form.parentNode.removeChild(this.form);
    this.shade.parentNode.removeChild(this.shade);
    this.callback(response);
    return false;
};
ErsatzPrompt.prototype.cancelled= function() {
    this.form.parentNode.removeChild(this.form);
    this.shade.parentNode.removeChild(this.shade);
    this.callback(null);
    return false;
}


// AJAX utilities ____________________________________________________________

// Get an object implementing XMLHttpRequest
//
function xmlhttp_make() {
    // IE7, Safari 1.2, Mozillae: use the XMLHttpRequest constructor.
    //
    if (window.XMLHttpRequest) {
        var request= new XMLHttpRequest();
        return request;
    }
    // IE<7/Win: try to get an XmlHttp ActiveX object. May fail due to security
    // settings etc.
    //
    if (window.ActiveXObject) {
        try {
            return new ActiveXObject('MSXML2.XmlHttp');
        } catch (e) {}
    }
    return null;
}


// Put a form-urlencoded query string together
//
function xmlhttp_query(pars) {
    if (!pars) return '';
  var query= '';
  for (par in pars) {
    if (query!='') query+= '&';
    query+= encodeURIComponent(par)+'='+encodeURIComponent(pars[par]);
  }
  return query;
}


// XMLHttpRequest asynchronous request helper object
// Create or subclass, set oncomplete and/or oncancel and call open(post, path, pars, timeout)
//
var AsyncRequest= Object.subclass();
AsyncRequest.prototype.req= AsyncRequest.prototype.timer= null;
AsyncRequest.prototype.open= function(post, path, pars, timeout) {
    this.req= xmlhttp_make();
    if (this.req==null) {
        setTimeout(this.timeout.bind(this), 0);
        return false;
    }
    this.req.onreadystatechange= this.onchange.bind(this);
    var query= xmlhttp_query(pars);
    this.req.open('POST', path, true);
    this.req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    this.req.send(query);
    this.timer= setTimeout(this.timeout.bind(this), timeout);
    return true;
};
AsyncRequest.prototype.close= function() {
    if (this.timer!=null) clearTimeout(this.timer);
    if (this.req!=null) {
        this.req.onreadystatechange= this.nothing;
        this.req.abort();
        this.req= null;
    }
};
AsyncRequest.prototype.nothing= Function_return(false);

AsyncRequest.prototype.onchange= function() {
    if (this.req.readyState!=4) return;
    if (this.timer!=null) clearTimeout(this.timer);
    var result= this.req.responseText;
    if (String_startsWith(result, '/*') && String_endsWith(result, '*/')) {
        result= JSON.parse(result.substring(2, result.length-2));
        this.oncomplete(result);
    } else {
        this.timeout();
    }
};
AsyncRequest.prototype.timeout= function() {
    this.timer= null;
    this.close();
    if (this.oncancel!=null) this.oncancel();
};

AsyncRequest.prototype.oncomplete= AsyncRequest.prototype.oncancel= null;

