#pragma once

#include "SettingInfo.hpp"

#include <string>
#include <map>
#include <list>
#include <functional>

#include "FileInfo.hpp"
#include "NameMatcher.hpp"
#include "ImpersonateUser.hpp"
#include "ActionInvoker.hpp"
#include "Logger.hpp"

/*!
 * ファイルのタイムスタンプが同一であるか判定する関数オブジェクト
 */
class FileTimeComparator
	: public std::binary_function<FILETIME, FILETIME, bool>
{
private:
	long tolerance_;

public:
	FileTimeComparator(long v_tolerance)
		: tolerance_(v_tolerance)
	{
	}

	inline bool operator()(const FILETIME& a, const FILETIME& b) const
	{
		const __int64& t1 = *(__int64*) &a;
		const __int64& t2 = *(__int64*) &b;
		const __int64 distance = (t2 - t1) / 10000;
		return !((distance < -tolerance_) || (distance > tolerance_));
	}
};


/*!
 * 停止イベントによる割り込み用例外
 */
class CWatchThreadInterruptedException : public std::exception
{
public:
	static void checkAndThrow(HANDLE hStopEvent);
};


/*!
 * 1つの設定に対する監視を行う監視部本体(スレッド)。
 */
class CWatchThread
{
private:
	CWatchThread(const CWatchThread&); //<! 実装なし
	CWatchThread& operator=(const CWatchThread&); //<! 実装なし

private:

	typedef std::map<tstring, CFileInfo> CFileInfoMap;

	CFileInfoMap fileInfoMap_;

	typedef std::list<tstring> FileNameList;

	FileNameList pendingList_;

	LoggerPtr pLogger_;

	ActionInvokerPtr pActionInvoker_;

	const FileTimeComparator fileTimeComparator_;

	CSettingInfo settingInfo_;

	HANDLE hStopEvent_;

	HANDLE hThread_;

	DWORD nThreadID_;


	DWORD spanSweepDeletedEntries_;

	DWORD spanHeapCompaction_;

	DWORD spanSavePersist_;

	DWORD baseCycle_;

	DWORD nResetRunCount_;

	bool bContinueWhenFailedImpersonate_;

	bool bRevertToSelfOnAction_;

	bool bPersistAtForceInterval_;

	CNameMatcher nameMatcher_;

public:

	CWatchThread(const CSettingInfo& v_settingInfo);

	virtual ~CWatchThread();

	const CSettingInfo& getSettingInfo() const;

	/*!
	 * スレッドを開始します。
	 * すでにスレッドが開始済みであれば何もしません。
	 */
	void Start();

	/*!
	 * 停止要求だけ送信しスレッドの停止を待ちません。
	 * リソースを解放するため、あとでStop()を呼び出す必要があります。
	 */
	void StopNoWait();

	/*!
	 * スレッドを停止します。
	 * スレッドが停止するまで待機します。
	 * すでに停止していれば何もしません。
	 */
	void Stop();

	/*!
	 * スレッドが実行中であるか判定します。
	 * \return 実行中であればtrue
	 */
	bool isRunning() const;

private:

	/*!
	 * スレッドプロシージャ
	 */
	static unsigned __stdcall ThreadProc(void* pParam);

	/*!
	 * スレッドのメインループ
	 */
	void run();

	/*!
	 * 削除予定のエントリのうち、猶予期間を過ぎたエントリを除去する。
	 * 削除されたエントリの数を返します。
	 * \param ftNow 現在時刻
	 * \param bSkipDeletePending 削除予定ファイルが一つもない場合にtrueに設定される。
	 * \return 削除されたエントリの数
	 */
	int sweepDeletedEntries(const FILETIME &ftNow, bool &bSkipDeletePending);

	/*!
	 * 監視ディレクトリを走査します。
	 * v_enableEventがtrueであれば、追加または更新による確定待ちフラグをたてます。
	 * falseの場合はエントリを追加・更新・削除するだけで確定待ちは発生しません。
	 * 追加・変更・削除されたエントリの数を返します。
	 * \param ftNow 現在日時
	 * \param v_enableEvent 確定待ちフラグをたてる場合はtrue
	 * \return 追加・変更・削除されたエントリの数
	 */
	int EnumFile(const FILETIME &ftNow, const bool v_enableEvent);

	/*!
	 * 監視ディレクトリを再帰的に走査します。
	 * \param ftNow 現在日時
	 * \param v_baseDir 走査ディレクトリ
	 * \param v_depth 探索深さの現在位置
	 * \param v_enableEvent 確定待ちフラグをたてる場合はtrue
	 * \param numOfModified 監視リストに変更があった個数を格納する参照
	 * \return 初回(depth=0)ディレクトリ走査に成功すればtrue、そうでなければfalse
	 */
	bool EnumFileRecursive(const FILETIME &ftNow,
						   const tstring &v_baseDir,
						   const int v_depth,
						   const bool v_enableEvent,
						   int &numOfModified);

	/*!
	 * 確定待ちのエントリが確定したかチェックし、確定した場合、アクションを実行します。
	 * 一回の確定判定により複数のエントリが確定する場合、実行順序はファイルの更新日の古い順、
	 * それでも同順位となる場合は絶対パス名の昇順で行われます。
	 * \param ftNow 現在時刻
	 * \param bSkipCommitPending 確定待ちファイル、または確定ファイルが一つもない場合にtrueに設定される。
	 * \param impersonateUser ユーザの偽装オブジェクト
	 * \return 確定したエントリ数
	 */
	int CheckDoAction(const FILETIME &ftNow, bool &bSkipCommitPending, CImpersonateUser& impersonateUser);

	/*!
	 * 監視状態を保存します。
	 * \return 保存された場合はtrue、保存されなかった場合はfalse
	 */
	bool Save();

	/*!
	 * 監視状態を復元します。
	 * 復元にあたり、現在の監視状態はリセットされます。
	 * \return 復元された場合はtrue、復元されなかった場合はfalse
	 */
	bool Load();

	/*!
	 * ファイルの絶対パスを指定して、そのファイルのファイルサイズを取得する。
	 * また、同時に、ファイルが読み込みモードでオープンできるかチェックし、できない場合はfalseを返します。
	 * falseが返された場合は格納先は変更されません。
	 * Win32エラーコードを取得する場合はGetLastError()を呼び出します。
	 * \param v_path ファイルの絶対パス
	 * \param pSize ファイルサイズの保存先、NULLの場合は格納しない
	 * \param pLastModified 最終更新日の保存先、NULLの場合は格納しない
	 * \param pAttrs 属性の保存先、NULLの場合は格納しない
	 * \return ファイルサイズの取得に失敗した場合
	 */
	static bool getFileInfo(const tstring& v_path, unsigned __int64 *pSize, FILETIME *pLastModified, DWORD *pAttrs) throw();

};
