using Microsoft.Extensions.Logging;
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
    {
        public static ILogger logger { get; set; }

        #region Inner class

        class CategoryDebugLevel
        {
            public object Category { get; set; }
            public DebugLogLevel LogLevel { get; set; }
        }

        #endregion

        #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;
        private DebugLogLevel? defaultCategoryDebugLogLevel;
        private CategoryDebugLevel latestCategoryDebugLogLevel;

        #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 !disposed && (DebugLogLevel.Normal <= GetDebugLevel(string.Empty));
            return true;
        }

        /// <summary>
        /// Tells whether the given category is active in the Normal level.
        /// </summary>
        public bool IsActive(object category)
        {
            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 !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 !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)
        {
            //if (disposed || Logger.IsClosed)
            //{
            //    return DebugLogLevel.Excluded;
            //}

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

            //Cached empty category
            if (category.Equals(string.Empty))
            {
                if (defaultCategoryDebugLogLevel.HasValue)
                    return defaultCategoryDebugLogLevel.Value;
            }

            //Cached latest category
            var localCachedItem = latestCategoryDebugLogLevel;
            if (localCachedItem != null && localCachedItem.Category.Equals(category))
            {
                return localCachedItem.LogLevel;
            }

            // Cached Dictionary lookup of category's debuglevel 
            lock (categoryDebugLevelLock)
            {
                DebugLogLevel cachedDebugLogLevel;
                if (categoryDebugLevel.TryGetValue(category, out cachedDebugLogLevel))
                {
                    latestCategoryDebugLogLevel = new CategoryDebugLevel()
                    {
                        Category = category,
                        LogLevel = cachedDebugLogLevel
                    };
                    return cachedDebugLogLevel;
            }
            }

            //Not cached -  evaluate and populate cache!

            // 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));
            //}

            //Populate Cache for default category
            if (category.Equals(string.Empty))
            {
                defaultCategoryDebugLogLevel = debugLogLevel;
            }
            else 
            {
                //Populate Cache for latest category
                latestCategoryDebugLogLevel = new CategoryDebugLevel()
                {
                    Category = category,
                    LogLevel = debugLogLevel
                };

            // Cache this debug level for future queries.
            lock (categoryDebugLevelLock)
            {
                    //Populate Level 4 cache
                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)
        {
            if (disposed)
                return;

            if (logPersistentInfo)
                LogPersistentInfo();

            logger.LogDebug(obj.ToString());
            //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)
        {
            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)
        {
            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)
        {
            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)
        {
            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)
        {
            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));
            logger.LogDebug("Hooked persistent DebugLogger to logfile: " + IdentifiableEntity.ToString(entity, true));
            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


    }
}