// Copyright (c) 2019 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/* eslint-disable */
/*
 * Copied from https://github.com/substack/node-deep-equal
 * License: MIT
 * Git Ref: 59c511f5aeae19e3dd1de054077a789d7302be34
 *
 * Changed to handle equality for `typeof === 'number'` to be within
 * the 1e-7
 */

var objectKeys = typeof Object.keys === 'function' ? Object.keys : shim;

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

var supportsArgumentsClass =
  (function() {
    return Object.prototype.toString.call(arguments);
  })() == '[object Arguments]';

var isArguments = supportsArgumentsClass ? supported : unsupported;

function supported(object) {
  return Object.prototype.toString.call(object) == '[object Arguments]';
}

function unsupported(object) {
  return (
    (object &&
      typeof object == 'object' &&
      typeof object.length == 'number' &&
      Object.prototype.hasOwnProperty.call(object, 'callee') &&
      !Object.prototype.propertyIsEnumerable.call(object, 'callee')) ||
    false
  );
}

var pSlice = Array.prototype.slice;

var deepEqual = (module.exports = function(actual, expected, opts) {
  const optionDefaults = {numberTolerance: 1e-7};
  const options = Object.assign({}, optionDefaults, opts);

  // Compare numbers to be within a tolerance
  if (typeof actual === 'number') {
    return Math.abs(actual - expected) < options.numberTolerance;
  }

  // 7.1. All identical values are equivalent, as determined by ===.
  if (actual === expected) {
    return true;
  } else if (actual instanceof Date && expected instanceof Date) {
    return actual.getTime() === expected.getTime();

    // 7.3. Other pairs that do not both pass typeof value == 'object',
    // equivalence is determined by ==.
  } else if (!actual || !expected || (typeof actual != 'object' && typeof expected != 'object')) {
    return options.strict ? actual === expected : actual == expected;

    // 7.4. For all other Object pairs, including Array objects, equivalence is
    // determined by having the same number of owned properties (as verified
    // with Object.prototype.hasOwnProperty.call), the same set of keys
    // (although not necessarily the same order), equivalent values for every
    // corresponding key, and an identical 'prototype' property. Note: this
    // accounts for both named and indexed properties on Arrays.
  } else {
    return objEquiv(actual, expected, options);
  }
});

function isUndefinedOrNull(value) {
  return value === null || value === undefined;
}

function isBuffer(x) {
  if (!x || typeof x !== 'object' || typeof x.length !== 'number') return false;
  if (typeof x.copy !== 'function' || typeof x.slice !== 'function') {
    return false;
  }
  if (x.length > 0 && typeof x[0] !== 'number') return false;
  return true;
}

function objEquiv(a, b, opts) {
  var i, key;
  if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) return false;
  // an identical 'prototype' property.
  if (a.prototype !== b.prototype) return false;
  //~~~I've managed to break Object.keys through screwy arguments passing.
  //   Converting to array solves the problem.
  if (isArguments(a)) {
    if (!isArguments(b)) {
      return false;
    }
    a = pSlice.call(a);
    b = pSlice.call(b);
    return deepEqual(a, b, opts);
  }
  if (isBuffer(a)) {
    if (!isBuffer(b)) {
      return false;
    }
    if (a.length !== b.length) return false;
    for (i = 0; i < a.length; i++) {
      if (a[i] !== b[i]) return false;
    }
    return true;
  }
  try {
    var ka = objectKeys(a),
      kb = objectKeys(b);
  } catch (e) {
    //happens when one is a string literal and the other isn't
    return false;
  }
  // having the same number of owned properties (keys incorporates
  // hasOwnProperty)
  if (ka.length != kb.length) return false;
  //the same set of keys (although not necessarily the same order),
  ka.sort();
  kb.sort();
  //~~~cheap key test
  for (i = ka.length - 1; i >= 0; i--) {
    if (ka[i] != kb[i]) return false;
  }
  //equivalent values for every corresponding key, and
  //~~~possibly expensive deep test
  for (i = ka.length - 1; i >= 0; i--) {
    key = ka[i];
    if (!deepEqual(a[key], b[key], opts)) return false;
  }
  return typeof a === typeof b;
}
