// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/objects/swiss-name-dictionary-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/test-swiss-name-dictionary-infra.h"
#include "test/cctest/test-swiss-name-dictionary-shared-tests.h"

namespace v8 {
namespace internal {
namespace test_swiss_hash_table {

// Executes tests by executing C++ versions of dictionary operations.
class RuntimeTestRunner {
 public:
  RuntimeTestRunner(Isolate* isolate, int initial_capacity, KeyCache& keys)
      : isolate_{isolate}, keys_{keys} {
    table = isolate->factory()->NewSwissNameDictionaryWithCapacity(
        initial_capacity, AllocationType::kYoung);
  }

  // The runtime implementations does not depend on the CPU features and
  // therefore always work.
  static bool IsEnabled() { return true; }

  void Add(Handle<Name> key, Handle<Object> value, PropertyDetails details);
  InternalIndex FindEntry(Handle<Name> key);
  // Updates the value and property details of the given entry.
  void Put(InternalIndex entry, Handle<Object> new_value,
           PropertyDetails new_details);
  void Delete(InternalIndex entry);
  void RehashInplace();
  void Shrink();

  // Retrieves data associated with |entry|, which must be an index pointing to
  // an existing entry. The returned array contains key, value, property details
  // in that order.
  Handle<FixedArray> GetData(InternalIndex entry);

  // Tests that the current table has the given capacity, and number of
  // (deleted) elements, based on which optional values are present.
  void CheckCounts(base::Optional<int> capacity, base::Optional<int> elements,
                   base::Optional<int> deleted);
  // Checks that |expected_keys| contains exactly the keys in the current table,
  // in the given order.
  void CheckEnumerationOrder(const std::vector<std::string>& expected_keys);
  void CheckCopy();
  void VerifyHeap();

  // Just for debugging.
  void PrintTable();

  Handle<SwissNameDictionary> table;

 private:
  Isolate* isolate_;
  KeyCache& keys_;
};

void RuntimeTestRunner::Add(Handle<Name> key, Handle<Object> value,
                            PropertyDetails details) {
  Handle<SwissNameDictionary> updated_table =
      SwissNameDictionary::Add(isolate_, this->table, key, value, details);
  this->table = updated_table;
}

InternalIndex RuntimeTestRunner::FindEntry(Handle<Name> key) {
  return table->FindEntry(isolate_, key);
}

Handle<FixedArray> RuntimeTestRunner::GetData(InternalIndex entry) {
  if (entry.is_found()) {
    Handle<FixedArray> data = isolate_->factory()->NewFixedArray(3);
    data->set(0, table->KeyAt(entry));
    data->set(1, table->ValueAt(entry));
    data->set(2, table->DetailsAt(entry).AsSmi());
    return data;
  } else {
    return handle(ReadOnlyRoots(isolate_).empty_fixed_array(), isolate_);
  }
}

void RuntimeTestRunner::Put(InternalIndex entry, Handle<Object> new_value,
                            PropertyDetails new_details) {
  CHECK(entry.is_found());

  table->ValueAtPut(entry, *new_value);
  table->DetailsAtPut(entry, new_details);
}

void RuntimeTestRunner::Delete(InternalIndex entry) {
  CHECK(entry.is_found());
  table = table->DeleteEntry(isolate_, table, entry);
}

void RuntimeTestRunner::CheckCounts(base::Optional<int> capacity,
                                    base::Optional<int> elements,
                                    base::Optional<int> deleted) {
  if (capacity.has_value()) {
    CHECK_EQ(capacity.value(), table->Capacity());
  }
  if (elements.has_value()) {
    CHECK_EQ(elements.value(), table->NumberOfElements());
  }
  if (deleted.has_value()) {
    CHECK_EQ(deleted.value(), table->NumberOfDeletedElements());
  }
}

void RuntimeTestRunner::CheckEnumerationOrder(
    const std::vector<std::string>& expected_keys) {
  ReadOnlyRoots roots(isolate_);
  int i = 0;
  for (InternalIndex index : table->IterateEntriesOrdered()) {
    Object key;
    if (table->ToKey(roots, index, &key)) {
      CHECK_LT(i, expected_keys.size());
      Handle<Name> expected_key =
          CreateKeyWithHash(isolate_, this->keys_, Key{expected_keys[i]});

      CHECK_EQ(key, *expected_key);
      ++i;
    }
  }
  CHECK_EQ(i, expected_keys.size());
}

void RuntimeTestRunner::RehashInplace() { table->Rehash(isolate_); }

void RuntimeTestRunner::Shrink() {
  table = SwissNameDictionary::Shrink(isolate_, table);
}

void RuntimeTestRunner::CheckCopy() {
  Handle<SwissNameDictionary> copy =
      SwissNameDictionary::ShallowCopy(isolate_, table);

  CHECK(table->EqualsForTesting(*copy));
}

void RuntimeTestRunner::VerifyHeap() {
#if VERIFY_HEAP
  table->SwissNameDictionaryVerify(isolate_, true);
#endif
}

void RuntimeTestRunner::PrintTable() {
#ifdef OBJECT_PRINT
  table->SwissNameDictionaryPrint(std::cout);
#endif
}

TEST(CapacityFor) {
  for (int elements = 0; elements <= 32; elements++) {
    int capacity = SwissNameDictionary::CapacityFor(elements);
    if (elements == 0) {
      CHECK_EQ(0, capacity);
    } else if (elements <= 3) {
      CHECK_EQ(4, capacity);
    } else if (elements == 4) {
      CHECK_IMPLIES(SwissNameDictionary::kGroupWidth == 8, capacity == 8);
      CHECK_IMPLIES(SwissNameDictionary::kGroupWidth == 16, capacity == 4);
    } else if (elements <= 7) {
      CHECK_EQ(8, capacity);
    } else if (elements <= 14) {
      CHECK_EQ(16, capacity);
    } else if (elements <= 28) {
      CHECK_EQ(32, capacity);
    } else if (elements <= 32) {
      CHECK_EQ(64, capacity);
    }
  }
}

TEST(MaxUsableCapacity) {
  CHECK_EQ(0, SwissNameDictionary::MaxUsableCapacity(0));
  CHECK_IMPLIES(SwissNameDictionary::kGroupWidth == 8,
                SwissNameDictionary::MaxUsableCapacity(4) == 3);
  CHECK_IMPLIES(SwissNameDictionary::kGroupWidth == 16,
                SwissNameDictionary::MaxUsableCapacity(4) == 4);
  CHECK_EQ(7, SwissNameDictionary::MaxUsableCapacity(8));
  CHECK_EQ(14, SwissNameDictionary::MaxUsableCapacity(16));
  CHECK_EQ(28, SwissNameDictionary::MaxUsableCapacity(32));
}

TEST(SizeFor) {
  int baseline = HeapObject::kHeaderSize +
                 // prefix:
                 4 +
                 // capacity:
                 4 +
                 // meta table:
                 kTaggedSize;

  int size_0 = baseline +
               // ctrl table:
               SwissNameDictionary::kGroupWidth;

  int size_4 = baseline +
               // data table:
               4 * 2 * kTaggedSize +
               // ctrl table:
               4 + SwissNameDictionary::kGroupWidth +
               // property details table:
               4;

  int size_8 = baseline +
               // data table:
               8 * 2 * kTaggedSize +
               // ctrl table:
               8 + SwissNameDictionary::kGroupWidth +
               // property details table:
               8;

  CHECK_EQ(SwissNameDictionary::SizeFor(0), size_0);
  CHECK_EQ(SwissNameDictionary::SizeFor(4), size_4);
  CHECK_EQ(SwissNameDictionary::SizeFor(8), size_8);
}

// Executes the tests defined in test-swiss-name-dictionary-shared-tests.h as if
// they were defined in this file, using the RuntimeTestRunner. See comments in
// test-swiss-name-dictionary-shared-tests.h and in
// swiss-name-dictionary-infra.h for details.
const char kRuntimeTestFileName[] = __FILE__;
SharedSwissTableTests<RuntimeTestRunner, kRuntimeTestFileName>
    execute_shared_tests_runtime;

}  // namespace test_swiss_hash_table
}  // namespace internal
}  // namespace v8
