/**
 * $Id: dynlib.c,v 1.8 2003/12/07 19:20:10 ton Exp $
 *
 * ***** BEGIN GPL/BL DUAL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version. The Blender
 * Foundation also sells licenses for use in proprietary software under
 * the Blender License.  See http://www.blender.org/BL/ for information
 * about this.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * The Original Code is: all of this file, with exception of below:
 *
 * Contributor(s): Peter O'Gorman
 * The functions osxdlopen() and osxerror() 
 * are Copyright (c) 2002 Peter O'Gorman <ogorman@users.sourceforge.net>
 *
 * ***** END GPL/BL DUAL LICENSE BLOCK *****
 */
#include <stdlib.h>
#include "../PIL_dynlib.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if !defined(CHAR_MAX)
#define CHAR_MAX 255
#endif

/*
 * XXX, should use mallocN so we can see
 * handle's not being released. fixme zr
 */
 
#ifdef WIN32

#include <windows.h>

struct PILdynlib {
	void *handle;
};

PILdynlib *PIL_dynlib_open(char *name) {
	void *handle= LoadLibrary(name);

	if (handle) {	
		PILdynlib *lib= malloc(sizeof(*lib));
		lib->handle= handle;
		
		return lib;
	} else {
		return NULL;
	}
}

void *PIL_dynlib_find_symbol(PILdynlib* lib, char *symname) {
	return GetProcAddress(lib->handle, symname);
}

char *PIL_dynlib_get_error_as_string(PILdynlib* lib) {
	int err;

	/* if lib is NULL reset the last error code */
	if (!lib) {
		SetLastError(ERROR_SUCCESS);
		return NULL;
	}

	err= GetLastError();
	if (err) {
		static char buf[1024];

		if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, 
					NULL, 
					err, 
					MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
					buf, 
					sizeof(buf), 
					NULL))
			return buf;
	}
	
	return NULL;
}

void PIL_dynlib_close(PILdynlib *lib) {
	FreeLibrary(lib->handle);
	
	free(lib);
}

#else
#ifdef __APPLE__	/* MacOS X */

#include <mach-o/dyld.h>
#include <dlfcn.h>
#include <stdarg.h>

#define ERR_STR_LEN 256

struct PILdynlib {
	void *handle;
};

char *osxerror(int setget, const char *str, ...)
{
	static char errstr[ERR_STR_LEN];
	static int err_filled = 0;
	char *retval;
	NSLinkEditErrors ler;
	int lerno;
	const char *dylderrstr;
	const char *file;
	va_list arg;
	if (setget <= 0)
	{
		va_start(arg, str);
		strncpy(errstr, "dlsimple: ", ERR_STR_LEN);
		vsnprintf(errstr + 10, ERR_STR_LEN - 10, str, arg);
		va_end(arg);
	/* We prefer to use the dyld error string if setget is 0 */
		if (setget == 0) {
			NSLinkEditError(&ler, &lerno, &file, &dylderrstr);
			printf("dyld: %s\n",dylderrstr);
			if (dylderrstr && strlen(dylderrstr))
				strncpy(errstr,dylderrstr,ERR_STR_LEN);
		}		
		err_filled = 1;
		retval = NULL;
	}
	else
	{
		if (!err_filled)
			retval = NULL;
		else
			retval = errstr;
		err_filled = 0;
	}
	return retval;
}

void *osxdlopen(const char *path, int mode)
{
	void *module = 0;
	NSObjectFileImage ofi = 0;
	NSObjectFileImageReturnCode ofirc;
	static int (*make_private_module_public) (NSModule module) = 0;
	unsigned int flags =  NSLINKMODULE_OPTION_RETURN_ON_ERROR | NSLINKMODULE_OPTION_PRIVATE;

	/* If we got no path, the app wants the global namespace, use -1 as the marker
	   in this case */
	if (!path)
		return (void *)-1;

	/* Create the object file image, works for things linked with the -bundle arg to ld */
	ofirc = NSCreateObjectFileImageFromFile(path, &ofi);
	switch (ofirc)
	{
		case NSObjectFileImageSuccess:
			/* It was okay, so use NSLinkModule to link in the image */
			if (!(mode & RTLD_LAZY)) flags += NSLINKMODULE_OPTION_BINDNOW;
			module = NSLinkModule(ofi, path,flags);
			/* Don't forget to destroy the object file image, unless you like leaks */
			NSDestroyObjectFileImage(ofi);
			/* If the mode was global, then change the module, this avoids
			   multiply defined symbol errors to first load private then make
			   global. Silly, isn't it. */
			if ((mode & RTLD_GLOBAL))
			{
			  if (!make_private_module_public)
			  {
			    _dyld_func_lookup("__dyld_NSMakePrivateModulePublic", 
				(unsigned long *)&make_private_module_public);
			  }
			  make_private_module_public(module);
			}
			break;
		case NSObjectFileImageInappropriateFile:
			/* It may have been a dynamic library rather than a bundle, try to load it */
			module = (void *)NSAddImage(path, NSADDIMAGE_OPTION_RETURN_ON_ERROR);
			break;
		case NSObjectFileImageFailure:
			osxerror(0,"Object file setup failure :  \"%s\"", path);
			return 0;
		case NSObjectFileImageArch:
			osxerror(0,"No object for this architecture :  \"%s\"", path);
			return 0;
		case NSObjectFileImageFormat:
			osxerror(0,"Bad object file format :  \"%s\"", path);
			return 0;
		case NSObjectFileImageAccess:
			osxerror(0,"Can't read object file :  \"%s\"", path);
			return 0;		
	}
	if (!module)
		osxerror(0, "Can not open \"%s\"", path);
	return module;
}

PILdynlib *PIL_dynlib_open(char *name) {
	void *handle= osxdlopen(name, RTLD_LAZY);

	if (handle) {	
		PILdynlib *lib= malloc(sizeof(*lib));
		lib->handle= handle;
		
		return lib;
	} else {
		return NULL;
	}
}

void *PIL_dynlib_find_symbol(PILdynlib* lib, char *symname) 
{
	int sym_len = strlen(symname);
	void *value = NULL;
	char *malloc_sym = NULL;
	NSSymbol *nssym = 0;
	malloc_sym = malloc(sym_len + 2);
	if (malloc_sym)
	{
		sprintf(malloc_sym, "_%s", symname);
		/* If the lib->handle is -1, if is the app global context */
		if (lib->handle == (void *)-1)
		{
			/* Global context, use NSLookupAndBindSymbol */
			if (NSIsSymbolNameDefined(malloc_sym))
			{
				nssym = NSLookupAndBindSymbol(malloc_sym);
			}
		}
		/* Now see if the lib->handle is a struch mach_header* or not, use NSLookupSymbol in image
		   for libraries, and NSLookupSymbolInModule for bundles */
		else
		{
			/* Check for both possible magic numbers depending on x86/ppc byte order */
			if ((((struct mach_header *)lib->handle)->magic == MH_MAGIC) ||
				(((struct mach_header *)lib->handle)->magic == MH_CIGAM))
			{
				if (NSIsSymbolNameDefinedInImage((struct mach_header *)lib->handle, malloc_sym))
				{
					nssym = NSLookupSymbolInImage((struct mach_header *)lib->handle,
												  malloc_sym,
												  NSLOOKUPSYMBOLINIMAGE_OPTION_BIND
												  | NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR);
				}
	
			}
			else
			{
				nssym = NSLookupSymbolInModule(lib->handle, malloc_sym);
			}
		}
		if (!nssym)
		{
			osxerror(0, "symname \"%s\" Not found", symname);
		}
		value = NSAddressOfSymbol(nssym);
		free(malloc_sym);
	}
	else
	{
		osxerror(-1, "Unable to allocate memory");
	}
	return value;
}

char *PIL_dynlib_get_error_as_string(PILdynlib* lib) 
{
	return osxerror(1, (char *)NULL);
}
	
void PIL_dynlib_close(PILdynlib *lib) 
{
	if ((((struct mach_header *)lib->handle)->magic == MH_MAGIC) ||
		(((struct mach_header *)lib->handle)->magic == MH_CIGAM))
	{
		osxerror(-1, "Can't remove dynamic libraries on darwin");
	}
	if (!NSUnLinkModule(lib->handle, 0))
	{
		osxerror(0, "unable to unlink module %s", NSNameOfModule(lib->handle));
	}
	
	free(lib);
}

#else	/* Unix */

#include <dlfcn.h>

struct PILdynlib {
	void *handle;
};

PILdynlib *PIL_dynlib_open(char *name) {
	void *handle= dlopen(name, RTLD_LAZY);

	if (handle) {	
		PILdynlib *lib= malloc(sizeof(*lib));
		lib->handle= handle;
		
		return lib;
	} else {
		return NULL;
	}
}

void *PIL_dynlib_find_symbol(PILdynlib* lib, char *symname) {
	return dlsym(lib->handle, symname);
}

char *PIL_dynlib_get_error_as_string(PILdynlib* lib) {
	return dlerror();
}
	
void PIL_dynlib_close(PILdynlib *lib) {
	dlclose(lib->handle);
	
	free(lib);
}

#endif
#endif
