// example_viewer.cpp
//
// conformallab++ — Interactive viewer example  (requires -DWITH_VIEWER=ON)
//
// This example demonstrates the full end-to-end pipeline WITH visual output:
//
//   1. Load (or synthesise) a mesh
//   2. Compute the Euclidean discrete conformal map (Newton solver)
//   3. Display the result in an interactive libigl / GLFW window
//      • Left pane:  input mesh, coloured by per-vertex conformal factor u_i
//      • Right pane: a flat parameterisation (future, placeholder)
//
// The viewer is split into two data sets using libigl's multi-mesh API so
// users can inspect geometry and solution simultaneously.
//
// Build (requires -DWITH_CGAL=ON -DWITH_VIEWER=ON):
//   cmake -S code -B build -DWITH_CGAL=ON -DWITH_VIEWER=ON
//   cmake --build build --target example_viewer
//   ./build/examples/example_viewer  [input.off]
//
// Navigation (libigl default):
//   Mouse drag   — rotate
//   Scroll       — zoom
//   C            — toggle camera mode (trackball / 2D)
//   Z / X / Y    — snap to axis-aligned view
//   Q / Esc      — quit

#include "conformal_mesh.hpp"
#include "mesh_builder.hpp"
#include "mesh_io.hpp"
#include "euclidean_functional.hpp"
#include "mesh_utils.hpp"
#include "newton_solver.hpp"
#include <igl/opengl/glfw/Viewer.h>
#include <Eigen/Dense>
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
#include <algorithm>

using namespace conformallab;

// ── Jet colour map: scalar → RGB ─────────────────────────────────────────────
static Eigen::RowVector3d jet(double t)
{
    t = std::max(0.0, std::min(1.0, t));
    double r = std::clamp(1.5 - std::abs(4.0 * t - 3.0), 0.0, 1.0);
    double g = std::clamp(1.5 - std::abs(4.0 * t - 2.0), 0.0, 1.0);
    double b = std::clamp(1.5 - std::abs(4.0 * t - 1.0), 0.0, 1.0);
    return {r, g, b};
}

int main(int argc, char* argv[])
{
    // ── Step 1: load or synthesise mesh ───────────────────────────────────
    ConformalMesh mesh;
    std::string input_path = (argc > 1) ? argv[1] : "";

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

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

    // ── Step 2: solve the Euclidean discrete conformal map ────────────────
    auto maps = setup_euclidean_maps(mesh);
    compute_euclidean_lambda0_from_mesh(mesh, maps);

    // Pin first vertex
    auto vit = mesh.vertices().begin();
    maps.v_idx[*vit++] = -1;
    int idx = 0;
    for (; vit != mesh.vertices().end(); ++vit)
        maps.v_idx[*vit] = idx++;
    const int n = idx;

    // Natural equilibrium
    {
        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)];
        }
    }

    // Perturb and solve
    std::vector<double> x0(static_cast<std::size_t>(n), -0.08);
    auto result = newton_euclidean(mesh, x0, maps, 1e-9, 200);

    if (result.converged)
        std::cout << "[example_viewer] Converged in " << result.iterations << " iterations.\n";
    else
        std::cout << "[example_viewer] Warning: did not converge fully "
                     "(||G||_inf = " << result.grad_inf_norm << ").\n";

    // ── Step 3: build Eigen V / F for libigl ─────────────────────────────
    using Kernel = CGAL::Simple_cartesian<double>;
    Eigen::MatrixXd V;
    Eigen::MatrixXi F;
    mesh_utils::cgal_to_eigen<Kernel>(mesh, V, F);

    // Per-vertex colour: conformal factor u_i, mapped via jet palette
    const int nv = static_cast<int>(V.rows());
    Eigen::MatrixXd C(nv, 3);

    // Collect all u values to normalise
    std::vector<double> u_all(static_cast<std::size_t>(nv), 0.0);
    for (auto v : mesh.vertices()) {
        int iv = maps.v_idx[v];
        if (iv >= 0)
            u_all[static_cast<std::size_t>(v.idx())] = result.x[static_cast<std::size_t>(iv)];
    }
    double u_min = *std::min_element(u_all.begin(), u_all.end());
    double u_max = *std::max_element(u_all.begin(), u_all.end());
    double u_range = (u_max > u_min) ? (u_max - u_min) : 1.0;

    for (int vi = 0; vi < nv; ++vi) {
        double t = (u_all[static_cast<std::size_t>(vi)] - u_min) / u_range;
        C.row(vi) = jet(t);
    }

    // ── Step 4: launch interactive viewer ─────────────────────────────────
    igl::opengl::glfw::Viewer viewer;
    viewer.data().set_mesh(V, F);
    viewer.data().set_colors(C);
    viewer.data().show_lines   = true;
    viewer.data().show_overlay = true;

    // Status text overlay
    viewer.data().add_label(
        Eigen::Vector3d(V.col(0).mean(), V.col(1).mean(), V.col(2).maxCoeff()),
        "Euclidean conformal factor u_i (jet: blue=min, red=max)");

    std::cout << "[example_viewer] Launching viewer.  Press Q or Esc to quit.\n";
    viewer.launch();

    return 0;
}
