/*******************************************************************************
  TPI - flexible but useless plug-in framework.
  Copyright (C) 2002-2009 Silky

  This library is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the Free
  Software Foundation; either version 2.1 of the License, or (at your option)
  any later version.

  This library is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
  for more details.

  You should have received a copy of the GNU Lesser General Public License along
  with this library; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

  $Id: eggArc.cpp 355 2010-09-25 03:19:57Z sirakaba $
*******************************************************************************/

//******************************************************************************
//    Includes
//******************************************************************************

#include "../../common/header/plugin.h"
#include "../../common/header/plugin-extra.h"
#include "../../common/library/library.h"
#include <windows.h>
#include "eggArc.h"

//******************************************************************************
//    Global varients
//******************************************************************************

HMODULE g_hLib;
TPI_PROC g_prProc;
TPI_SWITCHES * g_swInfo;
const wxArrayString * g_asFiles;

//******************************************************************************
//    Inside Functions
//******************************************************************************

#define GetAPIAddress(name) GetProcAddress(g_hLib, "EGG_" name)

int ErrorCodeConvert(int nErrorCode)
{
	switch (nErrorCode)
	{
	case EGG_ERROR_FAIL:        return TPI_ERROR_UNDEFINED;
	case EGG_ERROR_SUCCESS:     return TPI_ERROR_SUCCESS;
	case EGG_ERROR_SKIP:        return TPI_ERROR_D_SKIPPED;
	case EGG_ERROR_CANCEL:      return TPI_ERROR_D_SKIPPED;
	case EGG_ERROR_IO:          return TPI_ERROR_IO_MISC;
	case EGG_ERROR_TYPEMISMATCH:return TPI_ERROR_ARC_UNSUPPORTED;
	case EGG_ERROR_FILE:        return TPI_ERROR_IO_MISC;
	case EGG_ERROR_FORMAT:      return TPI_ERROR_IO_ARC_MISC;
	case EGG_ERROR_ALGORITHM:   return TPI_ERROR_UNDEFINED;
	default:                    return TPI_ERROR_UNDEFINED;
	}
}

int __stdcall GetFileInformation2
(
	void * _hArchive,
	TPI_FILEINFO * _fiInfo,
	wxULongLong_t _nIndex
)
{
	static FARPROC fpProc = ::GetAPIAddress("GetFileHeader");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}

	EGGFileHeader fhInfo;
	int nErrorCode = ErrorCodeConvert(((UINT (__cdecl *)(void *, size_t, EGGFileHeader *)) fpProc)(_hArchive, _nIndex, & fhInfo));
	if (nErrorCode == TPI_ERROR_SUCCESS)
	{
		_fiInfo->dwAttribute    = fhInfo.attribute;
		if (fhInfo.encrypted)
		{
			_fiInfo->dwAttribute |= TPI_ATTRIBUTE_ENCRYPTED;
		}
		_fiInfo->dwCRC32        = fhInfo.crc;
		_fiInfo->nPackedSize    = fhInfo.packedSize;
		_fiInfo->nUnpackedSize  = fhInfo.unpackedSize;
		_fiInfo->tmModified     = fhInfo.lastModified;
		_fiInfo->szComment      = WC2String(fhInfo.comment);
		_fiInfo->szStoredName   = WC2String(fhInfo.fileName);
		_fiInfo->szMethod       = fhInfo.method == 2 ? wxT("ALZ") : fhInfo.method == 0 ? wxT("EGG") : wxT("unknown");
		_fiInfo->nFileId        = _nIndex;
		_fiInfo->fnFileName     = wxFileName(_fiInfo->szStoredName);
	}

	return nErrorCode;
}

//******************************************************************************
//    Callback Wrapper
//******************************************************************************

UINT __cdecl CallbackProc(void * user, UINT code, void* param)
{
	// 構造体を初期化。
	static TPI_PROCESSINFO piInfo;
	piInfo.eMessage = TPI_MESSAGE_STATUS;

	switch (code)
	{
	case eggevent::Start::CODE:
		piInfo.eStatus = TPI_STATUS_OPENARCHIVE;
		break;
	case eggevent::Extract::CODE:
	{
		// 対象ファイルか確認。
		eggevent::Extract * p = (eggevent::Extract *) param;
		GetFileInformation2(* (void **) user, & piInfo.fiInfo, p->index);
		if (g_asFiles->Count() != 0 && g_asFiles->Index(piInfo.fiInfo.szStoredName) == wxNOT_FOUND)
		{
			return EGG_ERROR_SKIP;
		}

		// 処理するかどうか確認。
		piInfo.eMessage = TPI_MESSAGE_ASK;
		piInfo.eStatus  = TPI_PARAM_DEST;
		piInfo.fnDestination = wxFileName(g_swInfo->fnDestinationDirectory.GetPathWithSep() + (g_swInfo->fStoreDirectoryPathes ? piInfo.fiInfo.fnFileName.GetFullPath() : piInfo.fiInfo.fnFileName.GetFullName()));
		if (g_prProc != NULL && g_prProc(TPI_NOTIFY_COMMON, & piInfo) != TPI_CALLBACK_CONTINUE)
		{
			return EGG_ERROR_CANCEL;
		}
		if (! piInfo.fnDestination.IsOk())
		{
			return EGG_ERROR_SKIP;
		}

		// 展開する。
		wcsncpy(p->fileName, piInfo.fnDestination.GetFullPath().wchar_str(), MAX_PATH - 1);
		return EGG_ERROR_SUCCESS;
	}
	case eggevent::Test::CODE:
	{
		// 対象ファイルか確認。
		eggevent::Test * p = (eggevent::Test *) param;
		GetFileInformation2(* (void **) user, & piInfo.fiInfo, p->index);
		if (g_asFiles->Count() != 0 && g_asFiles->Index(piInfo.fiInfo.szStoredName) == wxNOT_FOUND)
		{
			p->skipThisFile = true;
			return EGG_ERROR_SKIP;
		}
		return EGG_ERROR_SUCCESS;
	}
	case eggevent::ActivityStart::CODE:
		piInfo.eStatus = TPI_STATUS_BEGINPROCESS;
		GetFileInformation2(* (void **) user, & piInfo.fiInfo, ((eggevent::ActivityStart *) param)->index);
		break;
	case eggevent::Progress::CODE:
		piInfo.eStatus = TPI_STATUS_INPROCESS;
		piInfo.nProcessedSize = ((eggevent::Progress *) param)->current * (piInfo.fiInfo.nUnpackedSize / 10000);
		break;
	case eggevent::ActivityFinish::CODE:
		piInfo.eStatus = TPI_STATUS_ENDPROCESS;
		break;
	case eggevent::Finish::CODE:
		piInfo.eStatus = TPI_STATUS_CLOSEARCHIVE;
		break;
	case eggevent::QueryPassword::CODE:
	{
		// パスワード問い合わせ。
		piInfo.eMessage = TPI_MESSAGE_ASK;
		piInfo.eStatus  = TPI_PARAM_PASSWORD;

		// コールバック関数に送信。
		eggevent::QueryPassword * p = (eggevent::QueryPassword *) param;
		// 初回は既定のパスワードを試す。
		if (p->queryCount == 1)
		{
			wcsncpy(p->password, g_swInfo->szPassword.wchar_str(), 1024 - 1);
			return EGG_ERROR_SUCCESS;
		}

		// 2回目以降はコールバック関数に送信する。
		if (g_prProc == NULL || g_prProc(TPI_NOTIFY_COMMON, & piInfo) != TPI_CALLBACK_CONTINUE)
		{
			return EGG_ERROR_CANCEL;
		}

		// 既定のパスワードも変更しておく。
		g_swInfo->szPassword = piInfo.szParam;
		wcsncpy(p->password, piInfo.szParam.wchar_str(), 1024 - 1);
		return EGG_ERROR_SUCCESS;
	}
	default:
		return EGG_ERROR_SUCCESS;
	}

	// コールバック関数に送信。
	if (g_prProc == NULL)
	{
		return EGG_ERROR_SUCCESS;
	}

	return g_prProc(TPI_NOTIFY_COMMON, & piInfo) != TPI_CALLBACK_CANCEL ? EGG_ERROR_SUCCESS : EGG_ERROR_CANCEL;
}

//******************************************************************************
//    Functions
//******************************************************************************

#ifdef __cplusplus
extern "C"
{
#endif

int __stdcall GetPluginInformation
(
	unsigned int _uInfoId,
	wxULongLong_t,
	void * _pPtr
)
{
	if (_pPtr == NULL)
	{
		return TPI_ERROR_D_PARAMETER;
	}
	switch (_uInfoId)
	{
	case TPI_INFO_VERSION_MAJOR:
	case TPI_INFO_VERSION_MINOR:
		* (int *) _pPtr = 0;
		break;
	case TPI_INFO_VERSION_API:
		* (int *) _pPtr = 2;
		break;
	default:
		return TPI_ERROR_D_UNSUPPORTED;
	}
	return TPI_ERROR_SUCCESS;
}

int __stdcall GetFormatInformation(TPI_FORMATINFO * _fiInfo, bool _bFirst)
{
	if (! _bFirst)
	{
		return TPI_ERROR_S_ENDOFDATA;
	}

	_fiInfo->szTypeName   = wxT("EGG/ALZ");
	_fiInfo->szSuffix     = wxT("egg;alz");
	_fiInfo->szEngineName = wxT("UnEGG32.dll");
	_fiInfo->szTPIName    = wxT("eggArc");
	_fiInfo->nTypeId      = 0;
	_fiInfo->eSupportedCommand = TPI_COMMAND_EXTRACT | TPI_COMMAND_TEST;
	_fiInfo->fArchive     = true;
	_fiInfo->fComment     = true;
	_fiInfo->fSFX         = true;
	_fiInfo->fSolid       = true;
	_fiInfo->fEncryptPassword = true;
	_fiInfo->fMultiVolume = true;

	return TPI_ERROR_SUCCESS;
}

int __stdcall LoadPlugin
(
	const wxString &,
	wxULongLong_t
)
{
	::RemoveCwdFromSearchPath();
	g_hLib = ::LoadLibrary(L"UnEGG32.dll");
	if (g_hLib == NULL)
	{
		::FreeLibrary(g_hLib);
		return TPI_ERROR_U_LOAD_LIBRARY;
	}

	return TPI_ERROR_SUCCESS;
}

int __stdcall FreePlugin
(
	void * // _pReserved
)
{
	::FreeLibrary(g_hLib);
	return TPI_ERROR_SUCCESS;
}

int __stdcall OpenArchive
(
	const wxString & _szArcName,
	void * * _hArchive,
	wxULongLong_t * _nFileCount
)
{
	FARPROC fpProc = ::GetAPIAddress("IsValidArchive");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}

	egg_type eType;
	int nErrorCode = ErrorCodeConvert(((UINT (__cdecl *)(const wchar_t *, egg_type *)) fpProc)(_szArcName.wchar_str(), & eType));
	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
		return nErrorCode;
	}

	fpProc = ::GetAPIAddress("CreateEgg");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	nErrorCode = ErrorCodeConvert(((UINT (__cdecl *)(void **, eggevent::Proc, void *)) fpProc)(_hArchive, CallbackProc, _hArchive));
	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
		return nErrorCode;
	}
	if (_hArchive == NULL)
	{
		return TPI_ERROR_UNDEFINED;
	}

	fpProc = ::GetAPIAddress("OpenArchive");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	nErrorCode = ErrorCodeConvert(((UINT (__cdecl *)(void *, wchar_t *)) fpProc)(* _hArchive, _szArcName.wchar_str()));
	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
		return nErrorCode;
	}

	if (_nFileCount != NULL)
	{
		fpProc = ::GetAPIAddress("GetFileCount");
		if (fpProc == NULL)
		{
			return TPI_ERROR_U_USE_LIBRARY;
		}
		unsigned int n;
		nErrorCode = ErrorCodeConvert(((UINT (__cdecl *)(void *, size_t *)) fpProc)(* _hArchive, & n));
		if (nErrorCode != TPI_ERROR_SUCCESS)
		{
			return nErrorCode;
		}
		* _nFileCount = n;
	}

	return TPI_ERROR_SUCCESS;
}

int __stdcall CloseArchive
(
	void * _hArchive
)
{
	FARPROC fpProc = ::GetAPIAddress("CloseArchive");
	if (fpProc == NULL || _hArchive == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	int nErrorCode = ErrorCodeConvert(((UINT (__cdecl *)(void *)) fpProc)(_hArchive));
	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
		return nErrorCode;
	}

	fpProc = ::GetAPIAddress("DestroyEgg");
	return fpProc == NULL ? TPI_ERROR_U_USE_LIBRARY : ErrorCodeConvert(((UINT (__cdecl *)(void **)) fpProc)(& _hArchive));
}

int __stdcall GetFileInformation
(
	void * _hArchive,
	TPI_FILEINFO * _fiInfo,
	bool _bFirst
)
{
	static wxULongLong_t s_nFileId;
	static size_t s_nFileCount;
	if (_bFirst)
	{
		s_nFileId = 0;
		FARPROC fpProc = ::GetAPIAddress("GetFileCount");
		if (fpProc == NULL)
		{
			return TPI_ERROR_U_USE_LIBRARY;
		}
		int nErrorCode = ErrorCodeConvert(((UINT (__cdecl *)(void *, size_t *)) fpProc)(_hArchive, & s_nFileCount));
		if (nErrorCode != TPI_ERROR_SUCCESS)
		{
			return nErrorCode;
		}
	}
	return s_nFileId >= s_nFileCount ? TPI_ERROR_S_ENDOFDATA : GetFileInformation2(_hArchive, _fiInfo, s_nFileId++);
}

int __stdcall GetArchiveInformation
(
	void *,
	TPI_ARCHIVEINFO * _aiInfo
)
{
	GetFormatInformation(& _aiInfo->fiInfo, true);
	return TPI_ERROR_SUCCESS;
}

int __stdcall Command
(
	wxULongLong_t _eCommand,
	TPI_SWITCHES * _swInfo,
	const wxString & _szArcName,
	const wxArrayString & _szFiles
)
{
	if (_eCommand != TPI_COMMAND_EXTRACT && _eCommand != TPI_COMMAND_TEST)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}

	// 開きなおす。
	void * hArc;
	int nErrorCode = OpenArchive(_szArcName, & hArc);
	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
		return nErrorCode;
	}

	// グローバル変数にポインタを保存。
	g_swInfo = _swInfo;
	g_asFiles = & _szFiles;

	FARPROC fpProc = _eCommand == TPI_COMMAND_EXTRACT ? ::GetAPIAddress("Extract") : ::GetAPIAddress("Test");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	nErrorCode = ErrorCodeConvert(((UINT (__cdecl *)(void *)) fpProc)(hArc));
	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
		return nErrorCode;
	}

	return CloseArchive(hArc);
}

int __stdcall SetCallbackProc
(
	TPI_PROC _prArcProc
)
{
	// ポインタを保存。
	if (_prArcProc == NULL)
	{
		return TPI_ERROR_D_PARAMETER;
	}
	g_prProc = * _prArcProc;

	return TPI_ERROR_SUCCESS;
}

#ifdef __cplusplus
}
#endif
