/**
 * WinPR: Windows Portable Runtime
 * Process Thread Functions
 *
 * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
 * Copyright 2015 Hewlett-Packard Development Company, L.P.
 * Copyright 2021 David Fort <contact@hardening-consulting.com>
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <winpr/config.h>

#include <winpr/winpr.h>
#include <winpr/assert.h>

#include <winpr/handle.h>

#include <winpr/thread.h>

#if defined(__FreeBSD__)
#include <pthread_np.h>
#elif defined(__linux__)
#include <sys/syscall.h>
#endif

#ifndef MIN
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#endif

#ifndef MAX
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#endif

/**
 * api-ms-win-core-processthreads-l1-1-1.dll
 *
 * CreateRemoteThread
 * CreateRemoteThreadEx
 * CreateThread
 * DeleteProcThreadAttributeList
 * ExitThread
 * FlushInstructionCache
 * FlushProcessWriteBuffers
 * GetCurrentThread
 * GetCurrentThreadId
 * GetCurrentThreadStackLimits
 * GetExitCodeThread
 * GetPriorityClass
 * GetStartupInfoW
 * GetThreadContext
 * GetThreadId
 * GetThreadIdealProcessorEx
 * GetThreadPriority
 * GetThreadPriorityBoost
 * GetThreadTimes
 * InitializeProcThreadAttributeList
 * OpenThread
 * OpenThreadToken
 * QueryProcessAffinityUpdateMode
 * QueueUserAPC
 * ResumeThread
 * SetPriorityClass
 * SetThreadContext
 * SetThreadPriority
 * SetThreadPriorityBoost
 * SetThreadStackGuarantee
 * SetThreadToken
 * SuspendThread
 * SwitchToThread
 * TerminateThread
 * UpdateProcThreadAttribute
 */

#ifndef _WIN32

#include <winpr/crt.h>
#include <winpr/platform.h>

#include <string.h>
#ifdef WINPR_HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef WINPR_HAVE_SYS_EVENTFD_H
#include <sys/eventfd.h>
#endif

#include <winpr/debug.h>

#include <errno.h>
#include <fcntl.h>

#include <winpr/collections.h>

#include "thread.h"
#include "apc.h"

#include "../handle/handle.h"
#include "../log.h"
#define TAG WINPR_TAG("thread")

static WINPR_THREAD mainThread;

#if defined(WITH_THREAD_LIST)
static wListDictionary* thread_list = nullptr;
#endif

static BOOL ThreadCloseHandle(HANDLE handle);
static void cleanup_handle(void* obj);

static BOOL ThreadIsHandled(HANDLE handle)
{
	return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_THREAD, FALSE);
}

static int ThreadGetFd(HANDLE handle)
{
	WINPR_THREAD* pThread = (WINPR_THREAD*)handle;

	if (!ThreadIsHandled(handle))
		return -1;

	return pThread->event.fds[0];
}

#define run_mutex_init(fkt, mux, arg) run_mutex_init_(fkt, #fkt, mux, arg)
static BOOL run_mutex_init_(int (*fkt)(pthread_mutex_t*, const pthread_mutexattr_t*),
                            const char* name, pthread_mutex_t* mutex,
                            const pthread_mutexattr_t* mutexattr)
{
	int rc = 0;

	WINPR_ASSERT(fkt);
	WINPR_ASSERT(mutex);

	rc = fkt(mutex, mutexattr);
	if (rc != 0)
	{
		char ebuffer[256] = WINPR_C_ARRAY_INIT;
		WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
	}
	return rc == 0;
}

#define run_mutex_fkt(fkt, mux) run_mutex_fkt_(fkt, #fkt, mux)
static BOOL run_mutex_fkt_(int (*fkt)(pthread_mutex_t* mux), const char* name,
                           pthread_mutex_t* mutex)
{
	int rc = 0;

	WINPR_ASSERT(fkt);
	WINPR_ASSERT(mutex);

	rc = fkt(mutex);
	if (rc != 0)
	{
		char ebuffer[256] = WINPR_C_ARRAY_INIT;
		WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
	}
	return rc == 0;
}

#define run_cond_init(fkt, cond, arg) run_cond_init_(fkt, #fkt, cond, arg)
static BOOL run_cond_init_(int (*fkt)(pthread_cond_t*, const pthread_condattr_t*), const char* name,
                           pthread_cond_t* condition, const pthread_condattr_t* conditionattr)
{
	int rc = 0;

	WINPR_ASSERT(fkt);
	WINPR_ASSERT(condition);

	rc = fkt(condition, conditionattr);
	if (rc != 0)
	{
		char ebuffer[256] = WINPR_C_ARRAY_INIT;
		WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
	}
	return rc == 0;
}

#define run_cond_fkt(fkt, cond) run_cond_fkt_(fkt, #fkt, cond)
static BOOL run_cond_fkt_(int (*fkt)(pthread_cond_t* mux), const char* name,
                          pthread_cond_t* condition)
{
	int rc = 0;

	WINPR_ASSERT(fkt);
	WINPR_ASSERT(condition);

	rc = fkt(condition);
	if (rc != 0)
	{
		char ebuffer[256] = WINPR_C_ARRAY_INIT;
		WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
	}
	return rc == 0;
}

static int pthread_mutex_checked_unlock(pthread_mutex_t* mutex)
{
	WINPR_ASSERT(mutex);
	WINPR_ASSERT(pthread_mutex_trylock(mutex) == EBUSY);
	return pthread_mutex_unlock(mutex);
}

static BOOL mux_condition_bundle_init(mux_condition_bundle* bundle)
{
	WINPR_ASSERT(bundle);

	bundle->val = FALSE;
	if (!run_mutex_init(pthread_mutex_init, &bundle->mux, nullptr))
		return FALSE;

	if (!run_cond_init(pthread_cond_init, &bundle->cond, nullptr))
		return FALSE;
	return TRUE;
}

static void mux_condition_bundle_uninit(mux_condition_bundle* bundle)
{
	mux_condition_bundle empty = WINPR_C_ARRAY_INIT;

	WINPR_ASSERT(bundle);

	run_cond_fkt(pthread_cond_destroy, &bundle->cond);
	run_mutex_fkt(pthread_mutex_destroy, &bundle->mux);
	*bundle = empty;
}

static BOOL mux_condition_bundle_signal(mux_condition_bundle* bundle)
{
	BOOL rc = TRUE;
	WINPR_ASSERT(bundle);

	if (!run_mutex_fkt(pthread_mutex_lock, &bundle->mux))
		return FALSE;
	bundle->val = TRUE;
	if (!run_cond_fkt(pthread_cond_signal, &bundle->cond))
		rc = FALSE;
	if (!run_mutex_fkt(pthread_mutex_checked_unlock, &bundle->mux))
		rc = FALSE;
	return rc;
}

static BOOL mux_condition_bundle_lock(mux_condition_bundle* bundle)
{
	WINPR_ASSERT(bundle);
	return run_mutex_fkt(pthread_mutex_lock, &bundle->mux);
}

static BOOL mux_condition_bundle_unlock(mux_condition_bundle* bundle)
{
	WINPR_ASSERT(bundle);
	return run_mutex_fkt(pthread_mutex_checked_unlock, &bundle->mux);
}

static BOOL mux_condition_bundle_wait(mux_condition_bundle* bundle, const char* name)
{
	BOOL rc = FALSE;

	WINPR_ASSERT(bundle);
	WINPR_ASSERT(name);
	WINPR_ASSERT(pthread_mutex_trylock(&bundle->mux) == EBUSY);

	while (!bundle->val)
	{
		int r = pthread_cond_wait(&bundle->cond, &bundle->mux);
		if (r != 0)
		{
			char ebuffer[256] = WINPR_C_ARRAY_INIT;
			WLog_ERR(TAG, "failed to wait for %s [%s]", name,
			         winpr_strerror(r, ebuffer, sizeof(ebuffer)));
			switch (r)
			{
				case ENOTRECOVERABLE:
				case EPERM:
				case ETIMEDOUT:
				case EINVAL:
					goto fail;

				default:
					break;
			}
		}
	}

	rc = bundle->val;

fail:
	return rc;
}

static BOOL signal_thread_ready(WINPR_THREAD* thread)
{
	WINPR_ASSERT(thread);

	return mux_condition_bundle_signal(&thread->isCreated);
}

static BOOL signal_thread_is_running(WINPR_THREAD* thread)
{
	WINPR_ASSERT(thread);

	return mux_condition_bundle_signal(&thread->isRunning);
}

static DWORD ThreadCleanupHandle(HANDLE handle)
{
	DWORD status = WAIT_FAILED;
	WINPR_THREAD* thread = (WINPR_THREAD*)handle;

	if (!ThreadIsHandled(handle))
		return WAIT_FAILED;

	if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
		return WAIT_FAILED;

	if (!thread->joined)
	{
		int rc = pthread_join(thread->thread, nullptr);

		if (rc != 0)
		{
			char ebuffer[256] = WINPR_C_ARRAY_INIT;
			WLog_ERR(TAG, "pthread_join failure: [%d] %s", rc,
			         winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
			goto fail;
		}
		else
			thread->joined = TRUE;
	}

	status = WAIT_OBJECT_0;

fail:
	if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
		return WAIT_FAILED;

	return status;
}

static HANDLE_OPS ops = { ThreadIsHandled, ThreadCloseHandle, ThreadGetFd, ThreadCleanupHandle,
	                      nullptr,         nullptr,           nullptr,     nullptr,
	                      nullptr,         nullptr,           nullptr,     nullptr,
	                      nullptr,         nullptr,           nullptr,     nullptr,
	                      nullptr,         nullptr,           nullptr,     nullptr,
	                      nullptr };

static void dump_thread(WINPR_THREAD* thread)
{
#if defined(WITH_DEBUG_THREADS)
	void* stack = winpr_backtrace(20);
	char** msg = nullptr;
	size_t used = 0;
	WLog_DBG(TAG, "Called from:");
	msg = winpr_backtrace_symbols(stack, &used);

	for (size_t i = 0; i < used; i++)
		WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);

	free(msg);
	winpr_backtrace_free(stack);
	WLog_DBG(TAG, "Thread handle created still not closed!");
	msg = winpr_backtrace_symbols(thread->create_stack, &used);

	for (size_t i = 0; i < used; i++)
		WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);

	free(msg);

	if (thread->started)
	{
		WLog_DBG(TAG, "Thread still running!");
	}
	else if (!thread->exit_stack)
	{
		WLog_DBG(TAG, "Thread suspended.");
	}
	else
	{
		WLog_DBG(TAG, "Thread exited at:");
		msg = winpr_backtrace_symbols(thread->exit_stack, &used);

		for (size_t i = 0; i < used; i++)
			WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);

		free(msg);
	}
#else
	WINPR_UNUSED(thread);
#endif
}

/**
 * TODO: implement thread suspend/resume using pthreads
 * http://stackoverflow.com/questions/3140867/suspend-pthreads-without-using-condition
 */
static BOOL set_event(WINPR_THREAD* thread)
{
	return winpr_event_set(&thread->event);
}

static BOOL reset_event(WINPR_THREAD* thread)
{
	return winpr_event_reset(&thread->event);
}

#if defined(WITH_THREAD_LIST)
static BOOL thread_compare(const void* a, const void* b)
{
	const pthread_t* p1 = a;
	const pthread_t* p2 = b;
	BOOL rc = pthread_equal(*p1, *p2);
	return rc;
}
#endif

static INIT_ONCE threads_InitOnce = INIT_ONCE_STATIC_INIT;
static pthread_t mainThreadId;
static DWORD currentThreadTlsIndex = TLS_OUT_OF_INDEXES;

static BOOL initializeThreads(WINPR_ATTR_UNUSED PINIT_ONCE InitOnce,
                              WINPR_ATTR_UNUSED PVOID Parameter, WINPR_ATTR_UNUSED PVOID* Context)
{
	if (!apc_init(&mainThread.apc))
	{
		WLog_ERR(TAG, "failed to initialize APC");
		goto out;
	}

	mainThread.common.Type = HANDLE_TYPE_THREAD;
	mainThreadId = pthread_self();

	currentThreadTlsIndex = TlsAlloc();
	if (currentThreadTlsIndex == TLS_OUT_OF_INDEXES)
	{
		WLog_ERR(TAG, "Major bug, unable to allocate a TLS value for currentThread");
	}

#if defined(WITH_THREAD_LIST)
	thread_list = ListDictionary_New(TRUE);

	if (!thread_list)
	{
		WLog_ERR(TAG, "Couldn't create global thread list");
		goto error_thread_list;
	}

	thread_list->objectKey.fnObjectEquals = thread_compare;
#endif

out:
	return TRUE;
}

static BOOL signal_and_wait_for_ready(WINPR_THREAD* thread)
{
	BOOL res = FALSE;

	WINPR_ASSERT(thread);

	if (!mux_condition_bundle_lock(&thread->isRunning))
		return FALSE;

	if (!signal_thread_ready(thread))
		goto fail;

	if (!mux_condition_bundle_wait(&thread->isRunning, "threadIsRunning"))
		goto fail;

#if defined(WITH_THREAD_LIST)
	if (!ListDictionary_Contains(thread_list, &thread->thread))
	{
		WLog_ERR(TAG, "Thread not in thread_list, startup failed!");
		goto fail;
	}
#endif

	res = TRUE;

fail:
	if (!mux_condition_bundle_unlock(&thread->isRunning))
		return FALSE;

	return res;
}

/* Thread launcher function responsible for registering
 * cleanup handlers and calling pthread_exit, if not done
 * in thread function. */
static void* thread_launcher(void* arg)
{
	DWORD rc = 0;
	WINPR_THREAD* thread = (WINPR_THREAD*)arg;
	LPTHREAD_START_ROUTINE fkt = nullptr;

	if (!thread)
	{
		WLog_ERR(TAG, "Called with invalid argument %p", arg);
		goto exit;
	}

	if (!TlsSetValue(currentThreadTlsIndex, thread))
	{
		WLog_ERR(TAG, "thread %" PRIu64 ", unable to set current thread value",
		         WINPR_CXX_COMPAT_CAST(uint64_t, pthread_self()));
		goto exit;
	}

	if (!(fkt = thread->lpStartAddress))
	{
		union
		{
			LPTHREAD_START_ROUTINE fkt;
			void* pv;
		} cnv;
		cnv.fkt = fkt;
		WLog_ERR(TAG, "Thread function argument is %p", cnv.pv);
		goto exit;
	}

	if (!signal_and_wait_for_ready(thread))
		goto exit;

	rc = fkt(thread->lpParameter);
exit:

	if (thread)
	{
		apc_cleanupThread(thread);

		if (!thread->exited)
			thread->dwExitCode = rc;

		set_event(thread);

		(void)signal_thread_ready(thread);

		if (thread->detached || !thread->started)
			cleanup_handle(thread);
	}

	return nullptr;
}

static BOOL winpr_StartThread(WINPR_THREAD* thread)
{
	BOOL rc = FALSE;
	BOOL locked = FALSE;
	pthread_attr_t attr = WINPR_C_ARRAY_INIT;

	if (!mux_condition_bundle_lock(&thread->isCreated))
		return FALSE;
	locked = TRUE;

	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

	if (thread->dwStackSize > 0)
		pthread_attr_setstacksize(&attr, thread->dwStackSize);

	thread->started = TRUE;
	reset_event(thread);

#if defined(WITH_THREAD_LIST)
	if (!ListDictionary_Add(thread_list, &thread->thread, thread))
	{
		WLog_ERR(TAG, "failed to add the thread to the thread list");
		goto error;
	}
#endif

	if (pthread_create(&thread->thread, &attr, thread_launcher, thread))
		goto error;

	if (!mux_condition_bundle_wait(&thread->isCreated, "threadIsCreated"))
		goto error;

	locked = FALSE;
	if (!mux_condition_bundle_unlock(&thread->isCreated))
		goto error;

	if (!signal_thread_is_running(thread))
	{
		WLog_ERR(TAG, "failed to signal the thread was ready");
		goto error;
	}

	rc = TRUE;
error:
	if (locked)
	{
		if (!mux_condition_bundle_unlock(&thread->isCreated))
			rc = FALSE;
	}

	pthread_attr_destroy(&attr);

	if (rc)
		dump_thread(thread);

	return rc;
}

BOOL SetThreadPriority(HANDLE hThread, int nPriority)
{
	ULONG Type = 0;
	WINPR_HANDLE* Object = nullptr;

	if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
		return FALSE;

	const int min = 19;
	const int max = 0;
	const int diff = (max - min);
	const int normal = min + diff / 2;
	const int off = MIN(1, diff / 4);
	int sched_priority = -1;

	switch (nPriority & ~(THREAD_MODE_BACKGROUND_BEGIN | THREAD_MODE_BACKGROUND_END))
	{
		case THREAD_PRIORITY_ABOVE_NORMAL:
			sched_priority = MIN(normal + off, max);
			break;
		case THREAD_PRIORITY_BELOW_NORMAL:
			sched_priority = MAX(normal - off, min);
			break;
		case THREAD_PRIORITY_HIGHEST:
			sched_priority = max;
			break;
		case THREAD_PRIORITY_IDLE:
			sched_priority = min;
			break;
		case THREAD_PRIORITY_LOWEST:
			sched_priority = min;
			break;
		case THREAD_PRIORITY_TIME_CRITICAL:
			sched_priority = max;
			break;
		default:
		case THREAD_PRIORITY_NORMAL:
			sched_priority = normal;
			break;
	}
#if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200809L) && defined(PTHREAD_SETSCHEDPRIO)
	WINPR_THREAD* thread = (WINPR_THREAD*)Object;
	const int rc = pthread_setschedprio(thread->thread, sched_priority);
	if (rc != 0)
	{
		char buffer[256] = WINPR_C_ARRAY_INIT;
		WLog_ERR(TAG, "pthread_setschedprio(%d) %s [%d]", sched_priority,
		         winpr_strerror(rc, buffer, sizeof(buffer)), rc);
	}
	return rc == 0;
#else
	WLog_WARN(TAG, "pthread_setschedprio(%d) not implemented, requires POSIX 2008 or later",
	          sched_priority);
	return TRUE;
#endif
}

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, size_t dwStackSize,
                    LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter,
                    DWORD dwCreationFlags, WINPR_ATTR_UNUSED LPDWORD lpThreadId)
{
	HANDLE handle = nullptr;
	WINPR_THREAD* thread = (WINPR_THREAD*)calloc(1, sizeof(WINPR_THREAD));

	if (!thread)
		return nullptr;

	thread->dwStackSize = dwStackSize;
	thread->lpParameter = lpParameter;
	thread->lpStartAddress = lpStartAddress;
	thread->lpThreadAttributes = lpThreadAttributes;
	thread->common.ops = &ops;
#if defined(WITH_DEBUG_THREADS)
	thread->create_stack = winpr_backtrace(20);
	dump_thread(thread);
#endif

	if (!winpr_event_init(&thread->event))
	{
		WLog_ERR(TAG, "failed to create event");
		goto fail;
	}

	if (!run_mutex_init(pthread_mutex_init, &thread->mutex, nullptr))
	{
		WLog_ERR(TAG, "failed to initialize thread mutex");
		goto fail;
	}

	if (!apc_init(&thread->apc))
	{
		WLog_ERR(TAG, "failed to initialize APC");
		goto fail;
	}

	if (!mux_condition_bundle_init(&thread->isCreated))
		goto fail;
	if (!mux_condition_bundle_init(&thread->isRunning))
		goto fail;

	WINPR_HANDLE_SET_TYPE_AND_MODE(thread, HANDLE_TYPE_THREAD, WINPR_FD_READ);
	handle = (HANDLE)thread;

	if (!InitOnceExecuteOnce(&threads_InitOnce, initializeThreads, nullptr, nullptr))
		goto fail;

	if (!(dwCreationFlags & CREATE_SUSPENDED))
	{
		if (!winpr_StartThread(thread))
			goto fail;
	}
	else
	{
		if (!set_event(thread))
			goto fail;
	}

	return handle;
fail:
	cleanup_handle(thread);
	return nullptr;
}

void cleanup_handle(void* obj)
{
	WINPR_THREAD* thread = (WINPR_THREAD*)obj;
	if (!thread)
		return;

	if (!apc_uninit(&thread->apc))
		WLog_ERR(TAG, "failed to destroy APC");

	mux_condition_bundle_uninit(&thread->isCreated);
	mux_condition_bundle_uninit(&thread->isRunning);
	run_mutex_fkt(pthread_mutex_destroy, &thread->mutex);

	winpr_event_uninit(&thread->event);

#if defined(WITH_THREAD_LIST)
	ListDictionary_Remove(thread_list, &thread->thread);
#endif
#if defined(WITH_DEBUG_THREADS)

	if (thread->create_stack)
		winpr_backtrace_free(thread->create_stack);

	if (thread->exit_stack)
		winpr_backtrace_free(thread->exit_stack);

#endif
	free(thread);
}

BOOL ThreadCloseHandle(HANDLE handle)
{
	WINPR_THREAD* thread = (WINPR_THREAD*)handle;

#if defined(WITH_THREAD_LIST)
	if (!thread_list)
	{
		WLog_ERR(TAG, "Thread list does not exist, check call!");
		dump_thread(thread);
	}
	else if (!ListDictionary_Contains(thread_list, &thread->thread))
	{
		WLog_ERR(TAG, "Thread list does not contain this thread! check call!");
		dump_thread(thread);
	}
	else
	{
		ListDictionary_Lock(thread_list);
#endif
		dump_thread(thread);

		if ((thread->started) && (WaitForSingleObject(thread, 0) != WAIT_OBJECT_0))
		{
			WLog_DBG(TAG, "Thread running, setting to detached state!");
			thread->detached = TRUE;
			pthread_detach(thread->thread);
		}
		else
		{
			cleanup_handle(thread);
		}

#if defined(WITH_THREAD_LIST)
		ListDictionary_Unlock(thread_list);
	}
#endif

	return TRUE;
}

HANDLE CreateRemoteThread(WINPR_ATTR_UNUSED HANDLE hProcess,
                          WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpThreadAttributes,
                          WINPR_ATTR_UNUSED size_t dwStackSize,
                          WINPR_ATTR_UNUSED LPTHREAD_START_ROUTINE lpStartAddress,
                          WINPR_ATTR_UNUSED LPVOID lpParameter,
                          WINPR_ATTR_UNUSED DWORD dwCreationFlags,
                          WINPR_ATTR_UNUSED LPDWORD lpThreadId)
{
	WLog_ERR(TAG, "not implemented");
	SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
	return nullptr;
}

VOID ExitThread(DWORD dwExitCode)
{
#if defined(WITH_THREAD_LIST)
	DWORD rc;
	pthread_t tid = pthread_self();

	if (!thread_list)
	{
		WLog_ERR(TAG, "function called without existing thread list!");
#if defined(WITH_DEBUG_THREADS)
		DumpThreadHandles();
#endif
		pthread_exit(0);
	}
	else if (!ListDictionary_Contains(thread_list, &tid))
	{
		WLog_ERR(TAG, "function called, but no matching entry in thread list!");
#if defined(WITH_DEBUG_THREADS)
		DumpThreadHandles();
#endif
		pthread_exit(0);
	}
	else
	{
		WINPR_THREAD* thread;
		ListDictionary_Lock(thread_list);
		thread = ListDictionary_GetItemValue(thread_list, &tid);
		WINPR_ASSERT(thread);
		thread->exited = TRUE;
		thread->dwExitCode = dwExitCode;
#if defined(WITH_DEBUG_THREADS)
		thread->exit_stack = winpr_backtrace(20);
#endif
		ListDictionary_Unlock(thread_list);
		set_event(thread);
		rc = thread->dwExitCode;

		if (thread->detached || !thread->started)
			cleanup_handle(thread);

		pthread_exit((void*)(size_t)rc);
	}
#else
	WINPR_UNUSED(dwExitCode);
#endif
}

BOOL GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode)
{
	ULONG Type = 0;
	WINPR_HANDLE* Object = nullptr;
	WINPR_THREAD* thread = nullptr;

	if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
	{
		WLog_ERR(TAG, "hThread is not a thread");
		SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}

	thread = (WINPR_THREAD*)Object;
	*lpExitCode = thread->dwExitCode;
	return TRUE;
}

WINPR_THREAD* winpr_GetCurrentThread(VOID)
{
	WINPR_THREAD* ret = nullptr;

	if (!InitOnceExecuteOnce(&threads_InitOnce, initializeThreads, nullptr, nullptr))
		return nullptr;
	if (mainThreadId == pthread_self())
		return (HANDLE)&mainThread;

	ret = TlsGetValue(currentThreadTlsIndex);
	return ret;
}

HANDLE _GetCurrentThread(VOID)
{
	return (HANDLE)winpr_GetCurrentThread();
}

DWORD GetCurrentThreadId(VOID)
{
#if defined(__FreeBSD__)
	return WINPR_CXX_COMPAT_CAST(DWORD, pthread_getthreadid_np());
#elif defined(__linux__)
	return WINPR_CXX_COMPAT_CAST(DWORD, syscall(SYS_gettid));
#else
	pthread_t tid = pthread_self();
	/* Since pthread_t can be 64-bits on some systems, take just the    */
	/* lower 32-bits of it for the thread ID returned by this function. */
	uintptr_t ptid = WINPR_REINTERPRET_CAST(tid, pthread_t, uintptr_t);
	return (ptid & UINT32_MAX) ^ (ptid >> 32);
#endif
}

typedef struct
{
	WINPR_APC_ITEM apc;
	PAPCFUNC completion;
	ULONG_PTR completionArg;
} UserApcItem;

static void userAPC(LPVOID arg)
{
	UserApcItem* userApc = (UserApcItem*)arg;

	userApc->completion(userApc->completionArg);

	userApc->apc.markedForRemove = TRUE;
}

DWORD QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData)
{
	ULONG Type = 0;
	WINPR_HANDLE* Object = nullptr;
	WINPR_APC_ITEM* apc = nullptr;
	UserApcItem* apcItem = nullptr;

	if (!pfnAPC)
		return 1;

	if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
	{
		WLog_ERR(TAG, "hThread is not a thread");
		SetLastError(ERROR_INVALID_PARAMETER);
		return (DWORD)0;
	}

	apcItem = calloc(1, sizeof(*apcItem));
	if (!apcItem)
	{
		SetLastError(ERROR_INVALID_PARAMETER);
		return (DWORD)0;
	}

	apc = &apcItem->apc;
	apc->type = APC_TYPE_USER;
	apc->markedForFree = TRUE;
	apc->alwaysSignaled = TRUE;
	apc->completion = userAPC;
	apc->completionArgs = apc;
	apcItem->completion = pfnAPC;
	apcItem->completionArg = dwData;
	apc_register(hThread, apc);
	return 1;
}

DWORD ResumeThread(HANDLE hThread)
{
	ULONG Type = 0;
	WINPR_HANDLE* Object = nullptr;
	WINPR_THREAD* thread = nullptr;

	if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
	{
		WLog_ERR(TAG, "hThread is not a thread");
		SetLastError(ERROR_INVALID_PARAMETER);
		return (DWORD)-1;
	}

	thread = (WINPR_THREAD*)Object;

	if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
		return (DWORD)-1;

	if (!thread->started)
	{
		if (!winpr_StartThread(thread))
		{
			run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex);
			return (DWORD)-1;
		}
	}
	else
		WLog_WARN(TAG, "Thread already started!");

	if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
		return (DWORD)-1;

	return 0;
}

DWORD SuspendThread(WINPR_ATTR_UNUSED HANDLE hThread)
{
	WLog_ERR(TAG, "not implemented");
	SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
	return (DWORD)-1;
}

BOOL SwitchToThread(VOID)
{
	/**
	 * Note: on some operating systems sched_yield is a stub returning -1.
	 * usleep should at least trigger a context switch if any thread is waiting.
	 */
	if (sched_yield() != 0)
		usleep(1);

	return TRUE;
}

BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode)
{
	ULONG Type = 0;
	WINPR_HANDLE* Object = nullptr;
	WINPR_THREAD* thread = nullptr;

	if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
		return FALSE;

	thread = (WINPR_THREAD*)Object;
	thread->exited = TRUE;
	thread->dwExitCode = dwExitCode;

	if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
		return FALSE;

#ifndef ANDROID
	pthread_cancel(thread->thread);
#else
	WLog_ERR(TAG, "Function not supported on this platform!");
#endif

	if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
		return FALSE;

	set_event(thread);
	return TRUE;
}

VOID DumpThreadHandles(void)
{
#if defined(WITH_DEBUG_THREADS)
	char** msg = nullptr;
	size_t used = 0;
	void* stack = winpr_backtrace(20);
	WLog_DBG(TAG, "---------------- Called from ----------------------------");
	msg = winpr_backtrace_symbols(stack, &used);

	for (size_t i = 0; i < used; i++)
	{
		WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
	}

	free(msg);
	winpr_backtrace_free(stack);
	WLog_DBG(TAG, "---------------- Start Dumping thread handles -----------");

#if defined(WITH_THREAD_LIST)
	if (!thread_list)
	{
		WLog_DBG(TAG, "All threads properly shut down and disposed of.");
	}
	else
	{
		ULONG_PTR* keys = nullptr;
		ListDictionary_Lock(thread_list);
		int x, count = ListDictionary_GetKeys(thread_list, &keys);
		WLog_DBG(TAG, "Dumping %d elements", count);

		for (size_t x = 0; x < count; x++)
		{
			WINPR_THREAD* thread = ListDictionary_GetItemValue(thread_list, (void*)keys[x]);
			WLog_DBG(TAG, "Thread [%d] handle created still not closed!", x);
			msg = winpr_backtrace_symbols(thread->create_stack, &used);

			for (size_t i = 0; i < used; i++)
			{
				WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
			}

			free(msg);

			if (thread->started)
			{
				WLog_DBG(TAG, "Thread [%d] still running!", x);
			}
			else
			{
				WLog_DBG(TAG, "Thread [%d] exited at:", x);
				msg = winpr_backtrace_symbols(thread->exit_stack, &used);

				for (size_t i = 0; i < used; i++)
					WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);

				free(msg);
			}
		}

		free(keys);
		ListDictionary_Unlock(thread_list);
	}
#endif

	WLog_DBG(TAG, "---------------- End Dumping thread handles -------------");
#endif
}
#endif
