#ifndef SERVER
#include <stdint.h>
#include <stdbool.h>
#include "common.hpp"
#include <cstring>
#include <list>
#include <raymath.h>
#include <iostream>
#include <rlgl.h>
#include <unordered_set>
#include <algorithm>
#include <vector>
#include <filesystem>
#include <map>

int currentlySelected = -1;
extern Font fnt;
extern GameState state;
extern bool online;
static Sound snd_click;
static Sound snd_hover;
extern Texture2D terrain;
Texture2D cubemap;
extern Camera camera;
static int MakeUIPOI(int x, int y) {
    return x << 16 | y;
}

std::unordered_set<uint32_t> ui_pois;

int TryConnect(const char* hostname);
//void InitSingleplayer();
void GameModeCleanup();

static int mouseX = -1;
static int mouseY = -1;
std::vector<std::filesystem::path> worldList;
static void LoadWorlds() {
    worldList.clear();
    const std::filesystem::path worlds{PathifyUser("worlds")};
    for (auto const& dir_entry : std::filesystem::directory_iterator{worlds})
        worldList.push_back(dir_entry.path());
}
void DrawBackground()
{
    ClearBackground(BLACK);
    const float unit = 1;
    camera.position = { 0 };
    BeginMode3D(camera);
        rlSetTexture(cubemap.id);
        rlColor4ub(128, 128, 128, 255);
        rlRotatef(GetTime(), 0, 1, 0);
        rlBegin(RL_QUADS);
            rlTexCoord2f(2/6.0f, 0); rlVertex3f(-unit,  unit,  unit);
            rlTexCoord2f(2/6.0f, 1); rlVertex3f(-unit, -unit,  unit);
            rlTexCoord2f(3/6.0f, 1); rlVertex3f(-unit, -unit, -unit);
            rlTexCoord2f(3/6.0f, 0); rlVertex3f(-unit,  unit, -unit);

            rlTexCoord2f(1/6.0f, 0); rlVertex3f(unit,  unit,  unit);
            rlTexCoord2f(1/6.0f, 1); rlVertex3f(unit, -unit,  unit);
            rlTexCoord2f(2/6.0f, 1); rlVertex3f(-unit, -unit, unit);
            rlTexCoord2f(2/6.0f, 0); rlVertex3f(-unit,  unit, unit);

            rlTexCoord2f(0/6.0f, 0); rlVertex3f(unit,  unit,  -unit);
            rlTexCoord2f(0/6.0f, 1); rlVertex3f(unit, -unit,  -unit);
            rlTexCoord2f(1/6.0f, 1); rlVertex3f(unit, -unit, unit);
            rlTexCoord2f(1/6.0f, 0); rlVertex3f(unit,  unit, unit);

            rlTexCoord2f(3/6.0f, 0); rlVertex3f(-unit,  unit,  -unit);
            rlTexCoord2f(3/6.0f, 1); rlVertex3f(-unit, -unit,  -unit);
            rlTexCoord2f(4/6.0f, 1); rlVertex3f(unit, -unit, -unit);
            rlTexCoord2f(4/6.0f, 0); rlVertex3f(unit,  unit, -unit);

            rlTexCoord2f(4/6.0f, 0); rlVertex3f(unit,  unit,  -unit);
            rlTexCoord2f(4/6.0f, 1); rlVertex3f(unit, unit,  unit);
            rlTexCoord2f(5/6.0f, 1); rlVertex3f(-unit, unit, unit);
            rlTexCoord2f(5/6.0f, 0); rlVertex3f(-unit,  unit, -unit);

            rlTexCoord2f(6/6.0f, 1); rlVertex3f(-unit,  -unit,  -unit);
            rlTexCoord2f(6/6.0f, 0); rlVertex3f(-unit, -unit,  unit);
            rlTexCoord2f(5/6.0f, 0); rlVertex3f(unit, -unit, unit);
            rlTexCoord2f(5/6.0f, 1); rlVertex3f(unit,  -unit, -unit);
        rlEnd();
    EndMode3D();
}
void InitMenu() {
    snd_click = LoadSound(Pathify("sound/select.wav"));
    snd_hover = LoadSound(Pathify("sound/hover.wav"));
    cubemap = LoadTexture(Pathify("cubemap.png"));
}
void DrawTextB(const char *text, int posX, int posY, int fontSize, Color color)
{
    DrawTextEx(fnt, text, (Vector2){ posX+2, posY+2 }, fontSize, 1, BLACK);
    DrawTextEx(fnt, text, (Vector2){ posX, posY }, fontSize, 1, color);
}
static int lastHover = 0;
static int lastHover0 = 0;
bool Button(const char* text, int x, int y, int width, int id) {
    int screenID = MakeUIPOI(x, y);
    ui_pois.insert(screenID);

    Color shadow0 = BLACK;
    Color shadow1 = WHITE;
    Color fill = GRAY;
    bool mouseOnButton = mouseX >= x && mouseY >= y && mouseX <= x+width && mouseY <= y+32;
    if(mouseOnButton) {
        lastHover = MakeUIPOI(x, y);
        fill = BLACK;
        shadow0 = WHITE;
        shadow1 = GRAY;
    }
    else {
        if(lastHover == screenID) {
            lastHover = 0;
        }
    }
    DrawRectangle(x-2, y-2, width, 32, shadow1);
    DrawRectangle(x+2, y+2, width, 32, shadow0);
    DrawRectangle(x, y, width, 32, fill);
    DrawRectangleGradientV(x,y, width, 32, fill, ColorLerp(shadow0, fill, 0.5f));
    Vector2 measures = MeasureTextEx(fnt, text, DEFAULT_FONT_SIZE, 1);
    DrawTextB(text, x + width / 2 - measures.x / 2, y + 32 / 2 - measures.y / 2, DEFAULT_FONT_SIZE, WHITE);
    if((IsMouseButtonPressed(0) || IsGamepadButtonPressed(0, GAMEPAD_BUTTON_RIGHT_FACE_UP)) && mouseOnButton) {
        PlaySound(snd_click);
        return true;
    }
    return false;
}

bool ButtonInventory(int x, int y, int width, int height) {
    int screenID = MakeUIPOI(x, y);
    ui_pois.insert(screenID);

    Color fill = Fade(WHITE, 0);
    bool mouseOnButton = mouseX >= x && mouseY >= y && mouseX <= x+width && mouseY <= y+height;
    if(mouseOnButton) {
        lastHover = MakeUIPOI(x, y);
        fill = Fade(WHITE, 0.25f);
    }
    else {
        if(lastHover == screenID) {
            lastHover = 0;
        }
    }
    DrawRectangle(x, y, width, height, fill);
    if((IsMouseButtonPressed(0) || IsGamepadButtonPressed(0, GAMEPAD_BUTTON_RIGHT_FACE_UP)) && mouseOnButton) {
        PlaySound(snd_click);
        return true;
    }
    return false;
}

bool TextField(int id, char *buffer, int bufferSize, int x, int y, int width) {
    Color shadow0 = BLACK;
    Color shadow1 = WHITE;
    Color fill = DARKGRAY;
    static int focusedId = -1;        // currently focused field
    static int caretBlinkTimer = 0;  // frames
    static int caretPos = 0;         // insertion index within buffer

    // Measure mouse-over and focus
    bool mouseOnField = mouseX >= x && mouseY >= y && mouseX <= x+width && mouseY <= y+32;
    if(mouseOnField) {
        fill = BLACK;
        shadow0 = WHITE;
        shadow1 = GRAY;
    }
    if(focusedId == id) {
        fill = BLACK;
        shadow0 = WHITE;
        shadow1 = GRAY;
    }

    // Draw field
    DrawRectangle(x-2, y-2, width, 32, shadow1);
    DrawRectangle(x+2, y+2, width, 32, shadow0);
    DrawRectangle(x, y, width, 32, GRAY);
    DrawRectangle(x+2, y+2, width-4, 28, fill);

    // Click to focus
    if(IsMouseButtonPressed(0) && mouseOnField) {
        focusedId = id;
        // position caret near mouse x: approximate by measuring characters
        int len = (int)strlen(buffer);
        caretPos = len;
        float relX = mouseX - (x + 6); // 6px padding
        // walk characters to determine caret index
        float acc = 0;
        for(int i = 0; i <= len; ++i) {
            char temp = buffer[i];
            buffer[i] = '\0';
            Vector2 m = MeasureTextEx(fnt, buffer, DEFAULT_FONT_SIZE, 1);
            buffer[i] = temp;
            if(m.x >= relX) { caretPos = i; break; }
        }
        caretBlinkTimer = 0;
    } else if(IsMouseButtonPressed(0) && !mouseOnField) {
        // clicking elsewhere removes focus
        // keep focusedId unless clicking outside any field; simplistic: clear focus
        if(focusedId == id) focusedId = -1;
    }

    // Draw text with padding
    const int padding = 6;
    Vector2 measures = MeasureTextEx(fnt, buffer, DEFAULT_FONT_SIZE, 1);
    DrawTextB(buffer, x + padding, y + 32 / 2 - measures.y / 2, DEFAULT_FONT_SIZE, WHITE);

    bool edited = false;

    // If focused, handle keyboard input
    if(focusedId == id) {
        // Blink caret
        caretBlinkTimer = (caretBlinkTimer + 1) % 60; // 60-frame cycle
        if(caretBlinkTimer < 30) {
            // compute caret x position by measuring substring
            char tmp[256];
            int len = (int)strlen(buffer);
            int cp = caretPos;
            if(cp < 0) cp = 0;
            if(cp > len) cp = len;
            int copyLen = (cp < (int)sizeof(tmp)-1) ? cp : (int)sizeof(tmp)-1;
            memcpy(tmp, buffer, copyLen);
            tmp[copyLen] = '\0';
            Vector2 subMeas = MeasureTextEx(fnt, tmp, DEFAULT_FONT_SIZE, 1);
            int cx = x + padding + (int)subMeas.x;
            DrawRectangle(cx, y + 6, 2, 20, WHITE); // caret
        }

        // Handle control keys: backspace, delete, left/right
        int key;
        while((key = GetKeyPressed()) != 0) {
            std::cout << key << std::endl;
            if(key == 259) { // Backspace
                int len = (int)strlen(buffer);
                if(caretPos > 0 && len > 0) {
                    // shift left
                    memmove(buffer + caretPos - 1, buffer + caretPos, len - caretPos + 1);
                    caretPos--;
                    edited = true;
                }
            } else if(key == 127) { // Delete
                int len = (int)strlen(buffer);
                if(caretPos < len) {
                    memmove(buffer + caretPos, buffer + caretPos + 1, len - caretPos);
                    edited = true;
                }
            } else if(key == 9) { // Tab (remove focus)
                focusedId = -1;
            } else if(key == 13 || key == 10) {
                focusedId = -1;
            } else if(key == 27) {
                focusedId = -1;
            } else if(key == 1270) {

            } else {

            }
        }

        // Character input (UTF-8 aware limited)
        int c;
        while((c = GetCharPressed()) > 0) {
            if(c >= 32 && c <= 125) {
                int len = (int)strlen(buffer);
                if(len + 1 < bufferSize) {
                    memmove(buffer + caretPos + 1, buffer + caretPos, len - caretPos + 1);
                    buffer[caretPos] = (char)c;
                    caretPos++;
                    edited = true;
                }
            }
        }
        int finalLen = (int)strlen(buffer);
        if(caretPos < 0) caretPos = 0;
        if(caretPos > finalLen) caretPos = finalLen;
    }

    return edited;
}

const char* error = "";
void UIBegin() {
    ui_pois.clear();
}
void UIResetMousePos() {
    mouseX = -1;
    mouseY = -1;
    ui_pois.clear();
}
static int controlsDelay = 0;
void UIHandleControls() {
    if(lastHover0 != lastHover) {
        lastHover0 = lastHover;
        PlaySound(snd_hover);
    }
    if(mouseX == -1 || mouseY == -1) {
        std::vector<std::pair<int, int>> v;
        for (int poi : ui_pois) {
            int x = (poi >> 16) & 0xFFFF;
            int y = poi & 0xFFFF;
            v.push_back({y, x});
        }
        std::sort(v.begin(), v.end(), [](const auto &a, const auto &b){
            if (a.first != b.first) return a.first < b.first;
            return a.second < b.second;
        });
        mouseX = v[0].second;
        mouseY = v[0].first;
    }
    Vector2 dpad = InputGetDPAD();
    if(controlsDelay == 0) {
        if(dpad.y > 0.1f) {
            std::vector<std::pair<int, int>> v;
            for (int poi : ui_pois) {
                int x = (poi >> 16) & 0xFFFF;
                int y = poi & 0xFFFF;
                v.push_back({y, x});
            }
            std::sort(v.begin(), v.end(), [](const auto &a, const auto &b){
                if (a.first != b.first) return a.first > b.first;
                return a.second < b.second;
            });
            for (const auto &poi : v) {
                int x = poi.second;
                int y = poi.first;
                if(y < mouseY && x == mouseX) {
                    mouseX = x;
                    mouseY = y;
                    controlsDelay = 30;
                    return;
                }
            }
        }
        if(dpad.y < -0.1f) {
            std::vector<std::pair<int, int>> v;
            for (int poi : ui_pois) {
                int x = (poi >> 16) & 0xFFFF;
                int y = poi & 0xFFFF;
                v.push_back({y, x});
            }
            std::sort(v.begin(), v.end(), [](const auto &a, const auto &b){
                if (a.first != b.first) return a.first < b.first;
                return a.second < b.second;
            });
            for (const auto &poi : v) {
                int x = poi.second;
                int y = poi.first;
                if(y > mouseY && x == mouseX) {
                    mouseX = x;
                    mouseY = y;
                    controlsDelay = 30;
                    return;
                }
            }
        }
        if(dpad.x > 0.1f) {
            std::vector<std::pair<int, int>> v;
            for (int poi : ui_pois) {
                int x = (poi >> 16) & 0xFFFF;
                int y = poi & 0xFFFF;
                v.push_back({y, x});
            }
            std::sort(v.begin(), v.end(), [](const auto &a, const auto &b){
                if (a.first != b.first) return a.first > b.first;
                return a.second < b.second;
            });
            for (const auto &poi : v) {
                int x = poi.second;
                int y = poi.first;
                if(x > mouseX && y == mouseY) {
                    mouseX = x;
                    mouseY = y;
                    controlsDelay = 30;
                    return;
                }
            }
        }
        if(dpad.x < -0.1f) {
            std::vector<std::pair<int, int>> v;
            for (int poi : ui_pois) {
                int x = (poi >> 16) & 0xFFFF;
                int y = poi & 0xFFFF;
                v.push_back({y, x});
            }
            std::sort(v.begin(), v.end(), [](const auto &a, const auto &b){
                if (a.first != b.first) return a.first > b.first;
                return a.second < b.second;
            });
            for (const auto &poi : v) {
                int x = poi.second;
                int y = poi.first;
                if(x < mouseX && y == mouseY) {
                    mouseX = x;
                    mouseY = y;
                    controlsDelay = 30;
                    return;
                }
            }
        }
    }
    else {
        controlsDelay--;
    }

    if(!IsGamepadAvailable(0)) {
        mouseX = GetMouseX();
        mouseY = GetMouseY();
    }
}
extern std::map <std::string, int> controls;
const std::string LocaleGet(std::string loc);
const std::string GetKeycodeName(KeyboardKey key);
static int currently_selected_control = -1;
void DrawControls() {
    UIBegin();
    BeginDrawing();
        DrawBackground();
        const int base_x = GetScreenWidth() / 2 - (160 + 128) / 2;
        const int base_y = 64;
        int n = 0;
        for (const auto& [name, key] : controls) {
            const char *text = "...\0";
            if(n != currently_selected_control) {
                text = GetKeycodeName((KeyboardKey)key).c_str();
            }
            else {
                int _key = GetKeyPressed();
                if(_key != 0) {
                    controls[name] = _key;
                    currently_selected_control = -1;
                }
            }
            DrawTextB(LocaleGet(name).c_str(), base_x, base_y + n * 36, DEFAULT_FONT_SIZE, WHITE);
            if (Button(text, base_x + 160, base_y + n * 36, 128, 0)) {
                currently_selected_control = n;
            };
            n++;
        }
        const char *text = " ";
        if(swap_mouse) {
            text = "X";
        }
        DrawTextB(LocaleGet("swap_mouse").c_str(), base_x, base_y + n * 36, DEFAULT_FONT_SIZE, WHITE);
        if (Button(text, base_x + 160, base_y + n * 36, 32, 0)) {
            swap_mouse = !swap_mouse;
        };
        if (Button(LocaleGet("exit").c_str(), GetScreenWidth() / 2 - 64, GetScreenHeight() - 48, 128, 0)) {
            InputWriteControls();
            state = GAME_STATE_MAINMENU;
        };
    EndDrawing();
    UIHandleControls();
}
void DrawMainMenu() {
    UIBegin();
    BeginDrawing();
        DrawBackground();
        DrawText(GAME_NAME, 24, 24, 64, BLACK);
        DrawText(GAME_NAME, 16, 16, 64, WHITE);
        DrawTextB(GAME_VERSION, 16, 72, DEFAULT_FONT_SIZE, WHITE);
        if (Button(LocaleGet("select_level").c_str(), 16, 128, 128, 0)) {
            LoadWorlds();
            UIResetMousePos();
            state = GAME_STATE_WORLDSELECT;
            return;
        }
        if (Button(LocaleGet("multiplayer").c_str(), 16, 128+48, 128, 0)) {
            UIResetMousePos();
            state = GAME_STATE_MULTIPLAYERSELECT;
            error = "";
            return;
        }
        if (Button(LocaleGet("options").c_str(), 16, 128+48+48, 128, 0)) {
            UIResetMousePos();
            state = GAME_STATE_OPTIONS;
            return;
        }
        if (Button(LocaleGet("exit").c_str(), 16, 128+48+48+48, 128, 0)) {
            exit(0);
        }
    EndDrawing();
    UIHandleControls();
}
void _LoadWorld(const char* path);
void DrawWorldSelect() {
    UIBegin();
    BeginDrawing();
        DrawBackground();
        int i = 0;
        for(std::filesystem::path path : worldList) {
            if(Button(reinterpret_cast<const char*>(path.filename().u8string().c_str()), GetScreenWidth()/2-64, i*48+8, 128, 0)) {
                online = false;
                SetTargetFPS(-1);
                _LoadWorld(reinterpret_cast<const char*>(path.u8string().c_str()));
                return;
            }
            i++;
        }
        if (Button(LocaleGet("exit").c_str(), GetScreenWidth()/2-196, GetScreenHeight()-48, 128, 0)) {
            UIResetMousePos();
            state = GAME_STATE_MAINMENU;
            return;
        }
        if (Button(LocaleGet("create_world").c_str(), GetScreenWidth()/2+196-128, GetScreenHeight()-48, 128, 0)) {
            UIResetMousePos();
            //InitSingleplayer();
            return;
        }
        if (Button(LocaleGet("create_world").c_str(), GetScreenWidth()/2+196-128, GetScreenHeight()-48-48, 128, 0)) {
            UIResetMousePos();
            InitWorldgen(2);
            state = GAME_STATE_LOADING;
            return;
        }
    EndDrawing();
    UIHandleControls();
}
char buffer[64];

void DrawMultiplayer() {
    UIBegin();
    BeginDrawing();
        DrawBackground();
        bool changed = TextField(1, buffer, sizeof(buffer), GetScreenWidth()/2-100, 32, 200);
        if (Button(LocaleGet("join").c_str(), GetScreenWidth()/2-196, GetScreenHeight()-48, 128, 0)) {
            int code = TryConnect(buffer);
            if(code != -1) {
                switch (code)
                {
                    case NET_ERROR_NOTFOUND:
                        error = LocaleGet("error_notfound").c_str();
                        break;
                    case NET_ERROR_INTERNAL:
                        error = LocaleGet("error_internal").c_str();
                        break;
                    case NET_ERROR_FAILED:
                        error = LocaleGet("error_unknown").c_str();
                        break;
                    default:
                        break;
                }
            }
        }
        if (Button(LocaleGet("exit").c_str(), GetScreenWidth()/2+196-128, GetScreenHeight()-48, 128, 0)) {
            state = GAME_STATE_MAINMENU;
            UIResetMousePos();
            return;
        }
        DrawTextB(error, GetScreenWidth()/2-100, 128, DEFAULT_FONT_SIZE, WHITE);
    EndDrawing();
    UIHandleControls();
}

void DrawPauseMenu() {
    UIBegin();
    DrawRectangle(0,0,GetScreenWidth(),GetScreenHeight(),Fade(GRAY,0.5f));
    if (Button(LocaleGet("exit").c_str(), GetScreenWidth()/2-64, 48, 128, 0)) {
        GameModeCleanup();
        UIResetMousePos();
        return;
    }
    UIHandleControls();
}
char bufferChat[MAX_MESSAGE_LENGTH];
char* DrawChat() {
    const int x = 0;
    const int y = 0;
    const int h = 240;
    const int buttonWidth = 120;
    UIBegin();
    DrawRectangle(x,y,GetScreenWidth(),h,Fade(GRAY,0.5f));
    bool changed = TextField(1, bufferChat, sizeof(bufferChat), x, y+h, GetScreenWidth());
    bool send = Button(LocaleGet("send").c_str(), x+GetScreenWidth()-buttonWidth, y+h, buttonWidth, 0) || IsKeyPressed(KEY_ENTER);
    if(send) {
        return bufferChat; 
    }
    UIHandleControls();
    return nullptr;
}
void DrawLogoCenter() {
    BeginDrawing();
        Vector2 m = MeasureTextEx(GetFontDefault(), GAME_NAME, 64, 0);
        DrawText(GAME_NAME, GetScreenWidth()/2  - m.x / 2 - 8, GetScreenHeight()/2 - m.y / 2 - 8, 64, BLACK);
        DrawText(GAME_NAME, GetScreenWidth()/2  - m.x / 2, GetScreenHeight()/2 - m.y / 2, 64, WHITE);
    EndDrawing();
}
#endif