// vim: et:ts=4:sw=4:sts=4:ft=javascript

// OxJS (c) 2011 0x2620, dual-licensed GPL/MIT, see http://oxjs.org for details

'use strict';

/*
Some conventions:
    Functions
        - only one var statement, in the first line of the function
        - return only once, from the last line of the function
    Variable names
        arg         argument
        args        arguments
        arr         array
        canFoo      boolean
        callback    callback function
        col         collection (array, string or object)
        date        date
        fn          function
        hasFoo      boolean
        i           index (integer key)
        isFoo       boolean
        k           key (of a key/value pair)
        key         key (of a key/value pair)
        max         maximum value
        min         minumum value
        num         number
        obj         object
        re          regexp
        ret         return value
        v           value (of a key/value pair)
        val         value (of a key/value pair)
    Indentation
        Variable definitions
            var a = {
                    key: value,
                    key: value,
                    key: value
                },
                b = {key: value},
                c = {key: value};
        Method chaining
            Obj.fnA({
                    key: value,
                    key: value,
                    key: value
                })
                .fnB({key: val})
                .fnC({key: val});
        Simple conditionals
            condition && expression;
        Conditionals
            if (condition) {
                expression;
            }
        Conditionals with long conditions
            if (
                condition
                && condition
                && condition
            ) {
                expression;
            }
        Ternary operator
            condition ? expression : expression;
        Ternary operator with long conditions or expressions
            condition ? expression
                : condition ? expression
                : expression;
*/

// FIXME: add memoize

// todo: check http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
// also see https://github.com/tlrobinson/narwhal/blob/master/lib/util.js

/*@
Ox <f> The <code>Ox</code> object
    See <code>Ox.wrap</code> for details.
    (value) -> <o> wrapped value
    value <*> Any value
@*/

window.Ox = function(val) {
    return Ox.wrap(val);
};

/*@
Ox.load <f> Loads a module
    A module named "Test" provides <code>Ox.Test/Ox.Test.js</code>, in which it
    defines one method, <code>Ox.load.Test</code>, that takes two arguments,
    <code>options</code> and <code>callback</code>, and calls
    <code>callback</code> with one argument, <code>true</code> for success or
    <code>false</code> if an error occurred. Generally, the module should
    define <code>Ox.Test</code> and attach its own methods there.
    (module, callback)          -> <u> undefined
    (module, options, callback) -> <u> undefined
    (modules, callback)         -> <u> undefined
    module   <s> Module name
    modules  <o> Multiple modules {name: options, ...}
    options  <o> Module options
    callback <f> Callback function
        success <b> If true, the module has been loaded successfully
@*/

Ox.load = function() {
    var callback = arguments[arguments.length - 1],
        counter = 0,
        isObject = Ox.isObject(arguments[0]),
        length,
        modules = isObject ? arguments[0] : {},
        success = 0;
    if (!isObject) {   
        modules[arguments[0]] = Ox.isObject(arguments[1]) ? arguments[1] : {};
    }
    length = Ox.len(modules);
    Ox.forEach(modules, function(options, module) {
        Ox.loadFile(Ox.PATH + 'Ox.' + module + '/Ox.' + module + '.js', function() {
            Ox.load[module](options, function(s) {
                success += s;
                ++counter == length && callback(success == counter);
            });
        });
    });
};

/*@
Ox.localStorage <o> localStorage wrapper
    () -> <o> key:value pairs
    (key) -> <*> value
    (key, val) -> <f> localStorage object
    ({key, val}) -> <f> localStorage object
@*/
Ox.localStorage = function(namespace) {
    if (!window.localStorage) {
        window.localStorage = {};
    }
    function storage(key, val) {
        var args, ret, value;
        if (arguments.length == 0) {
            ret = {};
            Ox.forEach(localStorage, function(val, key) {
                if (Ox.startsWith(key, namespace + '.')) {
                    ret[key.substr(namespace.length + 1)] = JSON.parse(val);
                }
            });
        } else if (arguments.length == 1 && typeof key == 'string') {
            // This gets called by Ox.Log before Type.js has loaded
            value = localStorage[namespace + '.' + key];
            ret = value === void 0 ? void 0 : JSON.parse(value);
        } else {
            args = Ox.makeObject(arguments);
            Ox.forEach(args, function(val, key) {
                localStorage[namespace + '.' + key] = JSON.stringify(val);
            });
            ret = this;
        }
        return ret;
    };
    storage.delete = function(key) {
        var keys = arguments.length == 0
            ? Object.keys(storage())
            : [key];
        keys.forEach(function(key) {
            delete localStorage[namespace + '.' + key];
        });
    };
    return storage;
};

/*@
Ox.Log <f> Logging module
@*/
Ox.Log = (function() {
    var storage = Ox.localStorage('Ox'),
        log = storage('log') || {filter: [], filterEnabled: true},
        that = function() {
            var ret;
            if (arguments.length == 0) {
                ret = log;
            } else {
                ret = that.log.apply(null, arguments);
            }
            return ret;
        };
    that.filter = function(val) {
        if (!Ox.isUndefined(val)) {
            that.filter.enable();
            log.filter = Ox.toArray(val);
            storage('log', log);
        }
        return log.filter;
    };
    that.filter.add = function(val) {
        return that.filter(Ox.unique(Ox.merge(log.filter, Ox.toArray(val))));
    };
    that.filter.disable = function() {
        log.filterEnabled = false;
        storage('log', log);
    };
    that.filter.enable = function() {
        log.filterEnabled = true;
        storage('log', log);
    };
    that.filter.remove = function(val) {
        val = Ox.toArray(val);
        return that.filter(log.filter.filter(function(v) {
            return val.indexOf(v) == -1;
        }));
    };
    that.log = function() {
        var args = Ox.makeArray(arguments), date, ret;
        if (!log.filterEnabled || log.filter.indexOf(args[0]) > -1) {
            date = new Date();
            args.unshift(
                Ox.formatDate(date, '%H:%M:%S.') + (+date).toString().substr(-3)
            );
            window.console && window.console.log.apply(window.console, args);
            ret = args.join(' ');
        }
        return ret;
    };
    return that;
}());

/*@
Ox.print <f> Prints its arguments to the console
    (arg, ...) -> <s> String
        The string contains the timestamp, the name of the caller function, and
        any arguments, separated by spaces
    arg <*> any value
    > Ox.print('foo').split(' ').pop()
    "foo"
@*/

Ox.print = function() {
    var args = Ox.makeArray(arguments),
        date = new Date();
    args.unshift(
        Ox.formatDate(date, '%H:%M:%S.') + (+date).toString().substr(-3)/*,
        (arguments.callee.caller && arguments.callee.caller.name)
        || '(anonymous)'*/
    );
    window.console && window.console.log.apply(window.console, args);
    return args.join(' ');
};

/*@
Ox.uid <f> Returns a unique id
    () -> <n> Unique id
    > Ox.uid() != Ox.uid()
    true
@*/

Ox.uid = (function() {
    var uid = 0;
    return function() {
        return uid++;
    };
}());

/*@
Ox.wrap <f> Wraps a value so that one can directly call any Ox function on it
    <code>Ox(value)</code> is a shorthand for <code>Ox.wrap(value)</code>.
    (value) -> <o> wrapped value
        chain <f> Wrap return values to allow chaining
        value <f> Unwrap the value wrapped by <code>chain()</chain>
    value <*> Any value
    > Ox("foobar").repeat(2)
    "foobarfoobar"
    > Ox("foobar").chain().reverse().toTitleCase().value()
    "Raboof"
    > Ox.wrap("foobar").value()
    "foobar"
@*/

Ox.wrap = function(val, chained) {
    // somewhat inspired by underscore.js
    var wrapper = {
        chain: function() {
            wrapper.chained = true;
            return wrapper;
        },
        chained: chained || false,
        value: function() {
            return val;
        }
    };
    Object.getOwnPropertyNames(Ox).forEach(function(name) {
        if (
            ['arguments', 'callee', 'caller', 'length'].indexOf(name) == -1
            && name[0] == name[0].toLowerCase()
            && Ox.isFunction(Ox[name])
        ) {
            wrapper[name] = function() {
                var args = Array.prototype.slice.call(arguments), ret;
                args.unshift(val);
                ret = Ox[name].apply(Ox, args);
                return wrapper.chained ? Ox.wrap(ret, true) : ret;
            };
        }
    });
    return wrapper;
};