/*-- Include
*/
#include "PatchBackupMaker.h"
#include <stdio.h>
#include <time.h>
#include <io.h>
/*-- Local definitions
*/
#define SafeDelete(P)	{ if (P!=NULL) { delete(P); (P)=NULL; } }       // 메모리를 안전하게 해제.

#define WM_PATCH_MSG	WM_USER+0x10
#define PM_COMPLETE		0
#define PM_ERROR_XCOPY  1
#define PM_ERROR        2



/*-- Global data
*/
HINSTANCE   g_instance   = NULL;
Patchmaker* g_patchmaker = NULL;

/*-- WinMain Prototype
*/
int WINAPI WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdLine, int cmdShow)
{
	// Initialize COM Library - INTERNET API(wininet.h/wininet.lib)를 사용하기 위해 선언.
	if ( CoInitialize( NULL ) != S_OK )
	{
		MessageBox( NULL, "CoInitialize Failed!\r\n", "Patchmaker", MB_OK );
		return 0L;
	}

	Patchmaker* patchMaker = new Patchmaker( );
	HANDLE      handle     = CreateMutex( NULL, FALSE, patchMaker->GetWindowName( ) );
	int         result     = GetLastError( );

	g_instance = instance;
	if ( handle != NULL )
	{
		if ( result != ERROR_ALREADY_EXISTS )
			result = patchMaker->Run( instance, prevInstance, cmdLine, cmdShow );
		CloseHandle( handle );
	}

	SafeDelete( patchMaker );

	// Finished with COM Library
	CoUninitialize( );
	return result;
}

/*-- DlgProc Prototype
*/
LRESULT CALLBACK DlgProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	return g_patchmaker->MsgProc( wnd, msg, wParam, lParam );
}

/*-- WorkerThreadStartingPoint Prototype
*/
DWORD WINAPI WorkerThreadStartingPoint(void* ptr)
{
	return g_patchmaker->WorkerThread( );
}

/*-- Patchmaker Constructor
*/
Patchmaker::Patchmaker(void)
{
	g_patchmaker   = this;
	m_wnd          = NULL;
	m_icon         = NULL;
	m_windowName   = TEXT( "BackupMaker - IRIS ON LINE" );
	m_endApp       = false;
	m_isCopyMode   = false;  

	m_packlist.pack    = NULL;
	m_packlist.offset  = 0;
	m_packlist.length  = 0;

	m_patchlist.file   = NULL;
	m_patchlist.offset = 0;
	m_patchlist.length = 0;

	m_workerThread = NULL;
}

/*-- ~Patchmaker Destructor
*/
Patchmaker::~Patchmaker(void)
{
	g_patchmaker = NULL;
}

/*-- EventWrite Method
*/
void Patchmaker::EventWrite(LPCTSTR format, ...)
{
	LPVOID  msgBuf = NULL;
	DWORD   bufferLength;
	va_list args;

	va_start( args, format );

	bufferLength = _vscprintf( format, args ) +1;
	msgBuf       = malloc( bufferLength );
	vsprintf( (char*)msgBuf, format, args );

	va_end( args );

	time_t t;
	tm *pt;
	char time_array[ 1024 ];

	time(&t);
	pt=localtime(&t);

	sprintf( time_array, "%d/%d/%d %d:%d:%d \0",	pt->tm_year+1900, pt->tm_mon+1, pt->tm_mday, pt->tm_hour,pt->tm_min,pt->tm_sec );

	if ( msgBuf != NULL )
	{
		char filename[1024];
		sprintf( filename, "%s\\%s.log", m_currentDirectory, GetWindowName( ) );

		FILE* stream = fopen( filename, "at" );
		if ( stream != NULL )
		{
			fputs( time_array   , stream );
			fputs( (char*)msgBuf, stream );
			fclose( stream );
			stream = NULL;
		}

		free( msgBuf );
	}
}

/*-- GetFileTimeUTC Method
*/
bool Patchmaker::GetFileTimeUTC(char* filename, SYSTEMTIME* utc)
{
	HANDLE file = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if ( file != INVALID_HANDLE_VALUE ) 
	{
		FILETIME write;
		GetFileTime( file, NULL, NULL, &write );
		FileTimeToSystemTime( &write, utc );
		CloseHandle( file );
		return true;
	}
	return false;
}

/*-- SetFileTimeUTC Method
*/
bool Patchmaker::SetFileTimeUTC(char* filename, SYSTEMTIME* utc)
{
	HANDLE file = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if ( file != INVALID_HANDLE_VALUE ) 
	{
		FILETIME write;
		SystemTimeToFileTime( utc, &write );
		SetFileTime( file, NULL, NULL, &write );
		CloseHandle( file );
		return true;
	}
	return false;
}

/*-- Run Method
*/
int Patchmaker::Run(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdLine, int cmdShow)
{
	// 현재 폴더 저장.
	GetCurrentDirectory( sizeof(m_currentDirectory), m_currentDirectory );

	// 응용 프로그램, 다이얼로그 생성
	m_wnd = CreateDialog( instance, MAKEINTRESOURCE(IDD_PATCHMAKER), NULL, (DLGPROC)DlgProc );
	if ( m_wnd == NULL )
		return GetLastError( );

	// 윈도우 이름 입력
	SetWindowText( m_wnd, GetWindowName( ) );

	/*-- Command Line 처리.
	*/
	if ( CommandLine( cmdLine ) == true )
		StartPatch( );

	// 응용 프로그램, 다이얼로그 표시
	ShowWindow( m_wnd, cmdShow );

	// 메시지 루프 시작
	MSG  msg;
	BOOL result;

	while ( (result = GetMessage( &msg, NULL, 0, 0 )) )
	{
		if ( result == -1 )
		{
			// handle the error and possibly exit
			return GetLastError( );
		}
		else if ( !IsWindow( m_wnd ) || !IsDialogMessage( m_wnd, &msg ) )
		{
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
	}
	return (int)msg.wParam;
}

/*-- MsgProc Method
*/
LRESULT Patchmaker::MsgProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch ( msg )
	{
	case WM_DESTROY:    return Destroy    ( wnd, wParam, lParam );
	case WM_INITDIALOG: return InitDialog ( wnd, wParam, lParam );
	case WM_COMMAND:    return Command    ( wnd, wParam, lParam );
	case WM_PATCH_MSG:  return PatchMsg   ( wnd, wParam, lParam );
	}
	return 0L;
}

/*-- CommandLine Method
*/
bool Patchmaker::CommandLine(LPSTR cmdLine)
{
	if ( strlen( cmdLine ) > 0 )
	{
		char  mode[ 1024 ] = { 0, };
		char  path[ 1024 ] = "\0";
		char  seps[] = " ";
		bool  patch  = true;

		char* token;
		DWORD retcode;

		token = strtok( cmdLine, seps );
		if ( token != NULL )
			strcpy( mode, token );
		else
			memset( mode, 0, sizeof(mode) );

		if( stricmp(mode, "/c") == 0 )
		{
			m_isCopyMode = true;
			SetWindowText( GetDlgItem( m_wnd, IDC_BACKUP_MODE ), "FILE COPY" );
		}
		else if( stricmp(mode, "/m") == 0 )
		{
			SetWindowText( GetDlgItem( m_wnd, IDC_BACKUP_MODE ), "FILE MOVE" );
		}
		else 
			patch = false;

		token = strtok( NULL, seps );
		if ( token != NULL )
			strcpy( m_backupDirectory, token );
		else
			memset( m_backupDirectory, 0, sizeof(m_backupDirectory) );

		retcode = GetFileAttributes( m_backupDirectory );
		if ( (retcode != INVALID_FILE_ATTRIBUTES) && (retcode & FILE_ATTRIBUTE_DIRECTORY) )
			SetWindowText( GetDlgItem( m_wnd, IDC_SOURCE_DIR ), m_backupDirectory );
		else
			patch = false;

		token = strtok( NULL, seps );
		if ( token != NULL )
			strcpy( m_targetDirectory, token );
		else
			memset( m_targetDirectory, 0, sizeof(m_targetDirectory) );

		retcode = GetFileAttributes( m_targetDirectory );
		if ( (retcode != INVALID_FILE_ATTRIBUTES) && (retcode & FILE_ATTRIBUTE_DIRECTORY) )
			SetWindowText( GetDlgItem( m_wnd, IDC_TARGET_DIR ), m_targetDirectory );
		else
			patch = false;

		return patch;
	}
	return false;
}

/*-- SetStatus Method
*/
BOOL Patchmaker::SetStatus(HWND wnd, char* msg)
{
	HWND dlgItem = GetDlgItem( wnd, IDC_STATUS );
	BOOL retcode = SetWindowText( dlgItem, msg );
	UpdateWindow( dlgItem );
	return retcode;
}

/*-- Progress Method
*/
void Patchmaker::Progress(DWORD length, DWORD offset)
{
	if ( m_wnd != NULL )
	{
		int percent = (int)((float)offset / (float)length * 100.f);
		SendMessage( GetDlgItem( m_wnd, IDC_PROGRESS ), PBM_SETPOS, percent, 0 );
	}
}

/*-- Destroy Method
*/
LRESULT Patchmaker::Destroy(HWND wnd, WPARAM wParam, LPARAM lParam)
{
	// 안전종료.
	m_endApp = true;

	// m_workerThread 핸들를 해제.
	if ( m_workerThread != NULL )
	{
		// WorkerThread가 종료될때까지 대기.
		if ( WaitForSingleObject( m_workerThread, 5000 ) != WAIT_OBJECT_0 )
		{
			DWORD exitCode;
			GetExitCodeThread( m_workerThread, &exitCode );
			if ( exitCode == STILL_ACTIVE )
			{
				TerminateThread( m_workerThread, 0 );
			}
		}
		CloseHandle( m_workerThread );
		m_workerThread = NULL;
	}

	// ICON 해제
	if ( m_icon != NULL )
	{
		DestroyIcon( m_icon );
		m_icon = NULL;
	}

	// 윈도우 탐색기로 부터 Drag & Drop 종료.
	DragAcceptFiles( wnd, FALSE );

	// 응용 프로그램을 종료 한다.
	PostQuitMessage( 0 );
	return 0L;
}

/*-- InitDialog Method
*/
LRESULT Patchmaker::InitDialog(HWND wnd, WPARAM wParam, LPARAM lParam)
{
	// 윈도우 탐색기로 부터 Drag & Drop 시작.
	DragAcceptFiles( wnd, TRUE );

	// ICON 로드
	m_icon = LoadIcon( g_instance, MAKEINTRESOURCE( IDI_ICON ) );
	SendMessage( wnd, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)m_icon );

	// 상태.
	SetStatus( wnd, "Ready" );
	return 1L;
}

/*-- Command Method
*/
LRESULT Patchmaker::Command(HWND wnd, WPARAM wParam, LPARAM lParam)
{
	switch ( LOWORD(wParam) )
	{
	case IDOK:
		//DestroyWindow( wnd );
		break;
	case IDCANCEL:
		DestroyWindow( wnd );
		break;
	}
	return 0L;
}

/*-- PatchMsg Method
*/
LRESULT Patchmaker::PatchMsg(HWND wnd, WPARAM wParam, LPARAM lParam)
{
	switch ( wParam )
	{
	case PM_COMPLETE:
		MessageBox( wnd, "Success", GetWindowName( ), MB_OK );
		break;
	case PM_ERROR_XCOPY:
		MessageBox( wnd, "Error Xcopy", GetWindowName( ), MB_OK );
		break;
	case PM_ERROR:
		MessageBox( wnd, "Error", GetWindowName( ), MB_OK );
		break;
	default:
		MessageBox( wnd, "Unkonwn", GetWindowName( ), MB_OK );
		break;
	}
	PostMessage( m_wnd, WM_CLOSE, 0, 0 );
	return 0L;
}


/*-- Xcopy Method
*/
int Patchmaker::Xcopy(char* sub)
{
	int             retcode = 0;	 // 복사 성공.
	HANDLE          search  = NULL;
	char            where[ 1024 ];

	char            filename[ 1024 ];
	WIN32_FIND_DATA fileData;


	// 하위 디렉토리 적용여부
	if ( sub )
		sprintf( where, "%s\\%s", m_targetDirectory, sub );
	else
		sprintf( where, "%s", m_targetDirectory);

	sprintf( filename, "%s\\*.*", where );

	search = FindFirstFile( filename, &fileData );
	if ( search != INVALID_HANDLE_VALUE )
	{
		do {
			if ( fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
			{
				if ( (strcmp( fileData.cFileName, "."  ) != 0) && (strcmp( fileData.cFileName, ".." ) != 0) )
				{
					char directory[ 1024 ];

					char backup[ 1024 ];
					char target[ 1024 ];

					if ( sub )
					{
						sprintf( directory, "%s\\%s", sub, fileData.cFileName );
						sprintf( backup, "%s\\%s\\%s", m_backupDirectory, sub, fileData.cFileName );
						sprintf( target, "%s\\%s\\%s", m_targetDirectory, sub, fileData.cFileName );
					}
					else
					{
						sprintf( directory, "%s", fileData.cFileName );
						sprintf( backup, "%s\\%s", m_backupDirectory, fileData.cFileName );
						sprintf( target, "%s\\%s", m_targetDirectory, fileData.cFileName );
					}

					strlwr(backup);
					if ( GetFileAttributes( backup ) == INVALID_FILE_ATTRIBUTES ) 
					{
						if ( CreateDirectory( backup, NULL ) == FALSE )
						{
							if ( GetLastError( ) != ERROR_ALREADY_EXISTS )
							{
								FindClose( search );
								return -2; // 폴더 생성 실패.
							}
						}
					}
					
					// 재귀호출.
					retcode = Xcopy( directory );
				}
			}
			else if ( fileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE || fileData.dwFileAttributes & FILE_ATTRIBUTE_READONLY )
			{
				char backup[ 1024 ];
				char target[ 1024 ];
                
				if ( sub )
				{
					sprintf( backup, "%s\\%s\\%s", m_backupDirectory, sub, fileData.cFileName );
					sprintf( target, "%s\\%s\\%s", m_targetDirectory, sub, fileData.cFileName );
				}
				else
				{
					sprintf( backup, "%s\\%s", m_backupDirectory, fileData.cFileName );
					sprintf( target, "%s\\%s", m_targetDirectory, fileData.cFileName );
				}

				/// 파일 백업 유형 선택. 복사 / 이동
				if ( m_isCopyMode == true )
				{
					SYSTEMTIME utc;

					// CopyFile에서 확장. [12/11/2008 Jo]
					if ( CopyFileEx( target, backup, NULL, NULL, (LPBOOL)m_endApp, 0 ) == TRUE ) 
					{
						GetFileTimeUTC( target, &utc );
						SetFileTimeUTC( backup, &utc );
					}
				}
				else 
				{
					if( rename( target, backup ) != 0 )
					{
						retcode = -4; // 파일 이동 실패.
						ErrorPrint( "XCopy:Fail to change file attribute" )	;
					}
				}
			}
		} while ( FindNextFile( search, &fileData ) && retcode == 0 );

		FindClose( search );
		search = NULL;

		return retcode;
	}
	return -1; // 이동 실패
}

/*-- StartPatch Method
*/
bool Patchmaker::StartPatch( )
{
	DWORD threadId;
	m_workerThread = CreateThread( NULL, 0, WorkerThreadStartingPoint, (LPVOID)this, 0, &threadId );
	return (m_workerThread != NULL) ? true : false;
	//return WorkerThread( ) == 0;
}

/*-- WorkerThread Method
*/
DWORD Patchmaker::WorkerThread( )
{
	bool success = false;

	SetStatus( m_wnd, "Start Xcopy..." );

	if( CreateTodayBackupDirectory() == false )
	{
		ErrorPrint( "today backup directory create fail" );
		return 0;
	}

	if ( Xcopy( NULL ) != 0 )
	{
		PostMessage( m_wnd, WM_PATCH_MSG, (WPARAM)PM_ERROR_XCOPY, (LPARAM)0 );
		SetStatus( m_wnd, "Error Xcopy..." );
		return 0;
	}

	success = true;

	SetStatus( m_wnd, "End Xcopy..." );

	
	if ( success )
		PostMessage( m_wnd, WM_PATCH_MSG, (WPARAM)PM_COMPLETE, (LPARAM)0 );
	else
		PostMessage( m_wnd, WM_PATCH_MSG, (WPARAM)PM_ERROR, (LPARAM)0 );

	return 0;
}

void Patchmaker::ErrorPrint(char *msg)
{
	LPVOID lpMsgBuf;
	FormatMessage( 
		FORMAT_MESSAGE_ALLOCATE_BUFFER|
		FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPSTR)&lpMsgBuf, 0, NULL);
	MessageBox(m_wnd, (LPCSTR)lpMsgBuf, msg, MB_ICONERROR);
	LocalFree(lpMsgBuf);
}

bool Patchmaker::CreateTodayBackupDirectory()
{
	char todayDirectory[ 1024 ] = { 0, };
	char todaySubDirectory[ 1024 ] = { 0, };
	int  todayCounter = 0;

	time_t t;
	tm *pt;

	time(&t);
	pt=localtime(&t);
	
	sprintf( todaySubDirectory, "%04d-%02d-%02d [%02d]",	pt->tm_year+1900, pt->tm_mon+1, pt->tm_mday, todayCounter );
	sprintf( todayDirectory, "%s\\%s", m_backupDirectory, todaySubDirectory );

	while( access( todayDirectory, 0 ) == 0 )
	{
		++todayCounter;
		sprintf( todaySubDirectory, "%04d-%02d-%02d [%02d]\0",	pt->tm_year+1900, pt->tm_mon+1, pt->tm_mday, todayCounter );
		sprintf( todayDirectory, "%s\\%s\0", m_backupDirectory, todaySubDirectory );
	}

	if ( CreateDirectory( todayDirectory, NULL ) == FALSE )
		if ( GetLastError( ) != ERROR_ALREADY_EXISTS )
		{
			ErrorPrint( "today backup directory create fail" );
			return false;
		}

	strcpy( m_backupDirectory, todayDirectory );
	

	return true;
}