#include <assert.h>
#include <cstring>
#include "Iop_Dmac.h"
#include "Iop_Intc.h"
#include "../states/RegisterStateFile.h"
#include "Log.h"

#define LOG_NAME ("iop_dmac")

#define STATE_REGS_XML ("iop_dmac/regs.xml")
#define STATE_REGS_DPCR ("DPCR")
#define STATE_REGS_DPCR2 ("DPCR2")
#define STATE_REGS_DPCR3 ("DPCR3")
#define STATE_REGS_DICR ("DICR")

using namespace Iop;
using namespace Iop::Dmac;

CDmac::CDmac(uint8* ram, CIntc& intc)
    : m_ram(ram)
    , m_intc(intc)
    , m_channelSpu0(CH4_BASE, CHANNEL_SPU0, Iop::CIntc::LINE_DMA_SPU0, *this)
    , m_channelSpu1(CH7_BASE, CHANNEL_SPU1, Iop::CIntc::LINE_DMA_SPU1, *this)
    , m_channelDev9(CH8_BASE, CHANNEL_DEV9, Iop::CIntc::LINE_DMA_DEV9, *this)
    , m_channelSio2In(CH11_BASE, CHANNEL_SIO2in, Iop::CIntc::LINE_DMA_SIO2in, *this)
    , m_channelSio2Out(CH12_BASE, CHANNEL_SIO2out, Iop::CIntc::LINE_DMA_SIO2out, *this)
{
	memset(m_channel, 0, sizeof(m_channel));
	m_channel[CHANNEL_SPU0] = &m_channelSpu0;
	m_channel[CHANNEL_SPU1] = &m_channelSpu1;
	m_channel[CHANNEL_DEV9] = &m_channelDev9;
	//Not enabled, only used for testing purposes
	//m_channel[CHANNEL_SIO2in] = &m_channelSio2In;
	//m_channel[CHANNEL_SIO2out] = &m_channelSio2Out;
	Reset();
}

void CDmac::Reset()
{
	m_DPCR = 0;
	m_DPCR2 = 0;
	m_DPCR3 = 0;
	m_DICR = 0;

	for(unsigned int i = 0; i < MAX_CHANNEL; i++)
	{
		CChannel* channel(m_channel[i]);
		if(!channel) continue;
		channel->Reset();
	}
}

void CDmac::SetReceiveFunction(unsigned int channelId, const CChannel::ReceiveFunctionType& handler)
{
	assert(channelId < MAX_CHANNEL);
	if(channelId >= MAX_CHANNEL) return;
	CChannel* channel(m_channel[channelId]);
	if(channel)
	{
		channel->SetReceiveFunction(handler);
	}
}

unsigned int CDmac::GetChannelIdFromAddress(uint32 address)
{
	unsigned int channelId = -1;
	if(address >= DMAC_ZONE2_START)
	{
		channelId = (address - DMAC_ZONE2_START) / 0x10;
		channelId += 7;
	}
	else if(address >= CH0_BASE && address < DPCR)
	{
		channelId = (address - DMAC_ZONE1_START) / 0x10;
	}
	return channelId;
}

CChannel* CDmac::GetChannelFromAddress(uint32 address)
{
	unsigned int channelId = GetChannelIdFromAddress(address);
	assert(channelId < MAX_CHANNEL);
	if(channelId >= MAX_CHANNEL) return NULL;
	return m_channel[channelId];
}

void CDmac::ResumeDma(unsigned int channelIdx)
{
	auto channel = m_channel[channelIdx];
	assert(channel != nullptr);
	if(channel == nullptr)
	{
		CLog::GetInstance().Warn(LOG_NAME, "Trying to resume DMA for channel %d, but the channel is not implemented.\r\n",
		                         channelIdx);
		return;
	}
	channel->ResumeDma();
}

void CDmac::AssertLine(unsigned int line)
{
	if(line < 7)
	{
		m_DICR |= 1 << (line + 24);
	}
	m_intc.AssertLine(CIntc::LINE_DMAC);
	m_intc.AssertLine(CIntc::LINE_DMA_BASE + line);
}

uint8* CDmac::GetRam()
{
	return m_ram;
}

uint32 CDmac::ReadRegister(uint32 address)
{
#ifdef _DEBUG
	LogRead(address);
#endif
	switch(address)
	{
	case DPCR:
		return m_DPCR;
		break;
	case DPCR2:
		return m_DPCR2;
		break;
	case DPCR3:
		return m_DPCR3;
		break;
	case DICR:
		return m_DICR;
		break;
	default:
	{
		CChannel* channel(GetChannelFromAddress(address));
		if(channel)
		{
			return channel->ReadRegister(address);
		}
		else
		{
			CLog::GetInstance().Warn(LOG_NAME, "Unknown DMA channel register read at 0x%08X.\r\n",
			                         address);
		}
	}
	break;
	}
	return 0;
}

uint32 CDmac::WriteRegister(uint32 address, uint32 value)
{
#ifdef _DEBUG
	LogWrite(address, value);
#endif
	switch(address)
	{
	case DPCR:
		m_DPCR = value;
		break;
	case DPCR2:
		m_DPCR2 = value;
		break;
	case DPCR3:
		m_DPCR3 = value;
		break;
	case DICR:
		m_DICR &= 0xFF000000;
		m_DICR |= value;
		m_DICR &= ~(value & 0xFF000000);
		break;
	default:
	{
		CChannel* channel(GetChannelFromAddress(address));
		if(channel)
		{
			channel->WriteRegister(address, value);
		}
		else
		{
			CLog::GetInstance().Warn(LOG_NAME, "Unknown DMA channel register write at 0x%08X.\r\n",
			                         address);
		}
	}
	break;
	}
	return 0;
}

void CDmac::LoadState(Framework::CZipArchiveReader& archive)
{
	{
		auto registerFile = CRegisterStateFile(*archive.BeginReadFile(STATE_REGS_XML));
		m_DPCR = registerFile.GetRegister32(STATE_REGS_DPCR);
		m_DPCR2 = registerFile.GetRegister32(STATE_REGS_DPCR2);
		m_DPCR3 = registerFile.GetRegister32(STATE_REGS_DPCR3);
		m_DICR = registerFile.GetRegister32(STATE_REGS_DICR);
	}

	for(unsigned int i = 0; i < MAX_CHANNEL; i++)
	{
		auto channel = m_channel[i];
		if(!channel) continue;
		channel->LoadState(archive);
	}
}

void CDmac::SaveState(Framework::CZipArchiveWriter& archive)
{
	{
		auto registerFile = std::make_unique<CRegisterStateFile>(STATE_REGS_XML);
		registerFile->SetRegister32(STATE_REGS_DPCR, m_DPCR);
		registerFile->SetRegister32(STATE_REGS_DPCR2, m_DPCR2);
		registerFile->SetRegister32(STATE_REGS_DPCR3, m_DPCR3);
		registerFile->SetRegister32(STATE_REGS_DICR, m_DICR);
		archive.InsertFile(std::move(registerFile));
	}

	for(unsigned int i = 0; i < MAX_CHANNEL; i++)
	{
		auto channel = m_channel[i];
		if(!channel) continue;
		channel->SaveState(archive);
	}
}

void CDmac::LogRead(uint32 address)
{
	switch(address)
	{
	case DPCR:
		CLog::GetInstance().Print(LOG_NAME, "= DPCR.\r\n");
		break;
	case DPCR2:
		CLog::GetInstance().Print(LOG_NAME, "= DPCR2.\r\n");
		break;
	case DPCR3:
		CLog::GetInstance().Print(LOG_NAME, "= DPCR3.\r\n");
		break;
	case DICR:
		CLog::GetInstance().Print(LOG_NAME, "= DICR.\r\n");
		break;
	default:
	{
		unsigned int channelId = GetChannelIdFromAddress(address);
		unsigned int registerId = address & 0xF;
		switch(registerId)
		{
		case CChannel::REG_MADR:
			CLog::GetInstance().Print(LOG_NAME, "ch%02d: = MADR.\r\n", channelId);
			break;
		case CChannel::REG_CHCR:
			CLog::GetInstance().Print(LOG_NAME, "ch%02d: = CHCR.\r\n", channelId);
			break;
		default:
			CLog::GetInstance().Warn(LOG_NAME, "Read an unknown register 0x%08X.\r\n",
			                         address);
			break;
		}
	}
	break;
	}
}

void CDmac::LogWrite(uint32 address, uint32 value)
{
	switch(address)
	{
	case DPCR:
		CLog::GetInstance().Print(LOG_NAME, "DPCR = 0x%08X.\r\n", value);
		break;
	case DPCR2:
		CLog::GetInstance().Print(LOG_NAME, "DPCR2 = 0x%08X.\r\n", value);
		break;
	case DPCR3:
		CLog::GetInstance().Print(LOG_NAME, "DPCR3 = 0x%08X.\r\n", value);
		break;
	case DICR:
		CLog::GetInstance().Print(LOG_NAME, "DICR = 0x%08X.\r\n", value);
		break;
	default:
	{
		unsigned int channelId = GetChannelIdFromAddress(address);
		unsigned int registerId = address & 0xF;
		switch(registerId)
		{
		case CChannel::REG_MADR:
			CLog::GetInstance().Print(LOG_NAME, "ch%02d: MADR = 0x%08X.\r\n", channelId, value);
			break;
		case CChannel::REG_BCR:
			CLog::GetInstance().Print(LOG_NAME, "ch%02d: BCR = 0x%08X.\r\n", channelId, value);
			break;
		case CChannel::REG_BCR + 2:
			CLog::GetInstance().Print(LOG_NAME, "ch%02d: BCR.ba = 0x%08X.\r\n", channelId, value);
			break;
		case CChannel::REG_CHCR:
			CLog::GetInstance().Print(LOG_NAME, "ch%02d: CHCR = 0x%08X.\r\n", channelId, value);
			break;
		default:
			CLog::GetInstance().Warn(LOG_NAME, "Wrote 0x%08X to unknown register 0x%08X.\r\n",
			                         value, address);
			break;
		}
	}
	break;
	}
}
