#pragma once

#include "imgui/imgui.h"
#include "imgui/imgui_stdlib.h"
#include "utils/format.h"
#include <vector>
#include <string>
#include <cstdint>
#include "core/style.h"
#include "dll_export.h"
#include "common/widgets/stepped_slider.h"

/*
Super Basic & Crappy remote ImGui Scheme,
but it works?

Need to improve later. Especially clean it up!
*/

namespace RImGui
{
    // Classes/Structs etc
    enum UiElemType
    {
        UI_ELEMENT_NONE,
        UI_ELEMENT_TEXT,
        UI_ELEMENT_BUTTON,
        UI_ELEMENT_RADIOBUTTON,
        UI_ELEMENT_STEPPEDSLIDERINT,
        UI_ELEMENT_STEPPEDSLIDERFLOAT,
        UI_ELEMENT_CHECKBOX,
        UI_ELEMENT_COMBO,
        UI_ELEMENT_INPUTDOUBLE,
        UI_ELEMENT_SEPARATOR,
        UI_ELEMENT_INPUTTEXT,
        UI_ELEMENT_ISITEMDEACTIVATEDAFTEREDIT,
        UI_ELEMENT_SAMELINE,

        UI_ELEMENT_BEGINDISABLED,
        UI_ELEMENT_ENDDISABLED,
    };

    struct UiElem
    {
        UiElemType t = UI_ELEMENT_NONE;
        int id = 0;
        float size_x = 0, size_y = 0;
        /////////////////////
        std::string sv = "";
        int iv = 0;
        bool bv = false;
        float fv = 0;
        //////////////
        double min = 0, max = 0;
        std::string sv2 = "";
        /////////////////////
        bool clicked = false;

        bool operator==(UiElem a)
        {
            return t == a.t &&
                   id == a.id &&
                   size_x == a.size_x &&
                   size_y == a.size_y &&
                   sv == a.sv &&
                   iv == a.iv &&
                   bv == a.bv &&
                   fv == a.fv &&
                   min == a.min &&
                   max == a.max &&
                   sv2 == a.sv2 &&
                   clicked == a.clicked;
        }

        ///////////////////

        int encode(uint8_t *buffer)
        {
            int pos = 0;

            buffer[pos++] = t;

            buffer[pos++] = id >> 8;
            buffer[pos++] = id & 0xFF;

            memcpy(buffer + pos, &size_x, sizeof(size_x));
            pos += 4;
            memcpy(buffer + pos, &size_y, sizeof(size_y));
            pos += 4;

            buffer[pos++] = (sv.size() >> 8) & 0xFF;
            buffer[pos++] = (sv.size() >> 0) & 0xFF;
            for (int i = 0; i < (int)sv.size(); i++)
                buffer[pos++] = sv[i];

            *((int *)&buffer[pos]) = iv;
            pos += 4;

            buffer[pos++] = bv;

            memcpy(buffer + pos, &fv, sizeof(fv));
            pos += 4;

            memcpy(buffer + pos, &min, sizeof(min));
            pos += 8;
            memcpy(buffer + pos, &max, sizeof(max));
            pos += 8;

            buffer[pos++] = (sv2.size() >> 8) & 0xFF;
            buffer[pos++] = (sv2.size() >> 0) & 0xFF;
            for (int i = 0; i < (int)sv2.size(); i++)
                buffer[pos++] = sv2[i];

            buffer[pos++] = clicked;

            return pos;
        }

        int decode(uint8_t *buffer)
        {
            int pos = 0;

            t = (UiElemType)buffer[pos++];

            id = buffer[pos + 0] << 8 | buffer[pos + 1];
            pos += 2;

            memcpy(&size_x, buffer + pos, sizeof(size_x));
            pos += 4;
            memcpy(&size_y, buffer + pos, sizeof(size_y));
            pos += 4;

            int svsize = buffer[pos + 0] << 8 | buffer[pos + 1];
            pos += 2;
            sv.resize(svsize);
            for (int i = 0; i < (int)sv.size(); i++)
                sv[i] = buffer[pos++];

            memcpy(&iv, buffer + pos, sizeof(iv));
            pos += 4;

            bv = buffer[pos++];

            memcpy(&fv, buffer + pos, sizeof(fv));
            pos += 4;

            memcpy(&min, buffer + pos, sizeof(min));
            pos += 8;
            memcpy(&max, buffer + pos, sizeof(max));
            pos += 8;

            int sv2size = buffer[pos + 0] << 8 | buffer[pos + 1];
            pos += 2;
            sv2.resize(sv2size);
            for (int i = 0; i < (int)sv2.size(); i++)
                sv2[i] = buffer[pos++];

            clicked = buffer[pos++];

            return pos;
        }
    };

    inline int encode_vec(uint8_t *buffer, std::vector<UiElem> elems)
    {
        if (elems.size() > 0)
        {
            buffer[0] = (elems.size() >> 8) & 0xFF;
            buffer[1] = (elems.size() >> 0) & 0xFF;
            int pos = 2;
            for (int i = 0; i < (int)elems.size(); i++)
                pos += elems[i].encode(buffer + pos);
            return pos;
        }
        else
        {
            return 0;
        }
    }

    inline std::vector<UiElem> decode_vec(uint8_t *buffer, int len)
    {
        if (len > 2)
        {
            std::vector<UiElem> elems;
            int nel = buffer[0] << 8 | buffer[1];
            elems.resize(nel);
            int pos = 2;
            for (int i = 0; i < (int)elems.size() && pos <= len; i++)
                pos += elems[i].decode(buffer + pos);
            return elems;
        }
        return std::vector<UiElem>();
    }

    struct RImGui
    {
        int current_id = 0;
        std::vector<UiElem> ui_elements;
        std::vector<UiElem> ui_elems_fbk;
        std::vector<UiElem> ui_elems_last;
    };

    // Global variables
    SATDUMP_DLL extern bool is_local;
    SATDUMP_DLL extern RImGui *current_instance;

    // Handlings functions for the "server" rendering
    inline std::vector<UiElem> end_frame(bool force = false)
    {
        std::vector<UiElem> elems; //= ui_elements;

        if (current_instance->ui_elements.size() == current_instance->ui_elems_last.size() && !force)
        {
            bool has_one_not_equal = false;
            for (int i = 0; i < (int)current_instance->ui_elements.size(); i++)
                if (!(current_instance->ui_elems_last[i] == current_instance->ui_elements[i]))
                    has_one_not_equal = true;
            if (has_one_not_equal)
                elems = current_instance->ui_elements;
        }
        else
        {
            elems = current_instance->ui_elements;
        }

        current_instance->ui_elems_last = current_instance->ui_elements;

        current_instance->current_id = 0;
        current_instance->ui_elements.clear();
        current_instance->ui_elems_fbk.clear();

        return elems;
    }

    inline void set_feedback(std::vector<UiElem> fbk)
    {
        current_instance->ui_elems_fbk = fbk;
    }

    // Drawing function "client"
    inline std::vector<UiElem> draw(RImGui *inst, std::vector<UiElem> elems)
    {
        if (elems.size() > 0)
            inst->ui_elems_last = elems;
        else
            elems = inst->ui_elems_last;
        std::vector<UiElem> elems_o = elems;
        for (auto &el : elems)
        {
            if (el.t == UI_ELEMENT_TEXT)
                ImGui::TextUnformatted(el.sv.c_str());
            else if (el.t == UI_ELEMENT_BUTTON)
                el.clicked = ImGui::Button(el.sv.c_str(), {el.size_x, el.size_y});
            else if (el.t == UI_ELEMENT_RADIOBUTTON)
                el.clicked = ImGui::RadioButton(el.sv.c_str(), el.bv);
            else if (el.t == UI_ELEMENT_STEPPEDSLIDERINT)
                el.clicked = widgets::SteppedSliderInt(el.sv.c_str(), &el.iv, el.min, el.max, (int)(el.size_x == 0.0f ? 1.0f : el.size_x),
                    el.sv2.c_str(), (ImGuiSliderFlags)el.size_y);
            else if (el.t == UI_ELEMENT_STEPPEDSLIDERFLOAT)
                el.clicked = widgets::SteppedSliderFloat(el.sv.c_str(), &el.fv, el.min, el.max, (el.size_x == 0.0f ? 1.0f : el.size_x),
                    el.sv2.c_str(), (ImGuiSliderFlags)el.size_y);
            else if (el.t == UI_ELEMENT_CHECKBOX)
                el.clicked = ImGui::Checkbox(el.sv.c_str(), &el.bv);
            else if (el.t == UI_ELEMENT_COMBO)
                el.clicked = ImGui::Combo(el.sv.c_str(), &el.iv, el.sv2.c_str());
            else if (el.t == UI_ELEMENT_INPUTDOUBLE)
                el.clicked = ImGui::InputDouble(el.sv.c_str(), &el.min, el.fv, el.max, el.sv2.c_str());
            else if (el.t == UI_ELEMENT_SEPARATOR)
                ImGui::Separator();
            else if (el.t == UI_ELEMENT_INPUTTEXT)
                el.clicked = ImGui::InputText(el.sv.c_str(), &el.sv2, el.iv);
            else if (el.t == UI_ELEMENT_ISITEMDEACTIVATEDAFTEREDIT)
                el.clicked = ImGui::IsItemDeactivatedAfterEdit();
            else if (el.t == UI_ELEMENT_SAMELINE)
                ImGui::SameLine();

            else if (el.t == UI_ELEMENT_BEGINDISABLED)
                style::beginDisabled();
            else if (el.t == UI_ELEMENT_ENDDISABLED)
                style::endDisabled();
        }
        std::vector<UiElem> elems_f;
        for (int i = 0; i < (int)elems_o.size(); i++)
            // if (elems[i].clicked != elems_o[i].clicked ||
            //     elems[i].iv != elems_o[i].iv)
            if (!(elems[i] == elems_o[i]))
                elems_f.push_back(elems[i]);
        return elems_f;
    }

    // Rendering functions, identical to ImGui's
    template <typename... T>
    void Text(const char *fmt, T &&...args)
    {
        if (is_local)
            ImGui::Text(fmt, args...);
        else
            current_instance->ui_elements.push_back({UI_ELEMENT_TEXT,
                                                     current_instance->current_id++,
                                                     0, 0, satdump::svformat(fmt, args...)});
    }

    inline bool Button(const char *label, ImVec2 size = ImVec2(0, 0))
    {
        if (is_local)
        {
            return ImGui::Button(label, size);
        }
        else
        {
            current_instance->ui_elements.push_back({UI_ELEMENT_BUTTON,
                                                     current_instance->current_id++,
                                                     size.x, size.y, std::string(label)});
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_BUTTON)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id - 1)
                            return el.clicked;
            return false;
        }
    }

    inline bool RadioButton(const char *label, bool active)
    {
        if (is_local)
        {
            return ImGui::RadioButton(label, active);
        }
        else
        {
            current_instance->ui_elements.push_back({UI_ELEMENT_RADIOBUTTON,
                                                     current_instance->current_id++,
                                                     0, 0, std::string(label), 0, active});
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_RADIOBUTTON)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id - 1)
                            return el.clicked;
            return false;
        }
    }

    inline bool SteppedSliderInt(const char* label, int* v, int v_min, int v_max, int v_rate = 1, const char* format = "%d", ImGuiSliderFlags flags = 0)
    {
        if (is_local)
            return widgets::SteppedSliderInt(label, v, v_min, v_max, v_rate, format, flags);
        else
        {
            for (auto& el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_STEPPEDSLIDERINT)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id)
                            *v = el.iv;
            current_instance->ui_elements.push_back({ UI_ELEMENT_STEPPEDSLIDERINT,
                                                     current_instance->current_id++,
                                                     (float)v_rate, (float)flags, std::string(label), *v, false, 0,
                                                     (double)v_min, (double)v_max, format});
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_STEPPEDSLIDERINT)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id - 1)
                            return el.clicked;
            return false;
        }
    }

    inline bool SteppedSliderFloat(const char *label, float *v, float v_min, float v_max, float v_rate = 1.0f, const char *format = "%.3f", ImGuiSliderFlags flags = 0)
    {
        if (is_local)
        {
            return widgets::SteppedSliderFloat(label, v, v_min, v_max, v_rate, format, flags);
        }
        else
        {
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_STEPPEDSLIDERFLOAT)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id)
                            *v = el.fv;
            current_instance->ui_elements.push_back({UI_ELEMENT_STEPPEDSLIDERFLOAT,
                                                     current_instance->current_id++,
                                                     v_rate, (float)flags, std::string(label), 0,
                                                     false, *v, v_min, v_max, format});
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_STEPPEDSLIDERFLOAT)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id - 1)
                            return el.clicked;
            return false;
        }
    }

    inline bool Checkbox(const char *label, bool *v)
    {
        if (is_local)
        {
            return ImGui::Checkbox(label, v);
        }
        else
        {
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_CHECKBOX)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id)
                            *v = el.bv;
            current_instance->ui_elements.push_back({UI_ELEMENT_CHECKBOX,
                                                     current_instance->current_id++,
                                                     0, 0, std::string(label), 0, *v});
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_CHECKBOX)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id - 1)
                            return el.clicked;
            return false;
        }
    }

    inline bool Combo(const char *label, int *current_item, const char *items_separated_by_zeros)
    {
        if (is_local)
        {
            return ImGui::Combo(label, current_item, items_separated_by_zeros);
        }
        else
        {
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_COMBO)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id)
                            *current_item = el.iv;
            std::string separated_elems;
            {
                const char *p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
                while (*p)
                    p += strlen(p) + 1;
                separated_elems.insert(separated_elems.end(), items_separated_by_zeros, p);
            }
            current_instance->ui_elements.push_back({UI_ELEMENT_COMBO,
                                                     current_instance->current_id++,
                                                     0, 0, std::string(label), *current_item,
                                                     false, 0, 0, 0, 
                                                     separated_elems});
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_COMBO)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id - 1)
                            return el.clicked;
            return false;
        }
    }

    inline bool InputDouble(const char *label, double *v, double step = (0.0), double step_fast = (0.0), const char *format = "%.6f")
    {
        if (is_local)
        {
            return ImGui::InputDouble(label, v);
        }
        else
        {
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_INPUTDOUBLE)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id)
                            *v = el.min;
            current_instance->ui_elements.push_back({UI_ELEMENT_INPUTDOUBLE,
                                                     current_instance->current_id++,
                                                     0, 0, std::string(label), 0,
                                                     false, (float)step, *v, step_fast, std::string(format)});
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_INPUTDOUBLE)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id - 1)
                            return el.clicked;
            return false;
        }
    }

    inline void Separator()
    {
        if (is_local)
            ImGui::Separator();
        else
            current_instance->ui_elements.push_back({UI_ELEMENT_SEPARATOR,
                                                     current_instance->current_id++,
                                                     0, 0, "##noid"});
    }

    inline bool InputText(const char *label, std::string *str, ImGuiInputTextFlags flags = 0)
    {
        if (is_local)
        {
            return ImGui::InputText(label, str, flags);
        }
        else
        {
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_INPUTTEXT)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id)
                            *str = el.sv2;
            current_instance->ui_elements.push_back({UI_ELEMENT_INPUTTEXT,
                                                     current_instance->current_id++,
                                                     0, 0, std::string(label), flags,
                                                     false, 0, 0, 0, std::string(*str)});
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_INPUTTEXT)
                    if (el.sv == std::string(label))
                        if (el.id == current_instance->current_id - 1)
                            return el.clicked;
            return false;
        }
    }

    inline bool IsItemDeactivatedAfterEdit()
    {
        if (is_local)
        {
            return ImGui::IsItemDeactivatedAfterEdit();
        }
        else
        {
            current_instance->ui_elements.push_back({UI_ELEMENT_ISITEMDEACTIVATEDAFTEREDIT,
                                                     current_instance->current_id++,
                                                     0, 0, "##nolabelisitemdeactivatedafteredit", 0,
                                                     false, 0, 0, 0, ""});
            for (auto &el : current_instance->ui_elems_fbk)
                if (el.t == UI_ELEMENT_ISITEMDEACTIVATEDAFTEREDIT)
                    if (el.sv == "##nolabelisitemdeactivatedafteredit")
                        if (el.id == current_instance->current_id - 1)
                            return el.clicked;
            return false;
        }
    }

    inline void SameLine()
    {
        if (is_local)
            ImGui::SameLine();
        else
            current_instance->ui_elements.push_back({UI_ELEMENT_SAMELINE,
                                                     current_instance->current_id++,
                                                     0, 0, "##noid"});
    }

    inline void beginDisabled()
    {
        if (is_local)
            style::beginDisabled();
        else
            current_instance->ui_elements.push_back({UI_ELEMENT_BEGINDISABLED,
                                                     current_instance->current_id++});
    }

    inline void endDisabled()
    {
        if (is_local)
            style::endDisabled();
        else
            current_instance->ui_elements.push_back({UI_ELEMENT_ENDDISABLED,
                                                     current_instance->current_id++});
    }
};
