// test_hyper_ideal_functional.cpp
//
// Phase 3b — HyperIdealFunctional ported to ConformalMesh.
//
// Corresponds to de.varylab.discreteconformal.functional.HyperIdealFunctionalTest.
//
// The Java tests use CoHDS + HyperIdealGenerator to build complex meshes and
// then verify correctness via a finite-difference gradient check (FunctionalTest).
// Here we apply the same gradient-check strategy on simple hand-crafted meshes
// (triangle, tetrahedron) to validate the port independently of the generator.
//
// Test map (Java → C++)
// ──────────────────────
//   testHessian (Ignored)         → GradientCheck_Hessian (SKIPPED, not implemented)
//   testGradientWithHyperIdeal…   → GradientCheck_AllHyperIdealTriangle  (ported)
//   testGradientInExtendedDomain  → GradientCheck_ExtendedDomain         (ported)
//   testGradientWithHyperelliptic → GradientCheck_TetrahedronAllVariable  (ported)
//   testFunctionalAtNaNValue      → EnergyFiniteAtTestPoint               (ported)

#include "conformal_mesh.hpp"
#include "mesh_builder.hpp"
#include "hyper_ideal_functional.hpp"
#include "hyper_ideal_hessian.hpp"
#include <gtest/gtest.h>
#include <Eigen/Dense>
#include <cmath>
#include <vector>

using namespace conformallab;

// ════════════════════════════════════════════════════════════════════════════
// Helpers
// ════════════════════════════════════════════════════════════════════════════

// Build a mesh with all vertices and edges variable, and set reasonable
// DOF values: b_i = b_val, a_e = a_val.
static std::vector<double> make_x_all_variable(
    ConformalMesh& mesh, HyperIdealMaps& maps,
    double b_val, double a_val)
{
    int n = assign_all_dof_indices(mesh, maps);
    std::vector<double> x(static_cast<std::size_t>(n));

    // Vertices first, then edges (matching assign_all_dof_indices order)
    for (auto v : mesh.vertices())
        x[static_cast<std::size_t>(maps.v_idx[v])] = b_val;
    for (auto e : mesh.edges())
        x[static_cast<std::size_t>(maps.e_idx[e])] = a_val;

    return x;
}

// ════════════════════════════════════════════════════════════════════════════
// @Ignore in Java: no Hessian implemented
// ════════════════════════════════════════════════════════════════════════════

TEST(HyperIdealFunctional, HessianSymmetryCheck)
{
    // Hessian is now implemented (numerical FD).  Verify it is symmetric.
    auto mesh = make_triangle();
    auto maps = setup_hyper_ideal_maps(mesh);
    int n = assign_all_dof_indices(mesh, maps);

    std::vector<double> x(static_cast<std::size_t>(n), 0.5);
    auto H = hyper_ideal_hessian_sym(mesh, x, maps);

    Eigen::MatrixXd Hd(H);
    EXPECT_NEAR((Hd - Hd.transpose()).norm(), 0.0, 1e-8)
        << "HyperIdeal Hessian must be symmetric";
}

// ════════════════════════════════════════════════════════════════════════════
// Gradient check: single triangle, all vertices hyper-ideal
//
// Mirrors testGradientWithHyperIdealAndIdealPoints (simplified to single face).
// DOFs: 3 vertex b-values + 3 edge a-values = 6 total.
// Point: b_i = 1.0, a_e = 0.5.
// ════════════════════════════════════════════════════════════════════════════

TEST(HyperIdealFunctional, GradientCheck_AllHyperIdealTriangle)
{
    auto mesh = make_triangle();
    auto maps = setup_hyper_ideal_maps(mesh);
    auto x    = make_x_all_variable(mesh, maps, /*b=*/1.0, /*a=*/0.5);

    EXPECT_TRUE(gradient_check(mesh, x, maps))
        << "Finite-difference gradient check failed on all-hyper-ideal triangle";
}

// ════════════════════════════════════════════════════════════════════════════
// Gradient check in the "extended domain"
//
// Mirrors testGradientInTheExtendedDomain: larger DOF values
// (Java: x_i = 1.2 + |rnd|, so typically > 1.2).
// ════════════════════════════════════════════════════════════════════════════

TEST(HyperIdealFunctional, GradientCheck_ExtendedDomain)
{
    auto mesh = make_triangle();
    auto maps = setup_hyper_ideal_maps(mesh);
    auto x    = make_x_all_variable(mesh, maps, /*b=*/2.0, /*a=*/1.5);

    EXPECT_TRUE(gradient_check(mesh, x, maps))
        << "Finite-difference gradient check failed in extended domain";
}

// ════════════════════════════════════════════════════════════════════════════
// Gradient check: tetrahedron (4 faces), all vertices and edges variable
//
// Mirrors testGradientWithHyperellipticCurve — larger mesh, closed surface.
// DOFs: 4 vertex b-values + 6 edge a-values = 10 total.
// ════════════════════════════════════════════════════════════════════════════

TEST(HyperIdealFunctional, GradientCheck_TetrahedronAllVariable)
{
    auto mesh = make_tetrahedron();
    auto maps = setup_hyper_ideal_maps(mesh);
    auto x    = make_x_all_variable(mesh, maps, /*b=*/1.0, /*a=*/0.5);

    EXPECT_TRUE(gradient_check(mesh, x, maps))
        << "Finite-difference gradient check failed on all-variable tetrahedron";
}

// ════════════════════════════════════════════════════════════════════════════
// Energy is finite (not NaN / Inf)
//
// Mirrors testFunctionalAtNaNValue: evaluates at a test point and checks
// the energy is a real number.  Uses a quad-strip to exercise the interior
// edge path (adjacent faces sharing one non-border edge).
// ════════════════════════════════════════════════════════════════════════════

TEST(HyperIdealFunctional, EnergyFiniteAtTestPoint)
{
    auto mesh = make_quad_strip();
    auto maps = setup_hyper_ideal_maps(mesh);
    int  n    = assign_all_dof_indices(mesh, maps);

    // Values taken from the Java testFunctionalAtNaNValue spirit:
    // large-ish but positive DOF values that could expose degenerate paths.
    std::vector<double> x(static_cast<std::size_t>(n), 1.5);

    auto res = evaluate_hyper_ideal(mesh, x, maps, true, false);

    EXPECT_FALSE(std::isnan(res.energy))  << "Energy must not be NaN";
    EXPECT_FALSE(std::isinf(res.energy))  << "Energy must not be Inf";
}

// ════════════════════════════════════════════════════════════════════════════
// Gradient check: mixed vertices (some ideal = pinned, some hyper-ideal)
//
// One vertex is ideal (v_idx = -1, b = 0), the other two are hyper-ideal.
// This exercises the lij / αij branches for the ideal-vertex case.
// ════════════════════════════════════════════════════════════════════════════

TEST(HyperIdealFunctional, GradientCheck_MixedIdealHyperIdeal)
{
    auto mesh = make_triangle();
    auto maps = setup_hyper_ideal_maps(mesh);

    // Make v0 ideal (pinned), v1 and v2 hyper-ideal.
    auto vit = mesh.vertices().begin();
    Vertex_index v0 = *vit++;
    Vertex_index v1 = *vit++;
    Vertex_index v2 = *vit;

    maps.v_idx[v0] = -1;   // ideal: b_0 = 0 (fixed)
    maps.v_idx[v1] = 0;
    maps.v_idx[v2] = 1;

    // All edges variable: indices 2, 3, 4
    int eidx = 2;
    for (auto e : mesh.edges()) maps.e_idx[e] = eidx++;

    // DOF vector: [b1, b2, a_e0, a_e1, a_e2]
    std::vector<double> x = {1.0, 1.0, 0.5, 0.5, 0.5};

    EXPECT_TRUE(gradient_check(mesh, x, maps))
        << "Finite-difference gradient check failed for mixed ideal / hyper-ideal";
}

// ════════════════════════════════════════════════════════════════════════════
// Gradient check: fan mesh (n=6), all variable
//
// Exercises the full halfedge-around-vertex traversal for a high-valence
// interior vertex.
// ════════════════════════════════════════════════════════════════════════════

TEST(HyperIdealFunctional, GradientCheck_Fan6AllVariable)
{
    auto mesh = make_fan(6);
    auto maps = setup_hyper_ideal_maps(mesh);
    auto x    = make_x_all_variable(mesh, maps, /*b=*/1.0, /*a=*/0.5);

    EXPECT_TRUE(gradient_check(mesh, x, maps))
        << "Finite-difference gradient check failed on fan-6 mesh";
}
