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

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

AZ_Blitter::AZ_Blitter()
	: m_pMainMemW(nullptr)
	, m_pMainMemWEnd(nullptr)
	, m_buffer{}
	, m_Cmd{}
	, m_nCmd(0)
	, m_nNumMoves(0)
	, m_nCurCmd(0)
	, m_nW(0)
	, m_nReadCmds(0)
	, m_nRunning(0)
	, m_bCmdFinished(true)
	, m_bNeedReadSrc(false)
	, m_bNeedReadDst(false)
	, m_adstw(nullptr)
	, m_asrcw(nullptr)
	, m_adstb(nullptr)
	, m_asrcb(nullptr)
	, m_nFill000(0)
{
}

AZ_Blitter::~AZ_Blitter()
{
}

void AZ_Blitter::AttachMemory(uint16_t *pMainMemW)
{
	m_pMainMemW = pMainMemW;
	m_pMainMemWEnd = m_pMainMemW + AZBK_MEMSIZE / 2;
}

void AZ_Blitter::Reset()
{
	m_nCmd = 0;
	m_nRunning = 0;
}

// надо сделать беспоточную функцию, которая будет выполнять заданное количество команд,
// укладывающееся в одну видеостроку
// при этом, если команда выполнилась, надо брать следующую, если все команды закончились -
// нужно прекращать работать

// запускаем команды на выполнение
void AZ_Blitter::DoExec(const int nCmd, const int nAddr)
{
	m_nReadCmds = AZ_BLT_READCMD;

	// если блиттер не работает, если работает - игнор
	if (!m_nRunning)
	{
		m_nRunning = AZ_BLT_RUNNING; // отметим, что блиттер заработал.
		m_nCmd = nCmd & 0x7f; // количество команд в цепочке
		m_nCurCmd = 0;
		// копируем во внутренний буфер все возможные 128 команд
		memcpy(m_buffer, reinterpret_cast<uint8_t *>(m_pMainMemW) + nAddr, 1024 * sizeof(uint16_t));
		m_bCmdFinished = true; // отметим, что надо читать команду
		m_nNumMoves = AZ_BLTCMDInLineNum;
	}

	m_nReadCmds = 0;
}

void AZ_Blitter::exec_chunk_cmd()
{
	// если блиттер ещё работает
	if (m_nRunning)
	{
		while (--m_nNumMoves > 0)
		{
			// если команда выполнилась
			if (m_bCmdFinished)
			{
l_skipCmd:

				if (m_nCurCmd >= m_nCmd)    // если команды закончились,
				{
					m_bCmdFinished = true;
					m_nRunning = 0;         // отметим, что блиттер закончил работу
					return;                 // и выйдем
				}

				// получим следующую команду
				m_bCmdFinished = false;     // отметим, что команда только началась.
				m_Cmd.read(&m_buffer[m_nCurCmd * 8]); // парсим параметры команды
				m_nCurCmd++;    // счётчик к следующей команде

				if (m_Cmd.nCMD & 4) // команда NOP
				{
					goto l_skipCmd; // эту команду надо пропустить.
				}

				// заполняем параметры
				m_bNeedReadSrc = !!(m_Cmd.nCMD & 1);
				m_bNeedReadDst = !!(m_Cmd.nCMD & 2);
				m_nW = m_Cmd.nWWidth;

				switch (m_Cmd.nCMD & 070)
				{
					case 000: // заполнение константой
						m_nFill000 = m_Cmd.nSRCconst | (m_Cmd.nSRCconst << 8); // слово заполнения
						[[fallthrough]];

					case 010: // копирование SRC => DST безусловное
					case 040: // копирование DST => SRC безусловное (сохранение фона)
						m_adstw = m_pMainMemW + m_Cmd.nDST + m_Cmd.nDstYAdd;
						m_asrcw = m_pMainMemW + m_Cmd.nSRC;
						break;

					case 020: // наложение SRC на DST и помещение результата в DST, наложение идёт только цветов которые не совпадают с прозрачным в SRC
					case 030: // наложение SRC на DST и помещение результата в DST, наложение идёт только цветов которые не совпадают с прозрачным в DST и не совпадают с прозрачным в SRC
					case 050: // наложение SRC на DST и помещение результата в DST, наложение идёт только цветов которые не совпадают с прозрачным в SRC
						m_adstb = reinterpret_cast<uint8_t *>(m_pMainMemW + m_Cmd.nDST + m_Cmd.nDstYAdd);
						m_asrcb = reinterpret_cast<uint8_t *>(m_pMainMemW + m_Cmd.nSRC);
						break;

					default: // неизвестные команды пропускаем.
						goto l_skipCmd;
				}
			}

			switch (m_Cmd.nCMD & 070)
			{
				case 000: // заполнение константой
				{
					uint16_t d = 0;

					if (m_bNeedReadDst)
					{
						d = *m_adstw;
					}

					*m_adstw++ = m_nFill000; // заполняем строку

					if (--m_nW <= 0) // строка закончилась
					{
						// переходим к следующей строке
						m_nW = m_Cmd.nWWidth;
						m_adstw += m_Cmd.nPitch;

						if (--m_Cmd.nHeight <= 0) // строки закончились
						{
							m_bCmdFinished = true; // надо перейти к следующей команде
						}
					}
				}
				break;

				case 010: // копирование SRC => DST безусловное
				{
					uint16_t s = 0;
					uint16_t d = 0;

					if (m_bNeedReadSrc)
					{
						s = *m_asrcw;
					}

					if (m_bNeedReadDst)
					{
						d = *m_adstw;
					}

					*m_adstw++ = s; // копируем строку
					m_asrcw++;

					if (--m_nW <= 0) // строка закончилась
					{
						// переходим к следующей строке
						m_nW = m_Cmd.nWWidth;
						m_adstw += m_Cmd.nPitch;

						if (--m_Cmd.nHeight <= 0) // строки закончились
						{
							m_bCmdFinished = true; // надо перейти к следующей команде
						}
					}
				}
				break;

				case 020: // наложение SRC на DST и помещение результата в DST, наложение идет только цветов которые не совпадают с прозрачным в SRC
				{
					uint8_t s = 0;
					uint8_t d = 0;

					for (int i = 0; i < 2; ++i)
					{
						if (m_bNeedReadSrc)
						{
							s = *m_asrcb;
						}

						if (m_bNeedReadDst)
						{
							d = *m_adstb;
						}

						if (s != m_Cmd.nSRCconst)
						{
							*m_adstb = s; // делаем наложение
						}

						m_adstb++; m_asrcb++;
					}

					if (--m_nW <= 0) // строка закончилась
					{
						// переходим к следующей строке
						m_nW = m_Cmd.nWWidth;
						m_adstb += m_Cmd.nPitch * 2;

						if (--m_Cmd.nHeight <= 0) // строки закончились
						{
							m_bCmdFinished = true; // надо перейти к следующей команде
						}
					}
				}
				break;

				case 030: // наложение SRC на DST и помещение результата в DST, наложение идет только цветов которые не совпадают с прозрачным в DST и не совпадают с прозрачным в SRC
				{
					uint8_t s = 0;
					uint8_t d = 0;

					for (int i = 0; i < 2; ++i)
					{
						if (m_bNeedReadSrc)
						{
							s = *m_asrcb;
						}

						if (m_bNeedReadDst)
						{
							d = *m_adstb;
						}

						if (s != m_Cmd.nSRCconst && d == m_Cmd.nDSTconst)
						{
							*m_adstb = s; // делаем наложение
						}

						m_adstb++; m_asrcb++;
					}

					if (--m_nW <= 0) // строка закончилась
					{
						// переходим к следующей строке
						m_nW = m_Cmd.nWWidth;
						m_adstb += m_Cmd.nPitch * 2;

						if (--m_Cmd.nHeight <= 0) // строки закончились
						{
							m_bCmdFinished = true; // надо перейти к следующей команде
						}
					}
				}
				break;

				case 040: // копирование DST => SRC безусловное (сохранение фона)
				{
					uint16_t s = 0;
					uint16_t d = 0;

					if (m_bNeedReadSrc)
					{
						s = *m_asrcw;
					}

					if (m_bNeedReadDst)
					{
						d = *m_adstw;
					}

					*m_asrcw++ = d; // копируем строку
					m_adstw++;

					if (--m_nW <= 0) // строка закончилась
					{
						// переходим к следующей строке
						m_nW = m_Cmd.nWWidth;
						m_adstw += m_Cmd.nPitch;

						if (--m_Cmd.nHeight <= 0) // строки закончились
						{
							m_bCmdFinished = true; // надо перейти к следующей команде
						}
					}
				}
				break;

				case 050: // наложение SRC на DST и помещение результата в DST, наложение идёт только цветов которые не совпадают с прозрачным в SRC
				{
					uint8_t s = 0;
					uint8_t d = 0;

					for (int i = 0; i < 2; ++i)
					{
						if (m_bNeedReadSrc)
						{
							s = *m_asrcb;
						}

						if (m_bNeedReadDst)
						{
							d = *m_adstb;
						}

						// важно - константа заполнения в младшем байте, а константа прозрачного цвета SRC в старшем,
						// (не как в 21й команде где константа прозрачного цвета SRC в младшем байте)
						if (s != m_Cmd.nDSTconst)
						{
							*m_adstb = m_Cmd.nSRCconst; // делаем наложение
						}

						m_adstb++; m_asrcb++;
					}

					if (--m_nW <= 0) // строка закончилась
					{
						// переходим к следующей строке
						m_nW = m_Cmd.nWWidth;
						m_adstb += m_Cmd.nPitch * 2;

						if (--m_Cmd.nHeight <= 0) // строки закончились
						{
							m_bCmdFinished = true; // надо перейти к следующей команде
						}
					}
				}
				break;
					//default:
					//  m_nRunning = 0;
					//  return;
			}

			// чтобы за пределы памяти не убежало, вводим проверку
			// и зацикливаем в пределах
			if (m_adstw >= m_pMainMemWEnd)
			{
				m_adstw -= AZBK_MEMSIZE / 2;
			}

			if (m_asrcw >= m_pMainMemWEnd)
			{
				m_asrcw -= AZBK_MEMSIZE / 2;
			}

			if (m_adstb >= reinterpret_cast<uint8_t *>(m_pMainMemWEnd))
			{
				m_adstb -= AZBK_MEMSIZE;
			}

			if (m_asrcb >= reinterpret_cast<uint8_t *>(m_pMainMemWEnd))
			{
				m_asrcb -= AZBK_MEMSIZE;
			}
		}

		m_nNumMoves = AZ_BLTCMDInLineNum;
	}
}

