using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Wayne.Lib.Log
{
    /// <summary>
    /// Class used to make debug logs.
    /// </summary>
    /// <example>
    /// This is an example of how to write a debug log entry.
    /// <code>
    /// using (DebugLogger dLog = new DebugLogger(this))
    /// {
    ///     if (dLog.IsActive(DebugLogLevel.Detailed))
    ///     {
    ///         dLog.Add("This is line 1.", DebugLogLevel.Detailed);
    ///         dLog.Add("This is line 2.", "MyCategory", DebugLogLevel.Detailed);
    ///     }
    /// }
    /// </code>
    /// </example>
    public sealed class DebugLogger : IDisposable, IIdentifiableEntity, IDebugLogger
    {
        static NLog.Logger fdcClientLogger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("FdcClient");

        #region Fields

        private IIdentifiableEntity entity;
        private bool persistent;
        private object categoryDebugLevelLock = new object();
        private Dictionary<object, DebugLogLevel> categoryDebugLevel = new Dictionary<object, DebugLogLevel>();
        private bool logPersistentInfo;
        private bool disposed;

        #endregion

        #region Constructors

        /// <summary>
        /// Construction of non-persistent DebugLogger.
        /// </summary>
        /// <param name="entity"></param>
        public DebugLogger(IIdentifiableEntity entity)
        {
            if (Logger.IsClosed)
                disposed = true;// throw new LogException(LogExceptionType.LoggerClosed);

            if (entity != null)
                this.entity = entity;
            else
                this.entity = IdentifiableEntity.Empty;
        }

        /// <summary>
        /// Construction
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="persistent"></param>
        public DebugLogger(IIdentifiableEntity entity, bool persistent)
        {
            //if (Logger.IsClosed)
            //    disposed = true;// throw new LogException(LogExceptionType.LoggerClosed);

            //else
            //{
            //    if (entity != null)
            //        this.entity = entity;
            //    else
            //        this.entity = IdentifiableEntity.Empty;

            //    this.persistent = persistent;
            //    if (persistent)
            //    {
            //        Logger.RegisterPersistentLogObject(this as DebugLogger);
            //        logPersistentInfo = true;
            //    }
            //}
        }

        /// <summary>
        /// Finalizer.
        /// </summary>
        ~DebugLogger()
        {
            Dispose(false);
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Dispose.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose.
        /// </summary>
        private void Dispose(bool disposing)
        {
            if (!disposed)
            {
                disposed = true;

                if (disposing)
                {
                    if (persistent)
                    {
                        Logger.UnregisterPersistentLogObject(this as DebugLogger);
                    }
                    Logger.UnregisterEntity(entity);
                }

                lock (categoryDebugLevelLock)
                {
                    categoryDebugLevel.Clear();
                }
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// The identifiable entity that has created this debug log. 
        /// </summary>
        public IIdentifiableEntity Entity
        {
            get { return entity; }
        }

        /// <summary>
        /// Tells whether the debug log is persistent or not. 
        /// </summary>
        public bool Persistent
        {
            get { return persistent; }
        }

        #endregion

        #region Methods: IsActive

        /// <summary>
        /// Tells whether the default category is active in the Normal level.
        /// </summary>
        public bool IsActive()
        {
            return true;
            return !disposed && (DebugLogLevel.Normal <= GetDebugLevel(string.Empty));
        }

        /// <summary>
        /// Tells whether the given category is active in the Normal level.
        /// </summary>
        public bool IsActive(object category)
        {
            return true;
            return !disposed && (DebugLogLevel.Normal <= GetDebugLevel(category));
        }

        /// <summary>
        /// Tells whether the default category is active in the given level.
        /// </summary>
        public bool IsActive(DebugLogLevel debugLogLevel)
        {
            return true;
            return !disposed && (debugLogLevel != DebugLogLevel.Excluded) && (debugLogLevel <= GetDebugLevel(string.Empty));
        }

        /// <summary>
        /// Tells whether the given category is active in the given level.
        /// </summary>
        public bool IsActive(object category, DebugLogLevel debugLogLevel)
        {
            return true;
            return !disposed && (debugLogLevel != DebugLogLevel.Excluded) && (debugLogLevel <= GetDebugLevel(category));
        }

        #endregion

        #region Methods: GetDebugLevel

        /// <summary>
        /// Get the current debug level for the default category.
        /// </summary>
        public DebugLogLevel GetDebugLevel()
        {
            return GetDebugLevel(string.Empty);
        }

        /// <summary>
        /// Get the current debug level for the given category.
        /// </summary>
        public DebugLogLevel GetDebugLevel(object category)
        {
            return DebugLogLevel.Detailed;
            if (disposed || Logger.IsClosed)
                return DebugLogLevel.Excluded;

            if (category == null)
                category = string.Empty;

            // Check if this category's debuglevel is cached.
            lock (categoryDebugLevelLock)
            {
                DebugLogLevel cachedDebugLogLevel;
                if (categoryDebugLevel.TryGetValue(category, out cachedDebugLogLevel))
                    return cachedDebugLogLevel;
            }

            // Get a list of the logWriters that wants to log me.
            EntityCategory entityCategory = Logger.DebugConfig.GetEntityCategory(entity, category);
            LogWriter[] logWriters = Logger.DebugConfig.GetLogWriters(entityCategory);

            // Iterate through the writers and get the highest debug level.
            DebugLogLevel debugLogLevel = DebugLogLevel.Excluded;
            foreach (LogWriter writer in logWriters)
            {
                debugLogLevel = MaxDebugLogLevel(debugLogLevel, writer.GetDebugLogLevel(entityCategory));
            }

            // Cache this debug level for future queries.
            lock (categoryDebugLevelLock)
            {
                categoryDebugLevel[category] = debugLogLevel;
            }

            return debugLogLevel;
        }

        #endregion

        #region Methods: Add

        /// <summary>
        /// Adds a new object to the debug log entry.
        /// </summary>
        /// <param name="obj">The log object that are added.</param>
        [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj")]
        public void Add(object obj)
        {
            fdcClientLogger.Debug(obj);
            return;
            if (disposed)
                return;

            if (logPersistentInfo)
                LogPersistentInfo();

            Logger.AddEntry(new DebugLogEntry(entity, obj));
        }

        /// <summary>
        /// Adds the object if debuglogger is active with the default category and detaillevel.
        /// </summary>
        /// <param name="args"></param>
        /// <param name="formatString"></param>
        public void AddIfActive(string formatString, params object[] args)
        {
            fdcClientLogger.Debug(string.Format(formatString, args));
            return;
            if (IsActive())
            {
                try
                {
                    Add(string.Format(formatString, args));
                }
                catch (FormatException fe)
                {
                    Add(fe);
                    Add(formatString);
                }
            }
        }

        /// <summary>
        /// Adds the object if debuglogger is active with the default category and detaillevel Detailed .
        /// </summary>
        /// <param name="formatString"></param>
        /// <param name="args"></param>
        public void AddIfActiveDetailed(string formatString, params object[] args)
        {
            fdcClientLogger.Debug(string.Format(formatString, args));
            return;
            if (IsActive(DebugLogLevel.Detailed))
            {
                try
                {
                    Add(string.Format(formatString, args), DebugLogLevel.Detailed);
                }
                catch (FormatException fe)
                {
                    Add(fe);
                    Add(formatString);
                }
            }
        }



        /// <summary>
        /// Adds a new object to the debug log entry.
        /// </summary>
        /// <param name="obj">The log object that are added.</param>
        /// <param name="level">TDB</param>
        public void Add(object obj, DebugLogLevel level)
        {
            fdcClientLogger.Debug(obj);
            return;
            if (disposed)
                return;

            if (logPersistentInfo)
                LogPersistentInfo();

            Logger.AddEntry(new DebugLogEntry(entity, obj, level));
        }

        /// <summary>
        /// Adds a new object to the debug log entry.
        /// </summary>
        /// <param name="obj">The log object that are added.</param>
        /// <param name="category">A specific category that this log is about.</param>
        [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj")]
        public void Add(object obj, object category)
        {
            fdcClientLogger.Debug(obj);
            return;
            if (disposed)
                return;

            if (logPersistentInfo)
                LogPersistentInfo();

            Logger.AddEntry(new DebugLogEntry(entity, obj, category));
        }

        /// <summary>
        /// Adds a new object to the debug log entry.
        /// </summary>
        /// <param name="obj">The log object that are added.</param>
        /// <param name="category">A specific category that this log is about.</param>
        /// <param name="level">TDB</param>
        [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj")]
        public void Add(object obj, object category, DebugLogLevel level)
        {
            fdcClientLogger.Debug(obj);
            return;
            if (disposed)
                return;

            if (logPersistentInfo)
                LogPersistentInfo();

            Logger.AddEntry(new DebugLogEntry(entity, obj, category, level));
        }

        /// <summary>
        /// 
        /// </summary>
        private void LogPersistentInfo()
        {
            //Logger.AddEntry(new DebugLogEntry(entity, "Hooked persistent DebugLogger to logfile: " + IdentifiableEntity.ToString(entity, true), string.Empty, DebugLogLevel.Normal));
            //logPersistentInfo = false;
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// Invalidates the internal flags for configuration reading.
        /// </summary>
        internal void Invalidate()
        {
            //lock (categoryDebugLevelLock)
            //{
            //    categoryDebugLevel.Clear();
            //}
        }

        /// <summary>
        /// Static method to get the highest of two DebugLogLevel's.
        /// </summary>
        /// <param name="level1"></param>
        /// <param name="level2"></param>
        /// <returns></returns>
        public static DebugLogLevel MaxDebugLogLevel(DebugLogLevel level1, DebugLogLevel level2)
        {
            if (level1 > level2)
                return level1;
            else
                return level2;
        }

        #endregion

        #region IIdentifiableEntity Members

        /// <summary>
        /// The Id of the Entity.
        /// </summary>
        public int Id
        {
            get { return entity.Id; }
        }

        /// <summary>
        /// The EntityType of the Entity.
        /// </summary>
        public string EntityType
        {
            get { return entity.EntityType; }
        }

        /// <summary>
        /// This is used by the logger and should never be set by implementing classes
        /// </summary>
        public string FullEntityName { get; set; }

        /// <summary>
        /// The EntitySubType of the Entity.
        /// </summary>
        public string EntitySubType
        {
            get { return entity.EntitySubType; }
        }

        /// <summary>
        /// The ParentEntity of the Entity.
        /// </summary>
        public IIdentifiableEntity ParentEntity
        {
            get { return entity.ParentEntity; }
        }

        #endregion


    }
}