/*
 * file.c — *** PROVISIONAL ***  minimal unbuffered FILE * implementation
 *                                on top of POSIX-style fd I/O
 *                                (open/read/write/lseek/close).
 *
 * ============================================================
 *  TODO (v2): replace with a proper BUFFERED implementation.
 *  See docs/TODO.md  /  Solid-C reference layout:
 *
 *    typedef struct {
 *        uint   flags;     // +0..1  file status flags
 *        int    level;     // +2..3  empty/fill level of buffer
 *        char  *curp;      // +4..5  current active pointer
 *        int    fd;        // +6..7  underlying low-level fd
 *        char  *buffer;    // +8..9  data transfer buffer
 *        char   hold;      // +10    ungetc byte if no buffer
 *        short  token;     // +11..12  reserved
 *        char   dummy;     // +13    reserved
 *    } FILE;
 *
 *  The current implementation maps each fputc/fgetc to one read/write
 *  syscall — fine for correctness checks, awful for throughput.  Issues
 *  3/4/5 from the stdio-review (fwrite short-write flag, fgets n=1,
 *  mode_to_flags break) are deferred until that rewrite.
 * ============================================================
 *
 * stdin/stdout/stderr are STATIC sentinel FILEs flagged with
 * _F_CONIN/_F_CONOUT; fputc/fgetc detect them and call putchar() /
 * getchar() which already handle CR/LF translation and ESTEX calls.
 *
 * Their fd fields are 0 / -1 / -2.  Negative values were chosen because
 * ESTEX OPEN can return small positive fds (1, 2, …) for ordinary
 * files — if we marked stdout/stderr with fd=1/2 a real file could
 * collide with their identifier.  fd=0 for stdin is kept (POSIX-style)
 * because ESTEX does not return 0.  Even so, none of these fd fields
 * is ever passed to a syscall — the _F_CONIN/_F_CONOUT flags drive
 * the dispatch.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

/* ---- console pseudo-streams ----------------------------------------*/
static FILE _stdin  = {  0, _F_READ  | _F_CONIN  };
static FILE _stdout = { -1, _F_WRITE | _F_CONOUT };
static FILE _stderr = { -2, _F_WRITE | _F_CONOUT };

FILE *const stdin  = &_stdin;
FILE *const stdout = &_stdout;
FILE *const stderr = &_stderr;

/* ---- fopen / fclose -------------------------------------------------*/

/* Translate a fopen() mode string to the open() flags subset our
 * libc/io/open.c understands.  Supported: r, w, a, with optional "+"
 * and trailing "b" (binary — we ignore as all I/O is binary).
 */
static int mode_to_flags(const char *mode, uint8_t *file_flags)
{
    if (!mode || !*mode) {
        errno = EINVAL;
        return -1;
    }
    int  oflags = 0;
    uint8_t ff = 0;
    char base = *mode;
    int  plus = 0;
    for (const char *p = mode + 1; *p; p++) {
        if (*p == '+') plus = 1;
        /* 'b' and 't' are ignored — all I/O is binary on Sprinter. */
    }
    switch (base) {
    case 'r':
        oflags = plus ? O_RDWR : O_RDONLY;
        ff = _F_READ | (plus ? _F_WRITE : 0);
        break;
    case 'w':
        oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC;
        ff = _F_WRITE | (plus ? _F_READ : 0);
        break;
    case 'a':
        oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_APPEND;
        ff = _F_WRITE | _F_APPEND | (plus ? _F_READ : 0);
        break;
    default:
        errno = EINVAL;
        return -1;
    }
    *file_flags = ff;
    return oflags;
}

FILE *fopen(const char *path, const char *mode)
{
    uint8_t ff;
    int oflags = mode_to_flags(mode, &ff);
    if (oflags < 0) return NULL;

    int fd = open(path, oflags);
    if (fd < 0) return NULL;

    FILE *fp = (FILE *)malloc(sizeof(FILE));
    if (!fp) {
        int saved = errno;
        close(fd);
        errno = saved ? saved : ENOMEM;
        return NULL;
    }
    fp->fd    = fd;
    fp->flags = ff;
    return fp;
}

int fclose(FILE *fp)
{
    if (!fp) {
        errno = EBADF;
        return EOF;
    }
    /* Don't close stdin/stdout/stderr. */
    if (fp == &_stdin || fp == &_stdout || fp == &_stderr) {
        return 0;
    }
    int r = close(fp->fd);
    free(fp);
    return r < 0 ? EOF : 0;
}

int fflush(FILE *fp)
{
    /* Unbuffered — nothing to flush. */
    (void)fp;
    return 0;
}

/* ---- char-at-a-time -------------------------------------------------*/

int fputc(int c, FILE *fp)
{
    if (!fp) { errno = EBADF; return EOF; }
    if (fp->flags & _F_CONOUT) {
        putchar(c);
        return (int)c;
    }
    if (!(fp->flags & _F_WRITE)) { errno = EBADF; return EOF; }
    uint8_t ch = (uint8_t)c;
    if (write(fp->fd, &ch, 1) != 1) {
        fp->flags |= _F_ERROR;
        return EOF;
    }
    return (int)ch;
}

int fgetc(FILE *fp)
{
    if (!fp) { errno = EBADF; return EOF; }
    if (fp->flags & _F_CONIN) {
        return getchar();
    }
    if (!(fp->flags & _F_READ)) { errno = EBADF; return EOF; }
    uint8_t ch;
    int r = read(fp->fd, &ch, 1);
    if (r == 0) { fp->flags |= _F_EOF; return EOF; }
    if (r < 0)  { fp->flags |= _F_ERROR; return EOF; }
    return (int)ch;
}

/* ---- string-at-a-time ----------------------------------------------*/

int fputs(const char *s, FILE *fp)
{
    if (!fp || !s) { errno = EBADF; return EOF; }
    if (fp->flags & _F_CONOUT) {
        while (*s) {
            putchar((unsigned char)*s++);;
        }
        return 0;
    }
    if (!(fp->flags & _F_WRITE)) { errno = EBADF; return EOF; }
    size_t n = strlen(s);
    int w = write(fp->fd, s, (uint16_t)n);
    if (w < 0 || (size_t)w != n) {
        fp->flags |= _F_ERROR;
        return EOF;
    }
    return 0;
}

char *fgets(char *buf, int n, FILE *fp)
{
    if (!buf || n < 2 || !fp) return NULL;
    int i = 0;
    while (i < n - 1) {
        int c = fgetc(fp);
        if (c == EOF) {
            if (i == 0) return NULL;
            break;
        }
        buf[i++] = (char)c;
        if (c == '\n') break;
    }
    buf[i] = '\0';
    return buf;
}

/* ---- block-at-a-time -----------------------------------------------*/

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp)
{
    if (!ptr || !fp) return 0;
    if (size == 0 || nmemb == 0) return 0;
    if (fp->flags & _F_CONIN) {
        /* line-buffered console read — not very useful but functional. */
        char *p = (char *)ptr;
        size_t total = size * nmemb;
        for (size_t i = 0; i < total; i++) {
            int c = getchar();
            if (c == EOF) return i / size;
            p[i] = (char)c;
        }
        return nmemb;
    }
    if (!(fp->flags & _F_READ)) { errno = EBADF; return 0; }
    size_t total = size * nmemb;
    int r = read(fp->fd, ptr, (uint16_t)total);
    if (r < 0) { fp->flags |= _F_ERROR; return 0; }
    if ((size_t)r < total) fp->flags |= _F_EOF;
    return (size_t)r / size;
}

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp)
{
    if (!ptr || !fp) return 0;
    if (size == 0 || nmemb == 0) return 0;
    if (fp->flags & _F_CONOUT) {
        const char *p = (const char *)ptr;
        size_t total = size * nmemb;
        for (size_t i = 0; i < total; i++) {
            putchar((unsigned char)p[i]);
        }
        return nmemb;
    }
    if (!(fp->flags & _F_WRITE)) { errno = EBADF; return 0; }
    size_t total = size * nmemb;
    int w = write(fp->fd, ptr, (uint16_t)total);
    if (w < 0) { fp->flags |= _F_ERROR; return 0; }
    return (size_t)w / size;
}

/* ---- positioning ---------------------------------------------------*/

int fseek(FILE *fp, long off, int whence)
{
    if (!fp || (fp->flags & (_F_CONIN | _F_CONOUT))) {
        errno = EBADF;
        return -1;
    }
    long r = lseek(fp->fd, off, whence);
    if (r < 0) return -1;
    fp->flags &= (uint8_t)~_F_EOF;
    return 0;
}

long ftell(FILE *fp)
{
    if (!fp || (fp->flags & (_F_CONIN | _F_CONOUT))) {
        errno = EBADF;
        return -1L;
    }
    return lseek(fp->fd, 0L, SEEK_CUR);
}

void rewind(FILE *fp)
{
    if (!fp) return;
    fseek(fp, 0L, SEEK_SET);
    fp->flags &= (uint8_t)~(_F_EOF | _F_ERROR);
}

/* ---- status --------------------------------------------------------*/

int feof  (FILE *fp) { return fp && (fp->flags & _F_EOF)   ? 1 : 0; }
int ferror(FILE *fp) { return fp && (fp->flags & _F_ERROR) ? 1 : 0; }
void clearerr(FILE *fp) {
    if (fp) fp->flags &= (uint8_t)~(_F_EOF | _F_ERROR);
}
