#include "stdafx.h"
#include "duplicator.h"
#include <vector>

#ifdef _DEBUG
	#include <crtdbg.h>
#endif //_DEBUG

////////////////////////////////////////////////////////////////////////////////////////////////////
// 定数
////////////////////////////////////////////////////////////////////////////////////////////////////

//リザルト列挙定数
enum DUP_RESULT
{
	DUP_OK								=  0,	//成功
	DUP_UNKNOWN_ARG						= -1,	//不明な引数
	DUP_DEFICIENCY_ARG					= -2,	//引数が不足( /sd /td は必ず必要 )
	DUP_SYNTAX_ERROR					= -3,	//構文エラー
	DUP_FATAL_ERROR						= -4,	//致命的なエラー
	DUP_OUT_OF_MEMORY					= -5,	//システムメモリが不足
	DUP__OPT__FILE_NOT_FOUND			= -6,	//OPT : ファイルが見つからない
	DUP__OPT__LARGE_FILE_SIZE			= -7,	//OPT : ファイルのサイズが大きすぎる
	DUP__OPT__READ_FILE_ERROR			= -8,	//OPT : ファイルの読み込みに失敗
	DUP__OPT__NOT_SUPPORT_FILE_FORMAT	= -9,	//OPT : サポートされないファイルフォーマット( UTF-16 限定 )
	DUP__OPT__CHAR_OVER					= -10,	//OPT : 文字数オーバー
	DUP__OPT__DIV_OVER					= -11,	//OPT : 区画オーバー
	DUP__OPT__SYNTAX_ERROR				= -12,	//OPT : 構文エラー
	DUP__OPT__UNKNOWN_PARAM				= -13,	//OPT : 不明なパラメータ
	DUP__OPT__ILLEGAL_PARAM_VALUE		= -13,	//OPT : パラメータの値の指定が間違っている
};

static const unsigned int DUP_PARSER_CHAR_MAX = 4096; //パーサーの最大文字数
static const unsigned int DUP_PARSER_DIV_MAX = 64;	//パーサーの最大区画数

const wchar_t* DUP_OEM_ILLEGAL_PARAM_VALUE = L"パラメータ \"%s\" の値の指定が間違っています。";

////////////////////////////////////////////////////////////////////////////////////////////////////
// 構造体
////////////////////////////////////////////////////////////////////////////////////////////////////

struct DUP_OPT_ERROR
{
	int lineNumber;
	wchar_t message[2048];
};

////////////////////////////////////////////////////////////////////////////////////////////////////
// マクロ
////////////////////////////////////////////////////////////////////////////////////////////////////

#define DUP_STR_CHECK( s0, s1 ) ( ::wcscmp( s0, s1 ) == 0 )

//プリント
#define DUP_PRINT( fmt, ... ) ::wprintf( fmt, __VA_ARGS__ )

//引数の処理 : 有無チェック
#define DUP_ARG_CONTINUE ( argIndex < argc )

//引数の処理 : 値の取得
#define DUP_ARG argv[argIndex]

//引数の処理 : 名前のチェック
#define DUP_ARG_CHECK( name ) ( ::wcscmp( DUP_ARG, name ) == 0 )

//引数の処理 : 値の設定を開始
#define DUP_ARG_SET_BEGIN \
	argIndex++; \
	if( argIndex < argc ) \
	{

//引数の処理 : 値の設定を終了
#define DUP_ARG_SET_END \
		argIndex++; \
	} \
	else \
	{ \
		result = DUP_SYNTAX_ERROR; \
	}

////////////////////////////////////////////////////////////////////////////////////////////////////
// パーサー
////////////////////////////////////////////////////////////////////////////////////////////////////

int OPT_Parse( _TCHAR* filePath, Duplicator* pDup, DUP_OPT_ERROR& err )
{
	HANDLE hFile = INVALID_HANDLE_VALUE;
	DWORD fileSize = 0;
	unsigned char* pBuffer = NULL;
	DWORD readSize = 0;
	WORD head = 0;

	////////////////////////////////////////////////////////////////////////////////////////////////////

	ZeroMemory( &err, sizeof( DUP_OPT_ERROR ) );

	////////////////////////////////////////////////////////////////////////////////////////////////////

	hFile = CreateFile( filePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
	if( hFile == INVALID_HANDLE_VALUE )
	{
		::wcscpy_s( err.message, sizeof( err.message ) >> 1, filePath );
		return DUP__OPT__FILE_NOT_FOUND;
	}

	fileSize = GetFileSize( hFile, NULL );
	if( fileSize == 0xFFFFFFFF )
	{
		::wcscpy_s( err.message, sizeof( err.message ) >> 1, filePath );
		CloseHandle( hFile );
		return DUP__OPT__LARGE_FILE_SIZE;
	}

	pBuffer = new unsigned char[fileSize];
	if( pBuffer == NULL )
	{
		CloseHandle( hFile );
		return DUP_OUT_OF_MEMORY;
	}

	if( ( ReadFile( hFile, pBuffer, fileSize, &readSize, NULL ) == FALSE ) ||
		( fileSize != readSize ) )
	{
		::wcscpy_s( err.message, sizeof( err.message ) >> 1, filePath );
		delete pBuffer;
		CloseHandle( hFile );
		return DUP__OPT__READ_FILE_ERROR;
	}

	CloseHandle( hFile );

	head = *( WORD* )pBuffer;
	if( head != 0xFEFF )
	{
		::wcscpy_s( err.message, sizeof( err.message ) >> 1, filePath );
		delete pBuffer;
		return DUP__OPT__NOT_SUPPORT_FILE_FORMAT;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////

	const wchar_t* pCode = reinterpret_cast<const wchar_t*>( pBuffer + 2 );
	const wchar_t* pCodeEnd = pCode + ( ( fileSize - 2 ) / 2 );

	wchar_t str[DUP_PARSER_CHAR_MAX] = { 0 };
	unsigned int charCount = 0;

	unsigned int div[DUP_PARSER_DIV_MAX] = { 0 };
	unsigned int divCount = 0;
	unsigned int divCharCount = 0;

	bool bParamValues = false;
	bool bParamValueLump = false;
	unsigned int lineNumber = 1;

	int result = DUP_OK;

	while( ( pCode != pCodeEnd ) && ( result == DUP_OK ) )
	{
		wchar_t code = *pCode;

		if( bParamValueLump == true )
		{
			if( code == L'\"' )
			{
				if( divCount < DUP_PARSER_DIV_MAX )
				{
					if( charCount < DUP_PARSER_CHAR_MAX )
					{
						str[charCount] = L'\0';
						charCount++;

						divCount = divCount + 1;
						divCharCount = 0;

						bParamValueLump = false;
					}
					else
					{
						//エラー : 文字数オーバー
						err.lineNumber = lineNumber;
						result = DUP__OPT__CHAR_OVER;
					}
				}
				else
				{
					//エラー : 区画オーバー
					err.lineNumber = lineNumber;
					result = DUP__OPT__DIV_OVER;
				}
			}
			else
			{
				if( charCount < DUP_PARSER_CHAR_MAX )
				{
					if( divCharCount == 0 )
					{
						div[divCount] = charCount;
					}

					str[charCount] = code;
					charCount++;

					divCharCount++;
				}
				else
				{
					//エラー : 文字数オーバー
					err.lineNumber = lineNumber;
					result = DUP__OPT__CHAR_OVER;
				}
			}
		}
		else
		{
			if( ( code == L' ' ) ||
				( code == L'\t' ) ||
				( code == L'\r' ) ||
				( code == L'\n' ) ||
				( code == L'\"' ) ||
				( code == L':' ) ||
				( code == L';' ) )
			{
				if( divCharCount > 0 )
				{
					if( divCount < DUP_PARSER_DIV_MAX )
					{
						if( charCount < DUP_PARSER_CHAR_MAX )
						{
							str[charCount] = L'\0';
							charCount++;

							divCount = divCount + 1;
							divCharCount = 0;
						}
						else
						{
							//エラー : 文字数オーバー
							err.lineNumber = lineNumber;
							result = DUP__OPT__CHAR_OVER;
						}
					}
					else
					{
						//エラー : 区画オーバー
						err.lineNumber = lineNumber;
						result = DUP__OPT__DIV_OVER;
					}
				}

				if( result == DUP_OK )
				{
					if( divCount == 2 )
					{
						if( bParamValues == false )
						{
							//エラー : 構文 : パラメータの値の前に ":" が無い
							err.lineNumber = lineNumber;
							wsprintf( err.message, L"\"%s\" の前に \":\" がありません。", &str[div[1]] );
							result = DUP__OPT__SYNTAX_ERROR;
						}
					}
				}

				if( result == DUP_OK )
				{
					if( code == L'\n' )
					{
						//行番号のカウント
						lineNumber++;
					}
					else if( code == L'\"' )
					{
						//パラメータの値のランプを開始
						bParamValueLump = true;
					}
					else if( code == L':' )
					{
						if( bParamValues == false )
						{
							//パラメータの値の取得を開始
							bParamValues = true;
						}
						else
						{
							//エラー : 構文
							err.lineNumber = lineNumber;
							wsprintf( err.message, L"\":\" はパラメータの名前と値を区切る目的として使用します。" );
							result = DUP__OPT__SYNTAX_ERROR;
						}
					}
					else if( code == L';' )
					{
						//区切り
						if( divCount >= 2 )
						{
							const wchar_t* pParamName = &( str[div[0]] );
							bool bParamValueOk = true;

							if( DUP_STR_CHECK( pParamName, L"ef" ) == true )
							{
								if( divCount == 2 )
								{
									pDup->AddExcludeFile( &str[div[1]], false );
								}
								else
								{
									bParamValueOk = false;
								}
							}
							else if( DUP_STR_CHECK( pParamName, L"efn" ) == true )
							{
								if( divCount == 2 )
								{
									pDup->AddExcludeFile( &str[div[1]], true );
								}
								else
								{
									bParamValueOk = false;
								}
							}
							else if( DUP_STR_CHECK( pParamName, L"efe" ) == true )
							{
								if( divCount == 2 )
								{
									pDup->AddExcludeFileExt( &str[div[1]] );
								}
								else
								{
									bParamValueOk = false;
								}
							}
							else if( DUP_STR_CHECK( pParamName, L"ed" ) == true )
							{
								if( divCount == 2 )
								{
									pDup->AddExcludeDirectory( &str[div[1]], false );
								}
								else
								{
									bParamValueOk = false;
								}
							}
							else if( DUP_STR_CHECK( pParamName, L"edf" ) == true )
							{
								if( divCount == 2 )
								{
									pDup->AddExcludeDirectory( &str[div[1]], true );
								}
								else
								{
									bParamValueOk = false;
								}
							}
							else
							{
								err.lineNumber = lineNumber;
								wsprintf( err.message, L"パラメータ \"%s\" は使用できません。", &str[div[0]] );
								result = DUP__OPT__UNKNOWN_PARAM;
							}

							if( bParamValueOk == true )
							{
								ZeroMemory( str, sizeof( str ) );
								charCount = 0;

								divCount = 0;
								divCharCount = 0;
								bParamValues = false;
							}
							else
							{
								err.lineNumber = lineNumber;
								wsprintf( err.message, DUP_OEM_ILLEGAL_PARAM_VALUE, &str[div[0]] );
								result = DUP__OPT__ILLEGAL_PARAM_VALUE;
							}
						}
						else
						{
							//エラー : 構文 : パラメータが値が指定されていない
							err.lineNumber = lineNumber;
							wsprintf( err.message, L"パラメータ \"%s\" の値が指定されていません。", &str[div[0]] );
							result = DUP__OPT__SYNTAX_ERROR;
						}
					}
				}
			}
			else
			{
				if( charCount < DUP_PARSER_CHAR_MAX )
				{
					if( divCharCount == 0 )
					{
						div[divCount] = charCount;
					}

					str[charCount] = code;
					charCount++;

					divCharCount++;
				}
				else
				{
					//エラー : 文字数オーバー
					err.lineNumber = lineNumber;
					result = DUP__OPT__CHAR_OVER;
				}
			}
		}

		pCode++;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////

	delete pBuffer;

	return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// メイン
////////////////////////////////////////////////////////////////////////////////////////////////////

int _tmain( int argc, _TCHAR* argv[] )
{
#ifdef _DEBUG
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif //_DEBUG

	DUP_OPT_ERROR optErr;
	int result = DUP_OK;

	_tsetlocale(LC_ALL, _T( "" ) );

	DUP_PRINT( L"----------------------------------------------------------------------\n" );
	DUP_PRINT( L" Duplicator\n" );

	if( argc > 1 )
	{
		Duplicator m_Duplicator;
		int argIndex = 1;

		while( DUP_ARG_CONTINUE )
		{
			if( DUP_ARG_CHECK( L"/sd" ) == true )
			{
				DUP_ARG_SET_BEGIN
					m_Duplicator.SetSourceDirectory( DUP_ARG );
				DUP_ARG_SET_END
			}
			else if( DUP_ARG_CHECK( L"/td" ) == true )
			{
				DUP_ARG_SET_BEGIN
					m_Duplicator.SetTargetDirectory( DUP_ARG );
				DUP_ARG_SET_END
			}
			else if( DUP_ARG_CHECK( L"/ef" ) == true )
			{
				DUP_ARG_SET_BEGIN
					m_Duplicator.AddExcludeFile( DUP_ARG, false );
				DUP_ARG_SET_END
			}
			else if( DUP_ARG_CHECK( L"/efn" ) == true )
			{
				DUP_ARG_SET_BEGIN
					m_Duplicator.AddExcludeFile( DUP_ARG, true );
				DUP_ARG_SET_END
			}
			else if( DUP_ARG_CHECK( L"/efe" ) == true )
			{
				DUP_ARG_SET_BEGIN
					m_Duplicator.AddExcludeFileExt( DUP_ARG );
				DUP_ARG_SET_END
			}
			else if( DUP_ARG_CHECK( L"/ed" ) == true )
			{
				DUP_ARG_SET_BEGIN
					m_Duplicator.AddExcludeDirectory( DUP_ARG, false );
				DUP_ARG_SET_END
			}
			else if( DUP_ARG_CHECK( L"/edf" ) == true )
			{
				DUP_ARG_SET_BEGIN
					m_Duplicator.AddExcludeDirectory( DUP_ARG, true );
				DUP_ARG_SET_END
			}
			else if( DUP_ARG_CHECK( L"/opt" ) == true )
			{
				DUP_ARG_SET_BEGIN
					result = OPT_Parse( DUP_ARG, &m_Duplicator, optErr );
				DUP_ARG_SET_END
			}
			else
			{
				result = DUP_UNKNOWN_ARG;
				break;
			}
		}

		if( result == DUP_OK )
		{
			if( m_Duplicator.IsAvailable() == true )
			{
				DUP_PRINT( L"   SourceDirectory : \"%s\"\n", m_Duplicator.GetSourceDirectory() );
				DUP_PRINT( L"   TargetDirectory : \"%s\"\n", m_Duplicator.GetTargetDirectory() );
				DUP_PRINT( L"----------------------------------------------------------------------\n" );

				if( m_Duplicator.Execute() == false )
				{
					result = DUP_FATAL_ERROR;
				}
			}
			else
			{
				result = DUP_DEFICIENCY_ARG;
			}
		}

		switch( result )
		{
		case DUP_UNKNOWN_ARG:
			DUP_PRINT( L" 引数 \"%s\" は使用できません。\n", argv[argIndex] );
			break;
		case DUP_DEFICIENCY_ARG:
			DUP_PRINT( L" 最低限必要な引数 /sd /td が定義されていません。\n" );
			break;
		case DUP_SYNTAX_ERROR:
			DUP_PRINT( L" 引数の指定が間違っています。\n" );
			break;
		case DUP_FATAL_ERROR:
			DUP_PRINT( L" 致命的なエラーが発生しました。\n" );
			break;
		case DUP_OUT_OF_MEMORY:
			DUP_PRINT( L" システムメモリが不足しています。\n" );
			break;
		case DUP__OPT__FILE_NOT_FOUND:
			DUP_PRINT( L"opt : ファイル \"%s\" が見つかりません。\n", optErr.message );
			break;
		case DUP__OPT__LARGE_FILE_SIZE:
			DUP_PRINT( L"opt : ファイル \"%s\" のサイズが大きすぎます。\n", optErr.message );
			break;
		case DUP__OPT__READ_FILE_ERROR:
			DUP_PRINT( L"opt : ファイル \"%s\" の読み込みに失敗しました。\n", optErr.message );
			break;
		case DUP__OPT__NOT_SUPPORT_FILE_FORMAT:
			DUP_PRINT( L"opt : ファイル \"%s\" のフォーマットが UTF-16 ではありません。\n", optErr.message );
			break;
		case DUP__OPT__CHAR_OVER:
			DUP_PRINT( L"opt(%d) : パラメータが総文字数( NULL文字含む )が%dを超えました。\n", optErr.lineNumber, DUP_PARSER_CHAR_MAX );
			break;
		case DUP__OPT__DIV_OVER:
			DUP_PRINT( L"opt(%d) : パラメータが総区画数が%dを超えました。\n", optErr.lineNumber, DUP_PARSER_DIV_MAX );
			break;
		case DUP__OPT__SYNTAX_ERROR:
		case DUP__OPT__UNKNOWN_PARAM:
			DUP_PRINT( L"opt(%d) : %s\n", optErr.lineNumber, optErr.message );
			break;
		}
	}
	else
	{
		DUP_PRINT( L" /sd 複製元のディレクトリのパス\n" );
		DUP_PRINT( L" /td 複製先のディレクトリのパス\n" );
		DUP_PRINT( L" /ef 除外するファイルのパス\n" );
		DUP_PRINT( L" /efn 除外するファイルの名前( 拡張子を含む )\n" );
		DUP_PRINT( L" /efe 除外するファイルの拡張子\n" );
		DUP_PRINT( L" /ed 除外するディレクトリのパス\n" );
		DUP_PRINT( L" /edf 除外するファイルを含むディレクトリのパス(ディレクトリは作成しますが、ファイルはコピーされません)\n" );
	}

	DUP_PRINT( L"----------------------------------------------------------------------\n" );

#ifdef _DEBUG
	::system( "pause" );
#endif //_DEBUG

	return result;
}

