#if (defined(DUMMYSPC) && !defined(__DUMMYSPC__H)) || (!defined(DUMMYSPC) && !defined(__SPC__H))
#ifdef DUMMYSPC
#define __DUMMYSPC__H
#else
#define __SPC__H
#endif

#include "stdafx.h"
#include "SpcTypes.h"
#include "CpuTypes.h"
#include "SpcTimer.h"
#include "../Utilities/ISerializable.h"

class Console;
class MemoryManager;
class SpcFileData;
class SPC_DSP;
struct AddressInfo;

class Spc : public ISerializable
{
public:
	static constexpr int SpcRamSize = 0x10000;
	static constexpr int SpcRomSize = 0x40;
	static constexpr int SpcSampleRate = 32040;

private:
	static constexpr int SampleBufferSize = 0x20000;
	static constexpr uint16_t ResetVector = 0xFFFE;

	Console* _console;
	MemoryManager* _memoryManager;
	unique_ptr<SPC_DSP> _dsp;

	double _clockRatio;

	/* Temporary data used in the middle of operations */
	uint16_t _operandA;
	uint16_t _operandB;
	uint16_t _tmp1;
	uint16_t _tmp2;
	uint16_t _tmp3;
	uint8_t _opCode;
	SpcOpStep _opStep;
	uint8_t _opSubStep;

	bool _enabled;

	SpcState _state;
	uint8_t* _ram;
	uint8_t _spcBios[64] {
		0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0,
		0xFC, 0x8F, 0xAA, 0xF4, 0x8F, 0xBB, 0xF5, 0x78,
		0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4,
		0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5,
		0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB,
		0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA,
		0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4, 0xDD,
		0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF
	};

	int16_t *_soundBuffer;

	//Store operations
	void STA();
	void STX();
	void STY();
	void STW();

	void STA_AutoIncX();
	void LDA_AutoIncX();

	//Load operations
	void LDA();
	void LDA_Imm();
	void LDX();
	void LDX_Imm();
	void LDY();
	void LDY_Imm();
	void LDW();

	//Transfer
	void Transfer(uint8_t & dst, uint8_t src);
	void TXA();
	void TYA();
	void TAX();
	void TAY();
	void TSX();
	void TXS();

	void STC();
	void LDC();
	void MOV();
	void MOV_Imm();

	//Arithmetic
	uint8_t Add(uint8_t a, uint8_t b);
	uint8_t Sub(uint8_t a, uint8_t b);

	void ADC();
	void ADC_Acc();
	void ADC_Imm();
	void ADDW();
	void SBC();
	void SBC_Acc();
	void SBC_Imm();
	void SUBW();

	void Compare(uint8_t a, uint8_t b);
	void CMP();
	void CMP_Acc();
	void CMP_Imm();
	void CPX();
	void CPX_Imm();
	void CPY();
	void CPY_Imm();
	void CMPW();

	//Increment/decrement
	void INC();
	void INC_Acc();
	void INX();
	void INY();
	void INCW();

	void DEC();
	void DEC_Acc();
	void DEX();
	void DEY();
	void DECW();

	void MUL();
	void DIV();

	//Decimal
	void DAA();
	void DAS();

	//Logical
	void AND();
	void AND_Acc();
	void AND_Imm();
	void OR();
	void OR_Acc();
	void OR_Imm();
	void EOR();
	void EOR_Acc();
	void EOR_Imm();

	void SetCarry(uint8_t carry);
	void OR1();
	void NOR1();
	void AND1();
	void NAND1();
	void EOR1();
	void NOT1();

	//Shift/rotate
	uint8_t ShiftLeft(uint8_t value);
	uint8_t RollLeft(uint8_t value);
	uint8_t ShiftRight(uint8_t value);
	uint8_t RollRight(uint8_t value);

	void ASL();
	void ASL_Acc();
	void LSR();
	void LSR_Acc();
	void ROL();
	void ROL_Acc();
	void ROR();
	void ROR_Acc();
	void XCN();

	//Branch operations
	void Branch();
	void BRA();
	void BEQ();
	void BNE();
	void BCS();
	void BCC();
	void BVS();
	void BVC();
	void BMI();
	void BPL();
	void BBS();
	void BBC();
	void CBNE();
	void DBNZ();
	void DBNZ_Y();
	void JMP();

	//Flag operations
	void CLRC();
	void SETC();
	void NOTC();
	void CLRV();
	void CLRP();
	void SETP();
	void EI();
	void DI();

	void TSET1();
	void TCLR1();

	template<uint8_t bit> void SET1();
	template<uint8_t bit> void CLR1();
	template<uint8_t bit> void BBS();
	template<uint8_t bit> void BBC();

	//Subroutine operations
	void PCALL();
	void JSR();
	void RTS();
	void BRK();
	void RTI();

	template<uint8_t offset> void TCALL();

	//Stack operations
	void PushOperation(uint8_t value);
	void PullOperation(uint8_t & dst);

	void PHA();
	void PHX();
	void PHY();
	void PHP();

	void PLA();
	void PLX();
	void PLY();
	void PLP();

	//Other operations
	void NOP();
	void SLEEP();
	void STOP();
	
	void Idle();
	void DummyRead();
	void DummyRead(uint16_t addr);
	uint8_t Read(uint16_t addr, MemoryOperationType type = MemoryOperationType::Read);
	uint16_t ReadWord(uint16_t addr, MemoryOperationType type = MemoryOperationType::Read);
	void Write(uint16_t addr, uint8_t value, MemoryOperationType type = MemoryOperationType::Write);

	void Addr_Dir();
	void Addr_DirIdxX();
	void Addr_DirIdxY();
	void Addr_DirToDir();
	void Addr_DirImm();
	void Addr_IndX();
	void Addr_IndXToIndY();
	void Addr_Abs();
	void Addr_AbsBit();
	void Addr_AbsIdxX();
	void Addr_AbsIdxY();
	void Addr_AbsIdxXInd();
	void Addr_DirIdxXInd();
	void Addr_DirIndIdxY();
	void Addr_Rel();
	void Addr_Imm();

	void ClearFlags(uint8_t flags);
	void SetFlags(uint8_t flags);
	bool CheckFlag(uint8_t flag);
	void SetZeroNegativeFlags(uint8_t value);
	void SetZeroNegativeFlags16(uint16_t value);

	uint8_t GetByteValue();

	void Push(uint8_t value);
	uint8_t Pop();

	uint16_t GetDirectAddress(uint8_t offset);

	uint8_t GetOpCode();
	uint8_t ReadOperandByte();

	void IncCycleCount(int32_t addr);
	void EndOp();
	void EndAddr();
	void ProcessCycle();
	void Exec();
	
	void UpdateClockRatio();

public:
	Spc(Console* console);
	virtual ~Spc();

	void SetSpcState(bool enabled);

	void Run();
	void Reset();

	uint8_t DebugRead(uint16_t addr);
	void DebugWrite(uint16_t addr, uint8_t value);

	uint8_t CpuReadRegister(uint16_t addr);
	void CpuWriteRegister(uint32_t addr, uint8_t value);

	uint8_t DspReadRam(uint16_t addr);
	void DspWriteRam(uint16_t addr, uint8_t value);

	void ProcessEndFrame();

	SpcState GetState();
	DspState GetDspState();

	bool IsMuted();
	AddressInfo GetAbsoluteAddress(uint16_t addr);
	int GetRelativeAddress(AddressInfo & absAddress);

	uint8_t* GetSpcRam();
	uint8_t* GetSpcRom();

	void LoadSpcFile(SpcFileData* spcData);

	void Serialize(Serializer &s) override;

#ifdef DUMMYSPC
private:
	uint32_t _writeCounter = 0;
	uint32_t _writeAddresses[10];
	uint8_t _writeValue[10];

	uint32_t _readCounter = 0;
	uint32_t _readAddresses[10];
	uint8_t _readValue[10];

	void LogRead(uint32_t addr, uint8_t value);
	void LogWrite(uint32_t addr, uint8_t value);

public:
	DummySpc(uint8_t *spcRam, SpcState &state);

	void Step();

	void SetDummyState(SpcState &state);
	int32_t GetLastOperand();

	uint32_t GetWriteCount();
	uint32_t GetReadCount();
	void GetWriteInfo(uint32_t index, uint32_t &addr, uint8_t &value);
	void GetReadInfo(uint32_t index, uint32_t &addr, uint8_t &value);
#endif
};

#endif