// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

// resolves . and .. elements in a path array with directory names there
// must be no slashes, empty elements, or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
class AssertionError extends Error {
  constructor(options) {
    super(options);

    this.actual = options.actual;
    this.expected = options.expected;
    this.operator = options.operator;
    if (options.message) {
      this.message = options.message;
      this.generatedMessage = false;
    } else {
      this.message = '';
      this.generatedMessage = true;
    }
    const stackStartFunction = options.stackStartFunction || fail;
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, stackStartFunction);
    } else {
      // non v8 browsers so we can have a stacktrace
      const err = new Error();
      if (err.stack) {
        let out = err.stack;

        // try to strip useless frames
        const fn_name =
          typeof stackStartFunction === 'function'
            ? stackStartFunction.name
            : stackStartFunction.toString();
        const idx = out.indexOf(`\n${fn_name}`);
        if (idx >= 0) {
          // once we have located the function frame
          // we need to strip out everything before it (and its line)
          const next_line = out.indexOf('\n', idx + 1);
          out = out.substring(next_line + 1);
        }

        this.stack = out;
      }
    }
  }
}

function fail(actual, expected, message, operator, stackStartFunction) {
  throw new AssertionError({
    actual,
    expected,
    message,
    operator,
    stackStartFunction,
  });
}

function assert(value, message) {
  if (!value) {
    fail(value, true, message, '==', assert);
  }
}
assert.equal = function equal(actual, expected, message) {
  // eslint-disable-next-line eqeqeq -- intentional inexact equality
  if (actual != expected) {
    fail(actual, expected, message, '==', equal);
  }
};
assert.strictEqual = function strictEqual(actual, expected, message) {
  if (actual !== expected) {
    fail(actual, expected, message, '===', strictEqual);
  }
};
assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (actual === expected) {
    fail(actual, expected, message, '!==', notStrictEqual);
  }
};
assert.notEqual = function notEqual(actual, expected, message) {
  // eslint-disable-next-line eqeqeq -- intentional inexact equality
  if (actual == expected) {
    fail(actual, expected, message, '!=', notEqual);
  }
};
assert.assert = assert.ok = assert;
assert.fail = fail;
assert.AssertionError = AssertionError;

module.exports = assert;
