#include "pch.h"
#include "BKASCFile.h"
#include <locale>
#include <format>

#pragma warning(disable:4996)

constexpr auto MAGIC_ASC = 0x3dee; // это адрес загрузки бин файла. фиктивный, но по нему распознаётся .asc файл
constexpr auto ASC_BLOCK_SIZE = 256;
constexpr auto ASC_TEXT_END = 032;

CBKASCFile::CBKASCFile(const fs::path &strName)
	: CBaseFile(strName)
{
	m_nTabWidth = TABWIDTH_DEFAULT;
}


CBKASCFile::~CBKASCFile()
{
	Close();
}

// выход: true - Файл открылся, false - не открылся.
bool CBKASCFile::Open(bool bWrite)
{
	constexpr auto BKFNAME_SIZE = 6;

	if (bWrite)
	{
		// сформируем корректное имя
		// как известно, в линуксе кодировка utf8, и по индексу получить символ строки нельзя
		// и как эти люди обрабатывают символьные переменные не ascii диапазона вообще непонятно
		// мы же, сделаем через жопу.
		std::wstring buff = L"      "; // размер 6 пробелов
		auto strFileName = m_pathFileName.filename().wstring(); // конвертируем имя из utf8 в wchar (работает в Linux)
		auto len = strFileName.length();

		for (size_t i = 0; i < BKFNAME_SIZE; ++i) // возьмём только первые 6 символов
		{
			auto ch = strFileName.at(i);

			// если длина имени меньше 6 символов или встретилась точка
			if (i >= len || ch == _T('.'))
			{
				break; // прекращаем цикл
			}

			buff[i] = ch; // формируем ASC бейсиковое имя
		}

		auto t = fs::path{ buff }; // конвертируем wchar обратно в utf8 (работает в Linux)
		m_strInternalAscName = m_pathFileName.parent_path() / (t._tstring() + _T(".ASC"));
		m_nInternalBlockNumber = 0;
		m_bEof = false;
		// сформируем заголовочный файл
		m_f = BKOpen((m_strInternalAscName._tstring() + _T(".bin")), _T("wb"));

		if (m_f)
		{
			WriteWord(MAGIC_ASC);
			WriteWord(2);
			WriteWord(0);
			Close();
			m_nInternalPos = 0;
			return true;
		}

		return false; // не открылось для записи
	}

	/*
	на вход подаётся имя файла заголовка. Причём формат всех файлов - .bin - генерирующихся
	эмулятором.
	*/
	// открываем файл заголовок и проверяем содержимое.
	m_f = BKOpen(m_pathFileName, _T("rb"));

	if (m_f)
	{
		const auto len = fs::file_size(m_pathFileName);

		if (len == BKFNAME_SIZE)
		{
			bool bRet = false;
			auto w = ReadWord();

			if (w == MAGIC_ASC) // магические числа. это адрес загрузки бин файла. фиктивный, но по нему распознаётся .asc файл
			{
				w = ReadWord();

				if (w == 2) // размер содержимого.
				{
					w = ReadWord();

					if (w == 0) // собственно содержимое.
					{
						Close();
						bRet = true;
					}
				}
			}

			if (bRet)
			{
				// заголовочный файл верный
				// теперь делаем внутреннее имя файла.
				auto ext = m_pathFileName.extension()._tstring();

				for (auto &c : ext)
				{
					c = std::tolower(c, std::locale());
				}

				if (ext == _T(".bin")) // если расширение бин
				{
					// то удаляем расширение
					m_strInternalAscName = m_pathFileName.parent_path() / m_pathFileName.stem();
					m_nInternalBlockNumber = 0;
					MakeintenalCurrentName();
					m_bEof = false;
					bRet = OpenInternalFile();
				}
			}
			else
			{
				Close();
			}

			return bRet;
		}
	}

	return false;
}

void CBKASCFile::MakeintenalCurrentName()
{
	auto buffer = std::format(_T(" #{:03d}.bin"), m_nInternalBlockNumber);
	m_strInternalCurrentName = m_strInternalAscName._tstring() + buffer;
}

bool CBKASCFile::OpenInternalFile()
{
	Close();
	m_nInternalPos = 0;
	m_f = BKOpen(m_strInternalCurrentName, _T("rb"));

	if (m_f)
	{
		auto w = ReadWord();

		if (w == MAGIC_ASC)
		{
			w = ReadWord();

			if (w == ASC_BLOCK_SIZE)
			{
				return true;
			}
		}
	}

	return false;
}

uint32_t CBKASCFile::ReadChar()
{
	// если конец файла - выдаём признак конца файла
	if (m_bEof)
	{
		return ASC_TEXT_END;
	}

	// если блок закончился - надо перейти к следующему блоку
	if (m_nInternalPos >= bufSize)
	{
		m_nInternalBlockNumber++;
		MakeintenalCurrentName();

		if (!OpenInternalFile()) // если следующий блок не открывается
		{
			m_bEof = true; // значит считаем, что конец файла
			return ASC_TEXT_END;
		}
	}

	uint32_t tch = 0;

	if (m_f)
	{
		uint8_t ch = 0;
		size_t n = fread(&ch, 1, sizeof(uint8_t), m_f);

		if (n == 1) // сколько прочлось? сколько нужно
		{
			m_nInternalPos++; // увеличиваем позицию.

			if (ch == ASC_TEXT_END) // вот это правильный конец файла
			{
				m_bEof = true;
				tch = ch;
			}
			else
			{
				tch = BKToUNICODE_Byte(ch);
			}
		}
		else
		{
			m_bEof = true;// если что-то не так - говорим что конец файла
			return ASC_TEXT_END;
		}
	}

	return tch;
}

bool CBKASCFile::WriteWord(uint16_t w)
{
	size_t n = 0;

	if (m_f)
	{
		n = fwrite(&w, 1, sizeof(uint16_t), m_f);
	}

	return (n == sizeof(uint16_t));
}

long CBKASCFile::GetPos() const
{
	if (m_f)
	{
		return ftell(m_f) - 4;
	}

	return 0;
}

int CBKASCFile::SetPos(long pos, long mode) const
{
	if (m_f)
	{
		return fseek(m_f, pos + 4, mode);
	}

	return 0;
}

bool CBKASCFile::WriteChar(uint32_t tch)
{
	bool bRet = true;

	// если буфер заполнен
	if (m_nInternalPos >= bufSize)
	{
		bRet = FlushInternalWrBuff(); // сбрасываем его на диск
	}

	if (tch == ASC_TEXT_END)
	{
		m_pWrBuf[m_nInternalPos++] = ASC_TEXT_END;
		m_bEof = true;
	}
	else
	{
		m_pWrBuf[m_nInternalPos++] = static_cast<char>(UNICODEtoBK_Byte(tch));
	}

	m_bFlushed = false;
	return bRet;
}

bool CBKASCFile::FlushInternalWrBuff()
{
	if (!m_bFlushed)
	{
		bool bRet = false;
		m_nInternalPos = 0;
		MakeintenalCurrentName();
		m_f = BKOpen(m_strInternalCurrentName, _T("wb"));

		if (m_f)
		{
			WriteWord(MAGIC_ASC);
			WriteWord(bufSize);
			fwrite(m_pWrBuf.data(), 1, m_pWrBuf.size(), m_f);
			m_pWrBuf.fill(' ');
			m_nInternalBlockNumber++;
			bRet = true;
			Close();
		}

		m_bFlushed = true;
		return bRet;
	}

	return true;
}

void ITCPC::TXT2ASC(std::unique_ptr<CBaseFile> pInFile, std::unique_ptr<CBKASCFile> pOutFile)
{
	if (pInFile && pOutFile)
	{
		int nCharsCount = 0;

		if (!pInFile->Open(false))
		{
			std::_tcout << _T("Ошибка! '") << pInFile->getFileName()._tstring() << _T("' не открывается.\n");
			return;
		}

		if (!pOutFile->Open(true))
		{
			std::_tcout << _T("Ошибка! '") << pOutFile->getFileName()._tstring() << _T("' не открывается.\n");
			return;
		}

		while (!pInFile->isEOF())
		{
			auto Sym = pInFile->ReadChar();

			if (Sym == 0)
			{
				Sym = CHR_N;
			}
			else if (Sym == CHR_R)
			{
				continue;
			}
			else if (Sym == CHR_TAB)
			{
				int nNextTabPos = (nCharsCount / pInFile->getTabWidth() + 1) * pInFile->getTabWidth();

				do
				{
					pOutFile->WriteChar(_T(' ')); nCharsCount++;
				}
				while (nCharsCount < nNextTabPos);

				continue;
			}

			pOutFile->WriteChar(Sym); nCharsCount++;

			if ((Sym == ASC_TEXT_END) || pInFile->isEOF())
			{
				pOutFile->WriteChar(ASC_TEXT_END); nCharsCount++;
				pOutFile->FlushInternalWrBuff();
				break;
			}
		}
	}
	else
	{
		std::_tcout << _T("Недостаточно памяти.\n");
	}
}
