text: var mapping = require('./_mapping'),
      mutateMap = mapping.mutate,
      fallbackHolder = require('./placeholder');
  
  /**
   * Creates a function, with an arity of `n`, that invokes `func` with the
   * arguments it receives.
   *
   * @private
   * @param {Function} func The function to wrap.
   * @param {number} n The arity of the new function.
   * @returns {Function} Returns the new function.
   */
  function baseArity(func, n) {
    return n == 2
      ? function(a, b) { return func.apply(undefined, arguments); }
      : function(a) { return func.apply(undefined, arguments); };
  }
  
  /**
   * Creates a function that invokes `func`, with up to `n` arguments, ignoring
   * any additional arguments.
   *
   * @private
   * @param {Function} func The function to cap arguments for.
   * @param {number} n The arity cap.
   * @returns {Function} Returns the new function.
   */
  function baseAry(func, n) {
    return n == 2
      ? function(a, b) { return func(a, b); }
      : function(a) { return func(a); };
  }
  
  /**
   * Creates a clone of `array`.
   *
   * @private
   * @param {Array} array The array to clone.
   * @returns {Array} Returns the cloned array.
   */
  function cloneArray(array) {
    var length = array ? array.length : 0,
        result = Array(length);
  
    while (length--) {
      result[length] = array[length];
    }
    return result;
  }
  
  /**
   * Creates a function that clones a given object using the assignment `func`.
   *
   * @private
   * @param {Function} func The assignment function.
   * @returns {Function} Returns the new cloner function.
   */
  function createCloner(func) {
    return function(object) {
      return func({}, object);
    };
  }
  
  /**
   * Creates a function that wraps `func` and uses `cloner` to clone the first
   * argument it receives.
   *
   * @private
   * @param {Function} func The function to wrap.
   * @param {Function} cloner The function to clone arguments.
   * @returns {Function} Returns the new immutable function.
   */
  function immutWrap(func, cloner) {
    return function() {
      var length = arguments.length;
      if (!length) {
        return result;
      }
      var args = Array(length);
      while (length--) {
        args[length] = arguments[length];
      }
      var result = args[0] = cloner.apply(undefined, args);
      func.apply(undefined, args);
      return result;
    };
  }
  
  /**
   * The base implementation of `convert` which accepts a `util` object of methods
   * required to perform conversions.
   *
   * @param {Object} util The util object.
   * @param {string} name The name of the function to convert.
   * @param {Function} func The function to convert.
   * @param {Object} [options] The options object.
   * @param {boolean} [options.cap=true] Specify capping iteratee arguments.
   * @param {boolean} [options.curry=true] Specify currying.
   * @param {boolean} [options.fixed=true] Specify fixed arity.
   * @param {boolean} [options.immutable=true] Specify immutable operations.
   * @param {boolean} [options.rearg=true] Specify rearranging arguments.
   * @returns {Function|Object} Returns the converted function or object.
   */
  function baseConvert(util, name, func, options) {
    var setPlaceholder,
        isLib = typeof name == 'function',
        isObj = name === Object(name);
  
    if (isObj) {
      options = func;
      func = name;
      name = undefined;
    }
    if (func == null) {
      throw new TypeError;
    }
    options || (options = {});
  
    var config = {
      'cap': 'cap' in options ? options.cap : true,
      'curry': 'curry' in options ? options.curry : true,
      'fixed': 'fixed' in options ? options.fixed : true,
      'immutable': 'immutable' in options ? options.immutable : true,
      'rearg': 'rearg' in options ? options.rearg : true
    };
  
    var forceCurry = ('curry' in options) && options.curry,
        forceFixed = ('fixed' in options) && options.fixed,
        forceRearg = ('rearg' in options) && options.rearg,
        placeholder = isLib ? func : fallbackHolder,
        pristine = isLib ? func.runInContext() : undefined;
  
    var helpers = isLib ? func : {
      'ary': util.ary,
      'assign': util.assign,
      'clone': util.clone,
      'curry': util.curry,
      'forEach': util.forEach,
      'isArray': util.isArray,
      'isFunction': util.isFunction,
      'iteratee': util.iteratee,
      'keys': util.keys,
      'rearg': util.rearg,
      'spread': util.spread,
      'toPath': util.toPath
    };
  
    var ary = helpers.ary,
        assign = helpers.assign,
        clone = helpers.clone,
        curry = helpers.curry,
        each = helpers.forEach,
        isArray = helpers.isArray,
        isFunction = helpers.isFunction,
        keys = helpers.keys,
        rearg = helpers.rearg,
        spread = helpers.spread,
        toPath = helpers.toPath;
  
    var aryMethodKeys = keys(mapping.aryMethod);
  
    var wrappers = {
      'castArray': function(castArray) {
        return function() {
          var value = arguments[0];
          return isArray(value)
            ? castArray(cloneArray(value))
            : castArray.apply(undefined, arguments);
        };
      },
      'iteratee': function(iteratee) {
        return function() {
          var func = arguments[0],
              arity = arguments[1],
              result = iteratee(func, arity),
              length = result.length;
  
          if (config.cap && typeof arity == 'number') {
            arity = arity > 2 ? (arity - 2) : 1;
            return (length && length <= arity) ? result : baseAry(result, arity);
          }
          return result;
        };
      },
      'mixin': function(mixin) {
        return function(source) {
          var func = this;
          if (!isFunction(func)) {
            return mixin(func, Object(source));
          }
          var methods = [],
              methodNames = [];
  
          each(keys(source), function(key) {
            var value = source[key];
            if (isFunction(value)) {
              methodNames.push(key);
              methods.push(func.prototype[key]);
            }
          });
  
          mixin(func, Object(source));
  
          each(methodNames, function(methodName, index) {
            var method = methods[index];
            if (isFunction(method)) {
              func.prototype[methodName] = method;
            } else {
              delete func.prototype[methodName];
            }
          });
          return func;
        };
      },
      'runInContext': function(runInContext) {
        return function(context) {
          return baseConvert(util, runInContext(context), options);
        };
      }
    };
  
    /*--------------------------------------------------------------------------*/
  
    /**
     * Creates a clone of `object` by `path`.
     *
     * @private
     * @param {Object} object The object to clone.
     * @param {Array|string} path The path to clone by.
     * @returns {Object} Returns the cloned object.
     */
    function cloneByPath(object, path) {
      path = toPath(path);
  
      var index = -1,
          length = path.length,
          result = clone(Object(object)),
          nested = result;
  
      while (nested != null && ++index < length) {
        var key = path[index],
            value = nested[key];
  
        if (value != null) {
          nested[key] = clone(Object(value));
        }
        nested = nested[key];
      }
      return result;
    }
  
    /**
     * Converts `lodash` to an immutable auto-curried iteratee-first data-last
     * version with conversion `options` applied.
     *
     * @param {Object} [options] The options object. See `baseConvert` for more details.
     * @returns {Function} Returns the converted `lodash`.
     */
    function convertLib(options) {
      return _.runInContext.convert(options)(undefined);
    }
  
    /**
     * Create a converter function for `func` of `name`.
     *
     * @param {string} name The name of the function to convert.
     * @param {Function} func The function to convert.
     * @returns {Function} Returns the new converter function.
     */
    function createConverter(name, func) {
      var oldOptions = options;
      return function(options) {
        var newUtil = isLib ? pristine : helpers,
            newFunc = isLib ? pristine[name] : func,
            newOptions = assign(assign({}, oldOptions), options);
  
        return baseConvert(newUtil, name, newFunc, newOptions);
      };
    }
  
    /**
     * Creates a function that wraps `func` to invoke its iteratee, with up to `n`
     * arguments, ignoring any additional arguments.
     *
     * @private
     * @param {Function} func The function to cap iteratee arguments for.
     * @param {number} n The arity cap.
     * @returns {Function} Returns the new function.
     */
    function iterateeAry(func, n) {
      return overArg(func, function(func) {
        return typeof func == 'function' ? baseAry(func, n) : func;
      });
    }
  
    /**
     * Creates a function that wraps `func` to invoke its iteratee with arguments
     * arranged according to the specified `indexes` where the argument value at
     * the first index is provided as the first argument, the argument value at
     * the second index is provided as the second argument, and so on.
     *
     * @private
     * @param {Function} func The function to rearrange iteratee arguments for.
     * @param {number[]} indexes The arranged argument indexes.
     * @returns {Function} Returns the new function.
     */
    function iterateeRearg(func, indexes) {
      return overArg(func, function(func) {
        var n = indexes.length;
        return baseArity(rearg(baseAry(func, n), indexes), n);
      });
    }
  
    /**
     * Creates a function that invokes `func` with its first argument passed
     * thru `transform`.
     *
     * @private
     * @param {Function} func The function to wrap.
     * @param {...Function} transform The functions to transform the first argument.
     * @returns {Function} Returns the new function.
     */
    function overArg(func, transform) {
      return function() {
        var length = arguments.length;
        if (!length) {
          return func();
        }
        var args = Array(length);
        while (length--) {
          args[length] = arguments[length];
        }
        var index = config.rearg ? 0 : (length - 1);
        args[index] = transform(args[index]);
        return func.apply(undefined, args);
      };
    }
  
    /**
     * Creates a function that wraps `func` and applys the conversions
     * rules by `name`.
     *
     * @private
     * @param {string} name The name of the function to wrap.
     * @param {Function} func The function to wrap.
     * @returns {Function} Returns the converted function.
     */
    function wrap(name, func) {
      name = mapping.aliasToReal[name] || name;
  
      var result,
          wrapped = func,
          wrapper = wrappers[name];
  
      if (wrapper) {
        wrapped = wrapper(func);
      }
      else if (config.immutable) {
        if (mutateMap.array[name]) {
          wrapped = immutWrap(func, cloneArray);
        }
        else if (mutateMap.object[name]) {
          wrapped = immutWrap(func, createCloner(func));
        }
        else if (mutateMap.set[name]) {
          wrapped = immutWrap(func, cloneByPath);
        }
      }
      each(aryMethodKeys, function(aryKey) {
        each(mapping.aryMethod[aryKey], function(otherName) {
          if (name == otherName) {
            var aryN = !isLib && mapping.iterateeAry[name],
                reargIndexes = mapping.iterateeRearg[name],
                spreadStart = mapping.methodSpread[name];
  
            result = wrapped;
            if (config.fixed && (forceFixed || !mapping.skipFixed[name])) {
              result = spreadStart === undefined
                ? ary(result, aryKey)
                : spread(result, spreadStart);
            }
            if (config.rearg && aryKey > 1 && (forceRearg || !mapping.skipRearg[name])) {
              result = rearg(result, mapping.methodRearg[name] || mapping.aryRearg[aryKey]);
            }
            if (config.cap) {
              if (reargIndexes) {
                result = iterateeRearg(result, reargIndexes);
              } else if (aryN) {
                result = iterateeAry(result, aryN);
              }
            }
            if (forceCurry || (config.curry && aryKey > 1)) {
              forceCurry  && console.log(forceCurry, name);
              result = curry(result, aryKey);
            }
            return false;
          }
        });
        return !result;
      });
  
      result || (result = wrapped);
      if (result == func) {
        result = forceCurry ? curry(result, 1) : function() {
          return func.apply(this, arguments);
        };
      }
      result.convert = createConverter(name, func);
      if (mapping.placeholder[name]) {
        setPlaceholder = true;
        result.placeholder = func.placeholder = placeholder;
      }
      return result;
    }
  
    /*--------------------------------------------------------------------------*/
  
    if (!isObj) {
      return wrap(name, func);
    }
    var _ = func;
  
    // Convert methods by ary cap.
    var pairs = [];
    each(aryMethodKeys, function(aryKey) {
      each(mapping.aryMethod[aryKey], function(key) {
        var func = _[mapping.remap[key] || key];
        if (func) {
          pairs.push([key, wrap(key, func)]);
        }
      });
    });
  
    // Convert remaining methods.
    each(keys(_), function(key) {
      var func = _[key];
      if (typeof func == 'function') {
        var length = pairs.length;
        while (length--) {
          if (pairs[length][0] == key) {
            return;
          }
        }
        func.convert = createConverter(key, func);
        pairs.push([key, func]);
      }
    });
  
    // Assign to `_` leaving `_.prototype` unchanged to allow chaining.
    each(pairs, function(pair) {
      _[pair[0]] = pair[1];
    });
  
    _.convert = convertLib;
    if (setPlaceholder) {
      _.placeholder = placeholder;
    }
    // Assign aliases.
    each(keys(_), function(key) {
      each(mapping.realToAlias[key] || [], function(alias) {
        _[alias] = _[key];
      });
    });
  
    return _;
  }
  
  module.exports = baseConvert;
  