/*
 * apck.h - Asset Pack (unpacker)
 * https://gitlab.com/bztsrc/assetpack
 *
 * Copyright (C) 2024 bzt, MIT license
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL ANY
 * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * @brief Asset Pack single header library
 */

#ifndef APCK_H
#define APCK_H
#ifdef  __cplusplus
extern "C" {
#endif

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

#define APCK_MAGIC "APCK"

#ifdef _MSC_VER
#pragma pack(push)
#pragma pack(1)
#define _PACK
#else
#define _PACK __attribute__((packed))
#endif

typedef struct {
  uint8_t   magic[4];
  uint32_t  hdr_size;
  uint8_t   engine[12];
  uint32_t  version;
  uint32_t  size;
  uint32_t  chksum;
  uint8_t   enckey[32];
} _PACK apck_hdr_t;

#ifdef _MSC_VER
#pragma pack(pop)
#endif
#undef _PACK

#ifndef DBG
#define DBG(a)
#endif
#ifndef __THROW
#define __THROW
#endif

int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen);
uint8_t *stbi_load_from_memory(uint8_t const *buffer, int len, int *x, int *y, int *comp, int req_comp);
int stb_vorbis_decode_memory(const uint8_t *mem, int len, int *channels, int *sample_rate, short **output);

enum { APCK_OK, APCK_ERR_BADINP, APCK_ERR_BADPCK, APCK_ERR_NOMEM };
enum { APCK_LOCALE, APCK_FONT = 32, APCK_BGM, APCK_SFX, APCK_VIDEO, APCK_MODEL, APCK_IMAGE, APCK_ATLAS, APCK_SPRITE, APCK_MAP,
    APCK_OBJECT = 46, APCK_SCRIPT, APCK_DATA01, APCK_DATA02, APCK_DATA03, APCK_DATA04, APCK_DATA05, APCK_DATA06, APCK_DATA07,
    APCK_DATA08, APCK_DATA09, APCK_DATA10, APCK_DATA11, APCK_DATA12, APCK_DATA13, APCK_DATA14, APCK_DATA15, APCK_DATA16,
    APCK_NUMTYPES };
enum { APCK_REGULAR = 0, APCK_BOLD = 1, APCK_ITALIC = 2, APCK_UNDERLINE = 16, APCK_STRIKETHROUGH = 32 };

#define APCK_COMPRESSED(c,i) (((c) < APCK_FONT && !(i)) || (c) == APCK_FONT || ((c) >= APCK_SPRITE && (c) <= APCK_SCRIPT))

typedef void* (*apck_initcb_t)(uint8_t *enckey);
typedef int (*apck_readcb_t)(void *enc, uint64_t offs, uint8_t *buf, uint64_t size);

typedef struct {
    void *f;
    uint64_t size;
    uint8_t *str;
    uint32_t aidx;
    void *enc;
} apck_archive_t;

typedef struct {
    int archive, w, h;
    char *name;
    uint32_t str;
    uint64_t offs;
    uint64_t size;
    uint64_t comp;
    uint8_t *buf;
} apck_file_t;

typedef struct {
    char engine[16];
    uint32_t version;
    uint32_t numarchive;
    uint32_t numfiles[APCK_NUMTYPES];
    apck_initcb_t initcb;
    apck_readcb_t readcb;
    apck_archive_t *archives;
    apck_file_t *files[APCK_NUMTYPES];
    uint32_t locale, *msgstr[APCK_FONT];
    void *font;
} apck_t;

uint8_t *apck_uleb128(uint8_t *ptr, uint64_t *value);
uint32_t apck_crc32(uint8_t *buf, uint32_t size);
int apck_init(apck_t *ctx, char *engine, uint32_t version, apck_initcb_t initcb, apck_readcb_t readcb);
uint8_t *apck_readbuf(apck_t *ctx, uint32_t archive, uint64_t offs, uint64_t size);
uint64_t apck_read(apck_t *ctx, uint32_t archive, uint64_t offs, uint8_t *buf, uint64_t size);
int apck_load(apck_t *ctx, char *fn);
int apck_free(apck_t *ctx);
int apck_release(apck_t *ctx, int type, char *name);
apck_file_t *apck_lookup(apck_t *ctx, int type, char *name);
uint8_t *apck_asset(apck_t *ctx, int type, char *name, uint64_t *size);
uint32_t *apck_image(apck_t *ctx, int type, char *name, int *w, int *h);
int16_t *apck_audio(apck_t *ctx, int type, char *name, int *numsamples, int *channels);
int apck_font(apck_t *ctx, char *name);
uint32_t *apck_text(apck_t *ctx, char *msgid, uint32_t color, int fontstyle, int fontsize, int *w, int *h);

#include "apck_file.h"
#include "apck_map.h"
#include "apck_locale.h"
#include "apck_sprite.h"

apck_map_t *apck_map(apck_t *ctx, char *name);

#ifdef APCK_IMPLEMENTATION

#define inline __inline__

#ifndef STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STBI_NO_FAILURE_STRINGS
#define STBI_NO_STDIO
#define STBI_NO_LINEAR
#define STBI_ONLY_PNG
/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb
                                  no warranty implied; use at your own risk
    https://github.com/nothings/stb */
#include "stb_image.h"
#endif

#ifndef APCK_OGG
#define APCK_OGG stb_vorbis_decode_memory
#define STB_VORBIS_NO_STDIO
#define STB_VORBIS_NO_PUSHDATA_API
#define STB_VORBIS_MAX_CHANNELS    8
/* Ogg Vorbis audio decoder - v1.22 - public domain
    https://github.com/nothings/stb */
#include "stb_vorbis.h"
#endif

#if !defined(SSFN_HEADERONLY) && !defined(SSFN_IMPLEMENTATION)
#define SSFN_IMPLEMENTATION
#define SSFN_MAXLINES 1024
#endif
/* ssfn.h - Scalable Screen Font - MIT license
    https://gitlab.com/bztsrc/scalable-font2 */
#include "ssfn.h"

#if !defined(M3D_HEADERONLY) && !defined(M3D_IMPLEMENTATION)
#define M3D_IMPLEMENTATION
#define M3D_NOTEXTURE
#endif
#endif /* APCK_IMPLEMENTATION */
/* m3d.h - Model3D - MIT license
    https://bztsrc.gitlab.com/model3d */
#include "m3d.h"
m3d_t *apck_model(apck_t *ctx, char *name);

#ifdef APCK_IMPLEMENTATION

/**
 * Read in an ULEB128 number
 */
uint8_t *apck_uleb128(uint8_t *ptr, uint64_t *value)
{
    uint32_t shift = 0, b;
    *value = 0;
    do {
        b = *ptr++;
        *value |= ((b & 0x7f) << shift);
        shift += 7;
    } while(shift < 64 && (b & 0x80));
    return ptr;
}

/**
 * Calculate checksum
 */
uint32_t apck_crc32(uint8_t *buf, uint32_t size)
{
    static uint32_t crc32_lookup[256] = { 0 };
    uint32_t crc32_val = 0xffffffff, i, j, d;
    if(!crc32_lookup[1])
        for(i = 0; i < 256; i++)
            for(d = i, j = 0; j < 8; j++)
                crc32_lookup[i] = d = d & 1 ? (d >> 1) ^ 0xedb88320 : d >> 1;
    while(size--) crc32_val = (crc32_val >> 8) ^ crc32_lookup[(crc32_val & 0xff) ^ *buf++];
    return crc32_val ^ 0xffffffff;
}

/**
 * Initialize unpacker context
 */
int apck_init(apck_t *ctx, char *engine, uint32_t version, apck_initcb_t initcb, apck_readcb_t readcb)
{
    if(!ctx) return APCK_ERR_BADINP;

    memset(ctx, 0, sizeof(apck_t));
    if(engine) strncpy((char*)ctx->engine, engine, 12);
    ctx->version = version;
    ctx->initcb = initcb;
    ctx->readcb = readcb;
    return APCK_OK;
}

/**
 * Read asset from archive into a newly allocated buffer. Supported by all ciphers.
 */
uint8_t *apck_readbuf(apck_t *ctx, uint32_t archive, uint64_t offs, uint64_t size)
{
    uint8_t *buf;
    uint64_t len;

    if(!ctx || !size || archive >= ctx->numarchive || offs >= ctx->archives[archive].size) return NULL;
    len = (size + 255) & ~255;
    if(!(buf = (uint8_t*)malloc(len))) return NULL;
    apck_fileseek(ctx->archives[archive].f, offs);
    if((size = apck_fileread(ctx->archives[archive].f, buf, len)) && ctx->readcb)
        (*ctx->readcb)(ctx->archives[archive].enc, offs, buf, size);
    if(!size) { free(buf); buf = NULL; }
    return buf;
}

/**
 * Read part of archive into an existing buffer. Not all ciphers supports this.
 */
uint64_t apck_read(apck_t *ctx, uint32_t archive, uint64_t offs, uint8_t *buf, uint64_t size)
{
    if(!ctx || !buf || !size || archive >= ctx->numarchive || offs >= ctx->archives[archive].size) return APCK_ERR_BADINP;
    if(offs + size > ctx->archives[archive].size) size = ctx->archives[archive].size - offs;
    apck_fileseek(ctx->archives[archive].f, offs);
    if((size = apck_fileread(ctx->archives[archive].f, buf, size)) && ctx->readcb)
        (*ctx->readcb)(ctx->archives[archive].enc, offs, buf, size);
    return size;
}

/**
 * Merge an asset into the global Asset Directory
 */
static int _apck_merge_asset(apck_t *ctx, char *name, uint32_t a, uint32_t j, uint32_t n, uint64_t o, uint64_t s, uint64_t c)
{
    uint32_t k = 0;

    if(j != APCK_ATLAS)
        for(k = 0; k < ctx->numfiles[j] && strcmp(ctx->files[j][k].name, name); k++);
    if(j == APCK_ATLAS || k >= ctx->numfiles[j]) {
        k = ctx->numfiles[j]++;
        if(!(ctx->files[j] = (apck_file_t*)realloc(ctx->files[j], ctx->numfiles[j] * sizeof(apck_file_t)))) {
            DBG(("unable to allocate files[%u] for '%s' (%u entries)\n", j, name, ctx->numfiles[j]));
            ctx->numfiles[j] = 0;
            return 0;
        }
        memset(&ctx->files[j][k], 0, sizeof(apck_file_t));
    }
    if(ctx->files[j][k].buf) { free(ctx->files[j][k].buf); ctx->files[j][k].buf = NULL; }
    ctx->files[j][k].archive = a;
    ctx->files[j][k].w = ctx->files[j][k].h = 0;
    ctx->files[j][k].name = name;
    ctx->files[j][k].str = n;
    ctx->files[j][k].offs = o;
    ctx->files[j][k].size = s;
    ctx->files[j][k].comp = c;
    return 1;
}

/**
 * Helper to sort assets
 */
int _apck_filecmp(const void *a, const void *b)
{
    return strcmp(((apck_file_t*)a)->name, ((apck_file_t*)b)->name);
}

/**
 * Add an archive to the context
 */
int apck_load(apck_t *ctx, char *fn)
{
    uint64_t n, m, o, s, c;
    uint32_t a, i, j, k = 0;
    uint64_t size;
    uint8_t *comp, *orig, *ptr, *end;
    apck_hdr_t hdr;
    void *f, *enc;
    char *str, *name;

    if(!ctx || !fn || !*fn) return APCK_ERR_BADINP;

    /* get header */
    if(!(f = apck_fileopen(fn, &size))) return APCK_ERR_BADPCK;
    if(!apck_fileread(f, &hdr, sizeof(hdr)) || memcmp(hdr.magic, APCK_MAGIC, 4) ||
      hdr.size < 9 || hdr.hdr_size - sizeof(apck_hdr_t) > hdr.size ||
      (ctx->engine[0] && memcmp(ctx->engine, hdr.engine, 12)) || (ctx->version && ctx->version < hdr.version) ||
      (*((uint64_t*)hdr.enckey) && (!ctx->initcb || !ctx->readcb))) {
            apck_fileclose(f);
            return APCK_ERR_BADPCK;
        }
    if(!(comp = (uint8_t*)malloc(hdr.hdr_size - sizeof(apck_hdr_t)))) {
        apck_fileclose(f);
        return APCK_ERR_NOMEM;
    }
    if(!(orig = (uint8_t*)malloc(hdr.size))) {
        free(comp);
        apck_fileclose(f);
        return APCK_ERR_NOMEM;
    }

    /* we can't use apck_read yet, because the archive isn't registered yet */
    enc = *((uint64_t*)hdr.enckey) ? (*ctx->initcb)(hdr.enckey) : NULL;
    if(!apck_fileread(f, comp, hdr.hdr_size - sizeof(apck_hdr_t)) ||
      (enc && (*ctx->readcb)(enc, sizeof(apck_hdr_t), comp, hdr.hdr_size - sizeof(apck_hdr_t))) ||
      stbi_zlib_decode_buffer((char*)orig, (int)hdr.size, (char*)comp, (int)(hdr.hdr_size - sizeof(apck_hdr_t))) < 1 ||
      apck_crc32(orig, hdr.size) != hdr.chksum || *((uint32_t*)orig) < 4 || *((uint32_t*)orig) >= hdr.size) {
        if(enc) free(enc);
        free(comp);
        free(orig);
        apck_fileclose(f);
        return APCK_ERR_BADPCK;
    }
    free(comp);

    /* add the archive to the list */
    a = ctx->numarchive++;
    if(!(ctx->archives = (apck_archive_t*)realloc(ctx->archives, ctx->numarchive * sizeof(apck_archive_t)))) {
        ctx->numarchive = 0;
        return APCK_ERR_NOMEM;
    }
    ctx->archives[a].f = f;
    ctx->archives[a].size = size;
    ctx->archives[a].str = orig;
    ctx->archives[a].enc = enc;
    ctx->archives[a].aidx = ctx->numfiles[APCK_ATLAS];

    /* merge its asset directory with the existing one */
    ptr = orig + *((uint32_t*)orig);
    end = orig + hdr.size;
    for(j = 0; j < APCK_NUMTYPES && ptr < end; j++) {
        ptr = apck_uleb128(ptr, &m);
        for(i = 0; i < m && ptr < end; i++) {
            ptr = apck_uleb128(ptr, &n);
            ptr = apck_uleb128(ptr, &o);
            ptr = apck_uleb128(ptr, &s);
            ptr = apck_uleb128(ptr, &c);
            o += hdr.hdr_size;
            name = (char*)orig + n;
            if(j < APCK_FONT) {
                /* locales need special treatment */
                if(!i) {
                    for(k = 0; k < APCK_FONT && ctx->files[k] && strcmp(ctx->files[k][0].name, name); k++);
                    if(k < APCK_FONT) {
                        if(!ctx->files[k]) {
                            if(!_apck_merge_asset(ctx, name, a, k, n, 0, 0, 0) || !ctx->files[k]) k = APCK_FONT;
                        }
                        if(k < APCK_FONT && (comp = apck_readbuf(ctx, a, o, c))) {
                            if((str = (char*)malloc(s))) {
                                if(stbi_zlib_decode_buffer((char*)str, (int)s, (char*)comp, (int)c) < 1 ||
                                  !_apck_merge_locale(ctx, k, str, s)) {
                                    k = APCK_FONT; DBG(("unable to uncompress locale '%s' from '%s'\n", name, fn));
                                }
                                free(str);
                            } else { k = APCK_FONT; DBG(("unable to allocate locale '%s' from '%s' (%u bytes)\n", name, fn, (uint32_t)s)); }
                            free(comp);
                        } else { k = APCK_FONT; DBG(("unable to read locale '%s' from '%s'\n", name, fn)); }
                    }
                } else
                if(k < APCK_FONT) {
                    /* voices and images with text for this locale */
                    _apck_merge_asset(ctx, name, a, k, n, o, s, c);
                }
            } else {
                /* every other asset */
                _apck_merge_asset(ctx, name, a, j, n, o, s, c);
            }
        }
    }
    /* sort assets by name so that we can do an O(log2) search later */
    for(j = 0; j < APCK_FONT; j++)
        if(ctx->files[j] && ctx->numfiles[j] > 1)
            qsort(&ctx->files[j][1], ctx->numfiles[j] - 1, sizeof(apck_file_t), _apck_filecmp);
    for(; j < APCK_NUMTYPES; j++)
        if(ctx->files[j] && ctx->numfiles[j] && j != APCK_ATLAS)
            qsort(ctx->files[j], ctx->numfiles[j], sizeof(apck_file_t), _apck_filecmp);
    return APCK_OK;
}

/**
 * Free buffer for one particular asset
 */
void _apck_asset_free(apck_file_t *file, int type)
{
    if(file && file->buf) {
        switch(type) {
            case APCK_MODEL: m3d_free((m3d_t*)file->buf); break;
            case APCK_SPRITE: apck_sprite_free((apck_sprite_t*)file->buf); break;
            case APCK_MAP: apck_map_free((apck_map_t*)file->buf); break;
            default: free(file->buf); break;
        }
        file->buf = NULL;
    }
}

/**
 * Free all resources
 */
int apck_free(apck_t *ctx)
{
    uint32_t i, j;

    if(!ctx) return APCK_ERR_BADINP;

    if(ctx->archives) {
        for(i = 0; i < ctx->numarchive; i++) {
            if(ctx->archives[i].f) apck_fileclose(ctx->archives[i].f);
            if(ctx->archives[i].str) free(ctx->archives[i].str);
            if(ctx->archives[i].enc) free(ctx->archives[i].enc);
        }
        free(ctx->archives);
    }
    for(j = 0; j < APCK_NUMTYPES; j++)
        if(ctx->files[j]) {
            for(i = 0; i < ctx->numfiles[j]; i++)
                _apck_asset_free(&ctx->files[j][i], j);
            free(ctx->files[j]);
        }
    for(i = 0; i < APCK_FONT; i++)
        if(ctx->msgstr[i]) free(ctx->msgstr[i]);
    if(ctx->font) {
        ssfn_free(ctx->font);
        free(ctx->font);
    }
    memset(ctx, 0, sizeof(apck_t));
    return APCK_OK;
}

/**
 * Look up an asset in the Global Asset Directory
 */
apck_file_t *apck_lookup(apck_t *ctx, int type, char *name)
{
    apck_file_t *dir;
    int s, e, h, m;

    if(!ctx || type < 0 || type >= APCK_NUMTYPES || !name || !*name || !ctx->files[type] || !ctx->numfiles[type])
        return NULL;

    /* use binary search */
    dir = ctx->files[type];
    s = !(type >= APCK_FONT);
    e = ctx->numfiles[type] - 1;
    while(s <= e) {
        h = ((e + s) >> 1);
        m = strcmp(dir[h].name, name);
        if(!m) return &dir[h];
        if(m > 0) e = h - 1; else s = h + 1;
    }
    return NULL;
}

/**
 * Free internal cache of one particular asset
 */
int apck_release(apck_t *ctx, int type, char *name)
{
    if(!ctx || type < 0 || type >= APCK_NUMTYPES || !name || !*name)
        return APCK_ERR_BADINP;

    _apck_asset_free(apck_lookup(ctx, type, name), type);
    return APCK_OK;
}

/**
 * Look up and return any arbitrary asset
 */
uint8_t *apck_asset(apck_t *ctx, int type, char *name, uint64_t *size)
{
    apck_file_t *file = NULL;
    uint8_t *buf, *comp;

    if(size) *size = 0;
    if(!ctx || type > APCK_MAP || type >= APCK_NUMTYPES || !name || !*name)
        return NULL;

    if((file = apck_lookup(ctx, type, name))) {
        /* we have this asset, let's see if we have cached its contents already */
        if(!file->buf) {
            if(file->size && (comp = apck_readbuf(ctx, file->archive, file->offs, file->comp ? file->comp : file->size))) {
                if(file->comp) {
                    /* we got the data from the archive, uncompress it */
                    if((buf = (uint8_t*)malloc(file->size))) {
                        if(stbi_zlib_decode_buffer((char*)buf, (int)file->size, (char*)comp, (int)file->comp) < 1) {
                            DBG(("unable to uncompress asset '%s'\n", name));
                            free(buf);
                            file->size = file->comp = 0;
                        } else
                            file->buf = buf;
                    } else { DBG(("unable to allocate asset '%s' (%u bytes)\n", name, (uint32_t)file->size)); }
                    free(comp);
                } else file->buf = comp;
            } else { DBG(("unable to read asset '%s'\n", name)); }
        }
        if(size) *size = file->size;
        return (uint8_t*)file->buf;
    }
    return NULL;
}

/**
 * Look up and set the font to be used by apck_text()
 */
int apck_font(apck_t *ctx, char *name)
{
    apck_file_t *file = NULL;
    uint8_t *buf, *comp;

    if(!ctx || !name || !*name)
        return APCK_ERR_BADINP;

    if(!ctx->font) {
        if(!(ctx->font = (ssfn_t*)malloc(sizeof(ssfn_t))))
            return APCK_ERR_NOMEM;
        memset(ctx->font, 0, sizeof(ssfn_t));
    } else
        ssfn_free(ctx->font);

    if((file = apck_lookup(ctx, APCK_FONT, name))) {
        /* we have this asset, let's see if we have cached its contents already */
        if(!file->buf) {
            if(file->comp && (comp = apck_readbuf(ctx, file->archive, file->offs, file->comp))) {
                /* we got the data from the archive, uncompress it */
                if((buf = (uint8_t*)malloc(file->size))) {
                    if(stbi_zlib_decode_buffer((char*)buf, (int)file->size, (char*)comp, (int)file->comp) < 1 || memcmp(buf, "SFN2", 4)) {
                        DBG(("unable to uncompress font '%s'\n", name));
                        file->size = file->comp = 0;
                        free(buf);
                    } else
                        file->buf = buf;
                } else { DBG(("unable to allocate font '%s' (%u bytes)\n", name, (uint32_t)file->size)); }
                free(comp);
            } else { DBG(("unable to read font '%s'\n", name)); }
        }
        if(ctx->font && file->buf)
            ssfn_load(ctx->font, file->buf);
        return APCK_OK;
    }
    return APCK_ERR_BADINP;
}

/**
 * Render localized text and return a pixel buffer as if the were an asset stored in archive
 */
uint32_t *apck_text(apck_t *ctx, char *msgid, uint32_t color, int fontstyle, int fontsize, int *w, int *h)
{
    ssfn_buf_t buf = { 0 };
    char *msgstr;
    int ret;

    if(w) *w = 0;
    if(h) *h = 0;
    if(!ctx || !ctx->font || !(color & 0xfc000000) || !(msgstr = apck_msgstr(ctx, msgid)) ||
      ssfn_select(ctx->font, SSFN_FAMILY_ANY, NULL, fontstyle | SSFN_STYLE_NOCACHE, fontsize) != SSFN_OK ||
      ssfn_bbox(ctx->font, msgstr, (int*)&buf.w, (int*)&buf.h, (int*)&buf.x, (int*)&buf.y) != SSFN_OK)
        return NULL;

    buf.fg = color;
    buf.p = buf.w * sizeof(uint32_t);
    if((buf.ptr = (uint8_t*)malloc(buf.p * buf.h))) {
        memset(buf.ptr, 0, buf.p * buf.h);
        while((ret = ssfn_render(ctx->font, &buf, msgstr)) > 0)
            msgstr += ret;
        if(w) *w = buf.w;
        if(h) *h = buf.h;
    }
    return (uint32_t*)buf.ptr;
}

/**
 * Look up and return a decoded 3D model
 */
m3d_t *apck_model(apck_t *ctx, char *name)
{
    apck_file_t *file = NULL;
    uint8_t *buf, *comp;

    if(!ctx || !name || !*name)
        return NULL;

    if((file = apck_lookup(ctx, APCK_MODEL, name))) {
        /* we have this asset, let's see if we have cached its contents already */
        if(!file->buf) {
            if(file->comp && (comp = apck_readbuf(ctx, file->archive, file->offs, file->comp))) {
                /* we got the data from the archive, uncompress it */
                if((buf = (uint8_t*)malloc(file->size))) {
                    if(stbi_zlib_decode_buffer((char*)buf, (int)file->size, (char*)comp, (int)file->comp) < 1) {
                        DBG(("unable to uncompress model '%s'\n", name));
                        file->size = file->comp = 0;
                    } else
                    if(!(file->buf = (uint8_t*)m3d_load(buf, NULL, NULL, NULL))) {
                        DBG(("unable to decode model '%s'\n", name));
                        file->size = file->comp = 0;
                    }
                    free(buf);
                } else { DBG(("unable to allocate model '%s' (%u bytes)\n", name, (uint32_t)file->size)); }
                free(comp);
            } else { DBG(("unable to read model '%s'\n", name)); }
        }
        return (m3d_t*)file->buf;
    }
    return NULL;
}

/**
 * Look up and return a decoded image asset
 */
uint32_t *apck_image(apck_t *ctx, int type, char *name, int *w, int *h)
{
    apck_file_t *file = NULL;
    uint8_t *buf;
    int p, i;

    if(w) *w = 0;
    if(h) *h = 0;
    if(!ctx || (type != APCK_IMAGE && type != APCK_ATLAS) || !name || !*name)
        return NULL;

    if(type == APCK_ATLAS) {
        /* atlases are generated so they don't have a real name, they use index instead */
        i = atoi(name) - 1;
        if(ctx->files[APCK_ATLAS] && i >= 0 && (uint32_t)i < ctx->numfiles[APCK_ATLAS])
            file = &ctx->files[APCK_ATLAS][i];
    } else {
        /* for images, try localized images first */
        if(ctx->locale < APCK_FONT && ctx->files[ctx->locale])
            file = apck_lookup(ctx, ctx->locale, name);
        if(!file) file = apck_lookup(ctx, type, name);
    }

    if(file) {
        /* we have this asset, let's see if we have cached its contents already */
        if(!file->buf) {
            if(file->size && (buf = apck_readbuf(ctx, file->archive, file->offs, file->size))) {
                if(!(file->buf = (uint8_t*)stbi_load_from_memory(buf, file->size, &file->w, &file->h, &p, 4))) {
                    DBG(("unable to decode image asset '%s'\n", name));
                    file->size = file->comp = 0;
                }
                free(buf);
            } else { DBG(("unable to read image asset '%s'\n", name)); }
        }
        /* extra check for images */
        if(!file->buf || file->w < 1 || file->h < 1) return NULL;
        /* return the decoded asset */
        if(w) *w = file->w;
        if(h) *h = file->h;
        return (uint32_t*)file->buf;
    }
    return NULL;
}

/**
 * Look up and return a decoded audio asset
 */
int16_t *apck_audio(apck_t *ctx, int type, char *name, int *numsamples, int *channels)
{
    apck_file_t *file = NULL;
    uint8_t *buf;
    int ch, hz;

    if(numsamples) *numsamples = 0;
    if(channels) *channels = 0;
    if(!ctx || (type != APCK_BGM && type != APCK_SFX) || !name || !*name)
        return NULL;

    /* for sound effects, try localized voices first */
    if(type == APCK_SFX && ctx->locale < APCK_FONT && ctx->files[ctx->locale])
        file = apck_lookup(ctx, ctx->locale, name);
    if(!file) file = apck_lookup(ctx, type, name);

    if(file) {
        /* we have this asset, let's see if we have cached its contents already */
        if(!file->buf) {
            if(file->size && (buf = apck_readbuf(ctx, file->archive, file->offs, file->size))) {
                if(!(file->w = APCK_OGG(buf, file->size, &ch, &hz, (int16_t**)&file->buf))) {
                    DBG(("unable to decode audio asset '%s'\n", name));
                    file->size = file->comp = 0;
                } else {
                    file->w *= ch;
                    file->h = -ch;
                }
                free(buf);
            } else { DBG(("unable to read audio asset '%s'\n", name)); }
        }
        /* extra check for audio */
        if(!file->buf || file->w < 1 || file->h > -1) return NULL;
        /* return the decoded asset */
        if(numsamples) *numsamples = file->w;
        if(channels) *channels = -file->h;
        return (int16_t*)file->buf;
    }
    return NULL;
}

/**
 * Look up and return a decoded sprite asset
 */
apck_sprite_t *apck_sprite(apck_t *ctx, char *name)
{
    apck_file_t *file = NULL;
    uint8_t *buf, *comp;

    if(!ctx || !name || !*name)
        return NULL;

    if((file = apck_lookup(ctx, APCK_SPRITE, name))) {
        /* we have this asset, let's see if we have cached its contents already */
        if(!file->buf) {
            if(file->comp && (comp = apck_readbuf(ctx, file->archive, file->offs, file->comp))) {
                if((buf = (uint8_t*)malloc(file->size))) {
                    if(stbi_zlib_decode_buffer((char*)buf, (int)file->size, (char*)comp, (int)file->comp) < 1 ||
                      !(file->buf = (uint8_t*)apck_sprite_decode(ctx->archives[file->archive].aidx,
                        ctx->archives[file->archive].str, buf, file->size))) {
                            DBG(("unable to uncompress sprite '%s'\n", name));
                            file->size = file->comp = 0;
                    }
                    free(buf);
                } else { DBG(("unable to allocate sprite '%s' (%u bytes)\n", name, (uint32_t)file->size)); }
                free(comp);
            } else { DBG(("unable to read sprite asset '%s'\n", name)); }
        }
        return (apck_sprite_t*)file->buf;
    }
    return NULL;
}

/**
 * Look up and return a decoded map asset
 */
apck_map_t *apck_map(apck_t *ctx, char *name)
{
    apck_file_t *file = NULL;
    uint8_t *buf, *comp;

    if(!ctx || !name || !*name)
        return NULL;

    if((file = apck_lookup(ctx, APCK_MAP, name))) {
        /* we have this asset, let's see if we have cached its contents already */
        if(!file->buf) {
            if(file->comp && (comp = apck_readbuf(ctx, file->archive, file->offs, file->comp))) {
                if((buf = (uint8_t*)malloc(file->size))) {
                    if(stbi_zlib_decode_buffer((char*)buf, (int)file->size, (char*)comp, (int)file->comp) < 1 ||
                      !(file->buf = (uint8_t*)apck_map_decode(ctx->archives[file->archive].str, buf, file->size))) {
                        DBG(("unable to uncompress map '%s'\n", name));
                        file->size = file->comp = 0;
                    }
                    free(buf);
                } else { DBG(("unable to allocate map '%s' (%u bytes)\n", name, (uint32_t)file->size)); }
                free(comp);
            } else { DBG(("unable to read map asset '%s'\n", name)); }
        }
        return (apck_map_t*)file->buf;
    }
    return NULL;
}

#endif /* APCK_IMPLEMENTATION */

#ifdef  __cplusplus
}
#endif
#endif /* APIC_H */
