using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Mercury.Mobile.Logging.Loggers
{
    /// <summary>
    /// A logger that can log to a number of hosted loggers.
    /// </summary>
    public class CompositeLogger
        : Logger, IDisposable
    {
        /// <summary>
        /// Initializes a default instance of the <see cref="Mercury.Mobile.Logging.Loggers.CompositeLogger"/> class.
        /// </summary>
        public CompositeLogger()
            : base("")
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Mercury.Mobile.Logging.Loggers.CompositeLogger"/> 
        /// class with the specified loggers.
        /// </summary>
        /// <param name="loggers">An array of named log objects, associating a logger with a name.</param>
        public CompositeLogger(params NamedLog[] loggers)
            : base("")
        {
            if (loggers != null && loggers.Length > 0)
            {
                foreach (var pair in loggers)
                    this._loggers.Add(pair);
            }
        }

        private IList<NamedLog> _loggers = new List<NamedLog>();
        private bool isDisposed;

        #region NamedLog
        /// <summary>
        /// Represents a logger and its associated name.
        /// </summary>
        public sealed class NamedLog
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="Mercury.Mobile.Logging.Loggers.CompositeLogger.NamedLog"/> class.
            /// </summary>
            /// <param name="logName">The name associated with the logger.</param>
            /// <param name="logger">The logger.</param>
            public NamedLog(string logName, Logger logger)
            {
                if (logName == null)
                    throw new ArgumentNullException("logName");
                if (logger == null)
                    throw new ArgumentNullException("logger");
                this.LogName = logName;
                this.Logger = logger;
                this.Logger.Name = logName;
            }

            /// <summary>
            /// Gets the associated log name.
            /// </summary>
            public string LogName { get; private set; }

            /// <summary>
            /// Gets the logger.
            /// </summary>
            public Logger Logger { get; private set; }
        }
        #endregion

        /// <summary>
        /// Adds the specified log name and logger to the composite collection.
        /// </summary>
        /// <param name="logName">The name associated with the specified logger.</param>
        /// <param name="logger">The logger to add.</param>
        public void Add(string logName, Logger logger)
        {
            this.Add(new NamedLog(logName, logger));
        }

        /// <summary>
        /// Adds the specified named log to the composite collection.
        /// </summary>
        /// <param name="logger">A named log object to add.</param>
        public void Add(NamedLog logger)
        {
            this._loggers.Add(logger);
        }

        /// <summary>
        /// Returns the first instance of a logger associated with the specified name.
        /// </summary>
        /// <param name="logName">The name of the logger to find.</param>
        /// <returns>The first instance of a logger associated with the specified name or null.</returns>
        public Logger Find(string logName)
        {
            if (logName == null)
                return null;
            var result = this._loggers.FirstOrDefault<NamedLog>((entry) => entry.LogName == logName);
            if (result != null)
                return result.Logger;
            return null;
        }

        /// <summary>
        /// Returns all loggers that are associated with the specified name.
        /// </summary>
        /// <param name="logName">The name of the loggers to find.</param>
        /// <returns>A list of all loggers that are associated with the specified name.</returns>
        public IList<Logger> FindAll(string logName)
        {
            var resList = new List<Logger>();
            if (logName != null)
            {
                foreach (var nl in this._loggers)
                {
                    if (nl.LogName == logName)
                        resList.Add(nl.Logger);
                }
            }
            return resList;
        }

        /// <summary>
        /// Removes the first instance of a logger associated with the specified name.
        /// </summary>
        /// <param name="logName">The name of the logger to remove.</param>
        /// <returns>True if the logger was found and removed; otherwise, false.</returns>
        public bool Remove(string logName)
        {
            return this.RemoveInternal(logName, false);
        }

        /// <summary>
        /// Removes all loggers associated with the specified name.
        /// </summary>
        /// <param name="logName">The name of the logger to remove.</param>
        /// <returns>True if any loggers were found and removed; otherwise, false.</returns>
        public bool RemoveAll(string logName)
        {
            return this.RemoveInternal(logName, true);
        }

        private bool RemoveInternal(string logName, bool allEntries)
        {
            NamedLog nl = null;
            bool flag = false;
            for (int i = 0; i < this._loggers.Count; i++)
            {
                nl = this._loggers[i];
                if (nl.LogName == logName)
                {
                    this._loggers.RemoveAt(i);
                    flag = true;
                    i--;
                    if (!allEntries)
                        break;
                }
            }
            return flag;
            
        }

        /// <summary>
        /// Releases any resources on hosted loggers by verifying if the logger implements IDisposable and calling Dispose.
        /// </summary>
        public void Close()
        {
            this.Dispose(true);
        }

        /// <summary>
        /// Flushes any output buffers, forcing data to be written to the log.
        /// </summary>
        public override void Flush()
        {
            foreach (var nl in this._loggers)
                nl.Logger.Flush();
        }

        /// <summary>
        /// Logs the specified log entry.
        /// </summary>
        /// <param name="entry">The log entry to log.</param>
        /// <returns>True if logging was successful; otherwise, false.</returns>
        protected override bool DoLogEntry(LogEntry entry)
        {
            if (entry.Severity < this.SeverityThreshold)
                return false;
            foreach (var nl in this._loggers)
            {
                if (CompositeLogger._AllowEntry(nl, entry))
                    nl.Logger.DoLogInternal(entry);
            }
            return true;
        }

        private static bool _AllowEntry(NamedLog nl, LogEntry entry)
        {
            if (nl == null || nl.Logger.Filter == null)
                return false;
            if (entry.Severity < nl.Logger.SeverityThreshold)
                return false;
            return nl.Logger.Filter.Allow(entry);
        }

        /// <summary>
        /// Determines whether the specified log entry can pass filtering.
        /// </summary>
        /// <param name="entry">The log entry to test.</param>
        /// <returns>True if the log entry passes filtering; otherwise, false.</returns>
        protected override bool AllowEntry(LogEntry entry)
        {
            if (entry.Severity < this.SeverityThreshold)
                return false;
            if (this.Filter == null)
                return true;
            return this.Filter.Allow(entry);
        }

        /// <summary>
        /// Logs the specified message string.
        /// </summary>
        /// <param name="message">The message string to log.</param>
        /// <returns>True if logging was successful; otherwise, false.</returns>
        protected override bool WriteLog(string message)
        {

            foreach (var nl in this._loggers)
            {
                nl.Logger.Write(message);
            }
            return true;
        }

        /// <summary>
        /// Dispsoses of the resources associated with this logger.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispsoses of the resources associated with this logger.
        /// </summary>
        /// <param name="disposing">True if called through the IDisposable.Dispose method; otherwise, false.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.isDisposed)
            {
                if (disposing)
                {
                    foreach (var nl in this._loggers)
                    {
                        IDisposable dispObj = nl.Logger as IDisposable;
                        if (dispObj != null)
                            dispObj.Dispose();
                    }
                }
            }
            this.isDisposed = true;
        }
    }
}
