#include "Thread.h"

#include "Globals.h"

#ifndef WIN32
#    include <unistd.h>
#endif

using namespace Framework;

// Contents of the Thread class from Thread.h
// Constructor
Thread::Thread()
    : ReferenceCounter()
{
    threadHandleSys = 0;
    threadHandle = 0;
    threadId = 0;
    thRegister->add(this);
    run = 0;
    lockCount = 0;
    name = 0;
}

// Destructor
Thread::~Thread()
{
    thRegister->remove(this);
#ifdef WIN32
    if (threadHandle != 0 && GetCurrentThreadId() == GetThreadId(threadHandle))
        return;
#else
    if (pthread_self() == threadHandle) return;
#endif
    if (run) waitForThread(100000);
    if (run) ende();
}

// non-constant
void Thread::start() // starts the thread
{
    if (!run)
    {
        getThis();
#ifdef WIN32
        threadHandle = CreateThread(0, 0, threadStart, this, 0, &threadId);
#else
        pthread_create(&threadHandle, NULL, threadStart, this);
#endif
    }
    run = 1;
}
#ifdef WIN32
void Thread::pause() // pauses the thread
{
    if (run) SuspendThread(threadHandle);
    run = 0;
}

void Thread::resume() // resumes the thread
{
    if (!run) ResumeThread(threadHandle);
    run = 1;
}
#endif
void Thread::ende() // terminates the thread
{
    if (run)
    {
        while (lockCount && *lockCount > 0)
            Sleep(100);
        bool rel = run;
#ifdef WIN32
#    pragma warning(suppress : 6258)
        TerminateThread(threadHandle, 0);
#else
        if (pthread_self() == threadHandle)
        {
            thRegister->addClosedThread(threadHandle);
            run = 0;
        }
        pthread_cancel(threadHandle);
#endif
        if (rel)
        {
            run = 0;
            release();
            return;
        }
    }
    run = 0;
}

void Thread::thread() // Thread
{}

void Thread::threadEnd()
{
    run = 0;
    lockCount = 0;
    release();
}

// constant
bool Thread::isRunning() const // checks if the thread is active
{
    return run;
}

int Thread::waitForThread(int zeit) // waits for the thread for the given time
{
#ifdef WIN32
    if (!run) return WAIT_OBJECT_0;
    if (GetCurrentThreadId() == GetThreadId(threadHandle)) return WAIT_OBJECT_0;
    return WaitForSingleObject(threadHandle, zeit);
#else
    if (!run) return 0;
    if (pthread_self() == threadHandle) return 0;
    timespec ts;
    if (clock_gettime(CLOCK_REALTIME, &ts) == -1) return -1;
    ts.tv_sec += (zeit + ts.tv_nsec / 1000) / 1000;
    ts.tv_nsec = 1000 * ((zeit + ts.tv_nsec / 1000) % 1000);
    if (threadHandleSys) *threadHandleSys = 0;
    int ret = pthread_timedjoin_np(threadHandle, 0, &ts);
    if (!ret)
        threadHandle = 0;
    else
        *threadHandleSys = threadHandle;
    return ret;
#endif
}

// Sets a framework pointer to a thread handle that is set to 0
// when the thread's resources have already been fully cleaned up
//  ths: A pointer to a thread handle that should be modified
void Thread::setSystemHandlePointer(pthread_t* ths)
{
    threadHandleSys = ths;
    *threadHandleSys = threadHandle;
}

pthread_t Thread::getThreadHandle() const
{
    return threadHandle;
}

// set the name of the thread
void Thread::setName(const char* name)
{
    this->name = name;
}

// get the name of the thread
const char* Thread::getName() const
{
    return name ? name : typeid(*this).name();
}

void Thread::setLockCount(int* lockCount)
{
    this->lockCount = lockCount;
}

// functions
#ifdef WIN32
unsigned long __stdcall Framework::threadStart(void* param)
{
    currentThreadId = GetCurrentThreadId();
    if (isThreadOk((Thread*)param))
    {
        ((Thread*)param)->setLockCount(&currentThreadLockCount);
        const char* name = ((Thread*)param)->getName();
        wchar_t* wc = new wchar_t[textLength(name) + 1];
        size_t l;
        mbstowcs_s(&l, wc, textLength(name) + 1, name, textLength(name));
        SetThreadDescription(((Thread*)param)->getThreadHandle(), wc);
        delete[] wc;
        ((Thread*)param)->thread();
    }
    if (isThreadOk((Thread*)param)) ((Thread*)param)->threadEnd();
    return 0;
}
#else
void* Framework::threadStart(void* param)
{
    currentThreadId = gettid();
    pthread_t handle = 0;
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
    if (isThreadOk((Thread*)param))
    {
        ((Thread*)param)->setLockCount(&currentThreadLockCount);
        pthread_setname_np(
            ((Thread*)param)->getThreadHandle(), ((Thread*)param)->getName());
        ((Thread*)param)->setSystemHandlePointer(&handle);
        ((Thread*)param)->thread();
    }
    if (isThreadOk((Thread*)param)) ((Thread*)param)->threadEnd();
    thRegister->addClosedThread(handle);
    pthread_exit(0);
    return 0;
}
#endif

// Constructor
ThreadRegister::ThreadRegister()
{
    InitializeCriticalSection(&cs);
}

// Destructor
ThreadRegister::~ThreadRegister()
{
    EnterCriticalSection(&cs);
    while (threads.has(0))
    {
        LeaveCriticalSection(&cs);
        Sleep(1000); // wait until all threads are done
        EnterCriticalSection(&cs);
    }
    cleanUpClosedThreads();
    LeaveCriticalSection(&cs);
    DeleteCriticalSection(&cs);
}

// Contents of the ThreadRegister class from Thread.h
void ThreadRegister::add(Thread* t)
{
    EnterCriticalSection(&cs);
    threads.add(t);
    LeaveCriticalSection(&cs);
}

void ThreadRegister::remove(Thread* t)
{
    EnterCriticalSection(&cs);
    threads.remove(threads.getValueIndex(t));
    LeaveCriticalSection(&cs);
}

bool ThreadRegister::isThread(Thread* t)
{
    EnterCriticalSection(&cs);
    bool ret = threads.has(threads.getValueIndex(t));
    LeaveCriticalSection(&cs);
    return ret;
}

void ThreadRegister::addClosedThread(pthread_t handle)
{
    EnterCriticalSection(&cs);
    if (handle) closedThreads.add(handle);
    LeaveCriticalSection(&cs);
}

// Searches for a specific thread and returns the associated object
// handle: A handle to the thread being searched for
Thread* ThreadRegister::zThread(pthread_t handle)
{
    if (handle == 0) return 0;
    EnterCriticalSection(&cs);
    for (auto i : threads)
    {
        if (i->getThreadHandle() && i->getThreadHandle() == handle)
        {
            LeaveCriticalSection(&cs);
            return i;
        }
    }
    LeaveCriticalSection(&cs);
    return 0;
}

// locks the register
void ThreadRegister::lock()
{
    EnterCriticalSection(&cs);
}

// unlocks the register
void ThreadRegister::unlock()
{
    LeaveCriticalSection(&cs);
}

// Deletes already finished threads and releases their resources
void ThreadRegister::cleanUpClosedThreads()
{
    EnterCriticalSection(&cs);
    while (closedThreads.getEntryCount() > 0)
    {
#ifndef WIN32
        pthread_join(closedThreads.get(0), 0);
#endif
        closedThreads.remove(0);
    }
    LeaveCriticalSection(&cs);
}