var Stack = require('./_Stack'),

arrayEach = require('./_arrayEach'),
assignValue = require('./_assignValue'),
baseAssign = require('./_baseAssign'),
baseAssignIn = require('./_baseAssignIn'),
cloneBuffer = require('./_cloneBuffer'),
copyArray = require('./_copyArray'),
copySymbols = require('./_copySymbols'),
copySymbolsIn = require('./_copySymbolsIn'),
getAllKeys = require('./_getAllKeys'),
getAllKeysIn = require('./_getAllKeysIn'),
getTag = require('./_getTag'),
initCloneArray = require('./_initCloneArray'),
initCloneByTag = require('./_initCloneByTag'),
initCloneObject = require('./_initCloneObject'),
isArray = require('./isArray'),
isBuffer = require('./isBuffer'),
isMap = require('./isMap'),
isObject = require('./isObject'),
isSet = require('./isSet'),
keys = require('./keys');

/** Used to compose bitmasks for cloning. */ var CLONE_DEEP_FLAG = 1,

CLONE_FLAT_FLAG = 2,
CLONE_SYMBOLS_FLAG = 4;

/** `Object#toString` result references. */ var argsTag = '[object Arguments]',

arrayTag = '[object Array]',
boolTag = '[object Boolean]',
dateTag = '[object Date]',
errorTag = '[object Error]',
funcTag = '[object Function]',
genTag = '[object GeneratorFunction]',
mapTag = '[object Map]',
numberTag = '[object Number]',
objectTag = '[object Object]',
regexpTag = '[object RegExp]',
setTag = '[object Set]',
stringTag = '[object String]',
symbolTag = '[object Symbol]',
weakMapTag = '[object WeakMap]';

var arrayBufferTag = '[object ArrayBuffer]',

dataViewTag = '[object DataView]',
float32Tag = '[object Float32Array]',
float64Tag = '[object Float64Array]',
int8Tag = '[object Int8Array]',
int16Tag = '[object Int16Array]',
int32Tag = '[object Int32Array]',
uint8Tag = '[object Uint8Array]',
uint8ClampedTag = '[object Uint8ClampedArray]',
uint16Tag = '[object Uint16Array]',
uint32Tag = '[object Uint32Array]';

/** Used to identify `toStringTag` values supported by `_.clone`. */ var cloneableTags = {}; cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = cloneableTags = true; cloneableTags = cloneableTags = cloneableTags = false;

/**

* The base implementation of `_.clone` and `_.cloneDeep` which tracks
* traversed objects.
*
* @private
* @param {*} value The value to clone.
* @param {boolean} bitmask The bitmask flags.
*  1 - Deep clone
*  2 - Flatten inherited properties
*  4 - Clone symbols
* @param {Function} [customizer] The function to customize cloning.
* @param {string} [key] The key of `value`.
* @param {Object} [object] The parent object of `value`.
* @param {Object} [stack] Tracks traversed objects and their clone counterparts.
* @returns {*} Returns the cloned value.
*/

function baseClone(value, bitmask, customizer, key, object, stack) {

var result,
    isDeep = bitmask & CLONE_DEEP_FLAG,
    isFlat = bitmask & CLONE_FLAT_FLAG,
    isFull = bitmask & CLONE_SYMBOLS_FLAG;

if (customizer) {
  result = object ? customizer(value, key, object, stack) : customizer(value);
}
if (result !== undefined) {
  return result;
}
if (!isObject(value)) {
  return value;
}
var isArr = isArray(value);
if (isArr) {
  result = initCloneArray(value);
  if (!isDeep) {
    return copyArray(value, result);
  }
} else {
  var tag = getTag(value),
      isFunc = tag == funcTag || tag == genTag;

  if (isBuffer(value)) {
    return cloneBuffer(value, isDeep);
  }
  if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
    result = (isFlat || isFunc) ? {} : initCloneObject(value);
    if (!isDeep) {
      return isFlat
        ? copySymbolsIn(value, baseAssignIn(result, value))
        : copySymbols(value, baseAssign(result, value));
    }
  } else {
    if (!cloneableTags[tag]) {
      return object ? value : {};
    }
    result = initCloneByTag(value, tag, isDeep);
  }
}
// Check for circular references and return its corresponding clone.
stack || (stack = new Stack);
var stacked = stack.get(value);
if (stacked) {
  return stacked;
}
stack.set(value, result);

if (isSet(value)) {
  value.forEach(function(subValue) {
    result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack));
  });
} else if (isMap(value)) {
  value.forEach(function(subValue, key) {
    result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack));
  });
}

var keysFunc = isFull
  ? (isFlat ? getAllKeysIn : getAllKeys)
  : (isFlat ? keysIn : keys);

var props = isArr ? undefined : keysFunc(value);
arrayEach(props || value, function(subValue, key) {
  if (props) {
    key = subValue;
    subValue = value[key];
  }
  // Recursively populate clone (susceptible to call stack limits).
  assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
});
return result;

}

module.exports = baseClone;