/*
MIT License

Copyright (c) 2002 Valavan Manohararajah
Copyright (c) 2020-2024 Rupert Carmichael

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include "e6809.h"
#include "vecx.h"
#include "vecx_mixer.h"
#include "vecx_render.h"
#include "vecx_serial.h"

#define DIV_PSG 8 // PSG Clock Divider
#define VECX_RAM_SIZE 1024
#define VECX_STATE_SIZE 1232

static ay38910_t psg; // PSG Context

// Pointers to BIOS and Cartridge ROM
static uint8_t *bios = NULL;
static uint8_t *cart = NULL;

static size_t cartsize = 0;
static size_t psgcycs = 0;
static int32_t leftover = 0;

// State Buffer
static uint8_t vstate[VECX_STATE_SIZE];

// System RAM
static uint8_t ram[VECX_RAM_SIZE];

// the via 6522 registers
static uint8_t via_ora;
static uint8_t via_orb;
static uint8_t via_ddra;
static uint8_t via_ddrb;
static uint8_t via_t1on;   /* is timer 1 on? */
static uint8_t via_t1int;  /* are timer 1 interrupts allowed? */
static uint16_t via_t1c;
static uint8_t via_t1ll;
static uint8_t via_t1lh;
static uint16_t via_t1pb7; /* timer 1 controlled version of pb7 */
static uint8_t via_t2on;   /* is timer 2 on? */
static uint8_t via_t2int;  /* are timer 2 interrupts allowed? */
static uint16_t via_t2c;
static uint8_t via_t2ll;
static uint8_t via_sr;
static uint16_t via_srb;   /* number of bits shifted so far */
static uint16_t via_src;   /* shift counter */
static uint16_t via_srclk;
static uint16_t via_acr;
static uint16_t via_pcr;
static uint16_t via_ifr;
static uint16_t via_ier;
static uint16_t via_ca2;
static uint16_t via_cb2h;  /* basic handshake version of cb2 */
static uint16_t via_cb2s;  /* version of cb2 controlled by the shift register */

// analog devices
static uint16_t alg_rsh;  /* zero ref sample and hold */
static uint16_t alg_xsh;  /* x sample and hold */
static uint16_t alg_ysh;  /* y sample and hold */
static uint16_t alg_zsh;  /* z sample and hold */
uint16_t alg_jch0;        /* joystick direction channel 0 */
uint16_t alg_jch1;        /* joystick direction channel 1 */
uint16_t alg_jch2;        /* joystick direction channel 2 */
uint16_t alg_jch3;        /* joystick direction channel 3 */
static uint16_t alg_jsh;  /* joystick sample and hold */
static uint16_t alg_compare;

static int32_t alg_dx;     /* delta x */
static int32_t alg_dy;     /* delta y */
static int32_t alg_curr_x; /* current x position */
static int32_t alg_curr_y; /* current y position */

static uint16_t alg_vectoring; /* are we drawing a vector right now? */
static int32_t alg_vector_x0;
static int32_t alg_vector_y0;
static int32_t alg_vector_x1;
static int32_t alg_vector_y1;
static int32_t alg_vector_dx;
static int32_t alg_vector_dy;
static uint8_t alg_vector_color;

uint8_t buttons = 0xff;

int32_t vector_draw_cnt;
int32_t vector_erse_cnt;
static vector_t vectors_set[2 * VECTOR_CNT];
vector_t *vectors_draw;
vector_t *vectors_erse;

static int32_t vector_hash[VECTOR_HASH];

static int32_t fcycles;

static int dacset = 0;
static int bios_internal = 0;

// Load the Vectrex BIOS from a file
int vecx_bios_load_file(const char *biospath) {
    FILE *file;
    int32_t size;

    if (!(file = fopen(biospath, "rb")))
        return 0;

    // Find out the file's size
    fseek(file, 0, SEEK_END);
    size = ftell(file);
    fseek(file, 0, SEEK_SET);

    // Make sure it is the correct size before attempting to load it
    if (size != SIZE_BIOS) {
        fclose(file);
        return 0;
    }

    // Allocate memory for the BIOS
    bios = (uint8_t*)calloc(SIZE_BIOS, sizeof(uint8_t));

    if (!fread(bios, SIZE_BIOS, 1, file))
        return 0;

    fclose(file);

    bios_internal = 1;
    return 1;
}

// Load the Vectrex BIOS from a memory buffer
int vecx_bios_load(void *data, size_t size) {
    if (size) { }
    bios = (uint8_t*)data;
    return 1;
}

// Load a Vectrex ROM Image
int vecx_rom_load(void *data, size_t size) {
    cart = (uint8_t*)data;
    cartsize = size;
    return 1;
}

// Update the snd chips internal registers when via_ora/via_orb changes
static inline void snd_update(void) {
    /* VIA ORB/IRB PB3 is connected to PSG's BC1 pin, PB4 is connected to BDIR.
       We assume BC2 on the PSG is high (1).
    */
    switch (via_orb & 0x18) {
        case 0x00: { // BDIR(0) BC2(1) BC1(0) - INACTIVE
            break;
        }
        case 0x08: { // BDIR(0) BC2(1) BC1(1) - DTB (Read from PSG)
            // Reads are performed elsewhere
            break;
        }
        case 0x10: { // BDIR(1) BC2(1) BC1(0) - DWS (Write to PSG)
            ay38910_wr(&psg, via_ora);
            break;
        }
        case 0x18: { // BDIR(1) BC2(1) BC1(1) - INTAK (Latch Address)
            if ((via_ora & 0xf0) == 0x00)
                ay38910_set_reg(&psg, via_ora & 0x0f);
            break;
        }
        default: {
            break;
        }
    }
}

// Update the various analog values when orb is written.
static inline void alg_update(void) {
    switch (via_orb & 0x06) {
        case 0x00: {
            alg_jsh = alg_jch0;
            if ((via_orb & 0x01) == 0x00) // demultiplexer is on
                alg_ysh = alg_xsh;
            break;
        }
        case 0x02: {
            alg_jsh = alg_jch1;
            if ((via_orb & 0x01) == 0x00) // demultiplexer is on
            alg_rsh = alg_xsh;
            break;
        }
        case 0x04: {
            alg_jsh = alg_jch2;
            if ((via_orb & 0x01) == 0x00) { // demultiplexer is on
                if (alg_xsh > 0x80)
                    alg_zsh = alg_xsh - 0x80;
                else
                    alg_zsh = 0;
            }
            break;
        }
        case 0x06: { // sound output line
            alg_jsh = alg_jch3;
            break;
        }
    }

    if ((via_orb & 0x07) == 0x06)
        dacset = 1;

    // compare the current joystick direction with a reference
    if (alg_jsh > alg_xsh)
        alg_compare = 0x20;
    else
        alg_compare = 0;

    // compute the new "deltas"
    alg_dx = (int32_t) alg_xsh - (int32_t) alg_rsh;
    alg_dy = (int32_t) alg_rsh - (int32_t) alg_ysh;
}

// Update IRQ and bit-7 of the ifr register after making an adjustment to ifr
static inline void int_update (void) {
    if ((via_ifr & 0x7f) & (via_ier & 0x7f))
        via_ifr |= 0x80;
    else
        via_ifr &= 0x7f;
}

static uint8_t read8(uint16_t address) {
    if ((address & 0xe000) == 0xe000) { // rom
        return bios[address & 0x1fff];
    }
    else if ((address & 0xe000) == 0xc000) {
        if (address & 0x800) { // ram
            return ram[address & 0x3ff];
        }
        else if (address & 0x1000) { // io
            switch (address & 0xf) {
                case 0x0: {
                    // compare signal is an input so the value does not come
                    // from via_orb.
                    if (via_acr & 0x80) // timer 1 has control of bit 7
                        return (uint8_t)((via_orb & 0x5f) |
                            via_t1pb7 | alg_compare);
                    else // bit 7 is being driven by via_orb
                        return (uint8_t)((via_orb & 0xdf) | alg_compare);
                }
                case 0x1: { // register 1 also performs handshakes if necessary
                    // if ca2 is in pulse mode or handshake mode, then it
                    // goes low whenever ira is read.
                    if ((via_pcr & 0x0e) == 0x08)
                        via_ca2 = 0;
                }
                // fallthrough
                case 0xf: { // the snd chip is driving port a
                    if ((via_orb & 0x18) == 0x08) { // 1,1,0 = DTB (Read PSG)
                        psg.reg[14] = buttons;
                        return ay38910_rd(&psg);
                    }
                    else {
                        return via_ora;
                    }
                    break;
                }
                case 0x2: {
                    return via_ddrb;
                }
                case 0x3: {
                    return via_ddra;
                }
                case 0x4: { // T1 low order counter
                    via_ifr &= 0xbf; // remove timer 1 interrupt flag
                    via_t1on = 0; // timer 1 is stopped
                    via_t1int = 0;
                    via_t1pb7 = 0x80;
                    int_update();
                    return (uint8_t)via_t1c;
                }
                case 0x5: { // T1 high order counter
                    return (uint8_t)(via_t1c >> 8);
                }
                case 0x6: { // T1 low order latch
                    return via_t1ll;
                }
                case 0x7: { // T1 high order latch
                    return via_t1lh;
                }
                case 0x8: {
                    // T2 low order counter
                    via_ifr &= 0xdf; // remove timer 2 interrupt flag
                    via_t2on = 0; // timer 2 is stopped
                    via_t2int = 0;
                    int_update();
                    return (uint8_t)via_t2c;
                }
                case 0x9: { // T2 high order counter
                    return (uint8_t)(via_t2c >> 8);
                }
                case 0xa: { // remove shift register interrupt flag
                    via_ifr &= 0xfb;
                    via_srb = 0;
                    via_srclk = 1;
                    int_update();
                    return (uint8_t)via_sr;
                }
                case 0xb: {
                    return (uint8_t) via_acr;
                }
                case 0xc: {
                    return (uint8_t) via_pcr;
                }
                case 0xd: { // interrupt flag register
                    return (uint8_t) via_ifr;
                }
                case 0xe: { // interrupt enable register
                    return (uint8_t)(via_ier | 0x80);
                }
                default: {
                    return 0xff;
                }
            }
        }
    }
    else if (address < 0x8000) { // cartridge
        return cart[address];
    }

    return 0xff;
}

static void write8(uint16_t address, uint8_t data) {
    if ((address & 0xe000) == 0xe000) {
        // rom
    }
    else if ((address & 0xe000) == 0xc000) {
        // it is possible for both ram and io to be written at the same!
        if (address & 0x800) {
            ram[address & 0x3ff] = data;
        }

        if (address & 0x1000) {
            switch (address & 0xf) {
                case 0x0: {
                    via_orb = data;
                    snd_update();
                    alg_update();

                    // if cb2 is in pulse mode or handshake mode, then it
                    // goes low whenever orb is written.
                    if ((via_pcr & 0xe0) == 0x80)
                        via_cb2h = 0;
                    break;
                }
                case 0x1: {
                    // register 1 also performs handshakes if necessary
                    if ((via_pcr & 0x0e) == 0x08) {
                        /* if ca2 is in pulse mode or handshake mode, then it
                         * goes low whenever ora is written.
                         */
                        via_ca2 = 0;
                    }
                }
                // fallthrough
                case 0xf: {
                    via_ora = data;
                    snd_update();
                    /* output of port a feeds directly into the dac which then
                     * feeds the x axis sample and hold.
                     */
                    alg_xsh = data ^ 0x80;
                    alg_update();
                    break;
                }
                case 0x2: {
                    via_ddrb = data;
                    break;
                }
                case 0x3: {
                    via_ddra = data;
                    break;
                }
                case 0x4: {
                    // T1 low order counter
                    via_t1ll = data;
                    break;
                }
                case 0x5: {
                    // T1 high order counter
                    via_t1lh = data;
                    via_t1c = (via_t1lh << 8) | via_t1ll;
                    via_ifr &= 0xbf; // remove timer 1 interrupt flag
                    via_t1on = 1; // timer 1 starts running
                    via_t1int = 1;
                    via_t1pb7 = 0;
                    int_update();
                    break;
                }
                case 0x6: {
                    // T1 low order latch
                    via_t1ll = data;
                    break;
                }
                case 0x7: {
                    // T1 high order latch
                    via_t1lh = data;
                    break;
                }
                case 0x8: {
                    // T2 low order latch
                    via_t2ll = data;
                    break;
                }
                case 0x9: {
                    // T2 high order latch/counter
                    via_t2c = (data << 8) | via_t2ll;
                    via_ifr &= 0xdf;
                    via_t2on = 1; // timer 2 starts running
                    via_t2int = 1;
                    int_update ();
                    break;
                }
                case 0xa: {
                    via_sr = data;
                    via_ifr &= 0xfb; // remove shift register interrupt flag
                    via_srb = 0;
                    via_srclk = 1;
                    int_update ();
                    break;
                }
                case 0xb: {
                    via_acr = data;
                    break;
                }
                case 0xc: {
                    via_pcr = data;
                    if ((via_pcr & 0x0e) == 0x0c) // ca2 is outputting low
                        via_ca2 = 0;
                    else // ca2 disabled, in pulse mode or outputting high
                        via_ca2 = 1;

                    if ((via_pcr & 0xe0) == 0xc0) // cb2 is outputting low
                        via_cb2h = 0;
                    else // cb2 disabled, in pulse mode, or outputting high
                        via_cb2h = 1;

                    break;
                }
                case 0xd: { // interrupt flag register
                    via_ifr &= ~(data & 0x7f);
                    int_update ();
                    break;
                }
                case 0xe: { // interrupt enable register
                    if (data & 0x80)
                        via_ier |= data & 0x7f;
                    else
                        via_ier &= ~(data & 0x7f);

                    int_update();
                    break;
                }
                default: {
                    break;
                }
            }
        }
    }
    else if (address < 0x8000) {
        // cartridge
    }
}

void vecx_reset(void) {
    // ram
    for (uint16_t r = 0; r < 1024; r++)
        ram[r] = r & 0xff;

    via_ora = 0;
    via_orb = 0;
    via_ddra = 0;
    via_ddrb = 0;
    via_t1on = 0;
    via_t1int = 0;
    via_t1c = 0;
    via_t1ll = 0;
    via_t1lh = 0;
    via_t1pb7 = 0x80;
    via_t2on = 0;
    via_t2int = 0;
    via_t2c = 0;
    via_t2ll = 0;
    via_sr = 0;
    via_srb = 8;
    via_src = 0;
    via_srclk = 0;
    via_acr = 0;
    via_pcr = 0;
    via_ifr = 0;
    via_ier = 0;
    via_ca2 = 1;
    via_cb2h = 1;
    via_cb2s = 0;

    alg_rsh = 128;
    alg_xsh = 128;
    alg_ysh = 128;
    alg_zsh = 0;
    alg_jch0 = 128;
    alg_jch1 = 128;
    alg_jch2 = 128;
    alg_jch3 = 128;
    alg_jsh = 128;

    alg_compare = 0; // check this - FIXME

    alg_dx = 0;
    alg_dy = 0;
    alg_curr_x = ALG_MAX_X / 2;
    alg_curr_y = ALG_MAX_Y / 2;

    alg_vectoring = 0;

    vector_draw_cnt = 0;
    vector_erse_cnt = 0;
    vectors_draw = vectors_set;
    vectors_erse = vectors_set + VECTOR_CNT;

    fcycles = FCYCLES_INIT;

    e6809_read8 = read8;
    e6809_write8 = write8;

    e6809_reset();

    ay38910_init(&psg);
    psg.bufpos = 0;
}

// Perform a single cycle worth of via emulation.
// via_sstep0 is the first postion of the emulation.
static inline void via_sstep0(void) {
    uint16_t t2shift;

    if (via_t1on) {
        via_t1c--;

        if ((via_t1c & 0xffff) == 0xffff) { // counter just rolled over
            if (via_acr & 0x40) { // continuous interrupt mode
                via_ifr |= 0x40;
                int_update ();
                via_t1pb7 = 0x80 - via_t1pb7;

                // reload counter
                via_t1c = (via_t1lh << 8) | via_t1ll;
            }
            else if (via_t1int) { // one shot mode
                via_ifr |= 0x40;
                int_update ();
                via_t1pb7 = 0x80;
                via_t1int = 0;
            }
        }
    }

    if (via_t2on && (via_acr & 0x20) == 0x00) {
        via_t2c--;

        if (((via_t2c & 0xffff) == 0xffff) && via_t2int) { // one shot mode
            via_ifr |= 0x20;
            int_update ();
            via_t2int = 0;
        }
    }

    // shift counter
    via_src--;

    if ((via_src & 0xff) == 0xff) {
        via_src = via_t2ll;

        if (via_srclk) {
            t2shift = 1;
            via_srclk = 0;
        }
        else {
            t2shift = 0;
            via_srclk = 1;
        }
    }
    else {
        t2shift = 0;
    }

    if (via_srb < 8) {
        switch (via_acr & 0x1c) {
            case 0x00: { // disabled
                break;
            }
            case 0x04: {
                // shift in under control of t2
                if (t2shift) {
                    // shifting in 0s since cb2 is always an output
                    via_sr <<= 1;
                    via_srb++;
                }
                break;
            }
            case 0x08: {
                // shift in under system clk control
                via_sr <<= 1;
                via_srb++;
                break;
            }
            case 0x0c: {
                // shift in under cb1 control - FIXME what is this here for?
                break;
            }
            case 0x10: {
                // shift out under t2 control (free run)
                if (t2shift) {
                    via_cb2s = (via_sr >> 7) & 1;
                    via_sr <<= 1;
                    via_sr |= via_cb2s;
                }
                break;
            }
            case 0x14: {
                // shift out under t2 control
                if (t2shift) {
                    via_cb2s = (via_sr >> 7) & 1;
                    via_sr <<= 1;
                    via_sr |= via_cb2s;
                    via_srb++;
                }
                break;
            }
            case 0x18: {
                // shift out under system clock control
                via_cb2s = (via_sr >> 7) & 1;
                via_sr <<= 1;
                via_sr |= via_cb2s;
                via_srb++;
                break;
            }
            case 0x1c: {
                // shift out under cb1 control - FIXME what is this here for?
                break;
            }
            default: {
                break;
            }
        }

        if (via_srb == 8) {
            via_ifr |= 0x04;
            int_update ();
        }
    }
}

// perform the second part of the via emulation
static inline void via_sstep1 (void) {
    // if ca2 is in pulse mode, make sure it gets restored to 1 after the pulse
    if ((via_pcr & 0x0e) == 0x0a)
        via_ca2 = 1;

    // if cb2 is in pulse mode, make sure it gets restored to 1 after the pulse
    if ((via_pcr & 0xe0) == 0xa0)
        via_cb2h = 1;
}

static void alg_addline(int32_t x0, int32_t y0, int32_t x1, int32_t y1,
    uint8_t color) {
    uint32_t key;
    int32_t index;

    key = (uint32_t)x0;
    key = key * 31 + (uint32_t)y0;
    key = key * 31 + (uint32_t)x1;
    key = key * 31 + (uint32_t)y1;
    key %= VECTOR_HASH;

    // first check if the line to be drawn is in the current draw list.
    // if it is, then it is not added again.
    index = vector_hash[key];

    if (index >= 0 && index < vector_draw_cnt &&
        x0 == vectors_draw[index].x0 &&
        y0 == vectors_draw[index].y0 &&
        x1 == vectors_draw[index].x1 &&
        y1 == vectors_draw[index].y1) {
        vectors_draw[index].color = color;
    }
    else {
        // missed on the draw list, now check if the line to be drawn is in
        // the erase list ... if it is, "invalidate" it on the erase list.
        if (index >= 0 && index < vector_erse_cnt &&
            x0 == vectors_erse[index].x0 &&
            y0 == vectors_erse[index].y0 &&
            x1 == vectors_erse[index].x1 &&
            y1 == vectors_erse[index].y1) {
            vectors_erse[index].color = VECTREX_COLORS;
        }

        vectors_draw[vector_draw_cnt].x0 = x0;
        vectors_draw[vector_draw_cnt].y0 = y0;
        vectors_draw[vector_draw_cnt].x1 = x1;
        vectors_draw[vector_draw_cnt].y1 = y1;
        vectors_draw[vector_draw_cnt].color = color;
        vector_hash[key] = vector_draw_cnt;
        vector_draw_cnt++;
    }
}

// perform a single cycle of analog emulation
static void alg_sstep(void) {
    int32_t sig_dx, sig_dy;
    uint16_t sig_ramp;
    uint16_t sig_blank;

    if ((via_acr & 0x10) == 0x10)
        sig_blank = via_cb2s;
    else
        sig_blank = via_cb2h;

    if (via_ca2 == 0) {
        // need to force the current point to the 'orgin' so just
        // calculate distance to origin and use that as dx,dy.
        sig_dx = ALG_MAX_X / 2 - alg_curr_x;
        sig_dy = ALG_MAX_Y / 2 - alg_curr_y;
    }
    else {
        if (via_acr & 0x80)
            sig_ramp = via_t1pb7;
        else
            sig_ramp = via_orb & 0x80;

        if (sig_ramp == 0) {
            sig_dx = alg_dx;
            sig_dy = alg_dy;
        }
        else {
            sig_dx = 0;
            sig_dy = 0;
        }
    }

    if (alg_vectoring == 0) {
        if (sig_blank == 1 &&
            alg_curr_x >= 0 && alg_curr_x < ALG_MAX_X &&
            alg_curr_y >= 0 && alg_curr_y < ALG_MAX_Y) {

            // start a new vector
            alg_vectoring = 1;
            alg_vector_x0 = alg_curr_x;
            alg_vector_y0 = alg_curr_y;
            alg_vector_x1 = alg_curr_x;
            alg_vector_y1 = alg_curr_y;
            alg_vector_dx = sig_dx;
            alg_vector_dy = sig_dy;
            alg_vector_color = (uint8_t)alg_zsh;
        }
    }
    else {
        // already drawing a vector ... check if we need to turn it off
        if (sig_blank == 0) {
            // blank just went on, vectoring turns off, and we've got a
            // new line.
            alg_vectoring = 0;

            alg_addline (alg_vector_x0, alg_vector_y0,
                         alg_vector_x1, alg_vector_y1,
                        alg_vector_color);
        }
        else if (sig_dx != alg_vector_dx ||
                   sig_dy != alg_vector_dy ||
                   (uint8_t) alg_zsh != alg_vector_color) {
            // the parameters of the vectoring processing has changed.
            // so end the current line.
            alg_addline (alg_vector_x0, alg_vector_y0,
                         alg_vector_x1, alg_vector_y1,
                         alg_vector_color);

            // we continue vectoring with a new set of parameters if the
            // current point is not out of limits.
            if (alg_curr_x >= 0 && alg_curr_x < ALG_MAX_X &&
                alg_curr_y >= 0 && alg_curr_y < ALG_MAX_Y) {
                alg_vector_x0 = alg_curr_x;
                alg_vector_y0 = alg_curr_y;
                alg_vector_x1 = alg_curr_x;
                alg_vector_y1 = alg_curr_y;
                alg_vector_dx = sig_dx;
                alg_vector_dy = sig_dy;
                alg_vector_color = (uint8_t) alg_zsh;
            }
            else {
                alg_vectoring = 0;
            }
        }
    }

    alg_curr_x += sig_dx;
    alg_curr_y += sig_dy;

    if (alg_vectoring == 1 &&
        alg_curr_x >= 0 && alg_curr_x < ALG_MAX_X &&
        alg_curr_y >= 0 && alg_curr_y < ALG_MAX_Y) {

        // we're vectoring ... current point is still within limits so
        // extend the current vector.
        alg_vector_x1 = alg_curr_x;
        alg_vector_y1 = alg_curr_y;
    }
}

void vecx_emu(int32_t cycles) {
    size_t c, icycles;
    cycles += leftover;

    while (cycles > 0) {
        icycles = e6809_sstep (via_ifr & 0x80, 0);

        for (c = 0; c < icycles; ++c) {
            if (++psgcycs % DIV_PSG == 0) {
                // Output DAC samples
                vecx_mixer_wr_dac(dacset ? (int16_t)(via_ora << 8) : 0);
                dacset = 0;

                // Output PSG samples
                ay38910_exec(&psg);
                psgcycs = 0;
            }

            via_sstep0();
            alg_sstep();
            via_sstep1();
        }

        cycles -= (int32_t)icycles;
        fcycles -= (int32_t)icycles;

        if (fcycles < 0) {
            vector_t *tmp;

            fcycles += FCYCLES_INIT;
            vecx_render();

            // everything that was drawn during this pass now enters the erase
            // list for the next pass.
            vector_erse_cnt = vector_draw_cnt;
            vector_draw_cnt = 0;

            tmp = vectors_erse;
            vectors_erse = vectors_draw;
            vectors_draw = tmp;
        }
    }

    leftover = cycles;

    // Resample audio and push to the frontend
    vecx_mixer_resamp(psg.bufpos);
}

void vecx_init(void) {
    ay38910_init(&psg);
    vecx_mixer_set_psg(&psg);
}

// Deinitialize any allocated memory
void vecx_deinit(void) {
    if (bios_internal)
        free(bios);
}

// Return the size of a state
size_t vecx_state_size(void) {
    return VECX_STATE_SIZE;
}

// Load raw state data into the running system
void vecx_state_load_raw(const void *sstate) {
    uint8_t *nstate = (uint8_t*)sstate;

    vecx_serial_begin();

    // RAM
    vecx_serial_popblk(ram, nstate, VECX_RAM_SIZE);

    // CPU
    cpust_t cpu_st = {0};

    cpu_st.reg_x = vecx_serial_pop16(nstate);
    cpu_st.reg_y = vecx_serial_pop16(nstate);
    cpu_st.reg_u = vecx_serial_pop16(nstate);
    cpu_st.reg_s = vecx_serial_pop16(nstate);
    cpu_st.reg_pc = vecx_serial_pop16(nstate);
    cpu_st.reg_a = vecx_serial_pop8(nstate);
    cpu_st.reg_b = vecx_serial_pop8(nstate);
    cpu_st.reg_dp = vecx_serial_pop8(nstate);
    cpu_st.reg_cc = vecx_serial_pop8(nstate);
    cpu_st.irq_status = vecx_serial_pop8(nstate);
    vecx_cpu_state_load(&cpu_st);

    // PSG
    for (int i = 0; i < 16; ++i)
        psg.reg[i] = vecx_serial_pop16(nstate);

    for (int i = 0; i < 3; ++i) {
        psg.tperiod[i] = vecx_serial_pop16(nstate);
        psg.tcounter[i] = vecx_serial_pop16(nstate);
        psg.amplitude[i] = vecx_serial_pop8(nstate);
        psg.tdisable[i] = vecx_serial_pop8(nstate);
        psg.ndisable[i] = vecx_serial_pop8(nstate);
        psg.emode[i] = vecx_serial_pop8(nstate);
        psg.sign[i] = vecx_serial_pop8(nstate);
    }

    psg.nperiod = vecx_serial_pop8(nstate);
    psg.ncounter = vecx_serial_pop16(nstate);
    psg.nshift = vecx_serial_pop32(nstate);
    psg.eperiod = vecx_serial_pop16(nstate);
    psg.ecounter = vecx_serial_pop16(nstate);
    psg.eseg = vecx_serial_pop8(nstate);
    psg.estep = vecx_serial_pop8(nstate);
    psg.evol = vecx_serial_pop8(nstate);

    // Other
    via_ora = vecx_serial_pop8(nstate);
    via_orb = vecx_serial_pop8(nstate);
    via_ddra = vecx_serial_pop8(nstate);
    via_ddrb = vecx_serial_pop8(nstate);
    via_t1on = vecx_serial_pop8(nstate);
    via_t1int = vecx_serial_pop8(nstate);
    via_t1ll = vecx_serial_pop8(nstate);
    via_t1lh = vecx_serial_pop8(nstate);
    via_t2on = vecx_serial_pop8(nstate);
    via_t2int = vecx_serial_pop8(nstate);
    via_t2ll = vecx_serial_pop8(nstate);
    via_sr = vecx_serial_pop8(nstate);
    alg_vector_color = vecx_serial_pop8(nstate);
    via_t1c = vecx_serial_pop16(nstate);
    via_t1pb7 = vecx_serial_pop16(nstate);
    via_t2c = vecx_serial_pop16(nstate);
    via_srb = vecx_serial_pop16(nstate);
    via_src = vecx_serial_pop16(nstate);
    via_srclk = vecx_serial_pop16(nstate);
    via_acr = vecx_serial_pop16(nstate);
    via_pcr = vecx_serial_pop16(nstate);
    via_ifr = vecx_serial_pop16(nstate);
    via_ier = vecx_serial_pop16(nstate);
    via_ca2 = vecx_serial_pop16(nstate);
    via_cb2h = vecx_serial_pop16(nstate);
    via_cb2s = vecx_serial_pop16(nstate);
    alg_rsh = vecx_serial_pop16(nstate);
    alg_xsh = vecx_serial_pop16(nstate);
    alg_ysh = vecx_serial_pop16(nstate);
    alg_zsh = vecx_serial_pop16(nstate);
    alg_jch0 = vecx_serial_pop16(nstate);
    alg_jch1 = vecx_serial_pop16(nstate);
    alg_jch2 = vecx_serial_pop16(nstate);
    alg_jch3 = vecx_serial_pop16(nstate);
    alg_jsh = vecx_serial_pop16(nstate);
    alg_compare = vecx_serial_pop16(nstate);
    alg_vectoring = vecx_serial_pop16(nstate);
    alg_dx = vecx_serial_pop32(nstate);
    alg_dy = vecx_serial_pop32(nstate);
    alg_curr_x = vecx_serial_pop32(nstate);
    alg_curr_y = vecx_serial_pop32(nstate);
    alg_vector_x0 = vecx_serial_pop32(nstate);
    alg_vector_y0 = vecx_serial_pop32(nstate);
    alg_vector_x1 = vecx_serial_pop32(nstate);
    alg_vector_y1 = vecx_serial_pop32(nstate);
    alg_vector_dx = vecx_serial_pop32(nstate);
    alg_vector_dy = vecx_serial_pop32(nstate);
    vector_draw_cnt = vecx_serial_pop32(nstate);
    vector_erse_cnt = vecx_serial_pop32(nstate);
    leftover = vecx_serial_pop32(nstate);
    fcycles = vecx_serial_pop32(nstate);
}

// Load a state from a file
int vecx_state_load(const char *filename) {
    FILE *file;
    size_t filesize, result;
    void *sstatefile;

    // Open the file for reading
    file = fopen(filename, "rb");
    if (!file)
        return 0;

    // Find out the file's size
    fseek(file, 0, SEEK_END);
    filesize = ftell(file);
    fseek(file, 0, SEEK_SET);

    // Allocate memory to read the file into
    sstatefile = (void*)malloc(filesize * sizeof(uint8_t));
    if (sstatefile == NULL)
        return 0;

    // Read the file into memory and then close it
    result = fread(sstatefile, sizeof(uint8_t), filesize, file);
    if (result != filesize)
        return 0;
    fclose(file);

    // File has been read, now copy it into the emulator
    vecx_state_load_raw((const void*)sstatefile);

    // Free the allocated memory
    free(sstatefile);

    return 1; // Success!
}

// Snapshot the running state and return the address of the raw data
const void* vecx_state_save_raw(void) {
    vecx_serial_begin();

    // RAM
    vecx_serial_pushblk(vstate, ram, VECX_RAM_SIZE);

    // CPU
    cpust_t cpu_st = {0};
    vecx_cpu_state_save(&cpu_st);
    vecx_serial_push16(vstate, cpu_st.reg_x);
    vecx_serial_push16(vstate, cpu_st.reg_y);
    vecx_serial_push16(vstate, cpu_st.reg_u);
    vecx_serial_push16(vstate, cpu_st.reg_s);
    vecx_serial_push16(vstate, cpu_st.reg_pc);
    vecx_serial_push8(vstate, cpu_st.reg_a);
    vecx_serial_push8(vstate, cpu_st.reg_b);
    vecx_serial_push8(vstate, cpu_st.reg_dp);
    vecx_serial_push8(vstate, cpu_st.reg_cc);
    vecx_serial_push8(vstate, cpu_st.irq_status);

    // PSG
    for (int i = 0; i < 16; ++i)
        vecx_serial_push16(vstate, psg.reg[i]);

    for (int i = 0; i < 3; ++i) {
        vecx_serial_push16(vstate, psg.tperiod[i]);
        vecx_serial_push16(vstate, psg.tcounter[i]);
        vecx_serial_push8(vstate, psg.amplitude[i]);
        vecx_serial_push8(vstate, psg.tdisable[i]);
        vecx_serial_push8(vstate, psg.ndisable[i]);
        vecx_serial_push8(vstate, psg.emode[i]);
        vecx_serial_push8(vstate, psg.sign[i]);
    }

    vecx_serial_push8(vstate, psg.nperiod);
    vecx_serial_push16(vstate, psg.ncounter);
    vecx_serial_push32(vstate, psg.nshift);
    vecx_serial_push16(vstate, psg.eperiod);
    vecx_serial_push16(vstate, psg.ecounter);
    vecx_serial_push8(vstate, psg.eseg);
    vecx_serial_push8(vstate, psg.estep);
    vecx_serial_push8(vstate, psg.evol);

    // Other
    vecx_serial_push8(vstate, via_ora);
    vecx_serial_push8(vstate, via_orb);
    vecx_serial_push8(vstate, via_ddra);
    vecx_serial_push8(vstate, via_ddrb);
    vecx_serial_push8(vstate, via_t1on);
    vecx_serial_push8(vstate, via_t1int);
    vecx_serial_push8(vstate, via_t1ll);
    vecx_serial_push8(vstate, via_t1lh);
    vecx_serial_push8(vstate, via_t2on);
    vecx_serial_push8(vstate, via_t2int);
    vecx_serial_push8(vstate, via_t2ll);
    vecx_serial_push8(vstate, via_sr);
    vecx_serial_push8(vstate, alg_vector_color);
    vecx_serial_push16(vstate, via_t1c);
    vecx_serial_push16(vstate, via_t1pb7);
    vecx_serial_push16(vstate, via_t2c);
    vecx_serial_push16(vstate, via_srb);
    vecx_serial_push16(vstate, via_src);
    vecx_serial_push16(vstate, via_srclk);
    vecx_serial_push16(vstate, via_acr);
    vecx_serial_push16(vstate, via_pcr);
    vecx_serial_push16(vstate, via_ifr);
    vecx_serial_push16(vstate, via_ier);
    vecx_serial_push16(vstate, via_ca2);
    vecx_serial_push16(vstate, via_cb2h);
    vecx_serial_push16(vstate, via_cb2s);
    vecx_serial_push16(vstate, alg_rsh);
    vecx_serial_push16(vstate, alg_xsh);
    vecx_serial_push16(vstate, alg_ysh);
    vecx_serial_push16(vstate, alg_zsh);
    vecx_serial_push16(vstate, alg_jch0);
    vecx_serial_push16(vstate, alg_jch1);
    vecx_serial_push16(vstate, alg_jch2);
    vecx_serial_push16(vstate, alg_jch3);
    vecx_serial_push16(vstate, alg_jsh);
    vecx_serial_push16(vstate, alg_compare);
    vecx_serial_push16(vstate, alg_vectoring);
    vecx_serial_push32(vstate, alg_dx);
    vecx_serial_push32(vstate, alg_dy);
    vecx_serial_push32(vstate, alg_curr_x);
    vecx_serial_push32(vstate, alg_curr_y);
    vecx_serial_push32(vstate, alg_vector_x0);
    vecx_serial_push32(vstate, alg_vector_y0);
    vecx_serial_push32(vstate, alg_vector_x1);
    vecx_serial_push32(vstate, alg_vector_y1);
    vecx_serial_push32(vstate, alg_vector_dx);
    vecx_serial_push32(vstate, alg_vector_dy);
    vecx_serial_push32(vstate, vector_draw_cnt);
    vecx_serial_push32(vstate, vector_erse_cnt);
    vecx_serial_push32(vstate, leftover);
    vecx_serial_push32(vstate, fcycles);

    return (const void*)vstate;
}

// Save a state to a file
int vecx_state_save(const char *filename) {
    // Open the file for writing
    FILE *file;
    file = fopen(filename, "wb");
    if (!file)
        return 0;

    // Snapshot the running state and get the memory address
    uint8_t *sstate = (uint8_t*)vecx_state_save_raw();

    // Write and close the file
    fwrite(sstate, vecx_state_size(), sizeof(uint8_t), file);
    fclose(file);

    return 1; // Success!
}
