//---------------------------------------------------------------------------
// Copyright 2005-2023 Joe Lowe
//
// Permission is granted to any person obtaining a copy of this Software,
// to deal in the Software without restriction, including the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and sell copies of
// the Software.
//
// The above copyright and permission notice must be left intact in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS WITHOUT WARRANTY.
//---------------------------------------------------------------------------
// filename:   ptdlfcn.h
// created:    2005.12.02
//
// plugin/dll runtime loader
//---------------------------------------------------------------------------
#ifndef PTDLFCN_H
#define PTDLFCN_H
#include "ptpublic.h"
PT_EXTERNC_START
#ifndef KERNEL

#ifdef _WIN32

#if defined _INC_WINDOWS || defined _APISETLIBLOADER_
#define PTDLFCN_LIBRARY HMODULE
#define PTDLFCN_FARPROC FARPROC
#else
   // This header supports use in code that does not include
   // windows.h . If any of the following definitions cause
   // conflicts then include windows.h before this header.
#define PTDLFCN_LIBRARY void*
#define PTDLFCN_FARPROC void*
#undef LoadLibraryExA
PT_IMPORT void* __stdcall LoadLibraryExA( const char*, void*, unsigned);
PT_IMPORT long __stdcall FreeLibrary( void*);
PT_IMPORT void* __stdcall GetProcAddress( void*, const char*);
#endif

PT_INLINE2 void* pt_dlopen( const char* name) { return LoadLibraryExA( name, 0, 0); }
PT_INLINE2 void pt_dlclose( void* library) { FreeLibrary( PT_RCAST( PTDLFCN_LIBRARY, library)); }
PT_INLINE2 void* pt_dlsym( void* library, const char* symbol_name) { union { PTDLFCN_FARPROC f; void* v; } s; s.f = GetProcAddress( PT_RCAST( PTDLFCN_LIBRARY, library), symbol_name); return s.v; }

#else

#ifndef _BSD_SOURCE
#define _BSD_SOURCE 1
#endif
#include <dlfcn.h>
#include <stdlib.h>

PT_INLINE2 void* pt_dlopen2( const char* name) { return dlopen( name, RTLD_NOW); }
PT_INLINE2 void pt_dlclose( void* library) { dlclose( library); }
PT_INLINE2 void* pt_dlsym( void* library, const char* symbol_name) { return dlsym( library, symbol_name); }

PT_INLINE2 PT_SIZE_T ptdlfcn_szcpy( char* dest, PT_SIZE_T dest_size, const char* src)
{
   if (!src) return 0;
   PT_SIZE_T i = 0;
   while (i != dest_size) { char d = dest[i] = src[i]; if (!d) break; i ++; }
   return i;
}

PT_INLINE2 PT_SIZE_T ptdlfcn_szlen( char* sz)
{
   PT_SIZE_T i = 0; if (sz) { while (sz[i]) i ++; }
   return i;
}

#ifdef __APPLE__

#include <mach-o/dyld.h>

PT_INLINE2 PT_SIZE_T ptdlfcn_get_exec_file( char* dest, PT_SIZE_T dest_size)
{
   if (!dest_size) return 0;
   unsigned n = (unsigned)dest_size;
   if (_NSGetExecutablePath( dest, &n) == 0) return ptdlfcn_szlen( dest);
   dest[0] = 0;
   return 0;
}

#else

#ifdef __linux__

#include <unistd.h>

PT_INLINE2 PT_SIZE_T ptdlfcn_get_exec_file( char* dest, PT_SIZE_T dest_size)
{
   int check = readlink( "/proc/self/exe", dest, dest_size - 1);
   if (check < 0) return ptdlfcn_szcpy( dest, dest_size, getenv( "_"));
   if ((PT_SIZE_T)check >= dest_size) return dest_size;
   dest[check] = 0;
   return (PT_SIZE_T)check;
}

#else

PT_INLINE2 PT_SIZE_T ptdlfcn_get_exec_file( char* dest, PT_SIZE_T dest_size)
{
   return ptdlfcn_szcpy( dest, dest_size, getenv( "_"));
}

#endif
#endif

PT_INLINE2 PT_SIZE_T ptdlfcn_szback( char* dest, PT_SIZE_T i)
{
   while (i && dest[i-1] != '/') i --;
   return i;
}

static void* pt_dlopen( const char* name)
{
   char buf[500]; buf[0] = 0; buf[500-1] = 0;
   PT_SIZE_T i = ptdlfcn_get_exec_file( buf, 500);
   i = ptdlfcn_szback( buf, i);
   i += ptdlfcn_szcpy( buf + i, 500 - i, name);
   void* library = pt_dlopen2( buf);
   if (library) return library;
#ifdef __APPLE__
   i = ptdlfcn_szback( buf, i);
   i = ptdlfcn_szback( buf, i);
   i += ptdlfcn_szcpy( buf + i, 500 - i, "Frameworks/");
   i += ptdlfcn_szcpy( buf + i, 500 - i, name);
   library = pt_dlopen2( buf);
   if (library) return library;
#endif
   library = pt_dlopen2( name);
   if (library) return library;
   i = ptdlfcn_szcpy( buf, 500, "/usr/local/lib/");
   i += ptdlfcn_szcpy( buf + i, 500 - i, name);
   library = pt_dlopen2( name);
   if (library) return library;
   i = ptdlfcn_szcpy( buf, 500, getenv( "HOME"));
   i += ptdlfcn_szcpy( buf + i, 500 - i, ".local/lib/");
   i += ptdlfcn_szcpy( buf + i, 500 - i, name);
   return pt_dlopen2( name);
}

#endif
#endif
PT_EXTERNC_END
#endif
