#include "stdafx.h"
#include "BKFloppyImage_HCDos.h"

#include <time.h>

#pragma warning(disable:4996)

CBKFloppyImage_HCDos::CBKFloppyImage_HCDos(const PARSE_RESULT &image)
	: CBKFloppyImage_Prototype(image)
	, m_nFilesNum(0)
{
	m_pKoi8tbl = koi8tbl11M;
	m_nBlockSize = 1024;
	m_nCatSize = 010000; //размер буфера - 8 секторов
	m_pCatBuffer = new uint8_t[m_nCatSize];
	m_bMakeAdd = true;
	m_bMakeDel = true;
	m_bMakeRename = true;
}


CBKFloppyImage_HCDos::~CBKFloppyImage_HCDos()
{
	if (m_pCatBuffer)
	{
		delete[] m_pCatBuffer;
	}
}

int CBKFloppyImage_HCDos::FindRecord(NCdosFileRecord *pHCrec, bool bFull)
{
	auto pDiskCat = reinterpret_cast<NCdosFileRecord *>(m_pCatBuffer + CATALOG_OFFSET);

	for (int i = 1; i < m_nFilesNum; ++i)
	{
		//сравниваем имя
		if (memcmp(pDiskCat[i].name, pHCrec->name, 16) == 0)
		{
			//если полное сравнение
			if (bFull)
			{
				//сравним ещё и другие параметры
				if (pDiskCat[i].address == pHCrec->address && pDiskCat[i].length == pHCrec->length)
				{
					return i;
				}
			}
			else
			{
				return i;
			}
		}
	}

	return -1;
}

void CBKFloppyImage_HCDos::ConvertAbstractToRealRecord(AbstractFileRecord *pFR, bool bRenameOnly)
{
	auto pHCRec = reinterpret_cast<NCdosFileRecord *>(pFR->pSpecificData); //Вот эту запись надо преобразовать

	//преобразовывать будем только если там ещё не сформирована реальная запись.
	if (pFR->nSpecificDataLength == 0 || bRenameOnly)
	{
		if (!bRenameOnly)
		{
			pFR->nSpecificDataLength = sizeof(NCdosFileRecord);
			memset(pHCRec, 0, sizeof(NCdosFileRecord));
		}
		//теперь сформируем HCшную запись из абстрактной
		wchar_t szName[_MAX_PATH];
		wchar_t szExt[_MAX_EXT];
		_tsplitpath_s(pFR->strName.c_str(), nullptr, 0, nullptr, 0, szName, _MAX_PATH, szExt, _MAX_EXT);
		wstring name = wstring(szName);
		trim(&name);
		strToUpper(&name);
		wstring ext = wstring(szExt);
		trim(&ext);
		strToUpper(&ext);

		if (!ext.empty())
		{
			UNICODEtoBK(name, pHCRec->name, 12, true);
			UNICODEtoBK(ext, pHCRec->ext, 4, true);
		}
		else
		{
			UNICODEtoBK(name, pHCRec->name, 16, true);
		}

		if (!bRenameOnly)
		{
			pHCRec->address = pFR->nAddress;

			if (pFR->nAttr & FR_ATTR_PROTECTED)
			{
				pHCRec->address |= 1;
			}

			pHCRec->length = pFR->nSize;
			tm ctm;
			gmtime_s(&ctm, &pFR->timeCreation);
			int year = (ctm.tm_year + 1900) > 1972 ? ctm.tm_year + 1900 - 1972 : 0;
			pHCRec->date = ((ctm.tm_mon & 0xf) << 10) | ((ctm.tm_mday & 0x1f) << 5) | (year & 0x1f) | ((year & 0x60) << 9);
		}
	}
}

void CBKFloppyImage_HCDos::ConvertRealToAbstractRecord(AbstractFileRecord *pFR)
{
	auto pHCRec = reinterpret_cast<NCdosFileRecord *>(pFR->pSpecificData); //Вот эту запись надо преобразовать

	if (pFR->nSpecificDataLength) //преобразовываем, только если есть реальные данные
	{
		if (pHCRec->ext[0] == '.') //если есть расширение
		{
			pFR->strName = BKToUNICODE(pHCRec->name, 12, m_pKoi8tbl);
			trim(&pFR->strName);
			wstring ext = BKToUNICODE(pHCRec->ext, 4, m_pKoi8tbl);
			trim(&ext);
			pFR->strName += ext;
		}
		else
		{
			pFR->strName = BKToUNICODE(pHCRec->name, 16, m_pKoi8tbl);
			trim(&pFR->strName);
		}

		pFR->nRecType = RECORD_TYPE_FILE;
		pFR->nDirBelong = 0;
		pFR->nDirNum = 0;
		pFR->nAddress = pHCRec->address & 0177776;
		pFR->nSize = pHCRec->length;
		pFR->nBlkSize = (pFR->nSize) ? ((((pFR->nSize - 1) | (m_nBlockSize - 1)) + 1) / m_nBlockSize) : 0;

		if (pHCRec->address & 1)
		{
			pFR->nAttr |= FR_ATTR_PROTECTED;
		}

		//обратная операция для времени
		tm ctm;
		memset(&ctm, 0, sizeof(tm));
		ctm.tm_mday = (pHCRec->date >> 5) & 0x1f;
		ctm.tm_mon = (pHCRec->date >> 10) & 0x0f;
		ctm.tm_year = ((pHCRec->date >> 9) & 0x60) | (pHCRec->date & 0x1f) + 1972 - 1900;

		if (ctm.tm_year < 0)
		{
			ctm.tm_year = 0;
		}

		pFR->timeCreation = mktime(&ctm);
	}
}

bool CBKFloppyImage_HCDos::ReadCurrentDir()
{
	if (!CBKFloppyImage_Prototype::ReadCurrentDir())
	{
		return false;
	}

	//если памяти под массив не выделилось - то ошибка, дальше работать невозможно
	if (m_pCatBuffer == nullptr)
	{
		m_nLastErrorNumber = IMAGE_ERROR_NOT_ENOUGHT_MEMORY;
		return false;
	}

	memset(m_pCatBuffer, 0, m_nCatSize);

	if (!SeektoBlkReadData(0, m_pCatBuffer, m_nCatSize)) //читаем каталог
	{
		return false;
	}

	m_nFilesNum = 0;
	m_sDiskCat.nDataBegin = 4; //блок начала данных
	m_sDiskCat.nTotalRecs = CATALOG_SIZE; //это у нас объём каталога, из него надо будет вычесть общее количество записей
	int used_size = 0;
	auto pDiskCat = reinterpret_cast<NCdosFileRecord *>(m_pCatBuffer + CATALOG_OFFSET);
	AbstractFileRecord AFR; //экземпляр абстрактной записи
	auto pHCRec = reinterpret_cast<NCdosFileRecord *>(AFR.pSpecificData); //а в ней копия оригинальной записи

	for (int i = 1; i < CATALOG_SIZE; ++i) //самую первую запись игнорируем, это метка диска
	{
		m_nFilesNum = i + 1; //нумерация файлов в каталоге начинается с 1, т.е. первая проигнорированная запись - №1

		//первый реальный файл - №2
		if (pDiskCat[i].name[0] == 0)
		{
			break; //теоретически это вообще конец каталога
		}

		//преобразуем запись и поместим в массив
		AFR.clear();
		AFR.nSpecificDataLength = sizeof(NCdosFileRecord);
		*pHCRec = pDiskCat[i]; //копируем текущую запись как есть
		ConvertRealToAbstractRecord(&AFR);
		used_size += AFR.nBlkSize;
		//в качестве начального сектора будем передавать номер файла
		AFR.nStartBlock = m_nFilesNum;
		//вот эту фигню надо как-то в конвертер перенести.
		int n = GetStartBlock(m_nFilesNum);
		uint8_t *pDiskMap = m_pCatBuffer + DISKMAP_OFFSET;

		if (pDiskMap[n] & 0x80)
		{
			AFR.nAttr |= FR_ATTR_HIDDEN;
		}

		m_sDiskCat.vecFC.push_back(AFR);
	}

	m_sDiskCat.nTotalRecs = 0176;
	m_sDiskCat.nFreeRecs = m_sDiskCat.nTotalRecs - m_nFilesNum;
	int imgSize = max(800, (int)m_sParseImgResult.nImageSize / m_nBlockSize); //неизвестно как определяется размер диска в блоках,
	m_sDiskCat.nTotalBlocks = imgSize - 4; //будем высчитывать это по размеру образа.
	m_sDiskCat.nFreeBlocks = m_sDiskCat.nTotalBlocks - used_size;
	return true;
}

bool CBKFloppyImage_HCDos::WriteCurrentDir()
{
	if (!CBKFloppyImage_Prototype::WriteCurrentDir())
	{
		return false;
	}

	//если памяти под массив не выделилось - то ошибка, дальше работать невозможно
	if (m_pCatBuffer == nullptr)
	{
		m_nLastErrorNumber = IMAGE_ERROR_NOT_ENOUGHT_MEMORY;
		return false;
	}

	if (!SeektoBlkWriteData(0, m_pCatBuffer, m_nCatSize)) //сохраняем каталог как есть
	{
		return false;
	}

	return true;
}

bool CBKFloppyImage_HCDos::ReadFile(AbstractFileRecord *pFR, uint8_t *pBuffer)
{
	m_nLastErrorNumber = IMAGE_ERROR_OK;
	bool bRet = true;

	//если /*плохой или удалённый или*/ директория - ничего не делаем
	if (pFR->nAttr & FR_ATTR_DIR)
	{
		m_nLastErrorNumber = IMAGE_ERROR_IS_NOT_FILE;
		return false; //если это не файл - выход с ошибкой
	}

	ConvertAbstractToRealRecord(pFR);
	int nCurBlock = GetStartBlock(pFR->nStartBlock);

	if (nCurBlock == -1)
	{
		m_nLastErrorNumber = IMAGE_ERROR_FS_FORMAT_ERROR;
		return false;
	}

	int nLen = pFR->nSize;
	//ФС допускает фрагментирование
	uint8_t *bufp = pBuffer;

	while (nLen > 0)
	{
		if (nCurBlock == -1)
		{
			m_nLastErrorNumber = IMAGE_ERROR_FS_FORMAT_ERROR;
			return false;
		}

		int nReaded = (nLen >= m_nBlockSize) ? m_nBlockSize : nLen;
		bRet = SeektoBlkReadData(nCurBlock, bufp, nReaded);

		if (!bRet)
		{
			break;
		}

		bufp += nReaded;
		nLen -= nReaded;
		nCurBlock = GetNextBlock(nCurBlock);
	}

	return bRet;
}
//выравниваем длину файла по границе блока
unsigned int CBKFloppyImage_HCDos::EvenLengthToBlkLen(unsigned int fileLen)
{
	return (fileLen) ? (((fileLen - 1) | (m_nBlockSize - 1)) + 1) : 0; //размер проги, выровненный по границе блока
}
unsigned int CBKFloppyImage_HCDos::ConvertLengthToBlkLen(unsigned int fileLen)
{
	unsigned int nBlkLen = EvenLengthToBlkLen(fileLen);
	return nBlkLen / m_nBlockSize; //размер в блоках
}
bool CBKFloppyImage_HCDos::WriteFile(AbstractFileRecord *pFR, uint8_t *pBuffer)
{
	m_nLastErrorNumber = IMAGE_ERROR_OK;
	bool bRet = false;

	if (m_bFileROMode)
	{
		//Если образ открылся только для чтения,
		m_nLastErrorNumber = IMAGE_ERROR_FILE_WRITE_PROTECRD;
		return false; //то записать в него мы ничего не сможем.
	}

	if (pFR->nAttr & (FR_ATTR_DIR | FR_ATTR_LINK))
	{
		m_nLastErrorNumber = IMAGE_ERROR_IS_NOT_FILE;
		return false; //если это не файл - выход с ошибкой
	}

	ConvertAbstractToRealRecord(pFR);
	auto pDiskCat = reinterpret_cast<NCdosFileRecord *>(m_pCatBuffer + CATALOG_OFFSET);//каталог диска
	auto pHCRec = reinterpret_cast<NCdosFileRecord *>(pFR->pSpecificData); //Вот эту запись надо добавить
	pFR->nDirBelong = m_sDiskCat.nCurrDirNum; //номер текущего открытого подкаталога

	if (m_sDiskCat.nFreeBlocks < (int)ConvertLengthToBlkLen(pHCRec->length))
	{
		m_nLastErrorNumber = IMAGE_ERROR_FS_DISK_FULL;
		return false;
	}

	if (m_sDiskCat.nFreeRecs <= 0)
	{
		m_nLastErrorNumber = IMAGE_ERROR_FS_CAT_FULL;
		return false;
	}

	//поищем, вдруг такое имя файла уже существует.
	int nIndex = FindRecord(pHCRec, true);

	if (nIndex >= 0)
	{
		m_nLastErrorNumber = IMAGE_ERROR_FS_FILE_EXIST; //если файл существует - выходим с ошибкой
		*pHCRec = pDiskCat[nIndex];
		ConvertRealToAbstractRecord(pFR); //и сделаем найденную запись актуальной.
		return false;
	}

	//попадая сюда, мы гарантированно имеем в конце каталога свободное место, там и создадим запись
	nIndex = m_nFilesNum; //это номер нашего добавляемого файла
	pDiskCat[m_nFilesNum - 1] = *pHCRec;
	memset(&pDiskCat[m_nFilesNum++], 0, sizeof(NCdosFileRecord)); //на всякий случай - обозначим конец каталога
	//теперь сохраним файл
	int nCurBlock = GetFreeBlock(0);

	if (nCurBlock == -1)
	{
		m_nLastErrorNumber = IMAGE_ERROR_FS_FORMAT_ERROR;
		return false;
	}

	//ФС допускает фрагментирование
	uint8_t *bufp = pBuffer;
	int nLen = pFR->nSize;

	if (nLen == 0)
	{
		nLen++; //запись файла нулевой длины не допускается, поэтому скорректируем.
	}

	while (nLen > 0)
	{
		int nReaded = (nLen >= m_nBlockSize) ? m_nBlockSize : nLen;
		bRet = SeektoBlkWriteData(nCurBlock, bufp, nReaded);

		if (!bRet)
		{
			break;
		}

		bufp += nReaded;
		nLen -= nReaded;
		MarkBlock(nCurBlock, nIndex);
		nCurBlock = GetFreeBlock(nCurBlock);

		if (nCurBlock == -1)
		{
			m_nLastErrorNumber = IMAGE_ERROR_FS_FORMAT_ERROR;
			return false;
		}
	}

	if (m_nLastErrorNumber == IMAGE_ERROR_OK)
	{
		//наконец сохраняем каталог
		bRet = WriteCurrentDir();
		//чтобы каталог заново не перечитывать, корректируем данные
		m_sDiskCat.nFreeBlocks -= ConvertLengthToBlkLen(pHCRec->length);
		m_sDiskCat.nFreeRecs--;
	}

	return bRet;
}

bool CBKFloppyImage_HCDos::DeleteFile(AbstractFileRecord *pFR, bool bForce)
{
	m_nLastErrorNumber = IMAGE_ERROR_OK;
	bool bRet = false;

	if (m_bFileROMode)
	{
		//Если образ открылся только для чтения,
		m_nLastErrorNumber = IMAGE_ERROR_FILE_WRITE_PROTECRD;
		return bRet; //то записать в него мы ничего не сможем.
	}

	if (pFR->nAttr & (FR_ATTR_DIR | FR_ATTR_LINK))
	{
		m_nLastErrorNumber = IMAGE_ERROR_IS_NOT_FILE;
		return false; //если это не файл - выход с ошибкой
	}

	if (pFR->nAttr & FR_ATTR_DELETED)
	{
		return true; //уже удалённое не удаляем повторно
	}

	ConvertAbstractToRealRecord(pFR);
	auto pDiskCat = reinterpret_cast<NCdosFileRecord *>(m_pCatBuffer + CATALOG_OFFSET);
	auto pHCRec = reinterpret_cast<NCdosFileRecord *>(pFR->pSpecificData); //Вот эту запись надо удалить
	int nIndex = FindRecord(pHCRec);

	if (nIndex > 0) //Если нашли
	{
		if ((pDiskCat[nIndex].address & 1) && !bForce)
		{
			m_nLastErrorNumber = IMAGE_ERROR_FS_FILE_PROTECTED;
		}
		else
		{
			//в карте диска пометим блоки, занятые файлом как свободные
			uint8_t *pDiskMap = m_pCatBuffer + DISKMAP_OFFSET;

			for (int n = 0; n < DISKMAP_SIZE; ++n)
			{
				if (pDiskMap[n] == 0)
				{
					break;
				}

				if ((pDiskMap[n] & 0x7f) == nIndex + 1)
				{
					pDiskMap[n] = 0;
				}
			}

			//а заданную запись в каталоге просто удалим, сократив каталог.
			while (nIndex < m_nFilesNum - 1)
			{
				pDiskCat[nIndex] = pDiskCat[nIndex + 1];
				nIndex++;
			}

			memset(&pDiskCat[nIndex], 0, sizeof(NCdosFileRecord));
			m_nFilesNum--;
			bRet = WriteCurrentDir(); //сохраним директорию
			m_sDiskCat.nFreeRecs++;
			m_sDiskCat.nFreeBlocks += ConvertLengthToBlkLen(pHCRec->length);
		}
	}
	else
	{
		m_nLastErrorNumber = IMAGE_ERROR_FILE_NOT_FOUND;
	}

	return bRet;
}

bool CBKFloppyImage_HCDos::RenameRecord(AbstractFileRecord *pFR)
{
	auto pHCRec = reinterpret_cast<NCdosFileRecord *>(pFR->pSpecificData); //оригинальная запись
	int nIndex = FindRecord(pHCRec, true); //сперва найдём её

	if (nIndex > 0)
	{
		ConvertAbstractToRealRecord(pFR, true); //теперь скорректируем имя реальной записи
		auto pDiskCat = reinterpret_cast<NCdosFileRecord *>(m_pCatBuffer + CATALOG_OFFSET);
		pDiskCat[nIndex] = *pHCRec; //теперь скорректируем в каталоге
		return WriteCurrentDir(); //сохраним каталог
	}
	else
	{
		//что-то не так
	}

	return false;
}

//находим первый свободный блок в карте диска от заданной позиции.
//вход: blk - текущая позиция в карте
//выход: -1 - ничего не найдено
//		или номер блока
int CBKFloppyImage_HCDos::GetFreeBlock(int blk)
{
	uint8_t *pDiskMap = m_pCatBuffer + DISKMAP_OFFSET;

	while (blk < DISKMAP_SIZE)
	{
		if (pDiskMap[blk] == 0)
		{
			return blk;
		}

		blk++;
	}

	return 0;
}

//помечаем блок как занятый файлом
bool CBKFloppyImage_HCDos::MarkBlock(int blk, int fn)
{
	uint8_t *pDiskMap = m_pCatBuffer + DISKMAP_OFFSET;

	if (blk < DISKMAP_SIZE)
	{
		pDiskMap[blk] = fn;
		return true;
	}

	return false;
}

//находим первый блок заданного файла
//вход: fn - номер файла, 1..0176, не может быть 0
//выход: -1 - ничего не найдено
//		или номер блока
int CBKFloppyImage_HCDos::GetStartBlock(int fn)
{
	uint8_t *pDiskMap = m_pCatBuffer + DISKMAP_OFFSET;

	for (int n = 0; n < DISKMAP_SIZE; ++n)
	{
		if ((pDiskMap[n] & 0x7f) == fn)
		{
			return n;
		}
	}

	return -1;
}

//находим следующий блок заданного файла
//вход: blk - текущий найденный блок заданного файла
//выход: -1 - ничего не найдено
//		или номер блока
int CBKFloppyImage_HCDos::GetNextBlock(int blk)
{
	uint8_t *pDiskMap = m_pCatBuffer + DISKMAP_OFFSET;
	int fn = pDiskMap[blk++] & 0x7f;

	while (blk < DISKMAP_SIZE)
	{
		if ((pDiskMap[blk] & 0x7f) == fn)
		{
			return blk;
		}

		blk++;
	}

	return -1;
}
