{% import "utils.j2" as u with context %}
{#- Built-in platform loader. Only included when --loader is passed. -#}
{% include "library.j2" %}
{% if fs.spec_name in ["gl", "gles1", "gles2", "glcore"] %}
/* ---- GL / GLES built-in loader ----------------------------------------- */

/* Transient userptr: set for the duration of a gloamLoaderLoad*Context call.
 * Holds the library handle and — for desktop GL — the correctly-typed
 * platform proc-addr function pointer so that the calling convention is
 * always right. Only valid between entry and return; never accessed
 * concurrently.
 */
struct gloam_gl_load_userptr {
    void *handle;
#if defined(GLOAM_PLATFORM_WINDOWS)
    /* wglGetProcAddress is WINAPI (__stdcall); must not be called through a
     * plain cdecl function pointer.
	 */
    GloamAPIProc (WINAPI *wgl_get_proc)(const char *);
#elif !defined(__APPLE__) && !defined(__HAIKU__)
    GloamAPIProc (*glx_get_proc)(const char *);
#endif
};
static struct gloam_gl_load_userptr gloam_gl_load_state;

/* GL desktop adapter — matches GloamLoadFunc (__cdecl), dispatches through
 * the correctly-typed platform proc-addr pointer stored in the userptr.
 */
static GloamAPIProc gloam_gl_get_proc(const char *name)
{
    GloamAPIProc result = NULL;
    struct gloam_gl_load_userptr *u = &gloam_gl_load_state;
    if (!u->handle)
        return NULL;
#if defined(__APPLE__) || defined(__HAIKU__)
    result = (GloamAPIProc)gloam_dlsym(u->handle, name);
#elif defined(GLOAM_PLATFORM_WINDOWS)
    if (u->wgl_get_proc)
        result = u->wgl_get_proc(name);
    if (!result)
        result = (GloamAPIProc)gloam_dlsym(u->handle, name);
#else
    if (u->glx_get_proc)
        result = u->glx_get_proc(name);
    if (!result)
        result = (GloamAPIProc)gloam_dlsym(u->handle, name);
#endif
    return result;
}

/* GLES adapter: all symbols (including extensions) are exported directly from
 * the GLES library so plain dlsym is sufficient — no platform indirection.
 */
static GloamAPIProc gloam_gles_get_proc(const char *name)
{
    struct gloam_gl_load_userptr *u = &gloam_gl_load_state;
    if (!u->handle)
        return NULL;
    return (GloamAPIProc)gloam_dlsym(u->handle, name);
}

{%- for api in fs.apis %}

int gloamLoaderLoad{{ api | api_display }}Context({{ u.ctx_arg() }})
{
    int did_open = 0;
    int version;
    void *handle;
{% if api in ["gles1", "gles2"] %}
    static const char * const kLibNames[] = {
#if defined(__APPLE__)
        "libGLESv2.dylib",
#elif defined(GLOAM_PLATFORM_WINDOWS)
        "GLESv2.dll", "libGLESv2.dll",
#else
        "libGLESv2.so.2", "libGLESv2.so",
#endif
    };
{% else %}
    static const char * const kLibNames[] = {
#if defined(__APPLE__)
        "../Frameworks/OpenGL.framework/OpenGL",
        "/Library/Frameworks/OpenGL.framework/OpenGL",
        "/System/Library/Frameworks/OpenGL.framework/OpenGL",
        "/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL",
#elif defined(GLOAM_PLATFORM_WINDOWS)
        "opengl32.dll",
#else
        "libGL.so.1",
        "libGL.so",
#endif
    };
{% endif %}
    handle = context->gloam_loader_handle;

    if (!handle) {
        handle = gloam_open_library(kLibNames, GLOAM_ARRAYSIZE(kLibNames));
        did_open = 1;
    }

    if (!handle)
        return 0;

    gloam_gl_load_state.handle = handle;
#if defined(GLOAM_PLATFORM_WINDOWS)
    gloam_gl_load_state.wgl_get_proc = (GloamAPIProc (WINAPI *)(const char *))gloam_dlsym(handle, "wglGetProcAddress");
#elif !defined(__APPLE__) && !defined(__HAIKU__)
    gloam_gl_load_state.glx_get_proc = (GloamAPIProc (*)(const char *))gloam_dlsym(handle, "glXGetProcAddressARB");
#endif

{% if api in ["gles1", "gles2"] %}
    version = gloamLoad{{ api | api_display }}Context(context, gloam_gles_get_proc);
{% else %}
    version = gloamLoad{{ api | api_display }}Context(context, gloam_gl_get_proc);
{% endif %}
    gloam_gl_load_state.handle = NULL;

    if (!version && did_open) {
        gloam_dlclose(handle);
        return 0;
    }

    context->gloam_loader_handle = handle;
    context->gloam_loader_owns_handle |= (uint8_t)did_open;

    return version;
}

int gloamLoaderLoad{{ api | api_display }}(void)
{
    return gloamLoaderLoad{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context);
}

void gloamLoaderUnload{{ api | api_display }}Context({{ u.ctx_arg() }})
{
    if (context->gloam_loader_handle && context->gloam_loader_owns_handle) {
        gloam_dlclose(context->gloam_loader_handle);
    }
    gloamLoaderReset{{ api | api_display }}Context(context);
}

void gloamLoaderUnload{{ api | api_display }}(void)
{
    gloamLoaderUnload{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context);
}

void gloamLoaderReset{{ api | api_display }}Context({{ u.ctx_arg() }})
{
    memset(context, 0, sizeof(*context));
}

void gloamLoaderReset{{ api | api_display }}(void)
{
    gloamLoaderReset{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context);
}
{%- endfor %}
{% elif fs.spec_name == "egl" %}
/* ---- EGL built-in loader ------------------------------------------------ */

static const char * const gloam_egl_lib_names[] = {
#if defined(__APPLE__)
    "libEGL.dylib",
#elif defined(GLOAM_PLATFORM_WINDOWS)
    "libEGL.dll", "EGL.dll",
#else
    "libEGL.so.1", "libEGL.so",
#endif
};

/* eglGetProcAddress has its own calling convention (declared in EGL headers as
 * PFNEGLGETPROCADDRESSPROC). Store it with the correct type so the call always
 * uses the right convention, even when the default differs (e.g. Win32).
 */
struct gloam_egl_load_userptr {
    void *handle;
    PFNEGLGETPROCADDRESSPROC get_proc_address;
};
static struct gloam_egl_load_userptr gloam_egl_load_state;

static GloamAPIProc gloam_egl_get_proc(const char *name)
{
    struct gloam_egl_load_userptr *u = &gloam_egl_load_state;
    GloamAPIProc result = (GloamAPIProc)gloam_dlsym(u->handle, name);
    if (!result && u->get_proc_address)
        result = u->get_proc_address(name);
    return result;
}

{% for api in fs.apis %}
int gloamLoaderLoad{{ api | api_display }}Context({{ u.ctx_arg(', ') }}EGLDisplay display)
{
    int did_open = 0;
    int version;
    void *handle;

    handle = context->gloam_loader_handle;

    if (!handle) {
        handle = gloam_open_library(gloam_egl_lib_names, GLOAM_ARRAYSIZE(gloam_egl_lib_names));
        did_open = 1;
    }

    if (!handle)
        return 0;

    gloam_egl_load_state.handle = handle;
    gloam_egl_load_state.get_proc_address =
        (PFNEGLGETPROCADDRESSPROC)gloam_dlsym(handle, "eglGetProcAddress");

    if (!gloam_egl_load_state.get_proc_address) {
        if (did_open)
            gloam_dlclose(handle);
        return 0;
    }

    version = gloamLoad{{ api | api_display }}Context(context, display, gloam_egl_get_proc);
    gloam_egl_load_state.handle = NULL;

    if (!version && did_open) {
        gloam_dlclose(handle);
        return 0;
    }

    context->gloam_loader_handle = handle;
    context->gloam_loader_owns_handle |= (uint8_t)did_open;

    return version;
}

int gloamLoaderLoad{{ api | api_display }}(EGLDisplay display)
{
    return gloamLoaderLoad{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context, display);
}

void gloamLoaderUnload{{ api | api_display }}Context({{ u.ctx_arg() }})
{
    if (context->gloam_loader_handle && context->gloam_loader_owns_handle) {
        gloam_dlclose(context->gloam_loader_handle);
    }
    gloamLoaderReset{{ api | api_display }}Context(context);
}

void gloamLoaderUnload{{ api | api_display }}(void)
{
    gloamLoaderUnload{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context);
}

void gloamLoaderReset{{ api | api_display }}Context({{ u.ctx_arg() }})
{
    memset(context, 0, sizeof(*context));
}

void gloamLoaderReset{{ api | api_display }}(void)
{
    gloamLoaderReset{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context);
}
{%- endfor %}
{%- elif fs.spec_name == "glx" %}
/* ---- GLX built-in loader ------------------------------------------------ */

static const char * const gloam_glx_lib_names[] = {
#if defined(__CYGWIN__)
    "libGL-1.so",
#endif
    "libGL.so.1", "libGL.so",
};

/* glXGetProcAddressARB returns __GLXextFuncPtr = void(*)(void); store with the
 * correct function-pointer type so the call goes through the right ABI.
 */
struct gloam_glx_load_userptr {
    void *handle;
    GloamAPIProc (*get_proc_address)(const char *);
};
static struct gloam_glx_load_userptr gloam_glx_load_state;

static GloamAPIProc gloam_glx_get_proc(const char *name)
{
    struct gloam_glx_load_userptr *u = &gloam_glx_load_state;
    GloamAPIProc result = NULL;
    if (u->get_proc_address)
        result = u->get_proc_address(name);
    if (!result)
        result = (GloamAPIProc)gloam_dlsym(u->handle, name);
    return result;
}

{%- for api in fs.apis %}

int gloamLoaderLoad{{ api | api_display }}Context({{ u.ctx_arg(', ') }}Display *display, int screen)
{
    int did_open = 0, version;
    void *handle;

    handle = context->gloam_loader_handle;

    if (!handle) {
        handle = gloam_open_library(gloam_glx_lib_names, GLOAM_ARRAYSIZE(gloam_glx_lib_names));
        did_open = 1;
    }

    if (!handle)
        return 0;

    gloam_glx_load_state.handle = handle;
    gloam_glx_load_state.get_proc_address = (GloamAPIProc (*)(const char *))gloam_dlsym(handle, "glXGetProcAddressARB");

    if (!gloam_glx_load_state.get_proc_address) {
        if (did_open)
            gloam_dlclose(handle);
        return 0;
    }

    version = gloamLoad{{ api | api_display }}Context(context, display, screen, gloam_glx_get_proc);
    gloam_glx_load_state.handle = NULL;

    if (!version && did_open) {
        gloam_dlclose(handle);
        return 0;
    }

    context->gloam_loader_handle = handle;
    context->gloam_loader_owns_handle |= (uint8_t)did_open;

    return version;
}

int gloamLoaderLoad{{ api | api_display }}(Display *display, int screen)
{
    return gloamLoaderLoad{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context, display, screen);
}

void gloamLoaderUnload{{ api | api_display }}Context({{ u.ctx_arg() }})
{
    if (context->gloam_loader_handle && context->gloam_loader_owns_handle) {
        gloam_dlclose(context->gloam_loader_handle);
    }
    gloamLoaderReset{{ api | api_display }}Context(context);
}

void gloamLoaderUnload{{ api | api_display }}(void)
{
    gloamLoaderUnload{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context);
}

void gloamLoaderReset{{ api | api_display }}Context({{ u.ctx_arg() }})
{
    memset(context, 0, sizeof(*context));
}

void gloamLoaderReset{{ api | api_display }}(void)
{
    gloamLoaderReset{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context);
}
{%- endfor %}
{% elif fs.spec_name == "wgl" %}
/* ---- WGL built-in loader ------------------------------------------------ */

static const char * const gloam_wgl_lib_names[] = { "opengl32.dll" };

/* wglGetProcAddress is WINAPI (__stdcall); must not be stored or called through
 * a plain cdecl pointer. Store with the correct type in a userptr struct.
 */
struct gloam_wgl_load_userptr {
    void *handle;
    GloamAPIProc (WINAPI *wgl_get_proc)(const char *);
};
static struct gloam_wgl_load_userptr gloam_wgl_load_state;

static GloamAPIProc gloam_wgl_get_proc(const char *name)
{
    struct gloam_wgl_load_userptr *u = &gloam_wgl_load_state;
    GloamAPIProc result = NULL;
    if (u->wgl_get_proc)
        result = u->wgl_get_proc(name);
    if (!result)
        result = (GloamAPIProc)gloam_dlsym(u->handle, name);
    return result;
}

{%- for api in fs.apis %}
int gloamLoaderLoad{{ api | api_display }}Context({{ u.ctx_arg(', ') }}HDC hdc)
{
    int did_open = 0;
    int version;
    void *handle = context->gloam_loader_handle;

    if (!handle) {
        handle = gloam_open_library(
            gloam_wgl_lib_names, GLOAM_ARRAYSIZE(gloam_wgl_lib_names));
        did_open = 1;
    }

    if (!handle)
        return 0;

    gloam_wgl_load_state.handle = handle;
    gloam_wgl_load_state.wgl_get_proc =
        (GloamAPIProc (WINAPI *)(const char *))
            gloam_dlsym(handle, "wglGetProcAddress");

    if (!gloam_wgl_load_state.wgl_get_proc) {
        if (did_open)
            gloam_dlclose(handle);
        return 0;
    }

    version = gloamLoad{{ api | api_display }}Context(context, hdc, gloam_wgl_get_proc);
    gloam_wgl_load_state.handle = NULL;

    if (!version && did_open) {
        gloam_dlclose(handle);
        return 0;
    }

    context->gloam_loader_handle = handle;
    context->gloam_loader_owns_handle |= (uint8_t)did_open;

    return version;
}

int gloamLoaderLoad{{ api | api_display }}(HDC hdc)
{
    return gloamLoaderLoad{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context, hdc);
}

void gloamLoaderUnload{{ api | api_display }}Context({{ u.ctx_arg() }})
{
    if (context->gloam_loader_handle && context->gloam_loader_owns_handle) {
        gloam_dlclose(context->gloam_loader_handle);
    }
    gloamLoaderReset{{ api | api_display }}Context(context);
}

void gloamLoaderUnload{{ api | api_display }}(void)
{
    gloamLoaderUnload{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context);
}

void gloamLoaderReset{{ api | api_display }}Context({{ u.ctx_arg() }})
{
    memset(context, 0, sizeof(*context));
}

void gloamLoaderReset{{ api | api_display }}(void)
{
    gloamLoaderReset{{ api | api_display }}Context(&gloam_{{ fs.spec_name }}_context);
}
{%- endfor %}
{% elif fs.spec_name == "vk" %}
/* ---- Vulkan built-in discovery loader ----------------------------------- */

/* gloamLoaderLoadVulkanContext
 *
 * Opens the Vulkan library into context->gloam_loader_handle if it is not
 * already set, stores vkGetInstanceProcAddr in the context, then delegates to
 * gloamVulkanDiscoverContext. Follows the same additive multi-call contract
 * as the underlying discover function.
 */
int gloamLoaderLoadVulkanContext({{ u.ctx_arg(', ') }}VkInstance instance, VkPhysicalDevice physical_device, VkDevice device)
{
    int did_open = 0;
    int version;
    void *handle;

    handle = context->gloam_loader_handle;

    if (!handle) {
        handle = gloam_open_library(
            gloam_vk_lib_names, GLOAM_ARRAYSIZE(gloam_vk_lib_names));
        did_open = 1;
    }

    if (!handle)
        return 0;

    if (!context->GetInstanceProcAddr)
        context->GetInstanceProcAddr =
            (PFN_vkGetInstanceProcAddr)gloam_dlsym(handle, "vkGetInstanceProcAddr");

    if (!context->GetInstanceProcAddr) {
        if (did_open)
            gloam_dlclose(handle);
        return 0;
    }

    version = gloamVulkanDiscoverContext(context, instance, physical_device, device);

    if (!version && did_open) {
        gloam_dlclose(handle);
        return 0;
    }

    context->gloam_loader_handle = handle;
    context->gloam_loader_owns_handle |= (uint8_t)did_open;

    return version;
}

int gloamLoaderLoadVulkan(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device)
{
    return gloamLoaderLoadVulkanContext(&gloam_vk_context, instance,
                                        physical_device, device);
}

/* Close the library handle (if set) then zero all context state. */
void gloamLoaderUnloadVulkanContext({{ u.ctx_arg() }})
{
    if (context->gloam_loader_handle && context->gloam_loader_owns_handle) {
        gloam_dlclose(context->gloam_loader_handle);
    }
    gloamLoaderResetVulkanContext(context);
}

void gloamLoaderUnloadVulkan(void)
{
    gloamLoaderUnloadVulkanContext(&gloam_vk_context);
}

/* Zero all context state without touching the library handle. */
void gloamLoaderResetVulkanContext({{ u.ctx_arg() }})
{
    memset(context, 0, sizeof(*context));
}

void gloamLoaderResetVulkan(void)
{
    gloamLoaderResetVulkanContext(&gloam_vk_context);
}
{% endif %}
