// ITCPC.cpp: определяет точку входа для консольного приложения.
//

#include "stdafx.h"

#include "ITCPC.h"
#include "getopt.h"
#include <io.h>
#include <locale>

#include "bk2ibm.h"
#include "ibm2bk.h"
#include "ibm2vxt.h"

int wmain(int argc, wchar_t *argv[], wchar_t *envp[])
{
	_wsetlocale(LC_ALL, L"Russian");
	int nRetCode = 0;
	std::wstring strStartPath;   // директория, откуда начинать искать образы
	static struct option long_options[] =
	{
		{ L"help",        ARG_NONE, nullptr, L'h' },
		{ L"input",       ARG_REQ,  nullptr, L'i' },
		{ L"output",      ARG_REQ,  nullptr, L'o' },
		{ L"tab",         ARG_REQ,  nullptr, L't' },
		{ nullptr,        ARG_NULL, nullptr, ARG_NULL }
	};
	static wchar_t optstring[] = L"hi:o:t:";
	workMode WorkParam;
	WorkParam.chInM = L'A';
	WorkParam.chOutM = L'U';
	WorkParam.nTabWidth = 0;

	if (argc < 2)
	{
		// если запускаем без аргументов, то сразу вывести подсказку и выйти.
		Usage();
		return nRetCode;
	}

	std::wstring strTmp;
	int option_index = 0;
	int c;

	while ((c = getopt_long(argc, argv, optstring, long_options, &option_index)) != -1)
	{
		// Handle options
		switch (c)
		{
			case L'i':
				strTmp = (optarg) ? std::wstring(optarg) : L"A";

				if (!strTmp.empty())
				{
					std::locale loc;
					WorkParam.chInM = toupper(strTmp.at(0), loc); // оставим только первую букву
				}

				break;

			case L'o':
				strTmp = (optarg) ? std::wstring(optarg) : L"U";

				if (!strTmp.empty())
				{
					std::locale loc;
					WorkParam.chOutM = toupper(strTmp.at(0), loc); // оставим только первую букву
				}

				break;

			case L't':
				WorkParam.nTabWidth = (optarg) ? wcstol(optarg, nullptr, 10) : 0;
				break;

			case L'h':
				Usage();
				return nRetCode;
		}
	}

	// проверяем параметры на корректность.
	if (!CheckInputParams(&WorkParam))
	{
		Usage();
		return nRetCode;
	}

	argc -= optind;
	argv += optind;
	wprintf(L"Преобразование %s -> %s\n", GetFormatName(WorkParam.chInM).c_str(), GetFormatName(WorkParam.chOutM).c_str());

	if (WorkParam.chInM == WorkParam.chOutM)
	{
		wprintf(L"Бессмысленное преобразование делать не будем.\n");
		return nRetCode;
	}

	//  if (WorkParam.chInM == L'A' && (WorkParam.chOutM != L'E' && WorkParam.chOutM != L'V'))
	//  {
	//      wprintf(L"Подозрительное преобразование на всякий случай делать не будем.\n");
	//      return nRetCode;
	//  }
	// из бейсик аск в вортекс преобразование невозможно
	// т.к. в аск потоке нереализован просмотр вперёд и произвольное перемещение по потоку.
	if (WorkParam.chInM == L'B' && WorkParam.chOutM == L'V')
	{
		wprintf(L"Такое преобразование невозможно по техническим причинам.\n");
		return nRetCode;
	}

	struct _wfinddatai64_t f_file;

	intptr_t hFile;

	int nFilesCount = 0; // счётчик обработанных файлов

	CBaseFile *pInFile = nullptr;

	CBaseFile *pOutFile = nullptr;

	for (; argc > 0; argc--, argv++) // теперь, пока аргументы не кончатся - обрабатывать
	{
		if ((hFile = _wfindfirsti64(*argv, &f_file)) == -1L)
		{
			wprintf(L"Файл(ы) '%s' не найден(ы)\n", *argv);
		}
		else
		{
			do
			{
				if (f_file.attrib & _A_SUBDIR)
				{
					// если директория
					if ((wcscmp(f_file.name, L"..") == 0) || (wcscmp(f_file.name, L".") == 0))
					{
						continue;
					}

					// TODO: когда понадобится, сделать рекурсивный обход директорий
					// _wchdir(f_file.name); // заходим в поддиректорию
					// _wchdir(L".."); // выходим обратно
				}
				else
				{
					std::wstring strFilePath;
					wchar_t strDrive[_MAX_DIR];
					wchar_t strPath[_MAX_PATH];
					// выделим путь из исходного шаблона
					_tsplitpath_s(*argv, strDrive, _MAX_DIR, strPath, _MAX_PATH, nullptr, 0, nullptr, 0);

					if (!std::wstring(strDrive).empty()) // если задан привод, то в маске был полный путь
					{
						strFilePath = std::wstring(strDrive) + std::wstring(strPath);
					}
					else
					{
						wchar_t CurrDir[_MAX_PATH];
						_wgetcwd(CurrDir, _MAX_PATH); // текущая директория
						strFilePath = std::wstring(CurrDir) + L"\\" + std::wstring(strPath);
					}

					// делаем имя входного файла
					WorkParam.strInFileName = strFilePath + std::wstring(f_file.name); // полный путь к имени файла
					// делаем имя выходного файла.
					MakeOutName(&WorkParam);
					wprintf(L"%s -> %s\n", WorkParam.strInFileName.c_str(), WorkParam.strOutFileName.c_str());

					switch (WorkParam.chInM)
					{
						default:
						case L'A':
							pInFile = new CWinFile(WorkParam.strInFileName);
							break;

						case L'U':
							pInFile = new CWinFile(WorkParam.strInFileName, CWinFile::FILE_CHARSET::UTF16LE);
							break;

						case L'W':
							pInFile = new CWinFile(WorkParam.strInFileName, CWinFile::FILE_CHARSET::CP1251);
							break;

						case L'O':
							pInFile = new CWinFile(WorkParam.strInFileName, CWinFile::FILE_CHARSET::CP866);
							break;

						case L'K':
							pInFile = new CWinFile(WorkParam.strInFileName, CWinFile::FILE_CHARSET::KOI8);
							break;

						case L'E':
							pInFile = new CBKFile(WorkParam.strInFileName);
							break;

						case L'V':
							pInFile = new CBKFile(WorkParam.strInFileName);
							break;

						case L'B':
							pInFile = new CBKASCFile(WorkParam.strInFileName);
							break;
					}

					if (2 <= WorkParam.nTabWidth && WorkParam.nTabWidth <= 16)
					{
						pInFile->setTabWidth(WorkParam.nTabWidth);
					}

					switch (WorkParam.chOutM)
					{
						default:
						case L'U':
							pOutFile = new CWinFile(WorkParam.strOutFileName, CWinFile::FILE_CHARSET::UTF16LE);
							BK2IBM(pInFile, pOutFile);
							break;

						case L'8':
							pOutFile = new CWinFile(WorkParam.strOutFileName, CWinFile::FILE_CHARSET::UTF8);
							BK2IBM(pInFile, pOutFile);
							break;

						case L'W':
							pOutFile = new CWinFile(WorkParam.strOutFileName, CWinFile::FILE_CHARSET::CP1251);
							BK2IBM(pInFile, pOutFile);
							break;

						case L'O':
							pOutFile = new CWinFile(WorkParam.strOutFileName, CWinFile::FILE_CHARSET::CP866);
							BK2IBM(pInFile, pOutFile);
							break;

						case L'K':
							pOutFile = new CWinFile(WorkParam.strOutFileName, CWinFile::FILE_CHARSET::KOI8);
							BK2IBM(pInFile, pOutFile);
							break;

						case L'E':
							pOutFile = new CBKFile(WorkParam.strOutFileName);
							IBM2BK(pInFile, pOutFile);
							break;

						case L'V':
							pOutFile = new CBKFile(WorkParam.strOutFileName);
							IBM2VXT(pInFile, pOutFile);
							break;

						case L'B':
							pOutFile = new CBKASCFile(WorkParam.strOutFileName);
							TXT2ASC(pInFile, pOutFile);
							break;
					}

					if (pInFile)
					{
						delete pInFile;
						pInFile = nullptr;
					}

					if (pOutFile)
					{
						delete pOutFile;
						pOutFile = nullptr;
					}

					nFilesCount++;
				}
			}
			while (_wfindnexti64(hFile, &f_file) == 0);

			_findclose(hFile);
		}
	}

	wprintf(L"Готово, обработано %d файлов.\n", nFilesCount);
	return nRetCode;
}

void Usage()
{
	wprintf(L"Конвертор текстов Win <--> БК\n" \
	        L"Использование:\n" \
	        L"ITCPC -h (--help)\n" \
	        L"  Вывод этой справки.\n" \
	        L"ITCPC [-i<in_mode>] [-o<out_mode>] [-t<n>] <InputFile1.ext> {<InputFileN.ext> ...}\n" \
	        L"  -i<in_mode> (--input <in_mode>) - задать формат входного файла.\n" \
	        L"  Возможные режимы: a - автоопределение (по умолчанию, если параметр\n" \
	        L"      отсутствует). Не работает для БКшных файлов. Они опознаются как\n" \
	        L"      файл в KOI8R, и будут обработаны неправильно.\n" \
	        L"                    b - Basic ASC файлы. InputFile1.ext должен быть\n" \
	        L"      заголовочным .ASC файлом в .bin формате.\n" \
	        L"                    u - Unicode UTF-16LE или Unicode UTF-8, форматы\n" \
	        L"      корректно определяются даже без BOM.\n" \
	        L"                    w - Windows CP-1251\n" \
	        L"                    o - OEM CP-866\n" \
	        L"                    k - KOI8R\n" \
	        L"                    e - БКшный файл в формате Edasp\n" \
	        L"                    v - БКшный файл в формате Vortex\n\n" \
	        L"  -o<out_mode> (--output <out_mode>) - задать формат выходного файла.\n" \
	        L"  Возможные режимы: u - Unicode UTF-16LE (по умолчанию, если параметр\n" \
	        L"      отсутствует)\n" \
	        L"                    8 - Unicode UTF-8\n" \
	        L"                    w - Windows CP-1251\n" \
	        L"                    o - OEM CP-866\n" \
	        L"                    k - KOI8R\n" \
	        L"                    e - БКшный файл в формате Edasp\n" \
	        L"                    v - БКшный файл в формате Vortex\n" \
	        L"                    b - Basic ASC файлы (Имеет смысл только для листингов\n" \
	        L"      текстов на Бейсике).\n\n" \
	        L"  -t<n> (--tab <n>) - задать ширину табулятора для входного файла.\n" \
	        L"      Допускаются значения от 2 до 16, остальные игнорируются.\n" \
	        L"      Принятые значения по умолчанию: для файлов Windows tab = 4,\n" \
	        L"      для файлов БК tab = 8.\n\n" \
	        L"  InputFile1.ext - входной файл. Допускается использование масок файлов.\n");
	//  wprintf(L"      и путей.\n"); // !!! пока без путей.
}

bool CheckInputParams(workMode *pwmode)
{
	std::wstring strInP  = L"ABUWOKEV";
	std::wstring strOutP = L"8BUWOKEV";

	if (strInP.find(pwmode->chInM) == std::wstring::npos)
	{
		return false;
	}

	if (strOutP.find(pwmode->chOutM) == std::wstring::npos)
	{
		return false;
	}

	return true;
}

std::wstring GetFormatName(wchar_t ch)
{
	switch (ch)
	{
		case L'A':
			return L"Авто";

		case L'U':
			return L"Unicode UTF-16LE";

		case L'W':
			return L"Windows 1251";

		case L'O':
			return L"OEM CP-866";

		case L'K':
			return L"KOI8R";

		case L'E':
			return L"Edasp";

		case L'V':
			return L"Vortex";

		case L'8':
			return L"Unicode UTF-8";

		case L'B':
			return L"Basic ASC";
	}

	return L"Неизвестно";
}

// делаем имя выходного файла
void MakeOutName(workMode *pwmode)
{
	// сперва скопируем имя файла
	pwmode->strOutFileName = pwmode->strInFileName;
	// если есть расширение, найдём его
	size_t l = pwmode->strOutFileName.length();
	size_t nCommaPos = pwmode->strOutFileName.rfind(L'.');
	std::wstring outExt;

	switch (pwmode->chOutM)
	{
		case L'E':
			outExt = L".edp";
			break;

		case L'V':
			outExt = L".vxt";
			break;

		case L'B':
			outExt = L".asc";
			break;

		default:
			outExt = L".txt";
			break;
	}

	if (nCommaPos != std::wstring::npos) // если расширение есть
	{
		std::wstring ext = pwmode->strOutFileName.substr(nCommaPos);

		if (wcscoll(ext.c_str(), outExt.c_str()))
		{
			// если расширения не равны
			pwmode->strOutFileName = pwmode->strOutFileName.substr(0, nCommaPos) + outExt; // то заменим старое на новое
		}
		else
		{
			// а если расширения равны
			pwmode->strOutFileName += outExt; // то добавим ещё одно.
		}
	}
	else
	{
		// а если расширения нету
		pwmode->strOutFileName += outExt; // то просто добавим новое.
	}
}
