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


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

AZ_Blitter::AZ_Blitter()
    = default;

AZ_Blitter::~AZ_Blitter()
    = default;

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 & 0x1ff; // количество команд в цепочке
		m_nCurCmd = 0;
		// копируем во внутренний буфер все возможные AZ_BLT_MAXCMDNUM команд
		memcpy(m_buffer.data(), reinterpret_cast<uint8_t *>(m_pMainMemW) + nAddr, AZ_BLT_MAXCMDNUM * AZ_BLT_CMDSIZE * sizeof(uint16_t));
		m_bCmdFinished = true; // отметим, что надо читать команду
		m_nNumMoves = AZ_BLTCMDInLineNum;
	}

	m_nReadCmds = 0;
}

void AZ_Blitter::exec_chunk_cmd()
{
	// если блиттер не работает, ничего не делаем
	if (!m_nRunning)
	{
		return;
	}

	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 * AZ_BLT_CMDSIZE]); // парсим параметры команды
			m_nCurCmd++;    // счётчик к следующей команде

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

			// заполняем параметры
			m_bNeedReadSrc = !!(m_Cmd.nCMD & BLT_RDSRC); // флаг, что будем читать src
			m_bNeedReadDst = !!(m_Cmd.nCMD & BLT_RDDST); // флаг, что будем читать dst
			m_bSwapBytes = !!(m_Cmd.nCMD & BLT_SWAPB);  // переставлять байты при зеркалировании
			m_nW = m_Cmd.nWWidth; // счётчик слов в строке для текущей команды

			switch ((m_Cmd.nCMD & (BLT_MIRRORH | BLT_MIRRORV)) >> 9)
			{
				case 0: // нет зеркалирования
					m_nHorStep = 1;
					m_nVertStep = m_Cmd.nPitch;
					break;

				case 1: // зеркалирование по горизонтали
					m_nHorStep = -1;
					m_nVertStep = m_Cmd.nPitch + 2;
					break;

				case 2: // зеркалирование по вертикали
					m_nHorStep = 1;
					m_nVertStep = -(m_Cmd.nPitch + 2);
					break;

				case 3: // зеркалирование по горизонтали и вертикали
					m_nHorStep = -1;
					m_nVertStep = -m_Cmd.nPitch;
					break;
			}

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

				case BLT_OP_COPYS2D: // копирование SRC => DST безусловное
				case BLT_OP_COPYD2S: // копирование DST => SRC безусловное (сохранение фона)
				case BLT_OP_OVLOVER: // наложение SRC на DST и помещение результата в DST, наложение идёт только цветов которые не совпадают с прозрачным в SRC
				case BLT_OP_OVLUNDR: // наложение SRC на DST и помещение результата в DST, наложение идёт только цветов которые не совпадают с прозрачным в DST и не совпадают с прозрачным в SRC
				case BLT_OP_CONTURF: // заполнение константой по контуру
					m_adstw = m_pMainMemW + m_Cmd.nDST + m_Cmd.nDstYAdd;
					m_asrcw = m_pMainMemW + m_Cmd.nSRC;
					break;

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

		uint16_t d = 0, s = 0;

		switch (m_Cmd.nCMD & BLT_OPMASK)
		{
			case BLT_OP_FILL: // заполнение константой
				*m_adstw = m_nFill000; // заполняем строку
				break;

			case BLT_OP_COPYS2D: // копирование SRC => DST безусловное
				if (m_bNeedReadSrc)
				{
					s = *m_asrcw;
				}

				if (m_bSwapBytes)
				{
					s = (s << 8) | (s >> 8);
				}

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

			case BLT_OP_OVLOVER: // наложение SRC на DST и помещение результата в DST, наложение идет только цветов которые не совпадают с прозрачным в SRC
				if (m_bNeedReadSrc)
				{
					s = *m_asrcw;
				}

				if (m_bSwapBytes)
				{
					s = (s << 8) | (s >> 8);
				}

				{
					auto pdst = reinterpret_cast<uint8_t *>(m_adstw);

					for (int i = 0; i < 2; ++i)
					{
						if ((s & 0xff) != m_Cmd.nSRCconst)
						{
							*pdst++ = LOBYTE(s); // делаем наложение
						}

						s >>= 8;
					}
				}

				m_asrcw++;
				break;

			case BLT_OP_OVLUNDR: // наложение SRC на DST и помещение результата в DST, наложение идет только цветов которые не совпадают с прозрачным в DST и не совпадают с прозрачным в SRC
				if (m_bNeedReadSrc)
				{
					s = *m_asrcw;
				}

				if (m_bNeedReadDst)
				{
					d = *m_adstw;
				}

				if (m_bSwapBytes)
				{
					s = (s << 8) | (s >> 8);
					d = (d << 8) | (d >> 8);
				}

				{
					auto pdst = reinterpret_cast<uint8_t *>(m_adstw);

					for (int i = 0; i < 2; ++i)
					{
						if ((s & 0xff) != m_Cmd.nSRCconst && (d & 0xff) == m_Cmd.nDSTconst)
						{
							*pdst++ = LOBYTE(s); // делаем наложение
						}

						s >>= 8;
						d >>= 8;
					}
				}

				m_asrcw++;
				break;

			case BLT_OP_COPYD2S: // копирование DST => SRC безусловное (сохранение фона)
				if (m_bNeedReadDst)
				{
					d = *m_adstw;
				}

				if (m_bSwapBytes)
				{
					d = (d << 8) | (d >> 8);
				}

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

			case BLT_OP_CONTURF: // наложение SRC на DST и помещение результата в DST, наложение идёт только цветов которые не совпадают с прозрачным в SRC
				if (m_bNeedReadSrc)
				{
					s = *m_asrcw;
				}

				if (m_bSwapBytes)
				{
					s = (s << 8) | (s >> 8);
				}

				{
					auto pdst = reinterpret_cast<uint8_t *>(m_adstw);

					for (int i = 0; i < 2; ++i)
					{
						// важно - константа заполнения в младшем байте, а константа прозрачного цвета SRC в старшем,
						// (не как в 21й команде где константа прозрачного цвета SRC в младшем байте)
						if ((s & 0xff) != m_Cmd.nDSTconst)
						{
							*pdst++ = m_Cmd.nSRCconst; // делаем наложение
						}

						s >>= 8;
					}
				}

				m_asrcw++;
				break;
		}

		m_adstw += m_nHorStep;

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

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

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

		if (m_adstw < m_pMainMemW)
		{
			m_adstw += AZBK_MEMSIZE / 2;
		}

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

		if (m_asrcw < m_pMainMemW)
		{
			m_asrcw += AZBK_MEMSIZE / 2;
		}
	}

	m_nNumMoves = AZ_BLTCMDInLineNum;
}

