#pragma once
#include <string>
#define calib_pressure_advance_dd

#include "GCode.hpp"
#include "GCodeWriter.hpp"
#include "PrintConfig.hpp"
#include "BoundingBox.hpp"

namespace Slic3r {

class GCode;
class Model;

enum class CalibMode : int {
    Calib_None = 0,
    Calib_PA_Line,
    Calib_PA_Pattern,
    Calib_PA_Tower,
    Calib_Flow_Rate,
    Calib_Temp_Tower,
    Calib_Vol_speed_Tower,
    Calib_VFA_Tower,
	Calib_Retraction_tower,
	Calib_Retraction_tower_speed,
	Calib_Limit_Speed,
	Calib_Limit_Acceleration,
	Calib_Speed_Tower,
	Calib_Acceleration_Tower,
	Calib_Arc2Lerance,
	Calib_Accel2Decel,
	Calib_X_Y_Jerk,
	Calib_Fan_Speed
};


enum class CalibState { Start = 0, Preset, Calibration, CoarseSave, FineCalibration, Save, Finish };

struct Calib_Params
{
    Calib_Params() : mode(CalibMode::Calib_None){};
    double    start, end, step;
    double    highStep;//creality add
    bool      print_numbers;
    CalibMode mode;
};

class X1CCalibInfos
{
public:
    struct X1CCalibInfo
    {
        int         tray_id;
        int         bed_temp;
        int         nozzle_temp;
        float       nozzle_diameter;
        std::string filament_id;
        std::string setting_id;
        float       max_volumetric_speed;
        float       flow_rate = 0.98f; // for flow ratio
    };

    std::vector<X1CCalibInfo> calib_datas;
};

class CaliPresetInfo
{
public:
    int         tray_id;
    float       nozzle_diameter;
    std::string filament_id;
    std::string setting_id;
    std::string name;

    CaliPresetInfo &operator=(const CaliPresetInfo &other)
    {
        this->tray_id         = other.tray_id;
        this->nozzle_diameter = other.nozzle_diameter;
        this->filament_id     = other.filament_id;
        this->setting_id      = other.setting_id;
        this->name            = other.name;
        return *this;
    }
};

struct PrinterCaliInfo
{
    std::string                 dev_id;
    bool                        cali_finished = true;
    float                       cache_flow_ratio;
    std::vector<CaliPresetInfo> selected_presets;
};

class PACalibResult
{
public:
    enum CalibResult {
        CALI_RESULT_SUCCESS = 0,
        CALI_RESULT_PROBLEM = 1,
        CALI_RESULT_FAILED  = 2,
    };
    int         tray_id;
    int         cali_idx = -1;
    float       nozzle_diameter;
    std::string filament_id;
    std::string setting_id;
    std::string name;
    float       k_value    = 0.0;
    float       n_coef     = 0.0;
    int         confidence = -1; // 0: success  1: uncertain  2: failed
};

struct PACalibIndexInfo
{
    int         tray_id;
    int         cali_idx;
    float       nozzle_diameter;
    std::string filament_id;
};

class FlowRatioCalibResult
{
public:
    int         tray_id;
    float       nozzle_diameter;
    std::string filament_id;
    std::string setting_id;
    float       flow_ratio;
    int         confidence; // 0: success  1: uncertain  2: failed
};

struct DrawBoxOptArgs
{
    DrawBoxOptArgs(int num_perimeters, double height, double line_width, double speed)
        : num_perimeters{num_perimeters}, height{height}, line_width{line_width}, speed{speed} {};
    DrawBoxOptArgs() = default;

    bool   is_filled{false};
    int    num_perimeters;
    double height;
    double line_width;
    double speed;
};
class CalibPressureAdvance
{
public:
    static float find_optimal_PA_speed(const DynamicPrintConfig &config, double line_width, double layer_height, int filament_idx = 0);

protected:
    CalibPressureAdvance()  = default;
    CalibPressureAdvance(const DynamicPrintConfig& config) : m_config(config){};
    CalibPressureAdvance(const FullPrintConfig &config) { m_config.apply(config); };
    ~CalibPressureAdvance() = default;

    enum class DrawDigitMode { Left_To_Right, Bottom_To_Top };

    void delta_scale_bed_ext(BoundingBoxf &bed_ext) const { bed_ext.scale(1.0f / 1.41421f); }

    std::string move_to(Vec2d pt, GCodeWriter &writer, std::string comment = std::string());
    double e_per_mm(double line_width, double layer_height, float nozzle_diameter, float filament_diameter, float print_flow_ratio) const;
    double speed_adjust(int speed) const { return speed * 60; };

    std::string convert_number_to_string(double num) const;
    double      number_spacing() const { return m_digit_segment_len + m_digit_gap_len; };
    std::string draw_digit(double                              startx,
                           double                              starty,
                           char                                c,
                           CalibPressureAdvance::DrawDigitMode mode,
                           double                              line_width,
                           double                              e_per_mm,
                           GCodeWriter                        &writer);
    std::string draw_number(double                              startx,
                            double                              starty,
                            double                              value,
                            CalibPressureAdvance::DrawDigitMode mode,
                            double                              line_width,
                            double                              e_per_mm,
                            double                              speed,
                            GCodeWriter                        &writer);

    std::string draw_line(
        GCodeWriter &writer, Vec2d to_pt, double line_width, double layer_height, double speed, const std::string &comment = std::string());
    std::string draw_box(GCodeWriter &writer, double min_x, double min_y, double size_x, double size_y, DrawBoxOptArgs opt_args);

    double to_radians(double degrees) const { return degrees * M_PI / 180; };
    double get_distance(Vec2d from, Vec2d to) const;

    Vec3d m_last_pos;
    DynamicPrintConfig m_config;

    const double m_encroachment{1. / 3.};
    DrawDigitMode                m_draw_digit_mode{DrawDigitMode::Left_To_Right};
    const double                 m_digit_segment_len{2};
    const double                 m_digit_gap_len{1};
    const std::string::size_type m_max_number_len{5};
};

class CalibPressureAdvanceLine : public CalibPressureAdvance
{
public:
    CalibPressureAdvanceLine(GCode *gcodegen) : CalibPressureAdvance(gcodegen->config()), mp_gcodegen(gcodegen),m_nozzle_diameter(gcodegen->config().nozzle_diameter.get_at(0)){};
    ~CalibPressureAdvanceLine(){};

    std::string generate_test(double start_pa = 0, double step_pa = 0.002, int count = 50);

    void set_speed(double fast = 100.0, double slow = 20.0)
    {
        m_slow_speed = slow;
        m_fast_speed = fast;
    }

    const double &line_width() { return m_line_width; };
    bool          is_delta() const;
    bool         &draw_numbers() { return m_draw_numbers; }

private:
    std::string print_pa_lines(double start_x, double start_y, double start_pa, double step_pa, int num);

    void delta_modify_start(double &startx, double &starty, int count);

    GCode *mp_gcodegen;

    double m_nozzle_diameter;
    double m_slow_speed, m_fast_speed;

    const double m_height_layer{0.2};
    const double m_line_width{0.6};
    const double m_thin_line_width{0.44};
    const double m_number_line_width{0.48};
    const double m_space_y{3.5};

    double m_length_short{20.0}, m_length_long{40.0};
    bool   m_draw_numbers{true};
};

struct SuggestedConfigCalibPAPattern
{
    const std::vector<std::pair<std::string, double>> float_pairs{{"initial_layer_print_height", 0.25},
                                                                  {"layer_height", 0.2},
                                                                  {"initial_layer_speed", 30}};

    const std::vector<std::pair<std::string, double>> nozzle_ratio_pairs{{"line_width", 112.5}, {"initial_layer_line_width", 140}};

    const std::vector<std::pair<std::string, int>> int_pairs{{"skirt_loops", 0}, {"wall_loops", 3}};

    const std::pair<std::string, BrimType> brim_pair{"brim_type", BrimType::btNoBrim};
};

class CalibPressureAdvancePattern : public CalibPressureAdvance
{
    friend struct DrawBoxOptArgs;

public:
    CalibPressureAdvancePattern(
        const Calib_Params &params, const DynamicPrintConfig &config, bool is_bbl_machine, Model &model, const Vec3d &origin);

    double handle_xy_size() const { return m_handle_xy_size; };
    double handle_spacing() const { return m_handle_spacing; };
    double print_size_x() const { return object_size_x() + pattern_shift(); };
    double print_size_y() const { return object_size_y(); };
    double max_layer_z() const { return height_first_layer() + ((m_num_layers - 1) * height_layer()); };

    void generate_custom_gcodes(const DynamicPrintConfig &config, bool is_bbl_machine, Model &model, const Vec3d &origin);

    void set_start_offset(const Vec3d &offset);
    Vec3d get_start_offset();

protected:
    double speed_first_layer() const { return m_config.option<ConfigOptionFloat>("initial_layer_speed")->value; };
    double speed_perimeter() const { return m_config.option<ConfigOptionFloat>("outer_wall_speed")->value; };
    double line_width_first_layer() const { return m_config.get_abs_value("initial_layer_line_width"); };
    double line_width() const { return m_config.get_abs_value("line_width"); };
    int    wall_count() const { return m_config.option<ConfigOptionInt>("wall_loops")->value; };

private:
    void refresh_setup(const DynamicPrintConfig &config, bool is_bbl_machine, const Model &model, const Vec3d &origin);
    void _refresh_starting_point(const Model &model);
    void _refresh_writer(bool is_bbl_machine, const Model &model, const Vec3d &origin);

    double    height_first_layer() const { return m_config.option<ConfigOptionFloat>("initial_layer_print_height")->value; };
    double    height_layer() const { return m_config.option<ConfigOptionFloat>("layer_height")->value; };
    const int get_num_patterns() const { return std::ceil((m_params.end - m_params.start) / m_params.step + 1); }

    /*
    from slic3r documentation: spacing = extrusion_width - layer_height * (1 - PI/4)
    "spacing" = center-to-center distance of adjacent extrusions, which partially overlap
        https://manual.slic3r.org/advanced/flow-math
        https://ellis3dp.com/Print-Tuning-Guide/articles/misconceptions.html#two-04mm-perimeters--08mm
    */
    double line_spacing() const { return line_width() - height_layer() * (1 - M_PI / 4); };
    double line_spacing_first_layer() const { return line_width_first_layer() - height_first_layer() * (1 - M_PI / 4); };
    double line_spacing_angle() const { return line_spacing() / std::sin(to_radians(m_corner_angle) / 2); };

    double object_size_x() const;
    double object_size_y() const;
    double frame_size_y() const { return std::sin(to_radians(double(m_corner_angle) / 2)) * m_wall_side_length * 2; };

    double glyph_start_x(int pattern_i = 0) const;
    double glyph_length_x() const;
    double glyph_tab_max_x() const;
    double max_numbering_height() const;

    double pattern_shift() const;

    const Calib_Params &m_params;

    GCodeWriter        m_writer;
    Vec3d              m_starting_point;
    bool               m_is_start_point_fixed = false;

    const double m_handle_xy_size{5};
    const double m_handle_spacing{2};
    const int    m_num_layers{4};

    const double m_wall_side_length{30.0};
    const int    m_corner_angle{90};
    const int    m_pattern_spacing{2};

    const double m_glyph_padding_horizontal{1};
    const double m_glyph_padding_vertical{1};
};
} // namespace Slic3r
