using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments;

using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;

namespace ExtensiblePortfolioSite
{
    public sealed class LoggerProvider : ILoggerProvider
    {
        private sealed class Logger : ILogger, IDisposable
        {
            // can't see what the scopes are event used for.
            //  0 restraints on the genericType so what am i supposed to do with them.
            //  log their type? serialize the scope object with every message?
            //  it seems a bit rediculus.

            private sealed class Scope : IDisposable
            {
                private readonly Logger Parent;
                public readonly String Identifier;

                public Scope(Logger Parent, String Identifier)
                {
                    this.Parent = Parent;
                    this.Identifier = Identifier;
                    this.Parent.ScopeStack.AddLast(this);
                }

                public void Dispose() => this.Parent.ScopeStack.Remove(this);
            }

            private readonly LinkedList<Scope> ScopeStack = new();
            private readonly LoggerProvider Provider;
            public readonly String Category;

            public Logger(LoggerProvider Provider, String Category)
            {
                this.Provider = Provider;
                this.Category = Category;
            }


            public IDisposable BeginScope<TState>(TState state) => new Scope(this, ScopeToString(state));
            public bool IsEnabled(LogLevel logLevel) => true;
            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) =>
                Provider.WriteLog(this, logLevel, eventId, formatter(state, exception));

            public void WriteScope(StringBuilder MessageBuilder)
            {
                LinkedListNode<Scope>? Curr = ScopeStack.First;

                while (Curr != null)
                {
                    MessageBuilder.Append(">");
                    MessageBuilder.Append(Curr.Value.Identifier);
                    Curr = Curr.Next;
                }
            }

            public void Dispose()
            {
                lock (ScopeStack)
                {
                    LinkedListNode<Scope>? Curr = ScopeStack.First;
                    while (Curr != null)
                    {
                        LinkedListNode<Scope>? next = Curr.Next;
                        Curr.Value.Dispose();
                        Curr = next;
                    }
                }
            }
        }

        #region Static
        private static readonly Encoding ENCODING = Encoding.Unicode;
        private static readonly Queue<StringBuilder> SBCache = new();
        private static StringBuilder GetStringBuilder()
        {
            lock (SBCache)
                if (SBCache.Count > 1)
                    return SBCache.Dequeue();

            return new StringBuilder(1024);
        }
        private static void ReclaimStringBuilder(StringBuilder sb)
        {
            if (sb.Capacity > 1024)
                return;
            sb.Clear();
            lock (SBCache)
                SBCache.Enqueue(sb);
        }

        private static String ScopeToString<TState>(TState Obj)
        {
#pragma warning disable IDE0046 // Convert to conditional expression

            if (Obj == null)
                return "null";

            if (Obj is Thread thread)
                return $"{thread.ManagedThreadId}:{thread.Name}";

            if (Obj is Socket socket)
                return socket.RemoteEndPoint?.ToString() ?? "null";

            return Obj.ToString() ?? "null";
#pragma warning restore IDE0046 // Convert to conditional expression
        }
        #endregion Static


        private Boolean Running;
        private readonly Thread LogWriter;
        private readonly TextWriter COut;
        private readonly StreamWriter? FOut;
        public LoggerProvider(String FileOutput)
        {
            this.COut = Console.Out;

            String Dir = Path.GetDirectoryName(FileOutput)!;
            if(!Directory.Exists(Dir)) Directory.CreateDirectory(Dir);
            this.FOut = new StreamWriter(new FileStream(FileOutput, FileMode.Append, FileAccess.Write, FileShare.Read, 1024, false), ENCODING, 1024, false);

            this.LogWriter = new Thread(LogWriter_thread)
            {
                Name = "Log Writer",
                Priority = ThreadPriority.BelowNormal,
                IsBackground = true,
            };
            this.Running = true;
            this.LogWriter.Start();
        }
        public LoggerProvider()
        {
            this.FOut = null;
            this.COut = Console.Out;

            this.LogWriter = new Thread(LogWriter_thread)
            {
                Name = "Log Writer",
                Priority = ThreadPriority.BelowNormal,
                IsBackground = true,
            };
            this.Running = true;
            this.LogWriter.Start();
        }


        private readonly SortedDictionary<String, Logger> LoggerLookup = new();
        public ILogger CreateLogger(string categoryName)
        {
            // optimize string matching by enforcing it into the strings table
            categoryName = string.IsInterned(categoryName) ?? string.Intern(categoryName);

            if (LoggerLookup.TryGetValue(categoryName, out Logger? logger))
                return logger;

            lock (LoggerLookup)
            {
                if (LoggerLookup.TryGetValue(categoryName, out logger))
                    return logger;

                logger = new Logger(this, categoryName);
                LoggerLookup.Add(categoryName, logger);
                return logger;
            }
        }
        public void Dispose()
        {
            this.Running = false;
            lock (LoggerLookup)
                foreach (Logger logger in LoggerLookup.Values)
                    logger.Dispose();

            this.LogWriter.Join();
        }



        private readonly ConcurrentQueue<String> LogQueue = new();
        private void WriteLog(Logger logger, LogLevel logLevel, EventId eventId, String Message)
        {
            StringBuilder MessageBuilder = GetStringBuilder();
            {
                MessageBuilder.Append('[');
                MessageBuilder.Append(DateTime.Now.ToShortTimeString());
                MessageBuilder.Append('\\');
                MessageBuilder.Append(logLevel.ToString());
                MessageBuilder.Append('\\');
                MessageBuilder.Append(logger.Category);
                MessageBuilder.Append(']');
                MessageBuilder.Append('{');
                MessageBuilder.Append(eventId.Id);
                MessageBuilder.Append(':');
                MessageBuilder.Append(eventId.Name);
                logger.WriteScope(MessageBuilder);
                MessageBuilder.Append("}::");
                MessageBuilder.Append(Message);

                LogQueue.Enqueue(MessageBuilder.ToString());
            }
            ReclaimStringBuilder(MessageBuilder);
        }
        private void LogWriter_thread()
        {
            [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
            async void WriteLog(String Log)
            {
                Task COutWrite_task = COut.WriteLineAsync(Log);
                if (FOut != null)
                    await FOut.WriteLineAsync(Log);
                await COutWrite_task;
            }

            while (this.Running)
            {
                while (LogQueue.TryDequeue(out String? Log))
                    WriteLog(Log);
                Thread.Sleep(1000);
            }
            lock (LogQueue)
                while (LogQueue.TryDequeue(out String? Log))
                    WriteLog(Log);
        }
    }
}
