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

using System.IO;

using BLL.Base;

using Model.UnitsOfWork;
using Model.Entities.Files.FS_Entities;
using Model.Entities.Files;

namespace BLL.Services.FS
{
    /// <summary>
    /// Сервис выполняет сканирование файловой системы
    /// </summary>
    public class ScanServices : BaseServices
    {
        public ScanServices(UOW uOW) : base(uOW) { }


        ///// <summary>
        ///// Для паралельной работы для каждого параллельного исполнения 
        ///// нужен свой контекст и uow
        ///// С точки зрения зависимостей очень кривая реализация
        ///// </summary>
        ///// <param name="IsParalle"></param>
        ///// <returns></returns>
        //private UOW GetUnit(bool IsParalle)
        //{
        //    return !IsParalle ? UOW : new UOW();
        //}



        /// <summary>
        /// Запускает полное сконирование базы от коренных папок
        /// Для корекнных папок в случае отцутствия создается папка
        /// </summary>
        /// <param name="IsParalle"></param>
        /// <returns></returns>
        public async Task ScanAllDirs()//bool IsParalle = false)
        {
            await Task.Run(async () =>
            {
                await RecursScanDirectoryAsync(UOW.Repo_SRootDirectory.All.ToArray(), true);//, IsParalle);
            });
        }



        public async Task RecursScanDirectoryAsync(IEnumerable<SDirectory> directorys, bool Recursive = true)//, bool IsParalle = false)
        {
            await Task.Run(async () =>
            {
                //Не параллельно
                //if (!IsParalle)
                //{
                foreach (var elem in directorys)
                    await RecursScanDirectoryAsync(elem, true);//, IsParalle);
                //}
                ////Параллельно
                //else
                //{
                //    var tasks = new List<Task>(directorys.Count());

                //    foreach (var elem in directorys)
                //        tasks.Add(RecursScanDirectoryAsync(elem, true, IsParalle));

                //    await Task.WhenAll(tasks);
                //}
            });
        }


        /// <summary>
        /// Сравнивает содержимое файловой системы и базы
        /// Приводя их в единый вид по правилу:
        /// 1) Если есть в базе, но нет в ФС, то удалить из базы
        /// 2) Если есть в ФС, но нет в базе, то добавить в базу
        /// </summary>
        /// <param name="directory"></param>
        /// <param name="Recursive"></param>
        /// <param name="IsParalle"></param>
        /// <returns></returns>
        public async Task RecursScanDirectoryAsync(SDirectory directory, bool Recursive = true)//, bool IsParalle = false)
        {
            await Task.Run(async () =>
            {
                //var uow = GetUnit(IsParalle);

                //Если папка коренная то проверить наличие и если нет, то создать
                if (directory.IsRoot)
                {
                    if (Directory.Exists(directory.PhysicalPath))
                        Directory.CreateDirectory(directory.PhysicalPath);
                }

                //Данные о папке из базы
                var db = new
                {
                    files = directory.Files.ToList(),
                    dirs = directory.Directories.ToList(),
                    upload = directory.UploadFiles.ToList(),
                };

                //Данные о папке из файловой системы
                var fs = new
                {
                    files = new LinkedList<string>(
                        Directory.GetFiles(directory.PhysicalPath).
                        Select(e => Path.GetFileName(e))
                        ),
                    dirs = new LinkedList<string>(
                        Directory.GetDirectories(directory.PhysicalPath).
                        Select(e => Path.GetFileName(e))
                        )
                };


                foreach (var elem in db.files)
                {
                    var file = fs.files.FirstOrDefault(e => e == elem.Name);

                    //Если файл из базы не найден в фс
                    if (file == null)
                        UOW.Repo_SFile.DeleteInList(elem);
                    //Если файл найден, то удаляем из списка файловой системы
                    else
                    {
                        //Проверка что размеры файла совпадают                       
                        var size = elem.Info.Length;
                        if (elem.Size != size)
                        {
                            elem._Size = size;
                            UOW.Repo_SFile.Update(elem);
                        }

                        fs.files.Remove(elem.Name);
                    }
                }

                //Если в файловой системе есть файлы, не зафиксированные в базе
                if (fs.files.Count != 0)
                {
                    var upload_name = db.upload.Select(e => e.Name);

                    foreach (var elem in fs.files)
                    {
                        //Файл находится в стадии загрузки
                        if (upload_name.Contains(elem))
                            continue;

                        //Файл вносится в базу
                        FileInfo file = new FileInfo(Path.Combine(directory.PhysicalPath, elem));
                        db.files.Add(UOW.Repo_SFile.Create(new SFile(directory, file.Name, file.Length, null)));
                    }
                }


                foreach (var elem in db.dirs)
                {
                    //Если папка из базы не найден в фс
                    if (!fs.dirs.Contains(elem.Name))
                    {
                        UOW.Repo_SDirectory.DeleteInList(elem);
                    }
                    //Если папка найден, то удаляем из списка файловой системы
                    else
                        fs.dirs.Remove(elem.Name);
                }

                //Если в файловой системе есть папки, не зафиксированные в базе
                if (fs.dirs.Count != 0)
                {
                    foreach (var elem in fs.dirs)
                    {
                        db.dirs.Add(UOW.Repo_SDirectory.Create(new SDirectory(directory, elem, null)));
                    }
                }

                fs.files.Clear();
                fs.dirs.Clear();

                db.files.Clear();
                db.upload.Clear();

                //Рекурсивно вызвать сканирование для подпапок
                if (Recursive)
                    await RecursScanDirectoryAsync(db.dirs, true);//, IsParalle);

                db.dirs.Clear();
            });
        }


    }
}
