(function() {
    var slice = Array.prototype.slice;
    var toArray = function(a){ return slice.call(a) }
    var tail = function(a){ return slice.call(a, 1) }

    // fn, [value] -> fn
    //-- create a curried function, incorporating any number of
    //-- pre-existing arguments (e.g. if you're further currying a function).
    var createFn = function(fn, args, totalArity){
        var remainingArity = totalArity - args.length;
        var ctx = this;

        switch (remainingArity) {
            case 0: return function(){ return processInvocation.call(this, fn, concatArgs(args, arguments), totalArity) };
            case 1: return function(a){ return processInvocation.call(this, fn, concatArgs(args, arguments), totalArity) };
            case 2: return function(a,b){ return processInvocation.call(this, fn, concatArgs(args, arguments), totalArity) };
            case 3: return function(a,b,c){ return processInvocation.call(this, fn, concatArgs(args, arguments), totalArity) };
            case 4: return function(a,b,c,d){ return processInvocation.call(this, fn, concatArgs(args, arguments), totalArity) };
            case 5: return function(a,b,c,d,e){ return processInvocation.call(this, fn, concatArgs(args, arguments), totalArity) };
            case 6: return function(a,b,c,d,e,f){ return processInvocation.call(this, fn, concatArgs(args, arguments), totalArity) };
            case 7: return function(a,b,c,d,e,f,g){ return processInvocation.call(this, fn, concatArgs(args, arguments), totalArity) };
            case 8: return function(a,b,c,d,e,f,g,h){ return processInvocation.call(this, fn, concatArgs(args, arguments), totalArity) };
            case 9: return function(a,b,c,d,e,f,g,h,i){ return processInvocation.call(this, fn, concatArgs(args, arguments), totalArity) };
            case 10: return function(a,b,c,d,e,f,g,h,i,j){ return processInvocation.call(this, fn, concatArgs(args, arguments), totalArity) };
            default: return createEvalFn(fn, args, remainingArity);
        }
    }

    // [value], arguments -> [value]
    //-- concat new arguments onto old arguments array
    var concatArgs = function(args1, args2){
        return args1.concat(toArray(args2));
    }

    // fn, [value], int -> fn
    //-- create a function of the correct arity by the use of eval,
    //-- so that curry can handle functions of any arity
    var createEvalFn = function(fn, args, arity){
        var argList = makeArgList(arity);

        //-- hack for IE's faulty eval parsing -- http://stackoverflow.com/a/6807726
        var fnStr = 'false||' +
                    'function(' + argList + '){ return processInvocation.call(this, fn, concatArgs(args, arguments)); }';
        return eval(fnStr);
    }

    var makeArgList = function(len){
        var a = [];
        for ( var i = 0; i < len; i += 1 ) a.push('a' + i.toString());
        return a.join(',');
    }

    var trimArrLength = function(arr, length){
        if ( arr.length > length ) return arr.slice(0, length);
        else return arr;
    }

    // fn, [value] -> value
    //-- handle a function being invoked.
    //-- if the arg list is long enough, the function will be called
    //-- otherwise, a new curried version is created.
    var processInvocation = function(fn, argsArr, totalArity){
        argsArr = trimArrLength(argsArr, totalArity);

        if ( argsArr.length === totalArity ) return fn.apply(this, argsArr);
        return createFn.call(this, fn, argsArr, totalArity);
    }

    // fn -> fn
    //-- curries a function! <3
    var curry = function(fn){
        return createFn.call(this, fn, [], fn.length);
    }

    // num, fn -> fn
    //-- curries a function to a certain arity! <33
    curry.to = curry(function(arity, fn){
        return createFn.call(this, fn, [], arity);
    });

    // num, fn -> fn
    //-- adapts a function in the context-first style
    //-- to a curried version. <3333
    curry.adaptTo = curry(function(num, fn){
        return curry.to.call(this, num, function(context){
            var args = tail(arguments).concat(context);
            return fn.apply(this, args);
        });
    })

    // fn -> fn
    //-- adapts a function in the context-first style to
    //-- a curried version. <333
    curry.adapt = function(fn){
        return curry.adaptTo.call(this, fn.length, fn)
    }
    window.curry = curry;
})();

Function.prototype.compose  = function(argFunction) {
    var invokingFunction = this;
    return function() {
        return  invokingFunction.call(this,argFunction.apply(this,arguments));
    }
}
