#include "json_editor.h"
#include "core/style.h"
#include "imgui/imgui.h"
#include "imgui/imgui_stdlib.h"

namespace satdump
{
    namespace widgets
    {
        template <typename T>
        inline void AddButton(T &json, bool allow_add)
        {
            if (allow_add && ImGui::Button("Add..."))
                ImGui::OpenPopup("Add Item");

            ImVec2 center = ImGui::GetMainViewport()->GetCenter();
            ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
            if (ImGui::BeginPopupModal("Add Item", NULL, ImGuiWindowFlags_AlwaysAutoResize))
            {
                static std::string new_key = "new_item";
                if (json.is_object())
                {
                    ImGui::TextUnformatted("Key:");
                    ImGui::SameLine();
                    ImGui::InputText("##label", &new_key, ImGuiInputTextFlags_AutoSelectAll);
                }

                static int selected = 0;
                ImGui::SameLine();
                ImGui::TextUnformatted("Type:");
                ImGui::SameLine();
                ImGui::Combo("##Type", &selected, "Object\0Array\0String\0Integer\0Double\0Boolean\0\0");
                ImGuiStyle &style = ImGui::GetStyle();
                ImGui::SetCursorPos({(ImGui::GetContentRegionAvail().x / 2) - ((ImGui::CalcTextSize("Add").x + ImGui::CalcTextSize("Cancel").x + style.ItemSpacing.x + style.FramePadding.x * 4) / 2),
                                     ImGui::GetCursorPosY() + 5.0f * ui_scale});

                if (ImGui::Button("Add"))
                {
                    if (json.is_object())
                    {
                        switch (selected)
                        {
                        case 0:
                            json[new_key] = T::object();
                            break;
                        case 1:
                            json[new_key] = T::array();
                            break;
                        case 2:
                            json[new_key] = "";
                            break;
                        case 3:
                            json[new_key] = 0;
                            break;
                        case 4:
                            json[new_key] = 0.0;
                            break;
                        case 5:
                            json[new_key] = false;
                        }
                    }
                    else
                    {
                        switch (selected)
                        {
                        case 0:
                            json.push_back(T::object());
                            break;
                        case 1:
                            json.push_back(T::array());
                            break;
                        case 2:
                            json.push_back("");
                            break;
                        case 3:
                            json.push_back(0);
                            break;
                        case 4:
                            json.push_back(0.0);
                            break;
                        case 5:
                            json.push_back(false);
                        }
                    }
                    ImGui::CloseCurrentPopup();
                }
                ImGui::SameLine();
                if (ImGui::Button("Cancel"))
                {
                    ImGui::CloseCurrentPopup();
                }
                ImGui::EndPopup();
            }
        }

        [[nodiscard]] inline bool DeleteButton()
        {
            ImGui::SameLine();
            ImGui::SetCursorPosY(ImGui::GetCursorPos().y - 2 * ui_scale);
            ImGui::PushStyleColor(ImGuiCol_Text, style::theme.red.Value);
            ImGui::PushStyleColor(ImGuiCol_Button, ImVec4());
            ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0);
            bool ret = ImGui::Button(u8"\uf00d");
            ImGui::PopStyleVar();
            ImGui::PopStyleColor(2);
            return ret;
        }

        template <typename T>
        void JSONTreeEditor(T &json, const char *id, bool allow_add)
        {
            ImGui::PushID(id);
            int array_index = 0;
            float start_pos = ImGui::GetCursorPosX();
            bool delete_item = false;

            for (auto jsonItem = json.begin(); jsonItem != json.end();)
            {
                std::string this_key;
                if (json.is_array())
                    this_key = std::to_string(array_index++);
                else
                    this_key = jsonItem.key();

                ImGui::PushID(this_key.c_str());
                if (jsonItem.value().is_object())
                {
                    bool nodeOpen = ImGui::TreeNode(std::string((json.is_array() ? "##" : "") + this_key).c_str());
                    ImGui::SameLine();
                    ImGui::TextDisabled("%s", "[Object]");
                    delete_item = DeleteButton();
                    if (nodeOpen)
                    {
                        JSONTreeEditor(jsonItem.value(), this_key.c_str());
                        ImGui::TreePop();
                    }
                }
                else if (jsonItem.value().is_array())
                {
                    bool nodeOpen = ImGui::TreeNode(std::string((json.is_array() ? "##" : "") + this_key).c_str());
                    ImGui::SameLine();
                    ImGui::TextDisabled("%s", jsonItem.value().dump().c_str());
                    delete_item = DeleteButton();
                    if (nodeOpen)
                    {
                        JSONTreeEditor(jsonItem.value(), this_key.c_str());
                        ImGui::TreePop();
                    }
                }
                else if (jsonItem.value().is_boolean())
                {
                    if (!json.is_array())
                    {
                        ImGui::BulletText("%s", this_key.c_str());
                        ImGui::SameLine();
                    }
                    bool val = jsonItem.value();
                    if (ImGui::Checkbox(std::string("##" + this_key).c_str(), &val))
                        jsonItem.value() = val;
                    delete_item = DeleteButton();
                }
                else if (jsonItem.value().is_number_integer() || jsonItem.value().is_number_unsigned())
                {
                    if (!json.is_array())
                    {
                        ImGui::BulletText("%s", this_key.c_str());
                        ImGui::SameLine();
                    }

                    ImGui::SetNextItemWidth(400 * ui_scale + start_pos - ImGui::GetCursorPosX());
                    int val = jsonItem.value();
                    if (ImGui::InputInt(std::string("##" + this_key).c_str(), &val))
                        jsonItem.value() = val;
                    delete_item = DeleteButton();
                }
                else if (jsonItem.value().is_number_float())
                {
                    if (!json.is_array())
                    {
                        ImGui::BulletText("%s", this_key.c_str());
                        ImGui::SameLine();
                    }

                    ImGui::SetNextItemWidth(400 * ui_scale + start_pos - ImGui::GetCursorPosX());
                    double val = jsonItem.value();
                    if (ImGui::InputDouble(std::string("##" + this_key).c_str(), &val))
                        jsonItem.value() = val;
                    delete_item = DeleteButton();
                }
                else if (jsonItem.value().is_string())
                {
                    if (!json.is_array())
                    {
                        ImGui::BulletText("%s", this_key.c_str());
                        ImGui::SameLine();
                    }

                    ImGui::SetNextItemWidth(400 * ui_scale + start_pos - ImGui::GetCursorPosX());
                    std::string val = jsonItem.value();
                    if (val.find("\n") == std::string::npos ? ImGui::InputText(std::string("##" + this_key).c_str(), &val) : ImGui::InputTextMultiline(std::string("##" + this_key).c_str(), &val))
                        jsonItem.value() = val;
                    delete_item = DeleteButton();
                }

                ImGui::PopID();
                if (delete_item)
                {
                    jsonItem = json.erase(jsonItem);
                    delete_item = false;
                }
                else
                    jsonItem++;
            }

            AddButton<T>(json, allow_add);
            ImGui::PopID();
        }

        bool JSONTableEditor(nlohmann::json &json, const char *id)
        {
            ImGui::PushID(id);
            ImVec2 start_pos = ImGui::GetCursorPos();
            ImGui::SetCursorPos({start_pos.x + 3 * ui_scale, start_pos.y + 10 * ui_scale});
            ImGui::AlignTextToFramePadding();
            ImGui::TextUnformatted(id);
            ImGui::SameLine();
            AddButton<nlohmann::json>(json, true);
            ImGui::SameLine();
            bool ret = ImGui::Button(std::string("Reset##" + std::string(id)).c_str());

            if (json.size() > 0)
            {
                if (ImGui::BeginTable(std::string(std::string(id) + "##table").c_str(), 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
                {
                    int array_index = 0;
                    bool delete_item = false;

                    for (auto jsonItem = json.begin(); jsonItem != json.end();)
                    {
                        std::string this_key;
                        if (json.is_array())
                            this_key = std::to_string(array_index++);
                        else
                            this_key = jsonItem.key();

                        ImGui::PushID(this_key.c_str());
                        ImGui::TableNextRow();
                        ImGui::TableSetColumnIndex(0);
                        if (!json.is_array())
                            ImGui::TextUnformatted(this_key.c_str());

                        ImGui::TableSetColumnIndex(1);
                        if (jsonItem.value().is_object())
                        {
                            ImGui::TextDisabled("%s", "[Object]");
                            delete_item = DeleteButton();
                            JSONTreeEditor(jsonItem.value(), this_key.c_str());
                        }
                        else if (jsonItem.value().is_array())
                        {
                            ImGui::TextDisabled("%s", jsonItem.value().dump().c_str());
                            delete_item = DeleteButton();
                            JSONTreeEditor(jsonItem.value(), this_key.c_str());
                        }
                        else if (jsonItem.value().is_boolean())
                        {
                            bool val = jsonItem.value();
                            if (ImGui::Checkbox(std::string("##" + this_key).c_str(), &val))
                                jsonItem.value() = val;
                            delete_item = DeleteButton();
                        }
                        else if (jsonItem.value().is_number_integer() || jsonItem.value().is_number_unsigned())
                        {
                            int val = jsonItem.value();
                            if (ImGui::InputInt(std::string("##" + this_key).c_str(), &val))
                                jsonItem.value() = val;
                            delete_item = DeleteButton();
                        }
                        else if (jsonItem.value().is_number_float())
                        {
                            double val = jsonItem.value();
                            if (ImGui::InputDouble(std::string("##" + this_key).c_str(), &val))
                                jsonItem.value() = val;
                            delete_item = DeleteButton();
                        }
                        else if (jsonItem.value().is_string())
                        {
                            std::string val = jsonItem.value();
                            if (val.find("\n") == std::string::npos ? ImGui::InputText(std::string("##" + this_key).c_str(), &val)
                                                                    : ImGui::InputTextMultiline(std::string("##" + this_key).c_str(), &val))
                                jsonItem.value() = val;
                            delete_item = DeleteButton();
                        }

                        ImGui::PopID();
                        if (delete_item)
                        {
                            jsonItem = json.erase(jsonItem);
                            delete_item = false;
                        }
                        else
                            jsonItem++;
                    }

                    ImGui::EndTable();
                }
            }
            else
                ImGui::Separator();

            ImGui::PopID();
            return ret;
        }

        template void JSONTreeEditor(nlohmann::ordered_json &, const char *, bool);
        template void JSONTreeEditor(nlohmann::json &, const char *, bool);
    } // namespace widgets
} // namespace satdump