/*@
Ox.compact <f> Returns an array w/o <code>null</code> or <code>undefined</code>
    > Ox.compact([null,,1,,2,,3])
    [1, 2, 3]
@*/

Ox.compact = function(arr) {
    return Ox.map(arr, function(val) {
        return Ox.isUndefined(val) ? null : val;
    });
};

/*@
    Ox.flatten <f> Flattens an array
    > Ox.flatten([1, [2, [3], 2], 1])
    [1, 2, 3, 2, 1]
@*/
Ox.flatten = function(arr) {
    // fixme: can this work for objects too?
    var ret = [];
    arr.forEach(function(val) {
        if (Ox.isArray(val)) {
            Ox.flatten(val).forEach(function(val) {
                ret.push(val);
            });
        } else {
            ret.push(val);
        }
    });
    return ret;
};

/*@ 
Ox.merge <f> Merges an array with one or more other arrays
    > Ox.merge([1], [2, 3, 2], [1])
    [1, 2, 3, 2, 1]
    > Ox.merge(1, [2, 3, 2], 1)
    [1, 2, 3, 2, 1]
@*/
Ox.merge = function(arr) {
    arr = Ox.isArray(arr) ? arr : [arr];
    Ox.forEach(Array.prototype.slice.call(arguments, 1), function(arg) {
        Ox.isArray(arg) ? Ox.forEach(arg, function(val) {
            arr.push(val);
        }) : arr.push(arg);
    });
    return arr;
};

/*@
Ox.sort <f> Sorts an array, handling leading digits and ignoring capitalization
    arr <a> Array
    fn <f|u> Optional map function that returns the value for the array element
    > Ox.sort(['10', '9', 'B', 'a'])
    ['9', '10', 'a', 'B']
    > Ox.sort([{id: 0, name: '80 Days'}, {id: 1, name: '8 Women'}], function(v) {return v.name});
    [{id: 1, name: '8 Women'}, {id: 0, name: '80 Days'}]
@*/
Ox.sort = function(arr, fn) {
    var len, matches = {}, sort = {},
        values = fn ? arr.map(fn) : arr;
    // find leading numbers
    values.forEach(function(val, i) {
        var match = /^\d+/.exec(val);
        matches[val] = match ? match[0] : '';
    });
    // get length of longest leading number
    len = Ox.max(Ox.map(matches, function(val) {
        return val.length;
    }));
    // pad leading numbers, and make lower case
    values.forEach(function(val) {
        sort[val] = (
            matches[val] ? Ox.pad(
                matches[val], len
            ) + val.toString().substr(matches[val].length) : val
        ).toLowerCase();
    });
    return arr.sort(function(a, b) {
        a = fn ? fn(a) : a;
        b = fn ? fn(b) : b;
        var ret = 0;
        if (sort[a] < sort[b]) {
            ret = -1;
        } else if (sort[a] > sort[b]) {
            ret = 1;
        }
        return ret;
    });
};

/*@
Ox.unique <f> Returns an array without duplicate values
    > Ox.unique([1, 2, 3, 2, 1])
    [1, 2, 3]
    > Ox.unique([NaN, NaN])
    []
@*/

Ox.unique = function(arr) {
    return Ox.map(arr, function(val, i) {
        return arr.indexOf(val) == i ? val : null;
    });
};

/*@
Ox.zip <f> Zips an array of arrays
    > Ox.zip([[0, 1], [2, 3], [4, 5]])
    [[0, 2, 4], [1, 3, 5]]
    > Ox.zip([0, 1, 2], [3, 4, 5])
    [[0, 3], [1, 4], [2, 5]]
@*/
Ox.zip = function() {
    var args = arguments.length == 1 ? arguments[0] : Ox.makeArray(arguments),
        arr = [];
    args[0].forEach(function(v, i) {
        arr[i] = [];
        args.forEach(function(v) {
            arr[i].push(v[i]);
        });
    });
    return arr;
};