/*
 * lib/apck_sprite.h
 * 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 Sprite functions
 */

#ifndef APCK_SPRITE_H
#define APCK_SPRITE_H

enum { APCK_ANIM_LOOP, APCK_ANIM_ONCE, APCK_ANIM_BF, APCK_TILE_WANG, APCK_TILE_WALL };
enum { APCK_DIR_S, APCK_DIR_SW, APCK_DIR_W, APCK_DIR_NW, APCK_DIR_N, APCK_DIR_NE, APCK_DIR_E, APCK_DIR_SE, APCK_NUMDIR };
enum { APCK_M_COPY, APCK_M_FLIPV, APCK_M_FLIPH, APCK_M_ROT90, APCK_M_ROT180, APCK_M_ROT270 };

typedef struct {
    int atlas, m, w, h, sx, sy, dx, dy, dur;
    void *sfx;
} apck_frame_t;

typedef struct {
    int type, w, h, nf[APCK_NUMDIR];
    apck_frame_t *frames[APCK_NUMDIR];
} apck_sprite_t;

apck_sprite_t *apck_sprite_new(int type, int w, int h);
int apck_sprite_frame(apck_sprite_t *sprite, int dir, int atlas, int m, int w, int h, int sx, int sy, int dx, int dy, int dur, char *sfx);
int apck_sprite_free(apck_sprite_t *sprite);
apck_sprite_t *apck_sprite_decode(uint32_t archive, uint8_t *str, uint8_t *buf, uint64_t size);
void apck_sprite_pose(apck_t *ctx, apck_sprite_t *sprite, int dir, int frame, uint32_t *buf, int x, int y, int w, int h, int p);

#ifdef APCK_IMPLEMENTATION

/**
 * Create a new sprite
 */
apck_sprite_t *apck_sprite_new(int type, int w, int h)
{
    apck_sprite_t *ret;

    if(w < 1 || w > 65535 || h < 1 || h > 65535 || !(ret = (apck_sprite_t*)malloc(sizeof(apck_sprite_t))))
        return NULL;
    memset(ret, 0, sizeof(apck_sprite_t));
    ret->type = type < APCK_ANIM_LOOP || type > APCK_ANIM_BF ? APCK_ANIM_LOOP : type;
    ret->w = w;
    ret->h = h;
    return ret;
}

/**
 * Add a frame to a sprite
 */
int apck_sprite_frame(apck_sprite_t *sprite, int dir, int atlas, int m, int w, int h, int sx, int sy, int dx, int dy, int dur, char *sfx)
{
    int i;

    if(!sprite || dir < APCK_DIR_S || dir >= APCK_NUMDIR || m < APCK_M_COPY || m > APCK_M_ROT270 ||
      w < 1 || h < 1 || sx < 0 || sy < 0 || dx < 0 || dx >= sprite->w || dy < 0 || dy >= sprite->h)
        return APCK_ERR_BADINP;

    if(dur < 1) dur = 1;
    if(dur > 65535) dur = 65535;
    if(dx + w > sprite->w) w = sprite->w - dx;
    if(dy + h > sprite->h) h = sprite->h - dy;
    i = sprite->nf[dir]++;
    if(!(sprite->frames[dir] = (apck_frame_t*)realloc(sprite->frames[dir], sprite->nf[dir] * sizeof(apck_frame_t)))) {
        sprite->nf[dir] = 0;
        return APCK_ERR_NOMEM;
    }
    sprite->frames[dir][i].m = m;
    sprite->frames[dir][i].w = w;
    sprite->frames[dir][i].h = h;
    sprite->frames[dir][i].sx = sx;
    sprite->frames[dir][i].sy = sy;
    sprite->frames[dir][i].dx = dx;
    sprite->frames[dir][i].dy = dy;
    sprite->frames[dir][i].dur = dur;
    sprite->frames[dir][i].atlas = atlas;
    sprite->frames[dir][i].sfx = sfx;
    return APCK_OK;
}

/**
 * Free a sprite
 */
int apck_sprite_free(apck_sprite_t *sprite)
{
    int i;

    if(!sprite) return APCK_ERR_BADINP;

    for(i = 0; i < APCK_NUMDIR; i++)
        if(sprite->frames[i])
            free(sprite->frames[i]);
    free(sprite);
    return APCK_OK;
}

/**
 * Deserialize a sprite
 */
apck_sprite_t *apck_sprite_decode(uint32_t aidx, uint8_t *str, uint8_t *buf, uint64_t size)
{
    apck_sprite_t *sprite = NULL;
    uint64_t i, j, k, w, h, t, l;
    uint8_t *ptr = buf + 4;

    if(!str || !buf || !size || memcmp(buf, "SPFR", 4)) return NULL;

    /* parse header */
    ptr = apck_uleb128(ptr, &t);
    ptr = apck_uleb128(ptr, &w);
    ptr = apck_uleb128(ptr, &h);
    if(!(sprite = apck_sprite_new(t, w, h)))
        return NULL;
    /* for each direction */
    for(j = 0; j < APCK_NUMDIR; j++) {
        /* parse frame header */
        ptr = apck_uleb128(ptr, &l);
        sprite->nf[j] = l;
        /* allocate all frames at once */
        if((sprite->frames[j] = (apck_frame_t*)realloc(sprite->frames[j], sprite->nf[j] * sizeof(apck_frame_t)))) {
            /* foreach frame */
            for(i = 0; i < l; i++) {
                ptr = apck_uleb128(ptr, &k); sprite->frames[j][i].atlas = aidx + k;
                ptr = apck_uleb128(ptr, &k); sprite->frames[j][i].m = k;
                ptr = apck_uleb128(ptr, &k); sprite->frames[j][i].w = k;
                ptr = apck_uleb128(ptr, &k); sprite->frames[j][i].h = k;
                ptr = apck_uleb128(ptr, &k); sprite->frames[j][i].sx = k;
                ptr = apck_uleb128(ptr, &k); sprite->frames[j][i].sy = k;
                ptr = apck_uleb128(ptr, &k); sprite->frames[j][i].dx = k;
                ptr = apck_uleb128(ptr, &k); sprite->frames[j][i].dy = k;
                ptr = apck_uleb128(ptr, &k); sprite->frames[j][i].dur = k;
                ptr = apck_uleb128(ptr, &k); sprite->frames[j][i].sfx = str && k ? str + k : NULL;
            }
        } else
            sprite->nf[j] = 0;
    }
    return sprite;
}

/**
 * Blit a sprite in a specific pose on a pixel buffer
 */
void apck_sprite_pose(apck_t *ctx, apck_sprite_t *sprite, int dir, int frame, uint32_t *buf, int x, int y, int w, int h, int p)
{
    apck_file_t *atlas;
    apck_frame_t *f;
    uint8_t *fbuf;
    uint32_t *src, *dst;
    int X, Y, W, H;

    if(!sprite || dir < APCK_DIR_S || dir >= APCK_NUMDIR || frame < 0 || !sprite->frames[dir] || !buf || w < 1 || h < 1 ||
      p < 4 || x >= w || y >= h)
        return;
    p >>= 2;

    /* get frame and atlas */
    f = &sprite->frames[dir][frame % sprite->nf[dir]];
    if(f->atlas >= (int)ctx->numfiles[APCK_ATLAS]) return;
    atlas = &ctx->files[APCK_ATLAS][f->atlas];
    /* if we don't have the atlas yet, try to load it */
    if(!atlas->buf || !atlas->w || !atlas->h) {
        if(atlas->size && (fbuf = apck_readbuf(ctx, atlas->archive, atlas->offs, atlas->size))) {
            if(!(atlas->buf = (uint8_t*)stbi_load_from_memory(fbuf, atlas->size, &atlas->w, &atlas->h, &W, 4)))
                atlas->size = atlas->comp = atlas->w = atlas->h = 0;
            free(fbuf);
        }
        if(!atlas->buf || !atlas->w || !atlas->h) return;
    }

    W = f->w;
    if(x + W > w) W = w - x;
    if(f->sx + W > atlas->w) W = atlas->w - f->sx;
    H = f->h;
    if(y + H > h) H = h - y;
    if(f->sy + H > atlas->h) H = atlas->h - f->sy;
    if(W < 1 || H < 1) return;
    src = (uint32_t*)atlas->buf + f->sy * atlas->w + f->sx;
    dst = buf + (y + f->dy) * p + x + f->dx;

    switch(f->m) {
        case APCK_M_ROT270:
            for(Y = 0; Y < H; Y++, dst += p)
                for(X = 0; X < W; X++)
                    dst[X] = src[(H - 1 - X) * atlas->w + Y];
        break;
        case APCK_M_ROT180:
            src += (f->h - 1) * atlas->w;
            for(Y = 0; Y < H; Y++, src -= atlas->w, dst += p)
                for(X = 0; X < W; X++)
                    dst[X] = src[f->w - 1 - X];
        break;
        case APCK_M_ROT90:
            for(Y = 0; Y < H; Y++, dst += p)
                for(X = 0; X < W; X++)
                    dst[X] = src[X * atlas->w + f->w - 1 - Y];
        break;
        case APCK_M_FLIPH:
            src += (f->h - 1) * atlas->w;
            for(Y = 0; Y < H; Y++, src -= atlas->w, dst += p)
                memcpy(dst, src, W * 4);
        break;
        case APCK_M_FLIPV:
            for(Y = 0; Y < H; Y++, src += atlas->w, dst += p)
                for(X = 0; X < W; X++)
                    dst[X] = src[f->w - 1 - X];
        break;
        default:
            for(Y = 0; Y < H; Y++, src += atlas->w, dst += p)
                memcpy(dst, src, W * 4);
        break;
    }
}

#endif /* APCK_IMPLEMENTATION */

#endif /* APIC_SPRITE_H */
