/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          8080 CPU emulation.
 *
 * Authors: Cacodemon345
 *
 *          Copyright 2022 Cacodemon345
 */

#include <stdint.h>
#include <stdlib.h>
#include "cpu.h"
#include <86box/timer.h>
#include <86box/i8080.h>
#include <86box/mem.h>
#include <86box/plat_unused.h>

static int completed  = 1;
static int in_rep     = 0;
static int repeating  = 0;
static int rep_c_flag = 0;
static int oldc;
static int cycdiff;
#ifdef UNUSED_8080_VARS
static int prefetching = 1;
static int refresh     = 0;
static int clear_lock  = 0;

static uint32_t cpu_src  = 0;
static uint32_t cpu_dest = 0;
static uint32_t cpu_data = 0;
#endif

static void
clock_start(void)
{
    cycdiff = cycles;
}

static void
clock_end(void)
{
    int diff = cycdiff - cycles;

    /* On 808x systems, clock speed is usually crystal frequency divided by an integer. */
    tsc += (uint64_t) diff * (xt_cpu_multi >> 32ULL); /* Shift xt_cpu_multi by 32 bits to the right and then multiply. */
    if (TIMER_VAL_LESS_THAN_VAL(timer_target, (uint32_t) tsc))
        timer_process();
}

static void
i8080_wait(int c, int bus)
{
    cycles -= c;
    if (bus < 2) {
        clock_end();
        clock_start();
    }
}

#ifdef UNUSED_8080_FUNCS
static uint8_t
readmemb(uint32_t a)
{
    uint8_t ret;

    i8080_wait(4, 1);
    ret = read_mem_b(a);

    return ret;
}

static uint8_t
ins_fetch(i8080 *cpu)
{
    uint8_t ret = cpu->readmembyte(cpu->pmembase + cpu->pc);

    cpu->pc++;
    return ret;
}
#endif

void
transfer_from_808x(i8080 *cpu)
{
    cpu->hl       = BX;
    cpu->bc       = CX;
    cpu->de       = DX;
    cpu->a        = AL;
    cpu->flags    = cpu_state.flags & 0xFF;
    cpu->sp       = BP;
    cpu->pc       = cpu_state.pc;
    cpu->oldpc    = cpu_state.oldpc;
    cpu->pmembase = cs;
    cpu->dmembase = ds;
}

void
transfer_to_808x(i8080 *cpu)
{
    BX = cpu->hl;
    CX = cpu->bc;
    DX = cpu->de;
    AL = cpu->a;
    cpu_state.flags &= 0xFF00;
    cpu_state.flags |= cpu->flags & 0xFF;
    BP           = cpu->sp;
    cpu_state.pc = cpu->pc;
}

uint8_t
getreg_i8080(i8080 *cpu, uint8_t reg)
{
    uint8_t ret = 0xFF;
    switch (reg) {
        case 0x0:
            ret = cpu->b;
            break;
        case 0x1:
            ret = cpu->c;
            break;
        case 0x2:
            ret = cpu->d;
            break;
        case 0x3:
            ret = cpu->e;
            break;
        case 0x4:
            ret = cpu->h;
            break;
        case 0x5:
            ret = cpu->l;
            break;
        case 0x6:
            ret = cpu->readmembyte(cpu->dmembase + cpu->sp);
            break;
        case 0x7:
            ret = cpu->a;
            break;
    }
    return ret;
}

uint8_t
getreg_i8080_emu(i8080 *cpu, uint8_t reg)
{
    uint8_t ret = 0xFF;
    switch (reg) {
        case 0x0:
            ret = CH;
            break;
        case 0x1:
            ret = CL;
            break;
        case 0x2:
            ret = DH;
            break;
        case 0x3:
            ret = DL;
            break;
        case 0x4:
            ret = BH;
            break;
        case 0x5:
            ret = BL;
            break;
        case 0x6:
            ret = cpu->readmembyte(cpu->dmembase + BP);
            break;
        case 0x7:
            ret = AL;
            break;
    }
    return ret;
}

void
setreg_i8080_emu(i8080 *cpu, uint8_t reg, uint8_t val)
{
    switch (reg) {
        case 0x0:
            CH = val;
            break;
        case 0x1:
            CL = val;
            break;
        case 0x2:
            DH = val;
            break;
        case 0x3:
            DL = val;
            break;
        case 0x4:
            BH = val;
            break;
        case 0x5:
            BL = val;
            break;
        case 0x6:
            cpu->writemembyte(cpu->dmembase + BP, val);
            break;
        case 0x7:
            AL = val;
            break;
    }
}

void
setreg_i8080(i8080 *cpu, uint8_t reg, uint8_t val)
{
    switch (reg) {
        case 0x0:
            cpu->b = val;
            break;
        case 0x1:
            cpu->c = val;
            break;
        case 0x2:
            cpu->d = val;
            break;
        case 0x3:
            cpu->e = val;
            break;
        case 0x4:
            cpu->h = val;
            break;
        case 0x5:
            cpu->l = val;
            break;
        case 0x6:
            cpu->writemembyte(cpu->dmembase + cpu->sp, val);
            break;
        case 0x7:
            cpu->a = val;
            break;
    }
}

void
interpret_exec8080(UNUSED(i8080 *cpu), uint8_t opcode)
{
    switch (opcode) {
        case 0x00:
            {
                break;
            }
    }
}

/* Actually implement i8080 emulation. */
void
exec8080(i8080 *cpu, int cycs)
{
#ifdef UNUSED_8080_VARS
    uint8_t  temp = 0, temp2;
    uint8_t  old_af;
    uint8_t  handled = 0;
    uint16_t addr, tempw;
    uint16_t new_ip;
    int      bits;
#endif

    cycles += cycs;

    while (cycles > 0) {
        cpu->startclock();

        if (!repeating) {
            cpu->oldpc = cpu->pc;
            opcode     = cpu->fetchinstruction(cpu);
            oldc       = cpu->flags & C_FLAG_I8080;
            i8080_wait(1, 0);
        }
        completed = 1;
        if (completed) {
            repeating  = 0;
            in_rep     = 0;
            rep_c_flag = 0;
            cpu->endclock();
            if (cpu->checkinterrupts)
                cpu->checkinterrupts();
        }
    }
}
