#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/Nef_polyhedron_3.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/IO/STL.h>
#include <CGAL/IO/OFF.h>
#include <CGAL/convex_hull_3.h>
#include <CGAL/Aff_transformation_3.h>
#include <CGAL/boost/graph/graph_traits_Polyhedron_3.h>
#include <CGAL/boost/graph/copy_face_graph.h>
#include <sstream>
#include <vector>
#include <cmath>
#include <fstream>
#include <algorithm>

namespace py = pybind11;

// Typedefs
typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel;
typedef Kernel::Point_3 Point_3;
typedef Kernel::Vector_3 Vector_3;
typedef Kernel::Aff_transformation_3 Aff_transformation_3;
typedef Kernel::Plane_3 Plane_3;
typedef CGAL::Polyhedron_3<Kernel> Polyhedron;
typedef CGAL::Nef_polyhedron_3<Kernel> Nef_polyhedron;
typedef CGAL::Surface_mesh<Point_3> Surface_mesh;
typedef Kernel::FT FT;

// Helper: Parse rational string/mix to exact FT
FT parse_ft(const std::string& s) {
    std::stringstream ss(s);
    FT val;
    ss >> val;
    return val;
}

// Helper: Calculate steps based on tolerance
// n = pi / acos(1 - delta/R)
int calculate_steps(FT R_ft, FT tol_ft) {
    double R = CGAL::to_double(R_ft);
    double tol = CGAL::to_double(tol_ft);
    
    if (tol <= 0 || R <= 0) return 30; // Fallback
    if (tol >= R) return 4; // Very coarse if tolerance is huge

    double val = 1.0 - (tol / R);
    if (val < -1.0) val = -1.0;
    if (val > 1.0) val = 1.0;
    
    double alpha = std::acos(val); // Half-angle of segment
    if (alpha < 1e-9) return 360; // Safety cap for extremely small tolerance
    
    int n = static_cast<int>(std::ceil(M_PI / alpha));
    
    // Ensure reasonable bounds
    if (n < 4) return 4;
    return n;
}

class Solid {
public:
    Nef_polyhedron nef;

    Solid() {}
    Solid(const Nef_polyhedron& n) : nef(n) {}

    Solid translate(const std::string& dx, const std::string& dy, const std::string& dz) {
        if (nef.is_empty()) return *this;
        Vector_3 v(parse_ft(dx), parse_ft(dy), parse_ft(dz));
        Aff_transformation_3 t(CGAL::TRANSLATION, v);
        Nef_polyhedron res = nef;
        res.transform(t);
        return Solid(res);
    }

    Solid rotate(const std::string& ax, const std::string& ay, const std::string& az, const std::string& angle_deg_str) {
        if (nef.is_empty()) return *this;
        
        FT u = parse_ft(ax);
        FT v = parse_ft(ay);
        FT w = parse_ft(az);
        FT angle_deg = parse_ft(angle_deg_str);

        // Normalize axis vector (approximate)
        double ud = CGAL::to_double(u);
        double vd = CGAL::to_double(v);
        double wd = CGAL::to_double(w);
        double len = std::sqrt(ud*ud + vd*vd + wd*wd);
        if (len < 1e-9) return *this;
        ud /= len; vd /= len; wd /= len;

        double ang_rad = CGAL::to_double(angle_deg) * M_PI / 180.0;
        
        // Check for exact 90-degree rotations on primary axes to preserve exactness where possible
        // This is a simplified check. For a robust arbitrary axis exact 90 deg rotation, 
        // we'd need more logic. Here we handle the general case with high precision rational approximation.
        
        double c = std::cos(ang_rad);
        double s = std::sin(ang_rad);
        double C = 1.0 - c;

        // Rotation matrix elements
        double m00 = ud*ud*C + c;
        double m01 = ud*vd*C - wd*s;
        double m02 = ud*wd*C + vd*s;

        double m10 = vd*ud*C + wd*s;
        double m11 = vd*vd*C + c;
        double m12 = vd*wd*C - ud*s;

        double m20 = wd*ud*C - vd*s;
        double m21 = wd*vd*C + ud*s;
        double m22 = wd*wd*C + c;

        // Convert doubles to FT for the transformation matrix.
        // CGAL will maintain these exact rational values during the transform,
        // preventing degradation *after* this operation, effectively snapping the rotation
        // to the nearest "rational rotation matrix".
        Aff_transformation_3 t(
            m00, m01, m02, 0,
            m10, m11, m12, 0,
            m20, m21, m22, 0,
            1.0 // scaling
        );

        Nef_polyhedron res = nef;
        res.transform(t);
        return Solid(res);
    }

    Solid scale(const std::string& sx, const std::string& sy, const std::string& sz) {
        if (nef.is_empty()) return *this;
        Aff_transformation_3 t(parse_ft(sx), 0, 0, 0,
                               0, parse_ft(sy), 0, 0,
                               0, 0, parse_ft(sz), 0);
        Nef_polyhedron res = nef;
        res.transform(t);
        return Solid(res);
    }
    
    Solid mirror(const std::string& a_str, const std::string& b_str, const std::string& c_str, const std::string& d_str) {
        if (nef.is_empty()) return *this;
        FT a = parse_ft(a_str);
        FT b = parse_ft(b_str);
        FT c = parse_ft(c_str);
        FT d = parse_ft(d_str);
        
        FT sq_norm = a*a + b*b + c*c;
        if (sq_norm == 0) return *this; 
        
        FT two = 2;
        FT f = two / sq_norm;
        
        FT m00 = 1 - f * a * a;
        FT m01 = -f * a * b;
        FT m02 = -f * a * c;
        
        FT m10 = -f * b * a;
        FT m11 = 1 - f * b * b;
        FT m12 = -f * b * c;
        
        FT m20 = -f * c * a;
        FT m21 = -f * c * b;
        FT m22 = 1 - f * c * c;
        
        FT tx = -f * a * d;
        FT ty = -f * b * d;
        FT tz = -f * c * d;
        
        Aff_transformation_3 t(m00, m01, m02, tx,
                               m10, m11, m12, ty,
                               m20, m21, m22, tz);
                               
        Nef_polyhedron res = nef;
        res.transform(t);
        return Solid(res);
    }

    Solid unite(const Solid& other) { return Solid(nef + other.nef); }
    Solid intersect(const Solid& other) { return Solid(nef * other.nef); }
    Solid difference(const Solid& other) { return Solid(nef - other.nef); }

    void to_stl(const std::string& path) {
        if (nef.is_empty()) return;
        Polyhedron P;
        nef.convert_to_polyhedron(P); 
        
        // Convert to Surface_mesh for robust IO
        Surface_mesh sm;
        CGAL::copy_face_graph(P, sm);
        
        std::ofstream out(path, std::ios::binary);
        CGAL::IO::write_STL(out, sm);
    }
    
     void to_off(const std::string& path) {
        if (nef.is_empty()) return;
        Polyhedron P;
        nef.convert_to_polyhedron(P);
        std::ofstream out(path);
        CGAL::IO::write_OFF(out, P);
    }
};

// Primitive: Cube
Solid make_cube(const std::string& sx, const std::string& sy, const std::string& sz, bool center) {
    FT x = parse_ft(sx);
    FT y = parse_ft(sy);
    FT z = parse_ft(sz);
    
    FT x0 = 0, y0 = 0, z0 = 0;
    FT x1 = x, y1 = y, z1 = z;

    if (center) {
        x0 = -x/FT(2); x1 = x/FT(2);
        y0 = -y/FT(2); y1 = y/FT(2);
        z0 = -z/FT(2); z1 = z/FT(2);
    }

    std::vector<Point_3> points;
    points.emplace_back(x0, y0, z0);
    points.emplace_back(x1, y0, z0);
    points.emplace_back(x1, y1, z0);
    points.emplace_back(x0, y1, z0);
    points.emplace_back(x0, y0, z1);
    points.emplace_back(x1, y0, z1);
    points.emplace_back(x1, y1, z1);
    points.emplace_back(x0, y1, z1);

    Polyhedron P;
    CGAL::convex_hull_3(points.begin(), points.end(), P);
    
    if (P.is_closed()) {
        return Solid(Nef_polyhedron(P));
    }
    return Solid();
}

// Primitive: Sphere
Solid make_sphere(const std::string& r_str, const std::string& tol_str) {
    FT r = parse_ft(r_str);
    FT tol = parse_ft(tol_str);
    
    int steps = calculate_steps(r, tol);
    
    double rd = CGAL::to_double(r);
    
    std::vector<Point_3> points;
    
    // Simple UV sphere logic
    for (int i = 0; i <= steps; ++i) {
        double theta = M_PI * i / steps; 
        for (int j = 0; j < 2*steps; ++j) {
            double phi = 2 * M_PI * j / (2*steps); 
            
            double x = rd * sin(theta) * cos(phi);
            double y = rd * sin(theta) * sin(phi);
            double z = rd * cos(theta);
            
            points.emplace_back(x, y, z);
        }
    }
    
    Polyhedron P;
    CGAL::convex_hull_3(points.begin(), points.end(), P);
    if (P.is_closed()) return Solid(Nef_polyhedron(P));
    return Solid();
}

Solid make_cylinder(const std::string& r_str, const std::string& h_str, const std::string& tol_str, bool center) { 
    FT r = parse_ft(r_str);
    FT h = parse_ft(h_str);
    FT tol = parse_ft(tol_str);
    
    int steps = calculate_steps(r, tol);
    
    double rd = CGAL::to_double(r);
    FT z0 = 0, z1 = h;
    if (center) {
        z0 = -h/FT(2);
        z1 = h/FT(2);
    }
    
    std::vector<Point_3> points;
    for (int i=0; i<steps; ++i) {
        double angle = 2 * M_PI * i / steps;
        double x = rd * cos(angle);
        double y = rd * sin(angle);
        points.emplace_back(x, y, z0);
        points.emplace_back(x, y, z1);
    }
     
    Polyhedron P;
    CGAL::convex_hull_3(points.begin(), points.end(), P);
    if (P.is_closed()) return Solid(Nef_polyhedron(P));
    return Solid();
}

Solid make_cone(const std::string& r1_str, const std::string& r2_str, const std::string& h_str, const std::string& tol_str, bool center) { 
    FT r1 = parse_ft(r1_str);
    FT r2 = parse_ft(r2_str);
    FT h = parse_ft(h_str);
    FT tol = parse_ft(tol_str);
    
    // Choose the larger radius for step calculation to ensure tolerance met everywhere
    FT step_r = (r1 > r2) ? r1 : r2;
    int steps = calculate_steps(step_r, tol);

    double r1d = CGAL::to_double(r1);
    double r2d = CGAL::to_double(r2);
    
    FT z0 = 0, z1 = h;
    if (center) {
        z0 = -h/FT(2);
        z1 = h/FT(2);
    }
    
    std::vector<Point_3> points;
    for (int i=0; i<steps; ++i) {
        double angle = 2 * M_PI * i / steps;
        points.emplace_back(r1d * cos(angle), r1d * sin(angle), z0);
        points.emplace_back(r2d * cos(angle), r2d * sin(angle), z1);
    }
     
    Polyhedron P;
    CGAL::convex_hull_3(points.begin(), points.end(), P);
    if (P.is_closed()) return Solid(Nef_polyhedron(P));
    return Solid();
}

PYBIND11_MODULE(_cgal_pycad, m) {
    m.doc() = "CGAL PyCAD Exact Backend";

    py::class_<Solid>(m, "Solid")
        .def(py::init<>())
        .def("translate", &Solid::translate)
        .def("rotate", &Solid::rotate)
        .def("scale", &Solid::scale)
        .def("mirror", &Solid::mirror)
        .def("union", &Solid::unite)
        .def("intersect", &Solid::intersect)
        .def("difference", &Solid::difference)
        .def("to_stl", &Solid::to_stl)
        .def("to_off", &Solid::to_off);

    m.def("cube", &make_cube);
    m.def("sphere", &make_sphere);
    m.def("cylinder", &make_cylinder);
    m.def("cone", &make_cone);
}
