// BKTurbo8.cpp: определяет точку входа для консольного приложения.
//

#include "pch.h"
#include "BKTurbo8.h"
#include "Globals.h"
#include "ErrorManager.h"
#include "BKToken.h"
#include "Assemble.h"
#include "LabelManager.h"
#include "Parser.h"
//#include "Parser2.h"
#include "Listing.h"
#include "Object.h"

#include <io.h>
#include <clocale>
#include "getopt.h"
#pragma warning(disable:4996)


constexpr auto EXT_OBJ = 0;
constexpr auto EXT_LST = 1;
constexpr auto EXT_BIN = 2;
constexpr auto EXT_RAW = 3;
static std::wstring m_strExt[] =
{
	L".obj",
	L".lst",
	L".bin",
	L".raw"
};

std::wstring strListingFilename, strObjectFileName, strTableFileName, strResultFile;
OP_MODE operation = OP_MODE::UNKN;

int nStartAddress = -1;
bool m_bMakeListing = false;
bool m_bMakeObject = false;
bool m_bMakeTable = false;
bool m_bMakeRaw = false;

int wmain(int argc, wchar_t *argv[], wchar_t *envp[])
{
	_wsetlocale(LC_ALL, L"Russian");
	InitGlobalParameters(&g_GlobalParameters);
	wchar_t curDir[_MAX_PATH] = { 0 };
	_wgetcwd(curDir, _MAX_PATH);
	g_GlobalParameters.strAppPath = std::wstring(curDir);
	static struct option long_options[] =
	{
		{ L"help",        ARG_NONE, nullptr, L'?' },
		{ L"verbose",     ARG_NONE, nullptr, L'v' },
		{ L"raw",         ARG_NONE, nullptr, L'r' },
		{ L"input",       ARG_REQ,  nullptr, L'i' },
		{ L"listing",     ARG_OPT,  nullptr, L'l' },
		{ L"object",      ARG_OPT,  nullptr, L'o' },
		{ L"table",       ARG_OPT,  nullptr, L't' },
		{ L"address",     ARG_REQ,  nullptr, L's' },
		{ nullptr,        ARG_NULL, nullptr, ARG_NULL }
	};
	static wchar_t optstring[] = L"?vri:l::o::t::s:";
	bool bShowHelp = false;
	int option_index = 0;
	int c;

	while ((c = getopt_long(argc, argv, optstring, long_options, &option_index)) != -1)
	{
		// Handle options
		c = tolower(c);

		switch (c)
		{
			case L'?':
				bShowHelp = true;
				break;

			case L'v':
				g_GlobalParameters.bVerbosity = true;
				break;

			case L'i': // задаём кодировку входного файла
			{
				std::wstring strTmp = (optarg) ? std::wstring(optarg) : L"a";

				if (!strTmp.empty())
				{
					wchar_t wch = tolower(strTmp.at(0)); // оставим только первую букву

					switch (wch)
					{
						case L'k':
							Charset = CReader::FILE_CHARSET::KOI8;
							break;

						case L'o':
							Charset = CReader::FILE_CHARSET::CP866;
							break;

						case L'w':
							Charset = CReader::FILE_CHARSET::CP1251;
							break;

						case L'8':
							Charset = CReader::FILE_CHARSET::UTF8;
							break;

						case L'u':
							Charset = CReader::FILE_CHARSET::UTF16LE;
							break;

						case L'a':
						default:
							Charset = CReader::FILE_CHARSET::UNDEFINE;
							break;
					}
				}
			}
			break;

			case L'r':
				m_bMakeRaw = true;
				break;

			case L'l':
				m_bMakeListing = true;

				if (optarg)
				{
					strListingFilename =  std::wstring(optarg);
				}

				break;

			case L'o':
				m_bMakeObject = true;

				if (optarg)
				{
					strObjectFileName = std::wstring(optarg);
				}

				break;

			case L't':
				m_bMakeTable = true;

				if (optarg)
				{
					strTableFileName = std::wstring(optarg);
				}

				break;

			case L's':
			{
				int nTmp = _tcstol(optarg, nullptr, 8) & 0xffff;

				if (0 <= nTmp && nTmp <= HIGH_BOUND)
				{
					nStartAddress = nTmp;
				}
			}
			break;
		}

		if (bShowHelp)
		{
			Usage();
			return 0;
		}
	}

	argc -= optind;
	argv += optind;

	if (argc < 1)
	{
		wprintf(L"Ошибка в командной строке: отсутствует команда.\n\n");
		Usage();
	}
	else
	{
		std::wstring strCommand = strToLower(std::wstring(*argv).substr(0, 2));

		if (strCommand == L"li")
		{
			operation = OP_MODE::LINK;
		}
		else if (strCommand == L"co")
		{
			operation = OP_MODE::COMP;
		}
		else if (strCommand == L"cl")
		{
			operation = OP_MODE::CL;
		}
		else
		{
			wprintf(L"Ошибка в командной строке: неопознанная команда.\n\n");
			Usage();
			return 0;
		}

		argc--;
		argv++;

		if (argc < 1)
		{
			wprintf(L"Ошибка в командной строке: не хватает аргументов после команды.\n\n");
			Usage();
			return 0;
		}

		if (operation == OP_MODE::LINK)
		{
			strResultFile = std::wstring(*argv);
			argc--;
			argv++;

			if (argc < 1)
			{
				wprintf(L"Ошибка в командной строке: не хватает аргументов после команды LI.\n\n");
				Usage();
				return 0;
			}

			wprintf(L"Линковка...\nСоздаётся файл: %s\n", strResultFile.c_str());

			if (nStartAddress != -1) // если в командной строке был задан адрес загрузки
			{
				// то применим его
				SetStartAddress(nStartAddress);
				g_GlobalParameters.bLinkerFirst = false; // а адреса объектных модулей будем игнорировать
			}
		}
		else if (operation == OP_MODE::COMP)
		{
			if (nStartAddress != -1) // если в командной строке был задан адрес загрузки
			{
				// то применим его
				SetStartAddress(nStartAddress);
			}
		}

		struct _wfinddatai64_t f_file;

		intptr_t hFile;

		bool bRes = true;

		for (; argc > 0; argc--, argv++) // теперь, пока аргументы не кончатся - обрабатывать
		{
			// тут теряется относительный путь в маске поиска. и как это исправить, пока не ясно.
			if ((hFile = _wfindfirsti64(*argv, &f_file)) == -1L)
			{
				wprintf(L"Файл(ы) '%s' не найден(ы)\n", *argv);
			}
			else
			{
				do
				{
					if (f_file.attrib & _A_SUBDIR) // если директория
					{
						if ((wcscmp(f_file.name, L"..") == 0) || (wcscmp(f_file.name, L".") == 0))
						{
							continue;
						}

						// остальные директории игнорируем
					}
					else
					{
						std::wstring strFilePath;
						wchar_t strDrive[_MAX_DIR];
						wchar_t strPath[_MAX_PATH];
						// выделим путь из исходного шаблона
						_tsplitpath_s(*argv, strDrive, _MAX_DIR, strPath, _MAX_PATH, nullptr, 0, nullptr, 0);

						if (!std::wstring(strDrive).empty()) // если задан привод, то в маске был полный путь
						{
							strFilePath = std::wstring(strDrive) + std::wstring(strPath);
						}
						else
						{
							wchar_t CurrDir[_MAX_PATH];
							_wgetcwd(CurrDir, _MAX_PATH); // текущая директория
							strFilePath = std::wstring(CurrDir) + L"\\" + std::wstring(strPath);
						}

						if (operation == OP_MODE::LINK)
						{
							wprintf(L"Линкуется объектный файл: %s ", f_file.name);
							bRes = LoadObj(strFilePath + std::wstring(f_file.name));

							if (bRes)
							{
								wprintf(L"- OK\n");
								LabelLinking();
							}
							else
							{
								wprintf(L"- Ошибка\n");
								break;
							}
						}
						else if (operation == OP_MODE::COMP || operation == OP_MODE::CL)
						{
							workCycle(strFilePath + std::wstring(f_file.name));
							ReInitGlobalParameters(&g_GlobalParameters);
							// чистим все векторы.
							g_labelRefs.Clear();
							g_labelGlobalDefs.Clear();
							g_labelLocalDefs.Clear();
							g_Listing.clear();
						}
					}
				}
				while (_wfindnexti64(hFile, &f_file) == 0);

				_findclose(hFile);
			}

			if (!bRes)
			{
				break;
			}
		}

		if (operation == OP_MODE::LINK && bRes)
		{
			wprintf(L"Адрес компоновки = %#6o\n", GetStartAddress());
			wprintf(L"Размер исполняемого файла = %#6o\n", g_GlobalParameters.nProgramLength);
			SaveFile(strResultFile);

			if (m_bMakeTable)
			{
				wchar_t pName[_MAX_FNAME] = { 0 };
				_wsplitpath_s((strTableFileName.empty() ? strResultFile : strTableFileName).c_str(), nullptr, 0, nullptr, 0, pName, _MAX_FNAME, nullptr, 0);
				std::wstring name = std::wstring(pName) + L"_tbl";
				MakeObj(name, m_strExt[EXT_OBJ], true);
			}

			if (m_bMakeObject)
			{
				MakeObj(strObjectFileName.empty() ? strResultFile : strObjectFileName, m_strExt[EXT_OBJ]);
			}

			if (m_bMakeListing)
			{
				MakeListing(strListingFilename.empty() ? strResultFile : strListingFilename, m_strExt[EXT_LST]);
			}

			PrintLabelTable(strListingFilename.empty() ? strResultFile : strListingFilename, m_strExt[EXT_LST]);
		}
	}

	return 0;
}

int workCycle(std::wstring &strInFileName)
{
	v_Reader.emplace_back(nullptr); // условие выхода из цикла
	g_pReader = new CReader(strInFileName, Charset);
#if (DEBUG_LABEL_MANAGER)
	DebugInit();
#endif

	if (!g_pReader)
	{
		wprintf(L"Недостаточно памяти.\n");
		return -1;
	}

	if (operation == OP_MODE::COMP)
	{
		wprintf(L"Компиляция исполняемого файла...\n");
		// режим компиляции CO
		g_GlobalParameters.nModeLinkage = 1;
	}
	else if (operation == OP_MODE::CL)
	{
		wprintf(L"Компиляция объектного файла...\n");
		// режим компиляции CL
		g_GlobalParameters.nModeLinkage = -1;
	}

	wprintf(L"Файл: %s\nАвтоопределение кодировки: ", strInFileName.c_str());

	switch (g_pReader->GetFileCharset())
	{
		case CReader::FILE_CHARSET::KOI8:
			wprintf(L"КОИ8-Р\n");
			break;

		case CReader::FILE_CHARSET::CP866:
			wprintf(L"OEM CP-866\n");
			break;

		case CReader::FILE_CHARSET::CP1251:
			wprintf(L"ANSI CP-1251\n");
			break;

		case CReader::FILE_CHARSET::UTF8:
			wprintf(L"UTF-8\n");
			break;

		case CReader::FILE_CHARSET::UTF16LE:
			wprintf(L"UTF-16LE(Unicode)\n");
			break;

		case CReader::FILE_CHARSET::UNDEFINE:
			wprintf(L"Не определена. Возможно, это не текстовый файл.\n");
			return -2;

		default:
			wprintf(L"\rОшибка открытия файла.     \n");
			return -3;
	}

	do
	{
		// первый проход
		while (!g_GlobalParameters.bENDReached && !g_pReader->isEOF())
		{
			ParseLine();

			if (g_GlobalParameters.nPC >= HIGH_BOUND)
			{
				wprintf(L"Критическая ошибка:: Достигнут предел свободной памяти!\n");
				break;
			}
		}

		if (!g_GlobalParameters.bENDReached)
		{
			if (g_GlobalParameters.bInclude)
			{
				g_GlobalParameters.bInclude = false;
			}
			else
			{
				OutError(ERROR_121, false); // Отсутствует .END
			}
		}

		delete g_pReader; // удаляем текущий экземпляр
		g_pReader = nullptr;

		if (!v_Reader.empty()) // если в векторе что-то есть
		{
			g_pReader = v_Reader.back(); // достаём
			v_Reader.pop_back();

			if (g_pReader)
			{
				g_GlobalParameters.bENDReached = false;
			}
		}
	}
	while (g_pReader);   // и продолжаем, пока есть что обрабатывать

	int nErr = IsError();

	if (nErr)
	{
		// если были ошибки - одно действие
		wprintf(L"Количество ошибок: %d\n", nErr);
	}
	else
	{
		// если не было ошибок - другое
		if (operation == OP_MODE::CL)
		{
			MakeObj(strObjectFileName.empty() ? strInFileName : strObjectFileName, m_strExt[EXT_OBJ]);
		}
		else
		{
			wprintf(L"Адрес компоновки = %#6o\n", GetStartAddress());
			SaveFile(strInFileName);

			if (m_bMakeObject)
			{
				MakeObj(strObjectFileName.empty() ? strInFileName : strObjectFileName, m_strExt[EXT_OBJ]);
			}
		}

		wprintf(L"Размер исполняемого файла = %#6o\n", g_GlobalParameters.nProgramLength);

		if (m_bMakeTable)
		{
			wchar_t pName[_MAX_FNAME] = { 0 };
			_wsplitpath_s((strTableFileName.empty() ? strInFileName : strTableFileName).c_str(), nullptr, 0, nullptr, 0, pName, _MAX_FNAME, nullptr, 0);
			std::wstring name = std::wstring(pName) + L"_tbl";
			MakeObj(name, m_strExt[EXT_OBJ], true);
		}
	}

	// листинг делаем в любом случае

	if (m_bMakeListing)
	{
		MakeListing(strListingFilename.empty() ? strInFileName : strListingFilename, m_strExt[EXT_LST]);
	}

	PrintLabelTable(strListingFilename.empty() ? strInFileName : strListingFilename, m_strExt[EXT_LST]);
	return 0;
}


/*
Пропускаем остаток строки.
Выход: true - встретился конец файла
false - просто конец строки.
*/
bool SkipTailString(wchar_t &ch)
{
	do
	{
		ch = g_pReader->readChar();
	}
	while (!g_pReader->isEOF() && g_pReader->getCurrCharType() != CReader::CHAR_TYPE::LN);

	if (g_pReader->isEOF())
	{
		return true;
	}

	return false;
}

/*
*Парсим строку, в строке может быть неограниченное число ассемблерных команд.
*Перед командой может быть неограниченное число меток.
*Конец файла тоже считается концом строки, затем надо делать конкретную проверку на именно конец файла.
g_pReader->m_nLineNum
*/
void ParseLine()
{
	CBKToken token;
	int cp = g_GlobalParameters.nPC;    // текущий временный PC, который приращивается во время построения инструкции или псевдокоманды
	Lst_PrepareLine(cp);
	wchar_t ch = g_pReader->readChar(); // читаем первый символ

	for (;;) // крутимся в цикле, пока не достигнем конца строки
	{
		// делаем так, чтобы можно было обрабатывать несколько инструкций в одной строке
		g_GlobalParameters.bInString = false;
		g_GlobalParameters.nAriphmType = 0;
		g_GlobalParameters.nPC = cp; // обычно сюда попадаем перед началом очередной команды, поэтому сохраняем счётчик.

		if (g_pReader->SkipWhitespaces(ch))  // пропустим возможные пробелы
		{
			// если после чтения получился конец строки,
			break;  // то выход из цикла.
		}

		// обрабатываем многострочный комментарий /*...*/
		// нормально парсится инлайн коммент, за которым может быть инструкция.
		// комментарий не может быть внутри инструкции, т.к. такой парсинг не реализован
		if (ch == L'/' && g_pReader->getNextChar() == L'*')
		{
			bool bex = false;
			Lst_AddPrepareLine(0, ListType::LT_COMMENTARY);

			do
			{
				ch = g_pReader->readChar();

				if (g_pReader->getCurrCharType() == CReader::CHAR_TYPE::LN)
				{
					Lst_PrepareLine(cp);
					Lst_AddPrepareLine(0, ListType::LT_COMMENTARY);
				}

				if (g_pReader->isEOF())
				{
					bex = true;
					break;
				}
			}
			while (!(ch == L'*' && g_pReader->getNextChar() == L'/'));

			if (bex)
			{
				break;
			}

			ch = g_pReader->readChar();     // читаем *
			ch = g_pReader->readChar(true); // читаем /
			continue;
		}

		// теперь обрабатываем строку
		if (ch == L';' || (ch == L'/' && g_pReader->getNextChar() == L'/')) // если комментарий ";" или "// "
		{
			SkipTailString(ch);
			Lst_AddPrepareLine(0, ListType::LT_COMMENTARY);
			break; // выходим из цикла, независимо от результата
		}

		if (ch == L'.') // если псевдокоманда или точка
		{
			// обработка псевдокоманды
			ch = g_pReader->readChar(); // читаем следующий за точкой символ.

			// присваивание значения точке
			if (needChar(L'=', ch))
			{
				Lst_AddPrepareLine(cp, ListType::LT_ASSIGN);
				ch = g_pReader->readChar(); // читаем следующий символ.
				int result = 0;
				g_GlobalParameters.nAriphmType = -1;

				if (AriphmParser(cp, result, ch))
				{
					cp = CorrectOffset(result);
					g_GlobalParameters.nPC = cp;
					Lst_AddPrepareLine2(cp);
				}
				else
				{
					SkipTailString(ch);
					break;
				}
			}
			else
			{
				if (!g_pReader->readToken(&token, ch)) // и читаем лексему
				{
					// если токен не прочёлся, значит сразу за точкой встретился посторонний символ.
					OutError(ERROR_103);
					SkipTailString(ch);
					break;
				}

				if (!PseudoCommandExec(&token, cp, ch))
				{
					// если встретилась ошибка, пропустим всю строку
					SkipTailString(ch);
				}
				else
				{
					if (g_GlobalParameters.bStepInclude)
					{
						g_GlobalParameters.bStepInclude = false;
						break;
					}
				}

				Lst_AddPrepareLine2(cp);
			}
		}
		else // дальше что-то, либо метка, либо присваивание, либо команда.
		{
			if (!g_pReader->readToken(&token, ch)) // считаем это лексемой
			{
				// если токен не прочёлся, значит сразу встретился посторонний символ.
				OutError(ERROR_103); // Недопустимый символ в строке.
				SkipTailString(ch);
				break;
			}

			// если сразу за лексемой идёт двоеточие
			if (ch == L':') // значит это метка.
			{
				ch = g_pReader->readChar(true);         // пропускаем ':' и пустоту за ним
				wchar_t wch = token.getName().at(0);    // определяем тип метки.
				// если метка начинается с буквы - это глобальная метка
				// если с цифры - то это локальная метка.
				// остальные символы - любые допустимые.
				bool bl;

				if (g_pReader->AnalyseChar(wch) == CReader::CHAR_TYPE::DIGITS)
				{
					// локальная метка
					bl = AddLocalLabel(&token, g_GlobalParameters.nPC, DEFINITE_LABEL);
				}
				else
				{
					// глобальная метка
					bl = AddGlobalLabel(&token, g_GlobalParameters.nPC, DEFINITE_LABEL);
				}

				Lst_AddPrepareLine(cp, ListType::LT_LABEL);

				if (bl) // если метка добавилась в таблицу
				{
					LabelLinking(); // то линкуем
				}
				else
				{
					OutError(ERROR_116); // иначе ошибка - повторное определение метки
				}
			}
			else if (needChar(L'=', ch)) // если за лексемой, возможно через пробелы идёт '=',
			{
				// то это присваивание
				ch = g_pReader->readChar(true); // пропустим =
				g_GlobalParameters.nAriphmType = -1;
				int result = 0;
				Lst_AddPrepareLine(cp, ListType::LT_ASSIGN);

				/// <summary>
				/// проверка нового парсера. В принципе, работает
				/// </summary>
// 				if (FullAriphmParser(cp, ch)) // если арифметическое выражение распарсилось
// 				{
// 					if (CalcRpn(result))
// 					{
// 						if (!g_labelGlobalDefs.AddLabel(&token, result, CONSTANT_LABEL)) // добавляем метку со значением, без удаления локальных меток
// 						{
// 							OutError(ERROR_116); // если не добавилось - ошибка. уже есть такая метка
// 						}
//
// 						Lst_AddPrepareLine2(result);
// 					}
// 					else
// 					{
// 						// нужно добавить цепочку в таблицу ссылок
// 					}
// 				}
// 				else
// 				{
// 					SkipTailString(ch);
// 					break;
// 				}

				if (AriphmParser(cp, result, ch)) // если арифметическое выражение распарсилось
				{
					if (!g_labelGlobalDefs.AddLabel(&token, result, CONSTANT_LABEL)) // добавляем метку со значением, без удаления локальных меток
					{
						OutError(ERROR_116); // если не добавилось - ошибка. уже есть такая метка
					}

					Lst_AddPrepareLine2(result);
				}
				else
				{
					SkipTailString(ch);
					break;
				}
			}
			else
			{
				g_pReader->SkipWhitespaces(ch); // пропускаем пробелы - на всякий случай

				// обработка ассемблерной команды.
				if (!AssembleCPUInstruction(&token, cp, ch))
				{
					// assert(false);
					// если встретилась ошибка, пропустим всю строку
					SkipTailString(ch);
				}

				Lst_AddPrepareLine2(cp);
			}
		}
	}
}

std::wstring trimLeft(const std::wstring &str, const wchar_t trim_char)
{
	std::wstring res = str;

	while (!res.empty() && (res.front() == trim_char))
	{
		res.erase(res.begin());
	}

	return res;
}

std::wstring trimRight(const std::wstring &str, const wchar_t trim_char)
{
	std::wstring res = str;

	while (!res.empty() && (res.back() == trim_char))
	{
		res.pop_back();
	}

	return res;
}

std::wstring trim(const std::wstring &str, const wchar_t trim_char)
{
	std::wstring res = trimLeft(str, trim_char);
	return trimRight(res, trim_char);
}

std::wstring strToLower(const std::wstring &str)
{
	std::wstring res;

	if (!str.empty())
	{
		_locale_t loc = _create_locale(LC_ALL, "Russian");

		for (wchar_t n : str)
		{
			res.push_back(_tolower_l(n, loc));
		}

		_free_locale(loc);
	}

	return res;
}


void SaveFile(const std::wstring &strName)
{
	wchar_t pDrive[_MAX_DIR] = { 0 };
	wchar_t pPath[_MAX_PATH] = { 0 };
	wchar_t pName[_MAX_FNAME] = { 0 };
	wchar_t pExt[_MAX_EXT] = { 0 };
	_wsplitpath_s(strName.c_str(), pDrive, _MAX_DIR, pPath, _MAX_PATH, pName, _MAX_FNAME, pExt, _MAX_EXT);

	// если получается очень большой файл
	if (g_GlobalParameters.nProgramLength >= HIGH_BOUND)
	{
		m_bMakeRaw = true; // то бин делать никак нельзя
	}

	std::wstring strTmp = std::wstring(pDrive) + std::wstring(pPath) + std::wstring(pName);
	// если у файла стандартное расширение, то его убираем, остальные - оставляем
	std::wstring strExt = trim(strToLower(std::wstring(pExt)));

	if (strExt != L".asm" && strExt != L".obj")
	{
		strTmp += std::wstring(pExt);
	}

	// сделаем новое имя файла - добавим расширение бин или рав
	std::wstring outName = strTmp + m_strExt[m_bMakeRaw ? EXT_RAW : EXT_BIN];
	FILE *of = _wfopen(outName.c_str(), L"wb");

	if (of)
	{
		if (!m_bMakeRaw)
		{
			uint16_t bin[2]{};
			bin[0] = GetStartAddress();
			bin[1] = g_GlobalParameters.nProgramLength;
			fwrite(bin, 1, 4, of);
		}

		fwrite(&g_Memory.b[BASE_ADDRESS], 1, g_GlobalParameters.nProgramLength, of);
		fclose(of);
	}
	else
	{
		wprintf(L"Ошибка создания файла %s\n", outName.c_str());
	}
}

// вывод на экран таблицы меток.
void PrintLabelTable(const std::wstring &strName, const std::wstring &strExt)
{
	wchar_t pDrive[_MAX_DIR] = { 0 };
	wchar_t pPath[_MAX_PATH] = { 0 };
	wchar_t pName[_MAX_FNAME] = { 0 };
	wchar_t pExt[_MAX_EXT] = { 0 };
	_wsplitpath_s(strName.c_str(), pDrive, _MAX_DIR, pPath, _MAX_PATH, pName, _MAX_FNAME, pExt, _MAX_EXT);
	// сделаем новое имя файла
	std::wstring outName = std::wstring(pDrive) + std::wstring(pPath) + std::wstring(pName) + strExt;
	FILE *of = nullptr;

	if (m_bMakeListing)
	{
		of = _wfopen(outName.c_str(), L"a+t");
	}

	// выведем таблицу глобальных меток.
	std::wstring wstr1 = L"\nГлобальные метки\n\n";

	if (g_GlobalParameters.bVerbosity)
	{
		wprintf(wstr1.c_str());
	}

	if (of)
	{
		size_t len = wstr1.length();
		auto *pbuf = new uint8_t[len + 1];

		if (pbuf)
		{
			UNICODEtoBK(wstr1, pbuf, len, false);
			pbuf[len] = 0;
			fprintf(of, "%s", pbuf);
			delete[] pbuf;
		}
	}

	size_t sz = g_labelGlobalDefs.getSize();
	size_t maxlen = 0;

	// сперва найдём самую длинную метку
	for (size_t i = 0; i < sz; ++i)
	{
		std::wstring name = g_labelGlobalDefs.GetLabel(i)->getName();

		if (name.length() > maxlen)
		{
			maxlen = name.length();
		}
	}

	// теперь сформируем строку формата
	static wchar_t buf[32] = { 0 };

	if (maxlen > 16)
	{
		maxlen = 16;
	}

	swprintf_s(buf, 32, L"%%-%ds = %%07o\0", int(maxlen));
	int n = 84 / (int(maxlen) + 12); // вот столько влазит в экран
	int j = n;

	for (size_t i = 0; i < sz; ++i)
	{
		std::wstring name = g_labelGlobalDefs.GetLabel(i)->getName();
		int value = g_labelGlobalDefs.GetValue(i);

		if ((g_labelGlobalDefs.GetType(i) & LABEL_DEFINITE_MASK) == DEFINITE_LABEL)
		{
			value = CorrectAddress(value);
		}

		if (name.length() > maxlen)
		{
			// если метка очень длинная, то её только одну в строке выведем.
			if (g_GlobalParameters.bVerbosity)
			{
				wprintf(L"%-s = %07o", name.c_str(), (value & 0xffff));
			}

			if (of)
			{
				fwprintf(of, L"%-s = %07o", name.c_str(), (value & 0xffff));
			}

			j = n;

			if (g_GlobalParameters.bVerbosity)
			{
				wprintf(L"\n");
			}

			if (of)
			{
				fwprintf(of, L"\n");
			}
		}
		else
		{
			if (g_GlobalParameters.bVerbosity)
			{
				wprintf(buf, name.c_str(), (value & 0xffff));
			}

			if (of)
			{
				fwprintf(of, buf, name.c_str(), (value & 0xffff));
			}

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

				if (g_GlobalParameters.bVerbosity)
				{
					wprintf(L"\n");
				}

				if (of)
				{
					fwprintf(of, L"\n");
				}
			}
			else
			{
				if (g_GlobalParameters.bVerbosity)
				{
					wprintf(L"  ");
				}

				if (of)
				{
					fwprintf(of, L"  ");
				}
			}
		}
	}

	// а теперь выведем оставшиеся ссылки на не найденные метки
	sz = g_labelRefs.getSize();

	if (sz)
	{
		wstr1 = L"\n\nСсылки на не определённые метки!\n\n";
		// if (g_GlobalParameters.bVerbosity)
		{
			wprintf(wstr1.c_str());
		}

		if (of)
		{
			size_t len = wstr1.length();
			auto *pbuf = new uint8_t[len + 1];

			if (pbuf)
			{
				UNICODEtoBK(wstr1, pbuf, len, false);
				pbuf[len] = 0;
				fprintf(of, "%s", pbuf);
				delete[] pbuf;
			}
		}

		// сперва найдём самую длинную метку
		for (size_t i = 0; i < sz; ++i)
		{
			std::wstring name = g_labelRefs.GetLabel(i)->getName();

			if (name.length() > maxlen)
			{
				maxlen = name.length();
			}
		}

		// теперь сформируем строку формата
		if (maxlen > 16)
		{
			maxlen = 16;
		}

		swprintf_s(buf, 32, L"%%07o : %%-%ds\0", int(maxlen));
		n = 84 / (int(maxlen) + 12); // вот столько влазит в экран
		j = n;

		for (size_t i = 0; i < sz; ++i)
		{
			std::wstring name = g_labelRefs.GetLabel(i)->getName();
			int value = g_labelRefs.GetValue(i);
			value = CorrectAddress(value);

			if (name.length() > maxlen)
			{
				// если метка очень длинная, то её только одну в строке выведем.
				// if (g_GlobalParameters.bVerbosity)
				{
					wprintf(L"%07o : %-s", value, name.c_str());
				}

				if (of)
				{
					fwprintf(of, L"%07o : %-s", value, name.c_str());
				}

				j = n;
				// if (g_GlobalParameters.bVerbosity)
				{
					wprintf(L"\n");
				}

				if (of)
				{
					fwprintf(of, L"\n");
				}
			}
			else
			{
				// if (g_GlobalParameters.bVerbosity)
				{
					wprintf(buf, value, name.c_str());
				}

				if (of)
				{
					fwprintf(of, buf, value, name.c_str());
				}

				if (--j <= 0)
				{
					j = n;
					// if (g_GlobalParameters.bVerbosity)
					{
						wprintf(L"\n");
					}

					if (of)
					{
						fwprintf(of, L"\n");
					}
				}
				else
				{
					// if (g_GlobalParameters.bVerbosity)
					{
						wprintf(L"  ");
					}

					if (of)
					{
						fwprintf(of, L"  ");
					}
				}
			}
		}
	}

	// if (g_GlobalParameters.bVerbosity)
	{
		wprintf(L"\n");
	}

	if (of)
	{
		fwprintf(of, L"\n"); fclose(of);
	}
}


void Usage()
{
	wprintf(L"Кросс ассемблер Turbo8 для БК0010-БК0011М\n" \
	        L"(с) 2014-2022 gid\n\n" \
	        L"Использование:\n" \
	        L"BKTurbo8 -? (--help)\n" \
	        L"  Вывод этой справки.\n\n" \
	        L"Имеется два режима работы. Режим компиляции, с созданием объектных модулей\n" \
	        L"и исполняемого файла. И режим линковки объектных модулей.\n\n" \
	        L"1. Режим компиляции.\n" \
	        L"BKTurbo8 [-i<c>][-v][-r][-l[name]][-o[name]][-s<0addr>] <cmd> <file_1 *[ file_n]>\n" \
	        L"  -i<c> (--input <c>) - задать кодировку исходного файла.\n" \
	        L"      Возможные кодировки:\n" \
	        L"        a - автоопределение (по умолчанию)\n" \
	        L"        k - KOI8-R\n" \
	        L"        o - OEM CP866\n" \
	        L"        w - ANSI CP1251\n" \
	        L"        8 - UTF8\n" \
	        L"        u - UNICODE UTF16LE\n" \
	        L"  Если автоопределение определило кодировку некорректно, необходимо задать\n" \
	        L"верную кодировку данным ключом.\n" \
	        L"  -v (--verbose) - вывод большего количества информации на экран.\n" \
	        L"      На данный момент дополнительно выводится таблица меток программы.\n\n" \
	        L"  -r (--raw) - создавать просто бинарный массив, не использовать формат .bin.\n\n" \
	        L"  -l[name] (--listing [name]) - генерировать lst Файл.\n" \
	        L"      Если имя файла задано, то используется оно для генерации листинга, если\n" \
	        L"      нет - то берётся имя файла исходного текста.\n\n" \
	        L"  -o[name] (--object [name]) - генерировать объектный файл.\n" \
	        L"      Если имя файла задано, то используется оно для генерации листинга, если\n" \
	        L"      нет - то берётся имя файла исходного текста.\n\n" \
	        L"  -t[name] (--table [name]) - создавать особый объектный файл, в котором\n" \
	        L"      содержатся только глобальные метки. (См. документацию)\n\n" \
	        L"  -s<0addr> (--address <0addr>) - задать начальный адрес компиляции.\n" \
	        L"      Адрес задаётся в восьмеричном виде.\n\n" \
	        L"  <cmd> - команда компиляции:\n" \
	        L"      CO - полная компиляция. В результате при отсутствии ошибок создаётся\n" \
	        L"           бинарный исполняемый файл и опционально создаются объектные файлы,\n" \
	        L"           заданные соответствующими ключами.\n" \
	        L"      CL - компиляция в объектный файл для дальнейшей линковки с другими\n" \
	        L"           объектными файлами. В результате при отсутствии ошибок всегда\n" \
	        L"           создаётся объектный файл. Бинарный файл не создаётся.\n" \
	        L"      Файл листинга создаётся в любом случае. При наличии ошибок код ошибки\n" \
	        L"      и его текстовое пояснение помещаются перед строкой листинга, вызвавшей\n" \
	        L"      ошибку.\n" \
	        L"      В конец файла листинга записывается таблица глобальных меток, а также\n" \
	        L"      список ссылок на неопределённые метки, если они есть.\n\n" \
	        L"  <file_1 *[ file_n]> - список исходных файлов, перечисленных через пробел.\n" \
	        L"      Допускаются маски файлов.\n\n" \
	        L"2. Режим линковки.\n" \
	        L"BKTurbo8 [-v][-r][-l[name]][-o[name]][-s<0addr>] LI <outfile> <file_1 *[ file_n]>\n" \
	        L"  Ключ -i не используется.\n\n" \
	        L"  Ключи -v, -r, -l, -o, -t и -s имеют тот же смысл, что и в режиме компиляции.\n\n" \
	        L"  Команда линковки - LI, за командой следует обязательное имя выходного файла\n" \
	        L"  <outfile>, маска файла не допускается. А затем список файлов объектных\n" \
	        L"  модулей.\n" \
	        L"  Листинг при этом не создаётся, потому что не из чего, но если задан ключ -l,\n" \
	        L"  в файл листинга сохраняется список меток, а так же список ссылок на \n" \
	        L"  неопределённые метки, если они есть.\n\n" \
	        L"  <file_1 *[ file_n]> - список файлов объектных модулей, перечисленных через\n" \
	        L"      пробел. Допускаются маски файлов.\n\n");
}

