#include"Install.h"
#include"../../Maid/Auxiliary/XML/CXMLWriter.h"
#include"../../Maid/Auxiliary/FileIO/CFileRead.h"
#include"../../Maid/Auxiliary/FileIO/CFileWrite.h"
#include"../../Maid/Auxiliary/Windows/CShell.h"
#include"../../Maid/Auxiliary/Windows/CRegistry.h"
#include"../../Maid/Auxiliary/CString.h"
#include"resource.h"
#include<io.h>
#include<direct.h>
#include<set>
#include"md5.h"
#include"../MaidUninstaller/SetupGlobal.h"

using namespace Maid;

/*!
 *	\class	Install Install.h
 *	\brief	インストールを行うクラス
\n			インストール自体は別スレッドで行っているので、メインスレッドから常時監視する形になります
 */

static const unt32 COPY_PACET = 1024*256;	//	一回のコピーで実行されるファイルサイズ
static const unt32 MD5CHECKSIZE = 1024*256;	//	一回のコピーで実行されるファイルサイズ
static unt08 COPYBUF[COPY_PACET];		//	パケットコピーで一時的に使われるバッファ
										//	MD5 のチェック時にも使われる






/*	全体処理の進み具合の目安

	・インストール設定の作成(00%-05%)
	・ファイルのコピー      (05%-90%)
	・アンインストール設定  (90%-95%)
	・レジストリの書き込み  (95%-100%)
*/
static const int TOTALPROGRESS_SETUPBEGIN = 0;
static const int TOTALPROGRESS_SETUPEND   = 5;
static const int TOTALPROGRESS_FILECOPYBEGIN  = TOTALPROGRESS_SETUPEND;
static const int TOTALPROGRESS_FILECOPYEND    = 90;
static const int TOTALPROGRESS_SHORTCUTBEGIN = TOTALPROGRESS_FILECOPYEND;
static const int TOTALPROGRESS_SHORTCUTEND   = 93;
static const int TOTALPROGRESS_REGISTORYBEGIN = TOTALPROGRESS_SHORTCUTBEGIN;
static const int TOTALPROGRESS_REGISTORYEND   = 96;
static const int TOTALPROGRESS_UNINSTALLBEGIN = TOTALPROGRESS_REGISTORYBEGIN;
static const int TOTALPROGRESS_UNINSTALLEND   = 100;

static const int TOTALPROGRESS_FINAL = TOTALPROGRESS_UNINSTALLEND;


float PER( int p )
{
	return float(p)/float(TOTALPROGRESS_FINAL);
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! インストールの開始
/*!
 *	この関数が成功したら GetStatus() で状態を見つつ 
\n	STATE_SUCCESS	がくると正常終了
\n	STATE_ERROR		がくると失敗して終了
 *
 *	\param	InstallData	[i ]	インストール設定
 *
 *	\return インストールが開始されると DENRESULT_OK
\n			されなかったら DENRESULT_OK 以外
 */
void Install::BeginInstall( const Install::INSTALLDATA& InstallData )
{
	m_NowState.Status = STATUS::THREADSTATUS_EXECUTING;

	m_InstallData = InstallData;

	m_Thread.SetFunc( MakeThreadObject( &Install::InstallThread, this) );
	m_Thread.Execute();
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! 現在の状態を取得する
/*!
 *	BeginInstall() が成功していること
 *
 *	\return 現在のインストール状況
\n			詳しくはヘッダ参照
 */
Install::STATUS Install::GetStatus()
{
	STATUS s;
	{
		CThreadMutexLocker Lock(m_Section);

		s = m_NowState;
	}

	return s;
}


void	Install::SetProgressText( const mstring& tex )
{
	CThreadMutexLocker Lock(m_Section);
	m_NowState.ProgressText = tex;
}

void	Install::SetNowProgress( float v )
{
	CThreadMutexLocker Lock(m_Section);
	m_NowState.NowProgress = v;
}

void	Install::SetTotalProgress( float v )
{
	CThreadMutexLocker Lock(m_Section);
	m_NowState.TotalProgress = v;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! インストールを中断する
/*!
 *	BeginInstall() が成功していること
 */
void   Install::OnCancel()
{
	m_Thread.Close();
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! 実際にインストールを行うスレッド
/*!
 *	\param	Brige	[i ]	渡されるデータ
 */
unt32	Install::InstallThread( volatile CThreadController::BRIGEDATA& Brige )
{
	CShell::CoInitialize();

	INSTALLDATA& Data = m_InstallData;


	const CSetupConfig::INSTALLTYPE Type = Data.pConfig->GetInstallType( Data.InstallTypeNo );


	{
		m_ExecutingInfo.TotalFileSize = 0.0f;
		m_ExecutingInfo.AccessFileSize = 0.0f;

		SetProgressText ( mstring() );
		SetNowProgress  ( 0 );
		SetTotalProgress( 0 );
	}


	{
		//	・インストール設定の作成(00%-05%)
		SetProgressText( MAIDTEXT("インストールするための情報を集めています") );
		SetTotalProgress( (float)TOTALPROGRESS_SETUPBEGIN );

		SetTotalProgress( PER(TOTALPROGRESS_SETUPEND) );
	}


	CXMLWriter		InstallLog;	//	インストールログ

	InstallLog.AscendNode( MAIDTEXT("UninstallInfo") );

	const mstring UninstallerPath = Data.pConfig->GetWindowsFolder() + MAIDTEXT("\\") + MAIDTEXT(s_UNINSTALLER_NAME);
	const mstring UninstallLogFileName = Data.pConfig->GetInstallFolder() + MAIDTEXT("\\") + MAIDTEXT(s_UNINSTALLERINFONAME);


	{
		//	・ファイルのコピー & MD5チェック     (05%-90%)
		SetProgressText( MAIDTEXT("インストールを開始します") );
		SetTotalProgress( PER(TOTALPROGRESS_FILECOPYBEGIN) );

		for( int i=0; i<(int)Type.CopyFileList.size(); ++i )
		{
			{
				const float per = float(i) / float(Type.CopyFileList.size());
				const int len = TOTALPROGRESS_FILECOPYEND-TOTALPROGRESS_FILECOPYBEGIN;

				SetTotalProgress( (float(len)*per + float(TOTALPROGRESS_SETUPEND))/float(TOTALPROGRESS_FINAL) );
			}

			const CSetupConfig::INSTALLTYPE::COPYFILE dat = Type.CopyFileList[i];

			//	ファイルが存在していなかったらディスク交換を要求する
			while( true )
			{
				if( CFileRead::IsExist(dat.SrcPath) ) { break; }

				char buf[256];
				sprintf( buf, "ディスク %d をドライブに入れてください", dat.DiskNo+1 );

				const int select = ::MessageBox( Data.hWnd, buf, "ディスクの交換", MB_OKCANCEL );

				if( select==IDOK )
				{
					continue;
				}else
				{	//	ＯＫが押されなかったらキャンセル扱いで終わる
					InstallCancel();
					return 0;
				}
			}


			while( true )
			{
				//	コピーの開始
				switch( FileCopy( dat.DstPath, dat.SrcPath, Brige ) )
				{
				case FUNCTIONRESULT_OK: { }break;
				case FUNCTIONRESULT_CANCEL: { InstallCancel(); return 0;}break;
				default: { return 2; }break;
				}

				//	MD5 チェック
				if( m_InstallData.IsMD5Check )
				{
					switch( CheckMD5( dat.DstPath, CString::ToLower(dat.MD5), Brige ) )
					{
					case FUNCTIONRESULT_OK: { }break;
					case FUNCTIONRESULT_CANCEL: { InstallCancel(); return 0;}break;
					case FUNCTIONRESULT_ERROR:
						{
							const char* pText = "コピーしたファイルが破損しています\nもう一度インストールしますか？";
							const int select = ::MessageBox( m_InstallData.hWnd, pText, "MD5エラー", MB_OKCANCEL );
							
							//	ＯＫが押されなかったらキャンセル扱いで終わる
							if( select==IDOK )	{ continue; }
							else				{ InstallCancel();	return 0;	}
						}break;

					default: { return 3; }break;
					}
				}
				//	ここまでくればＯＫ
				break;
			}

			//	コピーしたファイルのログを残す
			InstallLog.SetElementName( MAIDTEXT(s_UNINSTALLER_COPYFILE) );
			InstallLog.SetValueText( dat.DstPath );
			InstallLog.NextNode();
		}

		{
	
			//	アンインストーラーのコピー

			HRSRC hResource = FindResource( NULL, MAKEINTRESOURCE(IDR_UNINSTALL1), "uninstall" );

			MAID_ASSERT( hResource==NULL, MAIDTEXT("uninstall.exe が見つかりませんでした") );
			const DWORD ExeSize = SizeofResource( NULL,hResource);

			HGLOBAL hGlobal = LoadResource( NULL, hResource );

			void* pExeImage = LockResource( hGlobal );

			CFileWrite::Write( Data.pConfig->GetWindowsFolder() + MAIDTEXT("\\") + MAIDTEXT(s_UNINSTALLER_NAME), pExeImage, ExeSize );

			FreeResource( hGlobal );
	
		}

		SetTotalProgress( PER(TOTALPROGRESS_FILECOPYEND) );
	}



	{

		//	ショートカットの作成
		SetProgressText( MAIDTEXT("ショートカットを作成しています") );
		SetTotalProgress( PER(TOTALPROGRESS_SHORTCUTBEGIN) );

		for( int i=0; i<(int)Type.ShortcutList.size(); ++i )
		{
			const CSetupConfig::INSTALLTYPE::SHORTCUT dat = Type.ShortcutList[i];
			const mstring dir = CString::GetDirectory(dat.CreatePath);


			CShell::CreateDirectory( dir, m_ExecutingInfo.CreateDirectoryList );
			CShell::CreateShortCut( dat.CreatePath, dat.TargetObject, dat.Parameters );


			//	コピーしたファイルのログを残す
			InstallLog.SetElementName( MAIDTEXT(s_UNINSTALLER_SHORTCUT) );
			InstallLog.SetValueText( dat.CreatePath );
			InstallLog.NextNode();
		}
		SetTotalProgress( PER(TOTALPROGRESS_SHORTCUTEND) );


		SetTotalProgress( PER(TOTALPROGRESS_REGISTORYBEGIN) );


		{
			//	レジストリへの書き込み
			SetProgressText( MAIDTEXT("インストール情報をレジストリに書き込んでいます") );

			{
				CRegistry hReg;

				hReg.Create( CRegistry::KEY_CURRENT_USER, Data.pConfig->GetTargetRegistry() );
				hReg.SetValue( MAIDTEXT("InstallDirectory"), Data.pConfig->GetInstallFolder() );
				hReg.SetValue( MAIDTEXT("InstallType"), Type.InstallTypeName );

				InstallLog.SetElementName( MAIDTEXT(s_UNINSTALLER_CREATEREGISTRY) );
				InstallLog.SetValueText( Data.pConfig->GetTargetRegistry() );
				InstallLog.NextNode();

			}
		}

		{
			CRegistry hReg;

			const mstring SubKeyName = MAIDTEXT(s_UNINSTALLER_CTRLPANEL) + MAIDTEXT("\\") + Data.pConfig->GetGUID();

			hReg.Create( CRegistry::KEY_LOCAL_MACHINE, SubKeyName );

			hReg.SetValue( MAIDTEXT("DisplayName"), Data.pConfig->GetUninstallTittle() );

			const mstring UninstallCommand = UninstallerPath + MAIDTEXT(" \"") + UninstallLogFileName + MAIDTEXT("\"");

			hReg.SetValue( MAIDTEXT("UninstallString"), UninstallCommand );

			InstallLog.SetElementName( MAIDTEXT(s_UNINSTALLER_GUID) );
			InstallLog.SetValueText( Data.pConfig->GetGUID() );
			InstallLog.NextNode();

		}

		SetTotalProgress( PER(TOTALPROGRESS_REGISTORYEND) );
	}

	{
		//	アンインストールデータの作成
		SetProgressText( MAIDTEXT("アンインストール情報を作成しています") );
		SetTotalProgress( PER(TOTALPROGRESS_UNINSTALLBEGIN) );

		{
			//取りためたディレクトリの記録
			MySTL::set<mstring> s;

			for( int i=0; i<(int)m_ExecutingInfo.CreateDirectoryList.size(); ++i )
			{
				const mstring& fol = m_ExecutingInfo.CreateDirectoryList[i];
				s.insert( CString::ToLower(fol) );
			}

			for( MySTL::set<mstring>::reverse_iterator ite = s.rbegin();
						ite!=s.rend(); ++ite )
			{
				InstallLog.SetElementName( MAIDTEXT(s_UNINSTALLER_CREATEFOLDER) );
				InstallLog.SetValueText( *ite );
				InstallLog.NextNode();
			}
		}
		SetTotalProgress( PER(TOTALPROGRESS_UNINSTALLEND) );
	}

	InstallLog.DescendNode();
	InstallLog.Save( UninstallLogFileName );

	SetProgressText(MAIDTEXT("インストールが完了しました"));
	SetNowProgress(1);
	SetTotalProgress( 1 );


	m_NowState.Status = STATUS::THREADSTATUS_SUCCESS;

	return 0;
}

void Install::InstallCancel()
{
	SetProgressText( MAIDTEXT("インストールはキャンセルされました") );
	m_NowState.Status = STATUS::THREADSTATUS_CANCEL;
}

void Install::InstallError( const mstring& comment )
{
	SetProgressText( MAIDTEXT("インストール中にエラーが発生しました\nエラー内容：") + comment );
	m_NowState.Status = STATUS::THREADSTATUS_ERROR;
}


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! ファイルのコピー
/*!
 *	\param	DstFileName		[i ]	作成するディレクトリ名
 *	\param	SrcFileName		[i ]	作成するディレクトリ名
 *	\param	Brige			[i ]	外部スレッドからの命令
 *
 *	\return	コピーに成功したら FUNCTIONRESULT_OK
\n			外部からキャンセル命令がでたら FUNCTIONRESULT_CANCEL
\n			普通のエラーは FUNCTIONRESULT_ERROR
 */
Install::FUNCTIONRESULT Install::FileCopy( const mstring& DstFileName,  const mstring& SrcFileName, volatile CThreadController::BRIGEDATA& Brige  )
{
	//	ディレクトリを作る
	CShell::CreateDirectory( CString::GetDirectory(DstFileName), m_ExecutingInfo.CreateDirectoryList );


	CFileWrite hDst;
	CFileRead hSrc;

	hDst.Open(DstFileName,CFileWrite::OPENOPTION_NEW);
	hSrc.Open(SrcFileName);



	SetProgressText( SrcFileName+MAIDTEXT("から\n") + DstFileName + MAIDTEXT("にコピーしています") );

	const unt32 FileSize = hSrc.GetSize();

	unt32	LimitSize = FileSize;
	unt32	WriteSize = 0;

	while( LimitSize!=0 )
	{
		if( Brige.IsExit )
		{	//	中止命令が来ていたら
			return FUNCTIONRESULT_CANCEL;
		}

		const unt32 Size = (unt32)min(COPY_PACET,LimitSize);

		hSrc.Read( COPYBUF, Size );
		hDst.Write( COPYBUF, Size );


		LimitSize -= Size;
		WriteSize += Size;

		m_ExecutingInfo.AccessFileSize += Size;

		{	//	浮動小数点で計算しておかないと、オーバーフローすることがある
			float32 write = (float32)(int32)WriteSize;
			float32 total = (float32)(int32)FileSize;

			SetNowProgress( write/total );
		}
	}

	return FUNCTIONRESULT_OK;
}


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! MD5 チェックを行う
/*!
 *	\param	DstFileName		[i ]	チェックを行うファイル名
 *	\param	MD5Str			[i ]	↑のファイルのMD5（小文字であること）
 *	\param	Brige			[i ]	外部スレッドからの命令
 *
 *	\return	正しいファイルであったら FUNCTIONRESULT_OK
\n			外部からキャンセル命令がでたら FUNCTIONRESULT_CANCEL
\n			MD5 が合わなかったら FUNCTIONRESULT_ERROR
 */
Install::FUNCTIONRESULT	Install::CheckMD5( const mstring& DstFileName, const mstring& MD5Str, volatile CThreadController::BRIGEDATA& Brige )
{
	if( MD5Str.empty() ) { return FUNCTIONRESULT_OK; }

	CFileRead hSrc;

	hSrc.Open(DstFileName);


	SetProgressText( DstFileName + MAIDTEXT("のファイルチェックを行っています") );

	{
		MD5LIB::MD5_CTX context;

		MD5LIB::MD5Init( &context );

		unt32	LimitSize = hSrc.GetSize();
		unt32	ReadSize = 0;

		while( LimitSize!=0 )
		{
			if( Brige.IsExit )
			{	//	中止命令が来ていたら
				return FUNCTIONRESULT_CANCEL;
			}

			const unt32 Size = (unt32)min(MD5CHECKSIZE,LimitSize);

			hSrc.Read( COPYBUF, MD5CHECKSIZE );

			MD5LIB::MD5Update( &context, COPYBUF, Size );

			LimitSize -= Size;
			ReadSize += Size;
			m_ExecutingInfo.AccessFileSize += Size;

			{	//	浮動小数点で計算しておかないと、オーバーフローすることがある
				float32 write = (float32)(int32)ReadSize;
				float32 total = (float32)(int32)hSrc.GetSize();

				SetNowProgress( write/total );
			}
		}


		mstring NewMD5;
		{
			unsigned char digest[16];

			MD5LIB::MD5Final( digest, &context );

			for( unt32 i=0; i<16; ++i )
			{
				char buf[32];
				sprintf( buf, "%02x", digest[i] );

				NewMD5 += MAIDTEXT(buf);
			}
		}
		if( MD5Str != CString::ToLower(NewMD5) )
		{
			return FUNCTIONRESULT_ERROR;
		}

		::Sleep(1);
	}


	return FUNCTIONRESULT_OK;
}
