#pragma once

#include "../net/stream.hpp"

#include <boost/asio.hpp>

#include <openssl/evp.h>
#include <openssl/x509.h>

#include <functional>
#include <memory>
#include <optional>
#include <vector>

struct lua_State;

struct Settings
{
    bool tls;
    std::string host;
    std::uint16_t port;

    X509* client_cert;
    EVP_PKEY* client_key;
    std::string verify;
    std::string sni;

    std::string socks_host;
    std::uint16_t socks_port;
    std::string socks_user;
    std::string socks_pass;

    std::size_t buffer_size;
};

class irc_connection final : public std::enable_shared_from_this<irc_connection>
{
public:
    using stream_type = CommonStream;
    static std::size_t const irc_buffer_size = 131'072;

private:
    stream_type stream_;
    boost::asio::ip::tcp::resolver resolver_;
    std::vector<int> write_refs;
    std::vector<boost::asio::const_buffer> write_buffers;
    lua_State* L;
    bool writing_;

    struct Private
    {
    };

public:
    irc_connection(Private, boost::asio::io_context&, lua_State*);
    ~irc_connection();

    auto operator=(irc_connection const&) -> irc_connection& = delete;
    auto operator=(irc_connection&&) -> irc_connection& = delete;
    irc_connection(irc_connection const&) = delete;
    irc_connection(irc_connection&&) = delete;

    auto static create(boost::asio::io_context& io_context, lua_State* const L) -> std::shared_ptr<irc_connection>
    {
        return std::make_shared<irc_connection>(Private{}, io_context, L);
    }

    auto get_stream() -> stream_type&
    {
        return stream_;
    }

    auto set_stream(stream_type&& stream)
    {
        stream_ = std::move(stream);
    }

    auto get_lua() const -> lua_State*
    {
        return L;
    }

    // Queue messages for writing
    /**
     * @brief Write a message to the output stream
     *
     * @param msg The string to write including any needed line-terminators
     * @param ref A reference generated by luaL_ref keeping this string valid
     */
    auto write(std::string_view msg, int ref) -> void;

    auto close() -> void;

    auto connect(Settings) -> boost::asio::awaitable<std::string>;

private:
    // There's data now, actually write it
    auto write_actual() -> void;
};
