// test_conformal_mesh.cpp
//
// Phase 3a — CGAL Surface_mesh infrastructure tests.
//
// Verifies that ConformalMesh (CGAL::Surface_mesh<Point3>) and the
// mesh_builder factories behave correctly before we build the functionals
// on top of them (Phase 3b).
//
// Test groups
// ───────────
//   Topology     – vertex/edge/face counts, Euler characteristic
//   Traversal    – halfedge iteration around vertex / face / edge
//   PropertyMaps – read/write of lambda, theta, idx, alpha, f:type
//   Validity     – all make_* factories produce valid, consistent meshes

#include "conformal_mesh.hpp"
#include "mesh_builder.hpp"
#include <CGAL/boost/graph/iterator.h>
#include <gtest/gtest.h>
#include <cmath>

using namespace conformallab;

// ════════════════════════════════════════════════════════════
// Topology
// ════════════════════════════════════════════════════════════

// Single triangle: 3 vertices, 1 face, 3 edges.
TEST(ConformalMeshTopology, SingleTriangle)
{
    auto mesh = make_triangle();
    EXPECT_EQ(3u, mesh.number_of_vertices());
    EXPECT_EQ(1u, mesh.number_of_faces());
    EXPECT_EQ(3u, mesh.number_of_edges());
}

// Tetrahedron: V=4, E=6, F=4  →  Euler = 2 (sphere topology).
TEST(ConformalMeshTopology, TetrahedronEuler)
{
    auto mesh = make_tetrahedron();
    EXPECT_EQ(4u, mesh.number_of_vertices());
    EXPECT_EQ(6u, mesh.number_of_edges());
    EXPECT_EQ(4u, mesh.number_of_faces());

    int euler = (int)mesh.number_of_vertices()
              - (int)mesh.number_of_edges()
              + (int)mesh.number_of_faces();
    EXPECT_EQ(2, euler) << "Euler characteristic of closed sphere must be 2";
}

// Two-triangle strip: V=4, E=5, F=2.
// The interior edge (shared diagonal) has no border halfedge.
TEST(ConformalMeshTopology, QuadStrip)
{
    auto mesh = make_quad_strip();
    EXPECT_EQ(4u, mesh.number_of_vertices());
    EXPECT_EQ(5u, mesh.number_of_edges());
    EXPECT_EQ(2u, mesh.number_of_faces());

    // Count interior (non-boundary) edges
    int interior = 0;
    for (auto e : mesh.edges())
        if (!mesh.is_border(e)) ++interior;
    EXPECT_EQ(1, interior) << "Only the shared diagonal should be interior";
}

// Fan with n triangles: V=n+1, E=2n, F=n.
TEST(ConformalMeshTopology, FanCounts)
{
    for (int n : {3, 4, 6, 8}) {
        auto mesh = make_fan(n);
        EXPECT_EQ((std::size_t)(n + 1), mesh.number_of_vertices());
        EXPECT_EQ((std::size_t)(2 * n),  mesh.number_of_edges());
        EXPECT_EQ((std::size_t)(n),       mesh.number_of_faces());
    }
}

// ════════════════════════════════════════════════════════════
// Halfedge Traversal
// ════════════════════════════════════════════════════════════

// For a regular tetrahedron every vertex has valence 3.
TEST(ConformalMeshTraversal, TetrahedronVertexValence)
{
    auto mesh = make_tetrahedron();
    for (auto v : mesh.vertices()) {
        int degree = 0;
        for (auto h : CGAL::halfedges_around_target(v, mesh))
            { (void)h; ++degree; }
        EXPECT_EQ(3, degree) << "Each tetrahedron vertex has degree 3";
    }
}

// For a fan with n triangles the center vertex has valence n.
TEST(ConformalMeshTraversal, FanCenterValence)
{
    for (int n : {3, 5, 7}) {
        auto mesh = make_fan(n);

        // Center vertex is always the first one added (index 0).
        auto center = *mesh.vertices().begin();
        int degree = 0;
        for (auto h : CGAL::halfedges_around_target(center, mesh))
            { (void)h; ++degree; }
        EXPECT_EQ(n, degree)
            << "Fan center vertex must have valence == n=" << n;
    }
}

// Every face of the tetrahedron has exactly 3 halfedges.
TEST(ConformalMeshTraversal, FaceHalfedgeCount)
{
    auto mesh = make_tetrahedron();
    for (auto f : mesh.faces()) {
        int count = 0;
        for (auto h : CGAL::halfedges_around_face(mesh.halfedge(f), mesh))
            { (void)h; ++count; }
        EXPECT_EQ(3, count) << "Each triangular face must have exactly 3 halfedges";
    }
}

// opposite(h) and h share the same edge; opposite(opposite(h)) == h.
TEST(ConformalMeshTraversal, OppositeHalfedgeConsistency)
{
    auto mesh = make_tetrahedron();
    for (auto h : mesh.halfedges()) {
        auto opp = mesh.opposite(h);
        EXPECT_EQ(mesh.edge(h), mesh.edge(opp))
            << "h and opposite(h) must share the same edge";
        EXPECT_EQ(h, mesh.opposite(opp))
            << "opposite(opposite(h)) must equal h";
    }
}

// ════════════════════════════════════════════════════════════
// Property Maps
// ════════════════════════════════════════════════════════════

// The conformal variable lambda can be written and read back per vertex.
TEST(ConformalMeshProperties, VertexLambdaReadWrite)
{
    auto mesh = make_tetrahedron();
    auto [lambda, theta, idx] = add_vertex_properties(mesh);

    double value = 0.0;
    for (auto v : mesh.vertices()) {
        lambda[v] = value;
        value    += 1.0;
    }

    value = 0.0;
    for (auto v : mesh.vertices()) {
        EXPECT_DOUBLE_EQ(value, lambda[v]);
        value += 1.0;
    }
}

// Default solver index is -1 (pinned); can be overwritten.
TEST(ConformalMeshProperties, VertexSolverIndex)
{
    auto mesh = make_tetrahedron();
    auto [lambda, theta, idx] = add_vertex_properties(mesh);

    // All vertices start at -1 (pinned / boundary)
    for (auto v : mesh.vertices())
        EXPECT_EQ(-1, idx[v]) << "Default solver index must be -1";

    // Assign sequential indices
    int i = 0;
    for (auto v : mesh.vertices())
        idx[v] = i++;

    i = 0;
    for (auto v : mesh.vertices())
        EXPECT_EQ(i++, idx[v]);
}

// Edge alpha (intersection angle): set and retrieve per edge.
TEST(ConformalMeshProperties, EdgeAlpha)
{
    auto mesh = make_quad_strip();
    auto alpha = add_edge_properties(mesh);

    const double kAlpha = M_PI / 3.0;   // 60°
    for (auto e : mesh.edges())
        alpha[e] = kAlpha;

    for (auto e : mesh.edges())
        EXPECT_DOUBLE_EQ(kAlpha, alpha[e]);

    EXPECT_EQ(5u, mesh.number_of_edges());
}

// Face geometry type: Euclidean by default, switchable to Hyperbolic.
TEST(ConformalMeshProperties, FaceGeometryType)
{
    auto mesh = make_tetrahedron();
    auto ftype = add_face_properties(mesh);

    // Default: Euclidean
    for (auto f : mesh.faces())
        EXPECT_EQ(static_cast<int>(GeometryType::Euclidean), ftype[f]);

    // Switch all to Hyperbolic
    for (auto f : mesh.faces())
        ftype[f] = static_cast<int>(GeometryType::Hyperbolic);

    for (auto f : mesh.faces())
        EXPECT_EQ(static_cast<int>(GeometryType::Hyperbolic), ftype[f]);
}

// Adding the same named property map twice: second call returns ok=false
// and both handles alias the same storage.
TEST(ConformalMeshProperties, PropertyMapIdempotent)
{
    auto mesh = make_triangle();
    auto [pm1, ok1] = mesh.add_property_map<Vertex_index, double>("v:lambda", 0.0);
    auto [pm2, ok2] = mesh.add_property_map<Vertex_index, double>("v:lambda", 0.0);

    EXPECT_TRUE(ok1)  << "First add_property_map must succeed";
    EXPECT_FALSE(ok2) << "Second add_property_map on existing name must return ok=false";

    // Both handles must alias the same storage
    auto v = *mesh.vertices().begin();
    pm1[v] = 42.0;
    EXPECT_DOUBLE_EQ(42.0, pm2[v]) << "Both handles must alias the same storage";
}

// ════════════════════════════════════════════════════════════
// Mesh validity
// ════════════════════════════════════════════════════════════

// CGAL's built-in validity check must pass for all factory meshes.
TEST(ConformalMeshValidity, AllBuilders)
{
    EXPECT_TRUE(make_triangle().is_valid())    << "triangle mesh invalid";
    EXPECT_TRUE(make_tetrahedron().is_valid()) << "tetrahedron mesh invalid";
    EXPECT_TRUE(make_quad_strip().is_valid())  << "quad strip mesh invalid";
    EXPECT_TRUE(make_fan(6).is_valid())        << "fan-6 mesh invalid";
}
