#include "stdafx.h"

#include "../../source/Windows/AppleWin.h"
#include "../../source/CPU.h"

#include "../../source/Debugger/Debugger_Types.h"
#include "../../source/Debugger/Debugger_Assembler.h"	// Pull in default args for _6502_GetTargets()

// NB. DebugDefs.h must come after Debugger_Types.h which declares these as extern
#include "../../source/Debugger/DebugDefs.h"

// From FrameBase
class FrameBase
{
public:
	FrameBase() { g_hFrameWindow = (HWND)0; }
	HWND   g_hFrameWindow;
};

// From Win32Frame
class Win32Frame : public FrameBase
{
};

// From AppleWin.cpp
FrameBase& GetFrame()
{
	static Win32Frame sg_Win32Frame;
	return sg_Win32Frame;
}

// From CPU.cpp
regsrec regs;

static eCpuType g_MainCPU = CPU_65C02;

eCpuType GetMainCpu(void)
{
	return g_MainCPU;
}

// From Memory.cpp
LPBYTE         mem          = NULL;	// TODO: Init
LPBYTE         memdirty     = NULL;	// TODO: Init

//-------------------------------------

// From Debugger_Console.cpp
		char      g_aConsolePrompt[] = ">!"; // input, assembler // NUM_PROMPTS
		char      g_sConsolePrompt[] = ">"; // No, NOT Integer Basic!  The nostalgic '*' "Monitor" doesn't look as good, IMHO. :-(

Update_t ConsoleUpdate ()
{
	return 0;
}

void ConsoleBufferPush ( const char * pText )
{
}

// From Debugger_DisassemblerData.cpp
DisasmData_t* Disassembly_IsDataAddress ( WORD nAddress )
{
	return NULL;
}

// From Debugger_Parser.cpp
	int   g_nArgRaw;
	Arg_t g_aArgRaw[ MAX_ARGS ]; // pre-processing
	Arg_t g_aArgs  [ MAX_ARGS ]; // post-processing (cooked)

bool ArgsGetValue ( Arg_t *pArg, WORD * pAddressValue_, const int nBase )
{
	return false;
}

// From Debugger_Symbols.cpp
bool FindAddressFromSymbol ( const char* pSymbol, WORD * pAddress_, int * iTable_ )
{
	return false;
}

//-------------------------------------

void init(void)
{
	mem = (LPBYTE)VirtualAlloc(NULL,128*1024,MEM_COMMIT,PAGE_READWRITE);	// alloc >64K to test wrap-around at 64K boundary
}

void reset(void)
{
	regs.a  = 0;
	regs.x  = 0;
	regs.y  = 0;
	regs.pc = 0x300;
	regs.sp = 0x1FF;
	regs.ps = 0;
	regs.bJammed = 0;
}

//-------------------------------------

int GH445_test_PHn(BYTE op)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	mem[regs.pc] = op;

	regs.sp = 0x1FE;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[2] != regs.sp) return 1;

	regs.sp = 0x1FF;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[2] != regs.sp) return 1;

	regs.sp = 0x100;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[2] != regs.sp) return 1;

	return 0;
}

int GH445_test_PLn(BYTE op)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	mem[regs.pc] = op;

	regs.sp = 0x1FE;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[2] != (regs.sp+1)) return 1;

	regs.sp = 0x1FF;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[2] != 0x100) return 1;

	regs.sp = 0x100;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[2] != (regs.sp+1)) return 1;

	return 0;
}

int GH445_test_abs(BYTE op)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	const WORD target2 = 0x1234;

	mem[regs.pc] = op;
	mem[(regs.pc+1)&0xFFFF] = (BYTE) (target2&0xff);
	mem[(regs.pc+2)&0xFFFF] = (BYTE) ((target2>>8)&0xff);
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[2] != target2) return 1;

	return 0;
}

int GH445_test_jsr(void)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	WORD target2 = 0x1234;

	mem[regs.pc] = OPCODE_JSR;
	mem[(regs.pc+1)&0xFFFF] = (BYTE) (target2&0xff);
	mem[(regs.pc+2)&0xFFFF] = (BYTE) ((target2>>8)&0xff);

	regs.sp = 0x1FF;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != regs.sp-1 || TargetAddr[2] != target2) return 1;

	regs.sp = 0x100;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != 0x1FF || TargetAddr[2] != target2) return 1;

	regs.sp = 0x101;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != regs.sp-1 || TargetAddr[2] != target2) return 1;

	return 0;
}

int GH445_test_brk(void)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	mem[regs.pc] = OPCODE_BRK;

	regs.sp = 0x1FF;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != regs.sp-1) return 1;

	regs.sp = 0x100;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != 0x1FF) return 1;

	regs.sp = 0x101;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != regs.sp-1) return 1;

	return 0;
}

int GH445_test_rti_rts(WORD sp, const bool isRTI)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	mem[regs.pc] = isRTI ? OPCODE_RTI : OPCODE_RTS;
	regs.sp = sp;

	WORD sp_addr_p=0, sp_addr_l=0, sp_addr_h=0;
	if (isRTI)
	{
		sp_addr_p = 0x100 + ((regs.sp+1)&0xFF);
		sp_addr_l = 0x100 + ((regs.sp+2)&0xFF);
		sp_addr_h = 0x100 + ((regs.sp+3)&0xFF);
		mem[sp_addr_p] = 0xEA;
	}
	else
	{
		sp_addr_l = 0x100 + ((regs.sp+1)&0xFF);
		sp_addr_h = 0x100 + ((regs.sp+2)&0xFF);
	}

	WORD ret_addr = 0x1234;
	mem[sp_addr_l] = (BYTE) (ret_addr&0xFF);
	mem[sp_addr_h] = (BYTE) ((ret_addr>>8)&0xFF);

	if (!isRTI)
		ret_addr++;		// NB. return addr from stack is incremented before being transferred to PC

	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[0] != sp_addr_l || TargetAddr[1] != sp_addr_h || TargetAddr[2] != ret_addr) return 1;

	return 0;
}

int GH445_test_jmp(BYTE op)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	const WORD target16 = 0x1234;

	int target0=0, target1=0, target2=0;
	if (op == OPCODE_JMP_A)
	{
		target0 = NO_6502_TARGET;
		target1 = NO_6502_TARGET;
		target2 = target16;
	}
	else if (op == OPCODE_JMP_NA)
	{
		target0 = target16;
		target1 = (target16+1)&0xffff;
		target2 = 0x5678;
		mem[target0] = target2 & 0xff;
		mem[target1] = (target2>>8) & 0xff;
	}
	else if (op == OPCODE_JMP_IAX)
	{
		target0 = (target16+regs.x)&0xffff;
		target1 = (target16+regs.x+1)&0xffff;
		target2 = 0xABCD;
		mem[target0] = target2 & 0xff;
		mem[target1] = (target2>>8) & 0xff;
	}
	else
	{
		_ASSERT(0);
	}

	mem[regs.pc] = op;
	mem[(regs.pc+1)&0xFFFF] = (BYTE) (target16&0xff);
	mem[(regs.pc+2)&0xFFFF] = (BYTE) ((target16>>8)&0xff);
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[0] != target0 || TargetAddr[1] != target1 || TargetAddr[2] != target2) return 1;

	return 0;
}

// bIgnoreBranch == true (default)
int GH445_test_Bcc(void)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	mem[regs.pc] = 0x10;	// BPL next-op
	mem[regs.pc+1] = 0;

	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[0] != NO_6502_TARGET || TargetAddr[1] != NO_6502_TARGET || TargetAddr[2] != NO_6502_TARGET) return 1;

	mem[regs.pc] = 0x10;	// BPL this-op
	mem[regs.pc+1] = 0xfe;

	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes);
	if (!bRes || TargetAddr[0] != NO_6502_TARGET || TargetAddr[1] != NO_6502_TARGET || TargetAddr[2] != NO_6502_TARGET) return 1;

	return 0;
}

int GH445_test_sub(bool bIs65C02)
{
	int res;

	mem[0x10000] = 0xDD;	// Bad data if 64K wrap not working
	mem[0x10001] = 0xDD;

	mem[0x200] = 0xDD;		// Bad data if SP wrap not working
	mem[0x201] = 0xDD;

	regs.pc = 0x300;

	//
	// PHn/PLn

	res = GH445_test_PHn(0x08);	// PHP
	if (res) return res;
	res = GH445_test_PHn(0x48);	// PHA
	if (res) return res;

	if (bIs65C02)
	{
		res = GH445_test_PHn(0x5A);	// PHY
		if (res) return res;
		res = GH445_test_PHn(0xDA);	// PHX
		if (res) return res;
	}

	//

	res = GH445_test_PLn(0x28);	// PLP
	if (res) return res;
	res = GH445_test_PLn(0x68);	// PLA
	if (res) return res;

	if (bIs65C02)
	{
		res = GH445_test_PLn(0x7A);	// PLY
		if (res) return res;
		res = GH445_test_PLn(0xFA);	// PLX
		if (res) return res;
	}

	//
	// LDA abs

	regs.pc = 0xFFFD;
	res = GH445_test_abs(OPCODE_LDA_A);	// LDA ABS
	if (res) return res;

	regs.pc = 0xFFFE;
	res = GH445_test_abs(OPCODE_LDA_A);	// LDA ABS
	if (res) return res;

	regs.pc = 0xFFFF;
	res = GH445_test_abs(OPCODE_LDA_A);	// LDA ABS
	if (res) return res;

	//
	// JSR abs

	res = GH445_test_jsr();
	if (res) return res;

	//
	// BRK

	mem[_6502_BRK_VECTOR+0] = 0x40;		// BRK vector: $FA40
	mem[_6502_BRK_VECTOR+1] = 0xFA;

	regs.pc = 0x300;
	res = GH445_test_brk();
	if (res) return res;

	//
	// RTI

	res = GH445_test_rti_rts(0x1FE, true);
	if (res) return res;
	res = GH445_test_rti_rts(0x1FF, true);
	if (res) return res;
	res = GH445_test_rti_rts(0x100, true);
	if (res) return res;

	//
	// RTS

	res = GH445_test_rti_rts(0x1FE, false);
	if (res) return res;
	res = GH445_test_rti_rts(0x1FF, false);
	if (res) return res;
	res = GH445_test_rti_rts(0x100, false);
	if (res) return res;

	//
	// JMP

	res = GH445_test_jmp(OPCODE_JMP_A);			// JMP abs
	if (res) return res;

	res = GH445_test_jmp(OPCODE_JMP_NA);		// JMP (abs)
	if (res) return res;

	if (bIs65C02)
	{
		regs.x = 0xff;
		res = GH445_test_jmp(OPCODE_JMP_IAX);	// JMP (abs,x)
		if (res) return res;
	}

	//
	// Bcc

	res = GH445_test_Bcc();
	if (res) return res;

	return 0;
}

int GH445_test(void)
{
	int res;

	g_aOpcodes = g_aOpcodes65C02;
	res = GH445_test_sub(true);
	if (res) return res;

	g_aOpcodes = g_aOpcodes6502;
	res = GH445_test_sub(false);

	return res;
}

//-------------------------------------

//
// bIncludeNextOpcodeAddress == false, check that:
// . TargetAddr[2] gets set, eg. for LDA abs
// . TargetAddr[2] == NO_6502_TARGET for control flow instructions, eg. BRK,RTI,RTS,JSR,JMP,Bcc
//

int GH451_test_abs(BYTE op)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	const WORD target2 = 0x1234;

	mem[regs.pc] = op;
	mem[(regs.pc+1)&0xFFFF] = (BYTE) (target2&0xff);
	mem[(regs.pc+2)&0xFFFF] = (BYTE) ((target2>>8)&0xff);
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes, true, false);
	if (!bRes || TargetAddr[2] != target2) return 1;

	return 0;
}

int GH451_test_jsr(void)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	mem[regs.pc] = OPCODE_JSR;

	regs.sp = 0x1FF;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes, true, false);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != regs.sp-1 || TargetAddr[2] != NO_6502_TARGET) return 1;

	regs.sp = 0x100;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes, true, false);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != 0x1FF || TargetAddr[2] != NO_6502_TARGET) return 1;

	regs.sp = 0x101;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes, true, false);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != regs.sp-1 || TargetAddr[2] != NO_6502_TARGET) return 1;

	return 0;
}

int GH451_test_brk(void)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	mem[regs.pc] = OPCODE_BRK;

	regs.sp = 0x1FF;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes, true, false);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != regs.sp-1 || TargetAddr[2] != NO_6502_TARGET) return 1;

	regs.sp = 0x100;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes, true, false);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != 0x1FF || TargetAddr[2] != NO_6502_TARGET) return 1;

	regs.sp = 0x101;
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes, true, false);
	if (!bRes || TargetAddr[0] != regs.sp || TargetAddr[1] != regs.sp-1 || TargetAddr[2] != NO_6502_TARGET) return 1;

	return 0;
}

int GH451_test_rti_rts(WORD sp, const bool isRTI)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	mem[regs.pc] = isRTI ? OPCODE_RTI : OPCODE_RTS;
	regs.sp = sp;

	WORD sp_addr_p=0, sp_addr_l=0, sp_addr_h=0;
	if (isRTI)
	{
		sp_addr_p = 0x100 + ((regs.sp+1)&0xFF);
		sp_addr_l = 0x100 + ((regs.sp+2)&0xFF);
		sp_addr_h = 0x100 + ((regs.sp+3)&0xFF);
		mem[sp_addr_p] = 0xEA;
	}
	else
	{
		sp_addr_l = 0x100 + ((regs.sp+1)&0xFF);
		sp_addr_h = 0x100 + ((regs.sp+2)&0xFF);
	}

	WORD ret_addr = 0x1234;
	mem[sp_addr_l] = (BYTE) (ret_addr&0xFF);
	mem[sp_addr_h] = (BYTE) ((ret_addr>>8)&0xFF);

	if (!isRTI)
		ret_addr++;		// NB. return addr from stack is incremented before being transferred to PC

	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes, true, false);
	if (!bRes || TargetAddr[0] != sp_addr_l || TargetAddr[1] != sp_addr_h || TargetAddr[2] != NO_6502_TARGET) return 1;

	return 0;
}

int GH451_test_jmp(BYTE op)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	const WORD target16 = 0x1234;

	int target0=0, target1=0;
	if (op == OPCODE_JMP_A)
	{
		target0 = NO_6502_TARGET;
		target1 = NO_6502_TARGET;
	}
	else if (op == OPCODE_JMP_NA)
	{
		target0 = target16;
		target1 = (target16+1)&0xffff;
	}
	else if (op == OPCODE_JMP_IAX)
	{
		target0 = (target16+regs.x)&0xffff;
		target1 = (target16+regs.x+1)&0xffff;
	}
	else
	{
		_ASSERT(0);
	}

	mem[regs.pc] = op;
	mem[(regs.pc+1)&0xFFFF] = (BYTE) (target16&0xff);
	mem[(regs.pc+2)&0xFFFF] = (BYTE) ((target16>>8)&0xff);
	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes, true, false);
	if (!bRes || TargetAddr[0] != target0 || TargetAddr[1] != target1 || TargetAddr[2] != NO_6502_TARGET) return 1;

	return 0;
}

// bIgnoreBranch == true
int GH451_test_Bcc(void)
{
	bool bRes;
	int TargetAddr[3];
	int TargetBytes;

	mem[regs.pc] = 0x10;	// BPL next-op
	mem[regs.pc+1] = 0;

	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes, true, false);
	if (!bRes || TargetAddr[0] != NO_6502_TARGET || TargetAddr[1] != NO_6502_TARGET || TargetAddr[2] != NO_6502_TARGET) return 1;

	mem[regs.pc] = 0x10;	// BPL this-op
	mem[regs.pc+1] = 0xfe;

	bRes = _6502_GetTargets(regs.pc, &TargetAddr[0], &TargetAddr[1], &TargetAddr[2], &TargetBytes, true, false);
	if (!bRes || TargetAddr[0] != NO_6502_TARGET || TargetAddr[1] != NO_6502_TARGET || TargetAddr[2] != NO_6502_TARGET) return 1;

	return 0;
}

int GH451_test_sub(bool bIs65C02)
{
	int res;

	mem[0x10000] = 0xDD;	// Bad data if 64K wrap not working
	mem[0x10001] = 0xDD;

	mem[0x200] = 0xDD;		// Bad data if SP wrap not working
	mem[0x201] = 0xDD;

	regs.pc = 0x300;

	//
	// LDA abs

	res = GH451_test_abs(OPCODE_LDA_A);
	if (res) return res;

	//
	// JSR abs

	res = GH451_test_jsr();
	if (res) return res;

	//
	// BRK

	mem[_6502_BRK_VECTOR+0] = 0x40;		// BRK vector: $FA40
	mem[_6502_BRK_VECTOR+1] = 0xFA;

	res = GH451_test_brk();
	if (res) return res;

	//
	// RTI

	res = GH451_test_rti_rts(0x1FE, true);
	if (res) return res;
	res = GH451_test_rti_rts(0x1FF, true);
	if (res) return res;
	res = GH451_test_rti_rts(0x100, true);
	if (res) return res;

	//
	// RTS

	res = GH451_test_rti_rts(0x1FE, false);
	if (res) return res;
	res = GH451_test_rti_rts(0x1FF, false);
	if (res) return res;
	res = GH451_test_rti_rts(0x100, false);
	if (res) return res;

	//
	// JMP

	res = GH451_test_jmp(OPCODE_JMP_A);			// JMP abs
	if (res) return res;

	res = GH451_test_jmp(OPCODE_JMP_NA);		// JMP (abs)
	if (res) return res;

	if (bIs65C02)
	{
		regs.x = 0xff;
		res = GH451_test_jmp(OPCODE_JMP_IAX);	// JMP (abs),x
		if (res) return res;
	}

	//
	// Bcc

	res = GH451_test_Bcc();
	if (res) return res;

	return 0;
}

// debugger command 'bpm[r|w] addr16': JSR abs should not trigger a breakpoint at addr16
// . similarly for all other control flow opcodes (eg. Bcc, BRK, JMP, RTI, RTS)
int GH451_test(void)
{
	int res;

	g_aOpcodes = g_aOpcodes65C02;
	res = GH451_test_sub(true);
	if (res) return res;

	g_aOpcodes = g_aOpcodes6502;
	res = GH451_test_sub(false);

	return res;
}

//-------------------------------------

int _tmain(int argc, _TCHAR* argv[])
{
	int res = 1;
	init();
	reset();

	res = GH445_test();
	if (res) return res;

	res = GH451_test();
	if (res) return res;

	return 0;
}
