/*******************************************************************************
  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: spiLibrary-wx.cpp,v 1.9 2009/08/23 04:25:53 sirakaba Exp $
*******************************************************************************/

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

#include "../../common/header/plugin.h"
#include "../../common/header/plugin-extra.h"
#include "../../common/library/library.h"
#include <wx/dynlib.h>
#include <wx/file.h>
#include <wx/config.h>
#include <wx/wfstream.h>
#include <wx/stdpaths.h>
#include <wx/filesys.h>
#include <wx/dir.h>
#include <windows.h>
#include <imagehlp.h>
#include "spiLibrary-wx.h"

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

HMODULE g_hLib;
TPI_PROC g_prProc;
unsigned int g_uFileCount;
unsigned int g_uFilePointer;
wxString g_szAppPath;

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

#ifdef __LINUX__
void __attribute__((constructor)) Attach(void)
{
	wxStandardPaths p;
	g_szAppPath = wxPathOnly(p.GetExecutablePath());
}

void __attribute__((destructor)) Detach(void)
{
	::FreeLibrary(g_hLib);
}

#else
BOOL __stdcall DllMain(HMODULE, DWORD fdwReason, void *)
{
	switch (fdwReason)
	{
	case DLL_PROCESS_ATTACH:
	{
		wxStandardPaths p;
		g_szAppPath = ::wxPathOnly(p.GetExecutablePath());
		break;
	}
	case DLL_PROCESS_DETACH:
		::FreeLibrary(g_hLib);
		break;
	}
	return TRUE;
}
#endif

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

int __stdcall CallbackProc(int _nNow, int _nMax, long _lData)
{
	// 構造体を初期化。
	TPI_PROCESSINFO piInfo;
	piInfo.uMessage = TPI_MESSAGE_STATUS;
	piInfo.uStatus  = _nNow == 0     ? TPI_STATUS_BEGINPROCESS
					: _nNow == _nMax ? TPI_STATUS_ENDPROCESS
					: _nNow <  0     ? TPI_STATUS_OPENARCHIVE : TPI_STATUS_INPROCESS;
	piInfo.llProcessedSize       = _nNow;
	piInfo.fiInfo.llUnpackedSize = _nMax;
	piInfo.fiInfo.fnFileName = * (wxFileName *) _lData;

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

	return g_prProc(TPI_NOTIFY_COMMON, & piInfo) == TPI_CALLBACK_CANCEL;
}

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

int SpiErrorCodeConvert(int nSpiErrorCode)
{
	switch (nSpiErrorCode)
	{
	case -1: return TPI_ERROR_U_USE_LIBRARY;
	case 0:  return TPI_ERROR_SUCCESS;
	case 1:  return TPI_ERROR_D_SKIPPED;
	case 2:  return TPI_ERROR_ARC_UNSUPPORTED;
	case 3:  return TPI_ERROR_ARC_BROKEN_MISC;
	case 4:  return TPI_ERROR_D_OUTOFMEMORY;
	case 5:  return TPI_ERROR_D_USEMEMORY;
	case 6:  return TPI_ERROR_IO_ARC_READ;
	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 (LOWORD(_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 LoadPlugin
(
	const wxString & _szArcName,
	wxULongLong
)
{
	wxDir fs(g_szAppPath + wxT("/lib/"));
	wxString szSPIName = wxEmptyString;
	bool b = fs.GetFirst(& szSPIName, wxT("*.spi"));
	while (b)
	{
		// SPIをロード。
		wxString szLibName = g_szAppPath + wxT("/lib/") + szSPIName;
		g_hLib = ::LoadLibraryA(szLibName.char_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] != 'A' || szPluginType[3] != 'M')
		{
			::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.char_str(), (unsigned long) buffer))
	{
		hFile.Close();
		return TPI_ERROR_D_UNSUPPORTED;
	}
	hFile.Close();

	fpProc = ::GetProcAddress(g_hLib, "GetArchiveInfo");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}

	HLOCAL hMemory;
	int nReturnCode = SpiErrorCodeConvert(((int (PASCAL *)(const char *, long, unsigned int, HLOCAL *)) fpProc)(_szArcName.char_str(), 0, 0, & hMemory));
	if (nReturnCode == TPI_ERROR_SUCCESS)
	{
		if (_nFileCount != NULL)
		{
			* _nFileCount = int(::LocalSize(hMemory) / sizeof(fileInfo));
		}
		::LocalFree(hMemory);
	}

	return nReturnCode;
}

int __stdcall OpenArchive
(
	const wxString & _szArcName,
	void * * _hArchive
)
{
	FARPROC fpProc = ::GetProcAddress(g_hLib, "GetArchiveInfo");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}

	int nReturnCode = SpiErrorCodeConvert(((int (PASCAL *)(const char *, long, unsigned int, HLOCAL *)) fpProc)(_szArcName.char_str(), 0, 0, _hArchive));
	if (nReturnCode != TPI_ERROR_SUCCESS)
	{
		return nReturnCode;
	}

	g_uFilePointer = 0;
	g_uFileCount = (unsigned int) (::LocalSize(* _hArchive) / sizeof(fileInfo));

	return * _hArchive == NULL ? TPI_ERROR_IO_ARC_OPEN : nReturnCode;
}

int __stdcall CloseArchive
(
	void * _hArchive
)
{
	return ::LocalFree(_hArchive) == NULL ? TPI_ERROR_SUCCESS : TPI_ERROR_IO_ARC_CLOSE;
}

int __stdcall GetFileInformation
(
	void * _hArchive,
	TPI_FILEINFO * _fiInfo,
	bool _bFirst
)
{
	if (_bFirst)
	{
		g_uFilePointer = 0;
	}

	if (g_uFilePointer > g_uFileCount)
	{
		return TPI_ERROR_UNDEFINED;
	}

	fileInfo pfiInfo = ((fileInfo *) ::LocalLock(_hArchive))[g_uFilePointer];
	if (pfiInfo.method[0] == 0)
	{
		// 丁寧なプラグインのための数合わせ (^^;
		g_uFileCount = g_uFilePointer;
		return TPI_ERROR_UNDEFINED;
	}

	_fiInfo->dwAttribute    = 0;
	_fiInfo->dwCRC32        = pfiInfo.crc;
	_fiInfo->llPackedSize   = pfiInfo.compsize;
	_fiInfo->llUnpackedSize = pfiInfo.filesize;
	_fiInfo->wCompressRatio = pfiInfo.compsize >= pfiInfo.filesize ? 1000 : (WORD) (1000 * pfiInfo.compsize / pfiInfo.filesize);
	_fiInfo->llFileID       = g_uFilePointer++;
	_fiInfo->tmModified     = pfiInfo.timestamp;
	_fiInfo->pCustomInfo    = NULL;
	_fiInfo->szStoredName   = MB2String(pfiInfo.path) + MB2String(pfiInfo.filename);
	_fiInfo->fnFileName     = wxFileName::FileName(_fiInfo->szStoredName);
	_fiInfo->szMethod       = MB2String((char *) pfiInfo.method);
	_fiInfo->pCustomInfo    = (void *) pfiInfo.position;
	::LocalUnlock(_hArchive);

	return TPI_ERROR_SUCCESS;
}

int __stdcall GetArchiveInformation
(
	void *, // _hArchive,
	TPI_ARCHIVEINFO * // _aiInfo
)
{
	// TODO : wxFileNameから情報取得。
	return TPI_ERROR_SUCCESS;
}

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

	// 展開処理のみを行う。
	FARPROC fpProc = ::GetProcAddress(g_hLib, "GetFile");
	if (fpProc == NULL)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}

	// 書庫ハンドルを取得。
	void * hArchive;
	int nResult = OpenArchive(_szArcName, & hArchive);
	if (nResult != TPI_ERROR_SUCCESS)
	{
		return nResult;
	}

	// コールバックを送信。便宜上-1を送信。
	wxFileName _fnArcName(_szArcName);
	if (CallbackProc(-1, 0, (long) & _fnArcName))
	{
		return TPI_ERROR_D_SKIPPED;
	}

	// 処理実行。
	TPI_FILEINFO fiInfo;
	nResult = GetFileInformation(hArchive, & fiInfo, TRUE);
	if (nResult == TPI_ERROR_SUCCESS)
	{
		do
		{
			// コールバックを送信。
			if (CallbackProc(0, fiInfo.llUnpackedSize.ToULong(), (long) & fiInfo.fnFileName))
			{
				nResult = TPI_ERROR_D_SKIPPED;
				break;
			}

			// 処理対象かどうか判定。
			if (_szFiles.Count() != 0)
			{
				size_t i;
				for (i = 0; i < _szFiles.Count(); i++)
				{
					if (_szFiles[i] == fiInfo.szStoredName)
					{
						break;
					}
				}
				if (i == _szFiles.Count())
				{
					continue;
				}
			}

			// 出力名作成。
			wxString szTargetPath = _swInfo->fnDestinationDirectory.GetPathWithSep();
			if (_swInfo->fStoreDirectoryPathes)
			{
				// 展開先ディレクトリを作成。
				szTargetPath += fiInfo.fnFileName.GetFullPath();
				wxFileName fnDest(szTargetPath);
				if (! fnDest.Mkdir(0777, wxPATH_MKDIR_FULL) || ::wxDirExists(fnDest.GetFullPath()))
				{
					nResult = TPI_ERROR_IO_DIR_WRITE;
					break;
				}
			}
			else
			{
				szTargetPath += fiInfo.fnFileName.GetFullName();
			}

			// ファイル出力には対応してないのでメモリ出力で代行。
			HLOCAL hMemory = NULL;
			nResult = SpiErrorCodeConvert(((int (PASCAL *)(char *, long, char *, unsigned int, FARPROC, long)) fpProc)(_szArcName.char_str(), (long) fiInfo.pCustomInfo, (char *) & hMemory, 0x0100, (FARPROC) CallbackProc, (long) & fiInfo.fnFileName));
			if (nResult == TPI_ERROR_SUCCESS && hMemory == NULL)
			{
				nResult = TPI_ERROR_UNDEFINED;
			}
			if (nResult != TPI_ERROR_SUCCESS)
			{
				break;
			}

			// 展開先に出力。
			wxFile hFile;
			// 強制上書きするので注意。
			if (! hFile.Create(szTargetPath, true))
			{
				nResult = TPI_ERROR_IO_FILE_OPEN;
				break;
			}

			bool bErrorOccured = hFile.Write(::LocalLock(hMemory), (size_t) fiInfo.llUnpackedSize.GetValue()) != fiInfo.llUnpackedSize;
			::LocalUnlock(hMemory);
			::LocalFree(hMemory);
			hFile.Close();
			if (bErrorOccured)
			{
				nResult = TPI_ERROR_IO_FILE_WRITE;
				break;
			}
			nResult = TPI_ERROR_SUCCESS;

			// コールバックを送信。
			if (CallbackProc(fiInfo.llUnpackedSize.ToULong(), fiInfo.llUnpackedSize.ToULong(), (long) & fiInfo.fnFileName))
			{
				nResult = TPI_ERROR_D_SKIPPED;
				break;
			}
		}
		while ((nResult = GetFileInformation(hArchive, & fiInfo, FALSE)) != TPI_ERROR_UNDEFINED);
		if (nResult == TPI_ERROR_UNDEFINED)
		{
			// 終端に達した場合。
			nResult = TPI_ERROR_SUCCESS;
		}
	}
	CloseArchive(hArchive);

	return nResult;
}

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

	return TPI_ERROR_SUCCESS;
}

#ifdef __cplusplus
}
#endif
