/*
 * 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 <drv/ps1/cd.h>
#include <drv/ps1/bios.h>
#include <drv/ps1/cd/regs.h>
#include <drv/ps1/cd/routines.h>
#include <drv/ps1/cd/types.h>
#include <drv/ps1/event.h>
#include <stdint.h>

static int wait_event(void)
{
    struct cd_prv *const p = &drv_ps1_cd_prv;

    if (drv_ps1_event_test(p->event))
    {
        const union cd_prv_getstat g = {.status = CD_REG_RSP->rsp};

        p->available = !g.bits.shell_open;
    }

    return 0;
}

static void send_params(const struct cd_prv *const p)
{
    static const size_t n[] =
    {
        [CMD_SETLOC] = sizeof (struct cmd_setloc)
    };

    const uint8_t cmd = p->cmd.cmd, index = CD_REG_STATUS->index;

    if (cmd < sizeof n / sizeof *n)
    {
        const void *const vbuf = &p->cmd.params;
        const uint8_t *buf = vbuf;

        CD_REG_STATUS->index = 0;

        for (size_t i = 0; i < n[cmd]; i++)
            CD_REG_PARAM->param = *buf++;

        CD_REG_STATUS->index = index;
    }
}

static int send_data(void)
{
    struct cd_prv *const p = &drv_ps1_cd_prv;

    if (CD_REG_STATUS->BUSYSTS)
        return 0;

    send_params(p);
    CD_REG_CMD->cmd = p->cmd.cmd;
    p->next = wait_event;
    return 0;
}

static int reset_param_fifo(void)
{
    struct cd_prv *const p = &drv_ps1_cd_prv;

    if (CD_REG_STATUS->BUSYSTS)
        return 0;

    CD_REG_IF->w.ack = 0x1f;
    CD_REG_STATUS->index = 1;
    CD_REG_IF->w.CLRPRM = 1;
    p->next = send_data;
    return 0;
}

int drv_ps1_cd_send(const struct cmd *const cmd)
{
    struct cd_prv *const p = &drv_ps1_cd_prv;

    if (drv_ps1_cd_ensure_event(p))
        return -1;

    p->cmd = *cmd;
    p->next = reset_param_fifo;
    return 0;
}
