/* SPDX-License-Identifier: MIT. Copyright (c) 2026 Joerg Burbach.
 * WORM0-26 memory packer. Link lexa.c for the shared BriefLZ/text/font core.
 */
#include "worm0.h"
#include "../../lexa/codecs/lexa.h"
#include <stdlib.h>
#include <string.h>
#define LIMIT ((size_t)67108864)
static unsigned u16(const uint8_t *s) { return s[0] | (unsigned)s[1] << 8; }
static uint32_t u32(const uint8_t *s) { return s[0] | (uint32_t)s[1] << 8 | (uint32_t)s[2] << 16 | (uint32_t)s[3] << 24; }
static void put32(uint8_t *s, size_t v) { size_t i; for (i = 0; i < 4; i++) s[i] = (uint8_t)(v >> (i * 8)); }
static uint32_t hash(const uint8_t *s, size_t n) { uint32_t h = UINT32_C(2166136261); size_t i; for (i = 0; i < n; i++) h = (h ^ s[i]) * UINT32_C(16777619); return h; }
static int known(unsigned c) { return c == 0 || c == 1 || c == 16 || c == 32 || c == 48 || c == 64 || c == 65 || c == 80; }
static int detects(const uint8_t *s, size_t n, unsigned c) {
    if (n < 5) return 0;
    if (c == 32) return (!memcmp(s, "RA26", 4) && n >= 17) || !memcmp(s, "RC26", 4);
    if (s[4] != 26) return 0;
    if (c == 16) return n > 11 && !memcmp(s, "RFXL", 4) && (s[9] == 3 || s[9] == 4) && (s[10] == 0 || s[10] == 4);
    if (c == 48) return (n >= 32 && !memcmp(s, "RFXA", 4)) || (n >= 16 && !memcmp(s, "RFXZ", 4));
    if (c == 64 || c == 65) return n >= 8 && !memcmp(s, "LEXA", 4);
    return c == 80 && n >= 24 && !memcmp(s, "FMA1", 4);
}
void worm0_free(worm0_resource *resource) { if (resource) { free(resource->bytes); free(resource->name); memset(resource, 0, sizeof(*resource)); } }
int worm0_decode(const uint8_t *s, size_t n, worm0_resource *resource) {
    size_t raw, chunk, count, name_len, table, pos, out_pos = 0, i, length, packed, expected; unsigned method; uint8_t *decoded; const uint8_t *payload;
    lexa_document doc;
    if (!resource) return 1;
    memset(resource, 0, sizeof(*resource));
    if (!s || n < 28 || n > LIMIT || memcmp(s, "WORM", 4) || s[4] != 26 || s[5] > 6 || s[6] > 4 || s[7] || u16(s + 26)) return 1;
    raw = u32(s + 8); chunk = u32(s + 16); count = u32(s + 20); name_len = u16(s + 24); table = 28 + name_len;
    if (!raw || raw > LIMIT || !chunk || !count || table > n || count > (n - table) / 13) return 1;
    pos = table + count * 13; resource->bytes = (uint8_t *)malloc(raw); resource->name = (char *)calloc(name_len + 1, 1); if (!resource->bytes || !resource->name) goto error;
    memcpy(resource->name, s + 28, name_len);
    for (i = 0; i < count; i++) {
        const uint8_t *record = s + table + i * 13;
        method = record[0]; length = u32(record + 1); packed = u32(record + 5);
        if (!known(method) || !length || !packed || length > chunk || (i < count - 1 && length != chunk) || length > raw - out_pos || packed > n - pos) goto error;
        payload = s + pos; decoded = NULL; expected = length;
        if (packed == length && (method == 0 || detects(payload, packed, method))) { }
        else if (method == 1) { decoded = lexa_brief_lz_decode(payload, packed, length, NULL); if (!decoded) goto error; payload = decoded; }
        else if (method == 64 || method == 65) {
            if (lexa_decode(payload, packed, &doc)) goto error;
            if (method == 65 && doc.kind == 2) { payload = doc.font; expected = doc.font_size; }
            else if (method == 64 && doc.kind == 1 && doc.count) { payload = doc.entries[0].bytes; expected = doc.entries[0].size; }
            else { lexa_free(&doc); goto error; }
            if (expected != length || hash(payload, length) != u32(record + 9)) { lexa_free(&doc); goto error; }
            memcpy(resource->bytes + out_pos, payload, length); lexa_free(&doc); out_pos += length; pos += packed; continue;
        } else goto error;
        if (hash(payload, length) != u32(record + 9)) { free(decoded); goto error; }
        memcpy(resource->bytes + out_pos, payload, length); free(decoded); out_pos += length; pos += packed;
    }
    if (out_pos != raw || pos != n || hash(resource->bytes, raw) != u32(s + 12)) goto error;
    resource->size = raw; resource->type = s[5]; return 0;
error:
    worm0_free(resource); return 1;
}
int worm0_encode(const uint8_t *s, size_t n, const char *name, size_t chunk, uint8_t **data, size_t *size) {
    uint8_t **payload = NULL, *out = NULL, *decoded; size_t *packed = NULL, count, name_len, total, i, length, pos; unsigned compressed;
    if (!data || !size) return 1;
    *data = NULL; *size = 0;
    if (!s || !n || n > LIMIT || !name || strchr(name, '/') || strchr(name, '\\') || chunk > LIMIT) return 1;
    name_len = strlen(name); if (name_len > 65535) return 1;
    if (chunk < 4096) chunk = 65536;
    count = (n + chunk - 1) / chunk; total = 28 + name_len + count * 13;
    payload = (uint8_t **)calloc(count, sizeof(*payload)); packed = (size_t *)calloc(count, sizeof(*packed)); if (!payload || !packed) goto error;
    for (i = 0; i < count; i++) {
        length = n - i * chunk; if (length > chunk) length = chunk;
        payload[i] = lexa_brief_lz_encode(s + i * chunk, length, &packed[i]); if (!payload[i]) goto error;
        compressed = packed[i] < length;
        if (compressed) { decoded = lexa_brief_lz_decode(payload[i], packed[i], length, NULL); if (!decoded) goto error; compressed = memcmp(decoded, s + i * chunk, length) == 0; free(decoded); }
        if (!compressed) { free(payload[i]); payload[i] = NULL; packed[i] = length; }
        if (packed[i] > LIMIT - total) goto error;
        total += packed[i];
    }
    out = (uint8_t *)calloc(total, 1); if (!out) goto error;
    memcpy(out, "WORM", 4); out[4] = 26; out[6] = 4; put32(out + 8, n); put32(out + 12, hash(s, n)); put32(out + 16, chunk); put32(out + 20, count); out[24] = (uint8_t)name_len; out[25] = (uint8_t)(name_len >> 8); memcpy(out + 28, name, name_len);
    pos = 28 + name_len + count * 13;
    for (i = 0; i < count; i++) {
        uint8_t *record = out + 28 + name_len + i * 13; length = n - i * chunk; if (length > chunk) length = chunk;
        record[0] = payload[i] ? 1 : 0; put32(record + 1, length); put32(record + 5, packed[i]); put32(record + 9, hash(s + i * chunk, length));
        memcpy(out + pos, payload[i] ? payload[i] : s + i * chunk, packed[i]); pos += packed[i];
    }
    for (i = 0; i < count; i++) free(payload[i]);
    free(payload); free(packed); *data = out; *size = total; return 0;
error:
    if (payload) for (i = 0; i < count; i++) free(payload[i]);
    free(payload); free(packed); free(out); return 1;
}
