#include <mesytec-mnode/mnode_nng.h>

#include <spdlog/spdlog.h>

namespace mesytec::mnode::nng
{

void mnode_nng_fatal(const char *const msg, int rv)
{
    spdlog::error("{} ({})", msg, nng_strerror(rv));
    std::abort();
}

void mnode_nng_error(const std::string &msg, int rv)
{
    spdlog::error("{} ({})", msg, nng_strerror(rv));
}

void log_socket_info(nng_socket s, const std::string &info_text)
{
    auto url = socket_get_string_opt(s, NNG_OPT_URL);
    auto sockName = socket_get_string_opt(s, NNG_OPT_SOCKNAME);
    auto localAddress = socket_get_string_opt(s, NNG_OPT_LOCADDR);
    auto remoteAddress = socket_get_string_opt(s, NNG_OPT_REMADDR);

    spdlog::info("{}: {}={}", info_text, NNG_OPT_URL, url);
    spdlog::info("{}: {}={}", info_text, NNG_OPT_SOCKNAME, sockName);
    spdlog::info("{}: {}={}", info_text, NNG_OPT_LOCADDR, localAddress);
    spdlog::info("{}: {}={}", info_text, NNG_OPT_REMADDR, remoteAddress);
}

void log_pipe_info(nng_pipe p, const char *info_text)
{
    auto sockName = pipe_get_string_opt(p, NNG_OPT_SOCKNAME);
    auto localAddress = pipe_get_string_opt(p, NNG_OPT_LOCADDR);
    auto remoteAddress = pipe_get_string_opt(p, NNG_OPT_REMADDR);

    spdlog::info("{}: {}={}", info_text, NNG_OPT_SOCKNAME, sockName);
    spdlog::info("{}: {}={}", info_text, NNG_OPT_LOCADDR, localAddress);
    spdlog::info("{}: {}={}", info_text, NNG_OPT_REMADDR, remoteAddress);
}

const char *nng_stat_unit_to_string(int nng_unit)
{
    switch (nng_unit)
    {
    case NNG_UNIT_BYTES:
        return "B";
    case NNG_UNIT_MESSAGES:
        return "msgs";
    case NNG_UNIT_MILLIS:
        return "ms";
    case NNG_UNIT_EVENTS:
        return "events";
    }

    return nullptr;
}

const char *nng_stat_type_to_string(int nng_stat_type)
{
    switch (nng_stat_type)
    {
    case NNG_STAT_SCOPE:
        return "scope";
    case NNG_STAT_LEVEL:
        return "level";
    case NNG_STAT_COUNTER:
        return "counter";
    case NNG_STAT_STRING:
        return "string";
    case NNG_STAT_BOOLEAN:
        return "bool";
    case NNG_STAT_ID:
        return "id";
    }

    return nullptr;
}

template <typename Value>
std::string format_stat(int type, const char *name, const char *desc, std::uint64_t ts, Value value,
                        int unit)
{
    return fmt::format("type={}, name={}, desc={}, ts={}, value={}, unit={}",
                       nng::nng_stat_type_to_string(type), name, desc, ts, value,
                       nng::nng_stat_unit_to_string(unit));
}

std::string format_stat(nng_stat *stat)
{
    switch (nng_stat_type(stat))
    {
    case NNG_STAT_BOOLEAN:
        return format_stat(nng_stat_type(stat), nng_stat_name(stat), nng_stat_desc(stat),
                           nng_stat_timestamp(stat), nng_stat_value(stat), nng_stat_unit(stat));
    default:
        return format_stat(nng_stat_type(stat), nng_stat_name(stat), nng_stat_desc(stat),
                           nng_stat_timestamp(stat), nng_stat_value(stat), nng_stat_unit(stat));
    }
}

int send_message_retry(nng_socket socket, nng_msg *msg, retry_predicate rp, const char *debugInfo)
{
    int res = 0;

    do
    {
        res = nng_sendmsg(socket, msg, 0);

        if (res)
        {
            if (res != NNG_ETIMEDOUT)
            {
                spdlog::warn("send_message_retry: {} - send failed: {} (msg={})", debugInfo,
                             nng_strerror(res), fmt::ptr(msg));
                return res;
            }

            if (res == NNG_ETIMEDOUT)
                spdlog::trace("send_message_retry: {} - send timeout (msg={})", debugInfo,
                              fmt::ptr(msg));

            if (!rp(res))
                return res;
        }
    }
    while (res == NNG_ETIMEDOUT);

    return 0;
}

int send_empty_message(nng_socket socket, retry_predicate rp)
{
    nng_msg *msg = nullptr;

    if (int res = allocate_reserve_message(&msg, 0))
        return res;

    if (int res = send_message_retry(socket, msg, rp, "send_empty_message"))
    {
        nng_msg_free(msg);
        return res;
    }

    return 0;
}

nng_socket make_pair_socket(nng_duration timeout) { return make_socket(nng_pair0_open, timeout); }

nng_socket make_push_socket(nng_duration timeout) { return make_socket(nng_push0_open, timeout); }

nng_socket make_pull_socket(nng_duration timeout) { return make_socket(nng_pull0_open, timeout); }

nng_socket make_pub_socket(nng_duration timeout) { return make_socket(nng_pub0_open, timeout); }

nng_socket make_sub_socket(nng_duration timeout) { return make_socket(nng_sub0_open, timeout); }

nng_socket make_req_socket(nng_duration timeout) { return make_socket(nng_req0_open, timeout); }

nng_socket make_rep_socket(nng_duration timeout) { return make_socket(nng_rep0_open, timeout); }

} // namespace mesytec::mnode::nng
