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

#include "stdafx.h"
#include "BKDL.h"
#include <io.h>
#include <fcntl.h>
#include <list>
#include "getopt.h"

using namespace std;

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

FILE *file_txt = NULL;
FILE *file_lst = NULL;

CBKParseImage g_imgParser;

//опции
BOOL bRecursive = FALSE;    //флаг, искать ли образы во вложенных подкаталогах
BOOL bCSV = FALSE;          //флаг работы в режиме этом.
CString strCSVFile = _T("Files.csv");
CString strLstFile = _T("Files.lst");
CString strTxtFile = _T("Files.txt");


#define MAX_CONSOLE_LINES 500
BOOL bAttachedConsole = FALSE;


// Единственный объект приложения

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
	_tsetlocale(LC_ALL, _T("Russian"));
	int nRetCode = 0;
	HMODULE hModule = ::GetModuleHandle(NULL);

	if (hModule != NULL)
	{
		// инициализировать MFC, а также печать и сообщения об ошибках про сбое
		if (!AfxWinInit(hModule, NULL, ::GetCommandLine(), 0))
		{
			// TODO: измените код ошибки соответственно своим потребностям
			_tprintf(_T("Критическая ошибка: сбой при инициализации MFC\n"));
			nRetCode = 1;
		}
		else
		{
			CString strStartPath;   // директория, откуда начинать искать образы
			static struct option long_options[] =
			{
				{_T("help"),        ARG_NONE, 0, _T('h')},
				{_T("recursive"),   ARG_NONE, 0, _T('r')},
				{_T("csv"),         ARG_NONE, 0, _T('c')},
				{_T("list"),        ARG_REQ,  0, _T('l')},
				{_T("text"),        ARG_REQ,  0, _T('t')},
#ifndef BKDE_CONSOLE
				{_T("mode"),        ARG_REQ,  0, _T('m')},
#endif
				{ ARG_NULL , ARG_NULL , ARG_NULL , ARG_NULL }
			};
#ifndef BKDE_CONSOLE
			static TCHAR optstring[] = _T("hrcl:t:m:");
			nRetCode = FALSE;
#else
			static TCHAR optstring[] = _T("hrcl:t:");
			nRetCode = TRUE;
#endif
			BOOL bShowHelp = FALSE;
			int option_index = 0;
			int c;

			/* не прокатывает. если запускаем без параметров, никаких справок,
			сразу пытаемся делать свои дела. с настройками по умолчанию. как правило безуспешно.
			if (argc < 2)
			{
			//если запускаем без аргументов, то сразу вывести подсказку и выйти.
			Usage();
			return 0;
			}
			*/
			while ((c = getopt_long(argc, argv, optstring, long_options, &option_index)) != -1)
			{
				// Handle options
				switch (c)
				{
					case _T('r'):
						bRecursive = TRUE;
						break;

					case _T('c'):
						bCSV = TRUE;
						strLstFile = strCSVFile;
						break;

					case _T('l'):
						strLstFile = CString(optarg);
						break;

					case _T('t'):
						strTxtFile = CString(optarg);
						break;

					case _T('h'):
						bShowHelp = TRUE;
						break;
#ifndef BKDE_CONSOLE

					case _T('m'):
						nRetCode = (CString(optarg).CompareNoCase(_T("lst")) == 0);
						break;
#endif
				}

				if (bShowHelp)
				{
					break;
				}
			}

			if (bShowHelp)
			{
				Usage();
				return TRUE;
			}

			if (nRetCode)
			{
				argc -= optind;
				argv += optind;

				if (argc < 1)
				{
					TCHAR curDir[_MAX_PATH];
					GetCurrentDirectory(_MAX_PATH, curDir);
					strStartPath = CString(curDir);
				}
				else
				{
					//_tprintf(_T("Текущая директория: %s\n"), argv[0]);
					size_t l = _tcslen(argv[0]);

					//_tprintf(_T("длина: %d\nпоследние два символа: [%c][%x] [%c][%x]"), l, argv[0][l-2], argv[0][l-2], argv[0][l-1], argv[0][l-1] );
					//боремся методом тупого хака с багом консоли Conemu для FAR, в которой возникает неизлечимая ситуация <\">
					// (когда в конце пути обратный слэш и путь заключён в кавычки)
					if (argv[0][l - 1] == _T(' ') && argv[0][l - 2] == _T('"'))
					{
						argv[0][l - 2] = 0;
					}

					strStartPath = CString(argv[0]);
				}

				strStartPath.Trim(_T('"'));
				file_lst = _tfopen(strLstFile, _T("wt"));
				file_txt = _tfopen(strTxtFile, _T("wt"));

				if (file_lst == NULL)
				{
					_tprintf(_T("Ошибка создания файла %s\n"), strLstFile.GetBuffer());
				}
				else if (file_txt == NULL)
				{
					_tprintf(_T("Ошибка создания файла %s\n"), strTxtFile.GetBuffer());
				}
				else
				{
					if (bCSV)
					{
						_ftprintf(file_lst, _T("Image Path; Image Name; OSType; Bootable; LogDisk; Path in Img; Name; Blk Size; Addr; Size;\n"));
					}

					ScanDir(strStartPath);
					_tprintf(_T("\n"));
					fclose(file_lst);
					fclose(file_txt);
				}
			}

			return nRetCode;
		}
	}
	else
	{
		// TODO: Измените код ошибки соответственно своим потребностям
		_tprintf(_T("Критическая ошибка: неудачное завершение GetModuleHandle\n"));
		nRetCode = 1;
	}

	return nRetCode;
}


void Usage()
{
	_tprintf(_T("\nBKDL v1.2 Создание списка содержимого образов дискет БК.\n"));
	_tprintf(_T("Использование:\n"));
	_tprintf(_T("BKDL -h (--help)\n"));
	_tprintf(_T("  Вывод этой справки.\n"));
#ifndef BKDE_CONSOLE
	_tprintf(_T("BKDL -mlst [-r] [-l<Имя файла>] [-t<Имя файла>] [<Путь>]\n"));
	_tprintf(_T("  -mlst (--mode lst) - обязательный параметр, включающий режим\n  генерации списков.\n"));
	_tprintf(_T("Остальные параметры являются необязательными.\n"));
#else
	_tprintf(_T("BKDL [-r] [-l<Имя файла>] [-t<Имя файла>] [<Путь>]\n"));
	_tprintf(_T("Все параметры являются необязательными.\n"));
#endif
	_tprintf(_T("  -r (--recursive) - искать образы так же и во вложенных подкаталогах,\n  по умолчанию поиск только в заданном каталоге.\n"));
	_tprintf(_T("  -c (--csv) - на выходе создавать таблицу,\n  по умолчанию создаётся список.\n"));
	_tprintf(_T("  -l<Имя файла> (--list <Имя файла>) - задать имя файла полного списка,\n  по умолчанию Files.lst или Files.csv в режиме csv\n"));
	_tprintf(_T("  -t<Имя файла> (--text <Имя файла>) - задать имя файла краткого списка,\n  по умолчанию Files.txt\n"));
	_tprintf(_T("  <Путь> - путь к директории в которой находятся образы.\n"));
	_tprintf(_T("Если путь не указан, то берётся текущая директория.\n"));
	_tprintf(_T("Файлы полного списка и краткого списка создаются всегда в текущей директории.\n"));
}

void OutProgress(CString &strPath)
{
	static int nFilesTotal = -1; //начинаем с -1 потому что такой выбран алгоритм, первый вызов делается до цикла, остальные - в цикле.
	//чтобы строка влезала в 80 символов, надо урезать слишком длинные пути
	// длина постоянного текстового сообщения 30 символов, предполагаем 4хзначное число обработанных образов
	// значит на путь остаётся 50 символов
	int nPathLen = strPath.GetLength();
	CString outPath = strPath;

	if (nPathLen > 50)
	{
		outPath = _T("...") + strPath.Right(47); //47 - чтобы 3 точки полезали
	}

	_tprintf(_T("Images processed: %d; Dir: %-50s\r"), ++nFilesTotal, outPath.GetBuffer());
}

#define KNOWN_EXTS 3
BOOL ScanDir(CString strPath)
{
	static const CString pstrExts[KNOWN_EXTS] =
	{
		_T(".img"),
		_T(".bkd"),
		_T(".dsk")
	};
	CMyImgWorker g_myImgWorker;
	list<CString> l_Dirs;
	TCHAR pName[_MAX_FNAME];
	TCHAR pExt[_MAX_EXT];
	struct _tfinddata_t c_file;
	intptr_t hFile;
	int nImageSize = 0;
	CString strImgPathName;
	strPath.TrimRight(_T('\\'));

	if (!bCSV)
	{
		_ftprintf(file_lst, _T("%s\n"), strPath.GetBuffer());
	}

	_ftprintf(file_txt, _T("%s\n"), strPath.GetBuffer());

	if (_tchdir(strPath) != 0)
	{
		_ftprintf(file_lst, _T("Unable to enter the dir %s\n"), strPath.GetBuffer());
		_ftprintf(file_txt, _T("Unable to enter the dir %s\n"), strPath.GetBuffer());
		return FALSE;
	}

	OutProgress(strPath);

	if ((hFile = _tfindfirst(_T("*.*"), &c_file)) == -1L)
	{
		_ftprintf(file_lst, _T("Nothing at all in current directory!\n"));
		_ftprintf(file_txt, _T("Nothing at all in current directory!\n"));
	}
	else
	{
		do
		{
			CString name = CString(c_file.name);

			if (c_file.attrib & _A_SUBDIR)
			{
				if (name == _T(".") || name == _T(".."))
				{
					continue;
				}

				CString strPathDir = strPath + _T("\\") + name;
				l_Dirs.push_back(strPathDir);
			}
			else
			{
				_tsplitpath_s(c_file.name, NULL, 0, NULL, 0, pName, _MAX_FNAME, pExt, _MAX_EXT);
				CString strExt = CString(pExt).MakeLower();

				for (int n = 0; n < KNOWN_EXTS; n++)
				{
					if (strExt == pstrExts[n])
					{
						CString strName = CString(pName) + strExt;
						strImgPathName = strPath + _T("\\") + strName;
						PARSE_RESULT pres = g_imgParser.ParseImage(strImgPathName, 0);

						if (pres.imageOSType == IMAGE_TYPE_ERROR)
						{
							_ftprintf(file_lst, _T("%s = Read Error!\n"), strName.GetBuffer());
							_ftprintf(file_txt, _T("%s = Read Error!\n"), strName.GetBuffer());
						}
						else
						{
							if (!bCSV)
							{
								UINT nID = g_myImgWorker.GetImgFormatName(pres);
								CString strFormat;
								strFormat.LoadString(nID);
								CString strBootable = (pres.bImageBootable) ? _T("; Bootable") : _T("");
								_ftprintf(file_lst, _T("-------------------------------------------------------------------------\n"));
								_ftprintf(file_lst, _T("%s = Size: %d; Format: %s%s\n"), strName.GetBuffer(), nImageSize, strFormat.GetBuffer(), strBootable.GetBuffer());
							}

							_ftprintf(file_txt, _T("-------------------------------------------------------------------------\n"));
							_ftprintf(file_txt, _T("%s\n"), strName.GetBuffer());
							g_myImgWorker.ClearOverflowFlag();

							if (g_myImgWorker.SetImgName(pres, FALSE))
							{
								g_myImgWorker.SetCSVParams(bCSV);
								g_myImgWorker.OutDir(0);
							}

							g_myImgWorker.CloseImage();
							OutProgress(strPath);
						}
					}
				}
			}
		}
		while (_tfindnext(hFile, &c_file) == 0);

		_findclose(hFile);

		if (!bCSV)
		{
			_ftprintf(file_lst, _T("\n\n"));
		}

		_ftprintf(file_txt, _T("\n\n"));

		//сперва выводим все имена в подкаталоге, а потом обработаем вложенные подкаталоги
		if (bRecursive)
		{
			while (!l_Dirs.empty())
			{
				ScanDir(l_Dirs.front());
				l_Dirs.pop_front();
			}
		}
		else
		{
			l_Dirs.clear();
		}
	}

	return TRUE;
}


