#include "StdAfx.h"
#include "Object.h"
#include "Globals.h"
#ifdef TARGET_BIN2OBJ
#include "BKbin2obj.h"
#else
#include "BKTurbo8.h"
#endif // TARGET_BIN2OBJ


#pragma warning(disable:4996)

FILE *objFile = nullptr;

bool MakeObj(wstring &strName, wstring &strExt, bool bTbl)
{
	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);
	//сделаем новое имя файла
	wstring outName = wstring(pDrive) + wstring(pPath) + wstring(pName) + strExt;
	objFile = _wfopen(outName.c_str(), L"wb");

	if (objFile == nullptr)
	{
		wprintf(L"Ошибка создания файла %s\n", outName.c_str());
		return false;
	}

	OBJ_SaveIDBlk();

	if (!bTbl) //когда создаём таблицу меток, сохраняем только глобальные метки
	{
		OBJ_SaveData();
	}

	OBJ_SaveLabelTable(&g_labelGlobalDefs, OBJ_GLB);

	if (!bTbl)
	{
		OBJ_SaveLabelTable(&g_labelLocalDefs, OBJ_LOC);
		OBJ_SaveLabelTable(&g_labelRefs, OBJ_REF);
	}

	return true;
}

OBJIDBlock g_loadID;
bool LoadObj(wstring &strName)
{
	objFile = _wfopen(strName.c_str(), L"rb");

	if (objFile)
	{
		if (OBJ_ReadIDBlk(g_loadID))
		{
			if (g_loadID.nVersion == OBJVERSION)
			{
				if (g_GlobalParameters.bLinkerFirst)
				{
					g_GlobalParameters.nStartAddress = int(g_loadID.nStartAddress); //стартовый адрес берём только у первого модуля.
					g_GlobalParameters.bLinkerFirst = false;
				}

				bool bRes = true;
				OBJBlockHeader hdr;

				if (findBlock(hdr, OBJ_GLB))
				{
					bRes = bRes && OBJ_LoadLabelTable(&g_labelGlobalDefs, hdr);
				}

				if (findBlock(hdr, OBJ_LOC))
				{
					bRes = bRes && OBJ_LoadLabelTable(&g_labelGlobalDefs, hdr);
				}

				if (findBlock(hdr, OBJ_REF))
				{
					bRes = bRes && OBJ_LoadLabelTable(&g_labelRefs, hdr);
				}

				//данные подчитывать после меток, т.к. g_GlobalParameters.nProgramLength тут корректируется
				if (findBlock(hdr, OBJ_DATA))
				{
					bRes = bRes && OBJ_ReadData(hdr.nBlkLen);
				}

				return bRes;
			}
		}

		fclose(objFile);
	}

	return false;
}



bool OBJ_SaveIDBlk()
{
	OBJBlockHeader hdr;
	long fp = ftell(objFile);
	hdr.nHdrTag = OBJ_HDRTAG;
	hdr.nBlockTag = OBJ_ID;
	hdr.nBlkLen = sizeof(OBJIDBlock);
	hdr.nEntryCount = 1;
	hdr.nCheckSum = 0;
	OBJIDBlock data;
	data.nVersion = OBJVERSION;
	data.nStartAddress = int16_t(g_GlobalParameters.nStartAddress); //адрес запуска
	data.nMode = int16_t(g_GlobalParameters.nModeCompone); //режим компоновки
	fwrite(&hdr, 1, sizeof(hdr), objFile);
	fwrite(&data, 1, sizeof(data), objFile);
	return true;
}

bool findBlock(OBJBlockHeader &hdr, OBJTags tag)
{
	fseek(objFile, 0, SEEK_SET);

	while (true)
	{
		fread(&hdr, 1, sizeof(hdr), objFile);

		if (feof(objFile))
		{
			break;
		}

		if (hdr.nHdrTag == OBJ_HDRTAG)
		{
			if (hdr.nBlockTag == tag)
			{
				return true;
			}

			if (fseek(objFile, hdr.nBlkLen, SEEK_CUR))
			{
				break;
			}
		}
	}

	return false;
}

bool OBJ_ReadIDBlk(OBJIDBlock &data)
{
	OBJBlockHeader hdr;

	if (findBlock(hdr, OBJ_ID))
	{
		if (sizeof(data) == fread(&data, 1, sizeof(data), objFile))
		{
			return true;
		}
	}

	return false;
}


bool OBJ_SaveData()
{
	int dataLen = g_GlobalParameters.nProgramLength;
	OBJBlockHeader hdr;
	hdr.nHdrTag = OBJ_HDRTAG;
	hdr.nBlockTag = OBJ_DATA;
	hdr.nBlkLen = dataLen;
	hdr.nEntryCount = 1;
	hdr.nCheckSum = 0;
	fwrite(&hdr, 1, sizeof(hdr), objFile);

	if (dataLen)
	{
		fwrite(&g_Memory.b[START_ADDRESS], 1, dataLen, objFile);
	}

	return true;
}

bool OBJ_ReadData(uint32_t nLen)
{
	if (nLen)
	{
		if (START_ADDRESS + g_GlobalParameters.nProgramLength + nLen < HIGH_BOUND)
		{
			fread(&g_Memory.b[START_ADDRESS + g_GlobalParameters.nProgramLength], 1, nLen, objFile);
			g_GlobalParameters.nProgramLength += nLen;
			return true;
		}
		else
		{
			wprintf(L"Критическая ошибка:: Достигнут предел свободной памяти\n");
			return false;
		}
	}

	return true;
}

bool OBJ_SaveLabelTable(CLabelTable *lbltbl, uint32_t nTag)
{
	size_t sz = lbltbl->getSize();

	if (sz > 0)
	{
		OBJBlockHeader hdr;
		long fp = ftell(objFile);
		hdr.nHdrTag = OBJ_HDRTAG;
		hdr.nBlockTag = nTag;
		hdr.nBlkLen = 0;
		hdr.nEntryCount = uint32_t(sz);
		hdr.nCheckSum = 0;
		fwrite(&hdr, 1, sizeof(hdr), objFile);

		for (size_t i = 0; i < sz; ++i)
		{
			wstring name = lbltbl->GetLabel(i)->getName();
			uint32_t len = uint32_t(name.length());
			int value = lbltbl->GetValue(i);
			uint32_t type = lbltbl->GetType(i);
			uint32_t tag = OBJ_Label;
			fwrite(&tag, 1, sizeof(uint32_t), objFile);
			fwrite(&len, 1, sizeof(uint32_t), objFile);
			fwrite(name.c_str(), 1, sizeof(wchar_t)*len, objFile);
			tag = value;
			fwrite(&tag, 1, sizeof(uint32_t), objFile);
			tag = type;
			fwrite(&tag, 1, sizeof(uint32_t), objFile);
		}

		//как посчитать контрольную сумму пока неясно.
		long efp = ftell(objFile);
		hdr.nBlkLen = efp - fp - sizeof(hdr);
		fseek(objFile, fp, SEEK_SET);
		fwrite(&hdr, 1, sizeof(hdr), objFile); //скорректируем размер блока
		fseek(objFile, efp, SEEK_SET);
	}

	return true;
}

bool OBJ_LoadLabelTable(CLabelTable *lbltbl, OBJBlockHeader &hdr)
{
	//предполагается, что мы прочитали заголовок блока, и знаем нужную таблицу
	//и указатель стоит на начале массива
	uint32_t num = 0; //буфер, куда читаем данные
	uint32_t nLen = hdr.nEntryCount;

	for (uint32_t i = 0; i < nLen; ++i)
	{
		fread(&num, 1, sizeof(uint32_t), objFile);

		if (num != OBJ_Label)
		{
			return false;
		}

		fread(&num, 1, sizeof(uint32_t), objFile);

		if (int(num) <= 0)
		{
			return false;
		}

		auto *name = new wchar_t[num + 1];

		if (name)
		{
			fread(name, 1, sizeof(wchar_t)*num, objFile);
			name[num] = 0;
			wstring labelName = wstring(name);
			delete[] name;
			fread(&num, 1, sizeof(uint32_t), objFile);
			int value = num;
			fread(&num, 1, sizeof(uint32_t), objFile);
			uint32_t type = uint32_t(num);
			CBKToken token(labelName);

			//здесь надо корректировать адреса меток

			if ((type & LABEL_REFERENCE_MASK) == CURRENTPC_LABEL)
			{
				//для спец ссылок точка надо скорректировать PC
				//независимо от всего, правда они могут тут появиться только в режиме CL и только в ссылках на метки
				//но всё равно, на всякий случай тут проверку делать будем
				wstring strCPC = token.getName(); //имя метки используем как текущий PC.
				strCPC = strCPC.substr(1); //убираем первый символ
				int pc = std::stoi(strCPC) * 2;
				pc += g_GlobalParameters.nProgramLength - START_ADDRESS;
				wchar_t buf[32] = { 0 };
				swprintf_s(buf, 32, L"!%05o\0", (pc / 2));
				token.setName(wstring(buf));
			}

			if (hdr.nBlockTag == OBJ_REF) //в ссылках на метки корректируем вообще всё
			{
				//для ссылок на метки надо ко всем прибавить g_GlobalParameters.nProgramLength - START_ADDRESS
				value += g_GlobalParameters.nProgramLength - START_ADDRESS;
			}
			else
			{
				//в таблицах меток надо делать коррекцию в зависимости от режима компоновки
				if (g_loadID.nMode == -1) //какой режим компоновки?
				{
					//режим CL, тут надо корректировать всё

					//для глобальных меток типа DEFINITE_LABEL надо прибавлять g_GlobalParameters.nProgramLength - START_ADDRESS
					if ((type & LABEL_DEFINITE_MASK) == DEFINITE_LABEL)
					{
						value += g_GlobalParameters.nProgramLength - START_ADDRESS;
					}

					//константы надо оставлять нетронутыми
				}
				else
				{
					//режим CO, тут надо корректировать только ссылки на метки

					//если таблица меток - то их адреса надо по-особому корректировать
					if ((type & LABEL_DEFINITE_MASK) == DEFINITE_LABEL) //причём только определения
					{
						value += (g_GlobalParameters.nStartAddress - g_loadID.nStartAddress) - START_ADDRESS;
						value &= 0xffff; //не давать переполнения 16 разрядов.
					}

					//константы надо оставлять нетронутыми
				}
			}

			if (!lbltbl->AddLabel(&token, value, type))
			{
				//добавим немного интеллекту
				int n = lbltbl->SearchLabel(&token); //найдём уже существующую метку

				if (lbltbl->GetValue(n) != value)  //если значение её не совпадает со значением новой метки
				{
					//выведем предупреждение
					wprintf(L"LINK:: Метка '%s' уже существует. Повторная метка не добавлена.\nИсполняемый модуль может быть с ошибкой.\n", labelName.c_str());
				}
			}
		}
		else
		{
			return false;
		}
	}

	return true;
}
