/*
 * Copyright (c) 2021-2024 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 <stdlib.h>
#include <stdint.h>

#include "z80.h"

#include "cega.h"
#include "cega_serial.h"
#include "cega_z80.h"

static z80_t z80[NUMZ80];
static uint64_t delaycycs[NUMZ80];
static uint64_t pins[NUMZ80];
static uint8_t intvec[NUMZ80];
static uint8_t busreq[NUMZ80];
static uint8_t busack[NUMZ80];
static uint8_t busack_new[NUMZ80];
static uint8_t busack_timer[NUMZ80];
static uint8_t reset[NUMZ80];

// Function Pointers for Memory and IO Operations
uint8_t (*cega_z80_port_rd[NUMZ80])(uint8_t);
void (*cega_z80_port_wr[NUMZ80])(uint8_t, uint8_t);
uint8_t (*cega_z80_mem_rd[NUMZ80])(uint16_t);
void (*cega_z80_mem_wr[NUMZ80])(uint16_t, uint8_t);

// Initialize the Z80
void cega_z80_init(unsigned zid) {
    pins[zid] = z80_init(&z80[zid]);

    delaycycs[zid] = 0;
    intvec[zid] = 0;
    busack_new[zid] = 0;
    busack_timer[zid] = 0;

    // On the Mega Drive, the BUSREQ and RESET lines are asserted on boot
    if (cega_get_system() == SYSTEM_MD) {
        busack[zid] = 1;
        busreq[zid] = 1;
        reset[zid] = 1;
    }
    else {
        busack[zid] = 0;
        busreq[zid] = 0;
        reset[zid] = 0;
    }

    // SMS Startup Values
    z80[zid].sp = 0xdff0;
    z80[zid].ir = 0x0009;
    z80[zid].af = 0x0040;
    z80[zid].bc = 0x0000;
    z80[zid].de = 0x0000;
    z80[zid].hl = 0x0000;
    z80[zid].ix = 0x0000;
    z80[zid].iy = 0x0000;
    z80[zid].af2 = 0x0000;
    z80[zid].bc2 = 0x0000;
    z80[zid].de2 = 0x0000;
    z80[zid].hl2 = 0x0000;
}

// Assert the IRQ Line
void cega_z80_irq(unsigned zid, uint8_t data) {
    pins[zid] |= Z80_INT;
    intvec[zid] = data;
}

// Clear the IRQ Line
void cega_z80_irq_clr(unsigned zid) {
    pins[zid] &= ~Z80_INT;
}

// Read the value of the BUSACK Line
uint8_t cega_z80_busack(unsigned zid) {
    return busack[zid];
}

// Set the BUSREQ Line
void cega_z80_busreq(unsigned zid, uint8_t line) {
    busreq[zid] = line;
    busack_timer[zid] = 2;

    if (busreq[zid] && !reset[zid])
        busack_new[zid] = 1;
    else
        busack_new[zid] = 0;
}

// Assert the NMI Line
void cega_z80_nmi(unsigned zid) {
    pins[zid] |= Z80_NMI;
}

// Clear the NMI Line
void cega_z80_nmi_clr(unsigned zid) {
    pins[zid] &= ~Z80_NMI;
}

// Issue a Reset
void cega_z80_reset(unsigned zid) {
    pins[zid] = z80_prefetch(&z80[zid], 0x0000);
    reset[zid] = 0;
    z80[zid].im = 0;
    z80[zid].ir = 0;
    z80[zid].iff1 = 0;
    z80[zid].iff2 = 0;
}

// Read the value of the RESET Line
uint8_t cega_z80_reset_stat(unsigned zid) {
    return reset[zid];
}

// Assert or clear the RESET Line
void cega_z80_reset_line(unsigned zid, uint8_t data) {
    /* If the line is already in the desired state, go no further. If the line
       is being asserted or deasserted, change the status.
    */
    if (reset[zid] == data)
        return;
    else
        reset[zid] = data;

    /* Perform the reset if the line was freshly asserted -- When the line is
       no longer asserted, the Z80 will begin executing opcodes at 0x0000.
    */
    if (data)
        cega_z80_reset(zid);
}

// Delay by n cycles
void cega_z80_delay(unsigned zid, uint64_t n) {
    delaycycs[zid] += n;
}

void cega_z80_exec(unsigned zid) {
    if (delaycycs[zid]) {
        --delaycycs[zid];
        return;
    }

    if (busack_timer[zid]) {
        --busack_timer[zid];
        if (!busack_timer[zid])
            busack[zid] = busack_new[zid];
    }

    if (busack[zid] || reset[zid])
        return;

    pins[zid] = z80_tick(&z80[zid], pins[zid]);
    if (pins[zid] & Z80_MREQ) {
        const uint16_t addr = Z80_GET_ADDR(pins[zid]);
        if (pins[zid] & Z80_RD) {
            uint8_t data = cega_z80_mem_rd[zid](addr);
            Z80_SET_DATA(pins[zid], data);
        }
        else if (pins[zid] & Z80_WR) {
            uint8_t data = Z80_GET_DATA(pins[zid]);
            cega_z80_mem_wr[zid](addr, data);
        }
    }
    else if (pins[zid] & Z80_IORQ) {
        const uint16_t port = Z80_GET_ADDR(pins[zid]);
        if (pins[zid] & Z80_M1) {
            Z80_SET_DATA(pins[zid], intvec[zid]);
        }
        if (pins[zid] & Z80_RD) {
            uint8_t data = cega_z80_port_rd[zid](port & 0xff);
            Z80_SET_DATA(pins[zid], data);
        }
        else if (pins[zid] & Z80_WR) {
            uint8_t data = Z80_GET_DATA(pins[zid]);
            cega_z80_port_wr[zid](port & 0xff, data);
        }
    }
}

void cega_z80_state_load(unsigned zid, uint8_t *st) {
    z80[zid].step = cega_serial_pop16(st);
    z80[zid].addr = cega_serial_pop16(st);
    z80[zid].dlatch = cega_serial_pop8(st);
    z80[zid].opcode = cega_serial_pop8(st);
    z80[zid].hlx_idx = cega_serial_pop8(st);
    z80[zid].prefix_active = cega_serial_pop8(st);
    z80[zid].pins = cega_serial_pop64(st);
    z80[zid].int_bits = cega_serial_pop64(st);
    z80[zid].pc = cega_serial_pop16(st);
    z80[zid].af = cega_serial_pop16(st);
    z80[zid].bc = cega_serial_pop16(st);
    z80[zid].de = cega_serial_pop16(st);
    z80[zid].hl = cega_serial_pop16(st);
    z80[zid].ix = cega_serial_pop16(st);
    z80[zid].iy = cega_serial_pop16(st);
    z80[zid].wz = cega_serial_pop16(st);
    z80[zid].sp = cega_serial_pop16(st);
    z80[zid].ir = cega_serial_pop16(st);
    z80[zid].af2 = cega_serial_pop16(st);
    z80[zid].bc2 = cega_serial_pop16(st);
    z80[zid].de2 = cega_serial_pop16(st);
    z80[zid].hl2 = cega_serial_pop16(st);
    z80[zid].im = cega_serial_pop8(st);
    z80[zid].iff1 = cega_serial_pop8(st);
    z80[zid].iff2 = cega_serial_pop8(st);
    delaycycs[zid] = cega_serial_pop64(st);
    pins[zid] = cega_serial_pop64(st);
    intvec[zid] = cega_serial_pop8(st);
    busack[zid] = cega_serial_pop8(st);
    busack_new[zid] = cega_serial_pop8(st);
    busack_timer[zid] = cega_serial_pop8(st);
    busreq[zid] = cega_serial_pop8(st);
    reset[zid] = cega_serial_pop8(st);
}

void cega_z80_state_save(unsigned zid, uint8_t *st) {
    cega_serial_push16(st, z80[zid].step);
    cega_serial_push16(st, z80[zid].addr);
    cega_serial_push8(st, z80[zid].dlatch);
    cega_serial_push8(st, z80[zid].opcode);
    cega_serial_push8(st, z80[zid].hlx_idx);
    cega_serial_push8(st, z80[zid].prefix_active);
    cega_serial_push64(st, z80[zid].pins);
    cega_serial_push64(st, z80[zid].int_bits);
    cega_serial_push16(st, z80[zid].pc);
    cega_serial_push16(st, z80[zid].af);
    cega_serial_push16(st, z80[zid].bc);
    cega_serial_push16(st, z80[zid].de);
    cega_serial_push16(st, z80[zid].hl);
    cega_serial_push16(st, z80[zid].ix);
    cega_serial_push16(st, z80[zid].iy);
    cega_serial_push16(st, z80[zid].wz);
    cega_serial_push16(st, z80[zid].sp);
    cega_serial_push16(st, z80[zid].ir);
    cega_serial_push16(st, z80[zid].af2);
    cega_serial_push16(st, z80[zid].bc2);
    cega_serial_push16(st, z80[zid].de2);
    cega_serial_push16(st, z80[zid].hl2);
    cega_serial_push8(st, z80[zid].im);
    cega_serial_push8(st, z80[zid].iff1);
    cega_serial_push8(st, z80[zid].iff2);
    cega_serial_push64(st, delaycycs[zid]);
    cega_serial_push64(st, pins[zid]);
    cega_serial_push8(st, intvec[zid]);
    cega_serial_push8(st, busack[zid]);
    cega_serial_push8(st, busack_new[zid]);
    cega_serial_push8(st, busack_timer[zid]);
    cega_serial_push8(st, busreq[zid]);
    cega_serial_push8(st, reset[zid]);
}
