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

#pragma once

#include "fmt/core.h"

#define eeAddrInRange(name, addr) \
	(addr >= EEMemoryMap::name##_Start && addr < EEMemoryMap::name##_End)

static __ri bool _eelog_enabled( u32 addr )
{
	// Selective enable/disable ability for specific register maps
	if (eeAddrInRange(RCNT0, addr)) return false;
	if (eeAddrInRange(RCNT1, addr)) return true;
	if (eeAddrInRange(RCNT2, addr)) return true;
	if (eeAddrInRange(RCNT3, addr)) return true;

	if (eeAddrInRange(SBUS, addr)) return false;

	// INTC!
	if (addr == INTC_STAT || addr == INTC_MASK) return false;

	return true;
}

template< typename T>
static __ri const char* _eelog_GetHwName( u32 addr, T val )
{
#define EasyCase(label) case label: return #label

	switch( addr )
	{
		//  Counters!
		EasyCase(RCNT0_COUNT);
		EasyCase(RCNT0_MODE);
		EasyCase(RCNT0_TARGET);
		EasyCase(RCNT0_HOLD);

		EasyCase(RCNT1_COUNT);
		EasyCase(RCNT1_MODE);
		EasyCase(RCNT1_TARGET);
		EasyCase(RCNT1_HOLD);

		EasyCase(RCNT2_COUNT);
		EasyCase(RCNT2_MODE);
		EasyCase(RCNT2_TARGET);

		EasyCase(RCNT3_COUNT);
		EasyCase(RCNT3_MODE);
		EasyCase(RCNT3_TARGET);

		// IPU!
		EasyCase(IPU_CMD);
		EasyCase(IPU_CTRL);
		EasyCase(IPU_BP);
		EasyCase(IPU_TOP);

		// GIF!
		EasyCase(GIF_CTRL);
		EasyCase(GIF_MODE);
		EasyCase(GIF_STAT);
		EasyCase(GIF_TAG0);
		EasyCase(GIF_TAG1);
		EasyCase(GIF_TAG2);
		EasyCase(GIF_TAG3);
		EasyCase(GIF_CNT);
		EasyCase(GIF_P3CNT);
		EasyCase(GIF_P3TAG);

		// VIF!
		EasyCase(VIF0_STAT);
		EasyCase(VIF0_FBRST);
		EasyCase(VIF0_ERR);
		EasyCase(VIF0_MARK);
		EasyCase(VIF0_CYCLE);
		EasyCase(VIF0_MODE);
		EasyCase(VIF0_NUM);
		EasyCase(VIF0_MASK);
		EasyCase(VIF0_CODE);
		EasyCase(VIF0_ITOPS);
		EasyCase(VIF0_ITOP);
		EasyCase(VIF0_TOP);
		EasyCase(VIF0_ROW0);
		EasyCase(VIF0_ROW1);
		EasyCase(VIF0_ROW2);
		EasyCase(VIF0_ROW3);
		EasyCase(VIF0_COL0);
		EasyCase(VIF0_COL1);
		EasyCase(VIF0_COL2);
		EasyCase(VIF0_COL3);

		EasyCase(VIF1_STAT);
		EasyCase(VIF1_FBRST);
		EasyCase(VIF1_ERR);
		EasyCase(VIF1_MARK);
		EasyCase(VIF1_CYCLE);
		EasyCase(VIF1_MODE);
		EasyCase(VIF1_NUM);
		EasyCase(VIF1_MASK);
		EasyCase(VIF1_CODE);
		EasyCase(VIF1_ITOPS);
		EasyCase(VIF1_BASE);
		EasyCase(VIF1_OFST);
		EasyCase(VIF1_TOPS);
		EasyCase(VIF1_ITOP);
		EasyCase(VIF1_TOP);
		EasyCase(VIF1_ROW0);
		EasyCase(VIF1_ROW1);
		EasyCase(VIF1_ROW2);
		EasyCase(VIF1_ROW3);
		EasyCase(VIF1_COL0);
		EasyCase(VIF1_COL1);
		EasyCase(VIF1_COL2);
		EasyCase(VIF1_COL3);

		// VIF DMA!
		EasyCase(VIF0_CHCR);
		EasyCase(VIF0_MADR);
		EasyCase(VIF0_QWC);
		EasyCase(VIF0_TADR);
		EasyCase(VIF0_ASR0);
		EasyCase(VIF0_ASR1);

		EasyCase(VIF1_CHCR);
		EasyCase(VIF1_MADR);
		EasyCase(VIF1_QWC);
		EasyCase(VIF1_TADR);
		EasyCase(VIF1_ASR0);
		EasyCase(VIF1_ASR1);

		// GIF DMA!
		EasyCase(GIF_CHCR);
		EasyCase(GIF_MADR);
		EasyCase(GIF_QWC);
		EasyCase(GIF_TADR);
		EasyCase(GIF_ASR0);
		EasyCase(GIF_ASR1);

		// IPU DMA!
		EasyCase(fromIPU_CHCR);
		EasyCase(fromIPU_MADR);
		EasyCase(fromIPU_QWC);

		EasyCase(toIPU_CHCR);
		EasyCase(toIPU_MADR);
		EasyCase(toIPU_QWC);
		EasyCase(toIPU_TADR);

		// SIF DMA!
		EasyCase(SIF0_CHCR);
		EasyCase(SIF0_MADR);
		EasyCase(SIF0_QWC);

		EasyCase(SIF1_CHCR);
		EasyCase(SIF1_MADR);
		EasyCase(SIF1_QWC);
		EasyCase(SIF1_TADR);

		EasyCase(SIF2_CHCR);
		EasyCase(SIF2_MADR);
		EasyCase(SIF2_QWC);

		// Scratchpad DMA!  (SPRdma)

		EasyCase(fromSPR_CHCR);
		EasyCase(fromSPR_MADR);
		EasyCase(fromSPR_QWC);
		EasyCase(fromSPR_SADR);

		EasyCase(toSPR_CHCR);
		EasyCase(toSPR_MADR);
		EasyCase(toSPR_QWC);
		EasyCase(toSPR_TADR);
		EasyCase(toSPR_SADR);

		// DMAC!
		EasyCase(DMAC_CTRL);
		EasyCase(DMAC_STAT);
		EasyCase(DMAC_PCR);
		EasyCase(DMAC_SQWC);
		EasyCase(DMAC_RBSR);
		EasyCase(DMAC_RBOR);
		EasyCase(DMAC_STADR);
		EasyCase(DMAC_ENABLER);
		EasyCase(DMAC_ENABLEW);

		// INTC!
		EasyCase(INTC_STAT);
		EasyCase(INTC_MASK);

		// SIO
		EasyCase(SIO_LCR);
		EasyCase(SIO_LSR);
		EasyCase(SIO_IER);
		EasyCase(SIO_ISR);
		EasyCase(SIO_FCR);
		EasyCase(SIO_BGR);
		EasyCase(SIO_TXFIFO);
		EasyCase(SIO_RXFIFO);

		// SBUS (terribly mysterious!)
		EasyCase(SBUS_F200);
		EasyCase(SBUS_F210);
		EasyCase(SBUS_F220);
		EasyCase(SBUS_F230);
		EasyCase(SBUS_F240);
		EasyCase(SBUS_F250);
		EasyCase(SBUS_F260);

		// MCH (vaguely mysterious!)
		EasyCase(MCH_RICM);
		EasyCase(MCH_DRD);
	}

#define EasyZone(zone) \
	if ((addr >= EEMemoryMap::zone##_Start) && (addr < EEMemoryMap::zone##_End)) return #zone

#define EasyZoneR(zone) \
	EasyZone(zone) "_reserved"

	// Nothing discovered/handled : Check for "zoned" registers -- registers mirrored
	// across large sections of memory (FIFOs mainly).

	EasyZone(VIF0_FIFO);	EasyZone(VIF1_FIFO);	EasyZone(GIF_FIFO);

	if( (addr >= EEMemoryMap::IPU_FIFO_Start) && (addr < EEMemoryMap::IPU_FIFO_End) )
	{
		return (addr & 0x10) ? "IPUin_FIFO" : "IPUout_FIFO";
	}

	// Check for "reserved" regions -- registers that most likely discard writes and
	// return 0 when read.  To assist in having useful logs, we determine the general
	// "zone" of the register address and return the zone name in the unknown string.

	EasyZoneR(RCNT0);	EasyZoneR(RCNT1);
	EasyZoneR(RCNT2);	EasyZoneR(RCNT3);

	EasyZoneR(IPU);		EasyZoneR(GIF);
	EasyZoneR(VIF0);	EasyZoneR(VIF1);
	EasyZoneR(VIF0dma);	EasyZoneR(VIF1dma);
	EasyZoneR(GIFdma);	EasyZoneR(fromIPU);	EasyZoneR(toIPU);
	EasyZoneR(SIF0dma);	EasyZoneR(SIF1dma);	EasyZoneR(SIF2dma);
	EasyZoneR(fromSPR);	EasyZoneR(toSPR);

	EasyZoneR(DMAC);	EasyZoneR(INTC);
	EasyZoneR(SIO);		EasyZoneR(SBUS);
	EasyZoneR(MCH);		EasyZoneR(DMACext);

	// If we get this far it's an *unknown* register; plain and simple.

	return NULL; //"Unknown";
}

template< typename T>
static __ri void eeHwTraceLog( u32 addr, T val, bool mode )
{
	if (!IsDevBuild) return;
	if (!EmuConfig.Trace.Enabled) return;
	if (!_eelog_enabled(addr)) return;

	std::string labelStr(fmt::format("Hw{}{}", mode ? "Read" : "Write", sizeof(T) * 8));
	std::string valStr;

	if constexpr (sizeof(T) == 1)
	{
		valStr = fmt::format("0x{:02x}", val);
	}
	else if constexpr (sizeof(T) == 2)
	{
		valStr = fmt::format("0x{:04x}", val);
	}
	else if constexpr (sizeof(T) == 4)
	{
		valStr = fmt::format("0x{:08x}", val);
	}
	else if constexpr (sizeof(T) == 8)
	{
		valStr = fmt::format("0x{:08x}.{:08x}", static_cast<u32>(val >> 32), static_cast<u32>(val));
	}
	else if constexpr (sizeof(T) == 16)
	{
		valStr = StringUtil::U128ToString(r128_to_u128(val));
	}

	static const char* temp = "%-12s @ 0x%08X/%-16s %s %s";

	if( const char* regname = _eelog_GetHwName<T>( addr, val ) )
		HW_LOG( temp, labelStr.c_str(), addr, regname, mode ? "->" : "<-", valStr.c_str() );
	else
		UnknownHW_LOG( temp, labelStr.c_str(), addr, "Unknown", mode ? "->" : "<-", valStr.c_str() );
}
