/* 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 "mozilla/CompactPair.h"
#include "mozilla/HashTable.h"
#include "mozilla/PairHash.h"

#include <utility>

void TestMoveConstructor() {
  using namespace mozilla;

  HashMap<int, int> map;
  MOZ_RELEASE_ASSERT(map.putNew(3, 32));
  MOZ_RELEASE_ASSERT(map.putNew(4, 42));
  MOZ_RELEASE_ASSERT(map.count() == 2);
  MOZ_RELEASE_ASSERT(!map.empty());
  MOZ_RELEASE_ASSERT(!map.lookup(2));
  MOZ_RELEASE_ASSERT(map.lookup(3)->value() == 32);
  MOZ_RELEASE_ASSERT(map.lookup(4)->value() == 42);

  HashMap<int, int> moved = std::move(map);
  MOZ_RELEASE_ASSERT(moved.count() == 2);
  MOZ_RELEASE_ASSERT(!moved.empty());
  MOZ_RELEASE_ASSERT(!moved.lookup(2));
  MOZ_RELEASE_ASSERT(moved.lookup(3)->value() == 32);
  MOZ_RELEASE_ASSERT(moved.lookup(4)->value() == 42);

  MOZ_RELEASE_ASSERT(map.empty());
  MOZ_RELEASE_ASSERT(!map.count());
}

void CheckSwapMap1(const mozilla::HashMap<int, int>& map1) {
  MOZ_RELEASE_ASSERT(map1.count() == 2);
  MOZ_RELEASE_ASSERT(!map1.empty());
  MOZ_RELEASE_ASSERT(!map1.lookup(3));
  MOZ_RELEASE_ASSERT(!map1.lookup(4));
  MOZ_RELEASE_ASSERT(map1.lookup(1)->value() == 10);
  MOZ_RELEASE_ASSERT(map1.lookup(2)->value() == 20);
}

void CheckSwapMap2(const mozilla::HashMap<int, int>& map2) {
  MOZ_RELEASE_ASSERT(map2.count() == 2);
  MOZ_RELEASE_ASSERT(!map2.empty());
  MOZ_RELEASE_ASSERT(!map2.lookup(1));
  MOZ_RELEASE_ASSERT(!map2.lookup(2));
  MOZ_RELEASE_ASSERT(map2.lookup(3)->value() == 30);
  MOZ_RELEASE_ASSERT(map2.lookup(4)->value() == 40);
}

void TestSwap() {
  using namespace mozilla;

  HashMap<int, int> map1;
  MOZ_RELEASE_ASSERT(map1.putNew(1, 10));
  MOZ_RELEASE_ASSERT(map1.putNew(2, 20));
  CheckSwapMap1(map1);

  HashMap<int, int> map2;
  MOZ_RELEASE_ASSERT(map2.putNew(3, 30));
  MOZ_RELEASE_ASSERT(map2.putNew(4, 40));
  CheckSwapMap2(map2);

  map1.swap(map2);
  CheckSwapMap2(map1);
  CheckSwapMap1(map2);
}

enum SimpleEnum { SIMPLE_1, SIMPLE_2 };

enum class ClassEnum : int {
  CLASS_ENUM_1,
  CLASS_ENUM_2,
};

void TestEnumHash() {
  using namespace mozilla;

  HashMap<SimpleEnum, int> map;
  MOZ_RELEASE_ASSERT(map.put(SIMPLE_1, 1));
  MOZ_RELEASE_ASSERT(map.put(SIMPLE_2, 2));

  MOZ_RELEASE_ASSERT(map.lookup(SIMPLE_1)->value() == 1);
  MOZ_RELEASE_ASSERT(map.lookup(SIMPLE_2)->value() == 2);

  HashMap<ClassEnum, int> map2;
  MOZ_RELEASE_ASSERT(map2.put(ClassEnum::CLASS_ENUM_1, 1));
  MOZ_RELEASE_ASSERT(map2.put(ClassEnum::CLASS_ENUM_2, 2));

  MOZ_RELEASE_ASSERT(map2.lookup(ClassEnum::CLASS_ENUM_1)->value() == 1);
  MOZ_RELEASE_ASSERT(map2.lookup(ClassEnum::CLASS_ENUM_2)->value() == 2);
}

void TestHashPair() {
  using namespace mozilla;

  // Test with std::pair
  {
    HashMap<std::pair<int, bool>, int, PairHasher<int, bool>> map;
    std::pair<int, bool> key1 = std::make_pair(1, true);
    MOZ_RELEASE_ASSERT(map.putNew(key1, 1));
    MOZ_RELEASE_ASSERT(map.has(key1));
    std::pair<int, bool> key2 = std::make_pair(1, false);
    MOZ_RELEASE_ASSERT(map.putNew(key2, 1));
    std::pair<int, bool> key3 = std::make_pair(2, false);
    MOZ_RELEASE_ASSERT(map.putNew(key3, 2));
    MOZ_RELEASE_ASSERT(map.has(key3));

    MOZ_RELEASE_ASSERT(map.lookup(key1)->value() == 1);
    MOZ_RELEASE_ASSERT(map.lookup(key2)->value() == 1);
    MOZ_RELEASE_ASSERT(map.lookup(key3)->value() == 2);
  }
  // Test wtih compact pair
  {
    HashMap<mozilla::CompactPair<int, bool>, int, CompactPairHasher<int, bool>>
        map;
    mozilla::CompactPair<int, bool> key1 = mozilla::MakeCompactPair(1, true);
    MOZ_RELEASE_ASSERT(map.putNew(key1, 1));
    MOZ_RELEASE_ASSERT(map.has(key1));
    mozilla::CompactPair<int, bool> key2 = mozilla::MakeCompactPair(1, false);
    MOZ_RELEASE_ASSERT(map.putNew(key2, 1));
    mozilla::CompactPair<int, bool> key3 = mozilla::MakeCompactPair(2, false);
    MOZ_RELEASE_ASSERT(map.putNew(key3, 2));
    MOZ_RELEASE_ASSERT(map.has(key3));

    MOZ_RELEASE_ASSERT(map.lookup(key1)->value() == 1);
    MOZ_RELEASE_ASSERT(map.lookup(key2)->value() == 1);
    MOZ_RELEASE_ASSERT(map.lookup(key3)->value() == 2);
  }
}

void TestRekey() {
  using namespace mozilla;

  HashMap<int, int> map;
  MOZ_RELEASE_ASSERT(map.putNew(1, 10));
  MOZ_RELEASE_ASSERT(map.putNew(2, 20));
  MOZ_RELEASE_ASSERT(map.putNew(3, 30));

  // rekeyIfMoved: same key is a no-op.
  map.rekeyIfMoved(1, 1);
  MOZ_RELEASE_ASSERT(map.count() == 3);
  MOZ_RELEASE_ASSERT(map.lookup(1)->value() == 10);

  // rekeyIfMoved: replace an existing key.
  map.rekeyIfMoved(1, 11);
  MOZ_RELEASE_ASSERT(map.count() == 3);
  MOZ_RELEASE_ASSERT(!map.lookup(1));
  MOZ_RELEASE_ASSERT(map.lookup(11)->value() == 10);

  // rekeyIfMoved on a missing key is a no-op.
  map.rekeyIfMoved(99, 100);
  MOZ_RELEASE_ASSERT(map.count() == 3);
  MOZ_RELEASE_ASSERT(!map.lookup(99));
  MOZ_RELEASE_ASSERT(!map.lookup(100));

  // rekeyAs: returns true and replaces key when present.
  MOZ_RELEASE_ASSERT(map.rekeyAs(2, 22, 22));
  MOZ_RELEASE_ASSERT(map.count() == 3);
  MOZ_RELEASE_ASSERT(!map.lookup(2));
  MOZ_RELEASE_ASSERT(map.lookup(22)->value() == 20);

  // rekeyAs: returns false when the old key is absent.
  MOZ_RELEASE_ASSERT(!map.rekeyAs(2, 23, 23));
  MOZ_RELEASE_ASSERT(map.count() == 3);
  MOZ_RELEASE_ASSERT(!map.lookup(23));

  // Other entries are unaffected.
  MOZ_RELEASE_ASSERT(map.lookup(3)->value() == 30);
}

// A structure that holds an int and supports move semantics but not copy
// semantics.
struct WrappedInt {
  int mValue;

  explicit WrappedInt(int v) : mValue(v) {}

  WrappedInt(const WrappedInt&) = delete;
  WrappedInt& operator=(const WrappedInt&) = delete;

  WrappedInt(WrappedInt&& aOther) : mValue(aOther.mValue) { aOther.mValue = 0; }
  WrappedInt& operator=(WrappedInt&& aOther) {
    mValue = aOther.mValue;
    aOther.mValue = 0;
    return *this;
  }

  struct HashPolicy {
    using Key = WrappedInt;
    using Lookup = int;

    static mozilla::HashNumber hash(Lookup aLookup) { return aLookup; }

    static bool match(const Key& aKey, Lookup aLookup) {
      return aKey.mValue == aLookup;
    }

    static void rekey(Key& aKey, Key&& aNewKey) { aKey = std::move(aNewKey); }
  };
};

void TestRekeyWithRValue() {
  using namespace mozilla;

  HashMap<WrappedInt, int, WrappedInt::HashPolicy> map;
  MOZ_RELEASE_ASSERT(map.putNew(1, 10));
  MOZ_RELEASE_ASSERT(map.putNew(2, 20));
  MOZ_RELEASE_ASSERT(map.putNew(3, 30));

  // rekeyAs: replace an existing key.
  map.rekeyAs(1, 11, WrappedInt(11));
  MOZ_RELEASE_ASSERT(map.count() == 3);
  MOZ_RELEASE_ASSERT(!map.lookup(1));
  MOZ_RELEASE_ASSERT(map.lookup(11)->value() == 10);

  // rekeyAs on a missing key is a no-op.
  MOZ_RELEASE_ASSERT(!map.rekeyAs(99, 100, WrappedInt(100)));
  MOZ_RELEASE_ASSERT(map.count() == 3);
  MOZ_RELEASE_ASSERT(!map.lookup(99));
  MOZ_RELEASE_ASSERT(!map.lookup(100));

  // rekeyAs: returns true and replaces key when present.
  MOZ_RELEASE_ASSERT(map.rekeyAs(2, 22, WrappedInt(22)));
  MOZ_RELEASE_ASSERT(map.count() == 3);
  MOZ_RELEASE_ASSERT(!map.lookup(2));
  MOZ_RELEASE_ASSERT(map.lookup(22)->value() == 20);

  // rekeyAs: returns false when the old key is absent.
  MOZ_RELEASE_ASSERT(!map.rekeyAs(2, 23, WrappedInt(23)));
  MOZ_RELEASE_ASSERT(map.count() == 3);
  MOZ_RELEASE_ASSERT(!map.lookup(23));

  // Other entries are unaffected.
  MOZ_RELEASE_ASSERT(map.lookup(3)->value() == 30);
}

void TestModIteratorRekey() {
  using namespace mozilla;

  // Rekey one entry found during iteration.
  {
    HashMap<int, int> map;
    MOZ_RELEASE_ASSERT(map.putNew(1, 10));
    MOZ_RELEASE_ASSERT(map.putNew(2, 20));
    MOZ_RELEASE_ASSERT(map.putNew(3, 30));

    for (auto iter = map.modIter(); !iter.done(); iter.next()) {
      if (iter.get().key() == 2) {
        iter.rekey(22);
      }
    }

    MOZ_RELEASE_ASSERT(map.count() == 3);
    MOZ_RELEASE_ASSERT(!map.lookup(2));
    MOZ_RELEASE_ASSERT(map.lookup(22)->value() == 20);
    MOZ_RELEASE_ASSERT(map.lookup(1)->value() == 10);
    MOZ_RELEASE_ASSERT(map.lookup(3)->value() == 30);
  }

  // Rekey multiple entries.
  {
    HashMap<int, int> map;
    MOZ_RELEASE_ASSERT(map.putNew(1, 10));
    MOZ_RELEASE_ASSERT(map.putNew(2, 20));
    MOZ_RELEASE_ASSERT(map.putNew(3, 30));

    for (auto iter = map.modIter(); !iter.done(); iter.next()) {
      int key = iter.get().key();
      if (key > 0) {
        iter.rekey(-key);
      }
    }

    MOZ_RELEASE_ASSERT(map.count() == 3);
    MOZ_RELEASE_ASSERT(!map.lookup(1));
    MOZ_RELEASE_ASSERT(!map.lookup(2));
    MOZ_RELEASE_ASSERT(!map.lookup(3));
    MOZ_RELEASE_ASSERT(map.lookup(-1)->value() == 10);
    MOZ_RELEASE_ASSERT(map.lookup(-2)->value() == 20);
    MOZ_RELEASE_ASSERT(map.lookup(-3)->value() == 30);
  }
}

void TestCapacityAfterRemove() {
  mozilla::HashMap<int, int> map;
  MOZ_RELEASE_ASSERT(map.count() == 0);
  MOZ_RELEASE_ASSERT(map.capacity() == 0);

  MOZ_RELEASE_ASSERT(map.putNew(1, 1));
  MOZ_RELEASE_ASSERT(map.count() == 1);
  MOZ_RELEASE_ASSERT(map.capacity() != 0);

  map.remove(1);
  MOZ_RELEASE_ASSERT(map.count() == 0);
  MOZ_RELEASE_ASSERT(map.capacity() != 0);

  map.compact();
  MOZ_RELEASE_ASSERT(map.count() == 0);
  MOZ_RELEASE_ASSERT(map.capacity() == 0);

  MOZ_RELEASE_ASSERT(map.putNew(1, 1));
  MOZ_RELEASE_ASSERT(map.count() == 1);
  MOZ_RELEASE_ASSERT(map.capacity() != 0);

  {
    auto iter = map.modIter();
    MOZ_RELEASE_ASSERT(!iter.done());
    iter.remove();
  }
  MOZ_RELEASE_ASSERT(map.count() == 0);
  MOZ_RELEASE_ASSERT(map.capacity() != 0);

  map.compact();
  MOZ_RELEASE_ASSERT(map.count() == 0);
  MOZ_RELEASE_ASSERT(map.capacity() == 0);
}

int main() {
  TestMoveConstructor();
  TestEnumHash();
  TestHashPair();
  TestRekey();
  TestRekeyWithRValue();
  TestModIteratorRekey();
  TestCapacityAfterRemove();
  return 0;
}
