#include "Smoothing.hpp"

namespace Slic3r {

void SmoothCalculator::build_node(std::vector<OutwallCollection> &     wall_collection,
                                  const std::vector<int> &             object_label,
                                  const std::vector<PerExtruderAdjustments>  &per_extruder_adjustments)
{
    if (per_extruder_adjustments.empty())
        return;
    // QDS: update outwall feedrate
    // update feedrate of outwall after initial cooling process
    // initial and arrange node collection seq
    for (size_t object_idx = 0; object_idx < object_label.size(); ++object_idx) {
        OutwallCollection object_level;
        object_level.object_id = object_label[object_idx];
        wall_collection.push_back(object_level);
    }

    for (size_t extruder_idx = 0; extruder_idx < per_extruder_adjustments.size(); ++extruder_idx) {
        const PerExtruderAdjustments &extruder_adjustments = per_extruder_adjustments[extruder_idx];
        for (size_t line_idx = 0; line_idx < extruder_adjustments.lines.size(); ++line_idx) {
            const CoolingLine &line = extruder_adjustments.lines[line_idx];
            if (line.outwall_smooth_mark) {
                // search node id
                if (wall_collection[line.object_id].cooling_nodes.count(line.cooling_node_id) == 0) {
                    CoolingNode node;
                    wall_collection[line.object_id].cooling_nodes.emplace(line.cooling_node_id, node);
                }

                CoolingNode &node = wall_collection[line.object_id].cooling_nodes[line.cooling_node_id];
                if (line.type & CoolingLine::TYPE_EXTERNAL_PERIMETER) {
                    node.outwall_line.emplace_back(line_idx, extruder_idx);
                    if (node.max_feedrate < line.feedrate) {
                        node.max_feedrate    = line.feedrate;
                        node.filter_feedrate = node.max_feedrate;
                    }
                }

            }
        }
    }
}


static void exclude_participate_in_speed_slowdown(std::vector<std::pair<int, int>> &   lines,
                                                  std::vector<PerExtruderAdjustments>  &per_extruder_adjustments,
                                                  CoolingNode &                        node)
{
    // QDS: add protect, feedrate will be 0 if the outwall is overhang. just apply not adjust flage
    bool apply_speed = node.max_feedrate > 0 && node.filter_feedrate > 0;
    if (apply_speed) node.rate = node.filter_feedrate / node.max_feedrate;

    for (std::pair<int, int> line_pos : lines) {
        CoolingLine &line = per_extruder_adjustments[line_pos.second].lines[line_pos.first];
        if (apply_speed && line.feedrate > node.filter_feedrate) {
            line.feedrate = node.filter_feedrate;
            line.slowdown = true;
        }

        // not adjust outwal line speed
        line.type = line.type & (~CoolingLine::TYPE_ADJUSTABLE);
        // update time cost
        if (line.feedrate == 0 || line.length == 0)
            line.time = 0;
        else
            line.time = line.length / line.feedrate;
    }
}

float SmoothCalculator::recaculate_layer_time(int layer_id, std::vector<PerExtruderAdjustments> &extruder_adjustments)
{
    // rewrite feedrate
    for (size_t obj_id = 0; obj_id < layers_wall_collection[layer_id].size(); ++obj_id) {
        for (size_t node_id = 0; node_id < layers_wall_collection[layer_id][obj_id].cooling_nodes.size(); ++node_id) {
            CoolingNode &node = layers_wall_collection[layer_id][obj_id].cooling_nodes[node_id];
            // set outwall speed
            exclude_participate_in_speed_slowdown(node.outwall_line, extruder_adjustments, node);
        }
    }

    float layer_time = 0;
    for (PerExtruderAdjustments extruder : extruder_adjustments) {
       layer_time += extruder.collection_line_times_of_extruder();
    }

    return layer_time;
};

void SmoothCalculator::init_object_node_range()
{
    for (size_t object_id = 0; object_id < objects_node_range.size(); ++object_id) {
        for (size_t layer_id = 1; layer_id < layers_wall_collection.size(); ++layer_id) {
            const OutwallCollection &each_object = layers_wall_collection[layer_id][object_id];
            auto                     it          = each_object.cooling_nodes.begin();
            while (it != each_object.cooling_nodes.end()) {
                if (objects_node_range[object_id].count(it->first) == 0) {
                    objects_node_range[object_id].emplace(it->first, std::pair<int, int>(layer_id, layer_id));
                } else {
                    objects_node_range[object_id][it->first].second = layer_id;
                }
                it++;
            }
        }
    }
}

void SmoothCalculator::smooth_layer_speed()
{
    init_object_node_range();

    for (size_t obj_id = 0; obj_id < objects_node_range.size(); ++obj_id) {
        auto it = objects_node_range[obj_id].begin();
        while (it != objects_node_range[obj_id].end()) {
            int step_count = 0;
            while (step_count < max_steps_count && speed_filter_continue(obj_id, it->first)) {
                step_count++;
                layer_speed_filter(obj_id, it->first);
            }
            it++;
        }
    }
}

void SmoothCalculator::layer_speed_filter(const int object_id, const int node_id)
{
    int start_pos = guassian_filter.size() / 2;
    // first layer don't need to be smoothed
    int layer_start = objects_node_range[object_id][node_id].first;
    int layer_end   = objects_node_range[object_id][node_id].second;

    // QDS: some layers may empty as the support has indenpendent layer
    for (int layer_id = layer_start; layer_id <= layer_end; ++layer_id) {
        if (layers_wall_collection[layer_id].empty()) continue;

        if (layers_wall_collection[layer_id][object_id].cooling_nodes.count(node_id) == 0) break;

        CoolingNode &node = layers_wall_collection[layer_id][object_id].cooling_nodes[node_id];

        if (node.outwall_line.empty()) continue;

        double conv_sum = 0;
        for (int filter_pos_idx = 0; filter_pos_idx < guassian_filter.size(); ++filter_pos_idx) {
            int remap_data_pos = layer_id - start_pos + filter_pos_idx;

            if (remap_data_pos < layer_start)
                remap_data_pos = layer_start;
            else if (remap_data_pos > layer_end)
                remap_data_pos = layer_end;

            // some node may not start at layer 1
            double remap_data = node.filter_feedrate;
            if (!layers_wall_collection[remap_data_pos][object_id].cooling_nodes[node_id].outwall_line.empty())
                remap_data = layers_wall_collection[remap_data_pos][object_id].cooling_nodes[node_id].filter_feedrate;

            conv_sum += guassian_filter[filter_pos_idx] * remap_data;
        }
        double filter_res = conv_sum / filter_sum;
        if (filter_res < node.filter_feedrate) node.filter_feedrate = filter_res;
    }
}

bool SmoothCalculator::speed_filter_continue(const int object_id, const int node_id)
{
    int layer_id  = objects_node_range[object_id][node_id].first;
    int layer_end = objects_node_range[object_id][node_id].second;

    // QDS: some layers may empty as the support has indenpendent layer
    for (; layer_id < layer_end; ++layer_id) {
        if (std::abs(layers_wall_collection[layer_id + 1][object_id].cooling_nodes[node_id].filter_feedrate -
                     layers_wall_collection[layer_id][object_id].cooling_nodes[node_id].filter_feedrate) > guassian_stop_threshold)
            return true;
    }
    return false;
}

void SmoothCalculator::filter_layer_time()
{
    int start_pos = guassian_filter.size() / 2;
    // first layer don't need to be smoothed
    for (int layer_id = 1; layer_id < layers_cooling_time.size(); ++layer_id) {
        if (layers_cooling_time[layer_id] > layer_time_smoothing_threshold) continue;

        double conv_sum = 0;
        for (int filter_pos_idx = 0; filter_pos_idx < guassian_filter.size(); ++filter_pos_idx) {
            int remap_data_pos = layer_id - start_pos + filter_pos_idx;

            if (remap_data_pos < 1)
                remap_data_pos = 1;
            else if (remap_data_pos > layers_cooling_time.size() - 1)
                remap_data_pos = layers_cooling_time.size() - 1;

            // if the layer time big enough, surface defact will disappear
            double data_temp = layers_cooling_time[remap_data_pos] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[remap_data_pos];

            conv_sum += guassian_filter[filter_pos_idx] * data_temp;
        }
        double filter_res = conv_sum / filter_sum;
        filter_res        = filter_res > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : filter_res;
        if (filter_res > layers_cooling_time[layer_id]) layers_cooling_time[layer_id] = filter_res;
    }
}

bool SmoothCalculator::layer_time_filter_continue()
{
    for (int layer_id = 1; layer_id < layers_cooling_time.size() - 1; ++layer_id) {
        double layer_time     = layers_cooling_time[layer_id] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[layer_id];
        double layer_time_cmp = layers_cooling_time[layer_id + 1] > layer_time_smoothing_threshold ? layer_time_smoothing_threshold : layers_cooling_time[layer_id + 1];

        if (std::abs(layer_time - layer_time_cmp) > guassian_layer_time_stop_threshold) return true;
    }
    return false;
}

void SmoothCalculator::smooth_layer_time()
{
    int step_count = 0;
    while (step_count < max_steps_count && layer_time_filter_continue()) {
        step_count++;
        filter_layer_time();
    }
}

} // namespace Slic3r