using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using Inspectron.Ringbuffer.Interfaces;

namespace Inspectron.Ringbuffer
{
    public class RingbufferPath : IFolderWritter,IDisposable
    {
        private readonly string _pathFormat;
        private readonly RingbufferPathConfiguration _configuration;
        private readonly IFileVerifier _fileVerifier;
        
        public const string PRODUCT = "$PRODUCT$";
        public const string GOODBAD = "$GOODBAD$";
        public const string DATE = "$date$";
        public const string MONTH = "$month$";
        public const string YEAR = "$year$";

        private IIndex _index;
        ConcurrentQueue<SaveCandidate> _saveQueue = new ConcurrentQueue<SaveCandidate>();
        object _lock = new object();

        private string _ringbufferFolder;
        private int _productPartId;
        private int _goodBadPartId;
        private int _subPartId;

        public RingbufferPath(RingbufferPathConfiguration configuration, IFileVerifier fileVerifier)
        {
            _pathFormat = configuration.PathFormat;
            _configuration = configuration;
            _fileVerifier = fileVerifier;
            
            var pathFormat = configuration.PathFormat;

            var pathParts = Path.Combine(configuration.RootDirectory,pathFormat).Split(new char[]{ Path.DirectorySeparatorChar },StringSplitOptions.RemoveEmptyEntries);

            _ringbufferFolder = configuration.RootDirectory;

            _productPartId = -1;
            _goodBadPartId = -1;
            
            for (int i = 1; i < pathParts.Length; i++)
            {
                if (pathParts[i] == PRODUCT) _productPartId = i;
                if (pathParts[i] == GOODBAD) _goodBadPartId = i;
                
            }

            //if(_productPartId==-1||_goodBadPartId==-1)throw new Exception("one of the markers is missing");

            EnsureDirectory(_ringbufferFolder);
            InitIndex();


            var th1 = new Thread(RemoveLoop);
            th1.IsBackground = true;
            th1.Priority = ThreadPriority.Normal;
            th1.Start();

            var th2 = new Thread(SaveLoop);
            th2.IsBackground = true;
            th2.Priority = ThreadPriority.Normal;
            th2.Start();
        }

        private void InitIndex()
        {
            _index = new GroupIndex(_configuration.GroupPartId,_goodBadPartId);
            IndexFiles();
        }
        private void IndexFiles()
        {


            var allFiles = Directory.GetFiles(_ringbufferFolder, "*.*", SearchOption.AllDirectories);
            var verifiedFiles = allFiles.Where(x => _fileVerifier.IsMainFile(x)).ToList();
            var orderedFiles = verifiedFiles.OrderByDescending(x => new FileInfo(x).CreationTimeUtc).ToList();
            orderedFiles.ForEach(_index.Track);
        }

        private void RemoveLoop()
        {
            while (!_stop)
            {
                var filesToDelete = _index.GetFilesToDelete(_configuration.MaxAmountOfGoodImages, _configuration.MaxAmountOfBadImages);

                filesToDelete.ForEach(x =>
                {
                    DeleteFile(x);
                    _fileVerifier.LinkedFiles(x).ForEach(DeleteFile);
                });

                Thread.Sleep(1000);
            }
        }
        public void DeleteFile(string fileToDelete)
        {
            var fi = new System.IO.FileInfo(fileToDelete);
            if (fi.Exists)
            {
                lock (_lock)
                {
                    fi.Delete();
                }

                fi.Refresh();
                while (fi.Exists)
                {
                    System.Threading.Thread.Sleep(10);
                    fi.Refresh();
                }
            }
        }

        private bool _stop = false;
        private void SaveLoop()
        {
            while (!_stop)
            {
                if (_saveQueue.TryDequeue(out var candidate))
                {
                    lock (_lock)
                    {
                        EnsureDirectory(candidate.TargetDirectory);
                        var written = candidate.FileIdentity.WriteTo(candidate.TargetDirectory);
                        _index.Track(written);
                    }
                }
                else
                {
                    Thread.Sleep(1);
                }
            }
        }
        private void EnsureDirectory(string directory)
        {
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }
        }
        public static string GOOD_DIR = "Good";
        public static string BAD_DIR = "Bad";

        string TransformPath(string originalPath,bool isGood,string product)
        {
            var time = DateTime.Now;

            var res= originalPath.Replace(PRODUCT, product)
                .Replace(GOODBAD, isGood ? GOOD_DIR : BAD_DIR)
                .Replace(MONTH,time.ToString("MMMM"))
                .Replace(YEAR,time.Year.ToString())
                .Replace(DATE,time.ToString("dd.MM.yyyy"));
            return Path.Combine(_configuration.RootDirectory, res);
        }

        public void WriteGood(IFileIdentity fileIdentity, string product)
        {
            var path = TransformPath(_configuration.PathFormat, true, product);


            _saveQueue.Enqueue(new SaveCandidate(fileIdentity, path));
        }

        public void WriteBad(IFileIdentity fileIdentity, string product)
        {
            var path = TransformPath(_configuration.PathFormat, false, product);

            _saveQueue.Enqueue(new SaveCandidate(fileIdentity, path));
        }

        public void Dispose()
        {
            _stop = true;
        }
    }
}