#define _GNU_SOURCE
#include <dlfcn.h>

#include "config.h"

#include <string.h>
#include <stdio.h>
#include <time.h>

#define CK_PTR *
#define CK_DEFINE_FUNCTION(returnType, name) returnType name
#define CK_DECLARE_FUNCTION(returnType, name) returnType name
#define CK_DECLARE_FUNCTION_POINTER(returnType, name) returnType (* name)
#define CK_CALLBACK_FUNCTION(returnType, name) returnType (* name)
#ifndef NULL_PTR
#  define NULL_PTR 0
#endif

#include "./pkcs11/pkcs11.h"

#ifndef CACKEY_LIBRARY_FILE
#define CACKEY_LIBRARY_FILE "libcackey_g.so"
#endif

void abort(void);
void free(void *ptr);

static void *libcackey_wrap_handle = NULL_PTR;

typedef enum {
	LIBCACKEY_WRAP_MUTEX_UNINIT = 0,
	LIBCACKEY_WRAP_MUTEX_INIT,
	LIBCACKEY_WRAP_MUTEX_UNLOCKED,
	LIBCACKEY_WRAP_MUTEX_LOCKED,
} libcackey_wrap_mutexes_states_t;
static libcackey_wrap_mutexes_states_t libcackey_wrap_mutexes[16] = {LIBCACKEY_WRAP_MUTEX_UNINIT};

typedef libcackey_wrap_mutexes_states_t *pthread_mutex_t;
typedef int pthread_mutexattr_t;

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr) {
	libcackey_wrap_mutexes_states_t *mutex_;
	int idx;

	for (idx = 0; idx < (sizeof(libcackey_wrap_mutexes) / sizeof(libcackey_wrap_mutexes[0])); idx++) {
		if (libcackey_wrap_mutexes[idx] == LIBCACKEY_WRAP_MUTEX_UNINIT) {
			libcackey_wrap_mutexes[idx] = LIBCACKEY_WRAP_MUTEX_INIT;
			mutex_ = &libcackey_wrap_mutexes[idx];
			break;
		}
	}

	if (!mutex_) {
		return(1);
	}

	*mutex = mutex_;

	return(0);
}

int pthread_mutex_destroy(pthread_mutex_t *mutex) {
	**mutex = LIBCACKEY_WRAP_MUTEX_UNINIT;

	return(0);
}

int pthread_mutex_lock(pthread_mutex_t *mutex) {
	struct timespec sleeptime;

	while (**mutex == LIBCACKEY_WRAP_MUTEX_LOCKED) {
		sleeptime.tv_sec = 0;
		sleeptime.tv_nsec = 1000000;
		nanosleep(&sleeptime, NULL);
fprintf(stderr, "mutex %p is locked\n", mutex); fflush(stderr);
		/* Do nothing */
	}

fprintf(stderr, "locking mutex %p\n", mutex); fflush(stderr);
	**mutex = LIBCACKEY_WRAP_MUTEX_LOCKED;
fprintf(stderr, "locked mutex %p\n", mutex); fflush(stderr);

	return(CKR_OK);
}

int pthread_mutex_unlock(pthread_mutex_t *mutex) {
fprintf(stderr, "unlocking mutex %p\n", mutex); fflush(stderr);
	**mutex = LIBCACKEY_WRAP_MUTEX_UNLOCKED;
fprintf(stderr, "unlocked mutex %p\n", mutex); fflush(stderr);

	return(CKR_OK);
}

static void libcackey_wrap_init(void) {
	Dl_info libinfo;
	int dladdr_ret;
	char *library, *libraryDir, *libraryDirLastSlash;

	if (libcackey_wrap_handle) {
		return;
	}

	dladdr_ret = dladdr(libcackey_wrap_init, &libinfo);
	if (dladdr_ret == 0) {
		fprintf(stderr, "Unable to resolve path: %s\n", dlerror());

		abort();

		return;
	}

	if (!libinfo.dli_fname) {
		fprintf(stderr, "Unable to lookup filename\n");

		abort();

		return;
	}

	libraryDir = strdup(libinfo.dli_fname);
	libraryDirLastSlash = strrchr(libraryDir, '/');
	if (!libraryDirLastSlash) {
		fprintf(stderr, "File name returned is not an absolute path: %s\n", libraryDir);

		abort();

		return;
	}
	*libraryDirLastSlash = '\0';

	asprintf(&library, "%s/" CACKEY_LIBRARY_FILE, libraryDir);

	libcackey_wrap_handle = dlmopen(LM_ID_NEWLM, library, RTLD_LOCAL | RTLD_NOW);
	libcackey_wrap_handle = dlmopen(LM_ID_NEWLM, , RTLD_LOCAL | RTLD_NOW);

	if (!libcackey_wrap_handle) {
		fprintf(stderr, "Unable to load \"%s\": %s\n", library, dlerror());

		abort();

		return;
	}

	free(library);

	return;
}

static void libcackey_wrap_fini(void) {
	if (!libcackey_wrap_handle) {
		return;
	}

	dlclose(libcackey_wrap_handle);

	libcackey_wrap_handle = NULL_PTR;

	return;
}
