#include <catch2/catch.hpp>

#include <libslic3r/Emboss.hpp>
#include <libslic3r/SVG.hpp> // only debug visualization

#include <optional>
#include <libslic3r/AABBTreeIndirect.hpp>
#include <libslic3r/Utils.hpp> // for next_highest_power_of_2()

using namespace Slic3r;

namespace Private{
        
// calculate multiplication of ray dir to intersect - inspired by
// segment_segment_intersection when ray dir is normalized retur distance from
// ray point to intersection No value mean no intersection
std::optional<double> ray_segment_intersection(const Vec2d &r_point,
                                               const Vec2d &r_dir,
                                               const Vec2d &s0,
                                               const Vec2d &s1)
{
    auto denominate = [](const Vec2d &v0, const Vec2d &v1) -> double {
        return v0.x() * v1.y() - v1.x() * v0.y();
    };

    Vec2d  segment_dir = s1 - s0;
    double d           = denominate(segment_dir, r_dir);
    if (std::abs(d) < std::numeric_limits<double>::epsilon())
        // Line and ray are collinear.
        return {};

    Vec2d  s12         = s0 - r_point;
    double s_number    = denominate(r_dir, s12);
    bool   change_sign = false;
    if (d < 0.) {
        change_sign = true;
        d           = -d;
        s_number    = -s_number;
    }

    if (s_number < 0. || s_number > d)
        // Intersection outside of segment.
        return {};

    double r_number = denominate(segment_dir, s12);
    if (change_sign) r_number = -r_number;

    if (r_number < 0.)
        // Intersection before ray start.
        return {};

    return r_number / d;
}

Vec2d get_intersection(const Vec2d &               point,
                       const Vec2d &               dir,
                       const std::array<Vec2d, 3> &triangle)
{
    std::optional<double> t;
    for (size_t i = 0; i < 3; ++i) {
        size_t i2 = i + 1;
        if (i2 == 3) i2 = 0;
        if (!t.has_value()) {
            t = ray_segment_intersection(point, dir, triangle[i],
                                         triangle[i2]);
            continue;
        }

        // small distance could be preccission inconsistance
        std::optional<double> t2 = ray_segment_intersection(point, dir,
                                                            triangle[i],
                                                            triangle[i2]);
        if (t2.has_value() && *t2 > *t) t = t2;
    }
    assert(t.has_value()); // Not found intersection.
    return point + dir * (*t);
}

Vec3d calc_hit_point(const igl::Hit &          h,
                     const Vec3i &             triangle,
                     const std::vector<Vec3f> &vertices)
{
    double c1 = h.u;
    double c2 = h.v;
    double c0 = 1.0 - c1 - c2;
    Vec3d  v0 = vertices[triangle[0]].cast<double>();
    Vec3d  v1 = vertices[triangle[1]].cast<double>();
    Vec3d  v2 = vertices[triangle[2]].cast<double>();
    return v0 * c0 + v1 * c1 + v2 * c2;
}

Vec3d calc_hit_point(const igl::Hit &h, indexed_triangle_set &its)
{
    return calc_hit_point(h, its.indices[h.id], its.vertices);
}
} // namespace Private

std::string get_font_filepath() {
    std::string resource_dir = 
        std::string(TEST_DATA_DIR) + "/../../resources/";
    return resource_dir + "fonts/NotoSans-Regular.ttf";
}

#include "imgui/imstb_truetype.h"
TEST_CASE("Read glyph C shape from font, stb library calls ONLY", "[Emboss]") {
    std::string font_path = get_font_filepath();
    char  letter   = 'C';
    
    // Read  font file
    FILE *file = fopen(font_path.c_str(), "rb");
    REQUIRE(file != nullptr);
    // find size of file
    REQUIRE(fseek(file, 0L, SEEK_END) == 0);
    size_t size = ftell(file);
    REQUIRE(size != 0);
    rewind(file);
    std::vector<unsigned char> buffer(size);
    size_t count_loaded_bytes = fread((void *) &buffer.front(), 1, size, file);
    REQUIRE(count_loaded_bytes == size);

    // Use stb true type library
    int font_offset = stbtt_GetFontOffsetForIndex(buffer.data(), 0);
    REQUIRE(font_offset >= 0);
    stbtt_fontinfo font_info;
    REQUIRE(stbtt_InitFont(&font_info, buffer.data(), font_offset) != 0);    
    int unicode_letter = (int) letter;
    int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter);
    REQUIRE(glyph_index != 0);
    stbtt_vertex *vertices;
    int num_verts = stbtt_GetGlyphShape(&font_info, glyph_index, &vertices);
    CHECK(num_verts > 0);
}

#include <libslic3r/Utils.hpp>
TEST_CASE("Convert glyph % to model", "[Emboss]") 
{
    std::string font_path = get_font_filepath();
    char  letter   = '%';
    float flatness = 2.;

    auto font = Emboss::create_font_file(font_path.c_str());
    REQUIRE(font != nullptr);

    std::optional<Emboss::Glyph> glyph = Emboss::letter2glyph(*font, letter, flatness);
    REQUIRE(glyph.has_value());

    ExPolygons shape = glyph->shape;    
    REQUIRE(!shape.empty());

    float z_depth = 1.f;
    Emboss::ProjectZ projection(z_depth);
    indexed_triangle_set its = Emboss::polygons2model(shape, projection);

    CHECK(!its.indices.empty());    
}

TEST_CASE("Test hit point", "[AABBTreeIndirect]")
{
    indexed_triangle_set its;
    its.vertices = {
        Vec3f(1, 1, 1),
        Vec3f(2, 10, 2),
        Vec3f(10, 0, 2),
    };
    its.indices = {Vec3i(0, 2, 1)};
    auto tree   = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
        its.vertices, its.indices);

    Vec3d    ray_point(8, 1, 0);
    Vec3d    ray_dir(0, 0, 1);
    igl::Hit hit;
    AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices, tree,
                                              ray_point, ray_dir, hit);
    Vec3d hp = Private::calc_hit_point(hit, its);
    CHECK(abs(hp.x() - ray_point.x()) < .1);
    CHECK(abs(hp.y() - ray_point.y()) < .1);
}

TEST_CASE("ray segment intersection", "[MeshBoolean]")
{
    Vec2d r_point(1, 1);
    Vec2d r_dir(1, 0);

    // colinear
    CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(0, 0), Vec2d(2, 0)).has_value());
    CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 0), Vec2d(0, 0)).has_value());

    // before ray
    CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(0, 0), Vec2d(0, 2)).has_value());
    CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(0, 2), Vec2d(0, 0)).has_value());

    // above ray
    CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 2), Vec2d(2, 3)).has_value());
    CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 3), Vec2d(2, 2)).has_value());

    // belove ray
    CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 0), Vec2d(2, -1)).has_value());
    CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, -1), Vec2d(2, 0)).has_value());

    // intersection at [2,1] distance 1
    auto t1 = Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 0), Vec2d(2, 2));
    REQUIRE(t1.has_value());
    auto t2 = Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 2), Vec2d(2, 0));
    REQUIRE(t2.has_value());

    CHECK(abs(*t1 - *t2) < std::numeric_limits<double>::epsilon());
}

TEST_CASE("triangle intersection", "[]")
{
    Vec2d                point(1, 1);
    Vec2d                dir(-1, 0);
    std::array<Vec2d, 3> triangle = {Vec2d(0, 0), Vec2d(5, 0), Vec2d(0, 5)};
    Vec2d                i = Private::get_intersection(point, dir, triangle);
    CHECK(abs(i.x()) < std::numeric_limits<double>::epsilon());
    CHECK(abs(i.y() - 1.) < std::numeric_limits<double>::epsilon());
}

#ifndef __APPLE__
#include <string>
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
// Check function Emboss::is_italic that exist some italic and some non-italic font.
TEST_CASE("Italic check", "[Emboss]") 
{  
    std::queue<std::string> dir_paths;
#ifdef _WIN32
    dir_paths.push("C:/Windows/Fonts");
#elif defined(__linux__)
    dir_paths.push("/usr/share/fonts");
//#elif defined(__APPLE__)
//    dir_paths.push("//System/Library/Fonts");
#endif
    bool exist_italic = false;
    bool exist_non_italic = false;
    while (!dir_paths.empty()) {
        std::string dir_path = dir_paths.front();
        dir_paths.pop();
        for (const auto &entry : fs::directory_iterator(dir_path)) {
            const fs::path &act_path = entry.path();
            if (entry.is_directory()) {
                dir_paths.push(act_path.u8string());
                continue;
            }
            std::string ext = act_path.extension().u8string();
            std::transform(ext.begin(), ext.end(), ext.begin(),
                           [](unsigned char c) { return std::tolower(c); });
            if (ext != ".ttf") continue;
            std::string path_str = act_path.u8string();
            auto        font_opt = Emboss::create_font_file(path_str.c_str());
            if (font_opt == nullptr) continue;

            unsigned int collection_number = 0;
            if (Emboss::is_italic(*font_opt, collection_number))
                exist_italic = true;
            else
                exist_non_italic = true;

            if (exist_italic && exist_non_italic) break;
            //std::cout << ((Emboss::is_italic(*font_opt)) ? "[yes] " : "[no ] ") << entry.path() << std::endl;
        }
    }
    CHECK(exist_italic);
    CHECK(exist_non_italic);
}
#endif // not __APPLE__

#include <CGAL/Polygon_mesh_processing/corefinement.h>
#include <CGAL/Exact_integer.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Cartesian_converter.h>

/// <summary>
/// Distiguish point made by shape(Expolygon)
/// Referencing an ExPolygon contour plus a vertex base of the contour.
/// Used for adressing Vertex of mesh created by extrude ExPolygons
/// </summary>
struct ShapesVertexId {
    // Index of an ExPolygon in ExPolygons.
    int32_t expoly{ -1 };

    // Index of a contour in ExPolygon.
    // 0 - outer contour, >0 - hole
    int32_t contour{ -1 };

    // Base of the zero'th point of a contour in text mesh.
    // There are two vertices (front and rear) created for each contour,
    // thus there are 2x more vertices in text mesh than the number of contour points.
    int32_t vertex_base{ -1 };
};

/// <summary>
/// IntersectingElemnt
/// 
/// Adress polygon inside of ExPolygon
/// Keep information about source of vertex:
///     - from face (one of 2 possible)
///     - from edge (one of 2 possible)
/// 
/// V1~~~~V2
/// : f1 /|
/// :   / |
/// :  /e1| 
/// : /   |e2
/// :/ f2 |
/// V1'~~~V2'
/// 
/// | .. edge
/// / .. edge
/// : .. foreign edge - neighbor 
/// ~ .. no care edge - idealy should not cross model
/// V1,V1' .. projected 2d point to 3d
/// V2,V2' .. projected 2d point to 3d
/// 
/// f1 .. text_face_1 (triangle face made by side of shape contour)
/// f2 .. text_face_2
/// e1 .. text_edge_1 (edge on side of face made by side of shape contour)
/// e2 .. text_edge_2
/// 
/// </summary>
struct IntersectingElemnt
{
    // Index into vector of ShapeVertexId
    // describe point on shape contour
    int32_t vertex_index{ -1 };

    // index of point in Polygon contour
    int32_t point_index{-1};

    // vertex or edge ID, where edge ID is the index of the source point.
    // There are 4 consecutive indices generated for a single glyph edge:
    // 0th - 1st text edge (straight)
    // 1th - 1st text face
    // 2nd - 2nd text edge (diagonal)
    // 3th - 2nd text face
    // Type of intersecting element from extruded shape( 3d )
    enum class Type {
        edge_1 = 0,
        face_1 = 1,
        edge_2 = 2,
        face_2 = 3,

        undefined = 4
    } type = Type::undefined;
};

namespace Slic3r::MeshBoolean::cgal2 {

    namespace CGALProc = CGAL::Polygon_mesh_processing;
    namespace CGALParams = CGAL::Polygon_mesh_processing::parameters;

    using EpicKernel = CGAL::Exact_predicates_inexact_constructions_kernel;
    using _EpicMesh = CGAL::Surface_mesh<EpicKernel::Point_3>;
//    using EpecKernel = CGAL::Exact_predicates_exact_constructions_kernel;
//    using _EpecMesh = CGAL::Surface_mesh<EpecKernel::Point_3>;

    using CGALMesh = _EpicMesh;

    // Add an indexed triangle mesh to CGAL Surface_mesh.
    // Store map of CGAL face to source face index into face_map.
    void triangle_mesh_to_cgal(
        const std::vector<stl_vertex>                           &V,
        const std::vector<stl_triangle_vertex_indices>          &F,
        CGALMesh                                                &out, 
        CGALMesh::Property_map<CGAL::SM_Face_index, int32_t>     object_face_source_id)
    {
        if (F.empty())
            return;

        size_t vertices_count = V.size();
        size_t edges_count = (F.size() * 3) / 2;
        size_t faces_count = F.size();
        out.reserve(vertices_count, edges_count, faces_count);

        for (auto& v : V)
            out.add_vertex(typename CGALMesh::Point{ v.x(), v.y(), v.z() });

        using VI = typename CGALMesh::Vertex_index;
        for (auto& f : F) {
            auto fid = out.add_face(VI(f(0)), VI(f(1)), VI(f(2)));
            // index of face in source triangle mesh
            int32_t index = static_cast<int32_t>(&f - &F.front());
            object_face_source_id[fid] = index;
        }
    }
            
    /// <summary>
    /// Convert triangle mesh model to CGAL Surface_mesh
    /// Add property map for source face index
    /// </summary>
    /// <param name="its">Model</param>
    /// <param name="face_map_name">Property map name for store conversion from CGAL face to index to its</param>
    /// <returns>CGAL mesh - half edge mesh</returns>
    CGALMesh to_cgal(const indexed_triangle_set &its,
                     const std::string          &face_map_name)
    {
        CGALMesh result;
        if (its.empty()) return result;

        const std::vector<stl_vertex>                  &V = its.vertices;
        const std::vector<stl_triangle_vertex_indices> &F = its.indices;

        // convert from CGAL face to its face
        auto face_map = result.add_property_map<CGALMesh::Face_index, int32_t>(face_map_name).first;

        size_t vertices_count = V.size();
        size_t edges_count    = (F.size() * 3) / 2;
        size_t faces_count    = F.size();
        result.reserve(vertices_count, edges_count, faces_count);

        for (auto &v : V)
            result.add_vertex(typename CGALMesh::Point{v.x(), v.y(), v.z()});

        using VI = typename CGALMesh::Vertex_index;
        for (auto &f : F)
        {
            auto fid = result.add_face(VI(f(0)), VI(f(1)), VI(f(2)));
            // index of face in source triangle mesh
            int32_t index = static_cast<int32_t>(&f - &F.front());
            face_map[fid] = index;
        }

        return result;
    }

    /// <summary>
    /// Covert 2d shape (e.g. Glyph) to CGAL model
    /// </summary>
    /// <param name="shape">2d shape to project</param>
    /// <param name="projection">Define transformation 2d point into 3d</param>
    /// <param name="shape_id">Identify shape</param>
    /// <param name="edge_shape_map_name">Name of property map to store conversion from edge to contour</param>
    /// <param name="face_shape_map_name">Name of property map to store conversion from face to contour</param>
    /// <param name="contour_indices">Identify point on shape contour</param>
    /// <returns>CGAL model of extruded shape</returns>
    CGALMesh to_cgal(const ExPolygon                &shape,
                     const Slic3r::Emboss::IProject &projection,
                     int32_t                         shape_id,
                     const std::string              &edge_shape_map_name,
                     const std::string              &face_shape_map_name,
                     std::vector<ShapesVertexId>      &contour_indices)
    {
        CGALMesh result;
        if (shape.empty()) return result;
        
        auto edge_shape_map = result.add_property_map<CGALMesh::Edge_index, IntersectingElemnt>(edge_shape_map_name).first;
        auto face_shape_map = result.add_property_map<CGALMesh::Face_index, IntersectingElemnt>(face_shape_map_name).first;

        std::vector<CGALMesh::Vertex_index> indices;
        auto insert_contour = [&projection, &indices , &result, &contour_indices, &edge_shape_map, &face_shape_map](const Polygon& polygon, int32_t iexpoly, int32_t id) {
            indices.clear();
            indices.reserve(polygon.points.size() * 2);
            size_t num_vertices_old = result.number_of_vertices();
            int32_t vertex_index = static_cast<int32_t>(contour_indices.size());
            contour_indices.push_back({iexpoly, id, int32_t(num_vertices_old) });
            for (const Point& p2 : polygon.points) {
                auto p = projection.project(p2);
                auto vi = result.add_vertex(typename CGALMesh::Point{ p.first.x(), p.first.y(), p.first.z() });
                assert((size_t)vi == indices.size() + num_vertices_old);
                indices.emplace_back(vi);
                vi = result.add_vertex(typename CGALMesh::Point{ p.second.x(), p.second.y(), p.second.z() });
                assert((size_t)vi == indices.size() + num_vertices_old);
                indices.emplace_back(vi);
            }
            int32_t contour_index = 0;
            for (int32_t i = 0; i < int32_t(indices.size()); i += 2) {
                int32_t j = (i + 2) % int32_t(indices.size());
                auto find_edge = [&result](CGALMesh::Face_index fi, CGALMesh::Vertex_index from, CGALMesh::Vertex_index to) {
                    CGALMesh::Halfedge_index hi = result.halfedge(fi);
                    for (; result.target(hi) != to; hi = result.next(hi));
                    assert(result.source(hi) == from);
                    assert(result.target(hi) == to);
                    return hi;
                };
                auto fi = result.add_face(indices[i], indices[i + 1], indices[j]);
                edge_shape_map[result.edge(find_edge(fi, indices[i], indices[i + 1]))] = 
                    IntersectingElemnt{vertex_index, contour_index, IntersectingElemnt::Type::edge_1};
                face_shape_map[fi] =                     
                    IntersectingElemnt{vertex_index, contour_index, IntersectingElemnt::Type::face_1};
                edge_shape_map[result.edge(find_edge(fi, indices[i + 1], indices[j]))] =
                    IntersectingElemnt{vertex_index, contour_index, IntersectingElemnt::Type::edge_2};
                face_shape_map[result.add_face(indices[j], indices[i + 1], indices[j + 1])] =                     
                    IntersectingElemnt{vertex_index, contour_index, IntersectingElemnt::Type::face_2};
                ++contour_index;
            }
        };

        size_t count_point = count_points(shape);
        result.reserve(result.number_of_vertices() + 2 * count_point, result.number_of_edges() + 4 * count_point, result.number_of_faces() + 2 * count_point);

        // Identify polygon
        // (contour_id > 0) are holes
        size_t contour_id = 0;
        insert_contour(shape.contour, shape_id, contour_id++);
        for (const Polygon& hole : shape.holes) 
            insert_contour(hole, shape_id, contour_id++);
        
        return result;
    }
}

bool its_write_obj(const indexed_triangle_set& its, const std::vector<Vec3f> &color, const char* file)
{
    Slic3r::CNumericLocalesSetter locales_setter;
    FILE* fp = fopen(file, "w");
    if (fp == nullptr) {
        return false;
    }

    for (size_t i = 0; i < its.vertices.size(); ++i)
        fprintf(fp, "v %f %f %f %f %f %f\n", 
            its.vertices[i](0), its.vertices[i](1), its.vertices[i](2),
            color[i](0), color[i](1), color[i](2));
    for (size_t i = 0; i < its.indices.size(); ++i)
        fprintf(fp, "f %d %d %d\n", its.indices[i][0] + 1, its.indices[i][1] + 1, its.indices[i][2] + 1);
    fclose(fp);
    return true;
}

/// <summary>
/// Merge one triangle mesh to another
/// Added triangle set will allive
/// </summary>
/// <param name="its">IN / OUT triangle mesh</param>
/// <param name="its_add">IN triangle mesh</param>
void its_append(indexed_triangle_set &its, const indexed_triangle_set &its_add)
{
    if (its.empty()) {
        its = its_add; // copy
        return;
    }
    auto &verts = its.vertices;
    size_t verts_size = verts.size();
    verts.reserve(verts_size + its_add.vertices.size());    
    append(verts, its_add.vertices);

    auto &idxs = its.indices;
    idxs.reserve(idxs.size() + its_add.indices.size());

    // increase face indices
    int offset = static_cast<int>(verts_size);
    for (auto face : its_add.indices) {
        for (int i = 0; i < 3; ++i) face[i] += offset;
        idxs.emplace_back(face);
    }
}

/// <summary>
/// Merge one triangle mesh to another
/// Added triangle set will be consumed
/// </summary>
/// <param name="its">IN/OUT triangle mesh</param>
/// <param name="its_add">Triangle mesh (will be consumed)</param>
void its_append(indexed_triangle_set &its, indexed_triangle_set &&its_add)
{
    if (its.empty()) { 
        its = std::move(its_add);
        return;
    }
    auto &verts = its.vertices;
    size_t verts_size = verts.size();
    append(verts, std::move(its_add.vertices));

    // increase face indices
    int offset = static_cast<int>(verts_size);
    for (auto &face : its_add.indices)
        for (int i = 0; i < 3; ++i) face[i] += offset;
    append(its.indices, std::move(its_add.indices));
}

//// 1 ////

// Question store(1) Or calculate on demand(2) ??
// (1) type: vector <vector<vertex indices>>
// (1) Needs recalculation when merge and propagation togewther with its
// (2) Could appear surface mistakes(need calc - all half edges) 
// (2) NO need of trace cut outline and connect it with letter conture points 

/// <summary>
/// Cut surface shape from source model
/// </summary>
/// <param name="source">Input source mesh</param>
/// <param name="shape">Input 2d shape to cut from surface</param>
/// <param name="projection">Define transformation from 2d to 3d</param>
/// <returns>Cutted surface, Its do not represent Volume</returns>
indexed_triangle_set cut_shape(const indexed_triangle_set &source,
                               const ExPolygon            &shape,
                               const Emboss::IProject     &projection)
{
    throw std::exception("NOT implemented yet");
    return {};
}

/// <summary>
/// Cut surface shape from source model
/// </summary>
/// <param name="source">Input source mesh</param>
/// <param name="shapes">Input 2d shape to cut from surface</param>
/// <param name="projection">Define transformation from 2d to 3d</param>
/// <returns>Cutted surface, Its do not represent Volume</returns>
indexed_triangle_set cut_shape(const indexed_triangle_set &source,
                               const ExPolygons           &shapes,
                               const Emboss::IProject     &projection)
{
    indexed_triangle_set result;
    for (const ExPolygon &shape : shapes)
        its_merge(result, cut_shape(source, shape, projection));
    return result;
}

/// <summary>
/// Represents cutted surface from object
/// Extend index triangle set by outlines
/// </summary>
struct SurfaceCut : public indexed_triangle_set
{
    using Index = unsigned int;
    // cutted surface
    indexed_triangle_set mesh;

    // list of circulated open surface
    std::vector<std::vector<Index>> cut;
};

/// <summary>
/// Merge two surface cuts together
/// Added surface cut will be consumed
/// </summary>
/// <param name="sc">Surface cut to extend</param>
/// <param name="sc_add">Surface cut to consume</param>
void append(SurfaceCut &sc, SurfaceCut &&sc_add)
{
    if (sc.empty()) { 
        sc = std::move(sc_add);
        return;
    }

    if (!sc_add.cut.empty()) {
        SurfaceCut::Index offset = 
            static_cast<SurfaceCut::Index>(sc.vertices.size());
        size_t require = sc.cut.size() + sc_add.cut.size();
        if (sc.cut.capacity() < require) sc.cut.reserve(require);
        for (std::vector<SurfaceCut::Index> &cut : sc_add.cut)
            for (SurfaceCut::Index &i : cut) i += offset;
        append(sc.cut, std::move(sc_add.cut));
    }
    its_append(sc, std::move(sc_add));
}

using MyMesh = Slic3r::MeshBoolean::cgal2::CGALMesh;

/// <summary>
/// Cut surface shape from model
/// </summary>
/// <param name="model">Mesh to cut</param>
/// <param name="shape">Shape to cut from model</param>
/// <param name="projection">Define transformation from 2d shape to 3d</param>
/// <returns>Cutted surface from model</returns>
SurfaceCut cut_surface(const MyMesh           &model,
                       const ExPolygon        &shape,
                       const Emboss::IProject &projection)
{
    SurfaceCut result;
    return result;
}

/// <summary>
/// Cut surface shape from model
/// </summary>
/// <param name="model">Mesh to cut</param>
/// <param name="shapes">Multi shapes to cut from model</param>
/// <param name="projection">Define transformation from 2d shape to 3d</param>
/// <returns>Cutted surface from model</returns>
SurfaceCut cut_surface(const indexed_triangle_set &model,
                       const ExPolygons           &shapes,
                       const Emboss::IProject     &projection)
{
    SurfaceCut result;
    //for (const ExPolygon& shape : shapes)
    //    append(result, cut_surface(model, shape, projection));
    return result;
}

// First Idea //// 1 ////
// Use source model to modify ONLY surface of text ModelVolume

// Second Idea
// Store original its inside of text configuration[optional]
// Cause problem with next editation of object -> cut, simplify, Netfabb, Hollow, ...(transform original vertices)
TEST_CASE("Emboss extrude cut", "[Emboss-Cut]")
{
    std::string font_path = get_font_filepath();
    char  letter   = '$';
    float flatness = 2.;

    auto font = Emboss::create_font_file(font_path.c_str());
    REQUIRE(font != nullptr);

    std::optional<Emboss::Glyph> glyph = Emboss::letter2glyph(*font, letter,
                                                              flatness);
    REQUIRE(glyph.has_value());

    ExPolygons shape = glyph->shape;
    REQUIRE(!shape.empty());

    float            z_depth = 50.f;
    Emboss::ProjectZ projection(z_depth);

#if 0
    indexed_triangle_set text = Emboss::polygons2model(shape, projection);
    BoundingBoxf3 bbox = bounding_box(text);

    CHECK(!text.indices.empty());
#endif
    
    auto cube = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5);
    its_translate(cube, Vec3f(49 - 25, -10 - 25, 2.5));
    auto cube2 = cube;
//    its_translate(cube2, Vec3f(0, 0, 40));
    its_translate(cube2, Vec3f(100, -40, 40));
    its_append(cube, std::move(cube2));

    //cube = its_make_sphere(350., 1.);
    //for (auto &face : cube2.indices)
    //    for (int i = 0; i < 3; ++ i)
    //        face(i) += int(cube.vertices.size());
    //append(cube.vertices, cube2.vertices);
    //append(cube.indices, cube2.indices);

    using MyMesh = Slic3r::MeshBoolean::cgal2::CGALMesh;
    
    // name of CGAL property map for store source object face id - index into its.indices
    std::string face_map_name = "f:face_map";
    // identify glyph for intersected vertex
    std::string vert_shape_map_name = "v:glyph_id";
    MyMesh cgal_object = MeshBoolean::cgal2::to_cgal(cube, face_map_name);
    auto& face_map = cgal_object.property_map<MyMesh::Face_index, int32_t>(face_map_name).first;
    auto& vert_shape_map = cgal_object.add_property_map<MyMesh::Vertex_index, IntersectingElemnt>(vert_shape_map_name).first;

    std::string edge_shape_map_name = "e:glyph_id";
    std::string face_shape_map_name = "f:glyph_id";
    std::vector<ShapesVertexId> glyph_contours;

    //std::vector<MyMesh>       cgalShapes;
    //cgalShapes.reserve(shape.size());
    //for (const ExPolygon &expoly : shape) {
    //    size_t index = &expoly - &shape.front();
    //    cgalShapes
    //}

    MyMesh cgal_shape = MeshBoolean::cgal2::to_cgal(shape[0], projection, 0, edge_shape_map_name, face_shape_map_name, glyph_contours);    

    auto& edge_shape_map = cgal_shape.property_map<MyMesh::Edge_index, IntersectingElemnt>(edge_shape_map_name).first;
    auto& face_shape_map = cgal_shape.property_map<MyMesh::Face_index, IntersectingElemnt>(face_shape_map_name).first;

    //MeshBoolean::cgal2::glyph2model(shape, 0, projection, cgal_shape, glyph_contours, edge_glyph_map, face_glyph_map);

    struct Visitor {
        const MyMesh &object;
        const MyMesh &shape;
        // Properties of the shape mesh:
        MyMesh::Property_map<CGAL::SM_Edge_index, IntersectingElemnt> edge_shape_map;
        MyMesh::Property_map<CGAL::SM_Face_index, IntersectingElemnt> face_shape_map;
        // Properties of the object mesh.
        MyMesh::Property_map<CGAL::SM_Face_index, int32_t> face_map;
        MyMesh::Property_map<CGAL::SM_Vertex_index, IntersectingElemnt> vert_shape_map;

        typedef boost::graph_traits<MyMesh> GT;
        typedef typename GT::face_descriptor face_descriptor;
        typedef typename GT::halfedge_descriptor halfedge_descriptor;
        typedef typename GT::vertex_descriptor vertex_descriptor;

        int32_t source_face_id;

        void before_subface_creations(face_descriptor f_old, MyMesh& mesh)
        {
            assert(&mesh == &object);
            source_face_id = face_map[f_old];
        }
        void after_subface_created(face_descriptor f_new, MyMesh& mesh) {
            assert(&mesh == &object);
            face_map[f_new] = source_face_id;
        }

        std::vector<const IntersectingElemnt*> intersection_point_glyph;

        // Intersecting an edge hh_edge from tm_edge with a face hh_face of tm_face.
        void intersection_point_detected(
            // ID of the intersection point, starting at 0. Ids are consecutive.
            std::size_t         i_id,
            // Dimension of a simplex part of face(hh_face) that is intersected by hh_edge:
            // 0 for vertex: target(hh_face)
            // 1 for edge: hh_face
            // 2 for the interior of face: face(hh_face)
            int                 simplex_dimension,
            // Edge of tm_edge, see edge_source_coplanar_with_face & edge_target_coplanar_with_face whether any vertex of hh_edge is coplanar with face(hh_face).
            halfedge_descriptor hh_edge,
            // Vertex, halfedge or face of tm_face intersected by hh_edge, see comment at simplex_dimension.
            halfedge_descriptor hh_face,
            // Mesh containing hh_edge
            const MyMesh& tm_edge,
            // Mesh containing hh_face
            const MyMesh& tm_face,
            // source(hh_edge) is coplanar with face(hh_face).
            bool                edge_source_coplanar_with_face,
            // target(hh_edge) is coplanar with face(hh_face).
            bool                edge_target_coplanar_with_face)
        {
            if (i_id <= intersection_point_glyph.size()) {
                intersection_point_glyph.reserve(Slic3r::next_highest_power_of_2(i_id + 1));
                intersection_point_glyph.resize(i_id + 1);
            }

            const IntersectingElemnt* glyph = nullptr;
            if (&tm_face == &shape) {
                assert(&tm_edge == &object);
                switch (simplex_dimension) {
                case 1:
                    // edge x edge intersection
                    glyph = &edge_shape_map[shape.edge(hh_face)];
                    break;
                case 2:
                    // edge x face intersection
                    glyph = &face_shape_map[shape.face(hh_face)];
                    break;
                default:
                    assert(false);
                }
                if (edge_source_coplanar_with_face)
                    vert_shape_map[object.source(hh_edge)] = *glyph;
                if (edge_target_coplanar_with_face)
                    vert_shape_map[object.target(hh_edge)] = *glyph;
            } else {
                assert(&tm_edge == &shape && &tm_face == &object);
                assert(!edge_source_coplanar_with_face);
                assert(!edge_target_coplanar_with_face);
                glyph = &edge_shape_map[shape.edge(hh_edge)];
                if (simplex_dimension == 0)
                    vert_shape_map[object.target(hh_face)] = *glyph;
            }
            intersection_point_glyph[i_id] = glyph;
        }

        void new_vertex_added(std::size_t node_id, vertex_descriptor vh, const MyMesh &tm)
        {
            assert(&tm == &object);
            assert(node_id < intersection_point_glyph.size());
            const IntersectingElemnt * glyph = intersection_point_glyph[node_id];
            assert(glyph != nullptr);
            assert(glyph->vertex_index != -1);
            assert(glyph->point_index != -1);
            vert_shape_map[vh] = glyph ? *glyph : IntersectingElemnt{};
        }

        void after_subface_creations(MyMesh&) {}
        void before_subface_created(MyMesh&) {}
        void before_edge_split(halfedge_descriptor /* h */, MyMesh& /* tm */) {}
        void edge_split(halfedge_descriptor /* hnew */, MyMesh& /* tm */) {}
        void after_edge_split() {}
        void add_retriangulation_edge(halfedge_descriptor /* h */, MyMesh& /* tm */) {}
    }
    visitor { cgal_object, cgal_shape, edge_shape_map, face_shape_map, face_map, vert_shape_map};

    // bool map for affected edge
    auto ecm = get(CGAL::dynamic_edge_property_t<bool>(), cgal_object);
    const auto& p = CGAL::Polygon_mesh_processing::parameters::throw_on_self_intersection(false).visitor(visitor).edge_is_constrained_map(ecm);
    const auto& q = CGAL::Polygon_mesh_processing::parameters::do_not_modify(true);
    //    CGAL::Polygon_mesh_processing::corefine(cgal_object, cgalcube2, p, p);

    CGAL::Polygon_mesh_processing::corefine(cgal_object, cgal_shape, p, q);

    enum class SideType {
        inside,
        outside,
        not_constrained
    };
    
    auto side_type_map = cgal_object.add_property_map<MyMesh::Face_index, SideType>("f:side").first;

    for (auto fi : cgal_object.faces()) {
        SideType side_type = SideType::not_constrained;
        auto hi_end = cgal_object.halfedge(fi);
        auto hi = hi_end;
        do {
            CGAL::SM_Edge_index edge_index = cgal_object.edge(hi);
            // is edge new created - constrained?
            if (get(ecm, edge_index)) {
                // This face has a constrained edge.
                IntersectingElemnt shape_from = vert_shape_map[cgal_object.source(hi)];
                IntersectingElemnt shape_to = vert_shape_map[cgal_object.target(hi)];
                assert(shape_from.vertex_index != -1 && shape_from.vertex_index == shape_to.vertex_index);
                assert(shape_from.point_index != -1);
                assert(shape_to.point_index != -1);
                
                const ShapesVertexId &vertex_index = glyph_contours[shape_from.vertex_index];
                const ExPolygon &expoly  = shape[vertex_index.expoly];
                const Polygon &contour = vertex_index.contour == 0 ? expoly.contour : expoly.holes[vertex_index.contour - 1];
                bool is_inside = false;
                
                // 4 type 
                // index into contour
                int32_t i_from = shape_from.point_index;
                int32_t i_to = shape_to.point_index;
                if (i_from == i_to && shape_from.type == shape_to.type) {
                    // Outside is detect by face orientation
                    is_inside = true;
                } else if (i_from < i_to || (i_from == i_to && shape_from.type < shape_to.type)) {
                    bool is_last = i_from == 0 && (i_to + 1) == contour.size();
                    if (!is_last) is_inside = true;
                } else { // i_from > i_to || (i_from == i_to && shape_from.type > shape_to.type)
                    bool is_last = i_to == 0 && (i_from + 1) == contour.size();
                    if (is_last) is_inside = true;
                }

                if (is_inside) {
                    // Is this face oriented towards p or away from p?
                    const auto &a = cgal_object.point(cgal_object.source(hi));
                    const auto &b = cgal_object.point(cgal_object.target(hi));
                    const auto &c = cgal_object.point(cgal_object.target(cgal_object.next(hi)));
                    //FIXME prosim nahrad skutecnou projekci.
                    //projection.project()
                    const auto  p = a + MeshBoolean::cgal2::EpicKernel::Vector_3(0, 0, 10);
                    auto abcp = CGAL::orientation(a, b, c, p);
                    if (abcp == CGAL::POSITIVE)
                        side_type = SideType::inside;
                    else
                        is_inside = false;
                } 
                if (!is_inside) side_type = SideType::outside;                
                break;
            }
            // next half edge index inside of face
            hi = cgal_object.next(hi);
        } while (hi != hi_end);

        side_type_map[fi] = side_type;
    }
    
    // debug output
    auto face_colors = cgal_object.add_property_map<MyMesh::Face_index, CGAL::Color>("f:color").first;
    for (auto fi : cgal_object.faces()) { 
        auto &color = face_colors[fi];
        switch (side_type_map[fi]) {
        case SideType::inside: color = CGAL::Color{255, 0, 0}; break;
        case SideType::outside: color = CGAL::Color{255, 0, 255}; break;
        case SideType::not_constrained: color = CGAL::Color{0, 255, 0}; break;
        }
    }
    CGAL::IO::write_OFF("c:\\data\\temp\\constrained.off", cgal_object);

    
    // separate by direction of extrusion
    auto vertex_colors = cgal_object.add_property_map<MyMesh::Vertex_index, CGAL::Color>("v:color").first;
    // Seed fill the other faces inside the region.
    for (Visitor::face_descriptor fi : cgal_object.faces()) {
        if (side_type_map[fi] != SideType::not_constrained) continue;

        // check if neighbor face is inside
        Visitor::halfedge_descriptor hi     = cgal_object.halfedge(fi);
        Visitor::halfedge_descriptor hi_end = hi; 

        bool has_inside_neighbor = false;
        std::vector<MyMesh::Face_index> queue;
        do {
            Visitor::face_descriptor fi_opposite = cgal_object.face(cgal_object.opposite(hi));
            SideType side = side_type_map[fi_opposite];
            if (side == SideType::inside) {
                has_inside_neighbor = true;
            } else if (side == SideType::not_constrained) {
                queue.emplace_back(fi_opposite);
            }
            hi = cgal_object.next(hi);
        } while (hi != hi_end);
        if (!has_inside_neighbor) continue;
        side_type_map[fi] = SideType::inside;

        do {
            Visitor::face_descriptor fi = queue.back();
            queue.pop_back();
            // Do not fill twice
            if (side_type_map[fi] == SideType::inside) continue;
            side_type_map[fi] = SideType::inside;

            // check neighbor triangle
            Visitor::halfedge_descriptor hi     = cgal_object.halfedge(fi);
            Visitor::halfedge_descriptor hi_end = hi; 
            do {
                Visitor::face_descriptor fi_opposite = cgal_object.face(cgal_object.opposite(hi));
                SideType side = side_type_map[fi_opposite];
                if (side == SideType::not_constrained) 
                    queue.emplace_back(fi_opposite);                
                hi = cgal_object.next(hi);
            } while (hi != hi_end);
        } while (!queue.empty());            
    }

    // debug output
    for (auto fi : cgal_object.faces()) { 
        auto &color = face_colors[fi];
        switch (side_type_map[fi]) {
        case SideType::inside: color = CGAL::Color{255, 0, 0}; break;
        case SideType::outside: color = CGAL::Color{255, 0, 255}; break;
        case SideType::not_constrained: color = CGAL::Color{0, 255, 0}; break;
        }
    }
    CGAL::IO::write_OFF("c:\\data\\temp\\filled.off", cgal_object);

    // Mapping of its_extruded faces to source faces.
    enum class FaceState : int8_t {
        Unknown         = -1,
        Unmarked        = -2,
        UnmarkedSplit   = -3,
        Marked          = -4,
        MarkedSplit     = -5,
        UnmarkedEmitted = -6,
    };
    std::vector<FaceState> face_states(cube.indices.size(), FaceState::Unknown);
    for (auto fi_seed : cgal_object.faces()) {
        FaceState &state = face_states[face_map[fi_seed]];
        bool       m     = side_type_map[fi_seed] == SideType::inside;
        switch (state) {
        case FaceState::Unknown:
            state = m ? FaceState::Marked : FaceState::Unmarked;
            break;
        case FaceState::Unmarked:
        case FaceState::UnmarkedSplit:
            state = m ? FaceState::MarkedSplit : FaceState::UnmarkedSplit;
            break;
        case FaceState::Marked:
        case FaceState::MarkedSplit:
            state = FaceState::MarkedSplit;
            break;
        default:
            assert(false);
        }
    }

    indexed_triangle_set its_extruded;
    its_extruded.indices.reserve(cgal_object.number_of_faces());
    its_extruded.vertices.reserve(cgal_object.number_of_vertices());
    // Mapping of its_extruded vertices (original and offsetted) to cgalcuble's vertices.
    std::vector<std::pair<int32_t, int32_t>> map_vertices(cgal_object.number_of_vertices(), std::pair<int32_t, int32_t>{-1, -1});

    Vec3f extrude_dir { 0, 0, 5.f };
    for (auto fi : cgal_object.faces()) {
        const int32_t   source_face_id = face_map[fi];
        const FaceState state          = face_states[source_face_id];
        assert(state == FaceState::Unmarked || state == FaceState::UnmarkedSplit || state == FaceState::UnmarkedEmitted ||
               state == FaceState::Marked || state == FaceState::MarkedSplit);
        if (state == FaceState::UnmarkedEmitted) {
            // Already emitted.
        } else if (state == FaceState::Unmarked || state == FaceState::UnmarkedSplit) {
            // Just copy the unsplit source face.
            const Vec3i source_vertices = cube.indices[source_face_id];
            Vec3i       target_vertices;
            for (int i = 0; i < 3; ++i) {
                target_vertices(i) = map_vertices[source_vertices(i)].first;
                if (target_vertices(i) == -1) {
                    map_vertices[source_vertices(i)].first = target_vertices(i) = int(its_extruded.vertices.size());
                    its_extruded.vertices.emplace_back(cube.vertices[source_vertices(i)]);
                }
            }
            its_extruded.indices.emplace_back(target_vertices);
            face_states[source_face_id] = FaceState::UnmarkedEmitted;
        } else {
            auto hi = cgal_object.halfedge(fi);
            auto hi_prev = cgal_object.prev(hi);
            auto hi_next = cgal_object.next(hi);
            const Vec3i source_vertices{ int((std::size_t)cgal_object.target(hi)), int((std::size_t)cgal_object.target(hi_next)), int((std::size_t)cgal_object.target(hi_prev)) };
            Vec3i       target_vertices;
            if (side_type_map[fi] == SideType::inside) {
                // Extrude the face. Neighbor edges separating extruded face from non-extruded face will be extruded.
                bool boundary_vertex[3] = { false, false, false };
                Vec3i target_vertices_extruded { -1, -1, -1 };
                for (int i = 0; i < 3; ++i) {
                    if (side_type_map[cgal_object.face(cgal_object.opposite(hi))] != SideType::inside)
                        // Edge separating extruded / non-extruded region.
                        boundary_vertex[i] = boundary_vertex[(i + 2) % 3] = true;
                    hi = cgal_object.next(hi);
                }
                for (int i = 0; i < 3; ++ i) {
                    target_vertices_extruded(i) = map_vertices[source_vertices(i)].second;
                    if (target_vertices_extruded(i) == -1) {
                        map_vertices[source_vertices(i)].second = target_vertices_extruded(i) = int(its_extruded.vertices.size());
                        const auto& p = cgal_object.point(cgal_object.target(hi));
                        its_extruded.vertices.emplace_back(Vec3f{ float(p.x()), float(p.y()), float(p.z()) } + extrude_dir);
                    }
                    if (boundary_vertex[i]) {
                        target_vertices(i) = map_vertices[source_vertices(i)].first;
                        if (target_vertices(i) == -1) {
                            map_vertices[source_vertices(i)].first = target_vertices(i) = int(its_extruded.vertices.size());
                            const auto& p = cgal_object.point(cgal_object.target(hi));
                            its_extruded.vertices.emplace_back(p.x(), p.y(), p.z());
                        }
                    }
                    hi = cgal_object.next(hi);
                }
                its_extruded.indices.emplace_back(target_vertices_extruded);
                // Add the sides.
                for (int i = 0; i < 3; ++ i) {
                    int j = (i + 1) % 3;
                    assert(target_vertices_extruded[i] != -1 && target_vertices_extruded[j] != -1);
                    if (boundary_vertex[i] && boundary_vertex[j]) {
                        assert(target_vertices[i] != -1 && target_vertices[j] != -1);
                        its_extruded.indices.emplace_back(Vec3i{ target_vertices[i], target_vertices[j], target_vertices_extruded[i] });
                        its_extruded.indices.emplace_back(Vec3i{ target_vertices_extruded[i], target_vertices[j], target_vertices_extruded[j] });
                    }
                }
            } else {
                // Copy the face.
                Vec3i target_vertices;
                for (int i = 0; i < 3; ++ i) {
                    target_vertices(i) = map_vertices[source_vertices(i)].first;
                    if (target_vertices(i) == -1) {
                        map_vertices[source_vertices(i)].first = target_vertices(i) = int(its_extruded.vertices.size());
                        const auto &p = cgal_object.point(cgal_object.target(hi));
                        its_extruded.vertices.emplace_back(p.x(), p.y(), p.z());
                    }
                    hi = cgal_object.next(hi);
                }
                its_extruded.indices.emplace_back(target_vertices);
            }
        }
    }

    its_write_obj(its_extruded, "c:\\data\\temp\\text-extruded.obj");

    indexed_triangle_set edges_its;
    std::vector<Vec3f>   edges_its_colors;
    for (auto ei : cgal_object.edges())
        if (cgal_object.is_valid(ei)) {
            const auto &p1 = cgal_object.point(cgal_object.vertex(ei, 0));
            const auto &p2 = cgal_object.point(cgal_object.vertex(ei, 1));
            bool constrained = get(ecm, ei);
            Vec3f color = constrained ? Vec3f{ 1.f, 0, 0 } : Vec3f{ 0, 1., 0 };
            edges_its.indices.emplace_back(Vec3i(edges_its.vertices.size(), edges_its.vertices.size() + 1, edges_its.vertices.size() + 2));
            edges_its.vertices.emplace_back(Vec3f(p1.x(), p1.y(), p1.z()));
            edges_its.vertices.emplace_back(Vec3f(p2.x(), p2.y(), p2.z()));
            edges_its.vertices.emplace_back(Vec3f(p2.x(), p2.y(), p2.z() + 0.001));
            edges_its_colors.emplace_back(color);
            edges_its_colors.emplace_back(color);
            edges_its_colors.emplace_back(color);
        }
    its_write_obj(edges_its, edges_its_colors, "c:\\data\\temp\\corefined-edges.obj");

//    MeshBoolean::cgal::minus(cube, cube2);

//    REQUIRE(!MeshBoolean::cgal::does_self_intersect(cube));
}