#include "pch.h"
#include "VideoProc.h"
#include "Config.h"
#include "Platform.h"
#include "BK001001.h"

CVideoProc::CVideoProc()
	: m_pScreen(nullptr)
#if (VID_THREADED_MODEL)
	, m_bThreadTerminate(false)
	, m_bNotify(false)
#endif
{
}


CVideoProc::~CVideoProc()
{}

void CVideoProc::init()
{
	m_sOSCV.init();
	fMemory_Mod = 0.0;
	fMemoryTicks = 0.0;
#if (VID_THREADED_MODEL)
	StartThread();
#endif
}

void CVideoProc::clear()
{
	m_sOSCV.clear();
	fMemoryTicks = 0.0;
}

void CVideoProc::uninit()
{
#if (VID_THREADED_MODEL)
	StopThread();
#endif
}

void CVideoProc::InitEmulator()
{
	// параметры экрана
	m_pScreen->SetSmoothing(g_Config.m_bSmoothing);
	m_pScreen->SetColorMode(g_Config.m_bColorMode);
	m_pScreen->SetAdaptMode(g_Config.m_bAdaptBWMode);
	m_pScreen->SetLuminoforeEmuMode(g_Config.m_bLuminoforeEmulMode);
	g_Config.m_bFullscreenMode ? m_pScreen->SetFullScreenMode() : m_pScreen->SetWindowMode();
}

void CVideoProc::clock()
{
#if (VID_THREADED_MODEL)
	{
		std::lock_guard<std::mutex> lk(m_mutex);
		m_bNotify = true;
	}
	m_cv.notify_one();
#else

	if (--fMemoryTicks <= 0.0)
	{
		do
		{
			Make_One_Screen_Cycle(); // тут выполняются циклы экрана
			fMemoryTicks += fMemory_Mod;
		}
		while (fMemoryTicks < 1.0);
	}

#endif
}

// это вариант точного алгоритма из VP1-037
void CVideoProc::Make_One_Screen_Cycle()
{
	register int dww = m_sOSCV.nVideoAddress & 076; // счётчик слов внутри строки
	register int dwa = m_sOSCV.nVideoAddress & 037700; // адрес строки
	register auto pBoard = g_Platform.GetBoardPtr();

	if (!(m_sOSCV.nVGateCounter || m_sOSCV.bHgate))
	{
		m_pScreen->SetExtendedMode(!(pBoard->m_reg177664 & 01000));
		register DWORD_PTR nScrAddr = (static_cast<DWORD_PTR>(pBoard->GetScreenPage())) << 14;
		register uint8_t *nScr = pBoard->GetMainMemory() + nScrAddr + m_sOSCV.nVideoAddress;
		m_pScreen->PrepareScreenLineWordRGB32(m_sOSCV.nLineCounter, dww, *reinterpret_cast<uint16_t *>(nScr));
	}

	dww += 2; // переходим к следующему слову

	if (m_sOSCV.bHgate) // считаем служебное поле в строке
	{
		if (dww == 040) // отсчитали 16. служебных слов
		{
			dww = 0;
			m_sOSCV.bHgate = false;
		}
	}
	else if (dww == 0100)
	{
		// если отсчитали 32. слова, нужно теперь считать служебные
		dww = 0;
		m_sOSCV.bHgate = true;
		register int dwl = (m_sOSCV.nLineCounter + 1) & 0377; // Счётчик строк

		if (m_sOSCV.nVGateCounter) // считаем служебные
		{
			if (--m_sOSCV.nVGateCounter <= 0) // отсчитали 64 служебные строки
			{
				dwl = 0;  // снова начнём считать информационные
			}

			// когда считаем служебные строки, выбираем из них 353..350
			if ((m_sOSCV.nVGateCounter & 0374) == 050)    // if (050 <= m_sOSCV.nVGateCounter && m_sOSCV.nVGateCounter <= 053)
			{
				// если строки 351, 350
				if (!(m_sOSCV.nVGateCounter & 2))         // if (050 == m_sOSCV.nVGateCounter || m_sOSCV.nVGateCounter == 051)
				{
					// применяем новое смещение
					dwa = (pBoard->m_reg177664 & 0377) << 6; // VA[13:6] <= RA[7:0];
				}

				// при этом на новую строку не переходим. в одной крутимся.
			}
			else
			{
				// переход на следующую строку
				dwa += 0100; // VA[13:6] <= VA[13:6] + 8'b00000001;
			}
		}
		else
		{
			if (dwl == 0)
			{
				// если отсчитали 256 информационных строк, нужно теперь считать служебные.
				m_sOSCV.nVGateCounter = 64;

				if (!(pBoard->m_reg177662out & 040000)) // если бит 14 установлен, таймер не работает.
				{
					g_Platform.GetCPUPtr()->TickIRQ2();
					// для лога дампа регистров AY
					g_Platform.GetAY8910Ptr()->log_timerTick();
				}

				g_Platform.GetMainPtr()->SendMessage(WM_SCR_DRAW);
			}

			// переход на следующую строку
			dwa += 0100; // VA[13:6] <= VA[13:6] + 8'b00000001;
		}

		m_sOSCV.nLineCounter = dwl;
	}

	m_sOSCV.nVideoAddress = (dwa | dww) & 037776;
}

#if (VID_THREADED_MODEL)

void CVideoProc::StopThread()
{
	{
		std::lock_guard<std::mutex> lk(m_mutex);
		m_bThreadTerminate = true;
		m_bNotify = true;
	}
	m_cv.notify_one();
	// вот такая странная ситуация: после подачи notify_one, поток не успевает
	// пробудиться и захватить мутекс, и деструктор успевает убить объект CHDD.
	// а поток остаётся в памяти. Потому что мутекс захватывается тут, а потом
	// сразу освобождается. Поэтому приходится добавлять ещё один мутекс,
	// который залочен всегда, пока поток существует, и разлочивается при его
	// уничтожении, а тут мы ждём, пока сможем залочить этот второй мутекс
	std::lock_guard<std::mutex> lk(m_mutThr);
}

bool CVideoProc::StartThread()
{
	m_ExecuteThread = std::thread(&CVideoProc::ExecuteThreadFunc, this);

	if (m_ExecuteThread.joinable())
	{
		m_ExecuteThread.detach();
		return true;
	}

	return false;
}

void CVideoProc::ExecuteThreadFunc()
{
	std::lock_guard<std::mutex> lk(m_mutThr);

	for (;;)
	{
		std::unique_lock<std::mutex> lk(m_mutex);
		m_cv.wait(lk, [&]()
		{
			return m_bNotify;
		});
		m_bNotify = false;
		lk.unlock();

		if (m_bThreadTerminate) // сигнал к остановке потока получен?
		{
			break;   // и выходим
		}

		if (--fMemoryTicks <= 0.0)
		{
			do
			{
				Make_One_Screen_Cycle(); // тут выполняются циклы экрана
				fMemoryTicks += fMemory_Mod;
			}
			while (fMemoryTicks < 1.0);
		}
	}
}

#endif
