module yamalib.log.log;

private import std.stream;
private import std.format;
private import std.string;
private import std.date;
private import std.utf;
private import std.regexp;
private import std.thread;
private import std.path;

private import y4d_aux.widestring;
private import y4d_aux.filesys;

private import yamalib.log.logformatter;
private import yamalib.log.logwriter;
private import yamalib.date.date;
private import yamalib.auxil.properties;
private import yamalib.log.netreport;


version(Win32){
	
extern(Windows) export int MessageBoxA(
    void*   hWnd,       // オーナーウィンドウのハンドル
    char* pszText,    // 表示文字列
    char* pszCaption, // キャプションバーの表示文字列
    uint   uType       // メッセージボックスのタイプ
);

} else {
	// 未実装
}

/**
   ログ出力クラス
   */
class Log {
	/// ログ出力ファイル名
	package static final char[] LOG_FILENAME = "log";
	/// log出力ディレクトリ名
	package static final char[] LOG_DIR = "log";
	/// 標準ログ接頭辞
	package static final char[] LOG_INFO = "[INFO]--";
	/// 注目ログ接頭辞
	package static final char[] LOG_LOOK = "[LOOK]--";
	/// 警告ログ接頭辞
	package static final char[] LOG_WARN = "[WARN]--";
	/// エラーログ接頭辞
	package static final char[] LOG_ERROR = "[ERROR]-";
	/// エラーログ接頭辞
	package static final char[] LOG_FATAL = "[FATAL]-";
	/// ログ出力フラグ
	private static final bool LOG_OUT_FLG = true;
	/// スレッド対応させるか
	private static final bool THREAD_LOGGING = false;
	

	/// ログ出力が有効か
	static bool isLogEnable() {
		return LOG_OUT_FLG;
	}

	/// 初期化処理
	static void onInit() {
		try {
			Properties prop = Properties.getInstance("setting.txt");
			LOG_OUT_FLG = cast(bool) (prop.getPropertyNum("log_out", 1) == 1);
	
			DateTime dt = DateUtil.getDateTime();
			char[] date = DateUtil.format(dt, "yyyyMMddhhmmss");	
			char[] filename = LOG_DIR ~ LOG_FILENAME ~ date ~ ".txt";

			// TODO (デバッグバージョン)
			LOG_OUT_FLG = true;
			
			if (LOG_OUT_FLG) {
				onInitOutDir(LOG_DIR);
				logWriter = new LogWriter();
				logWriter.setPath(LOG_DIR);
				logWriterForSound = new LogWriter();
				logWriterForSound.setPath("log\\snd\\");
				logWriterForSound.write("== START SOUND LOG ==");
			}
			
			// 初期ログフィルターの設定
			try {
				char[] w = prop.getProperty("log_filtter");
				if (w is null) {
					return;
				}
				char[][] words = std.string.split( w, "|" );
				foreach (char[] str; words) {
					addLogFiltter(str);
				}
			} catch (Exception e) {
				printf("ERROR : log filtter setting failure!!");
			} 
		} finally {
			init = true;
		}
	}
	
	/// ログ出力ディレクトリを準備する
	static void onInitOutDir(char[] dirPath) {
		if (dirPath is null) {
			return;
		}
		if (std.file.exists(dirPath)) {
			return;
		}
		
		char[] normalizeDirPath = std.string.replace(dirPath, "/", .sep);
		char[][] dirs = std.string.split(normalizeDirPath, .sep);
		char[] workDir;
		foreach (dir;dirs) {
			if (dir.length == 0) {
				break;
			}
			workDir ~= dir ~ .sep;
			std.file.mkdir(workDir);
		}
		
	}
	
	/// 終了処理
	static void disporse() {
		if (logWriter !is null) {
			logWriter.shutdown();
		}
		if (logWriterForSound !is null) {
			logWriterForSound.write("== END SOUND LOG ==");
			logWriterForSound.shutdown();
		}
	}
	
	/// 静的デストラクタ
	static ~this() {
		disporse();
		// このクラスが破棄されたログ出力できないと思うしかない
		LOG_OUT_FLG = false; 
//		LogFormatter.createFormattedLog( FileSys.read(LOG_FILENAME) );
	}
	
	/// GUIダイアログの設定
	static void setDialogDelegate(void delegate(char[]) dg) {
		dgDialog = dg;
	}
	
	/// サウンドログを出力する
	static void printSoundLog(...) {
		if (!LOG_OUT_FLG) return;
		char[] strMessage = getFormatedMessage(_arguments, _argptr);
		innerPrintSingleSound(strMessage);
	}

	/// 標準ログを出力する
	static void print(...) {
		if (!LOG_OUT_FLG) return;
		
		char[] strMessage = getFormatedMessage(_arguments, _argptr);
		
		if (strMessage is null) return;
		
		innerPrint( LOG_INFO ~ strMessage );
	}
	
	/// 標準ログを出力する
	static void print(bool bOut, char[] strFromat, ...) {
		
		if (!bOut) return;
		
		print(strFromat, _arguments, _argptr);		
	}

	/// 警告メッセージの出力
	static void printWarn(bool bOut, char[] strFromat, ...) {
		if (!bOut) return;
		
		printWarn(strFromat, _arguments, _argptr);		
	}

	/// 警告メッセージの出力
	static void printWarn(...) {
		if (!LOG_OUT_FLG) return;

		char[] strMessage = getFormatedMessage(_arguments, _argptr);
		
		if (strMessage is null) return;
		
		innerPrint(LOG_WARN ~ strMessage);
	}
	
	/// エラーメッセージの出力
	static void printError(bool bOut, char[] strFromat, ...) {
		if (!bOut) return;
		
		printError(strFromat, _arguments, _argptr);		
	}

	/// エラーメッセージの出力
	static void printError(...) {
		if (!LOG_OUT_FLG) return;

		char[] strMessage = getFormatedMessage(_arguments, _argptr);
		
		if (strMessage is null) return;
		
		innerPrint(LOG_ERROR ~ strMessage);
	}
	
	/// 注目メッセージの出力
	static void printLook(...) {
		if (!LOG_OUT_FLG) return;

		char[] strMessage = getFormatedMessage(_arguments, _argptr);
		
		if (strMessage is null) return;
		
		innerPrint(LOG_LOOK ~ strMessage);
	}
	
	/// 注目メッセージの出力
	static void printLook(bool bOut, char[] strFromat, ...) {
		if (!bOut) return;
		
		printLook(strFromat, _arguments, _argptr);		
	}
	
	/// 致命的エラーログの出力
	static void printFatal(...) {
		if (!LOG_OUT_FLG) return;

		char[] strMessage = getFormatedMessage(_arguments, _argptr);
		
		if (strMessage is null) return;
		// 致命的エラー
		bFatal = true;
		innerPrint(LOG_FATAL ~ strMessage);
	}
	
	/// メッセージボックスを表示します
	static void showMessageBox(char* str) {
		// TODO
	}
	
	/**
	   コンソールに出力するかどうかを設定します
	   */
	static void setPrintConsorl(bool b) {
		printConsorl = b;
	}
	
	/// ログ出力するフィルターをセットします。
	static void setLogFiltter(char[] str) 
	/**
		ここでセットした文字が含まれているものに対してのみ
		ログを出力します
	*/
	{
		strFiltering = null;
		strFiltering ~= str;
	}

	/// ログ出力するフィルターを追加します。
	static void addLogFiltter(char[] str) 
	/**
		ここでセットした文字が含まれているものに対してのみ
		ログを出力します
		追加されたフィルター文字は OR 条件　（いずれかがふくまれている）
		となります
	*/
	{
		if ( !(str is null) ) {
			strFiltering ~= str;
		}
	}
	
	/// フィルタとして正規表現パターンを登録する
	static void addLogFiltterRegExp(char[] str) 
	/**
		ここで設定した正規表現パターンは、setLogFiltter / addLogFiltter の
		一致条件と AND になります。
	*/
	{
		strFilterRegExp = str;
	}
	
	/// フィルターをクリアする
	static void clearLogFiltter() 
	{
		strFiltering = null;
	}

protected:
	static bool printConsorl = true;
	static char[][] strFiltering;		//!< フィルタ文字配列
	static char[]	strFilterRegExp;	//!< フィルタ正規表現
	static bool init = false;
	static bool bFatal = false;
	static void delegate(char[]) dgDialog;
	
	static LogWriter logWriter;
	static LogWriter logWriterForSound;
	
private:

	/// 現在日時の取得
	static char[] getSystemTime() {

		DateTime dt = DateUtil.getDateTime();
		return DateUtil.format(dt, "yyyy/MM/dd hh:mm:ss");
	}
	
	/// フォーマットした結果の文字列を取得します
	static char[] getFormatedMessage(TypeInfo[] _arguments, void* _argptr) {
		char[] str;

		// ローカル関数
		void putc(dchar c) {
			str ~= c;
		}
		
		try {
			// printf風フォーマット
			std.format.doFormat(&putc, _arguments, _argptr);
		} catch ( FormatError fe ) {
			// フォーマットエラー
			printf("Log - Format Error:%*s\n", fe.toString());
			return null;
		} catch ( Error e ) {
			// フォーマットエラー
			printf("Log - Error:%*s\n", e.toString());
			return null;
		} catch {
			return null;
		}
		
		return str;
	}
	
	/// 改行コードを検索してインデントを行う
	static char[] indentMessage(char[] strMessage) {
		if (strMessage[length-1] == '\n') {
			strMessage.length = strMessage.length - 1;
		}
		
		return std.string.replace( strMessage, "\n", "\t\t\n" );
	}
	
	/// オンラインレポート
	static void sendReport(char[] msg) {
		NetReport nr = new NetReport();
		auto data = new ReportFormDoBBS();
printf("###########################report\n");
		try {
			bool res = false;
			data.setMessage(msg);
			if ( !nr.open( data.getHostName() ) ) {
				printf("open false!!\n");
				return;
			}
			
			if (!nr.send(cast(ReportForm) data)) {
				printf("send false!!\n");
				return;
			}
			printf("send ok!\n");
		} catch (Exception e) {
			Log.printLook("sendReport : [%s]", e.toString());
		} finally {
			nr.close();
		}
	}
	
	/// 内部プリント
	static void innerPrint(char[] strMessage) {
		try {
			
			if (!init) {
				onInit();
			}
		
			if ( !LOG_OUT_FLG || !init) return;
			if ( strFiltering.length != 0 ) {
				bool bFindMarker = false;
				foreach ( inout char[] str; strFiltering ) {
					if ( std.string.ifind(strMessage, str) >= 0 ) {
						bFindMarker = true;
						break;
					}
				} 
				/* セットしてあるフィルタに一致するモノはなかった */
				if ( !bFindMarker ) return;
			}
			if ( !(strFilterRegExp is null) ) {
				if ( std.regexp.find(strMessage, strFilterRegExp) == -1 ) {
					// 一致するものはなかった
					return;
				}
			}
			
			char[] strSysTime = getSystemTime();
	
			// メッセージが空だったら何もせんでいいでそ？
			if (strMessage is null) return;
			
			strMessage = indentMessage(strMessage);
			innerPrintSingle(strSysTime ~ ": " ~ strMessage);

			if (bFatal) {
				sendReport(strMessage);
			}
		
		} catch (Exception e) {
			// ログクラスではどんなエラーも無視する
			printf("Log#innerPrint RuntimeException [%s] [%s]\n", e.toString(), e.msg);
		} finally {
			bFatal = false;
		}

	}
	
	/// シングルスレッド版書き出し
	static void innerPrintSingle(char[] strMessage) {
//		if ( logFile.isOpen() ) {
//			logFile.writeLine(strMessage);
//		} 
		if (logWriter !is null) {
			logWriter.write(strMessage);
		}

		if (printConsorl) {
			printf( cast(char*) (strMessage ~ "\n") );
		}
	}
	
	/// シングルスレッド版書き出し
	static void innerPrintSingleSound(char[] strMessage) {
		if (logWriterForSound !is null) {
			logWriterForSound.write(strMessage);
		}

		if (printConsorl) {
			printf( cast(char*) (strMessage ~ "\n") );
		}
	}
	
}
