#include "pch.h"
#include "BKMessageBox.h"
#include "Config.h"
#include "Globals.h"
#include "resource.h"
#include "StringUtil.h"

// тут будут собраны все глобальные вещи, которые не относятся к конфигу


static const std::array<const std::wstring, 16> g_arStrSMKPgNums =
{
	_T("0000"), _T("2000"), _T("0004"), _T("2004"),
	_T("0010"), _T("2010"), _T("0014"), _T("2014"),
	_T("0001"), _T("2001"), _T("0005"), _T("2005"),
	_T("0011"), _T("2011"), _T("0015"), _T("2015")
};

static const std::array<const std::wstring, 4> g_arStrMMVw1 =
{
	_T("нет"), _T("ОЗУ wo"), _T("ПЗУ"), _T("ОЗУ")
};

static const std::array<const std::wstring, 5> g_arStrMMVw_A16M =
{
	_T("нет"), _T("{:} wo"), _T("ПЗУ"), _T("{:}"), _T("{:} ro")
};

static const std::array<const std::wstring, 5> g_arStrMMVw_SMK =
{
	_T("нет"), _T("{:}.{:} wo"), _T("ПЗУ"), _T("{:}.{:}"), _T("{:}.{:} ro")
};

static const std::array<const std::wstring, 4> g_arStrMMVw_AZ =
{
	_T("нет"), _T("{:o} wo"), _T("{:o} ro"), _T("{:o}"),
};

// массив подготовленных данных для карты памяти, в каждом менеджере памяти
// формируются строковые данные для каждого сегмента памяти каждого устройства
// сегментов памяти 8, +1 для особого ИОсегмента СМК512, имеет смысл только там
std::array<std::array<std::wstring, 9>, 2> g_arStrMMVData{};
// массив подготовленных данных для потокового дизассемблера
// должно быть в 2 раза больше, чем в g_arStrMMVData
// здесь сегмент - 4 кб, по методу работы AZBK, т.к. он может мапиться на всё адресное
// пространство БК (потенциально)
// сегментов 16, +1 для особого ИОсегмента СМК512, имеет смысл только там
// и +1 для чётности, чтобы не извращаться с алгоритмами
std::array<std::wstring, 18> g_arStrStDsData{};

static const CString g_ConsoleName = _T("Консоль");
static const std::wstring g_strEmptyUnit = _T("<empty>");


/* это правильная таблица, но на бк всё по особому, даже кои8 там нестандартный
// таблица соответствия верхней половины аскии кодов с 128 по 255, включая псевдографику
static const TCHAR koi8tbl_RFC1489[128] = {
0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524, 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590,
0x2591, 0x2592, 0x2593, 0x2320, 0x25A0, 0x2219, 0x221A, 0x2248, 0x2264, 0x2265, 0xA0,   0x2321, 0xB0,   0xB2,   0xB7,   0xF7,
0x2550, 0x2551, 0x2552, 0x451,  0x2553, 0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x255C, 0x255D, 0x255E,
0x255F, 0x2560, 0x2561, 0x401,  0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x256B, 0x256C, 0xA9,
_T('ю'), _T('а'), _T('б'), _T('ц'), _T('д'), _T('е'), _T('ф'), _T('г'), _T('х'), _T('и'), _T('й'), _T('к'), _T('л'), _T('м'), _T('н'), _T('о'),
_T('п'), _T('я'), _T('р'), _T('с'), _T('т'), _T('у'), _T('ж'), _T('в'), _T('ь'), _T('ы'), _T('з'), _T('ш'), _T('э'), _T('щ'), _T('ч'), _T('ъ'),
_T('Ю'), _T('А'), _T('Б'), _T('Ц'), _T('Д'), _T('Е'), _T('Ф'), _T('Г'), _T('Х'), _T('И'), _T('Й'), _T('К'), _T('Л'), _T('М'), _T('Н'), _T('О'),
_T('П'), _T('Я'), _T('Р'), _T('С'), _T('Т'), _T('У'), _T('Ж'), _T('В'), _T('Ь'), _T('Ы'), _T('З'), _T('Ш'), _T('Э'), _T('Щ'), _T('Ч'), _T('Ъ')
};
*/
// вот бкшная таблица
// таблица соответствия верхней половины аскии кодов с 128 по 255, включая псевдографику
static const std::array<TCHAR, 128> koi8tbl =
{
	// {200..237} этих символов на бк нету.
	_T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '),
	_T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '),
	_T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '),
	_T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '), _T(' '),
	// {240..277}
	0xb6,    0x2534,  0x2665,  0x2510,  0x2561,  0x251c,  0x2514,  0x2550,
	0x2564,  0x2660,  0x250c,  0x252c,  0x2568,  0x2193,  0x253c,  0x2551,
	0x2524,  0x2190,  0x256c,  0x2191,  0x2663,  0x2500,  0x256b,  0x2502,
	0x2666,  0x2518,  0x256a,  0x2565,  0x2567,  0x255e,  0x2192,  0x2593,
	// {300..337}
	_T('ю'), _T('а'), _T('б'), _T('ц'), _T('д'), _T('е'), _T('ф'), _T('г'),
	_T('х'), _T('и'), _T('й'), _T('к'), _T('л'), _T('м'), _T('н'), _T('о'),
	_T('п'), _T('я'), _T('р'), _T('с'), _T('т'), _T('у'), _T('ж'), _T('в'),
	_T('ь'), _T('ы'), _T('з'), _T('ш'), _T('э'), _T('щ'), _T('ч'), _T('ъ'),
	// {340..377}
	_T('Ю'), _T('А'), _T('Б'), _T('Ц'), _T('Д'), _T('Е'), _T('Ф'), _T('Г'),
	_T('Х'), _T('И'), _T('Й'), _T('К'), _T('Л'), _T('М'), _T('Н'), _T('О'),
	_T('П'), _T('Я'), _T('Р'), _T('С'), _T('Т'), _T('У'), _T('Ж'), _T('В'),
	_T('Ь'), _T('Ы'), _T('З'), _T('Ш'), _T('Э'), _T('Щ'), _T('Ч'), _T('Ъ')
};

CString Global::WordToOctString(const uint16_t word)
{
	CString str;
	WordToOctString(word, str);
	return str;
}


CString Global::ByteToOctString(const uint8_t byte)
{
	CString str;
	ByteToOctString(byte, str);
	return str;
}

void Global::WordToOctString(uint16_t word, CString &str)
{
	LPTSTR pstr = str.GetBufferSetLength(7);

	for (int i = 5; i >= 0; --i)
	{
		pstr[i] = TCHAR(060 + (word & 7));
		word >>= 3;
	}

	str.ReleaseBuffer(6);
}

void Global::ByteToOctString(uint8_t byte, CString &str)
{
	LPTSTR pstr = str.GetBufferSetLength(4);

	for (int i = 2; i >= 0; --i)
	{
		pstr[i] = TCHAR(060 + (byte & 7));
		byte >>= 3;
	}

	str.ReleaseBuffer(3);
}


uint16_t Global::OctStringToWord(const CString &str)
{
	const int word = _tcstol(str, nullptr, 8);
	return LOWORD(word);
}


CString Global::IntToString(const int iInt, const int radix)
{
	CString str;
	_itot_s(iInt, str.GetBufferSetLength(256), 256, radix);
	str.ReleaseBuffer();
	return str;
}

CString Global::IntToFileLengthString(const int iInt)
{
	CString str = IntToString(iInt, 10); // текст в виде строки
	int nPos = str.GetLength() - 3; // указатель на позицию разделителя разрядов

	while (nPos > 0) // пока есть куда вставлять
	{
		str.Insert(nPos, _T(" ")); // вставляем разделитель
		nPos -= 3; // и сдвигаемся левее
	}

	return str;
}

// то же что и для дир + меняем страшные символы '\' и '/' на нестрашную '.'
void Global::SetSafeName(std::wstring &str)
{
	SetSafeDir(str);
	str = strUtil::replaceChars(str, _T("\\/"), _T('.'));
}

// меняем страшные символы '>','<',':','|','?','*' на нестрашную '_'
// и все коды меньше пробела - на '_'
void Global::SetSafeDir(std::wstring &str)
{
	str = strUtil::replaceChars(str, _T("<>:|?*"), _T('_'));

	// а теперь заменим все символы с кодом меньше 32
	for (auto &wch : str)
	{
		if (wch < 32)
		{
			wch = _T('_');
		}
	}
}

// Вход: msTime время в миллисекундах
CString Global::MsTimeToTimeString(const int msTime)
{
	const CString strMin = IntToString(msTime / 60000, 10);
	CString strSec;
	strSec.Format(_T("%02i"), msTime / 1000 % 60);
	return strMin + _T(':') + strSec;
}


std::wstring Global::BKToUNICODE(std::span<uint8_t> pBuff)
{
	std::wstring strRet;

	for (const auto ch : pBuff)
	{
		const TCHAR tch = BKToWIDEChar(ch);
		strRet.push_back(tch);

		if (tch == 0)
		{
			break;
		}
	}

	return strRet;
}

TCHAR Global::BKToWIDEChar(const uint8_t b)
{
	if (b == 0)
	{
		return 0;
	}

	if (b < 32)
	{
		return _T(' ');
	}

	if (b == 127)
	{
		return TCHAR(0x25a0);
	}

	if (b >= 128)
	{
		return koi8tbl[b - 128];
	}

	return TCHAR(b);
}

// эта функция не выдаёт 0, заменяет его пробелом
TCHAR Global::BKToSafeWIDEChar(const uint8_t b)
{
	if (b < ' ')
	{
		return _T(' ');
	}

	if (b == 127)
	{
		return TCHAR(0x25a0);
	}

	if (b >= 128)
	{
		return TCHAR(koi8tbl[b - 128]);
	}

	return TCHAR(b);
}

/*
преобразование юникодной строки в бкшный кои8.
вход:
ustr - преобразуемая строка
pBuff - буфер, куда выводится результат
bufSize - размер буфера
bFillBuf - флаг. если строка короче размера буфера, буфер до конца забивается пробелами. (конца строки - 0 нету.)
*/
void Global::UNICODEtoBK(const std::wstring &ustr, std::span<uint8_t> pBuff, const bool bFillBuf)
{
	const size_t bufSize = pBuff.size();
	size_t bn = 0;

	for (const auto wch : ustr)
	{
		pBuff[bn++] = WIDEtoBKChar(wch);// берём очередной символ

		if (bn >= bufSize) // если буфер БКшной строки короче юникодной строки
		{
			break; // прерываем цикл
		}
	}

	if (bFillBuf) // если нужно и можно
	{
		while (bn < bufSize) // забьём конец БКшного буфера пробелами
		{
			pBuff[bn++] = 32;
		}
	}
}

uint8_t Global::WIDEtoBKChar(const TCHAR ch)
{
	if (ch == _T('\t'))
	{
		return 9;
	}

	if (ch == _T('\n'))
	{
		return 10;
	}

	if (ch < 32)    // если символ меньше пробела,
	{
		return 32;  // то будет пробел
	}

	if ((32 <= ch) && (ch < 127)) // если буквы-цифры- знаки препинания
	{
		return LOBYTE(ch); // то буквы-цифры- знаки препинания
	}

	if (ch == 0x401)    // Если Ё
	{
		return 0345;    // то Е
	}

	if (ch == 0x451)    // если ё
	{
		return 0305;    // то е
	}

	if (ch == 0x25a0)   // если такое
	{
		return 127; // то это. это единственное исключение в нижней половине аски кодов
	}

	// если всякие другие символы
	// то ищем в таблице нужный нам символ, а его номер - будет кодом кои8
	for (uint8_t i = 0; i < 128; ++i)
	{
		if (koi8tbl[i] == ch)
		{
			return (i + 0200);
		}
	}

	return 32; // если такого символа нету в таблице - будет пробел
}


int Global::ToInt(const CString &str)
{
	return std::stoi(str.GetString(), nullptr);
}

long Global::ToLong(const CString &str)
{
	return std::stol(str.GetString(), nullptr);
}

long long Global::ToLongLong(const CString &str)
{
	return std::stoll(str.GetString(), nullptr);
}

unsigned long Global::ToULong(const CString &str)
{
	return std::stoul(str.GetString(), nullptr);
}

unsigned long long Global::ToULongLong(const CString &str)
{
	return std::stoull(str.GetString(), nullptr);
}



CString Global::DwordToStr(DWORD d)
{
	CString str;
	LPTSTR buf = str.GetBufferSetLength(16);
	_sntprintf(buf, 16, _T("%08x"), d);
	str.ReleaseBuffer();
	return _T("#") + str;
}

DWORD Global::StrToDword(const CString &str)
{
	if (str.GetAt(0) == _T('#'))
	{
		return std::stol((str.GetString() + 1), nullptr, 16);
	}

	return 0;
}

#ifdef _DEBUG
// функция для вывода сообщений об ошибках при отладке, когда совсем ничего не понятно, почему не работает
void Global::GetLastErrorOut(LPTSTR lpszFunction)
{
	// Retrieve the system error message for the last-error code
	LPVOID lpMsgBuf = nullptr;
	DWORD dw = GetLastError();
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		nullptr,
		dw,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpMsgBuf,
		0, nullptr);
	// Display the error message and exit the process
	CString strErr;
	strErr.Format(_T("%s failed with error %u: %s"), lpszFunction, dw, (LPCTSTR)lpMsgBuf);
	MessageBox(nullptr, strErr.GetString(), _T("Error"), MB_OK);
	LocalFree(lpMsgBuf);
}
#endif


bool Global::CheckFFMPEG()
{
	const fs::path strName = g_Config.GetConfCurrPath() / _T("ffmpeg.exe");
	return !!fs::exists(strName);
}

bool Global::SaveRawFile(const uint8_t *buf, const uint16_t addr, const uint16_t len, const fs::path &strName)
{
	if (buf && !strName.empty())
	{
		FILE *file = nullptr;

		if ((file = _tfopen(strName.c_str(), _T("wb"))) != nullptr)
		{
			fwrite(buf, 1, len, file);
			fclose(file);
			return true;
		}

		g_BKMsgBox.Show(_T("Ошибка создания файла."), MB_OK);
	}

	return false;
}

/*
* Сохранить массив в формате bin
* Вход: buf - указатель на массив данных
* addr - адрес загрузки
* len - длина файла, == размеру массива buf
* strName - имя файла
*/
bool Global::SaveBinFile(const uint8_t *buf, const uint16_t addr, const uint16_t len, const fs::path &strName)
{
	if (buf && !strName.empty())
	{
		FILE *file = nullptr;

		if ((file = _tfopen(strName.c_str(), _T("wb"))) != nullptr)
		{
			// запишем заголовок
			fwrite(&addr, 1, sizeof(uint16_t), file);
			fwrite(&len, 1, sizeof(uint16_t), file);

			if (g_Config.m_bUseLongBinFormat)
			{
				constexpr auto BKNAME_LEN = 16;
				std::array<uint8_t, BKNAME_LEN> bkName{};
				const std::wstring str = strName.stem().wstring();
				UNICODEtoBK(str, bkName, true);
				fwrite(bkName.data(), 1, BKNAME_LEN, file); // имя файла
			}

			fwrite(buf, 1, len, file);

			if (g_Config.m_bUseLongBinFormat)
			{
				int crc = 0;

				for (int i = 0; i < len; ++i)
				{
					crc += buf[i];

					if (crc & 0xFFFF0000)
					{
						// если случился перенос в 17 разряд (т.е. бит С для word)
						crc &= 0x0000FFFF; // его обнулим
						crc++; // но прибавим к сумме
					}
				}

				fwrite(&crc, 1, sizeof(uint16_t), file);
			}

			fclose(file);
			return true;
		}

		g_BKMsgBox.Show(_T("Ошибка создания файла."), MB_OK);
	}

	return false;
}

/*Проверка на заголовок бин.
* Вход: fsize - реальный размер файла
*       bfp - структура, в которой заполнены поля addr и length - первые 2 слова файла
* Выход: true - bin файл,
*        false - не бин файл, и заполнены остальные поля структуры bfp - offset и isCrc
*/
bool Global::CheckBinHeader(const uintmax_t fsize, BinFileParameters_t &bfp)
{
	if (fsize < 65536)
	{
		bfp.offset = 4;
		bfp.isCrc = false;

		if (bfp.length == fsize - 4  // если простой бин
			|| (bfp.length <= fsize - 4 && fsize - 4 - bfp.length <= 03200 && (0700 <= bfp.addr && bfp.addr <= 02000))) // или кривой, но укладывающийся в логику бин файла
		{
			return true;
		}

		if (bfp.length == fsize - 6) // если усложнённый
		{
			bfp.isCrc = true;
			return true;
		}

		if (bfp.length == fsize - 22) // и не сложный
		{
			bfp.offset = 20;
			bfp.isCrc = true;
			return true;
		}

		bfp.offset = 0; // не бин файл
	}

	return false;
}

bool Global::CheckBinFile(const fs::path &pathName, BinFileParameters_t &bfp)
{
	FILE *binFile = nullptr;

	// если файл есть и открылся для чтения
	if (fs::exists(pathName) &&
		(binFile = _tfopen(pathName.c_str(), _T("rb"))) != nullptr)
	{
		// читаем первое слово - это адрес, второе слово - это длина
		if (fread(&bfp.addr, 1, sizeof(uint16_t), binFile) == sizeof(uint16_t) &&
			fread(&bfp.length, 1, sizeof(uint16_t), binFile) == sizeof(uint16_t))
		{
			const auto fsize = fs::file_size(pathName);
			fclose(binFile);
			return CheckBinHeader(fsize, bfp);
		}
		fclose(binFile);
	}

	return false;
}

/*
* Загрузить массив в формате bin (или не bin с адреса 0)
* CRC игнорируется
* Вход: buf - указатель на массив данных
* addr - адрес загрузки
* len - длина файла, == размеру массива buf
* strName - имя загружаемого файла
* bStrict == true - строго бин файл, false - любой файл
*/
bool Global::LoadBinFile(std::unique_ptr<uint8_t[]> &buf, uint16_t &addr, uint16_t &len, const fs::path &pathName, const bool bStrict)
{
	addr = len = 0;
	fs::path pathWorkName = pathName;

	if (!fs::exists(pathWorkName)) //если такого файла нет
	{
		if (!pathName.has_extension()) // если расширения нет
		{
			CString strExt(MAKEINTRESOURCE(IDS_FILEEXT_BINARY));
			pathWorkName.replace_extension(strExt.GetString()); // добавим расширение
		}
		else // если расширение есть
		{
			pathWorkName.replace_extension(); // удалим расширение
		}

		if (!fs::exists(pathWorkName)) //если и такого файла нет
		{
			return false; // выходим с ошибкой
		}
	}

	bool bRet = false;
	FILE *file = nullptr;

	if ((file = _tfopen(pathWorkName.c_str(), _T("rb"))) != nullptr)
	{
		BinFileParameters_t bfp{ 0, 0, 0, false };

		// читаем первое слово - это адрес, и второе - это размер
		if (fread(&bfp.addr, 1, sizeof(uint16_t), file) == sizeof(uint16_t) &&
			fread(&bfp.length, 1, sizeof(uint16_t), file) == sizeof(uint16_t))
		{
			const auto nFileLen = fs::file_size(pathWorkName);
			bool bIsBin = CheckBinHeader(nFileLen, bfp);
			fseek(file, bfp.offset, SEEK_SET);

			if (!bIsBin)
			{
				//будем загружать как простой файл.
				bfp.addr = 0;
				bfp.length = (min(0160000ull, nFileLen)) & 0xffff; //ограничим размер, 1600000 - это максимум в режиме ALL CMK-512, режимы ОЗУ11 и Hlt11, где доступно 170000 и 177600 байтов ОЗУ учитывать не будем
			}

			if (bIsBin || !bStrict)
			{
				buf = std::make_unique<uint8_t[]>(bfp.length);

				fread(buf.get(), 1, bfp.length, file);
				addr = bfp.addr;
				len = bfp.length;
				bRet = true;
			}
		}

		fclose(file);
	}

	return bRet;
}

// выход: true - строка равна g_strEmptyUnit
//      false - не равна
bool Global::isEmptyUnit(const CString &s)
{
	return isEmptyUnit(std::wstring(s.GetString()));
}

bool Global::isEmptyUnit(const std::wstring &s)
{
	return strUtil::equalNoCase(g_strEmptyUnit, s);
}

bool Global::isEmptyUnit(const fs::path &s)
{
	return isEmptyUnit(s.wstring());
}

void Global::SetMonospaceFont(HWND hwnd, CFont *pFont)
{
	LOGFONT lf{};
	::GetObject((HGDIOBJ)::SendMessage(hwnd, WM_GETFONT, 0, 0), sizeof(LOGFONT), &lf);
	_tcscpy_s(lf.lfFaceName, LF_FACESIZE, _T("Courier New\0"));

	if (!pFont->CreateFontIndirect(&lf))
	{
		_tcscpy_s(lf.lfFaceName, LF_FACESIZE, _T("Lucida Console\0"));
		pFont->CreateFontIndirect(&lf);
	}
}

