#include "stdafx.h"

#include <string>
#include <vector>

#define ARGS_NUM 5

enum RESULT
{
	SUCCESS = 0,
	ERROR_ILLEGALARG = -1,
	ERROR_FILENOTFOUND = -2,
	ERROR_LARGEFILE = -3,
	ERROR_FATAL = -10,
};

enum STEP
{
	STEP_FIND_PP = 0,
	STEP_GET_PP_NAME = 1,
	STEP_GET_IFP_FIRST = 2,
	STEP_GET_IFP_NEXT = 3,
};

static const wchar_t* CA_INPUTFILEPATH = L"/i";
static const wchar_t* CA_OUTPUTFILEPATH = L"/o";

BOOL AnsiToWide( const char* pSrc, std::wstring& dst )
{
	if( pSrc == NULL )
	{
		dst = L"";
		return TRUE;
	}

	int tempSize;
	wchar_t* temp;

	tempSize = ::MultiByteToWideChar( CP_THREAD_ACP, 0, pSrc, -1, NULL, 0 );
	if( tempSize > 0  )
	{
		tempSize += 1;
	}
	else
	{
		dst = L"";
		return FALSE;
	}

	temp = static_cast<wchar_t*>( ::HeapAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, ( tempSize << 1 ) ) );
	if( temp == NULL )
	{
		dst = L"";
		return FALSE;
	}

	if( ::MultiByteToWideChar( CP_THREAD_ACP, 0, pSrc, -1, temp, tempSize ) <= 0 )
	{
		::HeapFree( ::GetProcessHeap(), 0, temp );
		dst = L"";
		return FALSE;
	}

	dst = temp;
	::HeapFree( ::GetProcessHeap(), 0, temp );

	return TRUE;
}

int Process( const std::wstring& inputFilePath, const std::wstring& outputFilePath )
{
	DWORD fileNameStart;

	std::wstring inputDirPath;
	std::string includeFilePath;

	HANDLE hFile;
	DWORD fileSize;
	DWORD readSize;
	std::vector<char> buff;
	std::vector<char> incBuff;

	char* src;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 入力ディレクトリを取得
	////////////////////////////////////////////////////////////////////////////////////////////////////

	fileNameStart = inputFilePath.find_last_of( L'\\' );
	if( fileNameStart == std::wstring::npos )
	{
		fileNameStart = inputFilePath.find_last_of( L'/' );
	}

	if( fileNameStart != std::wstring::npos )
	{
		inputDirPath = inputFilePath.substr( 0, fileNameStart + 1 );
	}
	else
	{
		inputDirPath = L"";
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 入力ファイルをバッファにいれる
	////////////////////////////////////////////////////////////////////////////////////////////////////

	hFile = ::CreateFile( inputFilePath.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
	if( hFile == INVALID_HANDLE_VALUE )
	{
		return ERROR_FILENOTFOUND;
	}

	fileSize = ::GetFileSize( hFile, NULL );
	if( fileSize == 0xFFFFFFFF )
	{
		::CloseHandle( hFile );
		return ERROR_LARGEFILE;
	}

	buff.resize( fileSize );

	if( ::ReadFile( hFile, &( buff[0] ), fileSize, &readSize, NULL ) == FALSE )
	{
		::CloseHandle( hFile );
		return ERROR_FATAL;
	}

	::CloseHandle( hFile );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// インクルードを展開
	////////////////////////////////////////////////////////////////////////////////////////////////////

	DWORD step = STEP_FIND_PP;
	std::string ppName;
	DWORD writeStart = 0;
	DWORD writeSize;

	hFile = ::CreateFile( outputFilePath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
	if( hFile == INVALID_HANDLE_VALUE )
	{
		return ERROR_FATAL;
	}

	src = &( buff[0] );

	for( DWORD i = 0; i < fileSize; i++ )
	{
		char code = src[i];

		if( step == STEP_FIND_PP )
		{
			if( code == '#' )
			{
				if( ( i - writeStart ) > 0  )
				{
					if( ::WriteFile( hFile, &( buff[writeStart] ), ( i - writeStart ), &writeSize, NULL ) == FALSE )
					{
						::CloseHandle( hFile );
						return ERROR_FATAL;
					}
				}

				writeStart = i;

				ppName = "";
				step = STEP_GET_PP_NAME;
			}
		}
		else if( step == STEP_GET_PP_NAME )
		{
			if( ( code == ' ' ) ||
				( code == '\"' ) )
			{
				if( ppName == "include" )
				{
					if( code == '\"' )
					{
						i -= 1;
					}

					includeFilePath = "";

					step = STEP_GET_IFP_FIRST;
				}
				else
				{
					step = STEP_FIND_PP;
				}
			}
			else
			{
				ppName += code;
			}
		}
		else if( step == STEP_GET_IFP_FIRST )
		{
			if( code == '\"' )
			{
				step = STEP_GET_IFP_NEXT;
			}
		}
		else if( step == STEP_GET_IFP_NEXT )
		{
			if( code == '\"' )
			{
				std::wstring includeFilePathW;
				std::wstring incFilePath;
				DWORD incFileSize;

				if( AnsiToWide( includeFilePath.c_str(), includeFilePathW ) == TRUE )
				{
					incFilePath = inputDirPath + includeFilePathW;
				}
				else
				{
					::CloseHandle( hFile );
					return ERROR_FATAL;
				}

				HANDLE hIncFile = ::CreateFile( incFilePath.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
				if( hIncFile == INVALID_HANDLE_VALUE )
				{
					::CloseHandle( hFile );
					return ERROR_FATAL;
				}

				incFileSize = ::GetFileSize( hIncFile, NULL );
				if( incFileSize == 0xFFFFFFFF )
				{
					::CloseHandle( hIncFile );
					::CloseHandle( hFile );
					return ERROR_FATAL;
				}

				incBuff.resize( incFileSize );

				if( ::ReadFile( hIncFile, &( incBuff[0] ), incBuff.size(), &readSize, NULL ) == FALSE )
				{
					::CloseHandle( hIncFile );
					::CloseHandle( hFile );
					return ERROR_FATAL;
				}

				::CloseHandle( hIncFile );

				if( ::WriteFile( hFile, &( incBuff[0] ), incBuff.size(), &writeSize, NULL ) == FALSE )
				{
					::CloseHandle( hFile );
					return ERROR_FATAL;
				}

				writeStart = i + 1;

				step = STEP_FIND_PP;
			}
			else
			{
				includeFilePath += code;
			}
		}
	}

	if( writeStart != fileSize )
	{
		if( ::WriteFile( hFile, &( buff[writeStart] ), ( fileSize - writeStart ), &writeSize, NULL ) == FALSE )
		{
			::CloseHandle( hFile );
			return ERROR_FATAL;
		}
	}

	::CloseHandle( hFile );

	return SUCCESS;
}

int _tmain( int argc, _TCHAR* argv[] )
{
	if( ARGS_NUM != argc )
	{
		return ERROR_ILLEGALARG;
	}

	std::wstring inputFilePath;
	std::wstring outputFilePath;
	int ret;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// コマンド引数の解析
	////////////////////////////////////////////////////////////////////////////////////////////////////

	for( INT i = 1; i < argc; i++ )
	{
		if( ::wcscmp( argv[i], CA_INPUTFILEPATH ) == 0 )
		{
			inputFilePath = argv[i + 1];
			i++;
		}
		else if( ::wcscmp( argv[i], CA_OUTPUTFILEPATH ) == 0 )
		{
			outputFilePath = argv[i + 1];
			i++;
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// インクルードを展開
	////////////////////////////////////////////////////////////////////////////////////////////////////

	::wprintf_s( L"Including : \"%s\" -> \"%s\"...", inputFilePath.c_str(), outputFilePath.c_str() );

	ret = Process( inputFilePath, outputFilePath );
	if( ret == SUCCESS )
	{
		::wprintf_s( L"Success\n" );
	}
	else
	{
		::wprintf_s( L"Error(%d)\n", ret );
	}

	return ret;
}

