/* 
docLicense("BSD revised") 
docCredits("based on DllHandle.c, contributed by Daniel A. Koepke 
	Reorg, Steve Dekorte, 2003-08-30
	Window fix, Chuck Adams, 2004-02-06:)
*/

#include "Base.h" 
#include "DynLib.h"
#include "PortableStrlcpy.h"

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

#if defined(__WIN32__) || defined(WIN32) || defined(_WIN32) || defined(_MSC_VER)
#include <windows.h>

#define RTLD_NOW    0    /* don't support link flags */
#define RTLD_GLOBAL 0

static void *dlopen(const char *path, int mode)
{
    void *result;
    result = (void *)LoadLibraryEx(path, NULL, 0);
    if (result)
        SetLastError(ERROR_SUCCESS);

    return result;
}

static int dlclose(void *handle)
{
    return FreeLibrary((HANDLE)handle);
}

static const char *dlerror(void)
{
    // XXX this will leak the error string 
    
    LPSTR buf;
    DWORD err;
    err = GetLastError();
    if (err == ERROR_SUCCESS)
        return (char*)0;

    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
				  | FORMAT_MESSAGE_IGNORE_INSERTS
				  | FORMAT_MESSAGE_FROM_SYSTEM,
				  NULL, err,
				  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
				  (LPSTR)&buf, 0, NULL);
    return buf;
}

static void *dlsym(void *handle, const char *symbol)
{
    return (void*)GetProcAddress((HANDLE)handle, symbol);
}


#else
#include <dlfcn.h>
#endif

DynLib *DynLib_new(void)
{
    DynLib *self = (DynLib *)calloc(1, sizeof(DynLib));
    DynLib_setPath_(self, "");
    return self;
}

void DynLib_free(DynLib *self)
{
    //DynLib_close(self);
    if (self->path) free(self->path);
    if (self->initFuncName) free(self->initFuncName);
    if (self->freeFuncName) free(self->freeFuncName);
    if (self->error) free(self->error);
    free(self);
}

void DynLib_setPath_(DynLib *self, const char *path)
{ 
    size_t len = strlen(path);
    char* ptr = (char*)realloc(self->path, len + 1);
    PortableStrlcpy(ptr, path, len + 1);
    self->path = ptr;
}

char *DynLib_path(DynLib *self) 
{ 
    return self->path; 
}

void DynLib_setInitFuncName_(DynLib *self, const char *name)
{ 
    size_t len = strlen(name);
    char* ptr = (char*)realloc(self->initFuncName, len + 1);
    PortableStrlcpy(ptr, name, len + 1);
    self->initFuncName = ptr;
}

char *DynLib_initFuncName(DynLib *self) 
{ 
    return self->initFuncName; 
}

void DynLib_setInitArg_(DynLib *self, void *arg) 
{ 
    self->initArg = arg; 
}

void DynLib_setFreeFuncName_(DynLib *self, const char *name)
{ 
    size_t len = strlen(name);
    char* ptr = (char*)realloc(self->freeFuncName, len + 1);
    PortableStrlcpy(ptr, name, len + 1);
    self->freeFuncName = ptr;
}

char *DynLib_freeFuncName(DynLib *self) 
{ 
    return self->freeFuncName; 
}

void DynLib_setFreeArg_(DynLib *self, void *arg) 
{ 
    self->freeArg = arg; 
}

void DynLib_setError_(DynLib *self,const char *error)
{ 
    if (error)
    {
		self->error = strcpy((char *)realloc(self->error, strlen(error)+1), error);
    }
    else
    {
		if (self->error) free(self->error);
		self->error = NULL;
    }
}

char *DynLib_error(DynLib *self) 
{ 
    return self->error; 
}

void DynLib_updateError(DynLib *self) 
{ 
    DynLib_setError_(self, dlerror()); 
}

unsigned char DynLib_hasError(DynLib *self) 
{ 
    return self->error != NULL; 
}

void DynLib_open(DynLib *self)
{  
    self->handle = dlopen(self->path, RTLD_NOW | RTLD_GLOBAL); /* RTLD_LAZY); */
    //self->handle = dlopen(self->path, RTLD_NOW | RTLD_LAZY); 
    DynLib_updateError(self);
    
    if (DynLib_hasError(self)) 
    {
		return;
    }
    
    if (self->initFuncName)
    {
		void *f = DynLib_pointerForSymbolName_(self, self->initFuncName);
		
		if (!f) 
		{ 
			DynLib_setError_(self, "init function not found"); 
			return; 
		}
		
		if (self->initArg) 
		{ 
			//printf("DynLib: opening with 1 arg %p\n", self->initArg);
			(*(DynLibOneArgFunction *)f)(self->initArg); 
		} 
		else 
		{ 
			(*(DynLibNoArgFunction *)f)(); 
		}
    }
}

unsigned char DynLib_isOpen(DynLib *self) 
{ 
    return self->handle != NULL;
}

void DynLib_close(DynLib *self)
{
    if (self->freeFuncName)
    {
		void *f = DynLib_pointerForSymbolName_(self, self->freeFuncName);
		
		if (!f) 
		{ 
			DynLib_setError_(self, "free function not found"); 
			return; 
		}
		
		if (self->freeArg) 
		{ 
			(*(DynLibOneArgFunction *)f)(self->freeArg); 
		} 
		else 
		{ 
			(*(DynLibNoArgFunction *)f)(); 
		}
    }
    
    if (self->handle) 
    {
		dlclose(self->handle);
    }
    
    self->handle = NULL;
}

void *DynLib_pointerForSymbolName_(DynLib *self, const char *symbolName)
{
    DynLib_setError_(self, dlerror());
    return dlsym(self->handle, symbolName);
}

