#pragma once

class SY6522
{
public:
	SY6522(UINT slot, bool isMegaAudio) : m_slot(slot), m_isMegaAudio(isMegaAudio), m_isBusDriven(false), m_bad6522(false)
	{
		for (UINT i = 0; i < kNumTimersPer6522; i++)
			m_syncEvent[i] = NULL;
		Reset(true);
	}

	~SY6522(void)
	{
	}

	void InitSyncEvents(class SyncEvent* event0, class SyncEvent* event1)
	{
		m_syncEvent[0] = event0;
		m_syncEvent[1] = event1;
	}

	void InitBadState(bool bad6522)
	{
		m_bad6522 = bad6522;
	}

	void Reset(const bool powerCycle);

	void StartTimer1(void);
	void StopTimer1(void);
	bool IsTimer1Active(void) { return m_timer1Active; }
	void StopTimer2(void);
	bool IsTimer2Active(void) { return m_timer2Active; }

	void UpdateIFR(BYTE clr_ifr, BYTE set_ifr = 0);

	void UpdateTimer1(USHORT clocks);
	void UpdateTimer2(USHORT clocks);

	enum { rORB = 0, rORA, rDDRB, rDDRA, rT1CL, rT1CH, rT1LL, rT1LH, rT2CL, rT2CH, rSR, rACR, rPCR, rIFR, rIER, rORA_NO_HS, SIZE_6522_REGS };

	BYTE GetReg(BYTE reg)
	{
		switch (reg)
		{
		case rDDRA: return m_regs.DDRA;
		case rORA: return m_regs.ORA;
		case rACR: return m_regs.ACR;
		case rPCR: return m_regs.PCR;
		case rIFR: return m_regs.IFR;
		}
		_ASSERT(0);
		return 0;
	}
	BYTE GetBusViewOfORB(void) { return m_regs.ORB & m_regs.DDRB; }	// Return how the AY8913 sees ORB on the bus (ie. not CPU's view which will be OR'd with !DDRB)
	USHORT GetRegT1C(void) { return m_regs.TIMER1_COUNTER.w; }
	USHORT GetRegT2C(void) { return m_regs.TIMER2_COUNTER.w; }
	void GetRegs(BYTE regs[SIZE_6522_REGS]) { memcpy(&regs[0], (BYTE*)&m_regs, SIZE_6522_REGS); }	// For debugger
	void SetRegIRA(BYTE reg) { m_regs.ORA = reg; }
	bool IsTimer1IrqDelay(void) { return m_timer1IrqDelay ? true : false; }
	void SetBusBeingDriven(bool state) { m_isBusDriven = state; }
	bool IsBad(void) { return m_bad6522; }

	BYTE Read(BYTE nReg);
	void Write(BYTE nReg, BYTE nValue);

	void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper);
	void LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT version);
	void SetTimersActiveFromSnapshot(bool timer1Active, bool timer2Active, UINT version);

	// ACR
	static const BYTE ACR_RUNMODE = 1 << 6;
	static const BYTE ACR_RM_ONESHOT = 0 << 6;
	static const BYTE ACR_RM_FREERUNNING = 1 << 6;

	// IFR & IER:
	static const BYTE IxR_SSI263 = 1 << 1;
	static const BYTE IxR_VOTRAX = 1 << 4;
	static const BYTE IxR_TIMER2 = 1 << 5;
	static const BYTE IxR_TIMER1 = 1 << 6;
	static const BYTE IFR_IRQ = 1 << 7;

	static const UINT kExtraTimerCycles = 2;	// Rockwell, Fig.16: period = N+2 cycles
	static const UINT kNumTimersPer6522 = 2;

private:
	USHORT SetTimerSyncEvent(BYTE reg, USHORT timerLatch);

	USHORT GetTimer1Counter(BYTE reg);
	USHORT GetTimer2Counter(BYTE reg);
	bool IsTimer1Underflowed(BYTE reg);
	bool IsTimer2Underflowed(BYTE reg);

	bool CheckTimerUnderflow(USHORT& counter, int& timerIrqDelay, const USHORT clocks);
	int OnTimer1Underflow(USHORT& counter);

	UINT GetOpcodeCyclesForRead(BYTE reg);
	UINT GetOpcodeCyclesForWrite(BYTE reg);
	UINT GetOpcodeCycles(BYTE reg, UINT zpOpcodeCycles, UINT opcodeCycles, BYTE zpOpcode, BYTE opcode, bool abs16x, bool abs16y, bool indx, bool indy);

	void StartTimer2(void);
	void StartTimer1_LoadStateV1(void);

#pragma pack(push)
#pragma pack(1)	// Ensure 'struct Regs' is packed so that GetRegs() can just do a memcpy()

	struct IWORD
	{
		union
		{
			struct
			{
				BYTE l;
				BYTE h;
			};
			USHORT w;
		};
	};

	struct Regs
	{
		BYTE ORB;				// $00 - Port B
		BYTE ORA;				// $01 - Port A (with handshaking)
		BYTE DDRB;				// $02 - Data Direction Register B
		BYTE DDRA;				// $03 - Data Direction Register A
		IWORD TIMER1_COUNTER;	// $04 - Read counter (L) / Write latch (L)
								// $05 - Read / Write & initiate count (H)
		IWORD TIMER1_LATCH;		// $06 - Read / Write & latch (L)
								// $07 - Read / Write & latch (H)
		IWORD TIMER2_COUNTER;	// $08 - Read counter (L) / Write latch (L)
								// $09 - Read counter (H) / Write latch (H)
		BYTE SERIAL_SHIFT;		// $0A
		BYTE ACR;				// $0B - Auxiliary Control Register
		BYTE PCR;				// $0C - Peripheral Control Register
		BYTE IFR;				// $0D - Interrupt Flag Register
		BYTE IER;				// $0E - Interrupt Enable Register
		BYTE ORA_NO_HS;			// $0F - Port A (without handshaking)
		//
		IWORD TIMER2_LATCH;		// Doesn't exist in 6522
	};

#pragma pack(pop)

	Regs m_regs;

	int m_timer1IrqDelay;
	int m_timer2IrqDelay;
	bool m_timer1Active;
	bool m_timer2Active;

	class SyncEvent* m_syncEvent[kNumTimersPer6522];
	UINT m_slot;
	bool m_isMegaAudio;
	bool m_isBusDriven;

	static const UINT kExtraMegaAudioTimerCycles = kExtraTimerCycles + 1;

	// For mb-audit
	bool m_bad6522;
};
