/*
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-2021, Tom Charlesworth, Michael Pohoreski, Nick Westgate

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
*/

/* Description: Rockwell 6522 VIA emulation
 *
 * Author: Various
 */

#include "StdAfx.h"

#include "6522.h"
#include "CardManager.h"
#include "Core.h"
#include "CPU.h"
#include "Memory.h"
#include "SynchronousEventManager.h"
#include "YamlHelper.h"

void SY6522::Reset(const bool powerCycle)
{
	if (powerCycle)
	{
		memset(&m_regs, 0, sizeof(Regs));
		m_regs.TIMER1_LATCH.w = 0xffff;	// Some random value (but pick $ffff so it's deterministic)
										// . NB. if it's too small (< ~$0007) then MB detection routines will fail!
		m_isBusDriven = false;
	}

	CpuCreateCriticalSection();	// Reset() called by SY6522 global ctor, so explicitly create CPU's CriticalSection
	Write(rDDRB, 0x00);	// DDRB = 0x00: all pins are inputs
	Write(rDDRA, 0x00);	// DDRA = 0x00: all pins are inputs
	Write(rACR, 0x00);	// ACR = 0x00: T1 one-shot mode
	Write(rPCR, 0x00);	// PCR = 0x00: CB2/CA2 Control = Input (negative active edge)
	Write(rIFR, 0x7f);	// IFR = 0x7F: de-assert any IRQs
	Write(rIER, 0x7f);	// IER = 0x7F: disable all IRQs

	StopTimer1();
	StopTimer2();

	m_timer1IrqDelay = m_timer2IrqDelay = 0;
}

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

void SY6522::StartTimer1(void)
{
	m_timer1Active = true;
}

// The assumption was that timer1 was only active if IER.TIMER1=1
// . Not true, since IFR can be polled (with IER.TIMER1=0)
void SY6522::StartTimer1_LoadStateV1(void)
{
	if ((m_regs.IER & IxR_TIMER1) == 0x00)
		return;

	m_timer1Active = true;
}

void SY6522::StopTimer1(void)
{
	m_timer1Active = false;
}

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

void SY6522::StartTimer2(void)
{
	m_timer2Active = true;
}

void SY6522::StopTimer2(void)
{
	m_timer2Active = false;
}

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

// Insert a new synchronous event whenever the 6522 timer's counter is written.
// . NB. it doesn't matter if the timer's interrupt enable (IER) is set or not
//   - the state of IER is only important when the counter underflows - see: MB_SyncEventCallback()
USHORT SY6522::SetTimerSyncEvent(BYTE reg, USHORT timerLatch)
{
	_ASSERT(reg == rT1CH || reg == rT2CH);
	SyncEvent* syncEvent = reg == rT1CH ? m_syncEvent[0] : m_syncEvent[1];

	// NB. This TIMER adjustment value gets subtracted when this current opcode completes, so no need to persist to save-state
	const UINT opcodeCycleAdjust = GetOpcodeCyclesForWrite(reg);

	if (syncEvent->m_active)
		g_SynchronousEventMgr.Remove(syncEvent->m_id);

	if (m_isMegaAudio)
	{
		if (reg == rT1CH && timerLatch == 0x0000)
			timerLatch = 0xFFFF;	// MegaAudio && T1.LATCH=0: use 0xFFFF (or maybe 0x10000?)
		syncEvent->SetCycles(timerLatch + kExtraMegaAudioTimerCycles + opcodeCycleAdjust);	// MegaAudio asserts IRQ 1 cycle late!
	}
	else
	{
		syncEvent->SetCycles(timerLatch + kExtraTimerCycles + opcodeCycleAdjust);
	}
	g_SynchronousEventMgr.Insert(syncEvent);

	// It doesn't matter if this overflows (ie. >0xFFFF), since on completion of current opcode it'll be corrected
	return (USHORT)(timerLatch + opcodeCycleAdjust);
}

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

void SY6522::UpdateIFR(BYTE clr_ifr, BYTE set_ifr /*= 0*/)
{
	m_regs.IFR &= ~clr_ifr;
	m_regs.IFR |= set_ifr;

	if (m_regs.IFR & m_regs.IER & 0x7F)
		m_regs.IFR |= IFR_IRQ;
	else
		m_regs.IFR &= ~IFR_IRQ;

	if (GetCardMgr().GetObj(m_slot))	// If called from MockingboardCard ctor, then CardManager::m_slot[slot] == NULL
		GetCardMgr().GetMockingboardCardMgr().UpdateIRQ();
}

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

void SY6522::Write(BYTE nReg, BYTE nValue)
{
	switch (nReg)
	{
	case 0x00:	// ORB
		m_regs.ORB = nValue & m_regs.DDRB;
		break;
	case 0x01:	// ORA
	case 0x0f:	// ORA_NO_HS
		m_regs.ORA = nValue & m_regs.DDRA;
		break;
	case 0x02:	// DDRB
		m_regs.DDRB = nValue;
		break;
	case 0x03:	// DDRA
		m_regs.DDRA = nValue;
		break;
	case 0x04:	// TIMER1L_COUNTER
	case 0x06:	// TIMER1L_LATCH
		m_regs.TIMER1_LATCH.l = nValue;
		break;
	case 0x05:	// TIMER1H_COUNTER
	{
		UpdateIFR(IxR_TIMER1);				// Clear Timer1 Interrupt Flag
		m_regs.TIMER1_LATCH.h = nValue;
		m_regs.TIMER1_COUNTER.w = SetTimerSyncEvent(nReg, m_regs.TIMER1_LATCH.w);
		StartTimer1();
	}
	break;
	case 0x07:	// TIMER1H_LATCH
		UpdateIFR(IxR_TIMER1);				// Clear Timer1 Interrupt Flag
		m_regs.TIMER1_LATCH.h = nValue;
		break;
	case 0x08:	// TIMER2L
		m_regs.TIMER2_LATCH.l = nValue;
		break;
	case 0x09:	// TIMER2H
	{
		UpdateIFR(IxR_TIMER2);				// Clear Timer2 Interrupt Flag
		m_regs.TIMER2_LATCH.h = nValue;		// NB. Real 6522 doesn't have TIMER2_LATCH.h
		m_regs.TIMER2_COUNTER.w = SetTimerSyncEvent(nReg, m_regs.TIMER2_LATCH.w);
		StartTimer2();
	}
	break;
	case 0x0a:	// SERIAL_SHIFT
		break;
	case 0x0b:	// ACR
		m_regs.ACR = nValue;
		break;
	case 0x0c:	// PCR -  Used for Speech chip only
		m_regs.PCR = nValue;
		break;
	case 0x0d:	// IFR
		// - Clear those bits which are set in the lower 7 bits.
		// - Can't clear bit 7 directly.
		UpdateIFR(nValue);
		break;
	case 0x0e:	// IER
		if (!(nValue & 0x80))
		{
			// Clear those bits which are set in the lower 7 bits.
			nValue ^= 0x7F;
			m_regs.IER &= nValue;
		}
		else
		{
			// Set those bits which are set in the lower 7 bits.
			nValue &= 0x7F;
			m_regs.IER |= nValue;
		}
		if (m_syncEvent[0])
			m_syncEvent[0]->m_canAssertIRQ = (m_regs.IER & IxR_TIMER1) ? true : false;
		if (m_syncEvent[1])
			m_syncEvent[1]->m_canAssertIRQ = (m_regs.IER & IxR_TIMER2) ? true : false;
		UpdateIFR(0);
		break;
	}
}

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

void SY6522::UpdateTimer1(USHORT clocks)
{
	if (CheckTimerUnderflow(m_regs.TIMER1_COUNTER.w, m_timer1IrqDelay, clocks))
		m_timer1IrqDelay = OnTimer1Underflow(m_regs.TIMER1_COUNTER.w);
}

void SY6522::UpdateTimer2(USHORT clocks)
{
	// No TIMER2 latch so "after timing out, the counter will continue to decrement"
	CheckTimerUnderflow(m_regs.TIMER2_COUNTER.w, m_timer2IrqDelay, clocks);
}

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

bool SY6522::CheckTimerUnderflow(USHORT& counter, int& timerIrqDelay, const USHORT clocks)
{
	if (clocks == 0)
		return false;

	int oldTimer = counter;
	int timer = counter;
	timer -= clocks;
	counter = (USHORT)timer;

	bool timerIrq = false;

	if (timerIrqDelay)	// Deal with any previous counter underflow which didn't yet result in an IRQ
	{
		_ASSERT(timerIrqDelay == 1);
		timerIrqDelay = 0;
		timerIrq = true;
		// if LATCH is very small then could underflow for every opcode...
	}

	if (oldTimer >= 0 && timer < 0)	// Underflow occurs for 0x0000 -> 0xFFFF
	{
		if (m_isMegaAudio)
		{
			if (timer <= -3)				// TIMER = 0xFFFD (or less)
				timerIrq = true;
			else							// TIMER = 0xFFFF or 0xFFFE
				timerIrqDelay = 1;			// ...so 1 or 2 cycles until IRQ
		}
		else
		{
			if (timer <= -2)				// TIMER = 0xFFFE (or less)
				timerIrq = true;
			else							// TIMER = 0xFFFF
				timerIrqDelay = 1;			// ...so 1 cycle until IRQ
		}

	}

	return timerIrq;
}

int SY6522::OnTimer1Underflow(USHORT& counter)
{
	int timer = (int)(short)(counter);
	if (m_isMegaAudio)
	{
		const UINT timerLatch = m_regs.TIMER1_LATCH.w ? m_regs.TIMER1_LATCH.w : 0xFFFF;	// MegaAudio && T1.LATCH=0: use 0xFFFF (or maybe 0x10000?)
		while (timer < -2)
			timer += (timerLatch + kExtraMegaAudioTimerCycles);		// MegaAudio asserts IRQ 1 cycle late!
	}
	else
	{
		while (timer < -1)
			timer += (m_regs.TIMER1_LATCH.w + kExtraTimerCycles);	// GH#651: account for underflowed cycles / GH#652: account for extra 2 cycles
	}
	counter = (USHORT)timer;
	return (timer < 0) ? 1 : 0;			// timer1IrqDelay
}

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

USHORT SY6522::GetTimer1Counter(BYTE reg)
{
	USHORT counter = m_regs.TIMER1_COUNTER.w;	// NB. don't update the real T1C
	int timerIrqDelay = m_timer1IrqDelay;		// NB. don't update the real timer1IrqDelay
	const UINT opcodeCycleAdjust = GetOpcodeCyclesForRead(reg) - 1;	// to compensate for the 4/5/6 cycle read opcode
	if (CheckTimerUnderflow(counter, timerIrqDelay, opcodeCycleAdjust))
		OnTimer1Underflow(counter);
	return counter;
}

USHORT SY6522::GetTimer2Counter(BYTE reg)
{
	const UINT opcodeCycleAdjust = GetOpcodeCyclesForRead(reg) - 1;	// to compensate for the 4/5/6 cycle read opcode
	return m_regs.TIMER2_COUNTER.w - opcodeCycleAdjust;
}

bool SY6522::IsTimer1Underflowed(BYTE reg)
{
	USHORT counter = m_regs.TIMER1_COUNTER.w;	// NB. don't update the real T1C
	int timerIrqDelay = m_timer1IrqDelay;		// NB. don't update the real timer1IrqDelay
	const UINT opcodeCycleAdjust = GetOpcodeCyclesForRead(reg);	// to compensate for the 4/5/6 cycle read opcode
	return CheckTimerUnderflow(counter, timerIrqDelay, opcodeCycleAdjust);
}

bool SY6522::IsTimer2Underflowed(BYTE reg)
{
	USHORT counter = m_regs.TIMER2_COUNTER.w;	// NB. don't update the real T2C
	int timerIrqDelay = m_timer2IrqDelay;		// NB. don't update the real timer2IrqDelay
	const UINT opcodeCycleAdjust = GetOpcodeCyclesForRead(reg);	// to compensate for the 4/5/6 cycle read opcode
	return CheckTimerUnderflow(counter, timerIrqDelay, opcodeCycleAdjust);
}

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

BYTE SY6522::Read(BYTE nReg)
{
	BYTE nValue = 0x00;

	switch (nReg)
	{
	case 0x00:	// IRB
		nValue = m_regs.ORB | (m_regs.DDRB ^ 0xff);	// Input bits read back as 1's (GH#1260)
		if (m_isMegaAudio) nValue = 0x00;			// MegaAudio: IRB just reads as $00
		break;
	case 0x01:	// IRA
	case 0x0f:	// IRA_NO_HS
		nValue = m_regs.ORA | (m_isBusDriven ? 0x00 : (m_regs.DDRA ^ 0xff));	// NB. Inputs bits driven by AY8913 if in PSG READ mode
		if (m_isMegaAudio) nValue = 0x00;			// MegaAudio: IRA just reads as $00
		break;
	case 0x02:	// DDRB
		nValue = m_regs.DDRB;
		if (m_bad6522)
			nValue &= ~1;	// DDRB.b0 = 0 (for testing mb-audit)
		break;
	case 0x03:	// DDRA
		nValue = m_regs.DDRA;
		break;
	case 0x04:	// TIMER1L_COUNTER
		// NB. GH#701 (T1C:=0xFFFF, LDA T1C_L[4cy], A==0xFC)
		nValue = GetTimer1Counter(nReg) & 0xff;
		UpdateIFR(IxR_TIMER1);
		break;
	case 0x05:	// TIMER1H_COUNTER
		nValue = GetTimer1Counter(nReg) >> 8;
		break;
	case 0x06:	// TIMER1L_LATCH
		nValue = m_regs.TIMER1_LATCH.l;
		break;
	case 0x07:	// TIMER1H_LATCH
		nValue = m_regs.TIMER1_LATCH.h;
		break;
	case 0x08:	// TIMER2L
		nValue = GetTimer2Counter(nReg) & 0xff;
		UpdateIFR(IxR_TIMER2);
		if (m_isMegaAudio) nValue = 0xFF;	// MegaAudio: Timer2 just reads as $00FF
		break;
	case 0x09:	// TIMER2H
		nValue = GetTimer2Counter(nReg) >> 8;
		if (m_isMegaAudio) nValue = 0x00;	// MegaAudio: Timer2 just reads as $00FF
		break;
	case 0x0a:	// SERIAL_SHIFT
		break;
	case 0x0b:	// ACR
		nValue = m_regs.ACR;
		break;
	case 0x0c:	// PCR
		nValue = m_regs.PCR;
		break;
	case 0x0d:	// IFR
		nValue = m_regs.IFR;
		if (m_timer1Active && IsTimer1Underflowed(nReg))
			nValue |= IxR_TIMER1;
		if (m_timer2Active && IsTimer2Underflowed(nReg))
			nValue |= IxR_TIMER2;
		break;
	case 0x0e:	// IER
		nValue = 0x80 | m_regs.IER;	// GH#567
		if (m_isMegaAudio)
			nValue &= 0x7F;
		break;
	}

	return nValue;
}

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

// TODO: RMW opcodes: dec,inc,asl,lsr,rol,ror (abs16 & abs16,x) + 65C02 trb,tsb (abs16)
UINT SY6522::GetOpcodeCyclesForRead(BYTE reg)
{
	UINT zpOpcodeCycles = 0, opcodeCycles = 0;
	BYTE zpOpcode = 0, opcode = 0;	// these double-up as flags to indicate validity
	bool abs16x = false;
	bool abs16y = false;
	bool indx = false;
	bool indy = false;

	const BYTE opcodeMinus3 = mem[(::regs.pc - 3) & 0xffff];
	const BYTE opcodeMinus2 = mem[(::regs.pc - 2) & 0xffff];

	// Check 2-byte opcodes
	if (((opcodeMinus2 & 0x0f) == 0x01) && ((opcodeMinus2 & 0x10) == 0x00))	// ora (zp,x), and (zp,x), ..., sbc (zp,x)
	{
		// NB. this is for read, so don't need to exclude 0x81 / sta (zp,x)
		zpOpcodeCycles = 6;
		zpOpcode = opcodeMinus2;
		indx = true;
	}
	else if (((opcodeMinus2 & 0x0f) == 0x01) && ((opcodeMinus2 & 0x10) == 0x10))	// ora (zp),y, and (zp),y, ..., sbc (zp),y
	{
		// NB. this is for read, so don't need to exclude 0x91 / sta (zp),y
		zpOpcodeCycles = 5;
		zpOpcode = opcodeMinus2;
		indy = true;
	}
	else if (((opcodeMinus2 & 0x0f) == 0x02) && ((opcodeMinus2 & 0x10) == 0x10) && GetMainCpu() == CPU_65C02)	// ora (zp), and (zp), ..., sbc (zp) : 65C02-only
	{
		// NB. this is for read, so don't need to exclude 0x92 / sta (zp)
		zpOpcodeCycles = 5;
		zpOpcode = opcodeMinus2;
	}

	// Check 3-byte opcodes
	if ((((opcodeMinus3 & 0x0f) == 0x0D) && ((opcodeMinus3 & 0x10) == 0x00)) ||	// ora abs16, and abs16, ..., sbc abs16
		(opcodeMinus3 == 0x2C) ||			// bit abs16
		(opcodeMinus3 == 0xAC) ||			// ldy abs16
		(opcodeMinus3 == 0xAE) ||			// ldx abs16
		(opcodeMinus3 == 0xCC) ||			// cpy abs16
		(opcodeMinus3 == 0xEC))				// cpx abs16
	{
		opcodeCycles = 4;
		opcode = opcodeMinus3;
	}
	else if ((opcodeMinus3 == 0xBC) ||			// ldy abs16,x
		((opcodeMinus3 == 0x3C) && GetMainCpu() == CPU_65C02))		// bit abs16,x : 65C02-only
	{
		opcodeCycles = 4;
		opcode = opcodeMinus3;
		abs16x = true;
	}
	else if ((opcodeMinus3 == 0xBE))			// ldx abs16,y
	{
		opcodeCycles = 4;
		opcode = opcodeMinus3;
		abs16y = true;
	}
	else if ((opcodeMinus3 & 0x10) == 0x10)
	{
		if ((opcodeMinus3 & 0x0f) == 0x0D)		// ora abs16,x, and abs16,x, ..., sbc abs16,x
		{
			opcodeCycles = 4;
			opcode = opcodeMinus3;
			abs16x = true;
		}
		else if ((opcodeMinus3 & 0x0f) == 0x09) // ora abs16,y, and abs16,y, ..., sbc abs16,y
		{
			opcodeCycles = 4;
			opcode = opcodeMinus3;
			abs16y = true;
		}
	}

	//

	if (!opcode && !zpOpcode)	// Unsupported opcode
	{
		_ASSERT(0);
		return 0;
	}

	return GetOpcodeCycles(reg, zpOpcodeCycles, opcodeCycles, zpOpcode, opcode, abs16x, abs16y, indx, indy);
}

// TODO: RMW opcodes: dec,inc,asl,lsr,rol,ror (abs16 & abs16,x) + 65C02 trb,tsb (abs16)
UINT SY6522::GetOpcodeCyclesForWrite(BYTE reg)
{
	UINT zpOpcodeCycles = 0, opcodeCycles = 0;
	BYTE zpOpcode = 0, opcode = 0;	// these double-up as flags to indicate validity
	bool abs16x = false;
	bool abs16y = false;
	bool indx = false;
	bool indy = false;

	const BYTE opcodeMinus3 = mem[(::regs.pc - 3) & 0xffff];
	const BYTE opcodeMinus2 = mem[(::regs.pc - 2) & 0xffff];

	// Check 2-byte opcodes
	if (opcodeMinus2 == 0x81)			// sta (zp,x)
	{
		zpOpcodeCycles = 6;
		zpOpcode = opcodeMinus2;
		indx = true;
	}
	else if (opcodeMinus2 == 0x91)		// sta (zp),y
	{	// Eg. FT demos: OMT, PLS
		zpOpcodeCycles = 6;
		zpOpcode = opcodeMinus2;
		indy = true;
	}
	else if (opcodeMinus2 == 0x92 && GetMainCpu() == CPU_65C02)		// sta (zp) : 65C02-only
	{
		zpOpcodeCycles = 5;
		zpOpcode = opcodeMinus2;
	}

	// Check 3-byte opcodes
	if ((opcodeMinus3 == 0x8C) ||		// sty abs16
		(opcodeMinus3 == 0x8D) ||		// sta abs16
		(opcodeMinus3 == 0x8E))			// stx abs16
	{	// Eg. FT demos: CHIP, MADEF, MAD2
		opcodeCycles = 4;
		opcode = opcodeMinus3;
	}
	else if (opcodeMinus3 == 0x99)		// sta abs16,y
	{
		opcodeCycles = 5;
		opcode = opcodeMinus3;
		abs16y = true;
	}
	else if (opcodeMinus3 == 0x9D)		// sta abs16,x
	{	// Eg. Paleotronic microTracker demo
		opcodeCycles = 5;
		opcode = opcodeMinus3;
		abs16x = true;
	}
	else if (opcodeMinus3 == 0x9C && GetMainCpu() == CPU_65C02)		// stz abs16 : 65C02-only
	{
		opcodeCycles = 4;
		opcode = opcodeMinus3;
	}
	else if (opcodeMinus3 == 0x9E && GetMainCpu() == CPU_65C02)		// stz abs16,x : 65C02-only
	{
		opcodeCycles = 5;
		opcode = opcodeMinus3;
		abs16x = true;
	}

	//

	if (!opcode && !zpOpcode)	// Unsupported opcode
	{
		_ASSERT(0);
		return 0;
	}

	return GetOpcodeCycles(reg, zpOpcodeCycles, opcodeCycles, zpOpcode, opcode, abs16x, abs16y, indx, indy);
}

UINT SY6522::GetOpcodeCycles(BYTE reg, UINT zpOpcodeCycles, UINT opcodeCycles,
								BYTE zpOpcode, BYTE opcode,
								bool abs16x, bool abs16y, bool indx, bool indy)
{
	WORD zpAddr16 = 0, addr16 = 0;

	if (zpOpcode)
	{
		BYTE zp = mem[(::regs.pc - 1) & 0xffff];
		if (indx) zp += ::regs.x;
		zpAddr16 = (mem[zp] | (mem[(zp + 1) & 0xff] << 8));
		if (indy) zpAddr16 += ::regs.y;
	}

	if (opcode)
	{
		addr16 = mem[(::regs.pc - 2) & 0xffff] | (mem[(::regs.pc - 1) & 0xffff] << 8);
		if (abs16y) addr16 += ::regs.y;
		if (abs16x) addr16 += ::regs.x;
	}

	// Check we've reverse looked-up the 6502 opcode correctly
	const bool isZpAddrValid = (zpAddr16 & 0xF80F) == (0xC000 + reg);
	const bool isAbs16AddrValid = (addr16 & 0xF80F) == (0xC000 + reg);

	if ( (isZpAddrValid && isAbs16AddrValid)
		|| (!isZpAddrValid && !isAbs16AddrValid) )
	{
		_ASSERT(0);
		return 0;
	}

	return isZpAddrValid ? zpOpcodeCycles : opcodeCycles;
}

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

#define SS_YAML_KEY_SY6522 "SY6522"
// NB. No version - this is determined by the parent "Mockingboard C" or "Phasor" unit

#define SS_YAML_KEY_SY6522_REG_ORB "ORB"
#define SS_YAML_KEY_SY6522_REG_ORA "ORA"
#define SS_YAML_KEY_SY6522_REG_DDRB "DDRB"
#define SS_YAML_KEY_SY6522_REG_DDRA "DDRA"
#define SS_YAML_KEY_SY6522_REG_T1_COUNTER "Timer1 Counter"
#define SS_YAML_KEY_SY6522_REG_T1_LATCH "Timer1 Latch"
#define SS_YAML_KEY_SY6522_REG_T2_COUNTER "Timer2 Counter"
#define SS_YAML_KEY_SY6522_REG_T2_LATCH "Timer2 Latch"
#define SS_YAML_KEY_SY6522_REG_SERIAL_SHIFT "Serial Shift"
#define SS_YAML_KEY_SY6522_REG_ACR "ACR"
#define SS_YAML_KEY_SY6522_REG_PCR "PCR"
#define SS_YAML_KEY_SY6522_REG_IFR "IFR"
#define SS_YAML_KEY_SY6522_REG_IER "IER"

#define SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY "Timer1 IRQ Delay"
#define SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY "Timer2 IRQ Delay"
#define SS_YAML_KEY_SY6522_TIMER1_ACTIVE "Timer1 Active"
#define SS_YAML_KEY_SY6522_TIMER2_ACTIVE "Timer2 Active"

void SY6522::SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
{
	YamlSaveHelper::Label label(yamlSaveHelper, "%s:\n", SS_YAML_KEY_SY6522);

	yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_ORB, m_regs.ORB);
	yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_ORA, m_regs.ORA);
	yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_DDRB, m_regs.DDRB);
	yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_DDRA, m_regs.DDRA);
	yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T1_COUNTER, m_regs.TIMER1_COUNTER.w);
	yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T1_LATCH, m_regs.TIMER1_LATCH.w);
	yamlSaveHelper.SaveUint(SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY, m_timer1IrqDelay);	// v4
	yamlSaveHelper.SaveBool(SS_YAML_KEY_SY6522_TIMER1_ACTIVE, m_timer1Active);		// v8
	yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T2_COUNTER, m_regs.TIMER2_COUNTER.w);
	yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T2_LATCH, m_regs.TIMER2_LATCH.w);
	yamlSaveHelper.SaveUint(SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY, m_timer2IrqDelay);	// v4
	yamlSaveHelper.SaveBool(SS_YAML_KEY_SY6522_TIMER2_ACTIVE, m_timer2Active);		// v8
	yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_SERIAL_SHIFT, m_regs.SERIAL_SHIFT);
	yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_ACR, m_regs.ACR);
	yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_PCR, m_regs.PCR);
	yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_IFR, m_regs.IFR);
	yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_IER, m_regs.IER);
	// NB. No need to write ORA_NO_HS, since same data as ORA, just without handshake
}

void SY6522::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
{
	if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_SY6522))
		throw std::runtime_error("Card: Expected key: " SS_YAML_KEY_SY6522);

	m_regs.ORB = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_ORB);
	m_regs.ORA = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_ORA);
	m_regs.DDRB = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_DDRB);
	m_regs.DDRA = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_DDRA);
	m_regs.TIMER1_COUNTER.w = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_T1_COUNTER);
	m_regs.TIMER1_LATCH.w = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_T1_LATCH);
	m_regs.TIMER2_COUNTER.w = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_T2_COUNTER);
	m_regs.TIMER2_LATCH.w = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_T2_LATCH);
	m_regs.SERIAL_SHIFT = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_SERIAL_SHIFT);
	m_regs.ACR = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_ACR);
	m_regs.PCR = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_PCR);
	m_regs.IFR = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_IFR);
	m_regs.IER = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_IER);
	m_regs.ORA_NO_HS = 0;	// Not saved

	m_timer1IrqDelay = m_timer2IrqDelay = 0;

	if (version >= 4)
	{
		m_timer1IrqDelay = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY);
		m_timer2IrqDelay = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY);
	}

	if (version < 7)
	{
		// Assume t1_latch was never written to (so had the old default of 0x0000) - this now results in failure of Mockingboard detection!
		if (m_regs.TIMER1_LATCH.w == 0x0000)
			m_regs.TIMER1_LATCH.w = 0xFFFF;		// Allow Mockingboard detection to succeed
	}

	if (version >= 8)
	{
		bool timer1Active = yamlLoadHelper.LoadBool(SS_YAML_KEY_SY6522_TIMER1_ACTIVE);
		bool timer2Active = yamlLoadHelper.LoadBool(SS_YAML_KEY_SY6522_TIMER2_ACTIVE);
		SetTimersActiveFromSnapshot(timer1Active, timer2Active, version);
	}

	yamlLoadHelper.PopMap();
}

void SY6522::SetTimersActiveFromSnapshot(bool timer1Active, bool timer2Active, UINT version)
{
	m_timer1Active = timer1Active;
	m_timer2Active = timer2Active;

	//

	if (version == 1)
	{
		StartTimer1_LoadStateV1();	// Attempt to start timer
	}
	else	// version >= 2
	{
		if (IsTimer1Active())
			StartTimer1();			// Attempt to start timer
	}

	if (IsTimer1Active())
	{
		SyncEvent* syncEvent = m_syncEvent[0];
		syncEvent->SetCycles(GetRegT1C() + kExtraTimerCycles);	// NB. use COUNTER, not LATCH
		syncEvent->m_canAssertIRQ = (m_regs.IER & IxR_TIMER1) ? true : false;
		g_SynchronousEventMgr.Insert(syncEvent);
	}
	if (IsTimer2Active())
	{
		SyncEvent* syncEvent = m_syncEvent[1];
		syncEvent->SetCycles(GetRegT2C() + kExtraTimerCycles);	// NB. use COUNTER, not LATCH
		syncEvent->m_canAssertIRQ = (m_regs.IER & IxR_TIMER2) ? true : false;
		g_SynchronousEventMgr.Insert(syncEvent);
	}
}
