// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.

#define CAF_SUITE net.doorman

#include "caf/net/doorman.hpp"

#include "caf/binary_serializer.hpp"
#include "caf/error.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/ip.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/uri.hpp"

#include "caf/test/dsl.hpp"

#include "caf/net/test/host_fixture.hpp"

using namespace caf;
using namespace caf::net;
using namespace std::literals::string_literals;

namespace {

struct fixture : test_coordinator_fixture<>, host_fixture {
  fixture() {
    mpx = std::make_shared<multiplexer>();
    if (auto err = mpx->init())
      CAF_FAIL("mpx->init failed: " << err);
    mpx->set_thread_id();
    CAF_CHECK_EQUAL(mpx->num_socket_managers(), 1u);
    auth.port = 0;
    auth.host = "0.0.0.0"s;
  }

  bool handle_io_event() override {
    return mpx->poll_once(false);
  }

  multiplexer_ptr mpx;
  uri::authority_type auth;
};

class dummy_application {
public:
  template <class Parent>
  error init(Parent&) {
    return none;
  }

  template <class Parent>
  error write_message(Parent& parent,
                      std::unique_ptr<endpoint_manager_queue::message> msg) {
    auto payload_buf = parent.next_payload_buffer();
    binary_serializer sink{parent.system(), payload_buf};
    if (auto err = sink(msg->msg->payload))
      CAF_FAIL("serializing failed: " << err);
    parent.write_packet(payload_buf);
    return none;
  }

  template <class Parent>
  error handle_data(Parent&, span<const byte>) {
    return none;
  }

  template <class Parent>
  void resolve(Parent&, string_view path, const actor& listener) {
    anon_send(listener, resolve_atom_v,
              "the resolved path is still "
                + std::string(path.begin(), path.end()));
  }

  template <class Parent>
  void timeout(Parent&, const std::string&, uint64_t) {
    // nop
  }

  template <class Parent>
  void new_proxy(Parent&, actor_id) {
    // nop
  }

  template <class Parent>
  void local_actor_down(Parent&, actor_id, error) {
    // nop
  }

  void handle_error(sec) {
    // nop
  }
};

class dummy_application_factory {
public:
  using application_type = dummy_application;

  template <class Parent>
  error init(Parent&) {
    return none;
  }

  application_type make() const {
    return dummy_application{};
  }
};

} // namespace

CAF_TEST_FIXTURE_SCOPE(doorman_tests, fixture)

CAF_TEST(doorman accept) {
  auto acceptor = unbox(make_tcp_accept_socket(auth, false));
  auto port = unbox(local_port(socket_cast<network_socket>(acceptor)));
  auto acceptor_guard = make_socket_guard(acceptor);
  CAF_MESSAGE("opened acceptor on port " << port);
  auto mgr = make_endpoint_manager(
    mpx, sys,
    doorman<dummy_application_factory>{acceptor_guard.release(),
                                       dummy_application_factory{}});
  CAF_CHECK_EQUAL(mgr->init(), none);
  auto before = mpx->num_socket_managers();
  CAF_CHECK_EQUAL(before, 2u);
  uri::authority_type dst;
  dst.port = port;
  dst.host = "localhost"s;
  CAF_MESSAGE("connecting to doorman on: " << dst);
  auto conn = make_socket_guard(unbox(make_connected_tcp_stream_socket(dst)));
  CAF_MESSAGE("waiting for connection");
  while (mpx->num_socket_managers() != before + 1)
    run();
  CAF_MESSAGE("connected");
}

CAF_TEST_FIXTURE_SCOPE_END()
