/*
  AppleWin : An Apple //e emulator for Windows

  Copyright (C) 1994-1996, Michael O'Brien
  Copyright (C) 1999-2001, Oliver Schmidt
  Copyright (C) 2002-2005, Tom Charlesworth
  Copyright (C) 2006-2025, Tom Charlesworth, Michael Pohoreski

  AppleWin 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 2 of the License, or
  (at your option) any later version.

  AppleWin 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 AppleWin; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
/*
  Breakpoint card
  ---------------

  For debugger breakpoint regression tests.

  C0n0 (R) : Status
				b7: BP match
				b6: BP mismatch
				b1: FIFO full
				b0: FIFO empty
  C0n1 (R) : Cmd: Reset
				Flush FIFO
				NB. don't change intercept mode
  C0n2 (R) : Cmd: Intercept BP by card
  C0n3 (R) : Cmd: Intercept BP by debugger
  C0nX (R) : ID byte $0X (except when X=0)

  C0nX (W) : FIFO
				FIFO is 32 x 6 bytes deep
				where: 6-byte BP-set = {type.b, addrStart.w, addrEnd.w, access.b}

  Any r/w clears the IRQ
  RESET clears the IRQ, flushes FIFO and sets: Intercept BP by card

  Operation:
  . Cmd:Reset flushes FIFO & sets Status.empty=1 (all other bits are zero)
  . Cmd:Intercept BP by card
  . Write FIFO with multiple BP-sets for test code
    - these are the expected BP hit results
  . Status.full=1 after writing 32 BP-sets
  . Execute test code
  . AppleWin debugger:
    - Cmd:Intercept BP by card: when active, then debugger doesn't break on BP match
    - Instead debugger hands off BP to Breakpoint card (only 'bp' & 'bpm[r|w]')
  . Remove BP-set from front of FIFO
    - Check if it matches/mismatches and set status accordingly
  . Delay 1 cycle & assert IRQ
  . CPU vectors to IRQ handler
  . Reading Status clears IRQ
*/

#include "StdAfx.h"

#include "CardManager.h"
#include "Core.h"
#include "CPU.h"
#include "Debug.h"
#include "BreakpointCard.h"
#include "Memory.h"
#include "YamlHelper.h"

BreakpointCard::BreakpointCard(UINT slot) :
	Card(CT_BreakpointCard, slot),
	m_syncEvent(slot, 0, SyncEventCallback)	// use slot# as "unique" id for Disk2InterfaceCards
{
	Reset(true);
}

BreakpointCard::~BreakpointCard()
{
	if (m_syncEvent.m_active)
		g_SynchronousEventMgr.Remove(m_syncEvent.m_id);
}

void BreakpointCard::Reset(const bool powerCycle)
{
	m_interceptBPByCard = false;
	InterceptBreakpoints(m_slot, nullptr);
	ResetState();
	if (!powerCycle)	// if called by ctor: CriticalSection not created yet
		CpuIrqDeassert(IS_BREAKPOINTCARD);
}

void BreakpointCard::InitializeIO(LPBYTE pCxRomPeripheral)
{
	RegisterIoHandler(m_slot, &BreakpointCard::IORead, &BreakpointCard::IOWrite, IO_Null, IO_Null, this, NULL);
}

BYTE __stdcall BreakpointCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles)
{
	const UINT slot = ((addr & 0xff) >> 4) - 8;
	BreakpointCard* pCard = (BreakpointCard*)MemGetSlotParameters(slot);

	CpuIrqDeassert(IS_BREAKPOINTCARD);

	const BYTE reg = addr & 0xf;
	if (reg == 0)
	{
		uint8_t otherStatus = pCard->m_BP_FIFO.empty() ? kEmpty : 0;
		return pCard->m_status | otherStatus;
	}
	else if (reg == 1)	// Cmd: Reset
	{
		pCard->ResetState();
	}
	else if (reg == 2)	// Cmd: Intercept BP by card
	{
		pCard->m_interceptBPByCard = true;
		InterceptBreakpoints(slot, BreakpointCard::CbFunction);
	}
	else if (reg == 3)	// Cmd: Intercept BP by debugger
	{
		pCard->m_interceptBPByCard = false;
		InterceptBreakpoints(slot, nullptr);
	}

	return addr & 0x0f;	// ID bytes [$01-$0F]
}

BYTE __stdcall BreakpointCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles)
{
	const UINT slot = ((addr & 0xff) >> 4) - 8;
	BreakpointCard* pCard = (BreakpointCard*)MemGetSlotParameters(slot);

	CpuIrqDeassert(IS_BREAKPOINTCARD);

	pCard->m_BPSet[pCard->m_BPSetIdx++] = value;

	if (pCard->m_BPSetIdx == kNumParams)
	{
		pCard->m_BPSetIdx = 0;

		if (!(pCard->m_status & kFull))
		{
			INTERCEPTBREAKPOINT bpSet;
			bpSet.type = pCard->m_BPSet[0];
			bpSet.addrStart = (pCard->m_BPSet[2] << 8) | pCard->m_BPSet[1];
			bpSet.addrEnd   = (pCard->m_BPSet[4] << 8) | pCard->m_BPSet[3];
			bpSet.access = pCard->m_BPSet[5];

			pCard->m_BP_FIFO.push(bpSet);
		}

		if (pCard->m_BP_FIFO.size() == kFIFO_SIZE)
			pCard->m_status |= kFull;
	}

	return 0;
}

void BreakpointCard::CbFunction(uint8_t slot, INTERCEPTBREAKPOINT interceptBreakpoint)
{
	BreakpointCard* pCard = (BreakpointCard*)MemGetSlotParameters(slot);

	pCard->m_deferred = interceptBreakpoint;

	// Defer processing the breakpoint by 1 opcode (ie. 1 cycle), otherwise this happens:
	// . BP occurs at <addr>, but opcode hasn't executed yet
	// . Breakpoint card aserts IRQ, and IRQ is taken by 6502
	// . 6502 executes ISR, and returns to <addr>
	// . BP occurs at <addr>...

	pCard->m_syncEvent.m_cyclesRemaining = 1;	// Next opcode
	g_SynchronousEventMgr.Insert(&pCard->m_syncEvent);
}

int BreakpointCard::SyncEventCallback(int id, int cycles, ULONG uExecutedCycles)
{
	BreakpointCard& card = dynamic_cast<BreakpointCard&>(GetCardMgr().GetRef(id));
	card.Deferred(card.m_deferred.type, card.m_deferred.addrStart, card.m_deferred.addrEnd, card.m_deferred.access);
	return 0;	// Don't repeat event
}

void BreakpointCard::Deferred(uint8_t type, uint16_t addrStart, uint16_t addrEnd, uint8_t access)
{
	m_status &= ~(kMatch | kMismatch);

	CpuIrqAssert(IS_BREAKPOINTCARD);

	if (m_BP_FIFO.empty())
	{
		m_status |= kMismatch;
		return;
	}

	const INTERCEPTBREAKPOINT bpSet = m_BP_FIFO.front();
	m_BP_FIFO.pop();
	m_status &= ~kFull;

	if (bpSet.type != type)
	{
		m_status |= kMismatch;
		return;
	}

	switch (type)
	{
	case BPTYPE_PC:
	case BPTYPE_MEM:
		if ((bpSet.addrStart != addrStart) || (bpSet.access != access))
		{
			m_status |= kMismatch;
			break;
		}
		m_status |= kMatch;
		break;
	case BPTYPE_DMA:
		{
			bool validAddr = ((addrStart >= bpSet.addrStart && addrStart < bpSet.addrEnd) ||
							  (bpSet.addrStart >= addrStart && bpSet.addrStart < addrEnd));
			if (!validAddr || (bpSet.access != access))
			{
				m_status |= kMismatch;
				break;
			}
			m_status |= kMatch;
		}
		break;
	case BPTYPE_UNKNOWN:
	default:
		m_status |= kMismatch;
		break;
	}

}

//===========================================================================

static const UINT kUNIT_VERSION = 1;

const std::string& BreakpointCard::GetSnapshotCardName(void)
{
	static const std::string name("Breakpoint");
	return name;
}

void BreakpointCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
{
	YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION);

	YamlSaveHelper::Label unit(yamlSaveHelper, "%s: null\n", SS_YAML_KEY_STATE);
	// NB. No state for this card
}

bool BreakpointCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
{
	if (version < 1 || version > kUNIT_VERSION)
		ThrowErrorInvalidVersion(version);

	return true;
}
