#pragma once

#include <toml.h>
#include <glm/vec4.hpp>
#include <limits.h>
#include <iomanip>
#include "world/Universe.hpp"
#include "render/Renderer.hpp"
#include "contouring/Abstract.hpp"
#include "control/Camera.hpp"
#include "Window.hpp"

namespace config::client {

inline glm::vec4 fromHex(const std::string& str) {
    std::array<int, 3> rgb = {UCHAR_MAX};
    sscanf(str.c_str() + 1, "%02X%02X%02X", (unsigned int *)&rgb[0], (unsigned int *)&rgb[1], (unsigned int *)&rgb[2]);
    return glm::vec4(rgb[0] * 1.f / UCHAR_MAX, rgb[1] * 1.f / UCHAR_MAX, rgb[2] * 1.f / UCHAR_MAX, 1);
}
inline std::string toHexa(const glm::vec4& rgb) {
    std::ostringstream sstr;
    sstr << '#' << std::hex << std::setw(2) << std::setfill('0') <<
        static_cast<int>(rgb.x * UCHAR_MAX) << static_cast<int>(rgb.y * UCHAR_MAX) << static_cast<int>(rgb.z * UCHAR_MAX);
    return sstr.str();
}

/// Client side configuration
struct options {
public:
    options(toml::node_view<toml::node> config) {
        assert(config["enabled"]);

        window.targetFPS = config["window"]["target_fps"].value_or(window.targetFPS);
        window.sampling = config["window"]["sampling"].value_or(window.sampling);
        window.fullscreen = config["window"]["fullscreen"].value_or(window.fullscreen);
        renderer.inFlightFrames = config["window"]["parallel_frames"].value_or(renderer.inFlightFrames);

        preferVulkan = config["render"]["prefer_vulkan"].value_or(preferVulkan);
        renderer.textures = config["render"]["textures"].value_or(renderer.textures);
        renderer.textureQuality = config["render"]["texture_quality"].value_or(renderer.textureQuality);
        renderer.textureSharpness = config["render"]["texture_angular_quality"].value_or(renderer.textureSharpness);
        renderer.voxel.pbr = config["render"]["pbr"].value_or(renderer.voxel.pbr);
        renderer.voxel.planar = static_cast<render::Planar>(config["render"]["planar"].value_or(static_cast<int>(renderer.voxel.planar)));
        renderer.voxel.stochastic = config["render"]["stochastic"].value_or(renderer.voxel.stochastic);
        renderer.voxel.geometry = config["render"]["geometry"].value_or(renderer.voxel.geometry);
        renderer.voxel.blend = config["render"]["blend"].value_or(renderer.voxel.blend);
        renderer.voxel.fog = config["render"]["fog"].value_or(renderer.voxel.fog);
        const std::string fog = config["render"]["fog_color"].value_or(std::string{"#000000"});
        renderer.clear_color = fromHex(fog);
        renderer.skybox = config["render"]["skybox"].value_or(renderer.skybox);
        renderer.voxel.curvature = config["render"]["curvature"].value_or(renderer.voxel.curvature);
        renderer.voxel.curv_depth = config["render"]["curvature_depth"].value_or(renderer.voxel.curv_depth);
        renderer.voxel.transparency = config["render"]["transparency"].value_or(renderer.voxel.transparency);
        culling = config["render"]["culling"].value_or(culling);

        contouring = config["contouring"].value_or(std::string(""));

        camera.far_dist = config["camera"]["far"].value_or(camera.far_dist);
        camera.near_dist = config["camera"]["near"].value_or(camera.near_dist);
        camera.fov = config["camera"]["fov"].value_or(camera.fov);
        control.sensibility = config["control"]["sensibility"].value_or(control.sensibility);
        control.speed = config["control"]["speed"].value_or(control.speed);
        control.collide = config["control"]["collide"].value_or(control.collide);

        world.loadDistance = config["world"]["load_distance"].value_or(world.loadDistance);
        world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance);
        world.useAverages = config["world"]["use_averages"].value_or(world.useAverages);
        world.trustMajorant = config["world"]["trust_majorant"].value_or(world.trustMajorant);
        world.editPrediction = config["world"]["edit_prediction"].value_or(world.editPrediction);
        world.editHandling = config["world"]["edit_handling"].value_or(world.editHandling);
        world.movePrediction = config["world"]["move_prediction"].value_or(world.movePrediction);
        world.moveCollision = config["world"]["move_collision"].value_or(world.moveCollision);
        voxel_density = config["world"]["voxel_density"].value_or(voxel_density);

#ifndef STANDALONE
        const auto useLocal = config["connection"]["use_local"].value_or(true);
#else
        const auto useLocal = false;
#endif

        if(!useLocal) {
            world::client::Universe::connection ct;
            ct.host = config["connection"]["host"].value_or(ct.host);
            ct.port = config["connection"]["port"].value_or(ct.port);
            connection = ct;
        }

        editor.visible = config["editor"]["visible"].value_or(editor.visible);
        editor.tool.shape = static_cast<world::action::Shape>(config["editor"]["tool"]["shape"].value_or(static_cast<int>(editor.tool.shape)));
        editor.tool.radius = config["editor"]["tool"]["radius"].value_or(editor.tool.radius);
        editor.tool.material = config["editor"]["tool"]["material"].value_or(editor.tool.material);
        editor.tool.emptyAir = config["editor"]["tool"]["empty_air"].value_or(editor.tool.emptyAir);

        console.visible = config["console"]["visible"].value_or(console.visible);
        console.scroll = config["console"]["scroll"].value_or(console.scroll);
        console.opacity = config["console"]["opacity"].value_or(console.opacity);

        debugMenu.bar = config["debug_menu"]["bar"].value_or(debugMenu.bar);
        debugMenu.render = config["debug_menu"]["render"].value_or(debugMenu.render);
        debugMenu.world = config["debug_menu"]["world"].value_or(debugMenu.world);
        debugMenu.contouring = config["debug_menu"]["contouring"].value_or(debugMenu.contouring);
        debugMenu.controls = config["debug_menu"]["controls"].value_or(debugMenu.controls);

        overlay.visible = config["overlay"]["visible"].value_or(overlay.visible);
        overlay.corner = config["overlay"]["corner"].value_or(overlay.corner);
    }

    toml::table save() {
        auto config = toml::table();
        config.insert_or_assign("enabled", true);
        config.insert_or_assign("window", toml::table({
            {"target_fps", window.targetFPS},
            {"sampling", window.sampling},
            {"fullscreen", window.fullscreen},
            {"parallel_frames", renderer.inFlightFrames}
        }));
        config.insert_or_assign("render", toml::table({
            {"prefer_vulkan", preferVulkan},
            {"textures", renderer.textures},
            {"texture_quality", renderer.textureQuality},
            {"texture_angular_quality", renderer.textureSharpness},
            {"pbr", renderer.voxel.pbr},
            {"planar", static_cast<int>(renderer.voxel.planar)},
            {"stochastic", renderer.voxel.stochastic},
            {"geometry", renderer.voxel.geometry},
            {"blend", renderer.voxel.blend},
            {"fog", renderer.voxel.fog},
            {"fog_color", toHexa(renderer.clear_color)},
            {"skybox", renderer.skybox},
            {"curvature", renderer.voxel.curvature},
            {"curvature_depth", renderer.voxel.curv_depth},
            {"transparency", renderer.voxel.transparency},
            {"culling", culling}
        }));
        config.insert_or_assign("contouring", contouring);
        config.insert_or_assign("camera", toml::table({
            {"far", camera.far_dist},
            {"near", camera.near_dist},
            {"fov", camera.fov}
        }));
        config.insert_or_assign("control", toml::table({
            {"sensibility", control.sensibility},
            {"speed", control.speed},
            {"collide", control.collide}
        }));
        config.insert_or_assign("world", toml::table({
            {"load_distance", world.loadDistance},
            {"keep_distance", world.keepDistance},
            {"use_averages", world.useAverages},
            {"trust_majorant", world.trustMajorant},
            {"edit_prediction", world.editPrediction},
            {"edit_handling", world.editHandling},
            {"move_prediction", world.movePrediction},
            {"move_collision", world.moveCollision},
            {"voxel_density", voxel_density}
        }));
        if(connection.has_value()) {
            const auto &ct = connection.value();
            config.insert_or_assign("connection", toml::table({
#ifndef STANDALONE
                {"use_local", false},
#endif
                {"host", ct.host},
                {"port", ct.port}
            }));
        } else {
            config.insert_or_assign("connection", toml::table({{"use_local", true}}));
        }

        config.insert_or_assign("editor", toml::table({
            {"visible", editor.visible},
            {"tool", toml::table({
                {"shape", static_cast<int>(editor.tool.shape)},
                {"radius", editor.tool.radius},
                {"material", editor.tool.material},
                {"empty_air", editor.tool.emptyAir}
            })}
        }));
        config.insert_or_assign("console", toml::table({
            {"visible", console.visible},
            {"scroll", console.scroll},
            {"opacity", console.opacity}
        }));
        config.insert_or_assign("debug_menu", toml::table({
            {"bar", debugMenu.bar},
            {"render", debugMenu.render},
            {"world", debugMenu.world},
            {"contouring", debugMenu.contouring},
            {"controls", debugMenu.controls}
        }));
        config.insert_or_assign("overlay", toml::table({
            {"visible", overlay.visible},
            {"corner", overlay.corner}
        }));
        return config;
    }

    world::client::Universe::options world;
    int voxel_density = 1;

    struct {
        bool bar = false;
        bool render = false;
        bool world = false;
        bool contouring = false;
        bool controls = false;
    } debugMenu;

    windowOptions window;

    bool preferVulkan = false;
    render::renderOptions renderer;
    int culling = 0;

    std::string contouring;

    Controllable::options control;
    Camera::options camera;

    struct {
        bool visible = false;
        struct {
            world::action::Shape shape = world::action::Shape::Cube;
            int radius = 2;
            unsigned short material = 5;
            bool emptyAir = true;
        } tool;
    } editor;

    struct {
        bool visible = true;
        bool scroll = true;
        float opacity = .8f;
    } console;

    struct {
        bool visible = true;
        int corner = 3;
    } overlay;

    std::optional<world::client::Universe::connection> connection = {};
};
}