using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Siger.Middlelayer.Common;

namespace Siger.Middlelayer.Log
{
    public class MongoLogEngine : LogEngine
    {
        private readonly ConcurrentQueue<LogMessage> _failedLogs;
        private readonly ManualResetEvent _hasFailedLogEvent = new ManualResetEvent(false);

        private bool _disposed;
        ~MongoLogEngine()
        {
            DoDispose();
        }

        private void DoDispose()
        {
            if (!_disposed)
            {
                //_failedLogs.Clear();
                _disposed = true;
            }
        }

        public MongoLogEngine(Module module) : base(module)
        {
            _failedLogs = new ConcurrentQueue<LogMessage>();
            var loggingThread = new Thread(RetryWrite)
            {
                IsBackground = true
            };
            loggingThread.Start();
        }

        public override async Task WriteAsync(LogLevel level, string msg)
        {
            var logMessage = new LogMessage(level, Module, msg);

            try
            {
                await Task.Run(() => InsertLog(logMessage));
            }
            catch (Exception)
            {
                EnqueueFailed(logMessage);

                _hasFailedLogEvent.Set();
            }
        }

        public override void Write(LogLevel level, string msg)
        {
            var logMessage = new LogMessage(level, Module, msg);

            try
            {
                InsertLog(logMessage);
            }
            catch (Exception)
            {
                EnqueueFailed(logMessage);

                _hasFailedLogEvent.Set();
            }
        }

        private void EnqueueFailed(LogMessage log)
        {
            try
            {
                _failedLogs.Enqueue(log);

                if (_failedLogs.Count > 500) //remove the last log when the count > 500
                {
                    _failedLogs.TryDequeue(out _);
                }
            }
            catch
            {
                //do nothing
            }
        }

        private void RetryWrite()
        {
            try
            {
                while (true)
                {
                    _hasFailedLogEvent.WaitOne();

                    _hasFailedLogEvent.Reset();

                    Thread.Sleep(10);
                    if (_failedLogs.Any())
                    {
                        foreach (var logMessage in _failedLogs)
                        {
                            InsertLog(logMessage);
                            _failedLogs.TryDequeue(out _);
                        }
                    }
                }
            }
            catch (Exception)
            {
               //ignore
            }
        }

        private void InsertLog(LogMessage log)
        {
            MongoDbClient.Instance.MiddleLayerLogs.InsertOne(log);
        }

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