#include "config.h"

#include "test_thread_base.h"

#include <functional>
#include <future>
#include <thread>
#include <unistd.h>

#include "runtime.h"
#include "helpers/test_thread.h"
#include "helpers/test_utils.h"
#include "torrent/exceptions.h"
#include "torrent/utils/log.h"
#include "torrent/system/thread.h"

CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_thread_base, "torrent/utils");

void
test_thread_base::test_basic() {
  auto thread = test_thread::create();

  CPPUNIT_ASSERT(thread->flags() == 0);

  CPPUNIT_ASSERT(!thread->is_active());
  CPPUNIT_ASSERT(thread->internal_poll() != nullptr);

  // Check active...
}

void
test_thread_base::test_lifecycle() {
  auto thread = test_thread::create();

  torrent::Runtime::initialize();

  CPPUNIT_ASSERT(thread->state() == torrent::system::Thread::STATE_UNKNOWN);
  CPPUNIT_ASSERT(thread->test_state() == test_thread::TEST_NONE);

  thread->init_thread();
  CPPUNIT_ASSERT(thread->state() == torrent::system::Thread::STATE_INITIALIZED);
  CPPUNIT_ASSERT(thread->is_initialized());
  CPPUNIT_ASSERT(thread->test_state() == test_thread::TEST_PRE_START);

  thread->set_pre_stop();
  CPPUNIT_ASSERT(wait_for_not_true(std::bind(&test_thread::is_test_state, thread.get(), test_thread::TEST_PRE_STOP)));

  thread->start_thread();
  CPPUNIT_ASSERT(wait_for_true(std::bind(&test_thread::is_state, thread.get(), test_thread::STATE_ACTIVE)));
  CPPUNIT_ASSERT(thread->is_active());
  CPPUNIT_ASSERT(wait_for_true(std::bind(&test_thread::is_test_state, thread.get(), test_thread::TEST_PRE_STOP)));

  thread->stop_thread_wait();
  CPPUNIT_ASSERT(wait_for_true(std::bind(&test_thread::is_state, thread.get(), test_thread::STATE_INACTIVE)));
  CPPUNIT_ASSERT(thread->is_inactive());

  torrent::Runtime::cleanup();
}

void
test_thread_base::test_interrupt() {
  auto thread = test_thread::create();

  torrent::Runtime::initialize();

  thread->set_test_flag(test_thread::test_flag_long_timeout);

  thread->init_thread();
  thread->start_thread();

  // Vary the various timeouts.

  for (int i = 0; i < 20; i++) {
    std::this_thread::sleep_for(100ms);
    thread->interrupt();
    std::this_thread::sleep_for(100ms);
    // usleep(0);

    thread->set_test_flag(test_thread::test_flag_do_work);
    thread->interrupt();

    // Wait for flag to clear.
    CPPUNIT_ASSERT(wait_for_true(std::bind(&test_thread::is_not_test_flags, thread.get(), test_thread::test_flag_do_work)));
  }

  thread->stop_thread_wait();
  CPPUNIT_ASSERT(wait_for_true(std::bind(&test_thread::is_state, thread.get(), test_thread::STATE_INACTIVE)));

  torrent::Runtime::cleanup();
}

void
test_thread_base::test_stop() {
  std::unique_ptr<test_thread> thread;

  try {
    for (int i = 0; i < 20; i++) {
      thread = test_thread::create();

      torrent::Runtime::initialize();

      thread->set_test_flag(test_thread::test_flag_do_work);

      thread->init_thread();
      thread->start_thread();

      thread->stop_thread_wait();
      CPPUNIT_ASSERT(thread->is_inactive());

      torrent::Runtime::cleanup();
    }

  } catch (const torrent::internal_error& e) {
    if (thread && thread->is_active())
      thread->stop_thread_wait();

    CPPUNIT_FAIL(std::string("Caught internal error: ") + e.what());
  }
}
