// libcGno.c — GNO/ME backend for our libc.
//
// Two roles:
//   1. The GNO half of stdio: provides the GS/OS class-1 call wrappers
//      (gsosOpen/Read/Write/Close/...) that libc.c's FILE* layer calls
//      via weak externs, using GNO's INLINE GS/OS dispatch — so fopen /
//      fread / fwrite / fprintf / fgets to real GS/OS files Just Work.
//      Also provides the __putByte / __getByte console hooks libc.c
//      uses for putchar / getchar, routing them to the GNO console.
//   2. POSIX process glue: __gnoStartup (argv parse + main), _exit, and
//      the fork/exec/wait/etc. wrappers over the K* kernel primitives.

#include <stdint.h>
#include <gno/kernel.h>

extern int main(int argc, char **argv);

// GS/OS class-1 paramblock shapes (layout-matched to libc.c's).
typedef struct {
    uint16_t       pCount;
    uint16_t       refNum;
    void          *dataBuffer;
    unsigned long  requestCount;
    unsigned long  transferCount;
} GnoIORec;
typedef struct { uint16_t pCount; uint16_t refNum; void *pathname; uint16_t requestAccess; } GnoOpenParm;
typedef struct { uint16_t pCount; uint16_t refNum; } GnoRefNumRec;
typedef struct { uint16_t pCount; uint16_t refNum; unsigned long val; } GnoEOFRec;
typedef struct { uint16_t pCount; uint16_t refNum; unsigned long val; } GnoMarkRec;
typedef struct { uint16_t pCount; void *pathname; uint16_t access; uint16_t fileType; unsigned long auxType; uint16_t storageType; } GnoCreateParm;
typedef struct { uint16_t pCount; void *pathname; } GnoDestroyParm;
typedef struct { uint16_t pCount; void *oldPathname; void *newPathname; } GnoChangePathParm;

// GS/OS class-1 call numbers.
#define GSOS_CREATE      0x2001
#define GSOS_DESTROY     0x2002
#define GSOS_CHANGEPATH  0x2004
#define GSOS_OPEN        0x2010
#define GSOS_READ        0x2012
#define GSOS_WRITE       0x2013
#define GSOS_CLOSE       0x2014
#define GSOS_SETMARK     0x2016
#define GSOS_GETMARK     0x2017
#define GSOS_SETEOF      0x2018
#define GSOS_GETEOF      0x2019

// Generic inline-form GS/OS dispatch (asm helper, runtime/src/gnoGsos.s).
// GNO's $E100A8 interceptor reads callNum + pBlock from the inline bytes
// after the JSL; the helper self-modifies them and restores GNO's
// process DP around the call.  Returns the GS/OS error (0 = OK).
extern uint16_t __gnoGsosCall(void *pBlock, unsigned short callNum);


// ---- GS/OS file-call wrappers (override libc.c's weak externs) -------
// libc.c's FILE* layer (fopen with FILE_KIND_GSOS, fread/fwrite/fgetc/
// fputc/fclose) calls these.  Routing them through GNO's inline dispatch
// makes the whole buffered-stdio surface work for real GS/OS files.
//
// `retain` + `used` is the LTO survival policy (Phase 1.11): libc.c
// references these via undefined-weak extern, so without retain the
// LTO inliner can prove they're unreferenced from any non-weak root
// in libcGno.o itself and DCE the bodies — leaving the weak refs in
// libc.o to resolve to NULL.  `retain` keeps the symbol past linker
// GC; `used` keeps it past compiler DCE.  No-op in non-LTO builds.
#define KEEP __attribute__((retain, used))
KEEP uint16_t gsosCreate(GnoCreateParm *p){ return __gnoGsosCall(p, GSOS_CREATE); }
KEEP uint16_t gsosOpen(GnoOpenParm *p)    { return __gnoGsosCall(p, GSOS_OPEN); }
KEEP uint16_t gsosRead(GnoIORec *p)       { return __gnoGsosCall(p, GSOS_READ); }
KEEP uint16_t gsosWrite(GnoIORec *p)      { return __gnoGsosCall(p, GSOS_WRITE); }
KEEP uint16_t gsosClose(GnoRefNumRec *p)  { return __gnoGsosCall(p, GSOS_CLOSE); }
KEEP uint16_t gsosGetEOF(GnoEOFRec *p)    { return __gnoGsosCall(p, GSOS_GETEOF); }
KEEP uint16_t gsosSetEOF(GnoEOFRec *p)    { return __gnoGsosCall(p, GSOS_SETEOF); }
KEEP uint16_t gsosSetMark(GnoMarkRec *p)  { return __gnoGsosCall(p, GSOS_SETMARK); }
KEEP uint16_t gsosGetMark(GnoMarkRec *p)  { return __gnoGsosCall(p, GSOS_GETMARK); }
KEEP uint16_t gsosDestroy(GnoDestroyParm *p)      { return __gnoGsosCall(p, GSOS_DESTROY); }
KEEP uint16_t gsosChangePath(GnoChangePathParm *p){ return __gnoGsosCall(p, GSOS_CHANGEPATH); }

// Stub-mode sentinel.  GNO's gsosCreate/Open/Read/... above are real
// GS/OS dispatchers (through __gnoGsosCall), so when libcGno.o is in
// the link the dispatch surface is real and __gsosAvailable() must
// return 1.  Mirrors the value in iigsGsos.s for the bare-metal path.
KEEP int __gsosIsRealImpl = 1;


// ---- console hooks (override libc.c's weak __putByte/__getByte) ------
// GNO fds are 1-based GS/OS refNums mapped through the per-process
// open-files table by the kernel's GS/OS interceptor (GNORdWr,
// kern/gno/gsos.asm:1794): stdin = 1, stdout = 2, stderr = 3
// (include/unistd.h:51-53; main.c:388-390).  A GS/OS Read/Write with
// that refNum is the exact mechanism GNO's own libc read()/write() use
// (lib/libc/sys/syscall.c:756/1271) -- the interceptor routes it to the
// file ('<'), pipe ('|'), or TTY behind the fd, so redirection is
// honored transparently.  putchar/getchar in libc.c route here; the
// rest of stdio (puts/printf/fgets/...) builds on them.
//
// GNO 1-based fd indices for the inherited standard streams.
#define GNO_FD_STDIN   1
#define GNO_FD_STDOUT  2
#define GNO_FD_STDERR  3

// GS/OS error returned at end-of-file ($4C).  GNO's read() treats this
// as a successful (possibly short) transfer, not a hard error
// (syscall.c:765; texttool.asm:2250).
#define GSOS_ERR_EOF  0x4C

KEEP void __putByte(char c) {
    if (c == '\n') c = '\r';   // GNO console (Apple II TTY) wants CR
    GnoIORec r = { 4, GNO_FD_STDOUT, &c, 1, 0 };
    __gnoGsosCall(&r, GSOS_WRITE);
}

// Strong override for stderr (libc.c routes FILE_KIND_STDERR here).
// stderr is fd 3 -- distinct from stdout so '2>file' redirection works.
KEEP void __putByteErr(char c) {
    if (c == '\n') c = '\r';
    GnoIORec r = { 4, GNO_FD_STDERR, &c, 1, 0 };
    __gnoGsosCall(&r, GSOS_WRITE);
}

KEEP int __getByte(void) {
    unsigned char c;
    GnoIORec r = { 4, GNO_FD_STDIN, &c, 1, 0 };
    uint16_t err = __gnoGsosCall(&r, GSOS_READ);
    // Match GNO's read(): err 0 OR $4C is a valid transfer; only a
    // zero-byte transfer is genuine EOF.  Reading refNum 1 reaches the
    // live inherited stdin -- the file behind '<', the pipe behind '|',
    // or the console TTY -- via the kernel's GNORdWr interceptor.
    if ((err != 0 && err != GSOS_ERR_EOF) || r.transferCount != 1)
        return -1;            // EOF / error
    return (int)c;
}


// ---- raw POSIX read/write (used by demos that want fd-level I/O) -----
// POSIX callers pass Unix 0/1/2; GNO's fd table is 1-based (stdin = 1),
// so translate 0/1/2 -> 1/2/3 in one place.  Any other fd is assumed to
// already be a GNO 1-based fd (e.g. from pipe()/dup()) and passed
// straight through.
static uint16_t gnoFd(int fd) {
    if (fd >= 0 && fd <= 2)
        return (uint16_t)(fd + 1);
    return (uint16_t)fd;
}


long write(int fd, const void *buf, unsigned long n) {
    GnoIORec r = { 4, gnoFd(fd), (void *)buf, n, 0 };
    if (__gnoGsosCall(&r, GSOS_WRITE) != 0) return -1;
    return (long)r.transferCount;
}

long read(int fd, void *buf, unsigned long n) {
    GnoIORec r = { 4, gnoFd(fd), buf, n, 0 };
    uint16_t err = __gnoGsosCall(&r, GSOS_READ);
    if (err != 0 && err != GSOS_ERR_EOF) return -1;
    return (long)r.transferCount;
}


// Tokenize cmdline in-place using simple whitespace rules.  GNO's
// shell does proper quote handling — we'll match that once we have a
// test harness to drive it.  For the initial hello-world the cmdline
// is just the program name (no args), so a degenerate parser suffices.
//
// Writes into the static argv[] and writable copy buf[].  Caps argc
// at 31; longer command lines have their tail args silently dropped.
#define ARG_MAX  32
#define CMD_MAX  256

static char argBuf[CMD_MAX];
static char *argVec[ARG_MAX];


static int parseCmdline(const char *cmd, char ***argvOut) {
    int n = 0;
    int i = 0;
    // Copy cmdline into our writable buffer up to CMD_MAX-1.
    while (i < CMD_MAX - 1 && cmd[i]) {
        argBuf[i] = cmd[i];
        i++;
    }
    argBuf[i] = 0;

    // Tokenize on whitespace.  Replace separator runs with NULs,
    // record argv entries pointing at the first non-NUL char of each
    // run.
    char *p = argBuf;
    while (n < ARG_MAX - 1 && *p) {
        while (*p == ' ' || *p == '\t')
            p++;
        if (!*p)
            break;
        argVec[n++] = p;
        while (*p && *p != ' ' && *p != '\t')
            p++;
        if (*p)
            *p++ = 0;
    }
    argVec[n] = 0;
    *argvOut = argVec;
    return n;
}


// crt0Gno.s entry point.  Receives the command-line pointer the kernel
// handed us (Y:X on entry; crt0 forwards it as a ptr32 arg here).
// GNO prepends an 8-byte "BYTEWRKS" signature to the command line
// (kern/gno/sys.c builds child->args = "BYTEWRKS" + name; ~GNO_PARSEARG
// skips it with `add4 commandline,#8`).  Skip those 8 bytes, parse into
// argc/argv, invoke main(), and return to crt0 for the QUIT call.
int __gnoStartup(const char *cmdline) {
    char **argv;
    int argc;
    if (cmdline) cmdline += 8;        // skip "BYTEWRKS" prefix
    argc = parseCmdline(cmdline, &argv);
    return main(argc, argv);
}


// GS/OS QUIT ($2029) paramblock.  pCount=0 => plain QUIT: GNO's
// CommonQuit reaps the process and returns control to the launcher (gsh).
typedef struct { uint16_t pCount; } GnoQuitParm;

#define GSOS_QUIT  0x2029

// _exit: GNO/ME program termination via GS/OS QUIT, issued through the
// SAME inline GS/OS dispatch (__gnoGsosCall) as every other call here.
//
// Critical: this MUST use the inline form, not the stack-based form
// (`ldx #$2029 ; jsl $e100a8` with the pblock pushed).  GNO patches
// $E100A8 to OurGSOS, which reads the call number + pblock from the 6
// inline bytes AFTER the jsl.  The stack form leaves those inline bytes
// as whatever opcode follows, so OurGSOS mis-dispatches the QUIT and the
// process is left restartable — GNO then RE-RUNS main() (observed:
// main executes twice, silently draining redirected stdin on the first
// pass).  Routing QUIT through __gnoGsosCall (inline form + process-DP
// restore) terminates cleanly, so main() runs exactly once.
__attribute__((noreturn))
void _exit(int status) {
    (void)status;
    GnoQuitParm q = { 0 };          // pCount=0 — plain QUIT to launcher
    __gnoGsosCall(&q, GSOS_QUIT);   // inline dispatch; does not return
    for (;;) { }                    // unreachable
}


// Stubs for the K*-wrapped POSIX surface.  These mirror what
// runtime/src/gnoKernel.s already provides; we just rename them.
// Errno handling: each K* takes a `int *errno` arg; on failure the
// kernel writes the errno value there and returns -1.  Our wrappers
// stash it into the global `errno` variable.
//
// Only the minimum needed for the demo is non-trivial — the rest
// are scaffold-only and will be filled in as we add tests.

extern int errno;   // defined in libc.c


int fork(void) {
    int err = 0;
    int r = Kfork(0, &err);
    if (r < 0) errno = err;
    return r;
}


int kill(int pid, int sig) {
    int err = 0;
    int r = Kkill(pid, sig, &err);
    if (r < 0) errno = err;
    return r;
}


int getpid(void) {
    return Kgetpid();
}


int getppid(void) {
    int err = 0;
    int r = Kgetppid(&err);
    if (r < 0) errno = err;
    return r;
}


int dup(int oldfd) {
    int err = 0;
    int r = Kdup(oldfd, &err);
    if (r < 0) errno = err;
    return r;
}


int dup2(int oldfd, int newfd) {
    int err = 0;
    int r = Kdup2(oldfd, newfd, &err);
    if (r < 0) errno = err;
    return r;
}


int pipe(int fds[2]) {
    int err = 0;
    int r = Kpipe(fds, &err);
    if (r < 0) errno = err;
    return r;
}


unsigned long alarm(unsigned long seconds) {
    int err = 0;
    return Kalarm((void *)seconds, &err);
}
