#include <gtest/gtest.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>

extern "C" {
#include "reactor.h"
}

static pthread_t thread;
static volatile bool thread_running;

static void *reactor_thread(void *ptr) {
  reactor_t *reactor = (reactor_t *)ptr;

  thread_running = true;
  reactor_start(reactor);
  thread_running = false;

  return NULL;
}

static void spawn_reactor_thread(reactor_t *reactor) {
  int ret = pthread_create(&thread, NULL, reactor_thread, reactor);
  EXPECT_EQ(ret, 0);
}

static void join_reactor_thread() {
  pthread_join(thread, NULL);
}

static uint64_t get_timestamp(void) {
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

TEST(ReactorTest, reactor_new) {
  reactor_t *reactor = reactor_new();
  EXPECT_TRUE(reactor != NULL);
  reactor_free(reactor);
}

TEST(ReactorTest, reactor_free_null) {
  reactor_free(NULL);
}

TEST(ReactorTest, reactor_stop_start) {
  reactor_t *reactor = reactor_new();
  reactor_stop(reactor);
  reactor_start(reactor);
  reactor_free(reactor);
}

TEST(ReactorTest, reactor_repeated_stop_start) {
  reactor_t *reactor = reactor_new();
  for (int i = 0; i < 10; ++i) {
    reactor_stop(reactor);
    reactor_start(reactor);
  }
  reactor_free(reactor);
}

TEST(ReactorTest, reactor_multi_stop_start) {
  reactor_t *reactor = reactor_new();

  reactor_stop(reactor);
  reactor_stop(reactor);
  reactor_stop(reactor);

  reactor_start(reactor);
  reactor_start(reactor);
  reactor_start(reactor);

  reactor_free(reactor);
}

TEST(ReactorTest, reactor_start_wait_stop) {
  reactor_t *reactor = reactor_new();

  spawn_reactor_thread(reactor);
  usleep(50 * 1000);
  EXPECT_TRUE(thread_running);

  reactor_stop(reactor);
  join_reactor_thread();
  EXPECT_FALSE(thread_running);

  reactor_free(reactor);
}

TEST(ReactorTest, reactor_run_once_timeout) {
  reactor_t *reactor = reactor_new();

  uint64_t start = get_timestamp();
  reactor_status_t status = reactor_run_once_timeout(reactor, 50);
  EXPECT_GE(get_timestamp() - start, static_cast<uint64_t>(50));
  EXPECT_EQ(status, REACTOR_STATUS_TIMEOUT);

  reactor_free(reactor);
}
