// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+

#pragma once

#include "common/Assertions.h"
#include "common/StringUtil.h"

// Useful enums for some of the fields.
enum pce_values
{
	PCE_NOTHING = 0,
	PCE_RESERVED,
	PCE_DISABLED,
	PCE_ENABLED
};


enum tag_id
{
	TAG_CNTS = 0,
	TAG_REFE = 0, 	// Transfer Packet According to ADDR field, clear STR, and end
	TAG_CNT, 		// Transfer QWC following the tag.
	TAG_NEXT,		// Transfer QWC following tag. TADR = ADDR
	TAG_REF,			// Transfer QWC from ADDR field
	TAG_REFS,		// Transfer QWC from ADDR field (Stall Control)
	TAG_CALL,		// Transfer QWC following the tag, save succeeding tag
	TAG_RET,			// Transfer QWC following the tag, load next tag
	TAG_END			// Transfer QWC following the tag
};

enum mfd_type
{
	NO_MFD = 0,
	MFD_RESERVED,
	MFD_VIF1,
	MFD_GIF
};

enum sts_type
{
	NO_STS = 0,
	STS_SIF0,
	STS_fromSPR,
	STS_fromIPU
};

enum std_type
{
	NO_STD = 0,
	STD_VIF1,
	STD_GIF,
	STD_SIF1
};

enum LogicalTransferMode
{
	NORMAL_MODE = 0,
	CHAIN_MODE,
	INTERLEAVE_MODE,
	UNDEFINED_MODE
};

//
// --- DMA ---
//

// Doing double duty as both the top 32 bits *and* the lower 32 bits of a chain tag.
// Theoretically should probably both be in a u64 together, but with the way the
// code is layed out, this is easier for the moment.

union tDMA_TAG {
	struct {
		u32 QWC : 16;
		u32 _reserved2 : 10;
		u32 PCE : 2;
		u32 ID : 3;
		u32 IRQ : 1;
	};
	struct {
		u32 ADDR : 31;
		u32 SPR : 1;
	};
	u32 _u32;

	tDMA_TAG() = default;

	tDMA_TAG(u32 val) { _u32 = val; }
	u16 upper() const { return (_u32 >> 16); }
	u16 lower() const { return (u16)_u32; }
	std::string tag_to_str() const
	{
		switch(ID)
		{
			case TAG_REFE: return StringUtil::StdStringFromFormat("REFE %08X", _u32);
			case TAG_CNT: return "CNT";
			case TAG_NEXT: return StringUtil::StdStringFromFormat("NEXT %08X", _u32);
			case TAG_REF: return StringUtil::StdStringFromFormat("REF %08X", _u32);
			case TAG_REFS: return StringUtil::StdStringFromFormat("REFS %08X", _u32);
			case TAG_CALL: return "CALL";
			case TAG_RET: return "RET";
			case TAG_END: return "END";
			default: return "????";
		}
	}
	void reset() { _u32 = 0; }
};
#define DMA_TAG(value) ((tDMA_TAG)(value))

union tDMA_CHCR {
	struct {
		u32 DIR : 1;        // Direction: 0 - to memory, 1 - from memory. VIF1 & SIF2 only.
		u32 _reserved1 : 1;
		u32 MOD : 2;		// Logical transfer mode. Normal, Chain, or Interleave (see LogicalTransferMode enum)
		u32 ASP : 2;        // ASP1 & ASP2; Address stack pointer. 0, 1, or 2 addresses.
		u32 TTE : 1;        // Tag Transfer Enable. 0 - Disable / 1 - Enable.
		u32 TIE : 1;        // Tag Interrupt Enable. 0 - Disable / 1 - Enable.
		u32 STR : 1;        // Start. 0 while stopping DMA, 1 while it's running.
		u32 _reserved2 : 7;
		u32 TAG : 16;		// Maintains upper 16 bits of the most recently read DMAtag.
	};
	u32 _u32;

	tDMA_CHCR() = default;
	tDMA_CHCR( u32 val) { _u32 = val; }

	bool test(u32 flags) const { return !!(_u32 & flags); }
	void set(u32 value) { _u32 = value; }
	void set_flags(u32 flags) { _u32 |= flags; }
	void clear_flags(u32 flags) { _u32 &= ~flags; }
	void reset() { _u32 = 0; }
	u16 upper() const { return (_u32 >> 16); }
	u16 lower() const { return (u16)_u32; }
	std::string desc() const { return StringUtil::StdStringFromFormat("Chcr: 0x%x", _u32); }
	tDMA_TAG tag() { return (tDMA_TAG)_u32; }
};

#define CHCR(value) ((tDMA_CHCR)(value))

union tDMA_SADR {
	struct {
		u32 ADDR : 14;
		u32 reserved2 : 18;
	};
	u32 _u32;

	tDMA_SADR() = default;
	tDMA_SADR(u32 val) { _u32 = val; }

	void reset() { _u32 = 0; }
	std::string desc() const { return StringUtil::StdStringFromFormat("Sadr: 0x%x", _u32); }
	tDMA_TAG tag() const { return (tDMA_TAG)_u32; }
};

union tDMA_QWC {
	struct {
		u32 QWC;
	};
	u32 _u32;

	tDMA_QWC() = default;
	tDMA_QWC(u32 val) { _u32 = val; }

	void reset() { _u32 = 0; }
	std::string desc() const { return StringUtil::StdStringFromFormat("QWC: 0x%04x", QWC); }
	tDMA_TAG tag() const { return (tDMA_TAG)_u32; }
};

struct DMACh {
	tDMA_CHCR chcr;
	u32 _null0[3];
	u32 madr;
	u32 _null1[3];
	u32 qwc;
	u32 _null2[3];
	u32 tadr;
	u32 _null3[3];
	u32 asr0;
	u32 _null4[3];
	u32 asr1;
	u32 _null5[11];
	u32 sadr;

	void chcrTransfer(tDMA_TAG* ptag)
	{
	    chcr.TAG = ptag[0].upper();
	}

	void qwcTransfer(tDMA_TAG* ptag)
	{
	    qwc = ptag[0].QWC;
	}

	bool transfer(const char *s, tDMA_TAG* ptag);
	void unsafeTransfer(tDMA_TAG* ptag);
	tDMA_TAG *getAddr(u32 addr, u32 num, bool write);
	tDMA_TAG *DMAtransfer(u32 addr, u32 num);
	tDMA_TAG dma_tag();

	std::string cmq_to_str() const;
	std::string cmqt_to_str() const;
};

enum INTCIrqs
{
	INTC_GS = 0,
	INTC_SBUS,
	INTC_VBLANK_S,
	INTC_VBLANK_E,
	INTC_VIF0,
	INTC_VIF1,
	INTC_VU0,
	INTC_VU1,
	INTC_IPU,
	INTC_TIM0,
	INTC_TIM1,
	INTC_TIM2,
	INTC_TIM3,
	INTC_SFIFO,
	INTVU0_WD
};

enum dmac_conditions
{
	DMAC_STAT_SIS	= (1<<13),	 // stall condition
	DMAC_STAT_MEIS	= (1<<14),	 // mfifo empty
	DMAC_STAT_BEIS	= (1<<15),	 // bus error
	DMAC_STAT_SIM	= (1<<29),	 // stall mask
	DMAC_STAT_MEIM	= (1<<30)	 // mfifo mask
};

//DMA interrupts & masks
enum DMAInter
{
	BEISintr = 0x00008000,
	VIF0intr = 0x00010001,
	VIF1intr = 0x00020002,
	GIFintr =  0x00040004,
	IPU0intr = 0x00080008,
	IPU1intr = 0x00100010,
	SIF0intr = 0x00200020,
	SIF1intr = 0x00400040,
	SIF2intr = 0x00800080,
	SPR0intr = 0x01000100,
	SPR1intr = 0x02000200,
	SISintr  = 0x20002000,
	MEISintr = 0x40004000
};

union tDMAC_QUEUE
{
	struct
	{
	    u16 VIF0 : 1;
	    u16 VIF1 : 1;
	    u16 GIF  : 1;
	    u16 IPU0 : 1;
	    u16 IPU1 : 1;
	    u16 SIF0 : 1;
	    u16 SIF1 : 1;
	    u16 SIF2 : 1;
	    u16 SPR0 : 1;
        u16 SPR1 : 1;
	    u16 SIS  : 1;
	    u16 MEIS : 1;
	    u16 BEIS : 1;
	};
	u16 _u16;

	tDMAC_QUEUE() = default;
	tDMAC_QUEUE(u16 val) { _u16 = val; }
	void reset() { _u16 = 0; }
	bool empty() const { return (_u16 == 0); }
};

static __fi const char* ChcrName(u32 addr)
{
    switch (addr)
    {
        case D0_CHCR: return "Vif 0";
        case D1_CHCR: return "Vif 1";
        case D2_CHCR: return "GIF";
        case D3_CHCR: return "Ipu 0";
        case D4_CHCR: return "Ipu 1";
        case D5_CHCR: return "Sif 0";
        case D6_CHCR: return "Sif 1";
        case D7_CHCR: return "Sif 2";
        case D8_CHCR: return "SPR 0";
        case D9_CHCR: return "SPR 1";
        default: return "???";
    }
}

// Believe it or not, making this const can generate compiler warnings in gcc.
static __fi int ChannelNumber(u32 addr)
{
    switch (addr)
    {
        case D0_CHCR: return 0;
        case D1_CHCR: return 1;
        case D2_CHCR: return 2;
        case D3_CHCR: return 3;
        case D4_CHCR: return 4;
        case D5_CHCR: return 5;
        case D6_CHCR: return 6;
        case D7_CHCR: return 7;
        case D8_CHCR: return 8;
        case D9_CHCR: return 9;
		default:
		{
			pxFail("Invalid DMA channel number");
			return 51; // some value
		}
    }
}

union tDMAC_CTRL {
	struct {
		u32 DMAE : 1;       // 0/1 - disables/enables all DMAs
		u32 RELE : 1;       // 0/1 - cycle stealing off/on
		u32 MFD : 2;        // Memory FIFO drain channel (mfd_type)
		u32 STS : 2;        // Stall Control source channel (sts type)
		u32 STD : 2;        // Stall Control drain channel (std_type)
		u32 RCYC : 3;       // Release cycle (8/16/32/64/128/256)
		u32 _reserved1 : 21;
	};
	u32 _u32;

	tDMAC_CTRL() = default;
	tDMAC_CTRL(u32 val) { _u32 = val; }

	bool test(u32 flags) const { return !!(_u32 & flags); }
	bool is_mfifo(bool is_vif) const { return (is_vif) ? (MFD == MFD_VIF1) : (MFD == MFD_GIF); }
	void set_flags(u32 flags) { _u32 |= flags; }
	void clear_flags(u32 flags) { _u32 &= ~flags; }
	void reset() { _u32 = 0; }
	std::string desc() const { return StringUtil::StdStringFromFormat("Ctrl: 0x%x", _u32); }
};

union tDMAC_STAT {
	struct {
		u32 CIS : 10;
		u32 _reserved1 : 3;
		u32 SIS : 1;
		u32 MEIS : 1;
		u32 BEIS : 1;
		u32 CIM : 10;
		u32 _reserved2 : 3;
		u32 SIM : 1;
		u32 MEIM : 1;
		u32 _reserved3 : 1;
	};
	u32 _u32;
	u16 _u16[2];

	tDMAC_STAT() = default;
	tDMAC_STAT(u32 val) { _u32 = val; }

	bool test(u32 flags) const { return !!(_u32 & flags); }
	void set_flags(u32 flags) { _u32 |= flags; }
	void clear_flags(u32 flags) { _u32 &= ~flags; }
	void reset() { _u32 = 0; }
	std::string desc() const { return StringUtil::StdStringFromFormat("Stat: 0x%x", _u32); }

	bool TestForInterrupt() const
	{
		return ((_u16[0] & _u16[1]) != 0) || BEIS;
	}
};

union tDMAC_PCR {
	struct {
		u32 CPC : 10;
		u32 _reserved1 : 6;
		u32 CDE : 10;
		u32 _reserved2 : 5;
		u32 PCE : 1;
	};
	u32 _u32;

	tDMAC_PCR() = default;
	tDMAC_PCR(u32 val) { _u32 = val; }

	bool test(u32 flags) const { return !!(_u32 & flags); }
	void set_flags(u32 flags) { _u32 |= flags; }
	void clear_flags(u32 flags) { _u32 &= ~flags; }
	void reset() { _u32 = 0; }
	std::string desc() const { return StringUtil::StdStringFromFormat("Pcr: 0x%x", _u32); }
};

union tDMAC_SQWC {
	struct {
		u32 SQWC : 8;
		u32 _reserved1 : 8;
		u32 TQWC : 8;
		u32 _reserved2 : 8;
	};
	u32 _u32;

	tDMAC_SQWC() = default;
	tDMAC_SQWC(u32 val) { _u32 = val; }

	bool test(u32 flags) const { return !!(_u32 & flags); }
	void set_flags(u32 flags) { _u32 |= flags; }
	void clear_flags(u32 flags) { _u32 &= ~flags; }
	void reset() { _u32 = 0; }
	std::string desc() const { return StringUtil::StdStringFromFormat("Sqwc: 0x%x", _u32); }
};

union tDMAC_RBSR {
	struct {
		u32 RMSK : 31;
		u32 _reserved1 : 1;
	};
	u32 _u32;

	tDMAC_RBSR() = default;
	tDMAC_RBSR(u32 val) { _u32 = val; }

	void reset() { _u32 = 0; }
	std::string desc() const { return StringUtil::StdStringFromFormat("Rbsr: 0x%x", _u32); }
};

union tDMAC_RBOR {
	struct {
		u32 ADDR : 31;
		u32 _reserved1 : 1;
	};
	u32 _u32;

	tDMAC_RBOR() = default;
	tDMAC_RBOR(u32 val) { _u32 = val; }

	void reset() { _u32 = 0; }
	std::string desc() const { return StringUtil::StdStringFromFormat("Rbor: 0x%x", _u32); }
};

// --------------------------------------------------------------------------------------
//  tDMAC_ADDR
// --------------------------------------------------------------------------------------
// This struct is used for several DMA address types, including some that do not have
// effective SPR bit (the bit is ignored for all addresses that are not "allowed" to access
// the scratchpad, including STADR, toSPR.MADR, fromSPR.MADR, etc.).
//
union tDMAC_ADDR
{
	struct {
		u32 ADDR : 31;	// Transfer memory address
		u32 SPR : 1;	// Memory/SPR Address (only effective for MADR and TADR of non-SPR DMAs)
	};
	u32 _u32;

	tDMAC_ADDR() = default;
	tDMAC_ADDR(u32 val) { _u32 = val; }

	void clear() { _u32 = 0; }

	void AssignADDR(uint addr)
	{
		ADDR = addr;
		if (SPR) ADDR &= (Ps2MemSize::Scratch-1);
	}

	void IncrementQWC(uint incval = 1)
	{
		ADDR += incval;
		if (SPR) ADDR &= (Ps2MemSize::Scratch-1);
	}

	std::string ToString(bool sprIsValid=true) const
	{
		return StringUtil::StdStringFromFormat((sprIsValid && SPR) ? "0x%04X(SPR)" : "0x%08X", ADDR);
	}
};

struct DMACregisters
{
	tDMAC_CTRL	ctrl;
	u32 _padding[3];
	tDMAC_STAT	stat;
	u32 _padding1[3];
	tDMAC_PCR	pcr;
	u32 _padding2[3];

	tDMAC_SQWC	sqwc;
	u32 _padding3[3];
	tDMAC_RBSR	rbsr;
	u32 _padding4[3];
	tDMAC_RBOR	rbor;
	u32 _padding5[3];
	tDMAC_ADDR	stadr;
	u32 _padding6[3];
};

// Currently guesswork.
union tINTC_STAT {
	struct {
		u32 interrupts : 10;
	    u32 _placeholder : 22;
	};
	u32 _u32;

	tINTC_STAT() = default;
	tINTC_STAT(u32 val) { _u32 = val; }

	bool test(u32 flags) const { return !!(_u32 & flags); }
	void set_flags(u32 flags) { _u32 |= flags; }
	void clear_flags(u32 flags) { _u32 &= ~flags; }
	void reset() { _u32 = 0; }
	std::string desc() const { return StringUtil::StdStringFromFormat("Stat: 0x%x", _u32); }
};

union tINTC_MASK {
	struct {
	    u32 int_mask : 10;
	    u32 _placeholder:22;
	};
	u32 _u32;

	tINTC_MASK() = default;
	tINTC_MASK(u32 val) { _u32 = val; }

	bool test(u32 flags) const { return !!(_u32 & flags); }
	void set_flags(u32 flags) { _u32 |= flags; }
	void clear_flags(u32 flags) { _u32 &= ~flags; }
	void reset() { _u32 = 0; }
	std::string desc() const { return StringUtil::StdStringFromFormat("Mask: 0x%x", _u32); }
};

struct INTCregisters
{
	tINTC_STAT  stat;
	u32 _padding1[3];
	tINTC_MASK  mask;
	u32 _padding2[3];
};

#define intcRegs ((INTCregisters*)(eeHw+0xF000))

static DMACregisters& dmacRegs	= (DMACregisters&)eeHw[0xE000];

// Various useful locations
static DMACh& vif0ch	= (DMACh&)eeHw[0x8000];
static DMACh& vif1ch	= (DMACh&)eeHw[0x9000];
static DMACh& gifch		= (DMACh&)eeHw[0xA000];
static DMACh& spr0ch	= (DMACh&)eeHw[0xD000];
static DMACh& spr1ch	= (DMACh&)eeHw[0xD400];
static DMACh& ipu0ch	= (DMACh&)eeHw[0xb000];
static DMACh& ipu1ch	= (DMACh&)eeHw[0xb400];
static DMACh& sif0ch	= (DMACh&)eeHw[0xc000];
static DMACh& sif1ch	= (DMACh&)eeHw[0xc400];
static DMACh& sif2dma	= (DMACh&)eeHw[0xc800];

extern void throwBusError(const char *s);
extern void setDmacStat(u32 num);
extern tDMA_TAG *SPRdmaGetAddr(u32 addr, bool write);
extern tDMA_TAG *dmaGetAddr(u32 addr, bool write);

extern void hwIntcIrq(int n);
extern void hwDmacIrq(int n);

extern void FireMFIFOEmpty();
extern bool hwMFIFOWrite(u32 addr, const u128* data, uint size_qwc);
extern void hwMFIFOResume(u32 transferred);
extern void hwDmacSrcTadrInc(DMACh& dma);
extern bool hwDmacSrcChainWithStack(DMACh& dma, int id);
extern bool hwDmacSrcChain(DMACh& dma, int id);

template< uint page > u32 dmacRead32( u32 mem );
template< uint page > extern bool dmacWrite32( u32 mem, mem32_t& value );
