//
//  main.c
//  6502
//
//  Created by Tamas Rudnai on 7/14/19.
//  Copyright © 2019, 2020 Tamas Rudnai. All rights reserved.
//
// This file is part of Steve ][ -- The Apple ][ Emulator.
//
// Steve ][ is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Steve ][ is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Steve ][.  If not, see <https://www.gnu.org/licenses/>.
//
// Documentations:
//
// http://nesdev.com/6502_cpu.txt
// http://www.oxyron.de/html/opcodes02.html
// https://macgui.com/kb/article/46
// https://www.masswerk.at/6502/6502_instruction_set.html
//

#define CLK_WAIT

#define DEBUGGER
#undef DISASSEMBLER

#define FETCH_ADDR disass_addr

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include "6502.h"
#include "speaker.h"


#include "../util/common.h"


#define SOFTRESET_VECTOR    0x3F2

#define NMI_VECTOR          0xFFFA
#define RESET_VECTOR        0xFFFC
#define IRQ_VECTOR          0xFFFE

extern m6502_t m6502;
m6502_t m6502_saved;

uint16_t disass_addr = 0xFDED;

//#include "6502_dis.h"
#include "../dev/mem/mmio.h"



typedef struct {
    uint8_t L;
    uint8_t H;
} bytes_t;


/**
 Instruction Implementations
 !!!! `his has to be here!!!
 This idea is that "INLINE" would work only if it is
 located in the same source file -- hence the include...
**/


#include "6502_dbg.h"
#include "6502_instructions.h"
#include "6502_bp.h"

INLINE int m6502_Step_dbg(void) {
    disNewInstruction();

    switch ( fetch() ) {
#include "6502_std.h"           // Standard 6502 instructions
//#include "6502_und.h"           // Undocumented 6502 instructions
#include "6502_C.h"             // Extended 65C02 instructions
#include "6502_C_Rockwell.h"    // Extended 65C02 instructions

        default:
            dbgPrintf("%04X: Unimplemented Instruction 0x%02X\n", m6502.PC -1, memread( m6502.PC -1 ));
            m6502.interrupt = INV;
            return 2;
    } // switch fetch16

    return 2;
}


void m6502_Debug(void) {
    m6502.clktime += m6502.clkfrm;
    m6502.clkfrm = 0;
    m6502.lastIO = 0;
    m6502.interrupt = NO_INT; // TODO: This should be taken care by the interrupt handler

    m6502_dbg_on();

    if( diskAccelerator_count ) {
        if( --diskAccelerator_count <= 0 ) {
            // make sure we only adjust clock once to get back to normal
            diskAccelerator_count = 0;
            clk_6502_per_frm = clk_6502_per_frm_set;
        }
    }

    clk_6502_per_frm_max = clk_6502_per_frm;

    while ( m6502.clkfrm < clk_6502_per_frm_max ) {
        m6502_saved = m6502;
        m6502.clkfrm += m6502_Step_dbg();

        switch (m6502.interrupt) {
            case HALT:
                if (m6502.debugger.mask.hlt) {
                    cpuState = cpuState_halted;

                    return;
                }
                break;

            case BREAK:
                if (m6502.debugger.mask.brk) {
                    cpuState = cpuState_halted;

                    return;
                }
                break;

            case BREAKRDMEM:
            case BREAKWRMEM:
                if (m6502.debugger.mask.brk) {
                    cpuState = cpuState_halted;

                    // memory break happens *after* executing the instruction,
                    // therefore we need to step back to get it right in the debugger
                    m6502_saved.interrupt = m6502.interrupt; // we need to keep the new interrupt though
                    m6502 = m6502_saved; // copy over only A, X, Y, Status, PC & SP...

                    return;
                }
                break;

            case IRQ:
                if (m6502.debugger.mask.irq) {
                    cpuState = cpuState_halted;

                    return;
                }
                break;

            case NMI:
                if (m6502.debugger.mask.nmi) {
                    cpuState = cpuState_halted;

                    return;
                }
                break;

            case INV:
                if (m6502.debugger.mask.inv) {
                    cpuState = cpuState_halted;

                    return;
                }
                break;

            case RET:
                // Step_Out & Step_Over: Return to caller
                if (m6502.debugger.mask.out) {
                    if ( m6502.SP >= m6502.debugger.SP ) {
                        cpuState = cpuState_halted;

                        return;
                    }
                }
                break;

            case HARDRESET:
                hardReset();
                break;

            case SOFTRESET:
                softReset();
                break;

            default:
                // Step_Out: If there was a POP (PLA, PLX, PLY, PLP), then we should update the monitoring stack pointer
                // (so we can return to the caller, not stopping at the POP)
                if ( m6502.SP > m6502.debugger.SP ) {
                    m6502.debugger.SP = m6502.SP;
                }
                break;
        }

        m6502.interrupt = NO_INT;

        if ( m6502_dbg_bp_exists(breakpoints, m6502.PC) ) {
            cpuState = cpuState_halted;
            m6502.interrupt = BREAKPOINT;
            return;
        }

    }

    // play the entire sound buffer for this frame
    spkr_update();
    // this will take care of turning off disk motor sound when time is up
    spkr_update_disk_sfx();

}


/// Turn On Debugger
void m6502_dbg_on(void) {
    m6502.debugger.on = 1;
}


/// Turn Off Debugger
void m6502_dbg_off(void) {
    m6502.debugger.on = 0;
}


/// Initialize Breakpoints
void m6502_dbg_init(void) {
    m6502_dbg_on();
    m6502.debugger.wMask = 0;
    m6502.debugger.mask.hlt = 1;
    m6502.debugger.mask.brk = 1;
    m6502.debugger.mask.inv = 1;
    m6502.debugger.SP = 0xFF;
    m6502_dbg_bp_del_all(breakpoints);
    m6502_dbg_bp_del_all(mem_read_breakpoints);
    m6502_dbg_bp_del_all(mem_write_breakpoints);

    // TODO: TESTING ONLY!!!
//    m6502_dbg_bp_add(mem_read_breakpoints, 0xC000);
//    m6502_dbg_bp_add(mem_write_breakpoints, 0xC099);
}


