/*******************************************************************************
  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,v 1.19 2009/08/29 09:05:50 sirakaba Exp $
*******************************************************************************/

//******************************************************************************
//    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>
#include <windows.h>
#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_szAppPath;
wxArrayString g_asOutput;

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

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

void __attribute__((destructor)) Detach(void)
{
}
#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:
		break;
	}
	return TRUE;
}
#endif

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

DWORD myExecute(wxString szCommandLine, wxString * szOutput, wxString szCwd, bool bCheckExist = false)
{
#ifdef __LINUX__
	wxString sz = ::wxGetCwd();
	::wxSetWorkingDirectory(szCwd);
	FILE * fp = popen(szCommandLine.char_str(), "r");
	::wxSetWorkingDirectory(sz);
	if (fp == NULL)
	{
//		wxMessageBox(wxString::Format(wxT("Error :\n\nCommandLine:\n%s"), szCommandLine.c_str()));
		return 1;
	}
#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 1;
	}
	STARTUPINFO si;
	memset(& si, 0, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	si.dwFlags = STARTF_USESTDHANDLES;
	si.hStdOutput = 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))
	{
//		MessageBox(NULL, wxString::Format(wxT("Error :\n\nCommandLine:\n%s"), szCommandLine.c_str()).c_str(), NULL, 0);
		return 1;
	}
#endif

	// 実行できることを確認したいだけの場合は終了。
	if (bCheckExist)
	{
		return 0;
	}

#ifdef __LINUX__
	if (szOutput != NULL)
	{
		char sz[32769];
		memset(sz, 0, sizeof(sz));
		while (! feof(fp))
		{
			fread(sz, sizeof(char), sizeof(sz) - 1, fp);
			* szOutput += MB2String(sz);
		}
//		::wxMessageBox(* szOutput);
	}
	pclose(fp);
	return 0;
#else
	// 念のために上限を設定しておく。
	::WaitForInputIdle(pi.hProcess, 2000);
	::WaitForSingleObject(pi.hProcess, 2000);

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

	if (szOutput != NULL)
	{
		DWORD dwSize = 0;
		if (! ::PeekNamedPipe(hRead, NULL, 0, NULL, & dwSize, NULL) || dwSize <= 0)
		{
			return 1;
		}

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

	return nErrorCode;
#endif
}

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

#ifdef __cplusplus
extern "C"
{
#endif

int __stdcall GetPluginInformation
(
	unsigned int _uInfoId,
	wxULongLong _llSubOption,
	void * _pPtr
)
{
	// パス設定。TODO : 削除。
	wxStandardPaths p;
	g_szAppPath = wxPathOnly(p.GetExecutablePath());

	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;
	case TPI_INFO_SUPPORTED_TYPE:
	case TPI_INFO_SUPPORTED_SUFFIX:
	{
		// xml解析開始。
		wxXmlDocument config(g_szAppPath + wxT("/lib/cuiWrapper.xml"));
		// 一気に先頭のライブラリの情報を取得。
		wxXmlNode * xmlLibrary = config.GetRoot()->GetChildren();

		// 適当な位置まで移動。
		for (unsigned int i = 0; i < _llSubOption && xmlLibrary != NULL;)
		{
			xmlLibrary = xmlLibrary->GetNext();
			if (xmlLibrary != NULL && xmlLibrary->HasProp(wxT("suffix")) && myExecute(xmlLibrary->GetPropVal(wxT("name"), wxEmptyString), NULL, wxEmptyString, true) == 0)
			{
				i++;
			}
		}
		if (xmlLibrary == NULL || xmlLibrary->GetName() != wxT("library"))
		{
			// xml文法エラー。
			return TPI_ERROR_UNDEFINED;
		}

		* (wxString *) _pPtr = xmlLibrary->GetPropVal(LOWORD(_uInfoId) == TPI_INFO_SUPPORTED_TYPE ? wxT("typename") : wxT("suffix"), wxEmptyString);
		break;
	}
	default:
		return TPI_ERROR_D_UNSUPPORTED;
	}
	return TPI_ERROR_SUCCESS;
}

int __stdcall LoadPlugin
(
	const wxString & _szArcName,
	wxULongLong _llSubOption
)
{
	// パス設定。
	wxStandardPaths p;
	g_szAppPath = wxPathOnly(p.GetExecutablePath());

	// xml解析開始。
	wxXmlDocument config(g_szAppPath + wxT("/lib/cuiWrapper.xml"));
	if (! config.IsOk())
	{
		return TPI_ERROR_UNDEFINED;
	}
	// 一気に先頭のライブラリの情報を取得。
	wxXmlNode * xmlLibrary = config.GetRoot()->GetChildren();

	// 対象が存在するならば対応するライブラリを調査、
	// 対象が存在しないならば指示されたライブラリをロード。
	if (! ::wxFileExists(_szArcName))
	{
		// 適当な位置まで移動。
		int u = 0;
		for (unsigned int i = 0; i < _llSubOption && xmlLibrary != NULL; u++)
		{
			xmlLibrary = xmlLibrary->GetNext();
			if (xmlLibrary != NULL && xmlLibrary->HasProp(wxT("suffix")) && myExecute(xmlLibrary->GetPropVal(wxT("name"), wxEmptyString), NULL, wxEmptyString, true) == 0)
			{
				i++;
			}
		}
		g_LibInfo.nLibIndex = u;
		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 (::myExecute(g_LibInfo.szExeFile, NULL, wxEmptyString, true) == 0 && 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(";"));
	for (size_t i = 0; i < asExt.GetCount(); i++)
	{
		if (asExt[i].IsSameAs(fnArchive.GetExt(), false))
		{
			// 対応。
			if (_nFileCount != NULL)
			{
				// ファイル数は適当に。
				* _nFileCount = 1;
			}

			return TPI_ERROR_SUCCESS;
		}
	}

	return TPI_ERROR_ARC_UNSUPPORTED;
}

int __stdcall OpenArchive
(
	const wxString & _szArcName,
	void * * _hArchive
)
{
	wxString szOutput;
	if (myExecute(g_LibInfo.szExeFile + wxT(" ") + MakeCommandLineSend(g_LibInfo.node.GetPropVal(wxT("list"), wxEmptyString), _szArcName, NULL, NULL, wxEmptyString), & szOutput, wxEmptyString) != 0)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	g_asOutput = ::wxStringTokenize(szOutput, wxT("\r\n"));
	* _hArchive = & g_asOutput;
	return g_asOutput.Count() == 0 ? 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;

	wxString szStartLine = g_LibInfo.node.GetPropVal(wxT("list-line-s"), wxEmptyString);
	if (_bFirst)
	{
		s_llFileID = 0;
		if (! szStartLine.IsEmpty())
		{
			for (s_uCurrentLine = 0; s_uCurrentLine < asOutput.Count(); s_uCurrentLine++)
			{
				if (asOutput[s_uCurrentLine] == szStartLine)
				{
					// 次の行へ進んで終了。
					s_uCurrentLine++;
					break;
				}
			}
		}
	}

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

	// 最終行かどうか確認。
	if (asOutput[s_uCurrentLine] == g_LibInfo.node.GetPropVal(wxT("list-line-e"), szStartLine))
	{
		return TPI_ERROR_UNDEFINED;
	}

	// ファイル名を取得。
	unsigned long nStartPos = 0, nEndPos = 0, nLine = 0;
	g_LibInfo.node.GetPropVal(wxT("list-fname-s"), wxEmptyString).ToULong(& nStartPos);
	g_LibInfo.node.GetPropVal(wxT("list-fname-c"), wxEmptyString).ToULong(& nEndPos);
	g_LibInfo.node.GetPropVal(wxT("list-fname-l"), wxT("0")).ToULong(& nLine);
	_fiInfo->szStoredName = nEndPos == 0 ? asOutput[s_uCurrentLine + nLine].Mid(nStartPos) : asOutput[s_uCurrentLine + nLine].Mid(nStartPos, nEndPos);
	_fiInfo->szStoredName.Trim();
	_fiInfo->fnFileName = wxFileName::wxFileName(_fiInfo->szStoredName);

	// 圧縮後サイズ取得。
	wxULongLong_t nTemp;
	nStartPos = 0, nEndPos = 0;
	g_LibInfo.node.GetPropVal(wxT("list-psize-s"), wxEmptyString).ToULong(& nStartPos);
	g_LibInfo.node.GetPropVal(wxT("list-psize-c"), wxEmptyString).ToULong(& nEndPos);
	g_LibInfo.node.GetPropVal(wxT("list-psize-l"), wxT("0")).ToULong(& nLine);
	if (nStartPos != 0 || nEndPos != 0)
	{
		wxString sz = nEndPos == 0 ? asOutput[s_uCurrentLine + nLine].Mid(nStartPos) : asOutput[s_uCurrentLine + nLine].Mid(nStartPos, nEndPos);
		sz.ToULongLong(& nTemp);
		_fiInfo->llPackedSize = nTemp;
	}

	// 圧縮前サイズ取得。
	nStartPos = 0, nEndPos = 0;
	g_LibInfo.node.GetPropVal(wxT("list-usize-s"), wxEmptyString).ToULong(& nStartPos);
	g_LibInfo.node.GetPropVal(wxT("list-usize-c"), wxEmptyString).ToULong(& nEndPos);
	g_LibInfo.node.GetPropVal(wxT("list-usize-l"), wxT("0")).ToULong(& nLine);
	if (nStartPos != 0 || nEndPos != 0)
	{
		wxString sz = nEndPos == 0 ? asOutput[s_uCurrentLine + nLine].Mid(nStartPos) : asOutput[s_uCurrentLine + nLine].Mid(nStartPos, nEndPos);
		sz.ToULongLong(& nTemp);
		_fiInfo->llUnpackedSize = nTemp;
	}

	// 更新時刻取得。
	nStartPos = 0, nEndPos = 0;
	g_LibInfo.node.GetPropVal(wxT("list-date-s"), wxEmptyString).ToULong(& nStartPos);
	g_LibInfo.node.GetPropVal(wxT("list-date-c"), wxEmptyString).ToULong(& nEndPos);
	g_LibInfo.node.GetPropVal(wxT("list-date-l"), wxT("0")).ToULong(& nLine);
	if (nStartPos != 0 || nEndPos != 0)
	{
		_fiInfo->tmModified.ParseFormat(nEndPos == 0 ? asOutput[s_uCurrentLine + nLine].Mid(nStartPos) : asOutput[s_uCurrentLine + nLine].Mid(nStartPos, nEndPos), g_LibInfo.node.GetPropVal(wxT("list-date-f"), wxDefaultDateTimeFormat));
	}

	// 最後に次の行へ進めておく。
	_fiInfo->llFileID = s_llFileID++;
	g_LibInfo.node.GetPropVal(wxT("list-line-c"), wxT("1")).ToULong(& nStartPos);
	s_uCurrentLine += nStartPos;

	return TPI_ERROR_SUCCESS;
}

int __stdcall GetArchiveInformation
(
	void * _hArchive,
	TPI_ARCHIVEINFO * _aiInfo
)
{
	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),
		szCommandLineSend  = MakeCommandLineSend(szCommandLine, _szArcName, _swInfo, _szFiles, szResponceFileName);

	// コマンドライン実行。
	wxString szOutput;
	DWORD nCuiErrorCode = myExecute(g_LibInfo.szExeFile + wxT(" ") + szCommandLineSend, & szOutput, _swInfo->fnDestinationDirectory.GetFullPath());
	if (nCuiErrorCode != 0)
	{
#ifdef __LINUX__
		::wxMessageBox(wxString::Format(wxT("Error :\n%d\n\nCommandLine:\n%s\n\nOutput:\n%s"), nCuiErrorCode, szCommandLineSend.c_str(), szOutput.c_str()));
#else
		MessageBox(NULL, wxString::Format(wxT("Error :\n%d\n\nCommandLine:\n%s\n\nOutput:\n%s"), nCuiErrorCode, szCommandLineSend.c_str(), szOutput.c_str()), NULL, 0);
#endif
	}

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

	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
