// Copyright (c) 2008, NTT DATA Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Text;
using TERASOLUNA.Fw.Common.Logging;
using System.Reflection;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Collections;
using TERASOLUNA.Fw.Common;

namespace TERASOLUNA.Fw.Utlib.Logging
{
    /// <summary>
    /// 試験用のユーティティクラス。
    /// </summary>
    public class LogUTUtil : ILog
    {
        /// <summary>
        /// テストメソッド用の正規表現です。
        /// </summary>
        private static readonly string REGEX_FORMAT = "^[A-Z]([a-zA-Z0-9]*)Test([0-9]*)$";

        /// <summary>
        /// ログの出力結果を格納した <see cref="IList{UnitLogObject}"/> クラスのインスタンス。
        /// </summary>
        private static IDictionary<object, IList<LogObject>> _logObjects = new Dictionary<object, IList<LogObject>>();

        /// <summary>
        /// クラス情報格納インスタンス。
        /// </summary>
        private object _classInfo = null;

        /// <summary>
        /// クラス情報の取得および、設定をします。
        /// </summary>
        public object ClassInfo
        {
            get { return _classInfo; }
            set { _classInfo = value; }
        }

        /// <summary>
        /// クラス修飾名を引数にとるコンストラクタです。
        /// </summary>
        /// <param name="className">クラス修飾名。</param>
        public LogUTUtil(string className)
        {
            _classInfo = className;
        }

        /// <summary>
        /// クラスタイプを引数にとるコンストラクタです。
        /// </summary>
        /// <param name="classType">クラスタイプ。</param>
        public LogUTUtil(Type classType)
        {
            _classInfo = classType;
        }

        /// <summary>
        /// ログの出力結果を格納した <see cref="IList{UnitLogObject}"/> クラスのインスタンスを初期化します。
        /// </summary>
        public static void Clear()
        {
            _logObjects.Clear();
        }

        /// <summary>
        /// ログを保存します。
        /// </summary>
        /// <param name="logMessage">ログメッセージ。</param>
        /// <param name="exception">ログの原因となった例外。</param>
        /// <param name="logLevel">ログレベル。</param>
        protected static void AddUnitLogObject(object classInfo, object logMessage, Exception exception, LogLevel logLevel)
        {
            if (!_logObjects.ContainsKey(classInfo))
            {
                _logObjects.Add(classInfo, new List<LogObject>());
            }

            MethodBase methodBase = GetCalledTestCaseMethod();
            Type exceptionType = null;
            if (exception != null)
            {
                exceptionType = exception.GetType();
            }
            LogObject logObject = new LogObject(methodBase, logMessage, exceptionType, logLevel);

            _logObjects[classInfo].Add(logObject);

            System.Diagnostics.Trace.WriteLine(logObject.ToString());
        }

        /// <summary>
        /// ログをチェックします。
        /// </summary>
        /// <param name="logMessage">ログメッセージ。</param>
        /// <param name="exception">ログの原因となった例外。</param>
        /// <param name="logLevel">ログレベル。</param>
        protected static bool CheckUnitLogObject(object classInfo, Type exceptionType, LogLevel logLevel, object logMessage)
        {
            MethodBase methodBase = GetCalledTestCaseMethod();
            LogObject unitLogObject = new LogObject(methodBase, logMessage, exceptionType, logLevel);

            if (_logObjects.ContainsKey(classInfo))
            {
                return _logObjects[classInfo].Contains(unitLogObject);
            }
            
            return false;
        }


        /// <summary>
        /// ログをチェックします。
        /// </summary>
        /// <param name="logMessage">ログメッセージ。</param>
        /// <param name="exception">ログの原因となった例外。</param>
        /// <param name="logLevel">ログレベル。</param>
        protected static bool CheckUnitLogObjectUseMessageId(object classInfo, Type exceptionType, LogLevel logLevel, string code, params string[] args)
        {
            string message = MessageManager.GetMessage(code, args);
            MethodBase methodBase = GetCalledTestCaseMethod();
            LogObject unitLogObject = new LogObject(methodBase, message, exceptionType, logLevel);

            if (_logObjects.ContainsKey(classInfo))
            {
                return _logObjects[classInfo].Contains(unitLogObject);
            }

            return false;
        }

        /// <summary>
        /// このメソッドを呼び出したスレッドが、どのテストケースメソッドから発生したものかを調べる。
        /// </summary>
        /// <returns>メソッドを呼び出したスレッドの起動メソッド。</returns>
        protected static MethodBase GetCalledTestCaseMethod()
        {
            StackTrace stackTrace = new StackTrace();
            Regex regex = new Regex(REGEX_FORMAT);
            foreach(StackFrame sf in stackTrace.GetFrames())
            {
                MethodBase methodBase = sf.GetMethod();
                Match match = regex.Match(methodBase.Name);
                if (regex.IsMatch(methodBase.Name))
                {
                    return methodBase;
                }
            }
            return null;
        }

        /// <summary>
        /// クラス、ログレベル別のカウントを取得します。
        /// </summary>
        /// <param name="classInfo">クラス情報。</param>
        /// <param name="logLevel">ログレベル。</param>
        /// <returns></returns>
        public static int CountLogs(object classInfo, LogLevel logLevel)
        {
            int count = 0;

            if (!_logObjects.ContainsKey(classInfo))
            {
                return count;
            }

            foreach (LogObject logObject in _logObjects[classInfo])
            {
                if (logLevel >= logObject.LogLevel)
                {
                    count++;
                }
            }

            return count;
        }

        /// <summary>
        /// <seealso cref="LogLevel.TRACE"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckTrace(object classInfo, object message)
        {
            return CheckUnitLogObject(classInfo, null, LogLevel.TRACE, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.TRACE"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckTraceUseMessageId(object classInfo, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, null, LogLevel.TRACE, code, args);
        }

        /// <summary>
        /// <seealso cref="LogLevel.TRACE"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckTrace(object classInfo, Type exceptionType, object message)
        {
            return CheckUnitLogObject(classInfo, exceptionType, LogLevel.TRACE, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.TRACE"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckTraceUseMessageId(object classInfo, Type exceptionType, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, exceptionType, LogLevel.TRACE, code, args);
        }

        /// <summary>
        /// <seealso cref="LogLevel.DEBUG"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckDebug(object classInfo, object message)
        {
            return CheckUnitLogObject(classInfo, null, LogLevel.DEBUG, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.DEBUG"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckDebugUseMessageId(object classInfo, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, null, LogLevel.DEBUG, code, args);
        }

        /// <summary>
        /// <seealso cref="LogLevel.DEBIG"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckDebug(object classInfo, Type exceptionType, object message)
        {
            return CheckUnitLogObject(classInfo, exceptionType, LogLevel.DEBUG, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.DEBIG"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckDebugUseMessageId(object classInfo, Type exceptionType, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, exceptionType, LogLevel.DEBUG, code, args);
        }

        /// <summary>
        /// <seealso cref="LogLevel.INFO"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckInfo(object classInfo, object message)
        {
            return CheckUnitLogObject(classInfo, null, LogLevel.INFO, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.INFO"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckInfoUseMessageId(object classInfo, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, null, LogLevel.INFO, code, args);
        }

        /// <summary>
        /// <seealso cref="LogLevel.INFO"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckInfo(object classInfo, Type exceptionType, object message)
        {
            return CheckUnitLogObject(classInfo, exceptionType, LogLevel.INFO, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.INFO"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckInfoUseMessageId(object classInfo, Type exceptionType, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, exceptionType, LogLevel.INFO, code, args);
        }

        /// <summary>
        /// <seealso cref="LogLevel.WARN"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckWarn(object classInfo, object message)
        {
            return CheckUnitLogObject(classInfo, null, LogLevel.WARN, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.WARN"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckWarnUseMessageId(object classInfo, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, null, LogLevel.WARN, code, args);
        }

        /// <summary>
        /// <seealso cref="LogLevel.WARN"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckWarn(object classInfo, Type exceptionType, object message)
        {
            return CheckUnitLogObject(classInfo, exceptionType, LogLevel.WARN, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.WARN"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckWarnUseMessageId(object classInfo, Type exceptionType, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, exceptionType, LogLevel.WARN, code, args);
        }


        /// <summary>
        /// <seealso cref="LogLevel.ERROR"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckError(object classInfo, object message)
        {
            return CheckUnitLogObject(classInfo, null, LogLevel.ERROR, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.ERROR"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckErrorUseMessageId(object classInfo, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, null, LogLevel.ERROR, code, args);
        }

        /// <summary>
        /// <seealso cref="LogLevel.ERROR"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckError(object classInfo, Type exceptionType, object message)
        {
            return CheckUnitLogObject(classInfo, exceptionType, LogLevel.ERROR, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.ERROR"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckErrorUseMessageId(object classInfo, Type exceptionType, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, exceptionType, LogLevel.ERROR, code, args);
        }

        /// <summary>
        /// <seealso cref="LogLevel.FATAL"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckFatal(object classInfo, object message)
        {
            return CheckUnitLogObject(classInfo, null, LogLevel.FATAL, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.FATAL"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckFatalUseMessageId(object classInfo, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, null, LogLevel.FATAL, code, args);
        }

        /// <summary>
        /// <seealso cref="LogLevel.FATAL"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckFatal(object classInfo, Type exceptionType, object message)
        {
            return CheckUnitLogObject(classInfo, exceptionType, LogLevel.FATAL, message);
        }

        /// <summary>
        /// <seealso cref="LogLevel.FATAL"/> レベルで出力されたログのチェックを行う。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        /// <returns>チェックフラグ。</returns>
        public static bool CheckFatalUseMessageId(object classInfo, Type exceptionType, string code, params string[] args)
        {
            return CheckUnitLogObjectUseMessageId(classInfo, exceptionType, LogLevel.FATAL, code, args);
        }

        /// <summary>
        /// <seealso cref="LogLevel.TRACE"/> レベルのログ処理が現在有効かどうかチェックします。
        /// </summary>
        /// <remarks>
        /// 必ず true を返却します。
        /// </remarks>
        public bool IsTraceEnabled
        {
            get { return true; }
        }

        /// <summary>
        /// <seealso cref="LogLevel.DEBUG"/> レベルのログ処理が現在有効かどうかチェックします。
        /// </summary>
        /// <remarks>
        /// 必ず true を返却します。
        /// </remarks>
        public bool IsDebugEnabled
        {
            get { return true; }
        }

        /// <summary>
        /// <seealso cref="LogLevel.INFO"/> レベルのログ処理が現在有効かどうかチェックします。
        /// </summary>
        /// <remarks>
        /// 必ず true を返却します。
        /// </remarks>
        public bool IsInfoEnabled
        {
            get { return true; }
        }

        /// <summary>
        /// <seealso cref="LogLevel.WARN"/> レベルのログ処理が現在有効かどうかチェックします。
        /// </summary>
        /// <remarks>
        /// 必ず true を返却します。
        /// </remarks>
        public bool IsWarnEnabled
        {
            get { return true; }
        }

        /// <summary>
        /// <seealso cref="LogLevel.ERROR"/> レベルのログ処理が現在有効かどうかチェックします。
        /// </summary>
        /// <remarks>
        /// 必ず true を返却します。
        /// </remarks>
        public bool IsErrorEnabled
        {
            get { return true; }
        }

        /// <summary>
        /// <seealso cref="LogLevel.FATAL"/> レベルのログ処理が現在有効かどうかチェックします。
        /// </summary>
        /// <remarks>
        /// 必ず true を返却します。
        /// </remarks>
        public bool IsFatalEnabled
        {
            get { return true; }
        }

        /// <summary>
        /// <seealso cref="LogLevel.TRACE"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        public void Trace(object message)
        {
            Trace(message, null);
        }

        /// <summary>
        /// <seealso cref="LogLevel.TRACE"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        public void Trace(object message, Exception ex)
        {
            AddUnitLogObject(this.ClassInfo, message, ex, LogLevel.TRACE);
        }

        /// <summary>
        /// <seealso cref="LogLevel.DEBUG"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        public void Debug(object message)
        {
            Debug(message, null);
        }

        /// <summary>
        /// <seealso cref="LogLevel.DEBUG"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        public void Debug(object message, Exception ex)
        {
            AddUnitLogObject(this.ClassInfo, message, ex, LogLevel.DEBUG);
        }

        /// <summary>
        /// <seealso cref="LogLevel.INFO"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        public void Info(object message)
        {
            Info(message, null);
        }

        /// <summary>
        /// <seealso cref="LogLevel.INFO"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        public void Info(object message, Exception ex)
        {
            AddUnitLogObject(this.ClassInfo, message, ex, LogLevel.INFO);
        }

        /// <summary>
        /// <seealso cref="LogLevel.WARN"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        public void Warn(object message)
        {
            Warn(message, null);
        }

        /// <summary>
        /// <seealso cref="LogLevel.WARN"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        public void Warn(object message, Exception ex)
        {
            AddUnitLogObject(this.ClassInfo, message, ex, LogLevel.WARN);
        }

        /// <summary>
        /// <seealso cref="LogLevel.ERROR"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログ出力するメッセージ。</param>
        public void Error(object message)
        {
            Error(message, null);
        }

        /// <summary>
        /// <seealso cref="LogLevel.ERROR"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        public void Error(object message, Exception ex)
        {
            AddUnitLogObject(this.ClassInfo, message, ex, LogLevel.ERROR);
        }

        /// <summary>
        /// <seealso cref="LogLevel.FATAL"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        public void Fatal(object message)
        {
            Fatal(message, null);
        }

        /// <summary>
        /// <seealso cref="LogLevel.FATAL"/> レベルでエラーをログ出力します。
        /// </summary>
        /// <param name="message">ログメッセージ。</param>
        /// <param name="ex">ログ出力の原因となった例外。</param>
        public void Fatal(object message, Exception ex)
        {
            AddUnitLogObject(this.ClassInfo, message, ex, LogLevel.FATAL);
        }
    }
}
