#pragma once

#include <nanogui/opengl.h>
#include <nanogui/screen.h>
#include <nanogui/window.h>
#include <nanogui/layout.h>
#include <nanogui/label.h>
#include <nanogui/button.h>
#include <nanogui/popupbutton.h>
#include <nanogui/progressbar.h>
#include <nanogui/messagedialog.h>
#include <nanogui/texture.h>
#include <nanogui/textarea.h>
#include <nanogui/textbox.h>
#include <nanogui/tabwidget.h>
#include <nanogui/shader.h>
#include <nanogui/renderpass.h>
#include <iostream>
#include <memory>
#include "grbl_machine.h"
#include "render.h"
#include "glm/glm.hpp"

#define STB_IMAGE_STATIC
#define STB_IMAGE_IMPLEMENTATION
#if defined(_MSC_VER)
#  pragma warning (disable: 4505) // don't warn about dead code in stb_image.h
#elif defined(__GNUC__)
#   pragma GCC diagnostic ignored "-Wunused-function"
#endif

#include <stb_image.h>
#include "grbl.h"
#include <gtest/gtest.h>
#include <regex>
#include "grbl_machine.h"
#include "string_utils.h"
#include "render.h"
#include "glm/gtx/quaternion.hpp"
#include "nanogui/nanogui.h"
#include "glm/gtc/matrix_inverse.hpp"
#include "heightmap.h"
#include <glm/ext/quaternion_float.hpp>
#include <glm/ext/quaternion_trigonometric.hpp>
#include <glm/vec3.hpp> // glm::vec3
#include <glm/ext/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
#include <glm/ext/scalar_constants.hpp> // glm::pi
#include <fstream>

class SenderApp : public nanogui::Screen {
public:
    SenderApp();

private:
    void add_z_probe_markup();
    void add_work_parameters_markup();
    void add_status_markup();
    void add_jogging_markup();
    void add_dro_markup();
    void add_program_extents();
    void add_heightmap_markup();
    void add_pins_markup();

    static void save_heightmap(const grbl::heightmap &grid, std::string path);
    grbl::heightmap load_heightmap(std::string path);

    void update_dro();
    void update_grid();
    void refresh_offset();

    void fill_heightmap_controls_from_grid(const grbl::heightmap &grid) const;
    void fill_heightmap_from_model() const;
    void fill_in_parameters();
    void fill_in_settings();
    void init_program_geometry();
    void set_program_extents() const;

    bool resize_event(const nanogui::Vector2i &size) override;
    bool keyboard_event(int key, int scancode, int action, int modifiers) override;
    bool mouse_motion_event(const nanogui::Vector2i &p,
                            const nanogui::Vector2i &rel,
                            int button,
                            int modifiers) override;
    bool scroll_event(const nanogui::Vector2i &p, const nanogui::Vector2f &rel) override;
    void draw(NVGcontext *ctx) override;
    void draw_contents() override;

    grbl::machine cnc{};

    nanogui::TextBox *txt_min_z = nullptr, *txt_feed = nullptr;
    nanogui::TextBox *txt_heightmap_from_x = nullptr, *txt_heightmap_from_y = nullptr;
    nanogui::TextBox *txt_heightmap_to_x = nullptr, *txt_heightmap_to_y = nullptr;
    nanogui::TextBox *txt_heightmap_res = nullptr;
    nanogui::TextBox *txt_clearance_height = nullptr, *txt_start_probing_at = nullptr, *txt_max_negative_z = nullptr, *txt_final_z_height = nullptr;
    nanogui::Window *window = nullptr;
    nanogui::TextBox *txt_status = nullptr;
    nanogui::TextBox *txt_message = nullptr;
    nanogui::Color color_red = nanogui::Color(255, 0, 0, 255);
    nanogui::Color color_green = nanogui::Color(0, 255, 0, 255);
    nanogui::Button *btn_load_program = nullptr, *btn_check_program = nullptr, *btn_run_program = nullptr;

    nanogui::TabWidget *tab_widget = nullptr;

    nanogui::VScrollPanel *info_vscroll = nullptr;
    nanogui::Widget *info_layer = nullptr;

    nanogui::Widget *heightmap_layer = nullptr;


    nanogui::VScrollPanel *settings_vscroll = nullptr;
    nanogui::Widget *settings_layer = nullptr;

    nanogui::VScrollPanel *parameters_vscroll = nullptr;
    nanogui::Widget *parameters_layer = nullptr;

    nanogui::TextBox *mpos_x_text = nullptr, *mpos_y_text = nullptr, *mpos_z_text = nullptr;
    nanogui::ComboBox *cbo_work_offset = nullptr, *cbo_tool = nullptr, *cbo_jog_feed_rates = nullptr, *cbo_jog_distance = nullptr, *cbo_exaggeration = nullptr;

    nanogui::ToolButton *btn_pin_door = nullptr, *btn_pin_hold = nullptr, *btn_pin_reset = nullptr, *btn_pin_cycle_start = nullptr;
    nanogui::ToolButton *btn_pin_limit_x = nullptr, *btn_pin_limit_y = nullptr, *btn_pin_limit_z = nullptr, *btn_pin_probe = nullptr;

    nanogui::Button *btn_keyboard_jog = nullptr;
    nanogui::Button *btn_run_autoleveled = nullptr;
    std::stringstream dro_ss;
    grbl::heightmap heightmap_grid;

    nanogui::TextBox *extents_min_x = nullptr, *extents_max_x = nullptr;
    nanogui::TextBox *extents_min_y = nullptr, *extents_max_y = nullptr;
    nanogui::TextBox *extents_min_z = nullptr, *extents_max_z = nullptr;

    nanogui::ProgressBar *m_progress = nullptr;
    nanogui::ref<nanogui::RenderPass> m_render_pass;

    using ImageHolder = std::unique_ptr<uint8_t[], void (*)(void *)>;
    std::vector<std::pair<nanogui::ref<nanogui::Texture>, ImageHolder>> m_images;

    std::vector<std::string> jog_distances = {"0.01", "0.1", "1", "10"};
    std::vector<std::string> jog_feed_rates = {"5", "100", "500", "1000"};
    std::vector<std::string> exaggeration_factors = {"0", "1", "5", "10", "20", "50", "100"};

    int last_alarm = 0;

    grbl::program pgm{};
    grbl::realtime_status_report last_report;
    grbl::jog_state jog;
    grbl::program_renderer renderer{};
    glm::vec3 cam_target = glm::vec3(0);
    glm::vec3 cam_pan = glm::vec3(0);
    float cam_zoom = 0;
    glm::quat cam_src_rotation = glm::quat(1.0, 0.0, 0.0, 0.0); // identity quaternion
};