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

#include "pch.h"

#include "BKbin2obj.h"
#include "getopt.h"
#include "Globals.h"
#include "LabelManager.h"
#include "Object.h"
#include "StringUtil.h"
#include "versionNo.h"
#include <clocale>

// #pragma comment (lib,"Gdiplus.lib")
//
// // Update Manifest
// // cf: http://blogs.msdn.com/b/oldnewthing/archive/2007/05/31/2995284.aspx
// //
// // We use features from GDI+ v1.1 which was new as of Windows Vista. There is no redistributable for Windows XP.
// // This adds information to the .exe manifest to force the GDI+ 1.1 version of gdiplus.dll to be loaded.
// // Without this, Windows will load the GDI+ 1.0 version of gdiplus.dll and the application will fail to launch with missing entry points.
// #ifdef _WIN64
//  #pragma comment(linker, "\"/manifestdependency:type='Win32' name='Microsoft.Windows.GdiPlus' version='1.1.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
// #else
//  #pragma comment(linker, "\"/manifestdependency:type='Win32' name='Microsoft.Windows.GdiPlus' version='1.1.0.0' processorArchitecture='X86' publicKeyToken='6595b64144ccf1df' language='*'\"")
// #endif

#pragma warning(disable:4996)

namespace BKBin2Obj
{
	std::_tstring m_strType;
	std::_tstring m_strLabel, m_strFinalLabel;
	fs::path m_strInFileName, m_strOutFileName;
	/*
	опции:
	bColor - чб / цветная картинка
	bTransparency - прозрачность
	*/
	bool m_bColor = false, m_bTransparency = false, m_bEven = false;
	int m_nEvenBound = 0;
}

int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
	setlocale(LC_ALL, "ru_RU");
	g_Globals.InitGlobalParameters();
	static struct option long_options[] =
	{
		{ _T("help"),         ARG_NONE, nullptr, _T('h') },
		{ _T("verstion"),     ARG_NONE, nullptr, _T('v') },
		{ _T("source"),       ARG_REQ,  nullptr, _T('s') },
		{ _T("even"),         ARG_OPT,  nullptr, _T('e') },
		{ _T("label"),        ARG_REQ,  nullptr, _T('l') },
		{ _T("final"),        ARG_REQ,  nullptr, _T('f') },
		{ _T("color"),        ARG_NONE, nullptr, _T('c') },
		{ _T("transparency"), ARG_NONE, nullptr, _T('t') },
		{ nullptr,         ARG_NULL, nullptr, ARG_NULL }
	};
	static TCHAR optstring[] = _T("?s:e::l:f:ct");
	bool bShowHelp = false;
	int option_index = 0;
	int c{};

	while ((c = getopt_long(argc, argv, optstring, long_options, &option_index)) != -1)
	{
		// Handle options
		switch (c)
		{
			case _T('h'):
				bShowHelp = true;
				break;

			case _T('v'):
				BKBin2Obj::PrintVersion();
				return 0;

			case _T('s'):
				if (optarg)
				{
					BKBin2Obj::m_strType = strUtil::strToUpper(std::_tstring(optarg));
					BKBin2Obj::m_strType = BKBin2Obj::m_strType.substr(0, 3);
				}

				break;

			case _T('e'):
				BKBin2Obj::m_bEven = true;

				if (optarg)
				{
					const auto str = std::_tstring(optarg);
					const int nTmp = std::stoi(str, nullptr, 8) & 0xffff;

					if (0 <= nTmp && nTmp <= 0100000)
					{
						BKBin2Obj::m_nEvenBound = nTmp;
					}
				}

				break;

			case _T('l'):
				BKBin2Obj::m_bEven = true;

				if (optarg)
				{
					BKBin2Obj::m_strLabel = strUtil::strToUpper(std::_tstring(optarg));
				}

				break;

			case _T('f'):
				BKBin2Obj::m_bEven = true;

				if (optarg)
				{
					BKBin2Obj::m_strFinalLabel = strUtil::strToUpper(std::_tstring(optarg));
				}

				break;

			case _T('c'):
				BKBin2Obj::m_bColor = true;
				break;

			case _T('t'):
				BKBin2Obj::m_bTransparency = true;
				break;
		}

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

	argc -= optind;
	argv += optind;

	if (argc < 1)
	{
		std::_tcout << _T("Ошибка в командной строке: не задано имя входного файла.\n\n");
		BKBin2Obj::Usage();
	}
	else
	{
		BKBin2Obj::m_strInFileName = fs::path(*argv);
		argc--;
		argv++;

		if (argc >= 1)
		{
			BKBin2Obj::m_strOutFileName = fs::path(*argv);
		}
		else
		{
			BKBin2Obj::m_strOutFileName = BKBin2Obj::m_strInFileName;
		}

		if (BKBin2Obj::m_strLabel.empty())
		{
			// если имя метки не было задано
			// сделаем имя метки по умолчанию
			BKBin2Obj::m_strLabel = strUtil::strToUpper(BKBin2Obj::m_strInFileName.stem()._tstring());
		}

		g_Globals.SetLinkMode(LINKING_MODE::CL);

		if (BKBin2Obj::m_strType == _T("BIN"))
		{
			BKBin2Obj::ConvertBin();
		}
		else if (BKBin2Obj::m_strType == _T("IMG"))
		{
			BKBin2Obj::ConvertImage();
		}
	}

	return 0;
}

void BKBin2Obj::ConvertImage()
{
	CImage img;
#if defined(_UNICODE)
	auto wstr = m_strInFileName._tstring();
	// сперва узнаем, какого размера будет новая строка
	size_t t_len = stbi_convert_wchar_to_utf8(nullptr, 0, wstr.c_str());
	auto str = std::vector<char>(t_len + 1);
	// преобразуем
	t_len = stbi_convert_wchar_to_utf8(str.data(), t_len, wstr.c_str());
	const char *name = str.data();
#else
	const char *name = m_strInFileName.string().c_str();
#endif

	if (SUCCEEDED(img.Load(name)))
	{
		const int w = img.GetWidth();
		const int h = img.GetHeight();
		// посчитаем размер БКшного ресурса при таких размерах картинки.
		int size = 0;

		if (m_bColor)
		{
			// для цветной картинки.
			// 1 пиксел - 2 бита
			size = ((w + 3) / 4) * h * (m_bTransparency ? 2 : 1);
		}
		else
		{
			// для чёрно-белой картинки.
			// 1 пиксел - 1 бит
			size = ((w + 7) / 8) * h * (m_bTransparency ? 2 : 1);
		}

		if (size < 65536)
		{
			if (size >= 32768)
			{
				std::_tcout << _T("Предупреждение! Размер ресурса больше половины доступной памяти БК.\n");
			}

			// конвертируем
			if (m_bColor)
			{
				convertBitmap2(&g_Memory.b[BASE_ADDRESS], &img, m_bTransparency);
			}
			else
			{
				convertBitmap1(&g_Memory.b[BASE_ADDRESS], &img, m_bTransparency);
			}

#if !defined(_WIN32)
			std::wstring lbl = strUtil::s2ws(m_strLabel);
			CBKToken token(lbl); // добавляем метку
#else
			CBKToken token(m_strLabel); // добавляем метку
#endif
			g_labelGlobalDefs.AddLabel(&token, BASE_ADDRESS, LBL_GLOBAL);

			if (m_bEven)
			{
				if (m_nEvenBound)
				{
					// выравнивание по произвольной границе степени 2
					size = size ? ((size - 1) | (m_nEvenBound - 1)) + 1 : 0;
				}
				else
				{
					// выравниваем по чётному адресу
					size++;
					size &= ~1;
				}
			}

			g_Globals.SetProgramLength(size);
			ObjManger::MakeObj(m_strOutFileName, std::_tstring(_T(".obj")));
		}
		else
		{
			std::_tcout <<  _T("Размер ресурса слишком велик.\n");
		}

		img.Destroy();
	}
	else
	{
		std::_tcout << _T("Ошибка загрузки файла изображения ") << m_strInFileName << std::endl;
	}
}


// преобразовываем обычный массив бинарных данных
void BKBin2Obj::ConvertBin()
{
	FILE *inf = _tfopen(m_strInFileName._tstring().c_str(), _T("rb"));

	if (inf)
	{
		// узнаем размер файла
		auto size = static_cast<long>(fs::file_size(m_strInFileName));

		if (size < 65536)
		{
			if (size >= 32768)
			{
				std::_tcout << _T("Предупреждение! Размер ресурса больше половины доступной памяти БК.\n");
			}

			fread(&g_Memory.b[BASE_ADDRESS], 1, size, inf); // читаем файл
#if !defined(_WIN32)
			std::wstring lbl = strUtil::s2ws(m_strLabel);
			CBKToken token(lbl); // добавляем метку
#else
			CBKToken token(m_strLabel); // добавляем метку
#endif
			g_labelGlobalDefs.AddLabel(&token, BASE_ADDRESS, LBL_GLOBAL);

			if (m_bEven)
			{
				if (m_nEvenBound)
				{
					// выравнивание по произвольной границе степени 2
					size = size ? ((size - 1) | (m_nEvenBound - 1)) + 1 : 0;
				}
				else
				{
					// выравниваем по чётному адресу
					size++;
					size &= ~1;
				}
			}

			if (!m_strFinalLabel.empty())
			{
#if !defined(_WIN32)
				std::wstring lbl = strUtil::s2ws(m_strFinalLabel);
				CBKToken finalToken(lbl); // добавляем метку
#else
				CBKToken finalToken(m_strFinalLabel); // добавляем метку
#endif
				g_labelGlobalDefs.AddLabel(&finalToken, BASE_ADDRESS + size, LBL_GLOBAL);
			}

			g_Globals.SetProgramLength(size);
			ObjManger::MakeObj(m_strOutFileName, std::_tstring(_T(".obj")));
		}
		else
		{
			std::_tcout << _T("Размер ресурса слишком велик.\n");
		}

		fclose(inf);
	}
	else
	{
		std::_tcout << _T("Ошибка открытия файла ") << m_strInFileName << std::endl;
	}
}

// функции из pdp11asm vinxru
// это цветная картинка
void BKBin2Obj::convertBitmap2(uint8_t *destp, CImage *img, bool t)
{
	const int w = img->GetWidth();
	const int h = img->GetHeight();
	const int destbpl = (w + 3) / 4 * (t ? 2 : 1);

	for (int y = 0; y < h; ++y)
	{
		const size_t basep = static_cast<size_t>(y) * destbpl;

		for (int x = 0; x < w; ++x)
		{
			COLORREF сolor = img->GetPixel(x, y);
			uint8_t BKColor = 0;
			const bool tr = (сolor == 0xFF00FF); // это у нас прозрачный цвет?

			// теперь определим, какой цвет у пикселя
			if ((сolor & 0xff0000) && !(сolor & 0x00f8f8)) // если в синем канале что-то есть, а в остальных почти нету
			{
				BKColor = 1; // то это синий цвет
			}
			else if ((сolor & 0x00ff00) && !(сolor & 0xf800f8)) // если в зелёном канале что-то есть, а в остальных почти нету
			{
				BKColor = 2; // то это зелёный цвет
			}
			else if ((сolor & 0x0000ff) && !(сolor & 0xf8f800)) // если в красном канале что-то есть, а в остальных почти нету
			{
				BKColor = 3; // то это красный цвет
			}
			else
			{
				// все остальные комбинации - чёрный цвет. нефиг тут.
				BKColor = 0; // чёрный
			}

			const uint8_t cbit = BKColor << ((x & 3) * 2);
			const uint8_t bit = 3 << ((x & 3) * 2);
			const size_t x4 = x / 4;
			const size_t x40 = x4 & ~1;

			if (t)
			{
				if (!tr) // если не прозрачный цвет, то формируем точку
				{
					if (w >= 16)
					{
						destp[basep + x4 + x40 + 2] |= cbit;
						destp[basep + x4 + x40] |= bit;
					}
					else
					{
						destp[basep + x4 * 2 + 1] |= cbit;
						destp[basep + x4 * 2] |= bit;
					}
				}

				// если цвет прозрачный - то ничего не делаем.
			}
			else
			{
				destp[basep + x4] |= cbit;
			}
		}
	}
}

// -----------------------------------------------------------------------------
// это чёрно-белая картинка
void BKBin2Obj::convertBitmap1(uint8_t *destp, CImage *img, bool t)
{
	const int w = img->GetWidth();
	const int h = img->GetHeight();
	const size_t destbpl = ((static_cast<size_t>(w) + 7) / 8) * (t ? 2 : 1);

	for (int y = 0; y < h; ++y)
	{
		const size_t basep = static_cast<size_t>(y) * destbpl;

		for (int x = 0; x < w; ++x)
		{
			const uint8_t bit = 1 << (x & 7);
			const size_t x8 = x / 8;
			const size_t x80 = x8 & ~1;
			COLORREF c = img->GetPixel(x, y);
			const bool tr = (c == 0xFF00FF);

			if (c == 0)
			{
				c = 0;
			}
			else
			{
				c = 1;
			}

			if (t)
			{
				if (!tr)
				{
					if (w >= 16)
					{
						if (c)
						{
							destp[basep + x8 + x80 + 2] |= bit;
						}

						destp[basep + x8 + x80] |= bit;
					}
					else
					{
						if (c)
						{
							destp[basep + x8 * 2 + 1] |= bit;
						}

						destp[basep + x8 * 2] |= bit;
					}
				}
			}
			else
			{
				if (c)
				{
					destp[basep + x8] |= c << (x & 7);
				}
			}
		}
	}
}

void BKBin2Obj::Usage()
{
	std::_tcout << _T("Конвертер бинарных объектов в объектные модули кросс ассемблера Turbo8.\n") \
	            _T("(с) 2016-2025 gid\n\n") \
	            _T("Использование:\n") \
	            _T("BKbin2obj -h (--help)\n") \
	            _T("  Вывод этой справки.\n\n") \
	            _T("BKbin2obj -v (--version)\n") \
	            _T("  Показать информацию о версии.\n\n") \
	            _T("BKbin2obj [-s<type>][-c][-t][-e[0bound]][-l<label_name>][-f<label_name>] <input_file_name> [output_file_name]\n") \
	            _T("  -s<source> (--source <source>) - тип входного объекта.\n") \
	            _T("    Возможные типы:\n") \
	            _T("    bin - просто бинарный массив;\n") \
	            _T("    img - картинка в формате BMP, GIF, JPEG, PNG и TIFF.\n\n") \
	            _T("  -e[0bound] (--even[=0bound]) - выравнивание массива данных по границе блока.\n") \
	            _T("    Если параметр не задан - делается выравнивание по границе слова.\n") \
	            _T("    Параметр - число в восьмеричном виде. Предполагается, что число - степень\n") \
	            _T("    двойки. Если задать произвольное число, результат будет совсем не тем, что\n") \
	            _T("    ожидался.\n\n") \
	            _T("  -l<label_name> (--label <label_name>) - задать имя метки. Если имя метки не\n") \
	            _T("    задано, оно формируется из имени входного файла.\n\n") \
	            _T("  -f<label_name> (--final <label_name>) - задать имя финальной метки в конце.\n") \
	            _T("    Если задан ключ -e, то метка ставится в конце выравнивания.\n\n") \
	            _T("  Ключи, действующие только при выборе типа img:\n") \
	            _T("  -c (--color) - обрабатывать картинку как цветное изображение, иначе - чёрно-\n") \
	            _T("    белое.\n\n") \
	            _T("  -t (--transparency) - использование прозрачности.\n") \
	            _T("    Алгоритмы преобразования взяты из проекта pdp11asm vinxru, один к одному,\n") \
	            _T("    я даже не разбирался как они работают.\n\n") \
	            _T("  input_file_name - входной файл.\n") \
	            _T("  output_file_name - необязательное имя выходного файла, если нужно задать\n") \
	            _T("  объектному файлу имя, отличное от входного.\n");
}

void BKBin2Obj::PrintVersion()
{
	std::_tcout << _T(FILE_PRODUCT_STR"\n");
	const auto strVers = std::format(_T("Версия: {}.{}.{}.{}\n"), _T(VERSION_MAJ_STR), _T(VERSION_MIN_STR), _T(VERSION_REV_STR), _T(VERSION_BLD_STR));
	std::_tcout << strVers;
	// ревизия и билд могут начинаться с нуля, но они тем не менее десятичные числа
	// поэтому слегка поизвращаемся.
	std::_tstring str = _T(VERSION_MIN_STR);
	const int vmin = std::stol(str, nullptr, 10);
	str = _T(VERSION_REV_STR);
	const int rev = std::stol(str, nullptr, 10);
	str = _T(VERSION_BLD_STR);
	const int bld = std::stol(str, nullptr, 10);
	const auto strDate = std::format(_T(" {:02d}-{:02d}-{:04d}"), (rev / 100), vmin, (2000 + VERSION_MAJ));
	const auto strTime = std::format(_T(" {:02d}:{:02d}:{:02d}"), (rev % 100), (bld / 100), (bld % 100));
	std::_tcout << _T("Дата сборки:") << strDate << strTime << std::endl;
}

/*
конвертор всяких бинарных объектов в объектные файлы, чтобы их прилинковывать к
ассемблерным объектникам.
*/

/*
Конвертирование картинок.
1. Загружаем картинку используя класс CImage
1.1 Проверяем, BPP картинки совпадает с целевым BPP?
1.2 Если да и если CImage::IsDIBSection, то можно извлекать индексы цветов прямо из CImage
1.3 Тут надо будет проверить может ли вообще эта фигня работать.
2. Создаём объект Bitmap и передаём туда картинку (если это возможно.)
3. В соответствии с выбранным целевым BPP, конвертируем методом Bitmap::ConvertFormat
4. Достаём попиксельно пиксели из битмапа и формируем массив данных.

*/
