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

/////////////////////////////////////////////////////////////////////////////////
//
// AZ Blitter
//
/////////////////////////////////////////////////////////////////////////////////

AZ_Blitter::AZ_Blitter()
	: m_nCmd(0)
	, m_bReset(false)
#if (PREPARE_THREAD)
	, m_bPrepThreadTerminate(false)
#endif
	, m_pExecThreadNotify(false)
	, m_bExecThreadTerminate(false)
{
#if (PREPARE_THREAD)
	StartPrepThread();
#endif
	StartExecThread();
}

AZ_Blitter::~AZ_Blitter()
{
	StopExecThread();
#if (PREPARE_THREAD)
	StopPrepThread();
#endif
}

void AZ_Blitter::AttachMemory(uint16_t *pMainMemW)
{
	m_pMainMemW = pMainMemW;
}

#if (PREPARE_THREAD)
bool AZ_Blitter::StartPrepThread()
{
	if (m_mutPrepare.try_lock())
	{
		m_mutPrepare.unlock();
		m_bPrepThreadTerminate = false;
		m_thrPrepare = std::thread(&AZ_Blitter::PrepareThread, this);

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

	return false;
}

void AZ_Blitter::StopPrepThread()
{
	if (!m_mutPrepare.try_lock())
	{
		{
			std::lock_guard<std::mutex> lk(m_mutPrepSyn);
			m_bPrepThreadTerminate = true;
			m_vPrepNotify.push_back({ 0,0 });
		}
		m_cvPrepare.notify_one();

		std::lock_guard<std::mutex> lk(m_mutPrepare);
	}
	else
	{
		m_mutPrepare.unlock();
	}
}

// поток, который занимается подготовкой данных для блиттера.
void AZ_Blitter::PrepareThread()
{
	m_mutPrepare.lock();

	for (;;)
	{
		std::unique_lock<std::mutex> lk(m_mutPrepSyn);
		// ждём, пока не будет разбужен поток
		m_cvPrepare.wait(lk, [&]()
				  {
					  return !m_vPrepNotify.empty();
				  });
		PrepData d = m_vPrepNotify.back(); // данные
		m_vPrepNotify.pop_back();
		lk.unlock();
		m_nCmd = d.nCmd & 127; // количество команд в цепочке

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

		// копируем во внутренний буфер все возможные 128 команд
		memcpy(m_buffer, reinterpret_cast<uint8_t *>(m_pMainMemW) + d.nAddr, 2048);
	}

	m_mutPrepare.unlock();
}
#endif

bool AZ_Blitter::StartExecThread()
{
	if (m_mutExec.try_lock())
	{
		m_mutExec.unlock();
		m_bExecThreadTerminate = false;
		m_thrExec = std::thread(&AZ_Blitter::ExecThread, this);

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

	return false;

}

void AZ_Blitter::StopExecThread()
{
	if (!m_mutExec.try_lock())
	{
		{
			std::lock_guard<std::mutex> lk(m_mutExecSyn);
			m_bExecThreadTerminate = true;
			m_pExecThreadNotify = true;
		}
		m_cvExec.notify_one();

		std::lock_guard<std::mutex> lk(m_mutExec);
	}
	else
	{
		m_mutExec.unlock();
	}
}

// исполнительный поток.
void AZ_Blitter::ExecThread()
{
	// у потока 2 состояния: ждать сигнала на выполнение и работать, игнорируя поступающие сигналы на исполнение
	enum class EXEC_STATE
	{
		S_WAIT = 0,
		S_EXEC
	};

	m_mutExec.lock();

	EXEC_STATE state = EXEC_STATE::S_WAIT;	// начальное состояние - ждать
	int nCurCmd = 0; // номер текущей выполняемой команды

	for (;;)
	{
		// если мы ждём сигнала
		if (state == EXEC_STATE::S_WAIT)
		{
			// ждём, пока не будет команда разбудить поток
			std::unique_lock<std::mutex> lk(m_mutExecSyn);
			m_cvExec.wait(lk, [&]()
						  {
							  return m_pExecThreadNotify;
						  });
			m_pExecThreadNotify = false;
			lk.unlock();

			if (m_bExecThreadTerminate) // сигнал к остановке потока получен?
			{
				break;   // и выходим
			}
			// разбудили
			if (m_nCmd) // если есть команды на выполнение
			{
				nCurCmd = 0; // начинаем с нулевой команды
				state = EXEC_STATE::S_EXEC; // переходим в состояние выполнения
			}
			// если команд нет - ничего не делаем, идём снова ждать
		}
		// если работаем
		else if (state == EXEC_STATE::S_EXEC)
		{
			if (m_nCmd) // если есть команды на выполнение
			{
				if (m_bReset)	// если получили асинхронный ресет
				{
					m_nCmd = 0;	// то сбрасываем всё. Нужно будет заново подготовку делать
					state = EXEC_STATE::S_WAIT; // переводим поток в состояние ожидания
				}
				else if (nCurCmd >= m_nCmd) // если выполнили последнюю команду, то 
				{
					m_nCmd = 0; // это не обязательно, но на всякий случай сделаем сброс
					state = EXEC_STATE::S_WAIT; // переводим поток в состояние ожидания
				}
				else // иначе выполняем очередную команду
				{
					AZ_BLITTER_t Cmd;
					Cmd.read(&m_buffer[nCurCmd * 8]); // парсим параметры команды
					exec_cmd(&Cmd); // выполняем

					nCurCmd++; // переходим к следующей команде
				}
			}
			else // если нет - то переходим в состояние ожидания
			{
				state = EXEC_STATE::S_WAIT;
			}
		}
	}

	m_mutExec.unlock();
}

// подготовка данных
void AZ_Blitter::DoPrepare(int nCmd, int nAddr)
{
	if (nCmd) // если команды есть
	{
#if (PREPARE_THREAD)
		PrepData d{ nCmd, nAddr }; // готовим пакет к передаче

		{
			std::lock_guard<std::mutex> lk(m_mutPrepSyn);
			m_vPrepNotify.push_back(d);
		}
		m_cvPrepare.notify_one(); // разбудим поток
#else
		m_nCmd = nCmd & 127; // количество команд в цепочке
		// копируем во внутренний буфер все возможные 128 команд
		memcpy(m_buffer, reinterpret_cast<uint8_t *>(m_pMainMemW) + nAddr, 2048);
#endif
	}
}

// запускаем команды на выполнение
void AZ_Blitter::DoExec()
{
	if (m_nCmd) // если команды есть, т.е. подготовка была проведена
	{
		{
			std::lock_guard<std::mutex> lk(m_mutExecSyn);
			m_pExecThreadNotify = true;
		}
		m_cvExec.notify_one(); // разбудим поток
	}
}

void AZ_Blitter::Reset()
{
	m_bReset = true;
#if (PREPARE_THREAD)
	StopPrepThread();
#endif
	StopExecThread();
#if (PREPARE_THREAD)
	m_vPrepNotify.clear(); // на всякий случай.
	StartPrepThread();
#endif
	m_nCmd = 0;	// и это на всякий случай, хотя оно должно обнулиться в функции StopPrepThread
	StartExecThread();
	m_bReset = false;
}

// функция выполнения одной команды
// вход: pCmd - указатель на структуру параметров команды
//		 *m_pMainMemW - указатель на основную память AZBK в виде словного массива
void AZ_Blitter::exec_cmd(AZ_BLITTER_t *pCmd)
{
	const int cmd = pCmd->nCMD & 070;
	const bool bNeedReadSrc = !!(pCmd->nCMD & 1);
	const bool bNeedReadDst = !!(pCmd->nCMD & 2);

	switch (cmd)
	{
		case 000: // заполнение константой
		{
			uint16_t s = pCmd->nSRCconst | (pCmd->nSRCconst << 8); // слово заполнения
			uint16_t d = 0;
			auto adst = m_pMainMemW + pCmd->nDST;
			for (int h = pCmd->nHeight; h > 0; h--) // цикл по строкам
			{
				for (int w = pCmd->nWWidth; w > 0; w--) // цикл по словам в строке
				{
					if (bNeedReadDst)
					{
						d = *adst;
					}
					*adst++ = s; // заполняем строку
				}
				adst += pCmd->nPitch; // переходим к следующей строке
			}
		}
		break;
		case 010: // копирование SRC => DST безусловное
		{
			auto adst = m_pMainMemW + pCmd->nDST;
			auto asrc = m_pMainMemW + pCmd->nSRC;
			uint16_t s = 0;
			uint16_t d = 0;
			for (int h = pCmd->nHeight; h > 0; h--) // цикл по строкам
			{
				for (int w = pCmd->nWWidth; w > 0; w--) // цикл по словам в строке
				{
					if (bNeedReadSrc)
					{
						s = *asrc;
					}
					if (bNeedReadDst)
					{
						d = *adst;
					}

					*adst++ = s; // копируем строку
					asrc++;
				}
				adst += pCmd->nPitch; // переходим к следующей строке
			}
		}
		break;
		case 020: // наложение SRC на DST и помещение результата в DST, наложение идет только цветов которые не совпадают с прозрачным в SRC
		{
			auto adst = reinterpret_cast<uint8_t *>(m_pMainMemW + pCmd->nDST);
			auto asrc = reinterpret_cast<uint8_t *>(m_pMainMemW + pCmd->nSRC);
			uint8_t s = 0;
			uint8_t d = 0;
			for (int h = pCmd->nHeight; h > 0; h--) // цикл по строкам
			{
				for (int w = pCmd->nWWidth*2; w > 0; w--) // цикл по байтам в строке
				{
					if (bNeedReadSrc)
					{
						s = *asrc;
					}
					if (bNeedReadDst)
					{
						d = *adst;
					}

					if (s != pCmd->nSRCconst)
					{
						*adst = s; // делаем наложение
					}
					adst++; asrc++;
				}
				adst += pCmd->nPitch*2; // переходим к следующей строке
			}
		}
		break;
		case 030: // наложение SRC на DST и помещение результата в DST, наложение идет только цветов которые не совпадают с прозрачным в DST и  не совпадают с прозрачным в SRC
		{
			auto adst = reinterpret_cast<uint8_t *>(m_pMainMemW + pCmd->nDST);
			auto asrc = reinterpret_cast<uint8_t *>(m_pMainMemW + pCmd->nSRC);
			uint8_t s = 0;
			uint8_t d = 0;
			for (int h = pCmd->nHeight; h > 0; h--) // цикл по строкам
			{
				for (int w = pCmd->nWWidth*2; w > 0; w--) // цикл по байтам в строке
				{
					if (bNeedReadSrc)
					{
						s = *asrc;
					}
					if (bNeedReadDst)
					{
						d = *adst;
					}

					if (s != pCmd->nSRCconst && *adst != pCmd->nDSTconst)
					{
						*adst = s; // делаем наложение
					}
					adst++; asrc++;
				}
				adst += pCmd->nPitch*2; // переходим к следующей строке
			}
		}
		break;
		case 040: // копирование DST => SRC безусловное (сохранение фона)
		{
			auto adst = m_pMainMemW + pCmd->nDST;
			auto asrc = m_pMainMemW + pCmd->nSRC;
			uint16_t s = 0;
			uint16_t d = 0;
			for (int h = 0; h < pCmd->nHeight; ++h) // цикл по строкам
			{
				for (int w = 0; w < pCmd->nWWidth; ++w) // цикл по словам в строке
				{
					if (bNeedReadSrc)
					{
						s = *asrc;
					}
					if (bNeedReadDst)
					{
						d = *adst;
					}

					*asrc++ = d; // копируем строку
					adst++;
				}
				adst += pCmd->nPitch; // переходим к следующей строке
			}
		}
		break;
		// остальные команды игнорируем
	}
}

