
(function ($) {
  $.extend({

    /* Return an array of the keys of an object.
     *   obj -- the object whose keys to return
     *   non_local -- if true, return keys inherited from up the
     *                prototype chain.  [DEFAULT: false].
     */
    'keys': function (obj, non_local) {
        var k, keys = [];
        for (k in obj) {
          if (non_local || obj.hasOwnProperty(k)) {
            keys.push(k);
          }
        }
        return keys;
    },

    /* Function to fix scope bindings.
     *   target -- object to bind to the "this" keyword
     *   f -- either a function or else the string name of a property of
     *        target (which must be a function)
     *  
     * Returns a function which calls f with the scope bound to target.
     */
    'scope': function (target, f) {
        if (typeof(f) === 'string') {
            f = target[f];
        }
        // TODO: Check this is supported in IE6.
        return function () { return f.apply(target, arguments); };
    },

    /* Bind a list of members on the target.
     *
     * Usage: $.bindMethods(target, 'handler', 'run', 'clear', ...)
     *
     * For each argument after the first, looks in object for that
     * argument and (if it is not already local) replaces it with
     * a "bound" version.
     *
     * If no arguments are given beyond the target itself then
     * all of its non-personal, functional members will be shadowed
     * by bound, personal members.
     *
     * This utility allows an object to escape the "callback problem"
     * where an object's methods don't remain bound to it when passed
     * as callbacks.  The methods bound with this function will work
     * "correctly".
     * 
     * Note that this is expensive to do in general; it creates a new
     * closure for each method bound to each object.  So we don't do
     * it generically, only where its necessary.
     */
    'bindMethods': function () {
        var args = $.makeArray(arguments),
            target = args.shift();
        if (args.length === 0) {
          // Empty args, so loop over all properties of target.
          args = $.keys(target, true);
        };
        $.map(args, function (k) {    
            if (typeof(target[k]) === 'function' &&
                !(target.hasOwnProperty(k))) {
                target[k] = $.scope(target, k);
            } 
        });
    },

    /* Utility for param setting for functions.
     * This is the same as $.extend(target, defaults, params),
     * save that if the default value is 'REQUIRED' then an 
     * Error will be thrown if the required key is missing from
     * params.
     *
     * Note that any param-key which is found in params will
     * overwrite the default value, even if the value of that key
     * in params if falsey.  This allows us to pass undefined,
     * null, 0 etc as valid params and have them override defaults,
     * but it means client code can't assume that setting a value
     * to undefined or null is the same as removing it.
     *
     * Note that if only two arguments are passed they are taken to
     * be defaults and params and target is initialized to a new,
     * empty object.
     *
     * This function returns target so you can create and populate
     * a new target easily.
     */
    'params': function () {
        var target, defaults, params;
        if (arguments.length === 3) {
            target = arguments[0];
            defaults = arguments[1];
            params = arguments[2];
        } else if (arguments.length === 2) {
            target = {};
            defaults = arguments[0];
            params = arguments[1];
        } else {
            throw new Error("Invalid arguments: [" + $.makeArray(arguments).join(',') + "]");
        }

        $.each(defaults, function (k, def_val) {
            if (params.hasOwnProperty(k)) {
                target[k] = params[k];
            } else if (def_val === 'REQUIRED') {
                throw new Error("Missing required parameter '" + k + "'");
            } else {
                target[k] = def_val;
            }
        });
        return target;
    },

    /* Prototype extension a la Crockford.
     * See http://javascript.crockford.com/prototypal.html for details.
     * 
     * Returns a new object which inherits from the prototype object, proto.
     * If the 2nd argument is given it should be an object of properties
     * to extend (ie. shallowly copy) into the new object.
     */
    'proto': function (proto, overrides) {
        var new_obj, 
            F = function () {};
        F.prototype = proto;
        new_obj = new F();
        if (overrides) { $.extend(new_obj, overrides); }
        return new_obj;
    },

    /* Factory version of the above.  This basically provides all the
     * intelligence of eg. Prototype's Class object, save for the
     * specific function hooks.
     */
    'protoFactory': function (proto) {
        return function (params) {
            return $.proto(proto, params);
        };
    },

    /* Super function.  Check up the prototype chain to see if there
     * is a higher level definition of a function.
     *
     * Usage: 
     *
     *    SubClass.prototype.someMethod = function (x, y) {
     *        return 2 * $.super(this, 'someMethod', x, y);
     *    }
     *
     * Returns: the result of the super-method call or undefined.
     */
    'super': function () {
        var args = $.makeArray(arguments),
            target = args.shift(),
            method = args.shift();
        if (target.constructor && target.constructor.prototype && 
                typeof(target.constructor.prototype[method]) === 'function') {
            return target.constructor.prototype[method].apply(target, args);
        } else {
            return undefined;
        }
    },

    /* Empty function.  Always handy. */
    'e': function () {},

    /* Return a function which, when called, always returns the specified
     * return value.
     */
    'r': function (rtn) {
        return function () { return rtn; };
    },

    /* The identity function. */
    'i': function (x) { return x; },

    /* Truthiness to boolean conversion.
     */
    'bool': function (x) { return x ? true : false; },

    /* From Crockford as well: fix JS native typeof to correctly report
     * Arrays and nulls.
     */
    'typeOf': function (value) {
        var s = typeof(value);
        if (s === 'object') {
            if (value) {
                if (value instanceof Array) {
                    s = 'array';
                }
            } else {
                s = 'null';
            }
        }
        return s;
    },

    /* Given a regex and a string, remove all matches for the regex from the
     * string, returning the resultant string.
     */
    'stripAll': function (str, re) {
        var sc;
        while (true) {
            sc = str.replace(re, ''); 
            if (sc == str) { return sc; }
            str = sc;
        }
    },

    /* Take a given string and parse it as a potential number, removing non-number
     * characters.  Returns a Number, or NaN if the string contains no numeric characters.
     *
     * Note that this is not safe for all strings.  For instance, "I am 24 today.  I was 
     * born on the 4th of July!" will parse as 24.4.  Caveat coder.
     */
    'parseNumber': function (poti) {
        var cleaned = $.stripAll(poti, /[^\d\.]+/);
        return (cleaned.length > 0) ? Number(cleaned) : Number.NaN;
    }

  });

})(jQuery);
