#include "pch.h"
#include "BKFloppyImage_MKDos.h"


CBKFloppyImage_MKDos::CBKFloppyImage_MKDos(const PARSE_RESULT &image)
	: CBKFloppyImage_Prototype(image)
{
	m_nCatSize = 20 * m_nBlockSize; // всю нулевую дорожку будем считать каталогом. // *(uint16_t*)&m_mSector[FMT_MKDOS_FIRST_FILE_BLOCK] * m_nSectorSize;
	// хотя размер каталога в мкдосе почти 9 секторов, чуть-чуть до конца сектора не добивает.
	m_pCatBuffer = new uint8_t[m_nCatSize]; // необязательно выделять массив во время чтения каталога, размер-то его известен
	m_nMKCatSize = MKDOS_CAT_RECORD_SIZE;
	m_nMKLastCatRecord = m_nMKCatSize - 1;
	m_bMakeAdd = true;
	m_bMakeDel = true;
	m_bMakeRename = true;
	m_bChangeAddr = true;
	OnReopenFloppyImage();
}


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

int CBKFloppyImage_MKDos::FindRecord(MKDosFileRecord *pMKRec)
{
	int nIndex = -1;
	auto pDiskCat = reinterpret_cast<MKDosFileRecord *>(m_pCatBuffer + FMT_MKDOS_CAT_BEGIN); // каталог диска

	for (UINT i = 0; i < m_nMKLastCatRecord; ++i) // цикл по всему каталогу
	{
		if (pDiskCat[i].dir_num == m_sDiskCat.nCurrDirNum) // ищем только в текущей директории
		{
			if (memcmp(&pDiskCat[i], pMKRec, sizeof(MKDosFileRecord)) == 0)
			{
				nIndex = static_cast<int>(i);
				break;
			}
		}
	}

	return nIndex;
}

int CBKFloppyImage_MKDos::FindRecord2(MKDosFileRecord *pMKRec, bool bFull)
{
	int nIndex = -1;
	auto pDiskCat = reinterpret_cast<MKDosFileRecord *>(m_pCatBuffer + FMT_MKDOS_CAT_BEGIN);// каталог диска

	for (UINT i = 0; i < m_nMKLastCatRecord; ++i)
	{
		if ((pDiskCat[i].status == 0377) || (pDiskCat[i].status == 0200))
		{
			// если удалённый файл - дырка, то игнорируем
			continue;
		}
		else if (pDiskCat[i].dir_num == m_sDiskCat.nCurrDirNum) // проверяем только в текущей директории
		{
			if (memcmp(pMKRec->name, pDiskCat[i].name, 14) == 0)  // проверим имя
			{
				if (bFull)
				{
					if (pMKRec->name[0] == 0177) // если директория
					{
						if (pDiskCat[i].status == pMKRec->status) // то проверяем номер директории
						{
							nIndex = static_cast<int>(i);
							break;
						}
					}
					else // если файл - то проверяем параметры файла
					{
						if (pDiskCat[i].dir_num == pMKRec->dir_num)
						{
							nIndex = static_cast<int>(i);
							break;
						}
					}
				}
				else
				{
					nIndex = static_cast<int>(i);
					break;
				}
			}
		}
	}

	return nIndex;
}

void CBKFloppyImage_MKDos::ConvertAbstractToRealRecord(BKDirDataItem *pFR, bool bRenameOnly)
{
	auto pMKRec = reinterpret_cast<MKDosFileRecord *>(pFR->pSpecificData); // Вот эту запись надо добавить

	// преобразовывать будем только если там ещё не сформирована реальная запись.
	if (pFR->nSpecificDataLength == 0 || bRenameOnly)
	{
		if (!bRenameOnly)
		{
			pFR->nSpecificDataLength = sizeof(MKDosFileRecord);
			memset(pMKRec, 0, sizeof(MKDosFileRecord));
		}

		// надо сформировать мкдосную запись из абстрактной
		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);
		std::wstring strMKName = std::wstring(szName);
		std::wstring strMKExt = CropStr(std::wstring(szExt), 4); // включая точку
		size_t nNameLen = (14 - strMKExt.length()); // допустимая длина имени

		if (strMKName.length() > nNameLen) // если имя длиньше
		{
			strMKName = CropStr(strMKName, nNameLen); // обрезаем
		}

		strMKName += strMKExt; // прицепляем расширение

		if (pFR->nAttr & FR_ATTR::DIR)
		{
			std::wstring strDir = CropStr(pFR->strName, 13);
			UNICODEtoBK(strDir, pMKRec->name + 1, 13, true);

			if (!bRenameOnly)
			{
				pMKRec->name[0] = 0177; // признак каталога
				pMKRec->status = pFR->nDirNum;
				pMKRec->dir_num = m_sDiskCat.nCurrDirNum; // номер текущего открытого подкаталога
			}
		}
		else
		{
			UNICODEtoBK(strMKName, pMKRec->name, 14, true);
			pMKRec->address = pFR->nAddress; // возможно, если мы сохраняем бин файл, адрес будет браться оттуда

			if (!bRenameOnly)
			{
				if (pFR->nAttr & FR_ATTR::PROTECTED)
				{
					pMKRec->status = 1;
				}
				else
				{
					pMKRec->status = 0;
				}

				pMKRec->dir_num = pFR->nDirBelong;
				UINT nBlkLen = (pFR->nSize) ? (((pFR->nSize - 1) | (m_nBlockSize - 1)) + 1) : 0; // размер проги выровняем по границе блока
				pMKRec->len_blk = nBlkLen / m_nBlockSize; // размер в блоках
				pMKRec->length = pFR->nSize % 0200000; // остаток от длины по модулю 65535
			}
		}
	}
}

// на входе указатель на абстрактную запись.
// в ней заполнена копия реальной записи, по ней формируем абстрактную
void CBKFloppyImage_MKDos::ConvertRealToAbstractRecord(BKDirDataItem *pFR)
{
	auto pMKRec = reinterpret_cast<MKDosFileRecord *>(pFR->pSpecificData); // Вот эту запись надо преобразовать

	if (pFR->nSpecificDataLength) // преобразовываем, только если есть реальные данные
	{
		if (pMKRec->status == 0377)
		{
			pFR->nAttr |= FR_ATTR::DELETED;
		}
		else if (pMKRec->status == 0200)
		{
			pFR->nAttr |= FR_ATTR::BAD;

			if (pMKRec->name[0] == 0)
			{
				pMKRec->name[0] = 'B';
				pMKRec->name[1] = 'A';
				pMKRec->name[2] = 'D';
			}
		}

		if (pMKRec->name[0] == 0177)
		{
			// если директория
			pFR->nAttr |= FR_ATTR::DIR;
			pFR->nRecType = BKDirDataItem::RECORD_TYPE::DIR;
			pFR->strName = trim(BKToUNICODE(pMKRec->name + 1, 13, m_pKoi8tbl));
			pFR->nDirBelong = pMKRec->dir_num;
			pFR->nDirNum = pMKRec->status;
			pFR->nBlkSize = 0;

			// в МКДОС 3.17 вслед за АНДОС 3.30 появились ссылки на диски.
			// опознаются точно так же по ненулевому значению (имя буквы привода) в поле адреса у директории
			if (pMKRec->address && pMKRec->address < 0200)
			{
				pFR->nAttr |= FR_ATTR::LINK;
				pFR->nRecType = BKDirDataItem::RECORD_TYPE::LINK;
			}
		}
		else
		{
			// если файл
			std::wstring name = trim(BKToUNICODE(pMKRec->name, 14, m_pKoi8tbl));
			std::wstring ext;
			size_t l = name.length();
			size_t t = name.rfind(L'.');

			if (t != std::wstring::npos) // если расширение есть
			{
				ext = name.substr(t, l);
				name = trim(name.substr(0, t));
			}

			if (!ext.empty())
			{
				name += ext;
			}

			pFR->strName = name;
			pFR->nDirBelong = pMKRec->dir_num;
			pFR->nDirNum = 0;
			pFR->nBlkSize = pMKRec->len_blk;

			if (pFR->nAttr & FR_ATTR::DELETED)
			{
				pFR->nDirBelong = 0; // а то удалённые файлы вообще не видны. А всё потому, что у удалённых dir_num == 255 тоже.
				pFR->nRecType = BKDirDataItem::RECORD_TYPE::FILE;
			}

			if (pMKRec->status == 2)
			{
				// лог. диск это просто ещё каталог и файлы.
				pFR->nAttr |= FR_ATTR::LOGDISK;
				pFR->nRecType = BKDirDataItem::RECORD_TYPE::LOGDSK;
			}
			else
			{
				pFR->nRecType = BKDirDataItem::RECORD_TYPE::FILE;
			}

			if (pMKRec->status == 1)
			{
				pFR->nAttr |= FR_ATTR::PROTECTED;
			}
		}

		pFR->nAddress = pMKRec->address;

		if (pFR->nAttr & FR_ATTR::LOGDISK) // если у нас логический диск, то размер надо считать по блокам.
		{
			pFR->nSize = pMKRec->len_blk * m_nBlockSize;
		}
		else
		{
			/*  правильный расчёт длины файла.
			т.к. в curr_record.length хранится остаток длины по модулю 0200000, нам из длины в блоках надо узнать
			сколько частей по 0200000 т.е. сколько в блоках частей по 128 блоков.(128 блоков == 65536.== 0200000)

			hw = (curr_record.len_blk >>7 ) << 16; // это старшее слово двойного слова
			hw = (curr_record.len_blk / 128) * 65536 = curr_record.len_blk * m_nSectorSize
			*/
			uint32_t hw = (pMKRec->len_blk << 9) & 0xffff0000; // это старшее слово двойного слова
			pFR->nSize = hw + pMKRec->length; // это ст.слово + мл.слово
		}

		pFR->nStartBlock = pMKRec->start_block;
	}
}


void CBKFloppyImage_MKDos::OnReopenFloppyImage()
{
	m_sDiskCat.bHasDir = true;
	m_sDiskCat.nMaxDirNum = 0177;
}

bool CBKFloppyImage_MKDos::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);
	// каталог диска
	auto pDiskCat = reinterpret_cast<MKDosFileRecord *>(m_pCatBuffer + FMT_MKDOS_CAT_BEGIN);
	BKDirDataItem AFR; // экземпляр абстрактной записи
	auto pMKRec = reinterpret_cast<MKDosFileRecord *>(AFR.pSpecificData); // а в ней копия оригинальной записи

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

	int files_count = 0;
	int files_total = *(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_CAT_RECORD_NUMBER])); // читаем общее кол-во файлов. (НЕ записей!)
	m_sDiskCat.nDataBegin = *(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_FIRST_FILE_BLOCK])); // блок начала данных
	m_sDiskCat.nTotalRecs = m_nMKCatSize; // это у нас объём каталога, из него надо будет вычесть общее количество записей
	int used_size = 0;

	for (UINT i = 0; i < m_nMKCatSize; ++i) // цикл по всему каталогу
	{
		if (files_count >= files_total) // файлы кончились, выходим
		{
			m_nMKLastCatRecord = i;
			break;
		}

		// преобразуем запись и поместим в массив
		AFR.clear();
		AFR.nSpecificDataLength = sizeof(MKDosFileRecord);
		*pMKRec = pDiskCat[i]; // копируем текущую запись как есть
		ConvertRealToAbstractRecord(&AFR);

		if ((pDiskCat[i].status == 0377) || (pDiskCat[i].status == 0200))
		{
			// удалённые не считаются,
			// плохие наверное тоже не считаются, но проверить не на чём
		}
		else
		{
			files_count++;
		}

		if (!(AFR.nAttr & FR_ATTR::DELETED))
		{
			if (pDiskCat[i].name[0] == 0177)
			{
				// если директория
				if (!AppendDirNum(pDiskCat[i].status))
				{
					// встретили дублирование номеров директорий
					m_nLastErrorNumber = IMAGE_ERROR::FS_DIR_DUPLICATE;
				}
			}
			else
			{
				// если файл
				used_size += pDiskCat[i].len_blk;
			}
		}

		// выбираем только те записи, которые к нужной директории принадлежат.
		if (AFR.nDirBelong == m_sDiskCat.nCurrDirNum)
		{
			m_sDiskCat.vecFC.push_back(AFR);
		}
	}

#ifdef _DEBUG
	DebugOutCatalog(reinterpret_cast<MKDosFileRecord *>(m_pCatBuffer + FMT_MKDOS_CAT_BEGIN));
#endif
	m_sDiskCat.nFreeRecs = m_sDiskCat.nTotalRecs - files_total;
	m_sDiskCat.nTotalBlocks = *(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_DISK_SIZE])) - *(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_FIRST_FILE_BLOCK]));
	m_sDiskCat.nFreeBlocks = m_sDiskCat.nTotalBlocks - used_size;
	return true;
}

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

	OptimizeCatalog();

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

#ifdef _DEBUG
	DebugOutCatalog(reinterpret_cast<MKDosFileRecord *>(m_pCatBuffer + FMT_MKDOS_CAT_BEGIN));
#endif

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

	return true;
}

bool CBKFloppyImage_MKDos::ChangeDir(BKDirDataItem *pFR)
{
	if (CBKFloppyImage_Prototype::ChangeDir(pFR))
	{
		return true;
	}
	else if ((pFR->nAttr & FR_ATTR::LOGDISK) && (pFR->nRecType == BKDirDataItem::RECORD_TYPE::LOGDSK))
	{
		m_nLastErrorNumber = IMAGE_ERROR::OK_NOERRORS;
		return true;
	}

	m_nLastErrorNumber = IMAGE_ERROR::FS_IS_NOT_DIR;
	return false;
}

bool CBKFloppyImage_MKDos::VerifyDir(BKDirDataItem *pFR)
{
	if (pFR->nAttr & FR_ATTR::DIR)
	{
		ConvertAbstractToRealRecord(pFR);
		auto pMKRec = reinterpret_cast<MKDosFileRecord *>(pFR->pSpecificData); // Вот эту запись надо добавить
		// проверим, вдруг такая директория уже есть
		int nIndex = FindRecord2(pMKRec, false); // мы тут не знаем номер директории. мы можем только по имени проверить.

		if (nIndex >= 0)
		{
			return true;
		}
	}

	return false;
}

bool CBKFloppyImage_MKDos::CreateDir(BKDirDataItem *pFR)
{
	m_nLastErrorNumber = IMAGE_ERROR::OK_NOERRORS;
	bool bRet = false;

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

	if (m_sDiskCat.nFreeRecs <= 0)
	{
		m_nLastErrorNumber = IMAGE_ERROR::FS_CAT_FULL;
		return false;
	}

	if (pFR->nAttr & FR_ATTR::DIR)
	{
		ConvertAbstractToRealRecord(pFR);
		auto pDiskCat = reinterpret_cast<MKDosFileRecord *>(m_pCatBuffer + FMT_MKDOS_CAT_BEGIN);// каталог диска
		auto pMKRec = reinterpret_cast<MKDosFileRecord *>(pFR->pSpecificData); // Вот эту запись надо добавить
		pFR->nDirBelong = pMKRec->dir_num = m_sDiskCat.nCurrDirNum; // номер текущего открытого подкаталога
		// проверим, вдруг такая директория уже есть
		int nInd = FindRecord2(pMKRec, false); // мы тут не знаем номер директории. мы можем только по имени проверить.

		if (nInd >= 0)
		{
			m_nLastErrorNumber = IMAGE_ERROR::FS_DIR_EXIST;
			pFR->nDirNum = pDiskCat[nInd].status; // и заодно узнаем номер директории
		}
		else
		{
			UINT nIndex = 0;
			// найдём свободное место в каталоге.
			bool bHole = false;
			bool bFound = false;

			for (UINT i = 0; i < m_nMKLastCatRecord; ++i)
			{
				if ((pDiskCat[i].status == 0377) && (pDiskCat[i].name[0] == 0177))
				{
					// если нашли дырку в которой было имя директории
					bHole = true;
					nIndex = i;
					bFound = true;
					break;
				}
			}

			if (!bFound)
			{
				nIndex = m_nMKLastCatRecord;

				if (nIndex < m_nMKCatSize) // если в конце каталога вообще есть место
				{
					bFound = true; // то нашли, иначе - нет
				}
			}

			if (bFound)
			{
				pFR->nDirNum = pMKRec->status = AssignNewDirNum(); // назначаем номер директории.

				if (pFR->nDirNum == 0)
				{
					m_nLastErrorNumber = IMAGE_ERROR::FS_DIRNUM_FULL;
				}

				// если ошибок не произошло, сохраним результаты
				if (bHole)
				{
					// сохраняем нашу запись вместо удалённой директории
					pDiskCat[nIndex] = pMKRec;
				}
				else
				{
					// если нашли свободную область в конце каталога
					int nHoleSize = pDiskCat[nIndex].len_blk;
					int nStartBlock = pDiskCat[nIndex].start_block;
					// сохраняем нашу запись
					pDiskCat[nIndex] = pMKRec;
					// сохраняем признак конца каталога
					MKDosFileRecord hole;
					hole.start_block = nStartBlock + pMKRec->len_blk;
					hole.len_blk = nHoleSize - pMKRec->len_blk;
					// и запись с инфой о свободной области
					pDiskCat[nIndex + 1] = hole;
					m_nMKLastCatRecord++;
				}

				// сохраняем каталог
				*(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_CAT_RECORD_NUMBER])) += 1; // поправим параметры - количество файлов
				bRet = WriteCurrentDir();
				m_sDiskCat.nFreeRecs--;
			}
			else
			{
				m_nLastErrorNumber = IMAGE_ERROR::FS_CAT_FULL;
			}
		}
	}
	else
	{
		m_nLastErrorNumber = IMAGE_ERROR::FS_IS_NOT_DIR;
	}

	return bRet;
}

bool CBKFloppyImage_MKDos::DeleteDir(BKDirDataItem *pFR)
{
	m_nLastErrorNumber = IMAGE_ERROR::OK_NOERRORS;
	bool bRet = false;

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

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

	if (pFR->nAttr & FR_ATTR::DIR)
	{
		ConvertAbstractToRealRecord(pFR);
		// узнать есть ли файлы в директории.
		bool bExist = false;
		auto pDiskCat = reinterpret_cast<MKDosFileRecord *>(m_pCatBuffer + FMT_MKDOS_CAT_BEGIN);// каталог диска
#ifdef _DEBUG
		DebugOutCatalog(pDiskCat);
#endif

		for (UINT i = 0; i < m_nMKLastCatRecord; ++i)
		{
			if (pDiskCat[i].status == 0377)
			{
				// если удалённый файл - дырка, то игнорируем
				continue;
			}
			else if (pDiskCat[i].dir_num == pFR->nDirNum)
			{
				bExist = true; // нашли файл, принадлежащий этой директории
				break;
			}
		}

		// если есть - не удалять.
		if (bExist)
		{
			m_nLastErrorNumber = IMAGE_ERROR::FS_DIR_NOT_EMPTY;
		}
		else
		{
			// удалить можно пустую директорию.
			auto pMKRec = reinterpret_cast<MKDosFileRecord *>(pFR->pSpecificData); // Вот эту запись надо удалить
			int nIndex = FindRecord(pMKRec);

			if (nIndex >= 0) // если нашли
			{
				pDiskCat[nIndex].status = 0377; // пометим как удалённую
				pDiskCat[nIndex].dir_num = 0377;
				*(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_CAT_RECORD_NUMBER])) -= 1; // поправим параметры - количество файлов
				bRet = WriteCurrentDir(); // сохраним директорию
				m_sDiskCat.nFreeRecs++;
			}
			else
			{
				m_nLastErrorNumber = IMAGE_ERROR::FS_FILE_NOT_FOUND;
			}
		}
	}
	else
	{
		m_nLastErrorNumber = IMAGE_ERROR::FS_IS_NOT_DIR;
	}

	return bRet;
}

bool CBKFloppyImage_MKDos::GetNextFileName(BKDirDataItem *pFR)
{
	pFR->clear();
	auto pMKRec = reinterpret_cast<MKDosFileRecord *>(pFR->pSpecificData); // оригинальная запись
	auto pDiskCat = reinterpret_cast<MKDosFileRecord *>(m_pCatBuffer + FMT_MKDOS_CAT_BEGIN);// каталог диска
// #ifdef _DEBUG
//  DebugOutCatalog(pDiskCat);
// #endif

	// и выводим всё что найдём. но по одному.
	while (m_nCatPos < m_nMKLastCatRecord) // цикл по всему каталогу
	{
		if (pDiskCat[m_nCatPos].dir_num == m_sDiskCat.nCurrDirNum) // ищем только в текущей директории
		{
			*pMKRec = pDiskCat[m_nCatPos];
			pFR->nSpecificDataLength = sizeof(MKDosFileRecord);
			ConvertRealToAbstractRecord(pFR);
			m_nCatPos++;
			return true;
		}

		m_nCatPos++;
	}

	if (!m_vecPC.empty())
	{
		m_nCatPos = m_vecPC.back();
		m_vecPC.pop_back();
	}

	return false;
}

bool CBKFloppyImage_MKDos::RenameRecord(BKDirDataItem *pFR)
{
	auto pMKRec = reinterpret_cast<MKDosFileRecord *>(pFR->pSpecificData); // оригинальная запись
	int nIndex = FindRecord(pMKRec); // сперва найдём её

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

	return false;
}


bool CBKFloppyImage_MKDos::ReadFile(BKDirDataItem *pFR, uint8_t *pBuffer)
{
	m_nLastErrorNumber = IMAGE_ERROR::OK_NOERRORS;
	bool bRet = true;

	if (pFR->nAttr & (FR_ATTR::DIR | FR_ATTR::LINK))
	{
		m_nLastErrorNumber = IMAGE_ERROR::FS_IS_NOT_FILE;
		return false; // если это не файл - выход с ошибкой
	}

	ConvertAbstractToRealRecord(pFR);
	size_t len = pFR->nSize;
	size_t sector = pFR->nStartBlock;

	if (SeekToBlock(sector)) // перемещаемся к началу файла
	{
		// поскольку файл располагается единым куском, прочитаем его целиком
		if (len > 0)
		{
			if (!ReadData(pBuffer, len))
			{
				m_nLastErrorNumber = IMAGE_ERROR::IMAGE_CANNOT_READ;
				bRet = false;
			}
		}
	}
	else
	{
		bRet = false;
	}

	return bRet;
}

bool CBKFloppyImage_MKDos::WriteFile(BKDirDataItem *pFR, uint8_t *pBuffer)
{
	m_nLastErrorNumber = IMAGE_ERROR::OK_NOERRORS;
	bool bRet = false;

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

	if (pFR->nAttr & (FR_ATTR::DIR | FR_ATTR::LINK))
	{
		m_nLastErrorNumber = IMAGE_ERROR::FS_IS_NOT_FILE;
		return false; // если это не файл - выход с ошибкой
	}

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

	if (m_sDiskCat.nFreeBlocks < pMKRec->len_blk)
	{
		m_nLastErrorNumber = IMAGE_ERROR::FS_DISK_FULL;
		return false;
	}

	if (m_sDiskCat.nFreeRecs <= 0)
	{
		m_nLastErrorNumber = IMAGE_ERROR::FS_CAT_FULL;
		return false;
	}

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

	if (nInd >= 0)
	{
		m_nLastErrorNumber = IMAGE_ERROR::FS_FILE_EXIST;
		*pMKRec = pDiskCat[nInd];
		ConvertRealToAbstractRecord(pFR); // и сделаем найденную запись актуальной.
		return false;
	}

	UINT nIndex = 0;
	int nIter = 0;
l_wrCycle:
	// найдём свободное место в каталоге.
	bool bFound = false;
	bool bHole = false; // что нашли, дырку или место в конце, т.к. разные способы обработки каталога

	for (UINT i = 0; i < m_nMKLastCatRecord; ++i)
	{
		if ((pDiskCat[i].status == 0377) && (pDiskCat[i].len_blk >= pMKRec->len_blk))
		{
			// если нашли дырку подходящего размера
			bHole = true;
			nIndex = i;
			bFound = true;
			break;
		}
	}

	if (!bFound)
	{
		nIndex = m_nMKLastCatRecord;

		if (nIndex < m_nMKCatSize) // если в конце каталога вообще есть место
		{
			bFound = true; // то нашли, иначе - нет
		}
	}

	if (bFound)
	{
		// можно сохранять
		uint16_t nFirstFreeBlock = pMKRec->start_block = pDiskCat[nIndex].start_block;
		uint16_t nHoleSize = pDiskCat[nIndex].len_blk;

		if (bHole)
		{
			// если нашли дырку в каталоге, надо раздвинуть каталог, или не раздвигать, если размер дырки совпадает
			if (nHoleSize > pMKRec->len_blk)
			{
				// раздвинем каталог
				UINT i = m_nMKLastCatRecord + 1; // найдём конец каталога

				if (i >= m_nMKCatSize) // если каталог раздвинуть нельзя
				{
					m_nLastErrorNumber = IMAGE_ERROR::FS_CAT_FULL;
					goto l_squeeze;
				}
				else
				{
					// раздвигаем
					while (i > nIndex)
					{
						pDiskCat[i] = pDiskCat[i - 1];
						i--;
					}

					// сохраняем нашу запись
					pDiskCat[nIndex] = pMKRec;
					MKDosFileRecord hole;
					hole.status = 0377;
					hole.dir_num = 0377;
					hole.start_block = pMKRec->start_block + pMKRec->len_blk;
					hole.len_blk = nHoleSize - pMKRec->len_blk;
					UNICODEtoBK(L"<HOLE>", hole.name, 14, true);
					hole.length = (hole.len_blk * m_nBlockSize) % 0200000;
					// и запись с инфой о дырке
					pDiskCat[nIndex + 1] = hole;
					m_nMKLastCatRecord++;
				}
			}
			else
			{
				// ничего раздвигать не надо, просто
				// сохраняем нашу запись
				pDiskCat[nIndex] = pMKRec;
			}
		}
		else
		{
			// если нашли свободную область в конце каталога
			// сохраняем нашу запись
			pDiskCat[nIndex] = pMKRec;
			// сохраняем признак конца каталога
			MKDosFileRecord hole;
			hole.start_block = pMKRec->start_block + pMKRec->len_blk;
			hole.len_blk = nHoleSize - pMKRec->len_blk;
			// и запись с инфой о свободной области
			pDiskCat[nIndex + 1] = hole;
			m_nMKLastCatRecord++;
		}

		if (m_nLastErrorNumber == IMAGE_ERROR::OK_NOERRORS)
		{
			// перемещаемся к месту
			if (SeekToBlock(nFirstFreeBlock))
			{
				int nReaded;
				int nLen = pFR->nSize; // размер файла

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

				while (nLen > 0)
				{
					memset(m_mBlock, 0, COPY_BLOCK_SIZE);
					nReaded = (nLen >= COPY_BLOCK_SIZE) ? COPY_BLOCK_SIZE : nLen;
					memcpy(m_mBlock, pBuffer, nReaded);
					pBuffer += nReaded;

					// пишем с выравниванием по секторам.
					if (nReaded > 0)
					{
						if (!WriteData(m_mBlock, (((nReaded - 1) | (m_nBlockSize - 1)) + 1)))
						{
							return false;
						}
					}

					nLen -= nReaded;
				}

				if (m_nLastErrorNumber == IMAGE_ERROR::OK_NOERRORS)
				{
					// если ошибок не произошло, сохраним результаты
					*(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_CAT_RECORD_NUMBER])) += 1;
					*(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_TOTAL_FILES_USED_BLOCKS])) += pMKRec->len_blk;
					// наконец сохраняем каталог
					bRet = WriteCurrentDir();
					m_sDiskCat.nFreeRecs--;
					m_sDiskCat.nFreeBlocks -= pMKRec->len_blk;
				}
			}
		}
	}
	else
	{
l_squeeze:
		// если нет дырки, но суммарное свободное место на диске есть - делаем сквизирование и пишем в конец.
		LRESULT definite = AfxGetMainWnd()->SendMessage(WM_SEND_MESSAGEBOX, WPARAM(MB_YESNO | MB_ICONINFORMATION), reinterpret_cast<LPARAM>(L"Диск сильно фрагментирован. Выполнить сквизирование?"));

		switch (definite)
		{
			case IDYES:
				if (!Squeeze())
				{
					return false;
				}

				if (++nIter < 2)
				{
					goto l_wrCycle;
				}

				break;

			default:
			case IDNO:
				break;
		}

		m_nLastErrorNumber = IMAGE_ERROR::FS_DISK_NEED_SQEEZE;
	}

	return bRet;
}


bool CBKFloppyImage_MKDos::DeleteFile(BKDirDataItem *pFR, bool bForce)
{
	m_nLastErrorNumber = IMAGE_ERROR::OK_NOERRORS;
	bool bRet = false;

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

	if (pFR->nAttr & (FR_ATTR::DIR | FR_ATTR::LINK))
	{
		m_nLastErrorNumber = IMAGE_ERROR::FS_IS_NOT_FILE;
		return false; // если это не файл - выход с ошибкой
	}

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

	ConvertAbstractToRealRecord(pFR);
	auto pMKRec = reinterpret_cast<MKDosFileRecord *>(pFR->pSpecificData); // Вот эту запись надо удалить
	// найдём её в каталоге
	auto pDiskCat = reinterpret_cast<MKDosFileRecord *>(m_pCatBuffer + FMT_MKDOS_CAT_BEGIN);// каталог диска
	int nIndex = FindRecord(pMKRec);

	if (nIndex >= 0) // если нашли
	{
		if ((pDiskCat[nIndex].status == 2) && !bForce)
		{
			m_nLastErrorNumber = IMAGE_ERROR::FS_FILE_PROTECTED;
		}
		else
		{
			pDiskCat[nIndex].status = 0377; // пометим как удалённую
			pDiskCat[nIndex].dir_num = 0377;
			*(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_CAT_RECORD_NUMBER])) -= 1; // поправим параметры - количество файлов
			*(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_TOTAL_FILES_USED_BLOCKS])) -= pDiskCat[nIndex].len_blk; // кол-во занятых блоков
			bRet = WriteCurrentDir(); // сохраним директорию
			m_sDiskCat.nFreeRecs++;
			m_sDiskCat.nFreeBlocks += pDiskCat[nIndex].len_blk;
		}
	}
	else
	{
		m_nLastErrorNumber = IMAGE_ERROR::FS_FILE_NOT_FOUND;
	}

	return bRet;
}

// сквизирование диска
bool CBKFloppyImage_MKDos::Squeeze()
{
	m_nLastErrorNumber = IMAGE_ERROR::OK_NOERRORS;
	bool bRet = true;
	auto pDiskCat = reinterpret_cast<MKDosFileRecord *>(m_pCatBuffer + FMT_MKDOS_CAT_BEGIN);// каталог диска
	int nUsedBlocs = 0;
	UINT p = 0;

	while (p <= m_nMKLastCatRecord)
	{
		if (pDiskCat[p].status == 0377) // если нашли дырку
		{
			UINT n = p + 1; // индекс следующей записи

			if (n > m_nMKLastCatRecord) // если p - последняя запись
			{
				// Тут надо обработать выход
				pDiskCat[p].status = pDiskCat[p].dir_num = 0;
				memset(pDiskCat[p].name, 0, 14);
				pDiskCat[p].address = pDiskCat[p].length = 0;
				pDiskCat[p].start_block = pDiskCat[p - 1].start_block + pDiskCat[p - 1].len_blk;
				pDiskCat[p].len_blk = *(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_DISK_SIZE])) - nUsedBlocs;
				break;
			}

			if (pDiskCat[n].status == 0377) // и за дыркой снова дырка
			{
				pDiskCat[p].len_blk += pDiskCat[n].len_blk; // первую - укрупним

				// а вторую удалим.
				while (n < m_nMKLastCatRecord) // сдвигаем каталог
				{
					pDiskCat[n] = pDiskCat[n + 1];
					n++;
				}

				memset(&pDiskCat[m_nMKLastCatRecord--], 0, sizeof(MKDosFileRecord));
				continue; // и всё сначала
			}

			size_t nBufSize = size_t(pDiskCat[n].len_blk) * m_nBlockSize;
			auto pBuf = new uint8_t[nBufSize];

			if (pBuf)
			{
				if (SeekToBlock(pDiskCat[n].start_block))
				{
					if (!ReadData(pBuf, nBufSize))
					{
						m_nLastErrorNumber = IMAGE_ERROR::IMAGE_CANNOT_READ;
						delete[] pBuf;
						bRet = false;
						break;
					}

					if (SeekToBlock(pDiskCat[p].start_block))
					{
						if (!WriteData(pBuf, nBufSize))
						{
							m_nLastErrorNumber = IMAGE_ERROR::IMAGE_CANNOT_READ;
							delete[] pBuf;
							bRet = false;
							break;
						}

						// теперь надо записи местами поменять.
						std::swap(pDiskCat[p], pDiskCat[n]); // обменяем записи целиком
						std::swap(pDiskCat[p].start_block, pDiskCat[n].start_block); // начальные блоки вернём как было.
						pDiskCat[n].start_block = pDiskCat[p].start_block + pDiskCat[p].len_blk;
					}
					else
					{
						bRet = false;
					}
				}
				else
				{
					bRet = false;
				}

				delete[] pBuf;
			}
			else
			{
				m_nLastErrorNumber = IMAGE_ERROR::NOT_ENOUGHT_MEMORY;
				bRet = false;
				break;
			}
		}

		nUsedBlocs += pDiskCat[p].len_blk;
		p++;
	}

	return bRet;
}

// оптимизация каталога - объединение смежных дырок
bool CBKFloppyImage_MKDos::OptimizeCatalog()
{
	auto pDiskCat = reinterpret_cast<MKDosFileRecord *>(m_pCatBuffer + FMT_MKDOS_CAT_BEGIN);// каталог диска
	int nUsedBlocs = 0;
	UINT p = 0;

	while (p <= m_nMKLastCatRecord)
	{
		if (pDiskCat[p].status == 0377) // если нашли дырку
		{
			UINT n = p + 1; // индекс следующей записи

			if (n > m_nMKLastCatRecord) // если p - последняя запись
			{
				// Тут надо обработать выход
				pDiskCat[p].status = pDiskCat[p].dir_num = 0;
				memset(pDiskCat[p].name, 0, 14);
				pDiskCat[p].address = pDiskCat[p].length = 0;
				pDiskCat[p].start_block = pDiskCat[p - 1].start_block + pDiskCat[p - 1].len_blk;
				pDiskCat[p].len_blk = *(reinterpret_cast<uint16_t *>(&m_pCatBuffer[FMT_MKDOS_DISK_SIZE])) - nUsedBlocs;
				break;
			}

			if (pDiskCat[n].status == 0377) // и за дыркой снова дырка
			{
				pDiskCat[p].len_blk += pDiskCat[n].len_blk; // первую - укрупним

				// а вторую удалим.
				while (n < m_nMKLastCatRecord) // сдвигаем каталог
				{
					pDiskCat[n] = pDiskCat[n + 1];
					n++;
				}

				memset(&pDiskCat[m_nMKLastCatRecord--], 0, sizeof(MKDosFileRecord));
				continue; // и всё сначала
			}
		}

		nUsedBlocs += pDiskCat[p].len_blk;
		p++;
	}

	return true;
}

#ifdef _DEBUG
// отладочный вывод каталога
#pragma warning(disable:4996)
void CBKFloppyImage_MKDos::DebugOutCatalog(MKDosFileRecord *pMKRec)
{
	std::unique_ptr<wchar_t[]> strModuleName(new wchar_t[_MAX_PATH]);
	::GetModuleFileName(AfxGetInstanceHandle(), strModuleName.get(), _MAX_PATH);
	std::unique_ptr<wchar_t[]> strDrive(new wchar_t[_MAX_DRIVE]);
	std::unique_ptr<wchar_t[]> strPath(new wchar_t[_MAX_PATH]);
	_tsplitpath_s(strModuleName.get(), strDrive.get(), _MAX_DRIVE, strPath.get(), _MAX_PATH, nullptr, 0, nullptr, 0);
	std::wstring strName = std::wstring(strDrive.get()) + std::wstring(strPath.get()) + L"\\dirlog.txt";
	FILE *log = _wfopen(strName.c_str(), L"wt");
	fprintf(log, "N#  Stat.Dir.Name           start  lenblk  address length\n");
	uint8_t name[15];

	for (UINT i = 0; i < m_nMKLastCatRecord; ++i)
	{
		memcpy(name, pMKRec[i].name, 14);
		name[14] = 0;
		fprintf(log, "%03d %03d  %03d %-14s %06o %06o  %06o  %06o\n", i, pMKRec[i].status, pMKRec[i].dir_num, name, pMKRec[i].start_block, pMKRec[i].len_blk, pMKRec[i].address, pMKRec[i].length);
	}

	fclose(log);
}
#endif
