#include "pch.h"

#include "BKToken.h"
#include "ConvW2k.h"
#include "Globals.h"
#include "Reader.h"
#include <cstdlib>
#include <cstring>

#pragma warning(disable:4996)

const std::array<wchar_t, 256> CReader::m_cp866 =
{
	0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
	0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8, 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
	0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
	0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
	0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
	0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
	0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040E, 0x045E, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x2116, 0x00A4, 0x25A0, 0x00A0,
};

const std::array<wchar_t, 256> CReader::m_cp1251 =
{
	0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
	0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F,
	0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F,
	0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407,
	0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, 0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457,
	0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
	0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
	0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
	0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
};

const std::array <uint8_t, DEF_TBLSZ2> CReader::table_2s =
{
	0xFF, 0xFF, 0xFF, 0xC7, 0xFE, 0xBE, 0xF7, 0xFB,
	0xFD, 0xBF, 0xF7, 0xF9, 0xFC, 0xBE, 0xF1, 0x80, 0xFF, 0xFF, 0xF7, 0xBB, 0xFF, 0xFF, 0xFF,
	0xCF, 0xDE, 0xBF, 0xD1, 0x08, 0xFF, 0xBF, 0xF1, 0xBF, 0xFF, 0xFF, 0xFF, 0xC7, 0x1D, 0x3F,
	0x7F, 0x81, 0xA7, 0xB6, 0xF2, 0x82, 0xFF, 0xFF, 0x75, 0xDB, 0xFC, 0xBF, 0xD7, 0x9D, 0xFF,
	0xAE, 0xFB, 0xDF, 0xFF, 0xFF, 0xFF, 0xC7, 0x84, 0xB7, 0xF3, 0x9F, 0xFF, 0xFF, 0xFF, 0xDB,
	0xFF, 0xBF, 0xFF, 0xFF, 0xFD, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xC7, 0x84, 0x9E, 0xF0,
	0x12, 0xBC, 0xBF, 0xF0, 0x84, 0xA4, 0xBA, 0x10, 0x10, 0xA4, 0xBE, 0xB8, 0x88, 0xAC, 0xBF,
	0xF7, 0x0A, 0x84, 0x86, 0x90, 0x08, 0x04, 0x00, 0x00, 0x03, 0x7F, 0xFD, 0xF7, 0xC1, 0x7D,
	0xAE, 0x6F, 0xCB, 0x15, 0x3D, 0xFC, 0x00, 0x7F, 0x7D, 0xE7, 0xC2, 0x7F, 0xFD, 0xF7, 0xC3
};

union UN_BOM
{
	uint32_t dw = 0;
	std::array<uint8_t, sizeof(uint32_t)> bb;
};

constexpr auto UTF16LE_BOM = 0xFEFF;
constexpr auto UTF8_BOM = 0xBFBBEF;

CReader::CReader(const fs::path &strFileName, const FILE_CHARSET nInputCharset)
	: m_strFileName(strFileName)
	, m_nCharset(nInputCharset)
{
	const FILE_CHARSET nDetectedCharset = AnalyzeCharset(strFileName);

	if (nDetectedCharset == FILE_CHARSET::FILEERROR)
	{
		m_nCharset = nDetectedCharset;
		return;
	}

	// если кодировку вручную не задавали
	if (m_nCharset == FILE_CHARSET::UNDEFINE)
	{
		// то будем использовать автоопределённую.
		m_nCharset = nDetectedCharset;
	}
	else if (m_nCharset == FILE_CHARSET::UTF8 && nDetectedCharset == FILE_CHARSET::UTF16LE)
	{
		m_nCharset = nDetectedCharset; // уточняем юникод
	}
	else if (m_nCharset == FILE_CHARSET::UTF16LE && nDetectedCharset == FILE_CHARSET::UTF8)
	{
		m_nCharset = nDetectedCharset; // уточняем юникод
	}

	// а иначе считаем, что пользователю лучше знать, какую он использует кодировку.

	switch (m_nCharset)
	{
		case FILE_CHARSET::KOI8:
		case FILE_CHARSET::CP866:
		case FILE_CHARSET::CP1251:
			m_file = _tfopen(strFileName._tstring().c_str(), _T("rb"));
			break;	// просто откроем файл и всё

		case FILE_CHARSET::UTF8:
			m_file = _tfopen(strFileName._tstring().c_str(), _T("rb"));
			// skip BOM
		{
			UN_BOM BOM;
			fread(&BOM.bb, 1, 3, m_file);

			if (BOM.dw != UTF8_BOM)
			{
				fseek(m_file, 0, SEEK_SET);
			}
		}
		break;

		case FILE_CHARSET::UTF16LE:
			m_file = _tfopen(strFileName._tstring().c_str(), _T("rb"));
			// skip BOM
			{
				UN_BOM BOM;
				fread(&BOM.bb, 1, 2, m_file);

				if (BOM.dw != UTF16LE_BOM)
				{
					fseek(m_file, 0, SEEK_SET);
				}
			}
			break;

		default:
			return;
	}

	readChar();
}


CReader::~CReader()
{
	if (m_file)
	{
		fclose(m_file);
	}
}

wchar_t CReader::getChar()
{
	if ((m_nPos == -1 || m_nPos >= m_nLen) && !feof(m_file))
	{
		m_nLineNum++;
		getString(); // формируем текущую строку, для вывода на экран в сообщении об ошибке
	}

	// после чтения строки m_nPos и m_nLen могут измениться, поэтому надо проверять заново
	if (feof(m_file) && m_nPos >= m_nLen)
	{
		return 032;
	}

	return m_strCur[m_nPos++];
}

constexpr auto BUFFER_SIZE = 65536;


void CReader::getString()
{
	std::vector<wchar_t> wch(BUFFER_SIZE);
	size_t len = 0;

	switch (m_nCharset)
	{
		case FILE_CHARSET::KOI8:
		case FILE_CHARSET::CP866:
		case FILE_CHARSET::CP1251:
		{
			auto cch = std::make_unique<char[]>(BUFFER_SIZE);
			std::memset(cch.get(), 0, BUFFER_SIZE);
			char *str = fgets(cch.get(), BUFFER_SIZE - 1, m_file);
			len = (str) ? std::strlen(str) : 0;

			if ((len > 1) && (str[len - 2] == '\r'))
			{
				str[--len] = 0;
				str[len - 1] = '\n';
			}

			switch (m_nCharset)
			{
				case FILE_CHARSET::KOI8:
					for (size_t i = 0; i < len; ++i)
					{
						const uint8_t b = str[i];
						wch[i] = (b < KOI8TBL_SIZE) ? wchar_t(b) : koi8tbl[static_cast<size_t>(b) - KOI8TBL_SIZE];
					}

					break;

				case FILE_CHARSET::CP866:
					for (size_t i = 0; i < len; ++i)
					{
						wch[i] = o2w(str[i]);
					}

					break;

				case FILE_CHARSET::CP1251:
					for (size_t i = 0; i < len; ++i)
					{
						wch[i] = a2w(str[i]);
					}

					break;
			}

			break;
		}

		case FILE_CHARSET::UTF8:
		{
			len = 0;

			while ((len < BUFFER_SIZE - 1) && !feof(m_file))
			{
				auto ch = ReadUTF8Char();

				if (ch == L'\r') // этот знак пропускаем
				{
					continue;
				}

				if (ch == 0 || ch == L'\n') // конец строки
				{
					wch[len++] = L'\n';
					break;
				}

				wch[len++] = ch;
			}

			break;
		}

		case FILE_CHARSET::UTF16LE:
		{
			len = 0;

			while ((len < BUFFER_SIZE - 1) && !feof(m_file))
			{
				auto ch = ReadUtf16LEChar();

				if (ch == L'\r') // этот знак пропускаем
				{
					continue;
				}

				if (ch == 0 || ch == L'\n') // конец строки
				{
					wch[len++] = L'\n';
					break;
				}

				wch[len++] = ch;
			}

			break;
		}

		default:
			assert(false);
	}

	if (feof(m_file))
	{
		if (wch[len - 1] != L'\n')
		{
			wch[len++] = L'\n';
		}
	}

	wch[len] = 0;
	m_strCur = std::wstring(wch.data());
	m_nLen = len;
	m_nPos = 0;
}

#if !defined(_WIN32)
#define _ftelli64 ftello64
#define _fseeki64 fseeko64
#endif

void CReader::StoreState()
{
	RdrState s
	{
		m_chCurrent,
		m_chNext,
		m_nCurrCharType,
		m_nLen,
		m_nPos,
		m_nLineNum,
		_ftelli64(m_file),
		m_strCur
	};
	m_vState.push_back(s);
}


wchar_t CReader::RestoreState()
{
	if (!m_vState.empty())
	{
		RdrState &s     = m_vState.back();
		m_chCurrent     = s.chCurrent;
		m_chNext        = s.chNext;
		m_nCurrCharType = s.nCurrCharType;
		m_nLen          = s.nLen;
		m_nPos          = s.nPos;

		if (m_nLineNum != s.nLineNum)
		{
			m_nLineNum = s.nLineNum;
			_fseeki64(m_file, s.nFilePos, SEEK_SET); //подглючивает для UTF-8 и UNICODE
			m_strCur = s.strCurStr;
		}

		m_vState.pop_back();
		return m_chCurrent;
	}

	return 0;
}

void CReader::PopState()
{
	if (!m_vState.empty())
	{
		m_vState.pop_back();
	}
}

/*
 * Получение очередного символа.
 * С пропуском пробельных символов, за исключением конца строки, если задано режимом
 * С преобразованием маленьких букв в большие, если не приём строки в кавычках
 */
wchar_t CReader::readChar(bool bSkipWS)
{
	do
	{
		m_chCurrent = m_chNext;
		m_chNext = getChar();

		if (m_chNext <= 011)
		{
			m_chNext = L' ';
		}

		/*
		тут можно сделать регистронезависимость. преобразовывать a..z в A..Z
		именно тут, т.к. readChar используется для приёма символов
		*/
		if (!g_Globals.isInString()) // если мы не внутри строки
		{
			if ((L'a' <= m_chCurrent) && (m_chCurrent <= L'z')) // если у нас маленькие буквы
			{
				m_chCurrent = std::toupper(m_chCurrent, std::locale()); // превращаем маленькие лат. буквы в большие.
			}

			// это касается только латинских букв. все остальные - остаются как есть
		}

		// заодно и проанализируем текущий символ.
		m_nCurrCharType = AnalyzeChar(m_chCurrent);
	}
	while (bSkipWS && m_nCurrCharType == CHAR_TYPE::SPACES); // и заодно если задано пропускать пробельные символы, то мы их пропускаем

	return m_chCurrent;
}

bool CReader::isEOF() const
{
	return ((feof(m_file) && m_nPos >= m_nLen) || (m_chCurrent == 032 && m_chNext == 032));
}

/*
 * Анализ входного символа.
 * Выход: тип символа
 */
CReader::CHAR_TYPE CReader::AnalyzeChar(wchar_t ch)
{
//  if (ch == 032) // эта штука более вредит, чем помогает
//  {
//      return CT_EOF;
//  }
	if (ch == 0 || ch == 012 || ch == 032) // 032 == ^Z
	{
		return CHAR_TYPE::LN;
	}

	if (((L'A' <= ch) && (ch <= L'Z')) || (ch == L'_') || (ch == L'$'))
	{
		return CHAR_TYPE::LETTERS;
	}

	if ((L'0' <= ch) && (ch <= L'9'))
	{
		return CHAR_TYPE::DIGITS;
	}

	if (ch <= L' ')
	{
		return CHAR_TYPE::SPACES;
	}

	return CHAR_TYPE::OTHERS;
}

/*
 *Пропуск пробельных символов. На входе ch содержит текущий символ.
 *выход: true - конец строки строгий
 *       false - не конец строки
 *На выходе ch содержит текущий не пробельный символ.
 */
bool CReader::SkipWhitespacesLN(wchar_t &ch)
{
	CHAR_TYPE ct = AnalyzeChar(ch);

	while (ct == CHAR_TYPE::SPACES)
	{
		ch = readChar(true);
		ct = getCurrCharType();
	}

	return (ct == CHAR_TYPE::LN); // если конец строки
}

/*
 *Пропуск пробельных символов. На входе ch содержит текущий символ.
 *выход: true - конец строки или начало комментария
 *       false - не конец строки
 *На выходе ch содержит текущий не пробельный символ.
 */
bool CReader::SkipWhitespaces(wchar_t &ch)
{
	CHAR_TYPE ct = AnalyzeChar(ch);

	while (ct == CHAR_TYPE::SPACES)
	{
		ch = readChar(true);
		ct = getCurrCharType();
	}

	if (ct == CHAR_TYPE::LN) // если конец строки
	{
		return true;
	}

	// если комментарии, то их тоже за конец строки будем считать
	if (ch == L'/' && getNextChar() == L'*') // многострочный
	{
		// нельзя тут его пропускать
		// так он в листинг не попадёт
//      do  // цикл, пока не найдём конец комментария или конец файла
//      {
//          ch = readChar();
//
//          if (isEOF()) // если конец файла
//          {
//              break;
//          }
//      } while (!(ch == L'*' && getNextChar() == L'/'));
//      ch = readChar();     // читаем *
//      ch = readChar(true); // читаем /
		return true;
	}

	if (ch == L';' || (ch == L'/' && getNextChar() == L'/')) // однострочный
	{
		// пропустим его и дойдём действительно до конца строки
		do
		{
			ch = readChar(true);
			ct = getCurrCharType();
		}
		while (ct != CHAR_TYPE::LN && !isEOF());

		return true;
	}

	// всё остальное - не конец строки
	return false;
}

/*
 * Чтение лексемы.
 * На входе ch содержит текущий символ.
 * На выходе ch содержит текущий символ, не входящий в лексему.
 * Выход: true - в token занесено значение
 * false - не занесено.
 */
bool CReader::readToken(CBKToken *token, wchar_t &ch)
{
	std::wstring strTokenBuffer;
	CHAR_TYPE ct = AnalyzeChar(ch);
	int nCnt = 0;

	while (ct == CHAR_TYPE::DIGITS || ct == CHAR_TYPE::LETTERS || ((ch == L'.') && nCnt))
	{
		strTokenBuffer.push_back(ch);
		ch = readChar();
		ct = getCurrCharType();
		nCnt++; // вводим защиту. символ  L'.' не может быть первым.
	}

	if (!strTokenBuffer.empty())
	{
		token->setName(strTokenBuffer);
		return true;
	}

	return false;
}

/*
 * Специальный вариант, чтения имени регистра вида %n. Читается только 2 символа, % и цифра за ним
 * На входе ch содержит текущий символ.
 * На выходе ch содержит текущий символ, не входящий в лексему.
 * Выход: true - в token занесено значение
 * false - не занесено.
 */
bool CReader::readTokenR(CBKToken *token, wchar_t &ch)
{
	CHAR_TYPE ct = AnalyzeChar(ch);

	if (ch == L'%')
	{
		std::wstring strTokenBuffer;
		strTokenBuffer.push_back(ch);
		ch = readChar();
		ct = getCurrCharType();

		if (ct == CHAR_TYPE::DIGITS)
		{
			strTokenBuffer.push_back(ch);
			ch = readChar();
			ct = getCurrCharType();
			token->setName(strTokenBuffer);
			return true;
		}
	}

	return false;
}

CReader::FILE_CHARSET CReader::AnalyzeCharset(const fs::path &strFileName)
{
	m_file = _tfopen(strFileName._tstring().c_str(), _T("rb"));

	if (m_file)
	{
		UN_BOM BOM;
		FILE_CHARSET nRet = FILE_CHARSET::UNDEFINE;
		// сперва поищем BOM
		fread(&BOM.bb, 1, 3, m_file);

		if ((BOM.dw & 0xffff) == UTF16LE_BOM)
		{
			nRet = FILE_CHARSET::UTF16LE;
		}
		else if (BOM.dw == UTF8_BOM)
		{
			// утф8 без бом будет определяться некорректно возможно
			nRet = FILE_CHARSET::UTF8;
		}
		else
		{
			// дальше надо анализировать кодировку.
			fseek(m_file, 0, SEEK_SET);
			nRet = def_code(255);
		}

		fclose(m_file);
		m_file = nullptr;
		return nRet;
	}

	return FILE_CHARSET::FILEERROR;
}

int CReader::work_2s(int c1, int c2, bool check, std::span<uint8_t> buf) const
{
	int i = (c1 << 2) + (c2 >> 3); /* Номер байта в массиве. */
	int mask = 0x80 >> (c2 & 7); /* Маска, соответствующая номеру бита в байте. */

	/* Если check=1, проверяем: если соответствующий бит массива buf равен 0,
	значит, указанное сочетание уже встречалось раньше. Тогда выходим из
	функции, возвращая 0. Если же сочетание не встречалось, то помечаем, что
	оно встретилось (обнуляем соответствующий бит массива buf). */

	if (check)
	{
		if ((buf[i] & mask) == 0)
		{
			return (0);
		}

		buf[i] &= ~mask;
	}

	/* Проверяем, допустимо сочетание или нет. */

	if ((table_2s[i] & mask) != 0)
	{
		return (1);    /* Допустимо. */
	}

	return (2);        /* Недопустимо. */
}

int CReader::get_char()
{
	uint8_t ch = 0;

	if (!feof(m_file))
	{
		m_nLen += fread(&ch, 1, 1, m_file);
		return ch;
	}

	return -1;
}

CReader::FILE_CHARSET CReader::def_code(int n)
{
	m_nLen = 0;
	/* В массиве buf_1 хранится информация о том, какие сочетания русских букв
	уже встречались в варианте ALT, а в массиве buf_2 - в варианте WIN. */
	std::array<uint8_t, DEF_TBLSZ2> buf_1{};
	std::array<uint8_t, DEF_TBLSZ2> buf_2{};
	int bad_1 = 0;
	int bad_2 = 0;
	int bad_3 = 0;
	int all_1 = 0;
	int all_3 = 0;  /* all_2=all_3 */
	int c1 = 0; int c2 = -1; /* Символы текущего обрабатываемого сочетания. */
	/* Инициализация buf_1 и buf_2. */
	buf_1.fill(0xff);
	buf_2.fill(0xff);
	double nD0 = 0.0, nD1 = 0.0, nX04 = 0.0, nX00 = 0.0;
	double n00_suffix = 0.0;    // тут латинские и прочие буквы для юникода.
	double n04_suffix = 0.0;    // тут русские буквы для юникода.
	double nD0_suffix = 0.0;    // тут русские буквы с префиксом D0 в UTF8
	double nD1_suffix = 0.0;    // тут русские буквы с префиксом D1 в UTF8

	/* Главный цикл - обработка сочетаний для каждого из трёх вариантов. Цикл
	выполняется, пока не кончится текст или в каком-либо из вариантов не
	встретится n сочетаний. */

	while (((c1 = c2, c2 = get_char()) != -1) && (all_1 < n) && (all_3 < n))
	{
		// --- определение utf8, utf16 (только русский диапазон) ----------------
		switch (c2)
		{
			case 00:
				nX00++;
				break;

			case 04:
				nX04++;
				break;

			case 0xd0:
				nD0++;
				break;

			case 0xd1:
				nD1++;
				break;

			default:
			{
				if ((c1 == 0x00) && (0 <= c2 && c2 <= 0x7f))
				{
					n00_suffix++;
				}

				if ((c1 == 0x04) && ((0x10 <= c2 && c2 <= 0x19) || (0x1a <= c2 && c2 <= 0x2f) || (c2 == 01) || (0x30 <= c2 && c2 <= 0x39) || (0x3a <= c2 && c2 <= 0x4f) || (c2 == 51)))
				{
					n04_suffix++;
				}

				if ((c1 == 0xd0) && ((0x90 <= c2 && c2 <= 0xbf) || c2 == 0x81))
				{
					nD0_suffix++;
				}

				if ((c1 == 0xd1) && ((0x80 <= c2 && c2 <= 0x8f) || c2 == 0x91))
				{
					nD1_suffix++;
				}
			}
		}

		// ----------------------------------------------------------------------

		/* Вариант ALT. Вначале проверяем, являются ли символы текущего сочетания
		кодами русских букв в кодировке ALT. */

		if ((((c1 >= 0x80) && (c1 < 0xB0)) || ((c1 >= 0xE0) && (c1 < 0xF0))) &&
				(((c2 >= 0x80) && (c2 < 0xB0)) || ((c2 >= 0xE0) && (c2 < 0xF0))))
		{
			switch (work_2s(alt2num(c1), alt2num(c2), true, buf_1)) /* Обработали. */
			{
				case 2: bad_1++;
					[[fallthrough]];

				case 1: all_1++;
			}
		}

		/* Варианты WIN и KOI. Вначале проверяем, являются ли символы текущего
		сочетания кодами русских букв в этих кодировках (в обеих кодировках
		диапазоны кодов русских букв совпадают). */

		if ((c1 & c2) >= 0xC0) /* Эквивалентно условию (c1>=0xC0)&&(c2>=0xC0). */
		{
			switch (work_2s(c1 & 31, c2 & 31, true, buf_2)) /* Обработали. */
			{
				case 0: continue; /* Если сочетание букв уже встречалось в варианте WIN,
							  то оно уже встречалось и в варианте KOI, так что
							  пропускаем обработку варианта KOI и переходим
							  к следующей итерации главного цикла. */

				case 2: bad_2++;
			}

			/* Если сочетание букв ещё не встречалось в варианте WIN, то оно заведомо
			не встречалось и в варианте KOI, поэтому специально проверять это не
			надо - значит, функцию work_2s вызываем с параметром check, равным 0. */

			switch (work_2s(koi2num(c1), koi2num(c2), false, std::span<uint8_t>())) /* Обработали. */
			{
				case 2: bad_3++;
					[[fallthrough]];

				case 1: all_3++;
			}
		}
	}

	// --- определение utf8, utf16 (только русский диапазон) ----------------
	// теперь по полученным числам проверяем
	double nOnePercent = double(m_nLen) / 500.0; // полпроцента, один - слишком много
	// потому что псевдографика ломает всю статистику. там нужно более продвинутые алгоритмы придумывать
	/*
	* Если в тексте очень уж часто встречается код 04, а верхних кодов достаточно мало
	* то это - Unicode (UTF-16 LE)(CP-1200)
	*/
	bool b1 = (std::abs(nX00 + n00_suffix) > 0.000001);
	bool b2 = (std::abs(nX00 - n00_suffix) < nOnePercent);
	bool b3 = (std::abs(nX04 + n04_suffix) > 0.000001);
	bool b4 = (std::abs(nX04 - n04_suffix) < nOnePercent);

	if (b1 && b2 && b3 && b4)
	{
		return FILE_CHARSET::UTF16LE;
	}

	/*
	* Если количество символов D1 и количество кодов из интервала 80..9f и
	* количество символов D0 и количество кодов из интервала b0..cf
	* примерно равны друг другу (разница в пределах 1% от размера файла),
	* то это скорее всего UTF-8 (распознаётся только русская кодировка, остальные нам не надо.)
	*/
	b1 = (std::abs(nD0 + nD0_suffix) > nOnePercent);
	b2 = (std::abs(nD0 - nD0_suffix) < nOnePercent);
	b3 = (std::abs(nD1 + nD1_suffix) > nOnePercent);
	b4 = (std::abs(nD1 - nD1_suffix) < nOnePercent);

	if (b1 && b2 && b3 && b4)
	{
		return FILE_CHARSET::UTF8;
	}

	// ----------------------------------------------------------------------

	/* Данные собраны. Теперь, если в каком-либо из вариантов недопустимых
	сочетаний не больше 1/32 от общего их числа, то считаем, что их и не
	было. */

	if (bad_1 <= (all_1 >> 5))
	{
		bad_1 = 0;
	}

	if (bad_2 <= (all_3 >> 5))
	{
		bad_2 = 0;
	}

	if (bad_3 <= (all_3 >> 5))
	{
		bad_3 = 0;
	}

	/* Получаем результат. */
	unsigned int a = ((255 - bad_1) << 8) + all_1;
	unsigned int b = ((255 - bad_2) << 8) + all_3;
	unsigned int c = ((255 - bad_3) << 8) + all_3;

	if ((a >= b) && (a >= c))
	{
		return FILE_CHARSET::CP866;
	}

	if (b >= c)
	{
		return FILE_CHARSET::CP1251;
	}

	return FILE_CHARSET::KOI8;
}

// конвертирование cp866 -> unicode
uint32_t CReader::o2w(uint8_t b)
{
	if (b == CHR_TAB || b == CHR_N || b == CHR_R)
	{
		return b;
	}

	return m_cp866[b];
}

// конвертирование unicode -> cp866
uint8_t CReader::w2o(uint32_t tch)
{
	if (tch == CHR_TAB || tch == CHR_N || tch == CHR_R)
	{
		return (tch & 0xff);
	}

	for (int i = 0; i < 256; ++i)
	{
		if (m_cp866[i] == tch)
		{
			return i & 0xff;
		}
	}

	return ' ';
}

// конвертирование cp1251 -> unicode
uint32_t CReader::a2w(uint8_t b)
{
	if (b < CHR_SPACE)
	{
		return b;
	}

	return m_cp1251[b];
}

// конвертирование unicode -> cp1251
uint8_t CReader::w2a(uint32_t tch)
{
	if (tch <= CHR_SPACE)
	{
		return (tch & 0xff);
	}

	for (int i = CHR_SPACE; i < 256; ++i)
	{
		if (m_cp1251[i] == tch)
		{
			return i & 0xff;
		}
	}

	return ' ';
}

uint16_t CReader::ReadWord() const
{
	uint16_t w = 0;

	if (m_file)
	{
		auto readed = fread(&w, 1, sizeof(uint16_t), m_file);
	}

	return w;
}

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

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

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

// из-за того, что fgetpos/fsetpos хреново работают с UTF-8, будем самостоятельно читать UTF8 из бинарного файла.
uint32_t CReader::ReadUTF8Char() const
{
	/*
	The encoding used to represent Unicode into bytes is based on rules that define how to break-up the bit-string representing an UCS into bytes.

	If an UCS fits  7 bits, its coded as   0xxxxxxx. This makes ASCII character represented by themselves
	If an UCS fits 11 bits, it is coded as 110xxxxx 10xxxxxx
	If an UCS fits 16 bits, it is coded as 1110xxxx 10xxxxxx 10xxxxxx
	If an UCS fits 21 bits, it is coded as 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
	If an UCS fits 26 bits, it is coded as 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
	If an UCS fits 31 bits, it is coded as 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
	*/
	std::array<uint8_t, 8> ch = { 0 };
	int pos = 0;
	uint8_t mask = 0x80;
	fread(&ch[pos++], 1, sizeof(uint8_t), m_file); // читаем первый символ

	if (ch[0] & mask) // если старший бит установлен, то будем читать символы
	{
		ch[0] &= ~mask; // убираем ненужный больше бит
		mask >>= 1; // двигаем маску

		while (ch[0] & mask) // теперь, пока есть биты, читаем
		{
			if (pos >= 6) // если прочли уже максимум, а у нас всё ещё биты установлены
			{
				// значит ошибка. неверная кодировка
				return 0;
			}

			fread(&ch[pos++], 1, sizeof(uint8_t), m_file); // читаем очередной байт
			ch[0] &= ~mask; // убираем ненужный больше бит
			mask >>= 1;  // двигаем маску
		}
	}
	else
	{
		// если старший бит не установлен, то это просто ascii, и его возвращаем как есть
		return uint32_t(ch[0]);
	}

	if (pos == 1) // неверно, это мы в середину последовательности попали
	{
		return 0;
	}

	// всё верно, прочитали нужное количество байтов, теперь соберём код
	uint32_t code = 0;

	for (int i = 0; i < pos; ++i)
	{
		code <<= 6;
		code |= ch[i] & 0x3f;
	}

	return code;
}


int CReader::WriteUTF8Char(uint32_t tch)
{
	std::array<uint8_t, 8> ch = { 0 };
	int num = 0; // количество байтов

	if (tch < 0x80) // one octet
	{
		ch[num++] = static_cast<uint8_t>(tch);
	}
	else if (tch < (1 << 11))
	{
		// two octets
		ch[num++] = static_cast<uint8_t>((tch >> 6) | 0xc0);
		ch[num++] = static_cast<uint8_t>((tch & 0x3f) | 0x80);
	}
	else if (tch < (1 << 16))
	{
		// three octets
		ch[num++] = static_cast<uint8_t>((tch >> 12) | 0xe0);
		ch[num++] = static_cast<uint8_t>(((tch >> 6) & 0x3f) | 0x80);
		ch[num++] = static_cast<uint8_t>((tch & 0x3f) | 0x80);
	}
	else if (tch < (1 << 21))
	{
		// four octets
		ch[num++] = static_cast<uint8_t>((tch >> 18) | 0xf0);
		ch[num++] = static_cast<uint8_t>(((tch >> 12) & 0x3f) | 0x80);
		ch[num++] = static_cast<uint8_t>(((tch >> 6) & 0x3f) | 0x80);
		ch[num++] = static_cast<uint8_t>((tch & 0x3f) | 0x80);
	}
	else if (tch < (1 << 26))
	{
		// 5 octets
		ch[num++] = static_cast<uint8_t>((tch >> 24) | 0xf8);
		ch[num++] = static_cast<uint8_t>(((tch >> 18) & 0x3f) | 0x80);
		ch[num++] = static_cast<uint8_t>(((tch >> 12) & 0x3f) | 0x80);
		ch[num++] = static_cast<uint8_t>(((tch >> 6) & 0x3f) | 0x80);
		ch[num++] = static_cast<uint8_t>((tch & 0x3f) | 0x80);
	}
	else
	{
		// 6 octets
		ch[num++] = static_cast<uint8_t>((tch >> 30) | 0xfc);
		ch[num++] = static_cast<uint8_t>(((tch >> 24) & 0x3f) | 0x80);
		ch[num++] = static_cast<uint8_t>(((tch >> 18) & 0x3f) | 0x80);
		ch[num++] = static_cast<uint8_t>(((tch >> 12) & 0x3f) | 0x80);
		ch[num++] = static_cast<uint8_t>(((tch >> 6) & 0x3f) | 0x80);
		ch[num++] = static_cast<uint8_t>((tch & 0x3f) | 0x80);
	}

	const auto w = fwrite(&ch[0], sizeof(uint8_t), num, m_file);
	return static_cast<int>(w);
}

int CReader::WriteUtf16LEChar(uint32_t tch)
{
	if (tch < 0x10000)
	{
		if (WriteWord(tch))
		{
			return 1;
		}
	}
	else
	{
		tch -= 0x10000;
		uint16_t lo10 = (tch & 0x3ff) | 0xDC00;
		uint16_t hi10 = (tch >> 10) | 0xD800;

		if (WriteWord(hi10))
		{
			if (WriteWord(lo10))
			{
				return 2;
			}

			return 1;
		}
	}

	return 0;
}

uint32_t CReader::ReadUtf16LEChar()
{
	uint16_t Leading = ReadWord();

	if (Leading < 0xD800 || Leading > 0xDFFF)
	{
		return Leading;
	}

	if (Leading >= 0xDC00)
	{
		return 0; // Недопустимая кодовая последовательность
	}

	uint32_t Code = static_cast<uint32_t>(Leading & 0x3ff) << 10;
	uint16_t Trailing = ReadWord();

	if (Trailing < 0xDC00 || Trailing > 0xDFFF)
	{
		return 0; // Недопустимая кодовая последовательность
	}

	Code |= (Trailing & 0x3ff);
	return Code;
}

