#include <sfx.h>
#include <sfx/port.h>
#include <psxspu.h>
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

void sfx_free(struct sound *const s)
{
}

void sfx_deinit(void)
{
}

static int vag_ntohs(int n)
{
    return (n & 0xFF) << 24
        | (n & 0xFF00) << 16
        | (n & 0xFF0000) >> 8
        | (n & 0xFF000000) >> 24;
}

static const char magic[] = "VAGp";

struct vag_header
{
    char magic_bytes[sizeof magic - 1];
    uint32_t version;
    uint32_t :32;
    uint32_t size;
    uint32_t sample_rate;
    uint8_t reserved[12];
    char name[16];
};

static int get_header(struct vag_header *const h, FILE *const f)
{
    int ret = -1;

    if (!fread(h, sizeof *h, 1, f))
    {
        fprintf(stderr, "VAG header not found\n");
        goto end;
    }
    else if (strncmp(h->magic_bytes, magic, sizeof h->magic_bytes))
    {
        fprintf(stderr, "Expected magic bytes \"%s\", found: ", magic);
        enum {N = sizeof h->magic_bytes / sizeof *h->magic_bytes};

        for (size_t i = 0; i < N; i++)
        {
            const char c = h->magic_bytes[i];

            fprintf(stderr, "[%zu] = %#x", i, c);

            if ((i + 1) < N)
                fprintf(stderr, ", ");

            if (isalnum(c))
                fprintf(stderr, "(%c)", c);
        }

        putchar('\n');
        goto end;
    }

    /* File data is stored as big endian for some reason. */
    h->version = vag_ntohs(h->version);
    h->sample_rate = vag_ntohs(h->sample_rate);
    h->size = vag_ntohs(h->size);
    ret = 0;

end:
    return ret;
}

static sfx_spu_addr cur_addr = SPU_DATA_BASE_ADDR;

static int upload_vag(const struct vag_header *const h,
    struct sound *const s, FILE *const f)
{
    enum {SPU_RAM = 512 << 10};
    const sfx_spu_addr end_addr = cur_addr + h->size;

    if (end_addr >= SPU_RAM)
    {
        fprintf(stderr, "maximum SPU address exceeded (%#x, max %#x)\n",
            end_addr, SPU_RAM);
        return -1;
    }

    s->addr = cur_addr;
    s->sample_rate = h->sample_rate;

    for (size_t i = 0; i < h->size; /* See loop body. */)
    {
        /* From https://problemkaputt.de/psx-spx.htm#soundprocessingunitspu :
         * "Sound RAM Data Transfer Fifo" supports up to 32 samples. */
        uint16_t hwords[32];
        const size_t rem = h->size - i;
        const size_t n = rem > sizeof hwords ? sizeof hwords : rem;

        if (!fread(&hwords, n, 1, f))
        {
            fprintf(stderr, "%s: failed to read %zu bytes\n", __func__, n);
            return -1;
        }

        SsUpload(&hwords, n, cur_addr);
        cur_addr += n;
        i += n;
    }

#if 0
    const unsigned used = cur_addr - SPU_DATA_BASE_ADDR;
    printf("SPU RAM: %u bytes (%u%%)\n", used, (used * 100) / (SPU_RAM - SPU_DATA_BASE_ADDR));
#endif
    return 0;
}

typedef volatile struct
{
    uint16_t l_vol, r_vol;
    uint16_t adpcm_sample_rate;
    uint16_t adpcm_start_addr;

    union
    {
        struct
        {
            uint32_t sustain_lvl :4;
            uint32_t decay_shift :4;
            uint32_t attack_step :2;
            uint32_t attack_shift :5;
            uint32_t attack_mode :1;
            uint32_t release_shift :5;
            uint32_t release_mode :1;
            uint32_t sustain_step :2;
            uint32_t sustain_shift :5;
            uint32_t :1;
            uint32_t sustain_dir :1;
            uint32_t sustain_mode :1;
        } bit;

        uint32_t mask;
    } adsr;

    uint16_t adsr_vol;
    uint16_t adsr_repeat_addr;
} *spu_voice;

#define SPU_VOICE ((spu_voice)0x1f801c00)

int sfx_play(const struct sound *const s)
{
    const sfx_spu_voice voice = s->voice;

    SPU_VOICE[voice].adpcm_sample_rate = SsFreqToPitch(s->sample_rate);
    SPU_VOICE[voice].adpcm_start_addr = s->addr >> 3;
    SPU_VOICE[voice].l_vol = 0x3FFF;
    SPU_VOICE[voice].r_vol = 0x3FFF;

    if (voice > sizeof SPU_KEY_ON1 * 8)
        SPU_KEY_ON2 = 1 << (voice - sizeof SPU_KEY_ON1 * 8);
    else
        SPU_KEY_ON1 = 1 << voice;

    return 0;
}

int sfx_sound_from_fp(struct sound *const s, FILE *const f, const size_t sz)
{
    struct vag_header h;

    if (get_header(&h, f) || upload_vag(&h, s, f))
        return -1;

    return 0;
}

int sfx_init(void)
{
    SsInit();
    return 0;
}
