#pragma once
// inversive_distance_functional.hpp
//
// Phase 9a.2 — Inversive-distance circle-packing functional (Luo 2004).
//
// VERTEX-based circle packing.  Each vertex carries a circle of radius
// r_i = exp(u_i).  The inversive distance I_ij between two adjacent
// circles is a constant of the edge, derived once from the initial
// geometry via Bowers-Stephenson 2004.
//
// This is the FACE-DUAL of CPEuclideanFunctional (Phase 9a.1).  The
// correspondence is I_ij = cos θ_e (Glickenstein 2011 §5).
//
// ┌──────────────────────────────────────────────────────────────────────────┐
// │  Mathematical model                                                       │
// │  ──────────────────                                                       │
// │                                                                           │
// │  Variables:    u_i = log r_i           (per vertex; r_i is the radius)   │
// │  Constants:    I_ij                    (per edge; inversive distance)    │
// │                Θ_v                     (per vertex; target cone angle)   │
// │                                                                           │
// │  Bowers-Stephenson (init from initial geometry):                          │
// │    I_ij = ( ℓ_ij² − r_i² − r_j² ) / ( 2 r_i r_j )                        │
// │                                                                           │
// │  Edge length (Luo 2004 §3, Glickenstein 2011 eq. 2.1):                   │
// │    ℓ_ij(u)²  =  exp(2 u_i) + exp(2 u_j) + 2 I_ij exp(u_i + u_j)         │
// │             =  r_i² + r_j² + 2 I_ij r_i r_j                              │
// │                                                                           │
// │  Triangle angles:    same half-tangent law of cosines as the              │
// │                      Euclidean functional (numerically stable).           │
// │                                                                           │
// │  Gradient  (Luo 2004 Lemma 3.1):                                          │
// │    ∂E/∂u_v  =  Θ_v  −  Σ_{T ∋ v}  α_v(T)                                 │
// │                                                                           │
// │  Energy:     path integral E(u) = ∫₀¹ ⟨G(tu), u⟩ dt                      │
// │              (Luo's 1-form is closed; we use 10-point Gauss-Legendre      │
// │               quadrature, identical to euclidean_functional.hpp)          │
// │                                                                           │
// │  Hessian:    finite-difference for the MVP port; an analytic form is      │
// │              given in Glickenstein 2011 eq. (4.6) and may be added        │
// │              later for performance.                                       │
// └──────────────────────────────────────────────────────────────────────────┘
//
// Relation to euclidean_functional.hpp
// ────────────────────────────────────
// The two are structurally identical in:
//   • DOF layout (per vertex), DOF index sentinel (−1 = pinned)
//   • Gradient pattern (Θ − Σ α)
//   • Energy via path integral (same Gauss-Legendre constants)
//   • Halfedge convention (h0/h1/h2, source pattern, α opposite-edge)
//
// They differ ONLY in:
//   • Per-edge constant: λ°_ij (log²-length) vs I_ij (inversive distance)
//   • Edge-length formula:
//       Euclidean:           ℓ_ij  = exp((λ°_ij + u_i + u_j) / 2)
//       Inversive distance:  ℓ_ij² = exp(2u_i) + exp(2u_j)
//                                  + 2 I_ij exp(u_i + u_j)
//
// In particular at the tangential limit I_ij = 1 the inversive-distance length
// reduces to (exp(u_i) + exp(u_j))² ⇒ ℓ_ij = r_i + r_j (tangential circles),
// which is *different* from the Euclidean-conformal length even at the same
// initial geometry.  The two functionals describe distinct geometric objects.
//
// Property-map name prefix: "iv:" (vertex) and "ie:" (edge).

#include "conformal_mesh.hpp"
#include "constants.hpp"
#include "euclidean_geometry.hpp"   // euclidean_angles(λ12, λ23, λ31)
#include <CGAL/boost/graph/iterator.h>
#include <vector>
#include <cmath>
#include <cstdint>
#include <iostream>

namespace conformallab {

// ── Property-map type aliases ────────────────────────────────────────────────

using IDVMapI = ConformalMesh::Property_map<Vertex_index, int>;
using IDVMapD = ConformalMesh::Property_map<Vertex_index, double>;
using IDEMapD = ConformalMesh::Property_map<Edge_index, double>;

// ── Persistent map bundle ─────────────────────────────────────────────────────

struct InversiveDistanceMaps {
    IDVMapI v_idx;       ///< DOF index per vertex (−1 = pinned / u_v = 0)
    IDVMapD theta_v;     ///< target cone angle Θ_v   (default 2π)
    IDVMapD r0;          ///< initial radius r_i^(0)  (default 1)
    IDEMapD I_e;         ///< inversive distance I_ij (per edge, constant)
};

// Create the property maps with sensible defaults.
inline InversiveDistanceMaps setup_inversive_distance_maps(ConformalMesh& mesh)
{
    InversiveDistanceMaps m;
    m.v_idx   = mesh.add_property_map<Vertex_index, int>   ("iv:idx",   -1     ).first;
    m.theta_v = mesh.add_property_map<Vertex_index, double>("iv:theta", TWO_PI ).first;
    m.r0      = mesh.add_property_map<Vertex_index, double>("iv:r0",    1.0    ).first;
    m.I_e     = mesh.add_property_map<Edge_index,   double>("ie:I",     1.0    ).first;
    return m;
}

// Assign sequential DOF indices to all vertices (no gauge pinning here —
// the caller should set one v_idx to −1 before assigning).
inline int assign_inversive_distance_vertex_dof_indices(ConformalMesh&         mesh,
                                                          InversiveDistanceMaps& m)
{
    int idx = 0;
    for (auto v : mesh.vertices()) m.v_idx[v] = idx++;
    return idx;
}

// Count free DOFs.
inline int inversive_distance_dimension(const ConformalMesh&         mesh,
                                          const InversiveDistanceMaps& m)
{
    int dim = 0;
    for (auto v : mesh.vertices()) if (m.v_idx[v] >= 0) ++dim;
    return dim;
}

// ── Initialisation from initial mesh geometry ────────────────────────────────
//
// Two-phase init mirroring "compute_lambda0" for euclidean_functional:
//   1. Choose r_i^(0).  Simplest heuristic: r_i = (1/3)·(min adjacent ℓ).
//      Other choices (max ℓ, mean ℓ, length-of-shortest-vertex-cycle) are
//      possible; the user can override `m.r0[v]` between setup and init.
//   2. Compute I_ij = ( ℓ² − r_i² − r_j² ) / ( 2 r_i r_j ) for each edge.
//
// Note: a valid inversive-distance packing requires I_ij > −1 on every edge,
// and the triangle inequality must hold on every face under the resulting ℓ.
// The choice r_i = ⅓·min(ℓ_e adj v) keeps I_ij safely positive for most
// real meshes.
inline void compute_inversive_distance_init_from_mesh(ConformalMesh&         mesh,
                                                        InversiveDistanceMaps& m)
{
    // Phase 1: r_i = (1/3) · min adjacent edge length.
    for (auto v : mesh.vertices()) {
        double min_len = std::numeric_limits<double>::infinity();
        for (auto h : CGAL::halfedges_around_target(v, mesh)) {
            auto p1 = mesh.point(mesh.source(h));
            auto p2 = mesh.point(mesh.target(h));
            double dx = p1.x() - p2.x();
            double dy = p1.y() - p2.y();
            double dz = p1.z() - p2.z();
            double len = std::sqrt(dx*dx + dy*dy + dz*dz);
            if (len < min_len) min_len = len;
        }
        m.r0[v] = (std::isfinite(min_len) && min_len > 1e-15)
                ? min_len / 3.0
                : 1.0;
    }

    // Phase 2: I_ij from initial geometry.
    for (auto e : mesh.edges()) {
        auto h  = mesh.halfedge(e);
        auto vi = mesh.source(h);
        auto vj = mesh.target(h);
        auto p1 = mesh.point(vi);
        auto p2 = mesh.point(vj);
        double dx = p1.x() - p2.x();
        double dy = p1.y() - p2.y();
        double dz = p1.z() - p2.z();
        double l2 = dx*dx + dy*dy + dz*dz;
        double ri = m.r0[vi];
        double rj = m.r0[vj];
        m.I_e[e] = (l2 - ri*ri - rj*rj) / (2.0 * ri * rj);
    }
}

// ── Internal helpers ──────────────────────────────────────────────────────────

namespace id_detail {

inline double dof_val(int idx, const std::vector<double>& x) noexcept
{
    return idx >= 0 ? x[static_cast<std::size_t>(idx)] : 0.0;
}

inline std::size_t hidx(Halfedge_index h) noexcept
{
    return static_cast<std::size_t>(static_cast<std::uint32_t>(h));
}

// Inversive-distance edge length squared: ℓ² = exp(2u_i) + exp(2u_j) + 2 I r_i r_j
// where r_i = exp(u_i), so:  ℓ² = r_i² + r_j² + 2 I r_i r_j.
// Returns -1 if the result is non-positive (degenerate; the caller skips the face).
inline double edge_length_squared(double u_i, double u_j, double I_ij) noexcept
{
    double ri = std::exp(u_i);
    double rj = std::exp(u_j);
    double l2 = ri*ri + rj*rj + 2.0 * I_ij * ri * rj;
    return l2 > 0.0 ? l2 : -1.0;
}

} // namespace id_detail

// ── Gradient ──────────────────────────────────────────────────────────────────
//
// G_v = Θ_v − Σ_{f ∋ v} α_v(f)
//
// Halfedge convention (identical to euclidean_functional.hpp):
//   h_alpha[h] = corner angle OPPOSITE to the edge of halfedge h in its face.
inline std::vector<double> inversive_distance_gradient(
    const ConformalMesh&             mesh,
    const std::vector<double>&       x,
    const InversiveDistanceMaps&     m)
{
    const int n = inversive_distance_dimension(mesh, m);
    std::vector<double> G(static_cast<std::size_t>(n), 0.0);

    const std::size_t nh = mesh.number_of_halfedges();
    std::vector<double> h_alpha(nh, 0.0);

    // Pass 1 — per face, compute corner angles via the law of cosines.
    // We reuse euclidean_angles(λ12, λ23, λ31) which takes 2·log(ℓ) per edge.
    for (auto f : mesh.faces()) {
        Halfedge_index h0 = mesh.halfedge(f);
        Halfedge_index h1 = mesh.next(h0);
        Halfedge_index h2 = mesh.next(h1);

        Vertex_index v1 = mesh.source(h0);
        Vertex_index v2 = mesh.source(h1);
        Vertex_index v3 = mesh.source(h2);

        Edge_index e12 = mesh.edge(h0);
        Edge_index e23 = mesh.edge(h1);
        Edge_index e31 = mesh.edge(h2);

        double u1 = id_detail::dof_val(m.v_idx[v1], x);
        double u2 = id_detail::dof_val(m.v_idx[v2], x);
        double u3 = id_detail::dof_val(m.v_idx[v3], x);

        double l12sq = id_detail::edge_length_squared(u1, u2, m.I_e[e12]);
        double l23sq = id_detail::edge_length_squared(u2, u3, m.I_e[e23]);
        double l31sq = id_detail::edge_length_squared(u3, u1, m.I_e[e31]);

        if (l12sq <= 0 || l23sq <= 0 || l31sq <= 0) continue;

        // euclidean_angles expects 2·log(ℓ) per edge — feed log(ℓ²).
        auto fa = euclidean_angles(std::log(l12sq), std::log(l23sq), std::log(l31sq));
        if (!fa.valid) continue;

        h_alpha[id_detail::hidx(h0)] = fa.alpha3;
        h_alpha[id_detail::hidx(h1)] = fa.alpha1;
        h_alpha[id_detail::hidx(h2)] = fa.alpha2;
    }

    // Pass 2 — accumulate vertex gradient.
    for (auto v : mesh.vertices()) {
        int iv = m.v_idx[v];
        if (iv < 0) continue;
        double sum_alpha = 0.0;
        for (auto h : CGAL::halfedges_around_target(v, mesh)) {
            if (mesh.is_border(h)) continue;
            sum_alpha += h_alpha[id_detail::hidx(mesh.prev(h))];
        }
        G[static_cast<std::size_t>(iv)] = m.theta_v[v] - sum_alpha;
    }

    return G;
}

// ── Energy via Gauss-Legendre path integral ─────────────────────────────────
//
// E(u) = ∫₀¹ ⟨G(tu), u⟩ dt   (10-point GL, identical constants to euclidean_functional)
inline double inversive_distance_energy(
    const ConformalMesh&             mesh,
    const std::vector<double>&       x,
    const InversiveDistanceMaps&     m)
{
    static const double gl_s[10] = {
        -0.9739065285171717,  -0.8650633666889845,
        -0.6794095682990244,  -0.4333953941292472,
        -0.1488743389816312,   0.1488743389816312,
         0.4333953941292472,   0.6794095682990244,
         0.8650633666889845,   0.9739065285171717
    };
    static const double gl_w[10] = {
        0.0666713443086881,  0.1494513491505806,
        0.2190863625159820,  0.2692667193099963,
        0.2955242247147529,  0.2955242247147529,
        0.2692667193099963,  0.2190863625159820,
        0.1494513491505806,  0.0666713443086881
    };

    const std::size_t n = x.size();
    double E = 0.0;
    std::vector<double> tx(n);
    for (int k = 0; k < 10; ++k) {
        double t  = (1.0 + gl_s[k]) * 0.5;
        double wt = gl_w[k] * 0.5;
        for (std::size_t i = 0; i < n; ++i) tx[i] = t * x[i];
        auto G = inversive_distance_gradient(mesh, tx, m);
        double dot = 0.0;
        for (std::size_t i = 0; i < n; ++i) dot += G[i] * x[i];
        E += wt * dot;
    }
    return E;
}

// ── Finite-difference gradient check ─────────────────────────────────────────
inline bool gradient_check_inversive_distance(
    const ConformalMesh&             mesh,
    const std::vector<double>&       x,
    const InversiveDistanceMaps&     m,
    double eps = 1e-5,
    double tol = 1e-6)
{
    auto G = inversive_distance_gradient(mesh, x, m);
    const std::size_t n = G.size();

    for (std::size_t i = 0; i < n; ++i) {
        std::vector<double> xp = x, xm = x;
        xp[i] += eps;
        xm[i] -= eps;
        double Ep = inversive_distance_energy(mesh, xp, m);
        double Em = inversive_distance_energy(mesh, xm, m);
        double fd = (Ep - Em) / (2.0 * eps);
        if (std::abs(G[i] - fd) > tol) {
            std::cerr << "[inversive-distance] FD gradient mismatch at DOF " << i
                      << ": analytic=" << G[i]
                      << "  FD=" << fd
                      << "  diff=" << (G[i] - fd) << "\n";
            return false;
        }
    }
    return true;
}

// ── Newton equilibrium check: gradient vanishes at converged u ──────────────
inline bool is_inversive_distance_equilibrium(
    const ConformalMesh&             mesh,
    const std::vector<double>&       x,
    const InversiveDistanceMaps&     m,
    double tol = 1e-8)
{
    auto G = inversive_distance_gradient(mesh, x, m);
    for (double g : G)
        if (std::abs(g) > tol) return false;
    return true;
}

} // namespace conformallab
