#pragma once

#include "grbl_communication.h"
#include "grbl.h"

namespace grbl {

enum class machine_status {
    idle,
    run,
    hold,
    jog,
    alarm,
    door,
    check,
    home,
    sleep,
    tool,
    unknown
};

machine_status status_from_string(const std::string& status);
std::string status_to_string(const machine_status& status);
std::string alarm_to_string(int alarm);

struct realtime_status_report {
    machine_status status;
    std::string sub_status;
    float work_pos[3] = {0};
    float machine_pos[3] = {0};
    size_t buffers_free = 0;
    size_t rx_chars_free = 0;
    size_t line_number = 0;
    float feed_rate = 0;
    float programmed_rpm = 0;
    float actual_rpm = 0;
    std::string signals;
    float axis_offsets[3] = {0};
    std::string coordinate_system;
    std::string overrides;
    std::string accessory_status;
    bool mpg = false;
    bool homing_complete = false;
    std::string scaled_axis;
    bool tool_length_reference_offset_set = false;
    std::string firmware;
};

realtime_status_report parse_status_report(std::string line, grbl::realtime_status_report& result);

enum class grbl_machine_state {
    init,
    run_program,
    idle,
};

struct jog_state {
    bool left_pressed = false;
    bool right_pressed = false;
    bool up_pressed = false;
    bool down_pressed = false;
    bool z_up_pressed = false;
    bool z_down_pressed = false;
    bool speed_fast_pressed = false;
    bool speed_slow_pressed = false;

    [[nodiscard]] bool no_jogging() const;
};

static bool operator==(const jog_state& a, const jog_state& b) {
    if (a.left_pressed != b.left_pressed ||
        a.right_pressed != b.right_pressed ||
        a.up_pressed != b.up_pressed ||
        a.down_pressed != b.down_pressed ||
        a.z_up_pressed != b.z_up_pressed ||
        a.z_down_pressed != b.z_down_pressed ||
        a.speed_fast_pressed != b.speed_fast_pressed ||
        a.speed_slow_pressed != b.speed_slow_pressed) {
        return false;
    }
    return true;
}

static bool operator!=(const jog_state& a, const jog_state& b) {
    return !(a == b);
}

struct machine_listener {
    virtual void on_connected() = 0;
    virtual void on_disconnected() = 0;
    virtual void on_realtime_status_report(realtime_status_report) = 0;
    virtual void on_banner(std::string line) = 0;
    virtual void on_message(std::string message) = 0;
    virtual void on_alarm(int alarm) = 0;
};

struct machine : public transport_callbacks {
    machine();
    ~machine();

    void set_listener(grbl::machine_listener *listener);
    void connect();
    void run_program(const grbl::program& pgm);
    void request_jog(jog_state jog) const;
    void cancel_jog() const;

    realtime_status_report get_status() const { return last_report; };

    void request_unlock();
    void request_home();
    void request_reset();

    void request_cycle_start();
    void request_feed_hold();

protected:
    void on_connected(transport *transport) override;
    void on_disconnected(transport *transport) override;
    void on_line_received(std::string line, transport *transport) override;

    realtime_status_report last_report{};
    machine_listener* listener = nullptr;
    transport *pipe = nullptr;
    grbl_machine_state state = grbl_machine_state::init;
    program running_program;
    size_t executed_instructions = 0;
    void continue_program();
};


}

