#include "stdafx.h"

#include "Utility.hpp"

#include <assert.h>
#include <string.h>

#include <string>
#include <vector>
#include <algorithm>
#include <exception>

#include <strsafe.h>
#pragma comment(lib, "strsafe.lib")



/*!
 * コマンドライン引数を分解します。
 * 引数はヌル文字によって破壊されます。
 * \param lpCmdLine コマンドライン
 * \param argv 引数を格納する配列
 * \param mx 配列の大きさ
 * \return 得られた引数の数
 */
int GetCmdArgs(LPTSTR lpCmdLine,LPTSTR* argv,int mx)
{
	//required:
	assert(lpCmdLine != NULL && "lpCmdLineにNULLは指定できません。");
	assert(argv != NULL && "argvにnullは指定できません。");
	assert(mx >= 0 && "mxに負の値は指定できません。");

	//do:
	int argc = 0;
	LPTSTR p = lpCmdLine;
	while (*p && argc < mx) {
		while (*p == ' ' || *p == '\t') p++; // 先行する空白は除外される
		if (*p == '\"') {
			// ダブルクォーテーションで区切られた場合
			p++;
			argv[argc++] = p;
			while (*p && *p != '\"') {
				p = ::CharNext(p);
			}
		}
		else {
			// タブルクォーテーション以外で始まるトークン
			argv[argc++] = p;
			while (*p && *p != ' ' && *p != '\t') {
				p = ::CharNext(p);
			}
		}
		if (*p) {
			*p++ = 0;
		}
	}
	return argc;
}

/*!
 * 整数を文字列として返します。
 * 返されるオブジェクトは一時オブジェクトです。
 * \param v_value 整数
 * \return 文字列
 */
tstring IntToString(long v_value) throw()
{
	return ToString(_T("%ld"), v_value);
}

/*!
 * 引数を文字列に変換して返します。
 * 返されるオブジェクトは一時オブジェクトです。
 * \param v_format フォーマット文字列
 * \return 文字列
 */
tstring ToString(const LPCTSTR v_format, ...) throw()
{
	//required:
	assert(v_format != NULL && "v_formatにnullは指定できません。");

	//do:
	va_list marker;
	va_start(marker, v_format);

	return ToStringV(v_format, marker);
}

tstring ToStringV(const LPCTSTR v_format, va_list marker) throw()
{
	//required:
	assert(v_format != NULL && "v_formatにnullは指定できません。");

	//do:

	int bufsiz = 4096;
	for (;;) {
		std::vector<TCHAR> buf(bufsiz);

		LPTSTR pNext = NULL;
		size_t zan = 0;

		HRESULT hr;
		hr = StringCchVPrintfEx(
			&buf[0],
			bufsiz,
			&pNext,
			&zan,
			STRSAFE_IGNORE_NULLS | STRSAFE_FILL_BEHIND_NULL,
			v_format,
			marker
			);
		if (hr == STRSAFE_E_INSUFFICIENT_BUFFER) {
			bufsiz *= 2;
			continue;
		}
		if (SUCCEEDED(hr)) {
			return tstring(&buf[0]);
		}

		assert(false && "メッセージの構築に失敗しました。");
		break;
	}

	return _T("*FORMAT ERROR*");
}

/*!
 * 文字列から数値を取り出します。
 * 数値として変換できない場合は0となります。
 * \param v_text 文字列
 * \return 数値
 */
int atoi(const tstring& v_text) throw()
{
	return _tstoi(v_text.c_str());
}

namespace
{
	void InternalGetModuleFileName(std::vector<tstring::value_type> &buf)
	{
		DWORD bufsiz = MAX_PATH;
		for (;;) {
			buf.resize(bufsiz + 1); // ヌル終端ガード
			memset(&buf[0], 0, bufsiz + 1);

			DWORD siz = GetModuleFileName(NULL, &buf[0], bufsiz);

			if (siz == 0) {
				// エラーの場合
				// (自分自身のモジュールパスが得られないことはあり得ないと思う)
				assert(false && "GetModuleFileNameが失敗しました");
				throw std::logic_error("GetModuleFileNameが失敗しました");
			}

			if (siz >= bufsiz) {
				// バッファサイズはヌル終端を含み、バッファが小さい場合は
				// バッファサイズに切り詰められる。(末尾はヌル終端, ただしXP/2000は非ヌル終端?)
				// したがって、siz == bufsiz の場合はちょうど入ったか、もしくは不足している。
				// XP/2000ではエラーコードはERROR_SUCCESSなのでエラーでは不足しているか判定できない.
				bufsiz *= 2;
				continue;
			}

			break;
		}
	}
}

/*!
 * この実行ファイルのフルパスを取得します.
 * \return フルパス
 */
tstring GetModuleFileName()
{
	std::vector<tstring::value_type> buf;
	InternalGetModuleFileName(buf);
	return tstring(&buf[0]);
}

/*!
 * モジュールの実行ファイル名をベースに拡張子を変更したファイルパスを返します。
 * \param ExtName 拡張子
 * \return モジュールの実行ファイル名をベースに拡張子を変更したファイルパス
 */
tstring GetBaseFileNameWithExtention(LPCTSTR ExtName)
{
	// バッファに足すため拡張子の長さを事前に取得する.
	if (ExtName == NULL) {
		ExtName = _T("");
	}
	size_t extnameLen = _tcslen(ExtName);

	// 実行モジュールのフルパスを取得する
	std::vector<TCHAR> buf;
	InternalGetModuleFileName(buf);

	// 末尾までシークする
	LPTSTR pBase = &buf[0];
	LPTSTR p = pBase;
	while (*p) p++;

	// 末尾からフォルダ区切りを超えない最初の「.」の位置を発見する.
	// フォルダ区切りを超えるまで「.」がみつからない場合は末尾を「.」の位置とみなす.
	LPTSTR ext_p = NULL;
	while (p > pBase) {
		if (*p == '\\' || *p=='/') {
			break;
		}
		if (*p == '.') {
			ext_p = p;
			break;
		}
		// 先頭にさかのぼる
		p = CharPrev(pBase, p);
	}

	if (ext_p == NULL) {
		// 「.」がみつからない場合は末尾までシークし、それを「.」開始位置とみなす.
		while (*p) p++;
		ext_p = p;
	}

	// 拡張子位置までの長さ
	int len = ext_p - pBase;
	// バッファサイズに対する拡張子位置までの長さを引いた残り
	size_t bufsiz = buf.size();
	int zan = bufsiz - len - 1; // 終端分を引く

	if (extnameLen == 0) {
		// 拡張子が空である場合、ここで終端
		*ext_p = 0;
	}
	else {
		// 拡張子が空でない場合
		// 拡張子の「.」を設定
		assert(zan >= (int)(extnameLen + 1) && "バッファサイズが不足しています.");

		*ext_p++ = '.';
		zan--;

		// 拡張子を設定
		if (_tcscpy_s(ext_p, zan, ExtName)) {
			assert(false && "バッファサイズが不足しています.");
		}
	}

	// 成功
	return tstring(&buf[0]);
}

/*!
 * 環境変数を取得します.
 * 存在しない場合はfalseを返します.
 * \param name 環境変数名
 * \param value 環境変数の値の格納先(環境変数がない場合は変更なし)
 * \return 存在すればtrue、存在しないければfalse
 */
bool GetEnvironmentVariable(LPCTSTR name, tstring& value)
{
	if (name == NULL) {
		return false;
	}
	
	const DWORD strsize = ::GetEnvironmentVariable(name, NULL, 0);
	if (strsize != 0) {
		std::vector<tstring::value_type> buf(strsize);
		if (::GetEnvironmentVariable(name, &buf[0], strsize)) {
			value = tstring(&buf[0]);
			return true;
		}
	}
	return false;
}

/*!
 * フルパスから短いパスを取得します.
 * 変換できない場合は引数をそのまま返します.
 * \param path フルパス
 * \return 短いパス
 */
tstring GetShortPathName(const tstring& path)
{
	size_t bufsiz = 40;
	std::vector<tstring::value_type> buf(bufsiz);
	for (;;) {
		DWORD siz = GetShortPathName(path.c_str(), &buf[0], bufsiz);
		if (siz == 0) {
			// 短いパスの取得に失敗した場合は変換しない
			return path;
		}
		if (siz >= bufsiz) {
			bufsiz = siz + 1;
			buf.resize(bufsiz);
			memset(&buf[0], 0, bufsiz);
			continue;
		}
		break;
	}
	return tstring(&buf[0]);
}

/*!
 * 文字列から空白区切りの要素を1つ取り出します。
 * 先行する空白はスキップされます。
 * 先頭がダブルクォートで始まっている場合は空白ではなく次のダブルクォートまでが要素となります。
 * 終端まで達した場合は、InfPathは空となり、終端(ヌル文字)のアドレスが返されます。
 * バッファサイズが足りない場合はNULLが返され、GetLastErrorではERROR_INSUFFICIENT_BUFFERが取得されます。
 * \param InfPath 格納先、不要ならばnull
 * \param lpCmdLine 読み取り開始ポインタ、NULLを指定した場合はNULLが返されます。
 * \param bufsiz 格納先バッファのサイズ
 * \return 次の読み取り位置、もしくは終端
 */
LPCTSTR GetToken(LPTSTR InfPath, LPCTSTR lpCmdLine, int bufsiz)
{
	if (lpCmdLine == NULL) {
		SetLastError(ERROR_INVALID_PARAMETER);
		return NULL;
	}

	LPCTSTR p = lpCmdLine;

	LPTSTR d = InfPath;

	// 先行する空白またはタブをスキップする
	while (*p == ' '|| *p == '\t') p++;

	// ダブルクォートで始まっているか?
	if (*p == '\"') {
		// ダブルクォーテーションで始まっていれば空白を区切りとみなさず、
		// 次のダブルクォートまでを要素とする。
		p++;
		while (*p && *p!='\"') {
			TCHAR c = *p++;
			if (d) {
				if (bufsiz <= 0) {
					SetLastError(ERROR_INSUFFICIENT_BUFFER);
					return NULL;
				}
				bufsiz--;
				*d++ = c;
			}
		}
		if (*p == '\"') {
			p++;
		}
	}
	else {
		// 空白で区切りとみなす
		while (*p && *p!=' ') {
			TCHAR c = *p++;
			if (d) {
				if (bufsiz <= 0) {
					SetLastError(ERROR_INSUFFICIENT_BUFFER);
					return NULL;
				}
				bufsiz--;
				*d++ = c;
			}
		}
	}

	if (d) {
		if (bufsiz <= 0) {
			SetLastError(ERROR_INSUFFICIENT_BUFFER);
			return NULL;
		}
		bufsiz--;
		*d = 0;
	}
	return p;
}

/*!
 * 前後の空白とタブをトリムします。
 * 引数は破壊されます。
 * \param buf トリムする文字列
 * \return トリムされた文字列
 */
LPTSTR StrTrim(LPTSTR buf)
{
	LPTSTR p = buf;
	LPTSTR d = buf;
	while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
	LPTSTR t = p;
	while (*p) *d++ = *p++;
	*d = 0;
	while (d > t) {
		d = ::CharPrev(t, d);
		if (*d != ' ' && *d != '\t' && *p != '\r' && *p != '\n') {
			break;
		}
		*d = 0;
	}
	return t;
}

/*!
 * 文字列の前後に含まれる空白とタプ、改行コードを取り除い文字列を返します。
 * 引数は破壊されません。
 * 返される文字列は一時的オブジェクトです。
 */
tstring StrTrim(const tstring& v_in)
{
	if (v_in.empty()) {
		return v_in;
	}

	int st = v_in.find_first_not_of(_T(" \t\r\n"));
	int en = v_in.find_last_not_of(_T(" \t\r\n"));

	if (st == -1) {
		// 空白・タブ、改行コードなし
		return v_in;
	}
	return tstring(v_in, st, en - st + 1);
}


/*!
 * 文字列が指定した末尾で終端することを保障します。
 * 指定した末尾で終端していない文字列は末尾に終端文字列が追加されます。
 * 対象文字列は破壊的に操作されます。
 * 対象文字列はあらかじめ終端文字を追加しても安全な長さが必要です。
 * \param v_str 対象文字列
 * \param v_endStr 終端文字列
 * \param bufsiz バッファサイズ
 */
void EnsureLast(LPTSTR v_str, LPCTSTR v_endStr, int bufsiz)
{
	//required:
	assert(v_str != NULL && "v_strにNULLは指定できません。");
	assert(v_endStr != NULL && "v_endStrにNULLは指定できません。");

	//do:

	// 末尾までシークする
	LPTSTR p1 = v_str;
	LPCTSTR p2 = v_endStr;
	while (*p1) p1++;
	while (*p2) p2++;

	// 互いの文字長分だけ先頭にさかのぼる
	// どちらかが先に先頭に達した場合は、そこで止まる
	while (p1 > v_str && p2 > v_endStr) {
		p1 = CharPrev(v_str, p1);
		p2 = CharPrev(v_endStr, p2);
	}

	if (p2 == v_endStr) {
		// 終端文字列を先頭までシークできた場合
		if (_tcscmp(p1, p2) == 0) {
			// 文字列は指定した終端をもつ
			return;
		}
	}

	// 文字列は終端よりも短いか、終端が一致しない
	_tcscat_s(v_str, bufsiz, v_endStr);
}

/*!
 * 文字列が指定した末尾で終端することを保障します。
 * 引数は破壊されません。
 * 返される文字列は一時オブジェクトです。
 * \param v_str 文字列
 * \param v_endStr 終端する文字列
 * \return 終端していることが保障された文字列(一時オブジェクト)
 */
tstring EnsureLast(const tstring& v_str, const tstring& v_endStr)
{
	const int bufsiz = v_str.size() + v_endStr.size() + 1;
	std::vector<TCHAR> buf(bufsiz);
	_tcscpy_s(&buf[0], bufsiz, v_str.c_str());
	EnsureLast( &buf[0], v_endStr.c_str(), bufsiz);
	return tstring(&buf[0]);
}

/*!
 * パターンマッチを行います。
 * パターン文字には「*」と「?」の特殊文字を含めることができます。
 * 比較対照となる文字列には、この特殊文字を含めることはできません。
 * 「*」は終端を含めた0文字以上のマッチ、「?」は終端以外の任意の1文字とマッチします。
 * 大文字・小文字を区別します。
 * \param v_text 比較対照文字列
 * \param v_pattern パターン
 * \return マッチした場合、true
 */
bool IsMatch(LPCTSTR v_text, LPCTSTR v_pattern)
{
	//required:
	assert(v_text != NULL && v_pattern != NULL && "引数にnullは指定できません。");
	
	//do:
	LPCTSTR s = v_text;
	LPCTSTR p = v_pattern;
	while (*p) {
		const TCHAR cp = *p;
		if (cp == '?') {
			if ( !*s) {
				// 1文字を要求する場所で終端のため不一致
				return false;
			}
		}
		else if(cp == '*') {
			TCHAR cn = 0;
			do {
				p = ::CharNext(p);
				cn = *p;
			} while (cn == '*' || cn == '?');
			if (cn == 0) {
				// 次の文字が終端であるため、以降不問
				return true;
			}
			while (*s) {
				if (*s == cn) {
					if (IsMatch(s, p)) {
						return true;
					}
				}
				s = ::CharNext(s);
			}
			return false;
		}
		else {
			if (cp != *s) {
				// 一致せず。
				return false;
			}
#ifndef _UNICODE
			if (::IsDBCSLeadByte(cp)) {
				if (*(p + 1) != *(s + 1)) {
					// ダブルバイト文字の後続が一致せず
					return false;
				}
			}
#endif
		}
		s = ::CharNext(s); // 先に終端に達した場合は終端を指しつづける。
		p = ::CharNext(p);
	}
	
	return *s == 0 && *p == 0; // 双方が同時に終端にたどり着いたならマッチしている。
}


/*!
 * パスの最後の「\」までの文字列を返します。
 * 「\」を含みます。
 * 「\」がない場合は「.」を返します。
 * \param path パス
 * \return ディレクトリのパス、もしくは「.」
 */
tstring GetDirName(const tstring& path)
{
	LPCTSTR st = path.c_str();
	LPCTSTR p = st;
	while (*p) p++;
	while (st < p) {
		if (*p == '\\' || *p == '/') {
			break;
		}
		p = CharPrev(st, p);
	}
	if (p == st) {
		// フォルダ区切りがないので「.」を返します。
		return _T(".");
	}

	int len = p - st + 1;
	return path.substr(0, len);
}


/*!
 * パスの最後の「\」より後ろの文字列を返します。「\」は含みません。
 * 「\」がない場合は、パスをそのまま返します。
 * \param パス
 * \return ファイル名、もしくはパスそのもの
 */
tstring GetBaseName(const tstring& path)
{
	LPCTSTR st = path.c_str();
	LPCTSTR p = st;
	while (*p) p++;
	while (st < p) {
		if (*p == '\\' || *p == '/') {
			break;
		}
		p = CharPrev(st, p);
	}
	if (p == st) {
		// フォルダ区切りがないので、そのまま返す
		return path;
	}

	int len = p - st + 1;
	return path.substr(len);
}

/*!
 * パスの最後の「\」より後ろ以降で、最後の「.」までの文字列を返します。
 * 「\」がない場合は、最後の「.」までの文字列を返します。
 * \param パス
 * \return ファイルの拡張子を除いたもの、もしくは、ファイル名、もしくはパスそのもの。
 */
tstring GetNameBody(const tstring& path)
{
	tstring basename = GetBaseName(path);
	LPCTSTR st = basename.c_str();
	LPCTSTR p = st;
	while (*p) p++;
	while (st < p) {
		if (*p == '.') {
			break;
		}
		p = CharPrev(st, p);
	}
	if (p == st) {
		return basename;
	}
	int len = p - st;
	return basename.substr(0, len);
}

tstring GetRelativePath(const tstring& dirname, const tstring& absolutePath)
{
	// 末尾が「\」であれば絶対パスが示すのはフォルダである
	DWORD dwAttrTo = FILE_ATTRIBUTE_NORMAL;
	LPCTSTR pSt = absolutePath.c_str();
	LPCTSTR p = pSt;
	while (*p) p++;
	p = CharPrev(pSt, p);
	if (*p == '\\') {
		dwAttrTo = FILE_ATTRIBUTE_DIRECTORY;
	}

	// 相対パスに変換
	TCHAR buf[MAX_PATH] = {0};
	if (PathRelativePathTo(
		buf,
		dirname.c_str(),
		FILE_ATTRIBUTE_DIRECTORY,
		absolutePath.c_str(),
		dwAttrTo)) {
		return buf;
	}

	// 何らかの原因で失敗した場合はabsolutePathをそのままかえす。
	return absolutePath;
}

/*!
 * エラーコードを文字列にして返します。
 * \param errcode エラーコード
 * \return エラーメッセージ(一時オブジェクト)
 */
tstring ToErrorMessage(DWORD errcode) throw()
{
	LPTSTR lpMsgBuf;
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		errcode,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //  デフォルト言語
		(LPTSTR) &lpMsgBuf,
		0,
		NULL 
	);
	tstring mes(lpMsgBuf);
	LocalFree(lpMsgBuf);
	return mes;
}

/*!
 * 文字列に非印字文字が含まれる場合はエスケープします。
 * \r/\n/\tと\x00形式をサポートします。
 * 返される文字列は一時オブジェクトです。
 * \return エスケープされた文字列
 */
tstring EscapeUnprintableChars(const tstring& v_str)
{
	tstring result;

	LPCTSTR p = v_str.c_str();

	while (*p) {
		TCHAR ch = *p;
		if ((ch >= 0 && ch < ' ') || ch == 0x7e || ch == '\\') {
			if (ch == '\r') {
				result.append(_T("\\r"));
			}
			else if (ch == '\n') {
				result.append(_T("\\n"));
			}
			else if (ch == '\t') {
				result.append(_T("\\t"));
			}
			else if (ch == '\\') {
				result.append(_T("\\\\"));
			}
			else {
				result.append(ToString(_T("\\x%02x"), (unsigned int) ch & 0xff));
			}
		}
#ifdef _UNICODE
		else if ((ch >= 0x80 && ch <= 0xa1) || ch == 0xad) {
			result.append(ToString(_T("\\u%04x"), (unsigned int) ch));
		}
		else {
			result.push_back(ch);
		}
#else
		else {
			LPCTSTR p2 = CharNext(p);
			int len = p2 - p;
			if (len > 1) {
				result.append(p, len);
			}
			else {
				result.push_back(ch);
			}
		}
#endif
		p = CharNext(p);
	}

	return result;
}

/*!
 * 文字列にエスケープが含まれる場合はエスケープ解除します。
 * \r/\n/\tと\x00形式をサポートします。
 * 返される文字列は一時オブジェクトです。
 */
tstring UnescapeUnprintableChars(const tstring& v_str)
{
	tstring result;

	LPCTSTR p = v_str.c_str();

	while (*p) {
		TCHAR ch = *p;
		if (ch == '\\') {
			p = CharNext(p);

			TCHAR typ = *p;

			if (typ == 'r') {
				ch = '\r';
			}
			else if (typ == 'n') {
				ch = '\n';
			}
			else if (typ == 't') {
				ch = '\t';
			}
			else if (typ == 'x' || typ == 'u') {
				int len = (typ == 'x') ? 2 : 4;
				tstring hex;
				p = CharNext(p);
				while (*p && len > 0
					&& ((*p >= '0' && *p <= '9')
					|| (*p >= 'a' && *p <= 'f') 
					|| (*p >= 'A' && *p <= 'F'))) {
					hex.push_back(*p);
					p = CharNext(p);
					len--;
				}
				if ( !hex.empty()) {
					ch = (TCHAR) _tcstoul(hex.c_str(), NULL, 16);
					result.push_back(ch);
				}
				continue;
			}
		}

#ifdef _UNICODE
		result.push_back(ch);
#else
		LPCTSTR p2 = CharNext(p);
		int len = p2 - p;
		if (len > 1) {
			result.append(p, len);
		}
		else {
			result.push_back(ch);
		}
#endif
		p = CharNext(p);
	}

	return result;
}

/*!
 * 文字列に非印字文字が含まれる場合は置換文字列で置き換えます。
 * 返される文字列は一時オブジェクトです。
 * \return 置き換えられた文字列
 */
tstring EliminateUnprintableChars(const tstring& v_str, const tstring& v_replace)
{
	tstring result;

	LPCTSTR p = v_str.c_str();

	while (*p) {
		TCHAR ch = *p;
		if ((ch >= 0 && ch < ' ') || ch == 0x7e) {
			result.append(v_replace);
		}
#ifdef _UNICODE
		else if ((ch >= 0x80 && ch <= 0xa1) || ch == 0xad) {
			result.append(v_replace);
		}
		else {
			result.push_back(ch);
		}
#else
		else {
			LPCTSTR p2 = CharNext(p);
			int len = p2 - p;
			if (len > 1) {
				result.append(p, len);
			}
			else {
				result.push_back(ch);
			}
		}
#endif
		p = CharNext(p);
	}

	return result;
}

/*!
 * 指定時刻を経過しているか判定する。
 * nowで示す日時が、targetで示す日時 + spanミリ秒以上である場合、true。
 * \param v_now 現在時刻
 * \param v_span 猶予期間
 * \param v_target 対象時間
 */
bool IsExpired(const FILETIME& v_now, DWORD v_span, const FILETIME& v_target)
{
	LARGE_INTEGER now = {v_now.dwLowDateTime, v_now.dwHighDateTime};
	LARGE_INTEGER target = {v_target.dwLowDateTime, v_target.dwHighDateTime};

	target.QuadPart += (__int64) v_span * 10000; // mSec単位

	return now.QuadPart > target.QuadPart;
}

/*!
 * 文字列中に含まれる大文字を小文字に変換する.
 * 引数が更新されます.
 * \param 文字列(IN/OUT)
 */
void ToLower(tstring& str)
{
#ifdef _UNICODE
	std::transform(str.begin(), str.end(), str.begin(), tolower);
#else
	for (tstring::iterator ite = str.begin(); ite != str.end();) {
		TCHAR ch = *ite;
		if (!IsDBCSLeadByteEx(0, ch)) {
			// 2バイト文字の先頭バイトでなければ大文字->小文字変換を試行する
			int cc = tolower(ch);
			if (cc != ch) {
				*ite = static_cast<TCHAR>(cc);
			}
			++ite;
		} else {
			// 2バイト文字の先頭バイトであれば
			// 変換不要で2バイトともスキップする.
			++ite;
			if (ite != str.end()) {
				++ite;
			}
		}
	}
#endif
}
