#include <atomic>
#include <memory>

#include "proto/service.pb.h"
#include <mesytec-mnode/mnode_nng_async.h>
#include <mesytec-mvlc/mesytec-mvlc.h>
#include <mesytec-mvlc/util/signal_handling.h>

using namespace mesytec;
using namespace mesytec::mnode;
using namespace std::literals;

class PingClient: public nng::AsyncReqWork
{
  public:
    explicit PingClient(nng_socket socket)
        : AsyncReqWork(socket)
    {
    }

    nng::unique_msg make_request() override
    {
        ping_.set_peer_id(peer_id);
        ping_.set_sequence_number(sequence_number++);
        return nng::make_message(ping_.SerializeAsString());
    }

    void handle_reply(nng::unique_msg &&request, nng::unique_msg &&reply) override
    {
        ping_.ParseFromArray(nng_msg_body(request.get()), nng_msg_len(request.get()));
        pong_.ParseFromArray(nng_msg_body(reply.get()), nng_msg_len(reply.get()));

        if (ping_.sequence_number() != pong_.sequence_number())
            spdlog::error("Work: sequence_number mismatch: ping={}, pong={}",
                          ping_.sequence_number(), pong_.sequence_number());

        if (ping_.peer_id() != pong_.peer_id())
            spdlog::error("Work: peer_id mismatch: ping={}, pong={}", ping_.peer_id(),
                          pong_.peer_id());

        ++num_transactions;
    }

    void report()
    {
        spdlog::info("PingClient, peer_id={}, transactions={}", peer_id, num_transactions);
    }

  private:
    proto::Ping ping_;
    proto::Pong pong_;
    size_t sequence_number = 1;
    static std::atomic<size_t> next_peer_id;
    size_t peer_id = next_peer_id++;
    size_t num_transactions = 0;
};

std::atomic<size_t> PingClient::next_peer_id;

int main()
{
    spdlog::set_level(spdlog::level::info);

    auto socket = nng::make_req_socket(1000);

    if (int res = nng_dial(socket, "tcp://localhost:5555", nullptr, NNG_FLAG_NONBLOCK))
    {
        nng::mnode_nng_error("nng_dial", res);
        return res;
    }

    mvlc::util::Stopwatch sw;
    std::vector<std::unique_ptr<PingClient>> clients;

    for (int i = 0; i < 10; ++i)
        clients.emplace_back(std::make_unique<PingClient>(socket));

    for (auto &client: clients)
        client->work();

    for (;;)
    {
        std::this_thread::sleep_for(100ms);
        if (sw.get_interval() >= 1s)
        {
            for (auto &client: clients)
                client->report();
            sw.interval();
        }
    }

    return 0;
}
