using System;
using System.Linq.Expressions;
using System.Reflection;

namespace Websocket.Client.Logging.LogProviders
{
	internal class SerilogLogProvider : LogProviderBase
	{
		public SerilogLogProvider()
		{
			if (!SerilogLogProvider.IsLoggerAvailable())
			{
				throw new LibLogException("Serilog.Log not found");
			}
			this._getLoggerByNameDelegate = SerilogLogProvider.GetForContextMethodCall();
			SerilogLogProvider.s_pushProperty = SerilogLogProvider.GetPushProperty();
		}

		public static bool ProviderIsAvailableOverride { get; set; } = true;

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

		internal static bool IsLoggerAvailable()
		{
			return SerilogLogProvider.<ProviderIsAvailableOverride>k__BackingField && SerilogLogProvider.GetLogManagerType() != null;
		}

		protected override LogProviderBase.OpenNdc GetOpenNdcMethod()
		{
			return (string message) => SerilogLogProvider.s_pushProperty("NDC", message, false);
		}

		protected override LogProviderBase.OpenMdc GetOpenMdcMethod()
		{
			return (string key, object value, bool destructure) => SerilogLogProvider.s_pushProperty(key, value, destructure);
		}

		private static Func<string, object, bool, IDisposable> GetPushProperty()
		{
			MethodInfo method = LogProviderBase.FindType("Serilog.Context.LogContext", new string[] { "Serilog", "Serilog.FullNetFx" }).GetMethod("PushProperty", new Type[]
			{
				typeof(string),
				typeof(object),
				typeof(bool)
			});
			ParameterExpression parameterExpression = Expression.Parameter(typeof(string), "name");
			ParameterExpression parameterExpression2 = Expression.Parameter(typeof(object), "value");
			ParameterExpression parameterExpression3 = Expression.Parameter(typeof(bool), "destructureObjects");
			MethodCallExpression methodCallExpression = Expression.Call(null, method, parameterExpression, parameterExpression2, parameterExpression3);
			Func<string, object, bool, IDisposable> pushProperty = Expression.Lambda<Func<string, object, bool, IDisposable>>(methodCallExpression, new ParameterExpression[] { parameterExpression, parameterExpression2, parameterExpression3 }).Compile();
			return (string key, object value, bool destructure) => pushProperty(key, value, destructure);
		}

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

		private static Func<string, object> GetForContextMethodCall()
		{
			MethodInfo method = SerilogLogProvider.GetLogManagerType().GetMethod("ForContext", new Type[]
			{
				typeof(string),
				typeof(object),
				typeof(bool)
			});
			ParameterExpression parameterExpression = Expression.Parameter(typeof(string), "propertyName");
			ParameterExpression parameterExpression2 = Expression.Parameter(typeof(object), "value");
			ParameterExpression parameterExpression3 = Expression.Parameter(typeof(bool), "destructureObjects");
			MethodCallExpression methodCallExpression = Expression.Call(null, method, new Expression[] { parameterExpression, parameterExpression2, parameterExpression3 });
			Func<string, object, bool, object> func = Expression.Lambda<Func<string, object, bool, object>>(methodCallExpression, new ParameterExpression[] { parameterExpression, parameterExpression2, parameterExpression3 }).Compile();
			return (string name) => func("SourceContext", name, false);
		}

		private readonly Func<string, object> _getLoggerByNameDelegate;

		private static Func<string, object, bool, IDisposable> s_pushProperty;

		internal class SerilogLogger
		{
			internal SerilogLogger(object logger)
			{
				this._logger = logger;
			}

			private static bool Initialize()
			{
				bool flag;
				try
				{
					Type type = LogProviderBase.FindType("Serilog.Events.LogEventLevel", "Serilog");
					if (type == null)
					{
						throw new LibLogException("Type Serilog.Events.LogEventLevel was not found.");
					}
					SerilogLogProvider.SerilogLogger.s_debugLevel = Enum.Parse(type, "Debug", false);
					SerilogLogProvider.SerilogLogger.s_errorLevel = Enum.Parse(type, "Error", false);
					SerilogLogProvider.SerilogLogger.s_fatalLevel = Enum.Parse(type, "Fatal", false);
					SerilogLogProvider.SerilogLogger.s_informationLevel = Enum.Parse(type, "Information", false);
					SerilogLogProvider.SerilogLogger.s_verboseLevel = Enum.Parse(type, "Verbose", false);
					SerilogLogProvider.SerilogLogger.s_warningLevel = Enum.Parse(type, "Warning", false);
					Type type2 = LogProviderBase.FindType("Serilog.ILogger", "Serilog");
					if (type2 == null)
					{
						throw new LibLogException("Type Serilog.ILogger was not found.");
					}
					MethodInfo method = type2.GetMethod("IsEnabled", new Type[] { type });
					ParameterExpression parameterExpression = Expression.Parameter(typeof(object));
					UnaryExpression unaryExpression = Expression.Convert(parameterExpression, type2);
					ParameterExpression parameterExpression2 = Expression.Parameter(typeof(object));
					UnaryExpression unaryExpression2 = Expression.Convert(parameterExpression2, type);
					SerilogLogProvider.SerilogLogger.s_isEnabled = Expression.Lambda<Func<object, object, bool>>(Expression.Call(unaryExpression, method, new Expression[] { unaryExpression2 }), new ParameterExpression[] { parameterExpression, parameterExpression2 }).Compile();
					MethodInfo method2 = type2.GetMethod("Write", new Type[]
					{
						type,
						typeof(string),
						typeof(object[])
					});
					ParameterExpression parameterExpression3 = Expression.Parameter(typeof(string));
					ParameterExpression parameterExpression4 = Expression.Parameter(typeof(object[]));
					SerilogLogProvider.SerilogLogger.s_write = Expression.Lambda<Action<object, object, string, object[]>>(Expression.Call(unaryExpression, method2, unaryExpression2, parameterExpression3, parameterExpression4), new ParameterExpression[] { parameterExpression, parameterExpression2, parameterExpression3, parameterExpression4 }).Compile();
					MethodInfo method3 = type2.GetMethod("Write", new Type[]
					{
						type,
						typeof(Exception),
						typeof(string),
						typeof(object[])
					});
					ParameterExpression parameterExpression5 = Expression.Parameter(typeof(Exception));
					SerilogLogProvider.SerilogLogger.s_writeException = Expression.Lambda<Action<object, object, Exception, string, object[]>>(Expression.Call(unaryExpression, method3, new Expression[] { unaryExpression2, parameterExpression5, parameterExpression3, parameterExpression4 }), new ParameterExpression[] { parameterExpression, parameterExpression2, parameterExpression5, parameterExpression3, parameterExpression4 }).Compile();
					return true;
				}
				catch (Exception ex)
				{
					SerilogLogProvider.SerilogLogger.s_initializeException = ex;
					flag = false;
				}
				return flag;
			}

			public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
			{
				if (!SerilogLogProvider.SerilogLogger.Initialized.Value)
				{
					throw new LibLogException("Unable to log due to problem initializing the log provider. See inner exception for details.", SerilogLogProvider.SerilogLogger.s_initializeException);
				}
				object obj = SerilogLogProvider.SerilogLogger.TranslateLevel(logLevel);
				if (messageFunc == null)
				{
					return SerilogLogProvider.SerilogLogger.s_isEnabled(this._logger, obj);
				}
				if (!SerilogLogProvider.SerilogLogger.s_isEnabled(this._logger, obj))
				{
					return false;
				}
				if (exception != null)
				{
					this.LogException(obj, messageFunc, exception, formatParameters);
				}
				else
				{
					this.LogMessage(obj, messageFunc, formatParameters);
				}
				return true;
			}

			private void LogMessage(object translatedLevel, Func<string> messageFunc, object[] formatParameters)
			{
				SerilogLogProvider.SerilogLogger.s_write(this._logger, translatedLevel, messageFunc(), formatParameters);
			}

			private void LogException(object logLevel, Func<string> messageFunc, Exception exception, object[] formatParams)
			{
				SerilogLogProvider.SerilogLogger.s_writeException(this._logger, logLevel, exception, messageFunc(), formatParams);
			}

			private static object TranslateLevel(LogLevel logLevel)
			{
				switch (logLevel)
				{
				case LogLevel.Trace:
					return SerilogLogProvider.SerilogLogger.s_verboseLevel;
				case LogLevel.Info:
					return SerilogLogProvider.SerilogLogger.s_informationLevel;
				case LogLevel.Warn:
					return SerilogLogProvider.SerilogLogger.s_warningLevel;
				case LogLevel.Error:
					return SerilogLogProvider.SerilogLogger.s_errorLevel;
				case LogLevel.Fatal:
					return SerilogLogProvider.SerilogLogger.s_fatalLevel;
				}
				return SerilogLogProvider.SerilogLogger.s_debugLevel;
			}

			private static object s_debugLevel;

			private static object s_errorLevel;

			private static object s_fatalLevel;

			private static object s_informationLevel;

			private static object s_verboseLevel;

			private static object s_warningLevel;

			private static Func<object, object, bool> s_isEnabled;

			private static Action<object, object, string, object[]> s_write;

			private static Action<object, object, Exception, string, object[]> s_writeException;

			private static readonly Lazy<bool> Initialized = new Lazy<bool>(new Func<bool>(SerilogLogProvider.SerilogLogger.Initialize));

			private static Exception s_initializeException;

			private readonly object _logger;
		}
	}
}
