// libc symbols referenced by SuperCollider / libc++ that the minimal Rust-based wasm32-libc does
// not provide. Grouped by WHY they are needed. Stubs here are either (a) correct single-threaded
// implementations, or (b) no-ops for genuinely-unreached code paths under mRealTime=false.
//
// Real math (sinf/cosf/powf/...) is provided by compiled musl sources (see build.rs), NOT stubbed,
// because the DSP depends on it.
//
// We deliberately do NOT `#include <stdio.h>`: the fprintf/fwrite/puts stubs below redefine those
// symbols against our own opaque `GapFile` rather than musl's `FILE`, and pulling in the real
// header would clash with those definitions. The three resulting -Wbuiltin-requires-header warnings
// are harmless (the build.rs C compile passes `-w`).

#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>

// ---------------------------------------------------------------------------
// Aligned allocation.
//
// SC over-aligns its real-time memory pool and bus buffers (SC_MEMORY_ALIGNMENT=32) via
// boost::align::aligned_alloc, which (on a POSIX target) calls ::posix_memalign, and via libc++'s
// aligned operator new, which calls ::aligned_alloc - returning unaligned memory would corrupt SIMD
// loads in the UGens.
//
// CRUCIAL: both boost::align::aligned_free and libc++'s aligned operator delete free the result with
// PLAIN ::free, not a paired aligned-free. wasm32-libc's malloc already returns pointers aligned to
// MALLOC_MAX_ALIGN (64) and freeable by ::free, so for the alignments SC actually uses (boost::align
// requests 64; libc++ aligned new <= alignof(max) = 16) we delegate straight to malloc - the pointer
// is correctly aligned AND ::free-able, with no over-allocate/offset trickery to break ::free. Only
// for the (never-exercised) > 64 case do we fall back to an over-allocate-and-store-base scheme,
// which then REQUIRES aligned_free.
// ---------------------------------------------------------------------------

// Must match wasm32-libc's malloc alignment guarantee (src/ffi/malloc.rs MAX_ALIGN).
#define MALLOC_MAX_ALIGN 64

static void* aligned_alloc_impl(size_t alignment, size_t size) {
    if (alignment <= MALLOC_MAX_ALIGN)
        return malloc(size); // already MALLOC_MAX_ALIGN-aligned and ::free-able.
    // Over-allocate room for the alignment slack plus one pointer to recover the base on free.
    size_t total = size + alignment + sizeof(void*);
    void* base = malloc(total);
    if (!base)
        return NULL;
    uintptr_t raw = (uintptr_t)base + sizeof(void*);
    uintptr_t aligned = (raw + (alignment - 1)) & ~(uintptr_t)(alignment - 1);
    ((void**)aligned)[-1] = base; // stash base pointer just before the aligned block
    return (void*)aligned;
}

void* aligned_alloc(size_t alignment, size_t size) { return aligned_alloc_impl(alignment, size); }

int posix_memalign(void** out, size_t alignment, size_t size) {
    void* p = aligned_alloc_impl(alignment, size);
    if (!p)
        return 12; // ENOMEM
    *out = p;
    return 0;
}

// Frees an allocation from aligned_alloc / posix_memalign whose alignment EXCEEDED MALLOC_MAX_ALIGN
// (the offset scheme above). For alignment <= MALLOC_MAX_ALIGN the pointer is a plain malloc result
// and callers ::free it directly; this path is only reached if such a caller routes here explicitly.
void aligned_free(void* ptr) {
    if (ptr)
        free(((void**)ptr)[-1]);
}

// ---------------------------------------------------------------------------
// stdio fallbacks for libc++'s verbose_abort path and SC's scprintf default.
// Reached only on a fatal abort or when no PrintFunc is set (we set a no-op PrintFunc). No-ops.
// ---------------------------------------------------------------------------

typedef struct _GapFile GapFile;
GapFile* stderr;
GapFile* stdout;

int vfprintf(GapFile* stream, const char* format, va_list ap) {
    (void)stream;
    (void)format;
    (void)ap;
    return 0;
}
int fprintf(GapFile* stream, const char* format, ...) {
    (void)stream;
    (void)format;
    return 0;
}
int fputc(int c, GapFile* stream) {
    (void)stream;
    return c;
}
int fputs(const char* s, GapFile* stream) {
    (void)s;
    (void)stream;
    return 0;
}
int fflush(GapFile* stream) {
    (void)stream;
    return 0;
}
int puts(const char* s) {
    (void)s;
    return 0;
}

// ---------------------------------------------------------------------------
// Single-threaded pthread shims.
//
// libc++'s std::mutex / std::condition_variable (used by SC_Lock / SC_SyncCondition) and boost's
// emulation semaphore expand to pthread_* calls. Under mRealTime=false scsynth runs single-threaded
// (no NRT/audio thread is spawned - SC_NewAudioDriver is never called), so mutex lock/unlock are
// uncontended: returning success without doing anything is the CORRECT single-threaded behaviour
// and cannot deadlock. pthread_create returns an error so that, were any thread-spawn path ever
// reached, it would fail loudly rather than silently run nothing.
// ---------------------------------------------------------------------------

typedef struct {
    long _dummy;
} gap_pthread_t;

int pthread_mutex_init(void* m, const void* attr) {
    (void)m;
    (void)attr;
    return 0;
}
int pthread_mutex_destroy(void* m) {
    (void)m;
    return 0;
}
int pthread_mutex_lock(void* m) {
    (void)m;
    return 0;
}
int pthread_mutex_trylock(void* m) {
    (void)m;
    return 0;
}
int pthread_mutex_unlock(void* m) {
    (void)m;
    return 0;
}

int pthread_cond_init(void* c, const void* attr) {
    (void)c;
    (void)attr;
    return 0;
}
int pthread_cond_destroy(void* c) {
    (void)c;
    return 0;
}
int pthread_cond_signal(void* c) {
    (void)c;
    return 0;
}
int pthread_cond_broadcast(void* c) {
    (void)c;
    return 0;
}
int pthread_cond_wait(void* c, void* m) {
    // Reached only on the audio/NRT thread's blocking wait, which never runs under mRealTime=false.
    (void)c;
    (void)m;
    return 0;
}
int pthread_cond_timedwait(void* c, void* m, const void* ts) {
    (void)c;
    (void)m;
    (void)ts;
    return 0;
}

int pthread_create(void* thread, const void* attr, void* (*start)(void*), void* arg) {
    (void)thread;
    (void)attr;
    (void)start;
    (void)arg;
    return 1; // EPERM-ish: no threads on wasm; must never be reached under mRealTime=false.
}
int pthread_join(gap_pthread_t thread, void** retval) {
    (void)thread;
    (void)retval;
    return 0;
}
int pthread_detach(gap_pthread_t thread) {
    (void)thread;
    return 0;
}
gap_pthread_t pthread_self(void) {
    gap_pthread_t t = { 0 };
    return t;
}
int pthread_equal(gap_pthread_t a, gap_pthread_t b) {
    (void)a;
    (void)b;
    return 1;
}

// ---------------------------------------------------------------------------
// Time. server_timeseed() seeds the RNGs; the 440 Hz test uses SinOsc (deterministic, no entropy
// needed). gettimeofday is referenced by SC time code; return a fixed epoch.
// ---------------------------------------------------------------------------

struct gap_timeval {
    long tv_sec;
    long tv_usec;
};
int gettimeofday(struct gap_timeval* tv, void* tz) {
    (void)tz;
    if (tv) {
        tv->tv_sec = 0;
        tv->tv_usec = 0;
    }
    return 0;
}

struct gap_timespec {
    long tv_sec;
    long tv_nsec;
};
int clock_gettime(int clk, struct gap_timespec* ts) {
    (void)clk;
    if (ts) {
        ts->tv_sec = 0;
        ts->tv_nsec = 0;
    }
    return 0;
}
int nanosleep(const struct gap_timespec* req, struct gap_timespec* rem) {
    (void)req;
    (void)rem;
    return 0; // single-threaded, nothing to sleep for.
}

// ---------------------------------------------------------------------------
// Real math: modf (used by DelayUGens). MUST be correct - the DSP relies on it.
// ---------------------------------------------------------------------------
double modf(double x, double* iptr) {
    // Integer part toward zero; fractional part keeps x's sign. __builtin_trunc lowers to a wasm
    // f64.trunc instruction (no libc dependency).
    double i = __builtin_trunc(x);
    *iptr = i;
    return x - i;
}

// ---------------------------------------------------------------------------
// ctype + string helpers libc++/SC reference but the minimal libc omits.
// ---------------------------------------------------------------------------
int tolower(int c) { return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; }
int toupper(int c) { return (c >= 'a' && c <= 'z') ? c - ('a' - 'A') : c; }

// strtod / strtof / strtold: a correct base-10 floating-point parser (the minimal libc lacks them).
// Handles optional sign, fractional part, and a decimal exponent. Not bit-exact for the last ULP of
// adversarial inputs, but the engine only parses simple OSC/text numbers (never DSP-critical sample
// data, which arrives as binary IEEE floats).
double strtod(const char* s, char** end) {
    const char* p = s;
    while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == '\f' || *p == '\v')
        ++p;
    int sign = 1;
    if (*p == '+' || *p == '-') {
        if (*p == '-')
            sign = -1;
        ++p;
    }
    double value = 0.0;
    int any = 0;
    while (*p >= '0' && *p <= '9') {
        value = value * 10.0 + (*p - '0');
        ++p;
        any = 1;
    }
    if (*p == '.') {
        ++p;
        double scale = 0.1;
        while (*p >= '0' && *p <= '9') {
            value += (*p - '0') * scale;
            scale *= 0.1;
            ++p;
            any = 1;
        }
    }
    if (any && (*p == 'e' || *p == 'E')) {
        const char* e = p + 1;
        int esign = 1;
        if (*e == '+' || *e == '-') {
            if (*e == '-')
                esign = -1;
            ++e;
        }
        if (*e >= '0' && *e <= '9') {
            int exp = 0;
            while (*e >= '0' && *e <= '9') {
                exp = exp * 10 + (*e - '0');
                ++e;
            }
            p = e;
            double factor = 1.0;
            for (int i = 0; i < exp; ++i)
                factor *= 10.0;
            value = (esign < 0) ? value / factor : value * factor;
        }
    }
    if (end)
        *end = (char*)(any ? p : s);
    return sign * value;
}
float strtof(const char* s, char** end) { return (float)strtod(s, end); }
long double strtold(const char* s, char** end) { return (long double)strtod(s, end); }

static char gap_unknown_error[] = "unknown error";
char* strerror(int errnum) {
    (void)errnum;
    return gap_unknown_error;
}
int strerror_r(int errnum, char* buf, size_t buflen) {
    (void)errnum;
    if (buf && buflen) {
        size_t i = 0;
        for (; gap_unknown_error[i] && i + 1 < buflen; ++i)
            buf[i] = gap_unknown_error[i];
        buf[i] = 0;
    }
    return 0;
}

// ---------------------------------------------------------------------------
// stdio that SC's logging / number formatting touches. We have no real streams; behave as no-ops or
// route to snprintf (provided by wasm32-libc) where a buffer is involved.
//
// Reached only on logging / error / /version reply paths (the DSP never formats text). printf-family
// no-ops simply discard output, which is correct for a headless engine with verbosity = -1.
// ---------------------------------------------------------------------------
GapFile* stdin;

int printf(const char* fmt, ...) {
    (void)fmt;
    return 0;
}
int vprintf(const char* fmt, va_list ap) {
    (void)fmt;
    (void)ap;
    return 0;
}
int sprintf(char* s, const char* fmt, ...) {
    // Best-effort: emit an empty string. SC uses sprintf only for log/reply text.
    if (s)
        s[0] = 0;
    (void)fmt;
    return 0;
}
int sscanf(const char* s, const char* fmt, ...) {
    (void)s;
    (void)fmt;
    return 0;
}
int vsscanf(const char* s, const char* fmt, va_list ap) {
    (void)s;
    (void)fmt;
    (void)ap;
    return 0;
}
int vasprintf(char** out, const char* fmt, va_list ap) {
    (void)fmt;
    (void)ap;
    if (out)
        *out = NULL;
    return -1;
}
size_t fwrite(const void* ptr, size_t size, size_t nmemb, GapFile* stream) {
    (void)ptr;
    (void)stream;
    (void)size;
    return nmemb;
}
int getc(GapFile* stream) {
    (void)stream;
    return -1; // EOF
}
int ungetc(int c, GapFile* stream) {
    (void)stream;
    return c;
}

// ---------------------------------------------------------------------------
// wide-char + locale: pulled in by libc++ <locale>/<sstream> number formatting. Provide minimal
// correct ASCII-only behaviour (the engine only ever handles ASCII OSC/text).
// ---------------------------------------------------------------------------
size_t __ctype_get_mb_cur_max(void) { return 1; }

typedef int gap_wchar_t;

size_t wcslen(const gap_wchar_t* s) {
    size_t n = 0;
    if (s)
        while (s[n])
            ++n;
    return n;
}
gap_wchar_t* wmemchr(const gap_wchar_t* s, gap_wchar_t c, size_t n) {
    if (s)
        for (size_t i = 0; i < n; ++i)
            if (s[i] == c)
                return (gap_wchar_t*)(s + i);
    return NULL;
}
int mbtowc(gap_wchar_t* pwc, const char* s, size_t n) {
    if (!s)
        return 0;
    if (n == 0)
        return -1;
    if (pwc)
        *pwc = (unsigned char)*s;
    return *s ? 1 : 0;
}
size_t mbrtowc(gap_wchar_t* pwc, const char* s, size_t n, void* ps) {
    (void)ps;
    if (!s)
        return 0;
    if (n == 0)
        return (size_t)-2;
    if (pwc)
        *pwc = (unsigned char)*s;
    return *s ? 1 : 0;
}
size_t wcrtomb(char* s, gap_wchar_t wc, void* ps) {
    (void)ps;
    if (s)
        *s = (char)wc;
    return 1;
}
size_t mbrlen(const char* s, size_t n, void* ps) {
    return mbrtowc(NULL, s, n, ps);
}
size_t mbsrtowcs(gap_wchar_t* dst, const char** src, size_t len, void* ps) {
    (void)ps;
    if (!src || !*src)
        return 0;
    const char* s = *src;
    size_t i = 0;
    for (; (dst ? i < len : 1) && s[i]; ++i)
        if (dst)
            dst[i] = (unsigned char)s[i];
    if (dst) {
        if (i < len)
            dst[i] = 0;
        *src = s[i] ? s + i : NULL;
    }
    return i;
}
size_t mbsnrtowcs(gap_wchar_t* dst, const char** src, size_t nms, size_t len, void* ps) {
    (void)nms;
    return mbsrtowcs(dst, src, len, ps);
}
size_t wcsnrtombs(char* dst, const gap_wchar_t** src, size_t nwc, size_t len, void* ps) {
    (void)ps;
    if (!src || !*src)
        return 0;
    const gap_wchar_t* s = *src;
    size_t i = 0;
    for (; (dst ? i < len : 1) && i < nwc && s[i]; ++i)
        if (dst)
            dst[i] = (char)s[i];
    if (dst)
        *src = s[i] ? s + i : NULL;
    return i;
}
int fputwc(gap_wchar_t c, GapFile* stream) {
    (void)stream;
    return c;
}
gap_wchar_t getwc(GapFile* stream) {
    (void)stream;
    return -1;
}
gap_wchar_t ungetwc(gap_wchar_t c, GapFile* stream) {
    (void)stream;
    return c;
}

// locale objects: libc++ wants a non-null locale_t handle. Hand back a fixed dummy and treat all
// locale-aware queries as the C locale (ASCII), which is what the engine assumes anyway.
typedef void* gap_locale_t;
static int gap_locale_obj;
gap_locale_t newlocale(int mask, const char* name, gap_locale_t base) {
    (void)mask;
    (void)name;
    (void)base;
    return &gap_locale_obj;
}
void freelocale(gap_locale_t loc) { (void)loc; }
gap_locale_t uselocale(gap_locale_t loc) {
    (void)loc;
    return &gap_locale_obj;
}

int isdigit_l(int c, gap_locale_t loc) {
    (void)loc;
    return c >= '0' && c <= '9';
}
int isxdigit_l(int c, gap_locale_t loc) {
    (void)loc;
    return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
double strtod_l(const char* s, char** end, gap_locale_t loc) {
    (void)loc;
    return strtod(s, end);
}
float strtof_l(const char* s, char** end, gap_locale_t loc) {
    (void)loc;
    return strtof(s, end);
}
long double strtold_l(const char* s, char** end, gap_locale_t loc) {
    (void)loc;
    return strtold(s, end);
}
size_t strftime_l(char* s, size_t max, const char* fmt, const void* tm, gap_locale_t loc) {
    (void)max;
    (void)fmt;
    (void)tm;
    (void)loc;
    if (s && max)
        s[0] = 0;
    return 0;
}

// ---------------------------------------------------------------------------
// C++ runtime / dynamic loading. No atexit handlers run (the world lives for the page lifetime);
// dlopen-style plugin loading is unused (plugins are static). __cxa_atexit records nothing.
// ---------------------------------------------------------------------------
int __cxa_atexit(void (*func)(void*), void* arg, void* dso) {
    (void)func;
    (void)arg;
    (void)dso;
    return 0;
}
int dlclose(void* handle) {
    (void)handle;
    return 0;
}
void* dlsym(void* handle, const char* sym) {
    (void)handle;
    (void)sym;
    return NULL;
}

// ---------------------------------------------------------------------------
// POSIX syscalls referenced only by the unused shared-memory creator (mSharedMemoryID = 0) and the
// memory-locking path (mMemoryLocking is false / requires mRealTime). None run under our config; the
// stubs return failure/zero so that, if ever reached, the caller takes its error path rather than
// silently corrupting state.
// ---------------------------------------------------------------------------
int mlockall(int flags) {
    (void)flags;
    return -1;
}
void* mmap(void* addr, size_t len, int prot, int flags, int fd, long off) {
    (void)addr;
    (void)len;
    (void)prot;
    (void)flags;
    (void)fd;
    (void)off;
    return (void*)-1; // MAP_FAILED
}
int munmap(void* addr, size_t len) {
    (void)addr;
    (void)len;
    return 0;
}
int shm_open(const char* name, int oflag, unsigned mode) {
    (void)name;
    (void)oflag;
    (void)mode;
    return -1;
}
int shm_unlink(const char* name) {
    (void)name;
    return -1;
}
void* shmat(int id, const void* addr, int flag) {
    (void)id;
    (void)addr;
    (void)flag;
    return (void*)-1;
}
int shmdt(const void* addr) {
    (void)addr;
    return -1;
}
int shmctl(int id, int cmd, void* buf) {
    (void)id;
    (void)cmd;
    (void)buf;
    return -1;
}
int getrlimit(int resource, void* rlim) {
    (void)resource;
    (void)rlim;
    return -1;
}
long sysconf(int name) {
    (void)name;
    return -1;
}
int close(int fd) {
    (void)fd;
    return 0;
}
int fstat(int fd, void* st) {
    (void)fd;
    (void)st;
    return -1;
}
int stat(const char* path, void* st) {
    (void)path;
    (void)st;
    return -1;
}
int fchmod(int fd, unsigned mode) {
    (void)fd;
    (void)mode;
    return -1;
}
int ftruncate(int fd, long length) {
    (void)fd;
    (void)length;
    return -1;
}

// POSIX semaphores: referenced by boost's POSIX semaphore backend if it is selected. The quit
// semaphore (mQuitProgram) is constructed but never waited on under mRealTime=false. No-op success.
int sem_init(void* sem, int pshared, unsigned value) {
    (void)sem;
    (void)pshared;
    (void)value;
    return 0;
}
int sem_destroy(void* sem) {
    (void)sem;
    return 0;
}
int sem_post(void* sem) {
    (void)sem;
    return 0;
}

// pthread odds and ends (TLS keys, mutex attributes, scheduling) reached only via std::thread /
// std::call_once machinery that is never exercised single-threaded. Benign success.
int pthread_key_create(unsigned* key, void (*destructor)(void*)) {
    (void)destructor;
    if (key)
        *key = 0;
    return 0;
}
int pthread_setspecific(unsigned key, const void* value) {
    (void)key;
    (void)value;
    return 0;
}
int pthread_mutexattr_init(void* attr) {
    (void)attr;
    return 0;
}
int pthread_mutexattr_destroy(void* attr) {
    (void)attr;
    return 0;
}
int pthread_mutexattr_settype(void* attr, int type) {
    (void)attr;
    (void)type;
    return 0;
}
int pthread_mutexattr_setpshared(void* attr, int pshared) {
    (void)attr;
    (void)pshared;
    return 0;
}
int pthread_setschedparam(gap_pthread_t thread, int policy, const void* param) {
    (void)thread;
    (void)policy;
    (void)param;
    return 0;
}
int sched_yield(void) { return 0; }
int sched_get_priority_max(int policy) {
    (void)policy;
    return 0;
}
int sched_get_priority_min(int policy) {
    (void)policy;
    return 0;
}
