// resource.c - Apple IIgs Resource Manager - real implementation.
//
// Replaces the Phase 3.4 stub.  Reads .rsrc resource forks via the
// stdio surface (fopen/fread/fseek/fclose) and caches loaded payloads
// by (type, id) so repeated loadResource() calls return the same
// handle.  Read-only - no AddResource / DetachResource / partial-load.
//
// File format (Apple IIgs Toolbox Reference Vol 3, ch.42):
//   bytes 0..23 : ResourceMapHeaderT (little-endian fields)
//   bytes ...   : payload blobs at offsets recorded in the index
//   bytes at rmToIndex : rmIndexUsed * ResourceIndexEntryT entries
//
// Handle convention: we return a `void **` whose dereference yields the
// resource bytes.  The handle storage lives in this file's static
// table; the bytes themselves are malloc'd at first load and freed at
// releaseResource(verb=1) or closeResourceFile().

#include "iigs/resource.h"

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


// --- Prototypes ---
static int               freeHandleSlot(int slot);
static int               findHandleByPtr(void **handle);
static int               findHandleByTypeId(IigsResTypeT type, IigsResIdT id);
static int               findHandleSlot(void);
static int               findOpenFileSlot(void);
static int               loadIndex(int fileSlot);
static void             *readPayload(int fileSlot, uint32_t offset, uint32_t size);
static int               readU16(FILE *f, uint16_t *out);
static int               readU32(FILE *f, uint32_t *out);
static int               readMapHeader(FILE *f, ResourceMapHeaderT *hdr);


// --- Internal types ---
typedef struct {
    int                  inUse;
    FILE                *fp;
    ResourceMapHeaderT   hdr;
    ResourceIndexEntryT *index;          // malloc'd; rmIndexUsed entries
    uint16_t             refNum;         // 1..N, matches slot+1
} ResourceFileT;


typedef struct {
    int                  inUse;
    int                  fileSlot;       // which ResourceFileT owns it
    IigsResTypeT         type;
    IigsResIdT           id;
    void                *data;           // payload bytes
    uint32_t             size;
    void                *masterPtr;      // master ptr cell -> &data
} HandleSlotT;


// --- State ---
static int           gResourceReady = 0;
static ResourceFileT gFiles[IIGS_RES_MAX_FILES];
static HandleSlotT   gHandles[IIGS_RES_MAX_HANDLES];


int closeResourceFile(ResourceRefNumT refNum) {
    if (refNum == 0 || refNum > IIGS_RES_MAX_FILES) {
        return RES_ERR_BAD_HANDLE;
    }
    int slot = (int)refNum - 1;
    if (!gFiles[slot].inUse) {
        return RES_ERR_BAD_HANDLE;
    }
    // Free every cached handle owned by this file.
    for (int i = 0; i < IIGS_RES_MAX_HANDLES; i++) {
        if (gHandles[i].inUse && gHandles[i].fileSlot == slot) {
            freeHandleSlot(i);
        }
    }
    if (gFiles[slot].index) {
        free(gFiles[slot].index);
        gFiles[slot].index = (ResourceIndexEntryT *)0;
    }
    if (gFiles[slot].fp) {
        fclose(gFiles[slot].fp);
        gFiles[slot].fp = (FILE *)0;
    }
    gFiles[slot].inUse = 0;
    return RES_OK;
}


static int findHandleByPtr(void **handle) {
    if (!handle) {
        return -1;
    }
    for (int i = 0; i < IIGS_RES_MAX_HANDLES; i++) {
        if (gHandles[i].inUse && (void **)&gHandles[i].data == handle) {
            return i;
        }
    }
    return -1;
}


static int findHandleByTypeId(IigsResTypeT type, IigsResIdT id) {
    for (int i = 0; i < IIGS_RES_MAX_HANDLES; i++) {
        if (gHandles[i].inUse && gHandles[i].type == type && gHandles[i].id == id) {
            return i;
        }
    }
    return -1;
}


static int findHandleSlot(void) {
    for (int i = 0; i < IIGS_RES_MAX_HANDLES; i++) {
        if (!gHandles[i].inUse) {
            return i;
        }
    }
    return -1;
}


static int findOpenFileSlot(void) {
    for (int i = 0; i < IIGS_RES_MAX_FILES; i++) {
        if (!gFiles[i].inUse) {
            return i;
        }
    }
    return -1;
}


static int freeHandleSlot(int slot) {
    if (slot < 0 || slot >= IIGS_RES_MAX_HANDLES) {
        return RES_ERR_BAD_HANDLE;
    }
    if (!gHandles[slot].inUse) {
        return RES_ERR_BAD_HANDLE;
    }
    if (gHandles[slot].data) {
        free(gHandles[slot].data);
        gHandles[slot].data = (void *)0;
    }
    gHandles[slot].inUse    = 0;
    gHandles[slot].fileSlot = -1;
    gHandles[slot].type     = 0;
    gHandles[slot].id       = 0;
    gHandles[slot].size     = 0;
    return RES_OK;
}


uint32_t getResourceSize(void **handle) {
    int slot = findHandleByPtr(handle);
    if (slot < 0) {
        return 0;
    }
    return gHandles[slot].size;
}


// Convenience wrapper kept for backwards compat with the old probe.
// Scans the cache + open files for (type, id) and reports the size.
uint32_t iigsGetResourceSize(IigsResTypeT resType, IigsResIdT resId, int *err) {
    if (!gResourceReady) {
        if (err) {
            *err = RES_ERR_NOT_STARTED;
        }
        return 0;
    }
    int hSlot = findHandleByTypeId(resType, resId);
    if (hSlot >= 0) {
        if (err) {
            *err = RES_OK;
        }
        return gHandles[hSlot].size;
    }
    // Not cached - scan every open file's index for the entry.
    for (int f = 0; f < IIGS_RES_MAX_FILES; f++) {
        if (!gFiles[f].inUse || !gFiles[f].index) {
            continue;
        }
        uint32_t n = gFiles[f].hdr.rmIndexUsed;
        for (uint32_t i = 0; i < n; i++) {
            ResourceIndexEntryT *e = &gFiles[f].index[i];
            if (e->rType == resType && e->rID == resId) {
                if (err) {
                    *err = RES_OK;
                }
                return e->rSize;
            }
        }
    }
    if (err) {
        *err = RES_ERR_NOT_FOUND;
    }
    return 0;
}


// Convenience wrapper kept for backwards compat with the old probe.
void **iigsLoadResource(IigsResTypeT resType, IigsResIdT resId, int *err) {
    return loadResource(resType, resId, err);
}


// Reads the 20-byte rIndex table for a freshly-opened file.  Returns
// RES_OK or an RES_ERR_* code.  Caller has populated gFiles[slot].hdr.
static int loadIndex(int fileSlot) {
    ResourceFileT *rf = &gFiles[fileSlot];
    uint32_t n        = rf->hdr.rmIndexUsed;
    if (n == 0) {
        rf->index = (ResourceIndexEntryT *)0;
        return RES_OK;
    }
    // Sanity-check against malloc'ing absurd amounts.
    if (n > 1024) {
        return RES_ERR_TOOLBOX;
    }
    ResourceIndexEntryT *idx = (ResourceIndexEntryT *)malloc(sizeof(ResourceIndexEntryT) * n);
    if (!idx) {
        return RES_ERR_NO_MEM;
    }
    if (fseek(rf->fp, (long)rf->hdr.rmToIndex, 0) != 0) {
        free(idx);
        return RES_ERR_TOOLBOX;
    }
    for (uint32_t i = 0; i < n; i++) {
        uint16_t t;
        uint32_t id;
        uint32_t off;
        uint16_t attr;
        uint32_t sz;
        uint32_t h;
        if (readU16(rf->fp, &t) != 0 ||
            readU32(rf->fp, &id) != 0 ||
            readU32(rf->fp, &off) != 0 ||
            readU16(rf->fp, &attr) != 0 ||
            readU32(rf->fp, &sz) != 0 ||
            readU32(rf->fp, &h) != 0) {
            free(idx);
            return RES_ERR_TOOLBOX;
        }
        idx[i].rType   = t;
        idx[i].rID     = id;
        idx[i].rOffset = off;
        idx[i].rAttr   = attr;
        idx[i].rSize   = sz;
        idx[i].rHandle = h;
    }
    rf->index = idx;
    return RES_OK;
}


void **loadResource(IigsResTypeT type, IigsResIdT id, int *err) {
    if (!gResourceReady) {
        if (err) {
            *err = RES_ERR_NOT_STARTED;
        }
        return (void **)0;
    }
    // Cache hit?
    int hSlot = findHandleByTypeId(type, id);
    if (hSlot >= 0) {
        if (err) {
            *err = RES_OK;
        }
        return (void **)&gHandles[hSlot].data;
    }
    // Cache miss - find the resource in any open file.
    for (int f = 0; f < IIGS_RES_MAX_FILES; f++) {
        if (!gFiles[f].inUse || !gFiles[f].index) {
            continue;
        }
        uint32_t n = gFiles[f].hdr.rmIndexUsed;
        for (uint32_t i = 0; i < n; i++) {
            ResourceIndexEntryT *e = &gFiles[f].index[i];
            if (e->rType != type || e->rID != id) {
                continue;
            }
            int slot = findHandleSlot();
            if (slot < 0) {
                if (err) {
                    *err = RES_ERR_NO_MEM;
                }
                return (void **)0;
            }
            void *bytes = readPayload(f, e->rOffset, e->rSize);
            if (!bytes) {
                if (err) {
                    *err = RES_ERR_TOOLBOX;
                }
                return (void **)0;
            }
            gHandles[slot].inUse    = 1;
            gHandles[slot].fileSlot = f;
            gHandles[slot].type     = type;
            gHandles[slot].id       = id;
            gHandles[slot].data     = bytes;
            gHandles[slot].size     = e->rSize;
            if (err) {
                *err = RES_OK;
            }
            return (void **)&gHandles[slot].data;
        }
    }
    if (err) {
        *err = RES_ERR_NOT_FOUND;
    }
    return (void **)0;
}


ResourceRefNumT openResourceFile(const char *path, uint8_t accessByte, uint16_t fileType, int *err) {
    (void)accessByte;
    (void)fileType;
    if (!path) {
        if (err) {
            *err = RES_ERR_NOT_FOUND;
        }
        return 0;
    }
    int slot = findOpenFileSlot();
    if (slot < 0) {
        if (err) {
            *err = RES_ERR_NO_MEM;
        }
        return 0;
    }
    FILE *fp = fopen(path, "rb");
    if (!fp) {
        if (err) {
            *err = RES_ERR_NOT_FOUND;
        }
        return 0;
    }
    ResourceFileT *rf = &gFiles[slot];
    if (readMapHeader(fp, &rf->hdr) != 0) {
        fclose(fp);
        if (err) {
            *err = RES_ERR_TOOLBOX;
        }
        return 0;
    }
    rf->fp     = fp;
    rf->inUse  = 1;
    rf->refNum = (uint16_t)(slot + 1);
    rf->index  = (ResourceIndexEntryT *)0;
    int rc = loadIndex(slot);
    if (rc != RES_OK) {
        fclose(fp);
        rf->fp    = (FILE *)0;
        rf->inUse = 0;
        if (err) {
            *err = rc;
        }
        return 0;
    }
    gResourceReady = 1;
    if (err) {
        *err = RES_OK;
    }
    return rf->refNum;
}


// Allocates and reads `size` bytes at `offset` from the file at
// `fileSlot`.  Returns NULL on any error.
static void *readPayload(int fileSlot, uint32_t offset, uint32_t size) {
    if (size == 0) {
        return (void *)0;
    }
    void *buf = malloc(size);
    if (!buf) {
        return (void *)0;
    }
    FILE *fp = gFiles[fileSlot].fp;
    if (fseek(fp, (long)offset, 0) != 0) {
        free(buf);
        return (void *)0;
    }
    size_t got = fread(buf, 1, size, fp);
    if (got != size) {
        free(buf);
        return (void *)0;
    }
    return buf;
}


// Reads a little-endian uint16 from `f`.  Returns 0 on success.
static int readU16(FILE *f, uint16_t *out) {
    uint8_t b[2];
    if (fread(b, 1, 2, f) != 2) {
        return -1;
    }
    *out = (uint16_t)(b[0] | ((uint16_t)b[1] << 8));
    return 0;
}


// Reads a little-endian uint32 from `f`.  Returns 0 on success.
static int readU32(FILE *f, uint32_t *out) {
    uint8_t b[4];
    if (fread(b, 1, 4, f) != 4) {
        return -1;
    }
    *out = (uint32_t)b[0] |
           ((uint32_t)b[1] << 8) |
           ((uint32_t)b[2] << 16) |
           ((uint32_t)b[3] << 24);
    return 0;
}


// Reads the 24-byte rResourceMap header at offset 0.
static int readMapHeader(FILE *f, ResourceMapHeaderT *hdr) {
    if (fseek(f, 0L, 0) != 0) {
        return -1;
    }
    if (readU16(f, &hdr->rmVersion) != 0)      return -1;
    if (readU32(f, &hdr->rmToIndex) != 0)      return -1;
    if (readU16(f, &hdr->rmFileNum) != 0)      return -1;
    if (readU16(f, &hdr->rmID) != 0)           return -1;
    if (readU32(f, &hdr->rmIndexSize) != 0)    return -1;
    if (readU32(f, &hdr->rmIndexUsed) != 0)    return -1;
    if (readU16(f, &hdr->rmFreeListSize) != 0) return -1;
    if (readU16(f, &hdr->rmFreeListUsed) != 0) return -1;
    if (readU16(f, &hdr->rmPad) != 0)          return -1;
    return 0;
}


int releaseResource(int verb, void **handle) {
    int slot = findHandleByPtr(handle);
    if (slot < 0) {
        return RES_ERR_BAD_HANDLE;
    }
    if (verb == 0) {
        // Soft release: keep cached payload.  Real toolbox would decrement
        // a use-count; we just succeed.
        return RES_OK;
    }
    return freeHandleSlot(slot);
}


int resourceProbeInit(void) {
    // Zero the tables.  Safe to call repeatedly - subsequent calls do
    // not touch already-open files.
    if (!gResourceReady) {
        for (int i = 0; i < IIGS_RES_MAX_FILES; i++) {
            gFiles[i].inUse  = 0;
            gFiles[i].fp     = (FILE *)0;
            gFiles[i].index  = (ResourceIndexEntryT *)0;
            gFiles[i].refNum = 0;
        }
        for (int i = 0; i < IIGS_RES_MAX_HANDLES; i++) {
            gHandles[i].inUse    = 0;
            gHandles[i].fileSlot = -1;
            gHandles[i].data     = (void *)0;
            gHandles[i].size     = 0;
        }
        gResourceReady = 1;
    }
    return RES_OK;
}


int resourceRuntimeEnabled(void) {
    return gResourceReady;
}
