/*
 * tngp/spr.c
 *
 * Copyright (C) 2022 bzt
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * @brief Sprite manager
 *
 */

#include "tngp.h"

typedef struct {
    int w, h;
    uint32_t *data;
    SDL_Texture *txt;
} spr_atlas_t;

spr_atlas_t *atlas = NULL;
int spr_locked = 0;

/**
 * Initialize sprites
 */
void spr_init()
{
    SDL_Surface *srf;
    world_sprite_t *spr;
    stbi__context s;
    stbi__result_info ri;
    uint8_t *ptr, *buf;
    int i, k;

    if(world.numatl > 0) {
        spr = spr_get(world.uispr[E_APPICO].cat, world.uispr[E_APPICO].idx, 0);
        atlas = (spr_atlas_t*)main_alloc(world.numatl * sizeof(spr_atlas_t));
        for(i = 0; i < world.numatl; i++) {
            ptr = (uint8_t*)main_alloc(world.atl[i].size);
            world_seektng(world.atl[i].tng, 0, world.atl[i].offs);
            if(!world_readtng(world.atl[i].tng, 0, ptr, world.atl[i].size)) {
                if(verbose > 1) printf("tngp: error reading atlas %03X\r\n", i);
                free(ptr);
                continue;
            }
            memset(&s, 0, sizeof(s));
            memset(&ri, 0, sizeof(ri));
            s.img_buffer = s.img_buffer_original = ptr;
            s.img_buffer_end = s.img_buffer_original_end = ptr + world.atl[i].size;
            ri.bits_per_channel = 8;
            buf = (uint8_t*)stbi__png_load(&s, &atlas[i].w, &atlas[i].h, &k, 4, &ri);
            free(ptr);
            if(buf) {
                if(spr && spr->data[0].atlasid == i) {
                    ptr = (uint8_t*)main_alloc(spr->w * spr->h * 4);
                    for(k = 0; k < spr->data[0].h; k++)
                        memcpy(ptr + ((spr->data[0].t + k) * spr->w + spr->data[0].l) * 4,
                            buf + ((spr->data[0].y + k) * atlas[i].w + spr->data[0].x) * 4, spr->data[0].w * 4);
                    srf = SDL_CreateRGBSurfaceFrom((Uint32 *)ptr, spr->w, spr->h, 32, spr->w*4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
                    if(srf) {
                        SDL_SetWindowIcon(window, srf);
                        SDL_FreeSurface(srf);
                    }
                    free(ptr);
                }
                atlas[i].txt = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STATIC,
                    atlas[i].w, atlas[i].h);
                if(atlas[i].txt) {
                    SDL_SetTextureBlendMode(atlas[i].txt, SDL_BLENDMODE_BLEND);
                    SDL_UpdateTexture(atlas[i].txt, NULL, buf, atlas[i].w * 4);
                    atlas[i].data = (uint32_t*)buf;
                } else {
                    atlas[i].w = atlas[i].h = 0;
                    free(buf);
                }
            } else {
                if(verbose > 1) printf("tngp: PNG error in atlas %03X\r\n", i);
            }
        }
    }
    for(i = 0; i < 5; i++)
        main_cursors[i] = spr_get(world.uispr[E_PTR + i].cat, world.uispr[E_PTR + i].idx, 0);
    ui_init();
}

/**
 * Free resources
 */
void spr_free()
{
    int i;

    if(atlas) {
        for(i = 0; i < world.numatl; i++) {
            if(atlas[i].txt) SDL_DestroyTexture(atlas[i].txt);
            if(atlas[i].data) free(atlas[i].data);
        }
        free(atlas); atlas = NULL;
    }
}

/**
 * Set fading on atlases
 */
void spr_fade(int a)
{
    int i;

    for(i = 0; i < world.numatl; i++)
        if(atlas[i].txt) SDL_SetTextureAlphaMod(atlas[i].txt, a);
}

/**
 * Return a background sprite
 */
SDL_Texture *spr_getbg(int idx, int *w, int *h)
{
    SDL_Texture *ret = NULL;
    stbi__context s;
    stbi__result_info ri;
    uint8_t *ptr, *buf;
    int k, W, H;

    if(w && h) *w = *h = 0;
    if(idx < 0 || idx >= world.numspr[SPR_BG] || !world.bg) return NULL;
    ptr = (uint8_t*)main_alloc(world.bg[idx].size);
    world_seektng(world.bg[idx].tng, 0, world.bg[idx].offs);
    if(!world_readtng(world.bg[idx].tng, 0, ptr, world.bg[idx].size)) {
        if(verbose > 1) printf("tngp: error reading background %04X\r\n", idx);
        free(ptr);
        return NULL;
    }
    memset(&s, 0, sizeof(s));
    memset(&ri, 0, sizeof(ri));
    s.img_buffer = s.img_buffer_original = ptr;
    s.img_buffer_end = s.img_buffer_original_end = ptr + world.bg[idx].size;
    ri.bits_per_channel = 8;
    buf = (uint8_t*)stbi__png_load(&s, &W, &H, &k, 4, &ri);
    free(ptr);
    if(buf) {
        ret = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, W, H);
        if(ret) {
            SDL_SetTextureBlendMode(ret, SDL_BLENDMODE_BLEND);
            SDL_LockTexture(ret, NULL, (void**)&ptr, &k);
            memcpy(ptr, buf, W * H * 4);
            SDL_UnlockTexture(ret);
            if(w && h) { *w = W; *h = H; }
        }
        free(buf);
    } else {
        if(verbose > 1) printf("tngp: PNG error in background %04X\r\n", idx);
    }
    return ret;
}

/**
 * Return a sprite
 */
world_sprite_t *spr_get(int cat, int idx, int dir)
{
    world_sprite_t *spr;

    if(cat < SPR_UI || cat >= SPR_BG || !world.spr[cat] || idx < 0 || idx >= world.numspr[cat] || dir < 0 || dir > 7)
        return NULL;

    spr = &world.spr[cat][(idx << 3) + dir];
    if(!spr->data && dir > 0) spr = &world.spr[cat][(idx << 3) + dir - 1];
    if(!spr->data) spr = &world.spr[cat][(idx << 3)];
    return spr->data ? spr : NULL;
}

/**
 * Display a sprite
 */
void spr_render(int x, int y, world_sprite_t *spr, int frame, int flip, int w, int h, int a)
{
    SDL_Rect src, rect;
    world_sprmap_t *currframe;

    if(!spr || !spr->data) return;
    if(w < 1) w = spr->w;
    if(h < 1) h = spr->h;
    if(x + w < 0 || x - w >= win_w || y + h < 0 || y - h >= win_h) return;
    if(frame < 0 || frame >= spr->f) frame = 0;
    currframe = &spr->data[frame];
    if(currframe->atlasid < 0 || currframe->atlasid >= world.numatl || !spr->w || !spr->h) return;
    rect.x = x + (!flip ? currframe->l : spr->w - currframe->w - currframe->l) * w / spr->w; rect.y = y + currframe->t * h / spr->h;
    src.w = currframe->w; rect.w = currframe->w * w / spr->w; src.h = currframe->h; rect.h = currframe->h * h / spr->h;
    src.x = currframe->x; src.y = currframe->y;
    SDL_SetTextureAlphaMod(atlas[currframe->atlasid].txt, a);
    switch(currframe->op) {
        case TNG_ATLAS_VFLIP:
            SDL_RenderCopyEx(renderer, atlas[currframe->atlasid].txt, &src, &rect, 0, NULL, (flip ? SDL_FLIP_HORIZONTAL : 0) |
                SDL_FLIP_VERTICAL);
        break;
        case TNG_ATLAS_HFLIP:
            SDL_RenderCopyEx(renderer, atlas[currframe->atlasid].txt, &src, &rect, 0, NULL, flip ? 0 : SDL_FLIP_HORIZONTAL);
        break;
        case TNG_ATLAS_ROTCW:
            SDL_RenderCopyEx(renderer, atlas[currframe->atlasid].txt, &src, &rect, 90, NULL, flip ? SDL_FLIP_HORIZONTAL : 0);
        break;
        case TNG_ATLAS_ROT:
            SDL_RenderCopyEx(renderer, atlas[currframe->atlasid].txt, &src, &rect, 180, NULL, flip ? SDL_FLIP_HORIZONTAL : 0);
        break;
        case TNG_ATLAS_ROTCCW:
            SDL_RenderCopyEx(renderer, atlas[currframe->atlasid].txt, &src, &rect, 270, NULL, flip ? SDL_FLIP_HORIZONTAL : 0);
        break;
        case TNG_ATLAS_COPY:
        default:
            SDL_RenderCopyEx(renderer, atlas[currframe->atlasid].txt, &src, &rect, 0, NULL, flip ? SDL_FLIP_HORIZONTAL : 0);
        break;
    }
    SDL_SetTextureAlphaMod(atlas[currframe->atlasid].txt, 255);
}

/**
 * Blit a sprite with recolorization. Expects that atlases are locked
 */
void spr_blit(uint32_t *dst, int dw, int dh, int dp, int x, int y, int w, int h, world_sprite_t *spr, int frame, uint32_t *pal)
{
    world_sprmap_t *currframe;
    uint32_t *s = NULL, *d, *S, *D;
    int i, j, k;

    if(!dst || !spr || !spr->data || dw < 1 || dh < 1 || dp < 4 || w < 1 || x > dw || h < 1 || y > dh ||
      x + spr->w < 0 || y + spr->h < 0)
        return;
    if(frame < 0) frame = 0;
    if(frame >= spr->f) frame = frame % spr->f;
    currframe = &spr->data[frame];
    if(currframe->atlasid < 0 || currframe->atlasid >= world.numatl) return;
    dp >>= 2; d = dst + y * dp + x;
    switch(currframe->op) {
        case TNG_ATLAS_VFLIP:
            for(i = 0; i < h; i++, s -= atlas[currframe->atlasid].w, d += dp) {
                k = i % spr->h;
                if(k == currframe->t + currframe->h) { k = spr->h - k; d += k * dp; i += k; k = 0; if(i >= h) break; }
                if(!k) {
                    s = atlas[currframe->atlasid].data + (currframe->y + currframe->h - 1) * atlas[currframe->atlasid].w +
                        currframe->x;
                    d += currframe->t * dp; i += currframe->t;
                }
                for(D = d, S = s, j = 0; j < w; j++, D++, S++) {
                    k = j % spr->w;
                    if(k == currframe->l + currframe->w) { k = spr->w - k; D += k; j += k; k = 0; if(j >= w) break; }
                    if(!k) { D += currframe->l; j += currframe->l; S = s; }
                    ui_set(D, S, pal);
                }
            }
        break;
        case TNG_ATLAS_HFLIP:
            for(i = 0; i < h; i++, s += atlas[currframe->atlasid].w, d += dp) {
                k = i % spr->h;
                if(k == currframe->t + currframe->h) { k = spr->h - k; d += k * dp; i += k; k = 0; if(i >= h) break; }
                if(!k) {
                    s = atlas[currframe->atlasid].data + currframe->y * atlas[currframe->atlasid].w + currframe->x +
                        currframe->w - 1;
                    d += currframe->t * dp; i += currframe->t;
                }
                for(D = d, S = s, j = 0; j < w; j++, D++, S--) {
                    k = j % spr->w;
                    if(k == currframe->l + currframe->w) { k = spr->w - k; D += k; j += k; k = 0; if(j >= w) break; }
                    if(!k) { D += currframe->l; j += currframe->l; S = s; }
                    ui_set(D, S, pal);
                }
            }
        break;
        case TNG_ATLAS_ROTCW:
            for(i = 0; i < h; i++, s++, d += dp) {
                k = i % spr->w;
                if(k == currframe->t + currframe->w) { k = spr->w - k; d += k * dp; i += k; k = 0; if(i >= h) break; }
                if(!k) {
                    s = atlas[currframe->atlasid].data + (currframe->y + currframe->h - 1) * atlas[currframe->atlasid].w +
                        currframe->x;
                    d += currframe->t * dp; i += currframe->t;
                }
                for(D = d, S = s, j = 0; j < w; j++, D++, S -= atlas[currframe->atlasid].w) {
                    k = j % spr->h;
                    if(k == currframe->l + currframe->h) { k = spr->h - k; D += k; j += k; k = 0; if(j >= w) break; }
                    if(!k) { D += currframe->l; j += currframe->l; S = s; }
                    ui_set(D, S, pal);
                }
            }
        break;
        case TNG_ATLAS_ROT:
            for(i = 0; i < h; i++, s -= atlas[currframe->atlasid].w, d += dp) {
                k = i % spr->h;
                if(k == currframe->t + currframe->h) { k = spr->h - k; d += k * dp; i += k; k = 0; if(i >= h) break; }
                if(!k) {
                    s = atlas[currframe->atlasid].data + (currframe->y + currframe->h - 1) * atlas[currframe->atlasid].w +
                        currframe->x + currframe->w - 1;
                    d += currframe->t * dp; i += currframe->t;
                }
                for(D = d, S = s, j = 0; j < w; j++, D++, S--) {
                    k = j % spr->w;
                    if(k == currframe->l + currframe->w) { k = spr->w - k; D += k; j += k; k = 0; if(j >= w) break; }
                    if(!k) { D += currframe->l; j += currframe->l; S = s; }
                    ui_set(D, S, pal);
                }
            }
        break;
        case TNG_ATLAS_ROTCCW:
            for(i = 0; i < h; i++, s--, d += dp) {
                k = i % spr->w;
                if(k == currframe->t + currframe->w) { k = spr->w - k; d += k * dp; i += k; k = 0; if(i >= h) break; }
                if(!k) {
                    s = atlas[currframe->atlasid].data + (currframe->y + currframe->h - 1) * atlas[currframe->atlasid].w +
                        currframe->x + currframe->w - 1;
                    d += currframe->t * dp; i += currframe->t;
                }
                for(D = d, S = s, j = 0; j < w; j++, D++, S -= atlas[currframe->atlasid].w) {
                    k = j % spr->h;
                    if(k == currframe->l + currframe->h) { k = spr->h - k; D += k; j += k; k = 0; if(j >= w) break; }
                    if(!k) { D += currframe->l; j += currframe->l; S = s; }
                    ui_set(D, S, pal);
                }
            }
        break;
        case TNG_ATLAS_COPY:
        default:
            for(i = 0; i < h; i++, s += atlas[currframe->atlasid].w, d += dp) {
                k = i % spr->h;
                if(k == currframe->t + currframe->h) { k = spr->h - k; d += k * dp; i += k; k = 0; if(i >= h) break; }
                if(!k) {
                    s = atlas[currframe->atlasid].data + currframe->y * atlas[currframe->atlasid].w + currframe->x;
                    d += currframe->t * dp; i += currframe->t;
                }
                for(D = d, S = s, j = 0; j < w; j++, D++, S++) {
                    k = j % spr->w;
                    if(k == currframe->l + currframe->w) { k = spr->w - k; D += k; j += k; k = 0; if(j >= w) break; }
                    if(!k) { D += currframe->l; j += currframe->l; S = s; }
                    ui_set(D, S, pal);
                }
            }
        break;
    }
}
