// example_euclidean.cpp
//
// conformallab++ — Euclidean discrete conformal map  (headless example)
//
// This program demonstrates the full library pipeline for the EUCLIDEAN
// discrete conformal functional:
//
//   1. Load a triangle mesh from an OFF file
//   2. Set up the Euclidean functional maps
//   3. Pin one vertex (gauge fix for open surfaces)
//   4. Set target angles via "natural equilibrium" (x* = x_input)
//   5. Solve with Newton + backtracking line search
//   6. Print per-vertex conformal factors u_i = x[v_idx[v]]
//   7. Save the result mesh (same geometry, solver state printed)
//
// Build (requires -DWITH_CGAL=ON):
//   cmake -S code -B build -DWITH_CGAL=ON
//   cmake --build build --target example_euclidean
//   ./build/examples/example_euclidean  [input.off]  [output.off]
//
// If no input file is given the built-in make_quad_strip() mesh is used.

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

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_euclidean_out.off";

    if (input_path.empty()) {
        std::cout << "[example_euclidean] No input file given — using make_quad_strip().\n";
        mesh = make_quad_strip();
    } else {
        std::cout << "[example_euclidean] 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_euclidean] Mesh: "
              << mesh.number_of_vertices() << " vertices, "
              << mesh.number_of_faces()    << " faces.\n";

    // ── Step 2: set up functional maps ────────────────────────────────────
    auto maps = setup_euclidean_maps(mesh);
    compute_euclidean_lambda0_from_mesh(mesh, maps);

    // ── Step 3: pin the first vertex (gauge fix) ──────────────────────────
    auto vit = mesh.vertices().begin();
    Vertex_index v_pinned = *vit++;
    maps.v_idx[v_pinned] = -1;   // pinned: u[v_pinned] = 0 (fixed)
    int idx = 0;
    for (; vit != mesh.vertices().end(); ++vit)
        maps.v_idx[*vit] = idx++;
    const int n = idx;

    std::cout << "[example_euclidean] DOFs: " << n << " (1 vertex pinned).\n";

    // ── Step 4: natural equilibrium — set theta_v = actual angle sum at x=0 ─
    //    After this step x* = 0 is the equilibrium (no deformation).
    //    In a real application you would set theta_v = desired angle (e.g. 2π
    //    for flat disks, or the cone angles for a cone metric).
    {
        std::vector<double> x0(static_cast<std::size_t>(n), 0.0);
        auto G0 = euclidean_gradient(mesh, x0, maps);
        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)];
        }
    }

    // ── Step 5: solve from a small perturbation to demonstrate Newton ─────
    std::vector<double> x0(static_cast<std::size_t>(n), -0.05);
    std::cout << "[example_euclidean] Solving Newton system…\n";
    auto result = newton_euclidean(mesh, x0, maps, /*tol=*/1e-9, /*max_iter=*/100);

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

    std::cout << "[example_euclidean] Per-vertex conformal factors u_i:\n";
    for (auto v : mesh.vertices()) {
        int iv = maps.v_idx[v];
        double u = (iv >= 0) ? result.x[static_cast<std::size_t>(iv)] : 0.0;
        std::cout << "  v" << v << "  u = " << u;
        if (iv < 0) std::cout << "  (pinned)";
        std::cout << "\n";
    }

    // ── Step 7: write output mesh ─────────────────────────────────────────
    try {
        save_mesh(output_path, mesh);
        std::cout << "[example_euclidean] 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;
}
