#ifndef _INSTANCE_HPP
#define _INSTANCE_HPP
#include "config.hpp"
#include "uid.hpp"
#include "serviceBase.hpp"
#include "utility.hpp"
#include "incompletes.hpp"
#include "exceptions.hpp"

#include <uvpp.hpp>
#include <async/result.hpp>
#include <async/oneshot-event.hpp>
#include <frg/std_compat.hpp>
#include <fmt/format.h>
#include <string>
#include <string_view>
#include <optional>
#include <unordered_map>
#include <vector>
#include <memory>
#include <ctime>



struct Event { // DON'T std::move() this class. EVER!!!
    AnyUID sender;
    std::string name, raw_args, text;
    std::vector<std::string_view> args;

    std::string dump() const {
        return fmt::format(":{} {} {}{}\n", sender.str(), name, raw_args, (text.empty()?"":" :"+text));
    }
    void parse(std::string_view str);
    void splitArgs() {
        args = Utility::strSplit(raw_args, ' ');
    }
};

struct Command {
    std::string name, args, text;

    std::string dump() const {
        return fmt::format("{} {}{}\n", name, args, (text.empty()?"":" :"+text));
    }
    void parse(std::string_view str);
};

struct ModeSet {
    std::string str;
    std::vector<std::tuple<char, std::string>> params;

    template<bool channelModes>
    void parse(std::string_view str, NetworkInfo& netInfo);
};

struct Channel {
    SUID server;
    std::string name;
    ModeSet mode;
    std::string topic;
    std::vector<u_User*> members;

    void parse_sjoin(const Event &event, Cache& cache, NetworkInfo& netInfo);
    void removeMember(const u_User& member);

    Event get_sjoin(const UUID& initial_user) const {
        return {
            .sender = SUID(server),
            .name = "SJOIN",
            .raw_args = fmt::format("{} {} +{}", time(nullptr), name, mode.str),
            .text = fmt::format("@{}", initial_user.str())
        };
    }
    Event get_topic(std::string_view topic, const UUID& user) const {
        return {
            .sender = user,
            .name = "TOPIC",
            .raw_args = name,
            .text = std::string(topic)
        };
    }
};

struct User {
    SUID server;
    std::string nick;
    size_t hops = 1;
    ModeSet umode;
    std::string ident;
    std::optional<std::string> vhost;
    std::optional<std::string> ip;
    std::string realhost;
    UUID uid;
    std::string realname;
    std::vector<u_Channel*> channels;
    std::optional<std::string> loginName;

    void parse_euid(const Event &event, NetworkInfo& netInfo);
    void removeChannel(const u_Channel &channel);

    Event get_euid() const {
        return {
            .sender = SUID(server),
            .name = "EUID",
                        //           nick
                        //           |  hops
                        //           |  | ts
                        //           |  | |  umode
                        //           |  | |  |   ident
                        //           |  | |  |   |  vhost
                        //           |  | |  |   |  |  ip
                        //           |  | |  |   |  |  |  uid
                        //           |  | |  |   |  |  |  |  realhost
                        //           |  | |  |   |  |  |  |  |  account
                        //           |  | |  |   |  |  |  |  |  |  realname
            .raw_args = fmt::format("{} 1 {} +{} {} {} {} {} {} {} :{}", nick, time(nullptr), umode.str, ident, vhost.value_or(realhost), ip.value_or("*"), uid.str(), realhost, loginName.value_or("*"), realname)
        };
    }
    Event get_join(std::string_view channelName) const {
        return {
            .sender = uid,
            .name = "JOIN",
            .raw_args = fmt::format("{} {} +", time(nullptr), channelName)
        };
    }
    Event get_part(std::string_view channelName, std::string_view message = "") const {
        return {
            .sender = uid,
            .name = "PART",
            .raw_args = std::string(channelName),
            .text = std::string(message)
        };
    }
    Event get_quit(std::string_view message = "") const {
        return {
            .sender = uid,
            .name = "QUIT",
            .text = std::string(message)
        };
    }

    Event get_encap_su(std::string_view login_name) {
        //:<my_sid> ENCAP * SU <user_uid> <new_account_name>
        return {
            .sender = server,
            .name = "ENCAP",
            .raw_args = fmt::format("* SU {} {}", uid.str(), login_name)
        };
    }

    Event get_privmsg(std::string_view message, AnyUID target) const {
        return {
            .sender = uid,
            .name = "PRIVMSG",
            .raw_args = std::string(target.str()),
            .text = std::string(message)
        };
    }
    Event get_notice(std::string_view message, AnyUID target) const {
        return {
            .sender = uid,
            .name = "NOTICE",
            .raw_args = std::string(target.str()),
            .text = std::string(message)
        };
    }
};

struct Cache {
    std::vector<u_User> users;
    std::vector<u_Channel> channels;

    std::vector<u_User>::iterator find_user_by_nick(std::string_view nick);
    std::vector<u_User>::iterator find_user_by_uid(const UUID& uid);
    std::vector<u_Channel>::iterator find_channel_by_name(std::string_view name);
};

struct NetworkInfo {
    async::oneshot_event ready_event;
    size_t fields_received = 0;
    bool ready = false;
    std::string name;

    struct {
        // list modes first, then modes that take a param when setting but not when unsetting (just +k), then modes that can only be set once and take args, then modes that can only be set once but take no args.
        std::string listModes, // Lists like mode b
                    paramOnSetAndUnsetModes, // Properties like k
                    paramOnSetOnlyModes, // Properties like f
                    paramLessModes; // Properties like z
        std::unordered_map<char, char> prefixMap; // Maps for example '@' to 'o'

        bool isListMode(char mode) {
            return listModes.find(mode) != std::string::npos;
        }
        bool takesNoParam(char mode) {
            return paramLessModes.find(mode) != paramLessModes.npos;
        }
        bool takesParamOnUnset(char mode) {
            return isListMode(mode) ||
                   paramOnSetAndUnsetModes.find(mode) != std::string::npos;
        }
        bool takesParamOnSet(char mode) {
            return paramOnSetOnlyModes.find(mode) != std::string::npos ||
                   takesParamOnUnset(mode);
        }
    } channelModes;

    async::result<void> wait_ready() {
        if (!ready) {
            co_await ready_event.wait();
        }
    }

    void mark_ready() {
        ready = true;
        ready_event.raise();
    }
};

class Instance {
    std::reference_wrapper<uvpp::loop_service> s;
    uvpp::Addr addr;
    size_t lastUUID = 0;

    bool server_bursting = true, client_bursting = true;
    bool authed = false;

public:
    std::unique_ptr<uvpp::tcp> socket;
    const Config config;
    Config::Server connected_server;
    NetworkInfo netInfo;
    Cache cache;

    std::vector<std::unique_ptr<ServiceBase>> services;

    Instance(uvpp::loop_service &s, const Config& config) : s(s), config(config) {
        addr = uvpp::make_ipv4(config.connection.addr, config.connection.port);
    }

    async::result<void> run();
    async::result<void> login();
    async::result<void> burst();
    async::result<void> process(const Command);
    async::result<void> process(Event);

    async::result<void> send_event(const Event&);
    async::result<void> send_ping();

    // utils.cpp
    UUID UUIDGen();
};
#endif