'use strict';

/*@
Ox.avg <f> Returns the average of an array's values, or an object's properties
    (collection) -> <n> Average value
    collection <[n]|o> Array or object with numerical values
    > Ox.avg([-1, 0, 1])
    0
    > Ox.avg({a: 1, b: 2, c: 3})
    2
    > Ox.avg('avg is 0.1')
    0.1
@*/
Ox.avg = function(obj) {
    return Ox.sum(obj) / Ox.len(obj);
};

/*@
Ox.contains <f> Tests if a collection contains a value
    > Ox.contains(['foo', 'bar'], 'foo')
    true
    > Ox.contains({foo: 'bar'}, 'bar')
    true
    > Ox.contains({foo: 'bar'}, 'foo')
    false
    > Ox.contains("foobar", "bar")
    true
@*/ 
Ox.contains = function(col, val) {
    /*
    // fixme: rename to Ox.has or Ox.isIn?
    // then it'd become convenient for arrays
    */
    return (Ox.isObject(col) ? Ox.values(col) : col).indexOf(val) > -1;
};

/*@
Ox.copy <f> Returns a (shallow or deep) copy of an object or array
    > (function() { var a = ['v'], b = Ox.copy(a); a[0] = null; return b[0]; }())
    'v'
    > (function() { var a = {k: 'v'}, b = Ox.copy(a); a.k = null; return b.k; }())
    'v'
    > Ox.clone(0)
    0
@*/
Ox.copy = Ox.clone = function(col, deep) {
    // fixme: copy or clone?
    // fixme: is there any use case for shallow copy?
    var ret = Ox.isArray(col) ? [] : {};
    if (deep) {
        Ox.forEach(col, function(val, key) {
            ret[key] = ['array', 'object'].indexOf(Ox.typeOf(val)) > -1
                ? Ox.clone(val, true) : val;
        });
    } else {
        ret = Ox.isArray(col) ? col.slice()
            : Ox.isObject(col) ? Ox.extend({}, col)
            : col;
    }
    return ret;
};

/*@
Ox.count <f> Counts the occurences of values in a collection
    > Ox.count(['f', 'o', 'o'])
    {f: 1, o: 2}
    > Ox.count({a: 'f', b: 'o', c: 'o'})
    {f: 1, o: 2}
    > Ox.count('foo')
    {f: 1, o: 2}
@*/
Ox.count = function(arr) {
    var obj = {};
    Ox.forEach(arr, function(v) {
        obj[v] = (obj[v] || 0) + 1;
    });
    return obj;
};

/*@
Ox.every <f> Tests if every element of a collection satisfies a given condition
    Unlike <code>[].every()</code>, <code>Ox.every()</code> works for arrays,
    objects and strings.
    > Ox.every([0, 1, 2], function(v, i) { return i == v; })
    true
    > Ox.every({a: 1, b: 2, c: 3}, function(v) { return v == 1; })
    false
    > Ox.every("foo", function(v) { return v == 'f'; })
    false
    > Ox.every([true, true, true])
    true
@*/
Ox.every = function(col, fn) {
    return Ox.filter(Ox.values(col), fn || function(v) {
        return v;
    }).length == Ox.len(col);
};

/*@
Ox.filter <f> Filters a collection by a given condition
    Unlike <code>[].filter()</code>, <code>Ox.filter()</code> works for arrays,
    objects and strings.
    > Ox.filter([2, 1, 0], function(v, i) { return v == i; })
    [1]
    > Ox.filter({a: 'c', b: 'b', c: 'a'}, function(v, k) { return v == k; })
    {b: 'b'}
    > Ox.filter(' foo bar ', function(v) { return v != ' '; })
    'foobar'
@*/
Ox.filter = function(col, fn) {
    var type = Ox.typeOf(col),
        ret = type == 'array' ? [] : type == 'object' ? {} : '';
    Ox.forEach(col, function(val, key) {
        if (fn(val, key)) {
            if (type == 'array') {
                ret.push(val);
            } else if (type == 'object') {
                ret[key] = val;
            } else {
                ret += val;
            }
        }
    });
    return ret;
};

/*@
Ox.find <f> Returns array elements that match a string
    Returns an array of two arrays, the first containing leading matches
    (exact match first), the second containing non-leading matches
    > Ox.find(['foo', 'bar', 'foobar', 'barfoo'], 'foo')
    [['foo', 'foobar'], ['barfoo']]
@*/
// fixme: wouldn't it make more sense to return just one array?
Ox.find = function(arr, str) {
    var ret = [[], []];
    str = str.toLowerCase();
    arr.map(function(v) {
        return v.toLowerCase();
    }).forEach(function(v, i) {
        var index = v.indexOf(str);
        index > -1 && ret[index == 0 ? 0 : 1][v == str ? 'unshift' : 'push'](arr[i]);
    });
    return ret;
};

/*@
Ox.forEach <f> forEach loop
    <code>Ox.forEach()</code> loops over arrays, objects and strings.
    Returning <code>false</code> from the iterator function acts like a
    <code>break</code> statement (unlike <code>[].forEach()</code>, like
    <code>$.each()</code>). The arguments of the iterator function are
    <code>(value, key)</code> (like <code>[].forEach()</code>, unlike
    <code>$.each()</code>).
    (collection, callback) <a|o|s> The collection
    (collection, callback, includePrototype) <a|o|s> The collection
    collection <a|o|s> An array, object or string
    callback <f> Callback function
        value <*> Value
        key <n|s> Key
    includePrototype <b|false> If true, include prototype properties
    <script>
        Ox.test.string = "";
        Ox.forEach(["f", "o", "o"], function(v, i) { Ox.test.string += i; });
        Ox.forEach({a: "f", b: "o", c: "o"}, function(v, k) { Ox.test.string += k; });
        Ox.forEach("foo", function(v) { Ox.test.string += v; });
    </script>
    > Ox.test.string
    "012abcfoo"
@*/
Ox.forEach = function(col, fn, includePrototype) {
    var isObject = Ox.isObject(col), key;
    // Safari will not loop through an arguments array
    col = Ox.isArguments(col) ? Ox.makeArray(col) : col;
    for (key in col) {
        key = isObject ? key : parseInt(key);
        // fixme: fn.call(context, obj[key], key, obj) may be more standard...
        if ((
            includePrototype || Object.hasOwnProperty.call(col, key)
        ) && fn(col[key], key) === false) {
            break;
        }
    }
    return col;
};

/*@
Ox.getIndex <f> Returns the first array index of an object where obj[key] is val
    > Ox.getIndex([{a: 1}, {a: 2}, {a: 1}], 'a', 2)
    1
    > Ox.getIndex([{a: 1}, {a: 2}, {a: 1}], 'a', 1)
    0
    > Ox.getIndex([{a: 1}, {a: 2}, {a: 1}], 'a', 0)
    -1
@*/
Ox.getIndex = function(arr, key, val) {
    var ret = -1;
    Ox.forEach(arr, function(obj, ind) {
        if (obj[key] === val) {
            ret = ind;
            return false;
        }
    });
    return ret;
};

/*@
Ox.getIndexById <f> Returns the first array index of an object with a given id
    > Ox.getIndexById([{id: 'foo', str: 'Foo'}, {id: 'bar', str: 'Bar'}], 'foo')
    0
@*/
Ox.getIndexById = function(arr, id) {
    return Ox.getIndex(arr, 'id', id);
};

/*@
Ox.getObject <f> Returns the first object in an array where obj[key] is val
    > Ox.getObject([{a: 1, i: 0}, {a: 2, i: 1}, {a: 1, i: 2}], 'a', 2)
    {a: 2, i: 1}
    > Ox.getObject([{a: 1, i: 0}, {a: 2, i: 1}, {a: 1, i: 2}], 'a', 1)
    {a: 1, i: 0}
    > Ox.getObject([{a: 1, i: 0}, {a: 2, i: 1}, {a: 1, i: 2}], 'a', 0)
    null
@*/
Ox.getObject = function(arr, key, val) {
    var ret = null;
    Ox.forEach(arr, function(obj) {
        if (obj[key] === val) {
            ret = obj;
            return false;
        }
    });
    return ret;
};

/*@
Ox.getObjectById <f> Returns the first object in an array with a given id
    > Ox.getObjectById([{id: 'foo', str: 'Foo'}, {id: 'bar', str: 'Bar'}], 'foo')
    {id: "foo", str: "Foo"}
@*/
Ox.getObjectById = function(arr, id) {
    return Ox.getObject(arr, 'id', id);
};

/*@
Ox.getset <f> Generic getter and setter function
    See examples for details.
    # Usage --------------------------------------------------------------------
    Ox.getset(options, args=[])                              -> <o> all options
    Ox.getset(options, args=[key])                           -> <*> options[key]
    Ox.getset(options, args=[key, value], callback, context) -> <f|o> context
        sets options[key] to value and calls fn(key, value)
        if the key/value pair was added or modified
    Ox.getset(options, args=[{key: value}], callback, context) -> <f|o> context
        sets multiple options and calls fn(key, value)
        for every key/value pair that was added or modified
    # Arguments ----------------------------------------------------------------
    options  <obj> Options object (key/value pairs)
    args     <arr> The arguments "array" of the caller function
    callback <fun> Callback function 
        The callback is called for every key/value pair that was added or
        modified.
        key      <s> Key
        value    <*> Value
    context  <obj> The parent object of the caller function (for chaining)
    # Examples -----------------------------------------------------------------
    <script>
        Ox.test.object = new function() {
            var options = {},
                setOption = function(key, value) {
                    // handle added or modified options
                },
                that = this;
            that.options = function() {
                return Ox.getset(options, arguments, setOption, that);
            };
            return that;
        };
    </script>
    > Ox.test.object.options("key", "val").options("key")
    "val"
    > Ox.test.object.options({foo: "foo", bar: "bar"}).options()
    {"key": "val", "foo": "foo", "bar": "bar"}
@*/
Ox.getset = function(obj, args, callback, context) {
    var obj_ = Ox.clone(obj), ret;
    if (args.length == 0) {
        // []
        ret = obj_;
    } else if (args.length == 1 && !Ox.isObject(args[0])) {
        // [key]
        ret = Ox.clone(obj[args[0]]);
    } else {
        // [key, val] or [{key: val, ...}]
        args = Ox.makeObject(args);
        obj = Ox.extend(obj, args);
        Ox.forEach(args, function(val, key) {
            if (!obj_ || !Ox.isEqual(obj_[key], val)) {
                callback && callback(key, val);
            }
        });
        ret = context;                            
    }
    return ret;
}

/*@
Ox.isEmpty <f> Returns true if a collection is empty
    > Ox.isEmpty([])
    true
    > Ox.isEmpty({})
    true
    > Ox.isEmpty('')
    true
    > Ox.isEmpty(function() {})
    true
    > Ox.isEmpty(function(a) {})
    false
    > Ox.isEmpty(null)
    false
    > Ox.isEmpty(0)
    false
    > Ox.isEmpty()
    false
@*/
Ox.isEmpty = function(val) {
    return Ox.len(val) == 0;
};

/*@
Ox.keys <f> Returns the keys of a collection
    Unlike <code>Object.keys()</code>, <code>Ox.keys()</code> works for arrays,
    objects and strings.
    > Ox.keys([1, 2, 3])
    [0, 1, 2]
    > Ox.keys([1,,3])
    [0, 2]
    # fixme?
    # > Ox.keys([,])
    # [0]
    > Ox.keys({a: 1, b: 2, c: 3})
    ['a', 'b', 'c']
    > Ox.keys('abc')
    [0, 1, 2]
@*/
// fixme: is this really needed? arrays... ok... but strings??
Ox.keys = function(obj) {
    var keys = [];
    Ox.forEach(obj, function(v, k) {
        keys.push(k);
    });
    return keys.sort();
};

/*@
Ox.last <f> Gets or sets the last element of an array
    Unlike <code>foobarbaz[foobarbaz.length - 1]</code>,
    <code>Ox.last(foobarbaz)</code> is short.
    <script>
        Ox.test.array = [1, 2, 3];
    </script>
    > Ox.last(Ox.test.array)
    3
    > Ox.last(Ox.test.array, 4)
    [1, 2, 4]
    > Ox.test.array
    [1, 2, 4]
@*/
Ox.last = function(arr, val) {
    var ret;
    if (arguments.length == 1) {
        ret = arr[arr.length - 1];
    } else {
        arr[arr.length - 1] = val;
        ret = arr;
    }
    return ret;
};

/*@
Ox.len <f> Returns the length of an array, function, object or string
    Not to be confused with <code>Ox.length</code>, which is the
    <code>length</code> property of the <code>Ox</code> function
    (<code>1</code>). // FIXME: 1 becomes 67 in DocPanel
    > Ox.len([1, 2, 3])
    3
    > Ox.len([,])
    1
    > Ox.len({a: 1, b: 2, c: 3})
    3
    > Ox.len(function(a, b, c) {})
    3
    > Ox.len('abc')
    3
@*/
Ox.len = function(col) {
    var type = Ox.typeOf(col);
    return ['array', 'function', 'string'].indexOf(type) > -1 ? col.length
        : type == 'object' ? Ox.values(col).length : void 0;
};

/*@
Ox.makeArray <f> Takes an array-like object and returns a true array
    Alias for <code>Array.prototype.slice.call</code>
    (value) -> <a> True array
    value <*> Array-like object
    > (function() { return Ox.makeArray(arguments); }("foo", "bar"))
    ["foo", "bar"]
    > Ox.makeArray("foo")
    ["f", "o", "o"]
    > Ox.makeArray({0: "f", 1: "o", 2: "o", length: 3})
    ["f", "o", "o"]
@*/
Ox.makeArray = /MSIE/.test(navigator.userAgent)
    ? function(col) {
        var i, len, ret = [];
        try {
            ret = Array.prototype.slice.call(col);
        } catch(e) {
            // handle MSIE NodeLists
            len = col.length;
            for (i = 0; i < len; i++) {
                ret[i] = col[i];
            }
        }
        return ret;
    } 
    : function(col) {
        return Array.prototype.slice.call(col);
    };

/*@
Ox.makeObject <f> Takes an array and returns an object
    <code>Ox.makeObject</code> is a helper for functions with two alternative
    signatures like <code>('key', 'val')</code> and <code>({key: 'val'})</code>.
    > (function() { return Ox.makeObject(arguments); }({foo: 1, bar: 2}))
    {foo: 1, bar: 2}
    > (function() { return Ox.makeObject(arguments); }('foo', 1))
    {foo: 1}
    > (function() { return Ox.makeObject(arguments); }('foo'))
    {foo: void 0}
    > (function() { return Ox.makeObject(arguments); }())
    {}
@*/
Ox.makeObject = function(obj) {
    var ret = {};
    if (Ox.isObject(obj[0])) {
        // ({foo: 'bar'})
        ret = obj[0];
    } else if (obj.length) {
        // ('foo', 'bar')
        ret[obj[0]] = obj[1]
    }
    return ret;
};

/*@
Ox.map <f> Transforms the values of an array, object or string
    Unlike <code>[].map()</code>, <code>Ox.map()</code> works for arrays,
    objects and strings. Returning <code>null</code> from the iterator
    function will remove the element from the collection.
    > Ox.map([0, 0, 0], function(v, i) { return v == i; })
    [true, false, false]
    > Ox.map({a: 'a', b: 'a', c: 'a'}, function(v, k) { return v == k; })
    {a: true, b: false, c: false}
    > Ox.map("000", function(v, i) { return v == i; })
    [true, false, false]
    > Ox.map([0, 1, 2, 4], function(v, i) { return v ? i == v : null; })
    [true, true, false]
    # fixme?
    # > Ox.map([,], function(v, i) { return i; })
    # [0]
@*/

// FIXME: it would sometimes be nice to have Ox.map(3, function(i) {...})
// instead of Ox.range(3).map(function(i) {...})

Ox.map = function(obj, fn) {
    // fixme: return null to filter out may be a bit esoteric
    var isObject = Ox.isObject(obj),
        ret = isObject ? {} : [];
    Ox.forEach(obj, function(val, key) {
        var map;
        if ((map = fn(val, key)) !== null) {
            ret[isObject ? key : ret.length] = map;
        }
    });
    return ret;
};

/*@
Ox.max <f> Returns the maximum value of a collection
    > Ox.max([1, 2, 3])
    3
    > Ox.max({a: 1, b: 2, c: 3})
    3
    > Ox.max('123')
    3
@*/
Ox.max = function(col) {
    return Math.max.apply(Math, Ox.values(col));
};

/*@
Ox.min <f> Returns the minimum value of a collection
    > Ox.min([1, 2, 3])
    1
    > Ox.min({a: 1, b: 2, c: 3})
    1
    > Ox.min('123')
    1
@*/
Ox.min = function(col) {
    return Math.min.apply(Math, Ox.values(col));
};

/*@
Ox.setPropertyOnce <f> Sets a property once
    Given a array of objects, each of which has a property with a boolean
    value, this sets exactly one of these to true, and returns the index
    of the object whose property is true.
    > Ox.setPropertyOnce([{selected: false}, {selected: false}], 'selected')
    0
    > Ox.setPropertyOnce([{selected: false}, {selected: true}], 'selected')
    1
    > Ox.setPropertyOnce([{selected: true}, {selected: true}], 'selected')
    0    
@*/
// fixme: strange name, and shouldn't it return the full array?
Ox.setPropertyOnce = function(arr, str) {
    var pos = -1;
    Ox.forEach(arr, function(v, i) {
        if (pos == -1 && arr[i][str]) {
            pos = i;
        } else if (pos > -1 && arr[i][str]) {
            delete arr[i][str];
        }
    });
    if (pos == -1) {
        arr[0][str] = true;
        pos = 0;
    }
    return pos;
};

/*@
Ox.shuffle <f> Randomizes the order of values within a collection
    > Ox.shuffle([1, 2, 3]).length
    3
    > Ox.len(Ox.shuffle({a: 1, b: 2, c: 3}))
    3
    > Ox.shuffle('123').length
    3
@*/
Ox.shuffle = function(col) {
    var keys, ret, type = Ox.typeOf(col), values;
    function sort() {
        return Math.random() - 0.5;
    }
    if (type == 'array') {
        ret = col.sort(sort);
    } else if (type == 'object') {
        keys = Object.keys(col);
        values = Ox.values(col).sort(sort);
        ret = {};
        keys.forEach(function(key, i) {
            ret[key] = values[i]
        });
    } else if (type == 'string') {
        ret = col.split('').sort(sort).join('');
    }
    return ret;
};

/*@
Ox.some <f> Tests if one or more elements of a collection satisfy a given condition
    Unlike <code>[].some()</code>, <code>Ox.some()</code> works for arrays,
    objects and strings.
    > Ox.some([2, 1, 0], function(i, v) { return i == v; })
    true
    > Ox.some({a: 1, b: 2, c: 3}, function(v) { return v == 1; })
    true
    > Ox.some("foo", function(v) { return v == 'f'; })
    true
@*/
Ox.some = function(obj, fn) {
    return Ox.filter(Ox.values(obj), fn || function(v) {
        return v;
    }).length > 0;
};

/*@
Ox.sub <f> Returns a substring or sub-array
    Ox.sub behaves like collection[start:stop] in Python
    (or, for strings, like str.substring() with negative values for stop)
    > Ox.sub([1, 2, 3], 1, -1)
    [2]
    > Ox.sub('foobar', 1)
    "oobar"
    > Ox.sub('foobar', -1)
    "r"
    > Ox.sub('foobar', 1, 5)
    "ooba"
    > Ox.sub('foobar', 1, -1)
    "ooba"
    > Ox.sub('foobar', -5, 5)
    "ooba"
    > Ox.sub('foobar', -5, -1)
    "ooba"
    > Ox.sub('foo', -1, 0)
    ""
@*/
Ox.sub = function(col, start, stop) {
    stop = Ox.isUndefined(stop) ? col.length : stop;
    start = start < 0 ? col.length + start : start;
    stop = stop < 0 ? col.length + stop : stop;
    return Ox.isArray(col) ? Ox.filter(col, function(val, key) {
        return key >= start && key < stop;
    }) : col.substring(start, Math.max(start, stop));
}

/*@
Ox.sum <f> Returns the sum of the values of a collection
    > Ox.sum(1, 2, 3)
    6
    > Ox.sum([1, 2, 3])
    6
    > Ox.sum({a: 1, b: 2, c: 3})
    6
    > Ox.sum('123')
    6
    > Ox.sum('123foo')
    6
    > Ox.sum('08', -2, 'foo')
    6
@*/
Ox.sum = function(col) {
    var sum = 0;
    col = arguments.length > 1 ? Ox.makeArray(arguments) : col;
    Ox.forEach(col, function(val) {
        val = +val;
        sum += Ox.isNumber(val) ? val : 0;
    });
    return sum;
};

Ox.toArray = function(obj) {
    // fixme: can this be thrown out?
    /*
    >>> Ox.toArray('foo')
    ['foo']
    >>> Ox.toArray(['foo'])
    ['foo']
    */
    var arr;
    if (Ox.isArray(obj)) {
        arr = obj;
    } else if (Ox.isArguments(obj)) {
        arr = Ox.makeArray(obj);
    } else {
        arr = [obj];
    }
    return arr;
};

/*@
Ox.values <f> Returns the values of a collection
    > Ox.values([1, 2, 3])
    [1, 2, 3]
    > Ox.values({a: 1, b: 2, c: 3})
    [1, 2, 3]
    > Ox.values('abc')
    ['a', 'b', 'c']
    > Ox.values([1,,3])
    [1, 3]
@*/
Ox.values = function(col) {
    // Ox.values(str) is identical to str.split('')
    var values = [];
    Ox.forEach(col, function(val) {
        values.push(val);
    });
    return values;
};

/*@
Ox.walk <f> Recursively walk a tree-like key/value store
    <script>
        Ox.test.number = 0;
        Ox.walk({a: 1, b: {c: 2, d: 3}}, function (v) {
            Ox.test.number += Ox.isNumber(v) ? v : 0;
        });
    </script>
    > Ox.test.number
    6
@*/
Ox.walk = function(obj, fn) {
    Ox.forEach(obj, function(val, key) {
        fn(val, key, obj);
        Ox.walk(obj[key], fn);
    });
};