/*
 * Copyright (c) 2021-2022 Rupert Carmichael
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <stddef.h>
#include <stdint.h>

#include "cega_serial.h"
#include "emu2413.h"
#include "ym2413.h"

#define SIZE_YMBUF 1000

static OPLL *opll;

static int16_t ymbuf[SIZE_YMBUF]; // Buffer for raw output samples
static size_t bufpos = 0; // Keep track of the position in the output buffer

// Grab the pointer to the YM2413's buffer
int16_t* ym2413_get_buffer(void) {
    bufpos = 0;
    return &ymbuf[0];
}

void ym2413_init(void) {
    opll = OPLL_new();
    OPLL_setChipType(opll, 0);
    OPLL_reset(opll);
}

void ym2413_deinit(void) {
    OPLL_delete(opll);
}

void ym2413_disable(void) {
    bufpos = 0;
}

void ym2413_reset(void) {
    OPLL_reset(opll);
}

void ym2413_wr(uint8_t port, uint8_t data) {
    OPLL_writeIO(opll, port & 0x01, data);
}

size_t ym2413_exec(void) {
    // Clock the YM2413
    ymbuf[bufpos++] = OPLL_calc(opll) << 1;

    return 1;
}

void ym2413_state_load(uint8_t *st) {
    opll->adr = cega_serial_pop32(st);

    for (int i = 0; i < 0x40; i++)
        opll->reg[i] = cega_serial_pop8(st);

    opll->test_flag = cega_serial_pop8(st);
    opll->slot_key_status = cega_serial_pop32(st);
    opll->rhythm_mode = cega_serial_pop8(st);
    opll->eg_counter = cega_serial_pop32(st);
    opll->pm_phase = cega_serial_pop32(st);
    opll->am_phase = cega_serial_pop32(st);
    opll->lfo_am = cega_serial_pop8(st);
    opll->noise = cega_serial_pop32(st);
    opll->short_noise = cega_serial_pop8(st);

    for (int i = 0; i < 9; i++)
        opll->patch_number[i] = cega_serial_pop32(st);

    opll->mask = cega_serial_pop32(st);

    for (int i = 0; i < 14; i++)
        opll->ch_out[i] = cega_serial_pop16(st);

    for (int i = 0; i < 2; i++)
        opll->mix_out[i] = cega_serial_pop16(st);

    for (int i = 0; i < 18; i++) {
        opll->slot[i].number = cega_serial_pop8(st);
        opll->slot[i].type = cega_serial_pop8(st);
        opll->slot[i].output[0] = cega_serial_pop32(st);
        opll->slot[i].output[1] = cega_serial_pop32(st);
        opll->slot[i].pg_phase = cega_serial_pop32(st);
        opll->slot[i].pg_out = cega_serial_pop32(st);
        opll->slot[i].pg_keep = cega_serial_pop8(st);
        opll->slot[i].blk_fnum = cega_serial_pop16(st);
        opll->slot[i].fnum = cega_serial_pop16(st);
        opll->slot[i].blk = cega_serial_pop8(st);
        opll->slot[i].eg_state = cega_serial_pop8(st);
        opll->slot[i].volume = cega_serial_pop32(st);
        opll->slot[i].key_flag = cega_serial_pop8(st);
        opll->slot[i].sus_flag = cega_serial_pop8(st);
        opll->slot[i].tll = cega_serial_pop16(st);
        opll->slot[i].rks = cega_serial_pop8(st);
        opll->slot[i].eg_rate_h = cega_serial_pop8(st);
        opll->slot[i].eg_rate_l = cega_serial_pop8(st);
        opll->slot[i].eg_shift = cega_serial_pop32(st);
        opll->slot[i].eg_out = cega_serial_pop32(st);
        opll->slot[i].update_requests = cega_serial_pop32(st);
    }

    for (int i = 0; i < (19*2); i++) {
        opll->patch[i].TL = cega_serial_pop32(st);
        opll->patch[i].FB = cega_serial_pop32(st);
        opll->patch[i].EG = cega_serial_pop32(st);
        opll->patch[i].ML = cega_serial_pop32(st);
        opll->patch[i].AR = cega_serial_pop32(st);
        opll->patch[i].DR = cega_serial_pop32(st);
        opll->patch[i].SL = cega_serial_pop32(st);
        opll->patch[i].RR = cega_serial_pop32(st);
        opll->patch[i].KR = cega_serial_pop32(st);
        opll->patch[i].KL = cega_serial_pop32(st);
        opll->patch[i].AM = cega_serial_pop32(st);
        opll->patch[i].PM = cega_serial_pop32(st);
        opll->patch[i].WS = cega_serial_pop32(st);
    }

    OPLL_forceRefresh(opll);
}

void ym2413_state_save(uint8_t *st) {
    cega_serial_push32(st, opll->adr);

    for (int i = 0; i < 0x40; i++)
        cega_serial_push8(st, opll->reg[i]);

    cega_serial_push8(st, opll->test_flag);
    cega_serial_push32(st, opll->slot_key_status);
    cega_serial_push8(st, opll->rhythm_mode);
    cega_serial_push32(st, opll->eg_counter);
    cega_serial_push32(st, opll->pm_phase);
    cega_serial_push32(st, opll->am_phase);
    cega_serial_push8(st, opll->lfo_am);
    cega_serial_push32(st, opll->noise);
    cega_serial_push8(st, opll->short_noise);

    for (int i = 0; i < 9; i++)
        cega_serial_push32(st, opll->patch_number[i]);

    cega_serial_push32(st, opll->mask);

    for (int i = 0; i < 14; i++)
        cega_serial_push16(st, opll->ch_out[i]);

    for (int i = 0; i < 2; i++)
        cega_serial_push16(st, opll->mix_out[i]);

    for (int i = 0; i < 18; i++) {
        cega_serial_push8(st, opll->slot[i].number);
        cega_serial_push8(st, opll->slot[i].type);
        cega_serial_push32(st, opll->slot[i].output[0]);
        cega_serial_push32(st, opll->slot[i].output[1]);
        cega_serial_push32(st, opll->slot[i].pg_phase);
        cega_serial_push32(st, opll->slot[i].pg_out);
        cega_serial_push8(st, opll->slot[i].pg_keep);
        cega_serial_push16(st, opll->slot[i].blk_fnum);
        cega_serial_push16(st, opll->slot[i].fnum);
        cega_serial_push8(st, opll->slot[i].blk);
        cega_serial_push8(st, opll->slot[i].eg_state);
        cega_serial_push32(st, opll->slot[i].volume);
        cega_serial_push8(st, opll->slot[i].key_flag);
        cega_serial_push8(st, opll->slot[i].sus_flag);
        cega_serial_push16(st, opll->slot[i].tll);
        cega_serial_push8(st, opll->slot[i].rks);
        cega_serial_push8(st, opll->slot[i].eg_rate_h);
        cega_serial_push8(st, opll->slot[i].eg_rate_l);
        cega_serial_push32(st, opll->slot[i].eg_shift);
        cega_serial_push32(st, opll->slot[i].eg_out);
        cega_serial_push32(st, opll->slot[i].update_requests);
    }

    for (int i = 0; i < (19*2); i++) {
        cega_serial_push32(st, opll->patch[i].TL);
        cega_serial_push32(st, opll->patch[i].FB);
        cega_serial_push32(st, opll->patch[i].EG);
        cega_serial_push32(st, opll->patch[i].ML);
        cega_serial_push32(st, opll->patch[i].AR);
        cega_serial_push32(st, opll->patch[i].DR);
        cega_serial_push32(st, opll->patch[i].SL);
        cega_serial_push32(st, opll->patch[i].RR);
        cega_serial_push32(st, opll->patch[i].KR);
        cega_serial_push32(st, opll->patch[i].KL);
        cega_serial_push32(st, opll->patch[i].AM);
        cega_serial_push32(st, opll->patch[i].PM);
        cega_serial_push32(st, opll->patch[i].WS);
    }
}
