#include "stdafx.h"
#include "resource.h"
#include "BKImage.h"
#include "SprWnd.h"

//тут будут функции для просмотра файлов
#pragma warning(disable:4996)
void PutTchar(FILE *f, wchar_t tch, int &counter)
{
	fwrite(&tch, 1, sizeof(tch), f);
	counter++;
	fflush(f);
}

//вход: pBuff - указатель на буфер.
//		pos - текущая позиция в буфере
uint8_t GetByte(uint8_t *pBuff, int &pos)
{
	return *(pBuff + (pos++));
}

//вход: pBuff - указатель на буфер.
//		pos - текущая позиция в буфере
uint16_t GetWord(uint8_t *pBuff, int &pos)
{
	uint16_t w = GetByte(pBuff, pos) | (GetByte(pBuff, pos) << 8);
	return w;
}

constexpr auto TAB_WIDTH = 8;

bool CBKImage::ViewFile(AbstractFileRecord *fr)
{
	bool bRet = false;

	//если это не файл - выходим ничего не делая
	if (fr->nAttr & (FR_ATTR::DIR | FR_ATTR::LOGDISK))
	{
		return true;
	}

	wchar_t pTmpPath[_MAX_PATH];
	std::wstring strFName = SetSafeName(fr->strName);
	_tsplitpath_s(strFName.c_str(), nullptr, 0, nullptr, 0, nullptr, 0, pTmpPath, _MAX_PATH); //узнаем расширение
	std::wstring strExt = strToUpper(std::wstring(pTmpPath));
	GetTempPath(_MAX_PATH, pTmpPath);
	strFName = std::wstring(pTmpPath) + strFName;
	FILE *pFView;

	if ((pFView = _tfopen(strFName.c_str(), L"wb")))
	{
		const int len = fr->nSize;
		int pos = 0;
		auto pFBuffer = new uint8_t[m_pFloppyImage->EvenSizeByBlock(len)];

		if (pFBuffer)
		{
			if (m_pFloppyImage->ReadFile(fr, pFBuffer))
			{
				uint8_t b;
				int counter = 0;
				wchar_t tch = 0xfeff; //BOM

				if (strExt == L".VXT" || strExt == L".LST")
				{
					PutTchar(pFView, tch, counter); //запишем сперва BOM

					if (GetWord(pFBuffer, pos) == 0x0c40)
					{
						//это мы пропускаем всякие шрифты и т.п.
						counter = GetWord(pFBuffer, pos);
					}
					else
					{
						counter = 0;
					}

					//имитируем перемещение на нужную позицию относительно начала файла
					int tabs = 0;
					pos = counter;
					counter = 0; //счётчик символов в строке

					while (pos < len)
					{
						b = GetByte(pFBuffer, pos);

						if (pos >= len) //доп.проверка на всякий случай
						{
							break;
						}

						if (b == 0 || b == 10) //конец строки
						{
							PutTchar(pFView, L'\r', counter);
							counter = -1;
							tch = L'\n';
						}
						else if (b < 9) //обычно это просто количество пробелов, но бывает и количество табуляторов.
						{
							//если на входе tabs == 0, то b - это кол-во пробелов.
							//tabs != 0 будет в единственном случае, когда сюда попадаем повторно сразу же,
							//т.е. два подряд символа с кодом меньше 9.
							//в этом случае второй символ - уже количество табуляций
							//третий и последующие подряд - снова кол-во табуляций, хотя скорее всего это невозможно,
							//т.к. ширина таб == 8, то 8 табуляторов - уже вся БКшная строка.
							if (tabs == 0)
							{
								tabs++;

								for (int i = 0; i < b; ++i)
								{
									PutTchar(pFView, L' ', counter);
								}
							}
							else
							{
								for (int i = 0; i < b; ++i)
								{
									for (int j = 0; j < 8; ++j) //для vxt - строго на 8 пробелов. это не совсем табуляция
									{
										PutTchar(pFView, L' ', counter);
									}
								}
							}

							continue;
						}
						else if (b == 9) //табуляция, по 8 символов на табулятор
						{
							int nNextTabPos = (counter / TAB_WIDTH + 1) * TAB_WIDTH;

							do
							{
								PutTchar(pFView, L' ', counter);
							}
							while (counter < nNextTabPos);

							tabs = 0;
							continue;
						}
						else if (b < 32)
						{
							tabs = 0;
							continue; //эти коды в vxt используются для визуального форматирования, типа жирный текст, курсив и т.п.
						}
						else
						{
							tch = BKToUNICODE_Byte(b, m_pFloppyImage->m_pKoi8tbl);
						}

						tabs = 0;
						PutTchar(pFView, tch, counter);
					}
				}
				else
				{
					bool bFlg = false;
					PutTchar(pFView, tch, counter); //запишем сперва BOM

					while (pos < len)
					{
						b = GetByte(pFBuffer, pos);
						int repeater = 1;

						if (b == 0 || b == 10) //перевод строки
						{
							PutTchar(pFView, L'\r', counter);
							tch = L'\n';
							counter = -1;
						}
						else if (b == 9) //табуляция
						{
							int nNextTabPos = (counter / TAB_WIDTH + 1) * TAB_WIDTH;

							do
							{
								PutTchar(pFView, L' ', counter);
							}
							while (counter < nNextTabPos);

							continue;
						}
						else if (b < 020) //это количество пробелов, или табуляций
						{
							if (bFlg) //если число второй раз подряд - это уже число табуляций
							{
								for (int i = 0; i < b; ++i)
								{
									int nNextTabPos = (counter / TAB_WIDTH + 1) * TAB_WIDTH;

									do
									{
										PutTchar(pFView, L' ', counter);
									}
									while (counter < nNextTabPos);
								}

								bFlg = false;
							}
							else //если число один раз - это кол-во пробелов
							{
								for (int i = 0; i < b; ++i)
								{
									PutTchar(pFView, L' ', counter);
								}

								bFlg = true;
							}

							continue;
						}
						else if (b < 040) // это разные спец коды типа жирный, курсив, подчёркивание и т.п.
						{
							continue;
						}
						else
						{
							if (0200 < b && b < 0240) //то это количество следующих за кодом повторяющихся символов
							{
								repeater = b & 037;
								b = GetByte(pFBuffer, pos);

								//какая-то непонятная фигня. если после счётчика повторений коды 0..37, то они мапятся на 40..77
								if (b < 0140)
								{
									b += 040;
								} //это редкий диалект. обычно эта штука может вносить артефакты в текст
							}

							tch = BKToUNICODE_Byte(b, m_pFloppyImage->m_pKoi8tbl);
						}

						for (int i = 0; i < repeater; ++i)
						{
							PutTchar(pFView, tch, counter);
						}

						bFlg = false;
					}
				}

				bRet = true;
			}
			else
			{
				AfxGetMainWnd()->SendMessage(WM_SEND_ERRORNUM, WPARAM(0), LPARAM(m_pFloppyImage->GetErrorNumber()));
			}

			delete[] pFBuffer;
		}
		else
		{
			AfxGetMainWnd()->SendMessage(WM_SEND_ERRORNUM, WPARAM(0), LPARAM(IMAGE_ERROR::NOT_ENOUGHT_MEMORY));
		}

		fclose(pFView);
	}

	if (bRet)
	{
		//запускаем блокнот с этим файлом в качестве параметра
		STARTUPINFO si;
		PROCESS_INFORMATION pi;
		ZeroMemory(&si, sizeof(si));
		si.cb = sizeof(si);
		ZeroMemory(&pi, sizeof(pi));
		/*
		*если запускать текстовый файл через explorer, т.е. пытаться открыть прогой,
		которая с ассоциирована с *.txt, то процесс explorer успешно завершается передачей файла
		например блокноту, и прежде чем блокнот запустится, файл удаляется.
		Можно не удалять файлы во временной папке, но тогда перед повторным просмотром
		надо искать файл и удалять его, если он там есть, в общем всячески усложнять алгоритм
		К тому же, если расширение уже с чем-то в винде ассоциировано или наоборот, ни с чем не
		ассоциировано, то просмотреть файл будет затруднительно.
		Вариант с принудительным запуском блокнота самый оптимальный.
		*/
		std::wstring strCmdLine = L"notepad \"" + strFName + L"\"";

		if (CreateProcess(nullptr,      // No module name (use command line)
		                  LPWSTR(strCmdLine.c_str()), // 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.c_str());
	return bRet;
}

bool CBKImage::ViewFileRT11(AbstractFileRecord *fr)
{
	bool bRet = false;

	//если это не файл - выходим ничего не делая
	if (fr->nAttr & (FR_ATTR::DIR | FR_ATTR::LOGDISK))
	{
		return true;
	}

	wchar_t pTmpPath[MAX_PATH];
	std::wstring strFName = SetSafeName(fr->strName);
	_tsplitpath_s(strFName.c_str(), nullptr, 0, nullptr, 0, nullptr, 0, pTmpPath, MAX_PATH); //узнаем расширение
	std::wstring strExt = strToUpper(std::wstring(pTmpPath));
	GetTempPath(MAX_PATH, pTmpPath);
	strFName = std::wstring(pTmpPath) + strFName;
	int nMode = 1; //режим. 0 - КОИ7Н0, 1 - КОИ7Н1, 2 - КОИ7Н2, -1 - КОИ8
	FILE *pFView;

	if ((pFView = _tfopen(strFName.c_str(), L"wb")))
	{
		const int len = fr->nSize;
		auto pFBuffer = new uint8_t[m_pFloppyImage->EvenSizeByBlock(len)];

		if (pFBuffer)
		{
			if (m_pFloppyImage->ReadFile(fr, pFBuffer))
			{
				uint8_t b;
				int counter = 0;
				int ccnt = 0; //счётчик управляющих кодов SI SO
				int cntRL = 0; //счётчик кодов 0300..0337
				int cntRH = 0; //счётчик кодов 0340..0377
				int pos = 0;

				//сперва проверим - кои7 или кои8
				while (pos < len)
				{
					b = GetByte(pFBuffer, pos);

					if (b > 127)
					{
						counter++;

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

				//если кодов >= 128 нету и кодов SO SI тоже нету
				if (counter == 0 && ccnt < 6)
				{
					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
				pos = 0; //перемещаемся к началу файла
				counter = 0;
				wchar_t tch = 0xfeff; //BOM
				PutTchar(pFView, tch, counter); //запишем сперва BOM

				while (pos < len)
				{
					b = GetByte(pFBuffer, pos);

					if (b == 0 || b == 10) //перевод строки
					{
						PutTchar(pFView, _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); //это для ДВК, УКНЦ и т.п.
								tch = BKToUNICODE_Byte(b, koi8tbl11M); //это для БК.
							}
						}
						else if (nMode == 0) //кои7н0 (ЛАТ)
						{
							if (b == 044)
							{
								tch = 0x00a4; //исключение - знак $ в знак солнышко
							}
							else
							{
								tch = wchar_t(b);
							}
						}
						else if (nMode == 1) //кои7н1 (РУС)
						{
							if (b == 044)
							{
								tch = 0x00a4;
							}
							else if (b >= 0100)
							{
								tch = koi8tbl11M[b];
							}
							else
							{
								tch = wchar_t(b);
							}
						}
						else if (nMode == 2)  //кои7н2 (либо ascii)
						{
							if (b == 044)
							{
								tch = 0x00a4;
							}
							else if (b >= 0140)
							{
								tch = koi8tbl11M[b]; //будем предполагать по умолчанию, что это кои7н2
								//tch = TCHAR(b); //будем предполагать по умолчанию, что это не кои7н2, а исключительно английский текст
							}
							else
							{
								tch = wchar_t(b);
							}
						}
					}

					PutTchar(pFView, tch, counter);
				}

				bRet = true;
			}
			else
			{
				AfxGetMainWnd()->SendMessage(WM_SEND_ERRORNUM, WPARAM(0), LPARAM(m_pFloppyImage->GetErrorNumber()));
			}

			delete[] pFBuffer;
		}
		else
		{
			AfxGetMainWnd()->SendMessage(WM_SEND_ERRORNUM, WPARAM(0), LPARAM(IMAGE_ERROR::NOT_ENOUGHT_MEMORY));
		}

		fclose(pFView);
	}

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

		if (CreateProcess(nullptr,          // No module name (use command line)
		                  LPWSTR(strCmdLine.c_str()), // 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.c_str());
	return bRet;
}

bool CBKImage::ViewFileAsSprite(AbstractFileRecord *fr)
{
	bool bRet = false;

	//если это не файл - выходим ничего не делая
	if (fr->nAttr & (FR_ATTR::DIR | FR_ATTR::LOGDISK))
	{
		return true;
	}

	const int len = fr->nSize;

	if (len > 0)
	{
		auto pFBuffer = new uint8_t[m_pFloppyImage->EvenSizeByBlock(len)];

		if (pFBuffer)
		{
			if (m_pFloppyImage->ReadFile(fr, pFBuffer))
			{
				auto m_pSprWnd = new CSprWnd(pFBuffer, len); //обязательно создавать динамически.

				if (m_pSprWnd)
				{
					WNDCLASS wc;
					memset(&wc, 0, sizeof(wc));
					wc.style = CS_HREDRAW | CS_VREDRAW;
					wc.hInstance = AfxGetInstanceHandle();
					wc.lpfnWndProc = ::DefWindowProc;
					wc.lpszClassName = L"BKSpriteViewClass";
					wc.hbrBackground = (HBRUSH)::GetStockObject(GRAY_BRUSH);
					AfxRegisterClass(&wc);

					if (m_pSprWnd->Create(
					            L"BKSpriteViewClass",           //Имя класса виндовс
					            (std::wstring(L"SpriteView: ") + fr->strName).c_str(),   //имя окна
					            WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME, // window styles
					            CRect(0, 0, 200, 200),   //размер окна
					            AfxGetMainWnd(),       //родитель окна
					            nullptr,
					            WS_EX_TOOLWINDOW /*| WS_EX_OVERLAPPEDWINDOW*/ //extended window styles
					        ))
					{
						m_pSprWnd->ShowWindow(SW_SHOW);
					}

					bRet = true;
				}
				else
				{
					AfxGetMainWnd()->SendMessage(WM_SEND_ERRORNUM, WPARAM(0), LPARAM(IMAGE_ERROR::NOT_ENOUGHT_MEMORY));
				}
			}
			else
			{
				AfxGetMainWnd()->SendMessage(WM_SEND_ERRORNUM, WPARAM(0), LPARAM(m_pFloppyImage->GetErrorNumber()));
			}

			delete[] pFBuffer; //всё, этот буфер не нужен, его содержимое копируется во внутренний буфер в m_pSprWnd
		}
		else
		{
			AfxGetMainWnd()->SendMessage(WM_SEND_ERRORNUM, WPARAM(0), LPARAM(IMAGE_ERROR::NOT_ENOUGHT_MEMORY));
		}
	}

	return bRet;
}

