using System;
using System.Collections.Generic;
using System.Diagnostics;
using Websocket.Client.Logging.LogProviders;

namespace Websocket.Client.Logging
{
	public static class LogProvider
	{
		public static void SetCurrentLogProvider(ILogProvider logProvider)
		{
			LogProvider.s_currentLogProvider = logProvider;
			LogProvider.RaiseOnCurrentLogProviderSet();
		}

		public static bool IsDisabled { get; set; } = false;

		internal static Action<ILogProvider> OnCurrentLogProviderSet
		{
			set
			{
				LogProvider.s_onCurrentLogProviderSet = value;
				LogProvider.RaiseOnCurrentLogProviderSet();
			}
		}

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

		internal static ILog For<T>()
		{
			return LogProvider.GetLogger(typeof(T), "System.Object");
		}

		internal static ILog GetCurrentClassLogger()
		{
			return LogProvider.GetLogger(new StackFrame(1, false).GetMethod().DeclaringType, "System.Object");
		}

		internal static ILog GetLogger(Type type, string fallbackTypeName = "System.Object")
		{
			return LogProvider.GetLogger((type != null) ? type.ToString() : fallbackTypeName);
		}

		internal static ILog GetLogger(string name)
		{
			ILogProvider logProvider = LogProvider.s_currentLogProvider ?? LogProvider.ResolveLogProvider();
			if (logProvider != null)
			{
				return new LoggerExecutionWrapper(logProvider.GetLogger(name), () => LogProvider.IsDisabled);
			}
			return LogProvider.NoOpLogger.Instance;
		}

		internal static IDisposable OpenNestedContext(string message)
		{
			ILogProvider logProvider = LogProvider.s_currentLogProvider ?? LogProvider.ResolveLogProvider();
			if (logProvider != null)
			{
				return logProvider.OpenNestedContext(message);
			}
			return new DisposableAction(delegate
			{
			});
		}

		internal static IDisposable OpenMappedContext(string key, object value, bool destructure = false)
		{
			ILogProvider logProvider = LogProvider.s_currentLogProvider ?? LogProvider.ResolveLogProvider();
			if (logProvider != null)
			{
				return logProvider.OpenMappedContext(key, value, destructure);
			}
			return new DisposableAction(delegate
			{
			});
		}

		private static void RaiseOnCurrentLogProviderSet()
		{
			if (LogProvider.s_onCurrentLogProviderSet != null)
			{
				LogProvider.s_onCurrentLogProviderSet(LogProvider.s_currentLogProvider);
			}
		}

		internal static ILogProvider ResolveLogProvider()
		{
			return LogProvider.ResolvedLogProvider.Value;
		}

		internal static ILogProvider ForceResolveLogProvider()
		{
			try
			{
				foreach (Tuple<LogProvider.IsLoggerAvailable, LogProvider.CreateLogProvider> tuple in LogProvider.LogProviderResolvers)
				{
					if (tuple.Item1())
					{
						return tuple.Item2();
					}
				}
			}
			catch (Exception ex)
			{
				Console.WriteLine("Exception occurred resolving a log provider. Logging for this assembly {0} is disabled. {1}", typeof(LogProvider).Assembly.FullName, ex);
			}
			return null;
		}

		private static readonly Lazy<ILogProvider> ResolvedLogProvider = new Lazy<ILogProvider>(new Func<ILogProvider>(LogProvider.ForceResolveLogProvider));

		private static ILogProvider s_currentLogProvider;

		private static Action<ILogProvider> s_onCurrentLogProviderSet;

		internal static readonly List<Tuple<LogProvider.IsLoggerAvailable, LogProvider.CreateLogProvider>> LogProviderResolvers = new List<Tuple<LogProvider.IsLoggerAvailable, LogProvider.CreateLogProvider>>
		{
			new Tuple<LogProvider.IsLoggerAvailable, LogProvider.CreateLogProvider>(new LogProvider.IsLoggerAvailable(SerilogLogProvider.IsLoggerAvailable), () => new SerilogLogProvider()),
			new Tuple<LogProvider.IsLoggerAvailable, LogProvider.CreateLogProvider>(new LogProvider.IsLoggerAvailable(NLogLogProvider.IsLoggerAvailable), () => new NLogLogProvider()),
			new Tuple<LogProvider.IsLoggerAvailable, LogProvider.CreateLogProvider>(new LogProvider.IsLoggerAvailable(Class6.IsLoggerAvailable), () => new Class6()),
			new Tuple<LogProvider.IsLoggerAvailable, LogProvider.CreateLogProvider>(new LogProvider.IsLoggerAvailable(LoupeLogProvider.IsLoggerAvailable), () => new LoupeLogProvider())
		};

		internal delegate bool IsLoggerAvailable();

		internal delegate ILogProvider CreateLogProvider();

		internal class NoOpLogger : ILog
		{
			public bool Log(LogLevel logLevel, Func<string> messageFunc, Exception exception, params object[] formatParameters)
			{
				return false;
			}

			internal static readonly LogProvider.NoOpLogger Instance = new LogProvider.NoOpLogger();
		}
	}
}
