//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Shared library loading and symbol lookup.
//
// $NoKeywords: $
//=============================================================================//

#include "pch_tier0.h"
#include "tier0/dynfunction.h"

#if defined(WIN32)
typedef HMODULE LibraryHandle;
#define LoadLibraryHandle(libname) LoadLibrary(libname)
#define CloseLibraryHandle(handle) FreeLibrary(handle)
#define LookupInLibraryHandle(handle, fn) GetProcAddress(handle, fn)
#elif defined(POSIX)
#include <dlfcn.h>
typedef void *LibraryHandle;
#define LoadLibraryHandle(libname) dlopen(libname, RTLD_NOW)
#define CloseLibraryHandle(handle) dlclose(handle)
#define LookupInLibraryHandle(handle, fn) dlsym(handle, fn)
#else
#error Please define your platform.
#endif

#if 1
static inline void dbgdynfn(const char *fmt, ...) {}
#else
#define dbgdynfn printf
#endif

// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"

class CSharedLibraryCache
{
public:
	static CSharedLibraryCache &GetCache()
	{
		static CSharedLibraryCache Singleton;
		return Singleton;
	}

	struct CSharedLibraryItem
	{
		CSharedLibraryItem(LibraryHandle handle, const char *name)
		{
			m_handle = handle;
			m_name = new char[strlen(name) + 1];
			m_next = NULL;
			strcpy(m_name, name);
		}

		~CSharedLibraryItem()
		{
			dbgdynfn("CDynamicFunction: Closing library '%s' (%p)\n", m_name, (void *) m_handle);
			CloseLibraryHandle(m_handle);
			delete[] m_name;
			delete m_next;
		}

		char *m_name;
		CSharedLibraryItem *m_next;
		LibraryHandle m_handle;
	};

	CSharedLibraryCache() : m_pList(NULL) {}
	~CSharedLibraryCache() { CloseAllLibraries(); }

	LibraryHandle GetHandle(const char *name)
	{
		CSharedLibraryItem *item = GetCacheItem(name);
		if (item == NULL)
		{
			LibraryHandle lib = LoadLibraryHandle(name);
			dbgdynfn("CDynamicFunction: Loading library '%s' (%p)\n", name, (void *) lib);
			if (lib == NULL)
				return NULL;

			item = new CSharedLibraryItem(lib, name);
			item->m_next = m_pList;
			m_pList = item;
		}
		return item->m_handle;
	}

	void CloseLibrary(const char *name)
	{
		CSharedLibraryItem *item = GetCacheItem(name);
		if (item)
		{
			assert(item == m_pList);
			m_pList = item->m_next;
			item->m_next = NULL;
			delete item;
		}
	}

	void CloseAllLibraries()
	{
		delete m_pList;
	}

private:
	CSharedLibraryItem *GetCacheItem(const char *name)
	{
		CSharedLibraryItem *prev = NULL;
		CSharedLibraryItem *item = m_pList;
		while (item)
		{
			if (strcmp(item->m_name, name) == 0)
			{
				// move this item to the front of the list, since there will
				//  probably be a big pile of these lookups in a row
				//  and then none ever again.
				if (prev != NULL)
				{
					prev->m_next = item->m_next;
					item->m_next = m_pList;
					m_pList = item;
				}
				return item;
			}

			prev = item;
			item = item->m_next;
		}
		return NULL;  // not found.
	}

	CSharedLibraryItem *m_pList;
};

void *VoidFnPtrLookup_Tier0(const char *libname, const char *fn, void *fallback)
{
	LibraryHandle lib = CSharedLibraryCache::GetCache().GetHandle(libname);
	void *retval = NULL;
	if (lib != NULL)
	{
		retval = LookupInLibraryHandle(lib, fn);
		dbgdynfn("CDynamicFunction: Lookup of '%s' in '%s': %p\n", fn, libname, retval);
	}

	if (retval == NULL)
		retval = fallback;
	return retval;
}