#include "stdafx.h"
#include "Common.h"
#include "Dialog/ExistsDialog.h"
#include "Dialog/FileDialog.h"
#include "Dialog/FolderInputDialog.h"
#include "ArcFile.h"
//#include "DataBase/DataBase.h"
//#include "DragDrop/DataObject.h"
//#include "DragDrop/DropSource.h"
#include "ExtractData.h"

#define WM_INPUT_FILE	(WM_USER+1)
#define WM_OUTPUT_FILE	(WM_USER+2)
#define WM_THREAD_END	(WM_USER+3)

BOOL g_bThreadEnd;

CExtractData::CExtractData()
{
	m_hParentWnd = NULL;
	m_hParentInst = NULL;
	m_pOption = NULL;
	m_pListView = NULL;

	GetModuleFileName(NULL, m_szPathToTmpFileList, MAX_PATH);
	PathRemoveFileSpec(m_szPathToTmpFileList);
	PathAppend(m_szPathToTmpFileList, _T("TmpFileList.txt"));
}

void CExtractData::Init(HWND hWnd, SOption& option, CMainListView& listview)
{
	m_hParentWnd = hWnd;
	m_hParentInst = (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE);
	m_pOption = &option;
	m_pListView = &listview;
}

void CExtractData::Open(LPTSTR pOpenDir)
{
	std::vector<TCHAR> szFileNames(MAX_PATH * 1000, 0);

	CFileDialog			clFileDlg;

	if( clFileDlg.DoModal( m_hParentWnd, &szFileNames[0], pOpenDir ) )
	{
		Mount( &szFileNames[0] );
	}
}

void CExtractData::OpenHistory(YCString& clsFilePath)
{
	std::vector<TCHAR> szFileNames(32000, 0);
	lstrcpy(&szFileNames[0], clsFilePath);
	Mount(&szFileNames[0]);
}

void CExtractData::OpenDrop(WPARAM wp)
{
	HDROP hDrop = (HDROP)wp;
	UINT uFileNo = DragQueryFile(hDrop, -1, NULL, 0);

	std::vector<TCHAR> szFileNames(MAX_PATH * uFileNo, 0);

	DragQueryFile(hDrop, 0, &szFileNames[0], MAX_PATH);

	if (uFileNo >= 2) {
		// 1つ目のファイル名取得
		TCHAR filename[MAX_PATH];
		lstrcpy(filename, PathFindFileName(&szFileNames[0]));

		// ディレクトリ名取得
		PathRemoveFileSpec(&szFileNames[0]);

		// '\0'の次を指す
		TCHAR *pszFileName = &szFileNames[lstrlen(&szFileNames[0]) + 1];

		// '\0'の次にファイル名をくっ付ける
		memcpy(pszFileName, filename, lstrlen(filename));
		pszFileName += lstrlen(filename) + 1;

		for (int i = 1; i < (int)uFileNo; i++) {
			DragQueryFile(hDrop, i, filename, MAX_PATH);
			memcpy(pszFileName, PathFindFileName(filename), lstrlen(PathFindFileName(filename)));
			pszFileName += lstrlen(PathFindFileName(filename)) + 1;
		}
	}

	DragFinish(hDrop);

	Mount(&szFileNames[0]);
}

void CExtractData::Close()
{
	// 開いた書庫ファイルをすべて閉じる
	std::vector<CArcFile*>& rArcList = m_ArcList;
	if (!rArcList.empty()) {
		for (int i = 0; i < (int)rArcList.size(); i++)
			delete rArcList[i];
		rArcList.clear();
	}
	// デコードクラスをクリア
	CExtract::Close();
	// テンポラリファイル削除
	DeleteTmpFile();
	// リストビューを空にする
	m_pListView->Clear();
}

void CExtractData::Mount(LPCTSTR c_pclArcNames)
{
	Close(); // 前回開いていたものを閉じる
	m_pclArcNames = c_pclArcNames;
	m_bInput = TRUE;
	DialogBoxParam(m_hParentInst, _T("PROGBAR"), m_hParentWnd, (DLGPROC)WndStaticProc, (LPARAM)this);
	m_pListView->Show();
}

UINT WINAPI CExtractData::MountThread(LPVOID lpParam)
{
	CExtractData* pObj = (CExtractData*)lpParam;

	try {
		SOption* pOption = pObj->m_pOption;
		LPCTSTR pclArcNames = pObj->m_pclArcNames;
		std::vector<SFileInfo>& rEnt = pObj->m_pListView->GetFileInfo();

		std::vector<YCString> sArcNameList;

		YCString sArcNames(pclArcNames);
		sArcNameList.push_back(sArcNames);
		pclArcNames += lstrlen(pclArcNames) + 1; // ZeroMemoryで0にしてあるため、最後の\0を超えても問題ない

		// 複数選択の場合
		if (lstrcmp(pclArcNames, _T("")) != 0) {
			// ディレクトリ名取得
			TCHAR tmpDir[_MAX_DIR];
			lstrcpy(tmpDir, sArcNameList[0]);
			PathAddBackslash(tmpDir);
			YCString Dir(tmpDir);
			sArcNameList.clear();
			// ディレクトリ名 + ファイル名の形でリストに追加
			while (lstrcmp(pclArcNames, _T("")) != 0) {
				YCString sArcName(pclArcNames);
				sArcNameList.push_back(Dir + sArcName);
				pclArcNames += lstrlen(pclArcNames) + 1;
			}
			sort(sArcNameList.begin(), sArcNameList.end());
		}

		// 全体のファイルサイズを求める
		QWORD AllArcSize = 0;
		std::vector<CArcFile*>& rArcList = pObj->m_ArcList;
		for (std::vector<YCString>::iterator itr = sArcNameList.begin(); itr != sArcNameList.end(); ) {
			// アーカイブファイルを開く
			CArcFile* pclArc = new CArcFile();

			if( !pclArc->Open(*itr) )
			{
				itr = sArcNameList.erase(itr); // 開けなかったアーカイブファイルはネームリストから消す
				delete	pclArc;
			}
			else
			{
				// アーカイブファイルサイズ加算

				AllArcSize += pclArc->GetArcSize();
				rArcList.push_back(pclArc);
				itr++;
			}
		}

		// プログレスバー初期化
		CProgBar prog;
		prog.Init(pObj->m_hWnd, AllArcSize);

		// 読み込み
		DWORD dwArcID = 0;
		//for (std::vector<CArcFile*>::iterator itrArc = rArcList.begin(); itrArc != rArcList.end(); ) {
		for (int i = 0; i < (int)rArcList.size(); i++) {
			CArcFile* pclArc = rArcList[i];
			pclArc->SetArcID(dwArcID);
			pclArc->SetEnt(rEnt);
			pclArc->SetProg(prog);
			pclArc->SetOpt(pOption);

			// アーカイブファイル名表示
			prog.SetArcName(pclArc->GetArcName());

			// 対応ファイル
			if (CExtract::Mount(pclArc) == TRUE) {
				dwArcID++;
				pclArc->SetState(TRUE);
			}
		}
		//MessageBox(pObj->m_hWnd, "", "", MB_OK);
		// プログレスバーの進捗状況を100%にする
		prog.UpdatePercent();
	}
	catch (std::bad_alloc) {
		// メモリ不足
		CError error;
		error.bad_alloc(pObj->m_hWnd);
	}
	catch (...) {
		// キャンセルなど
	}

	PostMessage(pObj->m_hWnd, WM_THREAD_END, 0, 0);

	_endthreadex( 0 );

	return 0;
}

void CExtractData::Save(DWORD ExtractMode, LPTSTR pSaveDir, BOOL bConvert)
{
	SOption*			pstOption = m_pOption;

	if( pstOption->bSaveSel )
	{
		// 出力先を指定

		CFolderInputDialog	clFolderInputDlg;

		if( clFolderInputDlg.DoModal( m_hParentWnd, pSaveDir ) == IDOK )
		{
			Decode( ExtractMode, pSaveDir, bConvert );
		}
	}
	else if( pstOption->bSaveSrc )
	{
		// 入力先に出力

		Decode( ExtractMode, NULL, bConvert );
	}
	else if( pstOption->bSaveDir )
	{
		// 固定出力先に出力

		Decode( ExtractMode, pstOption->SaveDir, bConvert );
	}
}

void CExtractData::SaveSel(LPTSTR pSaveDir, BOOL bConvert)
{
	Save( EXTRACT_SELECT, pSaveDir, bConvert );
}

void CExtractData::SaveAll(LPTSTR pSaveDir, BOOL bConvert)
{
	Save( EXTRACT_ALL, pSaveDir, bConvert );
}

void CExtractData::SaveDrop()
{
/*
	YCString sFiles;
	YCString sSaveDir = m_pOption->TmpDir + _T('\\');
	CMainListView* pListView = m_pListView;
	int nItem = -1;
	while ((nItem = pListView->GetNextItem(nItem)) != -1) {
		sFiles += sSaveDir + pListView->GetFileInfo()[nItem].name;
		sFiles += _T('\0');
	}
	sFiles += _T('\0');

	CDataObject* pObj = new CDataObject();
	CDropSource* pSrc = new CDropSource();

	// FORMATETC構造体をセット
	FORMATETC fmtetc;
	fmtetc.cfFormat = CF_HDROP;
	fmtetc.ptd = NULL;
	fmtetc.dwAspect = DVASPECT_CONTENT;
	fmtetc.lindex = -1;
	fmtetc.tymed = TYMED_HGLOBAL;

	// STGMEDIUM構造体をセット
	STGMEDIUM medium;
	medium.tymed = TYMED_HGLOBAL;
	medium.hGlobal = GlobalAlloc(GMEM_MOVEABLE, sizeof(DROPFILES) + sFiles.size());
	medium.pUnkForRelease = NULL;
	LPTSTR p = (LPTSTR)GlobalLock(medium.hGlobal);
	((DROPFILES*)p)->pFiles = sizeof(DROPFILES);
	((DROPFILES*)p)->fWide = FALSE;
	CopyMemory(p + sizeof(DROPFILES), sFiles, sFiles.size());
	GlobalUnlock(medium.hGlobal);

	// IDataObjectをセット
	pObj->SetData(&fmtetc, &medium, FALSE);

	// OLE Drag & Drop開始
	DWORD effect;
	HRESULT hr = DoDragDrop(pObj, pSrc, DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK, &effect);

	// リソースを開放
	pObj->Release();
	pSrc->Release();
*/
}

void CExtractData::Decode(DWORD ExtractMode, LPCTSTR pSaveDir, BOOL bConvert)
{
	CExistsDialog ExistsDlg;
	ExistsDlg.GetOverWrite() = 0x01; // 上書き確認を行う
	m_dwExtractMode = ExtractMode;
	m_pSaveDir = pSaveDir;
	m_bConvert = bConvert;
	m_bInput = FALSE;
	DialogBoxParam(m_hParentInst, _T("PROGBAR"), m_hParentWnd, (DLGPROC)WndStaticProc, (LPARAM)this);
}

void CExtractData::DecodeTmp()
{
	CExistsDialog ExistsDlg;
	ExistsDlg.GetOverWrite() = 0x00; // 上書き確認を行わない
	m_dwExtractMode = EXTRACT_SELECT;
	m_pSaveDir = m_pOption->TmpDir;
	m_bConvert = TRUE;
	m_bInput = FALSE;
	DialogBoxParam(m_hParentInst, _T("PROGBAR"), m_hParentWnd, (DLGPROC)WndStaticProc, (LPARAM)this);
}

UINT WINAPI CExtractData::DecodeThread(LPVOID lpParam)
{
	CExtractData* pObj = (CExtractData*)lpParam;
	CArcFile* pclArc = NULL;

	try {
		SOption* pOption = pObj->m_pOption;
		LPCTSTR pSaveDir = pObj->m_pSaveDir;
		BOOL				bConvert = pObj->m_bConvert;

		// 全体のファイルサイズを求める
		std::vector<int> nSelects;
		QWORD AllFileSize = 0;
		std::vector<CArcFile*>& rArcList = pObj->m_ArcList;
		if (pObj->m_dwExtractMode == EXTRACT_SELECT) {
			int nItem = -1;
			while ((nItem = pObj->m_pListView->GetNextItem(nItem)) != -1) {
				nSelects.push_back(nItem);
				AllFileSize += rArcList[0]->GetFileInfo(nItem)->sizeOrg;
			}
		}
		else {
			size_t nItemCount = rArcList[0]->GetFileInfo().size();
			for (int nItem = 0; nItem < (int)nItemCount; nItem++) {
				nSelects.push_back(nItem);
				AllFileSize += rArcList[0]->GetFileInfo(nItem)->sizeOrg;
			}
		}

		// プログレスバー初期化
		CProgBar prog;
		prog.Init(pObj->m_hWnd, AllFileSize);

		for (int i = 0; i < (int)nSelects.size(); i++) {
			SFileInfo* pInfFile = rArcList[0]->GetFileInfo(nSelects[i]);
			pclArc = rArcList[pInfFile->arcID];
			pclArc->SetProg(prog);
			// 入力先ファイル名から出力先フォルダ名作成
			if (pSaveDir == NULL && pOption->bSaveSrc == TRUE) {
				TCHAR SaveDir[MAX_PATH];
				// 入力先を取得
				lstrcpy(SaveDir, pclArc->GetArcPath());
				// ファイル名削除
				PathRemoveFileSpec(SaveDir);

				if (pclArc->GetCtEnt() == 1) {
					// 格納されているファイルが1つなら書庫が置いてあるフォルダ作成
					TCHAR ArcDirName[_MAX_DIR];
					lstrcpy(ArcDirName, PathFindFileName(SaveDir));
					PathAppend(SaveDir, _T("_"));
					lstrcat(SaveDir, ArcDirName);
				}
				else {
					// 格納されているファイルが2つ以上なら書庫名のフォルダ作成
					PathAppend(SaveDir, _T("_"));
					lstrcat(SaveDir, pclArc->GetArcName());
				}
				pclArc->SetSaveDir(SaveDir);
			}
			else
				pclArc->SetSaveDir(pSaveDir);
			pclArc->SetFileInfo(nSelects[i]);
			// ファイル名表示
			prog.SetFileName(pInfFile->name);
			// 抽出
			CExtract::Decode(pclArc, bConvert);
			// ファイルクローズ(閉じ忘れてたときのために)
			pclArc->CloseFile();
		}
		//MessageBox(pObj->m_hWnd, "", "", MB_OK);
	}
	catch (std::bad_alloc) {
		// メモリ不足
		CError error;
		error.bad_alloc(pObj->m_hWnd);
	}
	catch (...) {
		// キャンセルなど
		//CError error;
		//error.Message(pObj->m_hWnd, "");
		// 例外発生でファイルが閉じられなかった場合に対応
		//if (pclArc != NULL)
		//	pclArc->CloseFile();
	}

	// 例外発生でファイルが閉じられなかった場合に対応
	if (pclArc != NULL)
		pclArc->CloseFile();

	// スレッドを終了させることを伝える
	PostMessage(pObj->m_hWnd, WM_THREAD_END, 0, 0);

	_endthreadex( 0 );

	return 0;
}

void CExtractData::OpenRelate()
{
	CMainListView*		pListView = m_pListView;
	int					nItem = -1;

	while( (nItem = pListView->GetNextItem( nItem )) != -1 )
	{
		std::set<YCString>&	sTmpFilePathList = pListView->GetFileInfo()[nItem].sTmpFilePath;

		if( !sTmpFilePathList.empty() )
		{
			YCString&		clsTmpFilePath = *sTmpFilePathList.begin();

			// 関連付けから開く

			if( ::ShellExecute( NULL, NULL, clsTmpFilePath, NULL, NULL, SW_SHOWNORMAL ) == (HINSTANCE) SE_ERR_NOASSOC )
			{
				// 関連付けられていなかった場合はアプリケーションを選択するダイアログを出す

				YCString			clsParam = _T("shell32.dll,OpenAs_RunDLL ") + clsTmpFilePath;

				::ShellExecute( NULL, NULL, _T("rundll32.exe"), clsParam, NULL, TRUE );
			}

			// テンポラリファイルリストに追加

			for( std::set<YCString>::iterator itr = sTmpFilePathList.begin() ; itr != sTmpFilePathList.end() ; itr++ )
			{
				if( *itr != _T("") )
				{
					m_ssTmpFile.insert( *itr );
				}
			}

			sTmpFilePathList.clear();
		}
	}
}

void CExtractData::DeleteTmpFile()
{
	// 前回残ったテンポラリファイル追加
	LoadTmpFileList();

	for (std::set<YCString>::iterator itr = m_ssTmpFile.begin(); itr != m_ssTmpFile.end(); ) {
		TCHAR szTmp[MAX_PATH];
		lstrcpy(szTmp, *itr);

		// テンポラリファイル削除

		if( PathFileExists( szTmp ) )
		{
			// ファイルが存在する

			if( !DeleteFile( szTmp ) )
			{
				// 削除に失敗

				itr++;
				continue;
			}
		}

		while (lstrcmp(szTmp, m_pOption->TmpDir) != 0) {
			// フォルダ削除

			if( !PathRemoveFileSpec(szTmp) )
			{
				break;
			}

			RemoveDirectory(szTmp);
		}

		itr = m_ssTmpFile.erase(itr);
	}

	// 今回残ったテンポラリファイル保存
	SaveTmpFileList();
}

void CExtractData::LoadTmpFileList()
{
	CFile clfTmpFileList;
	if (clfTmpFileList.Open(m_szPathToTmpFileList, FILE_READ) == INVALID_HANDLE_VALUE) {
		// 読み込みファイルのオープンに失敗
		return;
	}

	while (1) {
		char buf[MAX_PATH];

		if (clfTmpFileList.ReadLine(buf, sizeof(buf), TRUE) == 0)
			break;

		m_ssTmpFile.insert(buf);
	}
}

void CExtractData::SaveTmpFileList()
{
	if (m_ssTmpFile.empty()) {
		// 全て削除できた
		DeleteFile(m_szPathToTmpFileList);
		return;
	}

	CFile clfTmpFileList;
	if (clfTmpFileList.Open(m_szPathToTmpFileList, FILE_WRITE) == INVALID_HANDLE_VALUE) {
		// 書き込みファイルのオープンに失敗
		return;
	}

	for (std::set<YCString>::iterator itr = m_ssTmpFile.begin(); itr != m_ssTmpFile.end(); itr++) {
		clfTmpFileList.WriteLine(*itr);
		clfTmpFileList.Write("\r\n", 2);
	}

	m_ssTmpFile.clear();
}

LRESULT CExtractData::WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
	static HANDLE hThread;
	UINT thId;

	switch (msg) {
	case WM_INITDIALOG:
		CWindowBase::Init();

		g_bThreadEnd = FALSE;

		SetFocus(GetDlgItem(hWnd, IDC_CANCEL));
		if (m_bInput) {
			// 読み込み
			SetWindowText(hWnd, _T("ファイル読み込み中..."));
			PostMessage(hWnd, WM_INPUT_FILE, 0, 0);
		}
		else {
			// 抽出
			SetWindowText(hWnd, _T("抽出中..."));
			PostMessage(hWnd, WM_OUTPUT_FILE, 0, 0);
		}

		return FALSE;

	case WM_COMMAND:
		switch (LOWORD(wp)) {
		case IDCANCEL:
		case IDC_CANCEL:
			// キャンセルが押された
			g_bThreadEnd = TRUE;
			return FALSE;
		}
		return FALSE;

	case WM_INPUT_FILE:
		// 情報取得開始
		hThread = (HANDLE) _beginthreadex( NULL, 0, MountThread, this, 0, &thId );
//		hThread = CreateThread(NULL, 0, MountThread, this, 0, &thId);
		return FALSE;

	case WM_OUTPUT_FILE:
		// 抽出処理開始
		hThread = (HANDLE) _beginthreadex( NULL, 0, DecodeThread, this, 0, &thId );
//		hThread = CreateThread(NULL, 0, DecodeThread, this, 0, &thId);
		return FALSE;

	case WM_THREAD_END:
		// 処理を終える
		WaitForSingleObject(hThread, INFINITE);
		CloseHandle(hThread);
		EndDialog(hWnd, IDOK);
		return TRUE;
	}

	return FALSE;
}