#include "calib.hpp"
#include "BoundingBox.hpp"
#include "Config.hpp"
#include "Model.hpp"
#include <cmath>

namespace Slic3r {

// Calculate the optimal Pressure Advance speed
float CalibPressureAdvance::find_optimal_PA_speed(const DynamicPrintConfig &config, double line_width, double layer_height,
                                                                                                    int filament_idx) {
    const double general_suggested_min_speed = 100.0;
    double filament_max_volumetric_speed = config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->get_at(0);
    Flow pattern_line = Flow(line_width, layer_height, config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0));
    auto pa_speed = std::min(std::max(general_suggested_min_speed,config.option<ConfigOptionFloat>("outer_wall_speed")->value), filament_max_volumetric_speed / pattern_line.mm3_per_mm());

    return std::floor(pa_speed);
}

std::string CalibPressureAdvance::move_to(Vec2d pt, GCodeWriter& writer, std::string comment)
{
    std::stringstream gcode;

    gcode << writer.retract();
    gcode << writer.travel_to_xy(pt, comment);
    gcode << writer.unretract();

    m_last_pos = Vec3d(pt.x(), pt.y(), 0);

    return gcode.str(); 
}

double CalibPressureAdvance::e_per_mm(
    double line_width,
    double layer_height,
    float nozzle_diameter,
    float filament_diameter,
    float print_flow_ratio
) const
{
    const Flow line_flow = Flow(line_width, layer_height, nozzle_diameter);
    const double filament_area = M_PI * std::pow(filament_diameter / 2, 2);

    return line_flow.mm3_per_mm() / filament_area * print_flow_ratio;
}

std::string CalibPressureAdvance::convert_number_to_string(double num) const
{
    auto sNumber = std::to_string(num);
    sNumber.erase(sNumber.find_last_not_of('0') + 1, std::string::npos);
    sNumber.erase(sNumber.find_last_not_of('.') + 1, std::string::npos);

    return sNumber;
}

std::string CalibPressureAdvance::draw_digit(
    double startx,
    double starty,
    char c,
    CalibPressureAdvance::DrawDigitMode mode,
    double line_width,
    double e_per_mm,
    GCodeWriter& writer
)
{
    const double len = m_digit_segment_len;
    const double gap = line_width / 2.0;

    const auto dE = e_per_mm * len;
    const auto two_dE = dE * 2;

    Vec2d p0, p1, p2, p3, p4, p5;
    Vec2d p0_5, p4_5;
    Vec2d gap_p0_toward_p3, gap_p2_toward_p3;
    Vec2d dot_direction;

    if (mode == CalibPressureAdvance::DrawDigitMode::Bottom_To_Top) {
        //  1-------2-------5
        //  |       |       |
        //  |       |       |
        //  0-------3-------4
        p0 = Vec2d(startx, starty);
        p0_5 = Vec2d(startx, starty + len / 2);
        p1 = Vec2d(startx, starty + len);
        p2 = Vec2d(startx + len, starty + len);
        p3 = Vec2d(startx + len, starty);
        p4 = Vec2d(startx + len * 2, starty);
        p4_5 = Vec2d(startx + len * 2, starty + len / 2);
        p5 = Vec2d(startx + len * 2, starty + len);

        gap_p0_toward_p3 = p0 + Vec2d(gap, 0);
        gap_p2_toward_p3 = p2 + Vec2d(0, gap);

        dot_direction = Vec2d(-len / 2, 0);
    } else {
        //  0-------1 
        //  |       |
        //  3-------2
        //  |       |
        //  4-------5
        p0 = Vec2d(startx, starty);
        p0_5 = Vec2d(startx + len / 2, starty);
        p1 = Vec2d(startx + len, starty);
        p2 = Vec2d(startx + len, starty - len);
        p3 = Vec2d(startx, starty - len);
        p4 = Vec2d(startx, starty - len * 2);
        p4_5 = Vec2d(startx + len / 2, starty - len * 2);
        p5 = Vec2d(startx + len, starty - len * 2);

        gap_p0_toward_p3 = p0 - Vec2d(0, gap);
        gap_p2_toward_p3 = p2 - Vec2d(gap, 0);

        dot_direction = Vec2d(0, len / 2);
    }

    std::stringstream gcode;

    switch (c) {
    case '0':
        gcode << move_to(p0, writer, "Glyph: 0");
        gcode << writer.extrude_to_xy(p1, dE);
        gcode << writer.extrude_to_xy(p5, two_dE);
        gcode << writer.extrude_to_xy(p4, dE);
        gcode << writer.extrude_to_xy(gap_p0_toward_p3, two_dE);
        break;
    case '1':
        gcode << move_to(p0_5, writer, "Glyph: 1");
        gcode << writer.extrude_to_xy(p4_5, two_dE);
        break;
    case '2':
        gcode << move_to(p0, writer, "Glyph: 2");
        gcode << writer.extrude_to_xy(p1, dE);
        gcode << writer.extrude_to_xy(p2, dE);
        gcode << writer.extrude_to_xy(p3, dE);
        gcode << writer.extrude_to_xy(p4, dE);
        gcode << writer.extrude_to_xy(p5, dE);
        break;
    case '3':
        gcode << move_to(p0, writer, "Glyph: 3");
        gcode << writer.extrude_to_xy(p1, dE);
        gcode << writer.extrude_to_xy(p5, two_dE);
        gcode << writer.extrude_to_xy(p4, dE);
        gcode << move_to(gap_p2_toward_p3, writer);
        gcode << writer.extrude_to_xy(p3, dE);
        break;
    case '4':
        gcode << move_to(p0, writer, "Glyph: 4");
        gcode << writer.extrude_to_xy(p3, dE);
        gcode << writer.extrude_to_xy(p2, dE);
        gcode << move_to(p1, writer);
        gcode << writer.extrude_to_xy(p5, two_dE);
        break;
    case '5':
        gcode << move_to(p1, writer, "Glyph: 5");
        gcode << writer.extrude_to_xy(p0, dE);
        gcode << writer.extrude_to_xy(p3, dE);
        gcode << writer.extrude_to_xy(p2, dE);
        gcode << writer.extrude_to_xy(p5, dE);
        gcode << writer.extrude_to_xy(p4, dE);
        break;
    case '6':
        gcode << move_to(p1, writer, "Glyph: 6");
        gcode << writer.extrude_to_xy(p0, dE);
        gcode << writer.extrude_to_xy(p4, two_dE);
        gcode << writer.extrude_to_xy(p5, dE);
        gcode << writer.extrude_to_xy(p2, dE);
        gcode << writer.extrude_to_xy(p3, dE);
        break;
    case '7':
        gcode << move_to(p0, writer, "Glyph: 7");
        gcode << writer.extrude_to_xy(p1, dE);
        gcode << writer.extrude_to_xy(p5, two_dE);
        break;
    case '8':
        gcode << move_to(p2, writer, "Glyph: 8");
        gcode << writer.extrude_to_xy(p3, dE);
        gcode << writer.extrude_to_xy(p4, dE);
        gcode << writer.extrude_to_xy(p5, dE);
        gcode << writer.extrude_to_xy(p1, two_dE);
        gcode << writer.extrude_to_xy(p0, dE);
        gcode << writer.extrude_to_xy(p3, dE);
        break;
    case '9':
        gcode << move_to(p5, writer, "Glyph: 9");
        gcode << writer.extrude_to_xy(p1, two_dE);
        gcode << writer.extrude_to_xy(p0, dE);
        gcode << writer.extrude_to_xy(p3, dE);
        gcode << writer.extrude_to_xy(p2, dE);
        break;
    case '.':
        gcode << move_to(p4_5, writer, "Glyph: .");
        gcode << writer.extrude_to_xy(p4_5 + dot_direction, dE);
        break;
    default:
        break;
    }

    return gcode.str();
}

std::string CalibPressureAdvance::draw_number(
    double startx,
    double starty,
    double value,
    CalibPressureAdvance::DrawDigitMode mode,
    double line_width,
    double e_per_mm,
    double speed,
    GCodeWriter& writer
)
{
    auto sNumber = convert_number_to_string(value);
    std::stringstream gcode;
    gcode << writer.set_speed(speed);

    for (std::string::size_type i = 0; i < sNumber.length(); ++i) {
        if (i > m_max_number_len) {
            break;
        }
        switch (mode) {
            case DrawDigitMode::Bottom_To_Top:
                gcode << draw_digit(
                    startx,
                    starty + i * number_spacing(),
                    sNumber[i],
                    mode,
                    line_width,
                    e_per_mm,
                    writer
                );
                break;
            default:
                gcode << draw_digit(
                    startx + i * number_spacing(),
                    starty,
                    sNumber[i],
                    mode,
                    line_width,
                    e_per_mm,
                    writer
                );
        }
    }

    return gcode.str();
}

std::string CalibPressureAdvanceLine::generate_test(double start_pa /*= 0*/, double step_pa /*= 0.002*/, int count /*= 10*/)
{
    BoundingBoxf bed_ext = get_extents(mp_gcodegen->config().printable_area.values);
    if (is_delta()) {
        CalibPressureAdvanceLine::delta_scale_bed_ext(bed_ext);
    }

    auto bed_sizes = mp_gcodegen->config().printable_area.values;
    const auto &w = bed_ext.size().x();
    const auto &h = bed_ext.size().y();
    count = std::min(count, int((h - 10) / m_space_y));

    m_length_long = 40 + std::min(w - 120.0, 0.0);

    auto startx = (w - m_length_short * 2 - m_length_long - 20) / 2;
    auto starty = (h - count * m_space_y) / 2;
    if (is_delta()) {
        CalibPressureAdvanceLine::delta_modify_start(startx, starty, count);
    }

    return print_pa_lines(startx, starty, start_pa, step_pa, count);
}

bool CalibPressureAdvanceLine::is_delta() const
{
    return mp_gcodegen->config().printable_area.values.size() > 4;
}

std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double start_y, double start_pa, double step_pa, int num)
{
    auto& writer = mp_gcodegen->writer();
    const auto& config = mp_gcodegen->config();

    const auto filament_diameter = config.filament_diameter.get_at(0);
    const auto print_flow_ratio = config.print_flow_ratio;

    const double e_per_mm = CalibPressureAdvance::e_per_mm(
        m_line_width,
        m_height_layer,
        m_nozzle_diameter,
        filament_diameter,
        print_flow_ratio
    );
    const double thin_e_per_mm = CalibPressureAdvance::e_per_mm(
        m_thin_line_width,
        m_height_layer,
        m_nozzle_diameter,
        filament_diameter,
        print_flow_ratio
    );
    const double number_e_per_mm = CalibPressureAdvance::e_per_mm(
        m_number_line_width,
        m_height_layer,
        m_nozzle_diameter,
        filament_diameter,
        print_flow_ratio
    );

    const double fast = CalibPressureAdvance::speed_adjust(m_fast_speed);
    const double slow = CalibPressureAdvance::speed_adjust(m_slow_speed);
    std::stringstream gcode;
    gcode << mp_gcodegen->writer().travel_to_z(m_height_layer);
    double y_pos = start_y;

    // prime line
    auto prime_x = start_x - 2;
    gcode << move_to(Vec2d(prime_x, y_pos + (num - 4) * m_space_y), writer);
    gcode << writer.set_speed(slow);
    gcode << writer.extrude_to_xy(Vec2d(prime_x, y_pos + 3 * m_space_y), e_per_mm * m_space_y * num * 1.1);

    for (int i = 0; i < num; ++i) {
        gcode << writer.set_pressure_advance(start_pa + i * step_pa);
        gcode << move_to(Vec2d(start_x, y_pos + i * m_space_y), writer);
        gcode << writer.set_speed(slow);
        gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short, y_pos + i * m_space_y), e_per_mm * m_length_short);
        gcode << writer.set_speed(fast);
        gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long, y_pos + i * m_space_y), e_per_mm * m_length_long);
        gcode << writer.set_speed(slow);
        gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long + m_length_short, y_pos + i * m_space_y), e_per_mm * m_length_short);
    }
    gcode << writer.set_pressure_advance(0.0);

    if (m_draw_numbers) {
        // draw indicator lines
        gcode << writer.set_speed(fast);
        gcode << move_to(Vec2d(start_x + m_length_short, y_pos + (num - 1) * m_space_y + 2), writer);
        gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short, y_pos + (num - 1) * m_space_y + 7), thin_e_per_mm * 7);
        gcode << move_to(Vec2d(start_x + m_length_short + m_length_long, y_pos + (num - 1) * m_space_y + 7), writer);
        gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long, y_pos + (num - 1) * m_space_y + 2), thin_e_per_mm * 7);

        for (int i = 0; i < num; i += 2) {
            gcode << draw_number(
                start_x + m_length_short + m_length_long + m_length_short + 3,
                y_pos + i * m_space_y + m_space_y / 2,
                start_pa + i * step_pa,
                m_draw_digit_mode,
                m_number_line_width,
                number_e_per_mm,
                3600,
                writer
            );
        }
    }
    return gcode.str();
}

void CalibPressureAdvanceLine::delta_modify_start(double& startx, double& starty, int count)
{
    startx = -startx;
    starty = -(count * m_space_y) / 2;
}

CalibPressureAdvancePattern::CalibPressureAdvancePattern(
    const Calib_Params& params,
    const DynamicPrintConfig& config,
    bool is_bbl_machine,
    Model& model,
    const Vec3d& origin
) :
    m_params(params)
{
    this->m_draw_digit_mode = DrawDigitMode::Bottom_To_Top;

    refresh_setup(config, is_bbl_machine, model, origin);
};

void CalibPressureAdvancePattern::generate_custom_gcodes(
    const DynamicPrintConfig& config,
    bool is_bbl_machine,
    Model& model,
    const Vec3d& origin
)
{
    std::stringstream gcode;
    gcode << "; start pressure advance pattern for layer\n";

    refresh_setup(config, is_bbl_machine, model, origin);

    gcode << move_to(Vec2d(m_starting_point.x(), m_starting_point.y()), m_writer, "Move to start XY position");
    gcode << m_writer.travel_to_z(height_first_layer(), "Move to start Z position");
    gcode << m_writer.set_pressure_advance(m_params.start);

    const DrawBoxOptArgs default_box_opt_args(*this);

    // create anchoring frame
    gcode << draw_box(
        m_starting_point.x(),
        m_starting_point.y(),
        print_size_x(),
        frame_size_y(),
        default_box_opt_args
    );

    // create tab for numbers
    DrawBoxOptArgs draw_box_opt_args = default_box_opt_args;
    draw_box_opt_args.is_filled = true;
    draw_box_opt_args.num_perimeters = wall_count();
    gcode << draw_box(
        m_starting_point.x(),
        m_starting_point.y() + frame_size_y() + line_spacing_first_layer(),
        glyph_tab_max_x() - m_starting_point.x(),
        max_numbering_height() + line_spacing_first_layer() + m_glyph_padding_vertical * 2,
        draw_box_opt_args
    );

    std::vector<CustomGCode::Item> gcode_items;
    const DrawLineOptArgs default_line_opt_args(*this);
    const int num_patterns = get_num_patterns(); // "cache" for use in loops

    // draw pressure advance pattern
    for (int i = 0; i < m_num_layers; ++i) {
        if (i > 0) {
            gcode << "; end pressure advance pattern for layer\n";
            CustomGCode::Item item;
            item.print_z = height_first_layer() + (i - 1) * height_layer();
            item.type = CustomGCode::Type::Custom;
            item.extra = gcode.str();
            gcode_items.push_back(item);

            gcode = std::stringstream(); // reset for next layer contents
            gcode << "; start pressure advance pattern for layer\n";
            
            const double layer_height = height_first_layer() + (i * height_layer());
            gcode << m_writer.travel_to_z(layer_height, "Move to layer height");
        }

        // line numbering
        if (i == 1) {
            gcode << m_writer.set_pressure_advance(m_params.start);

            double number_e_per_mm = e_per_mm(
                line_width(),
                height_layer(),
                m_config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0),
                m_config.option<ConfigOptionFloats>("filament_diameter")->get_at(0),
                m_config.option<ConfigOptionFloats>("filament_flow_ratio")->get_at(0)
            );

            // glyph on every other line
            for (int j = 0; j < num_patterns; j += 2) {
                gcode << draw_number(
                    glyph_start_x(j),
                    m_starting_point.y() + frame_size_y() + m_glyph_padding_vertical + line_width(),
                    m_params.start + (j * m_params.step),
                    m_draw_digit_mode,
                    line_width(),
                    number_e_per_mm,
                    speed_first_layer(),
                    m_writer
                );
            }
        }

        DrawLineOptArgs draw_line_opt_args = default_line_opt_args;

        double to_x = m_starting_point.x() + pattern_shift();
        double to_y = m_starting_point.y();
        double side_length = m_wall_side_length;

        // shrink first layer to fit inside frame
        if (i == 0) {
            double shrink =
                (
                    line_spacing_first_layer() * (wall_count() - 1) +
                    (line_width_first_layer() * (1 - m_encroachment))
                ) / std::sin(to_radians(m_corner_angle) / 2)
            ;
            side_length = m_wall_side_length - shrink;
            to_x += shrink * std::sin(to_radians(90) - to_radians(m_corner_angle) / 2);
            to_y +=
                line_spacing_first_layer() * (wall_count() - 1) +
                (line_width_first_layer() * (1 - m_encroachment))
            ;
        }

        double initial_x = to_x;
        double initial_y = to_y;

        gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to pattern start");

        for (int j = 0; j < num_patterns; ++j) {
            // increment pressure advance
            gcode << m_writer.set_pressure_advance(m_params.start + (j * m_params.step));

            for (int k = 0; k < wall_count(); ++k) {
                to_x += std::cos(to_radians(m_corner_angle) / 2) * side_length;
                to_y += std::sin(to_radians(m_corner_angle) / 2) * side_length;
                
                draw_line_opt_args = default_line_opt_args;
                draw_line_opt_args.height       = i == 0 ? height_first_layer()                 : height_layer();
                draw_line_opt_args.line_width   = line_width(); // don't use line_width_first_layer so results are consistent across all layers
                draw_line_opt_args.speed        = i == 0 ? speed_adjust(speed_first_layer())    : speed_adjust(speed_perimeter());
                draw_line_opt_args.comment = "Print pattern wall";
                gcode << draw_line(Vec2d(to_x, to_y), draw_line_opt_args);

                to_x -= std::cos(to_radians(m_corner_angle) / 2) * side_length;
                to_y += std::sin(to_radians(m_corner_angle) / 2) * side_length;

                gcode << draw_line(Vec2d(to_x, to_y), draw_line_opt_args);

                to_y = initial_y;
                if (k != wall_count() - 1) {
                    // perimeters not done yet. move to next perimeter
                    to_x += line_spacing_angle();
                    gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to start next pattern wall");
                } else if (j != num_patterns - 1) {
                    // patterns not done yet. move to next pattern
                    to_x += m_pattern_spacing + line_width();
                    gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to next pattern");
                } else if (i != m_num_layers - 1) {
                    // layers not done yet. move back to start
                    to_x = initial_x;
                    gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move back to start position");
                } else {
                    // everything done
                }
            }
        }
    }

    gcode << m_writer.set_pressure_advance(m_params.start);
    gcode << "; end pressure advance pattern for layer\n";

    CustomGCode::Item item;
    item.print_z = max_layer_z();
    item.type = CustomGCode::Type::Custom;
    item.extra = gcode.str();
    gcode_items.push_back(item);

    CustomGCode::Info info;
    info.mode = CustomGCode::Mode::SingleExtruder;
    info.gcodes = gcode_items;

    model.plates_custom_gcodes[model.curr_plate_index] = info;
}

void CalibPressureAdvancePattern::refresh_setup(
    const DynamicPrintConfig& config,
    bool is_bbl_machine,
    const Model& model,
    const Vec3d& origin
)
{
    m_config = config;
    m_config.apply(model.objects.front()->config.get(), true);
    m_config.apply(model.objects.front()->volumes.front()->config.get(), true);

    m_is_delta = (m_config.option<ConfigOptionPoints>("printable_area")->values.size() > 4);

    _refresh_starting_point(model);
    _refresh_writer(is_bbl_machine, model, origin);
}

void CalibPressureAdvancePattern::_refresh_starting_point(const Model& model)
{
    ModelObject* obj = model.objects.front();
    BoundingBoxf3 bbox =
        obj->instance_bounding_box(
            *obj->instances.front(),
            false
        )
    ;

    m_starting_point = Vec3d(bbox.min.x(), bbox.max.y(), 0);
    m_starting_point.y() += m_handle_spacing;

    if (m_is_delta) {
        m_starting_point.x() *= -1;
        m_starting_point.y() -= (frame_size_y() / 2);
    }
}

void CalibPressureAdvancePattern::_refresh_writer(
    bool is_bbl_machine,
    const Model& model,
    const Vec3d& origin
)
{
    PrintConfig print_config;
    print_config.apply(m_config, true);

    m_writer.apply_print_config(print_config);
    m_writer.set_xy_offset(origin(0), origin(1));
    m_writer.set_is_bbl_machine(is_bbl_machine);
    
    const unsigned int extruder_id = model.objects.front()->volumes.front()->extruder_id();
    m_writer.set_extruders({ extruder_id });
    m_writer.set_extruder(extruder_id);
}

std::string CalibPressureAdvancePattern::draw_line(
    Vec2d to_pt,
    DrawLineOptArgs opt_args
)
{
    const double e_per_mm = CalibPressureAdvance::e_per_mm(
        opt_args.line_width,
        opt_args.height,
        m_config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0),
        m_config.option<ConfigOptionFloats>("filament_diameter")->get_at(0),
        m_config.option<ConfigOptionFloats>("filament_flow_ratio")->get_at(0)
    );

    const double length = get_distance(Vec2d(m_last_pos.x(), m_last_pos.y()), to_pt);
    auto dE = e_per_mm * length;

    std::stringstream gcode;

    gcode << m_writer.set_speed(opt_args.speed);
    gcode << m_writer.extrude_to_xy(to_pt, dE, opt_args.comment);

    m_last_pos = Vec3d(to_pt.x(), to_pt.y(), 0);

    return gcode.str();
}

std::string CalibPressureAdvancePattern::draw_box(
    double min_x,
    double min_y,
    double size_x,
    double size_y,
    DrawBoxOptArgs opt_args
)
{
    std::stringstream gcode;

    double x = min_x;
    double y = min_y;
    const double max_x = min_x + size_x;
    const double max_y = min_y + size_y;

    const double spacing = opt_args.line_width - opt_args.height * (1 - M_PI / 4);

    // if number of perims exceeds size of box, reduce it to max
    const int max_perimeters =
        std::min(
            // this is the equivalent of number of perims for concentric fill
            std::floor(size_x * std::sin(to_radians(45))) / (spacing / std::sin(to_radians(45))),
            std::floor(size_y * std::sin(to_radians(45))) / (spacing / std::sin(to_radians(45)))
        )
    ;

    opt_args.num_perimeters = std::min(opt_args.num_perimeters, max_perimeters);

    gcode << move_to(Vec2d(min_x, min_y), m_writer, "Move to box start");

    DrawLineOptArgs line_opt_args(*this);
    line_opt_args.height = opt_args.height;
    line_opt_args.line_width = opt_args.line_width;
    line_opt_args.speed = opt_args.speed;

    for (int i = 0; i < opt_args.num_perimeters; ++i) {
        if (i != 0) { // after first perimeter, step inwards to start next perimeter
            x += spacing;
            y += spacing;
            gcode << move_to(Vec2d(x, y), m_writer, "Step inwards to print next perimeter");
        }

        y += size_y - i * spacing * 2;
        line_opt_args.comment = "Draw perimeter (up)";
        gcode << draw_line(Vec2d(x, y), line_opt_args);

        x += size_x - i * spacing * 2;
        line_opt_args.comment = "Draw perimeter (right)";
        gcode << draw_line(Vec2d(x, y), line_opt_args);

        y -= size_y - i * spacing * 2;
        line_opt_args.comment = "Draw perimeter (down)";
        gcode << draw_line(Vec2d(x, y), line_opt_args);

        x -= size_x - i * spacing * 2;
        line_opt_args.comment = "Draw perimeter (left)";
        gcode << draw_line(Vec2d(x, y), line_opt_args);
    }

    if (!opt_args.is_filled) {
        return gcode.str();
    }

    // create box infill
    const double spacing_45 = spacing / std::sin(to_radians(45));

    const double bound_modifier =
        (spacing * (opt_args.num_perimeters - 1)) +
        (opt_args.line_width * (1 - m_encroachment))
    ;
    const double x_min_bound = min_x + bound_modifier;
    const double x_max_bound = max_x - bound_modifier;
    const double y_min_bound = min_y + bound_modifier;
    const double y_max_bound = max_y - bound_modifier;
    const int x_count = std::floor((x_max_bound - x_min_bound) / spacing_45);
    const int y_count = std::floor((y_max_bound - y_min_bound) / spacing_45);

    double x_remainder = std::fmod((x_max_bound - x_min_bound), spacing_45);
    double y_remainder = std::fmod((y_max_bound - y_min_bound), spacing_45);

    x = x_min_bound;
    y = y_min_bound;

    gcode << move_to(Vec2d(x, y), m_writer, "Move to fill start");

    for (int i = 0; i < x_count + y_count + (x_remainder + y_remainder >= spacing_45 ? 1 : 0); ++i) { // this isn't the most robust way, but less expensive than finding line intersections
        if (i < std::min(x_count, y_count)) {
            if (i % 2 == 0) {
                x += spacing_45;
                y = y_min_bound;
                gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step right");

                y += x - x_min_bound;
                x = x_min_bound;
                line_opt_args.comment = "Fill: Print up/left";
                gcode << draw_line(Vec2d(x, y), line_opt_args);
            } else {
                y += spacing_45;
                x = x_min_bound;
                gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step up");

                x += y - y_min_bound;
                y = y_min_bound;
                line_opt_args.comment = "Fill: Print down/right";
                gcode << draw_line(Vec2d(x, y), line_opt_args);
            }
        } else if (i < std::max(x_count, y_count)) {
            if (x_count > y_count) {
                // box is wider than tall
                if (i % 2 == 0) {
                    x += spacing_45;
                    y = y_min_bound;
                    gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step right");

                    x -= y_max_bound - y_min_bound;
                    y = y_max_bound;
                    line_opt_args.comment = "Fill: Print up/left";
                    gcode << draw_line(Vec2d(x, y), line_opt_args);
                } else {
                    if (i == y_count) {
                        x += spacing_45 - y_remainder;
                        y_remainder = 0;
                    } else {
                        x += spacing_45;
                    }
                    y = y_max_bound;
                    gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step right");
                    
                    x += y_max_bound - y_min_bound;
                    y = y_min_bound;
                    line_opt_args.comment = "Fill: Print down/right";
                    gcode << draw_line(Vec2d(x, y), line_opt_args);
                }
            } else {
                // box is taller than wide
                if (i % 2 == 0) {
                    x = x_max_bound;
                    if (i == x_count) {
                        y += spacing_45 - x_remainder;
                        x_remainder = 0;
                    } else {
                        y += spacing_45;
                    }
                    gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step up");

                    x = x_min_bound;
                    y += x_max_bound - x_min_bound;
                    line_opt_args.comment = "Fill: Print up/left";
                    gcode << draw_line(Vec2d(x, y), line_opt_args);
                } else {
                    x = x_min_bound;
                    y += spacing_45;
                    gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step up");

                    x = x_max_bound;
                    y -= x_max_bound - x_min_bound;
                    line_opt_args.comment = "Fill: Print down/right";
                    gcode << draw_line(Vec2d(x, y), line_opt_args);
                }
            }
        } else {
            if (i % 2 == 0) {
                x = x_max_bound;
                if (i == x_count) {
                    y += spacing_45 - x_remainder;
                } else {
                    y += spacing_45;
                }
                gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step up");

                x -= y_max_bound - y;
                y = y_max_bound;
                line_opt_args.comment = "Fill: Print up/left";
                gcode << draw_line(Vec2d(x, y), line_opt_args);
            } else {
                if (i == y_count) {
                    x += spacing_45 - y_remainder;
                } else {
                    x += spacing_45;
                }
                y = y_max_bound;
                gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step right");

                y -= x_max_bound - x;
                x = x_max_bound;
                line_opt_args.comment = "Fill: Print down/right";
                gcode << draw_line(Vec2d(x, y), line_opt_args);
            }
        }
    }

    return gcode.str();
}

double CalibPressureAdvancePattern::get_distance(Vec2d from, Vec2d to) const
{
    return std::hypot((to.x() - from.x()), (to.y() - from.y()));
}

double CalibPressureAdvancePattern::object_size_x() const
{
    return get_num_patterns() * ((wall_count() - 1) * line_spacing_angle()) +
        (get_num_patterns() - 1) * (m_pattern_spacing + line_width()) +
        std::cos(to_radians(m_corner_angle) / 2) * m_wall_side_length +
        line_spacing_first_layer() * wall_count()
    ;
}

double CalibPressureAdvancePattern::object_size_y() const
{
    return 2 * (std::sin(to_radians(m_corner_angle) / 2) * m_wall_side_length) +
        max_numbering_height() +
        m_glyph_padding_vertical * 2 +
        line_width_first_layer();
}

double CalibPressureAdvancePattern::glyph_start_x(int pattern_i) const
{
    // note that pattern_i is zero-based!
    // align glyph's start with first perimeter of specified pattern
    double x =
        // starting offset
        m_starting_point.x() +
        pattern_shift() +

        // width of pattern extrusions
        pattern_i * (wall_count() - 1) * line_spacing_angle() + // center to center distance of extrusions
        pattern_i * line_width() + // endcaps. center to end on either side = 1 line width

        // space between each pattern
        pattern_i * m_pattern_spacing
    ;

    // align to middle of pattern walls
    x += wall_count() * line_spacing_angle() / 2;

    // shift so glyph is centered on pattern
    // m_digit_segment_len = half of X length of glyph
    x -= (glyph_length_x() / 2);

    return x;
}

double CalibPressureAdvancePattern::glyph_length_x() const
{
    // half of line_width sticks out on each side
    return line_width() + (2 * m_digit_segment_len);
}

double CalibPressureAdvancePattern::glyph_tab_max_x() const
{
    // only every other glyph is shown, starting with 1
    int num = get_num_patterns();
    int max_num =
        (num % 2 == 0)
        ? num - 1
        : num
    ;

    // padding at end should be same as padding at start
    double padding = glyph_start_x(0) - m_starting_point.x();
    
    return
        glyph_start_x(max_num - 1) + // glyph_start_x is zero-based
        (glyph_length_x() - line_width() / 2) +
        padding
    ;
}

double CalibPressureAdvancePattern::max_numbering_height() const
{
    std::string::size_type most_characters = 0;
    const int num_patterns = get_num_patterns();

    // note: only every other number is printed
    for (std::string::size_type i = 0; i < num_patterns; i += 2) {
        std::string sNumber = convert_number_to_string(m_params.start + (i * m_params.step));

        if (sNumber.length() > most_characters) {
            most_characters = sNumber.length();
        }
    }

    most_characters = std::min(most_characters, m_max_number_len);

    return (most_characters * m_digit_segment_len) + ((most_characters - 1) * m_digit_gap_len);
}

double CalibPressureAdvancePattern::pattern_shift() const
{
    return
        (wall_count() - 1) * line_spacing_first_layer() +
        line_width_first_layer() +
        m_glyph_padding_horizontal
    ;
}
} // namespace Slic3r
