/*
 * open / creat / close — ESTEX file-handle primitives.
 *
 * The interesting part is the open() flag state machine.  We expose POSIX
 * flag bits (O_RDONLY/O_WRONLY/O_RDWR + O_CREAT/O_EXCL/O_TRUNC/O_APPEND)
 * and dispatch onto the three ESTEX entry points:
 *
 *     $11 OPEN        — open existing file
 *     $0A CREATE      — create (truncate if exists), return new handle
 *     $0B CREATE_NEW  — create only if file does not exist
 *
 * Three private __naked wrappers do the raw RST 10h calls; the public
 * open() / creat() / close() are plain C orchestrators.
 *
 *   IX is saved across every RST 10h.  Failures set errno and return -1.
 */

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

/* ---- raw ESTEX wrappers ---------------------------------------------- */

/*  ESTEX $11 OPEN: A=mode (1=R, 2=W, 0=R/W), HL=path → A=handle, CF=err. */
static int _estex_open_raw(const char *path, int posix_mode) __naked
{
    (void)path; (void)posix_mode;
    __asm
        push  ix
        ;; HL = path, DE = posix_mode.  Translate to ESTEX numbering.
        ld    a, e
        and   a, #0x03
        ld    c, #1
        or    a, a
        jr    Z, _oopen_real
        ld    c, #2
        dec   a
        jr    Z, _oopen_real
        ld    c, #0
    _oopen_real:
        ld    a, c
        ld    c, #0x11                 ; ESTEX OPEN
        rst   #0x10
        pop   ix
        jr    c, _oopen_err
        ld    e, a
        ld    d, #0
        ret
    _oopen_err:
        call  __errno_set
        ld    de, #-1
        ret
    __endasm;
}

/*  ESTEX $0A CREATE: A=attr, HL=path → A=handle, CF=err.
 *  Truncates an existing file. */
static int _estex_create_raw(const char *path) __naked
{
    (void)path;
    __asm
        push  ix
        xor   a, a                     ; A = 0 (normal attribute)
        ld    c, #0x0A
        rst   #0x10
        pop   ix
        jr    c, _ocreat_err
        ld    e, a
        ld    d, #0
        ret
    _ocreat_err:
        call  __errno_set
        ld    de, #-1
        ret
    __endasm;
}

/*  ESTEX $0B CREATE_NEW: A=attr, HL=path → A=handle, CF=err.
 *  Fails (errno=EEXIST) if file already exists. */
static int _estex_create_new_raw(const char *path) __naked
{
    (void)path;
    __asm
        push  ix
        xor   a, a
        ld    c, #0x0B
        rst   #0x10
        pop   ix
        jr    c, _ocreatn_err
        ld    e, a
        ld    d, #0
        ret
    _ocreatn_err:
        call  __errno_set
        ld    de, #-1
        ret
    __endasm;
}

/* ---- public surface --------------------------------------------------- */

int open(const char *path, int flags)
{
    int fd;

    if (flags & O_CREAT) {
        if (flags & O_EXCL) {
            /* Must not already exist. */
            fd = _estex_create_new_raw(path);
        } else if (flags & O_TRUNC) {
            /* Always create or truncate. */
            fd = _estex_create_raw(path);
        } else {
            /* Open if it exists, otherwise create. */
            fd = _estex_open_raw(path, flags);
            if (fd < 0 && errno == ENOENT) {
                fd = _estex_create_raw(path);
            }
        }
    } else {
        fd = _estex_open_raw(path, flags);
    }

    if (fd < 0) {
        return -1;
    }

    if (flags & O_APPEND) {
        /* Position at end of file so future writes append. */
        (void)lseek(fd, 0L, SEEK_END);
    }
    return fd;
}

int creat(const char *path, int mode)
{
    (void)mode;          /* Sprinter has no per-file permission bits */
    return open(path, O_WRONLY | O_CREAT | O_TRUNC);
}

int close(int fd) __naked
{
    (void)fd;
    __asm
        push  ix
        ld    a, l
        ld    c, #0x12
        rst   #0x10
        pop   ix
        jr    c, _oclose_err
        ld    de, #0
        ret
    _oclose_err:
        call  __errno_set
        ld    de, #-1
        ret
    __endasm;
}
