/**
 * @fileoverview
 * Define misceleneous functions and classes.
 */

/**
 * Same as the prototype "$" function, but also asserts that the element
 * exists (mnemonic: get element by I-d).
 *
 * @param {String} id
 *  The id of the element to fetch.
 *
 * @return
 *  The DOM element with that id.
 *
 * @type Element
 */
function $I(id)
{{
    Claim.isString(id, "$I.id");
    var element = $(id);
    Claim.isObject(element, "Required HTML element id: " + id);
    return element;
}}

/**
 * Override the prototype "$F" function to always asser that the element
 * exists.
 *
 * @param {String} id
 *  The id of the form element to fetch.
 *
 * @return
 *  The user provided value of the form element.
 *
 * @type String
 */
function $F(id)
{{
    Claim.isString(id, "$F.id")
    Claim.isObject($(id), "Required form element id: " + id)
    return Form.Element.getValue(id)
}}

/**
 * Fetch the (first if there is more than one) element with a particular
 * tag. useful for things like $T("head"), $T("body") etc. (mnemonic: get
 * element by T-ag).
 *
 * @param {String} tagName
 *  The name of the tag of the element to fetch.
 *
 * @return
 *  The (first is there is more than one) DOM element with that tag name.
 *
 * @type Element
 */
function $T(tagName)
{{
    var element = document.getElementsByTagName(tagName).item(0)
    Claim.isObject(element, "Required HTML element tag: " + tagName)
    return element
}}

/**
 * Fetch the selected option element of a "select" form element
 * (mnemonic: get S-elected O-ption element).
 *
 * @param {String} id
 *  The id of the select form element to fetch.
 *
 * @return
 *  The currently selected option DOM element of the select element. May be
 *  undefined.
 *
 * @type Element/undefined
 */
function $SO(id)
{{
    return $I(id).options[$I(id).selectedIndex]
}}
/**
 * @fileoverview
 * Extend the built-in number object.
 */

/**
 * @class
 * Extend the built-in number object.
 * @constructor
 */
var Number_ = {}

/**
 * Convert number to a zero-padded string with a minimal width.
 *
 * @param {Number} base
 *  The base to use (usually 8, 10 or 16).
 *
 * @param {Number} width
 *  The minimal width of the string to return.
 *
 * @return
 *  A string representation of the number that is at least as wide as the
 *  minimal width (padded with "0" is necessary).
 *
 * @type String
 */
Number_.toText = function(base, width)
{{
    Claim.isNumber(base, "Number.toText.base")
    Claim.isTrue(base > 0, "Number.toText.base")
    Claim.isNumber(width, "Number.toText.width")
    Claim.isTrue(width >= 0, "Number.toText.width")
    var text = this.toString(base || 10) + ""
    while (text.length < width)
        text = "0" + text
    return text
}}

/**
 * Convert number to a zero-padded decimal string with a minimal width.
 *
 * @see #toText
 *
 * @param {Number} width
 *  The minimal width of the string to return.
 *
 * @return
 *  A string representation of the number that is at least as wide as the
 *  minimal width (padded with "0" is necessary).
 *
 * @type String
 */
Number_.toDec = function(width)
{{
    Claim.isNumber(width, "Number.toText.width")
    Claim.isTrue(width >= 0, "Number.toText.width")
    return this.toText(10, width)
}}

/**
 * Convert number to a zero-padded hexadecimal string with a minimal width.
 *
 * @see #toText
 *
 * @param {Number} width
 *  The minimal width of the string to return.
 *
 * @return
 *  A string representation of the number that is at least as wide as the
 *  minimal width (padded with "0" is necessary).
 *
 * @type String
 */
Number_.toHex = function(width)
{{
    Claim.isNumber(width, "width", "Number.toText.width")
    Claim.isTrue(width >= 0, "width", "Number.toText.width")
    return this.toText(16, width)
}}

Object.extend(Number.prototype, Number_)
/**
 * @fileoverview
 * Extend the built-in Date object.
 */

/**
 * @class
 * Extend the built-in Date object.
 * @constructor
 */
var Date_ = {}

/**
 * Attention!!! suffer from low performance.
 * Convert a date to a human readable (YAML) format. Used for {@link Log4Js}
 * events.
 *
 * @return
 *  Human readable (YAML) formatted date.
 *
 * @type String
 */
Date_.toText = function()
{{
    var year = this.getUTCFullYear().toDec(4)
    var month = this.getUTCMonth().toDec(2)
    var day = this.getUTCDate().toDec(2)
    var hours = this.getUTCHours().toDec(2)
    var minutes = this.getUTCMinutes().toDec(2)
    var seconds = this.getUTCSeconds().toDec(2)
    var milliseconds = this.getUTCMilliseconds().toDec(3)
    return year + "-" + month + "-" + day + " "
         + hours + ":" + minutes + ":" + seconds + "." + milliseconds
}}

Object.extend(Date.prototype, Date_)
/**
 * @fileoverview
 * Start collecting pre-load actions.
 */

/**
 * Manage pre-loading actions. These actions are registered by the separate
 * classes (each in its own source file) and then executed once all classes
 * have been defined. This allows avoiding nasty loops like Cookies and
 * Log4Js depending on each other.
 * @constructor
 */
var PreLoad = {}

/**
 * The first pre-load action done - creates the "PreLoad" logger.
 */
PreLoad.preLoad = function()
{{
    PreLoad.log = new Log4Js.Logger("PreLoad")
}}

/**
 * The first on-load action done - signify the end of the pre-load phase.
 */
PreLoad.onLoad = function()
{{
    PreLoad.log.debug("Done pre-load")
}}

/**
 * List of actions to perform. Just push anything you want at the end.
 * @type Array
 */
PreLoad.actions = []

/*
 * The rest of the sources use a similar way to register their actions.
 */
PreLoad.actions.push(PreLoad.preLoad)
Event.observe(window, "load", PreLoad.onLoad)
/**
 * @fileoverview
 * URL parsing and formatting.
 */

/**
 * @class
 * URL parsing and formatting.
 * @constructor
 */
var Url = {}

/**
 * Parse a string URL to components.
 *
 * @param {String} url
 *  The url to parse.
 *
 * @return
 *  A hash with the following keys:
 *  <dl compact>
 *  <dt>full</dt>
 *      <dd>The original full url.</dd>
 *  <dt>base</dt>
 *      <dd>The full url minus the query part (without the "?...").</dd>
 *  <dt>protocol</dt>
 *      <dd>Just the protocol name (usually "http", or "https").</dd>
 *  <dt>domain</dt>
 *      <dd>Just the domain name.</dd>
 *  <dt>path</dt>
 *      <dd>Just the file path within the domain.</dd>
 *  <dt>query</dt>
 *      <dd>The query part of the URL (without the leading "?").</dd>
 *  <dt>params</dt>
 *      <dd>A hash from parameter name to parameter value.</dd>
 *  </dl>
 *  Note no unescaping/unencoding is done.
 *
 * @type Hash
 */
Url.parse = function(url)
{{
    Claim.isString(url, "Url.parse.url")
    var parsed = {}
    parsed.full = url
    parsed.base = url.replace(/\?.*$/, '')
    parsed.protocol = url.replace(/:.*$/, '')
    parsed.domain =
        parsed.base.replace(/^[^:]*:\/[\/]/, '').replace(/[:\/].*$/, '')
    parsed.path = parsed.base.replace(/^[^\/]*\/\/[^\/]*[\/]/, '/')
    parsed.query = url.replace(/^[^?]*\??/, '')
    parsed.params =
        parsed.query.split("&").inject({}, function(params, paramValue)
    {
        pair = paramValue.split("=")
        params[pair[0]] = unescape(pair[1])
        return params
    })
    return parsed
}}

/**
 * Append a list of parameters to a URL. Note no escaping is done.
 *
 * @param {String} url
 *  The URL to append parameters to. It may or may not have a query part
 *  already.
 *
 * @param {Hash/String/Udefined/Null} params
 *  A hash where each key is a param name amd each value is its value; or a
 *  string of parameters seperated by "&" characters, but no leading "&" or "?"
 *  character, or undefined, or null. If a hash is given, parameters are added
 *  at an alphabetic order to help produce repeatable URLs for improved
 *  caching.
 *
 * @return
 *  The url with the additional parameters (if any), seperated by "&" or "?" as
 *  appropriate.
 *
 * @type String
 */
Url.appendParams = function(url, params)
{{
    Claim.isString(url, "Url.appendParams.url")
    if (!params || params == "")
        return url
    if (typeof(params) == "string")
       return url + (url.match(/\?/) ? "&" : "?") + params
    return $A($H(params).keys().sort()).inject(url, function(url, param)
    {
        return Url.appendParamValue(url, param, params[param])
    })
}}

/**
 * Append a single parameter value to a URL. Note no escaping is done.
 *
 * @param {String} url
 *  The URL to append parameters to. It may or may not have a query part
 *  already.
 *
 * @param {String} param
 *  The name of the parameter to append to the URL.
 *
 * @param {String} value
 *  The value of the parameter to append to the URL.
 *
 * @return
 *  The url with the additional parameter, seperated by "&" or "?" as
 *  appropriate.
 *
 * @type String
 */
Url.appendParamValue = function(url, param, value)
{{
    Claim.isString(url, "Url.appendParamValue.url")
    Claim.isString(param, "Url.appendParamValue.param")
    Claim.isScalar(value, "Url.appendParamValue.value")
    return url + (url.match(/\?/) ? "&" : "?") + escape(param) + "=" + escape(value)
}}

/**
 * Build a new URL relative to the current one.
 *
 * @param {Hash} options
 *  Values to build URL from. Valid options are:
 *  <dl compact>
 *  <dt>protocol</dt><dd>
 *      The protocol to use. If not specified, the current page protocol is
 *      used.
 *  </dd>
 *  <dt>domain</dt><dd>
 *      The domain to use. If not specified, the current page domain is used.
 *  </dd>
 *  <dt>path</dt><dd>
 *      The path to use. If not specified, the current page path is used. If a
 *      value starting with a "/" is given, it is used as-is. Otherwise it is
 *      assumed to be relative to the current page path, and may use ".." and
 *      "/" to access parent folders.
 *  </dd>
 *  <dt>withClearanceParams</dt><dd>
 *      If missing, the {@link Clearance#params} are added if the domain is
 *      different than the current one. Specifying this to true or false forces
 *      these parameters to be excluded/included even for the same/different
 *      domain.
 *  </dd>
 *  <dt>withHereParams</dt><dd>
 *      If specified and is true, the {@link #here}.params are added to the
 *      URL. Note this doesn't include the {@Link Clearance#params} even if
 *      such parameters are part of {@link #here}.full.
 *  </dd>
 *  <dt>params</dt><dd>
 *      Hash of additional parameters to add to the URL.
 *  </dd>
 *  </dl>
 *
 * @return {String}
 *  The URL build from the specified options.
 */
Url.relativeUrl = function(options)
{{
    Claim.isObject(options, "Url.relativeUrl.options")

    var protocol = options.protocol || Url.here.protocol
    Claim.isString(protocol, "Url.relativeUrl.options.protocol")

    var domain = options.domain || Url.here.domain
    Claim.isString(domain, "Url.relativeUrl.options.domain")

    var path = options.path || Url.here.path
    Claim.isString(path, "Url.relativeUrl.options.path")

    if (!path.match(/^[\/]/))
    {
        var base = Url.here.path
        path = "../" + path // TRICKY - treat file name as folder name
        while (path.match(/^\.\.[\/]/))
        {
            path = path.replace(/^\.\.[\/]/, "")
            base = base.replace(/\/[^\/]+$/, "")
        }
        path = base + "/" + path
    }

    var params = {}

    var withHereParams = options.withHereParams || false
    Claim.isBoolean(withHereParams,
                    "Url.relativeUrl.options.withHereParams")
    if (withHereParams)
        Object.extend(params, Url.here.params)

    var withClearanceParams = options.withClearanceParams
    if (withClearanceParams == undefined)
        withClearanceParams = domain != Url.here.domain
    Claim.isBoolean(withClearanceParams,
                    "Url.relativeUrl.options.withClearanceParams")
    if (withClearanceParams)
        Object.extend(params, Clearance.params)

    var optionsParams = options.params || {}
    Claim.isObject(optionsParams, "Url.relativeUrl.options.params")
    Object.extend(params, options.params || {})

    return Url.appendParams(protocol + ":/" + "/" + domain + path, params)
}}

/**
 * The parsed current document Url.
 */
Url.here = undefined

/**
 * Parse the current document URL and make it available in {@link
 * Url#here}.
 */
Url.preLoad = function()
{{
    Url.here = Url.parse(location.href)
}}

PreLoad.actions.push(Url.preLoad)
/**
 * @fileoverview
 * Yet another assertion framework for JavaScript... sigh. Can't use the JsUnit
 * one, don't want to use the JsAssertionUnit one, so... it isn't as if this is
 * a big deal. Just to avoid collision with the zillion and one assertion
 * packages out there, we name this "claim" - it is also consistent with the
 * way we do things in the C# code.
 */

/**
 * @class
 * Provide a set of assertion methods. A valid execution is expected never to
 * trigger any of these; if it does, there is a bug in the code, so the code
 * throws an exception. In addition, successful claims are logged as debug
 * events and failing claims as errors, so you can control logging using the
 * standard {@link Log4Js} mechanism using the "Claim" logger.
 * @constructor
 */
var Claim = {}

/**
 * @private
 * Check whether a condition holds and act accordingly.
 *
 * @param {Boolean} condition
 *  Condition to check.
 *
 * @param {String} claim
 *  The name of the claim method (for logging).
 *
 * @param {String} comment
 *  Optional comment (for logging).
 */
Claim.check = function(condition, claim, comment)
{{
    if (!comment)
        comment = claim
    else
        comment = claim + ": " + comment

    // Claim.log may be undefined in two cases. First, for a very brief
    // time in the pre-load while it is being created. Second, for obvious
    // reasons we need to disable claim logging during claim logging.
    var log = Claim.log
    Claim.log = undefined
    try
    {
        if (condition)
        {
            if (log)
                log.debug(comment)
        }
        else
        {
            if (log)
                log.error(comment)
            else
                alert(comment)
            throw comment
        }
    }
    finally
    {
        Claim.log = log
    }
}}

/**
 * The value and type of some object.
 *
 * @param object
 *  To convert to value and type.
 *
 * @return {String}
 *  Containing the type and value of the object.
 */
Claim.valueType = function(object)
{{
    if (object == undefined)
        return "undefined"
    if (object == null)
        return "null"
    return typeof(object) + "(" + object + ")"
}}

/**
 * Claim that a given condition holds.
 *
 * @param {Boolean} condition
 *  The condition that is claimed to be true.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isTrue = function(condition, comment)
{{
    Claim.check(condition,
                "isTrue(" + Claim.valueType(condition) + ")",
                comment)
}}

/**
 * Claim that a given condition does not hold.
 *
 * @param {Boolean} condition
 *  The condition that is claimed to be false.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isFalse = function(condition, comment)
{{
    Claim.check(!condition,
                "isFalse(" + Claim.valueType(condition) + ")",
                comment)
}}

/**
 * Claim that a given object is a null.
 *
 * @param object
 *  The object expected to be a null.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isNull = function(object, comment)
{{
    Claim.check(typeof(object) == null,
                "isNull(" + Claim.valueType(object) + ")",
                comment)
}}

/**
 * Claim that a given object is not a null.
 *
 * @param object
 *  The object expected not to be a null.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isNotNull = function(object, comment)
{{
    Claim.check(typeof(object) != null,
                "isNotNull(" + Claim.valueType(object) + ")",
                comment)
}}

/**
 * Claim that a given object is undefined.
 *
 * @param object
 *  The object expected to be undefined.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isUndefined = function(object, comment)
{{
    Claim.check(typeof(object) == undefined,
                "isUndefined(" + Claim.valueType(object) + ")",
                comment)
}}

/**
 * Claim that a given object is not undefined.
 *
 * @param object
 *  The object expected not to be undefined.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isNotUndefined = function(object, comment)
{{
    Claim.check(typeof(object) != undefined,
                "isNotUndefined(" + Claim.valueType(object) + ")",
                comment)
}}

/**
 * Claim that a given object is a real object - not null and not undefined.
 *
 * @param object
 *  The object expected to be a real object.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isObject = function(object, comment)
{{
    Claim.check(!!object,
                "isObject(" + Claim.valueType(object) + ")",
                comment)
}}

/**
 * Claim that a given object is a number.
 *
 * @param object
 *  The object expected to be a number.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isNumber = function(object, comment)
{{
    Claim.check(typeof(object) == "number",
                "isNumber(" + Claim.valueType(object) + ")",
                comment)
}}

/**
 * Claim that a given object is a boolean.
 *
 * @param object
 *  The object expected to be a boolean.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isBoolean = function(object, comment)
{{
    Claim.check(typeof(object) == "boolean",
                "isBoolean(" + Claim.valueType(object) + ")",
                comment)
}}

/**
 * Claim that a given object is a string.
 *
 * @param object
 *  The object expected to be a string.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isString = function(object, comment)
{{
    Claim.check(typeof(object) == "string",
                "isString(" + Claim.valueType(object) + ")",
                comment)
}}

/**
 * Claim that a given object is a real scalar - a string or a number or a
 * boolean.
 *
 * @param object
 *  The object expected to be a real scalar.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isScalar = function(object, comment)
{{
    Claim.check(typeof(object) == "string"
             || typeof(object) == "number"
             || typeof(object) == "boolean",
                "isScalar(" + Claim.valueType(object) + ")",
                comment)
}}

/**
 * Claim that a given object is an array.
 *
 * @param object
 *  The object expected to be an array.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.isArray = function(object, comment)
{{
    Claim.check(object && object.length != undefined,
                "isArray(" + Claim.valueType(object) + ")",
                comment)
}}

/**
 * Claim that two objects are equal.
 *
 * @param object1
 *  The first object to compare.
 *
 * @param object2
 *  The first object to compare.
 *
 * @param comment
 *  Optional comment for error message.
 */
Claim.areEqual = function(object1, object2, comment)
{{
    Claim.check(object1 == object2,
                "areEqual(" + Claim.valueType(object1)
              + " ? " + Claim.valueType(object2) + ")",
                comment)
}}

/*
 * @private
 * Sets up a "Claim" logger.
 */
Claim.preLoad = function()
{{
    Claim.log = new Log4Js.Logger("Claim")
}}

PreLoad.actions.push(Claim.preLoad)
/**
 * @fileoverview
 * Cookie manipulation.
 */

/**
 * @class
 * Cookie manipulation. Note that this assumes all cookie operations are done
 * through its interface. If you manually manipulate cookies, it will get out
 * of sync with the browser's notion of available cookies.
 * @constructor
 */
var Cookies = {}

/**
 * The default options are added to each cookie unless an explicit value is
 * specified. This applies both to the {@link #set} and {@link #clear} calls.
 * Changing the defaults only effects future calls, of course.
 * @type Hash
 */
Cookies.defaultOptions = {}

/**
 * @private
 * Hash mapping cookie name to raw value. Is initialized on {@link #preLoad}
 * and is aware of {@link #set} operations.
 */
Cookies.rawByName = {}

/**
 * @private
 * Hash mapping cookie name to interpreted (evaluated) value. Is initialized on
 * {@link #preLoad} and is aware of {@link #set} operations.
 */
Cookies.valueByName = {}

/**
 * Set a cookie. Updates the {@link #rawByName} and {@link #valueByName}
 * accordingly.
 *
 * @param {String} name
 *  The cookie's unique name. If one already exists it is overwritten.
 *
 * @param value
 *  In theory, an arbitrary JavaScript object. In practice it has to survive a
 *  {@link #toJson} call.
 *
 * @param {Hash} options
 *  Valid options are:
 *  <dl compact>
 *  <dt>expires</dt><dd>
 *      If a string, is assumed to be already properly formatted for a cookie.
 *      If a number, is assumed to be time in milliseconds relative to now. If
 *      undefined, is a session cookie (expires when browser is closed).
 *  </dd>
 *  <dt>domain</dt><dd>
 *      The cookie will only be visible for pages under this domain. Must be
 *      specified, must start with ".". The {@link #defaultOptions} value is
 *      the most generic domain allowed for this page (striping one name out of
 *      the domain specified in the URL).
 *  </dd>
 *  <dt>path</dt><dd>
 *      The cookie will only be visible for pages under this path. Must be
 *      specified, must start with "/". The {@link #defaultOptions} value is
 *      the most generic path allowed ("/").
 *  </dd>
 *  </dl>
 */
Cookies.set = function(name, value, options)
{{
    Claim.isString(name, "Cookies.set.name")
    Claim.isObject(value, "Cookies.set.value")
    var fullOptions = Cookies.fullOptions(name, options)
    var raw = Cookies.toJson(value)
    Cookies.valueByName[name] = value
    Cookies.rawByName[name] = raw
    var cookie = Cookies.fullCookie(name, raw, fullOptions)
    Cookies.log.info("Set cookie: " + cookie)
    document.cookie = cookie
}}

/**
 * Fetch the value of a cookie.
 *
 * @param {string} name
 *  The name of the cookie to fetch.
 *
 * @return
 *  One of the following:
 *  <dl compact>
 *  <dt>undefined</dt>
 *      <dd>If the cookie doesn't exist.</dd>
 *  <dt>null</dt>
 *      <dd>If the cookie contained a nonsensical value.</dd>
 *  </dt>value</dt>
 *      <dd>(A clone of) the JavaScript object given to {@link #set}.</dd>
 *  </dl>
 *
 * @type value/undefined/null
 */
Cookies.get = function(name)
{{
    Claim.isString(name, "Cookies.get.name")
    return Cookies.valueByName[name]
}}

/**
 * Clear a cookie. Behaves similarly to {@link #set} except no value is given,
 * and the expires option is overriden to be in the past, which immediately
 * expires the cookie.
 *
 * @see #get
 *
 * @param {String} name
 *  The cookie's unique name.
 *
 * @param options
 *  As for {@link #get}, but expires will be overwritten to a time in the past.
 *  Note that the domain and path must match that of the cookie, even though
 *  there is no API to access them given the cookie name.
 */
Cookies.clear = function(name, options)
{{
    Claim.isString(name, "Cookies.clear.name")
    var fullOptions = Cookies.fullOptions(name, options)
    fullOptions.expires = Cookies.expiration(-1)
    var cookie = Cookies.fullCookie(name, "", fullOptions)
    delete(Cookies.rawByName[name])
    delete(Cookies.valueByName[name])
    Cookies.log.info("Clear cookie: " + cookie)
    document.cookie = cookie
}}

/**
 * @private
 * Compute full set of options.
 *
 * @param {String} name
 *  The cookie's name (for error messages).
 *
 * @param {Hash} options
 *  The caller specified options.
 *
 * @return
 *  A hash of the full options, based on the current value of {@link
 *  #defaultOptions}.
 *
 * @type Hash
 */
Cookies.fullOptions = function(name, options)
{{
    Claim.isString(name, "Cookies.fullOptions.name")
    var fullOptions = Object.extend({}, Cookies.defaultOptions)
    fullOptions = Object.extend(fullOptions, options || {})
    if (!fullOptions.path)
    {
        var error = "Set cookie name: " + name + " without a path"
        Cookies.log.error(error)
        throw error
    }
    if (fullOptions.path.charAt(0) != "/")
    {
        var error = "Set cookie name: " + name
                  + " invalid path: " + fullOptions.path
        Cookies.log.error(error)
        throw error
    }
    if (!fullOptions.domain)
    {
        var error = "Set cookie name: " + name + " without a domain"
        Cookies.log.error(error)
        throw error
    }
    if (fullOptions.domain.charAt(0) != ".")
    {
        var error = "Set cookie name: " + name
                  + " invalid domain: " + fullOptions.domain
        Cookies.log.error(error)
        throw error
    }
    return fullOptions
}}

/**
 * Convert the expires option to the weird format stored in the cookie.
 *
 * @param {Number} millis
 *  The expiration time, in milliseconds, relative to now.
 *
 * @return
 *  The expiration time, ready to be used in the cookie.
 *
 * @type String
 */
Cookies.expiration = function(millis)
{{
    Claim.isNumber(millis, "Cookies.expiration.millis")
    var today = new Date()
    var now = Date.parse(today)
    today.setTime(now + 1 * millis)
    return today.toUTCString()
}}

/**
 * @private
 * Create the full cookie string to be given to the browser. Parameters are as
 * for {@link #get}. Note that options are expected to be full at this point,
 * but the value is still an object.
 *
 * @see #get
 *
 * @param {String} name
 *  The cookie's unique name.
 *
 * @param raw
 *  The raw (JSON-ed) cookie value.
 *
 * @param options
 *  As for {@link #get}.
 *
 * @return
 *  The cookie string, ready to be passed to the browser.
 *
 * @type String
 */
Cookies.fullCookie = function(name, raw, options)
{{
    Claim.isString(name, "Cookies.fullCookie.name")
    var cookie = name + "=" + raw
    $H(options).each(function(pair)
    {
        if (pair[1] != undefined)
            cookie += ";" + pair[0] + "=" + pair[1]
    })
    return cookie
}}

/**
 * Convert a JavaScript object to a JSON string so it can be put into a cookie.
 * This is a hack, it should be converted to an Object method. As it is, there
 * are some restrictions. It works fine for Array, Hash, String, Number and
 * Boolean objects (which happen to be the built-in JSON data types). It will
 * also work for any object defining the toJson method, which is expected to
 * return a string (see {@link Log4Js.AlertTarget#toJson} for an example). It
 * will do horrible things for anything else (for example, Date - we really
 * should support that one too).
 * <p>
 * Note the result always quotes ";" characters in strings, which allows it to
 * be directly embedded in the cookie. While this quoting is valid JSON, it
 * isn't required.
 * </p><p>
 * Finally, note the result need not actually be valid JSON, it can be any
 * JavaScript expression that can be eval-ed into the (clone of the) object.
 * Again, {@link Log4Js.AlertTarget#toJson} is a good example. This is OK as
 * long as we don't try to use this value for anything other than cookies,
 * which is part of the reason this is a Cookies member and not an Object
 * method.
 * </p><p>
 * @param {Hash/Array/Number/String/Boolean} object
 *  In theory, an arbitrary JavaScript object.
 * </p>
 * @return
 *  A JSON(-ish) string that evaluates to (a clone of) the object.
 *
 * @type String
 */
Cookies.toJson = function(object)
{{
    if (object == undefined)
        return "undefined"
    if (object == null)
        return "null"
    if (typeof(object) == "string")
        return "\"" + Cookies.jsonEscape(object.toString()) + "\""
    if (typeof(object) == "number" || typeof(object) == "boolean")
        return object.toString()
    if (object.toJson)
        return object.toJson()
    var json = ""
    var seperator = ""
    if (typeof object == 'object'
     && object.constructor.toString().match(/array/i) != null)
    {
        $A(object).each(function(value)
        {
            json += seperator + Cookies.toJson(value)
            seperator = ","
        })
        return "[" + json + "]"
    }
    else
    {
        var json = ""
        $H(object).each(function(pair)
        {
            json += seperator + Cookies.toJson(pair[0])
                  + ":" + Cookies.toJson(pair[1])
            seperator = ","
        })
        return "{" + json + "}"
    }
}}

/**
 * Escape a string value for JSON. Also escapes ";" so the result is safe in
 * cookies. Note this does NOT surround the value with '"', it just escapes it.
 *
 * @param {String} text
 *  The text string to escape.
 *
 * @return
 *  The escaped text string.
 *
 * @type String
 */
Cookies.jsonEscape = function(text)
{{
    Claim.isString(text, "Claim.jsonEscape.text")
    var escaped = ""
    for (var i = 0; i < text.length; i++)
    {
        var code = text.charCodeAt(i)
        // TRICKY: Encoding ";" protects cookie values.
        if (code < 32 || code == 59)
        {
            escaped += "\\u" + code.toHex(4)
        }
        else
        {
            var nextChar = text.charAt(i)
            if (nextChar == "\"" || nextChar == "\\")
                escaped += "\\"
            escaped += nextChar
        }
    }
    return escaped
}}

/*
 * @private
 * Sets up a "Cookies" logger, initialize the default options, and loads all
 * cookies.
 */
Cookies.preLoad = function()
{{
    Cookies.log = new Log4Js.Logger("Cookies")
    Cookies.defaultOptions.path = "/"
    Cookies.defaultOptions.domain =
        "." + Url.here.domain.replace(/^[a-zA-Z0-9\-]+./, '')
    document.cookie.split(';').each(function(cookie)
    {
        if (!cookie)
            throw $break
        var name = cookie.replace(/^\s*([^=]+)=.*$/, '$1')
        var raw = cookie.replace(/^[^=]*=/, '')
        Cookies.rawByName[name] = raw
        Cookies.log.info("Load cookie name: " + name + " value: " + raw)
        // TRICKY: Eval does all the un-escaping becuse toJson escapes.
        try
        {
            var value = undefined
            eval("value=" + raw)
            Cookies.valueByName[name] = value
        }
        catch(error)
        {
            Cookies.log.error("Invalid cookie name: " + name
                            + " value: " + value
                            + " error: " + error)
            Cookies.valueByName[name] = null
        }
    })
}}

PreLoad.actions.push(Cookies.preLoad)/**
 * @fileoverview
 *
 * Framework for JavaScript logging.
 * <p>
 * The basic idea is that you create a named logger object:
 * <pre>
 * log = new Log4Js.Logger("Some.Logger.Name")
 * </pre>
 * And then use it to log stuff:
 * <pre>
 * log.debug("something")
 * </pre>
 * Which is the same idea as as Log4J, Log4Net etc. Where this is very
 * different is where the logging goes to and how you configure it.
 * </p><p>
 * There are target classes (currently, {@link Log4Js.AlertTarget}, {@link
 * Log4Js.PopupTarget} and {@link Log4Js.TableTarget}; it may be a good idea to
 * some day add Log4Js.PostTarget that submits the log to a web server).
 * Instances of these need to be created, and they need to be associated with
 * the loggers somehow. Obviously, there is API for all that, described in the
 * {@link Log4Js} class.
 * </p><p>
 * Doing this programattically sucks, however. Each Log4X provides a way of
 * doing it through a config file somehow. However, in JavaScript we don't have
 * the luxury of accessing files, especially considering that the code will run
 * in production web sites under strange domains.
 * </p><p>
 * What JavaScript does give us is cookies. So - the {@link Log4Js} class knows
 * how to load the configuration from a cookie named "log4js.config". The
 * example test page (log4js.html) allows you to create configurations and
 * store/load them into/from cookies; if you use it to store a config in the
 * "log4js.config" cookie, it will remain in effect for other pages for as long
 * as the cookie survives.
 * </p><p>
 * Cookies are bound to a particular domain, but we don't want to drag
 * log4js.html into each and every server. So - to log stuff in some.domain,
 * add an entry in your hosts file pointing log4js.some.domain at some server
 * that has log4js.html (could be your local development web server, could be a
 * hosting server set up for this purpose). Access the log4js.html file in as
 * log4js.some.domain/.../log4js.html, set up the cookie, then browse the web
 * site as usual. In theory, this should allow you to log any page in any
 * domain.
 * </p><p>
 * The current log4js.html is anything but a wonder of good GUI design, it was
 * meant as a quick-and-dirty way to demonstrate (and test) the logging
 * framework. Since it will become a major tool in debugging and testing our
 * web sites, someone should take the trouble of re-writing it into something
 * more user friendly.
 * </p>
 */

/**
 * @class
 * Main logging class framework, handles configurations.
 * @constructor
 */
var Log4Js = {}

/**
 * Convert integer logging levels to human-readable strings.
 * @type Array
 */
Log4Js.levelNames = [ "All", "Debug", "Info", "Warn", "Error", "Fatal", "None" ]

/**
 * This level is used only for filtering; it lets all messages be emitted.
 * @type Number
 */
Log4Js.ALL = 0

/**
 * Debug messages are usually way to much to track on a regular basis, and are
 * only used when, well, debugging the system.
 * @type Number
 */
Log4Js.DEBUG = 1

/**
 * Informational messages describe what the system is doing. It should be
 * possible to run the system with logging set at this stage for a long while.
 * Which is not to say the log files won't be big.
 * @type Number
 */
Log4Js.INFO = 2

/**
 * Warning messages indicate some error condition that the system has managed
 * to recover from.
 * @type Number
 */
Log4Js.WARN = 3

/**
 * Error messages indicate some error condition that the system could not
 * recover from, causing loss of functionality. The system however continues to
 * operate.
 * @type Number
 */
Log4Js.ERROR = 4

/**
 * Fatal messages indicate some error condition that the system could not
 * recover from and that cause the system to cease operation.
 * @type Number
 */
Log4Js.FATAL = 5

/**
 * This level is used only for filtering; it lets no messages to be emitted.
 * @type Number
 */
Log4Js.NONE = 6

/**
 * @private
 * Whenever a change is made to the cofniguration, this number is incremented,
 * allowing targets to adjust their behavior.
 * @type Number
 */
Log4Js.configVersion = 0

/**
 * @private
 * Hash mapping each logger name (prefix) to a list of targets.
 * @type Hash
 */
Log4Js.targetsByName = { "*": [] }

/**
 * Set the list of targets associated with a logger name (prefix).
 *
 * @see #findTargets
 *
 * @param {string} name
 *  The name (prefix) of the loggers that will be directed at the targets.
 *
 * @param {Array} targets
 *  Array of targets (derived from {@link Log4Js.AbstractTarget}) to
 *  associate with the name (prefix).
 */
Log4Js.setTargets = function(name, targets)
{{
    Claim.isString(name, "Log4Js.setTargets.name")
    Claim.isArray(targets, "Log4Js.setTargets.targets")
    Log4Js.targetsByName[name] = targets
    Log4Js.configVersion++
}}

/**
 * Remove a name (prefix) targets list.
 *
 * @see #findTargets
 *
 * @param {string} name
 *  The name (prefix) of the loggers to remove the targets list for.
 */
Log4Js.removeTargets = function(name)
{{
    Claim.isString(name, "Log4Js.removeTargets.name")
    if (name == "*")
        Log4Js.targetsByName[name] = []
    else
        delete(Log4Js.targetsByName[name])
    Log4Js.configVersion++
}}

/**
 * Get the list of targets associated with a logger name (prefix).
 *
 * @see #findTargets
 *
 * @param {string} name
 *  The name (prefix) of the loggers to fetch the targets list for.
 *
 * @return
 *  The list of targets associated with the logger name (prefix).
 *
 * @type Array
 */
Log4Js.getTargets = function(name)
{{
    Claim.isString(name, "Log4Js.getTargets.name")
    return Log4Js.targetsByName[name]
}}

/**
 * Find the most specific prefix for a name. This is the most specific prefix
 * of the name that was explicitly associated with a list of targets using
 * {@link #setTargets}.
 *
 * @see #findTargets
 *
 * @param {String} name
 *  The logger (full) name
 *
 * @return
 *  The most-specific prefix for the name.
 *
 * @type String
 */
Log4Js.findPrefix = function(name)
{{
    Claim.isString(name, "Log4Js.findPrefix.name")
    while (true)
    {
        if (name == "")
            name = "*"
        var targets = Log4Js.targetsByName[name]
        if (targets)
            return name
        var lastDot = name.lastIndexOf(".")
        name = lastDot > 0 ? name.substring(0, lastDot) : ""
    }
}}

/**
 * Find the list of targets for a specific logger. The list is the one
 * associated with the most specific prefix of the logger name for which a call
 * to {@link #setTargets} was made. Thus setting a targets list for "Jast" will
 * effect "Jast.Request" and "Jast.Responders", unless a specific targets list
 * was provided for them. Note that testing for a prefix removes one
 * dot-seperated word at a time, so setting a targets list for "Jast.R" will
 * not effect "Jast.Request" and "Jast.Responders".
 * <p>
 * If no matching prefix is found, the target list associated with "*" is used.
 * This special "prefix" matches any name and is associated with an empty list
 * of targets by default.
 * </p>
 *
 * @param {String} name
 *  The logger (full) name
 *
 * @return
 *  The list of targets the logger should emit messages to.
 *
 * @type Array
 */
Log4Js.findTargets = function(name)
{{
    Claim.isString(name, "Log4Js.findTargets.name")
    return this.getTargets(this.findPrefix(name))
}}

/**
 * Dump the total set of mappings to a configuration object. This object may be
 * used to restore the configuration later. This object can be safely converted
 * to and from JSON. Due to JSON limitations, this is not a simple mapping from
 * name (prefix) to targets list; instead, an explicit anchor/alias system is
 * used. YAML would have done this automatically for us.
 *
 * @see #fromConfig
 *
 * @type Hash
 */
Log4Js.toConfig = function()
{{
    var anchorsByName = {}
    var targetAnchorByJson = {}
    var targetByAnchor = {}
    var nextAnchor = 1
    $H(Log4Js.targetsByName).each(function(pair)
    {
        var name = pair[0]
        var targets = pair[1]
        anchorsByName[name] = targets.inject([], function(anchors, target)
        {
            var json = target.toJson()
            var anchor = targetAnchorByJson[json]
            if (!anchor)
            {
                anchor = targetAnchorByJson[json] = "t" + nextAnchor++
                targetByAnchor[anchor] = target
            }
            anchors.push(anchor)
            return anchors
        })
    })
    return { targetByAnchor: targetByAnchor, anchorsByName: anchorsByName }
}}

/**
 * Replace the total current mapping from name (prefix) to targets with the one
 * specified in the config object.
 *
 * @see #toConfig
 *
 * @param {config}
 *  A configuration fetched from {@link #toConfig} to load from.
 */
Log4Js.fromConfig = function(config)
{{
    Claim.isObject(config, "Log4Js.fromConfig.config")
    Claim.isObject(config.anchorsByName,
                   "Log4Js.fromConfig.config.anchorsByName")
    Claim.isObject(config.targetByAnchor,
                   "Log4Js.fromConfig.config.targetByAnchor")
    var targetsByName =
        $H(config.anchorsByName).inject({}, function(targetsByName, pair)
    {
        var name = pair[0]
        var anchors = pair[1]
        targetsByName[name] =
            $A(anchors).inject([], function(targets, anchor)
        {
            targets.push(config.targetByAnchor[anchor])
            return targets
        })
        return targetsByName
    })
    Log4Js.targetsByName = targetsByName
    Log4Js.configVersion++
}}

/**
 * Load the configuration stored in the "log4js.config" cookie, if any.
 */
Log4Js.preLoad = function()
{{
    var config = Cookies.get("log4js.config")
    if (config)
        Log4Js.fromConfig(config)
    PreLoad.log.debug("Start pre-load")
}}

PreLoad.actions.push(Log4Js.preLoad)
/**
 * @fileoverview
 * Manage Log4Js loggers.
 */

/**
 * @class
 * A logger accepts messages at different levels and forwards them at a list of
 * targets determined by the current configuration.
 * @constructor
 */
Log4Js.Logger = Class.create()

Log4Js.Logger.prototype = {}

/**
 * Prototype constructor.
 *
 * @see Log4Js#findTargets
 *
 * @param {String} name
 *  The name of the logger. Is expected to be a dot-seperated list of names, to
 *  allow configurations to easily control the targets for a group of loggers
 *  based on a shared prefix.
 */
Log4Js.Logger.prototype.initialize = function(name)
{{
    Claim.isString(name, "Log4Js.Logger.name")
    this.name = name
    this.configVersion = -1
}}

/**
 * Log debug message.
 *
 * @see Log4Js#DEBUG
 *
 * @param {String} text
 *  The text of the message to log.
 */
Log4Js.Logger.prototype.debug = function(text)
{
    this.emit(Log4Js.DEBUG, text)
}

/**
 * Log an informational message.
 *
 * @see Log4Js#INFO
 *
 * @param {String} text
 *  The text of the message to log.
 */
Log4Js.Logger.prototype.info = function(text)
{
    this.emit(Log4Js.INFO, text)
}

/**
 * Log a warning message.
 *
 * @see Log4Js#WARN
 *
 * @param {String} text
 *  The text of the message to log.
 */
Log4Js.Logger.prototype.warn = function(text)
{
    this.emit(Log4Js.WARN, text)
}

/**
 * Log an error message.
 *
 * @see Log4Js#ERROR
 *
 * @param {String} text
 *  The text of the message to log.
 */
Log4Js.Logger.prototype.error = function(text)
{
    this.emit(Log4Js.ERROR, text)
}

/**
 * Log a fatal message.
 *
 * @see Log4Js#FATAL
 *
 * @param {String} text
 *  The text of the message to log.
 */
Log4Js.Logger.prototype.fatal = function(text)
{
    this.emit(Log4Js.FATAL, text)
}

/**
 * Log a message at some level.
 *
 * @param {Number} level
 *  The level of the message to log.
 *
 * @param {String} text
 *  The text of the message to log.
 */
Log4Js.Logger.prototype.emit = function(level, text)
{{
    if (this.configVersion < Log4Js.configVersion)
    {
        this.targets = Log4Js.findTargets(this.name)
        this.configVersion = Log4Js.configVersion
    }
    if( this.targets.length > 0){
        Claim.isNumber(level, "Log4Js.Logger.emit.level")
        Claim.isTrue(Log4Js.DEBUG <= level && level <= Log4Js.FATAL)
        Claim.isScalar(text, "text")
        
        var event =
        {
            time: new Date().toText(),
            level: level,
            name: this.name,
            text: text,
            url: location.href
        } 
        this.targets.each(function(target) { target.emit(event) })
    }
}}
/**
 * @fileoverview
 * Basis for all Log4Js targets.
 */

/**
 * @class
 * Base class for all logging targets.
 */
Log4Js.AbstractTarget = function() {}

Log4Js.AbstractTarget.prototype = {}

/**
 * Emit a log message. Tests is has sufficiently high level first. Assumes the
 * object has a level member with the minimal level to allow, and an
 * _emit(event) function to actually emit the messsage.
 *
 * @param {Hash} event
 *  The log message. Contains the following keys:
 *  <dl compact>
 *  <dt>time</dt><dd>
 *  Human readable rendition of the time of the message.
 *  </dd>
 *  <dt>level</dt><dd>
 *  The level integer code.
 *  </dd>
 *  <dt>name</dt><dd>
 *  The name of the logger emitting this message.
 *  </dd>
 *  <dt>text</dt><dd>
 *  The text of the message.
 *  </dd>
 *  <dt>url</dt><dd>
 *  The URL of the page triggering the log message. This is currently always
 *  equal to location.href. It is here in case we get fancy with submitting
 *  logs to a common page from different pages.
 *  </dd>
 *  </dl>
 */
Log4Js.AbstractTarget.prototype.emit = function(event)
{{
    Claim.isObject(event, "Log4Js.AbstractTarget.emit.event")
    Claim.isNumber(event.level, "Log4Js.AbstractTarget.emit.event.level")
    if (event.level >= this.level)
        this._emit(event)
}}
/**
 * @fileoverview
 * Log4Js target that calls <tt>alert()</tt>.
 */

/**
 * @class
 * Logging target that calls alert(). Note this does horrible things if
 * timeouts are involved, but it is useful for the higher level events (say,
 * {@link Log4Js#ERROR} and up).
 * @constructor
 */
Log4Js.AlertTarget = Class.create()

Log4Js.AlertTarget.prototype = new Log4Js.AbstractTarget()

/**
 * Prototype constructor.
 *
 * @param {Number} level
 *  The minimal logging level to actually emit.
 */
Log4Js.AlertTarget.prototype.initialize = function(level)
{{
    Claim.isNumber(level, "Log4Js.AlertTarget.level")
    Claim.isTrue(Log4Js.ALL <= level && level <= Log4Js.NONE,
                 "Log4Js.AlertTarget.level")
    this.level = level
}}

/**
 * Actually emit the log message.
 *
 * @param {Hash} event
 *  Same as for {@link Log4Js.AbstractTarget#emit}.
 */
Log4Js.AlertTarget.prototype._emit = function(event)
{{
    Claim.isObject(event, "Log4Js.AlertTarget._emit.event")
    alert(event.time + " " + event.url + " "
        + Log4Js.levelNames[event.level] + " " + event.name + ":\n"
        + event.text)
}}

/**
 * Convert to JSON string. The result actually is not valid JSON. It is an
 * expression that when evaluated creates a clone of this object. This is
 * good enough to be placed in a cookie.
 * @type String
 */
Log4Js.AlertTarget.prototype.toJson = function()
{{
    return "new Log4Js.AlertTarget(Log4Js."
         + Log4Js.levelNames[this.level].toUpperCase() + ")"
}}
/**
 * @fileoverview
 * Log4Js target that writes into an HTML table.
 */

/**
 * @class
 * Logging target that appends rows in a table. Header row is inserted unless
 * they already exist. Cells are time, level, logger name, and event text. The
 * URL is assumed to be the same for all messages and is placed in a header
 * row. TODO: Add CSS classes to the header row and the cells for style
 * control.
 * <p>
 * Note that the table element needs to exist for this to work. When logging
 * events in the pre-load phase, this is not always the case. This target will
 * collect all log messages until such a time when the table element appears,
 * and then inject all the messages back log to the table when the next event
 * is logged or the page is done loading - which ever comes first.
 * </p>
 * @constructor
 */
Log4Js.TableTarget = Class.create()

Log4Js.TableTarget.prototype = new Log4Js.AbstractTarget()

/**
 * @private
 * Array of messages not emitted yet because the table element wasn't
 * available yet.
 */
Log4Js.TableTarget.prototype.backLog = []

/**
 * Prototype constructor.
 *
 * @param {Number} level
 *  The minimal logging level to actually emit.
 *
 * @param {String/Element} table
 *  Either the table element or the id of one. The convention is to call the
 *  table "Log4JsTable".
 *
 * @param {Boolean} isLastOnTop
 *  If true, new messages are added at the top of the table. If false, at the
 *  end.
 */
Log4Js.TableTarget.prototype.initialize = function(level, table, isLastOnTop)
{{
    Claim.isNumber(level, "Log4Js.TableTarget.level")
    Claim.isTrue(Log4Js.ALL <= level && level <= Log4Js.NONE,
                 "Log4Js.TableTarget.level")
    Claim.isObject(table, "Log4Js.TableTarget.table")
    Claim.isBoolean(isLastOnTop, "Log4Js.TableTarget.isLastOnTop")
    this.level = level
    this.isLastOnTop = isLastOnTop
    if (typeof(table) == "string")
    {
        this.name = table
        this.table = $(table)
    }
    else
    {
        this.name = table.id
        this.table = table
    }
}}

/**
 * @private
 * Initialize the table, adding header rows if necessary.
 *
 * @param {Hash} event
 *  As for {@link Log4Js.AbstractTarget#emit}. Actually only the url field is
 *  used to populate the header row, if one is cerated.
 *
 * @return
 *  Whether a table object exists and was initialized.
 *
 * @type Boolean
 */
Log4Js.TableTarget.prototype.initTable = function(event)
{{
    Claim.isObject(event, "Log4Js.TableTarget.initTable.event")
    this.table = this.table || $(this.name)
    if (!this.table)
        return false
    if (this.table.rows.length > 0)
        return true

    var row = this.table.insertRow(-1)
    row.insertCell(-1).innerHTML = "Time"
    row.insertCell(-1).innerHTML = "Level"
    row.insertCell(-1).innerHTML = "Name"
    row.insertCell(-1).innerHTML = "Text"

    row = this.table.insertRow(-1)
    row.insertCell(-1).innerHTML = event.time
    row.insertCell(-1).innerHTML = "<hr />"
    row.insertCell(-1).innerHTML = "LOG4JS"
    row.insertCell(-1).innerHTML = event.url

    row = this.table.insertRow(-1)
    row.insertCell(-1).innerHTML = event.time
    row.insertCell(-1).innerHTML = "<hr />"
    row.insertCell(-1).innerHTML = "LOG4JS"
    row.insertCell(-1).innerHTML = event.url

    row = this.table.insertRow(-1)
    row.insertCell(-1).innerHTML = "Time"
    row.insertCell(-1).innerHTML = "Level"
    row.insertCell(-1).innerHTML = "Name"
    row.insertCell(-1).innerHTML = "Text"

    return true
}}

/**
 * Actually emit the log message. May place it in the {@link #backLog} if the
 * table element does not exist yet.
 *
 * @param {Hash} event
 *  Same as for {@link Log4Js.AbstractTarget#emit}.
 */
Log4Js.TableTarget.prototype._emit = function(event)
{{
    Claim.isObject(event, "Log4Js.TableTarget._emit.event")
    this.backLog.push(event)
    if (!this.initTable(event))
        return
    while (this.backLog.length > 0)
    {
        var event = this.backLog.shift()
        var row = this.table.insertRow(this.isLastOnTop ? 2 : -1)
        row.insertCell(-1).innerHTML = event.time
        row.insertCell(-1).innerHTML = Log4Js.levelNames[event.level]
        row.insertCell(-1).innerHTML = event.name
        row.insertCell(-1).innerHTML = event.text
    }
}}

/**
 * Convert to JSON string. The result actually is not valid JSON. It is an
 * expression that when evaluated creates a clone of this object. This is good
 * enough to be placed in a cookie.
 * @type String
 */
Log4Js.TableTarget.prototype.toJson = function()
{{
    return "new Log4Js.TableTarget(Log4Js."
          + Log4Js.levelNames[this.level].toUpperCase() + ","
          + Cookies.toJson(this.name) + ","
          + Cookies.toJson(this.isLastOnTop) + ")"
}}
/**
 * @fileoverview
 * Log4Js target that writes into a table in a popup window.
 */

/**
 * @class
 * Logging target that appends rows in a table in a popup window. Uses the
 * {@link Log4Js.TableTarget} to emit the messages to a table in the new
 * window. TODO: Allow the ability to add a CSS stylesheet to the popup window
 * for style control.
 * @constructor
 */
Log4Js.PopupTarget = Class.create()

/**
 * @private
 * Hash mapping window names to handles, to avoid creating the same one twice.
 * Yes, we could avoid using this and just see whether a table element already
 * exists in the window.
 */
Log4Js.PopupTarget.windows = {}

Log4Js.PopupTarget.prototype = new Log4Js.AbstractTarget()

/**
 * Prototype constructor.
 *
 * @param {Number} level
 *  The minimal logging level to actually emit.
 *
 * @param {String} name
 *  The name of the popup window. If the name contains the special strings "%U"
 *  or "%T" they are replaced by this page's URL or the time of the call,
 *  respectively. Also, any "suspicious characters" are replaced by "_". The
 *  result is used as the window name. Note that if the same name is used from
 *  two different pages, the latter popup will replace the former. Adding the
 *  URL and/or time allows each page invocation has its own popup, which may or
 *  may not be a good thing :-)
 *
 * @param {Boolean} isLastOnTop
 *  If true, new messages are added at the top of the table. If false, at the
 *  end.
 */
Log4Js.PopupTarget.prototype.initialize = function(level, name, isLastOnTop)
{{
    Claim.isNumber(level, "Log4Js.Prototype.level")
    Claim.isTrue(Log4Js.ALL <= level && level <= Log4Js.NONE,
                 "Log4Js.Prototype.level")
    Claim.isString(name, "Log4Js.Prototype.name")
    Claim.isBoolean(isLastOnTop, "Log4Js.Prototype.isLastOnTop")
    this.name = name
    name = name.replace(/%T/, new Date().toText())
    name = name.replace(/%U/, location.href)
    name = name.replace(/\W+/g, '_')
    name = name.replace(/[_]+/g, '_')
    this.windowName = name
    this.level = level
    this.isLastOnTop = isLastOnTop
}}

/**
 * Actually emit the log message.
 *
 * @param {Hash} event
 *  Same as for {@link Log4Js.AbstractTarget#emit}.
 */
Log4Js.PopupTarget.prototype._emit = function(event)
{{
    Claim.isObject(event, "Log4Js.Prototype._emit.event")
    if (!this.window || !this.window.document)
    {
        this.window = Log4Js.PopupTarget.windows[name]
        if (!this.window || !this.window.document)
        {
            this.window = Log4Js.PopupTarget.windows[name] =
                window.open("", this.windowName,
                            'width=640,height=480,'
                          + 'scrollbars=1,status=0,toolbars=0,resizable=1')
            if (!this.window || !this.window.document)
            {
                alert("A popup window manager is blocking the logger "
                    + "popup display.\n"
                    + "You need to allow popups to see the logged events.")
                this.emit = function(event) {}
                return
            }
            this.window.document.writeln("<table id='log-table'></table>")
            this.window.document.close()
        }
        this.table = this.window.document.getElementById("log-table")
        this.target =
            new Log4Js.TableTarget(this.level, this.table, this.isLastOnTop)
    }
    this.target._emit(event)
}}

/**
 * Convert to JSON string. The result actually is not valid JSON. It is an
 * expression that when evaluated creates a clone of this object. This is good
 * enough to be placed in a cookie.
 * @type String
 */
Log4Js.PopupTarget.prototype.toJson = function()
{{
    return "new Log4Js.PopupTarget(Log4Js."
          + Log4Js.levelNames[this.level].toUpperCase() + ","
          + Cookies.toJson(this.name) + ","
          + Cookies.toJson(this.isLastOnTop) + ")"
}}
/**
 * @fileoverview
 * Manage user authentication and clearance levels.
 */

/**
 * @class
 * Handle authentication/clearance issues.
 * @constructor
 */ 
var Clearance = {}

/**
 * Convert a {@link Clearance#level} integer to a human readable name.
 */
Clearance.levelNames =
    [ "Anonymnous", "Unclassified", "Restricted", "Confidential" ]

/**
 * At this level the user isn't known at all.
 * @type Number
 */
Clearance.ANONYMOUS = 0

/**
 * At this level the user is known but may only view non-sensitive data.
 * Usually this level persists all through a session (and if the user chose to,
 * even between sessions for a long period of time).
 * @type Number
 */
Clearance.UNCLASSIFIED = 1

/**
 * At this level the user is known and may view and edit sensitive data (PII).
 * This level reverts to unclassified within a medium period of time.
 * @type Number
 */
Clearance.RESTRICTED = 2

/**
 * At this level the user is known and may view and edit highly sensitive data
 * (financial). This level reverts to restricted within a short period of time.
 * @type Number
 */
Clearance.CONFIDENTIAL = 3

/**
 * The current clearance level. Initialized by {@link #preLoad}.
 *
 * @see #ANONYMOUS
 * @see #UNCLASSIFIED
 * @see #RESTRICTED
 * @see #CONFIDENTIAL
 * @see #isLevel
 * @see #hasLevel
 * @see #isUnclassified
 * @see #hasUnclassified
 * @see #isRestricted
 * @see #hasRestricted
 * @see #isConfidential
 * @see #hasConfidential
 * @see #requiredLevel
 */
Clearance.level = undefined

/**
 * The user identifier (if level is higher than anonymous). Note this isn't the
 * GUID, it is a one way hash of the GUID, so it is useless for performing
 * server operations. It is however unique for each user so it is safe to use
 * this as a key for caching client side per-user data (typically using
 * cookies). Initialized by {@link #preLoad}.
 */
Clearance.userId = undefined

/**
 * Test whether the current clearance level is exactly the one asked for.
 *
 * @param {Clearance#level} level
 *  The clearance level to test for.
 *
 * @return True if and only if the current level is exactly the one asked
 *  for.
 *
 * @type Boolean
 */
Clearance.isLevel = function(level)
{{
    return Clearance.level == level
}}

/**
 * Test whether the current clearance level is sufficient for accessing data at
 * the specified one.
 *
 * @param {Clearance#level} level
 *  The clearance level to test for.
 *
 * @return {Boolean}
 *  True if and only if the current level is as high or higher than the one
 *  asked for.
 *
 * @type Boolean
 */
Clearance.hasLevel = function(level)
{{
    Claim.isNumber(level, "Clearance.hasLevel.level")
    Claim.isTrue(0 <= level && level <= Clearance.CONFIDENTIAL,
                 "Clearance.hasLevel.level")
    return Clearance.level >= level
}}

/**
 * Ensure the user has the necessary clearance to view the current page. If
 * not, redirect the browser to an authentication page, and provide it the URL
 * of the current page to come back to if/when the user does gain the required
 * clearance level. This is usually placed in a script tag that is interpreted
 * as soon as possible before the page is loaded. See the test HTML pages for
 * examples.
 *
 * @param {Clearance#level} requiredLevel
 *  The required clearance level for accessing this page. The required level
 *  for non-https pages must not be higher than unclassified.
 *
 * @param {string} loginUrl
 *  The URL of the page where the user can gain the required clearance level if
 *  he doesn't have it already.
 *
 * @param {string} urlParam
 *  The name of the parameter to attach to the loginUrl with a value containing
 *  the current url. This allows the loginUrl to redirect the user back to here
 *  once he has achieved the required clearance level.
 */
Clearance.requireLevel = function(requiredLevel, loginUrl, urlParam)
{{
    Claim.isNumber(requiredLevel, "Clearance.requireLevel.requiredLevel")
    var minLevel = Clearance.ANONYMOUS
    var maxLevel = Url.here.protocol == "https"
                 ? Clearance.CONFIDENTIAL
                 : Clearance.UNCLASSIFIED
    Claim.isTrue(minLevel <= requiredLevel && requiredLevel <= maxLevel,
                 "Clearance.requireLevel.requiredLevel")
    Claim.isString(loginUrl, "Clearance.requireLevel.loginUrl")
    Claim.isString(urlParam, "Clearance.requireLevel.urlParam")
    Clearance.log.debug("Required: " + requiredLevel
                      + " level: " + Clearance.level)
    if (Clearance.level < requiredLevel)
    {
        var url = Url.appendParamValue(loginUrl, urlParam, location.href)
        Clearance.log.info("Redirect to " + url)
        location = url
    }
}}

/**
 * Test whether the user current clearance level is exactly unclassified.
 * @type Boolean
 */
Clearance.isUnclassified = function()
{{
    return Clearance.isLevel(Clearance.UNCLASSIFIED)
}}

/**
 * Test whether the user has sufficient clearance for unclasssified data (i.e.
 * whether we know who he is).
 * @type Boolean
 */
Clearance.hasUnclassified = function()
{{
    return Clearance.hasLevel(Clearance.UNCLASSIFIED)
}}

/**
 * Ensure the user has unclassified clearance (that is, we know who he is) for
 * accessing this page, otherwise redirect him to a login page.
 *
 * @see #requireLevel
 */
Clearance.requireUnclassified = function(loginUrl, param)
{{
    Clearance.requireLevel(Clearance.UNCLASSIFIED, loginUrl, param)
}}

/**
 * Test whether the user current clearance level is exactly restricted.
 * @type Boolean
 */
Clearance.isRestricted = function()
{{
    return Clearance.isLevel(Clearance.RESTRICTED)
}}

/**
 * Test whether the user has sufficient clearance for restricted data (i.e.
 * whether he is allowed to view and edit PII data).
 * @type Boolean
 */
Clearance.hasRestricted = function()
{{
    return Clearance.hasLevel(Clearance.RESTRICTED)
}},

/**
 * Ensure the user has restricted clearance (that is, he may view and edit PII
 * data) for accessing this page, otherwise redirect him to a login page.
 *
 * @see #requireLevel
 */
Clearance.requireRestricted = function(loginUrl, param)
{{
    Clearance.requireLevel(Clearance.RESTRICTED, loginUrl, param)
}}

/**
 * Test whether the user current clearance level is exactly confidential.
 * @type Boolean
 */
Clearance.isConfidential = function()
{{
    return Clearance.isLevel(Clearance.CONFIDENTIAL)
}}

/**
 * Test whether the user has sufficient clearance for confidential data (i.e.
 * whether he is allowed to view and edit financial data).
 * @type Boolean
 */
Clearance.hasConfidential = function()
{{
    return Clearance.hasLevel(Clearance.CONFIDENTIAL)
}}

/**
 * Ensure the user has confidential clearance (that is, he may view and edit
 * financial data) for accessing this page, otherwise redirect him to a login
 * page.
 *
 * @see #requireLevel
 */
Clearance.requireConfidential = function(loginUrl, param)
{{
    Clearance.requireLevel(Clearance.CONFIDENTIAL, loginUrl, param)
}}

/**
 * Obtain a list of parameters that can be added to a URL to authenticate the
 * user at the specified level.
 *
 * @param {Clearance#level} level
 *  The clearance level to get the parameters for. Must be at least {@link
 *  #UNCLASSIFIED}.
 *
 * @return {null/Hash}
 *  A hash of parameters that can be given to the server to authenticate the
 *  user at the specified level, or null if the user doesn't have this level.
 */
Clearance.getParams = function(level)
{{
    Claim.isNumber(level, "Clearance.getEncrypted.level")
    Claim.isTrue(level >= Clearance.UNCLASSIFIED
              && level <= Clearance.CONFIDENTIAL,
                 "Clearance.getEncrypted.level")     
    if (!Clearance.hasLevel(level))
        return null
    var params = {}
    params.ui = Clearance.params.ui
    params.un = Clearance.params.un
    params.ux = Clearance.params.ux
    if (level >= Clearance.RESTRICTED)
    {
        params.rn = Clearance.params.rn
        params.rx = Clearance.params.rx
    }
    if (level >= Clearance.CONFIDENTIAL)
    {
        params.cn = Clearance.params.cn
        params.cx = Clearance.params.cx
    }
    return params
}}

/**
 * Obtain a magic string that can be given to the server to authenticate the
 * user at the specified level. This string is identical to the URL parameters
 * of a URL containing this information (when we go across domains). Note it
 * includes the leading '?' and the seperation '&' characters.
 *
 * @param {Clearance#level} level
 *  The clearance level to get the magic string for. Must be at least {@link
 *  #UNCLASSIFIED}.
 *
 * @return {null/String}
 *  A magic string that can be given to the server to authenticate the user at
 *  the specified level, or null if the user doesn't have this level.
 */
Clearance.getMagic = function(level)
{{
    params = Clearance.getParams(level)
    // What an abuse! But so elegant...
    if (params)
        return Url.appendParams("", params)
    return null
}}

/**
 * @private
 *
 * Convert a {@link Clearance#level} integer to a cookie prefix.
 */
Clearance.prefix = [ "Oberon.A.", "Oberon.U.", "Oberon.R.", "Oberon.C." ],

/**
 * @private
 *
 * Name of a clearance cookie.
 *
 * @param {Clearance#level} level
 *  The clearance level to get the cookie name for.
 *
 * @param {Boolean} isTimed
 *  If true, a global (timed) cookie, otherwise a session cookie. This has no
 *  effect for unclassified cookies (there's only one which may be either timed
 *  or session depending on user preference).
 *
 * @return {String}
 *  The cookie name.
 */
Clearance.cookieName = function(level, isTimed)
{{
    Claim.isNumber(level, "Clearance.cookieName.level")
    Claim.isTrue(level >= Clearance.UNCLASSIFIED
              && level <= Clearance.CONFIDENTIAL,
                 "Clearance.cookieName.level")
    Claim.isBoolean(isTimed, "Clearance.cookieName.isTimed")
    if (level > Clearance.UNCLASSIFIED)
        return Clearance.prefix[level] + (isTimed ? "T" : "S")
    else
        return Clearance.prefix[level]
}}

/**
 * @private
 *
 * Set the cookies for a single clearance level.
 *
 * @param {Clearance#level} level
 *  The clearance level to set the cookies for.
 *
 * @param {String} userId
 *  The hash of the userId GUID.
 *
 * @param {String} encrypted
 *  Magic encrypted string verifying this for the server.
 *
 * @param {null/Number} expires
 *  Expiration time for the cookie in milliseconds since 1970-01-01. If null,
 *  only a session cookie is created. This only works for unclassified.
 */
Clearance.setCookies = function(level, userId, encrypted, expires)
{{
    Claim.isNumber(level, "Clearance.setCookies.level")
	Claim.isTrue(level >= Clearance.UNCLASSIFIED
              && level <= Clearance.CONFIDENTIAL,
                 "Clearance.setCookies.level")
    Claim.isString(userId, "Clearance.setCookies.userId")
    Claim.isString(encrypted, "Clearance.setCookies.encrypted")
    
    var value = { ui: userId, en: encrypted }	
    if (expires)
    {
        value.ex = Math.round(expires / 1000)
		var date = new Date()
		date.setSeconds(date.getSeconds() + value.ex )
		Cookies.set(Clearance.cookieName(level, true), value,
		{ expires: date.toUTCString() })

	    if (level > Clearance.UNCLASSIFIED)
	    {
            Cookies.set(Clearance.cookieName(level, false), value,
                        { expires: undefined })
        }
    }
    else
    {
        Claim.isTrue(level == Clearance.UNCLASSIFIED,
                     "Clearance.setCookies.level")
        Cookies.set(Clearance.cookieName(level, true), value,
                    { expires: undefined })
        //Cookies.clear(Clearance.cookieName(level, true), {})
    }		
}}

/**
 * @private
 *
 * Clear cookies for a single clearance level.
 *
 * @param {Clearance#level} level
 *  The clearance level to clear the cookies for.
 */
Clearance.clearCookies = function(level)
{{
    Claim.isNumber(level, "Clearance.clearCookies.level")
    Claim.isTrue(level >= Clearance.UNCLASSIFIED
              && level <= Clearance.CONFIDENTIAL,
                 "Clearance.clearCookies.level")
    Cookies.clear(Clearance.cookieName(level, false), {})
    Cookies.clear(Clearance.cookieName(level, true), {})
}}

/**
 * Forget the user altogether when in a secure page. In theory we may want to
 * reduce the clearance in a more granular fashion, in practice cookie
 * expiration does this for us. We still need to allow the user to "log out",
 * for public terminals etc.
 * <p>
 * Since we maintain clearance cookies in two domains (the partner's domain for
 * normal pages and Oberon's domain for SSL), we need to clear it in both
 * domains. This method clears the cookies in the current domain and then
 * redirects the browser to a page in the other domain that will clears the
 * cookies in it as well.
 * </p>
 *
 * @param {String} url
 *  A URL in the second domain holding clearance cookies. Is automatically
 *  appended a "ui=none" parameter. Lacking any other clearance parameters,
 *  this should automatically clear the cookies. Make sure you specify
 *  <tt>withClearanceParams: false</tt> when you prepare this url using {@link
 *  Url#relativeUrl}!
 *  <p>
 *  Normally, when the current page is a non-secure page, this (secure) url
 *  immediately redirects the user back to a non-secure url - possibly even the
 *  current one. This, however, is beyond the scope of this function; in
 *  principle, the url may be the (secure) login page and not perform any
 *  redirection.
 *  </p>
 */  
Clearance.forget = function(url)
{
    Clearance.log.debug("Forget user")
    Clearance.clearCookies(Clearance.UNCLASSIFIED)
    Clearance.clearCookies(Clearance.RESTRICTED)
    Clearance.clearCookies(Clearance.CONFIDENTIAL)
    var url = Url.appendParamValue(url, "ui", "none")
    Clearance.log.info("Redirect to " + url)
    location = url
}

/**
 * URL parameters to use when crossing over to a different domain. Initialized
 * by {@link #preLoad}.
 * <p>
 * Cookies don't survive cross-domain trips, and we need to split the
 * application across several domains (mainly due to SSL reasons). This set of
 * parameters provides the other domain page with all the required clearance
 * information. These parameters must be appended to any URL outside the
 * current domain, unless it is a true 3rd party page that doesn't care about
 * clearance.
 * </p>
 * The clearance URL parameters are:
 * <dl compact>
 * <dt>ui</dt><dd>
 *  User Id (hash of user GUID). If missing no cookies are created.
 * </dd>
 * <dt>un, ux</dt><dd>
 *  Unclassified eNcrypted data and eXpiration time (seconds since 1970). If
 *  either is missing no unclassified cookies are created.
 * </dd>
 * <dt>rn, rx</dt><dd>
 *  Restricted eNcrypted data and eXpiration time (seconds since 1970). If
 *  either is missing no restricted cookies are created.
 * </dd>
 * <dt>cn, cx</dt><dd>
 *  Confidential eNcrypted data and eXpiration time (seconds since 1970). If
 *  either is missing no confidential cookies are created.
 * </dd>
 * </dl>
 */
Clearance.params = {},

/**
 * @private
 *
 * Initialize stuff on page load. Sets up a "Clearance" logger and fetches the
 * current clearance level. First initializes cookies from url ({@link
 * #urlParamsToCookies}) and then attempts to {@link #processCookies} for an
 * increasing clearance {@link #level}. As a side effect, this also initializes
 * the {@Link #params}.
 */
Clearance.preLoad = function()
{{
    Clearance.urlParamsToCookies()
    Clearance.level = Clearance.ANONYMOUS
    Clearance.log.debug("Achieved Anonymnous")
    if (Clearance.processCookies(Clearance.UNCLASSIFIED, "un", "ux"))
        if (Clearance.processCookies(Clearance.RESTRICTED, "rn", "rx"))
            Clearance.processCookies(Clearance.CONFIDENTIAL, "cn", "cx")
}}

/**
 * Examine this document's URL for clearance parameters. If such parameters
 * exist, initialize the clearance cookies accordingly. This allows us to
 * migrate clearance between different domains (typically for SSL). The
 * clearance URL parameters are as listed in {@link #params}.
 * <p>
 * Note that after looking for the above parameters, they are removed from the
 * {@link Url#here}.params hash table, so that passing "all parameters" to
 * other web pages will not reuse them. To include the clearance parameters,
 * explicitly use {@link Url#appendParams} with {@link #params}. This will work
 * regardless of whether the current URL contained the parameters.
 * </p>
 */
Clearance.urlParamsToCookies = function()
{{
    Clearance.log = new Log4Js.Logger("Clearance")
    Clearance.level = Clearance.ANONYMOUS
    Clearance.log.debug("Try to access current URL clearance parameters")

    if (Url.here.params.ui)
    {
		if (Url.here.params.un && Url.here.params.ux)
            Clearance.setCookies(Clearance.UNCLASSIFIED,
                                 Url.here.params.ui,
                                 Url.here.params.un,
								 ((typeof(Url.here.params.ux)=="number")?(Url.here.params.ux * 1000.0):Url.here.params.ux))
        else if (Url.here.params.un)
            Clearance.setCookies(Clearance.UNCLASSIFIED,
                                 Url.here.params.ui,
                                 Url.here.params.un,
                                 null)
        else			
            Clearance.clearCookies(Clearance.UNCLASSIFIED)
        if (Url.here.params.rn && Url.here.params.rx)
            Clearance.setCookies(Clearance.RESTRICTED,
                                 Url.here.params.ui,
                                 Url.here.params.rn,
								 (typeof(Url.here.params.rx)=="number")?(Url.here.params.rx * 1000.0):Url.here.params.rx)
        else
            Clearance.clearCookies(Clearance.RESTRICTED)
        if (Url.here.params.cn && Url.here.params.cx)
            Clearance.setCookies(Clearance.CONFIDENTIAL,
                                 Url.here.params.ui,
                                 Url.here.params.cn,
								 (typeof(Url.here.params.cx)=="number"?(Url.here.params.cx * 1000.0):Url.here.params.cx))
        else
            Clearance.clearCookies(Clearance.CONFIDENTIAL)
    }
    Clearance.log.debug("Strip clearance params from URL")
    delete(Url.here.params["ui"])
    delete(Url.here.params["un"])
    delete(Url.here.params["ux"])
    delete(Url.here.params["rn"])
    delete(Url.here.params["rx"])
    delete(Url.here.params["cn"])
    delete(Url.here.params["cx"])
}}

/**
 * Examine cookies in an attempt to raise the current clearance level. If
 * successful, also initializes the {@link #params} for the newly achieved
 * level. Uses a pair of cookies with the level's {@link #prefix}, with a
 * suffix of ".S" for session and ".T" for times cookies. The use of a pair of
 * cookies allows combining expiration times with automatic expiration at the
 * end of the session. In the case of the unclassified level (knowing who the
 * user is), we want it as sticky as possible so we use either one. In the case
 * of the higher levels, we want it as safe as possible so we require both. The
 * cookie values are expected to be hash tables with the following keys:
 * <dl compact>
 * <dt>ui</dt><dd>
 *  The {@link #userId}.
 * </dd>
 * <dt>en</dt><dd>
 *  Encrypted data used by server.
 * </dd>
 * <dt>ex</dt><dd>
 *  Expiration time (seconds since 1970-01-01, only for timed cookies).
 * </dd>
 * </dl>
 *
 * @param {Number} level
 *  The clearance level to process the cookies for.
 *
 * @param {String} en
 *  The name of the encrypted parameter to set in {@link #params} if
 *  achieving the new level.
 *
 * @param {String} ex
 *  The name of the expiration parameter to set in {@link #params} if
 *  achieving the new level.
 *
 * @return {Boolean}
 *  Whether the new level was achieved.
 */
Clearance.processCookies = function(level, en, ex)
{{
    Claim.isNumber(level, "Clearance.processCookies.level")
    Claim.isTrue(level > Clearance.level
              && level <= Clearance.CONFIDENTIAL,
                 "Clearance.processCookies.level")
    
    Clearance.log.debug("Process " + Clearance.prefix[level] + " cookies")

    // TRICKY: These two are actually the same cookie for unclassified.
    var sessionName = Clearance.cookieName(level, false)
    var timedName = Clearance.cookieName(level, true)

    var session = Cookies.get(sessionName)
    var timed = Cookies.get(timedName)

    if (!session)
    {
        Clearance.log.debug("No " + sessionName + " cookie => "
                          + Clearance.levelNames[Clearance.level])
        return false
    }
    if (!timed)
    {
        Clearance.log.debug("No " + timedName + " cookie => "
                          + Clearance.levelNames[Clearance.level])
        return false
    }

    if (!session.ui)
    {
        Clearance.log.debug("No " + sessionName + ".ui => "
                          + Clearance.levelNames[Clearance.level])
        return false
    }
    if (!timed.ui)
    {
        Clearance.log.debug("No " + timedName + ".ui => "
                          + Clearance.levelNames[Clearance.level])
        return false
    }
    if (session.ui != timed.ui)
    {
        Clearance.log.debug("Mismatch {" + sessionName + "," + timedName
                          + "}.ui => "
                          + Clearance.levelNames[Clearance.level])
        return false
    }

    if (!session.en)
    {
        Clearance.log.debug("No " + sessionName + ".en => "
                          + Clearance.levelNames[Clearance.level])
        return false
    }
    if (!timed.en)
    {
        Clearance.log.debug("No " + timedName + ".en => "
                          + Clearance.levelNames[Clearance.level])
        return false
    }
    if (session.en != timed.en)
    {
        Clearance.log.debug("Mismatch {" + sessionName + "," + timedName
                          + "}.en => "
                          + Clearance.levelNames[Clearance.level])
        return false
    }

    if (level > Clearance.UNCLASSIFIED)
    {
		if (session.ui != Clearance.userId)
        {
            Clearance.log.debug("Mismatch " + sessionName + ".ui => "
                              + Clearance.levelNames[Clearance.level])
            return false
        }
    }
    else
    {
        Clearance.params.ui = session.ui
        Clearance.userId = session.ui
    }
    
    Clearance.params[en] = session.en
    Clearance.params[ex] = timed.ex ? timed.ex : 1
    Clearance.level = level
    Clearance.log.debug("Achieved " + Clearance.levelNames[Clearance.level])
    return true
}}

PreLoad.actions.push(Clearance.preLoad)/**
 * @fileoverview
 * Jast - JSON Asynchronous Script Tag.  This mimics the Ajax prototype
 * functionality as much as possible/reasonable. Hopefully this is close enough
 * so that migration between them if/when necessary will be a realtively
 * painless process.
 * <p>
 * Since we are using the script tag, we don't get the sophisticated event
 * progress and error handling provided by the Ajax library. Instead we rely on
 * timeout to let us know a reuqest has failed, and on the server to provide us
 * explicit in-band response meta-data for requests that do arrive on time.
 * </p><p>
 * Basic usage is simple:
 * <pre>
 * new Jast.Request("url", {
 *     onSuccess: function(request) { use request.result }
 *     onFailure: function(request) { use request.result }
 *     onTimeout: function(request) { no request.result }
 * })
 * </pre>
 * <p>
 * The server is expected to accept a URL with a "jastId=id" parameter and
 * write something like this for success:
 * <pre>
 * Jast.response("id",true,{result data})
 * </pre>
 * Or this for failure:
 * <pre>
 * Jast.response("id",false,{error data})
 * </pre>
 * </p>
 */

/**
 * @class
 * Static singleton class holding global Jast data.
 * @constructor
 */
var Jast = {}

/**
 * The default amount of time to wait until a request is given up as as lost.
 * @type Number
 */
Jast.timeout = 20000

/**
 * Hash mapping request URLs to pending {@link Jast.Request} objects.
 * @type Hash
 */
Jast.pendingRequestByUrl = {}

/**
 * Hash mapping request ids to pending {@link Jast.Request} objects.
 * @type Hash
 */
Jast.pendingRequestById = {}

/**
 * Whether this is the pre-load phase (when document.write is valid) or
 * after it (when it isn't).
 * @type Boolean
 */
Jast.isPreLoad = true

/**
 * The next unused request id.
 * @type Number
 */
Jast.nextRequestId = 0

/**
 * The number of requests that are currently active.
 * @type Number
 */
Jast.activeRequestCount = 0

/**
 * A Jast server HTTP response contains a single JavaScript function call to
 * this function.
 *
 * @param {Number} id
 *  The request id as passed in the jastId query parameter in the request URL.
 *
 * @param {Boolean} isOk
 *  Whether this is a success or failure response.
 *
 * @param {Object} result
 *  The data associated with the response.
 *
 * @param {Hash} caching
 *  This is expected to contain the following keys. All are optional.
 *  <dl>
 *      <dt>expires</dt><dd>
 *          The time in milliseconds to cache this cookie for. If not
 *          specified, no timed cookie is created.
 *      </dd>
 *      <dt>session</dt><dd>
 *          If not specified or has a false value, no session cookie is
 *          created.
 *      </dd>
 *      <dt>domain</dt><dd>
 *          The cookie will only be visible for pages under this domain. If not
 *          specified, the {@link Cookies#defaultOptions} domain will be used.
 *      </dd>
 *      <dt>path</dt><dd>
 *          The cookie will only be visible for pages under this path. If not
 *          specified, the {@link Cookies#defaultOptions} path will be used.
 *      </dd>
 *  </dl>
 *  You'd notice this is mostly compatible with {@link Cookies#set} options.
 */
Jast.response = function(id, isOk, result, caching)
{{
    Claim.isNumber(id, "Jast.response.id")
    Claim.isBoolean(isOk, "Jast.response.isOk")
    request = Jast.pendingRequestById[id]
    if (!request)
    {
        Jast.Request.log.warn("Unknown load request id: " + id
                            + " isOk: " + isOk)
        return
    }
    request.loaded(isOk, result, caching)
}}

/**
 * Forget all Jast responses cached in cookies. This is a blunt instrument
 * used whenever we suspect the possibility that the user will do something to
 * invalidate any of the cached responses. Examples are when sending a POST
 * request that will potentially change some DB data, and when redirecting the
 * user to an SLL page (in another domain) that may potentially lead to the
 * user invoking such POST requests.
 * <p>
 * This is of course horribly inefficient. Ideally we'd like only the changed
 * data to be invalidated. Given we are operating in two different domains for
 * SSL reasons, this is very difficult to achieve.
 * </p><p>
 * Also note that we may have "deluxe" games and so on that don't work from
 * within a browser. For that matter, the user may have several browsers (IE
 * and FF) with independent cookie stores. This method obviously only works
 * for the browser it is in.
 * </p><p>
 * Bottom line is that this is a stopgag measure and we need to revisit this
 * as soon as possible. For example we could enhance this with a 1-minute
 * expired cookie containing the "version number" of the totality of the usser
 * data, and automatically clear the cache if the new "version number" is not
 * the same as the last "version number". This means one hit per minute per
 * user on our servers, and we'd still need to explicitly clear the cache on
 * some occasions.
 * </p>
 */
Jast.clearCache = function()
{{
    $H(Cookies.valueByName).keys.each(function(name)
    {
        if (name.substr(0, 5) == "Jast.")
            Cookies.clear(name)
    })
}}/**
 * @fileoverview
 * Manage Jast requests.
 */

/**
 * @class
 * A Jast request has the following main properties that may be examined by
 * event handlers - state, status and result.  The state refers to how far along
 * are we in processing the request, the status refers to what we know on the
 * result.  Changes in each trigger an event. The names were chose attempting
 * compatibility with Ajax events. The life cycle is as follows:
 * <dl compact>
 * <dt>onUninitialized</dt><dd>
 *  Request is just being constructed. Pretty useless.
 * </dd>
 * <dt>onCreate</dt><dd>
 *  Pretty much the same as onUnitialized, driven by status rather than state.
 * </dd>
 * <dt>onLoading</dt><dd>
 *  The script tag has been written and the browser is hopefully making the
 *  request to the server. IF this is done after the onload event, and there is
 *  a timeout, it starts ticking.
 * </dd>
 * <dt>onLoaded</dt><dd>
 *  A response has arrived (or the request timed out). The result and status
 *  are NOT available yet. All requests made prior to the onload event are kept
 *  at this level until onload for two reasons; maximizing merging and ensuring
 *  that the handlers have access to all the HTML elements. This means that
 *  handlers can only change the content of the page, not the structure (i.e.,
 *  no document.write in handlers).
 * </dd>
 * <dt>onInteractive</dt><dd>
 *  The response result and status are available. It is usually easier to catch
 *  the following specific events, one of which immediately follows:
 * </dd>  
 * <dt>onSuccess, onFailure, onTimeout</dt><dd>
 *  Specific events depending on the result, driven by status rather than
 *  state. The content of the result onSuccess and onFailure are as specified
 *  by the server. Timeout indicates anything from the server not responding to
 *  a whole list of error conditions when a response does arrive. There is no
 *  way to get at the details, so the result field is not available onTimeout.
 * </dd>
 * <dt>onComplete</dt><dd>
 *  Processing is done. The request is disposed of when all handlers are done.
 * </dd>
 * </dl>
 * Requests are not always sent to the server. They may be fetched from a
 * cookie instead. This raises several additional issues - What type of cookie
 * to use - timed, session or both? What should be the expiration time? What
 * are the cookie names? It is tempting to specify these as additional
 * parameters when creating a request. However, the client simply doesn't know
 * what is the appropriate caching policy. Hence the server is responsible for
 * specifying these values. See the {@link Jast#response} method for details.
 * @constructor
 */
Jast.Request = Class.create()

/**
 * Map state integer value to a human readable string.
 */
Jast.Request.stateNames = [
    "Uninitialized", "Loading", "Loaded", "Interactive", "Complete"
]

/**
 * A request begins its life in the uninitialized state.
 */
Jast.Request.UNINITIALIZED = 0

/**
 * Once a script tag has been created, a request moves to the loading state.
 */
Jast.Request.LOADING = 1

/**
 * When a response arrived, the request enters the loaded state. Note that
 * result and status are not available yet.
 */
Jast.Request.LOADED = 2

/**
 * When the result has been parsed, the requests enters the interactive state.
 * It is in this state that most callbacks are invoked.
 */
Jast.Request.INTERACTIVE = 3

/**
 * After the result has been preocessed, the request is complete and once
 * any final handlers do their thing, it is disposed of.
 */
Jast.Request.COMPLETE = 4

/**
 * Convert status integer values to human readable string.
 */
Jast.Request.statusNames = [
    "Create", "Success", "Failure", "Timeout"
]

/**
 * A request begins its life with a create status.
 */
Jast.Request.CREATE = 0

/**
 * When a result enters the interactive state, its status will be success if a
 * response has arrived in time and its server provided content indicates all
 * is well. The result field will hold the server provided result data.
 */
Jast.Request.SUCCESS = 1

/**
 * When a result enters the interactive state, its status will be failure if a
 * response has arrived in time and its server provided content indicates an
 * error occured. The result field will hold the server provided error data.
 */
Jast.Request.FAILURE = 2

/**
 * When a result enters the interactive state, its status will be timeout if a
 * response has not arrived in time. There is no way to know what the problem
 * was so the result field is left undefined.
 */
Jast.Request.TIMEOUT = 3

/**
 * @private
 * Sets up a "Jast.Request" logger.
 */
Jast.Request.preLoad = function()
{{
    Jast.Request.log = new Log4Js.Logger("Jast.Request")
}}
PreLoad.actions.push(Jast.Request.preLoad)

/**
 * @private
 * When the page is finally loaded, process all requests made during the
 * pre-load phase. This allows the handlers to safely access all the HTML
 * elements.
 */
Jast.Request.onLoad = function()
{{
    Jast.isPreLoad = false
    var length = Jast.nextRequestId
    Jast.Request.log.debug("Complete " + length + " pre-load request(s)")
    length.times(function(id)
    {
        request = Jast.pendingRequestById[id]
        if (request && !request.uses)
            if (request.result)
                request.complete()
            else{
				if (request.options.timeout > 0)
				{
					request.setTimeout =
						setTimeout(request.timedOut.bind(request), request.options.timeout)
					Jast.Request.log.debug("Reset timeout for Preloaded Request. Request id: " + request.id
										 + " setTimeout: " + request.setTimeout)
				}
			}
    })
}}
Event.observe(window, "load", Jast.Request.onLoad)

Jast.Request.prototype = {}

/**
 * Prototype constructor.
 *
 * @param {string} url
 *  The URL to request data from. Note that the request may be merged with a
 *  compatible pending request, and or fetched from a compatible request made
 *  in the past and cached in a cookie. Compatible URLs have the same protocol,
 *  domain and path, and same set of "important" parameters. Jast is smart
 *  enough to disregard the <tt>jastId</tt> parameter it automatically adds. It
 *  also ignores all user authentication parameters except for the (hash of
 *  the) user id. Otherwise it assumes all url parameters are "important",
 *  unless told otherwise (see below). Note that the client determines whether
 *  or not to merge two pending requests, but it is the server that determines
 *  whether a result may be fetched from a cookie (see {@link Jast#response}).
 *
 * @param {Hash} options
 * Valid options are:
 * <dl compact>
 * <dt>timeout</dt><dd>
 *  If you want to override the default {@link Jast#timeout} for this request.
 * </dd>
 * <dt>toReuse</dt><dd>
 *  If this is not specified or true, then Jast will combine requests for the
 *  same URL if possible. If false, each request will be independent. Request
 *  merging takes the parameters into account.
 * </dd>
 * <dt>unimportant</dt><dd>
 *  Array of parameter names that should be ignored when looking for a cached
 *  response.
 * </dd>
 * </dl>
 */
Jast.Request.prototype.initialize = function(url, options)
{{
    Claim.isString(url, "Jast.Request.url")
    this.url = url
    this.options = {
        timeout: Jast.timeout,
        toReuse: true,
        unimportant: []
    }
    Object.extend(this.options, options || {})

    var parsed = Url.parse(url)
    $A(this.options.unimportant).each(function(param)
    {
        delete(parsed.params[param])
    })
    $A(["un", "ux", "rn", "rx", "cn", "cx" ]).each(function(param)
    {
        delete(parsed.params[param])
    })
    this.cacheId = Url.appendParams(parsed.base, parsed.params)
    this.cacheId = "Just." + this.cacheId.replace(/[=;]/g, '_')

    this.usedBy = []
    this.id = Jast.nextRequestId++
    this.state = -1
    this.status = Jast.Request.CREATE
    Jast.Request.log.debug("Create request id: " + this.id
                         + " url: " + this.url
                         + " unimportant: "
                         + Cookies.toJson(this.options.unimportant)
                         + " cacheId: " + this.cacheId
                         + " toReuse: " + this.options.toReuse
                         + " timeout: " + this.options.timeout)
    this.advanceTo(Jast.Request.UNINITIALIZED)
    this.dispatch("onCreate")

    var name
    var cached = Cookies.get(name = this.cacheId + ".T")
              || Cookies.get(name = this.cacheId + ".S")
    if (cached)
    {
        Jast.Request.log.debug("Fetch request id: " + this.id
                             + " from cookie name: " + name)
        if (Jast.isPreLoad)
        {
            Jast.pendingRequestByUrl[this.cacheId] = this
            Jast.pendingRequestById[this.id] = this
        }
        this.loaded(cached.isOk, cached.result, undefined)
        return
    }

    var request = Jast.pendingRequestByUrl[this.cacheId]
    if (request
     && this.options.toReuse
     && request.options.toReuse
     && request.state <= Jast.Request.LOADED)
    {
        Jast.Request.log.debug("Merge request id: " + this.id
                             + " with request id: " + request.id
                             + " state: "
                             + Jast.Request.stateNames[request.state]
                             + " status: "
                             + Jast.Request.statusNames[request.status])
        this.uses = request
        this.advanceTo(request.state)
        this.result = request.result
        this.status = request.status
        request.usedBy.push(this)
    }
    else
    {
        this.makeRequest()
    }
}}

/**
 * @private
 * Actually make the request for data to the server. This is done by writing a
 * script tag (using document.write before page load is done, or through DHTML
 * afterwards).
 */
Jast.Request.prototype.makeRequest = function()
{{
    this.fullUrl = Url.appendParamValue(this.url, "jastId", this.id)
    Jast.Request.log.info("Make request id: " + this.id
                        + " fullUrl: " + this.fullUrl)
    this.scriptId = "jast-script-" + this.id
    Jast.pendingRequestByUrl[this.cacheId] = this
    Jast.pendingRequestById[this.id] = this
	var isHeadNotExists = (0 == document.getElementsByTagName("HEAD").length);

	if (Jast.isPreLoad && isHeadNotExists )
    {
        Jast.Request.log.debug("Write script id: " + this.scriptId
                             + " for request id: " + this.id)
        document.write("<" + "script")
        document.write(' id="' + this.scriptId + '"')
        document.write(' type="text/javascript"')
        document.write(' src="' + this.fullUrl + '"')
        document.write("></" + "script" + ">")
    }
    else
    {
        Jast.Request.log.debug("Create script id: " + this.scriptId
                             + " for request id: " + this.id)
        script = document.createElement("script")
        script.setAttribute("id", this.scriptId)
        script.setAttribute("type", "text/javascript")
        script.setAttribute("src", this.fullUrl)
        $T("head").appendChild(script)
        if (this.options.timeout > 0)
        {
            this.setTimeout =
                setTimeout(this.timedOut.bind(this), this.options.timeout)
            Jast.Request.log.debug("Request id: " + this.id
                                 + " setTimeout: " + this.setTimeout)
        }
    }
    this.advanceTo(Jast.Request.LOADING)
}}

/**
 * @private
 * Handle a response arriving from the server.
 *
 * @param {Boolean} isOk
 *  Whether this is a success or failure result.
 *
 * @param {Object} result
 *  The server-provided result.
 *
 * @param {Hash} caching
 *  Control caching of result in cookies. See {@link Jast#response} for
 *  details.
 */
Jast.Request.prototype.loaded = function(isOk, result, caching)
{{
    Claim.isBoolean(isOk, "Jast.Request.loaded.isOk")
    if (this.state >= Jast.Request.LOADED)
    {
        Jast.Request.log.warn("Reloaded request id: " + this.id
                            + " isOk: " + isOk)
        return
    }
    Jast.Request.log.info("Loaded request id: " + this.id
                        + " isOk: " + isOk)
    if (this.setTimeout)
    {
        clearTimeout(this.setTimeout)
        Jast.Request.log.debug("Request id: " + this.id
                             + " clearTimeout: " + this.setTimeout)
        delete(this["setTimeout"])
    }

    if (caching && caching.expires || session)
    {
        var value = { isOk: isOk, result: result }
        var session = caching.session
        delete(caching["session"])
        if (caching.expires)
        {
            Jast.Request.log.debug("Cache in: " + this.cacheId + ".T for "
                                 + caching.expires + "ms")
            Cookies.set(this.cacheId + ".T", value, caching)
        }
        delete(caching["expires"])
        if (session)
        {
            Jast.Request.log.debug("Cache in: " + this.cacheId
                                 + ".S for rest of session")
            Cookies.set(this.cacheId + ".S", value, caching)
        }
    }

    this.advanceTo(Jast.Request.LOADED)
    this.result = result
    this.status = isOk ? Jast.Request.SUCCESS : Jast.Request.FAILURE
    var status = this.status
    this.usedBy.each(function(request)
    {
        request.result = result
        request.status = status
    })
    // This holds back completing requests make before onload to maximize
    // merging and ensure handlers can access all the HTML elements.
    if (!Jast.isPreLoad)
        this.complete()
}}

/**
 * @private
 *  Handle a request that timed out without a response from the server.
 */
Jast.Request.prototype.timedOut = function()
{{
    if (this.state >= Jast.Request.LOADED)
    {
        Jast.Request.log.warn("Retimedout request id: " + this.id)
        return
    }
    Jast.Request.log.info("Timedout request id: " + this.id)
    this.advanceTo(Jast.Request.LOADED)
    this.status = Jast.Request.TIMEOUT
    this.usedBy.each(function(request)
    {
        request.status = Jast.Request.TIMEOUT
    })
    this.complete()
}}

/**
 * @private
 * Complete the processing of a request following the termination of the
 * interactive phase.
 */
Jast.Request.prototype.complete = function()
{{
    Jast.Request.log.debug("Complete request id: " + this.id)
    this.advanceTo(Jast.Request.INTERACTIVE)
    this.dispatchUsed("on" + Jast.Request.statusNames[this.status])
    this.advanceTo(Jast.Request.COMPLETE)
    delete(Jast.pendingRequestByUrl[this.cacheId])
    delete(Jast.pendingRequestById[this.id])
    this.usedBy.each(function(request)
    {
        delete(Jast.pendingRequestById[request.id])
    })
    if (this.scriptId)
    {
        Jast.Request.log.debug("Remove script id: " + this.scriptId
                             + " for request id: " + this.id)
        var script = $I(this.scriptId)
        script.parentNode.removeChild(script)
    }
}}

/**
 * @private
 * Advance the request to the specified state, invoking all event handlers.
 * This steps through all intermediate states to ensure no handlers are missed.
 *
 * @param {Number} state
 *  The state to advance the request to.
 */
Jast.Request.prototype.advanceTo = function(state)
{{
    Claim.isNumber(state, "Jast.Remove.advanceTo.state")
    Claim.isTrue(0 <= state && state <= Jast.Request.COMPLETE,
                 "Jast.Remove.advanceTo.state")
    while (this.state < state)
    {
        var nextState = this.state++
        this.dispatch("on" + Jast.Request.stateNames[this.state])
        this.usedBy.each(function(request)
        {
            request.advanceTo(nextState)
        })
    }
}}

/**
 * @private
 * Dispatch an event to all request-specific and general handlers.
 *
 * @param {string} event
 *  The event to dispatch.
 */
Jast.Request.prototype.dispatch = function(event)
{{
    Claim.isString(event, "Jast.Request.dispatch.event")
    Jast.Request.log.debug("Dispatch event: " + event
                         + " for request id: " + this.id)
    if (this.options[event])
    {
        try
        {
            this.options[event](this)
        }
        catch (e) {
	Jast.Request.log.warn("Exception in " + event
                 + " for request id: " + request.id
                 + " exception: " + e.message );

		}
    }
    Jast.Responders.dispatch(event, this)
}}

/**
 * @private
 * Dispatch an event to this request and all the requests that were merged into
 * it.
 *
 * @param {string} event
 *  The event to dispatch.
 */
Jast.Request.prototype.dispatchUsed = function(event)
{{
    Claim.isString(event, "Jast.Responders.dispatchUsed.event")
    Jast.Request.log.debug("DispatchUsed event: " + event
                         + " for request id: " + this.id)
    this.dispatch(event)
    this.usedBy.each(function(request)
    {
        request.dispatchUsed(event)
    })
}}
/**
 * @fileoverview
 * Manage global Jast event handlers.
 */

/**
 * @class
 * Global Jast event handlers. Handlers registered here will be triggered
 * regardless of the particular request that caused the event. See {@link
 * Jast.Request} below for the list of events.
 * @constructor
 */
Jast.Responders = {}

/**
 * Array of current responders.
 * @type Array
 */
Jast.Responders.responders = []

/**
 * @private
 * Iterate on all responders.
 */
Jast.Responders._each = function(iterator)
{{
    Claim.isObject(iterator, "Jast.Responders._each.iterator")
    this.responders._each(iterator)
}}

/**
 * Register some global event handlers.
 *
 * @param {Hash} responderToAdd
 *  Contains a key for each event type of interest, whose value is the callback
 *  to invoke when the event occurs (regardless of the request). See {@link
 *  Jast.Request} for the list of events.  The callback is given the request
 *  object in the same way as request specific handlers.
 */
Jast.Responders.register = function(responderToAdd)
{{
    Claim.isObject(responderToAdd, "Jast.Request.register.responderToAdd")
    if (!this.include(responderToAdd))
        this.responders.push(responderToAdd)
}}

/**
 * Remove some global event handlers.
 *
 * @param {Hash} responderToRemove
 *  Must be the same object as the one given to {@link #register}.
 */
Jast.Responders.unregister = function(responderToRemove)
{{
    Claim.isObject(responderToRemove,
                   "Jast.Responders.unregister.responderToRemove")
    this.responders = this.responders.without(responderToRemove)
}}

/**
 * @private
 * Send an event to all the registered handlers.
 *
 * @param {String} event
 *  The event to dispatch. See {@link Jast.Request} for the list of events.
 *
 * @param {Jast.Request} request
 *  The request that caused the event.
 */
Jast.Responders.dispatch = function(event, request)
{{
    Claim.isString(event, "Jast.Responders.dispatch.event")
    Claim.isObject(request, "Jast.Responders.dispatch.request")
    Claim.isNumber(request.id, "Jast.Responders.dispatch.request.id")
    this.log.debug("Dispatch event " + event
                 + " for request id: " + request.id)
    this.each(function(responder)
    {
        if (responder[event])
        {
            try
            {
                responder[event](request)
            }
            catch (e) {
		        this.log.warn(" Exception in " + event
                 + " for request id: " + request.id
                 + " exception: " + e.message );
		}
        }
    })
}}

/**
 * @private
 * Sets up a "Jast.Responders" logger.
 */
Jast.Responders.preLoad = function()
{{
    Jast.Responders.log = new Log4Js.Logger("Jast.Responders")
}}

Object.extend(Jast.Responders, Enumerable)
PreLoad.actions.push(Jast.Responders.preLoad)

/*
 * Track number of active requests, like Ajax.
 * Use Log4Js, unlike Ajax.
 */
Jast.Responders.register({
    onCreate: function()
    {{
        Jast.activeRequestCount++
        Jast.Responders.log.debug("Active requests up to "
                                + Jast.activeRequestCount)
        Claim.isTrue(Jast.activeRequestCount >= 1,
                     "Jast.Responders.activeRequestCount")
    }},

    onComplete: function()
    {{
        Jast.activeRequestCount--
        Jast.Responders.log.debug("Active requests down to "
                                + Jast.activeRequestCount)
        Claim.isTrue(Jast.activeRequestCount >= 0,
                     "Jast.Responders.activeRequestCount")
    }}
})
/**
 * @fileoverview
 * Perform collected pre-load actions. Execute all collected {@link PreLoad}
 * actions when all the JavaScript libraries are created, but before the actual
 * loading of the page begins.
 */
$A(PreLoad.actions).each(function(action) { action() })
