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

namespace Wayne.Lib.Log
{
    /// <summary>
    /// The abstract base class for a LogWriter.
    /// </summary>
    internal abstract class LogWriter : IDisposable
    {
        #region Fields

        private string logName;
        private object dataLock = new object();
        private Dictionary<EntityCategory, DebugLogLevel> debugLogLevelDict = new Dictionary<EntityCategory, DebugLogLevel>();
        private bool disposed;

        #endregion

        #region Construction

        /// <summary>
        /// Protected constructor.
        /// </summary>
        /// <param name="logName"></param>
        protected LogWriter(string logName)
        {
            this.logName = logName;
        }

        /// <summary>
        /// Finalizer.
        /// </summary>
        ~LogWriter()
        {
            Dispose(false, "Shutting down.");
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Disposes the instance of LogWriter.
        /// </summary>
        [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")]
        public void Dispose()
        {
            Dispose("Shutting down.");
        }

        /// <summary>
        /// Disposes the instance of LogWriter.
        /// </summary>
        [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")]
        public void Dispose(string reason)
        {
            Dispose(true, reason);
            GC.SuppressFinalize(this);
            disposed = true;
        }

        /// <summary>
        /// Internal dispose method that is called either by the public Dispose method or the finalizer.
        /// </summary>
        /// <param name="disposing"></param>
        /// <param name="reason"></param>
        protected abstract void Dispose(bool disposing, string reason);

        #endregion

        #region Properties

        /// <summary>
        /// Log name that identifies this log writer.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        public string LogName
        {
            get { return logName; }
        }

        ///// <summary>
        ///// The number of character each lines is indented with as default.
        ///// </summary>
        //public abstract int IndentLength { get;}

        #endregion

        #region Virtual Methods

        public abstract void PerformWrite(LogEntry logEntry);

        #endregion

        #region Internal Methods

        /// <summary>
        /// Writes the LogEntry to the log.
        /// </summary>
        /// <param name="logEntry"></param>
        internal void Write(LogEntry logEntry)
        {
            if (disposed)
                return;

            bool doPerformWrite;
            DebugLogEntry debugLogEntry = logEntry as DebugLogEntry;
            EventLogEntry eventLogEntry = logEntry as EventLogEntry;

            if (debugLogEntry != null)
            {
                if (debugLogEntry.LogLevel == DebugLogLevel.Excluded)
                    return;
                DebugLogLevel level = GetDebugLogLevel(debugLogEntry.EntityCategory);
                doPerformWrite = (level >= debugLogEntry.LogLevel);
            }
            else if (eventLogEntry != null)
            {
                if (eventLogEntry.LogLevel == DebugLogLevel.Excluded)
                    return;
                DebugLogLevel level = GetDebugLogLevel(eventLogEntry.EntityCategory);
                doPerformWrite = (level >= eventLogEntry.LogLevel);
            }
            else
                doPerformWrite = true;

            if (doPerformWrite)
            {
                // Call the abstract method that is overriden by the descendant classes.
                PerformWrite(logEntry);
            }
        }

        /// <summary>
        /// Cache the filter data for the given EntityCategory.
        /// </summary>
        /// <param name="entityCategory"></param>
        /// <param name="logLevel"></param>
        internal void CacheFilterData(EntityCategory entityCategory,
            DebugLogLevel logLevel)
        {
            // Cache the log level.
            lock (dataLock)
            {
                DebugLogLevel currentDebugLogLevel;
                if (debugLogLevelDict.TryGetValue(entityCategory, out currentDebugLogLevel))
                    debugLogLevelDict[entityCategory] = DebugLogger.MaxDebugLogLevel(currentDebugLogLevel, logLevel);
                else
                    debugLogLevelDict.Add(entityCategory, logLevel);
            }
        }

        internal virtual void PerformListCleaning(DateTime oldestAllowedTouch)
        {
            #region Clean the logwriterPreparedDict.
            lock (dataLock)
            {
                List<EntityCategory> entityCategoriesToRemove = new List<EntityCategory>();
                foreach (EntityCategory entityCategory in debugLogLevelDict.Keys)
                {
                    if (entityCategory.LastTouched < oldestAllowedTouch)
                        entityCategoriesToRemove.Add(entityCategory);
                }

                //Remove from the list.
                foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove)
                {
                    debugLogLevelDict.Remove(entityCategoryToRemove);
                }
            }

            #endregion
        }

        internal void RemoveEntity(IIdentifiableEntity entity)
        {
            lock (dataLock)
            {
                List<EntityCategory> entityCategoriesToRemove = new List<EntityCategory>();
                foreach (EntityCategory entityCategory in debugLogLevelDict.Keys)
                {
                    if (IdentifiableEntity.Equals(entityCategory.Entity, entity))
                        entityCategoriesToRemove.Add(entityCategory);
                }

                //Remove from the list.
                foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove)
                {
                    debugLogLevelDict.Remove(entityCategoryToRemove);
                }
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Get the debug level for the given EntityCategory.
        /// </summary>
        /// <param name="entityCategory"></param>
        /// <returns></returns>
        public DebugLogLevel GetDebugLogLevel(EntityCategory entityCategory)
        {
            if (disposed)
                return DebugLogLevel.Excluded;
            else if (entityCategory != null)
            {
                lock (dataLock)
                {
                    DebugLogLevel debugLevel;
                    if (debugLogLevelDict.TryGetValue(entityCategory, out debugLevel))
                        return debugLevel;
                }
            }
            return DebugLogLevel.Normal;
        }

        #endregion
    }
}