/*******************************************************************************
  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: rarArc.cpp 346 2010-09-24 14:36:04Z sirakaba $
*******************************************************************************/

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

#define wxUSE_DYNLIB_CLASS 1
#include "../../common/header/plugin.h"
#include "../../common/header/plugin-extra.h"
#include "../../common/library/library.h"
#include <wx/dynlib.h>
#include "rarArc.h"

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

wxDynamicLibrary g_hLib;
TPI_PROC g_prProc;
char g_szComment[64001];
RAROpenArchiveDataEx g_oaInfo;
RARHeaderDataEx * g_hdInfo;

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

int __stdcall CallbackProc(unsigned int msg, long, long P1, long P2)
{
	// 構造体を初期化。
	static TPI_PROCESSINFO piInfo;

	switch (msg)
	{
	case UCM_CHANGEVOLUME:
		switch (P2)
		{
		case RAR_VOL_ASK:
			// 分割書庫の次の部分を要求。
			piInfo.eMessage = TPI_MESSAGE_ASK;
			piInfo.eStatus  = TPI_PARAM_NEXTVOLUME;
			piInfo.szParam.Empty();
			break;
		case RAR_VOL_NOTIFY:
			// 分割部分の読み込みを開始。
			piInfo.eMessage = TPI_MESSAGE_STATUS;
			piInfo.eStatus  = TPI_STATUS_OPENARCHIVE;
			piInfo.fiInfo.fnFileName = wxFileName(UTF82String((char *) P1));
			break;
		default:
			return 1;
		}
		break;
	case UCM_PROCESSDATA:
		piInfo.eMessage = TPI_MESSAGE_STATUS;
		piInfo.eStatus  = TPI_STATUS_INPROCESS;
		piInfo.nProcessedSize += P2;
		break;
	case UCM_NEEDPASSWORD:
		piInfo.eMessage = TPI_MESSAGE_ASK;
		piInfo.eStatus  = TPI_PARAM_PASSWORD;
		piInfo.szParam.Empty();
		break;
	default:
		return 1;
	}

	// コールバック関数に送信。
	if (g_prProc == NULL || g_prProc(TPI_NOTIFY_COMMON, & piInfo) == TPI_CALLBACK_CONTINUE)
	{
		if (msg != UCM_PROCESSDATA && P2 != RAR_VOL_NOTIFY)
		{
			strncpy((char *) P1, piInfo.szParam.char_str(), (msg == UCM_CHANGEVOLUME ? 1024 : P2) - 1);
		}
		return 1;
	}
	else
	{
		return -1;
	}
}

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

int ErrorCodeConvert(int nErrorCode)
{
	switch (nErrorCode)
	{
	case 0:                     return TPI_ERROR_SUCCESS;
	case 1:                     return TPI_ERROR_SUCCESS;
	case ERAR_END_ARCHIVE:      return TPI_ERROR_S_ENDOFDATA;
	case ERAR_NO_MEMORY:        return TPI_ERROR_D_OUTOFMEMORY;
	case ERAR_BAD_DATA:         return TPI_ERROR_ARC_BROKEN_MISC;
	case ERAR_BAD_ARCHIVE:      return TPI_ERROR_ARC_UNSUPPORTED;
	case ERAR_UNKNOWN_FORMAT:   return TPI_ERROR_ARC_ENCRYPTED;
	case ERAR_EOPEN:            return TPI_ERROR_IO_ARC_OPEN;
	case ERAR_ECREATE:          return TPI_ERROR_IO_FILE_OPEN;
	case ERAR_ECLOSE:           return TPI_ERROR_IO_CLOSE;
	case ERAR_EREAD:            return TPI_ERROR_IO_ARC_READ;
	case ERAR_EWRITE:           return TPI_ERROR_IO_FILE_WRITE;
	case ERAR_SMALL_BUF:        return TPI_ERROR_UNDEFINED;
	case ERAR_UNKNOWN:          return TPI_ERROR_UNDEFINED;
	case ERAR_MISSING_PASSWORD: return TPI_ERROR_ARC_ENCRYPTED;
	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_UNSUPPORTED;
	}
	return TPI_ERROR_SUCCESS;
}

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

	_fiInfo->szTypeName   = wxT("RAR");
	_fiInfo->szSuffix     = wxT("rar");
	_fiInfo->szEngineName = g_hLib.CanonicalizeName(wxT("unrar"));
	_fiInfo->szTPIName    = wxT("rarArc");
	_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.Load(g_hLib.CanonicalizeName(wxT("unrar")));
	if (! g_hLib.IsLoaded())
	{
		g_hLib.Unload();
		return TPI_ERROR_U_LOAD_LIBRARY;
	}

	return TPI_ERROR_SUCCESS;
}

int __stdcall FreePlugin
(
	void * // _pReserved
)
{
	g_hLib.Unload();
	return TPI_ERROR_SUCCESS;
}

int __stdcall OpenArchive
(
	const wxString & _szArcName,
	void * * _hArchive,
	wxULongLong_t *
)
{
	if (! g_hLib.HasSymbol(wxT("RAROpenArchiveEx")))
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	void * p = g_hLib.GetSymbol(wxT("RAROpenArchiveEx"));
	if (! p)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}

	memset(& g_oaInfo, 0, sizeof(g_oaInfo));
	g_oaInfo.ArcName    = NULL;
	g_oaInfo.ArcNameW   = (wchar_t *) malloc((_szArcName.Len() + 1) * sizeof(wchar_t));
	wcscpy(g_oaInfo.ArcNameW, _szArcName.wchar_str());
	g_oaInfo.OpenMode   = RAR_OM_EXTRACT;
	g_oaInfo.CmtBuf     = g_szComment;
	g_oaInfo.CmtBufSize = sizeof(g_szComment) - 1;
	* _hArchive = ((void * (__stdcall *)(RAROpenArchiveDataEx *)) p)(& g_oaInfo);
	free(g_oaInfo.ArcNameW);
	if (* _hArchive == NULL)
	{
		return TPI_ERROR_UNDEFINED;
	}
	int nErrorCode = ErrorCodeConvert(g_oaInfo.OpenResult);
	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
		return nErrorCode;
	}

	// コールバック関数を設定。
	if (! g_hLib.HasSymbol(wxT("RARSetCallback")))
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	p = g_hLib.GetSymbol(wxT("RARSetCallback"));
	if (! p)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	((void (__stdcall *)(void *, void *, long)) p)(* _hArchive, (void *) CallbackProc, (long) & g_hdInfo);

	return TPI_ERROR_SUCCESS;
}

int __stdcall CloseArchive
(
	void * _hArchive
)
{
	if (! g_hLib.HasSymbol(wxT("RARCloseArchive")))
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	void * p = g_hLib.GetSymbol(wxT("RARCloseArchive"));
	return (! p || _hArchive == NULL) ? TPI_ERROR_U_USE_LIBRARY : ErrorCodeConvert(((int (__stdcall *)(void *)) p)(_hArchive));
}

int __stdcall GetFileInformation
(
	void * _hArchive,
	TPI_FILEINFO * _fiInfo,
	bool _bFirst
)
{
	static wxULongLong_t s_nFileId;
	static void * pR, * pP;
	int nErrorCode;

	if (_bFirst)
	{
		s_nFileId = 0;
		pR = g_hLib.HasSymbol(wxT("RARReadHeaderEx")) ? g_hLib.GetSymbol(wxT("RARReadHeaderEx")) : NULL;
		pP = g_hLib.HasSymbol(wxT("RARProcessFileW"))  ? g_hLib.GetSymbol(wxT("RARProcessFileW")) : NULL;
		if (! pR || ! pP)
		{
			return TPI_ERROR_U_USE_LIBRARY;
		}
	}

	RARHeaderDataEx hdInfo;
	char szComment[64001];
	hdInfo.CmtBuf     = szComment;
	hdInfo.CmtBufSize = sizeof(szComment) - 1;
	nErrorCode = ErrorCodeConvert(((int (__stdcall *)(void *, RARHeaderDataEx *)) pR)(_hArchive, & hdInfo));
	if (nErrorCode == TPI_ERROR_SUCCESS)
	{
		_fiInfo->dwAttribute    = hdInfo.FileAttr;
		if (hdInfo.Flags & 0x04)
		{
			_fiInfo->dwAttribute |= TPI_ATTRIBUTE_ENCRYPTED;
		}
		_fiInfo->dwCRC32        = hdInfo.FileCRC;
		_fiInfo->nPackedSize    = hdInfo.PackSizeHigh;
		_fiInfo->nPackedSize    = _fiInfo->nPackedSize << 32;
		_fiInfo->nPackedSize   += hdInfo.PackSize;
		_fiInfo->nUnpackedSize  = hdInfo.UnpSizeHigh;
		_fiInfo->nUnpackedSize  = _fiInfo->nUnpackedSize << 32;
		_fiInfo->nUnpackedSize += hdInfo.UnpSize;
		_fiInfo->tmModified.SetFromDOS(hdInfo.FileTime);
		_fiInfo->eOSType        = hdInfo.HostOS;
		_fiInfo->szStoredName   = WC2String(hdInfo.FileNameW);
		_fiInfo->szMethod.Printf(wxT("%x"), hdInfo.Method);
		_fiInfo->szComment      = UTF82String(hdInfo.CmtBuf);
		_fiInfo->nFileId        = s_nFileId++;
		_fiInfo->fnFileName     = wxFileName(_fiInfo->szStoredName);

		// 次のファイルへ。
		nErrorCode = ErrorCodeConvert(((int (__stdcall *)(void *, int, wchar_t *, wchar_t *)) pP)(_hArchive, RAR_SKIP, NULL, NULL));
	}

	return nErrorCode;
}

int __stdcall GetArchiveInformation
(
	void *,
	TPI_ARCHIVEINFO * _aiInfo
)
{
	_aiInfo->fSolid         = (g_oaInfo.Flags & 0x0008) == 1;
	_aiInfo->fEncryptHeader = (g_oaInfo.Flags & 0x0080) == 1;
	_aiInfo->szComment      = UTF82String(g_szComment);
	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;
	}

	void
		* pS = g_hLib.HasSymbol(wxT("RARSetPassword"))  ? g_hLib.GetSymbol(wxT("RARSetPassword"))  : NULL,
		* pR = g_hLib.HasSymbol(wxT("RARReadHeaderEx")) ? g_hLib.GetSymbol(wxT("RARReadHeaderEx")) : NULL,
		* pP = g_hLib.HasSymbol(wxT("RARProcessFileW")) ? g_hLib.GetSymbol(wxT("RARProcessFileW")) : NULL;
	if (! pR || ! pP)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	if (pS)
	{
		((void (__stdcall *)(void *, char *)) pS)(hArc, _swInfo->szPassword.char_str());
	}

	RARHeaderDataEx hdInfo;
	g_hdInfo = & hdInfo;
	while (nErrorCode == TPI_ERROR_SUCCESS && ErrorCodeConvert(((int (__stdcall *)(void *, RARHeaderDataEx *)) pR)(hArc, & hdInfo)) == TPI_ERROR_SUCCESS)
	{
		TPI_PROCESSINFO piInfo;
		piInfo.fiInfo.fnFileName     = wxFileName(WC2String(hdInfo.FileNameW));
		piInfo.fnDestination         = wxFileName(_swInfo->fnDestinationDirectory.GetPathWithSep() + (_swInfo->fStoreDirectoryPathes ? piInfo.fiInfo.fnFileName.GetFullPath() : piInfo.fiInfo.fnFileName.GetFullName()));
		bool bSkip = _szFiles.GetCount() != 0 && _szFiles.Index(piInfo.fiInfo.fnFileName.GetFullPath()) == wxNOT_FOUND;
		if (! bSkip && _eCommand == TPI_COMMAND_EXTRACT)
		{
			// 処理するかどうか確認。
			piInfo.eMessage = TPI_MESSAGE_ASK;
			piInfo.eStatus  = TPI_PARAM_DEST;
			piInfo.fiInfo.nUnpackedSize  = hdInfo.UnpSizeHigh;
			piInfo.fiInfo.nUnpackedSize  = piInfo.fiInfo.nUnpackedSize << 32;
			piInfo.fiInfo.nUnpackedSize += hdInfo.UnpSize;
			piInfo.fiInfo.tmModified.SetFromDOS(hdInfo.FileTime);
			if (g_prProc != NULL && g_prProc(TPI_NOTIFY_COMMON, & piInfo) != TPI_CALLBACK_CONTINUE)
			{
				nErrorCode = TPI_ERROR_D_SKIPPED;
				break;
			}
			bSkip = ! piInfo.fnDestination.IsOk();
		}

		nErrorCode = ErrorCodeConvert(((int (__stdcall *)(void *, int, wchar_t *, wchar_t *)) pP)(hArc, bSkip ? RAR_SKIP : _eCommand == TPI_COMMAND_EXTRACT ? RAR_EXTRACT : RAR_TEST, NULL, piInfo.fnDestination.GetFullPath().wchar_str()));
	}
	CloseArchive(hArc);
	return nErrorCode;
}

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

	return TPI_ERROR_SUCCESS;
}

#ifdef __cplusplus
}
#endif
