#ifndef slic3r_GCodeEditer_hpp_
#define slic3r_GCodeEditer_hpp_

#include "../libslic3r.h"
#include <map>
#include <string>
#include <cfloat>
#include <libslic3r/Point.hpp>
#include <libslic3r/Slicing.hpp>

namespace Slic3r {

class GCode;
class Layer;

struct CoolingLine
{
    enum Type {
        TYPE_SET_TOOL           = 1 << 0,
        TYPE_EXTRUDE_END        = 1 << 1,
        TYPE_OVERHANG_FAN_START = 1 << 2,
        TYPE_OVERHANG_FAN_END   = 1 << 3,
        TYPE_G0                 = 1 << 4,
        TYPE_G1                 = 1 << 5,
        TYPE_ADJUSTABLE         = 1 << 6,
        TYPE_EXTERNAL_PERIMETER = 1 << 7,
        // The line sets a feedrate.
        TYPE_HAS_F = 1 << 8,
        TYPE_WIPE  = 1 << 9,
        TYPE_G4    = 1 << 10,
        TYPE_G92   = 1 << 11,
        // QDS: add G2 G3 type
        TYPE_G2                     = 1 << 12,
        TYPE_G3                     = 1 << 13,
        TYPE_FORCE_RESUME_FAN       = 1 << 14,
        TYPE_SET_FAN_CHANGING_LAYER = 1 << 15,
        TYPE_OBJECT_START           = 1 << 16,
        TYPE_OBJECT_END             = 1 << 17,
    };

    CoolingLine(unsigned int type, size_t line_start, size_t line_end)
        : type(type), line_start(line_start), line_end(line_end), length(0.f), feedrate(0.f), origin_feedrate(0.f), time(0.f), time_max(0.f), slowdown(false)
    {}

    bool adjustable(bool slowdown_external_perimeters) const
    {
        return (this->type & TYPE_ADJUSTABLE) && (!(this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) && this->time < this->time_max;
    }

    bool adjustable() const { return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max; }

    size_t type;
    // Start of this line at the G-code snippet.
    size_t line_start;
    // End of this line at the G-code snippet.
    size_t line_end;
    // XY Euclidian length of this segment.
    float length;
    // Current feedrate, possibly adjusted.
    float feedrate;
    // Current duration of this segment.
    float time;
    // Maximum duration of this segment.
    float time_max;
    // If marked with the "slowdown" flag, the line has been slowed down.
    bool slowdown;
    // Current feedrate, possibly adjusted.
    float origin_feedrate = 0;
    float origin_time_max = 0;
    // Current duration of this segment.
    //float origin_time;
    bool  outwall_smooth_mark = false;
    int   object_id = -1;
    int   cooling_node_id = -1;
};

struct PerExtruderAdjustments
{
    // Calculate the total elapsed time per this extruder, adjusted for the slowdown.
    float elapsed_time_total() const
    {
        float time_total = 0.f;
        for (const CoolingLine &line : lines) time_total += line.time;
        return time_total;
    }
    // Calculate the total elapsed time when slowing down
    // to the minimum extrusion feed rate defined for the current material.
    float maximum_time_after_slowdown(bool slowdown_external_perimeters) const
    {
        float time_total = 0.f;
        for (const CoolingLine &line : lines)
            if (line.adjustable(slowdown_external_perimeters)) {
                if (line.time_max == FLT_MAX)
                    return FLT_MAX;
                else
                    time_total += line.time_max;
            } else
                time_total += line.time;
        return time_total;
    }
    // Calculate the adjustable part of the total time.
    float adjustable_time(bool slowdown_external_perimeters) const
    {
        float time_total = 0.f;
        for (const CoolingLine &line : lines)
            if (line.adjustable(slowdown_external_perimeters)) time_total += line.time;
        return time_total;
    }
    // Calculate the non-adjustable part of the total time.
    float non_adjustable_time(bool slowdown_external_perimeters) const
    {
        float time_total = 0.f;
        for (const CoolingLine &line : lines)
            if (!line.adjustable(slowdown_external_perimeters)) time_total += line.time;
        return time_total;
    }
    // Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material.
    // Used by both proportional and non-proportional slow down.
    float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters)
    {
        float time_total = 0.f;
        for (CoolingLine &line : lines) {
            if (line.adjustable(slowdown_external_perimeters)) {
                assert(line.time_max >= 0.f && line.time_max < FLT_MAX);
                line.slowdown = true;
                line.time     = line.time_max;
                line.feedrate = line.length / line.time;
            }
            time_total += line.time;
        }
        return time_total;
    }
    // Slow down each adjustable G-code line proportionally by a factor.
    // Used by the proportional slow down.
    float slow_down_proportional(float factor, bool slowdown_external_perimeters)
    {
        assert(factor >= 1.f);
        float time_total = 0.f;
        for (CoolingLine &line : lines) {
            if (line.adjustable(slowdown_external_perimeters)) {
                line.slowdown = true;
                line.time     = std::min(line.time_max, line.time * factor);
                line.feedrate = line.length / line.time;
            }
            time_total += line.time;
        }
        return time_total;
    }

    // Sort the lines, adjustable first, higher feedrate first.
    // Used by non-proportional slow down.
    void sort_lines_by_decreasing_feedrate()
    {
        std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) {
            bool adj1 = l1.adjustable();
            bool adj2 = l2.adjustable();
            return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1;
        });
        for (n_lines_adjustable = 0; n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable(); ++n_lines_adjustable)
            ;
        time_non_adjustable = 0.f;
        for (size_t i = n_lines_adjustable; i < lines.size(); ++i) time_non_adjustable += lines[i].time;
    }

    // Calculate the maximum time stretch when slowing down to min_feedrate.
    // Slowdown to min_feedrate shall be allowed for this extruder's material.
    // Used by non-proportional slow down.
    float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const
    {
        float time_stretch = 0.f;
        assert(this->slow_down_min_speed < min_feedrate + EPSILON);
        for (size_t i = 0; i < n_lines_adjustable; ++i) {
            const CoolingLine &line = lines[i];
            if (line.feedrate > min_feedrate) time_stretch += line.time * (line.feedrate / min_feedrate - 1.f);
        }
        return time_stretch;
    }

    // Slow down all adjustable lines down to min_feedrate.
    // Slowdown to min_feedrate shall be allowed for this extruder's material.
    // Used by non-proportional slow down.
    void slow_down_to_feedrate(float min_feedrate)
    {
        assert(this->slow_down_min_speed < min_feedrate + EPSILON);
        for (size_t i = 0; i < n_lines_adjustable; ++i) {
            CoolingLine &line = lines[i];
            if (line.feedrate > min_feedrate) {
                line.time *= std::max(1.f, line.feedrate / min_feedrate);
                line.feedrate = min_feedrate;
                line.slowdown = true;
            }
        }
    }

    //collect lines time
    float collection_line_times_of_extruder() {
        float times = 0;
        for (const CoolingLine &line: lines) {
            times += line.time;
        }
        return times;
    }

    // Extruder, for which the G-code will be adjusted.
    unsigned int extruder_id = 0;
    // Is the cooling slow down logic enabled for this extruder's material?
    bool cooling_slow_down_enabled = false;
    // Slow down the print down to slow_down_min_speed if the total layer time is below slow_down_layer_time.
    float slow_down_layer_time = 0.f;
    // Minimum print speed allowed for this extruder.
    float slow_down_min_speed = 0.f;

    //w14
    bool dont_slow_down_outer_wall = false;

    // Parsed lines.
    std::vector<CoolingLine> lines;
    // The following two values are set by sort_lines_by_decreasing_feedrate():
    // Number of adjustable lines, at the start of lines.
    size_t n_lines_adjustable = 0;
    // Non-adjustable time of lines starting with n_lines_adjustable.
    float time_non_adjustable = 0;
    // Current total time for this extruder.
    float time_total = 0;
    // Maximum time for this extruder, when the maximum slow down is applied.
    float time_maximum = 0;

    // Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable.
    size_t idx_line_begin = 0;
    size_t idx_line_end   = 0;
};
// A standalone G-code filter, to control cooling of the print.
// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
// and the print is modified to stretch over a minimum layer time.
//
// The simple it sounds, the actual implementation is significantly more complex.
// Namely, for a multi-extruder print, each material may require a different cooling logic.
// For example, some materials may not like to print too slowly, while with some materials 
// we may slow down significantly.
//
class GCodeEditor {
public:
    GCodeEditor(GCode &gcodegen);
    void        reset(const Vec3d &position);
    void set_current_extruder(unsigned int extruder_id)
    {
        m_current_extruder     = extruder_id;
        m_parse_gcode_extruder = extruder_id;
    }
    std::string process_layer(std::string &&                       gcode,
                              const size_t                         layer_id,
                              std::vector<PerExtruderAdjustments> &per_extruder_adjustments,
                              const std::vector<int> &             object_label,
                              const bool                           flush,
                              const bool                           spiral_vase);

    // float       calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
    // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
    // Returns the adjusted G-code.
    std::string write_layer_gcode(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);

private :
	GCodeEditor& operator=(const GCodeEditor&) = delete;
    std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &                       gcode,
                                                          std::vector<float> &                      current_pos,
                                                          const std::vector<int> &                  object_label,
                                                          bool                                      spiral_vase,
                                                          bool                                      join_z_smooth);

    // G-code snippet cached for the support layers preceding an object layer.
    std::string                 m_gcode;
    // Internal data.
    // QDS: X,Y,Z,E,F,I,J
    std::vector<char>           m_axis;
    std::vector<float>          m_current_pos;
    // Current known fan speed or -1 if not known yet.
    int                         m_fan_speed;
    int                         m_additional_fan_speed;
    // Cached from GCodeWriter.
    // Printing extruder IDs, zero based.
    std::vector<unsigned int>   m_extruder_ids;
    // Highest of m_extruder_ids plus 1.
    unsigned int                m_num_extruders { 0 };
    const std::string           m_toolchange_prefix;
    // Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
    // the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
    const PrintConfig          &m_config;
    unsigned int                m_current_extruder;
    unsigned int                m_parse_gcode_extruder;
    //QDS: current fan speed
    int                         m_current_fan_speed;
    //QDS:
    bool                        m_set_fan_changing_layer = false;
    bool                        m_set_addition_fan_changing_layer = false;
};

}

#endif
