'use strict'; /* globals Symbol: false, Uint8Array: false, WeakMap: false */ /*!

* deep-eql
* Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/

var type = require('type-detect'); function FakeMap() {

this._key = 'chai/deep-eql__' + Math.random() + Date.now();

}

FakeMap.prototype = {

get: function getMap(key) {
  return key[this._key];
},
set: function setMap(key, value) {
  if (Object.isExtensible(key)) {
    Object.defineProperty(key, this._key, {
      value: value,
      configurable: true,
    });
  }
},

};

var MemoizeMap = typeof WeakMap === 'function' ? WeakMap : FakeMap; /*!

* Check to see if the MemoizeMap has recorded a result of the two operands
*
* @param {Mixed} leftHandOperand
* @param {Mixed} rightHandOperand
* @param {MemoizeMap} memoizeMap
* @returns {Boolean|null} result

*/ function memoizeCompare(leftHandOperand, rightHandOperand, memoizeMap) {

// Technically, WeakMap keys can *only* be objects, not primitives.
if (!memoizeMap || isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) {
  return null;
}
var leftHandMap = memoizeMap.get(leftHandOperand);
if (leftHandMap) {
  var result = leftHandMap.get(rightHandOperand);
  if (typeof result === 'boolean') {
    return result;
  }
}
return null;

}

/*!

* Set the result of the equality into the MemoizeMap
*
* @param {Mixed} leftHandOperand
* @param {Mixed} rightHandOperand
* @param {MemoizeMap} memoizeMap
* @param {Boolean} result

*/ function memoizeSet(leftHandOperand, rightHandOperand, memoizeMap, result) {

// Technically, WeakMap keys can *only* be objects, not primitives.
if (!memoizeMap || isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) {
  return;
}
var leftHandMap = memoizeMap.get(leftHandOperand);
if (leftHandMap) {
  leftHandMap.set(rightHandOperand, result);
} else {
  leftHandMap = new MemoizeMap();
  leftHandMap.set(rightHandOperand, result);
  memoizeMap.set(leftHandOperand, leftHandMap);
}

}

/*!

* Primary Export
*/

module.exports = deepEqual; module.exports.MemoizeMap = MemoizeMap;

/**

* Assert deeply nested sameValue equality between two objects of any type.
*
* @param {Mixed} leftHandOperand
* @param {Mixed} rightHandOperand
* @param {Object} [options] (optional) Additional options
* @param {Array} [options.comparator] (optional) Override default algorithm, determining custom equality.
* @param {Array} [options.memoize] (optional) Provide a custom memoization object which will cache the results of
   complex objects for a speed boost. By passing `false` you can disable memoization, but this will cause circular
   references to blow the stack.
* @return {Boolean} equal match
*/

function deepEqual(leftHandOperand, rightHandOperand, options) {

// If we have a comparator, we can't assume anything; so bail to its check first.
if (options && options.comparator) {
  return extensiveDeepEqual(leftHandOperand, rightHandOperand, options);
}

var simpleResult = simpleEqual(leftHandOperand, rightHandOperand);
if (simpleResult !== null) {
  return simpleResult;
}

// Deeper comparisons are pushed through to a larger function
return extensiveDeepEqual(leftHandOperand, rightHandOperand, options);

}

/**

* Many comparisons can be canceled out early via simple equality or primitive checks.
* @param {Mixed} leftHandOperand
* @param {Mixed} rightHandOperand
* @return {Boolean|null} equal match
*/

function simpleEqual(leftHandOperand, rightHandOperand) {

// Equal references (except for Numbers) can be returned early
if (leftHandOperand === rightHandOperand) {
  // Handle +-0 cases
  return leftHandOperand !== 0 || 1 / leftHandOperand === 1 / rightHandOperand;
}

// handle NaN cases
if (
  leftHandOperand !== leftHandOperand && // eslint-disable-line no-self-compare
  rightHandOperand !== rightHandOperand // eslint-disable-line no-self-compare
) {
  return true;
}

// Anything that is not an 'object', i.e. symbols, functions, booleans, numbers,
// strings, and undefined, can be compared by reference.
if (isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) {
  // Easy out b/c it would have passed the first equality check
  return false;
}
return null;

}

/*!

* The main logic of the `deepEqual` function.
*
* @param {Mixed} leftHandOperand
* @param {Mixed} rightHandOperand
* @param {Object} [options] (optional) Additional options
* @param {Array} [options.comparator] (optional) Override default algorithm, determining custom equality.
* @param {Array} [options.memoize] (optional) Provide a custom memoization object which will cache the results of
   complex objects for a speed boost. By passing `false` you can disable memoization, but this will cause circular
   references to blow the stack.
* @return {Boolean} equal match

*/ function extensiveDeepEqual(leftHandOperand, rightHandOperand, options) {

options = options || {};
options.memoize = options.memoize === false ? false : options.memoize || new MemoizeMap();
var comparator = options && options.comparator;

// Check if a memoized result exists.
var memoizeResultLeft = memoizeCompare(leftHandOperand, rightHandOperand, options.memoize);
if (memoizeResultLeft !== null) {
  return memoizeResultLeft;
}
var memoizeResultRight = memoizeCompare(rightHandOperand, leftHandOperand, options.memoize);
if (memoizeResultRight !== null) {
  return memoizeResultRight;
}

// If a comparator is present, use it.
if (comparator) {
  var comparatorResult = comparator(leftHandOperand, rightHandOperand);
  // Comparators may return null, in which case we want to go back to default behavior.
  if (comparatorResult === false || comparatorResult === true) {
    memoizeSet(leftHandOperand, rightHandOperand, options.memoize, comparatorResult);
    return comparatorResult;
  }
  // To allow comparators to override *any* behavior, we ran them first. Since it didn't decide
  // what to do, we need to make sure to return the basic tests first before we move on.
  var simpleResult = simpleEqual(leftHandOperand, rightHandOperand);
  if (simpleResult !== null) {
    // Don't memoize this, it takes longer to set/retrieve than to just compare.
    return simpleResult;
  }
}

var leftHandType = type(leftHandOperand);
if (leftHandType !== type(rightHandOperand)) {
  memoizeSet(leftHandOperand, rightHandOperand, options.memoize, false);
  return false;
}

// Temporarily set the operands in the memoize object to prevent blowing the stack
memoizeSet(leftHandOperand, rightHandOperand, options.memoize, true);

var result = extensiveDeepEqualByType(leftHandOperand, rightHandOperand, leftHandType, options);
memoizeSet(leftHandOperand, rightHandOperand, options.memoize, result);
return result;

}

function extensiveDeepEqualByType(leftHandOperand, rightHandOperand, leftHandType, options) {

switch (leftHandType) {
  case 'String':
  case 'Number':
  case 'Boolean':
  case 'Date':
    // If these types are their instance types (e.g. `new Number`) then re-deepEqual against their values
    return deepEqual(leftHandOperand.valueOf(), rightHandOperand.valueOf());
  case 'Promise':
  case 'Symbol':
  case 'function':
  case 'WeakMap':
  case 'WeakSet':
  case 'Error':
    return leftHandOperand === rightHandOperand;
  case 'Arguments':
  case 'Int8Array':
  case 'Uint8Array':
  case 'Uint8ClampedArray':
  case 'Int16Array':
  case 'Uint16Array':
  case 'Int32Array':
  case 'Uint32Array':
  case 'Float32Array':
  case 'Float64Array':
  case 'Array':
    return iterableEqual(leftHandOperand, rightHandOperand, options);
  case 'RegExp':
    return regexpEqual(leftHandOperand, rightHandOperand);
  case 'Generator':
    return generatorEqual(leftHandOperand, rightHandOperand, options);
  case 'DataView':
    return iterableEqual(new Uint8Array(leftHandOperand.buffer), new Uint8Array(rightHandOperand.buffer), options);
  case 'ArrayBuffer':
    return iterableEqual(new Uint8Array(leftHandOperand), new Uint8Array(rightHandOperand), options);
  case 'Set':
    return entriesEqual(leftHandOperand, rightHandOperand, options);
  case 'Map':
    return entriesEqual(leftHandOperand, rightHandOperand, options);
  default:
    return objectEqual(leftHandOperand, rightHandOperand, options);
}

}

/*!

* Compare two Regular Expressions for equality.
*
* @param {RegExp} leftHandOperand
* @param {RegExp} rightHandOperand
* @return {Boolean} result
*/

function regexpEqual(leftHandOperand, rightHandOperand) {

return leftHandOperand.toString() === rightHandOperand.toString();

}

/*!

* Compare two Sets/Maps for equality. Faster than other equality functions.
*
* @param {Set} leftHandOperand
* @param {Set} rightHandOperand
* @param {Object} [options] (Optional)
* @return {Boolean} result
*/

function entriesEqual(leftHandOperand, rightHandOperand, options) {

// IE11 doesn't support Set#entries or Set#@@iterator, so we need manually populate using Set#forEach
if (leftHandOperand.size !== rightHandOperand.size) {
  return false;
}
if (leftHandOperand.size === 0) {
  return true;
}
var leftHandItems = [];
var rightHandItems = [];
leftHandOperand.forEach(function gatherEntries(key, value) {
  leftHandItems.push([ key, value ]);
});
rightHandOperand.forEach(function gatherEntries(key, value) {
  rightHandItems.push([ key, value ]);
});
return iterableEqual(leftHandItems.sort(), rightHandItems.sort(), options);

}

/*!

* Simple equality for flat iterable objects such as Arrays, TypedArrays or Node.js buffers.
*
* @param {Iterable} leftHandOperand
* @param {Iterable} rightHandOperand
* @param {Object} [options] (Optional)
* @return {Boolean} result
*/

function iterableEqual(leftHandOperand, rightHandOperand, options) {

var length = leftHandOperand.length;
if (length !== rightHandOperand.length) {
  return false;
}
if (length === 0) {
  return true;
}
var index = -1;
while (++index < length) {
  if (deepEqual(leftHandOperand[index], rightHandOperand[index], options) === false) {
    return false;
  }
}
return true;

}

/*!

* Simple equality for generator objects such as those returned by generator functions.
*
* @param {Iterable} leftHandOperand
* @param {Iterable} rightHandOperand
* @param {Object} [options] (Optional)
* @return {Boolean} result
*/

function generatorEqual(leftHandOperand, rightHandOperand, options) {

return iterableEqual(getGeneratorEntries(leftHandOperand), getGeneratorEntries(rightHandOperand), options);

}

/*!

* Determine if the given object has an @@iterator function.
*
* @param {Object} target
* @return {Boolean} `true` if the object has an @@iterator function.
*/

function hasIteratorFunction(target) {

return typeof Symbol !== 'undefined' &&
  typeof target === 'object' &&
  typeof Symbol.iterator !== 'undefined' &&
  typeof target[Symbol.iterator] === 'function';

}

/*!

* Gets all iterator entries from the given Object. If the Object has no @@iterator function, returns an empty array.
* This will consume the iterator - which could have side effects depending on the @@iterator implementation.
*
* @param {Object} target
* @returns {Array} an array of entries from the @@iterator function
*/

function getIteratorEntries(target) {

if (hasIteratorFunction(target)) {
  try {
    return getGeneratorEntries(target[Symbol.iterator]());
  } catch (iteratorError) {
    return [];
  }
}
return [];

}

/*!

* Gets all entries from a Generator. This will consume the generator - which could have side effects.
*
* @param {Generator} target
* @returns {Array} an array of entries from the Generator.
*/

function getGeneratorEntries(generator) {

var generatorResult = generator.next();
var accumulator = [ generatorResult.value ];
while (generatorResult.done === false) {
  generatorResult = generator.next();
  accumulator.push(generatorResult.value);
}
return accumulator;

}

/*!

* Gets all own and inherited enumerable keys from a target.
*
* @param {Object} target
* @returns {Array} an array of own and inherited enumerable keys from the target.
*/

function getEnumerableKeys(target) {

var keys = [];
for (var key in target) {
  keys.push(key);
}
return keys;

}

/*!

* Determines if two objects have matching values, given a set of keys. Defers to deepEqual for the equality check of
* each key. If any value of the given key is not equal, the function will return false (early).
*
* @param {Mixed} leftHandOperand
* @param {Mixed} rightHandOperand
* @param {Array} keys An array of keys to compare the values of leftHandOperand and rightHandOperand against
* @param {Object} [options] (Optional)
* @return {Boolean} result
*/

function keysEqual(leftHandOperand, rightHandOperand, keys, options) {

var length = keys.length;
if (length === 0) {
  return true;
}
for (var i = 0; i < length; i += 1) {
  if (deepEqual(leftHandOperand[keys[i]], rightHandOperand[keys[i]], options) === false) {
    return false;
  }
}
return true;

}

/*!

* Recursively check the equality of two Objects. Once basic sameness has been established it will defer to `deepEqual`
* for each enumerable key in the object.
*
* @param {Mixed} leftHandOperand
* @param {Mixed} rightHandOperand
* @param {Object} [options] (Optional)
* @return {Boolean} result
*/

function objectEqual(leftHandOperand, rightHandOperand, options) {

var leftHandKeys = getEnumerableKeys(leftHandOperand);
var rightHandKeys = getEnumerableKeys(rightHandOperand);
if (leftHandKeys.length && leftHandKeys.length === rightHandKeys.length) {
  leftHandKeys.sort();
  rightHandKeys.sort();
  if (iterableEqual(leftHandKeys, rightHandKeys) === false) {
    return false;
  }
  return keysEqual(leftHandOperand, rightHandOperand, leftHandKeys, options);
}

var leftHandEntries = getIteratorEntries(leftHandOperand);
var rightHandEntries = getIteratorEntries(rightHandOperand);
if (leftHandEntries.length && leftHandEntries.length === rightHandEntries.length) {
  leftHandEntries.sort();
  rightHandEntries.sort();
  return iterableEqual(leftHandEntries, rightHandEntries, options);
}

if (leftHandKeys.length === 0 &&
    leftHandEntries.length === 0 &&
    rightHandKeys.length === 0 &&
    rightHandEntries.length === 0) {
  return true;
}

return false;

}

/*!

* Returns true if the argument is a primitive.
*
* This intentionally returns true for all objects that can be compared by reference,
* including functions and symbols.
*
* @param {Mixed} value
* @return {Boolean} result
*/

function isPrimitive(value) {

return value === null || typeof value !== 'object';

}