#include "registration.hpp"

#include "connection.hpp"
#include "ircmsg.hpp"
#include "sasl_mechanism.hpp"

#include <memory>
#include <random>
#include <unordered_map>

Registration::Registration(
    Settings settings,
    std::shared_ptr<Client> client
)
    : settings_{std::move(settings)}
    , client_{std::move(client)}
{
}

auto Registration::on_connect() -> void
{
    auto &connection = client_->get_connection();

    client_->list_caps();
    caps_slot_ = client_->sig_cap_ls.connect([self = shared_from_this()](auto &caps) {
        self->caps_slot_.disconnect();
        self->on_cap_list(caps);
    });

    slot_ = connection.sig_ircmsg.connect(
        [self = shared_from_this()](const auto cmd, auto &msg)
        {
            self->on_ircmsg(cmd, msg);
        }
    );

    if (not settings_.password.empty())
    {
        connection.send_pass(settings_.password);
    }
    connection.send_user(settings_.username, settings_.realname);
    connection.send_nick(settings_.nickname);
}

auto Registration::on_cap_list(const std::unordered_map<std::string, std::string> &caps) -> void
{
    std::string request;
    static const char * const want [] {
        "account-notify",
        "account-tag",
        "batch",
        "chghost",
        "draft/chathistory",
        "extended-join",
        "invite-notify",
        "server-time",
        "setname",
        "soju.im/no-implicit-names",
        "solanum.chat/identify-msg",
        "solanum.chat/oper",
        "solanum.chat/realhost",
    };

    for (const auto cap : want)
    {
        if (caps.contains(cap))
        {
            request.append(cap);
            request.push_back(' ');
        }
    }

    bool do_sasl = not settings_.sasl_mechanism.empty() && caps.contains("sasl");
    if (do_sasl) {
        request.append("sasl ");
    }

    if (not request.empty())
    {
        request.pop_back(); // trailing space
        client_->get_connection().send_cap_req(request);
    }

    if (do_sasl && settings_.sasl_mechanism == "PLAIN") {
        client_->start_sasl(
            std::make_unique<SaslPlain>(
                    settings_.sasl_authcid,
                    settings_.sasl_authzid,
                    settings_.sasl_password));
    } else if (do_sasl && settings_.sasl_mechanism == "EXTERNAL") {
        client_->start_sasl(std::make_unique<SaslExternal>(settings_.sasl_authzid));
    } else {
        client_->get_connection().send_cap_end();
    }
}

auto Registration::start(
    Settings settings,
    std::shared_ptr<Client> client
) -> std::shared_ptr<Registration>
{
    const auto thread = std::make_shared<Registration>(std::move(settings), std::move(client));

    thread->slot_ = thread->client_->get_connection().sig_connect.connect([thread]() {
        thread->slot_.disconnect();
        thread->on_connect();
    });

    return thread;
}

auto Registration::randomize_nick() -> void
{
    std::string new_nick;
    new_nick += settings_.nickname.substr(0, 8);

    std::random_device rd;
    std::mt19937 gen{rd()};
    std::uniform_int_distribution<> distrib(0, 35);

    for (int i = 0; i < 8; ++i) {
        const auto x = distrib(gen);
        new_nick += x < 10 ? '0' + x : 'A' + (x-10);
    }

    client_->get_connection().send_nick(new_nick);
}

auto Registration::on_ircmsg(const IrcCommand cmd, const IrcMsg &msg) -> void
{
    switch (cmd)
    {
    default: break;

    case IrcCommand::ERR_NICKNAMEINUSE:
    case IrcCommand::ERR_ERRONEUSNICKNAME:
    case IrcCommand::ERR_UNAVAILRESOURCE:
        randomize_nick();
        break;

    case IrcCommand::RPL_WELCOME:
        slot_.disconnect();
        caps_slot_.disconnect();
        break;

    case IrcCommand::RPL_SASLSUCCESS:
    case IrcCommand::ERR_SASLFAIL:
        client_->get_connection().send_cap_end();
        break;
    }
}