// -*- indent-tabs-mode: nil -*-
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif


#include <string>
#include <unistd.h>

#include <cppunit/extensions/HelperMacros.h>

#include <arc/Thread.h>

class ThreadTest
  : public CppUnit::TestFixture {

  CPPUNIT_TEST_SUITE(ThreadTest);
  CPPUNIT_TEST(TestThread);
  CPPUNIT_TEST(TestBroadcast);
  CPPUNIT_TEST_SUITE_END();

public:
  void setUp();
  void tearDown();
  void TestThread();
  void TestBroadcast();

private:
  static void func(void*);
  static void func_wait(void* arg);
  static int counter;
  static std::mutex lock;
  Arc::SimpleCondition cond;
};

static int titem_created = 0;
static int titem_deleted = 0;

class TItem: public Arc::ThreadDataItem {
private:
  std::string tid;
  int id;
  ~TItem(void);
public:
  TItem(void);
  TItem(const TItem& parent);
  virtual void Dup(void);
};

TItem::TItem(void) {
  Attach(tid);
  id=(++titem_created);
}

TItem::TItem(const TItem& parent):Arc::ThreadDataItem(parent.tid) {
  tid = parent.tid;
  id=(++titem_created);
}

TItem::~TItem(void) {
  ++titem_deleted;
}

void TItem::Dup(void) {
  new TItem(*this);
}

void ThreadTest::setUp() {
  counter = 0;
  cond.reset();
  new TItem;
}

void ThreadTest::tearDown() {}

void ThreadTest::TestThread() {
  // Simply run 500 threads and see if executable crashes
  for(int n = 0;n<500;++n) {
    CPPUNIT_ASSERT(Arc::CreateThreadFunction(&func,NULL));
  }
  // Wait for all threads
  // In worst case it should be no more than one thread simultaneously.
  for(int n = 0; n<(500*10); ++n) {
    sleep(1);
    if(counter >= 500) break;
  }
  sleep(1);
  CPPUNIT_ASSERT_EQUAL(500,counter);
  CPPUNIT_ASSERT_EQUAL(501,titem_created);
  CPPUNIT_ASSERT_EQUAL(500,titem_deleted);
}

void ThreadTest::TestBroadcast() {
  // Create 2 threads which wait and check broadcast wakes them both up
  CPPUNIT_ASSERT(Arc::CreateThreadFunction(&func_wait, this));
  CPPUNIT_ASSERT(Arc::CreateThreadFunction(&func_wait, this));
  // Wait for threads to start
  sleep(1);
  cond.broadcast();
  // Wait for result
  sleep(1);
  CPPUNIT_ASSERT_EQUAL(2, counter);
}

void ThreadTest::func_wait(void* arg) {
  ThreadTest* test = (ThreadTest*)arg;
  test->cond.wait();
  lock.lock();
  test->counter++;
  lock.unlock();
}

void ThreadTest::func(void*) {
  sleep(1);
  lock.lock();
  ++counter;
  lock.unlock();
}

int ThreadTest::counter = 0;
std::mutex ThreadTest::lock;

CPPUNIT_TEST_SUITE_REGISTRATION(ThreadTest);

