#include "../../src/ErrorSimulator.hpp"
#include "../Tests.hpp"
#include "core/Test.hpp"
#include "core/Thread.hpp"

static int runDone = 0;

struct IntHolder {
    int value;
};

static void run(void*) {
    runDone = 1;
}

static void testStart() {
    runDone = 0;
    {
        Core::Thread t;
        TEST_FALSE(t.start(run, nullptr));
    }
    TEST(1, runDone);
}

static void testLambda() {
    IntHolder i(0);
    Core::Thread t;
    TEST_FALSE(t.start([](void* p) {
        IntHolder* ip = static_cast<IntHolder*>(p);
        ip->value = 2;
    }, &i));
    TEST_FALSE(t.join());
    TEST(2, i.value);
}

static void testJoinWithoutStart() {
    Core::Thread t;
    TEST_FALSE(t.join());
}

static void testAutoJoin() {
    Core::Thread t;
    TEST_FALSE(t.start([](void*) {}, nullptr));
}

static void testMove() {
    Core::Thread t;
    TEST_FALSE(t.start([](void*) {}, nullptr));
    Core::Thread m = Core::move(t);
    TEST_FALSE(m.join());
}

static void testMoveAssignment() {
    Core::Thread t;
    TEST_FALSE(t.start([](void*) {}, nullptr));
    Core::Thread m;
    m = Core::move(t);
    TEST_FALSE(m.join());
}

static void testMoveIntoActive() {
    Core::Thread t;
    TEST_FALSE(t.start([](void*) {}, nullptr));
    Core::Thread m;
    t = Core::move(m);
}

static void testDoubleJoin() {
    Core::Thread t;
    TEST_FALSE(t.start([](void*) {}, nullptr));
    TEST_FALSE(t.join());
    TEST_FALSE(t.join());
}

struct MutexCounter {
    Core::Mutex m{};
    int counter = 0;
};

static void incrementMutexCounter(void* p) {
    MutexCounter* mcp = static_cast<MutexCounter*>(p);
    for(int i = 0; i < 100'000; i++) {
        Core::MutexGuard mg(mcp->m);
        mcp->counter++;
    }
}

static void testMutex() {
    MutexCounter mc;
    Core::Thread t[2];
    TEST_FALSE(t[0].start(incrementMutexCounter, &mc));
    TEST_FALSE(t[1].start(incrementMutexCounter, &mc));
    TEST_FALSE(t[0].join());
    TEST_FALSE(t[1].join());
    TEST(200'000, mc.counter);
}

static void testDoubleStart() {
    Core::Thread t;
    TEST_FALSE(t.start([](void*) {}, nullptr));
    TEST_TRUE(t.start([](void*) {}, nullptr));
}

static void testFail() {
#ifdef ERROR_SIMULATOR
    Core::Mutex m;
    failStepThrow = 1;
    m.lock();
    failStepThrow = 1;
    m.unlock();

    Core::Thread t;
    failStepThrow = 1;
    TEST_TRUE(t.start([](void*) {}, nullptr));
    TEST_FALSE(t.start([](void*) {}, nullptr));
    failStepThrow = 1;
    TEST_TRUE(t.join());
#endif
}

void testThread() {
    testStart();
    testLambda();
    testJoinWithoutStart();
    testAutoJoin();
    testMove();
    testMoveAssignment();
    testMoveIntoActive();
    testDoubleJoin();
    testMutex();
    testDoubleStart();
    testFail();
}