/*******************************************************************************
  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: cuiWrapper.cpp 125 2009-11-19 14:17:38Z sirakaba $
*******************************************************************************/

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

#include "../../common/header/plugin.h"
#include "../../common/header/plugin-extra.h"
#include "../../common/library/library.h"
#include <wx/config.h>
#include <wx/stdpaths.h>
#include <wx/xml/xml.h>
#include <wx/tokenzr.h>
#ifdef __WINDOWS__
#include <windows.h>
#endif
#include "cuiWrapper.h"

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

struct g_LibInfo
{
	wxString szExeFile;
	wxString szExeFileAlt;
	wxString szListCommand;
	int nLibIndex;
	wxXmlNode node;
}	g_LibInfo;

TPI_PROC g_prProc;
wxString g_szCurrentArchive;
wxArrayString g_asOutput;

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

#ifdef __WINDOWS__
BOOL __stdcall DllMain(HMODULE, DWORD, void *)
{
	return TRUE;
}
#endif

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

int myExecute(wxString szCommandLine, wxString * szOutput, wxString szCwd, bool bCheckExist = false)
{
#ifdef __LINUX__
	wxString sz = ::wxGetCwd();
	::wxSetWorkingDirectory(szCwd);
	FILE * fp = popen(szCommandLine.ToUTF8(), "r");
	::wxSetWorkingDirectory(sz);
	if (fp == NULL)
	{
//		wxMessageBox(wxString::Format(wxT("Error :\n\nCommandLine:\n%s"), szCommandLine.c_str()));
		return TPI_ERROR_U_USE_LIBRARY;
	}

	if (bCheckExist)
	{
		return TPI_ERROR_SUCCESS;
	}

	if (szOutput != NULL)
	{
		char sz[32769];
		while (! feof(fp))
		{
			memset(sz, 0, sizeof(sz));
			fread(sz, sizeof(char), sizeof(sz) - 1, fp);
			* szOutput += UTF82String(sz);
		}
//		::wxMessageBox(* szOutput);
	}

	pclose(fp);
	return TPI_ERROR_SUCCESS;
#else
	SECURITY_ATTRIBUTES sa;
	memset(& sa, 0, sizeof(SECURITY_ATTRIBUTES));
	sa.bInheritHandle = TRUE;
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	HANDLE hRead, hWrite;
	if (! ::CreatePipe(& hRead, & hWrite, & sa, 65537))
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	STARTUPINFO si;
	memset(& si, 0, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	si.dwFlags = STARTF_USESTDHANDLES;
	si.hStdOutput = hWrite;
	si.hStdError = hWrite;
	PROCESS_INFORMATION pi;
	if (! ::CreateProcess(NULL, szCommandLine.wchar_str(), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, szCwd.IsEmpty() ? NULL : szCwd.wchar_str(), & si, & pi))
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}

	::WaitForInputIdle(pi.hProcess, INFINITE);
	::WaitForSingleObject(pi.hProcess, INFINITE);

	DWORD nErrorCode = 0;
	::GetExitCodeProcess(pi.hProcess, & nErrorCode);
	::CloseHandle(pi.hThread);
	::CloseHandle(pi.hProcess);

	if (bCheckExist)
	{
		return TPI_ERROR_SUCCESS;
	}

	if (szOutput != NULL)
	{
		DWORD dwSize = 0;
		char sz[32769];
		for (wxULongLong llSize = 0; llSize < ::GetFileSize(hRead, NULL); llSize += dwSize)
		{
			memset(sz, 0, sizeof(sz));
			dwSize = 0;
			::ReadFile(hRead, sz, sizeof(sz) - 1, & dwSize, NULL);
			* szOutput += UTF82String(sz);
//			::MessageBoxA(NULL, sz, NULL, 0);
		}
	}
	::CloseHandle(hRead);
	::CloseHandle(hWrite);

	return nErrorCode == 0 ? TPI_ERROR_SUCCESS : TPI_ERROR_UNDEFINED;
#endif
}

PosInfo MakePosInfo(wxString szPrefix)
{
	PosInfo pi;
	g_LibInfo.node.GetPropVal(wxT("list-") + szPrefix + wxT("-s"), wxEmptyString).ToULong(& pi.nStart);
	g_LibInfo.node.GetPropVal(wxT("list-") + szPrefix + wxT("-c"), wxEmptyString).ToULong(& pi.nCount);
	g_LibInfo.node.GetPropVal(wxT("list-") + szPrefix + wxT("-l"), wxT("0")).ToULong(& pi.nLine);
	return pi;
}

wxULongLong GetSize(PosInfo & pi, size_t uCurrent, wxArrayString & as)
{
	if (pi.nStart == 0 && pi.nCount == 0)
	{
		return 0;
	}

	wxULongLong_t nTemp;
	size_t nPos = uCurrent + pi.nLine;
	wxString sz = pi.nCount == 0 ? as[nPos].Mid(pi.nStart) : as[nPos].Mid(pi.nStart, pi.nCount);
	sz.ToULongLong(& nTemp);
	return nTemp;
}

//******************************************************************************
//    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_UNSUPPORTED;
	}
	return TPI_ERROR_SUCCESS;
}

int __stdcall GetFormatInformation(TPI_FORMATINFO * _fiInfo, bool _bFirst)
{
	static unsigned int s_uFileID;
	wxStandardPaths p;
	wxXmlDocument config(wxPathOnly(p.GetExecutablePath()) + wxT("/lib/cuiWrapper.xml"));
	// 一気に先頭のライブラリの情報を取得。
	wxXmlNode * xmlLibrary = config.GetRoot()->GetChildren();

	if (_bFirst)
	{
		// xml解析開始。
		s_uFileID = 0;
	}
	else
	{
		for (unsigned int i = 0; i < s_uFileID && xmlLibrary != NULL; i++)
		{
			xmlLibrary = xmlLibrary->GetNext();
		}
	}

	if (xmlLibrary == NULL || xmlLibrary->GetName() != wxT("library"))
	{
		// 終端に達した場合。
		return TPI_ERROR_S_ENDOFDATA;
	}

	MakeFormatInfo(wxT("cuiWrapper"), _fiInfo, xmlLibrary, s_uFileID++);
	if (myExecute(xmlLibrary->GetPropVal(wxT("name"), wxEmptyString), NULL, wxEmptyString, true) != TPI_ERROR_SUCCESS)
	{
		_fiInfo->llSupportedCommand = 0;
	}

	return TPI_ERROR_SUCCESS;
}

int __stdcall LoadPlugin
(
	const wxString & _szArcName,
	wxULongLong _llSubOption
)
{
	// xml解析開始。
	wxStandardPaths p;
	wxXmlDocument config(wxPathOnly(p.GetExecutablePath()) + wxT("/lib/cuiWrapper.xml"));
	if (! config.IsOk())
	{
		return TPI_ERROR_UNDEFINED;
	}
	// 一気に先頭のライブラリの情報を取得。
	wxXmlNode * xmlLibrary = config.GetRoot()->GetChildren();

	// 対象が存在するならば対応するライブラリを調査、
	// 対象が存在しないならば指示されたライブラリをロード。
	if (! ::wxFileExists(_szArcName))
	{
		// 適当な位置まで移動。
		for (g_LibInfo.nLibIndex = 0; g_LibInfo.nLibIndex < _llSubOption && xmlLibrary != NULL; g_LibInfo.nLibIndex++)
		{
			xmlLibrary = xmlLibrary->GetNext();
		}
		if (xmlLibrary == NULL || xmlLibrary->GetName() != wxT("library"))
		{
			// xml文法エラー。
			return TPI_ERROR_UNDEFINED;
		}
		g_LibInfo.szExeFile = xmlLibrary->GetPropVal(wxT("name"), wxEmptyString);
		g_LibInfo.szExeFileAlt = xmlLibrary->GetPropVal(wxT("name-alt"), wxEmptyString);
		g_LibInfo.node = * xmlLibrary;

		return TPI_ERROR_SUCCESS;
	}

	// 無限ループに陥らないよう上限を設定。
	for (g_LibInfo.nLibIndex = 0; g_LibInfo.nLibIndex < 300 && xmlLibrary != NULL; g_LibInfo.nLibIndex++)
	{
		// ライブラリをロード。
		g_LibInfo.szExeFile = xmlLibrary->GetPropVal(wxT("name"), wxEmptyString);
		g_LibInfo.szExeFileAlt = xmlLibrary->GetPropVal(wxT("name-alt"), wxEmptyString);
		g_LibInfo.node = * xmlLibrary;

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

		xmlLibrary = xmlLibrary->GetNext();
	}

	return TPI_ERROR_U_LOAD_LIBRARY;
}

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

int __stdcall CheckArchive
(
	const wxString & _szArcName,
	int * _nFileCount
)
{
	wxFileName fnArchive(_szArcName);
	wxArrayString asExt = ::wxStringTokenize(g_LibInfo.node.GetPropVal(wxT("suffix"), wxEmptyString), wxT(";"));
	if (! g_LibInfo.node.HasProp(wxT("list")))
	{
		return TPI_ERROR_ARC_UNSUPPORTED;
	}

	for (size_t i = 0; i < asExt.GetCount(); i++)
	{
		// .tar.XXXなど二重判定への対応。
//		if (asExt[i].IsSameAs(fnArchive.GetExt(), false))
		if (fnArchive.GetFullName().EndsWith(wxT('.') + asExt[i]))
		{
			// 開いて確認。先行してデータを取得しておく。
			int nErrorCode = OpenArchive(_szArcName, NULL);

			// 対応。
			if (_nFileCount != NULL)
			{
				// ファイル数は多めに取っておく。
				* _nFileCount = g_asOutput.GetCount();
			}
			return nErrorCode;
		}
	}

	return TPI_ERROR_ARC_UNSUPPORTED;
}

int __stdcall OpenArchive
(
	const wxString & _szArcName,
	void * * _hArchive
)
{
	if (g_szCurrentArchive != _szArcName)
	{
		wxString szOutput;
		if (myExecute(g_LibInfo.szExeFile + wxT(" ") + MakeCommandLineSend(g_LibInfo.node.GetPropVal(wxT("list"), wxEmptyString), _szArcName, NULL, NULL, wxEmptyString), & szOutput, wxEmptyString) != TPI_ERROR_SUCCESS)
		{
			return TPI_ERROR_U_USE_LIBRARY;
		}
		g_szCurrentArchive = _szArcName;
		g_asOutput = ::wxStringTokenize(szOutput, wxT("\r\n"));
		g_asOutput.Shrink();
	}
	if (_hArchive != NULL)
	{
		* _hArchive = & g_asOutput;
	}
	return g_asOutput.IsEmpty() ? TPI_ERROR_UNDEFINED : TPI_ERROR_SUCCESS;
}

int __stdcall CloseArchive
(
	void * _hArchive
)
{
	((wxArrayString *) _hArchive)->Clear();
	return TPI_ERROR_SUCCESS;
}

int __stdcall GetFileInformation
(
	void * _hArchive,
	TPI_FILEINFO * _fiInfo,
	bool _bFirst
)
{
	static size_t s_uCurrentLine;
	static wxULongLong s_llFileID;
	if (_hArchive == NULL)
	{
		return TPI_ERROR_UNDEFINED;
	}
	wxArrayString asOutput = * (wxArrayString *) _hArchive;

	// XMLからの読み込みは初回に行う。
	static wxString szEndLine, szDateFormat;
	static unsigned long nProcessPerLine;
	static PosInfo piFName, piPSize, piUSize, piDate;
	if (_bFirst)
	{
		s_llFileID = 0;
		wxString szStartLine = g_LibInfo.node.GetPropVal(wxT("list-line-s"), wxEmptyString);
		if (! szStartLine.IsEmpty())
		{
			// 開始行の次の行にセット。
			s_uCurrentLine = asOutput.Index(szStartLine) + 1;
			if (s_uCurrentLine == wxNOT_FOUND + 1)
			{
				// 書庫が読み込めなかった？
				return TPI_ERROR_ARC_UNSUPPORTED;
			}
		}

		// 初期設定。
		g_LibInfo.node.GetPropVal(wxT("list-line-c"), wxT("1")).ToULong(& nProcessPerLine);
		szEndLine = g_LibInfo.node.GetPropVal(wxT("list-line-e"), szStartLine);
		szDateFormat = g_LibInfo.node.GetPropVal(wxT("list-date-f"), wxDefaultDateTimeFormat);
		piFName = MakePosInfo(wxT("fname"));
		piPSize = MakePosInfo(wxT("psize"));
		piUSize = MakePosInfo(wxT("usize"));
		piDate = MakePosInfo(wxT("date"));
	}

	if (s_uCurrentLine >= asOutput.GetCount())
	{
		// 書庫が読み込めなかった？
		return TPI_ERROR_ARC_UNSUPPORTED;
	}

	// 最終行かどうか確認。
	if (asOutput[s_uCurrentLine] == szEndLine)
	{
		return TPI_ERROR_S_ENDOFDATA;
	}

	// ファイル名を取得。
	_fiInfo->szStoredName = piFName.nCount == 0 ? asOutput[s_uCurrentLine + piFName.nLine].Mid(piFName.nStart) : asOutput[s_uCurrentLine + piFName.nLine].Mid(piFName.nStart, piFName.nCount);
	_fiInfo->szStoredName.Trim();
	_fiInfo->fnFileName = wxFileName::wxFileName(_fiInfo->szStoredName);

	// サイズ取得。
	_fiInfo->llPackedSize   = GetSize(piPSize, s_uCurrentLine, asOutput);
	_fiInfo->llUnpackedSize = GetSize(piUSize, s_uCurrentLine, asOutput);

	// 更新時刻取得。
	if (piDate.nStart != 0 || piDate.nCount != 0)
	{
		_fiInfo->tmModified.ParseFormat(piDate.nCount == 0 ? asOutput[s_uCurrentLine + piDate.nLine].Mid(piDate.nStart) : asOutput[s_uCurrentLine + piDate.nLine].Mid(piDate.nStart, piDate.nCount), szDateFormat);
	}

	// 最後に次の行へ進めておく。
	_fiInfo->llFileID = s_llFileID++;
	s_uCurrentLine += nProcessPerLine;

	return TPI_ERROR_SUCCESS;
}

int __stdcall GetArchiveInformation
(
	void *,
	TPI_ARCHIVEINFO * _aiInfo
)
{
	// 形式に関する情報を取得。
	MakeFormatInfo(wxT("cuiWrapper"), & _aiInfo->fiInfo, & g_LibInfo.node, 0);
	return TPI_ERROR_SUCCESS;
}

int __stdcall Command
(
	unsigned int _uCommand,
	TPI_SWITCHES * _swInfo,
	const wxString & _szArcName,
	const wxArrayString & _szFiles
)
{
	// xmlからコマンドラインを取得。
	wxString szPath, szCommandLine;

	// APIアドレス取得。
	if (! g_LibInfo.node.GetPropVal(
			_uCommand == TPI_COMMAND_ADD     ? wxT("add") :
			_uCommand == TPI_COMMAND_EXTRACT ? wxT("extract") : 
			_uCommand == TPI_COMMAND_DELETE  ? wxT("delete") : 
			_uCommand == TPI_COMMAND_UPDATE  ? wxT("update") : 
			_uCommand == TPI_COMMAND_TEST    ? wxT("test") : 
			_uCommand == TPI_COMMAND_REPAIR  ? wxT("repair") : 
			_uCommand == TPI_COMMAND_MOVE    ? wxT("move") : 
			_uCommand == TPI_COMMAND_SFX     ? wxT("sfx") : 
			_uCommand == TPI_COMMAND_UNSFX   ? wxT("unsfx") : wxEmptyString, & szCommandLine))
	{
		g_LibInfo.node.GetPropVal(
			_uCommand == TPI_COMMAND_ADD     ? wxT("add-alt") :
			_uCommand == TPI_COMMAND_EXTRACT ? wxT("extract-alt") : 
			_uCommand == TPI_COMMAND_DELETE  ? wxT("delete-alt") : 
			_uCommand == TPI_COMMAND_UPDATE  ? wxT("update-alt") : 
			_uCommand == TPI_COMMAND_TEST    ? wxT("test-alt") : 
			_uCommand == TPI_COMMAND_REPAIR  ? wxT("repair-alt") : 
			_uCommand == TPI_COMMAND_MOVE    ? wxT("move-alt") : 
			_uCommand == TPI_COMMAND_SFX     ? wxT("sfx-alt") : 
			_uCommand == TPI_COMMAND_UNSFX   ? wxT("unsfx-alt") : wxEmptyString, & szCommandLine);
	}

	if (szCommandLine.IsEmpty())
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}

	// コマンドライン・レスポンスファイル作成。
	wxString
		szResponceFileName = MakeResponceFile(_szFiles, g_LibInfo.node.GetPropVal(wxT("quote-resp"), wxT("1")) == wxT("1")),
		szCommandLineSend  = MakeCommandLineSend(szCommandLine, _szArcName, _swInfo, _szFiles, szResponceFileName);

	// コマンドライン実行。
	wxString szOutput;
	int nErrorCode = myExecute(g_LibInfo.szExeFile + wxT(" ") + szCommandLineSend, & szOutput, _swInfo->fnDestinationDirectory.GetFullPath());

	// レスポンスファイル削除。
	::wxRemoveFile(szResponceFileName);

	if (nErrorCode != TPI_ERROR_SUCCESS)
	{
#ifdef __LINUX__
		::wxMessageBox(wxString::Format(wxT("Error :\n%d\n\nCommandLine:\n%s\n\nOutput:\n%s"), nErrorCode, szCommandLineSend.c_str(), szOutput.c_str()));
#else
		MessageBox(NULL, wxString::Format(wxT("Error :\n%d\n\nCommandLine:\n%s\n\nOutput:\n%s"), nErrorCode, szCommandLineSend.c_str(), szOutput.c_str()), NULL, 0);
#endif
	}
	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
