#include "Mix/Tool/Win32/Archiver.h"

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

//#define MAF_DEBUG_LOG_WRITE

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

#define MAF_SET_EVENT( flags, bit )\
	{\
		if( ( flags & bit ) != bit )\
		{\
			flags |= bit;\
		}\
	}\

////////////////////////////////////////////////////////////////////////////////////////////////////
// 関数
////////////////////////////////////////////////////////////////////////////////////////////////////

MAF_RESULT MAF_CollectFile_Phase1( const wchar_t* pDirectoryName, const unsigned int filePathStart, MAF_CONTEXT& context, MAF_CONTEXT_WRITE& contextWrite )
{
	::WIN32_FIND_DATAW wfd;
	::HANDLE hFind;
	std::wstring directoryName;
	std::wstring searchPath;
	std::wstring temp1;
	std::wstring temp2;

	::ZeroMemory( &wfd, sizeof( wfd ) );

	directoryName = pDirectoryName;
	if( ( pDirectoryName[::wcslen( pDirectoryName ) - 1] != '\\' ) &&
		( pDirectoryName[::wcslen( pDirectoryName ) - 1] != '/' ) )
	{
		directoryName += L'\\';
	}

	searchPath = directoryName + L"*.*";

	hFind = ::FindFirstFileW( searchPath.c_str(), &wfd );
	if( hFind != INVALID_HANDLE_VALUE )
	{
		do
		{
			if( ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) == FILE_ATTRIBUTE_DIRECTORY )
			{
				//ディレクトリ

				if( ( ::wcscmp( wfd.cFileName, L"." ) != 0 ) &&
					( ::wcscmp( wfd.cFileName, L".." ) != 0 ) )
				{
					MAF_RESULT result;

					temp1 = directoryName;
					temp1 += wfd.cFileName;
					temp1 += L'\\';

					result = MAF_CollectFile_Phase1( temp1.c_str(), filePathStart, context, contextWrite );
					if( result != MAF_OK )
					{
						::FindClose( hFind );
						return result;
					}
				}
			}
			else
			{
				unsigned int namePos;
				MAF_FILE file;

				temp1 = ( directoryName + wfd.cFileName );
				temp2 = &( temp1[filePathStart] );

				namePos = context.stringBuffer.size();
				context.stringBuffer.resize( namePos + temp2.size() + 1 );
				::CopyMemory( &( context.stringBuffer[namePos] ), temp2.c_str(), ( temp2.size() << 1 ) );
				context.stringBuffer[context.stringBuffer.size() - 1] = L'\0';

				file.namePos = namePos;
				file.dataPos = 0;
				file.dataSize = 0;
				file.reserve = 0;

				context.fileList.push_back( file );
				contextWrite.srcFileNameList.push_back( temp1 );
			}
		}
		while( ::FindNextFileW( hFind, &wfd ) != NULL );

		::FindClose( hFind );

		if( ::GetLastError() != ERROR_NO_MORE_FILES )
		{
			//ディレクトリの検索中にエラーが発生
			return MAF_ERROR_FATAL;
		}
	}
	else
	{
		return MAF_ERROR_FATAL;
	}

	return MAF_OK;
}

MAF_RESULT MAF_CollectFile_Phase2( MAF_CONTEXT_WRITE& contextWrite, MAF_CONTEXT& context )
{
	unsigned int dataPos = 0;
	std::vector<MAF_FILE>::iterator it_file_begin = context.fileList.begin();
	std::vector<MAF_FILE>::iterator it_file_end = context.fileList.end();
	std::vector<std::wstring>::const_iterator it_src = contextWrite.srcFileNameList.begin();

	for( std::vector<MAF_FILE>::iterator it_file = it_file_begin; it_file != it_file_end; ++it_file, ++it_src )
	{
		MAF_FILE& file = ( *it_file );

		HANDLE hFile;
		LARGE_INTEGER fileSize64;
		unsigned int dataSize;
		unsigned int dataAlignmentSize;

		hFile = ::CreateFileW( ( *it_src ).c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
		if( hFile == INVALID_HANDLE_VALUE )
		{
			return MAF_ERROR_IO_COLLECTFILE;
		}

		if( ::GetFileSizeEx( hFile, &fileSize64 ) == TRUE )
		{
			if( fileSize64.QuadPart <= MAF_MAX_FILESIZE )
			{
				dataSize = static_cast<unsigned int>( fileSize64.QuadPart );
			}
			else
			{
				::CloseHandle( hFile );
				return MAF_ERROR_IO_COLLECTFILE_BIGFILE;
			}
		}
		else
		{
			::CloseHandle( hFile );
			return MAF_ERROR_IO_COLLECTFILE;
		}

		::CloseHandle( hFile );

		file.dataPos = dataPos;
		file.dataSize = dataSize;

		dataAlignmentSize = ( dataSize / context.alignment );
		if( dataSize % context.alignment )
		{
			dataAlignmentSize++;
		}

		dataPos += dataAlignmentSize;

		context.totalFileSize = dataPos;
		contextWrite.totalDataSize += dataSize;
		contextWrite.totalDataAlignmentSize += dataAlignmentSize;
/*
		wchar_t temp[1024];
		::wsprintf( temp, L"%s : name[%s] pos[%d] size[%d]\n", ( *it_src ).c_str(), &( context.stringBuffer[file.namePos] ), file.dataPos, file.dataSize );
		::OutputDebugStringW( temp );
*/
	}

	context.totalSize = ( sizeof( MAF_HEADER ) + context.totalFileSize + ( sizeof( MAF_FILE ) * context.fileList.size() ) + ( sizeof( wchar_t ) * context.stringBuffer.size() ) );

	return MAF_OK;
}

MAF_RESULT MAF_InitializeContext(	const wchar_t* pRootDirectoryName,
									const wchar_t* pInputDirectoryName,
									const wchar_t* pOutputFileName,
									unsigned int alignment,
									unsigned int tempBuffSize,
									MAF_CONTEXT& context,
									MAF_CONTEXT_WRITE& contextWrite )
{
	if( ( pRootDirectoryName == NULL ) ||
		( ::wcslen( pRootDirectoryName ) == 0 ) ||
		( pInputDirectoryName == NULL ) ||
		( ::wcslen( pInputDirectoryName ) == 0 ) ||
		( pOutputFileName == NULL ) ||
		( ::wcslen( pOutputFileName ) == 0 ) ||
		( alignment == 0 ) ||
		( tempBuffSize != 0 ) && ( alignment > tempBuffSize ) ||
		( tempBuffSize == 0 ) && ( alignment > MAF_DEF_TEMPBUFFSIZE ) )
	{
		return MAF_ERROR_INVALID_ARGUMENT;
	}

	MAF_RESULT result;
	unsigned int filePathStart = 0;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// コンテキストの初期化 1
	////////////////////////////////////////////////////////////////////////////////////////////////////

	context.initialized = false;
	context.alignment = 0;
	context.fileList.clear();
	context.stringBuffer.clear();
	context.totalFileSize = 0;
	context.totalSize = 0;

	contextWrite.initialized = false;
	contextWrite.srcFileNameList.clear();
	contextWrite.totalDataAlignmentSize = 0;
	contextWrite.totalDataSize = 0;
	contextWrite.outputDataSize = 0;
	contextWrite.tempBuff = NULL;
	contextWrite.tempBuffSize = 0;
	contextWrite.outputProgress = 0;
	contextWrite.outputHandle = INVALID_HANDLE_VALUE;
	contextWrite.fileIndex = 0;
	contextWrite.fileSize = 0;
	contextWrite.fileProgress = 0;
	contextWrite.fileHandle = INVALID_HANDLE_VALUE;
	contextWrite.eventFlags = ( MAF_EVENT_OUTPUTPROGRESS | MAF_EVENT_FILEINDEX | MAF_EVENT_FILEPROGRESS );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// アーカイブするファイルパスからルートディレクトリのパスを削り取る位置を取得
	////////////////////////////////////////////////////////////////////////////////////////////////////

	{
		int i;

		//入力ディレクトリがルートディレクトリに含まれているかどうかをチェック

		if( ::wcscmp( pRootDirectoryName, pInputDirectoryName ) == 0 )
		{
			return MAF_ERROR_ILLEGAL_DIRECTORYNAME;
		}

		for( i = 0; pRootDirectoryName[i] != L'\0'; i++ )
		{
			if( pInputDirectoryName[i] != pRootDirectoryName[i] )
			{
				return MAF_ERROR_ILLEGAL_DIRECTORYNAME;
			}
		}

		filePathStart = ::wcslen( pRootDirectoryName );
		if( ( pRootDirectoryName[::wcslen( pRootDirectoryName ) - 1] != L'\\' ) &&
			( pRootDirectoryName[::wcslen( pRootDirectoryName ) - 1] != L'/') )
		{
			filePathStart++;
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ファイル情報を収集
	////////////////////////////////////////////////////////////////////////////////////////////////////

	context.alignment = alignment;

	result = MAF_CollectFile_Phase1( pInputDirectoryName, filePathStart, context, contextWrite );
	if( result != MAF_OK )
	{
		return result;
	}

	result = MAF_CollectFile_Phase2( contextWrite, context );
	if( result != MAF_OK )
	{
		return result;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// コンテキストの初期化 2
	////////////////////////////////////////////////////////////////////////////////////////////////////

	contextWrite.tempBuffSize = ( tempBuffSize == 0 )? MAF_DEF_TEMPBUFFSIZE : tempBuffSize;
	contextWrite.tempBuff = static_cast<unsigned char*>( ::malloc( contextWrite.tempBuffSize ) );
	::ZeroMemory( contextWrite.tempBuff, contextWrite.tempBuffSize );

	contextWrite.outputHandle = ::CreateFile( pOutputFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
	if( contextWrite.outputHandle == INVALID_HANDLE_VALUE )
	{
		return MAF_ERROR_IO_CREATEOUTPUTFILE;
	}

	context.initialized = true;
	contextWrite.initialized = true;

	return MAF_OK;
}

unsigned int MAF_TakeEvent( MAF_CONTEXT_WRITE& contextWrite )
{
	unsigned int flags = contextWrite.eventFlags;

	contextWrite.eventFlags = 0;

	return flags;
}

const wchar_t* MAF_GetFileName( const MAF_CONTEXT& context, const MAF_CONTEXT_WRITE& contextWrite )
{
	if( ( context.initialized == false ) ||
		( contextWrite.initialized == false ) ||
		( context.fileList.size() <= contextWrite.fileIndex ) )
	{
		return NULL;
	}

	return &( context.stringBuffer[context.fileList[contextWrite.fileIndex].namePos] );
}

MAF_RESULT MAF_BeginWrite( const MAF_CONTEXT& context, MAF_CONTEXT_WRITE& contextWrite )
{
	if( ( context.initialized == false ) ||
		( contextWrite.initialized == false ) )
	{
		return MAF_ERROR_FATAL;
	}

	MAF_HEADER header;
	DWORD writtenSize;

	header.magicNumber = MAF_HEADER_MAGICNUMBER;
	header.version = MAF_HEADER_VERSION;
	header.alignment = context.alignment;
	header.fileCount = context.fileList.size();
	header.stringSize = context.stringBuffer.size();
	header.reserve = 0;
	header.fileStart = ( sizeof( MAF_HEADER ) + ( contextWrite.totalDataAlignmentSize * context.alignment ) );
	header.stringStart = ( header.fileStart + ( sizeof( MAF_FILE ) * context.fileList.size() ) );

	writtenSize = 0;
	if( ::WriteFile( contextWrite.outputHandle, &header, sizeof( header ), &writtenSize, NULL ) == FALSE )
	{
		return MAF_ERROR_IO_WRITEOUTPUTFILE;
	}

	return MAF_OK;
}

MAF_RESULT MAF_ProcWrite( const MAF_CONTEXT& context, MAF_CONTEXT_WRITE& contextWrite )
{
	if( ( context.initialized == false ) ||
		( contextWrite.initialized == false ) )
	{
		return MAF_ERROR_FATAL;
	}

	if( contextWrite.fileHandle == INVALID_HANDLE_VALUE )
	{
		const MAF_FILE& file = context.fileList[contextWrite.fileIndex];
		const std::wstring& fileName = contextWrite.srcFileNameList[contextWrite.fileIndex];

		contextWrite.fileHandle = ::CreateFile( fileName.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
		if( contextWrite.fileHandle == INVALID_HANDLE_VALUE )
		{
			return MAF_ERROR_IO_WRITEOUTPUTFILE;
		}

		contextWrite.fileSize = file.dataSize;
		contextWrite.fileProgress = 0;

		MAF_SET_EVENT( contextWrite.eventFlags, MAF_EVENT_FILEPROGRESS );

#ifdef MAF_DEBUG_LOG_WRITE
		::OutputDebugStringW( &( context.stringBuffer[context.fileList[contextWrite.fileIndex].namePos] ) );
		::OutputDebugStringW( L"" );
#endif //MAF_DEBUG_LOG_WRITE
	}
	else
	{
		if( contextWrite.fileSize == 0 )
		{
			::CloseHandle( contextWrite.fileHandle );
			contextWrite.fileHandle = INVALID_HANDLE_VALUE;

#ifdef MAF_DEBUG_LOG_WRITE
			::OutputDebugStringW( L"Success\n" );
#endif //MAF_DEBUG_LOG_WRITE

			contextWrite.fileIndex++;
			if( context.fileList.size() > contextWrite.fileIndex )
			{
				MAF_SET_EVENT( contextWrite.eventFlags, MAF_EVENT_FILEINDEX );
			}
			else
			{
#ifdef MAF_DEBUG_LOG_WRITE
				::OutputDebugStringW( L"Finish\n" );
#endif //MAF_DEBUG_LOG_WRITE
				return MAF_OK;
			}
		}
		else
		{
			const MAF_FILE& file = context.fileList[contextWrite.fileIndex];
			const std::wstring& fileName = contextWrite.srcFileNameList[contextWrite.fileIndex];
			DWORD readSize = 0;
			DWORD writeSize = 0;
			DWORD size = ( contextWrite.tempBuffSize < contextWrite.fileSize )? contextWrite.tempBuffSize : contextWrite.fileSize;
			unsigned int nextFileProgress = 0;
			unsigned int nextOutputProgress = 0;

			if( ( ::ReadFile( contextWrite.fileHandle, contextWrite.tempBuff, size, &readSize, NULL ) == FALSE ) ||
				( size != readSize ) )
			{
#ifdef MAF_DEBUG_LOG_WRITE
				::OutputDebugStringW( L"Error\n" );
#endif //MAF_DEBUG_LOG_WRITE
				return MAF_ERROR_IO_READSOURCEFILE;
			}

			if( ( ::WriteFile( contextWrite.outputHandle, contextWrite.tempBuff, size, &writeSize, NULL ) == FALSE ) ||
				( size != writeSize ) )
			{
#ifdef MAF_DEBUG_LOG_WRITE
				::OutputDebugStringW( L"Error\n" );
#endif //MAF_DEBUG_LOG_WRITE
				return MAF_ERROR_IO_WRITEOUTPUTFILE;
			}

			if( ( contextWrite.fileSize - size ) == 0 )
			{
				DWORD gapSize;

				gapSize = ( file.dataSize / context.alignment );
				if( ( file.dataSize % context.alignment ) != 0 )
				{
					gapSize++;
				}

				gapSize = ( ( gapSize * context.alignment ) - file.dataSize );
				if( gapSize > 0 )
				{
					::ZeroMemory( contextWrite.tempBuff, gapSize );

					writeSize = 0;
					if( ::WriteFile( contextWrite.outputHandle, contextWrite.tempBuff, gapSize, &writeSize, NULL ) == FALSE )
					{
#ifdef MAF_DEBUG_LOG_WRITE
						::OutputDebugStringW( L"Error\n" );
#endif //MAF_DEBUG_LOG_WRITE
						return MAF_ERROR_IO_WRITEOUTPUTFILE;
					}
				}
			}

			contextWrite.fileSize -= size;
			contextWrite.outputDataSize += size;

			nextFileProgress = ( 100 - ( ( contextWrite.fileSize * 100 ) / file.dataSize ) );
			if( nextFileProgress != contextWrite.fileProgress )
			{
				contextWrite.fileProgress = nextFileProgress;
				MAF_SET_EVENT( contextWrite.eventFlags, MAF_EVENT_FILEPROGRESS );
			}

			nextOutputProgress = static_cast<unsigned int>( ( contextWrite.outputDataSize * 100 / contextWrite.totalDataSize ) );
			if( nextOutputProgress != contextWrite.outputProgress )
			{
				contextWrite.outputProgress = nextOutputProgress;
				MAF_SET_EVENT( contextWrite.eventFlags, MAF_EVENT_OUTPUTPROGRESS );
			}

#ifdef MAF_DEBUG_LOG_WRITE
			::OutputDebugStringW( L"." );
#endif //MAF_DEBUG_LOG_WRITE
		}
	}

	return MAF_CONTINUE;
}

MAF_RESULT MAF_EndWrite( const MAF_CONTEXT& context, MAF_CONTEXT_WRITE& contextWrite )
{
	if( ( context.initialized == false ) ||
		( contextWrite.initialized == false ) )
	{
		return MAF_ERROR_FATAL;
	}

	DWORD writtenSize;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ファイル情報を書き込む
	////////////////////////////////////////////////////////////////////////////////////////////////////

	writtenSize = 0;
	if( ::WriteFile( contextWrite.outputHandle, &( context.fileList[0] ), ( sizeof( MAF_FILE ) * context.fileList.size() ), &writtenSize, NULL ) == FALSE )
	{
		return MAF_ERROR_IO_WRITEOUTPUTFILE;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 文字列バッファを書き込む
	////////////////////////////////////////////////////////////////////////////////////////////////////

	writtenSize = 0;
	if( ::WriteFile( contextWrite.outputHandle, &( context.stringBuffer[0] ), ( sizeof( wchar_t ) * context.stringBuffer.size() ), &writtenSize, NULL ) == FALSE )
	{
		return MAF_ERROR_IO_WRITEOUTPUTFILE;
	}

	return MAF_OK;
}

void MAF_ReleaseContext( MAF_CONTEXT& context, MAF_CONTEXT_WRITE& contextWrite )
{
	context.alignment = 0;
	context.fileList.clear();
	context.stringBuffer.clear();
	context.initialized = false;

	if( contextWrite.tempBuff != NULL )
	{
		::free( contextWrite.tempBuff );
		contextWrite.tempBuff = NULL;
	}

	contextWrite.srcFileNameList.clear();
	contextWrite.totalDataSize = 0;
	contextWrite.outputDataSize = 0;

	contextWrite.outputProgress = 0;

	if( contextWrite.outputHandle != INVALID_HANDLE_VALUE )
	{
		::CloseHandle( contextWrite.outputHandle );
		contextWrite.outputHandle = INVALID_HANDLE_VALUE;
	}

	contextWrite.fileIndex = 0;
	contextWrite.fileSize = 0;
	contextWrite.fileProgress = 0;

	if( contextWrite.fileHandle != INVALID_HANDLE_VALUE )
	{
		::CloseHandle( contextWrite.fileHandle );
		contextWrite.fileHandle = INVALID_HANDLE_VALUE;

		contextWrite.tempBuffSize = 0;
	}

	contextWrite.eventFlags = 0;

	contextWrite.initialized = false;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 定数、マクロの解除
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

#undef MAF_SET_EVENT
