#include "cmUVHandlePtr.h"

#include <algorithm>
#include <chrono>
#include <iostream>
#include <thread>

#include "cm_uv.h"

static void signal_reset_fn(uv_async_t* handle)
{
  auto ptr = static_cast<cm::uv_async_ptr*>(handle->data);
  ptr->reset();
}

// A common pattern is to use an async signal to shutdown the server.
static bool testAsyncShutdown()
{
  uv_loop_t Loop;
  auto err = uv_loop_init(&Loop);
  if (err != 0) {
    std::cerr << "Could not init loop" << std::endl;
    return false;
  }

  {
    cm::uv_async_ptr signal;
    signal.init(Loop, &signal_reset_fn, &signal);

    std::thread([&] {
      std::this_thread::sleep_for(std::chrono::seconds(2));
      signal.send();
    }).detach();

    if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) {
      std::cerr << "Unclean exit state in testAsyncDtor" << std::endl;
      return false;
    }

    if (signal.get()) {
      std::cerr << "Loop exited with signal not being cleaned up" << std::endl;
      return false;
    }
  }

  uv_loop_close(&Loop);

  return true;
}

static void signal_fn(uv_async_t*)
{
}

// Async dtor is sort of a pain; since it locks a mutex we must be sure its
// dtor always calls reset otherwise the mutex is deleted then locked.
static bool testAsyncDtor()
{
  uv_loop_t Loop;
  auto err = uv_loop_init(&Loop);
  if (err != 0) {
    std::cerr << "Could not init loop" << std::endl;
    return false;
  }

  {
    cm::uv_async_ptr signal;
    signal.init(Loop, signal_fn);
  }

  if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) {
    std::cerr << "Unclean exit state in testAsyncDtor" << std::endl;
    return false;
  }

  uv_loop_close(&Loop);

  return true;
}

// Async needs a relatively stateful deleter; make sure that is properly
// accounted for and doesn't try to hold on to invalid state when it is
// moved
static bool testAsyncMove()
{
  uv_loop_t Loop;
  auto err = uv_loop_init(&Loop);
  if (err != 0) {
    std::cerr << "Could not init loop" << std::endl;
    return false;
  }

  {
    cm::uv_async_ptr signal;
    {
      cm::uv_async_ptr signalTmp;
      signalTmp.init(Loop, signal_fn);
      signal = std::move(signalTmp);
    }
  }

  if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) {
    std::cerr << "Unclean exit state in testAsyncDtor" << std::endl;
    return false;
  }

  uv_loop_close(&Loop);
  return true;
}

// When a type is castable to another uv type (pipe -> stream) here,
// and the deleter is convertible as well, we should allow moves from
// one type to the other.
static bool testCrossAssignment()
{
  uv_loop_t Loop;
  auto err = uv_loop_init(&Loop);
  if (err != 0) {
    std::cerr << "Could not init loop" << std::endl;
    return false;
  }

  {
    cm::uv_pipe_ptr pipe;
    pipe.init(Loop, 0);

    cm::uv_stream_ptr stream = std::move(pipe);
    if (pipe.get()) {
      std::cerr << "Move should be sure to invalidate the previous ptr"
                << std::endl;
      return false;
    }
    cm::uv_handle_ptr handle = std::move(stream);
    if (stream.get()) {
      std::cerr << "Move should be sure to invalidate the previous ptr"
                << std::endl;
      return false;
    }
  }

  if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) {
    std::cerr << "Unclean exit state in testCrossAssignment" << std::endl;
    return false;
  }

  uv_loop_close(&Loop);
  return true;
}

// This test can't fail at run time; but this makes sure we have all our move
// ctors created correctly.
static bool testAllMoves()
{
  using namespace cm;
  struct allTypes
  {
    uv_stream_ptr _7;
    uv_tty_ptr _9;
    uv_pipe_ptr _12;
    uv_async_ptr _13;
    uv_signal_ptr _14;
    uv_handle_ptr _15;
  };

  allTypes a;
  allTypes b(std::move(a));
  allTypes c = std::move(b);
  return true;
};

int testUVRAII(int, char** const)
{
  if ((testAsyncShutdown() &&
       testAsyncDtor() & testAsyncMove() & testCrossAssignment() &
         testAllMoves()) == 0) {
    return -1;
  }
  return 0;
}
