/**
 * Filename    : cerny.js
 * Author      : Robert Cerny
 * Created     : 2006-11-10
 * Last Change : 2007-04-03
 *
 * Description:
 *   This is the file that provides the core functionality of the
 *   Cerny.js library: interception, type checking, logging,
 *   dependency declaration, configuration and dictionaries.
 *
 *   To use the Cerny.js library this file must be included in your
 *   page. The configuration file (a copy of cerny.conf.js) must be
 *   included beforehand.
 *
 *   Some conventions used:
 *
 *   If a current function or member is overwritten in a script,
 *   e.g. 'insertBefore', but will be referred to later, the new name
 *   of the member is composed of an underscore followed by the old
 *   name, e.g. '_insertBefore'.
 *
 *   If a simple name is not possible due to keyword restrictions, the
 *   name is proceeded by an underscore, e.g. '_delete'.
 *
 *   If a function takes arbitrarily many arguments, the documentation
 *   for these parameters must be called 'arguments'.
 *
 *   If the documentation for a function does not specify a return
 *   value, the function returns undefined.
 *
 * History:
 *   2007-04-03 Exceptions thrown in intercepted functions are always visible.
 *   2007-04-03 Loggers got the enabled functions.
 *   2007-04-02 CERNY.load works for global functions.
 *   2007-03-21 Logging an info when reapplying interception with CERNY.intercept.
 *   2007-03-21 Logging name of the logger of the object that is intercepted in CERNY.intercept.
 *   2007-03-16 Added additional parameter (interceptors) to CERNY.intercept.
 *   2007-03-15 Added additional parameter (interceptors) to CERNY.method.
 *   2007-03-11 Added signature, checkType and the TypeChecker interceptor.
 *   2007-03-02 Added layout and appenders to CERNY.Logger.
 *   2007-03-01 Moved loggers from CERNY to CERNY.Logger.
 *   2007-02-08 Fixed bug in intercept, which caused objects to be intercepted.
 *   2006-12-31 Added the interceptor concept.
 *   2006-12-30 Added the dump method.
 *   2006-11-10 Created.
 */

if (typeof CERNY != 'object') {
    CERNY = {};
}

/**
 * Prototypal inheritance [DCP].
 *
 * obj - the object to be the prototype of the new object.
 * return - the new object
 */
CERNY.object = function(obj) {
    obj = obj || {};
    function F() {}
    F.prototype = obj;
    return new F();
};

// Type checking is dependant on interception which is only available
// after the configuration cut. For documentation purposes the
// signature of the functions before the cut is mentioned in a
// comment.
// CERNY.signature(CERNY.object, "object", "object");

/**
 * Attach a function as a method to an object. This allows to create
 * arbitrarliy many functions around the actual function. It aims for
 * separation of concerns. The interceptors are wrapped around the
 * function starting with the last one. This allows a "natural order",
 * when filling the array regarding dependency. So more basic
 * interceptors (e.g. LogIndenter) are pushed first.
 *
 * obj - the obj to attach the function to
 * name - the name under which the function will be known to the object
 * func - the function to attach
 * interceptors - the interceptors to use; defaults to CERNY.Configuration.Interception.active
 */
CERNY.method = function(obj, name, func, interceptors) {
    var f = func, interceptor;

    if (!interceptors) {
        interceptors = CERNY.Configuration.Interception.active;
    }

    for (var i = interceptors.length - 1; i >= 0; i--) {
        interceptor = interceptors[i];
        interceptor.create = CERNY.Interceptors.create;
        f = interceptor.create(obj, name, f, i);
    }
    obj[name] = f;
};
// CERNY.signature(CERNY.method, "undefined", "object", "string", "function", ["undefined", Array]);

/**
 * Specify the signature of a function. The types can be specified
 * either by a string ("boolean", "string", "number", "object",
 * "function", "undefined", "null", "any") or by a function, against
 * which the actual value will be tested against with instanceof.
 *
 * In the future it should be possible to specify an object as a type
 * and the prototype chain is inspected for that object. Even more in
 * the future it Cerny schemas could be used.
 *
 * func - the function to specify the signature for
 * returnType - the type of the return value
 * arguments - the types of the parameters
 */
CERNY.signature = function(func, returnType) {
    var parameterTypes = [];
    for (var i = 2; i < arguments.length; i++) {
        parameterTypes[i-2] = arguments[i];
    }
    func._signature = {
        returnType: returnType,
        parameterTypes: parameterTypes
    }
};
// CERNY.signature(CERNY.signature, "undefined", "function", ["string", "function", "array"], ["undefined", "string", "function", "array"]);

/**
 * Dump a value for logging purposes. Returns the value of the
 * variable followed by it's type in braces. If the variable is a
 * string, the value is enclosed by a single quote.
 *
 * value - the value to dump
 * return - a string useful for logging
 */
CERNY.dump = function(value) {
    var encloser = "";
    if (isString(value)) {
        encloser = "'";
    }
    return encloser + value + encloser + " (" + typeof value + ")";
};
// CERNY.signature(CERNY.dump, "string", "any");

CERNY.instanceOf = function(obj, func) {
    if (isObject(obj)) {
        return obj instanceof func;
    }
};
// CERNY.signature(CERNY.instanceOf, "boolean", "object", "function");

/*
 * The following functions make type checking shorter. They are taken
 * from [DCR].
 */

function isAlien(a) {
   return isObject(a) && typeof a.constructor != 'function';
}

function isArray(a) {
    return isObject(a) && a.constructor == Array;
}

function isBoolean(a) {
    return typeof a == 'boolean';
}

function isFunction(a) {
    return typeof a == 'function';
}

function isNull(a) {
    return typeof a == 'object' && !a;
}

function isNumber(a) {
    return typeof a == 'number' && isFinite(a);
}

function isObject(a) {
    return (a && typeof a == 'object') || isFunction(a);
}

function isString(a) {
    return typeof a == 'string';
}

function isUndefined(a) {
    return typeof a == 'undefined';
}

function isRegexp(a) {
    return isObject(a) && a.constructor == RegExp;
}

function isDate(a) {
    return isObject(a) && a.constructor == Date;
}

/**
 * Create a logger. For every category there exists exactly one
 * logger.
 *
 * name - the name of the category
 * return - the logger for the category identified by name
 */
CERNY.Logger = function(name) {

    // Does a logger for this category exist already ?
    if (isObject(CERNY.Logger[name])) {
        return CERNY.Logger[name];
    }

    // A small function to look for the appropriate category and log
    // level in the configuration
    function getLogLevelStr(name) {
        var segments = name.split("."), part;
        var logLevelStr = null;
        for (var i = segments.length; i > 0 && logLevelStr == null; i--) {
            part = "";
            for (var j = 0; j < i; j++) {
                if (part != "") {
                    part += ".";
                }
                part += segments[j];
            }
            logLevelStr = CERNY.Configuration.Logger[part];
        }

        return logLevelStr || CERNY.Configuration.Logger["ROOT"] || "OFF";
    };

    // Create the logger
    var self = CERNY.object(CERNY.Logger.Logger);
    self.name = name;

    // The log level
    var logLevelStr = getLogLevelStr(name);
    self.logLevel = CERNY.Logger[logLevelStr];
    if (!isNumber(self.logLevel)) {
        CERNY.print("Invalid log level '" + logLevelStr + "' for '" + name + "'. Defaults to FATAL." +
                    "Log level must be one of 'OFF', 'FATAL', 'ERROR', 'WARN', 'INFO', 'DEBUG' or 'TRACE'.");
        self.logLevel = CERNY.Logger.FATAL;
    }

    // Store the logger
    CERNY.Logger[name] = self;
    return self;
}
// CERNY.signature(CERNY.Logger, "object", "string");

// The log levels
CERNY.Logger.OFF   = 10;
CERNY.Logger.FATAL = 20;
CERNY.Logger.ERROR = 30;
CERNY.Logger.WARN  = 40;
CERNY.Logger.INFO  = 50;
CERNY.Logger.DEBUG = 60;
CERNY.Logger.TRACE = 70;
CERNY.Logger.indent = -1;
CERNY.Logger.indentStr = CERNY.Configuration.Logger.indentStr || "  ";

// Appenders and layout for logging
CERNY.Logger.appenders = [CERNY.print];
CERNY.Logger.layout = function(date, levelName, indentStr, message, loggerName) {
    return date.getTime() + ", " + levelName + ": " + indentStr + message + " | " + loggerName;
};

// The prototype logger
(function() {

    // A function for creating logging functions
    function createLogFunction(level, levelStr) {
        return function(message, time) {
            if (level  <= this.logLevel) {
                var indentStr = "";
                for (var i = 0; i < CERNY.Logger.indent; i++) {
                    indentStr += CERNY.Logger.indentStr;
                }
                for (var i = 0; i < CERNY.Logger.appenders.length; i++) {
                    CERNY.Logger.appenders[i](CERNY.Logger.layout(new Date(), levelStr, indentStr, message, this.name));
                }
            }
        }
    };
    // CERNY.signature(createLogFunction, "function", "number", "string");

    function createEnabledFunction(level) {
        return function() {
            return level <= this.logLevel;
        }
    };
    // CERNY.signature(createEnabledFunction, "function", "number");

    CERNY.Logger.Logger = {
        fatal: createLogFunction(CERNY.Logger.FATAL, "FATAL"),
        warn:  createLogFunction(CERNY.Logger.WARN,  "WARN "),
        info:  createLogFunction(CERNY.Logger.INFO,  "INFO "),
        error: createLogFunction(CERNY.Logger.ERROR, "ERROR"),
        debug: createLogFunction(CERNY.Logger.DEBUG, "DEBUG"),
        trace: createLogFunction(CERNY.Logger.TRACE, "TRACE"),
        isFatalEnabled: createEnabledFunction(CERNY.Logger.FATAL),
        isWarnEnabled: createEnabledFunction(CERNY.Logger.WARN),
        isInfoEnabled: createEnabledFunction(CERNY.Logger.INFO),
        isErrorEnabled: createEnabledFunction(CERNY.Logger.ERROR),
        isDebugEnabled: createEnabledFunction(CERNY.Logger.DEBUG),
        isTraceEnabled: createEnabledFunction(CERNY.Logger.TRACE)
    };
})();

CERNY.logger = CERNY.Logger("CERNY");

/*
 * The Interceptor concept allows to dynamically add aspects to the
 * function calls. This concept is a very valueable help for debugging
 * and supports separations of concerns.
 */
CERNY.Interceptors = {};

/*
 * Create an interceptor.
 *
 * obj - the object to create the intercepted method on
 * name - the name under which the intercepted method will be available
 * func - the function which holds the definition of the method
 * position - the position of the interceptor in the interceptors array
 * return - an interceptor
 */
CERNY.Interceptors.create = function(obj, name, func, position) {
    var t = this;

    // Obtain the logger name
    var loggerName = "NONE";
    if (isObject(obj.logger) && isString(obj.logger.name)) {
        loggerName = obj.logger.name;
    }
    return function() {

        // The object holding information about the function call
        var call = {};
        call.arguments = arguments;
        call.signature = func._signature;

        call.logger = CERNY.Logger(loggerName + "." + name);

        t.before(call);

        // func must be applied to this and not obj, so that the
        // mechanism also works for definitions on prototype
        try {
            call.returnValue = func.apply(this, arguments);
        } catch (e) {
            // Log the exception *one* time
            if (position == 0) {

                // Exceptions should never be unnoticed either log it
                // or print it
                if (call.logger.isErrorEnabled()) {
                    call.logger.error("Exception: " + e.message);
                } else {
                    CERNY.print("Exception: " + e.message + " | " + call.logger.name);
                }
            }
            throw e;
        } finally {
            t.after(call);
        }
        return call.returnValue;
    };
};
// CERNY.signature(CERNY.Interceptors.create, "function", "object", "string", "function", "number");

/*
 * This interceptor traces the depth of the log level
 * indentation. Each function call that *is reported to the logger*
 * increases the depth in the beginning and decreases it at the
 * end.
 *
 * It should be pushed into the interceptors array before any logging
 * interceptor.
 */
CERNY.Interceptors.LogIndenter = {
    before: function(call) {
        if (call.logger.logLevel >= CERNY.Logger.TRACE) {
            CERNY.Logger.indent += 1;
        }
    },
    after: function(call) {
        if (call.logger.logLevel >= CERNY.Logger.TRACE) {
            CERNY.Logger.indent -= 1;
        }
    }
}

/*
 * This interceptor traces function calls and logs the arguments and
 * the return value.
 */
CERNY.Interceptors.Tracer = {
    before: function(call) {
        call.logger.trace("entry");
        for (var i = 0; i < call.arguments.length; i++) {
            call.logger.trace("arg " + i + ": " + CERNY.dump(call.arguments[i]));
        }
    },
    after: function(call) {
        call.logger.trace("return: " + CERNY.dump(call.returnValue));
    }
}

/*
 * This interceptor profiles function calls. It reports the time (in
 * ms) it took to complete the function call to the logger.
 */
CERNY.Interceptors.Profiler = {
    before: function(call) {
        call.logger.trace("start");
        call.start = new Date();
    },
    after: function(call) {
        call.logger.trace("stop: " + (new Date().getTime() - call.start.getTime()) + " ms");
    }
}

/*
 * This interceptor checks the type of arguments and return value as
 * defined by CERNY.signature.
 */
CERNY.Interceptors.TypeChecker = {
    before: function(call) {
        var type, argument;
        if (isObject(call.signature)) {
            for (var i = 0; i < call.signature.parameterTypes.length; i++) {
                type = call.signature.parameterTypes[i];
                argument = call.arguments[i];

                try {
                    CERNY.checkType(type, argument);
                } catch (te) {
                    if (te instanceof TypeError) {
                        call.logger.fatal("arg " + i + ": " + te.message);
                    }
                    throw te;
                }
            }

            // All exceeding arguments are checked against the last type
            for (var j = i; j < call.arguments.length; j++) {
                argument = call.arguments[j];

                try {
                    CERNY.checkType(type, argument);
                } catch (te) {
                    if (te instanceof TypeError) {
                        call.logger.fatal("arg " + i + ": " + te.message);
                    }
                    throw te;
                }
            }
        }
    },
    after: function(call) {
        if (isObject(call.signature)) {
            try {
                CERNY.checkType(call.signature.returnType, call.returnValue);
            } catch (te) {
                if (te instanceof TypeError) {
                    call.logger.fatal("return: " + te.message);
                }
                throw te;
            }
        }
    }
}

/*
 * The configuration cut.
 *
 * Anything that depends on something that is done in CERNY.configure
 * must be defined after this cut. Anything that is defined in
 * CERNY.configure will only apply after this cut.
 */
if (isFunction(CERNY.configure)) {
    CERNY.configure();
}

/**
 * Instrument methods of an object for interception. Can be called
 * multiple times with the same effect, only depending on
 * CERNY.Configuration.Interception.active or the passed interceptors.
 *
 * This function does not work on the window object in IE.
 *
 * If one overrides methods of an existing object, where interception
 * was already installed with this function and use this function
 * again to intercept the method, the old method will be installed and
 * a information will be logged. This is unfortunate, but is necessary
 * so that interception always has the same effect. Workaround: Use
 * CERNY.method to attach the methods (best to both, but definitely
 * when overriding) instead.
 *
 * obj - the object to intercept methods of
 * arguments - strings or regexps specifying which functions to intercept. If none are specified
 *             all functions are intercepted; or an array of interceptors, if not present configured
 *             interceptors are used, if more than one array is given, the last one will be used.
 * return - an array of the names of the intercepted functions
 */
CERNY._intercept = function(obj) {
    var intercepted = [], specs = [], spec, intercept, i, j, name, newName, interceptors, count, logger = CERNY.Logger("CERNY.intercept");

    // Check if obj features hasOwnProperty
    // In IE the window object doesn't.
    if (!isFunction(obj.hasOwnProperty)) {
        CERNY.print("The object of interception does not feature the method 'hasOwnProperty'.");
        CERNY.print("Interception cannot be applied.");
        return intercepted;
    }

    // Output the name of the logger, so identify of obj can be determined
    if (obj.logger) {
        logger.debug("obj.logger.name: " + obj.logger.name);
    }

    for (var i = 1; i < arguments.length; i++) {
        if (isArray(arguments[i])) {
            interceptors = arguments[i];
        } else {
            specs.push(arguments[i]);
        }
    }

    if (specs.length == 0) {
        specs.push(/.*/);
    }

    // Iterate over all parameters, that specify a function name
    for (i = 0; i < specs.length; i++) {
        spec = specs[i];

        var count = 0;

        // Iterate over all properties in object and install
        // interceptions on all functions which follow the current
        // specification
        for (name in obj) {
            intercept = false;
            if (isFunction(obj[name]) && obj.hasOwnProperty(name)) {
                if (isString(spec)) {
                    if (name == spec) {
                        intercept = true;
                    }
                } else if (isRegexp(spec)) {
                    if (name.match(spec)) {
                        intercept = true;
                    }
                }
            }

            if (intercept) {

                count += 1;

                // Create a garage for intercepted methods
                if (!isObject(obj["_intercepted"])) {
                    obj._intercepted = {};
                }

                // If interception was already installed on this
                // object for this function, get it from the garage!
                if (isFunction(obj._intercepted[name])) {
                    logger.info("Intercepted method '" + name + "' already existing . Using existing one, not passed one.");
                    obj[name] = obj._intercepted[name];
                }

                // Park the intercepted method
                obj._intercepted[name] = obj[name];

                // Install interception
                CERNY.method(obj, name, obj._intercepted[name], interceptors);
                intercepted.push(name);
            }
        }

        if (isString(spec) && count == 0) {
            logger.error("Method specified, but not intercepted: " + spec);
        }

    }

    return intercepted;
};
CERNY.signature(CERNY._intercept, Array, ["object", "function"], ["undefined", "string", RegExp, Array]);
CERNY.method(CERNY, "intercept", CERNY._intercept);

/*
 * Check the type of a value. Throws a TypeError if type of value does
 * not match type.
 *
 * type - the type to check the value against
 * value - the value to check the type of
 * throwException - whether to throw an TypeError Exception or not; defaults to true
 * return - true, if the value is of the type; false, otherwise and if
 *          throwException is false
 */
CERNY._checkType = function(type, value, throwException) {
    if (!isBoolean(throwException)) {
        throwException = true;
    }

    var logger = CERNY.Logger("CERNY.checkType"), message = null;
    switch (typeof type) {
    case "string":
        switch (type) {
        case "any":
            break;

        case "null":
            if (value !== null) {
                message = "Type error: " + CERNY.dump(value) + "should be of type null";
            }
            break;

        default:
            if (typeof value != type) {
                message = "Type error: " + CERNY.dump(value) + " should be of type " + type;
            }
            break;
        }
        break;

    case "function":
        if (!CERNY.instanceOf(value, type)) {
            message = "Type error: " + CERNY.dump(value);
            if (type.prototype.constructor.name) {
                message += " should be of type "  + type.prototype.constructor.name;
            }
        }
        break;

    case "object":
        if (isArray(type)) {
            var typeError = true;
            for (var i = 0; i < type.length && typeError; i++) {
                typeError = !CERNY.checkType(type[i], value, false);
            }

            if (typeError) {
                message = "Type error (Array): " + CERNY.dump(value);
            }
        }
        break;

    default:
        logger.error("Type not handled: " + CERNY.dump(type));
    }

    if (message) {
        if (throwException) {
            logger.debug(message);
            throw TypeError(message);
        }
        return false;
    }
    return true;
};
// CERNY.signature(CERNY._checkType, "boolean", "any", ["string","function"], ["undefined", "boolean"]);
CERNY.method(CERNY, "checkType", CERNY._checkType);

/**
 * Create a namespace in CERNY.
 * This function is inspired by the Yahoo! UI Library [YUI].
 *
 * name - The name of the namespace to create
 * parentNameSpace - The parent name space
 */
CERNY._namespace = function(name, parentNameSpace) {
    var i,
        parentNameSpace = parentNameSpace || CERNY,
        segments = name.split(".");

    for (i = 0; i < segments.length; i++) {
        if (!parentNameSpace.hasOwnProperty(segments[i])) {
            parentNameSpace[segments[i]] = {};

        }
        parentNameSpace = parentNameSpace[segments[i]];

    }
};
CERNY.signature(CERNY._namespace, "undefined", "string", ["undefined", "object"]);
CERNY.method(CERNY, "namespace", CERNY._namespace);

/**
 * Join functions into one function. The new function returns the
 * return value of the last function.
 *
 * Useful for extending event handlers on popular objects (like
 * window).
 *
 * arguments - the functions to join
 * return - the new function
 */
CERNY._joinFunctions = function() {
    var args = arguments;
    return function() {
        var result;
        for (var i = 0; i < args.length; i++) {
            result = args[i].apply(this, arguments);
        }
        return result;
    };
};
CERNY.signature(CERNY._joinFunctions, "function", "function");
CERNY.method(CERNY, "joinFunctions", CERNY._joinFunctions);

CERNY.identity = function(arg) {
    return arg;
}

CERNY.empty = function() {
};

/**
 * Check whether an expression is present during runtime.
 *
 * exp - the expression to check, a string
 * return - false, if the string does not reference anything
 *          or the string references 'undefined'
 */
CERNY._isPresent = function(exp) {
    var present = true, result, logger= CERNY.Logger("CERNY.isPresent");
    try {
        result = eval(exp);
        if (isUndefined(result)) {
            present = false;
        }
        logger.debug("result: "+ CERNY.dump(result));
    } catch (e) {
        logger.debug("exception: " + e);
        present = false;
    }
    return present;
};
CERNY.signature(CERNY._isPresent, "boolean", "string");
CERNY.method(CERNY, "isPresent", CERNY._isPresent);

/**
 * Check for the presence of expressions.
 *
 * script - the script that requires the expression
 * arguments - the exressions that are required for further execution
 * return - an array of missing expressions with 0 or more items
 */
CERNY._require = function(script) {
    var exp, present, i, missing = [], missingMsg, logger = CERNY.Logger("CERNY.require");

    for (i = 1; i < arguments.length; i++) {

        exp = arguments[i];
        logger.debug("exp: '" + exp + "'");

        if (isString(exp)) {
            present = CERNY.isPresent(exp);
            if (!present) {
                CERNY.load(CERNY.Catalog.lookup(exp));
                present = CERNY.isPresent(exp);
            }
            if (!present) {
                logger.error("Expression missing: " + exp);
                missing.push(exp);
            }
        }
    }
    logger.debug("missing: " + missing);
    if (missing.length > 0) {
        for (i = 0; i < missing.length; i++) {
            if (missingMsg) {
                missingMsg += ", ";
            } else {
                missingMsg = script + " is missing ";
            }
            missingMsg += missing[i];
        }
        logger.fatal(missingMsg + "!");
        CERNY.print(missingMsg + "!");
    }

    return missing;
};
CERNY.signature(CERNY._require, Array, "string", ["undefined", "string"]);
CERNY.method(CERNY, "require", CERNY._require);


// ie6
if (typeof XMLHttpRequest == 'undefined') {
    XMLHttpRequest = function() {
        return new ActiveXObject("Microsoft.XMLHTTP");
    };
}

/**
 * Load a script. This function is called in require. It has different
 * implementations in various environments (browser, Rhino [RHI]). The
 * default runtime enviornment is the browser.
 *
 * location - the location of the script to load
 */
CERNY._load = function(location) {
    if (isUndefined(location)) {
        return;
    }
    var logger = CERNY.Logger("CERNY.load");
    var request = new XMLHttpRequest();
    request.open("GET", location, false);
    try {
        request.send(null);
        if (request.status < 400) {
            var sourceCode = request.responseText;
            if (window.execScript) {
                window.execScript(sourceCode);
            } else {
                window.eval(sourceCode);
            }
        } else {
            throw new Error("HTTP status code: " + request.status + " " + request.statusText);
        }
    } catch (e) {
        logger.error("Script at location '" + location + "' could not be loaded. " +
                     "Exception: " + e.message);
    }
};
CERNY.signature(CERNY._load, "undefined", ["undefined", "string"]);
CERNY.method(CERNY, "load", CERNY._load);

/**
 * Dictionary maker.
 *
 * A dictionary is a mapping from terms to definitions. Definitions
 * may contain nested terms. The process of evaluating a term is done
 * by lookup.
 *
 * obj - the obj this dictionary will inherit from
 * return - a Dictionary object
 */
CERNY.Dictionary = function(obj) {
    var self = CERNY.object(obj);
    self.logger = CERNY.Logger("CERNY.Dictionary");
    CERNY.method(self, "lookup", CERNY.Dictionary_lookup);
    return self;
};
CERNY.signature(CERNY.Dictionary, "object", "object");

/**
 * Lookup a term in this dictionary.
 *
 * term - the term to lookup
 * return - the value of the term in the dictionary
 *          or undefined if the term is unknown
 */
CERNY.Dictionary_lookup = function(term) {
    var i,
        value = this[term],
        subTerms,
        subTerm,
        subTermValue;

    if (value) {
        subTerms = value.match(/{.*?}/g);
        if (subTerms) {
            for (i = 0; i < subTerms.length; i++) {

                // Lookup the next sub term
                subTerm = subTerms[i].substring(1, subTerms[i].length - 1);
                if (subTerm !== term) {
                    subTermValue = this.lookup(subTerm);

                    // Replace all occurrences
                    while (value.indexOf(subTerm) >= 0) {
                        value = value.replace("{" + subTerm + "}",
                                              subTermValue);
                    }
                }
            }
        }
    }
    return value;
};
CERNY.signature(CERNY.Dictionary_lookup, ["undefined", "string"], "string");

CERNY.Catalog = CERNY.Dictionary(CERNY.Configuration.Catalog);

CERNY.require("CERNY");

CERNY.namespace("text");
CERNY.namespace("json");
CERNY.namespace("js");
CERNY.namespace("js.doc");

CERNY.Glossary = {
    "Appender":
      "A function used for log output. It must take one string parameter.",
    "Fulfilling a predicate":
      "An object or value is said to fulfill a predicate, if it returns true on application.",
    "Interception":
      "Method call interception. A programming technique to allow separation of concerns.",
    "Predicate":
      "A function which takes an argument and returns true or false.",
    "Script":
      "A file containing JavaScript code."
};

CERNY.References = {
    "DCP": {title: "Prototypal Inheritance in JavaScript",
            author: "Douglas Crockford",
            uri: "http://javascript.crockford.com/prototypal.html"},
    "DCR": {title: "Remedial JavaScript",
            author: "Douglas Crockford",
            uri: "http://javascript.crockford.com/remedial.html"},
    "JSO": {title: "Introducing JSON",
            author: "Douglas Crockford",
            uri: "http://www.json.org/"},
    "RHI": {title: "Rhino: JavaScript for Java",
            uri: "http://www.mozilla.org/rhino/"},
    "YUI": {title: "The YUI library",
            uri: "http://sourceforge.net/projects/yui"}
};

