1var mapping = require('./_mapping'),
2    fallbackHolder = require('./placeholder');
3
4/** Built-in value reference. */
5var push = Array.prototype.push;
6
7/**
8 * Creates a function, with an arity of `n`, that invokes `func` with the
9 * arguments it receives.
10 *
11 * @private
12 * @param {Function} func The function to wrap.
13 * @param {number} n The arity of the new function.
14 * @returns {Function} Returns the new function.
15 */
16function baseArity(func, n) {
17  return n == 2
18    ? function(a, b) { return func.apply(undefined, arguments); }
19    : function(a) { return func.apply(undefined, arguments); };
20}
21
22/**
23 * Creates a function that invokes `func`, with up to `n` arguments, ignoring
24 * any additional arguments.
25 *
26 * @private
27 * @param {Function} func The function to cap arguments for.
28 * @param {number} n The arity cap.
29 * @returns {Function} Returns the new function.
30 */
31function baseAry(func, n) {
32  return n == 2
33    ? function(a, b) { return func(a, b); }
34    : function(a) { return func(a); };
35}
36
37/**
38 * Creates a clone of `array`.
39 *
40 * @private
41 * @param {Array} array The array to clone.
42 * @returns {Array} Returns the cloned array.
43 */
44function cloneArray(array) {
45  var length = array ? array.length : 0,
46      result = Array(length);
47
48  while (length--) {
49    result[length] = array[length];
50  }
51  return result;
52}
53
54/**
55 * Creates a function that clones a given object using the assignment `func`.
56 *
57 * @private
58 * @param {Function} func The assignment function.
59 * @returns {Function} Returns the new cloner function.
60 */
61function createCloner(func) {
62  return function(object) {
63    return func({}, object);
64  };
65}
66
67/**
68 * A specialized version of `_.spread` which flattens the spread array into
69 * the arguments of the invoked `func`.
70 *
71 * @private
72 * @param {Function} func The function to spread arguments over.
73 * @param {number} start The start position of the spread.
74 * @returns {Function} Returns the new function.
75 */
76function flatSpread(func, start) {
77  return function() {
78    var length = arguments.length,
79        lastIndex = length - 1,
80        args = Array(length);
81
82    while (length--) {
83      args[length] = arguments[length];
84    }
85    var array = args[start],
86        otherArgs = args.slice(0, start);
87
88    if (array) {
89      push.apply(otherArgs, array);
90    }
91    if (start != lastIndex) {
92      push.apply(otherArgs, args.slice(start + 1));
93    }
94    return func.apply(this, otherArgs);
95  };
96}
97
98/**
99 * Creates a function that wraps `func` and uses `cloner` to clone the first
100 * argument it receives.
101 *
102 * @private
103 * @param {Function} func The function to wrap.
104 * @param {Function} cloner The function to clone arguments.
105 * @returns {Function} Returns the new immutable function.
106 */
107function wrapImmutable(func, cloner) {
108  return function() {
109    var length = arguments.length;
110    if (!length) {
111      return;
112    }
113    var args = Array(length);
114    while (length--) {
115      args[length] = arguments[length];
116    }
117    var result = args[0] = cloner.apply(undefined, args);
118    func.apply(undefined, args);
119    return result;
120  };
121}
122
123/**
124 * The base implementation of `convert` which accepts a `util` object of methods
125 * required to perform conversions.
126 *
127 * @param {Object} util The util object.
128 * @param {string} name The name of the function to convert.
129 * @param {Function} func The function to convert.
130 * @param {Object} [options] The options object.
131 * @param {boolean} [options.cap=true] Specify capping iteratee arguments.
132 * @param {boolean} [options.curry=true] Specify currying.
133 * @param {boolean} [options.fixed=true] Specify fixed arity.
134 * @param {boolean} [options.immutable=true] Specify immutable operations.
135 * @param {boolean} [options.rearg=true] Specify rearranging arguments.
136 * @returns {Function|Object} Returns the converted function or object.
137 */
138function baseConvert(util, name, func, options) {
139  var isLib = typeof name == 'function',
140      isObj = name === Object(name);
141
142  if (isObj) {
143    options = func;
144    func = name;
145    name = undefined;
146  }
147  if (func == null) {
148    throw new TypeError;
149  }
150  options || (options = {});
151
152  var config = {
153    'cap': 'cap' in options ? options.cap : true,
154    'curry': 'curry' in options ? options.curry : true,
155    'fixed': 'fixed' in options ? options.fixed : true,
156    'immutable': 'immutable' in options ? options.immutable : true,
157    'rearg': 'rearg' in options ? options.rearg : true
158  };
159
160  var defaultHolder = isLib ? func : fallbackHolder,
161      forceCurry = ('curry' in options) && options.curry,
162      forceFixed = ('fixed' in options) && options.fixed,
163      forceRearg = ('rearg' in options) && options.rearg,
164      pristine = isLib ? func.runInContext() : undefined;
165
166  var helpers = isLib ? func : {
167    'ary': util.ary,
168    'assign': util.assign,
169    'clone': util.clone,
170    'curry': util.curry,
171    'forEach': util.forEach,
172    'isArray': util.isArray,
173    'isError': util.isError,
174    'isFunction': util.isFunction,
175    'isWeakMap': util.isWeakMap,
176    'iteratee': util.iteratee,
177    'keys': util.keys,
178    'rearg': util.rearg,
179    'toInteger': util.toInteger,
180    'toPath': util.toPath
181  };
182
183  var ary = helpers.ary,
184      assign = helpers.assign,
185      clone = helpers.clone,
186      curry = helpers.curry,
187      each = helpers.forEach,
188      isArray = helpers.isArray,
189      isError = helpers.isError,
190      isFunction = helpers.isFunction,
191      isWeakMap = helpers.isWeakMap,
192      keys = helpers.keys,
193      rearg = helpers.rearg,
194      toInteger = helpers.toInteger,
195      toPath = helpers.toPath;
196
197  var aryMethodKeys = keys(mapping.aryMethod);
198
199  var wrappers = {
200    'castArray': function(castArray) {
201      return function() {
202        var value = arguments[0];
203        return isArray(value)
204          ? castArray(cloneArray(value))
205          : castArray.apply(undefined, arguments);
206      };
207    },
208    'iteratee': function(iteratee) {
209      return function() {
210        var func = arguments[0],
211            arity = arguments[1],
212            result = iteratee(func, arity),
213            length = result.length;
214
215        if (config.cap && typeof arity == 'number') {
216          arity = arity > 2 ? (arity - 2) : 1;
217          return (length && length <= arity) ? result : baseAry(result, arity);
218        }
219        return result;
220      };
221    },
222    'mixin': function(mixin) {
223      return function(source) {
224        var func = this;
225        if (!isFunction(func)) {
226          return mixin(func, Object(source));
227        }
228        var pairs = [];
229        each(keys(source), function(key) {
230          if (isFunction(source[key])) {
231            pairs.push([key, func.prototype[key]]);
232          }
233        });
234
235        mixin(func, Object(source));
236
237        each(pairs, function(pair) {
238          var value = pair[1];
239          if (isFunction(value)) {
240            func.prototype[pair[0]] = value;
241          } else {
242            delete func.prototype[pair[0]];
243          }
244        });
245        return func;
246      };
247    },
248    'nthArg': function(nthArg) {
249      return function(n) {
250        var arity = n < 0 ? 1 : (toInteger(n) + 1);
251        return curry(nthArg(n), arity);
252      };
253    },
254    'rearg': function(rearg) {
255      return function(func, indexes) {
256        var arity = indexes ? indexes.length : 0;
257        return curry(rearg(func, indexes), arity);
258      };
259    },
260    'runInContext': function(runInContext) {
261      return function(context) {
262        return baseConvert(util, runInContext(context), options);
263      };
264    }
265  };
266
267  /*--------------------------------------------------------------------------*/
268
269  /**
270   * Casts `func` to a function with an arity capped iteratee if needed.
271   *
272   * @private
273   * @param {string} name The name of the function to inspect.
274   * @param {Function} func The function to inspect.
275   * @returns {Function} Returns the cast function.
276   */
277  function castCap(name, func) {
278    if (config.cap) {
279      var indexes = mapping.iterateeRearg[name];
280      if (indexes) {
281        return iterateeRearg(func, indexes);
282      }
283      var n = !isLib && mapping.iterateeAry[name];
284      if (n) {
285        return iterateeAry(func, n);
286      }
287    }
288    return func;
289  }
290
291  /**
292   * Casts `func` to a curried function if needed.
293   *
294   * @private
295   * @param {string} name The name of the function to inspect.
296   * @param {Function} func The function to inspect.
297   * @param {number} n The arity of `func`.
298   * @returns {Function} Returns the cast function.
299   */
300  function castCurry(name, func, n) {
301    return (forceCurry || (config.curry && n > 1))
302      ? curry(func, n)
303      : func;
304  }
305
306  /**
307   * Casts `func` to a fixed arity function if needed.
308   *
309   * @private
310   * @param {string} name The name of the function to inspect.
311   * @param {Function} func The function to inspect.
312   * @param {number} n The arity cap.
313   * @returns {Function} Returns the cast function.
314   */
315  function castFixed(name, func, n) {
316    if (config.fixed && (forceFixed || !mapping.skipFixed[name])) {
317      var data = mapping.methodSpread[name],
318          start = data && data.start;
319
320      return start  === undefined ? ary(func, n) : flatSpread(func, start);
321    }
322    return func;
323  }
324
325  /**
326   * Casts `func` to an rearged function if needed.
327   *
328   * @private
329   * @param {string} name The name of the function to inspect.
330   * @param {Function} func The function to inspect.
331   * @param {number} n The arity of `func`.
332   * @returns {Function} Returns the cast function.
333   */
334  function castRearg(name, func, n) {
335    return (config.rearg && n > 1 && (forceRearg || !mapping.skipRearg[name]))
336      ? rearg(func, mapping.methodRearg[name] || mapping.aryRearg[n])
337      : func;
338  }
339
340  /**
341   * Creates a clone of `object` by `path`.
342   *
343   * @private
344   * @param {Object} object The object to clone.
345   * @param {Array|string} path The path to clone by.
346   * @returns {Object} Returns the cloned object.
347   */
348  function cloneByPath(object, path) {
349    path = toPath(path);
350
351    var index = -1,
352        length = path.length,
353        lastIndex = length - 1,
354        result = clone(Object(object)),
355        nested = result;
356
357    while (nested != null && ++index < length) {
358      var key = path[index],
359          value = nested[key];
360
361      if (value != null &&
362          !(isFunction(value) || isError(value) || isWeakMap(value))) {
363        nested[key] = clone(index == lastIndex ? value : Object(value));
364      }
365      nested = nested[key];
366    }
367    return result;
368  }
369
370  /**
371   * Converts `lodash` to an immutable auto-curried iteratee-first data-last
372   * version with conversion `options` applied.
373   *
374   * @param {Object} [options] The options object. See `baseConvert` for more details.
375   * @returns {Function} Returns the converted `lodash`.
376   */
377  function convertLib(options) {
378    return _.runInContext.convert(options)(undefined);
379  }
380
381  /**
382   * Create a converter function for `func` of `name`.
383   *
384   * @param {string} name The name of the function to convert.
385   * @param {Function} func The function to convert.
386   * @returns {Function} Returns the new converter function.
387   */
388  function createConverter(name, func) {
389    var realName = mapping.aliasToReal[name] || name,
390        methodName = mapping.remap[realName] || realName,
391        oldOptions = options;
392
393    return function(options) {
394      var newUtil = isLib ? pristine : helpers,
395          newFunc = isLib ? pristine[methodName] : func,
396          newOptions = assign(assign({}, oldOptions), options);
397
398      return baseConvert(newUtil, realName, newFunc, newOptions);
399    };
400  }
401
402  /**
403   * Creates a function that wraps `func` to invoke its iteratee, with up to `n`
404   * arguments, ignoring any additional arguments.
405   *
406   * @private
407   * @param {Function} func The function to cap iteratee arguments for.
408   * @param {number} n The arity cap.
409   * @returns {Function} Returns the new function.
410   */
411  function iterateeAry(func, n) {
412    return overArg(func, function(func) {
413      return typeof func == 'function' ? baseAry(func, n) : func;
414    });
415  }
416
417  /**
418   * Creates a function that wraps `func` to invoke its iteratee with arguments
419   * arranged according to the specified `indexes` where the argument value at
420   * the first index is provided as the first argument, the argument value at
421   * the second index is provided as the second argument, and so on.
422   *
423   * @private
424   * @param {Function} func The function to rearrange iteratee arguments for.
425   * @param {number[]} indexes The arranged argument indexes.
426   * @returns {Function} Returns the new function.
427   */
428  function iterateeRearg(func, indexes) {
429    return overArg(func, function(func) {
430      var n = indexes.length;
431      return baseArity(rearg(baseAry(func, n), indexes), n);
432    });
433  }
434
435  /**
436   * Creates a function that invokes `func` with its first argument transformed.
437   *
438   * @private
439   * @param {Function} func The function to wrap.
440   * @param {Function} transform The argument transform.
441   * @returns {Function} Returns the new function.
442   */
443  function overArg(func, transform) {
444    return function() {
445      var length = arguments.length;
446      if (!length) {
447        return func();
448      }
449      var args = Array(length);
450      while (length--) {
451        args[length] = arguments[length];
452      }
453      var index = config.rearg ? 0 : (length - 1);
454      args[index] = transform(args[index]);
455      return func.apply(undefined, args);
456    };
457  }
458
459  /**
460   * Creates a function that wraps `func` and applys the conversions
461   * rules by `name`.
462   *
463   * @private
464   * @param {string} name The name of the function to wrap.
465   * @param {Function} func The function to wrap.
466   * @returns {Function} Returns the converted function.
467   */
468  function wrap(name, func, placeholder) {
469    var result,
470        realName = mapping.aliasToReal[name] || name,
471        wrapped = func,
472        wrapper = wrappers[realName];
473
474    if (wrapper) {
475      wrapped = wrapper(func);
476    }
477    else if (config.immutable) {
478      if (mapping.mutate.array[realName]) {
479        wrapped = wrapImmutable(func, cloneArray);
480      }
481      else if (mapping.mutate.object[realName]) {
482        wrapped = wrapImmutable(func, createCloner(func));
483      }
484      else if (mapping.mutate.set[realName]) {
485        wrapped = wrapImmutable(func, cloneByPath);
486      }
487    }
488    each(aryMethodKeys, function(aryKey) {
489      each(mapping.aryMethod[aryKey], function(otherName) {
490        if (realName == otherName) {
491          var data = mapping.methodSpread[realName],
492              afterRearg = data && data.afterRearg;
493
494          result = afterRearg
495            ? castFixed(realName, castRearg(realName, wrapped, aryKey), aryKey)
496            : castRearg(realName, castFixed(realName, wrapped, aryKey), aryKey);
497
498          result = castCap(realName, result);
499          result = castCurry(realName, result, aryKey);
500          return false;
501        }
502      });
503      return !result;
504    });
505
506    result || (result = wrapped);
507    if (result == func) {
508      result = forceCurry ? curry(result, 1) : function() {
509        return func.apply(this, arguments);
510      };
511    }
512    result.convert = createConverter(realName, func);
513    result.placeholder = func.placeholder = placeholder;
514
515    return result;
516  }
517
518  /*--------------------------------------------------------------------------*/
519
520  if (!isObj) {
521    return wrap(name, func, defaultHolder);
522  }
523  var _ = func;
524
525  // Convert methods by ary cap.
526  var pairs = [];
527  each(aryMethodKeys, function(aryKey) {
528    each(mapping.aryMethod[aryKey], function(key) {
529      var func = _[mapping.remap[key] || key];
530      if (func) {
531        pairs.push([key, wrap(key, func, _)]);
532      }
533    });
534  });
535
536  // Convert remaining methods.
537  each(keys(_), function(key) {
538    var func = _[key];
539    if (typeof func == 'function') {
540      var length = pairs.length;
541      while (length--) {
542        if (pairs[length][0] == key) {
543          return;
544        }
545      }
546      func.convert = createConverter(key, func);
547      pairs.push([key, func]);
548    }
549  });
550
551  // Assign to `_` leaving `_.prototype` unchanged to allow chaining.
552  each(pairs, function(pair) {
553    _[pair[0]] = pair[1];
554  });
555
556  _.convert = convertLib;
557  _.placeholder = _;
558
559  // Assign aliases.
560  each(keys(_), function(key) {
561    each(mapping.realToAlias[key] || [], function(alias) {
562      _[alias] = _[key];
563    });
564  });
565
566  return _;
567}
568
569module.exports = baseConvert;
570