#define _GNU_SOURCE
#include <dlfcn.h>

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.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

static void *libcackey_wrap_handle = NULL_PTR;

static void libcackey_wrap_init(void);

static CK_RV libcackey_wrap_createmutex(CK_VOID_PTR_PTR ppMutex) {
	int (*create_mutex)(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
	pthread_mutex_t *pthread_mutex;
	int pthread_retval;

	libcackey_wrap_init();

	pthread_mutex = malloc(sizeof(*pthread_mutex));

	create_mutex = dlsym(libcackey_wrap_handle, "pthread_mutex_init");
	pthread_retval = create_mutex(pthread_mutex, NULL);
	if (pthread_retval != 0) {
		return(CKR_GENERAL_ERROR);
	}

	*ppMutex = pthread_mutex;

	return(CKR_OK);
}

#define libcackey_wrap_genericmutexfunc(funcName, pthreadName) \
	static CK_RV funcName(CK_VOID_PTR pMutex) { \
		int (*func)(pthread_mutex_t *mutex); \
		pthread_mutex_t *pthread_mutex; \
		int pthread_retval; \
		libcackey_wrap_init(); \
		pthread_mutex = pMutex; \
		func = dlsym(libcackey_wrap_handle, pthreadName); \
		pthread_retval = func(pthread_mutex); \
		if (pthread_retval != 0) { \
			return(CKR_GENERAL_ERROR); \
		} \
		if (strcmp(pthreadName, "pthread_mutex_destroy") == 0) { \
			free(pthread_mutex); \
		} \
		return(CKR_OK); \
	}

libcackey_wrap_genericmutexfunc(libcackey_wrap_destroymutex, "pthread_mutex_destroy")
libcackey_wrap_genericmutexfunc(libcackey_wrap_lockmutex, "pthread_mutex_lock")
libcackey_wrap_genericmutexfunc(libcackey_wrap_unlockmutex, "pthread_mutex_unlock")

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);

	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;
}
