using MongoDB.Bson.IO;
using Siger.Middlelayer.Common;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Siger.Middlelayer.Log
{
    public class TxtLogEngine : LogEngine
    {
        private const string InfoTag = "I ; ";
        private const string DebguTag = "D ; ";
        private const string WarnTag = "W ; ";
        private const string ErrorTag = "E ; ";
        private const string VerboseTag = "V ; ";

        private bool _disposed;
        private readonly string _logDirectory;

        private readonly ConcurrentQueue<Action> _writeLogQueue;
        private readonly ManualResetEvent _hasLogEvent = new ManualResetEvent(false);

        private static ConcurrentDictionary<Module, SpinLock> dictFileLock = new ConcurrentDictionary<Module, SpinLock>();
        private readonly Module curModule;

        ~TxtLogEngine()
        {
            DoDispose();
        }

        private void DoDispose()
        {
            if (!_disposed)
            {
                foreach (var write in _writeLogQueue)
                {
                    write();
                }
                _disposed = true;
            }
        }

        public TxtLogEngine(Module module): base(module)
        {
            _writeLogQueue = new ConcurrentQueue<Action>();
            curModule = Module;
            if (!dictFileLock.ContainsKey(curModule))
            {
                dictFileLock.TryAdd(curModule, new SpinLock());
            }
            _logDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TxtLogs", module.ToString());
            if (!Directory.Exists(_logDirectory))
            {
                Directory.CreateDirectory(_logDirectory);
            }

            var loggingThread = new Thread(Process)
            {
                IsBackground = true
            };
            loggingThread.Start();
        }

        public override async Task WriteAsync(LogLevel level, string msg)
        {
            try
            {
                await Task.Run(() => WriteLog(level, msg));
            }
            catch (Exception)
            {
               //ignore
            }
        }

        public override void Write(LogLevel level, string msg)
        {
            try
            {
                WriteLog(level, msg);
            }
            catch (Exception)
            {
                //ignore
            }
        }

        private void Process()
        {
            while (true)
            {
                _hasLogEvent.WaitOne();

                _hasLogEvent.Reset();


                foreach (var write in _writeLogQueue)
                {
                    write();
                    _writeLogQueue.TryDequeue(out var _);
                }
                Thread.Sleep(100);
            }
        }

        private void WriteLog(LogLevel level, string content)
        {           
            _writeLogQueue.Enqueue(() =>
            {
                bool isLock = false;
                if (dictFileLock.TryGetValue(curModule, out SpinLock spinLock))
                {
                    spinLock.Enter(ref isLock);
                }
                //File.AppendAllText(LogFilePath(), FormatLog(level, content));
                var filePath = LogFilePath();
                var fileCotent = FormatLog(level, content);
                FileStream fstream = null;
                try
                {
                    fstream = new FileStream(filePath, FileMode.OpenOrCreate);
                    var arrContent = Encoding.UTF8.GetBytes(fileCotent);
                    fstream.Position = fstream.Length;
                    fstream.Write(arrContent, 0, arrContent.Length);
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    if (fstream != null)
                    {
                        fstream.Flush();
                        fstream.Close();
                        fstream.Dispose();
                    }
                }
               
                if (isLock)
                {
                    spinLock.Exit();
                }

            });
            _hasLogEvent.Set();
        }

        private string LogFilePath()
        {
            var dateTag = DateTime.Now.ToString("yyyyMMdd");
            return Path.Combine(_logDirectory, $"Log_{dateTag}.log");
        }

        private string FormatLog(LogLevel level, string message)
        {
            var now = DateTime.Now;
            var sb = new StringBuilder();
            sb.Append($"[{now:yyyyMMddHHmmss},{now.Millisecond:d3}]-");
            switch (level)
            {
                case LogLevel.Error:
                    sb.Append(ErrorTag);
                    break;
                case LogLevel.Warn:
                    sb.Append(WarnTag);
                    break;
                case LogLevel.Info:
                    sb.Append(InfoTag);
                    break;
                case LogLevel.Verbose:
                    sb.Append(VerboseTag);
                    break;
                case LogLevel.Debug:
                    sb.Append(DebguTag);
                    break;
            }
            sb.Append(message);
            sb.AppendLine();
            return sb.ToString();
        }

        public override void Dispose()
        {
            DoDispose();
            GC.SuppressFinalize(this);
        }
    }
}
