#pragma once

#include <memory>

/*!
 * ロガー
 */
class Logger
{
public:
	virtual ~Logger() = 0 {};

	/*!
	 * ログ出力が有効であるか?
	 * \return 有効であればtrue
	 */
	virtual bool IsEnableLogging() const throw() = 0;

	/*!
	 * ログレベル
	 */
	virtual int GetLogThreshold() const throw() = 0;


	/*!
	 * ログファイルに書き込む。
	 * ログが有効になっていないか、ログファイル名が空の場合は出力しない。
	 * また、ログの書き込みに失敗してもエラーは返さない。
	 * ログファイルはログを書き込むたびに追記モードでオープンして書き込み後即座にクローズされる。
	 * 書き込みはログファイル名が複数の監視スレッドで同一である場合を想定して書き込みは排他ロックがかけられる。
	 * \param ログレベル
	 * \param systime タイムスタンプ、nullの場合は現在時刻が使用される。
	 * \param errcode Win32のエラーコード、0以外の場合はログに出力される。
	 * \param mes メッセージ、nullの場合は空文字
	 */
	virtual void LogMessage(int logLevel, const SYSTEMTIME* systime, DWORD errcode, LPCTSTR mes) = 0;

	/*!
	 * メッセージIDによるログファイルに書き込む。
	 * それ以外は他のメソッドと同様である。
	 * \param ログレベル
	 * \param systime タイムスタンプ、nullの場合は現在時刻が使用される。
	 * \param errcode エラーコード
	 * \param messageID メッセージID
	 */
	virtual void LogMessage(int logLevel, const SYSTEMTIME* systime, DWORD errcode, DWORD messageID, ...) = 0;

	/*!
	 * 最後にファイルに書き込んだ時刻
	 */
	virtual const FILETIME& GetLastWrite() const = 0;

	/*!
	 * 定期的に呼び出すことでログのフラッシュまたはクローズなどの時間処理を実施する。
	 * ログを書き込む場合は定期的に、このメソッドを呼び出す必要があります。
	 */
	virtual void Tick(const FILETIME &ftNow) = 0;
};


typedef std::auto_ptr<Logger> LoggerPtr;



/*!
 * ロガーの実装。
 * ログファイルは必要になったときに開かれます。
 * 都度閉じるかどうか指示するフラグを指定できます。
 * このフラグが有効でなければ明示的にClose()を呼び出すか、デストラクタがあるまで
 * ログファイルは開かれたままとなります。
 */
class FileLogger
	: public Logger
{
private:

	HANDLE hLogFile_;

	FILETIME lastWrite_;

	bool enabled_;

	int logThreshold_;

	int lockTryLimit_;

	tstring logPath_;

public:

	FileLogger();

	FileLogger(LPCTSTR logPath);

	virtual ~FileLogger() throw();

	virtual LPCTSTR GetLogPath() const throw();

	virtual void SetLogPath(LPCTSTR logPath) throw();

	virtual void SetEnableLogging(bool enabled) throw();

	virtual bool IsEnableLogging() const throw();

	virtual int GetLogThreshold() const throw();

	virtual void SetLogThreshold(int logLevel);

	virtual void LogMessage(int logLevel, const SYSTEMTIME* systime, DWORD errcode, LPCTSTR mes);

	virtual void LogMessage(int logLevel, const SYSTEMTIME* systime, DWORD errcode, DWORD messageID, ...);

	virtual const FILETIME& GetLastWrite() const;

	/*!
	 * 定期的に呼び出すことでログのフラッシュまたはクローズなどの時間処理を実施する。
	 * ログを書き込む場合は定期的に、このメソッドを呼び出す必要があります。
	 */
	virtual void Tick(const FILETIME &ftNow);

	/*!
	 * ログファイルが開いているか?
	 * \return 開いている場合はtrue
	 */
	virtual bool IsOpened() const;

	/*!
	 * ログファイルが開いてなければ開きます。
	 * ログが無効であるか、ログファイル名の指定がない場合は開きません。
	 * またファイルのオープンに失敗した場合はfalseを返します。
	 * ファイルを開いたか、すでに開いている場合はtrueを返します。
	 * \return ログファイルがオープンされている場合はtrue、そうでなければfalse
	 */
	virtual bool Open();

	/*!
	 * ログファイルが開かれていれば閉じます。
	 */
	virtual void Close();

	/*!
	 * ログの日付が変更されていればローテートします。
	 * ファイルが開かれてなければ開きます。
	 * 自動的に閉じることはありません。
	 * 現在のログは.bakファイルにコピーされたあと、サイズ0まで縮退します。
	 */
	virtual void RotateLog();

protected:

	/*!
	 * 内部用のログ出力メソッド。
	 * ログメッセージの構築とファイル出力を行う。
	 * 排他ロック制御やファイルのオープン処理は呼び出しもとで処理されている前提としている。
	 * ファイル出力後のログクローズもしくはフラッシュの処理も行わない。
	 */
	virtual void InternalLogMessage(int logLevel, const SYSTEMTIME* systime, DWORD errcode, LPCTSTR mes);
};


/*!
 * ファイルの排他ロックオブジェクト
 */
class FileLockObject
{
private:
	bool locked_;

	HANDLE hFile_;

	OVERLAPPED overlapped_;

public:

	/*!
	 * 排他ロックします。。
	 * 排他ロックできなかった場合、100mSecごとにtryLimit回数まで試行します。
	 * 排他ロックできない場合はロックなしのまま進みます。
	 * tryLimitが0の場合は排他ロックを行いません。
	 * 排他ロックされているかはIsLockedで判定します。
	 * \param tryLimit 排他ロックを試行する最大回数、0の場合は排他ロックしない。
	 */
	FileLockObject(HANDLE hFile, int tryLimit);

	~FileLockObject();

	inline bool IsLocked() const
	{
		return locked_;
	}

	void Release();
};
