// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.IO.Abstractions;
using System.Runtime.CompilerServices;
using System.Text;
using NLog;
using NLog.Config;
using NLog.Targets;

namespace Wox.Plugin.Logger
{
    public static class Log
    {
        private static readonly IFileSystem FileSystem = new FileSystem();
        private static readonly IPath Path = FileSystem.Path;
        private static readonly IDirectory Directory = FileSystem.Directory;

        public const string DirectoryName = "Logs";

        public static string CurrentLogDirectory { get; }

        static Log()
        {
            CurrentLogDirectory = Path.Combine(Constant.DataDirectory, DirectoryName, Constant.Version);
            if (!Directory.Exists(CurrentLogDirectory))
            {
                Directory.CreateDirectory(CurrentLogDirectory);
            }

            var configuration = new LoggingConfiguration();
            var target = new FileTarget();
            target.Layout = NLog.Layouts.Layout.FromString("[${longdate}] [${level:uppercase=true}]${message}\n");
            configuration.AddTarget("file", target);

            // Adding CurrentCulture since this is user facing
            target.FileName = CurrentLogDirectory.Replace(@"\", "/", StringComparison.CurrentCulture) + "/${shortdate}.txt";
#if DEBUG
            var rule = new LoggingRule("*", LogLevel.Debug, target);
#else
            var rule = new LoggingRule("*", LogLevel.Info, target);
#endif
            configuration.LoggingRules.Add(rule);
            target.Dispose();
            LogManager.Configuration = configuration;
        }

        private static void LogInternalException(string message, System.Exception e, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
        {
            var logger = GetLogger(fullClassName.FullName, methodName);
            var formattedOutput = new StringBuilder();

            formattedOutput.AppendLine("-------------------------- Begin exception --------------------------");
            formattedOutput.AppendLine($"Message: {message}");

            do
            {
                formattedOutput.Append(
                    "\n" +
                    $"Exception full name  : {e.GetType().FullName}\n" +
                    $"Exception message    : {e.Message}\n" +
                    $"Exception stack trace:\n{e.StackTrace}\n" +
                    $"Exception source     : {e.Source}\n" +
                    $"Exception target site: {e.TargetSite}\n" +
                    $"Exception HResult    : {e.HResult}\n");

                e = e.InnerException;
            }
            while (e != null);

            formattedOutput.AppendLine("-------------------------- End exception --------------------------");
            LogInternal(LogLevel.Error, formattedOutput.ToString(), logger, sourceFilePath, sourceLineNumber);
        }

        public static void Info(string message, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
        {
            if (fullClassName == null)
            {
                throw new ArgumentNullException(nameof(fullClassName));
            }

            LogInternal(LogLevel.Info, message, fullClassName, methodName, sourceFilePath, sourceLineNumber);
        }

        public static void Debug(string message, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
        {
            if (fullClassName == null)
            {
                throw new ArgumentNullException(nameof(fullClassName));
            }

            LogInternal(LogLevel.Debug, message, fullClassName, methodName, sourceFilePath, sourceLineNumber);
        }

        public static void Warn(string message, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
        {
            if (fullClassName == null)
            {
                throw new ArgumentNullException(nameof(fullClassName));
            }

            LogInternal(LogLevel.Warn, message, fullClassName, methodName, sourceFilePath, sourceLineNumber);
        }

        public static void Error(string message, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
        {
            if (fullClassName == null)
            {
                throw new ArgumentNullException(nameof(fullClassName));
            }

            LogInternal(LogLevel.Error, message, fullClassName, methodName, sourceFilePath, sourceLineNumber);
        }

        public static void Exception(string message, System.Exception ex, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
        {
            if (fullClassName == null)
            {
                throw new ArgumentNullException(nameof(fullClassName));
            }

            LogInternalException(message, ex, fullClassName, methodName, sourceFilePath, sourceLineNumber);
        }

        private static void LogInternal(LogLevel level, string message, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
        {
            var logger = GetLogger(fullClassName.FullName, methodName);

            LogInternal(level, message, logger, sourceFilePath, sourceLineNumber);
        }

        private static void LogInternal(LogLevel level, string message, NLog.Logger logger, [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
        {
            var msg = $" [{sourceFilePath}::{sourceLineNumber}]" +
                      $"\n{message}";

            logger.Log(level, msg);
        }

        private static NLog.Logger GetLogger(string fullClassName, string methodName)
        {
            var classNameWithMethod = $"{fullClassName}.{methodName}";

            return LogManager.GetLogger(classNameWithMethod);
        }
    }
}
