#include "StdAfx.h"
#include "BKDRT11.h"


const TCHAR CBKDRT11::RADIX50[050] =
{
	//000..007
	_T(' '), _T('A'), _T('B'), _T('C'), _T('D'), _T('E'), _T('F'), _T('G'),
	//010..017
	_T('H'), _T('I'), _T('J'), _T('K'), _T('L'), _T('M'), _T('N'), _T('O'),
	//020..027
	_T('P'), _T('Q'), _T('R'), _T('S'), _T('T'), _T('U'), _T('V'), _T('W'),
	//030..037
	_T('X'), _T('Y'), _T('Z'), _T('$'), _T('.'), _T(' '), _T('0'), _T('1'),
	//040..047
	_T('2'), _T('3'), _T('4'), _T('5'), _T('6'), _T('7'), _T('8'), _T('9')
};

CBKDRT11::CBKDRT11(const PARSE_RESULT &image) : CBKDImage(image)
{
}

CBKDRT11::~CBKDRT11()
{
}

bool CBKDRT11::ReadCatalog()
{
	if (!CBKDImage::ReadCatalog())
	{
		return false;
	}

	RT11FileRecord CurrFileRecord;
	RT11SegmentHdr CurrSegmentHeader;
	FileRecord curr_fr;
	CString ext;

	//Перемещаемся к 1му сектору
	if (m_sParseImgResult.nBaseOffset + 512 != m_FloppyImgFile.Seek(m_sParseImgResult.nBaseOffset + 512, CFile::begin))
	{
		m_nLastErrorNumber = IMAGE_ERROR_CANNOT_SEEK_FILE;
		return false;
	}

	//читаем первый сектор, хотя нам надо оттуда всего 1 слово
	if (m_FloppyImgFile.Read(m_mSector, sizeof(m_mSector)) != sizeof(m_mSector))
	{
		m_nLastErrorNumber = IMAGE_ERROR_CANNOT_READ_FILE;
		return false;
	}

	int nSegment = 1; //текущий сегмент каталога, отсчёт ведётся с 1, т.к. 0 - признак конца цепочки сегментов
	int nBeginBlock = *(uint16_t *)(m_mSector + 0724); //блок, с которого начинается каталог берём из служебной области,
	//и если там неправильное значение. тем хуже для образа
	int nTotalRecords = 0;  //всего возможных записей в каталоге
	int nTotalSegments = 0; //всего возможных сегментов в каталоге
	int nUsedRecords = 0;   //занято записей в каталоге
	int nUsedSegments = 0;  //занято сегментов в каталоге
	int nUsedSize = 0;  //занято блоков файлами в каталоге
	int nAddWords = 0; //количество дополнительных слов

	//!!! фикс для АДОС
	if (nBeginBlock > 255)
	{
		nBeginBlock = 6;
	}

	//можно приступать к чтению каталога
	while (nSegment > 0)
	{
		//переместимся к нужному сегменту
		int offs = ((nSegment - 1) * 2 + nBeginBlock) * 512;

		if (m_sParseImgResult.nBaseOffset + offs != m_FloppyImgFile.Seek(m_sParseImgResult.nBaseOffset + offs, CFile::begin))
		{
			m_nLastErrorNumber = IMAGE_ERROR_CANNOT_SEEK_FILE;
			return false;
		}

		//прочитаем заголовок сегмента, оттуда нам надо номер следующего сегмента, номер начального блока файлов
		//и кол-во доп слов.
		if (m_FloppyImgFile.Read(&CurrSegmentHeader, sizeof(RT11SegmentHdr)) != sizeof(RT11SegmentHdr))
		{
			m_nLastErrorNumber = IMAGE_ERROR_CANNOT_READ_FILE;
			return false;
		}

		if (nTotalSegments == 0) //берём запись об общем количестве сегментов только из первого сегмента.
		{
			nTotalSegments = CurrSegmentHeader.segments_num;

			if (nTotalSegments > 31)
			{
				m_nLastErrorNumber = IMAGE_ERROR_FS_FORMAT_ERROR;
				return false;
			}
		}

		nUsedSegments++;
		//теперь читаем записи о файлах.
		int nStartFileBlock = CurrSegmentHeader.file_block;

		for (int i = 0; i < 72; ++i) //в сегменте помещается максимум 72 записи
		{
			if (m_FloppyImgFile.Read(&CurrFileRecord, sizeof(RT11FileRecord)) != sizeof(RT11FileRecord))
			{
				m_nLastErrorNumber = IMAGE_ERROR_CANNOT_READ_FILE;
				return false;
			}

			//если есть доп. слова в записи, их надо проигнорировать. т.к. мы всё равно не знаем, как их интерпретировать
			nAddWords = CurrSegmentHeader.filerec_add;

			if (nAddWords)
			{
				m_FloppyImgFile.Seek(nAddWords * 2, CFile::current);
			}

			if (CurrFileRecord.status & RT11_FILESTATUS_EOS)
			{
				break; //если конец сегмента. то запись обрабатывать не надо
			}

			nUsedRecords++;
			curr_fr.clear();
			curr_fr.strName = DecodeRadix50(CurrFileRecord.name, 2).Trim();
			ext = DecodeRadix50(&CurrFileRecord.ext, 1).Trim();

			if (!ext.IsEmpty())
			{
				curr_fr.strName += _T(".") + ext;
			}

			//разберёмся с атрибутами.
			if (CurrFileRecord.status & RT11_FILESTATUS_TEMPORARY)
			{
				curr_fr.nAttr |= FR_ATTR_TEMPORARY;
			}

			if (CurrFileRecord.status & RT11_FILESTATUS_PROTECTED)
			{
				curr_fr.nAttr |= FR_ATTR_PROTECTED;
			}

			if (CurrFileRecord.status & RT11_FILESTATUS_UNUSED)
			{
				curr_fr.nAttr |= FR_ATTR_DELETED;
				nUsedRecords--; //удалённые записи не считаются
			}

			if (ext == _T("DSK"))
			{
				curr_fr.nAttr |= FR_ATTR_LOGDISK;
				curr_fr.nRecType = RECORD_TYPE_LOGDSK;
			}
			else
			{
				curr_fr.nRecType = RECORD_TYPE_FILE;
			}

			curr_fr.nDirBelong = 0; //директорий нет
			curr_fr.nDirNum = 0;
			curr_fr.nAddress = 0; //в рт11 у файла нету адреса загрузки, т.е. есть, но он не в каталоге хранится.
			curr_fr.nSize = CurrFileRecord.blk_size * 512;
			curr_fr.nSpecificData = (CurrFileRecord.task_num << 24) | (CurrFileRecord.chan_num << 16) | CurrFileRecord.date;
			curr_fr.nStartBlock = nStartFileBlock;

			if (!(curr_fr.nAttr & FR_ATTR_DELETED))
			{
				nUsedSize += CurrFileRecord.blk_size;
			}

			nStartFileBlock += CurrFileRecord.blk_size;
			curr_fr.nBlkSize = CurrFileRecord.blk_size;
			m_sDiskCat.vecFC.push_back(curr_fr);
		}

		nSegment = CurrSegmentHeader.next_segment;
	}

	//рассчитаем по официальной формуле
	nTotalRecords = nTotalSegments * ((512 - 5) / (7 + nAddWords)) - 3;
	m_sDiskCat.nTotalRecs = nTotalRecords;
	m_sDiskCat.nFreeRecs = nTotalRecords - nUsedRecords; //сколько записей в каталоге свободно
	int nBlkSize = ((int(m_sParseImgResult.nImageSize) - 1) | 0777) + 1; //выровняем по границе блока
	nBlkSize >>= 9; //получим размер образа в блоках
	nBlkSize -= (nBeginBlock + nTotalSegments * 2);
	//будем верить в то, что если каталог читался, то за каталогом данные всё-таки есть
	m_sDiskCat.nTotalBlocks = nBlkSize;
	m_sDiskCat.nFreeBlocks = nBlkSize - nUsedSize; //сколько блоков на диске свободно.
	//поскольку размер логического диска нигде в нём самом не задаётся, то он
	//вычисляется по размеру файла образа, и если файл оборван - то и размер будет неправильный.
	return true;
}

/*
декодирование строки в radix50.
вход: buf - массив слов,
len - размер буфера в словах
*/
CString CBKDRT11::DecodeRadix50(uint16_t *buf, int len)
{
	CString ret = _T("");

	for (int i = 0; i < len; i++)
	{
		uint16_t w = buf[i];
		//будем решать в лоб, не будем делать изящный алгоритм
		TCHAR tch3 = RADIX50[w % 050];
		w /= 050;
		TCHAR tch2 = RADIX50[w % 050];
		w /= 050;
		TCHAR tch1 = RADIX50[w % 050];
		ret += tch1;
		ret += tch2;
		ret += tch3;
	}

	return ret;
}

CString CBKDRT11::GetSpecificData(uint32_t dd)
{
	//поле под месяц - 4 бита. верные значения: 1..12, остальные - ошибочные
	//чтобы не усложнять метод вывода, просто добавим в массив несуществующие месяцы,
	// при ошибке всё будет хорошо видно
	static const TCHAR *strMons[16] =
	{
		_T("Нул"),
		_T("Янв"), _T("Фев"), _T("Мар"), _T("Апр"),
		_T("Май"), _T("Июн"), _T("Июл"), _T("Авг"),
		_T("Сен"), _T("Окт"), _T("Ноя"), _T("Дек"),
		_T("?13"), _T("?14"), _T("?15")
	};
	CString str, strDate = _T("");
	int nTask = (dd >> 24) & 0xff;
	int nChan = (dd >> 16) & 0xff;
	int nDay = (dd >> 5) & 0x1f;
	int nMon = (dd >> 10) & 0xf;
	int nYear = (dd & 0x1f) + 1972 + ((dd >> 14) & 3) * 32;

	if (nDay && nMon)
	{
		//если день != 0 и месяц != 0, то будем считать, что поле даты верно.
		strDate.Format(_T("%02d-%s-%4d"), nDay, strMons[nMon], nYear);
	}

	str.Format(_T("%03d:%03d; "), nTask, nChan);
	return str + strDate;
}

//на входе запись с файлом

bool CBKDRT11::ViewFile(FileRecord *sFile)
{
	m_nLastErrorNumber = IMAGE_ERROR_OK;
	bool bRet = true;

	//если это не файл - выходим ничего не делая
	if (sFile->nAttr & (FR_ATTR_DIR | FR_ATTR_LOGDISK))
	{
		return bRet;
	}

	TCHAR pTmpPath[MAX_PATH];
	CFile tmpFile;
	CString strFName = sFile->strName;
	SetSafeName(strFName);
	_tsplitpath_s(strFName, nullptr, 0, nullptr, 0, nullptr, 0, pTmpPath, MAX_PATH); //узнаем расширение
	CString strExt = CString(pTmpPath).MakeUpper();
	GetTempPath(MAX_PATH, pTmpPath);
	strFName = CString(pTmpPath) + strFName;
	int nMode = 0; //режим. 0 - КОИ7Н0, 1 - КОИ7Н1, 2 - КОИ7Н2, -1 - КОИ8

	//чудно, но CFile::typeText не поддерживается, поэтому файл открываем бинарно, и переводы строк формируем сами.
	if (tmpFile.Open(strFName, CFile::modeCreate | CFile::modeWrite | CFile::shareExclusive))
	{
		uint8_t b;
		int len = sFile->nSize;
		int counter = 0;
		int ccnt = 0; //счётчик управляющих кодов SI SO
		int cntRL = 0; //счётчик кодов 0300..0337
		int cntRH = 0; //счётчик кодов 0340..0377
		SeekToBeginFile(&m_FloppyImgFile, sFile); //перемещаемся к началу файла

		//сперва проверим - кои7 или кои8
		while (len > 0)
		{
			b = GetByte(&m_FloppyImgFile, &len);

			if (b > 127)
			{
				counter++;

				if (b >= 0340)
				{
					cntRH++;
				}
				else if (b >= 0300)
				{
					cntRL++;
				}
			}
			else if (b == 016 || b == 017)
			{
				ccnt++;
			}
		}

		len = sFile->nSize;

		//если кодов >= 128 нету и кодов SO SI тоже нету
		if (counter == 0 && ccnt < 4)
		{
			nMode = 2; //то это вероятно кои7н2. Или это ascii 128 чисто английский текст
		}
		//если коды >= 128 есть, сколько-то
		else if ((counter > len / 10) || (counter && (len < 512)))
		{
			//если строчных букв больше, чем заглавных
			if (cntRL > cntRH)
			{
				nMode = -1; //то это вероятно кои8
			}
			else
			{
				nMode = 2; // если это не так, то это наверное не кои8, а какая-то другая кодировка
			}
		}

		//иначе, это вероятно смесь из кои7н0 и кои7н1
		SeekToBeginFile(&m_FloppyImgFile, sFile); //перемещаемся к началу файла
		counter = 0;
		TCHAR tch = 0xfeff; //BOM
		PutTchar(&tmpFile, tch, counter); //запишем сперва BOM

		while (len > 0)
		{
			b = GetByte(&m_FloppyImgFile, &len);

			if (b == 0 || b == 10) //перевод строки
			{
				PutTchar(&tmpFile, _T('\r'), counter);
				tch = _T('\n');
			}
			else if (b == 9) //табуляция
			{
				tch = _T('\t');
			}
			else if (b < 32) //это прочие управляющие символы
			{
				if (b == 016 && nMode == 0) //переключение в кои7н1 (РУС)
				{
					nMode = 1;
				}
				else if (b == 017 && nMode == 1) //переключение в кои7н0 (ЛАТ)
				{
					nMode = 0;
				}

				continue;
				//остальные коды, в том числе и \r - 015 игнорируем
			}
			else
			{
				if (nMode < 0)
				{
					if (b == 044)
					{
						tch = 0x00a4; //исключение - знак $ в знак солнышко
					}
					else
					{
						tch = BKToUNICODE_Byte(b, koi8tbl_RFC1489);
					}
				}
				else if (nMode == 0) //кои7н0 (ЛАТ)
				{
					if (b == 044)
					{
						tch = 0x00a4; //исключение - знак $ в знак солнышко
					}
					else
					{
						tch = TCHAR(b);
					}
				}
				else if (nMode == 1) //кои7н1 (РУС)
				{
					if (b == 044)
					{
						tch = 0x00a4;
					}
					else if (b > 0100)
					{
						tch = koi8tbl10[b];
					}
					else
					{
						tch = TCHAR(b);
					}
				}
				else if (nMode == 2)  //кои7н2 (либо ascii)
				{
					if (b == 044)
					{
						tch = 0x00a4;
					}
					else if (b >= 0140)
					{
						tch = koi8tbl10[b]; //будем предполагать по умолчанию, что это кои7н2
						//tch = TCHAR(b); //будем предполагать по умолчанию, что это не кои7н2, а исключительно английский текст
					}
					else
					{
						tch = TCHAR(b);
					}
				}
			}

			PutTchar(&tmpFile, tch, counter);
		}

		tmpFile.Close();
		//запускаем блокнот с этим файлом в качестве параметра
		STARTUPINFO si;
		PROCESS_INFORMATION pi;
		ZeroMemory(&si, sizeof(si));
		si.cb = sizeof(si);
		ZeroMemory(&pi, sizeof(pi));
		/*
		*если запускать текстовый файл через explorer, т.е. пытаться открыть прогой,
		которая сассоциирована с *.txt, то процесс explorer успешно завершается передачей файла
		например блокноту, и прежде чем блокнот запустится, файл удаляется.
		Можно не удалять файлы во временной папке, но тогда перед повторным просмотром
		надо искать файл и удалять его, если он там есть, в общем всячески усложнять алгоритм
		*/
		CString strCmdLine = _T("notepad \"") + strFName + _T("\"");

		if (CreateProcess(nullptr,           // No module name (use command line)
		                  strCmdLine.GetBuffer(), // Command line
		                  nullptr,           // Process handle not inheritable
		                  nullptr,           // Thread handle not inheritable
		                  false,          // Set handle inheritance to false
		                  0,              // No creation flags
		                  nullptr,           // Use parent's environment block
		                  nullptr,           // Use parent's starting directory
		                  &si,            // Pointer to STARTUPINFO structure
		                  &pi))           // Pointer to PROCESS_INFORMATION structure
		{
			// Wait until child process exits.
			WaitForSingleObject(pi.hProcess, INFINITE);
			// Close process and thread handles.
			CloseHandle(pi.hProcess);
			CloseHandle(pi.hThread);
		}

		//и удалим временный файл
		CFile::Remove(strFName);
	}
	else
	{
		m_nLastErrorNumber = IMAGE_ERROR_FILE_CANNOT_CREATE;
		bRet = false;
	}

	return bRet;
}

/*
КОИ-7 включает в себя 3 «набора» — Н0, Н1, Н2.
Н0 — это просто US-ASCII;
в Н1 все латинские буквы заменены на русские, а символ доллара ($) заменен на символ валюты (¤);
(Лат.Загл. -> Рус.Стр. Лат.Стр. -> Рус.Загл.)
в Н2 заглавные латинские буквы оставлены, а строчные заменены на заглавные русские, символ доллара также заменен.

На практике использовался либо набор Н2 сам по себе, либо Н0/Н1 с переключением: для перехода в русский режим (Н1)
использовался управляющий символ 0xE (SO, РУС), а для возврата в латинский (Н0) — символ 0xF (SI, ЛАТ).
Иногда смена набора, используемого устройством, осуществлялась ручным переключателем.

Буквы Ё не было; отсутствовала также заглавная буква Ъ. Поскольку русские буквы были размещены на позициях
аналогичных латинских букв, текст оставался более-менее понятным даже при неправильно выбранном режиме.
*/
