// Copyright (c) 2025 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#include <NCollection_IndexedDataMap.hxx>
#include <TCollection_AsciiString.hxx>

#include <gtest/gtest.h>
#include <algorithm>
#include <vector>

// Basic test types for the IndexedDataMap
typedef int    KeyType;
typedef double ItemType;

// Custom class for testing complex keys
class TestKey
{
public:
  TestKey(int id = 0, const char* name = "")
      : myId(id),
        myName(name)
  {
  }

  bool operator==(const TestKey& other) const
  {
    return myId == other.myId && myName == other.myName;
  }

  int GetId() const { return myId; }

  const std::string& GetName() const { return myName; }

private:
  int         myId;
  std::string myName;
};

namespace std
{
template <>
struct hash<TestKey>
{
  size_t operator()(const TestKey& key) const
  {
    // Combine the hash
    size_t aCombintation[2] = {std::hash<int>()(key.GetId()),
                               std::hash<std::string>()(key.GetName())};
    return opencascade::hashBytes(aCombintation, sizeof(aCombintation));
  }
};
} // namespace std

// Custom class for testing complex items
class TestItem
{
public:
  TestItem(double value = 0.0, const char* description = "")
      : myValue(value),
        myDescription(description)
  {
  }

  bool operator==(const TestItem& other) const
  {
    return fabs(myValue - other.myValue) < 1e-10 && myDescription == other.myDescription;
  }

  double GetValue() const { return myValue; }

  const std::string& GetDescription() const { return myDescription; }

  void SetValue(double value) { myValue = value; }

private:
  double      myValue;
  std::string myDescription;
};

TEST(NCollection_IndexedDataMapTest, DefaultConstructor)
{
  // Test default constructor
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;
  EXPECT_TRUE(aMap.IsEmpty());
  EXPECT_EQ(aMap.Extent(), 0);
  EXPECT_EQ(aMap.Size(), 0);
}

TEST(NCollection_IndexedDataMapTest, BasicAddFind)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Test adding elements
  int index1 = aMap.Add(10, 1.0);
  int index2 = aMap.Add(20, 2.0);
  int index3 = aMap.Add(30, 3.0);

  EXPECT_EQ(index1, 1);
  EXPECT_EQ(index2, 2);
  EXPECT_EQ(index3, 3);
  EXPECT_EQ(aMap.Extent(), 3);

  // Test finding elements by key
  EXPECT_EQ(aMap.FindIndex(10), 1);
  EXPECT_EQ(aMap.FindIndex(20), 2);
  EXPECT_EQ(aMap.FindIndex(30), 3);

  // Test finding keys by index
  EXPECT_EQ(aMap.FindKey(1), 10);
  EXPECT_EQ(aMap.FindKey(2), 20);
  EXPECT_EQ(aMap.FindKey(3), 30);

  // Test finding items by index
  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(1), 1.0);
  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(2), 2.0);
  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(3), 3.0);

  // Test operator()
  EXPECT_DOUBLE_EQ(aMap(1), 1.0);
  EXPECT_DOUBLE_EQ(aMap(2), 2.0);
  EXPECT_DOUBLE_EQ(aMap(3), 3.0);

  // Test finding items by key
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(10), 1.0);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(20), 2.0);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(30), 3.0);

  // Test Contains
  EXPECT_TRUE(aMap.Contains(10));
  EXPECT_TRUE(aMap.Contains(20));
  EXPECT_TRUE(aMap.Contains(30));
  EXPECT_FALSE(aMap.Contains(40));
}

TEST(NCollection_IndexedDataMapTest, DuplicateKey)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Add initial elements
  int index1 = aMap.Add(10, 1.0);
  int index2 = aMap.Add(20, 2.0);

  EXPECT_EQ(index1, 1);
  EXPECT_EQ(index2, 2);

  // Try to add a duplicate - should return the existing index and not change the item
  int indexDup = aMap.Add(10, 3.0);
  EXPECT_EQ(indexDup, 1);
  EXPECT_EQ(aMap.Extent(), 2);                 // Size should not change
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(10), 1.0); // Original value should be preserved
}

TEST(NCollection_IndexedDataMapTest, ChangeValues)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Add elements
  aMap.Add(10, 1.0);
  aMap.Add(20, 2.0);

  // Change values through ChangeFromIndex
  aMap.ChangeFromIndex(1) = 1.5;
  aMap.ChangeFromIndex(2) = 2.5;

  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(1), 1.5);
  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(2), 2.5);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(10), 1.5);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(20), 2.5);

  // Change values through ChangeFromKey
  aMap.ChangeFromKey(10) = 1.75;
  aMap.ChangeFromKey(20) = 2.75;

  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(1), 1.75);
  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(2), 2.75);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(10), 1.75);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(20), 2.75);

  // Change values through operator()
  aMap(1) = 1.9;
  aMap(2) = 2.9;

  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(1), 1.9);
  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(2), 2.9);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(10), 1.9);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(20), 2.9);
}

TEST(NCollection_IndexedDataMapTest, SeekTests)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Add elements
  aMap.Add(10, 1.0);
  aMap.Add(20, 2.0);

  // Test Seek
  const ItemType* item1 = aMap.Seek(10);
  const ItemType* item2 = aMap.Seek(20);
  const ItemType* item3 = aMap.Seek(30); // Not present

  EXPECT_TRUE(item1 != nullptr);
  EXPECT_TRUE(item2 != nullptr);
  EXPECT_TRUE(item3 == nullptr);

  EXPECT_DOUBLE_EQ(*item1, 1.0);
  EXPECT_DOUBLE_EQ(*item2, 2.0);

  // Test ChangeSeek
  ItemType* changeItem1 = aMap.ChangeSeek(10);
  ItemType* changeItem2 = aMap.ChangeSeek(20);
  ItemType* changeItem3 = aMap.ChangeSeek(30); // Not present

  EXPECT_TRUE(changeItem1 != nullptr);
  EXPECT_TRUE(changeItem2 != nullptr);
  EXPECT_TRUE(changeItem3 == nullptr);

  *changeItem1 = 1.5;
  *changeItem2 = 2.5;

  EXPECT_DOUBLE_EQ(aMap.FindFromKey(10), 1.5);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(20), 2.5);

  // Test FindFromKey with copy
  ItemType value;
  bool     found = aMap.FindFromKey(10, value);
  EXPECT_TRUE(found);
  EXPECT_DOUBLE_EQ(value, 1.5);

  found = aMap.FindFromKey(30, value);
  EXPECT_FALSE(found);
}

TEST(NCollection_IndexedDataMapTest, RemoveLast)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Add elements
  aMap.Add(10, 1.0);
  aMap.Add(20, 2.0);
  aMap.Add(30, 3.0);

  // Remove the last element
  aMap.RemoveLast();

  EXPECT_EQ(aMap.Extent(), 2);
  EXPECT_TRUE(aMap.Contains(10));
  EXPECT_TRUE(aMap.Contains(20));
  EXPECT_FALSE(aMap.Contains(30));

  // Check indices - they should still be sequential
  EXPECT_EQ(aMap.FindIndex(10), 1);
  EXPECT_EQ(aMap.FindIndex(20), 2);
}

TEST(NCollection_IndexedDataMapTest, RemoveFromIndex)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Add elements
  aMap.Add(10, 1.0);
  aMap.Add(20, 2.0);
  aMap.Add(30, 3.0);
  aMap.Add(40, 4.0);

  // Remove element at index 2
  aMap.RemoveFromIndex(2);

  EXPECT_EQ(aMap.Extent(), 3);
  EXPECT_TRUE(aMap.Contains(10));
  EXPECT_FALSE(aMap.Contains(20)); // Removed
  EXPECT_TRUE(aMap.Contains(30));
  EXPECT_TRUE(aMap.Contains(40));

  // Test how the indices were rearranged
  // Index 2 should now contain what was previously at the last position
  EXPECT_EQ(aMap.FindKey(2), 40); // Last element moved to position 2
  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(2), 4.0);

  EXPECT_EQ(aMap.FindKey(1), 10); // First element unchanged
  EXPECT_EQ(aMap.FindKey(3), 30); // Third element unchanged
}

TEST(NCollection_IndexedDataMapTest, RemoveKey)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Add elements
  aMap.Add(10, 1.0);
  aMap.Add(20, 2.0);
  aMap.Add(30, 3.0);

  // Remove by key
  aMap.RemoveKey(20);

  EXPECT_EQ(aMap.Extent(), 2);
  EXPECT_TRUE(aMap.Contains(10));
  EXPECT_FALSE(aMap.Contains(20));
  EXPECT_TRUE(aMap.Contains(30));

  // Try to remove non-existent key
  aMap.RemoveKey(50); // Should have no effect
  EXPECT_EQ(aMap.Extent(), 2);
}

TEST(NCollection_IndexedDataMapTest, Substitute)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Add elements
  aMap.Add(10, 1.0);
  aMap.Add(20, 2.0);
  aMap.Add(30, 3.0);

  // Substitute key and value at index 2
  aMap.Substitute(2, 25, 2.5);

  EXPECT_EQ(aMap.Extent(), 3);
  EXPECT_TRUE(aMap.Contains(10));
  EXPECT_FALSE(aMap.Contains(20));
  EXPECT_TRUE(aMap.Contains(25));
  EXPECT_TRUE(aMap.Contains(30));

  // Check indices after substitution
  EXPECT_EQ(aMap.FindIndex(10), 1);
  EXPECT_EQ(aMap.FindIndex(25), 2);
  EXPECT_EQ(aMap.FindIndex(30), 3);

  // Check values after substitution
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(10), 1.0);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(25), 2.5);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(30), 3.0);
}

TEST(NCollection_IndexedDataMapTest, Swap)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Add elements
  aMap.Add(10, 1.0);
  aMap.Add(20, 2.0);
  aMap.Add(30, 3.0);

  // Swap elements at indices 1 and 3
  aMap.Swap(1, 3);

  EXPECT_EQ(aMap.Extent(), 3);

  // Check that keys maintained their values
  EXPECT_TRUE(aMap.Contains(10));
  EXPECT_TRUE(aMap.Contains(20));
  EXPECT_TRUE(aMap.Contains(30));

  // But indices are swapped
  EXPECT_EQ(aMap.FindKey(1), 30);
  EXPECT_EQ(aMap.FindKey(2), 20);
  EXPECT_EQ(aMap.FindKey(3), 10);

  EXPECT_EQ(aMap.FindIndex(10), 3);
  EXPECT_EQ(aMap.FindIndex(20), 2);
  EXPECT_EQ(aMap.FindIndex(30), 1);

  // And values follow their keys
  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(1), 3.0);
  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(2), 2.0);
  EXPECT_DOUBLE_EQ(aMap.FindFromIndex(3), 1.0);

  EXPECT_DOUBLE_EQ(aMap.FindFromKey(10), 1.0);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(20), 2.0);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(30), 3.0);
}

TEST(NCollection_IndexedDataMapTest, Clear)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Add elements
  aMap.Add(10, 1.0);
  aMap.Add(20, 2.0);
  aMap.Add(30, 3.0);

  EXPECT_EQ(aMap.Extent(), 3);

  // Clear the map
  aMap.Clear();

  EXPECT_TRUE(aMap.IsEmpty());
  EXPECT_EQ(aMap.Extent(), 0);

  // Ensure we can add elements again
  int index1 = aMap.Add(40, 4.0);
  EXPECT_EQ(index1, 1);
  EXPECT_EQ(aMap.Extent(), 1);
}

TEST(NCollection_IndexedDataMapTest, CopyConstructor)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap1;

  // Add elements to first map
  aMap1.Add(10, 1.0);
  aMap1.Add(20, 2.0);
  aMap1.Add(30, 3.0);

  // Create a copy
  NCollection_IndexedDataMap<KeyType, ItemType> aMap2(aMap1);

  // Check that copy has the same elements
  EXPECT_EQ(aMap2.Extent(), 3);
  EXPECT_TRUE(aMap2.Contains(10));
  EXPECT_TRUE(aMap2.Contains(20));
  EXPECT_TRUE(aMap2.Contains(30));

  // Check indices
  EXPECT_EQ(aMap2.FindIndex(10), 1);
  EXPECT_EQ(aMap2.FindIndex(20), 2);
  EXPECT_EQ(aMap2.FindIndex(30), 3);

  // Check values
  EXPECT_DOUBLE_EQ(aMap2.FindFromKey(10), 1.0);
  EXPECT_DOUBLE_EQ(aMap2.FindFromKey(20), 2.0);
  EXPECT_DOUBLE_EQ(aMap2.FindFromKey(30), 3.0);

  // Modify original to ensure deep copy
  aMap1.Add(40, 4.0);
  EXPECT_EQ(aMap1.Extent(), 4);
  EXPECT_EQ(aMap2.Extent(), 3);
  EXPECT_FALSE(aMap2.Contains(40));
}

TEST(NCollection_IndexedDataMapTest, AssignmentOperator)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap1;
  NCollection_IndexedDataMap<KeyType, ItemType> aMap2;

  // Add elements to first map
  aMap1.Add(10, 1.0);
  aMap1.Add(20, 2.0);

  // Add different elements to second map
  aMap2.Add(30, 3.0);
  aMap2.Add(40, 4.0);
  aMap2.Add(50, 5.0);

  // Assign first to second
  aMap2 = aMap1;

  // Check that second map now matches first
  EXPECT_EQ(aMap2.Extent(), 2);
  EXPECT_TRUE(aMap2.Contains(10));
  EXPECT_TRUE(aMap2.Contains(20));
  EXPECT_FALSE(aMap2.Contains(30));
  EXPECT_FALSE(aMap2.Contains(40));
  EXPECT_FALSE(aMap2.Contains(50));

  // Check values
  EXPECT_DOUBLE_EQ(aMap2.FindFromKey(10), 1.0);
  EXPECT_DOUBLE_EQ(aMap2.FindFromKey(20), 2.0);
}

TEST(NCollection_IndexedDataMapTest, Iterator)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Add elements
  aMap.Add(10, 1.0);
  aMap.Add(20, 2.0);
  aMap.Add(30, 3.0);

  // Use iterator to check all elements
  bool   found10 = false;
  bool   found20 = false;
  bool   found30 = false;
  size_t count   = 0;

  for (NCollection_IndexedDataMap<KeyType, ItemType>::Iterator it(aMap); it.More();
       it.Next(), ++count)
  {
    const KeyType&  key   = it.Key();
    const ItemType& value = it.Value();

    if (key == 10 && value == 1.0)
    {
      found10 = true;
    }
    else if (key == 20 && value == 2.0)
    {
      found20 = true;
    }
    else if (key == 30 && value == 3.0)
    {
      found30 = true;
    }
  }

  EXPECT_EQ(count, 3);
  EXPECT_TRUE(found10);
  EXPECT_TRUE(found20);
  EXPECT_TRUE(found30);

  // Test modification through iterator
  for (NCollection_IndexedDataMap<KeyType, ItemType>::Iterator it(aMap); it.More(); it.Next())
  {
    it.ChangeValue() *= 2;
  }

  EXPECT_DOUBLE_EQ(aMap.FindFromKey(10), 2.0);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(20), 4.0);
  EXPECT_DOUBLE_EQ(aMap.FindFromKey(30), 6.0);
}

TEST(NCollection_IndexedDataMapTest, StlIterator)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap;

  // Add elements
  aMap.Add(10, 1.0);
  aMap.Add(20, 2.0);
  aMap.Add(30, 3.0);

  // Use STL-style iterator
  bool   found1 = false;
  bool   found2 = false;
  bool   found3 = false;
  size_t count  = 0;

  for (auto it = aMap.begin(); it != aMap.end(); ++it, ++count)
  {
    if (*it == 1.0)
    {
      found1 = true;
    }
    else if (*it == 2.0)
    {
      found2 = true;
    }
    else if (*it == 3.0)
    {
      found3 = true;
    }
  }

  EXPECT_EQ(count, 3);
  EXPECT_TRUE(found1);
  EXPECT_TRUE(found2);
  EXPECT_TRUE(found3);

  // Test const iterator
  count  = 0;
  found1 = false;
  found2 = false;
  found3 = false;

  for (auto it = aMap.cbegin(); it != aMap.cend(); ++it, ++count)
  {
    if (*it == 1.0)
    {
      found1 = true;
    }
    else if (*it == 2.0)
    {
      found2 = true;
    }
    else if (*it == 3.0)
    {
      found3 = true;
    }
  }

  EXPECT_EQ(count, 3);
  EXPECT_TRUE(found1);
  EXPECT_TRUE(found2);
  EXPECT_TRUE(found3);
}

TEST(NCollection_IndexedDataMapTest, StringKeys)
{
  // Test with string keys
  NCollection_IndexedDataMap<TCollection_AsciiString, ItemType> aStringMap;

  // Add string keys
  int index1 = aStringMap.Add(TCollection_AsciiString("First"), 1.0);
  int index2 = aStringMap.Add(TCollection_AsciiString("Second"), 2.0);
  int index3 = aStringMap.Add(TCollection_AsciiString("Third"), 3.0);

  EXPECT_EQ(index1, 1);
  EXPECT_EQ(index2, 2);
  EXPECT_EQ(index3, 3);

  // Find by key
  EXPECT_EQ(aStringMap.FindIndex(TCollection_AsciiString("First")), 1);
  EXPECT_EQ(aStringMap.FindIndex(TCollection_AsciiString("Second")), 2);
  EXPECT_EQ(aStringMap.FindIndex(TCollection_AsciiString("Third")), 3);

  // Find by index
  EXPECT_TRUE(aStringMap.FindKey(1).IsEqual("First"));
  EXPECT_TRUE(aStringMap.FindKey(2).IsEqual("Second"));
  EXPECT_TRUE(aStringMap.FindKey(3).IsEqual("Third"));

  // Find values
  EXPECT_DOUBLE_EQ(aStringMap.FindFromKey(TCollection_AsciiString("First")), 1.0);
  EXPECT_DOUBLE_EQ(aStringMap.FindFromKey(TCollection_AsciiString("Second")), 2.0);
  EXPECT_DOUBLE_EQ(aStringMap.FindFromKey(TCollection_AsciiString("Third")), 3.0);
}

TEST(NCollection_IndexedDataMapTest, ComplexKeyAndValue)
{
  // Create map with custom key and hasher
  NCollection_IndexedDataMap<TestKey, TestItem> aComplexMap;

  // Add complex keys and values
  TestKey key1(1, "One");
  TestKey key2(2, "Two");
  TestKey key3(3, "Three");

  TestItem item1(1.1, "Item One");
  TestItem item2(2.2, "Item Two");
  TestItem item3(3.3, "Item Three");

  int index1 = aComplexMap.Add(key1, item1);
  int index2 = aComplexMap.Add(key2, item2);
  int index3 = aComplexMap.Add(key3, item3);

  EXPECT_EQ(index1, 1);
  EXPECT_EQ(index2, 2);
  EXPECT_EQ(index3, 3);

  // Find by key
  EXPECT_EQ(aComplexMap.FindIndex(key1), 1);
  EXPECT_EQ(aComplexMap.FindIndex(key2), 2);
  EXPECT_EQ(aComplexMap.FindIndex(key3), 3);

  // Find by index
  EXPECT_EQ(aComplexMap.FindKey(1).GetId(), 1);
  EXPECT_EQ(aComplexMap.FindKey(2).GetId(), 2);
  EXPECT_EQ(aComplexMap.FindKey(3).GetId(), 3);

  // Find values
  EXPECT_DOUBLE_EQ(aComplexMap.FindFromKey(key1).GetValue(), 1.1);
  EXPECT_DOUBLE_EQ(aComplexMap.FindFromKey(key2).GetValue(), 2.2);
  EXPECT_DOUBLE_EQ(aComplexMap.FindFromKey(key3).GetValue(), 3.3);

  // Test contains
  EXPECT_TRUE(aComplexMap.Contains(TestKey(1, "One")));
  EXPECT_FALSE(aComplexMap.Contains(TestKey(4, "Four")));

  // Test value modification
  aComplexMap.ChangeFromKey(key1).SetValue(1.5);
  EXPECT_DOUBLE_EQ(aComplexMap.FindFromKey(key1).GetValue(), 1.5);
}

TEST(NCollection_IndexedDataMapTest, Exchange)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap1;
  NCollection_IndexedDataMap<KeyType, ItemType> aMap2;

  // Add elements to first map
  aMap1.Add(10, 1.0);
  aMap1.Add(20, 2.0);

  // Add different elements to second map
  aMap2.Add(30, 3.0);
  aMap2.Add(40, 4.0);
  aMap2.Add(50, 5.0);

  // Exchange maps
  aMap1.Exchange(aMap2);

  // Check that maps are exchanged
  EXPECT_EQ(aMap1.Extent(), 3);
  EXPECT_TRUE(aMap1.Contains(30));
  EXPECT_TRUE(aMap1.Contains(40));
  EXPECT_TRUE(aMap1.Contains(50));
  EXPECT_DOUBLE_EQ(aMap1.FindFromKey(30), 3.0);
  EXPECT_DOUBLE_EQ(aMap1.FindFromKey(40), 4.0);
  EXPECT_DOUBLE_EQ(aMap1.FindFromKey(50), 5.0);

  EXPECT_EQ(aMap2.Extent(), 2);
  EXPECT_TRUE(aMap2.Contains(10));
  EXPECT_TRUE(aMap2.Contains(20));
  EXPECT_DOUBLE_EQ(aMap2.FindFromKey(10), 1.0);
  EXPECT_DOUBLE_EQ(aMap2.FindFromKey(20), 2.0);
}

TEST(NCollection_IndexedDataMapTest, ReSize)
{
  NCollection_IndexedDataMap<KeyType, ItemType> aMap(3); // Start with small bucket count

  // Add many elements to trigger resize
  for (int i = 1; i <= 100; ++i)
  {
    aMap.Add(i, static_cast<double>(i) / 10.0);
  }

  // Verify all elements are present
  EXPECT_EQ(aMap.Extent(), 100);
  for (int i = 1; i <= 100; ++i)
  {
    EXPECT_TRUE(aMap.Contains(i));
    EXPECT_EQ(aMap.FindIndex(i), i);
    EXPECT_DOUBLE_EQ(aMap.FindFromKey(i), static_cast<double>(i) / 10.0);
  }

  // Explicitly resize
  aMap.ReSize(200);

  // Check that elements are still accessible
  EXPECT_EQ(aMap.Extent(), 100);
  for (int i = 1; i <= 100; ++i)
  {
    EXPECT_TRUE(aMap.Contains(i));
    EXPECT_EQ(aMap.FindIndex(i), i);
    EXPECT_DOUBLE_EQ(aMap.FindFromKey(i), static_cast<double>(i) / 10.0);
  }
}

TEST(NCollection_IndexedDataMapTest, STLAlgorithmCompatibility_MinMax)
{
  NCollection_IndexedDataMap<int, int> aMap;

  // Add some sequential values to make results predictable
  for (int anIdx = 10; anIdx <= 50; anIdx += 5)
  {
    aMap.Add(anIdx, anIdx * 2);
  }

  EXPECT_FALSE(aMap.IsEmpty());

  // Test that STL algorithms work with OCCT iterators
  auto aMinElement = std::min_element(aMap.cbegin(), aMap.cend());
  auto aMaxElement = std::max_element(aMap.cbegin(), aMap.cend());

  EXPECT_TRUE(aMinElement != aMap.cend());
  EXPECT_TRUE(aMaxElement != aMap.cend());
  EXPECT_LE(*aMinElement, *aMaxElement);
}

TEST(NCollection_IndexedDataMapTest, STLAlgorithmCompatibility_Find)
{
  NCollection_IndexedDataMap<int, int> aMap;

  // Add known values
  aMap.Add(100, 200);
  aMap.Add(200, 400);
  aMap.Add(300, 600);

  // Test std::find compatibility
  auto aFound = std::find(aMap.cbegin(), aMap.cend(), 200);
  EXPECT_TRUE(aFound != aMap.cend());
  EXPECT_EQ(*aFound, 200);

  // Test finding non-existent value
  auto aNotFound = std::find(aMap.cbegin(), aMap.cend(), 999);
  EXPECT_TRUE(aNotFound == aMap.cend());
}

TEST(NCollection_IndexedDataMapTest, TryBound_NewKey)
{
  NCollection_IndexedDataMap<int, double> aMap;

  // TryBound on new key should add and return reference to new item
  double& aRef1 = aMap.TryBound(10, 1.0);
  EXPECT_DOUBLE_EQ(1.0, aRef1);
  EXPECT_DOUBLE_EQ(1.0, aMap.FindFromKey(10));
  EXPECT_EQ(1, aMap.Extent());
  EXPECT_EQ(1, aMap.FindIndex(10));

  // Add another key
  double& aRef2 = aMap.TryBound(20, 2.0);
  EXPECT_DOUBLE_EQ(2.0, aRef2);
  EXPECT_DOUBLE_EQ(2.0, aMap.FindFromKey(20));
  EXPECT_EQ(2, aMap.Extent());
  EXPECT_EQ(2, aMap.FindIndex(20));

  // Modify through reference
  aRef1 = 1.5;
  EXPECT_DOUBLE_EQ(1.5, aMap.FindFromKey(10));
}

TEST(NCollection_IndexedDataMapTest, TryBound_ExistingKey)
{
  NCollection_IndexedDataMap<int, double> aMap;

  // First add a key
  aMap.Add(10, 1.0);
  EXPECT_DOUBLE_EQ(1.0, aMap.FindFromKey(10));

  // TryBound on existing key should return reference to existing item (not replace)
  double& aRef = aMap.TryBound(10, 999.0);
  EXPECT_DOUBLE_EQ(1.0, aRef);                 // Should be the original value
  EXPECT_DOUBLE_EQ(1.0, aMap.FindFromKey(10)); // Map should have original value
  EXPECT_EQ(1, aMap.Extent());                 // Size should not change
  EXPECT_EQ(1, aMap.FindIndex(10));            // Index should be the same

  // Modify through reference
  aRef = 1.5;
  EXPECT_DOUBLE_EQ(1.5, aMap.FindFromKey(10));
}

TEST(NCollection_IndexedDataMapTest, TryBound_MoveSemantics)
{
  NCollection_IndexedDataMap<TCollection_AsciiString, double> aMap;

  // Test with rvalue key
  double& aRef1 = aMap.TryBound(TCollection_AsciiString("Key1"), 1.0);
  EXPECT_DOUBLE_EQ(1.0, aRef1);
  EXPECT_EQ(1, aMap.Extent());

  // Test with existing key - should not replace
  double& aRef2 = aMap.TryBound(TCollection_AsciiString("Key1"), 999.0);
  EXPECT_DOUBLE_EQ(1.0, aRef2); // Should return original value
  EXPECT_EQ(1, aMap.Extent());  // Size should not change
}

TEST(NCollection_IndexedDataMapTest, TryBound_ReferenceValidity)
{
  NCollection_IndexedDataMap<int, double> aMap;

  // Add multiple keys and verify references point to map contents
  double& aRef1 = aMap.TryBound(10, 1.0);
  double& aRef2 = aMap.TryBound(20, 2.0);
  double& aRef3 = aMap.TryBound(30, 3.0);

  // Verify values
  EXPECT_DOUBLE_EQ(1.0, aRef1);
  EXPECT_DOUBLE_EQ(2.0, aRef2);
  EXPECT_DOUBLE_EQ(3.0, aRef3);

  // Verify references point to actual map values
  EXPECT_EQ(&aRef1, &aMap.ChangeFromIndex(1));
  EXPECT_EQ(&aRef2, &aMap.ChangeFromIndex(2));
  EXPECT_EQ(&aRef3, &aMap.ChangeFromIndex(3));
}

TEST(NCollection_IndexedDataMapTest, Bind_NewKey)
{
  NCollection_IndexedDataMap<int, double> aMap;

  // Bind new key should return true
  bool isNew1 = aMap.Bind(10, 1.0);
  EXPECT_TRUE(isNew1);
  EXPECT_DOUBLE_EQ(1.0, aMap.FindFromKey(10));
  EXPECT_EQ(1, aMap.Extent());
  EXPECT_EQ(1, aMap.FindIndex(10));

  // Bind another new key
  bool isNew2 = aMap.Bind(20, 2.0);
  EXPECT_TRUE(isNew2);
  EXPECT_DOUBLE_EQ(2.0, aMap.FindFromKey(20));
  EXPECT_EQ(2, aMap.Extent());
  EXPECT_EQ(2, aMap.FindIndex(20));
}

TEST(NCollection_IndexedDataMapTest, Bind_ExistingKey)
{
  NCollection_IndexedDataMap<int, double> aMap;

  // First bind a key
  aMap.Bind(10, 1.0);
  EXPECT_DOUBLE_EQ(1.0, aMap.FindFromKey(10));

  // Bind same key with different value - should overwrite and return false
  bool isNew = aMap.Bind(10, 999.0);
  EXPECT_FALSE(isNew);                           // Key already existed
  EXPECT_DOUBLE_EQ(999.0, aMap.FindFromKey(10)); // Value should be replaced
  EXPECT_EQ(1, aMap.Extent());                   // Size should not change
  EXPECT_EQ(1, aMap.FindIndex(10));              // Index should be the same
}

TEST(NCollection_IndexedDataMapTest, Bind_MoveSemantics)
{
  NCollection_IndexedDataMap<TCollection_AsciiString, double> aMap;

  // Test with rvalue key
  bool isNew1 = aMap.Bind(TCollection_AsciiString("Key1"), 1.0);
  EXPECT_TRUE(isNew1);
  EXPECT_EQ(1, aMap.Extent());
  EXPECT_DOUBLE_EQ(1.0, aMap.FindFromKey(TCollection_AsciiString("Key1")));

  // Test with existing key - should replace
  bool isNew2 = aMap.Bind(TCollection_AsciiString("Key1"), 999.0);
  EXPECT_FALSE(isNew2);        // Key existed
  EXPECT_EQ(1, aMap.Extent()); // Size should not change
  EXPECT_DOUBLE_EQ(999.0, aMap.FindFromKey(TCollection_AsciiString("Key1"))); // Value replaced
}

TEST(NCollection_IndexedDataMapTest, Bound_NewKey)
{
  NCollection_IndexedDataMap<int, double> aMap;

  // Bound on new key should add and return pointer to new item
  double* pVal1 = aMap.Bound(10, 1.0);
  EXPECT_NE(nullptr, pVal1);
  EXPECT_DOUBLE_EQ(1.0, *pVal1);
  EXPECT_DOUBLE_EQ(1.0, aMap.FindFromKey(10));
  EXPECT_EQ(1, aMap.Extent());
  EXPECT_EQ(1, aMap.FindIndex(10));

  // Add another key
  double* pVal2 = aMap.Bound(20, 2.0);
  EXPECT_NE(nullptr, pVal2);
  EXPECT_DOUBLE_EQ(2.0, *pVal2);
  EXPECT_DOUBLE_EQ(2.0, aMap.FindFromKey(20));
  EXPECT_EQ(2, aMap.Extent());
  EXPECT_EQ(2, aMap.FindIndex(20));

  // Modify through pointer
  *pVal1 = 1.5;
  EXPECT_DOUBLE_EQ(1.5, aMap.FindFromKey(10));
}

TEST(NCollection_IndexedDataMapTest, Bound_ExistingKey)
{
  NCollection_IndexedDataMap<int, double> aMap;

  // First add a key
  aMap.Add(10, 1.0);
  EXPECT_DOUBLE_EQ(1.0, aMap.FindFromKey(10));

  // Bound on existing key should replace value and return pointer
  double* pVal = aMap.Bound(10, 999.0);
  EXPECT_NE(nullptr, pVal);
  EXPECT_DOUBLE_EQ(999.0, *pVal);                // Should be the new value
  EXPECT_DOUBLE_EQ(999.0, aMap.FindFromKey(10)); // Map should have new value
  EXPECT_EQ(1, aMap.Extent());                   // Size should not change
  EXPECT_EQ(1, aMap.FindIndex(10));              // Index should be the same

  // Modify through pointer
  *pVal = 1.5;
  EXPECT_DOUBLE_EQ(1.5, aMap.FindFromKey(10));
}

TEST(NCollection_IndexedDataMapTest, Bound_MoveSemantics)
{
  NCollection_IndexedDataMap<TCollection_AsciiString, double> aMap;

  // Test with rvalue key
  double* pVal1 = aMap.Bound(TCollection_AsciiString("Key1"), 1.0);
  EXPECT_NE(nullptr, pVal1);
  EXPECT_DOUBLE_EQ(1.0, *pVal1);
  EXPECT_EQ(1, aMap.Extent());

  // Test with existing key - should replace
  double* pVal2 = aMap.Bound(TCollection_AsciiString("Key1"), 999.0);
  EXPECT_NE(nullptr, pVal2);
  EXPECT_DOUBLE_EQ(999.0, *pVal2); // Should return new value
  EXPECT_EQ(1, aMap.Extent());     // Size should not change
}

TEST(NCollection_IndexedDataMapTest, Bind_Bound_VsAdd_Behavior)
{
  // Test the difference between Add (no overwrite) vs Bind (overwrites)
  NCollection_IndexedDataMap<int, double> aMap;

  // Add initial value
  aMap.Add(10, 1.0);
  EXPECT_DOUBLE_EQ(1.0, aMap.FindFromKey(10));

  // Add same key - should NOT overwrite
  aMap.Add(10, 999.0);
  EXPECT_DOUBLE_EQ(1.0, aMap.FindFromKey(10)); // Still 1.0

  // Bind same key - SHOULD overwrite
  aMap.Bind(10, 2.0);
  EXPECT_DOUBLE_EQ(2.0, aMap.FindFromKey(10)); // Now 2.0

  // Bound same key - SHOULD overwrite
  aMap.Bound(10, 3.0);
  EXPECT_DOUBLE_EQ(3.0, aMap.FindFromKey(10)); // Now 3.0

  // TryBound same key - should NOT overwrite
  aMap.TryBound(10, 999.0);
  EXPECT_DOUBLE_EQ(3.0, aMap.FindFromKey(10)); // Still 3.0
}

// Tests for Emplace methods
TEST(NCollection_IndexedDataMapTest, Emplace_NewKey)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;

  // Emplace with new key should return index
  int aIdx = aMap.Emplace(1, "One");
  EXPECT_EQ(1, aIdx);
  EXPECT_EQ(1, aMap.Extent());
  EXPECT_STREQ("One", aMap.FindFromKey(1).ToCString());
}

TEST(NCollection_IndexedDataMapTest, Emplace_ExistingKey)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;
  aMap.Add(1, "One");

  // Emplace on existing key should destroy+reconstruct (return existing index)
  int aIdx = aMap.Emplace(1, "New One");
  EXPECT_EQ(1, aIdx);
  EXPECT_EQ(1, aMap.Extent());
  EXPECT_STREQ("New One", aMap.FindFromKey(1).ToCString());
}

TEST(NCollection_IndexedDataMapTest, Emplaced_NewKey)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;

  // Emplaced with new key should return reference
  TCollection_AsciiString& aRef = aMap.Emplaced(1, "One");
  EXPECT_STREQ("One", aRef.ToCString());
  EXPECT_EQ(1, aMap.Extent());
}

TEST(NCollection_IndexedDataMapTest, Emplaced_ExistingKey)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;
  aMap.Add(1, "One");

  // Emplaced on existing should destroy+reconstruct and return reference
  TCollection_AsciiString& aRef = aMap.Emplaced(1, "New One");
  EXPECT_STREQ("New One", aRef.ToCString());
}

TEST(NCollection_IndexedDataMapTest, TryEmplace_NewKey)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;

  // TryEmplace with new key should return index
  int aIdx = aMap.TryEmplace(1, "One");
  EXPECT_EQ(1, aIdx);
  EXPECT_EQ(1, aMap.Extent());
  EXPECT_STREQ("One", aMap.FindFromKey(1).ToCString());
}

TEST(NCollection_IndexedDataMapTest, TryEmplace_ExistingKey)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;
  aMap.Add(1, "One");

  // TryEmplace on existing key should NOT modify (return existing index)
  int aIdx = aMap.TryEmplace(1, "New One");
  EXPECT_EQ(1, aIdx);
  EXPECT_EQ(1, aMap.Extent());
  EXPECT_STREQ("One", aMap.FindFromKey(1).ToCString()); // Still original
}

TEST(NCollection_IndexedDataMapTest, TryEmplaced_NewKey)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;

  // TryEmplaced with new key should return reference
  TCollection_AsciiString& aRef = aMap.TryEmplaced(1, "One");
  EXPECT_STREQ("One", aRef.ToCString());
  EXPECT_EQ(1, aMap.Extent());
}

TEST(NCollection_IndexedDataMapTest, TryEmplaced_ExistingKey)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;
  aMap.Add(1, "One");

  // TryEmplaced on existing should return existing without modification
  TCollection_AsciiString& aRef = aMap.TryEmplaced(1, "New One");
  EXPECT_STREQ("One", aRef.ToCString()); // Original value
}

// Tests for hasher constructor
TEST(NCollection_IndexedDataMapTest, HasherConstructorCopy)
{
  // Custom hasher with state
  struct StatefulHasher
  {
    int mySalt;

    StatefulHasher(int theSalt = 0)
        : mySalt(theSalt)
    {
    }

    size_t operator()(int theKey) const { return std::hash<int>{}(theKey + mySalt); }

    bool operator()(int theKey1, int theKey2) const { return theKey1 == theKey2; }
  };

  StatefulHasher                                                           aHasher(42);
  NCollection_IndexedDataMap<int, TCollection_AsciiString, StatefulHasher> aMap(aHasher, 10);

  aMap.Add(1, "One");
  aMap.Add(2, "Two");

  EXPECT_EQ(2, aMap.Extent());
  EXPECT_STREQ("One", aMap.FindFromIndex(1).ToCString());
  EXPECT_STREQ("Two", aMap.FindFromIndex(2).ToCString());

  // Verify hasher was copied
  const StatefulHasher& aMapHasher = aMap.GetHasher();
  EXPECT_EQ(42, aMapHasher.mySalt);
}

TEST(NCollection_IndexedDataMapTest, HasherConstructorMove)
{
  struct StatefulHasher
  {
    int mySalt;

    StatefulHasher(int theSalt = 0)
        : mySalt(theSalt)
    {
    }

    size_t operator()(int theKey) const { return std::hash<int>{}(theKey + mySalt); }

    bool operator()(int theKey1, int theKey2) const { return theKey1 == theKey2; }
  };

  NCollection_IndexedDataMap<int, TCollection_AsciiString, StatefulHasher> aMap(StatefulHasher(99),
                                                                                10);

  aMap.Add(10, "Ten");

  EXPECT_EQ(1, aMap.Extent());
  EXPECT_EQ(99, aMap.GetHasher().mySalt);
}

TEST(NCollection_IndexedDataMapTest, CopyConstructorPreservesHasher)
{
  struct StatefulHasher
  {
    int mySalt;

    StatefulHasher(int theSalt = 0)
        : mySalt(theSalt)
    {
    }

    size_t operator()(int theKey) const { return std::hash<int>{}(theKey + mySalt); }

    bool operator()(int theKey1, int theKey2) const { return theKey1 == theKey2; }
  };

  NCollection_IndexedDataMap<int, TCollection_AsciiString, StatefulHasher> aMap1(
    StatefulHasher(123),
    10);
  aMap1.Add(1, "One");

  // Copy construct
  NCollection_IndexedDataMap<int, TCollection_AsciiString, StatefulHasher> aMap2(aMap1);

  EXPECT_EQ(123, aMap2.GetHasher().mySalt);
  EXPECT_EQ(1, aMap2.Extent());
  EXPECT_STREQ("One", aMap2.FindFromIndex(1).ToCString());
}

// Tests for Items() key-value pair iteration
TEST(NCollection_IndexedDataMapTest, ItemsIteration)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;
  aMap.Add(1, "One");
  aMap.Add(2, "Two");
  aMap.Add(3, "Three");

  // Test Items() iteration
  std::set<int>         aFoundKeys;
  std::set<std::string> aFoundValues;
  for (auto aKeyValue : aMap.Items())
  {
    aFoundKeys.insert(aKeyValue.Key);
    aFoundValues.insert(aKeyValue.Value.ToCString());
  }

  EXPECT_EQ(3u, aFoundKeys.size());
  EXPECT_TRUE(aFoundKeys.count(1) > 0);
  EXPECT_TRUE(aFoundKeys.count(2) > 0);
  EXPECT_TRUE(aFoundKeys.count(3) > 0);

  EXPECT_EQ(3u, aFoundValues.size());
  EXPECT_TRUE(aFoundValues.count("One") > 0);
  EXPECT_TRUE(aFoundValues.count("Two") > 0);
  EXPECT_TRUE(aFoundValues.count("Three") > 0);
}

TEST(NCollection_IndexedDataMapTest, ItemsStructuredBindings)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;
  aMap.Add(10, "Ten");
  aMap.Add(20, "Twenty");

  // Test structured bindings with Items() - key and value only
  int aSum = 0;
  for (auto [aKey, aValue] : aMap.Items())
  {
    aSum += aKey;
    (void)aValue;
  }

  EXPECT_EQ(30, aSum);
}

TEST(NCollection_IndexedDataMapTest, ItemsModifyValue)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;
  aMap.Add(1, "Original");

  // Test modifying values through Items()
  for (auto [aKey, aValue] : aMap.Items())
  {
    (void)aKey;
    aValue = "Modified";
  }

  EXPECT_STREQ("Modified", aMap.FindFromIndex(1).ToCString());
}

TEST(NCollection_IndexedDataMapTest, ConstItemsIteration)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;
  aMap.Add(1, "One");
  aMap.Add(2, "Two");

  const NCollection_IndexedDataMap<int, TCollection_AsciiString>& aConstMap = aMap;

  // Test const Items() iteration
  int aCount = 0;
  for (const auto& [aKey, aValue] : aConstMap.Items())
  {
    (void)aKey;
    (void)aValue;
    ++aCount;
  }

  EXPECT_EQ(2, aCount);
}

TEST(NCollection_IndexedDataMapTest, IndexedItemsIteration)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;
  aMap.Add(10, "Ten");
  aMap.Add(20, "Twenty");
  aMap.Add(30, "Thirty");

  // Test IndexedItems() with key, value, and index
  int aKeySum   = 0;
  int aIndexSum = 0;
  for (auto [aKey, aValue, anIndex] : aMap.IndexedItems())
  {
    aKeySum += aKey;
    aIndexSum += anIndex;
    (void)aValue;
  }

  EXPECT_EQ(60, aKeySum);  // 10 + 20 + 30
  EXPECT_EQ(6, aIndexSum); // 1 + 2 + 3 (1-based indices)
}

TEST(NCollection_IndexedDataMapTest, ConstIndexedItemsIteration)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;
  aMap.Add(1, "One");
  aMap.Add(2, "Two");

  const NCollection_IndexedDataMap<int, TCollection_AsciiString>& aConstMap = aMap;

  // Test const IndexedItems() iteration with index
  int aIndexSum = 0;
  for (const auto& [aKey, aValue, anIndex] : aConstMap.IndexedItems())
  {
    (void)aKey;
    (void)aValue;
    aIndexSum += anIndex;
  }

  EXPECT_EQ(3, aIndexSum); // 1 + 2 = 3
}

// Test iterator equality for Items() view
TEST(NCollection_IndexedDataMapTest, ItemsIteratorEquality)
{
  NCollection_IndexedDataMap<int, TCollection_AsciiString> aMap;
  aMap.Add(1, "One");
  aMap.Add(2, "Two");
  aMap.Add(3, "Three");

  auto aView = aMap.Items();

  // begin() should equal begin()
  EXPECT_EQ(aView.begin(), aView.begin());

  // end() should equal end()
  EXPECT_EQ(aView.end(), aView.end());

  // begin() should not equal end() when map is not empty
  EXPECT_NE(aView.begin(), aView.end());

  // Two iterators at different positions must NOT be equal
  auto anIt1 = aView.begin();
  auto anIt2 = aView.begin();
  ++anIt2;
  EXPECT_NE(anIt1, anIt2);

  // Iterators at same position should be equal
  auto anIt3 = aView.begin();
  ++anIt3;
  EXPECT_EQ(anIt2, anIt3);
}
