#include "Iop_PsfSubSystem.h"
#include "Ps2Const.h"
#include <thread>

using namespace Iop;

#define FRAMES_PER_SEC	(60)

#ifdef SPU2X_REMOVED
#else
extern "C" void SPU2async(uint32 cycles, int16* spu_pOutput);
#endif

CPsfSubSystem::CPsfSubSystem(bool ps2Mode)
: m_iop(ps2Mode)
, m_frameTicks(0)
, m_spuUpdateTicks(0)
, m_spuUpdateCounter(0)
, m_frameCounter(0)
, m_currentBlock(0)
#ifdef VIOPSF2_REMOVED
#else
, m_ps2Mode(ps2Mode)
, m_frameRate(FRAMES_PER_SEC)
#endif
{
	uint32 cpuFreq = ps2Mode ? PS2::IOP_CLOCK_OVER_FREQ : PS2::IOP_CLOCK_BASE_FREQ;
	m_frameTicks = (cpuFreq / FRAMES_PER_SEC);
#ifdef VIOPSF2_REMOVED
	m_spuUpdateTicks = (cpuFreq / 1000);
#else
#ifdef SPU2X_REMOVED
	m_spuUpdateTicks = (cpuFreq / 44100);
#else
	m_spuUpdateTicks = ps2Mode ? (cpuFreq / 48000) : (cpuFreq / 44100);
#endif
#endif

	Reset();
}

void CPsfSubSystem::Reset()
{
	m_iop.Reset();
	m_currentBlock = 0;
	m_frameCounter = m_frameTicks;
	m_spuUpdateCounter = m_spuUpdateTicks;
}

CMIPS& CPsfSubSystem::GetCpu()
{
	return m_iop.m_cpu;
}

CMipsExecutor& CPsfSubSystem::GetCpuExecutor()
{
	return m_iop.m_executor;
}

CSpuBase& CPsfSubSystem::GetSpuCore(unsigned int coreId)
{
	if(coreId == 0)
	{
		return m_iop.m_spuCore0;
	}
	else
	{
		return m_iop.m_spuCore1;
	}
}

uint8* CPsfSubSystem::GetRam()
{
	return m_iop.m_ram;
}

uint8* CPsfSubSystem::GetSpr()
{
	return m_iop.m_scratchPad;
}

void CPsfSubSystem::SetBios(const BiosBasePtr& bios)
{
	m_iop.SetBios(bios);
}

#ifdef VIOPSF2_REMOVED
#else
void CPsfSubSystem::SetFrameRate(uint32 frameRate)
{
	if (frameRate == 50)
		m_frameRate = frameRate;
	else
		m_frameRate = FRAMES_PER_SEC;

	uint32 cpuFreq(0);
	uint32 hline(0);
	uint32 dots(0);

	// I referred to Highly Experimental(Thanks Neill). Fixed World Soccer Winning Eleven 5 PSF2(tempo was too fast).
	if (m_ps2Mode)
	{
		cpuFreq = PS2::IOP_CLOCK_OVER_FREQ;
		dots = 858;
	}
	else
	{
		cpuFreq = PS2::IOP_CLOCK_BASE_FREQ;
		dots = 429;
	}
	if (FRAMES_PER_SEC == m_frameRate)
		hline = 262;
	else
		hline = 312;

	m_frameTicks = (cpuFreq / m_frameRate);
	m_frameCounter = m_frameTicks;
	m_iop.m_counters.SetRates(cpuFreq, m_frameRate, hline, dots);
}
#endif

void CPsfSubSystem::Update(bool singleStep, CSoundHandler* soundHandler)
{
#ifdef VIOPSF2_REMOVED
	if(soundHandler && !soundHandler->HasFreeBuffers())
	{
		std::this_thread::sleep_for(std::chrono::milliseconds(1));
		soundHandler->RecycleBuffers();
	}
	else
#endif
	{
		//Execute CPU and step devices
		{
			int ticks = 0;
#ifdef VIOPSF2_REMOVED
			int quota = 500;
#else
			int quota = 100;
			int executed_ticks = 0;
#endif

#ifdef VIOPSF2_REMOVED
			if(m_iop.IsCpuIdle())
			{
				ticks += (quota * 2);
				quota /= 50;
			}
#else
			//Fix FINAL FANTASY X's "victory!".
			bool executeCpu = false;
			const int minUpdateCounter = m_spuUpdateCounter <= m_frameCounter ? m_spuUpdateCounter : m_frameCounter;
			for (; ticks <= minUpdateCounter; )
			{
				if (!m_iop.IsCpuIdle())
				{
					executeCpu = true;
					ticks = executed_ticks;
					break;
				}
				ticks += quota;
				m_iop.CountTicks(quota);
				executed_ticks = m_iop.ExecuteCpu(10);
			}
#endif

#ifdef VIOPSF2_REMOVED
#else
			if (executeCpu)
			{
#endif
				ticks += m_iop.ExecuteCpu(singleStep ? 1 : quota);
				m_iop.CountTicks(ticks);
#ifdef VIOPSF2_REMOVED
#else
			}
#endif
			m_spuUpdateCounter -= ticks;
			m_frameCounter -= ticks;
		}

#ifdef VIOPSF2_REMOVED
		if(m_spuUpdateCounter < 0)
#else
		while (m_spuUpdateCounter < 0)
#endif
		{
			m_spuUpdateCounter += m_spuUpdateTicks;
			unsigned int blockOffset = (BLOCK_SIZE * m_currentBlock);
			int16* samplesSpu0 = m_samples + blockOffset;

#ifdef SPU2X_REMOVED
#else
			if (m_ps2Mode)
			{
				SPU2async(m_spuUpdateTicks, samplesSpu0);
			}
			else
			{
#endif
			m_iop.m_spuCore0.Render(samplesSpu0, BLOCK_SIZE, 44100);

			if(m_iop.m_spuCore1.IsEnabled())
			{
				int16 samplesSpu1[BLOCK_SIZE];
				m_iop.m_spuCore1.Render(samplesSpu1, BLOCK_SIZE, 44100);

				for(unsigned int i = 0; i < BLOCK_SIZE; i++)
				{
					int32 resultSample = static_cast<int32>(samplesSpu0[i]) + static_cast<int32>(samplesSpu1[i]);
					resultSample = std::max<int32>(resultSample, SHRT_MIN);
					resultSample = std::min<int32>(resultSample, SHRT_MAX);
					samplesSpu0[i] = static_cast<int16>(resultSample);
				}
			}

#ifdef SPU2X_REMOVED
#else
			}
#endif

			m_currentBlock++;
			if(m_currentBlock == BLOCK_COUNT)
			{
#if defined(VIOPSF2_REMOVED) || defined(_DEBUG)
				if(soundHandler)
#endif
				{
					soundHandler->Write(m_samples, BLOCK_SIZE * BLOCK_COUNT, 44100);
				}
				m_currentBlock = 0;
			}
		}
		if(m_frameCounter < 0)
		{
			m_frameCounter += m_frameTicks;
			m_iop.m_intc.AssertLine(CIntc::LINE_VBLANK);
			m_iop.NotifyVBlankStart();
			m_iop.NotifyVBlankEnd();
#ifdef VIOPSF2_REMOVED
			if(!OnNewFrame.empty())
			{
				OnNewFrame();
			}
#endif
		}
	}
}

#ifdef DEBUGGER_INCLUDED

bool CPsfSubSystem::MustBreak()
{
	return m_iop.m_executor.MustBreak();
}

void CPsfSubSystem::DisableBreakpointsOnce()
{
	m_iop.m_executor.DisableBreakpointsOnce();
}

CBiosDebugInfoProvider* CPsfSubSystem::GetBiosDebugInfoProvider()
{
	return m_iop.m_bios.get();
}

void CPsfSubSystem::LoadDebugTags(Framework::Xml::CNode* tagsNode)
{
	m_iop.m_bios->LoadDebugTags(tagsNode);
}

void CPsfSubSystem::SaveDebugTags(Framework::Xml::CNode* tagsNode)
{
	m_iop.m_bios->SaveDebugTags(tagsNode);
}

#endif
