//function composition goodness
function c0(f, g) {
  var last;
  if (!arguments.length) return;
  if (arguments.length === 1) return f;
  if (arguments.length === 2) {
    return function (x) {
      var args = [].slice.call(arguments);
      args.shift();
      args.push(g(x));
      return f.apply(this, args);
    };
  }
  last = [].pop.call(arguments);
  return c0.apply(null, ([].push.call(arguments, c0([].pop.call(arguments), last)), arguments));
}

//automagical self-currying function goodness
function cu(fn) {
  var args = [].slice.call(arguments);
  if (args.length - 1 >= fn.length) return fn.apply(this, args.slice(1));
  return function () {
    var tempArgs = args.concat([].slice.call(arguments));
    return cu.apply(this, tempArgs);
  }
}

/********************************************************************
 * Example 1: Single argument to composed function
 * -----------------------------------------------
*********************************************************************/
//filters out a particular item (given by 'x') from the array
function filterOut(x, arr) {
  return arr.filter(function (v) {
    if (x !== v) return v;
  })
}

//multiples each item in the array with the value 'x'
function multiplyWith(x, arr) {
  return arr.map(function (v) {
    return x * v;
  });
}

//make a self-currying version of [].reduce
reduce = cu([].reduce);

//display the array and then sum up all its items and return it
function showAndReduce(arr) {
  console.log(arr);
  return reduce.call(arr, function (p, c) {
    return p + c
  }, 0);
}

//test input
var someArray = [100, 1, 2, 100, 100, 3, 100, 4, 100];

//create a chain that filters out all 100's, 
//multiplies each item with 10 and then shows and returns the sum
var chainOne = c0(showAndReduce, cu(multiplyWith, 10), cu(filterOut, 100));

chainOne(someArray);

//output:
//[10, 20, 30, 40]
//100

chainOne([100, 100, 100, 100, 100, 1, 1, 1, 1, 2, 100]);

//output:
//[10, 10, 10, 10, 20]
//60


/********************************************************************
 * Example 2: multiple args to a composed function
 * -----------------------------------------------
*********************************************************************/
//We shall be using map, filter and with a few helpers

//map, filter, add & lessThan all auto-curry. Flip need not auto-curry but its result does
var map = cu(function map(list, fn) {
  try {
    return list.map(fn);
  }
  catch (e) {
    return [].map.call(list, fn);
  }
});
var filter = cu(function filter(list, fn) {
  try {
    return list.filter(fn);
  }
  catch (e) {
    return [].filter.call(list, fn);
  }
});
var add = cu(function(a, b){ return a+b; });
var lessThan = cu(function(a,b){ return b<a; }); //the function reads "lessThan 'a' is 'b'?" or "is 'b' lessThan 'a'"
var flip = function(fn){
  return cu(function(a,b){
    return fn.call(this, b, a);
  });
};

//add 1 to every element in the array and return the result
c0(map, add)(1, [1,2,3,4]); //[2, 3, 4, 5]

//give me every element that is lesser than 4 in the array
c0(filter, lessThan)(4, [0,1,2,3,4]); //[0, 1, 2, 3]

//string fun
c0(map, add)(" ola", "abcd"); //[" olaa", " olab", " olac", " olad"]
//but wait, that's not what we wanted. We wanted ["a ola", "b ola", ...]
c0(map, flip(add))(" ola", "abcd"); //["a ola", "b ola", "c ola", "d ola"]