// Apple IIgs save-file HAL.
//
// GS/OS-backed stdio (fopen/fread/fwrite -- the runtime's libc GS/OS layer)
// handles the blob + stream paths, so jlSaveOpen/Write/Read/Exists just work.
// The three ops stdio cannot express are issued as GS/OS class-1 calls through
// the runtime wrappers in <iigs/gsos.h>:
//
//   - delete: remove() (maps to GS/OS Destroy).
//   - directory create: GS/OS Create ($2001) with storageType 13 (directory).
//   - disk free: the volume that holds the default prefix (prefix 0, which
//     unrooted paths like "SAVES/x" resolve against) does not have a name the
//     Volume call accepts directly -- Volume ($2008) wants a DEVICE name. So
//     the chain is GetPrefix(0) -> ":VOL:..." -> GetDevNumber(":VOL") -> devNum
//     -> DInfo(devNum) -> device name ".XXX" -> Volume(".XXX") -> free blocks.
//
// Every GS/OS call is guarded by __gsosAvailable(): when only the universal-
// success stub is linked (sysless smoke harness) the wrappers are not real, so
// dir-ensure falls back to trusting a prestaged SAVES/ and disk-free reports 0.

#include <stdio.h>

#include <iigs/gsos.h>

#include "joey/file.h"
#include "port.h"


// GS/OS length-prefixed input string, sized for our short save/device paths.
typedef struct {
    unsigned short length;
    char           text[64];
} GsInStr;

// GS/OS ResultBuf (maxLen + length + text), sized for a prefix / name string.
typedef struct {
    unsigned short maxLen;
    unsigned short length;
    char           text[80];
} GsResult;

#define GSOS_ERR_DUP     0x47u    // duplicate pathname (already exists)
#define GSOS_DIR_TYPE    0x0Fu    // directory file type
#define GSOS_DIR_STORE   0x000Du  // storageType 13 = directory
#define GSOS_FULL_ACCESS 0xC3u    // read/write/rename/destroy enabled
#define IIGS_MAX_U32     0xFFFFFFFFu


static void gsStrSet(GsInStr *dst, const char *src, unsigned short cap);


// Copy a C string into a GS/OS length-prefixed string (truncating at cap).
static void gsStrSet(GsInStr *dst, const char *src, unsigned short cap) {
    unsigned short  i;

    i = 0u;
    while (src[i] != '\0' && i < cap) {
        dst->text[i] = src[i];
        i++;
    }
    dst->length = i;
}


uint32_t jlpDiskFree(void) {
    // Static (not stack): keeps this leaf HAL off the shallow IIgs soft stack.
    static GsResult  prefix;
    static GsResult  devNameBuf;
    static GsResult  volNameOut;
    static GsInStr   volArg;
    static GsInStr   devArg;
    PrefixRecGS      pr;
    DevNumRecGS      dn;
    DInfoRecGS       di;
    VolumeRecGS      vr;
    unsigned short   i;
    unsigned short   n;
    unsigned short   cut;
    unsigned short   bytesPerBlock;

    if (!__gsosAvailable()) {
        return 0u;
    }

    // Prefix 0 is what unrooted paths (like "SAVES/x") resolve against, so its
    // volume is the one SAVES/ lives on. It comes back in ":VOL:..." form.
    prefix.maxLen = (unsigned short)(sizeof(prefix) - 2u);
    pr.pCount     = 2u;
    pr.prefixNum  = 0u;
    pr.prefix     = &prefix;
    if (gsosGetPrefix(&pr) != 0u || prefix.length == 0u) {
        return 0u;
    }

    // Extract the leading volume component ":VOL" (up to the 2nd separator;
    // '/' on ProDOS, ':' in GS/OS native form).
    cut = prefix.length;
    for (i = 1u; i < prefix.length; i++) {
        if (prefix.text[i] == '/' || prefix.text[i] == ':') {
            cut = i;
            break;
        }
    }
    n = 0u;
    for (i = 0u; i < cut && n < (unsigned short)sizeof(volArg.text); i++) {
        volArg.text[n++] = prefix.text[i];
    }
    volArg.length = n;

    // Volume name -> device number -> device name (what Volume needs).
    dn.pCount  = 2u;
    dn.devName = &volArg;
    dn.devNum  = 0u;
    if (gsosGetDevNumber(&dn) != 0u) {
        return 0u;
    }
    devNameBuf.maxLen = (unsigned short)(sizeof(devNameBuf) - 2u);
    di.pCount  = 2u;
    di.devNum  = dn.devNum;
    di.devName = &devNameBuf;
    if (gsosDInfo(&di) != 0u || devNameBuf.length == 0u) {
        return 0u;
    }
    n = 0u;
    for (i = 0u; i < devNameBuf.length && n < (unsigned short)sizeof(devArg.text); i++) {
        devArg.text[n++] = devNameBuf.text[i];
    }
    devArg.length = n;

    // Device name -> free blocks + block size.
    volNameOut.maxLen = (unsigned short)(sizeof(volNameOut) - 2u);
    vr.pCount      = 6u;
    vr.devName     = &devArg;
    vr.volName     = &volNameOut;
    vr.totalBlocks = 0uL;
    vr.freeBlocks  = 0uL;
    vr.fileSysID   = 0u;
    vr.blockSize   = 0u;
    if (gsosVolume(&vr) != 0u) {
        return 0u;
    }

    // free bytes = freeBlocks * blockSize, saturating in 32-bit.
    bytesPerBlock = vr.blockSize;
    if (bytesPerBlock == 0u) {
        return 0u;
    }
    if (vr.freeBlocks > IIGS_MAX_U32 / bytesPerBlock) {
        return IIGS_MAX_U32;
    }
    return (uint32_t)(vr.freeBlocks * bytesPerBlock);
}


bool jlpSaveDelete(const char *path) {
    return remove(path) == 0;
}


bool jlpSaveDirEnsure(const char *dir) {
    GsInStr        name;
    CreateRecGS    cr;
    unsigned short err;

    if (!__gsosAvailable()) {
        return true;   // no real GS/OS surface -- trust a prestaged SAVES/
    }
    gsStrSet(&name, dir, (unsigned short)sizeof(name.text));

    cr.pCount      = 5u;
    cr.pathname    = &name;
    cr.access      = GSOS_FULL_ACCESS;
    cr.fileType    = GSOS_DIR_TYPE;
    cr.auxType     = 0uL;
    cr.storageType = GSOS_DIR_STORE;
    err = gsosCreate(&cr);
    return err == 0u || err == GSOS_ERR_DUP;   // created, or already present
}
