// example_hyper_ideal.cpp
//
// conformallab++ — Hyper-ideal discrete conformal map  (headless example)
//
// Demonstrates the full library pipeline for the HYPER-IDEAL discrete conformal
// functional (Springborn 2020).  The hyper-ideal functional operates in
// hyperbolic geometry: vertices have "horoball radii" (DOF b_i) and edges have
// "intersection lengths" (DOF a_e).  The energy is strictly convex, so Newton
// converges globally from any valid starting point.
//
// Pipeline:
//   1. Load (or synthesise) a triangle mesh
//   2. Set up HyperIdeal maps + assign all vertex and edge DOFs
//   3. Choose equilibrium base point (b=1.0, a=0.5) and set natural targets
//   4. Perturb and solve with Newton
//   5. Print DOF values at equilibrium
//   6. Save result mesh
//
// Build (requires -DWITH_CGAL=ON):
//   cmake -S code -B build -DWITH_CGAL=ON
//   cmake --build build --target example_hyper_ideal
//   ./build/examples/example_hyper_ideal  [input.off]  [output.off]

#include "conformal_mesh.hpp"
#include "mesh_builder.hpp"
#include "mesh_io.hpp"
#include "hyper_ideal_functional.hpp"
#include "newton_solver.hpp"
#include <iostream>
#include <string>
#include <vector>
#include <cmath>

using namespace conformallab;

int main(int argc, char* argv[])
{
    // ── Step 1: obtain mesh ───────────────────────────────────────────────
    ConformalMesh mesh;
    std::string input_path  = (argc > 1) ? argv[1] : "";
    std::string output_path = (argc > 2) ? argv[2] : "/tmp/conformallab_hyper_ideal_out.off";

    if (input_path.empty()) {
        std::cout << "[example_hyper_ideal] No input file — using make_triangle().\n";
        mesh = make_triangle();
    } else {
        std::cout << "[example_hyper_ideal] Loading mesh from: " << input_path << "\n";
        try { mesh = load_mesh(input_path); }
        catch (const std::exception& e) {
            std::cerr << "Error loading mesh: " << e.what() << "\n";
            return 1;
        }
    }

    std::cout << "[example_hyper_ideal] Mesh: "
              << mesh.number_of_vertices() << " vertices, "
              << mesh.number_of_faces()    << " faces.\n";

    // ── Step 2: set up functional maps ────────────────────────────────────
    auto maps = setup_hyper_ideal_maps(mesh);
    int n = assign_all_dof_indices(mesh, maps);

    std::cout << "[example_hyper_ideal] DOFs: " << n
              << " (" << mesh.number_of_vertices() << " vertex + "
              << mesh.number_of_edges()            << " edge).\n";

    // ── Step 3: choose equilibrium base point and set natural targets ─────
    //
    //    x = 0 is degenerate for the HyperIdeal functional (log-space).
    //    We pick a valid base point (b_i = b_base, a_e = a_base), evaluate
    //    the gradient there, and absorb it into the target angles so that
    //    G(xbase) = 0.  This makes xbase the equilibrium x*.
    //
    //    In a real application you would set theta_v / theta_e to the desired
    //    hyperbolic angle targets (e.g. from a reference mesh).
    const double b_base = 1.0;   // horoball radii at equilibrium
    const double a_base = 0.5;   // edge-length DOFs at equilibrium

    const auto sz = static_cast<std::size_t>(n);
    std::vector<double> xbase(sz, 0.0);
    for (auto v : mesh.vertices()) {
        int iv = maps.v_idx[v];
        if (iv >= 0) xbase[static_cast<std::size_t>(iv)] = b_base;
    }
    for (auto e : mesh.edges()) {
        int ie = maps.e_idx[e];
        if (ie >= 0) xbase[static_cast<std::size_t>(ie)] = a_base;
    }

    // G = Σβ − theta_target; absorb G(xbase) into targets so G(xbase) = 0
    auto G0 = evaluate_hyper_ideal(mesh, xbase, maps, false).gradient;
    for (auto v : mesh.vertices()) {
        int iv = maps.v_idx[v];
        if (iv >= 0) maps.theta_v[v] += G0[static_cast<std::size_t>(iv)];
    }
    for (auto e : mesh.edges()) {
        int ie = maps.e_idx[e];
        if (ie >= 0) maps.theta_e[e] += G0[static_cast<std::size_t>(ie)];
    }

    // ── Step 4: perturb and solve ─────────────────────────────────────────
    const double perturb = 0.25;
    std::vector<double> x0 = xbase;
    for (auto& v : x0) v += perturb;

    double g_start = 0.0;
    for (double v : G0) g_start = std::max(g_start, std::abs(v));
    std::cout << "[example_hyper_ideal] Starting Newton from perturbation +" << perturb
              << " (G at xbase = " << g_start << ").\n";

    auto result = newton_hyper_ideal(mesh, x0, maps, /*tol=*/1e-9, /*max_iter=*/200);

    // ── Step 5: report ────────────────────────────────────────────────────
    if (result.converged) {
        std::cout << "[example_hyper_ideal] Converged in " << result.iterations
                  << " iterations.  ||G||_inf = " << result.grad_inf_norm << "\n";
    } else {
        std::cout << "[example_hyper_ideal] Did NOT converge after " << result.iterations
                  << " iterations.  ||G||_inf = " << result.grad_inf_norm << "\n";
    }

    std::cout << "[example_hyper_ideal] DOF values at equilibrium:\n";
    for (auto v : mesh.vertices()) {
        int iv = maps.v_idx[v];
        if (iv < 0) continue;
        std::cout << "  v" << v
                  << "  b = " << result.x[static_cast<std::size_t>(iv)]
                  << "  (expected " << b_base << ")\n";
    }
    for (auto e : mesh.edges()) {
        int ie = maps.e_idx[e];
        if (ie < 0) continue;
        std::cout << "  e" << e
                  << "  a = " << result.x[static_cast<std::size_t>(ie)]
                  << "  (expected " << a_base << ")\n";
    }

    // ── Step 6: write output mesh ─────────────────────────────────────────
    try {
        save_mesh(output_path, mesh);
        std::cout << "[example_hyper_ideal] Mesh saved to: " << output_path << "\n";
    } catch (const std::exception& e) {
        std::cerr << "Warning: could not write output: " << e.what() << "\n";
    }

    return result.converged ? 0 : 1;
}
