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

#include <MeshDataBaseForTests.hpp>
#include <mesh/Connectivity.hpp>
#include <mesh/ItemValue.hpp>
#include <mesh/Mesh.hpp>
#include <utils/Messenger.hpp>

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

TEST_CASE("ItemValue", "[mesh]")
{
  SECTION("default constructors")
  {
    REQUIRE_NOTHROW(NodeValue<int>{});
    REQUIRE_NOTHROW(EdgeValue<int>{});
    REQUIRE_NOTHROW(FaceValue<int>{});
    REQUIRE_NOTHROW(CellValue<int>{});

    REQUIRE(not NodeValue<int>{}.isBuilt());
    REQUIRE(not EdgeValue<int>{}.isBuilt());
    REQUIRE(not FaceValue<int>{}.isBuilt());
    REQUIRE(not CellValue<int>{}.isBuilt());
  }

  SECTION("1D")
  {
    const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
    const Connectivity<1>& connectivity  = mesh_1d.connectivity();

    EdgeValue<int>{connectivity};

    REQUIRE_NOTHROW(NodeValue<int>{connectivity});
    REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
    REQUIRE_NOTHROW(FaceValue<int>{connectivity});
    REQUIRE_NOTHROW(CellValue<int>{connectivity});

    REQUIRE(NodeValue<int>{connectivity}.isBuilt());
    REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
    REQUIRE(FaceValue<int>{connectivity}.isBuilt());
    REQUIRE(CellValue<int>{connectivity}.isBuilt());

    NodeValue<int> node_value{connectivity};
    EdgeValue<int> edge_value{connectivity};
    FaceValue<int> face_value{connectivity};
    CellValue<int> cell_value{connectivity};

    REQUIRE(edge_value.size() == node_value.size());
    REQUIRE(face_value.size() == node_value.size());
    REQUIRE(cell_value.size() + 1 == node_value.size());
  }

  SECTION("2D")
  {
    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
    const Connectivity<2>& connectivity  = mesh_2d.connectivity();

    EdgeValue<int>{connectivity};

    REQUIRE_NOTHROW(NodeValue<int>{connectivity});
    REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
    REQUIRE_NOTHROW(FaceValue<int>{connectivity});
    REQUIRE_NOTHROW(CellValue<int>{connectivity});

    REQUIRE(NodeValue<int>{connectivity}.isBuilt());
    REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
    REQUIRE(FaceValue<int>{connectivity}.isBuilt());
    REQUIRE(CellValue<int>{connectivity}.isBuilt());

    EdgeValue<int> edge_value{connectivity};
    FaceValue<int> face_value{connectivity};

    REQUIRE(edge_value.size() == face_value.size());
  }

  SECTION("3D")
  {
    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
    const Connectivity<3>& connectivity  = mesh_3d.connectivity();

    EdgeValue<int>{connectivity};

    REQUIRE_NOTHROW(NodeValue<int>{connectivity});
    REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
    REQUIRE_NOTHROW(FaceValue<int>{connectivity});
    REQUIRE_NOTHROW(CellValue<int>{connectivity});

    REQUIRE(NodeValue<int>{connectivity}.isBuilt());
    REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
    REQUIRE(FaceValue<int>{connectivity}.isBuilt());
    REQUIRE(CellValue<int>{connectivity}.isBuilt());
  }

  SECTION("set values from array")
  {
    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
    const Connectivity<3>& connectivity  = mesh_3d.connectivity();

    CellValue<size_t> cell_value{connectivity};

    Array<size_t> values{cell_value.size()};
    for (size_t i = 0; i < values.size(); ++i) {
      values[i] = i;
    }

    cell_value = values;

    for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
      REQUIRE(cell_value[i_cell] == i_cell);
    }
  }

  SECTION("copy")
  {
    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
    const Connectivity<3>& connectivity  = mesh_3d.connectivity();

    CellValue<int> cell_value{connectivity};
    cell_value.fill(parallel::rank());

    CellValue<const int> const_cell_value;
    const_cell_value = copy(cell_value);

    cell_value.fill(0);

    for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
      REQUIRE(cell_value[i_cell] == 0);
    }

    for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
      REQUIRE(const_cell_value[i_cell] == static_cast<std::int64_t>(parallel::rank()));
    }
  }

  SECTION("WeakItemValue")
  {
    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
    const Connectivity<2>& connectivity  = mesh_2d.connectivity();

    WeakFaceValue<int> weak_face_value{connectivity};

    weak_face_value.fill(parallel::rank());

    FaceValue<const int> face_value{weak_face_value};

    REQUIRE(face_value.connectivity_ptr() == weak_face_value.connectivity_ptr());
  }

#ifndef NDEBUG
  SECTION("error")
  {
    SECTION("checking for build ItemValue")
    {
      CellValue<int> cell_value;
      REQUIRE_THROWS_AS(cell_value[CellId{0}], AssertError);

      FaceValue<int> face_value;
      REQUIRE_THROWS_AS(face_value[FaceId{0}], AssertError);

      EdgeValue<int> edge_value;
      REQUIRE_THROWS_AS(edge_value[EdgeId{0}], AssertError);

      NodeValue<int> node_value;
      REQUIRE_THROWS_AS(node_value[NodeId{0}], AssertError);
    }

    SECTION("checking for bounds violation")
    {
      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
      const Connectivity<3>& connectivity  = mesh_3d.connectivity();

      CellValue<int> cell_value{connectivity};
      CellId invalid_cell_id = connectivity.numberOfCells();
      REQUIRE_THROWS_AS(cell_value[invalid_cell_id], AssertError);

      FaceValue<int> face_value{connectivity};
      FaceId invalid_face_id = connectivity.numberOfFaces();
      REQUIRE_THROWS_AS(face_value[invalid_face_id], AssertError);

      EdgeValue<int> edge_value{connectivity};
      EdgeId invalid_edge_id = connectivity.numberOfEdges();
      REQUIRE_THROWS_AS(edge_value[invalid_edge_id], AssertError);

      NodeValue<int> node_value{connectivity};
      NodeId invalid_node_id = connectivity.numberOfNodes();
      REQUIRE_THROWS_AS(node_value[invalid_node_id], AssertError);
    }

    SECTION("set values from invalid array size")
    {
      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
      const Connectivity<3>& connectivity  = mesh_3d.connectivity();

      CellValue<size_t> cell_value{connectivity};

      Array<size_t> values{3 + cell_value.size()};
      REQUIRE_THROWS_AS(cell_value = values, AssertError);
    }
  }
#endif   // NDEBUG
}