#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_all.hpp>

#include <MeshDataBaseForTests.hpp>
#include <mesh/DualMeshManager.hpp>

#include <mesh/Connectivity.hpp>
#include <mesh/Mesh.hpp>

// clazy:excludeall=non-pod-global-static

TEST_CASE("DualMeshManager", "[mesh]")
{
  SECTION("same 1D dual connectivities ")
  {
    using ConnectivityType = Connectivity<1>;
    using MeshType         = Mesh<ConnectivityType>;

    std::shared_ptr<const MeshType> mesh = MeshDataBaseForTests::get().unordered1DMesh();

    std::shared_ptr p_diamond_dual_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);
    std::shared_ptr p_median_dual_mesh  = DualMeshManager::instance().getMedianDualMesh(*mesh);
    std::shared_ptr p_dual1d_mesh       = DualMeshManager::instance().getDual1DMesh(*mesh);

    // In 1d all these dual meshes are the same
    REQUIRE(p_dual1d_mesh.get() == p_diamond_dual_mesh.get());
    REQUIRE(p_dual1d_mesh.get() == p_median_dual_mesh.get());
  }

  SECTION("2D")
  {
    using ConnectivityType = Connectivity<2>;
    using MeshType         = Mesh<ConnectivityType>;

    std::shared_ptr<const MeshType> mesh = MeshDataBaseForTests::get().hybrid2DMesh();

    SECTION("diamond dual mesh access")
    {
      std::shared_ptr p_diamond_dual_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);

      const auto ref_counter = p_diamond_dual_mesh.use_count();

      {
        std::shared_ptr p_diamond_dual_mesh2 = DualMeshManager::instance().getDiamondDualMesh(*mesh);

        REQUIRE(p_diamond_dual_mesh == p_diamond_dual_mesh2);
        REQUIRE(p_diamond_dual_mesh.use_count() == ref_counter + 1);
      }

      REQUIRE(p_diamond_dual_mesh.use_count() == ref_counter);

      DualMeshManager::instance().deleteMesh(mesh.get());
      REQUIRE(p_diamond_dual_mesh.use_count() == ref_counter - 1);

      // Can delete mesh from the list again. This means that no
      // dual mesh associated with it is managed.
      REQUIRE_NOTHROW(DualMeshManager::instance().deleteMesh(mesh.get()));
      REQUIRE(p_diamond_dual_mesh.use_count() == ref_counter - 1);

      // A new dual mesh is built
      std::shared_ptr p_diamond_dual_mesh_rebuilt = DualMeshManager::instance().getDiamondDualMesh(*mesh);
      REQUIRE(p_diamond_dual_mesh != p_diamond_dual_mesh_rebuilt);
      REQUIRE(p_diamond_dual_mesh.get() != p_diamond_dual_mesh_rebuilt.get());

      // Exactly two references to the dual mesh. One here and
      // one in the manager.
      REQUIRE(p_diamond_dual_mesh_rebuilt.use_count() == 2);
    }

    SECTION("median dual mesh access")
    {
      std::shared_ptr p_median_dual_mesh = DualMeshManager::instance().getMedianDualMesh(*mesh);

      const auto ref_counter = p_median_dual_mesh.use_count();

      {
        std::shared_ptr p_median_dual_mesh2 = DualMeshManager::instance().getMedianDualMesh(*mesh);

        REQUIRE(p_median_dual_mesh == p_median_dual_mesh2);
        REQUIRE(p_median_dual_mesh.use_count() == ref_counter + 1);
      }

      REQUIRE(p_median_dual_mesh.use_count() == ref_counter);

      DualMeshManager::instance().deleteMesh(mesh.get());
      REQUIRE(p_median_dual_mesh.use_count() == ref_counter - 1);

      // Can delete mesh from the list again. This means that no
      // dual mesh associated with it is managed.
      REQUIRE_NOTHROW(DualMeshManager::instance().deleteMesh(mesh.get()));
      REQUIRE(p_median_dual_mesh.use_count() == ref_counter - 1);

      // A new dual mesh is built
      std::shared_ptr p_median_dual_mesh_rebuilt = DualMeshManager::instance().getMedianDualMesh(*mesh);
      REQUIRE(p_median_dual_mesh != p_median_dual_mesh_rebuilt);
      REQUIRE(p_median_dual_mesh.get() != p_median_dual_mesh_rebuilt.get());

      // Exactly two references to the dual mesh. One here and
      // one in the manager.
      REQUIRE(p_median_dual_mesh_rebuilt.use_count() == 2);
    }

    SECTION("check multiple dual mesh using/freeing")
    {
      std::shared_ptr p_median_dual_mesh  = DualMeshManager::instance().getMedianDualMesh(*mesh);
      std::shared_ptr p_diamond_dual_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);

      const auto median_ref_counter  = p_median_dual_mesh.use_count();
      const auto diamond_ref_counter = p_diamond_dual_mesh.use_count();

      REQUIRE(p_median_dual_mesh != p_diamond_dual_mesh);

      REQUIRE_NOTHROW(DualMeshManager::instance().deleteMesh(mesh.get()));
      REQUIRE(p_median_dual_mesh.use_count() == median_ref_counter - 1);
      REQUIRE(p_diamond_dual_mesh.use_count() == diamond_ref_counter - 1);
    }
  }

  SECTION("3D")
  {
    using ConnectivityType = Connectivity<3>;
    using MeshType         = Mesh<ConnectivityType>;

    std::shared_ptr<const MeshType> mesh = MeshDataBaseForTests::get().hybrid3DMesh();

    SECTION("diamond dual mesh access")
    {
      std::shared_ptr p_diamond_dual_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);

      const auto ref_counter = p_diamond_dual_mesh.use_count();

      {
        std::shared_ptr p_diamond_dual_mesh2 = DualMeshManager::instance().getDiamondDualMesh(*mesh);

        REQUIRE(p_diamond_dual_mesh == p_diamond_dual_mesh2);
        REQUIRE(p_diamond_dual_mesh.use_count() == ref_counter + 1);
      }

      REQUIRE(p_diamond_dual_mesh.use_count() == ref_counter);

      DualMeshManager::instance().deleteMesh(mesh.get());
      REQUIRE(p_diamond_dual_mesh.use_count() == ref_counter - 1);

      // Can delete mesh from the list again. This means that no
      // dual mesh associated with it is managed.
      REQUIRE_NOTHROW(DualMeshManager::instance().deleteMesh(mesh.get()));
      REQUIRE(p_diamond_dual_mesh.use_count() == ref_counter - 1);

      // A new dual mesh is built
      std::shared_ptr p_diamond_dual_mesh_rebuilt = DualMeshManager::instance().getDiamondDualMesh(*mesh);
      REQUIRE(p_diamond_dual_mesh != p_diamond_dual_mesh_rebuilt);
      REQUIRE(p_diamond_dual_mesh.get() != p_diamond_dual_mesh_rebuilt.get());

      // Exactly two references to the dual mesh. One here and
      // one in the manager.
      REQUIRE(p_diamond_dual_mesh_rebuilt.use_count() == 2);
    }
  }
}