#pragma once
#include <fstream>

#include <imgui.h>

#include "../Module.h"
#include "../ModuleCategory.h"
#include "../esp_text.h"
#include "../hotkeys.h"
#include "../../util/i18n.h"
#include "../item_pricing.h"
#include "../../config/ActionRowValue.h"
#include "../../config/CheckboxValue.h"
#include "../../config/InfoDrawValue.h"
#include "../../config/IntSliderValue.h"
#include "../../config/MenuSectionValue.h"
#include "../../config/LanguageSelectValue.h"
#include "../../config/SelectValue.h"

#include "../../util/globals.h"
#include "../../util/logger.h"

std::vector<Module*> static modules = {};
inline ConfigModule* config_module_self = nullptr;

class ConfigModule : Module
{
public:
    ConfigModule(const std::vector<Module*>& m) : Module("Configs", Config, "configs_mod")
    {
        modules = m;
        config_module_self = this;
    }

    static void for_each_module(const std::function<void(Module*)>& fn)
    {
        for (auto* module : modules)
            fn(module);
        if (config_module_self)
            fn(config_module_self);
    }

    SelectValue* config = conf(new SelectValue(0, {
                                                   "legit_sel", "rage_sel", "config_1_sel", "config_2_sel", "config_3_sel", "config_4_sel",
                                                   "config_5_sel"
                                               }, "config_file_sel"));

    ActionRowValue* load = conf(new ActionRowValue([&]
    {
        load_config(config->get_selected());
    }, "load_btn"));

    ActionRowValue* save = conf(new ActionRowValue([&]
    {
        save_config(config->get_selected());
    }, "save_btn", true));

    ActionRowValue* reset_defaults = conf(new ActionRowValue([&]
    {
        reset_all_to_defaults();
    }, "reset_defaults_btn", true));

    MenuSectionValue* section_language_ = conf(new MenuSectionValue("menu_language_sec"));
    LanguageSelectValue* menu_language_ = conf(new LanguageSelectValue(0, "menu_language_sec"));
    bool menu_language_initialized_ = false;

    MenuSectionValue* section_text_size_ = conf(new MenuSectionValue("on_screen_text_size_sec"));
    IntSliderValue* esp_text_font_size = conf(new IntSliderValue(12, 8, 36, "esp_text_size_itm"));
    IntSliderValue* quest_esp_font_size = conf(new IntSliderValue(12, 8, 36, "quest_esp_text_size_itm"));

    MenuSectionValue* section_esp_text_ = conf(new MenuSectionValue("esp_text_readability_sec"));
    CheckboxValue* esp_text_outline = conf(new CheckboxValue(false, "text_outline_cb"));
    CheckboxValue* esp_text_background = conf(new CheckboxValue(false, "text_background_cb"));
    CheckboxValue* esp_text_clustering = conf(new CheckboxValue(false, "label_clustering_cb"));
    CheckboxValue* esp_text_depth_sort = conf(new CheckboxValue(false, "depth_text_sort_cb"));

    MenuSectionValue* section_dev_ = conf(new MenuSectionValue("dev_sec"));
    CheckboxValue* show_map_editor_menu = conf(new CheckboxValue(false, "show_map_editor_cb"));

    MenuSectionValue* section_market_prices_ = conf(new MenuSectionValue("market_prices_sec"));

    ActionRowValue* update_market_prices = conf(new ActionRowValue([&]
    {
        item_pricing::refresh_async();
    }, "update_prices_btn"));

    InfoDrawValue* market_prices_info_ = conf(new InfoDrawValue([this]
    {
        draw_market_prices_info();
    }));

    MenuSectionValue* section_cache_ = conf(new MenuSectionValue("cache_sec"));

    ActionRowValue* clear_cache = conf(new ActionRowValue([&]
    {
        clear_all_caches();
    }, "clear_cache_btn", true));

    InfoDrawValue* cache_info_ = conf(new InfoDrawValue([this]
    {
        draw_cache_info();
    }));

    static std::string format_bytes(const size_t bytes)
    {
        if (bytes < 1024)
            return std::to_string(bytes) + " B";
        if (bytes < 1024 * 1024)
        {
            char buf[32];
            snprintf(buf, sizeof(buf), "%.1f KB", static_cast<double>(bytes) / 1024.0);
            return buf;
        }
        char buf[32];
        snprintf(buf, sizeof(buf), "%.2f MB", static_cast<double>(bytes) / (1024.0 * 1024.0));
        return buf;
    }

    void clear_all_caches()
    {
        if (item_pricing::is_updating()) return;

        item_pricing::clear_cache();
        for_each_module([](Module* module)
        {
            module->clear_runtime_cache();
        });

        if (globals::verbose) logger::info("All caches cleared");
    }

    static void reset_all_to_defaults()
    {
        for_each_module([](Module* module)
        {
            module->apply([](ConfigValue* c)
            {
                c->set(c->get_default());
            });
        });

        for (auto& binding : hotkeys::all())
            hotkeys::binding_key(binding) = 0;
    }

    void init_menu_language_options()
    {
        if (menu_language_initialized_)
            return;

        menu_language_->rebuild_labels();
        menu_language_->set_value(i18n::locale_to_index(i18n::current_locale()));
        sync_menu_language_settings();
        menu_language_initialized_ = true;
    }

    void sync_menu_language_settings()
    {
        i18n::set_locale(i18n::index_to_locale(menu_language_->get_value()));
    }

    void prepare_menu_draw() override
    {
        const bool busy = item_pricing::is_updating();
        update_market_prices->set_disabled(busy);
        clear_cache->set_disabled(busy);
        reset_defaults->set_disabled(busy);
    }

    void draw_market_prices_info()
    {
        ImGui::Dummy(ImVec2(0, 4.f));

        if (item_pricing::is_updating())
        {
            ImGui::TextColored(ImVec4(0.95f, 0.82f, 0.35f, 1.f), "%s", i18n::tr("updating_prices_inf").c_str());
            return;
        }

        const std::string source_path = item_pricing::get_active_file_path();
        if (!source_path.empty())
            ImGui::TextWrapped("%s\n%s", i18n::tr("source_file_inf").c_str(), source_path.c_str());
        else
            ImGui::TextWrapped("%s", i18n::tr("source_file_not_loaded_inf").c_str());

        const size_t price_count = item_pricing::get_price_count();
        if (price_count > 0)
            ImGui::Text("%s %zu %s", i18n::tr("loaded_inf").c_str(), price_count, i18n::tr("prices_inf").c_str());

        const std::string file_mtime = item_pricing::get_active_file_mtime_string();
        if (!file_mtime.empty())
            ImGui::Text("%s %s", i18n::tr("file_updated_inf").c_str(), file_mtime.c_str());

        const std::string status = item_pricing::get_status_message();
        if (!status.empty())
            ImGui::TextWrapped("%s", status.c_str());
    }

    void draw_cache_info()
    {
        ImGui::Dummy(ImVec2(0, 4.f));

        size_t total_entries = 0;
        size_t total_bytes = 0;

        for_each_module([&](Module* module)
        {
            if (module == config_module_self) return;

            const size_t entries = module->get_runtime_cache_count();
            const size_t bytes = module->get_runtime_cache_bytes();
            if (entries == 0 && bytes == 0) return;

            total_entries += entries;
            total_bytes += bytes;
            ImGui::Text("%s: %zu %s (~%s)",
                i18n::tr(module->get_display_key()).c_str(), entries,
                i18n::tr("entries_inf").c_str(), format_bytes(bytes).c_str());
        });

        if (item_pricing::is_loaded())
        {
            const size_t price_entries = item_pricing::get_price_count();
            const size_t price_bytes = item_pricing::get_cache_bytes();
            total_entries += price_entries;
            total_bytes += price_bytes;
            ImGui::Text("%s: %zu %s (~%s)",
                i18n::tr("price_table_inf").c_str(), price_entries,
                i18n::tr("entries_inf").c_str(), format_bytes(price_bytes).c_str());
        }

        ImGui::Dummy(ImVec2(0, 2.f));
        ImGui::Text("%s %zu %s, ~%s",
            i18n::tr("total_inf").c_str(), total_entries, i18n::tr("entries_inf").c_str(),
            format_bytes(total_bytes).c_str());
    }

    void draw_overlay(ImDrawList* draw_list) override
    {
    }

    void application_update() override
    {
        static int last_language = 0;
        const int language = menu_language_->get_value();
        if (language != last_language)
        {
            sync_menu_language_settings();
            last_language = language;
        }

        hotkeys::tick();
    }

    void gameworld_update(const Il2CppClass* game_world_class, Il2CppObjectInstance game_world_instance,
                          Il2CppObjectInstance main_player) override
    {
    }

    void init() override
    {
        i18n::init();
        esp_text::config_module = this;
        load_config(config->get_selected());
    }

    static void load_config(const std::string& config)
    {
        if (globals::verbose) logger::info("Loading Config " + config);

        // try to create folder
        char* p_value;
        size_t len;
        errno_t _ = _dupenv_s(&p_value, &len, "APPDATA");
        std::string path = std::string(p_value) + "\\TarkovAddons";
        CreateDirectory(std::wstring(path.begin(), path.end()).c_str(), nullptr);

        // read file;
        std::ifstream t(path + "\\" + config);

        if (!t.good())
        {
            t = create_config(config);
        }

        std::stringstream buffer;
        buffer << t.rdbuf();
        t.close();

        // map keys to values
        auto res = utils::split(buffer.str(), ';');
        auto values = std::unordered_map<std::string, std::string>();
        for (size_t i = 0; i < res.size(); i += 2)
        {
            values[res.at(i)] = res.at(i + 1);
        }

        for_each_module([&values](Module* module)
        {
            module->apply([&values](ConfigValue* c)
            {
                const auto iter = values.find(c->get_config_key());
                if (iter == values.end()) c->set(c->get_default());
                else c->set(iter->second);
            });
        });

        hotkeys::load(values);
    }

    static void save_config(const std::string& config)
    {
        if (globals::verbose) logger::info("Saving Config " + config);

        // try to create folder
        char* p_value;
        size_t len;
        errno_t _ = _dupenv_s(&p_value, &len, "APPDATA");
        std::string path = std::string(p_value) + "\\TarkovAddons";
        CreateDirectory(std::wstring(path.begin(), path.end()).c_str(), nullptr);

        // create config string
        std::string value;
        for_each_module([&value](Module* module)
        {
            module->apply([&value](ConfigValue* c)
            {
                value.append(c->get_config_key());
                value.append(";");
                value.append(c->get());
                value.append(";");
            });
        });

        hotkeys::append_save(value);

        // write file
        std::ofstream t(path + "\\" + config);
        t.write(value.c_str(), static_cast<std::streamsize>(value.size()));
        t.close();
    }

    static std::ifstream create_config(const std::string& config)
    {
        std::string value;
        for_each_module([&value](Module* module)
        {
            module->apply([&value](ConfigValue* c)
            {
                value.append(c->get_config_key());
                value.append(";");
                value.append(c->get_default());
                value.append(";");
            });
        });

        hotkeys::append_defaults(value);

        // write file
        // try to create folder
        char* p_value;
        size_t len;
        errno_t _ = _dupenv_s(&p_value, &len, "APPDATA");
        std::string path = std::string(p_value) + "\\TarkovAddons";

        std::ofstream ot(path + "\\" + config);
        ot.write(value.c_str(), static_cast<std::streamsize>(value.size()));
        ot.close();

        return std::ifstream(path + "\\" + config);
    }
};