#pragma once
// gauss_bonnet.hpp
//
// Phase 6 — Gauss–Bonnet consistency check for prescribed target angles.
//
// Before calling newton_*() with custom target angles, verify that
// the angle defect sum matches the topology:
//
//   Σ_v (2π − Θ_v)  =  2π · χ(M)         (Euclidean / flat)
//   Σ_v (2π − Θ_v)  >  0                  (spherical, χ > 0)
//   Σ_v (2π − Θ_v)  <  0                  (hyperbolic, χ < 0)
//
// If this fails, no conformal factor can realise the target angles and
// Newton will silently fail to converge.
//
// API:
//   int    euler_characteristic(mesh)
//   int    genus(mesh)
//   double gauss_bonnet_sum(mesh, maps)      — Σ(2π − Θ_v)
//   double gauss_bonnet_rhs(mesh)            — 2π · χ(M)
//   double gauss_bonnet_deficit(mesh, maps)  — lhs − rhs  (0 = satisfied)
//   void   check_gauss_bonnet(mesh, maps [, tol])   — throws if violated
//   void   enforce_gauss_bonnet(mesh, maps)  — shifts θ_v by uniform Δ

#include "conformal_mesh.hpp"
#include "euclidean_functional.hpp"
#include "spherical_functional.hpp"
#include "hyper_ideal_functional.hpp"
#include "constants.hpp"

#include <stdexcept>
#include <sstream>
#include <cmath>
#include <string>

namespace conformallab {

// ── Topology helpers ──────────────────────────────────────────────────────────

/// Euler characteristic χ = V − E + F.
/// For closed orientable surfaces: χ = 2 − 2g.
inline int euler_characteristic(const ConformalMesh& mesh)
{
    return static_cast<int>(mesh.number_of_vertices())
         - static_cast<int>(mesh.number_of_edges())
         + static_cast<int>(mesh.number_of_faces());
}

/// Genus of a closed orientable surface: g = (2 − χ) / 2.
/// Returns 0 for open meshes (boundary present) — callers should check.
inline int genus(const ConformalMesh& mesh)
{
    int chi = euler_characteristic(mesh);
    return (2 - chi) / 2;
}

// ── Left-hand side  Σ(2π − Θ_v) ─────────────────────────────────────────────

inline double gauss_bonnet_sum(
    const ConformalMesh&                                      mesh,
    const ConformalMesh::Property_map<Vertex_index, double>&  theta)
{
    double s = 0.0;
    for (auto v : mesh.vertices())
        s += TWO_PI - theta[v];
    return s;
}

inline double gauss_bonnet_sum(const ConformalMesh& m, const EuclideanMaps&   mp)
    { return gauss_bonnet_sum(m, mp.theta_v); }
inline double gauss_bonnet_sum(const ConformalMesh& m, const SphericalMaps&   mp)
    { return gauss_bonnet_sum(m, mp.theta_v); }
inline double gauss_bonnet_sum(const ConformalMesh& m, const HyperIdealMaps&  mp)
    { return gauss_bonnet_sum(m, mp.theta_v); }

// ── Right-hand side  2π · χ(M) ───────────────────────────────────────────────

inline double gauss_bonnet_rhs(const ConformalMesh& mesh)
{
    return TWO_PI * static_cast<double>(euler_characteristic(mesh));
}

// ── Deficit: lhs − rhs  (0 = Gauss–Bonnet satisfied) ─────────────────────────

template <typename Maps>
inline double gauss_bonnet_deficit(const ConformalMesh& mesh, const Maps& maps)
{
    return gauss_bonnet_sum(mesh, maps) - gauss_bonnet_rhs(mesh);
}

// ── check_gauss_bonnet — throws std::runtime_error if |deficit| > tol ─────────

inline void check_gauss_bonnet(const ConformalMesh& mesh,
                                double lhs,
                                double tol = 1e-8)
{
    double rhs = gauss_bonnet_rhs(mesh);
    double def = lhs - rhs;
    if (std::abs(def) > tol) {
        std::ostringstream msg;
        msg << "Gauss–Bonnet violated:\n"
            << "  Σ(2π−Θ_v) = " << lhs
            << "  expected 2π·χ = " << rhs
            << "  (χ = " << euler_characteristic(mesh)
            << ", genus = " << genus(mesh) << ")\n"
            << "  deficit = " << def;
        throw std::runtime_error(msg.str());
    }
}

template <typename Maps>
inline void check_gauss_bonnet(const ConformalMesh& mesh,
                                const Maps& maps,
                                double tol = 1e-8)
{
    check_gauss_bonnet(mesh, gauss_bonnet_sum(mesh, maps), tol);
}

// ── enforce_gauss_bonnet — adjust θ_v by uniform Δ ───────────────────────────
//
// Adds δ = (rhs − lhs) / V to every θ_v so that Gauss–Bonnet holds exactly.
// After this call, check_gauss_bonnet() will not throw (up to floating-point).
// Only modifies free vertices (v_idx[v] >= 0 for EuclideanMaps / SphericalMaps;
// always all vertices for the raw property-map overload).

inline void enforce_gauss_bonnet(
    ConformalMesh&                                           mesh,
    ConformalMesh::Property_map<Vertex_index, double>&       theta)
{
    double lhs   = gauss_bonnet_sum(mesh, theta);
    double rhs   = gauss_bonnet_rhs(mesh);
    // Adding δ to every θ_v decreases the sum Σ(2π−θ_v) by V·δ.
    // We need lhs − V·δ = rhs, so δ = (lhs − rhs) / V.
    double delta = (lhs - rhs) / static_cast<double>(mesh.number_of_vertices());
    for (auto v : mesh.vertices())
        theta[v] += delta;
}

template <typename Maps>
inline void enforce_gauss_bonnet(ConformalMesh& mesh, Maps& maps)
{
    enforce_gauss_bonnet(mesh, maps.theta_v);
}

} // namespace conformallab
