var LodashWrapper = require('./_LodashWrapper'),

flatRest = require('./_flatRest'),
getData = require('./_getData'),
getFuncName = require('./_getFuncName'),
isArray = require('./isArray'),
isLaziable = require('./_isLaziable');

/** Error message constants. */ var FUNC_ERROR_TEXT = 'Expected a function';

/** Used to compose bitmasks for function metadata. */ var WRAP_CURRY_FLAG = 8,

WRAP_PARTIAL_FLAG = 32,
WRAP_ARY_FLAG = 128,
WRAP_REARG_FLAG = 256;

/**

* Creates a `_.flow` or `_.flowRight` function.
*
* @private
* @param {boolean} [fromRight] Specify iterating from right to left.
* @returns {Function} Returns the new flow function.
*/

function createFlow(fromRight) {

return flatRest(function(funcs) {
  var length = funcs.length,
      index = length,
      prereq = LodashWrapper.prototype.thru;

  if (fromRight) {
    funcs.reverse();
  }
  while (index--) {
    var func = funcs[index];
    if (typeof func != 'function') {
      throw new TypeError(FUNC_ERROR_TEXT);
    }
    if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
      var wrapper = new LodashWrapper([], true);
    }
  }
  index = wrapper ? index : length;
  while (++index < length) {
    func = funcs[index];

    var funcName = getFuncName(func),
        data = funcName == 'wrapper' ? getData(func) : undefined;

    if (data && isLaziable(data[0]) &&
          data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) &&
          !data[4].length && data[9] == 1
        ) {
      wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
    } else {
      wrapper = (func.length == 1 && isLaziable(func))
        ? wrapper[funcName]()
        : wrapper.thru(func);
    }
  }
  return function() {
    var args = arguments,
        value = args[0];

    if (wrapper && args.length == 1 && isArray(value)) {
      return wrapper.plant(value).value();
    }
    var index = 0,
        result = length ? funcs[index].apply(this, args) : value;

    while (++index < length) {
      result = funcs[index].call(this, result);
    }
    return result;
  };
});

}

module.exports = createFlow;