
/*
 * Libpdw: Primitives Done Well!
 * Copyright (C) 2022 Rebekah Rowe
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <bitset>
#include <chrono>
#include <iostream>
#include <stdexcept>

#include <X11/Xutil.h>

#include <embed_resources.hpp>
#include <glm/gtx/color_space.hpp> // glm::rgbColor(its used for HSL to RGB)
#include <glez/detail/render.hpp>
#include <glez/color.hpp>
#include <glez/draw.hpp>
#include <glez/glez.hpp>

#include <hydride.h>
#include <thread>

// xorg conflict
#undef RootWindow

#include "libpdw/gui/widgets/slider.hpp"
#include "libpdw/gui/widgets/titlebar.hpp"
#include "libpdw/gui/canvas.hpp"
#include "libpdw/gui/widgets/basebutton.hpp"
#include "libpdw/gui/widgets/basewindow.hpp"
#include "libpdw/gui/widgets/checkbox.hpp"
#include "libpdw/gui/widgets/dropdown.hpp"

#include "libpdw/gui/ncc/logo.hpp"

namespace x11 {

using RawKey = decltype(XK_2);
constexpr std::pair<std::string_view, RawKey> key_dict[] = { { "0", XK_0 }, { "1", XK_1 }, { "2", XK_2 },
    { "3", XK_3 }, { "4", XK_4 }, { "5", XK_5 },
    { "6", XK_6 }, { "7", XK_7 }, { "8", XK_8 },
    { "9", XK_9 }, { "a", XK_A }, { "b", XK_B },
    { "c", XK_C }, { "d", XK_D }, { "e", XK_E },
    { "f", XK_F }, { "g", XK_G }, { "h", XK_H },
    { "i", XK_I }, { "j", XK_J }, { "k", XK_K },
    { "l", XK_L }, { "m", XK_M }, { "n", XK_N },
    { "o", XK_O }, { "p", XK_P }, { "q", XK_Q },
    { "r", XK_R }, { "s", XK_S }, { "t", XK_T },
    { "u", XK_U }, { "v", XK_V }, { "w", XK_W },
    { "x", XK_X }, { "y", XK_Y }, { "z", XK_Z },

    { "escape", XK_Escape }, { "{", XK_bracketleft },
    { "}", XK_bracketright }, { ";", XK_semicolon },
    { "'", XK_apostrophe }, { ",", XK_apostrophe },
    { ".", XK_period }, { "/", XK_slash },
    { "\\", XK_backslash }, { "minus", XK_minus },
    { "=", XK_equal }, { "return", XK_Return },
    { "space", XK_space }, { "backspace", XK_BackSpace },
    { "tab", XK_Tab }, { "capslock", XK_Caps_Lock },

    { "insert", XK_Insert },
    { "delete", XK_Delete },
    { "home", XK_Home }, { "end", XK_End },
    { "pageup", XK_Page_Up }, { "pagedown", XK_Page_Down },

    { "shift_r", XK_Shift_L }, { "shift_r", XK_Shift_R },
    { "alt_l", XK_Alt_L }, { "alt_r", XK_Alt_R },
    { "control_l", XK_Control_L }, { "control_l", XK_Control_R },

    { "0_pad", XK_KP_0 }, { "1_pad", XK_KP_1 }, { "2_pad", XK_KP_2 },
    { "3_pad", XK_KP_3 }, { "4_pad", XK_KP_4 }, { "5_pad", XK_KP_5 },
    { "6_pad", XK_KP_6 }, { "7_pad", XK_KP_7 }, { "8_pad", XK_KP_8 },
    { "9_pad", XK_KP_9 },

    { "/_pad", XK_KP_Divide }, { "*_pad", XK_KP_Multiply },
    { "-_pad", XK_KP_Subtract }, { "+_pad", XK_KP_Add },
    { "enter_pad", XK_KP_Enter }, { "._pad", XK_KP_Decimal },

    { "up", XK_Up }, { "left", XK_Left },
    { "down", XK_Down }, { "right", XK_Right },

    { "f1", XK_F1 }, { "f2", XK_F2 }, { "f3", XK_F3 },
    { "f4", XK_F4 }, { "f5", XK_F5 }, { "f6", XK_F6 },
    { "f7", XK_F7 }, { "f8", XK_F8 }, { "f9", XK_F9 },
    { "f10", XK_F10 }, { "f11", XK_F11 }, { "f12", XK_F12 },

    //{ "mouse_1", XK_Pointer_DfltBtnPrev },
    //{ "mouse_2", XK_Pointer_DfltBtnNext }
    { "mouse_wheel_up", XK_Pointer_DfltBtnPrev },
    { "mouse_wheel_down", XK_Pointer_DfltBtnNext } };
constexpr auto key_dict_size = []() {
    std::size_t ret = 0;
    for (auto i : key_dict)
        ret++;
    return ret;
}();
std::pair<std::pair<int, int>, std::bitset<3>> QueryPointer(decltype(hydride_library.display) dis, decltype(hydride_library.window) win) {
    Window root_return, child_return;
    std::pair<int, int> mouse, root;
    unsigned int mask_return;
    if (!XQueryPointer(hydride_library.display, hydride_library.window, &root_return, &child_return, &root.first, &root.second, &mouse.first, &mouse.second, &mask_return))
        throw std::runtime_error("Unable to query pointer from X11");

    std::bitset<3> ret_buttons;
    ret_buttons[0] = (mask_return & (Button1Mask));
    ret_buttons[1] = (mask_return & (Button2Mask));
    ret_buttons[2] = (mask_return & (Button3Mask));
    return { mouse, ret_buttons };
}

using Keymap = std::array<char, 32>;
bool PickFromKeymap(decltype(hydride_library.display) dis, Keymap map, decltype(XK_2) k) {
    int current_key = XKeysymToKeycode(dis, k);
    return (map[current_key / 8] & (1 << (current_key % 8)));
}
Keymap QueryKeymap(decltype(hydride_library.display) dis) {
    Keymap keys;
    XQueryKeymap(dis, keys.data());
    return keys;
}
bool QueryKey(decltype(hydride_library.display) dis, decltype(XK_2) k) {
    auto map = QueryKeymap(dis);
    return PickFromKeymap(dis, map, k);
}

template <int size>
class ChangeDetector { // generate async from sync
    std::pair<int, int> previous_mouse { -1, -1 };
    std::pair<int, int> previous_bounds { -1, -1 };
    std::bitset<size> previous_states;

public:
    ChangeDetector() {
        this->previous_states.reset();
    }
    bool UpdateKey(std::size_t k, bool state) {
        if (state == previous_states[k]) // Just to prevent issues, idk if we need this at runtime ;-;
            return false;
        previous_states.set(k, state);
        return true;
    }
    decltype(previous_mouse) UpdateMouse(const decltype(previous_mouse)& state) {
        if (state == previous_mouse) // Just to prevent issues, idk if we need this at runtime ;-;
            return { 0, 0 };

        decltype(previous_mouse) delta = { state.first - previous_mouse.first, state.second - previous_mouse.second };
        previous_mouse = state;
        return delta;
    }
    decltype(previous_bounds) UpdateBounds(const decltype(previous_bounds)& state) {
        if (state == previous_bounds) // Just to prevent issues, idk if we need this at runtime ;-;
            return { 0, 0 };

        decltype(previous_bounds) delta = { state.first - previous_bounds.first, state.second - previous_bounds.second };
        previous_bounds = state;
        return delta;
    }
};

class X11Poller {
    ChangeDetector<key_dict_size> async_gen;
    decltype(hydride_library.display) display;
    decltype(hydride_library.window) window;

public:
    X11Poller(decltype(hydride_library.display) display, decltype(hydride_library.window) window)
        : display(display)
        , window(window) {
        UpdateKeys();
        UpdateMouse();
    }
    std::vector<std::pair<std::string_view, bool>> UpdateKeys() {
        const auto km = QueryKeymap(display);

        std::vector<std::pair<std::string_view, bool>> ret;
        for (std::size_t i = 0; i < key_dict_size; i++) {
            bool key_state = PickFromKeymap(display, km, key_dict[i].second);
            if (async_gen.UpdateKey(i, key_state))
                ret.push_back({ key_dict[i].first, key_state });
        }
        return ret;
    }
    std::tuple<std::pair<int, int>, std::pair<int, int>, std::bitset<3>> UpdateMouse() {
        const auto pointer_info = QueryPointer(this->display, this->window);

        const auto delta = async_gen.UpdateMouse(pointer_info.first);
        return { pointer_info.first, delta, pointer_info.second };
    }
};

} // namespace x11

#include "util/geometry.hpp"

namespace qubel {

enum VoxelTypes {
    kAir,
    kStone,
    kDirt,
    kSand,
    kGrass
};

enum CardinalDirections {
    kNorth,
    kSouth,
    kEast,
    kWest // ,
    /*kUp,
    kDown*/
};

class Chunk {
public:
    // "typedefs" or templates go here lol
    static constexpr uint _dim_length = 2; // its in 2d!
    static constexpr uint _size_in_power = 16; // 8x8 size
    static constexpr std::size_t _chunk_array_size = _size_in_power * _size_in_power;

    using BlockType = uint8_t;
    // Using a single digit to locate the exact voxel's byte in the chunk, for very fast use.
    using ChunkDataType = std::array<uint8_t, _chunk_array_size>;
    using ChunkVecType = geo::internal::BVec2<uint8_t>;
    ChunkDataType raw_chunk_data = {
        kStone,
        kStone,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kStone,
        kStone,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kGrass,
        kGrass,
        kGrass,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kGrass,
        kDirt,
        kDirt,
        kDirt,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kGrass,
        kGrass,
        kDirt,
        kDirt,
        kDirt,
        kDirt,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kSand,
        kSand,
        kDirt,
        kDirt,
        kDirt,
        kStone,
        kStone,
        kStone,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kSand,
        kSand,
        kDirt,
        kDirt,
        kStone,
        kStone,
        kStone,
        kStone,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kAir,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kAir,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kAir,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kAir,
        kSand,
        kAir,
        kSand,
        kAir,
        kAir,
        kAir,
        kAir,
        kSand,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kSand,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kAir,
        kStone,
    };
    class LocalVoxel {
    public:
        std::size_t pos; // yes a single integer, x y and z can all exist due to the way chunking keeps it localized and x + (z * 64) means the values co-exist.
        // LocalVoxel(geo::internal::BVec2<uint8_t> raw_chunk_pos)
        //: pos(raw_chunk_pos) { }
        static LocalVoxel GetVoxelFromPosInChunk(geo::internal::BVec2<uint8_t> in_pos) { // basicly your 2d/3d position to a index, make sure ur in the chunk! lol
            assert(in_pos.x < _size_in_power && in_pos.y < _size_in_power);
            LocalVoxel index;
            index.pos = 0;
            return index.AddPos(in_pos);
        }
        LocalVoxel& AddPos(geo::internal::BVec2<uint8_t> in_pos) {
            this->pos += in_pos.x;
            this->pos += in_pos.y * _size_in_power;
            // if constexpr (_dim_length >= 3) // We gotta move these to a seperate class all-together... do this once the templates are split.
            //     index.pos += in_pos.z * (_size_in_power * _size_in_power);
            return *this;
        }
        ChunkVecType ExtractVec() const {
            ChunkVecType ret_pos;
            ret_pos.x = this->pos % _size_in_power;
            ret_pos.y = (this->pos / _size_in_power) % _size_in_power;
            // ret_pos.z = ((this->pos / _size_in_power) / _size_in_power) % _size_in_power;
            return ret_pos;
        }
        // tmp, TODO replace/add with chunk pointer or chunk x and y... both??? oof if so
        // need good way to keep the handle associated.
        // Local HANDLES AND "standard voxel"/global voxel in other words one for chunk and one inheriting with the extra details? ah genious!
    };
    static LocalVoxel GetVoxelFromPosInChunk(geo::internal::BVec2<uint8_t> in_pos) {
        return LocalVoxel::GetVoxelFromPosInChunk(in_pos);
    }
    BlockType GetVoxelType(LocalVoxel v) const {
        return raw_chunk_data[v.pos];
    }
    class AxisIterator {
    public:
        bool end_flagged = false;
        ChunkVecType cur;
        using VecMaskType = glm::bvec2;
        const VecMaskType mask, reverse_mask; // stick it in reverse spongebob!
        constexpr AxisIterator(const ChunkVecType& src, std::pair<glm::bvec2, glm::bvec2> mask)
            : AxisIterator(src, mask.first, mask.second) { }
        constexpr AxisIterator(const ChunkVecType& src, glm::bvec2 mask, glm::bvec2 reverse_mask = { false, false })
            : cur(src)
            , mask(mask)
            , reverse_mask(reverse_mask) { }
        AxisIterator& operator++(int) {
            // new_iter.cur.AddPos(this->GetPosToAdd()); we cant do this cause we have to test it ;-;
            this->cur += this->GetPosToAdd();
            if (!IsPosValidWithinChunk(this->cur))
                this->end_flagged = true;
            return *this;
        }
        bool operator==(const AxisIterator& other) const {
            if (other.end_flagged)
                return this->end_flagged;

            return other.cur == this->cur && other.mask == this->mask && other.reverse_mask == this->reverse_mask;
        }
        bool operator!=(const AxisIterator& other) const {
            return !(*this == other);
        }
        static constexpr AxisIterator end() {
            AxisIterator ret({}, glm::bvec2 {});
            ret.end_flagged = true;
            return ret;
        }
        ChunkVecType GetPosToAdd() const {
            ChunkVecType pos_to_add = { 0, 0 };
            if (mask.x)
                pos_to_add.x = 1;
            if (mask.y)
                pos_to_add.y = 1;
            if (reverse_mask.x && pos_to_add.x)
                pos_to_add.x = -pos_to_add.x;
            if (reverse_mask.y && pos_to_add.y)
                pos_to_add.y = -pos_to_add.y;
            return pos_to_add;
        }
        static constexpr auto CreateCardinalMask(const CardinalDirections dir) {
            VecMaskType mask = { false, false }, reverse_mask = { false, false };
            switch (dir) {
            case CardinalDirections::kNorth:
                reverse_mask.y = true;
            case CardinalDirections::kSouth:
                mask.y = true;
                break;
            case CardinalDirections::kWest:
                reverse_mask.x = true;
            case CardinalDirections::kEast:
                mask.x = true;
                break;
            default:
                throw std::logic_error("Trying to access a cardinal Direction that does not work!");
            }
            return std::make_pair(mask, reverse_mask);
        }
    };
    enum MesherComplexity {
        kPlain,
        kNieve,
        kGreedy, // Just expand same type faces
        kIntrinsicGreedy, // Using bitwise operations :O
        kGlobalLattice // literally Impossible to recreate, meshing everything INCLUDING air, and rendering it straight up in shader
    };
    static constexpr float box_size = 25.0f;
    // glez::vertex not verbose enough for us sadly...
    struct ChunkMeshedQuad {
        geo::Box<geo::Vec2> quad;
        glez::rgba clr;
    };
    static constexpr glez::rgba chunk_textures[] = {
        glez::rgba(0, 0, 0, 0),
        glez::rgba(40, 40, 40),
        glez::rgba(120, 40, 40),
        glez::color::yellow,
        glez::color::green
    };
    std::vector<ChunkMeshedQuad> MeshChunk(MesherComplexity cmplxity) const {
        std::vector<ChunkMeshedQuad> finished_quads;
        std::cout << "Start Meshing: ";
        const auto start_time = std::chrono::high_resolution_clock::now();
        switch (cmplxity) {
        case MesherComplexity::kNieve: // I dont think this is possible in 2d for the time being...
        case MesherComplexity::kPlain: {
            std::cout << "Plain" << std::endl;
            for (uint dim_x = 0; dim_x < _size_in_power; dim_x++) {
                for (uint dim_y = 0; dim_y < _size_in_power; dim_y++) {
                    const LocalVoxel cube = GetVoxelFromPosInChunk({ dim_x, dim_y });
                    const auto type = this->GetVoxelType(cube);
                    if (type == VoxelTypes::kAir)
                        continue;
                    ChunkMeshedQuad new_face;
                    const auto start = geo::Vec2(dim_x * box_size, dim_y * box_size);
                    // auto end = /*start +*/ geo::Vec2(box_size, box_size);
                    const auto size = /*start +*/ geo::Vec2(box_size, box_size);
                    new_face.quad = geo::Box<geo::Vec2>(start, size);
                    new_face.clr = chunk_textures[type];

                    finished_quads.push_back(new_face);
                    std::cout << "              *Added quad! : {" << start.x << ", " << start.y << "} with size {" << size.x << ", " << size.y << "}" << std::endl;
                }
            }
            std::cout << "Plain ";
            break;
        }
            /*case MesherComplexity::kNieve: {

            } */
        case MesherComplexity::kGreedy: { // https://www.youtube.com/watch?v=4xs66m1Of4A - Greedy Meshing Voxels Fast - Optimism in Design Handmade Seattle 2022
            std::cout << "Greedy" << std::endl;
            std::bitset<_chunk_array_size> chunk_finished_checksum;
            const auto FindOpenSpace = [&]() -> std::pair<std::size_t, std::optional<decltype(chunk_finished_checksum)::reference>> {
                for (std::size_t x = 0; x < _size_in_power; x++) {
                    for (std::size_t y = 0; y < _size_in_power; y++) {
                        auto vox = this->GetVoxelFromPosInChunk({ x, y });
                        if (!chunk_finished_checksum[vox.pos])
                            return { vox.pos, chunk_finished_checksum[vox.pos] };
                    }
                }
                return { std::size_t(-1), {} };
            };
            while (!chunk_finished_checksum.all()) {
                const auto unmeshed_face = FindOpenSpace();
                if (!unmeshed_face.second.has_value())
                    throw std::logic_error("Greedy ChunkMesher checksum thinks there is a unmeshed chunk but unable to find it!");

                LocalVoxel cube;
                cube.pos = unmeshed_face.first;
                const auto type = this->GetVoxelType(cube);
                if (type == VoxelTypes::kAir) {
                    chunk_finished_checksum[cube.pos] = true;
                    continue;
                }
                {
                    const auto ext_cube = cube.ExtractVec();
                    std::cout << "Greedy found Seed block \"" << (int)type << "\": {" << (int)ext_cube.x << ", " << (int)ext_cube.y << "}" << std::endl;
                }

                ChunkVecType block_quad_size = { 1, 1 };
                const auto src_block_loc = cube.ExtractVec();

                // Need a better "search line down" func... instead of one block we need to check multiple and return how many are the same.
                constexpr auto south_dir_mask = AxisIterator::CreateCardinalMask(CardinalDirections::kSouth);
                auto SearchLineSouthForSameTypes = [&](ChunkVecType src_line, std::uint8_t max_size = _size_in_power) -> std::uint8_t {
                    std::size_t alike_counter = 0;
                    const auto end = AxisIterator::end();
                    for (auto axis_crawl = AxisIterator(src_line, south_dir_mask); axis_crawl != end; axis_crawl++) {
                        const LocalVoxel block_to_check = GetVoxelFromPosInChunk(axis_crawl.cur);
                        auto below_block_type = this->GetVoxelType(block_to_check);
                        if (below_block_type != type)
                            break;
                        if (chunk_finished_checksum[block_to_check.pos])
                            break;
                        alike_counter++;
                        if (alike_counter >= max_size)
                            break;
                    }
                    return alike_counter;
                };
                const auto same_blocks_south_count = SearchLineSouthForSameTypes(src_block_loc);
                assert(same_blocks_south_count);
                block_quad_size.y = same_blocks_south_count;

                std::cout << "  Found blocks going south: " << (int)block_quad_size.y << std::endl;

                const auto SearchLineEastForSameTypes = [&]() -> std::uint8_t {
                    std::size_t alike_counter = 1;
                    const auto end = AxisIterator::end();
                    constexpr auto east_dir_mask = AxisIterator::CreateCardinalMask(CardinalDirections::kEast);
                    auto side_axis_crawl = AxisIterator(src_block_loc, east_dir_mask);
                    side_axis_crawl++; // starting one ahead
                    // if (side_axis_crawl != end) {
                    std::cout << "    Searching More East!!" << std::endl;
                    for (; side_axis_crawl != end; side_axis_crawl++) {
                        LocalVoxel side_block_to_check = GetVoxelFromPosInChunk(side_axis_crawl.cur);
                        auto below_block_type = this->GetVoxelType(side_block_to_check);
                        if (below_block_type != type)
                            break;

                        // here we go again, dip down to this max length
                        auto additional_same_blocks_south_count = SearchLineSouthForSameTypes(side_axis_crawl.cur, same_blocks_south_count);
                        assert(additional_same_blocks_south_count <= same_blocks_south_count);
                        if (additional_same_blocks_south_count < same_blocks_south_count)
                            break;
                        alike_counter++;
                    }
                    //}
                    return alike_counter;
                };
                if (uint(src_block_loc.x + 1) < _size_in_power) { // only if we have room to check!
                    const auto same_blocks_east_count = SearchLineEastForSameTypes();
                    block_quad_size.x = same_blocks_east_count;
                    std::cout << "  Found blocks going east: " << (int)block_quad_size.x << std::endl;
                }

                ChunkMeshedQuad new_face;
                const auto start = geo::Vec2(src_block_loc.x * box_size, src_block_loc.y * box_size);
                const auto size = geo::Vec2(block_quad_size.x * box_size, block_quad_size.y * box_size);

                new_face.quad = geo::Box<geo::Vec2>(start, size);
                new_face.clr = chunk_textures[type];
                std::cout << "              *Added quad! : {" << start.x << ", " << start.y << "} with size {" << size.x << ", " << size.y << "}" << std::endl;
                finished_quads.push_back(new_face);

                const auto MarkAreaFinished = [&](ChunkVecType origin, ChunkVecType size) {
                    assert(size.x && size.y);
                    for (std::size_t x = 0; x < size.x; x++) {
                        for (std::size_t y = 0; y < size.y; y++) {
                            auto block_to_clear = origin + ChunkVecType(x, y);
                            std::cout << "            ------Marking Block Finished: {" << (int)block_to_clear.x << ", " << (int)block_to_clear.y << "}" << std::endl;
                            chunk_finished_checksum[this->GetVoxelFromPosInChunk(block_to_clear).pos] = true;
                        }
                    }
                };
                MarkAreaFinished(src_block_loc, block_quad_size);
                // mark it off of the chunk_finished_checksum by setting the chunk_finished_checksum[i] to 1
            }
            std::cout << "Greedy ";
            break;
        }
        default:
            // Do nothing, no fail just no-op any mesher un-implimented.
            break;
        };
        auto end_time = std::chrono::high_resolution_clock::now(); // Calculate duration and convert to milliseconds
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
        std::cout << "Meshing Finished!!! QuadCount: " << (int)finished_quads.size() << ", Within: " << duration << " microseconds" << std::endl;

        return finished_quads;
    }
    static bool IsPosValidWithinChunk(ChunkVecType voxel_loc) {
        if (voxel_loc.x >= _size_in_power)
            return false;
        if (voxel_loc.y >= _size_in_power)
            return false;
        return true;
    }
};

} // namespace qubel

class QubelMeshingTestWindow : public CBaseWindow {
public:
    CCheckbox* activate;
    CCheckbox* wireframe;
    CDropdown* dropdown;
    QubelMeshingTestWindow(IWidget* parent)
        : CBaseWindow(parent, "qubelmesh_test_settings") {
        this->always_visible = false;
        this->hover = false;
        this->SetMaxSize(1270, 1000);
        this->SetPositionMode(PositionMode::FLOATING);

        this->Add<CTitleBar>("~SimpleQubelMeshingTester~");

        this->Add<CTextLabel>("activate_label", "Activate Viewing:");
        activate = this->Add<CCheckbox>("activate", false);

        this->Add<CTextLabel>("spread_label", "VoxelSpread:");
        this->Add<CSlider>("spread")->SetStep(0.1f);

        this->Add<CTextLabel>("wireframe_label", "Wireframe Mesh:");
        wireframe = this->Add<CCheckbox>("wireframe", true);

        this->Add<CTextLabel>("wanted_mesher_label", "Meshing Algorithm:");
        dropdown = this->Add<CDropdown>("wanted_mesher");
        dropdown->AddValue("NoFaceOcclusion, Plain");
        dropdown->AddValue("Nieve");
        dropdown->AddValue("Greedy"); // Just expand same type faces
        dropdown->AddValue("Intrinsic Greedy"); // Using bitwise operations :O
        dropdown->AddValue("Global Lattice"); // literally Impossible to recreate, meshing everything INCLUDING air, and rendering it straight up in shader
        dropdown->SetSize(150, 16);
        dropdown->SetValue(2);

        this->Add<CTextLabel>("binary_meshing_label", "Binary Meshing(ignore types):");
        this->Add<CCheckbox>("binary_meshing", false);
    }
};

using namespace qubel;
class QubelMeshingTestRenderingWindow : public CBaseWindow {
public:
    const CTitleBar* titlebar;
    const QubelMeshingTestWindow* settings;
    Chunk world_slice;

    class ChunkRenderer : public CBaseWidget {
    public:
        static constexpr std::pair<int, int> min_size = { 856, 480 };
        const Chunk* world_slice;
        const QubelMeshingTestWindow* settings;
        ChunkRenderer(IWidget* parent, QubelMeshingTestWindow* settings, Chunk* world_slice)
            : CBaseWidget("qubelmesh_test_renderer_sceneoutput", parent)
            , world_slice(world_slice)
            , settings(settings) {
            assert(settings && world_slice);
            settings->dropdown->SetCallback([&](CDropdown*, int value) {
                current_render_quads.clear();
            });
            this->SetSize(min_size.first, min_size.second);
        }
        std::vector<Chunk::ChunkMeshedQuad> current_render_quads;
        virtual void Draw(ICanvas* the_drawing_machine) override {
            this->CBaseWidget::Draw(the_drawing_machine);
            const auto ConvertGLMVecToSTDPairVec = [](const geo::Vec2& vec_in) -> std::pair<int, int> {
                return std::pair<int, int>(vec_in.x, vec_in.y);
            };
            const auto ConvertGLMQuadToSTDPairQuad = [&](const geo::Box<geo::Vec2>& box_in) -> Canvas::TranslationMatrix {
                return Canvas::TranslationMatrix(ConvertGLMVecToSTDPairVec(box_in.origin), ConvertGLMVecToSTDPairVec(box_in.GetSize()));
            };
            for (const Chunk::ChunkMeshedQuad& kinda_a_vert : current_render_quads)
                the_drawing_machine->Rect(ConvertGLMQuadToSTDPairQuad(kinda_a_vert.quad), kinda_a_vert.clr);
            if (this->settings->wireframe->Value()) {
                for (const Chunk::ChunkMeshedQuad& kinda_a_vert : current_render_quads) {
                    the_drawing_machine->Rect({ ConvertGLMQuadToSTDPairQuad(kinda_a_vert.quad) }, glez::color::black, Canvas::RectType::Outline);
                    the_drawing_machine->Line({ ConvertGLMQuadToSTDPairQuad(kinda_a_vert.quad) }, glez::color::black);
                }
            }
        }
        virtual void Update() override {
            this->CBaseWidget::Update();
            if (current_render_quads.empty()) {
                current_render_quads = world_slice->MeshChunk(Chunk::MesherComplexity(settings->dropdown->Value()));

                std::pair<int, int> found_size;
                for (const Chunk::ChunkMeshedQuad& kinda_a_vert : current_render_quads) {
                    const auto max_coord = kinda_a_vert.quad.GetMax();
                    if (max_coord.x > found_size.first)
                        found_size.first = max_coord.x;
                    if (max_coord.y > found_size.second)
                        found_size.second = max_coord.y;
                }
                std::cout << "Created mesh with pixel size: " << found_size.first << ", " << found_size.second << std::endl;
            }
        }
    };
    ChunkRenderer* render_scene;
    class SkyRenderer : public CBaseWidget {
    public:
        SkyRenderer(IWidget* parent)
            : CBaseWidget("qubelmesh_test_renderer_skybox", parent) {
            this->SetPositionMode(FLOATING);
            this->zindex = -999;

            this->UpdateTime(12000);
            PrintDebug();
        }
        virtual void Update() override {
            this->CBaseWidget::Update();
            const auto psize = this->GetParent()->GetSize();
            this->SetSize(psize.first, psize.second);
            this->UpdateTime(this->curtime + 1);
            PrintDebug();
        }
        virtual void Draw(ICanvas* the_drawing_machine) override {
            this->CBaseWidget::Draw(the_drawing_machine);
            the_drawing_machine->Rect({ { 0, 0 }, this->GetSize() }, this->GetAtmosphereColor());
        }
        void UpdateTime(int time_of_day) {
            this->curtime = time_of_day;
            this->cur_external_planetary_rotation_angle = CalcCelestialAngle(time_of_day);
            this->brightness_modifier = CalcBrightnessModifier(this->GetCelestialAngle());
            this->base_color = CalcBaseColor(this->GetBrightnessModifier());
            this->fog_color = CalcFogColor(this->GetCelestialAngle());
            this->atmosphere_color = this->CalcAtmosphereColor();
        }
        void PrintDebug() const {
            auto PrintColor = [](const glez::rgba& clr) {
                std::cout << "{ " << clr.r << ", " << clr.g << ", " << clr.b << ", " << clr.a << " }";
            };
            std::cout << "SkyboxDebug: "
                      << "\n    Curtime: " << this->curtime
                      << "\n    CelestialAngle: " << this->GetCelestialAngle()
                      << "\n    BrightnessModifier: " << this->GetBrightnessModifier();

            std::cout << "\n    FogColor: ";
            PrintColor(this->GetWorldFogColor());
            std::cout << "\n    Base/SkyColor: ";
            PrintColor(this->GetWorldSkyColor());
            std::cout << "\n    AirColor: ";
            PrintColor(this->GetAtmosphereColor());
            std::cout << std::endl;
        }

        float GetBrightnessModifier() const { return this->brightness_modifier; }
        const glez::rgba& GetWorldSkyColor() const { return this->base_color; }
        const glez::rgba& GetWorldFogColor() const { return this->fog_color; }
        const glez::rgba& GetAtmosphereColor() const { return this->atmosphere_color; }

    private:
        float GetCelestialAngle() const { return this->cur_external_planetary_rotation_angle; }
        int curtime;
        float cur_external_planetary_rotation_angle;
        float brightness_modifier;
        glez::rgba base_color; // worldskycolor
        glez::rgba fog_color;
        glez::rgba atmosphere_color;

        // Thank you very much for these foundational findings TrueCraft <3
        // These algorithms are kinda hard to recreate and not rip out as is, its MIT so its supposidly safe to add.
        // Please Please Please!!! Be sure to keep these credits as they put the impressive work in.
        // https://github.com/ddevault/TrueCraft/blob/master/TrueCraft.Client/Modules/SkyModule.cs
        static float CalcCelestialAngle(int time_of_day) {
            float x = (time_of_day % 24000) / 24000.0f - 0.25f;
            std::cout << "First X: " << x << std::endl;
            if (x < 0)
                x = 0;
            if (x > 1)
                x = 1;
            return x + ((1 - (cos(x * glm::pi<float>()) + 1) / 2) - x) / 3;
        }
        static glez::rgba CalcBaseColor(float brightness_modifier) {
            constexpr float temp = 0.8f / 3;
            const auto raw_color = glm::rgbColor(glm::vec3(0.6222222f - temp * 0.05f, 0.5f + temp * 0.1f, brightness_modifier));
            glez::rgba ret;
            ret.r = raw_color.r;
            ret.g = raw_color.g;
            ret.b = raw_color.b;
            ret.a = 1.0f;
            return ret;
        }
        static float CalcBrightnessModifier(float celestial_angle) {
            float mod = cos(celestial_angle * glm::two_pi<float>()) * 2 + 0.5f;
            if (mod < 0)
                mod = 0;
            if (mod > 1)
                mod = 1;
            return mod;
        }
        static glez::rgba CalcFogColor(float celestial_angle) {
            const float y = cos(celestial_angle * glm::two_pi<float>()) * 2 + 0.5f;
            std::cout << "fog color y: " << y << std::endl;
            glez::rgba fog_color;
            fog_color.r = 0.7529412f * y * 0.94f + 0.06f;
            fog_color.g = 0.8470588f * y * 0.94f + 0.06f;
            fog_color.b = 1.0f * y * 0.91f + 0.09f;
            fog_color.a = 1.0f;
            return fog_color;
            // TODO, Nether at a constant (0.2, 0.03, 0.03)
        }
        glez::rgba CalcAtmosphereColor() const {
            constexpr float blend_factor = 0.29f; // TODO: Compute based on view distance
            const auto blend = [&](float src, float dest) -> float {
                return dest + (src - dest) * blend_factor;
            };
            const auto &fog = this->GetWorldFogColor(), sky = this->GetWorldSkyColor();
            glez::rgba air_color;
            air_color.r = blend(sky.r, fog.r) * 0.5f;
            air_color.g = blend(sky.g, fog.g) * 0.5f;
            air_color.b = blend(sky.b, fog.b) * 0.5f;
            air_color.a = 1.0f;
            return air_color;
        }
    };
    SkyRenderer* skybox;

public:
    QubelMeshingTestRenderingWindow(IWidget* parent, QubelMeshingTestWindow* settings)
        : CBaseWindow(parent, "qubelmesh_test_renderer_frame")
        , settings(settings) {
        assert(settings);
        this->always_visible = false;
        this->hover = false;
        this->SetPositionMode(PositionMode::FLOATING);
        this->SetMaxSize(1270, 1000);
        this->zindex = -999;

        titlebar = this->Add<CTitleBar>("QubelWorld Mesh Test uwu~");
        render_scene = this->Add<ChunkRenderer>(settings, &world_slice);
        skybox = this->Add<SkyRenderer>();
    }
    ~QubelMeshingTestRenderingWindow() { }
    virtual bool AlwaysVisible() const override {
        return this->settings->activate->Value();
    }
    virtual bool IsVisible() const override {
        return this->settings->activate->Value() && this->CBaseWindow::IsVisible();
    }

public:
    // std::chrono::time_point<std::chrono::system_clock> last_update;
};

int start_nyqubel_client() {
    hydride_init();

    glez::init(hydride_library.width, hydride_library.height);

    Canvas* canvas;
    x11::X11Poller x11_poller(hydride_library.display, hydride_library.window);
    {
        input::RefreshInput();

        hydride_draw_begin();
        glez::begin();

        canvas = new Canvas();
        canvas->Setup();

        glez::end();
        hydride_draw_end();
    }

    canvas->Add<ncc::Logo>()->SetOffset(500, 525);

    auto mesh_test_settings_window = canvas->Add<QubelMeshingTestWindow>();
    auto mesh_test_rendering_window = canvas->Add<QubelMeshingTestRenderingWindow>(mesh_test_settings_window);
    mesh_test_settings_window->SetOffset(2000, 400);
    mesh_test_rendering_window->SetOffset(3000, 800);

    bool client_exiting = false;
    mesh_test_settings_window->Add<CBaseButton>("exit_button", "Press to Quit-rite", [&](CBaseButton*) {
        client_exiting = true;
        std::cout << "User Requested Client quit." << std::endl;
    });

    // Need a 856/480 size window.

    hydride_show();
    while (1) {
        input::RefreshInput();
        // Must be called in that order.
        hydride_draw_begin();
        glez::begin();
        {
            x11_poller.UpdateKeys();
            x11_poller.UpdateMouse();

            canvas->Update();
            using namespace std::chrono_literals;
            std::this_thread::sleep_for(500us);
        }
        glez::end();
        hydride_draw_end();

        if (client_exiting)
            break;
    }
    std::cout << "Client is Exiting!!!" << std::endl;
    return 0;
}
