/*******************************************************************************
  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: xpiLibrary.cpp 153 2009-11-22 12:56:09Z sirakaba $
*******************************************************************************/

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

#include "../../common/header/plugin.h"
#include "../../common/header/plugin-extra.h"
#include "../../common/library/library.h"
#include <wx/file.h>
#include <wx/stdpaths.h>
#include <wx/dir.h>
#include <windows.h>
#include "xpiLibrary.h"

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

HMODULE g_hLib;
TPI_PROC g_prProc;
wxString g_szArcName;

//******************************************************************************
//    Entry
//******************************************************************************

BOOL __stdcall DllMain(HMODULE, DWORD fdwReason, void *)
{
	switch (fdwReason)
	{
	case DLL_PROCESS_DETACH:
		::FreeLibrary(g_hLib);
		break;
	}
	return TRUE;
}

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

int __stdcall CallbackProc(int _nNow, int _nMax, long _lData)
{
	// 構造体を初期化。
	TPI_PROCESSINFO * piInfo = (TPI_PROCESSINFO *) _lData;
	piInfo->llProcessedSize       = _nNow;
	piInfo->fiInfo.llUnpackedSize = _nMax;

	// コールバック関数に送信。
	return g_prProc != NULL && g_prProc(TPI_NOTIFY_COMMON, piInfo) == TPI_CALLBACK_CANCEL;
}

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

int ErrorCodeConvert(int nErrorCode)
{
	switch (nErrorCode)
	{
	case -1:case XPI_NO_FUNCTION:       return TPI_ERROR_U_USE_LIBRARY;
	case 0:                             return TPI_ERROR_SUCCESS;
	case 1: case XPI_ABORT:             return TPI_ERROR_D_SKIPPED;
	case 2: case XPI_NOT_SUPPRORT:      return TPI_ERROR_ARC_UNSUPPORTED;
	case 3: case XPI_OUT_OF_ORDER:      return TPI_ERROR_ARC_BROKEN_MISC;
	case 4: case XPI_NO_MEMORY:         return TPI_ERROR_D_OUTOFMEMORY;
	case 5: case XPI_MEMORY_ERROR:      return TPI_ERROR_D_USEMEMORY;
	case 6: case XPI_FILE_READ_ERROR:   return TPI_ERROR_IO_ARC_READ;
			case XPI_OTHER_ERROR:		return TPI_ERROR_UNDEFINED;
			case XPI_WINDOW_ERROR:      return TPI_ERROR_UNDEFINED;
			case XPI_FILE_WRITE_ERROR:  return TPI_ERROR_IO_FILE_WRITE;
			case XPI_END_OF_FILE:       return TPI_ERROR_ARC_BROKEN_MISC;
	default:                            return TPI_ERROR_UNDEFINED;
	}
}

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

#ifdef __cplusplus
extern "C"
{
#endif

int __stdcall GetPluginInformation
(
	unsigned int _uInfoId,
	wxULongLong,
	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_PARAMETER;
	}
	return TPI_ERROR_SUCCESS;
}

int __stdcall GetFormatInformation(TPI_FORMATINFO *, bool)
{
	return TPI_ERROR_D_UNSUPPORTED;
}

int __stdcall LoadPlugin
(
	const wxString & _szArcName,
	wxULongLong _llSubOption
)
{
	wxStandardPaths p;
	wxString szSPIPath = wxPathOnly(p.GetExecutablePath()) + wxT("/lib/"), szSPIName;
	wxDir fs(szSPIPath);
	bool b = fs.GetFirst(& szSPIName, wxT("*.spi"));
	while (b)
	{
		// SPIをロード。
		wxString szLibName = szSPIPath + szSPIName;
		g_hLib = ::LoadLibrary(szLibName.wchar_str());
		if (g_hLib == NULL)
		{
			b = fs.GetNext(& szSPIName);
			continue;
		}

		// GetPluginInfoを実行。
		FARPROC	fpProc = ::GetProcAddress(g_hLib, "GetPluginInfo");
		char szPluginType[5]; // 種類4bytes + NULL
		if (fpProc == NULL
			|| ((int (PASCAL *)(int, char *, int)) fpProc)(0, szPluginType, sizeof(szPluginType)) <= 0
			|| szPluginType[2] != 'I' || szPluginType[3] != 'N')
		{
			::FreeLibrary(g_hLib);
			b = fs.GetNext(& szSPIName);
			continue;
		}

		// 書庫に対応しているかチェック。
		if (CheckArchive(_szArcName, NULL) == TPI_ERROR_SUCCESS)
		{
			// 対応していれば処理を終了。
			return TPI_ERROR_SUCCESS;
		}

		b = fs.GetNext(& szSPIName);
	}
	return TPI_ERROR_U_LOAD_LIBRARY;
}

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

int __stdcall CheckArchive
(
	const wxString & _szArcName,
	int * _nFileCount
)
{
	FARPROC	fpProc = ::GetProcAddress(g_hLib, "IsSupported");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}

	wxFile hFile(_szArcName, wxFile::read);
	if (! hFile.IsOpened())
	{
		return TPI_ERROR_IO_ARC_OPEN;
	}

	char buffer[2050];
	::ZeroMemory(buffer, sizeof(buffer));
	if (hFile.Read(buffer, sizeof(buffer)) == wxInvalidOffset)
	{
		hFile.Close();
		return TPI_ERROR_IO_ARC_READ;
	}

	if (! ((BOOL (PASCAL *)(const char *, unsigned long)) fpProc)(_szArcName.ToUTF8(), (unsigned long) buffer))
	{
		hFile.Close();
		return TPI_ERROR_D_UNSUPPORTED;
	}
	hFile.Close();

	if (_nFileCount != NULL)
	{
		* _nFileCount = 1;
	}

	return TPI_ERROR_SUCCESS;
}

int __stdcall OpenArchive
(
	const wxString & _szArcName,
	void * * _hArchive
)
{
	g_szArcName = _szArcName;
	* _hArchive = & g_szArcName;
	return TPI_ERROR_SUCCESS;
}

int __stdcall CloseArchive
(
	void * _hArchive
)
{
	g_szArcName.Clear();
	return TPI_ERROR_SUCCESS;
}

int __stdcall GetFileInformation
(
	void * _hArchive,
	TPI_FILEINFO * _fiInfo,
	bool _bFirst
)
{
	static wxString szXPIPath, szXPIName, szArcName = * (wxString *) _hArchive;
	static int nColorDepth;
	static wxDir fs;
	bool b;
	if (_bFirst)
	{
		wxStandardPaths p;
		szXPIPath = wxPathOnly(p.GetExecutablePath()) + wxT("/lib/");
		fs.Open(szXPIPath);

		// 画像の情報を取得。
		FARPROC fpProc = ::GetProcAddress(g_hLib, "GetPictureInfo");
		if (fpProc == NULL)
		{
			return TPI_ERROR_U_USE_LIBRARY;
		}
		PictureInfo picInfo;
		int nErrorCode = ErrorCodeConvert(((int (PASCAL *)(const char *, long, unsigned int, PictureInfo *)) fpProc)(szArcName.ToUTF8(), 0, 0, & picInfo));
		if (nErrorCode != TPI_ERROR_SUCCESS)
		{
			return nErrorCode;
		}
		nColorDepth = picInfo.colorDepth;
		::LocalUnlock(picInfo.hInfo);
		::LocalFree(picInfo.hInfo);

		b = fs.GetFirst(& szXPIName, wxT("*.xpi"));
	}
	else
	{
		b = fs.GetNext(& szXPIName);
	}
	while (b)
	{
		// XPIをロード。
		wxString szLibName = szXPIPath + szXPIName;
		HMODULE hXPI = ::LoadLibrary(szLibName.wchar_str());
		if (hXPI == NULL)
		{
			b = fs.GetNext(& szXPIName);
			continue;
		}

		// GetPluginInfoを実行。
		FARPROC fpProc = ::GetProcAddress(hXPI, "GetPluginInfo");
		char szTemp[20];
		if (fpProc == NULL
			|| ((int (PASCAL *)(int, char *, int)) fpProc)(0, szTemp, sizeof(szTemp)) <= 0
			|| szTemp[2] != 'X' || szTemp[3] != 'N'
			|| ((int (PASCAL *)(int, char *, int)) fpProc)(2, szTemp, sizeof(szTemp)) <= 0)
		{
			::FreeLibrary(hXPI);
			b = fs.GetNext(& szXPIName);
			continue;
		}

		// 対応確認。
		fpProc = ::GetProcAddress(hXPI, "IsSupported");
		if (fpProc == NULL || ! ((BOOL (PASCAL *)(int)) fpProc)(nColorDepth))
		{
			::FreeLibrary(hXPI);
			b = fs.GetNext(& szXPIName);
			continue;
		}

		_fiInfo->fnFileName = wxFileName(szArcName);
		_fiInfo->fnFileName.SetVolume(wxEmptyString);
		_fiInfo->fnFileName.SetPath(szXPIName);
		_fiInfo->fnFileName.SetExt(MB2String(szTemp));
		_fiInfo->szStoredName = _fiInfo->fnFileName.GetFullPath();

		return TPI_ERROR_SUCCESS;
	}

	return TPI_ERROR_S_ENDOFDATA;
}

int __stdcall GetArchiveInformation
(
	void * _hArchive,
	TPI_ARCHIVEINFO * _aiInfo
)
{
	// 画像の情報を取得。
	FARPROC fpProc = ::GetProcAddress(g_hLib, "GetPictureInfo");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	PictureInfo piInfo;
	int nErrorCode = ErrorCodeConvert(((int (PASCAL *)(const char *, long, unsigned int, PictureInfo *)) fpProc)(((wxString *) _hArchive)->ToUTF8(), 0, 0, & piInfo));
	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
		return nErrorCode;
	}
	_aiInfo->szComment = MB2String((char *) ::LocalLock(piInfo.hInfo));
	::LocalUnlock(piInfo.hInfo);
	::LocalFree(piInfo.hInfo);

	// GetPluginInfoを実行。
	fpProc = ::GetProcAddress(g_hLib, "GetPluginInfo");
	if (fpProc != NULL)
	{
		char szTemp[20];
		if (((int (PASCAL *)(int, char *, int)) fpProc)(2, szTemp, sizeof(szTemp)) > 0)
		{
			_aiInfo->fiInfo.szSuffix = MB2String(szTemp);
		}
		if (((int (PASCAL *)(int, char *, int)) fpProc)(3, szTemp, sizeof(szTemp)) > 0)
		{
			_aiInfo->fiInfo.szTypeName = MB2String(szTemp);
		}
	}
	_aiInfo->fiInfo.fArchive = false;
	_aiInfo->fiInfo.szTPIName = wxT("xpiLibrary");
	_aiInfo->fiInfo.llSupportedCommand = TPI_COMMAND_EXTRACT;

	return TPI_ERROR_SUCCESS;
}

int __stdcall Command
(
	unsigned int _uCommand,
	TPI_SWITCHES * _swInfo,
	const wxString & _szArcName,
	const wxArrayString & _asFiles
)
{
	if (_uCommand != TPI_COMMAND_EXTRACT)
	{
		return TPI_ERROR_D_UNSUPPORTED;
	}

	// 画像の情報を取得。
	FARPROC fpProc = ::GetProcAddress(g_hLib, "GetPictureInfo");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	PictureInfo picInfo;
	int nErrorCode = ErrorCodeConvert(((int (PASCAL *)(const char *, long, unsigned int, PictureInfo *)) fpProc)(_szArcName.ToUTF8(), 0, 0, & picInfo));
	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
		return nErrorCode;
	}

	// コールバックを送信。
	wxFileName _fnArcName(_szArcName);
	TPI_PROCESSINFO piInfo;
	piInfo.uMessage = TPI_MESSAGE_STATUS;
	piInfo.uStatus  = TPI_STATUS_OPENARCHIVE;
	piInfo.fiInfo.fnFileName = _fnArcName;
	if (CallbackProc(0, 0, (long) & piInfo))
	{
		return TPI_ERROR_D_SKIPPED;
	}

	// メモリ上に展開。
	HANDLE hInfo, hMemory;
	fpProc = ::GetProcAddress(g_hLib, "GetPicture");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	nErrorCode = ErrorCodeConvert(((int (PASCAL *)(const char *, long, unsigned int, HANDLE *, HANDLE *, FARPROC, long)) fpProc)(_szArcName.ToUTF8(), 0, 0, & hInfo, & hMemory, (FARPROC) CallbackProc, (long) & piInfo));
	if (nErrorCode == TPI_ERROR_SUCCESS && (hMemory == NULL || hInfo == NULL))
	{
		return TPI_ERROR_UNDEFINED;
	}
	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
		return nErrorCode;
	}

	for (size_t i = 0; i < _asFiles.GetCount(); i++)
	{
		// XPIをロード。
		HMODULE hXPI = ::LoadLibrary(wxFileName(_asFiles[i]).GetPath().wchar_str());
		if (hXPI == NULL)
		{
			nErrorCode = TPI_ERROR_U_USE_LIBRARY;
			break;
		}

		// GetPluginInfoを実行。
		FARPROC fpProc = ::GetProcAddress(hXPI, "GetPluginInfo");
		char szTemp[20];
		if (fpProc == NULL
			|| ((int (PASCAL *)(int, char *, int)) fpProc)(2, szTemp, sizeof(szTemp)) <= 0)
		{
			::FreeLibrary(hXPI);
			nErrorCode = TPI_ERROR_U_USE_LIBRARY;
			break;
		}

		// コールバックを送信。
		piInfo.uStatus = TPI_STATUS_BEGINPROCESS;
		piInfo.fiInfo.fnFileName = wxFileName(_asFiles[i]);
		piInfo.fnDestination = piInfo.fiInfo.fnFileName;
		piInfo.fnDestination.SetPath(_swInfo->fnDestinationDirectory.GetPath());
		if (CallbackProc(0, 0, (long) & piInfo))
		{
			::FreeLibrary(hXPI);
			nErrorCode = TPI_ERROR_D_SKIPPED;
			break;
		}

		// 書き込み。
		piInfo.uStatus = TPI_STATUS_INPROCESS;
		fpProc = ::GetProcAddress(hXPI, "CreatePicture");
		if (fpProc == NULL)
		{
			::FreeLibrary(hXPI);
			nErrorCode = TPI_ERROR_U_USE_LIBRARY;
			break;
		}
		nErrorCode = ErrorCodeConvert(((int (PASCAL *)(const char *, long, HANDLE *, HANDLE *, PictureInfo *, FARPROC, long)) fpProc)(piInfo.fnDestination.GetFullPath().ToUTF8(), 0, & hInfo, & hMemory, & picInfo, (FARPROC) CallbackProc, (long) & piInfo));
		::FreeLibrary(hXPI);

		// 最後にコールバックを送信。
		piInfo.uStatus  = TPI_STATUS_ENDPROCESS;
		if (CallbackProc(0, 0, (long) & piInfo))
		{
			nErrorCode = TPI_ERROR_D_SKIPPED;
			break;
		}
	}
	::LocalUnlock(hInfo);
	::LocalUnlock(hMemory);
	::LocalUnlock(picInfo.hInfo);
	::LocalFree(hInfo);
	::LocalFree(hMemory);
	::LocalFree(picInfo.hInfo);
	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
		return nErrorCode;
	}

	// 最終のコールバック。
	piInfo.uStatus  = TPI_STATUS_CLOSEARCHIVE;
	piInfo.fiInfo.fnFileName = _fnArcName;
	if (CallbackProc(0, 0, (long) & piInfo))
	{
		return TPI_ERROR_D_SKIPPED;
	}

	return TPI_ERROR_SUCCESS;
}

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

	return TPI_ERROR_SUCCESS;
}

#ifdef __cplusplus
}
#endif
