#ifndef GATEWAY_WEBSOCKET_HPP_
#define GATEWAY_WEBSOCKET_HPP_
#include <includes.h>
#include <thread>
#include <chrono>
#include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXWebSocket.h>
using namespace std::chrono;
using namespace std::chrono_literals;
class WebSocket {
private:
    bool isBot;
    int intents;
    std::string token;
    ix::WebSocket webSocket;
    nlohmann::json payload = { {"op", 1},{"d", nullptr} }, id;
    std::unordered_map<std::string, std::function<void(const nlohmann::json&)>> eventHandlers;
    WebSocket& operator=(const WebSocket&) = delete;
    WebSocket(const WebSocket&) = delete;
    WebSocket(const std::string& token, const int& intents, const bool& isBot) {
        WebSocket::token = token;
        WebSocket::intents = intents;
        WebSocket::isBot = isBot;
        id = {
            {"op", 2},
            {"d", {
                {"token", token},
                {"intents", intents},
                {"properties", {
                    {"os", "linux"},
                    {"browser", "firefox"},
                    {"device", "firefox"}
                }},
                //{"compress", 1},
                {"presence", {
                    {"activities", nlohmann::json::array({
                        {
                            //{"name", "asdsadsadsadsa"},
                            //{"type", 2}
                        }
                    })},
                    {"status", "idle"},
                    {"since", 91879201},
                    {"afk", false}
                }}
            }}
        };
        ix::initNetSystem();
        webSocket.setUrl("wss://gateway.discord.gg/?v=10&encoding=json");
        webSocket.setHandshakeTimeout(5);
        Log::create(INFO, WEBSOCKET, "ixwebsocket init");
        webSocket.setOnMessageCallback([this, res = nlohmann::json(), heartbeat_interval = 0, connected = false](const ix::WebSocketMessagePtr& msg) mutable {
            if (msg->type == ix::WebSocketMessageType::Message) {
                res = nlohmann::json::parse(msg->str);
                Log::create(INFO, WEBSOCKET, res["op"].dump() + " " + res["t"].dump());
                switch (res["op"].get<int>()) {
                case 10:
                    heartbeat_interval = res["d"]["heartbeat_interval"].get<int>();
                    !connected ? connected = true, webSocket.send(id.dump()) : 0;
                    std::thread([this, &heartbeat_interval, &connected]() {
                        while (connected && heartbeat_interval != -1) {
                            Log::create(INFO, WEBSOCKET, "Heartbeat " + std::to_string(heartbeat_interval));
                            std::this_thread::sleep_for(milliseconds(heartbeat_interval));
                            webSocket.send(payload.dump());
                        }
                    }).detach();
                    break;
                case 0:
                    if (eventHandlers.find(res["t"].get<std::string>()) != eventHandlers.end()) {
                        eventHandlers[res["t"].get<std::string>()](res);
                    }
                    break;
                }
            }
        });
        webSocket.start();
    }
public:
    /*
    void sendPresenceUpdate(int statusType, const std::string& activityName) {
        json prsUpdate = {
            {"op", 3},
            {"d", {
                {"since", 0},
                {"activities", json::array({{"name", activityName}, {"type", 2}})},
                {"status", statusType == 1 ? "online" : "idle"},
                {"afk", false}
            }}
        };
        webSocket.send(prsUpdate.dump());
    }
    */
    static WebSocket& getInstance(const std::string& token = "", const int intents = 0, bool bot = true) {
        Log::create(WARNING, WEBSOCKET, "Instance event");
        static WebSocket instance(token, intents, bot);
        return instance;
    }
    ~WebSocket() {
        webSocket.close();
        ix::uninitNetSystem();
    }
    std::string getToken() const {
        return isBot ? std::string("Bot " + WebSocket::token) : WebSocket::token;
    }
    int getIntents() const {
        return WebSocket::intents;
    }
    void on(const std::string& event, std::function<void(const nlohmann::json&)> handler) {
        eventHandlers[event] = [handler](const nlohmann::json& message) {
            handler(message.get<nlohmann::json>());
        };
    }
    void once(const std::string& event, std::function<void(const nlohmann::json&)> handler) {
        eventHandlers[event] = [event, handler, isCalled = false](const nlohmann::json& message) mutable {
            isCalled == false ? isCalled = true : 0, handler(message.get<nlohmann::json>());
        };
    }
    void start() {
        while (1) std::this_thread::sleep_for(1ms);
    }
};
#endif