#include "pch.h"
#include "BKCrun.h"
#include "ConvW2k.h"

#pragma warning(disable:4996)

/*
автораспаковщик не работает. он и изначально не работал
потому что по алгоритму источник и приёмник не должны пересекаться.

поэтому ценность данной вещи сомнительна
*/

MemoryModel CrunMemory;

// блок автораспаковки
uint16_t CrunAUNP[] =
{
	0032737, 0000000, 0177716,
	0000412,
	0000352,
	0071103, 0067165, 0060520, 0065543, // .ascii  "CrunPack by (c)GID"
	0061040, 0020171, 0061450, 0043451,
	0042111,

	0010702,
	0062702, 0000344,

	0012200,                        // адрес куда распаковывать
	0012201,                        // длина упакованного файла
	0060201,                        // конечный адрес упакованного
	0014103,                        // начальный счетчик битов
	0014105,                        // начальное значение накопителя
	0012204,                        // длина распакованного файла
	0060004,                        // конечный адрес распаковки
	0010167, 0000262,               // конечный адрес упакованного массива
	0010267, 0000234,               // начальный адрес упакованного массива
	0010046,
	0010667, 0000256,

	0004767, 0000204,               // получаем бит
	0103424,                        // 1
	0012702, 0000002,               // 0
	0012701, 0000010,
	0004767, 0000166,               // получаем бит
	0103433,                        // 01
	0012701, 0000003,               // 00
	0004767, 0000140,               // получаем 3хбитное слово
	0005200,                        // +1
	0010002,                        // счетчик байтов
	0012701, 0000010,
	0004767, 0000124,               // получаем байт
	0110044,                        // помещаем на место
	0077206,                        // сколько надо.
	0000751,

	0004767, 0000126,               // получаем бит
	0103421,                        // 11
	0012702, 0000003,               // маркер 10
	0012701, 0000011,
	0004767, 0000110,               // получаем бит
	0103004,                        // 100
	0012702, 0000004,               // 101
	0012701, 0000012,

	0004767, 0000056,               // получаем смещения
	0060400,                        // получаем адрес, откуда брать цепочку
	0114044,                        // копируем цепочку
	0077202,                        // такой длины
	0000725,

	0004767, 0000056,               // получаем бит
	0103007,
	0012701, 0000010,               // маркер 111
	0004767, 0000030,               // извлекаем байт
	0062700, 0000011,               // счетчик байтов +11
	0000732,                        // идем извлекать байты

	0012701, 0000010,               // маркер 110
	0004767, 0000012,               // извлекаем байт
	0005200,                        // +1
	0010002,                        // длина цепочки байтов
	0012701, 0000014,               // 14бит - размер смещения?
	0000747,                        //

	// S U B R O U T I N E  GETLEX
	0005000,                        // приемник слова
	0004767, 0000006,               // получаем бит
	0006100,                        // формируем байт
	0077104,                        // счетчик битов в слове
	0000207,
	// End of Function GETLEX
	// S U B R O U T I N E  GETBIT
	0005303,                        // уменьшаем счетчик битов
	0100420,                        // <0 - все, конец работы
	0001402,                        // ==0 - надо брать новое слово
	0006005,                        // достаем очередной бит из накопителя
	0000207,

	0026727, 0000022,               // до начала дошли?
	0000000,
	0101402,                        // да - пропустим инициализацию счетчика,
	// чтобы он стал <0 и можно было выйти.
	0012703, 0000020,               // в слове 16 бит
	0162767, 0000002, 0000004,      // двигаем указатель
	0006005,                        // достаем очередной бит из накопителя
	0013705,                        // и загружаем новое слово в накопитель
	0000000,
	0000207,

	0012706,                        // выход из п/п распаковки
	0000000,
	0012600,
	0000207,
	// End of Function GETBIT
	0051103, 0047125, 0044103, 0027067, // .ascii  "CRUNCH7.2 "
	0020062,
	0000000, 0000000,               // расширение
	0000000,                        // резерв
	0000000,                        // адрес загрузки
	0000000,                        // длина упакованного файла
	0000000                         // длина неупакованного файла
};
// размер блока автораспаковки в байтах
constexpr auto LENAUNP = sizeof(CrunAUNP);

// переменные
int     nBitCounter = 0;    // счётчик битов в накопителе
uint16_t nBitAccum;         // накопитель битов

int nUniqueByteCnt; // счётчик одиночных байтов
int LENCHN; // длина найденной цепочки
int OFSCHN; // смещение до найденной цепочки
int ENDADR; // конечный адрес программы
int TENDAD; // конец текущего интервала
int INDCHN; // индекс найденной цепочки


constexpr auto ARR_SIZE = 4;
// длины цепочек : 2, 3, 4, (5 <= n < 400)
std::array<int, ARR_SIZE> LIMBOU = { 0400, 01000, 02000, 010000 }; // граничные смещения для цепочек соответствующей длины
std::array<int, ARR_SIZE> L2032 = { 010, 011, 012, 014 }; // длина в битах для смещения
std::array<int, ARR_SIZE> L2042 = { 00, 00, 00, 010 }; // длина кодирования цепочки - 0 для предзаданной длины цепочек
std::array<int, ARR_SIZE> L2052 = { 02, 03, 03, 03 }; // длина маркеров в битах
std::array<int, ARR_SIZE> L2062 = { 01, 04, 05, 06 }; // сами маркеры

int R5 = 0; // указатель в исходном массиве
int R4 = 0; // указатель в формируемом массиве
int FOREAD = 010000; // насколько вперед просматривать файл


static void PackBits(int R3, int R0)
{
	for (int i = 0; i < R0; ++i)
	{
		nBitAccum <<= 1;

		if (R3 & 1)
		{
			nBitAccum |= 1;
		}

		R3 >>= 1;

		if (++nBitCounter >= 16)
		{
			nBitCounter = 0;
			CrunMemory.w[R4++] = nBitAccum;
			nBitAccum = 1;
		}
	}
}

static void L1724()
{
	int R3 = nUniqueByteCnt;

	if (R3) // одиночные байты были?
	{
		nUniqueByteCnt = 0; // да - счётчик обнуляем

		if (R3 >= 011)
		{
			// если их >=011
			R3 -= 011;
			R3 |= 03400;
			PackBits(R3, 013);  // маркер 111 и байт
		}
		else
		{
			// если меньше 011
			R3--;
			PackBits(R3, 5);    // маркер 00 и 3 бита
		}
	}
}

static int L1412()
{
	int R3 = R5 + FOREAD; // сформируем адрес конца

	if (R3 > ENDADR)
	{
		R3 = ENDADR; // и следим, чтобы за конец не выходило
	}

	TENDAD = R3; // запомним конец интервала
	LENCHN = 1; // длина лучшей найденной цепочки
	int R0 = R5 + 2;

	if (R0 < TENDAD)
	{
		do
		{
			if (MMemory.b[R5] == MMemory.b[R0] && MMemory.b[R5 + 1] == MMemory.b[R0 + 1])
			{
				// два байта совпали
				// посмотрим, сколько ещё совпадёт
				int R3 = R5;

				while (R0 < TENDAD)
				{
					if (MMemory.b[R3++] != MMemory.b[R0++])
					{
						break;
					}
				}

				int R2 = R0 - 1;
				R3--;
				R2 -= R3; // это - смещение до совпадающей цепочки
				R3 -= R5; // это - длина совпадающей цепочки

				if (R3 > LENCHN)
				{
					int R1{};

					if (R3 > 4)
					{
						if (R3 > 0400)
						{
							R3 = 0400; // если больше - ограничим 400
						}

						R1 = 3;
					}
					else
					{
						// если длина <=4
						R1 = R3 - 2;
					}

					if (R2 < LIMBOU[R1]) // сравним смещение с граничным, если больше - то пропустим это всё
					{
						LENCHN = R3; // сохраним длину найденной цепочки
						OFSCHN = R2; // сохраним смещение до найденной цепочки
						INDCHN = R1; // сохраним индекс
					}
				}
			}
			else
			{
				R0++;
			}

			// 1602:
		}
		while (R0 < TENDAD);

		if (LENCHN > 1)
		{
			// если хоть что-то нашли
			for (int i = 0; i < LENCHN; ++i)
			{
				MMemory.b[R5++] = 0;
			}

			L1724(); // для начала запакуем счётчик несовпадений
			PackBits(OFSCHN, L2032[INDCHN]); // запаковываем смещение

			if (L2042[INDCHN])
			{
				PackBits(LENCHN - 1, L2042[INDCHN]); // запаковываем длину цепочки
			}

			PackBits(L2062[INDCHN], L2052[INDCHN]); // маркеры
			return 0;
		}
	}

	// 1704: совпадений не найдено
	R3 = MMemory.b[R5]; // берём байт
	MMemory.b[R5++] = 0; // его обнуляем, чтобы не мешался
	PackBits(R3, 8); // пакуем
	return 1;
}

void CrunPack(int nFileLoadAddr, int nFileLen, int nFileActualAddr)
{
	ENDADR = nFileLoadAddr + nFileLen; // определим конечный адрес
	// в выходной массив надо поместить автораспаковщик
	R4 = 0;

	for (uint16_t i : CrunAUNP)
	{
		CrunMemory.w[R4++] = i;
	}

	// for (int i = 121; i < LENAUNP / 2; ++i) // перемещаем только заголовок
	// {
	//  CrunMemory.w[R4++] = CrunAUNP[i];
	// }
	int nBase = R4; // вот с этого начинаем сохранять упакованное
	nBitAccum = 1; // инициализируем накопитель битов
	nBitCounter = 0; //
	nUniqueByteCnt = 0; // счётчик одиночных байтов

	do
	{
		int R0 = L1412(); // ищем и пакуем цепочки
		nUniqueByteCnt += R0; // если цепочек не нашли - увеличим счётчик одиночных байтов

		if (nUniqueByteCnt >= 0410)
		{
			L1724(); // запакуем счётчик несовпадений
		}
	}
	while (R5 < ENDADR);

	L1724(); // запакуем счётчик несовпадений
	int R1 = 0;

	if (nBitAccum != 1) // накопитель битов пуст?
	{
		// нет
		CrunMemory.w[R4++] = nBitAccum; // накопитель битов в упакованный массив

		do
		{
			R1++;           // сформируем счётчик битов
			nBitAccum <<= 1;    // в накопителе битов
		}
		while (++nBitCounter < 16);
	}

	CrunMemory.w[R4++] = 16 - R1; // и его тоже в массив
	// тут надо скорректировать параметры в автораспаковщике
	// сохраним расширение
	auto strExt = g_strInFileName.extension().wstring();

	if (!strExt.empty())
	{
		auto *dst = &CrunMemory.b[(nBase - 6) * 2];

		for (size_t i = 0; i < 3; ++i)
		{
			dst[i] = UNICODEtoBK_ch(strExt[i + 1]);
		}
	}

	g_nFileLen = R4 * 2;
	g_nFileAddress = CrunMemory.w[nBase - 3] = nFileActualAddr; // адрес загрузки
	int nPackLen = CrunMemory.w[nBase - 2] = (R4 - nBase + 1) * 2; // длина упакованного файла, без автораспаковщика

	if (nFileLen & 1)
	{
		nFileLen++;
	}

	CrunMemory.w[nBase - 1] = nFileLen; // длина неупакованного файла
	int nRatio = (nPackLen + LENAUNP) * 100 / nFileLen;
	wprintf(L" OK, Ratio %d%%\n", nRatio);
}

// распаковщик
int W1 = 0, W2 = 0;

static bool GETBIT()
{
	if (--nBitCounter > 0)
	{
		bool b = (nBitAccum & 1) ? true : false;
		nBitAccum >>= 1;
		return b;
	}

	if (W1 > W2)
	{
		nBitCounter = 16;
	}
	else
	{
		throw 1; // бросаем исключение для выхода в любой момент
	}

	bool b = (nBitAccum & 1) ? true : false;
	nBitAccum = MMemory.w[--W1];
	return b;
}

static uint16_t GETLEX(int R1)
{
	uint16_t R0 = 0;

	for (int i = 0; i < R1; ++i)
	{
		R0 <<= 1;

		if (GETBIT())
		{
			R0 |= 1;
		}
	}

	return R0;
}

/*
 маркеры
 00, 3бита - счётчик неповторяющихся байтов {, байты}
 01, 8бит - смещение (длина цепочки = 2)
 100, 9бит - смещение (длина цепочки = 3)
 101, 10бит - смещение (длина цепочки = 4)
 111, 8бит - счётчик неповторяющихся байтов-9 {, байты}
 110, 8бит - длина цепочки байтов, 12бит - смещение

*/
void CrunUnPack(int nFileLoadAddr, int nFileLen, int nFileActualAddr)
{
	int nBase = (MMemory.w[4] + 030) / 2; // в автораспаковщике смещение к заголовку
	// int nBase = 8; // когда только заголовок
	g_nFileAddress = MMemory.w[nBase++]; // адрес куда распаковывать (в байтах)
	int R0 = 0;
	int R1 = MMemory.w[nBase++] / 2; // длина упакованного файла (в словах)
	R1 += nBase; // конечный адрес упакованного массива (в словах)
	nBitCounter = MMemory.w[--R1]; // начальный счётчик битов
	nBitAccum = MMemory.w[--R1]; // начальное значение накопителя
	R4 = MMemory.w[nBase++]; // длина распакованного файла (в байтах)
	g_nFileLen = R4;
	R4 += R0; // конечный адрес распаковки
	W1 = R1; // конечный адрес упакованного массива (в словах)
	W2 = nBase; // начальный адрес упакованного массива (в словах)

	try
	{
		int R2 = 0;

		for (;;)
		{
			if (GETBIT()) // получаем бит
			{
				// 1
				if (GETBIT()) // получаем бит
				{
					// 11
					if (GETBIT()) // получаем бит
					{
						// маркер 111: {байт - счётчик байтов-11}{байты}
						R2 = GETLEX(8) + 011;

						for (int i = 0; i < R2; ++i)
						{
							CrunMemory.b[--R4] = LOBYTE(GETLEX(8));
						}
					}
					else
					{
						// маркер 110: {байт - длина цепочки}{014бит - смещение}
						R2 = GETLEX(8) + 1;
						R0 = GETLEX(014); // получаем смещения
						R0 += R4; // получаем адрес, откуда брать цепочку

						for (int i = 0; i < R2; ++i)
						{
							CrunMemory.b[--R4] = CrunMemory.b[--R0];
						}
					}
				}
				else
				{
					// 10
					if (GETBIT()) // получаем бит
					{
						// маркер 101: {012 смещение} 4 байтная цепочка
						R2 = 4;
						R1 = 012;
					}
					else
					{
						// маркер 100: {011 смещение} 3 байтная цепочка
						R2 = 3;
						R1 = 011;
					}

					R0 = GETLEX(R1); // получаем смещения
					R0 += R4; // получаем адрес, откуда брать цепочку

					for (int i = 0; i < R2; ++i)
					{
						CrunMemory.b[--R4] = CrunMemory.b[--R0];
					}
				}
			}
			else
			{
				// 0
				if (GETBIT()) // получаем бит
				{
					// маркер 01: {байт - смещение} цепочка из двух байтов
					R0 = GETLEX(8); // получаем смещения
					R0 += R4; // получаем адрес, откуда брать цепочку

					for (int i = 0; i < 2; ++i)
					{
						CrunMemory.b[--R4] = CrunMemory.b[--R0];
					}
				}
				else
				{
					// маркер 00: {3битный счётчик байтов}{байты}
					R2 = GETLEX(3) + 1;

					for (int i = 0; i < R2; ++i)
					{
						CrunMemory.b[--R4] = LOBYTE(GETLEX(8));
					}
				}
			}
		}
	}
	catch (int e)
	{
		// выход из функции, потому что конец распаковки
	}
}


