///|/ Copyright (c) Prusa Research 2024 Filip Sykala @Jony01
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef SLA_SUPPORTPOINTGENERATOR_HPP
#define SLA_SUPPORTPOINTGENERATOR_HPP

#include <vector>
#include <functional>

#include <boost/container/small_vector.hpp>

#include "libslic3r/Point.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/SLA/SupportPoint.hpp"
#include "libslic3r/SLA/SupportIslands/SampleConfig.hpp"

namespace Slic3r::sla {

std::vector<Vec2f> create_default_support_curve();
SampleConfig create_default_island_configuration(float head_diameter_in_mm);

/// <summary>
/// Configuration for automatic support placement
/// </summary>
struct SupportPointGeneratorConfig{
    /// <summary>
    /// 0 mean only one support point for each island
    /// lower than one mean less amount of support points
    /// 1 mean fine tuned sampling
    /// more than one mean bigger amout of support points
    /// </summary>
    float density_relative{1.f};

    /// <summary>
    /// Size range for support point interface (head)
    /// </summary>
    float head_diameter = 0.4f; // [in mm]

    // maximal distance to nearest support point(define radiuses per layer)
    // x axis .. mean distance on layer(XY)
    // y axis .. mean difference of height(Z)
    // Points of lines [in mm]
    std::vector<Vec2f> support_curve = create_default_support_curve();

    // Configuration for sampling island
    SampleConfig island_configuration = create_default_island_configuration(head_diameter);

    // maximal allowed distance to layer part for permanent(manual edited) support
    // helps to identify not wanted support points during automatic support generation.
    double max_allowed_distance_sq = scale_(1) * scale_(1); // 1mm
};

struct LayerPart; // forward decl.
using LayerParts = std::vector<LayerPart>;

using PartLink = LayerParts::const_iterator;
#ifdef NDEBUG
// In release mode, use the optimized container.
using PartLinks = boost::container::small_vector<PartLink, 4>;
#else
// In debug mode, use the standard vector, which is well handled by debugger visualizer.
using PartLinks = std::vector<PartLink>;
#endif

// Large one layer overhang that needs to be supported on the overhanging side
struct Peninsula{
    // shape of peninsula
    //ExPolygon shape;

    // Prev layer is extended by self support const and subtracted from current one.
    // This part of layer is supported as island
    ExPolygon unsuported_area;

    // Flag for unsuported_area line about source
    // Same size as Slic3r::to_lines(unsuported_area)
    // True .. peninsula outline(coast)
    // False .. connection to land(already supported by previous layer)
    std::vector<bool> is_outline;
};
using Peninsulas = std::vector<Peninsula>;

// Part on layer is defined by its shape 
struct LayerPart {
    // Pointer to expolygon stored in input
    const ExPolygon *shape;

    // To detect irelevant support poinst for part
    ExPolygons extend_shape;

    // rectangular bounding box of shape
    BoundingBox shape_extent;

    // uniformly sampled shape contour
    Points samples;

    // Parts from previous printed layer, which is connected to current part
    PartLinks prev_parts;
    PartLinks next_parts;

    // half island is supported as special case
    Peninsulas peninsulas;
};

/// <summary>
/// Extend support point with information from layer
/// </summary>
struct LayerSupportPoint: public SupportPoint
{
    // 2d coordinate on layer
    // use only when part is not nullptr
    Point position_on_layer; // [scaled_ unit]

    // index into curve to faster found radius for current layer
    size_t radius_curve_index = 0;
    coord_t current_radius = 0; // [in scaled mm]

    // information whether support point is active in current investigated layer
    // Flagged false when no part on layer in Radius 'r' around support point
    // Tool to support overlapped overhang area multiple times
    bool active_in_part = true;

    // When true support point position is not generated by algorithm
    bool is_permanent = false;
};
using LayerSupportPoints = std::vector<LayerSupportPoint>;

/// <summary>
/// One slice divided into 
/// </summary>
struct Layer
{
    // Absolute distance from Zero - copy value from heights<float>
    // It represents center of layer(range of layer is half layer size above and belowe)
    float print_z; // [in mm]

    // data for one expolygon
    LayerParts parts;
};
using Layers = std::vector<Layer>;

/// <summary>
/// Keep state of Support Point generation
/// Used for resampling with different configuration
/// </summary>
struct SupportPointGeneratorData
{
    // Input slices of mesh
    std::vector<ExPolygons> slices;

    // Keep information about layer and its height
    // and connection between layers for its part
    // NOTE: contain links into slices
    Layers layers;

    // Manualy edited supports by user should be permanent
    SupportPoints permanent_supports;
};

// call during generation of support points to check cancel event
using ThrowOnCancel = std::function<void(void)>;
// call to say progress of generation into gui in range from 0 to 100
using StatusFunction= std::function<void(int)>;

struct PrepareGeneratorDataConfig
{
    // Discretization of overhangs outline,
    // smaller will slow down the process but will be more precise
    double discretize_overhang_sample_in_mm = 2.; // [in mm]

    // Define minimal width of overhang to be considered as peninsula
    // (partialy island - sampled not on edge)
    coord_t peninsula_width = scale_(2.); // [in scaled mm]
};

/// <summary>
/// Prepare data for generate support points
/// Used for interactive resampling to store permanent data between configuration changes.,
/// Everything which could be prepared are stored into result.
/// Need to regenerate on mesh change(Should be connected with ObjectId) OR change of slicing heights
/// </summary>
/// <param name="slices">Countour cut from mesh</param>
/// <param name="heights">Heights of the slices - Same size as slices</param>
/// <param name="config">Preparation parameters</param>
/// <param name="throw_on_cancel">Call in meanwhile to check cancel event</param>
/// <param name="statusfn">Say progress of generation into gui</param>
/// <returns>Data prepared for generate support points</returns>
SupportPointGeneratorData prepare_generator_data(
    std::vector<ExPolygons> &&slices,
    const std::vector<float> &heights,
    const PrepareSupportConfig &config = {},
    ThrowOnCancel throw_on_cancel = []() {},
    StatusFunction statusfn = [](int) {}
);

/// <summary>
/// Generate support points on islands by configuration parameters
/// </summary>
/// <param name="data">Preprocessed data needed for sampling</param>
/// <param name="config">Define density of samples</param>
/// <param name="throw_on_cancel">Call in meanwhile to check cancel event</param>
/// <param name="statusfn">Progress of generation into gui</param>
/// <returns>Generated support points</returns>
LayerSupportPoints generate_support_points(
    const SupportPointGeneratorData &data,
    const SupportPointGeneratorConfig &config,
    ThrowOnCancel throw_on_cancel = []() {},
    StatusFunction statusfn = [](int) {}
);
} // namespace Slic3r::sla

// TODO: Not sure if it is neccessary & Should be in another file
namespace Slic3r{
class AABBMesh;
namespace sla {
/// <summary>
/// Move support points on surface of mesh
/// </summary>
/// <param name="points">Support points to move on surface</param>
/// <param name="mesh">Define surface for move points</param>
/// <param name="throw_on_cancel">Call in meanwhile to check cancel event</param>
/// <returns>Support points laying on mesh surface</returns>
SupportPoints move_on_mesh_surface(
    const LayerSupportPoints &points,
    const AABBMesh &mesh,
    double allowed_move,
    ThrowOnCancel throw_on_cancel = []() {}
);

}}

#endif // SLA_SUPPORTPOINTGENERATOR_HPP
