// FWatch.cpp : アプリケーション用のエントリ ポイントの定義
//

#include "stdafx.h"
#include "FWatchApp.hpp"
#include "resource.h"

#include "FWatchUI.hpp"
#include "ServiceEntry.hpp"
#include "Utility.hpp"
#include "StringExpander.hpp"

#include <assert.h>
#include <vector>

#include <Commdlg.h>
#include <shlobj.h>

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

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



LPCTSTR ResourceAccessor::getApplicationName() const
{
	return NULL;
}

HWND ResourceAccessor::getMainHWnd() const
{
	return NULL;
}

int ResourceAccessor::LoadString(UINT uMessageID, LPTSTR buffer, int nBufferMax) const
{
	return ::LoadString(getInstanceHandle(), uMessageID, buffer, nBufferMax);
}

tstring ResourceAccessor::LoadString(UINT uMessageID) const
{
	TCHAR buffer[4096] = {0};
	::LoadString(getInstanceHandle(), uMessageID, buffer, 4096);
	return buffer;
}

int ResourceAccessor::LoadInteger(UINT uMessageID) const
{
	return atoi(LoadString( uMessageID ).c_str());
}

tstring ResourceAccessor::LoadStringFormat(UINT uMessageID, ...) const
{
	va_list marker;
	va_start(marker, uMessageID);

	return LoadStringFormatV(uMessageID, marker);
}

tstring ResourceAccessor::LoadStringFormatV(UINT uMessageID, va_list marker) const
{
	const tstring format = LoadString(uMessageID);
	return ToStringV(format.c_str(), marker);
}

int ResourceAccessor::showMessageBox(UINT uType, LPCTSTR caption, UINT uMessageID, ...) const
{
	va_list marker;
	va_start(marker, uMessageID);

	const tstring message = LoadStringFormatV(uMessageID, marker);

	if (caption == NULL) {
		caption = getApplicationName();
	}

	return ::MessageBox(NULL, message.c_str(), caption, uType);
}

int ResourceAccessor::showMessageBox(HWND hWnd, UINT uType, LPCTSTR caption, UINT uMessageID, ...) const
{
	va_list marker;
	va_start(marker, uMessageID);

	const tstring message = LoadStringFormatV(uMessageID, marker);

	if (caption == NULL) {
		caption = getApplicationName();
	}

	return ::MessageBox(hWnd, message.c_str(), caption, uType);
}


//////////////////////////


bool Configuration::IsDeleteLog() const
{
	return GetLong(_T("CONFIG"), _T("DELETELOG"), 0) != 0;
}

bool Configuration::IsRotateLog() const
{
	return GetLong(_T("CONFIG"), _T("ROTATELOG"), 1) != 0;
}

bool Configuration::IsCurrentDirSetToWatchDir() const
{
	return GetLong(_T("CONFIG"), _T("CURDIRECTORY"), 1) != 0;
}

long Configuration::GetLogCloseSpan() const
{
	return GetLong(_T("CONFIG"), _T("LOGCLOSESPAN"), 10);
}

tstring Configuration::GetImpersonatePasswordEncryption() const
{
	return GetString(_T("SECURITY"), _T("IMPERSONATE_PASSWORD_ENCRYPTION"), _T(""));
}


tstring Configuration::GetString(LPCTSTR appName, LPCTSTR name, LPCTSTR defValue) const
{
	assert(appName && name && "appName, nameにnullは指定できません。");

	int bufsiz = 1024;
	std::vector<TCHAR> buf(bufsiz);
	::GetPrivateProfileString(appName, name, defValue, &buf[0], bufsiz, getIniPath().c_str());
	return tstring(&buf[0]);
}

long Configuration::GetLong(LPCTSTR appName, LPCTSTR name, int defValue) const
{
	tstring value = GetString(appName, name, _T(""));
	if (value.length() > 0) {
		return _tstol(value.c_str());
	}
	return defValue;
}


///////////////////////////



FWatchApp::FWatchApp()
	: hMainWnd_( NULL )
	, cl_( false )
{
}

FWatchApp::~FWatchApp()
{
}

HINSTANCE FWatchApp::getInstanceHandle() const
{
	//required:
	assert(hInstance_ != NULL && "まだ初期化されていません。");
	
	//do:
	return hInstance_;
}

LPCTSTR FWatchApp::getApplicationName() const
{
	return applicationName_.c_str();
}

HWND FWatchApp::getMainHWnd() const
{
	return hMainWnd_;
}

CWatchThreadGroup& FWatchApp::getCL()
{
	return cl_;
}

void FWatchApp::initDefaultPath()
{
	// ヘルプファイルの位置
	TCHAR szHelpFile[MAX_PATH] = {0};
	GetBaseFileNameWithExtention(szHelpFile, _T("chm"), MAX_PATH);
	helpFile_ = szHelpFile;

	// デフォルトの設定ファイル
	TCHAR szIniPath[MAX_PATH] = {0};
	GetBaseFileNameWithExtention(szIniPath, _T("ini"), MAX_PATH);
	iniPath_ = szIniPath;

	// デフォルトの監視リストファイル
	TCHAR szInfPath[MAX_PATH] = {0};
	GetBaseFileNameWithExtention(szInfPath, _T("lst"), MAX_PATH);
	infPath_ = szInfPath;

	// モジュールディレクトリ
	moduleDir_ = EnsureLast(GetDirName(iniPath_), _T("\\"));

	// APPDATA環境変数からユーザ別設定ファイル格納先(C:\Users\XXXXX\AppData\Roaming\fwatch.ini\)を決定する。
	userDataDir_.clear();
	TCHAR szAppData[MAX_PATH] = {0};
	if (GetEnvironmentVariable(_T("APPDATA"), szAppData, MAX_PATH)) {
		TCHAR szUserDataDir[MAX_PATH] = {0};
		PathCombine(szUserDataDir, szAppData, GetBaseName(iniPath_).c_str());
		PathAddBackslash(szUserDataDir);
		userDataDir_ = szUserDataDir;
	}
	if (userDataDir_.empty()) {
		userDataDir_ = moduleDir_;
	}

	// ユーザ別設定ファイル格納先にiniファイルがあれば、それを優先する。
	tstring userIniPath = userDataDir_ + GetBaseName(iniPath_);
	if (PathFileExists(userIniPath.c_str())) {
		iniPath_ = userIniPath;
	}

	// iniファイルのディレクトリ
	iniDir_ = EnsureLast(GetDirName(iniPath_), _T("\\"));

	// ユーザ別設定ファイル格納先にiniまたはlstファイルがあれば、
	// ユーザ別設定ファイル格納先に監視リストファイルがあるものとする。
	tstring userInfPath = userDataDir_ + GetBaseName(infPath_);
	if (PathFileExists(userIniPath.c_str()) || PathFileExists(userInfPath.c_str())) {
		infPath_ = userInfPath;
	}

	// infファイルのディレクトリ
	infDir_ = EnsureLast(GetDirName(infPath_), _T("\\"));
}

void FWatchApp::setAppConfVariablesTo(PropertyStringExpander& varExpander, bool useInfDir)
{
	varExpander.addProperty(_T("INIDIR"), iniDir_);
	varExpander.addProperty(_T("CONFDIR"), userDataDir_);
	varExpander.addProperty(_T("MODULEDIR"), moduleDir_);
	if (useInfDir) {
		varExpander.addProperty(_T("LSTDIR"), infDir_);
	}
}

int FWatchApp::run(HINSTANCE, LPCTSTR v_lpCmdLine)
{
	//required:
	assert(v_lpCmdLine != NULL && "v_lpCmdLineにnullは指定できません。");

	//do:

	// 基本設定の初期化
	hInstance_ = ::GetModuleHandle(NULL);
	applicationName_ = app.LoadString(IDS_APP_TITLE);

	// デフォルトのパス
	initDefaultPath();

	// パラメーターの取得
	const int cmdLineBufSiz = lstrlen(v_lpCmdLine) + 1;
	std::vector<TCHAR> cmdLineBuf(cmdLineBufSiz);
	_tcscpy_s(&cmdLineBuf[0], cmdLineBufSiz, v_lpCmdLine);

	const int argvMx = 16;
	LPTSTR argv[argvMx];
	const int argc = GetCmdArgs(&cmdLineBuf[0], argv, argvMx);

	// 起動モード
	enum CMDTYPE
	{
		CMDTYPE_NONE     = 0,
		CMDTYPE_REGIST   = 1,
		CMDTYPE_UNREGIST = 2,
		CMDTYPE_NORMAL   = 3,
		CMDTYPE_SERVICE  = 4,
		CMDTYPE_UNKNOWN  = 9
	} cmdtyp = CMDTYPE_NONE;

	// ファイルダイアログでユーザに対話的にinfファイルを選ぶ場合
	bool bChooseInfFile = false;

	// オプションの解析
	int idx;
	for (idx = 0 ; idx < argc ; idx++) {
		if (argv[idx][0] == '/') {
			// サービス関連
			if ( !_tcsicmp(argv[idx], _T("/service")))			cmdtyp = CMDTYPE_REGIST;
			else if ( !_tcsicmp(argv[idx], _T("/unregister")))	cmdtyp = CMDTYPE_UNREGIST;
			else if ( !_tcsicmp(argv[idx], _T("/noservice")))	cmdtyp = CMDTYPE_NORMAL;
			else if ( !_tcsicmp(argv[idx], _T("/runservice")))	cmdtyp = CMDTYPE_SERVICE;

			// オプションに設定ファイルが指定されていれば設定ファイルの差し替え
			else if (_tcsstr(argv[idx], _T("/ini:")) == argv[idx]) {
				TCHAR szIniPath[MAX_PATH] = {0};
				_tcscpy_s(szIniPath, MAX_PATH, argv[idx] + 5);
				if (::GetFileAttributes(szIniPath) == -1) {
					showMessageBox(MB_ICONERROR | MB_OK, NULL, IDS_OPTION_INI_NOTFOUND, szIniPath);
					return 1;
				}
				iniPath_ = szIniPath;
				iniDir_ = EnsureLast(GetDirName(iniPath_), _T("\\"));
			}

			// ファイルダイアログによる選択
			else if (_tcsstr(argv[idx], _T("/nodefault")) == argv[idx]) {
				if (cmdtyp != CMDTYPE_SERVICE) {
					// サービス時にはダイアログは開けないので無視する。
					// そうでなければユーザによる対話的な選択とする。
					bChooseInfFile = true;
				}
			}

			else {
				showMessageBox(MB_ICONERROR | MB_OK, NULL, IDS_OPTION_FAIL, argv[idx]);
				return 1;
			}
		}
	}

	// 設定ファイル上のデフォルトの監視リストファイル位置を取得する。
	tstring defaultInfPath = GetString(_T("CONFIG"), _T("DEFAULT_LST_FILE"), _T(""));
	if ( !defaultInfPath.empty()) {
		PropertyStringExpander varExpander;
		setAppConfVariablesTo(varExpander, false);
		tstring tmp = StrTrim(varExpander.expandString(defaultInfPath));
		if ( !tmp.empty()) {
			infPath_ = tmp;
			infDir_ = EnsureLast(GetDirName(infPath_), _T("\\"));
		}
	}

	// オプションでない最初の引数が存在すれば、監視リストファイルとして採用する。
	for (idx=0; idx < argc; idx++) {
		if (argv[idx][0] != '/') {
			tstring tmp = StrTrim(tstring(argv[idx]));
			if ( !tmp.empty()) {
				infPath_ = tmp;
				infDir_ = EnsureLast(GetDirName(infPath_), _T("\\"));
			}
			break;
		}
	}

	// 手動で監視リストファイルを選択する。
	if (bChooseInfFile) {
		OPENFILENAME  ofn = {0};
		TCHAR szChooseInfPath[MAX_PATH] = {0};
		ofn.lStructSize = sizeof(OPENFILENAME);
		ofn.lpstrFilter = _T("lst(*.lst)\0*.lst\0All Files\0*.*\0");
		ofn.lpstrFile = szChooseInfPath;
		ofn.nMaxFile = MAX_PATH;
		ofn.lpstrInitialDir = iniDir_.c_str();
		ofn.lpstrDefExt = _T("lst");
		ofn.Flags = OFN_ENABLESIZING | OFN_HIDEREADONLY;
		if (!GetOpenFileName(&ofn)) {
			// キャンセルされた場合
			return 2;
		}
		infPath_ = ofn.lpstrFile;
		infDir_ = EnsureLast(GetDirName(infPath_), _T("\\"));
	}


	// パラメーターをチェックし、サービスとして登録するか検査します。
	if (cmdtyp == CMDTYPE_REGIST) {
		// サービスとして登録して終了
		return RegistService();
	}
	else if (cmdtyp == CMDTYPE_UNREGIST) {
		// サービスの登録解除して終了
		return UnregistService();
	}

	// サービスの準備
	ServiceInit();

	BOOL bStartService = FALSE;
	const BOOL bRegistService = IsRegistService(bStartService);

	if (cmdtyp == CMDTYPE_SERVICE) {
		// サービスプロセスの起動引数の場合 (サービス登録時に設定される引数)
		if ( !bRegistService) {
			// サービスとして登録されていない場合
			app.showMessageBox(MB_SERVICE_NOTIFICATION | MB_ICONERROR | MB_OK, NULL, IDS_MAINPROC_SERVICENOREG);
		}
		else if (bStartService) {
			// サービスが既に実行されている場合
			app.showMessageBox(MB_SERVICE_NOTIFICATION | MB_ICONERROR | MB_OK, NULL, IDS_SERVICE_RUNNING);
		}
		else if ( !StartService()) {
			// サービスが起動できなかった場合
			app.showMessageBox(MB_SERVICE_NOTIFICATION | MB_ICONERROR | MB_OK, NULL, IDS_SERVICE_STARTERROR);
		}
	}
	else {
		// サービスプロセスとして起動されていない場合 (通常のプロセスとして実行)
		FWatchUI ui;
		if ( !ui.create()) {
			// UIの構築に失敗
			app.showMessageBox(MB_ICONERROR, NULL, IDS_UI_CREATIONFAILED);
		}
		else {
			// スタンドアロンで監視の開始
			cl_.LoadInfData(getInfPath());
			cl_.startAll();

			// 起動時の画面状態
			DWORD showOnStartup = GetLong(_T("CONFIG"), _T("SHOWONSTARTUP"), 0);
			if ((showOnStartup == 1 && !cl_.isRunning()) || (showOnStartup >= 2)) {
				ui.showEditDialog();
			}

			// ヘルプの準備
			DWORD dwCookie = 0;
			HtmlHelp(NULL, NULL, HH_INITIALIZE, (DWORD_PTR) &dwCookie);

			// メッセージループ
			MSG msg;
			while (GetMessage(&msg, NULL, 0, 0)) {
				if (!HtmlHelp(NULL, NULL, HH_PRETRANSLATEMESSAGE, (DWORD_PTR) &msg)) {
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
			}

			// ヘルプ終了
			HtmlHelp(NULL, NULL, HH_UNINITIALIZE, (DWORD) dwCookie);

			// 監視終了
			cl_.ReleaseAllThreads();
		}
	}

	return 0;
}

const tstring& FWatchApp::getInfPath() const
{
	return infPath_;
}

const tstring& FWatchApp::getInfDir() const
{
	return infDir_;
}

const tstring& FWatchApp::getIniPath() const
{
	return iniPath_;
}

const tstring& FWatchApp::getIniDir() const
{
	return iniDir_;
}

void FWatchApp::showHelp(HWND hWnd) const
{
	HtmlHelp(hWnd, helpFile_.c_str(), HH_DISPLAY_TOC, NULL);
}

void FWatchApp::showContextHelp(HWND hWnd, int helpid, POINT pt) const
{
	RECT margin = {8, 8, 8, 8};
	HH_POPUP popup = {0};
	popup.cbStruct = sizeof(HH_POPUP);
	popup.hinst = NULL;
	popup.idString = helpid;
	popup.pt = pt;
	popup.clrForeground = (COLORREF) -1;
	popup.clrBackground = (COLORREF) -1;
	popup.rcMargins = margin;
	popup.pszFont = _T("MS UI Gothic,10");

	tstring popupText = _T("::/popup.txt");
	HtmlHelp(hWnd, (helpFile_ + popupText).c_str(), HH_DISPLAY_TEXT_POPUP, (DWORD_PTR) &popup);
}

FWatchApp app;


/*!
 * エントリポイント
 * \param hInstance インスタンス
 * \return 終了コード
 */
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
	// デバッグフラグ(_DEBUG時のみ有効)
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

	// ランタイムの初期設定
	setlocale(LC_ALL, "");

	// COMの初期化
	if (FAILED(::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) {
		app.showMessageBox(MB_ICONERROR | MB_OK, NULL, IDS_MAINPROC_COINITFAILED);
		return 1;
	}

	// コマンドラインの取得
	LPCTSTR lpCmdLine = GetCommandLine();
	lpCmdLine = GetToken(NULL, lpCmdLine, 0); // 最初の要素(実行ファイル名)を読み飛ばす.

	// メイン
	const int result = app.run(hInstance, lpCmdLine);

	// COMの初期化解除
	::CoUninitialize();
	return result;
}
