// BK0011M.cpp: implementation of the CBK0011M class.
//


#include "pch.h"
#include "resource.h"
#include "BKMessageBox.h"
#include "BK0011M.h"
#include "Platform.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#define new DEBUG_NEW
#endif

/////////////////////////////////////////////////////////////////////////////
// Construction/Destruction

IMPLEMENT_DYNAMIC(CBK0011M, CBK001001)

CBK0011M::CBK0011M()
	: m_sCustom({false})
	, m_nROMinWnd1(0)
{
	m_nStartAddr = 0140000;
}

CBK0011M::~CBK0011M()
    = default;

bool CBK0011M::InitialSetDevice()
{
	m_nMemorySize = 0640000;

	if (CBKMPIDevice::InitialSetDevice())
	{
		g_Platform.SetCPUBaseFreq(CPU_SPEED_BK11); // частота задаётся этой константой
		m_reg177716out_mem = 0;
		g_Platform.GetCPUPtr()->AttachBoard(this);
		CString str; //заполним список окон

		for (int i = 0; i < 8; ++i)
		{
			str.Format(_T("БК11:%d"), i);
			const int addr = (i == 0) ? 0 : -1;
			g_Platform.GetWndVectorPtr()->push_back({ str, addr, 040000, GetMainMemory() + (size_t(i) * 040000) });
		}

		return true;
	}

	return false;
}

bool CBK0011M::InitMemory(const uint16_t wHighLim)
{
	m_nBKPortsIOArea = wHighLim;
	m_pMemory.clear();

	if (m_nMemorySize)
	{
		m_pMemory.resize(m_nMemorySize);

		if (m_pMemory.data())
		{
			uint16_t val = 0;
			auto pwMem = reinterpret_cast<uint16_t *>(m_pMemory.data());
			int n = 8;
			int flag = 8;

			for (uint32_t i = 0; i < m_nMemorySize / 2; ++i)
			{
				pwMem[i] = val;

				if (--n <= 0)
				{
					n = 8;

					if (--flag > 0)
					{
						val = ~val;
					}
					else
					{
						flag = 8;
					}
				}
			}

			return true;
		}

		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
		throw;
	}

	return false;
}

/////////////////////////////////////////////////////////////////////////////
// нажали на кнопку стоп
void CBK0011M::StopInterrupt() const
{
	if (!(m_reg177716out_tap & 010000)) // если кнопка СТОП не заблокирована
	{
		g_Platform.GetCPUPtr()->SetIRQ1();
	}
}

/////////////////////////////////////////////////////////////////////////////
// отжали кнопку стоп
void CBK0011M::UnStopInterrupt() const
{
	if (!(m_reg177716out_tap & 010000)) // если кнопка СТОП не заблокирована
	{
		g_Platform.GetCPUPtr()->UnsetIRQ1();
	}
}

int CBK0011M::GetScreenPage() const
{
	return ((m_reg177662out & 0100000) ? 6 : 5);
}

void CBK0011M::ChangePalette() const
{
	g_Platform.GetScreenPtr()->SetPalette(m_reg177662out >> 8);
}

void CBK0011M::OnReset()
{
	CBK001001::OnReset();
	ChangePalette();
}

/////////////////////////////////////////////////////////////////////////////
// кастомный вызов функции InitMemoryModules с индивидуальными параметрами
bool CBK0011M::OnInitDevice()
{
	static const CBKMPIDevice::BoardRomList_t m_arrBK11MRomList[BK11RomListSize] =
	{
		// инициализируем список загружаемых ПЗУ
		{ IDS_INI_BK11M_RE2_328_BASIC2, BRD_11_BASIC2_BNK },
		{ IDS_INI_BK11M_RE2_329_BASIC3, BRD_11_BASIC3_BNK },
		{ IDS_INI_BK11M_RE2_327_BASIC1, BRD_11_BASIC1_BNK },
		{ IDS_INI_BK11M_RE2_325_EXT,    BRD_11_EXT_BNK    },
		{ IDS_INI_BK11M_RE2_324_BOS,    BRD_11_BOS_BNK    },
		// Подгружаем опциональные страницы 012 и 013.
		{ IDS_INI_BK11_RE_OPT_PG12_1,   BRD_11_PG12_1_BNK },
		{ IDS_INI_BK11_RE_OPT_PG12_2,   BRD_11_PG12_2_BNK },
		{ IDS_INI_BK11_RE_OPT_PG13_1,   BRD_11_PG13_1_BNK },
		{ IDS_INI_BK11_RE_OPT_PG13_2,   BRD_11_PG13_2_BNK }
	};
	// инициализация карты памяти
	return InitMemoryModules(m_arrBK11MRomList, BK11RomListSize);
}

bool CBK0011M::InitMemoryModules(const BoardRomList_t arrBoardRomList[], const int size)
{
	m_nROMPresent = 0; // маска доступных ПЗУ в своих банках

	// проверяем загрузку всех ПЗУ в списке.
	// если чего-то нет, выдаём сообщение, но продолжаем
	for (int n = 0; n < size; ++n)
	{
		LoadRomModule11(arrBoardRomList[n].nResource, arrBoardRomList[n].nBank);
	}

	// и проинициализируем карту памяти
	for (int i = 0; i < DEC_BANKS_NUM; ++i)
	{
		BKMEMBank_t &m = m_pMemoryMap[i];
		m.bReadable = TRUE;
		m.bWritable = TRUE;
		m.nBank = i;
		m.nOffset = BANK_TO_OFFSET(i);
		m.nTimingCorrection = RAM_TIMING_CORR_VALUE_D;
	}

	// по адресу 140000 - основное ПЗУ БОС
	BKMEMBank_t &m = m_pMemoryMap[6];
	m.bReadable = !!(m_nROMPresent & (1 << BRD_11_BOS_BNK));
	m.bWritable = FALSE;
	m.nBank = BRD_11_BOS_BNK;
	m.nOffset = BANK_TO_OFFSET(BRD_11_BOS_BNK);
	m.nTimingCorrection = ROM_TIMING_CORR_VALUE;
	// по адресу 160000 - пусто
	m_pMemoryMap[7].bReadable = FALSE;
	m_pMemoryMap[7].bWritable = FALSE;
	MemoryManager();
	return true;
}

bool CBK0011M::LoadRomModule11(const int iniRomNameIndex, const int bank)
{
	const CString strName = g_Config.GetRomModuleName(iniRomNameIndex);
	const int nBit = 1 << bank;

	if (Global::isEmptyUnit(strName))          // если там пусто
	{
		m_nROMPresent &= ~nBit; // сбрасываем бит
		return true; // Там ПЗУ не предусмотрено, но это не ошибка
	}

	if (CBKMPIDevice::LoadRomModule(iniRomNameIndex, BANK_TO_OFFSET(bank), bank))
	{
		m_nROMPresent |= nBit;      // устанавливаем бит
		return true;
	}

	m_nROMPresent &= ~nBit; // сбрасываем бит, потому что не смогли правильно прочитать ПЗУ
	return false;
}



void CBK0011M::BOS11RomOFF(const bool off)
{
	m_pMemoryMap[6].bReadable = !off;
	m_pMemoryMap[6].bWritable = FALSE;
}

void CBK0011M::RAM11Wn1OFF(const bool off)
{
	m_sCustom.bWnd1Off = off;
	MemoryManager();
}


void CBK0011M::Set177716RegMem(const uint16_t w)
{
	constexpr uint16_t mask = 077433;
	m_reg177716out_mem = (~mask & m_reg177716out_mem) | (mask & w);
	MemoryManager();
}


void CBK0011M::Set177716RegTap(const uint16_t w)
{
	constexpr uint16_t mask = 010344;
	m_reg177716out_tap = (~mask & m_reg177716out_tap) | (mask & w);
	g_Platform.GetSpeakerPtr()->SetData(m_reg177716out_tap);
}

void CBK0011M::MemoryManager()
{
	static const int PageNums[8] = { 1, 5, 2, 3, 4, 7, 0, 6 };  // перекодировка БКшной кодировки номеров страниц в нормальную.
	const uint16_t mem = m_reg177716out_mem;
	m_nROMinWnd1 = 0; // номер страницы ПЗУ в окне 1, если 0 - нет там ПЗУ, там ОЗУ

	// стр 8 ПЗУ
	if (mem & 1)
	{
		m_nROMinWnd1 = 8;
	}
	// стр 9 ПЗУ
	else if (mem & 2)
	{
		m_nROMinWnd1 = 9;
	}
	// стр 10 ПЗУ
	else if (mem & 010)
	{
		m_nROMinWnd1 = 10;
	}
	// стр 11 ПЗУ (контроллер альтпро использует этот сигнал, для отключения ОЗУ в окне 1.
	// страницы ПЗУ соответственно у него нету)
	else if ((mem & 020) || m_sCustom.bWnd1Off)
	{
		m_nROMinWnd1 = 11;
	}

	// теперь надо в соответствии с этим поправить карту памяти
	// с учётом, того, что AZBK может отключить 037
	for (int i = 0; i < 2; ++i)
	{
		BKMEMBank_t &m = m_pMemoryMap[i];
		m.bReadable = m.bWritable = !g_Config.m_bAZ037OffState;
	}

	// сперва разберёмся с окном 0, там может быть только ОЗУ, в том числе и экран
	const int nRAMPageWnd0 = (mem >> 12) & 7; // номер страницы ОЗУ в окне 0
	const uint32_t nBnk = (PageNums[nRAMPageWnd0] << 1);
	m_strPageNum0.Format(_T(" %d"), PageNums[nRAMPageWnd0]);

	for (int i = 2, j = 0; i < 4; ++i, ++j)
	{
		BKMEMBank_t &m = m_pMemoryMap[i];
		m.bReadable = m.bWritable = !g_Config.m_bAZ037OffState;
		m.nBank = nBnk + j;
		m.nOffset = BANK_TO_OFFSET(m.nBank);
		m.nTimingCorrection = RAM_TIMING_CORR_VALUE_D;
	}

	// теперь разберёмся с окном 1, там может быть как ПЗУ, так и ОЗУ, а так же может быть пусто, для страниц 10 и 11
	if (m_nROMinWnd1)
	{
		const int nBnk = (m_nROMinWnd1 << 1); // из номера окна получается банк 8->16, 9->18, 10->20, 11->22

		for (int i = 4, j = 0; i < 6; ++i, ++j)
		{
			BKMEMBank_t &m = m_pMemoryMap[i];
			m.bReadable = !!(m_nROMPresent & (1 << (nBnk + j)));
			m.bWritable = FALSE;
			m.nBank = nBnk + j;
			m.nOffset = BANK_TO_OFFSET(m.nBank);
			m.nTimingCorrection = ROM_TIMING_CORR_VALUE;
		}

		m_strPageNum1.Format(_T(" %d"), m_nROMinWnd1);
	}
	else
	{
		const int nRAMPageWnd1 = (mem >> 8) & 7;  // номер страницы ОЗУ в окне 1
		const uint32_t nBnk = (PageNums[nRAMPageWnd1] << 1);
		m_strPageNum1.Format(_T(" %d"), PageNums[nRAMPageWnd1]);

		for (int i = 4, j = 0; i < 6; ++i, ++j)
		{
			BKMEMBank_t &m = m_pMemoryMap[i];
			m.bReadable = m.bWritable = !g_Config.m_bAZ037OffState;
			m.nBank = nBnk + j;
			m.nOffset = BANK_TO_OFFSET(m.nBank);
			m.nTimingCorrection = RAM_TIMING_CORR_VALUE_D;
		}
	}
}

CString CBK0011M::GetMemoryMapViewData(const int seg)
{
	BKMEMBank_t &mb = m_pMemoryMap[seg];
	int n = 0;
	if (mb.bReadable) n |= 2;
	if (mb.bWritable) n |= 1;
	CString strDat = g_arStrMMVw1[n];
	if (n)
	{
		switch (seg)
		{
			case 0:
			case 1:
				strDat += _T(" 0");
				break;
			case 2:
			case 3:
				strDat += m_strPageNum0;
				break;
			case 4:
			case 5:
				strDat += m_strPageNum1;
				break;
		}
	}

	return strDat;
}


// вход: pg0 - номер страницы в окно 0, если -1, то не устанавливать
//       pg1 - номер страницы в окно 1, если -1, то не устанавливать
void CBK0011M::SetMemPages(const int pg0, const int pg1)
{
	if (pg0 >= 0)
	{
		uint32_t nBnk = (pg0 & 7) << 1;

		for (int i = 2, j = 0; i < 4; ++i, ++j)
		{
			BKMEMBank_t &m = m_pMemoryMap[i];
			m.bReadable = m.bWritable = TRUE;
			m.nBank = nBnk + j;
			m.nOffset = BANK_TO_OFFSET(m.nBank);
			m.nTimingCorrection = RAM_TIMING_CORR_VALUE_D;
		}
	}

	if (pg1 >= 0)
	{
		uint32_t nBnk = (pg1 & 7) << 1;

		for (int i = 4, j = 0; i < 6; ++i, ++j)
		{
			BKMEMBank_t &m = m_pMemoryMap[i];
			m.bReadable = m.bWritable = TRUE;
			m.nBank = nBnk + j;
			m.nOffset = BANK_TO_OFFSET(m.nBank);
			m.nTimingCorrection = RAM_TIMING_CORR_VALUE_D;
		}
	}
}

void CBK0011M::RestoreMemPages()
{
	MemoryManager();
}
/*
Запись данных в системные регистры БК0011М
вход: addr - адрес регистра (177660, 177716 и т.п.)
      src - записываемое значение.
      dwFlags - флаги операции, GSSR_BYTEOP - байтовая, GSSR_INTERNAL - внутренняя, без эмуляции действий
*/
bool CBK0011M::SetSystemRegister(const uint16_t addr, uint16_t src, UINT dwFlags)
{
	switch (addr & 0177776)
	{
		case 0177660:
			/*
			177660
			Регистр состояния клавиатуры.
			(0100)бит 6 -- маскирование прерывание от клавиатуры,
			    "0" -- разрешено прерывание по вектору 060 либо 0274.
			    Прерывание вызывается при появлении "1" в бите 7. Начальное состояние: "0".
			    Доступен по чтению и по записи
			(0200)бит 7 -- готовность: "1" -- в регистре данных клавиатуры (177662) готов код нажатой клавиши.
			    Устанавливается при нажатии на клавишу, сбрасывается при чтении регистра данных клавиатуры.
			    Начальное состояние: "1".
			    Доступен только по чтению.
			другие биты: "0". Доступны только по чтению.
			*/
		{
			const uint16_t mask = dwFlags & GSSR_INTERNAL ? 0300 : 0100;

			if (dwFlags & GSSR_BYTEOP)
			{
				src &= 0377;

				if (addr & 1)
				{
					src <<= 8;
				}
			}

			// сбрасываем используемые биты и устанавливаем их значения из слова, которое записываем.
			// остальные биты - которые не используются - остаются нетронутыми.
			uint16_t prev = m_reg177660 & 0100;
			m_reg177660 = (m_reg177660 & ~mask) | (src & mask);
			// если мы сбросили запрет прерываний
			if (prev && !(m_reg177660 & 0100))
			{
				// если был запрос на прерывание, то делаем его.
				g_Platform.DelayedKeyboardInterrupt();
			}
		}

		return true;

		case 0177662:
		{
			constexpr uint16_t mask = 0147400;

			/*
			177662
			Регистр данных клавиатуры.
			    биты 0-6: код клавиши. Доступ только по чтению.
			биты 08-11: палитра
			(040000)бит 14: разрешение прерывания по (внешнему) таймеру (50 Гц),
			    "0" -- прерывание разрешено,
			    "1" -- таймер отключён.
			    Доступен только по записи.
			(0100000)бит 15: переключение буферов экрана 0 - №5, 1 - №6
			*/
			if (dwFlags & GSSR_BYTEOP)
			{
				src &= 0377;    // работаем с младшим байтом

				if (addr & 1)
				{
					src <<= 8;  // работаем со старшим байтом
				}
			}

			// сбрасываем используемые биты и устанавливаем их значения из слова, которое записываем.
			// остальные биты - которые не используются - остаются нетронутыми.
			m_reg177662out = (~mask & m_reg177662out) | (mask & src);
		}

		ChangePalette();
		return true;

		case 0177664:
			/*
			177664
			Регистр скроллинга. Доступен по записи и чтению.
			    биты 0-7: смещение скроллинга, строк. Начальное значение -- 0330.
			(01000)бит 9: сокращённый режим экрана, "0" -- сокращённый (1/4 экрана, старшие адреса),
			    "1" -- полный экран 256 строк.
			*/
		{
			constexpr uint16_t mask = 01377;

			if (dwFlags & GSSR_BYTEOP)
			{
				src &= 0377;
				/*
				оказалось, что в этот регистр не работает байтовая запись.
				чтение байта работает, но записывается всегда слово.
				а при байтовой операции, старший байт просто теряется.
				*/
			}

			m_reg177664 = (~mask & m_reg177664) | (mask & src);
		}

		return true;

		case 0177714:

			/*177714
			    Регистр параллельного программируемого порта ввода вывода - два регистра, входной по чтению и выходной по записи.
			    из выходного нельзя ничего прочитать т.к., читается оно из входного,
			    во входной невозможно ничего записать, т.к. записывается оно в выходной.
			*/
			if (dwFlags & GSSR_BYTEOP)
			{
				src &= 0377; // работаем с младшим байтом

				if (addr & 1)
				{
					src <<= 8; // работаем со старшим байтом
				}
			}

			if (g_Platform.GetAYSndPtr())
			{
				if (dwFlags & GSSR_BYTEOP)
				{
					g_Platform.GetAYSndPtr()->write_data(LOBYTE(src));
				}
				else
				{
					g_Platform.GetAYSndPtr()->write_address(src);
				}
			}

			if (const auto p = g_Platform.GetCovoxPtr())
			{
				p->SetData(src);
			}

			if (const auto p = g_Platform.GetMenestrelPtr())
			{
				p->SetData(src);
			}

			m_reg177714out = src;

			if (g_Config.m_bICLBlock) // если включён блок нагрузок
			{
				// данные записанные в выходной порт передаются во входной
				m_reg177714in = src;
			}
			else if (g_Config.m_bMouseMars)
			{
				const auto p = g_Platform.GetScreenPtr();
				p->SetMouseStrobe(src);
				m_reg177714in = p->GetMouseStatus();
			}

			return true;

		case 0177716:

			/*
			177716
			Системный регистр. Внешний регистр 1 (ВР1, SEL1) процессора ВМ1, регистр начального пуска.
			как и 177714 состоит из двух регистров, раздельных по чтению и по записи
			По чтению:
			(004)бит 2: признак записи в системный регистр. Устанавливается в "1" при
			    любой записи в регистр, сбрасывается в "0" по окончании операции чтения из регистра.
			(040)бит 5: данные с магнитофона, "0" - логический 0, "1" - логическая 1
			(100)бит 6: индикатор нажатия клавиши, установлен в "0" если нажата клавиша клавиатуры, "1" если нет нажатых клавиш.
			(200)бит 7: константа "1", указывающая на отсутствие в системе команд расширенной арифметики
			    биты 8-15: адрес начального пуска, 140000 (БК-0011), младший байт при этом игнорируется
			    остальные биты не используются, "0".
			По записи:
			    бит 11==0
			(004)бит 2: данные на магнитофон. Начальное состояние "0".
			(040)бит 5: данные на магнитофон. Начальное состояние "0".
			(100)бит 6: данные на магнитофон и на пьезодинамик. Начальное состояние "0".
			(200)бит 7: включение двигателя магнитофона, "1" -- стоп, "0" -- пуск. Начальное состояние "1".
			(010000)бит 12: разрешение прерывания по клавише СТОП, 0 - разрешено, 1 - запрещено
			    биты 0,1,3,4 не используются, "0".
			    бит 11==1
			(001)бит 0: подключение страницы №8 пзу в окно №1
			(002)бит 1: подключение страницы №9 пзу в окно №1
			(010)бит 3: подключение страницы №10 пзу в окно №1
			(020)бит 4: подключение страницы №11 пзу в окно №1
			    биты 8-10: номер страницы озу в окно №1
			    биты 12-14: номер страницы озу в окно №0
			*/
			if (dwFlags & GSSR_BYTEOP)
			{
				src &= 0377; // работаем с младшим байтом

				if (addr & 1)
				{
					src <<= 8; // работаем со старшим байтом
				}
			}

			if (src & 04000)
			{
				Set177716RegMem(src);
			}
			else
			{
				Set177716RegTap(src);
			}

			// В БК 2й разряд SEL1 фиксирует любую запись в этот регистр, взводя триггер D9.1 на неограниченное время, сбрасывается который любым чтением этого регистра.
			if (!(dwFlags & GSSR_INTERNAL))
			{
				m_reg177716in |= 4;
			}

			return true;
	}

	return false;
}


/////////////////////////////////////////////////////////////////////////////
// перехват разных подпрограмм монитора, для их эмуляции
bool CBK0011M::Interception(const uint16_t nPC) const
{
	if (CBK001001::Interception(nPC))
	{
		return true;
	}

	switch (nPC & 0177776)
	{
		case 0155170:
			return g_Platform.EmulateSaveTape11M();

		case 0155604:
			return g_Platform.EmulateLoadTape11M();
	}

	return false;
}

bool CBK0011M::RestoreCustomData(DWORD id, CMSFManager *pMsf)
{
	if (pMsf->IsLoad())
	{
		bool bRet = pMsf->GetBlockCustomData(MSF_BLOCKTYPE::CUSTOMEX, id, reinterpret_cast<uint8_t *>(&m_sCustom), sizeof(CustomData_t));

		if (bRet)
		{
			MemoryManager();
		}

		return bRet;
	}

	return pMsf->SetBlockCustomData(MSF_BLOCKTYPE::CUSTOMEX, id, reinterpret_cast<uint8_t *>(&m_sCustom), sizeof(CustomData_t));
}


