/*
 * 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/private.h>
#include <drv/ps1/bios.h>
#include <drv/ps1/dma.h>
#include <drv/ps1/gpu.h>
#include <drv/ps1/irq.h>
#include <stddef.h>

struct res
{
    int hres, hres2, vres, interlace;
};

static void getres(const short w, struct res *const r)
{
    *r = (const struct res){0};

    switch (w)
    {
        case 320:
            r->hres = 1;
            break;
        case 368:
            r->hres2 = 1;
            break;
        case 512:
            r->hres = 2;
            break;
        case 640:
            r->hres = 3;
            r->vres = 1;
            r->interlace = 1;
            break;
    }
}

volatile int vblank_set;

static int irq(void)
{
    vblank_set = 1;
    I_STAT->bits.vblank = 0;
    return 0;
}

int gfx_init(void)
{
    const int event = OpenEvent(CLASS_VBLANK, SPEC_INTERRUPTED, MODE_EXECUTE,
        irq);

    if (event == -1)
        return -1;

    I_MASK->bits.vblank = 1;
    EnableEvent(gfx_vblank_ev = event);

    GP1->mask = (const union drv_ps1_gpu_gp1)
        {.reset_gpu.cmd = GP1_RESET_GPU}.mask;

    GP0->mask = (const union drv_ps1_gpu_gp0)
    {
        .fill_vram =
        {
            .cmd = GP0_FILL_VRAM,
            .w = 1024 / 16,
            .h = 512
        }
    }.mask;

    GP1->mask = (const union drv_ps1_gpu_gp1)
        {.reset_cmdbuf.cmd = GP1_RESET_CMDBUF}.mask;

    DPCR->mask |= (const union dpcr){.bits.gpu_en = 1}.mask;
    GP1->mask = (const union drv_ps1_gpu_gp1)
    {
        .dma =
        {
            .cmd = GP1_DMA_DIR,
            .dir = GP1_DMA_DIR_CPU_TO_GP0
        }
    }.mask;

    GP1->mask = (const union drv_ps1_gpu_gp1)
    {
        .display =
        {
            .cmd = GP1_DISPLAY_ENABLE,
        }
    }.mask;

    GP0->mask = (const union drv_ps1_gpu_gp0)
    {
        .draw_mode =
        {
            .cmd = GP0_DRAW_MODE,
            .draw_to_display = 1
        }
    }.mask;

    GP0->mask = (const union drv_ps1_gpu_gp0)
        {.tex_window.cmd = GP0_TEX_WINDOW}.mask;

    GP0->mask = (const union drv_ps1_gpu_gp0)
        {.draw_area_tl.cmd = GP0_DRAW_AREA_TOP_LEFT}.mask;

    GP0->mask = (const union drv_ps1_gpu_gp0)
    {
        .draw_area_br =
        {
            .cmd = GP0_DRAW_AREA_BOTTOM_RIGHT,
            .x = SCREEN_W - 1,
            .y = SCREEN_H - 1,
        }
    }.mask;

    GP1->mask = (const union drv_ps1_gpu_gp1)
    {
        .h_display_range =
        {
            .cmd = GP1_H_DISPLAY_RANGE,
            .x1 = 0x260,
            .x2 = 0x260 + 320 * 8
        }
    }.mask;

    GP1->mask = (const union drv_ps1_gpu_gp1)
    {
        .disparea =
        {
            .cmd = GP1_START_DISPLAY_AREA
        }
    }.mask;

#if 0
    GP1->mask = (const union drv_ps1_gpu_gp1)
    {
        .v_display_range =
        {
            .cmd = GP1_V_DISPLAY_RANGE,
#if VIDEO_MODE == VMODE_PAL
            .y1 = 0xa3,
            .y2 = 0xa3 + (SCREEN_H / 2)
#elif VIDEO_MODE == VMODE_NTSC
            .y1 = 0x88 - (224 / 2),
            .y2 = 0x88 + (224 / 2)
#endif
        }
    }.mask;
#else
#if 0
    /* TODO: use structs above. */
    GP1->mask = 0x06C4E24E;
    GP1->mask = 0x07040010;
#else
    /* values found from BIOS init state*/
    GP1->mask = 0x06c7e27e;
    GP1->mask = 0x0704682b;
#endif
#endif

    struct res r;

    getres(SCREEN_W, &r);

    GP1->mask = (const union drv_ps1_gpu_gp1)
    {
        .display_mode =
        {
            .cmd = GP1_DISPLAY_MODE,
            .hres = r.hres,
            .hres2 = r.hres2,
            .vres = r.vres,
            .vinterlace = r.interlace,
#if VIDEO_MODE == VMODE_PAL
            .vmode = 1
#endif
        }
    }.mask;

    return 0;
}
