//===============================================================================
// LibLog
//
// https://github.com/damianh/LibLog
//===============================================================================
// Copyright Â© 2011-2015 Damian Hickey.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//===============================================================================

// ReSharper disable PossibleNullReferenceException

// Define LIBLOG_PORTABLE conditional compilation symbol for PCL compatibility
//
// Define LIBLOG_PUBLIC to enable ability to GET a logger (LogProvider.For<>() etc) from outside this library. NOTE:
// this can have unintended consequences of consumers of your library using your library to resolve a logger. If the
// reason is because you want to open this functionality to other projects within your solution,
// consider [InternalsVisibleTo] instead.
// 
// Define LIBLOG_PROVIDERS_ONLY if your library provides its own logging API and you just want to use the
// LibLog providers internally to provide built in support for popular logging frameworks.

#pragma warning disable 1591

using System.Diagnostics.CodeAnalysis;

[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "NEventSocket.Logging")]
[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "NEventSocket.Logging.Logger.#Invoke(NEventSocket.Logging.LogLevel,System.Func`1<System.String>,System.Exception,System.Object[])")]

// If you copied this file manually, you need to change all "YourRootNameSpace" so not to clash with other libraries
// that use LibLog
#if LIBLOG_PROVIDERS_ONLY
namespace NEventSocket.LibLog
#else
namespace NEventSocket.Logging
#endif
{
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
#if LIBLOG_PROVIDERS_ONLY
    using NEventSocket.LibLog.LogProviders;
#else
    using NEventSocket.Logging.LogProviders;
#endif
    using System;
#if !LIBLOG_PROVIDERS_ONLY
    using System.Diagnostics;
#if !LIBLOG_PORTABLE
    using System.Runtime.CompilerServices;
#endif
#endif

#if LIBLOG_PROVIDERS_ONLY
    internal
#else
    public
#endif
    delegate bool Logger(LogLevel logLevel, Func<string> messageFunc, Exception exception = null, params object[] formatParameters);

#if !LIBLOG_PROVIDERS_ONLY
    /// <summary>
    /// Simple interface that represent a logger.
    /// </summary>
#if LIBLOG_PUBLIC
    public
#else
    internal
#endif
    interface ILog
    {
        /// <summary>
        /// Log a message the specified log level.
        /// </summary>
        /// <param name="logLevel">The log level.</param>
        /// <param name="messageFunc">The message function.</param>
        /// <param name="exception">An optional exception.</param>
        /// <param name="formatParameters">Optional format parameters for the message generated by the messagefunc. </param>
        /// <returns>true if the message was logged. Otherwise false.</returns>
        /// <remarks>
        /// Note to implementers: the message func should not be called if the loglevel is not enabled
        /// so as not to incur performance penalties.
        /// 
        /// To check IsEnabled call Log with only LogLevel and check the return value, no event will be written.
        /// </remarks>
        bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception = null, params object[] formatParameters );
    }
#endif

    /// <summary>
    /// The log level.
    /// </summary>
#if LIBLOG_PROVIDERS_ONLY
    internal
#else
    public
#endif
    enum LogLevel
    {
        Trace,
        Debug,
        Info,
        Warn,
        Error,
        Fatal
    }

#if !LIBLOG_PROVIDERS_ONLY
#if LIBLOG_PUBLIC
    public
#else
    internal
#endif
    static partial class LogExtensions
    {
        public static bool IsDebugEnabled(this ILog logger)
        {
            GuardAgainstNullLogger(logger);
            return logger.Log(LogLevel.Debug, null);
        }

        public static bool IsErrorEnabled(this ILog logger)
        {
            GuardAgainstNullLogger(logger);
            return logger.Log(LogLevel.Error, null);
        }

        public static bool IsFatalEnabled(this ILog logger)
        {
            GuardAgainstNullLogger(logger);
            return logger.Log(LogLevel.Fatal, null);
        }

        public static bool IsInfoEnabled(this ILog logger)
        {
            GuardAgainstNullLogger(logger);
            return logger.Log(LogLevel.Info, null);
        }

        public static bool IsTraceEnabled(this ILog logger)
        {
            GuardAgainstNullLogger(logger);
            return logger.Log(LogLevel.Trace, null);
        }

        public static bool IsWarnEnabled(this ILog logger)
        {
            GuardAgainstNullLogger(logger);
            return logger.Log(LogLevel.Warn, null);
        }

        public static void Debug(this ILog logger, Func<string> messageFunc)
        {
            GuardAgainstNullLogger(logger);
            logger.Log(LogLevel.Debug, messageFunc);
        }

        public static void Debug(this ILog logger, string message)
        {
            if (logger.IsDebugEnabled())
            {
                logger.Log(LogLevel.Debug, message.AsFunc());
            }
        }

        public static void DebugFormat(this ILog logger, string message, params object[] args)
        {
            if (logger.IsDebugEnabled())
            {
                logger.LogFormat(LogLevel.Debug, message, args);
            }
        }

        public static void DebugException(this ILog logger, string message, Exception exception)
        {
            if (logger.IsDebugEnabled())
            {
                logger.Log(LogLevel.Debug, message.AsFunc(), exception);
            }
        }

        public static void DebugException(this ILog logger, string message, Exception exception, params object[] formatParams)
        {
            if (logger.IsDebugEnabled())
            {
                logger.Log(LogLevel.Debug, message.AsFunc(), exception, formatParams);
            }
        }

        public static void Error(this ILog logger, Func<string> messageFunc)
        {
            GuardAgainstNullLogger(logger);
            logger.Log(LogLevel.Error, messageFunc);
        }

        public static void Error(this ILog logger, string message)
        {
            if (logger.IsErrorEnabled())
            {
                logger.Log(LogLevel.Error, message.AsFunc());
            }
        }

        public static void ErrorFormat(this ILog logger, string message, params object[] args)
        {
            if (logger.IsErrorEnabled())
            {
                logger.LogFormat(LogLevel.Error, message, args);
            }
        }

        public static void ErrorException(this ILog logger, string message, Exception exception, params object[] formatParams)
        {
            if (logger.IsErrorEnabled())
            {
                logger.Log(LogLevel.Error, message.AsFunc(), exception, formatParams);
            }
        }

        public static void Fatal(this ILog logger, Func<string> messageFunc)
        {
            logger.Log(LogLevel.Fatal, messageFunc);
        }

        public static void Fatal(this ILog logger, string message)
        {
            if (logger.IsFatalEnabled())
            {
                logger.Log(LogLevel.Fatal, message.AsFunc());
            }
        }

        public static void FatalFormat(this ILog logger, string message, params object[] args)
        {
            if (logger.IsFatalEnabled())
            {
                logger.LogFormat(LogLevel.Fatal, message, args);
            }
        }

        public static void FatalException(this ILog logger, string message, Exception exception, params object[] formatParams)
        {
            if (logger.IsFatalEnabled())
            {
                logger.Log(LogLevel.Fatal, message.AsFunc(), exception, formatParams);
            }
        }

        public static void Info(this ILog logger, Func<string> messageFunc)
        {
            GuardAgainstNullLogger(logger);
            logger.Log(LogLevel.Info, messageFunc);
        }

        public static void Info(this ILog logger, string message)
        {
            if (logger.IsInfoEnabled())
            {
                logger.Log(LogLevel.Info, message.AsFunc());
            }
        }

        public static void InfoFormat(this ILog logger, string message, params object[] args)
        {
            if (logger.IsInfoEnabled())
            {
                logger.LogFormat(LogLevel.Info, message, args);
            }
        }

        public static void InfoException(this ILog logger, string message, Exception exception, params object[] formatParams)
        {
            if (logger.IsInfoEnabled())
            {
                logger.Log(LogLevel.Info, message.AsFunc(), exception, formatParams);
            }
        }

        public static void Trace(this ILog logger, Func<string> messageFunc)
        {
            GuardAgainstNullLogger(logger);
            logger.Log(LogLevel.Trace, messageFunc);
        }

        public static void Trace(this ILog logger, string message)
        {
            if (logger.IsTraceEnabled())
            {
                logger.Log(LogLevel.Trace, message.AsFunc());
            }
        }

        public static void TraceFormat(this ILog logger, string message, params object[] args)
        {
            if (logger.IsTraceEnabled())
            {
                logger.LogFormat(LogLevel.Trace, message, args);
            }
        }

        public static void TraceException(this ILog logger, string message, Exception exception, params object[] formatParams)
        {
            if (logger.IsTraceEnabled())
            {
                logger.Log(LogLevel.Trace, message.AsFunc(), exception, formatParams);
            }
        }

        public static void Warn(this ILog logger, Func<string> messageFunc)
        {
            GuardAgainstNullLogger(logger);
            logger.Log(LogLevel.Warn, messageFunc);
        }

        public static void Warn(this ILog logger, string message)
        {
            if (logger.IsWarnEnabled())
            {
                logger.Log(LogLevel.Warn, message.AsFunc());
            }
        }

        public static void WarnFormat(this ILog logger, string message, params object[] args)
        {
            if (logger.IsWarnEnabled())
            {
                logger.LogFormat(LogLevel.Warn, message, args);
            }
        }

        public static void WarnException(this ILog logger, string message, Exception exception, params object[] formatParams)
        {
            if (logger.IsWarnEnabled())
            {
                logger.Log(LogLevel.Warn, message.AsFunc(), exception, formatParams);
            }
        }

        // ReSharper disable once UnusedParameter.Local
        private static void GuardAgainstNullLogger(ILog logger)
        {
            if (logger == null)
            {
                throw new ArgumentNullException("logger");
            }
        }

        private static void LogFormat(this ILog logger, LogLevel logLevel, string message, params object[] args)
        {
            logger.Log(logLevel, message.AsFunc(), null, args);
        }

        // Avoid the closure allocation, see https://gist.github.com/AArnott/d285feef75c18f6ecd2b
        private static Func<T> AsFunc<T>(this T value) where T : class
        {
            return value.Return;
        }

        private static T Return<T>(this T value)
        {
            return value;
        }
    }
#endif

    /// <summary>
    /// Represents a way to get a <see cref="ILog"/>
    /// </summary>
#if LIBLOG_PROVIDERS_ONLY
    internal
#else
    public
#endif
    interface ILogProvider
    {
        /// <summary>
        /// Gets the specified named logger.
        /// </summary>
        /// <param name="name">Name of the logger.</param>
        /// <returns>The logger reference.</returns>
        Logger GetLogger(string name);

        /// <summary>
        /// Opens a nested diagnostics context. Not supported in EntLib logging.
        /// </summary>
        /// <param name="message">The message to add to the diagnostics context.</param>
        /// <returns>A disposable that when disposed removes the message from the context.</returns>
        IDisposable OpenNestedContext(string message);

        /// <summary>
        /// Opens a mapped diagnostics context. Not supported in EntLib logging.
        /// </summary>
        /// <param name="key">A key.</param>
        /// <param name="value">A value.</param>
        /// <returns>A disposable that when disposed removes the map from the context.</returns>
        IDisposable OpenMappedContext(string key, string value);
    }

    /// <summary>
    /// Provides a mechanism to create instances of <see cref="ILog" /> objects.
    /// </summary>
#if LIBLOG_PROVIDERS_ONLY
    internal
#else
    public
#endif
    static class LogProvider
    {
#if !LIBLOG_PROVIDERS_ONLY
        /// <summary>
        /// The disable logging environment variable. If the environment variable is set to 'true', then logging
        /// will be disabled.
        /// </summary>
        public const string DisableLoggingEnvironmentVariable = "NEventSocket_LIBLOG_DISABLE";
        private const string NullLogProvider = "Current Log Provider is not set. Call SetCurrentLogProvider " +
                                               "with a non-null value first.";
        private static dynamic s_currentLogProvider;
        private static Action<ILogProvider> s_onCurrentLogProviderSet;

        [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
        static LogProvider()
        {
            IsDisabled = false;
        }

        /// <summary>
        /// Sets the current log provider.
        /// </summary>
        /// <param name="logProvider">The log provider.</param>
        public static void SetCurrentLogProvider(ILogProvider logProvider)
        {
            s_currentLogProvider = logProvider;

            RaiseOnCurrentLogProviderSet();
        }

        /// <summary>
        /// Gets or sets a value indicating whether this is logging is disabled.
        /// </summary>
        /// <value>
        /// <c>true</c> if logging is disabled; otherwise, <c>false</c>.
        /// </value>
        public static bool IsDisabled { get; set; }

        /// <summary>
        /// Sets an action that is invoked when a consumer of your library has called SetCurrentLogProvider. It is 
        /// important that hook into this if you are using child libraries (especially ilmerged ones) that are using
        /// LibLog (or other logging abstraction) so you adapt and delegate to them.
        /// <see cref="SetCurrentLogProvider"/> 
        /// </summary>
        internal static Action<ILogProvider> OnCurrentLogProviderSet
        {
            set
            {
                s_onCurrentLogProviderSet = value;
                RaiseOnCurrentLogProviderSet();
            }
        }

        internal static ILogProvider CurrentLogProvider
        {
            get
            {
                return s_currentLogProvider;
            }
        }

        /// <summary>
        /// Gets a logger for the specified type.
        /// </summary>
        /// <typeparam name="T">The type whose name will be used for the logger.</typeparam>
        /// <returns>An instance of <see cref="ILog"/></returns>
#if LIBLOG_PUBLIC
        public
#else
        internal
#endif
        static ILog For<T>()
        {
            return GetLogger(typeof(T));
        }

#if !LIBLOG_PORTABLE
        /// <summary>
        /// Gets a logger for the current class.
        /// </summary>
        /// <returns>An instance of <see cref="ILog"/></returns>
        [MethodImpl(MethodImplOptions.NoInlining)]
#if LIBLOG_PUBLIC
        public
#else
        internal
#endif
        static ILog GetCurrentClassLogger()
        {
            var stackFrame = new StackFrame(1, false);
            return GetLogger(stackFrame.GetMethod().DeclaringType);
        }
#endif

        /// <summary>
        /// Gets a logger for the specified type.
        /// </summary>
        /// <param name="type">The type whose name will be used for the logger.</param>
        /// <returns>An instance of <see cref="ILog"/></returns>
#if LIBLOG_PUBLIC
        public
#else
        internal
#endif
        static ILog GetLogger(Type type)
        {
            return GetLogger(type.FullName);
        }

        /// <summary>
        /// Gets a logger with the specified name.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns>An instance of <see cref="ILog"/></returns>
#if LIBLOG_PUBLIC
        public
#else
        internal
#endif
        static ILog GetLogger(string name)
        {
            ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider();
            return logProvider == null 
                ? NoOpLogger.Instance
                : (ILog)new LoggerExecutionWrapper(logProvider.GetLogger(name), () => IsDisabled);
        }

        /// <summary>
        /// Opens a nested diagnostics context.
        /// </summary>
        /// <param name="message">A message.</param>
        /// <returns>An <see cref="IDisposable"/> that closes context when disposed.</returns>
        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SetCurrentLogProvider")]
#if LIBLOG_PUBLIC
        public
#else
        internal
#endif
        static IDisposable OpenNestedContext(string message)
        {
            return CurrentLogProvider == null 
                ? new DisposableAction(() => {}) 
                : CurrentLogProvider.OpenNestedContext(message);
        }

        /// <summary>
        /// Opens a mapped diagnostics context.
        /// </summary>
        /// <param name="key">A key.</param>
        /// <param name="value">A value.</param>
        /// <returns>An <see cref="IDisposable"/> that closes context when disposed.</returns>
        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SetCurrentLogProvider")]
#if LIBLOG_PUBLIC
        public
#else
        internal
#endif
        static IDisposable OpenMappedContext(string key, string value)
        {
            return CurrentLogProvider == null 
                ? new DisposableAction(() => { }) 
                : CurrentLogProvider.OpenMappedContext(key, value);
        }
#endif

#if LIBLOG_PROVIDERS_ONLY
    private
#else
    internal
#endif
    delegate bool IsLoggerAvailable();

#if LIBLOG_PROVIDERS_ONLY
    private
#else
    internal
#endif
    delegate ILogProvider CreateLogProvider();

#if LIBLOG_PROVIDERS_ONLY
    private
#else
    internal
#endif
    static readonly List<Tuple<IsLoggerAvailable, CreateLogProvider>> LogProviderResolvers =
            new List<Tuple<IsLoggerAvailable, CreateLogProvider>>
        {
            new Tuple<IsLoggerAvailable, CreateLogProvider>(SerilogLogProvider.IsLoggerAvailable, () => new SerilogLogProvider()),
            new Tuple<IsLoggerAvailable, CreateLogProvider>(NLogLogProvider.IsLoggerAvailable, () => new NLogLogProvider()),
            new Tuple<IsLoggerAvailable, CreateLogProvider>(Log4NetLogProvider.IsLoggerAvailable, () => new Log4NetLogProvider()),
            new Tuple<IsLoggerAvailable, CreateLogProvider>(EntLibLogProvider.IsLoggerAvailable, () => new EntLibLogProvider()),
            new Tuple<IsLoggerAvailable, CreateLogProvider>(LoupeLogProvider.IsLoggerAvailable, () => new LoupeLogProvider()),
        };

#if !LIBLOG_PROVIDERS_ONLY
        private static void RaiseOnCurrentLogProviderSet()
        {
            if (s_onCurrentLogProviderSet != null)
            {
                s_onCurrentLogProviderSet(s_currentLogProvider);
            }
        }
#endif

        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String,System.Object,System.Object)")]
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        internal static ILogProvider ResolveLogProvider()
        {
            try
            {
                foreach (var providerResolver in LogProviderResolvers)
                {
                    if (providerResolver.Item1())
                    {
                        return providerResolver.Item2();
                    }
                }
            }
            catch (Exception ex)
            {
#if LIBLOG_PORTABLE
                Debug.WriteLine(
#else
                Console.WriteLine(
#endif
                    "Exception occurred resolving a log provider. Logging for this assembly {0} is disabled. {1}",
                    typeof(LogProvider).GetAssemblyPortable().FullName,
                    ex);
            }
            return null;
        }

#if !LIBLOG_PROVIDERS_ONLY
        internal class NoOpLogger : ILog
        {
            internal static readonly NoOpLogger Instance = new NoOpLogger();

            public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
            {
                return false;
            }
        }
#endif
    }

#if !LIBLOG_PROVIDERS_ONLY
    internal class LoggerExecutionWrapper : ILog
    {
        private readonly Logger _logger;
        private readonly Func<bool> _getIsDisabled;
        internal const string FailedToGenerateLogMessage = "Failed to generate log message";

        internal LoggerExecutionWrapper(Logger logger, Func<bool> getIsDisabled = null)
        {
            _logger = logger;
            _getIsDisabled = getIsDisabled ?? (() => false);
        }

        internal Logger WrappedLogger
        {
            get { return _logger; }
        }

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception = null, params object[] formatParameters)
        {
            if (_getIsDisabled())
            {
                return false;
            }
#if !LIBLOG_PORTABLE
            var envVar = Environment.GetEnvironmentVariable(LogProvider.DisableLoggingEnvironmentVariable);

            if (envVar != null && envVar.Equals("true", StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }
#endif

            if (messageFunc == null)
            {
                return _logger(logLevel, null);
            }

            Func<string> wrappedMessageFunc = () =>
            {
                try
                {
                    return messageFunc();
                }
                catch (Exception ex)
                {
                    Log(LogLevel.Error, () => FailedToGenerateLogMessage, ex);
                }
                return null;
            };
            return _logger(logLevel, wrappedMessageFunc, exception, formatParameters);
        }
    }
#endif
}

#if LIBLOG_PROVIDERS_ONLY
namespace NEventSocket.LibLog.LogProviders
#else
namespace NEventSocket.Logging.LogProviders
#endif
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
#if !LIBLOG_PORTABLE
    using System.Diagnostics;
#endif
    using System.Globalization;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
#if !LIBLOG_PORTABLE
    using System.Text;
#endif
    using System.Text.RegularExpressions;

    internal abstract class LogProviderBase : ILogProvider
    {
        protected delegate IDisposable OpenNdc(string message);
        protected delegate IDisposable OpenMdc(string key, string value);

        private readonly Lazy<OpenNdc> _lazyOpenNdcMethod;
        private readonly Lazy<OpenMdc> _lazyOpenMdcMethod;
        private static readonly IDisposable NoopDisposableInstance = new DisposableAction();

        protected LogProviderBase()
        {
            _lazyOpenNdcMethod 
                = new Lazy<OpenNdc>(GetOpenNdcMethod);
            _lazyOpenMdcMethod
               = new Lazy<OpenMdc>(GetOpenMdcMethod);
        }

        public abstract Logger GetLogger(string name);

        public IDisposable OpenNestedContext(string message)
        {
            return _lazyOpenNdcMethod.Value(message);
        }

        public IDisposable OpenMappedContext(string key, string value)
        {
            return _lazyOpenMdcMethod.Value(key, value);
        }

        protected virtual OpenNdc GetOpenNdcMethod()
        {
            return _ => NoopDisposableInstance;
        }

        protected virtual OpenMdc GetOpenMdcMethod()
        {
            return (_, __) => NoopDisposableInstance;
        }
    }

    internal class NLogLogProvider : LogProviderBase
    {
        private readonly Func<string, object> _getLoggerByNameDelegate;
        private static bool s_providerIsAvailableOverride = true;

        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogManager")]
        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "NLog")]
        public NLogLogProvider()
        {
            if (!IsLoggerAvailable())
            {
                throw new InvalidOperationException("NLog.LogManager not found");
            }
            _getLoggerByNameDelegate = GetGetLoggerMethodCall();
        }

        public static bool ProviderIsAvailableOverride
        {
            get { return s_providerIsAvailableOverride; }
            set { s_providerIsAvailableOverride = value; }
        }

        public override Logger GetLogger(string name)
        {
            return new NLogLogger(_getLoggerByNameDelegate(name)).Log;
        }

        public static bool IsLoggerAvailable()
        {
            return ProviderIsAvailableOverride && GetLogManagerType() != null;
        }

        protected override OpenNdc GetOpenNdcMethod()
        {
            Type ndcContextType = Type.GetType("NLog.NestedDiagnosticsContext, NLog");
            MethodInfo pushMethod = ndcContextType.GetMethodPortable("Push", typeof(string));
            ParameterExpression messageParam = Expression.Parameter(typeof(string), "message");
            MethodCallExpression pushMethodCall = Expression.Call(null, pushMethod, messageParam);
            return Expression.Lambda<OpenNdc>(pushMethodCall, messageParam).Compile();
        }

        protected override OpenMdc GetOpenMdcMethod()
        {
            Type mdcContextType = Type.GetType("NLog.MappedDiagnosticsContext, NLog");

            MethodInfo setMethod = mdcContextType.GetMethodPortable("Set", typeof(string), typeof(string));
            MethodInfo removeMethod = mdcContextType.GetMethodPortable("Remove", typeof(string));
            ParameterExpression keyParam = Expression.Parameter(typeof(string), "key");
            ParameterExpression valueParam = Expression.Parameter(typeof(string), "value");

            MethodCallExpression setMethodCall = Expression.Call(null, setMethod, keyParam, valueParam);
            MethodCallExpression removeMethodCall = Expression.Call(null, removeMethod, keyParam);

            Action<string, string> set = Expression
                .Lambda<Action<string, string>>(setMethodCall, keyParam, valueParam)
                .Compile();
            Action<string> remove = Expression
                .Lambda<Action<string>>(removeMethodCall, keyParam)
                .Compile();

            return (key, value) =>
            {
                set(key, value);
                return new DisposableAction(() => remove(key));
            };
        }

        private static Type GetLogManagerType()
        {
            return Type.GetType("NLog.LogManager, NLog");
        }

        private static Func<string, object> GetGetLoggerMethodCall()
        {
            Type logManagerType = GetLogManagerType();
            MethodInfo method = logManagerType.GetMethodPortable("GetLogger", typeof(string));
            ParameterExpression nameParam = Expression.Parameter(typeof(string), "name");
            MethodCallExpression methodCall = Expression.Call(null, method, nameParam);
            return Expression.Lambda<Func<string, object>>(methodCall, nameParam).Compile();
        }

        internal class NLogLogger
        {
            private readonly dynamic _logger;

            internal NLogLogger(dynamic logger)
            {
                _logger = logger;
            }

            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
            public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
            {
                if (messageFunc == null)
                {
                    return IsLogLevelEnable(logLevel);
                }
                messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters);

                if(exception != null)
                {
                    return LogException(logLevel, messageFunc, exception);
                }
                switch (logLevel)
                {
                    case LogLevel.Debug:
                        if (_logger.IsDebugEnabled)
                        {
                            _logger.Debug(messageFunc());
                            return true;
                        }
                        break;
                    case LogLevel.Info:
                        if (_logger.IsInfoEnabled)
                        {
                            _logger.Info(messageFunc());
                            return true;
                        }
                        break;
                    case LogLevel.Warn:
                        if (_logger.IsWarnEnabled)
                        {
                            _logger.Warn(messageFunc());
                            return true;
                        }
                        break;
                    case LogLevel.Error:
                        if (_logger.IsErrorEnabled)
                        {
                            _logger.Error(messageFunc());
                            return true;
                        }
                        break;
                    case LogLevel.Fatal:
                        if (_logger.IsFatalEnabled)
                        {
                            _logger.Fatal(messageFunc());
                            return true;
                        }
                        break;
                    default:
                        if (_logger.IsTraceEnabled)
                        {
                            _logger.Trace(messageFunc());
                            return true;
                        }
                        break;
                }
                return false;
            }

            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
            private bool LogException(LogLevel logLevel, Func<string> messageFunc, Exception exception)
            {
                switch (logLevel)
                {
                    case LogLevel.Debug:
                        if (_logger.IsDebugEnabled)
                        {
                            _logger.DebugException(messageFunc(), exception);
                            return true;
                        }
                        break;
                    case LogLevel.Info:
                        if (_logger.IsInfoEnabled)
                        {
                            _logger.InfoException(messageFunc(), exception);
                            return true;
                        }
                        break;
                    case LogLevel.Warn:
                        if (_logger.IsWarnEnabled)
                        {
                            _logger.WarnException(messageFunc(), exception);
                            return true;
                        }
                        break;
                    case LogLevel.Error:
                        if (_logger.IsErrorEnabled)
                        {
                            _logger.ErrorException(messageFunc(), exception);
                            return true;
                        }
                        break;
                    case LogLevel.Fatal:
                        if (_logger.IsFatalEnabled)
                        {
                            _logger.FatalException(messageFunc(), exception);
                            return true;
                        }
                        break;
                    default:
                        if (_logger.IsTraceEnabled)
                        {
                            _logger.TraceException(messageFunc(), exception);
                            return true;
                        }
                        break;
                }
                return false;
            }

            private bool IsLogLevelEnable(LogLevel logLevel)
            {
                switch (logLevel)
                {
                    case LogLevel.Debug:
                        return _logger.IsDebugEnabled;
                    case LogLevel.Info:
                        return _logger.IsInfoEnabled;
                    case LogLevel.Warn:
                        return _logger.IsWarnEnabled;
                    case LogLevel.Error:
                        return _logger.IsErrorEnabled;
                    case LogLevel.Fatal:
                        return _logger.IsFatalEnabled;
                    default:
                        return _logger.IsTraceEnabled;
                }
            }
        }
    }

    internal class Log4NetLogProvider : LogProviderBase
    {
        private readonly Func<string, object> _getLoggerByNameDelegate;
        private static bool s_providerIsAvailableOverride = true;

        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogManager")]
        public Log4NetLogProvider()
        {
            if (!IsLoggerAvailable())
            {
                throw new InvalidOperationException("log4net.LogManager not found");
            }
            _getLoggerByNameDelegate = GetGetLoggerMethodCall();
        }

        public static bool ProviderIsAvailableOverride
        {
            get { return s_providerIsAvailableOverride; }
            set { s_providerIsAvailableOverride = value; }
        }

        public override Logger GetLogger(string name)
        {
            return new Log4NetLogger(_getLoggerByNameDelegate(name)).Log;
        }

        internal static bool IsLoggerAvailable()
        {
            return ProviderIsAvailableOverride && GetLogManagerType() != null;
        }

        protected override OpenNdc GetOpenNdcMethod()
        {
            Type logicalThreadContextType = Type.GetType("log4net.LogicalThreadContext, log4net");
            PropertyInfo stacksProperty = logicalThreadContextType.GetPropertyPortable("Stacks");
            Type logicalThreadContextStacksType = stacksProperty.PropertyType;
            PropertyInfo stacksIndexerProperty = logicalThreadContextStacksType.GetPropertyPortable("Item");
            Type stackType = stacksIndexerProperty.PropertyType;
            MethodInfo pushMethod = stackType.GetMethodPortable("Push");

            ParameterExpression messageParameter =
                Expression.Parameter(typeof(string), "message");

            // message => LogicalThreadContext.Stacks.Item["NDC"].Push(message);
            MethodCallExpression callPushBody =
                Expression.Call(
                    Expression.Property(Expression.Property(null, stacksProperty),
                                        stacksIndexerProperty,
                                        Expression.Constant("NDC")),
                    pushMethod,
                    messageParameter);

            OpenNdc result =
                Expression.Lambda<OpenNdc>(callPushBody, messageParameter)
                          .Compile();

            return result;
        }

        protected override OpenMdc GetOpenMdcMethod()
        {
            Type logicalThreadContextType = Type.GetType("log4net.LogicalThreadContext, log4net");
            PropertyInfo propertiesProperty = logicalThreadContextType.GetPropertyPortable("Properties");
            Type logicalThreadContextPropertiesType = propertiesProperty.PropertyType;
            PropertyInfo propertiesIndexerProperty = logicalThreadContextPropertiesType.GetPropertyPortable("Item");

            MethodInfo removeMethod = logicalThreadContextPropertiesType.GetMethodPortable("Remove");

            ParameterExpression keyParam = Expression.Parameter(typeof(string), "key");
            ParameterExpression valueParam = Expression.Parameter(typeof(string), "value");

            MemberExpression propertiesExpression = Expression.Property(null, propertiesProperty);

            // (key, value) => LogicalThreadContext.Properties.Item[key] = value;
            BinaryExpression setProperties = Expression.Assign(Expression.Property(propertiesExpression, propertiesIndexerProperty, keyParam), valueParam);

            // key => LogicalThreadContext.Properties.Remove(key);
            MethodCallExpression removeMethodCall = Expression.Call(propertiesExpression, removeMethod, keyParam);

            Action<string, string> set = Expression
                .Lambda<Action<string, string>>(setProperties, keyParam, valueParam)
                .Compile();

            Action<string> remove = Expression
                .Lambda<Action<string>>(removeMethodCall, keyParam)
                .Compile();

            return (key, value) =>
            {
                set(key, value);
                return new DisposableAction(() => remove(key));
            };
        }

        private static Type GetLogManagerType()
        {
            return Type.GetType("log4net.LogManager, log4net");
        }

        private static Func<string, object> GetGetLoggerMethodCall()
        {
            Type logManagerType = GetLogManagerType();
            MethodInfo method = logManagerType.GetMethodPortable("GetLogger", typeof(string));
            ParameterExpression nameParam = Expression.Parameter(typeof(string), "name");
            MethodCallExpression methodCall = Expression.Call(null, method, nameParam);
            return Expression.Lambda<Func<string, object>>(methodCall, nameParam).Compile();
        }

        internal class Log4NetLogger
        {
            private readonly dynamic _logger;
            private static Type s_callerStackBoundaryType;
            private static readonly object CallerStackBoundaryTypeSync = new object();

            private readonly object _levelDebug;
            private readonly object _levelInfo;
            private readonly object _levelWarn;
            private readonly object _levelError;
            private readonly object _levelFatal;
            private readonly Func<object, object, bool> _isEnabledForDelegate;
            private readonly Action<object, Type, object, string, Exception> _logDelegate;

            [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")]
            internal Log4NetLogger(dynamic logger)
            {
                _logger = logger.Logger;

                var logEventLevelType = Type.GetType("log4net.Core.Level, log4net");
                if (logEventLevelType == null)
                {
                    throw new InvalidOperationException("Type log4net.Core.Level was not found.");
                }

                var levelFields = logEventLevelType.GetFieldsPortable().ToList();
                _levelDebug = levelFields.First(x => x.Name == "Debug").GetValue(null);
                _levelInfo = levelFields.First(x => x.Name == "Info").GetValue(null);
                _levelWarn = levelFields.First(x => x.Name == "Warn").GetValue(null);
                _levelError = levelFields.First(x => x.Name == "Error").GetValue(null);
                _levelFatal = levelFields.First(x => x.Name == "Fatal").GetValue(null);

                // Func<object, object, bool> isEnabledFor = (logger, level) => { return ((log4net.Core.ILogger)logger).IsEnabled(level); }
                var loggerType = Type.GetType("log4net.Core.ILogger, log4net");
                if (loggerType == null)
                {
                    throw new InvalidOperationException("Type log4net.Core.ILogger, was not found.");
                }
                MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabledFor", logEventLevelType);
                ParameterExpression instanceParam = Expression.Parameter(typeof(object));
                UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType);
                ParameterExpression callerStackBoundaryDeclaringTypeParam = Expression.Parameter(typeof(Type));
                ParameterExpression levelParam = Expression.Parameter(typeof(object));
                ParameterExpression messageParam = Expression.Parameter(typeof(string));
                UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType);
                MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast);
                _isEnabledForDelegate = Expression.Lambda<Func<object, object, bool>>(isEnabledMethodCall, instanceParam, levelParam).Compile();

                // Action<object, object, string, Exception> Log =
                // (logger, callerStackBoundaryDeclaringType, level, message, exception) => { ((ILogger)logger).Write(callerStackBoundaryDeclaringType, level, message, exception); }
                MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Log",
                    typeof(Type),
                    logEventLevelType,
                    typeof(string),
                    typeof(Exception));
                ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception));
                var writeMethodExp = Expression.Call(
                    instanceCast,
                    writeExceptionMethodInfo,
                    callerStackBoundaryDeclaringTypeParam,
                    levelCast,
                    messageParam,
                    exceptionParam);
                _logDelegate = Expression.Lambda<Action<object, Type, object, string, Exception>>(
                    writeMethodExp,
                    instanceParam,
                    callerStackBoundaryDeclaringTypeParam,
                    levelParam,
                    messageParam,
                    exceptionParam).Compile();
            }

            public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
            {
                if (messageFunc == null)
                {
                    return IsLogLevelEnable(logLevel);
                }

                if (!IsLogLevelEnable(logLevel))
                {
                    return false;
                }

                messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters);

                // determine correct caller - this might change due to jit optimizations with method inlining
                if (s_callerStackBoundaryType == null)
                {
                    lock (CallerStackBoundaryTypeSync)
                    {
#if !LIBLOG_PORTABLE
                        StackTrace stack = new StackTrace();
                        Type thisType = GetType();
                        s_callerStackBoundaryType = Type.GetType("LoggerExecutionWrapper");
                        for (var i = 1; i < stack.FrameCount; i++)
                        {
                            if (!IsInTypeHierarchy(thisType, stack.GetFrame(i).GetMethod().DeclaringType))
                            {
                                s_callerStackBoundaryType = stack.GetFrame(i - 1).GetMethod().DeclaringType;
                                break;
                            }
                        }
#else
                        s_callerStackBoundaryType = typeof (LoggerExecutionWrapper);
#endif
                    }
                }

                var translatedLevel = TranslateLevel(logLevel);
                _logDelegate(_logger, s_callerStackBoundaryType, translatedLevel, messageFunc(), exception);
                return true;
            }

            private static bool IsInTypeHierarchy(Type currentType, Type checkType)
            {
                while (currentType != null && currentType != typeof(object))
                {
                    if (currentType == checkType)
                    {
                        return true;
                    }
                    currentType = currentType.GetBaseTypePortable();
                }
                return false;
            }

            private bool IsLogLevelEnable(LogLevel logLevel)
            {
                var level = TranslateLevel(logLevel);
                return _isEnabledForDelegate(_logger, level);
            }

            private object TranslateLevel(LogLevel logLevel)
            {
                switch (logLevel)
                {
                    case LogLevel.Trace:
                    case LogLevel.Debug:
                        return _levelDebug;
                    case LogLevel.Info:
                        return _levelInfo;
                    case LogLevel.Warn:
                        return _levelWarn;
                    case LogLevel.Error:
                        return _levelError;
                    case LogLevel.Fatal:
                        return _levelFatal;
                    default:
                        throw new ArgumentOutOfRangeException("logLevel", logLevel, null);
                }
            }
        }
    }

    internal class EntLibLogProvider : LogProviderBase
    {
        private const string TypeTemplate = "Microsoft.Practices.EnterpriseLibrary.Logging.{0}, Microsoft.Practices.EnterpriseLibrary.Logging";
        private static bool s_providerIsAvailableOverride = true;
        private static readonly Type LogEntryType;
        private static readonly Type LoggerType;
        private static readonly Type TraceEventTypeType;
        private static readonly Action<string, string, int> WriteLogEntry;
        private static readonly Func<string, int, bool> ShouldLogEntry;

        [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
        static EntLibLogProvider()
        {
            LogEntryType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "LogEntry"));
            LoggerType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "Logger"));
            TraceEventTypeType = TraceEventTypeValues.Type;
            if (LogEntryType == null
                 || TraceEventTypeType == null
                 || LoggerType == null)
            {
                return;
            }
            WriteLogEntry = GetWriteLogEntry();
            ShouldLogEntry = GetShouldLogEntry();
        }

        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "EnterpriseLibrary")]
        public EntLibLogProvider()
        {
            if (!IsLoggerAvailable())
            {
                throw new InvalidOperationException("Microsoft.Practices.EnterpriseLibrary.Logging.Logger not found");
            }
        }

        public static bool ProviderIsAvailableOverride
        {
            get { return s_providerIsAvailableOverride; }
            set { s_providerIsAvailableOverride = value; }
        }

        public override Logger GetLogger(string name)
        {
            return new EntLibLogger(name, WriteLogEntry, ShouldLogEntry).Log;
        }

        internal static bool IsLoggerAvailable()
        {
            return ProviderIsAvailableOverride
                 && TraceEventTypeType != null
                 && LogEntryType != null;
        }

        private static Action<string, string, int> GetWriteLogEntry()
        {
            // new LogEntry(...)
            var logNameParameter = Expression.Parameter(typeof(string), "logName");
            var messageParameter = Expression.Parameter(typeof(string), "message");
            var severityParameter = Expression.Parameter(typeof(int), "severity");

            MemberInitExpression memberInit = GetWriteLogExpression(
                messageParameter,
                Expression.Convert(severityParameter, TraceEventTypeType),
                logNameParameter);

            //Logger.Write(new LogEntry(....));
            MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("Write", LogEntryType);
            var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit);

            return Expression.Lambda<Action<string, string, int>>(
                writeLogEntryExpression,
                logNameParameter,
                messageParameter,
                severityParameter).Compile();
        }

        private static Func<string, int, bool> GetShouldLogEntry()
        {
            // new LogEntry(...)
            var logNameParameter = Expression.Parameter(typeof(string), "logName");
            var severityParameter = Expression.Parameter(typeof(int), "severity");

            MemberInitExpression memberInit = GetWriteLogExpression(
                Expression.Constant("***dummy***"),
                Expression.Convert(severityParameter, TraceEventTypeType),
                logNameParameter);

            //Logger.Write(new LogEntry(....));
            MethodInfo writeLogEntryMethod = LoggerType.GetMethodPortable("ShouldLog", LogEntryType);
            var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit);

            return Expression.Lambda<Func<string, int, bool>>(
                writeLogEntryExpression,
                logNameParameter,
                severityParameter).Compile();
        }

        private static MemberInitExpression GetWriteLogExpression(Expression message,
            Expression severityParameter, ParameterExpression logNameParameter)
        {
            var entryType = LogEntryType;
            MemberInitExpression memberInit = Expression.MemberInit(Expression.New(entryType), 
                Expression.Bind(entryType.GetPropertyPortable("Message"), message),
                Expression.Bind(entryType.GetPropertyPortable("Severity"), severityParameter),
                Expression.Bind(
                    entryType.GetPropertyPortable("TimeStamp"),
                    Expression.Property(null, typeof (DateTime).GetPropertyPortable("UtcNow"))),
                Expression.Bind(
                    entryType.GetPropertyPortable("Categories"),
                    Expression.ListInit(
                        Expression.New(typeof (List<string>)),
                        typeof (List<string>).GetMethodPortable("Add", typeof (string)),
                        logNameParameter)));
            return memberInit;
        }

        internal class EntLibLogger
        {
            private readonly string _loggerName;
            private readonly Action<string, string, int> _writeLog;
            private readonly Func<string, int, bool> _shouldLog;

            internal EntLibLogger(string loggerName, Action<string, string, int> writeLog, Func<string, int, bool> shouldLog)
            {
                _loggerName = loggerName;
                _writeLog = writeLog;
                _shouldLog = shouldLog;
            }

            public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
            {
                var severity = MapSeverity(logLevel);
                if (messageFunc == null)
                {
                    return _shouldLog(_loggerName, severity);
                }


                messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters);
                if (exception != null)
                {
                    return LogException(logLevel, messageFunc, exception);
                }
                _writeLog(_loggerName, messageFunc(), severity);
                return true;
            }

            public bool LogException(LogLevel logLevel, Func<string> messageFunc, Exception exception)
            {
                var severity = MapSeverity(logLevel);
                var message = messageFunc() + Environment.NewLine + exception;
                _writeLog(_loggerName, message, severity);
                return true;
            }

            private static int MapSeverity(LogLevel logLevel)
            {
                switch (logLevel)
                {
                    case LogLevel.Fatal:
                        return TraceEventTypeValues.Critical;
                    case LogLevel.Error:
                        return TraceEventTypeValues.Error;
                    case LogLevel.Warn:
                        return TraceEventTypeValues.Warning;
                    case LogLevel.Info:
                        return TraceEventTypeValues.Information;
                    default:
                        return TraceEventTypeValues.Verbose;
                }
            }
        }
    }

    internal class SerilogLogProvider : LogProviderBase
    {
        private readonly Func<string, object> _getLoggerByNameDelegate;
        private static bool s_providerIsAvailableOverride = true;

        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")]
        public SerilogLogProvider()
        {
            if (!IsLoggerAvailable())
            {
                throw new InvalidOperationException("Serilog.Log not found");
            }
            _getLoggerByNameDelegate = GetForContextMethodCall();
        }

        public static bool ProviderIsAvailableOverride
        {
            get { return s_providerIsAvailableOverride; }
            set { s_providerIsAvailableOverride = value; }
        }

        public override Logger GetLogger(string name)
        {
            return new SerilogLogger(_getLoggerByNameDelegate(name)).Log;
        }

        internal static bool IsLoggerAvailable()
        {
            return ProviderIsAvailableOverride && GetLogManagerType() != null;
        }

        protected override OpenNdc GetOpenNdcMethod()
        {
            return message => GetPushProperty()("NDC", message);
        }

        protected override OpenMdc GetOpenMdcMethod()
        {
            return (key, value) => GetPushProperty()(key, value);
        }

        private static Func<string, string, IDisposable> GetPushProperty()
        {
            Type ndcContextType = Type.GetType("Serilog.Context.LogContext, Serilog.FullNetFx");
            MethodInfo pushPropertyMethod = ndcContextType.GetMethodPortable(
                "PushProperty", 
                typeof(string),
                typeof(object),
                typeof(bool));
            ParameterExpression nameParam = Expression.Parameter(typeof(string), "name");
            ParameterExpression valueParam = Expression.Parameter(typeof(object), "value");
            ParameterExpression destructureObjectParam = Expression.Parameter(typeof(bool), "destructureObjects");
            MethodCallExpression pushPropertyMethodCall = Expression
                .Call(null, pushPropertyMethod, nameParam, valueParam, destructureObjectParam);
            var pushProperty = Expression
                .Lambda<Func<string, object, bool, IDisposable>>(
                    pushPropertyMethodCall,
                    nameParam,
                    valueParam,
                    destructureObjectParam)
                .Compile();
            
            return (key, value) => pushProperty(key, value, false);
        }

        private static Type GetLogManagerType()
        {
            return Type.GetType("Serilog.Log, Serilog");
        }

        private static Func<string, object> GetForContextMethodCall()
        {
            Type logManagerType = GetLogManagerType();
            MethodInfo method = logManagerType.GetMethodPortable("ForContext", typeof(string), typeof(object), typeof(bool));
            ParameterExpression propertyNameParam = Expression.Parameter(typeof(string), "propertyName");
            ParameterExpression valueParam = Expression.Parameter(typeof(object), "value");
            ParameterExpression destructureObjectsParam = Expression.Parameter(typeof(bool), "destructureObjects");
            MethodCallExpression methodCall = Expression.Call(null, method, new Expression[]
            {
                propertyNameParam, 
                valueParam,
                destructureObjectsParam
            });
            var func = Expression.Lambda<Func<string, object, bool, object>>(
                methodCall,
                propertyNameParam,
                valueParam,
                destructureObjectsParam)
                .Compile();
            return name => func("Name", name, false);
        }

        internal class SerilogLogger
        {
            private readonly object _logger;
            private static readonly object DebugLevel;
            private static readonly object ErrorLevel;
            private static readonly object FatalLevel;
            private static readonly object InformationLevel;
            private static readonly object VerboseLevel;
            private static readonly object WarningLevel;
            private static readonly Func<object, object, bool> IsEnabled;
            private static readonly Action<object, object, string, object[]> Write;
            private static readonly Action<object, object, Exception, string, object[]> WriteException;

            [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
            [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
            [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")]
            [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "LogEventLevel")]
            [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Serilog")]
            static SerilogLogger()
            {
                var logEventLevelType = Type.GetType("Serilog.Events.LogEventLevel, Serilog");
                if (logEventLevelType == null)
                {
                    throw new InvalidOperationException("Type Serilog.Events.LogEventLevel was not found.");
                }
                DebugLevel = Enum.Parse(logEventLevelType, "Debug", false);
                ErrorLevel = Enum.Parse(logEventLevelType, "Error", false);
                FatalLevel = Enum.Parse(logEventLevelType, "Fatal", false);
                InformationLevel = Enum.Parse(logEventLevelType, "Information", false);
                VerboseLevel = Enum.Parse(logEventLevelType, "Verbose", false);
                WarningLevel = Enum.Parse(logEventLevelType, "Warning", false);

                // Func<object, object, bool> isEnabled = (logger, level) => { return ((SeriLog.ILogger)logger).IsEnabled(level); }
                var loggerType = Type.GetType("Serilog.ILogger, Serilog");
                if (loggerType == null)
                {
                    throw new InvalidOperationException("Type Serilog.ILogger was not found.");
                }
                MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabled", logEventLevelType);
                ParameterExpression instanceParam = Expression.Parameter(typeof(object));
                UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType);
                ParameterExpression levelParam = Expression.Parameter(typeof(object));
                UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType);
                MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast);
                IsEnabled = Expression.Lambda<Func<object, object, bool>>(isEnabledMethodCall, instanceParam, levelParam).Compile();

                // Action<object, object, string> Write =
                // (logger, level, message, params) => { ((SeriLog.ILoggerILogger)logger).Write(level, message, params); }
                MethodInfo writeMethodInfo = loggerType.GetMethodPortable("Write", logEventLevelType, typeof(string), typeof(object[]));
                ParameterExpression messageParam = Expression.Parameter(typeof(string));
                ParameterExpression propertyValuesParam = Expression.Parameter(typeof(object[]));
                MethodCallExpression writeMethodExp = Expression.Call(
                    instanceCast,
                    writeMethodInfo,
                    levelCast,
                    messageParam,
                    propertyValuesParam);
                var expression = Expression.Lambda<Action<object, object, string, object[]>>(
                    writeMethodExp, 
                    instanceParam,
                    levelParam,
                    messageParam,
                    propertyValuesParam);
                Write = expression.Compile();

                // Action<object, object, string, Exception> WriteException =
                // (logger, level, exception, message) => { ((ILogger)logger).Write(level, exception, message, new object[]); }
                MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Write", 
                    logEventLevelType,
                    typeof(Exception),
                    typeof(string),
                    typeof(object[]));
                ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception));
                writeMethodExp = Expression.Call(
                    instanceCast,
                    writeExceptionMethodInfo,
                    levelCast,
                    exceptionParam,
                    messageParam,
                    propertyValuesParam);
                WriteException = Expression.Lambda<Action<object, object, Exception, string, object[]>>(
                    writeMethodExp, 
                    instanceParam,
                    levelParam,
                    exceptionParam,
                    messageParam,
                    propertyValuesParam).Compile();
            }

            internal SerilogLogger(object logger)
            {
                _logger = logger;
            }

            public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
            {
                if (messageFunc == null)
                {
                    return IsEnabled(_logger, logLevel);
                }
                if (exception != null)
                {
                    return LogException(logLevel, messageFunc, exception, formatParameters);
                }

                switch (logLevel)
                {
                    case LogLevel.Debug:
                        if (IsEnabled(_logger, DebugLevel))
                        {
                            Write(_logger, DebugLevel, messageFunc(), formatParameters);
                            return true;
                        }
                        break;
                    case LogLevel.Info:
                        if (IsEnabled(_logger, InformationLevel))
                        {
                            Write(_logger, InformationLevel, messageFunc(), formatParameters);
                            return true;
                        }
                        break;
                    case LogLevel.Warn:
                        if (IsEnabled(_logger, WarningLevel))
                        {
                            Write(_logger, WarningLevel, messageFunc(), formatParameters);
                            return true;
                        }
                        break;
                    case LogLevel.Error:
                        if (IsEnabled(_logger, ErrorLevel))
                        {
                            Write(_logger, ErrorLevel, messageFunc(), formatParameters);
                            return true;
                        }
                        break;
                    case LogLevel.Fatal:
                        if (IsEnabled(_logger, FatalLevel))
                        {
                            Write(_logger, FatalLevel, messageFunc(), formatParameters);
                            return true;
                        }
                        break;
                    default:
                        if (IsEnabled(_logger, VerboseLevel))
                        {
                            Write(_logger, VerboseLevel, messageFunc(), formatParameters);
                            return true;
                        }
                        break;
                }
                return false;
            }

            private bool LogException(LogLevel logLevel, Func<string> messageFunc, Exception exception, object[] formatParams)
            {
                switch (logLevel)
                {
                    case LogLevel.Debug:
                        if (IsEnabled(_logger, DebugLevel))
                        {
                            WriteException(_logger, DebugLevel, exception, messageFunc(), formatParams);
                            return true;
                        }
                        break;
                    case LogLevel.Info:
                        if (IsEnabled(_logger, InformationLevel))
                        {
                            WriteException(_logger, InformationLevel, exception, messageFunc(), formatParams);
                            return true;
                        }
                        break;
                    case LogLevel.Warn:
                        if (IsEnabled(_logger, WarningLevel))
                        {
                            WriteException(_logger, WarningLevel, exception, messageFunc(), formatParams);
                            return true;
                        }
                        break;
                    case LogLevel.Error:
                        if (IsEnabled(_logger, ErrorLevel))
                        {
                            WriteException(_logger, ErrorLevel, exception, messageFunc(), formatParams);
                            return true;
                        }
                        break;
                    case LogLevel.Fatal:
                        if (IsEnabled(_logger, FatalLevel))
                        {
                            WriteException(_logger, FatalLevel, exception, messageFunc(), formatParams);
                            return true;
                        }
                        break;
                    default:
                        if (IsEnabled(_logger, VerboseLevel))
                        {
                            WriteException(_logger, VerboseLevel, exception, messageFunc(), formatParams);
                            return true;
                        }
                        break;
                }
                return false;
            }
        }
    }

    internal class LoupeLogProvider : LogProviderBase
    {
        /// <summary>
        /// The form of the Loupe Log.Write method we're using
        /// </summary>
        internal delegate void WriteDelegate(
            int severity,
            string logSystem,
            int skipFrames,
            Exception exception,
            bool attributeToException,
            int writeMode,
            string detailsXml,
            string category,
            string caption,
            string description,
            params object[] args
            );

        private static bool s_providerIsAvailableOverride = true;
        private readonly WriteDelegate _logWriteDelegate;

        public LoupeLogProvider()
        {
            if (!IsLoggerAvailable())
            {
                throw new InvalidOperationException("Gibraltar.Agent.Log (Loupe) not found");
            }

            _logWriteDelegate = GetLogWriteDelegate();
        }

        /// <summary>
        /// Gets or sets a value indicating whether [provider is available override]. Used in tests.
        /// </summary>
        /// <value>
        /// <c>true</c> if [provider is available override]; otherwise, <c>false</c>.
        /// </value>
        public static bool ProviderIsAvailableOverride
        {
            get { return s_providerIsAvailableOverride; }
            set { s_providerIsAvailableOverride = value; }
        }

        public override Logger GetLogger(string name)
        {
            return new LoupeLogger(name, _logWriteDelegate).Log;
        }

        public static bool IsLoggerAvailable()
        {
            return ProviderIsAvailableOverride && GetLogManagerType() != null;
        }

        private static Type GetLogManagerType()
        {
            return Type.GetType("Gibraltar.Agent.Log, Gibraltar.Agent");
        }

        private static WriteDelegate GetLogWriteDelegate()
        {
            Type logManagerType = GetLogManagerType();
            Type logMessageSeverityType = Type.GetType("Gibraltar.Agent.LogMessageSeverity, Gibraltar.Agent");
            Type logWriteModeType = Type.GetType("Gibraltar.Agent.LogWriteMode, Gibraltar.Agent");

            MethodInfo method = logManagerType.GetMethodPortable(
                "Write",
                logMessageSeverityType, typeof(string), typeof(int), typeof(Exception), typeof(bool), 
                logWriteModeType, typeof(string), typeof(string), typeof(string), typeof(string), typeof(object[]));

            var callDelegate = (WriteDelegate)method.CreateDelegate(typeof(WriteDelegate));
            return callDelegate;
        }

        internal class LoupeLogger
        {
            private const string LogSystem = "LibLog";

            private readonly string _category;
            private readonly WriteDelegate _logWriteDelegate;
            private readonly int _skipLevel;

            internal LoupeLogger(string category, WriteDelegate logWriteDelegate)
            {
                _category = category;
                _logWriteDelegate = logWriteDelegate;
#if DEBUG
                _skipLevel = 2;
#else
                _skipLevel = 1;
#endif
            }

            public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
            {
                if (messageFunc == null)
                {
                    //nothing to log..
                    return true;
                }

                messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters);

                _logWriteDelegate(ToLogMessageSeverity(logLevel), LogSystem, _skipLevel, exception, true, 0, null,
                    _category, null, messageFunc.Invoke());

                return true;
            }

            private static int ToLogMessageSeverity(LogLevel logLevel)
            {
                switch (logLevel)
                {
                    case LogLevel.Trace:
                        return TraceEventTypeValues.Verbose;
                    case LogLevel.Debug:
                        return TraceEventTypeValues.Verbose;
                    case LogLevel.Info:
                        return TraceEventTypeValues.Information;
                    case LogLevel.Warn:
                        return TraceEventTypeValues.Warning;
                    case LogLevel.Error:
                        return TraceEventTypeValues.Error;
                    case LogLevel.Fatal:
                        return TraceEventTypeValues.Critical;
                    default:
                        throw new ArgumentOutOfRangeException("logLevel");
                }
            }
        }
    }

    internal static class TraceEventTypeValues
    {
        internal static readonly Type Type;
        internal static readonly int Verbose;
        internal static readonly int Information;
        internal static readonly int Warning;
        internal static readonly int Error;
        internal static readonly int Critical;

        [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
        static TraceEventTypeValues()
        {
            var assembly = typeof(Uri).GetAssemblyPortable(); // This is to get to the System.dll assembly in a PCL compatible way.
            if (assembly == null)
            {
                return;
            }
            Type = assembly.GetType("System.Diagnostics.TraceEventType");
            if (Type == null) return;
            Verbose = (int)Enum.Parse(Type, "Verbose", false);
            Information = (int)Enum.Parse(Type, "Information", false);
            Warning = (int)Enum.Parse(Type, "Warning", false);
            Error = (int)Enum.Parse(Type, "Error", false);
            Critical = (int)Enum.Parse(Type, "Critical", false);
        }
    }

    internal static class LogMessageFormatter
    {
        private static readonly Regex Pattern = new Regex(@"\{@?\w{1,}\}");

        /// <summary>
        /// Some logging frameworks support structured logging, such as serilog. This will allow you to add names to structured data in a format string:
        /// For example: Log("Log message to {user}", user). This only works with serilog, but as the user of LibLog, you don't know if serilog is actually 
        /// used. So, this class simulates that. it will replace any text in {curly braces} with an index number. 
        /// 
        /// "Log {message} to {user}" would turn into => "Log {0} to {1}". Then the format parameters are handled using regular .net string.Format.
        /// </summary>
        /// <param name="messageBuilder">The message builder.</param>
        /// <param name="formatParameters">The format parameters.</param>
        /// <returns></returns>
        public static Func<string> SimulateStructuredLogging(Func<string> messageBuilder, object[] formatParameters)
        {
            if (formatParameters == null || formatParameters.Length == 0)
            {
                return messageBuilder;
            }

            return () =>
            {
                string targetMessage = messageBuilder();
                int argumentIndex = 0;
                foreach (Match match in Pattern.Matches(targetMessage))
                {
                    int notUsed;
                    if (!int.TryParse(match.Value.Substring(1, match.Value.Length -2), out notUsed))
                    {
                        targetMessage = ReplaceFirst(targetMessage, match.Value,
                            "{" + argumentIndex++ + "}");
                    }
                }
                try
                {
                    return string.Format(CultureInfo.InvariantCulture, targetMessage, formatParameters);
                }
                catch (FormatException ex)
                {
                    throw new FormatException("The input string '" + targetMessage + "' could not be formatted using string.Format", ex);
                }
            };
        }

        private static string ReplaceFirst(string text, string search, string replace)
        {
            int pos = text.IndexOf(search, StringComparison.Ordinal);
            if (pos < 0)
            {
                return text;
            }
            return text.Substring(0, pos) + replace + text.Substring(pos + search.Length);
        }
    }

    internal static class TypeExtensions
    {
        internal static MethodInfo GetMethodPortable(this Type type, string name)
        {
#if LIBLOG_PORTABLE
            return type.GetRuntimeMethods().SingleOrDefault(m => m.Name == name);
#else
            return type.GetMethod(name);
#endif
        }

        internal static MethodInfo GetMethodPortable(this Type type, string name, params Type[] types)
        {
#if LIBLOG_PORTABLE
            return type.GetRuntimeMethod(name, types);
#else
            return type.GetMethod(name, types);
#endif
        }

        internal static PropertyInfo GetPropertyPortable(this Type type, string name)
        {
#if LIBLOG_PORTABLE
            return type.GetRuntimeProperty(name);
#else
            return type.GetProperty(name);
#endif
        }

        internal static IEnumerable<FieldInfo> GetFieldsPortable(this Type type)
        {
#if LIBLOG_PORTABLE
            return type.GetRuntimeFields();
#else
            return type.GetFields();
#endif
        }

        internal static Type GetBaseTypePortable(this Type type)
        {
#if LIBLOG_PORTABLE
            return type.GetTypeInfo().BaseType;
#else
            return type.BaseType;
#endif
        }

#if LIBLOG_PORTABLE
        internal static MethodInfo GetGetMethod(this PropertyInfo propertyInfo)
        {
            return propertyInfo.GetMethod;
        }

        internal static MethodInfo GetSetMethod(this PropertyInfo propertyInfo)
        {
            return propertyInfo.SetMethod;
        }
#endif

#if !LIBLOG_PORTABLE
        internal static object CreateDelegate(this MethodInfo methodInfo, Type delegateType)
        {
            return Delegate.CreateDelegate(delegateType, methodInfo);
        }
#endif

        internal static Assembly GetAssemblyPortable(this Type type)
        {
#if LIBLOG_PORTABLE
            return type.GetTypeInfo().Assembly;
#else
            return type.Assembly;
#endif
        }
    }

    internal class DisposableAction : IDisposable
    {
        private readonly Action _onDispose;

        public DisposableAction(Action onDispose = null)
        {
            _onDispose = onDispose;
        }

        public void Dispose()
        {
            if(_onDispose != null)
            {
                _onDispose();
            }
        }
    }
}
