//------------------------------------------------------------------------
// Project     : SDK Base
// Version     : 1.0
//
// Category    : Helpers
// Filename    : base/source/fdynlib.cpp
// Created by  : Steinberg, 1998
// Description : Dynamic library loading
//
//-----------------------------------------------------------------------------
// LICENSE
// (c) 2024, Steinberg Media Technologies GmbH, All Rights Reserved
//-----------------------------------------------------------------------------
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
//   * Redistributions of source code must retain the above copyright notice, 
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation 
//     and/or other materials provided with the distribution.
//   * Neither the name of the Steinberg Media Technologies nor the names of its
//     contributors may be used to endorse or promote products derived from this 
//     software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

#include "base/source/fdynlib.h"
#include "pluginterfaces/base/fstrdefs.h"
#include "base/source/fstring.h"

#if SMTG_OS_WINDOWS
#include <windows.h>

#elif SMTG_OS_MACOS
#include <mach-o/dyld.h>
#include <CoreFoundation/CoreFoundation.h>

#if !SMTG_OS_IOS
static const Steinberg::tchar kUnixDelimiter = STR ('/');
#endif
#endif

namespace Steinberg {

#if SMTG_OS_MACOS
#include <dlfcn.h>

// we ignore for the moment that the NSAddImage functions are deprecated
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"

static bool CopyProcessPath (Steinberg::String& name)
{
	Dl_info info;
	if (dladdr ((const void*)CopyProcessPath, &info))
	{
		if (info.dli_fname)
		{
			name.assign (info.dli_fname);
			#ifdef UNICODE
			name.toWideString ();
			#endif
			return true;
		}
	}
	return false;
}

#endif

//------------------------------------------------------------------------
// FDynLibrary
//------------------------------------------------------------------------
FDynLibrary::FDynLibrary (const tchar* n, bool addExtension)
: isloaded (false)
, instance (nullptr)
{
	if (n)
		init (n, addExtension);
}

//------------------------------------------------------------------------
FDynLibrary::~FDynLibrary ()
{
	unload ();
}

//------------------------------------------------------------------------
bool FDynLibrary::init (const tchar* n, bool addExtension)
{
	if (isLoaded ())
		return true;

	Steinberg::String name (n);
		
#if SMTG_OS_WINDOWS
	if (addExtension)
		name.append (STR (".dll"));

	instance = LoadLibrary (name);
	if (instance)
		isloaded = true;
		
#elif SMTG_OS_MACOS
	isBundle = false;
	// first check if it is a bundle
	if (addExtension)
		name.append (STR (".bundle"));
	if (name.getChar16 (0) != STR('/')) // no absoltue path
	{
		Steinberg::String p;
		if (CopyProcessPath (p))
		{
			Steinberg::int32 index = p.findLast (STR ('/'));
			p.remove (index+1);
			name = p + name;
		}
	}
	CFStringRef fsString = (CFStringRef)name.toCFStringRef ();
	CFURLRef url = CFURLCreateWithFileSystemPath (NULL, fsString, kCFURLPOSIXPathStyle, true);
	if (url)
	{
		CFBundleRef bundle = CFBundleCreate (NULL, url);
		if (bundle)
		{
			if (CFBundleLoadExecutable (bundle))
			{
				isBundle = true;
				instance = (void*)bundle;
			}
			else
				CFRelease (bundle);
		}
		CFRelease (url);
	}
	CFRelease (fsString);

	name.assign (n);

#if !SMTG_OS_IOS
	if (!isBundle)
	{
		// now we check for a dynamic library
		firstSymbol = NULL;
		if (addExtension)
		{
			name.append (STR (".dylib"));
		}
		// Only if name is a relative path we use the Process Path as root:
		if (name[0] != kUnixDelimiter)
		{
			Steinberg::String p;
			if (CopyProcessPath (p))
			{
				Steinberg::int32 index = p.findLast (STR ("/"));
				p.remove (index+1);
				p.append (name);
				p.toMultiByte (Steinberg::kCP_Utf8);
				instance = (void*) NSAddImage (p, NSADDIMAGE_OPTION_RETURN_ON_ERROR);
			}
		}
		// Last but not least let the system search for it
		// 
		if (instance == 0)
		{
			name.toMultiByte (Steinberg::kCP_Utf8);
			instance = (void*) NSAddImage (name, NSADDIMAGE_OPTION_RETURN_ON_ERROR);
		}
	}
#endif // !SMTG_OS_IOS

	if (instance)
		isloaded = true;

#endif

	return isloaded;
}

//------------------------------------------------------------------------
bool FDynLibrary::unload ()
{
	if (!isLoaded ())
		return false;

#if SMTG_OS_WINDOWS
	FreeLibrary ((HINSTANCE)instance);
	
#elif SMTG_OS_MACOS
	if (isBundle)
	{
		if (CFGetRetainCount ((CFTypeRef)instance) == 1)
			CFBundleUnloadExecutable ((CFBundleRef)instance);
		CFRelease ((CFBundleRef)instance);
	}
	else
	{
		// we don't use this anymore as the darwin dyld can't unload dynamic libraries yet and may crash
/*		if (firstSymbol)
		{
			NSModule module = NSModuleForSymbol ((NSSymbol)firstSymbol);
			if (module)
				NSUnLinkModule (module, NSUNLINKMODULE_OPTION_NONE);
		}*/
	}
#endif
	instance = nullptr;
	isloaded = false;
	return true;
}

//------------------------------------------------------------------------
void* FDynLibrary::getProcAddress (const char* name)
{
	if (!isloaded)
		return nullptr;
	
#if SMTG_OS_WINDOWS
	return (void*)GetProcAddress ((HINSTANCE)instance, name);
	
#elif SMTG_OS_MACOS
	if (isBundle)
	{
		CFStringRef functionName = CFStringCreateWithCString (NULL, name, kCFStringEncodingASCII);
		void* result = CFBundleGetFunctionPointerForName ((CFBundleRef)instance, functionName);
		CFRelease (functionName);
		return result;
	}
#if !SMTG_OS_IOS
	else
	{
		char* symbolName = (char*) malloc (strlen (name) + 2);
		strcpy (symbolName, "_");
		strcat (symbolName, name);
		NSSymbol symbol;
		symbol = NSLookupSymbolInImage ((const struct mach_header*)instance, symbolName, NSLOOKUPSYMBOLINIMAGE_OPTION_BIND_NOW | NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR);
		free (symbolName);
		if (symbol)
		{
			if (firstSymbol == NULL)
				firstSymbol = symbol;
			return NSAddressOfSymbol (symbol);
		}
	}
#endif // !SMTG_OS_IOS

	return nullptr;
#else
	return nullptr;
#endif
}

//------------------------------------------------------------------------
} // namespace Steinberg
