using MediaBrowser.Model.Logging;
using NLog;
using NLog.Config;
using NLog.Targets;
using NLog.Targets.Wrappers;
using System;
using System.IO;
using System.Linq;

namespace MediaBrowser.Common.Implementations.Logging
{
    /// <summary>
    /// Class NlogManager
    /// </summary>
    public class NlogManager : ILogManager
    {
        /// <summary>
        /// Occurs when [logger loaded].
        /// </summary>
        public event EventHandler LoggerLoaded;
        /// <summary>
        /// Gets or sets the log directory.
        /// </summary>
        /// <value>The log directory.</value>
        private string LogDirectory { get; set; }
        /// <summary>
        /// Gets or sets the log file prefix.
        /// </summary>
        /// <value>The log file prefix.</value>
        private string LogFilePrefix { get; set; }
        /// <summary>
        /// Gets the log file path.
        /// </summary>
        /// <value>The log file path.</value>
        public string LogFilePath { get; private set; }

        /// <summary>
        /// Gets or sets the exception message prefix.
        /// </summary>
        /// <value>The exception message prefix.</value>
        public string ExceptionMessagePrefix { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="NlogManager" /> class.
        /// </summary>
        /// <param name="logDirectory">The log directory.</param>
        /// <param name="logFileNamePrefix">The log file name prefix.</param>
        public NlogManager(string logDirectory, string logFileNamePrefix)
        {
            LogDirectory = logDirectory;
            LogFilePrefix = logFileNamePrefix;

			LogManager.Configuration = new LoggingConfiguration ();
        }

        private LogSeverity _severity = LogSeverity.Debug;
        public LogSeverity LogSeverity
        {
            get
            {
                return _severity;
            }
            set
            {
                var changed = _severity != value;

                _severity = value;

                if (changed)
                {
                    UpdateLogLevel(value);
                }
            }
        }

        private void UpdateLogLevel(LogSeverity newLevel)
        {
            var level = GetLogLevel(newLevel);

            var rules = LogManager.Configuration.LoggingRules;

            foreach (var rule in rules)
            {
                if (!rule.IsLoggingEnabledForLevel(level))
                {
                    rule.EnableLoggingForLevel(level);
                }
                foreach (var lev in rule.Levels.ToArray())
                {
                    if (lev < level)
                    {
                        rule.DisableLoggingForLevel(lev);
                    }
                }
            }
        }

        /// <summary>
        /// Adds the file target.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="level">The level.</param>
        private void AddFileTarget(string path, LogSeverity level)
        {
			RemoveTarget("ApplicationLogFileWrapper");

			var wrapper = new AsyncTargetWrapper ();
			wrapper.Name = "ApplicationLogFileWrapper";

			var logFile = new FileTarget
            {
                FileName = path,
                Layout = "${longdate} ${level} ${logger}: ${message}"
            };

            logFile.Name = "ApplicationLogFile";

			wrapper.WrappedTarget = logFile;

            AddLogTarget(wrapper, level);
        }

        /// <summary>
        /// Adds the log target.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="level">The level.</param>
        public void AddLogTarget(Target target, LogSeverity level)
        {
            var config = LogManager.Configuration;
            config.AddTarget(target.Name, target);

            var rule = new LoggingRule("*", GetLogLevel(level), target);
            config.LoggingRules.Add(rule);

            LogManager.Configuration = config;
        }

        /// <summary>
        /// Removes the target.
        /// </summary>
        /// <param name="name">The name.</param>
        public void RemoveTarget(string name)
        {
            var config = LogManager.Configuration;

            var target = config.FindTargetByName(name);

            if (target != null)
            {
                foreach (var rule in config.LoggingRules.ToList())
                {
                    var contains = rule.Targets.Contains(target);

                    rule.Targets.Remove(target);

                    if (contains)
                    {
                        config.LoggingRules.Remove(rule);
                    }
                }

                config.RemoveTarget(name);
                LogManager.Configuration = config;
            }
        }

        /// <summary>
        /// Gets the logger.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns>ILogger.</returns>
        public Model.Logging.ILogger GetLogger(string name)
        {
            return new NLogger(name, this);
        }

        /// <summary>
        /// Gets the log level.
        /// </summary>
        /// <param name="severity">The severity.</param>
        /// <returns>LogLevel.</returns>
        /// <exception cref="System.ArgumentException">Unrecognized LogSeverity</exception>
        private LogLevel GetLogLevel(LogSeverity severity)
        {
            switch (severity)
            {
                case LogSeverity.Debug:
                    return LogLevel.Debug;
                case LogSeverity.Error:
                    return LogLevel.Error;
                case LogSeverity.Fatal:
                    return LogLevel.Fatal;
                case LogSeverity.Info:
                    return LogLevel.Info;
                case LogSeverity.Warn:
                    return LogLevel.Warn;
                default:
                    throw new ArgumentException("Unrecognized LogSeverity");
            }
        }

        /// <summary>
        /// Reloads the logger.
        /// </summary>
        /// <param name="level">The level.</param>
        public void ReloadLogger(LogSeverity level)
        {
            LogFilePath = Path.Combine(LogDirectory, LogFilePrefix + "-" + decimal.Round(DateTime.Now.Ticks / 10000000) + ".txt");

			Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath));

            AddFileTarget(LogFilePath, level);

            LogSeverity = level;

            if (LoggerLoaded != null)
            {
                try
                {
                    LoggerLoaded(this, EventArgs.Empty);
                }
                catch (Exception ex)
                {
                    GetLogger("Logger").ErrorException("Error in LoggerLoaded event", ex);
                }
            }
        }

        /// <summary>
        /// Flushes this instance.
        /// </summary>
        public void Flush()
        {
            LogManager.Flush();
        }


        public void AddConsoleOutput()
        {
			RemoveTarget("ConsoleTargetWrapper");

			var wrapper = new AsyncTargetWrapper ();
			wrapper.Name = "ConsoleTargetWrapper";

            var target = new ConsoleTarget()
            {
                Layout = "${level}, ${logger}, ${message}",
                Error = false
            };

            target.Name = "ConsoleTarget";

			wrapper.WrappedTarget = target;

			AddLogTarget(wrapper, LogSeverity);
        }

        public void RemoveConsoleOutput()
        {
			RemoveTarget("ConsoleTargetWrapper");
        }
    }
}