/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <limits>
#include <math.h>

#include "js/Equality.h"  // JS::LooselyEqual
#include "js/GlobalObject.h"
#include "jsapi-tests/tests.h"

using namespace std;

struct LooseEqualityFixture : public jsapitest::RuntimeTest {
  virtual ~LooseEqualityFixture() {}

  bool leq(JS::HandleValue x, JS::HandleValue y) {
    bool equal;
    CHECK(JS::LooselyEqual(cx, x, y, &equal) && equal);
    CHECK(JS::LooselyEqual(cx, y, x, &equal) && equal);
    return true;
  }

  bool nleq(JS::HandleValue x, JS::HandleValue y) {
    bool equal;
    CHECK(JS::LooselyEqual(cx, x, y, &equal) && !equal);
    CHECK(JS::LooselyEqual(cx, y, x, &equal) && !equal);
    return true;
  }
};

struct LooseEqualityData {
  JS::RootedValue qNaN;
  JS::RootedValue sNaN;
  JS::RootedValue d42;
  JS::RootedValue i42;
  JS::RootedValue undef;
  JS::RootedValue null;
  JS::RootedValue obj;
  JS::RootedValue poszero;
  JS::RootedValue negzero;

  explicit LooseEqualityData(JSContext* cx)
      : qNaN(cx),
        sNaN(cx),
        d42(cx),
        i42(cx),
        undef(cx),
        null(cx),
        obj(cx),
        poszero(cx),
        negzero(cx) {
    qNaN = JS::CanonicalizedDoubleValue(numeric_limits<double>::quiet_NaN());
    sNaN =
        JS::CanonicalizedDoubleValue(numeric_limits<double>::signaling_NaN());
    d42 = JS::DoubleValue(42.0);
    i42 = JS::Int32Value(42);
    undef = JS::UndefinedValue();
    null = JS::NullValue();
    obj = JS::ObjectOrNullValue(JS::CurrentGlobalOrNull(cx));
    poszero = JS::DoubleValue(0.0);
    negzero = JS::DoubleValue(-0.0);
#ifdef XP_WIN
#  define copysign _copysign
#endif
    MOZ_RELEASE_ASSERT(copysign(1.0, poszero.toDouble()) == 1.0);
    MOZ_RELEASE_ASSERT(copysign(1.0, negzero.toDouble()) == -1.0);
#ifdef XP_WIN
#  undef copysign
#endif
  }
};

// 11.9.3 1a
BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_undef) {
  LooseEqualityData d(cx);
  CHECK(leq(d.undef, d.undef));
  return true;
}
END_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_undef)

// 11.9.3 1b
BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_null) {
  LooseEqualityData d(cx);
  CHECK(leq(d.null, d.null));
  return true;
}
END_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_null)

// 11.9.3 1ci
BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_nan_nleq_all) {
  LooseEqualityData d(cx);

  CHECK(nleq(d.qNaN, d.qNaN));
  CHECK(nleq(d.qNaN, d.sNaN));

  CHECK(nleq(d.sNaN, d.sNaN));
  CHECK(nleq(d.sNaN, d.qNaN));

  CHECK(nleq(d.qNaN, d.d42));
  CHECK(nleq(d.qNaN, d.i42));
  CHECK(nleq(d.qNaN, d.undef));
  CHECK(nleq(d.qNaN, d.null));
  CHECK(nleq(d.qNaN, d.obj));

  CHECK(nleq(d.sNaN, d.d42));
  CHECK(nleq(d.sNaN, d.i42));
  CHECK(nleq(d.sNaN, d.undef));
  CHECK(nleq(d.sNaN, d.null));
  CHECK(nleq(d.sNaN, d.obj));
  return true;
}
END_FIXTURE_TEST(LooseEqualityFixture, test_nan_nleq_all)

// 11.9.3 1cii
BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_all_nleq_nan) {
  LooseEqualityData d(cx);

  CHECK(nleq(d.qNaN, d.qNaN));
  CHECK(nleq(d.qNaN, d.sNaN));

  CHECK(nleq(d.sNaN, d.sNaN));
  CHECK(nleq(d.sNaN, d.qNaN));

  CHECK(nleq(d.d42, d.qNaN));
  CHECK(nleq(d.i42, d.qNaN));
  CHECK(nleq(d.undef, d.qNaN));
  CHECK(nleq(d.null, d.qNaN));
  CHECK(nleq(d.obj, d.qNaN));

  CHECK(nleq(d.d42, d.sNaN));
  CHECK(nleq(d.i42, d.sNaN));
  CHECK(nleq(d.undef, d.sNaN));
  CHECK(nleq(d.null, d.sNaN));
  CHECK(nleq(d.obj, d.sNaN));
  return true;
}
END_FIXTURE_TEST(LooseEqualityFixture, test_all_nleq_nan)

// 11.9.3 1ciii
BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_leq_same_nums) {
  LooseEqualityData d(cx);

  CHECK(leq(d.d42, d.d42));
  CHECK(leq(d.i42, d.i42));
  CHECK(leq(d.d42, d.i42));
  CHECK(leq(d.i42, d.d42));
  return true;
}
END_FIXTURE_TEST(LooseEqualityFixture, test_leq_same_nums)

// 11.9.3 1civ
BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_pz_leq_nz) {
  LooseEqualityData d(cx);
  CHECK(leq(d.poszero, d.negzero));
  return true;
}
END_FIXTURE_TEST(LooseEqualityFixture, test_pz_leq_nz)

// 11.9.3 1cv
BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_nz_leq_pz) {
  LooseEqualityData d(cx);
  CHECK(leq(d.negzero, d.poszero));
  return true;
}
END_FIXTURE_TEST(LooseEqualityFixture, test_nz_leq_pz)

// 1cvi onwards NOT TESTED

// 11.9.3 2
BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_undef) {
  LooseEqualityData d(cx);
  CHECK(leq(d.null, d.undef));
  return true;
}
END_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_undef)

// 11.9.3 3
BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_null) {
  LooseEqualityData d(cx);
  CHECK(leq(d.undef, d.null));
  return true;
}
END_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_null)

// 4 onwards NOT TESTED
