using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace VCommon.Logging
{
    public class TextLogPool : IDisposable
    {
        private const int IntervalToCheckCloseInSec = 1;

        private bool _isDisposed;

        private readonly Dictionary<string, Entry> _dictionary = new Dictionary<string, Entry>();

        private class Entry
        {
            public FileStream FileStream { get; set; }
            public DateTime LastWriteTime { get; set; }
        }

        public TextLogPool(int idleToCloseInMin = 10)
        {
            Init(idleToCloseInMin);
        }

        public bool AppendText(string path, string content)
        {
            if (_isDisposed) throw new ObjectDisposedException("Cannot access a disposed object.");

            Entry inst = null;
            try
            {
                inst = GetInstance(path);
                if (inst == null) return false;

                lock (inst)
                {
                    inst.LastWriteTime = DateTime.Now;
                    var buf = Encoding.UTF8.GetBytes(content);
                    inst.FileStream.Write(buf, 0, buf.Length);
                    inst.FileStream.Flush();
                    return true;
                }
            }
            catch (Exception exception)
            {
                Logger.Warn("TextLogPool write fail", new { path, FileStreamInstance = inst?.FileStream.GetHashCode().ToString("X4"), content, exception });
                return false;
            }
        }

        public void Dispose()
        {
            _isDisposed = true;
            foreach (var entry in _dictionary.Values) entry.FileStream.Dispose();
            _dictionary.Clear();
        }

        private void Init(int idleToCloseInMin)
        {
            Task.Factory.StartNew(() =>
            {
                while (false == _isDisposed)
                {
                    lock (_dictionary)
                    {
                        var now = DateTime.Now;
                        var toFree = _dictionary.Where(p => (now - p.Value.LastWriteTime).TotalMinutes > idleToCloseInMin)
                            .ToArray();
                        foreach (var pair in toFree) FreeInstance(pair.Key);
                    }

                    Thread.Sleep(1000 * IntervalToCheckCloseInSec);
                }
            });
        }

        private void FreeInstance(string path)
        {
            if (!_dictionary.TryGetValue(path, out var inst)) return;
            try
            {
                lock (inst) inst.FileStream.Dispose();
                _dictionary.Remove(path);
                Logger.Info("TextLogPool entry freed", new { path, FileStreamInstance = inst.FileStream.GetHashCode().ToString("X4") });
            }
            catch (Exception exception)
            {
                Logger.Warn($"TextLogPool on freeing entry", new { path, exception });
            }
        }

        private Entry GetInstance(string path)
        {
            lock (_dictionary)
            {
                if (_dictionary.TryGetValue(path, out var inst)) return inst;
                try
                {
                    var fs = File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
                    var entry = new Entry { FileStream = fs, LastWriteTime = DateTime.Now };
                    Logger.Info("TextLogPool entry created", new { path, FileStreamInstance = fs.GetHashCode().ToString("X4") });
                    _dictionary[path] = entry;
                    return entry;
                }
                catch (Exception exception)
                {
                    Logger.Info("TextLogPool error on creating entry", new { path, exception });
                    return null;
                }
            }
        }
    }
}
