/**
 * @file fork_safe.c
 * @brief Fork-safety mutex registry and Wine/Proton detection
 *
 * Multi-Vendor GPU Aggregation Layer for Linux
 */

#include "mvgal/mvgal_fork.h"

#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

/* ------------------------------------------------------------------ */
/*  Internal state                                                    */
/* ------------------------------------------------------------------ */

static pthread_mutex_t  g_fork_locks[MVGAL_FORK_MAX_LOCKS];
static int              g_fork_lock_count       = 0;
static volatile bool    g_fork_atfork_armed     = false;
static volatile pid_t   g_fork_parent_pid       = 0;
static volatile bool    g_fork_prepared         = false;

/* ------------------------------------------------------------------ */
/*  pthread_atfork handlers                                           */
/* ------------------------------------------------------------------ */

static void fork_prepare(void)
{
    for (int i = 0; i < g_fork_lock_count; i++) {
        pthread_mutex_lock(&g_fork_locks[i]);
    }
    g_fork_prepared = true;
}

static void fork_parent(void)
{
    /* Release in reverse order */
    for (int i = g_fork_lock_count - 1; i >= 0; i--) {
        pthread_mutex_unlock(&g_fork_locks[i]);
    }
    g_fork_prepared = false;
}

static void fork_child(void)
{
    /*
     * In the child the mutexes are locked (prepare held them).
     * We cannot safely unlock them in a portable way, so we just
     * set the forked flag.  The child MUST call mvgal_init() before
     * using any other MVGAL function; mvgal_init() calls
     * mvgal_fork_reset() which re-initialises every registered mutex
     * while the child is still single-threaded.
     */
    g_fork_prepared     = false;
    g_fork_parent_pid   = getpid();
}

/* ------------------------------------------------------------------ */
/*  Public API                                                        */
/* ------------------------------------------------------------------ */

int mvgal_fork_register(pthread_mutex_t *lock)
{
    if (!lock) return -1;

    /* Dedup */
    for (int i = 0; i < g_fork_lock_count; i++) {
        if (&g_fork_locks[i] == lock) return 0;
    }

    if (g_fork_lock_count >= MVGAL_FORK_MAX_LOCKS) return -1;

    g_fork_locks[g_fork_lock_count++] = *lock;

    /* If atfork is already armed the new lock is NOT covered until
     * the next fork, which is fine — the calling subsystem is still
     * single-threaded during init. */
    return 0;
}

int mvgal_fork_unregister(pthread_mutex_t *lock)
{
    if (!lock || g_fork_lock_count <= 0) return -1;

    int found = -1;
    for (int i = 0; i < g_fork_lock_count; i++) {
        if (&g_fork_locks[i] == lock) {
            found = i;
            break;
        }
    }
    if (found < 0) return -1;

    /* Shift down */
    for (int i = found; i < g_fork_lock_count - 1; i++) {
        g_fork_locks[i] = g_fork_locks[i + 1];
    }
    g_fork_lock_count--;
    return 0;
}

int mvgal_fork_init(void)
{
    if (g_fork_atfork_armed) return 0; /* idempotent */

    g_fork_parent_pid = getpid();

    int rc = pthread_atfork(fork_prepare, fork_parent, fork_child);
    if (rc != 0) return -1;

    g_fork_atfork_armed = true;
    return 0;
}

void mvgal_fork_shutdown(void)
{
    g_fork_lock_count    = 0;
    g_fork_atfork_armed  = false;
    g_fork_parent_pid    = 0;
    g_fork_prepared      = false;
}

void mvgal_fork_reset(void)
{
    /*
     * Called from mvgal_init() after fork().
     * The child is single-threaded so it is safe to re-initialise
     * every registered mutex with pthread_mutex_init().
     */
    for (int i = 0; i < g_fork_lock_count; i++) {
        pthread_mutex_init(&g_fork_locks[i], NULL);
    }
    g_fork_prepared   = false;
    g_fork_parent_pid = getpid();
}

bool mvgal_was_forked(void)
{
    return g_fork_parent_pid != 0 && g_fork_parent_pid != getpid();
}

void mvgal_clear_forked(void)
{
    g_fork_parent_pid = getpid();
}

/* ------------------------------------------------------------------ */
/*  Wine / Proton detection                                           */
/* ------------------------------------------------------------------ */

bool mvgal_running_under_wine(void)
{
    const char *prefix  = getenv("WINEPREFIX");
    const char *wine    = getenv("WINELOADER");
    const char *server  = getenv("WINESERVER");

    if (prefix && prefix[0]) return true;
    if (wine   && wine[0])   return true;
    if (server && server[0]) return true;
    return false;
}

bool mvgal_running_under_proton(void)
{
    const char *steam_compat = getenv("STEAM_COMPAT_DATA_PATH");
    const char *proton_ver   = getenv("PROTON_VERSION");
    const char *wineprefix  = getenv("WINEPREFIX");

    if (steam_compat && steam_compat[0]) return true;
    if (proton_ver   && proton_ver[0])   return true;
    if (wineprefix   && wineprefix[0])   return true;
    return false;
}
