#include "pch.h"
#include "AZBK.h"
#include "Platform.h"

/////////////////////////////////////////////////////////////////////////////////
//
// AZ Screen
//
/////////////////////////////////////////////////////////////////////////////////

const std::vector<int> CAZBK::m_vAZBPP = { 1, 2, 4, 8 };
const std::vector<int> CAZBK::m_vAZLLEN = { 32 * 2, 64 * 2, 128 * 2, 256 * 2 }; // ширина строки в байтах
const std::vector<int> CAZBK::m_vAZXS = { 1, 2, 4, 8 };
const std::vector<int> CAZBK::m_vAZYS = { 1, 2, 3, 4 };

const std::vector<int> CAZBK::m_vAZLSCRL = { 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304, 131072, 196608, 262144 };


bool CAZBK::StartVideoThread()
{
	if (!m_lockVidThr.IsLocked())
	{
		m_VideoThread = std::thread(&CAZBK::VideoThreadFunc, this);

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

	return false;
}

void CAZBK::StopVideoThread()
{
	if (m_lockVidThr.IsLocked())
	{
		m_lockVid.Lock();

		do
		{
			SleepEx(10, FALSE);
		} while (m_lockVidThr.IsLocked());
	}
}

void CAZBK::VideoThreadFunc() const
{
	DWORD_PTR oldmask = ::SetThreadAffinityMask(::GetCurrentThread(), 1);
	LARGE_INTEGER liDueTime{};    //интервал таймера
	liDueTime.QuadPart = -10000000LL / 60;
	// создаём waitable timer.
	HANDLE hWaitableTimer = ::CreateWaitableTimer(nullptr, FALSE, nullptr);

	if (hWaitableTimer)
	{
		m_lockVidThr.Lock();
		// Устанавливаем таймер.
		::SetWaitableTimer(hWaitableTimer, &liDueTime, 17, nullptr, nullptr, TRUE);

		do
		{
			FillTexture();
			g_Platform.GetMainPtr()->PostMessage(WM_SCR_DRAW, 1);
			// ждём срабатывания таймера.
			::WaitForSingleObject(hWaitableTimer, INFINITE);
			// если включен таймер и тежим 60Гц - делаем прерывание
			if ((m_AZRegs.nAZ_mapper_ctrl & (AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER)) == AZ_MMU_V100_ON)
			{
				g_Platform.GetCPUPtr()->TickIRQ2();
			}
		} while (!m_lockVid.IsLocked());     //пока не придёт событие остановки

		::CancelWaitableTimer(hWaitableTimer);
		::CloseHandle(hWaitableTimer);
		m_lockVid.UnLock(); //выставляем событие, что таймер остановился, и можно продолжать остальные действия по остановке
		m_lockVidThr.UnLock();
	}

	::SetThreadAffinityMask(::GetCurrentThread(), oldmask);
}


uint32_t CAZBK::R5G5B5toRGB(uint16_t c) const
{
	register uint8_t b = (c & 0x1f) << 3;
	register uint8_t g = ((c >> 5) & 0x1f) << 3;
	register uint8_t r = ((c >> 10) & 0x1f) << 3;
	return 0xff000000 | (uint32_t(r) << 16) | (uint32_t(g) << 8) | b;
}

uint16_t CAZBK::RGBtoR5G5B5(uint32_t c) const
{
	register uint8_t b = (c & 0xff) >> 3;
	register uint8_t g = ((c >> 8) & 0xff) >> 3;
	register uint8_t r = ((c >> 16) & 0xff) >> 3;
	return (uint16_t(r) << 10) | (uint16_t(g) << 5) | b;
}

void CAZBK::SetScreenParam(uint16_t v)
{
	std::lock_guard<std::mutex> lk(m_mutScrParam);

	m_AZData.nAZVideoMode = v & 7;
	m_AZData.nAZBpp = m_vAZBPP[v & 3];
	m_AZData.nAZLineLen = m_vAZLLEN[(v >> 3) & 3];
	m_AZData.nAZXStretch = m_vAZXS[(v >> 6) & 3];
	m_AZData.nAZYStretch = m_vAZYS[(v >> 9) & 3];
	int idx = (v >> 12) & 0x0f;

	if (idx > 11)
	{
		idx = 11;
	}

	m_AZData.nAZScrlSize = m_vAZLSCRL[idx];
	// теперь посчитаем сколько строк будет рулон
	m_AZData.nAZScrlLines = (m_AZData.nAZScrlSize * 2 / m_AZData.nAZLineLen);
}

// формирование экрана
void CAZBK::FillTexture() const
{
	std::lock_guard<std::mutex> lk(m_mutScrParam);
	register uint32_t *pBits = m_pTexture;
	register int nOffs = AZ_BANK_TO_OFFSET(m_AZRegs.nSCRPageNum); // это смещение до начала экрана в памяти
	register auto pDataBase = const_cast<uint8_t*>(&m_pMemory[nOffs]); // вот отсюда начинаются данные верхнего слоя
	nOffs = AZ_BANK_TO_OFFSET(m_AZRegs.nSCRSprPageNum); // это смещение до начала экрана в памяти
	register auto pDataBaseSpr = const_cast<uint8_t *>(&m_pMemory[nOffs]); // вот отсюда начинаются данные среднего слоя
	nOffs = AZ_BANK_TO_OFFSET(m_AZRegs.nSCRBgfPageNum); // это смещение до начала экрана в памяти
	register auto pDataBaseBgf = const_cast<uint8_t *>(&m_pMemory[nOffs]); // вот отсюда начинаются данные нижнего слоя

	switch (m_AZData.nAZVideoMode)
	{
		default:
		case 0:
		{
			register int nHeight = 768; // фиксированная реальная высота в строках
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_2_COLOR_IDX]; // начало палитры
			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register uint8_t *pData = pDataBase + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nSCRL177250) % m_AZData.nAZScrlLines);
				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_AZData.nAZLineLen; ++c) // длина линии в байтах
				{
					register uint8_t w = *pData++; // берём очередной байт

					for (register int i = 0; i < 8; ++i) // 8 бит
					{
						register const size_t p = w & 1; w >>= 1;
						register const uint32_t px = *(pPaletteIdx + p);
						register int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px; // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl0; // если нарисовали всю строку, то хватит
							}
						} while (--sx);
					}
				}

l_endl0:
				--nHeight; nLinesCount++;
				register int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			} while (nHeight > 0);
		}
		break;

		case 1:
		{
			register int nHeight = 768;
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_4_COLOR_IDX + m_AZData.nLegacyPalette * 4];
			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register uint8_t *pData = pDataBase + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nSCRL177250) % m_AZData.nAZScrlLines);
				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_AZData.nAZLineLen; ++c) // длина линии в байтах
				{
					register uint8_t w = *pData++; // берём очередной байт

					for (register int i = 0; i < 4; ++i) // 4 двухбития
					{
						register const size_t p = w & 0x3; w >>= 2;
						register const uint32_t px = *(pPaletteIdx + p);
						register int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px;  // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl1; // если нарисовали всю строку, то хватит
							}
						} while (--sx);
					}
				}

l_endl1:
				--nHeight; nLinesCount++;
				register int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			} while (nHeight > 0);
		}
		break;

		case 2:
		{
			register int nHeight = 768;
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_16_COLOR_IDX]; // начало палитры
			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register uint8_t *pData = pDataBase + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nSCRL177250) % m_AZData.nAZScrlLines);
				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_AZData.nAZLineLen; ++c) // длина линии в байтах
				{
					register uint8_t w = *pData++; // берём очередной байт

					for (register int i = 0; i < 2; ++i)    // 2 четырёхбития
					{
						register const size_t p = w & 0xf; w >>= 4;
						register const uint32_t px = *(pPaletteIdx + p);
						register int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px;  // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl2; // если нарисовали всю строку, то хватит
							}
						} while (--sx);
					}
				}

l_endl2:
				--nHeight; nLinesCount++;
				register int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			} while (nHeight > 0);
		}
		break;

		case 3:
		{
			register int nHeight = 768;
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_256_COLOR_IDX]; // начало палитры
			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register uint8_t *pData = pDataBase + m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nSCRL177250) % m_AZData.nAZScrlLines);
				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_AZData.nAZLineLen; ++c) // длина линии в байтах
				{
					register uint32_t p = *pData++; // берём очередной байт
					register const uint32_t px = *(pPaletteIdx + p);
					register int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

					do
					{
						*pBits++ = px;  // рисуем точку

						if (!(--nWidth))
						{
							goto l_endl3; // если нарисовали всю строку, то хватит
						}
					} while (--sx);
				}

l_endl3:
				--nHeight; nLinesCount++;
				register int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			} while (nHeight > 0);
		}
		break;

		case 4:
		{
			register int nHeight = 768; // фиксированная реальная высота в строках
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_256_COLOR_IDX]; // начало палитры

			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register const size_t nDataOffset = m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nSCRL177250) % m_AZData.nAZScrlLines);
				register uint8_t *pData = pDataBase + nDataOffset; // верхний слой
				register uint8_t *pData2 = pDataBaseSpr + nDataOffset; // средний слой
				register uint8_t *pData3 = pDataBaseBgf + nDataOffset; // нижний слой

				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_AZData.nAZLineLen; ++c) // длина линии в байтах
				{
					register uint8_t w = *pData++; // берём очередной байт
					register uint8_t w2 = *pData2++; // берём очередной байт
					register uint8_t w3 = *pData3++; // берём очередной байт

					for (register int i = 0; i < 8; ++i) // 8 бит
					{
						register size_t p = (static_cast<size_t>(w & 1)) << 2; w >>= 1;
						p |= (static_cast<size_t>(w2 & 1)) << 1; w2 >>= 1;
						p |= (w3 & 1); w3 >>= 1;
						register const uint32_t px = *(pPaletteIdx + p);
						register int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px; // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl4; // если нарисовали всю строку, то хватит
							}
						} while (--sx);
					}
				}

l_endl4:
				--nHeight; nLinesCount++;
				register int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			} while (nHeight > 0);
		}
		break;


		case 5:
		{ // со слоями.
			register int nHeight = 768;
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_4_COLOR_IDX + m_AZData.nLegacyPalette * 4];
			register const uint32_t *pPaletteIdx2 = &m_azPalette[AZ_4_COLOR_IDX + ((m_AZData.nLegacyPalette + 1) & 0xf) * 4];
			register const uint32_t *pPaletteIdx3 = &m_azPalette[AZ_4_COLOR_IDX + ((m_AZData.nLegacyPalette + 2) & 0xf) * 4];
			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register const size_t nDataOffset = m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nSCRL177250) % m_AZData.nAZScrlLines);
				register uint8_t *pData = pDataBase + nDataOffset; // верхний слой
				register uint8_t *pData2 = pDataBaseSpr + nDataOffset; // средний слой
				register uint8_t *pData3 = pDataBaseBgf + nDataOffset; // нижний слой

				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_AZData.nAZLineLen; ++c) // длина линии в байтах
				{
					register uint8_t w = *pData++; // берём очередной байт
					register uint8_t w2 = *pData2++; // берём очередной байт
					register uint8_t w3 = *pData3++; // берём очередной байт

					for (register int i = 0; i < 4; ++i) // 4 двухбития
					{
						register const size_t p = w & 0x3;
						register const size_t p2 = w2 & 0x3;
						register const size_t p3 = w3 & 0x3;
						register const uint32_t px = p ? *(pPaletteIdx + p) : (p2 ? *(pPaletteIdx2 + p2) : *(pPaletteIdx3 + p3));

						w >>= 2; w2 >>= 2; w3 >>= 2;
						register int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px;  // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl5; // если нарисовали всю строку, то хватит
							}
						} while (--sx);
					}
				}

l_endl5:
				--nHeight; nLinesCount++;
				register int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			} while (nHeight > 0);
		}
		break;

		case 6:
		{ // со слоями
			register int nHeight = 768;
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_256_COLOR_IDX]; // начало палитры
			register const uint32_t *pPaletteIdx2 = &m_azPalette[AZ_256_COLOR_IDX + 16]; // начало палитры
			register const uint32_t *pPaletteIdx3 = &m_azPalette[AZ_256_COLOR_IDX + 32]; // начало палитры
			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register const size_t nDataOffset = m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nSCRL177250) % m_AZData.nAZScrlLines);
				register uint8_t *pData = pDataBase + nDataOffset; // верхний слой
				register uint8_t *pData2 = pDataBaseSpr + nDataOffset; // средний слой
				register uint8_t *pData3 = pDataBaseBgf + nDataOffset; // нижний слой

				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_AZData.nAZLineLen; ++c) // длина линии в байтах
				{
					register uint8_t w = *pData++; // берём очередной байт
					register uint8_t w2 = *pData2++; // берём очередной байт
					register uint8_t w3 = *pData3++; // берём очередной байт

					for (register int i = 0; i < 2; ++i)    // 2 четырёхбития
					{
						register const size_t p = w & 0xf;
						register const size_t p2 = w2 & 0xf;
						register const size_t p3 = w3 & 0xf;
						register const uint32_t px = p ? *(pPaletteIdx + p) : (p2 ? *(pPaletteIdx2 + p2) : *(pPaletteIdx3 + p3));

						w >>= 4; w2 >>= 4; w3 >>= 4;
						register int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

						do
						{
							*pBits++ = px;  // рисуем точку

							if (!(--nWidth))
							{
								goto l_endl6; // если нарисовали всю строку, то хватит
							}
						} while (--sx);
					}
				}

l_endl6:
				--nHeight; nLinesCount++;
				register int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			} while (nHeight > 0);
		}
		break;

		case 7:
		{// со слоями
			register int nHeight = 768;
			register const uint32_t *pPaletteIdx = &m_azPalette[AZ_256_COLOR_IDX]; // начало палитры
			register int nLinesCount = 0;

			do
			{
				// рисуем одну линию
				register int nWidth = 1024;         // фиксированная реальная ширина в точках
				register const size_t nDataOffset = m_AZData.nAZLineLen * ((static_cast<size_t>(nLinesCount) + m_AZRegs.nSCRL177250) % m_AZData.nAZScrlLines);
				register uint8_t *pData = pDataBase + nDataOffset; // верхний слой
				register uint8_t *pData2 = pDataBaseSpr + nDataOffset; // средний слой
				register uint8_t *pData3 = pDataBaseBgf + nDataOffset; // нижний слой

				register uint32_t *pLine = pBits;   // начало текущей линии

				for (register int c = 0; c < m_AZData.nAZLineLen; ++c) // длина линии в байтах
				{
					register uint32_t p = *pData++; // берём очередной байт
					register uint32_t p2 = *pData2++; // берём очередной байт
					register uint32_t p3 = *pData3++; // берём очередной байт

					register const uint32_t px = p ? *(pPaletteIdx + p) : (p2 ? *(pPaletteIdx + p2) : *(pPaletteIdx + p3));
					register int sx = m_AZData.nAZXStretch; // повторим точку нужное количество раз

					do
					{
						*pBits++ = px;  // рисуем точку

						if (!(--nWidth))
						{
							goto l_endl7; // если нарисовали всю строку, то хватит
						}
					} while (--sx);
				}

l_endl7:
				--nHeight; nLinesCount++;
				register int sy = m_AZData.nAZYStretch; // теперь повторим строку нужное количество раз

				while (--sy)
				{
					memcpy(pBits, pLine, 1024 * sizeof(uint32_t));
					pBits += 1024;
					--nHeight;
				}
			} while (nHeight > 0);
		}
		break;
	}
}


void CAZBK::Stop50НzThread()
{
	if (m_lock50НzThr.IsLocked()) // если поток запущен
	{
		m_lock50Нz.Lock();

		do
		{
			SleepEx(10, FALSE);
		} while (m_lock50НzThr.IsLocked());
	}
}

bool CAZBK::Start50НzThread()
{
	if (!m_lock50НzThr.IsLocked()) // если поток ещё не запущен
	{
		m_50НzThread = std::thread(&CAZBK::Clk50НzThreadFunc, this);

		if (m_50НzThread.joinable())
		{
			m_50НzThread.detach();
			return true;
		}
	}

	return false;
}

void CAZBK::Clk50НzThreadFunc() const
{
	DWORD_PTR oldmask = ::SetThreadAffinityMask(::GetCurrentThread(), 1);
	LARGE_INTEGER liDueTime{};    //интервал таймера
	liDueTime.QuadPart = -10000000LL / 48;
	// создаём waitable timer.
	HANDLE hWaitableTimer = ::CreateWaitableTimer(nullptr, FALSE, nullptr);

	if (hWaitableTimer)
	{
		m_lock50НzThr.Lock();

		do
		{
			// Устанавливаем таймер.
			::SetWaitableTimer(hWaitableTimer, &liDueTime, 0, nullptr, nullptr, FALSE);
			// ждём срабатывания таймера.
			::WaitForSingleObject(hWaitableTimer, INFINITE);
			// если включен таймер и режим 50Гц - делаем прерывание
			if ((m_AZRegs.nAZ_mapper_ctrl & (AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER)) == (AZ_MMU_V100_ON | AZ_MMU_50HZ_TIMER))
			{
				g_Platform.GetCPUPtr()->TickIRQ2();
			}
		} while (!m_lock50Нz.IsLocked());     //пока не придёт событие остановки

		::CancelWaitableTimer(hWaitableTimer);
		::CloseHandle(hWaitableTimer);
		m_lock50Нz.UnLock(); //выставляем событие, что таймер остановился, и можно продолжать остальные действия по остановке
		m_lock50НzThr.UnLock();
	}

	::SetThreadAffinityMask(::GetCurrentThread(), oldmask);
}
