// test_mesh_io.cpp
//
// Phase 4b — CGAL::IO mesh round-trip tests.
//
// Tests:
//   1. OFF write + read round-trip: vertex count, face count, edge count preserved.
//   2. OBJ write + read round-trip: same topology checks.
//   3. load_mesh() throws on non-existent file.
//   4. Round-trip preserves vertex positions (within floating-point precision).

#include "conformal_mesh.hpp"
#include "mesh_builder.hpp"
#include "mesh_io.hpp"
#include <gtest/gtest.h>
#include <cmath>
#include <cstdio>
#include <filesystem>
#include <stdexcept>

using namespace conformallab;

// Helper: temp file path with given extension
static std::string tmp_path(const char* ext)
{
    return std::string("/tmp/conformallab_test.") + ext;
}

// Helper: delete file if it exists
static void rm(const std::string& path)
{
    std::filesystem::remove(path);
}

// ════════════════════════════════════════════════════════════════════════════
// OFF round-trip: tetrahedron
// ════════════════════════════════════════════════════════════════════════════

TEST(MeshIO, OFF_RoundTrip_Tetrahedron)
{
    auto path = tmp_path("off");
    rm(path);

    auto mesh_out = make_tetrahedron();
    ASSERT_TRUE(write_mesh(path, mesh_out))
        << "write_mesh should succeed for tetrahedron";

    ConformalMesh mesh_in;
    ASSERT_TRUE(read_mesh(path, mesh_in))
        << "read_mesh should succeed for the written OFF file";

    EXPECT_EQ(mesh_in.number_of_vertices(), mesh_out.number_of_vertices());
    EXPECT_EQ(mesh_in.number_of_faces(),    mesh_out.number_of_faces());
    EXPECT_EQ(mesh_in.number_of_edges(),    mesh_out.number_of_edges());

    rm(path);
}

// ════════════════════════════════════════════════════════════════════════════
// OFF round-trip: quad strip
// ════════════════════════════════════════════════════════════════════════════

TEST(MeshIO, OFF_RoundTrip_QuadStrip)
{
    auto path = tmp_path("off");
    rm(path);

    auto mesh_out = make_quad_strip();
    ASSERT_TRUE(write_mesh(path, mesh_out));

    ConformalMesh mesh_in;
    ASSERT_TRUE(read_mesh(path, mesh_in));

    EXPECT_EQ(mesh_in.number_of_vertices(), mesh_out.number_of_vertices());
    EXPECT_EQ(mesh_in.number_of_faces(),    mesh_out.number_of_faces());

    rm(path);
}

// ════════════════════════════════════════════════════════════════════════════
// OBJ round-trip: tetrahedron
// ════════════════════════════════════════════════════════════════════════════

TEST(MeshIO, OBJ_RoundTrip_Tetrahedron)
{
    auto path = tmp_path("obj");
    rm(path);

    auto mesh_out = make_tetrahedron();
    ASSERT_TRUE(write_mesh(path, mesh_out))
        << "write_mesh (OBJ) should succeed";

    ConformalMesh mesh_in;
    ASSERT_TRUE(read_mesh(path, mesh_in))
        << "read_mesh (OBJ) should succeed";

    EXPECT_EQ(mesh_in.number_of_vertices(), mesh_out.number_of_vertices());
    EXPECT_EQ(mesh_in.number_of_faces(),    mesh_out.number_of_faces());

    rm(path);
}

// ════════════════════════════════════════════════════════════════════════════
// load_mesh throws on missing file
// ════════════════════════════════════════════════════════════════════════════

TEST(MeshIO, LoadMeshThrowsOnMissingFile)
{
    EXPECT_THROW(load_mesh("/tmp/does_not_exist_conflab.off"), std::runtime_error);
}

// ════════════════════════════════════════════════════════════════════════════
// Vertex positions survive a round-trip (OFF)
// ════════════════════════════════════════════════════════════════════════════

TEST(MeshIO, OFF_VertexPositionsPreserved)
{
    auto path = tmp_path("off");
    rm(path);

    auto mesh_out = make_tetrahedron();
    ASSERT_TRUE(write_mesh(path, mesh_out));

    ConformalMesh mesh_in;
    ASSERT_TRUE(read_mesh(path, mesh_in));

    // Collect and sort vertex positions from both meshes to compare
    auto collect_sorted = [](const ConformalMesh& m) {
        std::vector<std::array<double,3>> pts;
        for (auto v : m.vertices()) {
            auto p = m.point(v);
            pts.push_back({CGAL::to_double(p.x()),
                           CGAL::to_double(p.y()),
                           CGAL::to_double(p.z())});
        }
        std::sort(pts.begin(), pts.end());
        return pts;
    };

    auto pts_out = collect_sorted(mesh_out);
    auto pts_in  = collect_sorted(mesh_in);

    ASSERT_EQ(pts_out.size(), pts_in.size());
    for (std::size_t i = 0; i < pts_out.size(); ++i) {
        EXPECT_NEAR(pts_out[i][0], pts_in[i][0], 1e-10);
        EXPECT_NEAR(pts_out[i][1], pts_in[i][1], 1e-10);
        EXPECT_NEAR(pts_out[i][2], pts_in[i][2], 1e-10);
    }

    rm(path);
}

// ════════════════════════════════════════════════════════════════════════════
// save_mesh / load_mesh convenience wrappers
// ════════════════════════════════════════════════════════════════════════════

TEST(MeshIO, SaveLoadConvenienceWrappers)
{
    auto path = tmp_path("off");
    rm(path);

    auto mesh_out = make_quad_strip();
    EXPECT_NO_THROW(save_mesh(path, mesh_out));

    ConformalMesh mesh_in;
    EXPECT_NO_THROW(mesh_in = load_mesh(path));

    EXPECT_EQ(mesh_in.number_of_vertices(), mesh_out.number_of_vertices());

    rm(path);
}
