// dlfcn.h -- runtime dynamic linking.

#pragma once

#ifndef NULL
#define NULL 0
#endif

// dlopen() mode flags. RTLD_LAZY / RTLD_NOW agree across hosts, but the
// scope and extra-mode bits diverge -- the value reaches the host's
// dlopen, so each target uses its own numbering. On Windows these are
// no-ops for cross-platform fixtures that pass them through.
#define RTLD_LAZY    1
#define RTLD_NOW     2
#ifdef __APPLE__
#define RTLD_LOCAL    4
#define RTLD_GLOBAL   8
#define RTLD_NOLOAD   0x10
#define RTLD_NODELETE 0x80
#define RTLD_FIRST    0x100
#elif defined(__linux__)
#define RTLD_LOCAL    0
#define RTLD_GLOBAL   0x100
#define RTLD_NOLOAD   0x04
#define RTLD_DEEPBIND 0x08
#define RTLD_NODELETE 0x1000
// Linux `dladdr1` request type: yield the matching object's link_map.
#define RTLD_DL_LINKMAP 2
#define RTLD_DL_SYMENT  1
#else
#define RTLD_LOCAL   0
#define RTLD_GLOBAL  256
#endif

// `RTLD_DEFAULT` / `RTLD_NEXT` are pseudo-handles whose numeric
// values differ across platforms:
//
//   * Linux:          DEFAULT = NULL,         NEXT = (void*)-1
//   * macOS dyld:     DEFAULT = (void*)-2,    NEXT = (void*)-1
//   * Windows:        not meaningful (LoadLibraryA wants a real path)
//
// Get them right per-target -- a NULL handle on macOS dlsym hits
// the slow "search every image" fallback instead of the dyld
// shortcut that DEFAULT actually maps to. Programs that pass
// RTLD_DEFAULT to dlsym will silently miss libc symbols on macOS
// otherwise.
#ifdef __APPLE__
#define RTLD_DEFAULT ((void*)-2)
#define RTLD_NEXT    ((void*)-1)
#endif
#ifdef __linux__
#define RTLD_DEFAULT ((void*)0)
#define RTLD_NEXT    ((void*)-1)
#endif
#ifdef _WIN32
#define RTLD_DEFAULT ((void*)0)
#define RTLD_NEXT    ((void*)-1)
#endif

#ifdef __APPLE__
#pragma dylib(libc, "/usr/lib/libSystem.B.dylib")
#pragma binding(libc::dlopen,  "_dlopen")
#pragma binding(libc::dlsym,   "_dlsym")
#pragma binding(libc::dlclose, "_dlclose")
#pragma binding(libc::dlerror, "_dlerror")
#pragma binding(libc::dladdr,  "_dladdr")
#endif

#ifdef __linux__
#pragma dylib(libdl, "libdl.so.2")
#pragma binding(libdl::dlopen,  "dlopen")
#pragma binding(libdl::dlsym,   "dlsym")
#pragma binding(libdl::dlclose, "dlclose")
#pragma binding(libdl::dlerror, "dlerror")
#pragma binding(libdl::dladdr,  "dladdr")
// GNU extension: dladdr plus an extra out-parameter selected by
// `flags` (RTLD_DL_LINKMAP / RTLD_DL_SYMENT).
#pragma binding(libdl::dladdr1, "dladdr1")
#endif

#ifdef _WIN32
#pragma dylib(kernel32, "kernel32.dll")
#pragma binding(kernel32::dlopen,  "LoadLibraryA")
#pragma binding(kernel32::dlsym,   "GetProcAddress")
#pragma binding(kernel32::dlclose, "FreeLibrary")
#pragma binding(kernel32::dlerror, "GetLastError")
#endif

char *dlopen(char *path, int flags);
char *dlsym(char *handle, char *name);
int dlclose(char *handle);
char *dlerror();

#ifndef __BADC_WINDOWS__
// Symbol / module lookup for an address (POSIX `dladdr`).
typedef struct {
    const char *dli_fname;
    void *dli_fbase;
    const char *dli_sname;
    void *dli_saddr;
} Dl_info;
int dladdr(const void *addr, Dl_info *info);
#ifdef __linux__
int dladdr1(const void *addr, Dl_info *info, void **extra_info, int flags);
#endif
#endif
