/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

#include "threadstate.h"
#include "runtime.h"

static void threadstate_dispose(git_threadstate *threadstate);

/**
 * Handle the thread-local state
 *
 * `git_threadstate_global_init` will be called as part
 * of `git_libgit2_init` (which itself must be called
 * before calling any other function in the library).
 *
 * This function allocates a TLS index (using pthreads
 * or fiber-local storage in Win32) to store the per-
 * thread state.
 *
 * Any internal method that requires thread-local state
 * will then call `git_threadstate_get()` which returns a
 * pointer to the thread-local state structure; this
 * structure is lazily allocated on each thread.
 *
 * This mechanism will register a shutdown handler
 * (`git_threadstate_global_shutdown`) which will free the
 * TLS index.  This shutdown handler will be called by
 * `git_libgit2_shutdown`.
 *
 * If libgit2 is built without threading support, the
 * `git_threadstate_get()` call returns a pointer to a single,
 * statically allocated global state. The `git_thread_`
 * functions are not available in that case.
 */

#if defined(GIT_THREADS) && defined(GIT_WIN32)

static DWORD fls_index;

static void git_threadstate_global_shutdown(void)
{
	FlsFree(fls_index);
}

static void WINAPI fls_free(void *threadstate)
{
	threadstate_dispose(threadstate);
	git__free(threadstate);
}

int git_threadstate_global_init(void)
{
	if ((fls_index = FlsAlloc(fls_free)) == FLS_OUT_OF_INDEXES)
		return -1;

	return git_runtime_shutdown_register(git_threadstate_global_shutdown);
}

git_threadstate *git_threadstate_get(void)
{
	git_threadstate *threadstate;

	if ((threadstate = FlsGetValue(fls_index)) != NULL)
		return threadstate;

	if ((threadstate = git__calloc(1, sizeof(git_threadstate))) == NULL ||
	    git_buf_init(&threadstate->error_buf, 0) < 0)
		return NULL;

	FlsSetValue(fls_index, threadstate);
	return threadstate;
}

#elif defined(GIT_THREADS) && defined(_POSIX_THREADS)

static pthread_key_t tls_key;

static void git_threadstate_global_shutdown(void)
{
	git_threadstate *threadstate;

	threadstate = pthread_getspecific(tls_key);
	pthread_setspecific(tls_key, NULL);

	threadstate_dispose(threadstate);
	git__free(threadstate);

	pthread_key_delete(tls_key);
}

static void tls_free(void *threadstate)
{
	threadstate_dispose(threadstate);
	git__free(threadstate);
}

int git_threadstate_global_init(void)
{
	if (pthread_key_create(&tls_key, &tls_free) != 0)
		return -1;

	return git_runtime_shutdown_register(git_threadstate_global_shutdown);
}

git_threadstate *git_threadstate_get(void)
{
	git_threadstate *threadstate;

	if ((threadstate = pthread_getspecific(tls_key)) != NULL)
		return threadstate;

	if ((threadstate = git__calloc(1, sizeof(git_threadstate))) == NULL ||
	    git_buf_init(&threadstate->error_buf, 0) < 0)
		return NULL;

	pthread_setspecific(tls_key, threadstate);
	return threadstate;
}

#elif defined(GIT_THREADS)
# error unknown threading model
#else

static git_threadstate threadstate;

static void git_threadstate_global_shutdown(void)
{
	threadstate_dispose(&threadstate);
	memset(&threadstate, 0, sizeof(git_threadstate));
}

int git_threadstate_global_init(void)
{
	return git_runtime_shutdown_register(git_threadstate_global_shutdown);
}

git_threadstate *git_threadstate_get(void)
{
	return &threadstate;
}

#endif

static void threadstate_dispose(git_threadstate *threadstate)
{
	if (!threadstate)
		return;

	git__free(threadstate->error_t.message);
	threadstate->error_t.message = NULL;
}
