using System;
using System.Collections.Generic;
using System.Threading;

namespace Wayne.Lib.Log
{
    internal class LoggerThread : IDisposable
    {
        #region Fields

        private readonly Thread thread;
        private readonly AutoResetEvent waitEvent;
        private readonly Queue<LogEntry> entryQueue = new Queue<LogEntry>();
        private readonly object entryQueueLock = new object();
        private readonly object logWritingLock = new object();
        private bool threadShouldExit; // Flag that is used to indicate to the thread that it should exit gracefully.
        private bool disposed;
        private readonly LogConfig logConfig;

        #endregion

        #region Construction & Finalizer

        /// <summary>
        /// Initializes a new instance of the Logger thread and starts it.
        /// </summary>
        /// <param name="logType"></param>
        /// <param name="logConfig"></param>
        public LoggerThread(LogType logType, LogConfig logConfig)
        {
            this.logConfig = logConfig;

            //Create the wait event
            waitEvent = new AutoResetEvent(false);

            //Create the thread.
            thread = new Thread(Execute) { Priority = ThreadPriority.Lowest };
            switch (logType)
            {
                case LogType.Debug:
                    thread.Name = "Debug Logger thread";
                    break;
                case LogType.Event:
                    thread.Name = "Event Logger thread";
                    break;
            }
            thread.IsBackground = true;
            thread.Start();
        }

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

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Disposes the resources owned by the logger thread.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Internal dispose method.
        /// </summary>
        /// <param name="disposing"></param>
        private void Dispose(bool disposing)
        {
            if (!disposed)
            {
                disposed = true;
                if (disposing)
                {
                    threadShouldExit = true;

                    waitEvent.Set();
                    waitEvent.Close();

                    thread.Join();
                }
            }
        }

        #endregion

        #region The thread method

        /// <summary>
        /// The main function for the logger thread.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")]
        private void Execute()
        {
            while (!threadShouldExit)
            {
                try
                {
                    // Wait for an event.
                    waitEvent.WaitOne();

                    if (!disposed)
                    {
                        // Clear the entry queue.
                        bool queueEmpty;
                        do
                        {
                            // Pick the next logEntry in the queue safely in a locked way.
                            LogEntry logEntry = null;
                            lock (entryQueueLock)
                            {
                                if (entryQueue.Count > 0)
                                    logEntry = entryQueue.Dequeue();
                                queueEmpty = (entryQueue.Count == 0);
                            }
                            
                            // Was there a log entry for me?
                            if (!disposed && (logEntry != null))
                                PerformLog(logEntry);
                        }
                        while (!queueEmpty);
                    }
                }
                catch (Exception exception)
                {
                    Logger.FireOnThreadException(new LogException(LogExceptionType.GeneralThreadException,
                        "An Exception is caught in the Logger.Execute() method. See inner exception!", exception));
                }
#if WindowsCE
                catch
                {
                    Logger.FireOnThreadException(new LogException(LogExceptionType.GeneralThreadException,
                        "An unknown exception is caught in the Logger.Execute() method."));
                }
#endif
            }
        }

        /// <summary>
        /// Performs the actual logging.
        /// </summary>
        /// <param name="logEntry"></param>
        private void PerformLog(LogEntry logEntry)
        {
            lock (logWritingLock)
            {
                LogWriter[] logWriters;
                DebugMarkerLogEntry debugMarkerLogEntry = logEntry as DebugMarkerLogEntry;
                if (debugMarkerLogEntry != null)
                {
                    // Get all active LogWriters.
                    logWriters = logConfig.GetLogWriters();
                }
                else
                {
                    // Get a list of the LogWriters that wants to log this entry.
                    logWriters = logConfig.GetLogWriters(logEntry.EntityCategory);
                }

                // Call them in turn and write the log entry.
                foreach (LogWriter writer in logWriters)
                    writer.Write(logEntry);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Adds an entry that should be handled by the logger thread.
        /// </summary>
        /// <param name="logEntry"></param>
        public void AddEntry(LogEntry logEntry)
        {
            if (disposed)
                return;

            if (Logger.Synchronized)
            {
                PerformLog(logEntry);
            }
            else
            {
                lock (entryQueueLock)
                {
                    entryQueue.Enqueue(logEntry);
                }
                waitEvent.Set();
            }
        }

        #endregion
    }
}