"use strict";

var exports = module.exports = require('./underscore/underscore-min.js'),
    async = require('async'),
    lib = exports;

exports.fn_ = function (method) {
    return function (obj) {
        obj[method]();
    };
};

exports.mapValues = function (inp, mapper) {
    return exports.reduce(inp, function (obj, v, k) {
        obj[k] = mapper(v, k);
        return obj;
    }, {});
};

exports.flattenOnce = function (inp) {
    var _ret = [];
    return _ret.concat.apply(_ret, inp);
};

exports.not = function (a) {
    return !a;
};

function firstHandler(handlers, test_func) {
    var res = null;
    handlers.some(function () {
        res = test_func.apply(this, Array.slice(arguments));
        return res;
    });
    return res;
}

exports.ncb_withRes = function (cb_reshandler, ncb_continue) {
    return function (err, res) {
        if (typeof res !== 'undefined') {
            res = cb_reshandler(res);
        }
        ncb_continue(err, res);
    };
};

exports.ncb_withErr = function (cb_errhandler, ncb_continue) {
    return function (err, res) {
        if (typeof err !== 'undefined' && err !== null) {
            err = cb_errhandler(err);
        }
        ncb_continue(err, res);
    };
};

exports.iterativeParallel = function (taskhandler, ncb_finishhandler, start_state) {
    var expect = [], overall_res = [];

    function ncb_register_done(state, err, res) {
        if (err) {
            return ncb_finishhandler(err);
        }

        overall_res.push(res);
        expect = lib.without(expect, state);
        if (expect.length === 0) {
            return ncb_finishhandler(null, overall_res);
        }
    }

    function start_task(state) {
        expect.push(state);

        return taskhandler(state, start_task,
                           ncb_register_done.bind(null, state));
    }

    start_task(start_state);
};

exports.numForOutput = function (v) {
    return Number.prototype.toFixed.call(v, 2);
};

exports.numSort = function (list) {
    return list.sort(function (a, b) {
        return a < b ? -1 : (a > b ? 1 : 0);
    });
};

/**
 * An asynchronous forEach stopping after the first iterator call yielding a
 * result. Returns the result of the iterator (as res) or an array containing
 * all errors received (as err), if no iterator yielded a value.
 */
exports.untilValue = function (arr, iterator, ncb_callback) {
    var errs = [];
    return async.forEachSeries(arr, function (item, callback) {
        iterator(item, function (err, res) {
            if (res) {
                // Trigger series aborting
                callback(res);
            } else {
                if (err) {
                    errs.push(err);
                }
                callback();
            }
        });
    }, function (res) {
        if (res) {
            ncb_callback(null, res);
        } else {
            ncb_callback(errs);
        }
    });
};

exports.retry = function (fn, delay, retries, ncb_callback) {
    var last_err = null;

    ncb_callback = arguments[arguments.length - 1];
    if (typeof delay !== 'number') {
        delay = 1000;
    }
    if (typeof retries !== 'number') {
        retries = 5;
    }

    async.whilst(function () {
        return retries-- > 0;
    }, function (callback) {
        fn(function (err, res) {
            if (err) {
                last_err = err;
                setTimeout(callback, delay);
            } else {
                ncb_callback(null, res);
            }
        });
    }, function () {
        ncb_callback(last_err || 'Maximum number of retries reached');
    });

};

exports.simpleTime = function (str) {
    var val, match,
        mapping = [
            {unit: 's', amount:  1},
            {unit: 'm', amount: 60},
            {unit: 'h', amount: 60},
            {unit: 'd', amount: 24},
            {unit: 'w', amount:  7}
        ];
    if (typeof str === 'number') {
        return str;
    }
    match = str.match(/^(-?[\d.]+)(\w)?$/);
    val = parseFloat(match[1]);
    if (match[2]) {
        exports.some(mapping, function (v) {
            val *= v.amount;
            if (v.unit === match[2]) {
                return true;
            }
        });
    }
    return val;
};

exports.cached = function (val_producer, expiry) {
    var value = null,
        valid_until = null;

    if (typeof expiry === 'undefined') {
        expiry = '1d';
    }
    expiry = exports.simpleTime(expiry);

    return function (ncb_val_handler) {
        if (valid_until === null || valid_until < Date.now()) {
            val_producer(exports.ncb_withRes(function (res) {
                value = res;
                valid_until = Date.now() + expiry;
                return res;
            }, ncb_val_handler));
        } else {
            ncb_val_handler(null, value);
        }
    };
};

@@ -136,41 +136,339 @@
 s;%0A%0A
-exports.fn_ = function (method) %7B
+/**%0A * Construct a function which calls a specific method on it%E2%80%99s first parameter%0A *%0A * This is similar to Scala%E2%80%99s very concise, underscore-powered function%0A * literals:%0A *   col.map(_.toString) -%3E col.map(lib.fn_('toString'))%0A */%0Aexports.fn_ = function (method/*, ... */) %7B%0A    var args = Array.prototype.slice.call(arguments, 1);
 %0A   
@@ -496,24 +496,31 @@
 ) %7B%0A        
+return 
 obj%5Bmethod%5D(
@@ -518,17 +518,32 @@
 %5Bmethod%5D
-(
+.apply(obj, args
 );%0A    %7D
@@ -865,16 +865,72 @@
 !a;%0A%7D;%0A%0A
+exports.minus = function (a, b) %7B%0A    return a - b;%0A%7D;%0A%0A
 function
@@ -2358,119 +2358,34 @@
 t = 
-function (list) %7B%0A    return list.sort(function (a, b) %7B%0A        return a %3C b ? -1 : (a %3E b ? 1 : 0);%0A    %7D);%0A%7D
+lib.fn_('sort', lib.minus)
 ;%0A%0A/

Fix lib.fn_, use it for numSort