/**
 * We assume JavaScript 1.2 compatability.  If you're using a browser that only
 * supports JavaScript 1.1 or 1.0, then you need to upgrade.  Seriously.  You've
 * had enough time.
 */

////////////////////////////////////////////////////////////////////////////////
// Constants
////////////////////////////////////////////////////////////////////////////////

var __CFINIT_ERROR_ARRAY_POP = "can't pop element from empty array";
var __CFINIT_ERROR_ARRAY_REMOVE = "element is not contained in array";
var __CFINIT_ERROR_DATE_FORMAT_STRING = "bad date format string";
var __CFINIT_ERROR_DATE_STRING = "date string does not match format string";
var __CFINIT_ERROR_DATE_YEAR = "can't set year";
var __CFINIT_ERROR_NUMBER_HEX = "invalid hexadecimal digit string";

var __CFINIT_STRING_DECIMAL_DIGITS = "0123456789";
var __CFINIT_STRING_HEXADECIMAL_DIGITS = "0123456789abcdefABCDEF";
var __CFINIT_STRING_LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
var __CFINIT_STRING_UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var __CFINIT_STRING_ALPHANUMERIC = __CFINIT_STRING_LOWERCASE +
                                   __CFINIT_STRING_UPPERCASE +
                                   __CFINIT_STRING_DECIMAL_DIGITS;

var __CFINIT_WARNING_ALL = "no getElementById(); using document.all";
var __CFINIT_WARNING_LAYERS = "no getElementById(); using document.layers";
var __CFINIT_WARNING_UNDEFINED = "no getElementById() and no substitute found";

////////////////////////////////////////////////////////////////////////////////
// Classes
////////////////////////////////////////////////////////////////////////////////

// Array

Array.fromArguments = function(args)
{
    var a = new Array();
    for (var i = 0; i < args.length; i++) {
        a.push(args[i]);
    }
    return a;
}

Array.fromString = function(s)
{
    return s.split('');
}

var prototype = Array.prototype;

function __cfInitArrayEvery(func, thisObj)
{
    var length = this.length;
    for (var i = 0; i < length; i++) {
        if (! func.call(thisObj, this[i], i, this)) {
            return false;
        }
    }
    return true;
}

function __cfInitArrayFilter(func, thisObj)
{
    var length = this.length;
    var result = new Array();
    for (var i = 0; i < length; i++) {
        var element = this[i];
        if (func.call(thisObj, element, i, this)) {
            result.push(element);
        }
    }
    return result;
}

function __cfInitArrayForEach(func, thisObj)
{
    this.map(func, thisObj);
}

function __cfInitArrayIndexOf(element, index)
{
    var length = this.length;
    if (arguments.length < 2) {
        index = 0;
    } else if (index < 0) {
        index += length;
        if (index < 0) {
            index = 0;
        }
    }
    for (; index < length; index++) {
        if (element === this[index]) {
            return index;
        }
    }
    return -1;
}

function __cfInitArrayLastIndexOf(element, index)
{
    var length = this.length;
    if (arguments.length < 2) {
        index = length - 1;
    } else if (index < 0) {
        index += length;
    }
    for (; index >= 0; index--) {
        if (element === this[index]) {
            return index;
        }
    }
    return -1;
}

function __cfInitArrayMap(func, thisObj)
{
    var length = this.length;
    var result = new Array();
    for (var i = 0; i < length; i++) {
        result.push(func.call(thisObj, this[i], i, this));
    }
    return result;
}

function __cfInitArraySome(func, thisObj)
{
    var length = this.length;
    for (var i = 0; i < length; i++) {
        if (func.call(thisObj, this[i], i, this)) {
            return true;
        }
    }
    return false;
}

if (! prototype.every) {
    prototype.every = __cfInitArrayEvery;
}
if (! prototype.filter) {
    prototype.filter = __cfInitArrayFilter;
}
if (! prototype.forEach) {
    prototype.forEach = __cfInitArrayForEach;
}
if (! prototype.indexOf) {
    prototype.indexOf = __cfInitArrayIndexOf;
}
if (! prototype.lastIndexOf) {
    prototype.lastIndexOf = __cfInitArrayLastIndexOf;
}
if (! prototype.map) {
    prototype.map = __cfInitArrayMap;
}
if (! prototype.some) {
    prototype.some = __cfInitArraySome;
}

prototype.contains = function(element)
{
    return this.indexOf(element) != -1;
}

prototype.copy = function()
{
    var result = new Array();
    result.extend(this);
    return result;
}

prototype.count = function(element)
{
    var n = 0;
    for (var i = 0; i < this.length; i++) {
        if (this[i] === element) {
            n++;
        }
    }
    return n;
}

prototype.discard = function(element)
{
    var pos = this.indexOf(element);
    var result = (pos != -1);
    if (result) {
        this.splice(pos, 1);
    }
    return result;
}

prototype.discardAll = function(element)
{
    while (this.discard(element));
}

prototype.extend = function(a)
{
    this.push.apply(this, a);
}

prototype.insert = function(index, element)
{
    this.splice(index, 0, element);
}

prototype.remove = function(element)
{
    if (! this.discard(element)) {
        return cfErrorTrigger("Array::remove: '" + element + "': " +
                              __CFINIT_ERROR_ARRAY_REMOVE);
    }
}

// Date

Date.fromFormattedString = function(dateString, formatString, ignoreDateError)
{

    function triggerDateError()
    {
        if (ignoreDateError) {
            return undefined;
        }
        return cfErrorTrigger("Date::fromFormattedString: '" + dateString +
                              "': '" + formatString + "': " +
                              __CFINIT_ERROR_DATE_STRING);
    }

    function triggerFormatError()
    {
        return cfErrorTrigger("Date::fromFormattedString: '" + formatString +
                              "': " + __CFINIT_ERROR_DATE_FORMAT_STRING);
    }

    dateString = dateString.strip();
    var date = new Date();
    var dateChars = dateString.split('');
    var day = date.getDate();
    var formatChars = formatString.split('');
    var hour = date.getHours();
    var minute = date.getMinutes();
    var month = date.getMonth();
    var period = undefined;
    var second = date.getSeconds();
    var usingSubHours = false;
    var year = date.getYear();
    while (formatChars.length) {
        var c = formatChars.splice(0, 1)[0];
        if (c.isWhiteSpace()) {
            while (dateChars.length && dateChars[0].isWhiteSpace()) {
                dateChars.splice(0, 1);
            }
            continue;
        }
        if (c != '%') {
            if (c != dateChars.splice(0, 1)[0]) {
                return triggerDateError();
            }
            continue;
        }
        if (! formatChars.length) {
            return triggerFormatError();
        }
        c = formatChars.splice(0, 1)[0];
        switch (c) {
        case 'd':
            day = __cfInitParseArrayDigits(dateChars, 2);
            if (typeof(day) == "undefined") {
                return triggerDateError();
            }
            break;
        case 'H':
            hour = __cfInitParseArrayDigits(dateChars, 2);
            if (typeof(hour) == "undefined") {
                return triggerDateError();
            }
            usingSubHours = false;
            break;
        case 'I':
            hour = __cfInitParseArrayDigits(dateChars, 2);
            if ((typeof(hour) == "undefined") || (hour < 1) || (hour > 12)) {
                return triggerDateError();
            }
            usingSubHours = true;
            break;
        case 'm':
            month = __cfInitParseArrayDigits(dateChars, 2);
            if (typeof(month) == "undefined") {
                return triggerDateError();
            }
            month -= 1;
            break;
        case 'M':
            minute = __cfInitParseArrayDigits(dateChars, 2);
            if (typeof(minute) == "undefined") {
                return triggerDateError();
            }
            break;
        case 'p':
            if ((dateChars.length < 2) || (dateChars[1].toLowerCase() != 'm')) {
                return triggerDateError();
            }
            switch (dateChars[0].toLowerCase()) {
            case 'a':
                period = "AM";
                break;
            case 'p':
                period = "PM";
                break;
            default:
                return triggerDateError();
            }
            dateChars.splice(0, 2);
            break;
        case 'S':
            second = __cfInitParseArrayDigits(dateChars, 2);
            if (typeof(second) == "undefined") {
                return triggerDateError();
            }
            break;
        case 'y':
            year = __cfInitParseArrayDigits(dateChars, 2);
            if ((typeof(year) == "undefined") || (year < 0) || (year > 99)) {
                return triggerDateError();
            }
            year += (year < 38) ? 2000 : 1900;
            break;
        case 'Y':
            year = __cfInitParseArrayDigits(dateChars, 6);
            if (typeof(year) == "undefined") {
                return triggerDateError();
            }
            break;
        case '%':
            if (dateString.splice(0, 1) != '%') {
                return triggerDateError();
            }
            break;
        default:
            return triggerFormatError();
        }
    }
    if (dateChars.join('').length) {
        return triggerDateError();
    }
    if (usingSubHours) {
        if (typeof(period) == "undefined") {
            return triggerFormatError();
        }
        hour %= 12;
        if (period == "PM") {
            hour += 12;
        }
    }
    return new Date(year, month, day, hour, minute, second);
}

function __cfInitDateGetFullYear()
{
    var year = this.getYear() % 100;
    return year + ((year < 38) ? 2000 : 1900);
}

function __cfInitDateSetFullYear(year)
{
    if ((year >= 0) && (year <= 99)) {
        // If (0 <= year <= 99), then setYear sets the year to (1900 + year).
        return cfErrorTrigger("__cfInitDateSetFullYear: '" + year + "': " +
                              __CFINIT_ERROR_DATE_YEAR);
    }
    this.setYear(year);
}

prototype = Date.prototype;
if (! prototype.getFullYear) {
    prototype.getFullYear = __cfInitDateGetFullYear;
}
if (! prototype.setFullYear) {
    prototype.setFullYear = __cfInitDateSetFullYear;
}

prototype.addDays = function(n)
{
    this.setDate(this.getDate() + n);
}

prototype.addHours = function(n)
{
    this.setHours(this.getHours() + n);
}

prototype.addMinutes = function(n)
{
    this.setMinutes(this.getMinutes() + n);
}

prototype.addMonths = function(n)
{
    var month = this.getMonth() + n;
    this.setMonth(month);
    if (this.getMonth() != month) {
        this.setDate(0);
    }
}

prototype.addSeconds = function(n)
{
    this.setSeconds(this.getSeconds() + n);
}

prototype.addYears = function(n)
{
    this.setFullYear(this.getFullYear() + n);
}

prototype.compare = function(d)
{
    return this.getTime() - d.getTime();
}

prototype.isEqual = function(d)
{
    return this.compare(d) == 0;
}

prototype.isGreaterThan = function(d)
{
    return this.compare(d) > 0;
}

prototype.isGreaterThanOrEqual = function(d)
{
    return this.compare(d) >= 0;
}

prototype.isLessThan = function(d)
{
    return this.compare(d) < 0;
}

prototype.isLessThanOrEqual = function(d)
{
    return this.compare(d) <= 0;
}

prototype.toFormattedString = function(s)
{
    var lastCharPos = s.length - 1;
    var newPos;
    var result = new Array();
    for (var pos = 0; pos <= lastCharPos; pos = newPos + 2) {
        newPos = s.indexOf('%', pos);
        if (newPos == lastCharPos) {
            return cfErrorTrigger("Date::toFormattedString: '" + s + "': " +
                                  __CFINIT_ERROR_DATE_FORMAT_STRING);
        }
        if (newPos == -1) {
            result.push(s.substring(pos));
            break;
        }
        result.push(s.substring(pos, newPos));
        var formatPart;
        switch (s.charAt(newPos + 1)) {
        case 'c':
            formatPart = this.toLocaleString();
            break;
        case 'd':
            formatPart = this.getDate().toString().padLeft('0', 2);
            break;
        case 'H':
            formatPart = this.getHour().toString().padLeft('0', 2);
            break;
        case 'I':
            formatPart = ((this.getHours() % 12) || 12).toString();
            break;
        case 'm':
            formatPart = (this.getMonth() + 1).toString().padLeft('0', 2);
            break;
        case 'M':
            formatPart = this.getMinutes().toString().padLeft('0', 2);
            break;
        case 'p':
            formatPart = (this.getHours() < 12) ? "AM" : "PM";
            break;
        case 'S':
            formatPart = this.getSeconds().toString().padLeft('0', 2);
            break;
        case 'w':
            formatPart = this.getDay().toString();
            break;
        case 'x':
            formatPart = this.getLocateDateString();
            break;
        case 'X':
            formatPart = this.getLocateTimeString();
            break;
        case 'y':
            formatPart = (this.getFullYear() % 100).toString().padLeft('0', 2);
            break;
        case 'Y':
            formatPart = this.getFullYear().toString();
            break;
        case '%':
            formatPart = '%';
            break;
        default:
            return cfErrorTrigger("Date::toFormattedString: '" + s + "': " +
                                  __CFINIT_ERROR_DATE_FORMAT_STRING);
        }
        result.push(formatPart);
    }
    return result.join('');
}

// document

function __cfInitDocumentGetElementByIdAll(id)
{
    return document.all[id];
}

function __cfInitDocumentGetElementByIdFindLayer(obj, id)
{
    var layers = obj.layers;
    for (var i = 0; i < layers.length; i++) {
        var layer = layers[i];
        if (layer.id == id) {
            return layer;
        }
        layer = __cfInitFindLayer(layer, id);
        if (layer) {
            return layer;
        }
    }
    return undefined;
}

function __cfInitDocumentGetElementByIdLayers(id)
{
    var layer = __cfInitDocumentGetElementByIdFindLayer(document, id);
    if ((layer) && (! layer.style)) {
        layer.style = layer;
    }
    return layer;
}

function __cfInitDocumentGetElementByIdUndefined(id)
{
    return undefined;
}

if (! document.getElementById) {
    if (document.all) {
        document.getElementById = __cfInitDocumentGetElementByIdAll;
        cfWarningTrigger(__CFINIT_WARNING_ALL);
    } else if (document.layers) {
        document.getElementById = __cfInitDocumentGetElementByIdLayers;
        cfWarningTrigger(__CFINIT_WARNING_LAYERS);
    } else {
        document.getElementById = __cfInitDocumentGetElementByIdUndefined;
        cfWarningTrigger(__CFINIT_WARNING_UNDEFINED);
    }
}

// Function

function __cfInitFunctionApply(obj, args)
{
    argStrings = new Array();
    if (args) {
        for (var i = 0; i < args.length; i++) {
            argStrings.push("args[" + i + "]");
        }
    }
    var evalStart;
    var useApply = typeof(obj) != "undefined";
    if (useApply) {
        obj.__apply__ = this;
        evalStart = "obj.__apply__(";
    } else {
        evalStart = "this(";
    }
    var result = eval(evalStart + argStrings.join(", ") + ')');
    if (useApply) {
        obj.__apply__ = undefined;
    }
    return result;
}

function __cfInitFunctionCall(obj)
{
    return this.apply(obj, arguments);
}

prototype = Function.prototype;
if (! prototype.apply) {
    prototype.apply = __cfInitFunctionApply;
}
if (! prototype.call) {
    prototype.call = __cfInitFunctionCall;
}

prototype.bind = function()
{
    var a = Array.fromArguments(arguments);
    var f = this;
    var t = a.shift();
    return function()
    {
        return f.apply(t, a.concat(Array.fromArguments(arguments)));
    };
}

prototype.bindMethod = function()
{
    var a = Array.fromArguments(arguments);
    var f = this;
    return function()
    {
        return f.apply(this, a.concat(Array.fromArguments(arguments)));
    };
}

prototype.extendClasses = function()
{
    var subPrototype = this.prototype;
    for (var i = arguments.length - 1; i >= 0; i--) {
        var superPrototype = arguments[i].prototype;
        for (var name in superPrototype) {
            subPrototype[name] = superPrototype[name];
        }
    }
}

prototype.getClassKeys = function()
{
    var names = new Array();
    for (var name in this.prototype) {
        names.push(name);
    }
    return names;
}

// Number

Number.fromHexadecimalDigits = function(hex)
{
    var result = new Number("0x" + hex);
    if (result === Number.NaN) {
        return cfErrorTrigger("Number::fromHexadecimalDigits: '" + hex +
                              "': " + __CFINIT_ERROR_NUMBER_HEX);
    }
    return result;
}

prototype = Number.prototype;

prototype.getBase = function()
{
    return parseInt(this, 10);
}

prototype.getFraction = function()
{
    return this - this.getBase();
}

// String

String.ALPHANUMERIC = __CFINIT_STRING_ALPHANUMERIC;
String.DIGITS = __CFINIT_STRING_DECIMAL_DIGITS;
String.HEXADECIMAL_DIGITS = __CFINIT_STRING_HEXADECIMAL_DIGITS;
String.LOWERCASE = __CFINIT_STRING_LOWERCASE;
String.UPPERCASE = __CFINIT_STRING_UPPERCASE;

prototype = String.prototype;

prototype.containsAllChars = function(s)
{
    for (var i = 0; i < s.length; i++) {
        if (this.indexOf(s.charAt(i)) == -1) {
            return false;
        }
    }
    return true;
}

prototype.count = function(s)
{
    return this.split(s).length - 1;
}

prototype.endsWith = function(s)
{
    return this.substring(this.length - s.length) == s;
}

prototype.isAlphanumeric = function()
{
    return String.ALPHANUMERIC.containsAllChars(this);
}

prototype.isDigits = function()
{
    return String.DIGITS.containsAllChars(this);
}

prototype.isHexadecimal = function()
{
    return String.HEXADECIMAL_DIGITS.containsAllChars(this);
}

prototype.isLowercase = function()
{
    return String.LOWERCASE.containsAllChars(this);
}

prototype.isWhiteSpace = function()
{
    return ! this.strip().length;
}

prototype.isUppercase = function()
{
    return String.UPPERCASE.containsAllChars(this);
}

prototype.padLeft = function(s, n)
{
    var result = this;
    while (result.length < n) {
        result = s + result;
    }
    return result;
}

prototype.padRight = function(s, n)
{
    var result = this;
    while (result.length < n) {
        result += s;
    }
    return result;
}

prototype.replaceHTML = function()
{
    return this.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;").
                replace('\"', "&quot;");
}

prototype.reverse = function()
{
    var a = Array.fromString(this);
    a.reverse();
    return a.join('');
}

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

prototype.strip = function()
{
    return this.replace(/^\s*|\s*$/g, '');
}

prototype.stripLeft = function()
{
    return this.replace(/^\s*/g, '');
}

prototype.stripRight = function()
{
    return this.replace(/\s*$/g, '');
}

prototype.toHTML = function()
{
    return this.replaceHTML().replace('\t', "        ").replace('\n', "<br />").
                replace("  ", " &nbsp;");
}

////////////////////////////////////////////////////////////////////////////////
// Private functions
////////////////////////////////////////////////////////////////////////////////

function __cfInitParseArrayDigits(a, n)
{
    if (! a.length) {
        return undefined;
    }
    var digits = new Array();
    while (a.length && (digits.length < n)) {
        var c = a[0];
        if (! c.isDigits()) {
            break;
        }
        digits.push(c);
        a.splice(0, 1);
    }
    var s = digits.join('');
    if (! s.length) {
        return undefined;
    }
    return new Number(s);
}
