/*
 * Copyright (C) 2009 Andrzej K. Haczewski <ahaczewski@gmail.com>
 *
 * DISCLAIMER: The implementation is Git-specific, it is subset of original
 * Pthreads API, without lots of other features that Git doesn't use.
 * Git also makes sure that the passed arguments are valid, so there's
 * no need for double-checking.
 */

#include "../../git-compat-util.h"
#include "pthread.h"

#include <errno.h>
#include <limits.h>

static unsigned __stdcall win32_start_routine(void *arg)
{
	pthread_t *thread = arg;
	thread->tid = GetCurrentThreadId();
	thread->arg = thread->start_routine(thread->arg);
	return 0;
}

int pthread_create(pthread_t *thread, const void *attr UNUSED,
		   void *(*start_routine)(void *), void *arg)
{
	thread->arg = arg;
	thread->start_routine = start_routine;
	thread->handle = (HANDLE)_beginthreadex(NULL, 0, win32_start_routine,
						thread, 0, NULL);

	if (!thread->handle)
		return errno;
	else
		return 0;
}

int win32_pthread_join(pthread_t *thread, void **value_ptr)
{
	DWORD result = WaitForSingleObject(thread->handle, INFINITE);
	switch (result) {
	case WAIT_OBJECT_0:
		if (value_ptr)
			*value_ptr = thread->arg;
		CloseHandle(thread->handle);
		return 0;
	case WAIT_ABANDONED:
		CloseHandle(thread->handle);
		return EINVAL;
	default:
		/* the wait failed, so do not detach */
		return err_win_to_posix(GetLastError());
	}
}

pthread_t pthread_self(void)
{
	pthread_t t = { NULL };
	t.tid = GetCurrentThreadId();
	return t;
}

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
{
	if (SleepConditionVariableCS(cond, mutex, INFINITE) == 0)
		return err_win_to_posix(GetLastError());
	return 0;
}

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
			   const struct timespec *abstime)
{
	struct timeval now;
	long long now_ms, deadline_ms;
	DWORD timeout_ms;

	gettimeofday(&now, NULL);
	now_ms = (long long)now.tv_sec * 1000 + now.tv_usec / 1000;
	deadline_ms = (long long)abstime->tv_sec * 1000 +
		      abstime->tv_nsec / 1000000;

	if (deadline_ms <= now_ms)
		return ETIMEDOUT;
	else
		timeout_ms = (DWORD)(deadline_ms - now_ms);

	if (SleepConditionVariableCS(cond, mutex, timeout_ms) == 0) {
		DWORD err = GetLastError();
		if (err == ERROR_TIMEOUT)
			return ETIMEDOUT;
		return err_win_to_posix(err);
	}
	return 0;
}
