/*
 * wnix, a Unix-like operating system for WebAssembly applications.
 * Copyright (C) 2025  Xavier Del Campo Romero
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <gfx/gfx.h>
#include <gfx/sprite.h>
#include <gfx/private.h>
#include <drv/ps1/gpu.h>
#include <stdint.h>

static void init(struct gfx_sprite *const s)
{
    *s = (const struct gfx_sprite){0};
    s->sznext.f.sz = (sizeof *s - sizeof s->sznext) / sizeof (uint32_t);
    s->common.f.cmd = GP0_DRAW_MODE;
    s->common.f.draw_to_disp = 1;
    s->cmd = GP0_TEXTRECT_VARSZ_OPAQ_RAW;
}

struct tim_pos
{
    uint16_t x, y, w, h;
};

static void transfer_init(const struct tim_pos *const p)
{
	while (!GPUSTAT->bits.ready_cmd)
        ;

    GP0->mask = (const union drv_ps1_gpu_gp0)
        {.copy_rect.cmd = GP0_COPY_RECT_CPU_VRAM}.mask;
    GP0->mask = (const union drv_ps1_gpu_gp0)
        {.coord = {.x = p->x, .y = p->y}}.mask;
    GP0->mask = (const union drv_ps1_gpu_gp0)
        {.size = {.w = p->w, .h = p->h}}.mask;
}

static int transfer(const size_t sz, const gfx_read r, void *const args)
{
    const size_t rem = sz % sizeof (uint32_t);

    if (sz >= sizeof (uint32_t))
    {
        for (size_t i = 0; i < sz / sizeof (uint32_t); i++)
        {
            uint32_t pix;

            if (r(&pix, sizeof pix, args))
                return -1;

            GP0->mask = pix;
        }
    }

    if (rem)
    {
        uint32_t pix = 0;

        if (r(&pix, rem, args))
            return -1;

        GP0->mask = pix;
    }

    return 0;
}

struct header
{
    uint32_t sz;
    struct tim_pos pos;
};

static short get_clutid(const short x, const short y)
{
    return (x & 0x3ff) >> 4 | (y & 0x1ff) << 6;
}

static int upload_clut(struct gfx_sprite *const s, const gfx_read r,
    void *const args)
{
    struct header clut;

    if (r(&clut, sizeof clut, args))
        return -1;

    transfer_init(&clut.pos);

    const size_t sz = clut.sz - sizeof clut;

    if (transfer(sz, r, args))
        return -1;

    s->clutid = get_clutid(clut.pos.x, clut.pos.y);
    return 0;
}

enum bpp
{
    BPP_4 = 0,
    BPP_8 = 1,
    BPP_16 = 2,
    BPP_24 = 4
};

static int upload_img(struct gfx_sprite *const s, const enum bpp bpp,
    const gfx_read r, void *const args)
{
    struct header img;

    if (r(&img, sizeof img, args))
        return -1;

    transfer_init(&img.pos);

    const size_t sz = img.sz - sizeof img;

    if (transfer(sz, r, args))
        return -1;

    enum
    {
        VRAM_X = 1024,
        VRAM_Y = 512,
        TPAGE_WIDTH = 64
    };

    s->common.f.tpagex = img.pos.x / TPAGE_WIDTH;
    s->common.f.tpagey = img.pos.y / (VRAM_Y / 2);
    s->u = img.pos.x % TPAGE_WIDTH;
    s->v = img.pos.y % (VRAM_Y / 2);

    switch (bpp)
    {
        case BPP_4:
            s->w = img.pos.w * 4;
            s->u <<= 2;
            break;

        case BPP_8:
            s->w = img.pos.w * 2;
            s->u <<= 1;
            break;

        case BPP_16:
            s->w = img.pos.w;
            break;

        case BPP_24:
            s->w = img.pos.w + (img.pos.w / 2);
            break;
    }

    s->h = img.pos.h;
    return 0;
}

int gfx_sprite_load(struct gfx_sprite *s, const gfx_read r, void *const args)
{
    init(s);

    struct
    {
        uint32_t version, bpp :3, has_clut :1, :28;
    } h;

    enum {VERSION_ID = 0x10};

    if (r(&h, sizeof h, args)
        || h.version != VERSION_ID
        || h.bpp == BPP_24
        || (h.has_clut && upload_clut(s, r, args))
        || upload_img(s, h.bpp, r, args))
        return -1;

    s->common.f.bpp = h.bpp ? __builtin_ctz(h.bpp) + 1 : 0;
    return 0;
}
