/*******************************************************************************
  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 346 2010-09-24 14:36:04Z 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;

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

int __stdcall CallbackProc(int _nNow, int _nMax, long _lData)
{
	// 構造体を初期化。
	TPI_PROCESSINFO * piInfo = (TPI_PROCESSINFO *) _lData;
	piInfo->nProcessedSize          = _nNow;
	piInfo->fiInfo.nUnpackedSize    = _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_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_PARAMETER;
	}
	return TPI_ERROR_SUCCESS;
}

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

int __stdcall LoadPlugin
(
	const wxString & _szArcName,
	wxULongLong_t
)
{
	// ファイルを開く。
	wxFile hFile;
	if (! hFile.Exists(_szArcName) || ! hFile.Open(_szArcName, wxFile::read))
	{
		return TPI_ERROR_IO_ARC_OPEN;
	}

	// 最初の2KBを読み込み。
	char buffer[2050];
	::ZeroMemory(buffer, sizeof(buffer));
	if (hFile.Read(buffer, sizeof(buffer)) == wxInvalidOffset)
	{
		hFile.Close();
		return TPI_ERROR_IO_ARC_READ;
	}
	hFile.Close();

	wxStandardPaths p;
	wxString szSPIPath = wxPathOnly(p.GetExecutablePath()) + wxT("/lib/"), szSPIName;
	wxDir fs(szSPIPath);
	if (fs.GetFirst(& szSPIName, wxT("*.spi")))
	{
		do
		{
			// SPIをロード。
			g_hLib = ::LoadLibrary((szSPIPath + szSPIName).wchar_str());
			if (g_hLib == NULL)
			{
				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);
				continue;
			}

			// 書庫に対応しているかチェック。
			fpProc = ::GetProcAddress(g_hLib, "IsSupported");
			if (fpProc == NULL)
			{
				::FreeLibrary(g_hLib);
				continue;
			}

			if (((BOOL (PASCAL *)(const char *, unsigned long)) fpProc)(_szArcName.ToUTF8(), (unsigned long) buffer))
			{
				// 対応していれば処理を終了。
				return TPI_ERROR_SUCCESS;
			}
		}
		while (fs.GetNext(& szSPIName));
	}
	return TPI_ERROR_U_LOAD_LIBRARY;
}

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

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

int __stdcall CloseArchive
(
	void * _hArchive
)
{
	delete (wxString *) _hArchive;
	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;
	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);
	}

	if (_bFirst ? fs.GetFirst(& szXPIName, wxT("*.xpi")) : fs.GetNext(& szXPIName))
	{
		do
		{
			// XPIをロード。
			HMODULE hXPI = ::LoadLibrary((szXPIPath + szXPIName).wchar_str());
			if (hXPI == NULL)
			{
				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);
				continue;
			}

			// 対応確認。
			fpProc = ::GetProcAddress(hXPI, "IsSupported");
			if (fpProc == NULL || ! ((BOOL (PASCAL *)(int)) fpProc)(nColorDepth))
			{
				::FreeLibrary(hXPI);
				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;
		}
		while (fs.GetNext(& szXPIName));
	}
	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.eSupportedCommand = TPI_COMMAND_EXTRACT;

	return TPI_ERROR_SUCCESS;
}

int __stdcall Command
(
	wxULongLong_t _eCommand,
	TPI_SWITCHES * _swInfo,
	const wxString & _szArcName,
	const wxArrayString & _asFiles
)
{
	if (_eCommand != 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.eMessage = TPI_MESSAGE_STATUS;
	piInfo.eStatus  = 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;
	}

	wxString szXPIPath;
	{
		wxStandardPaths p;
		szXPIPath = wxPathOnly(p.GetExecutablePath()) + wxT("/lib/");
	}

	for (size_t i = 0; i < _asFiles.GetCount(); i++)
	{
		// 処理を行うか確認。
		piInfo.eMessage = TPI_MESSAGE_ASK;
		piInfo.eStatus = TPI_PARAM_DEST;
		piInfo.fiInfo.fnFileName = wxFileName(_asFiles[i]);
		piInfo.fnDestination = piInfo.fiInfo.fnFileName;
		piInfo.fnDestination.SetPath(_swInfo->fnDestinationDirectory.GetPath());
		if (CallbackProc(0, 0, (long) & piInfo))
		{
			nErrorCode = TPI_ERROR_D_SKIPPED;
			break;
		}
		if (! piInfo.fnDestination.IsOk())
		{
			continue;
		}

		// XPIをロード。
		HMODULE hXPI = ::LoadLibrary((szXPIPath + piInfo.fiInfo.fnFileName.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.eMessage = TPI_MESSAGE_STATUS;
		piInfo.eStatus = TPI_STATUS_BEGINPROCESS;
		if (CallbackProc(0, 0, (long) & piInfo))
		{
			::FreeLibrary(hXPI);
			nErrorCode = TPI_ERROR_D_SKIPPED;
			break;
		}

		// 書き込み。
		piInfo.eStatus = 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.eStatus  = 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.eMessage = TPI_MESSAGE_STATUS;
	piInfo.eStatus  = 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
