using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Library
{
    public class ItemController
    {
        #region PreBeginResolvePath Event
        /// 
        /// Fires when a path is about to be resolved, but before child folders and files 
        /// have been collected from the file system.
        /// This gives listeners a chance to cancel the operation and cause the path to be ignored.
        /// 
        public event EventHandler PreBeginResolvePath;
        private bool OnPreBeginResolvePath(PreBeginResolveEventArgs args)
        {
            if (PreBeginResolvePath != null)
            {
                PreBeginResolvePath(this, args);
            }
            return !args.Cancel;
        }
        #endregion
        #region BeginResolvePath Event
        /// 
        /// Fires when a path is about to be resolved, but after child folders and files 
        /// have been collected from the file system.
        /// This gives listeners a chance to cancel the operation and cause the path to be ignored.
        /// 
        public event EventHandler BeginResolvePath;
        private bool OnBeginResolvePath(ItemResolveEventArgs args)
        {
            if (BeginResolvePath != null)
            {
                BeginResolvePath(this, args);
            }
            return !args.Cancel;
        }
        #endregion
        private BaseItem ResolveItem(ItemResolveEventArgs args)
        {
            // Try first priority resolvers
            for (int i = 0; i < Kernel.Instance.EntityResolvers.Length; i++)
            {
                var item = Kernel.Instance.EntityResolvers[i].ResolvePath(args);
                if (item != null)
                {
                    return item;
                }
            }
            return null;
        }
        /// 
        /// Resolves a path into a BaseItem
        /// 
        public async Task GetItem(string path, Folder parent = null, WIN32_FIND_DATA? fileInfo = null, bool allowInternetProviders = true)
        {
            ItemResolveEventArgs args = new ItemResolveEventArgs()
            {
                FileInfo = fileInfo ?? FileData.GetFileData(path),
                Parent = parent,
                Cancel = false,
                Path = path
            };
            if (!OnPreBeginResolvePath(args))
            {
                return null;
            }
            WIN32_FIND_DATA[] fileSystemChildren;
            // Gather child folder and files
            if (args.IsDirectory)
            {
                fileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray();
                bool isVirtualFolder = parent != null && parent.IsRoot;
                fileSystemChildren = FilterChildFileSystemEntries(fileSystemChildren, isVirtualFolder);
            }
            else
            {
                fileSystemChildren = new WIN32_FIND_DATA[] { };
            }
            args.FileSystemChildren = fileSystemChildren;
            // Fire BeginResolvePath to see if anyone wants to cancel this operation
            if (!OnBeginResolvePath(args))
            {
                return null;
            }
            BaseItem item = ResolveItem(args);
            if (item != null)
            {
                await Kernel.Instance.ExecuteMetadataProviders(item, args, allowInternetProviders: allowInternetProviders).ConfigureAwait(false);
                if (item.IsFolder)
                {
                    // If it's a folder look for child entities
                    (item as Folder).Children = (await Task.WhenAll(GetChildren(item as Folder, fileSystemChildren, allowInternetProviders)).ConfigureAwait(false))
                        .Where(i => i != null).OrderBy(f =>
                        {
                            return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName;
                        });
                }
            }
            return item;
        }
        /// 
        /// Finds child BaseItems for a given Folder
        /// 
        private Task[] GetChildren(Folder folder, WIN32_FIND_DATA[] fileSystemChildren, bool allowInternetProviders)
        {
            Task[] tasks = new Task[fileSystemChildren.Length];
            for (int i = 0; i < fileSystemChildren.Length; i++)
            {
                var child = fileSystemChildren[i];
                tasks[i] = GetItem(child.Path, folder, child, allowInternetProviders: allowInternetProviders);
            }
            return tasks;
        }
        /// 
        /// Transforms shortcuts into their actual paths
        /// 
        private WIN32_FIND_DATA[] FilterChildFileSystemEntries(WIN32_FIND_DATA[] fileSystemChildren, bool flattenShortcuts)
        {
            WIN32_FIND_DATA[] returnArray = new WIN32_FIND_DATA[fileSystemChildren.Length];
            List resolvedShortcuts = new List();
            for (int i = 0; i < fileSystemChildren.Length; i++)
            {
                WIN32_FIND_DATA file = fileSystemChildren[i];
                // If it's a shortcut, resolve it
                if (Shortcut.IsShortcut(file.Path))
                {
                    string newPath = Shortcut.ResolveShortcut(file.Path);
                    WIN32_FIND_DATA newPathData = FileData.GetFileData(newPath);
                    // Find out if the shortcut is pointing to a directory or file
                    if (newPathData.IsDirectory)
                    {
                        // If we're flattening then get the shortcut's children
                        if (flattenShortcuts)
                        {
                            returnArray[i] = file;
                            WIN32_FIND_DATA[] newChildren = FileData.GetFileSystemEntries(newPath, "*").ToArray();
                            resolvedShortcuts.AddRange(FilterChildFileSystemEntries(newChildren, false));
                        }
                        else
                        {
                            returnArray[i] = newPathData;
                        }
                    }
                    else
                    {
                        returnArray[i] = newPathData;
                    }
                }
                else
                {
                    returnArray[i] = file;
                }
            }
            if (resolvedShortcuts.Count > 0)
            {
                resolvedShortcuts.InsertRange(0, returnArray);
                return resolvedShortcuts.ToArray();
            }
            else
            {
                return returnArray;
            }
        }
        /// 
        /// Gets a Person
        /// 
        public Task GetPerson(string name)
        {
            return GetImagesByNameItem(Kernel.Instance.ApplicationPaths.PeoplePath, name);
        }
        /// 
        /// Gets a Studio
        /// 
        public Task GetStudio(string name)
        {
            return GetImagesByNameItem(Kernel.Instance.ApplicationPaths.StudioPath, name);
        }
        /// 
        /// Gets a Genre
        /// 
        public Task GetGenre(string name)
        {
            return GetImagesByNameItem(Kernel.Instance.ApplicationPaths.GenrePath, name);
        }
        /// 
        /// Gets a Year
        /// 
        public Task GetYear(int value)
        {
            return GetImagesByNameItem(Kernel.Instance.ApplicationPaths.YearPath, value.ToString());
        }
        private ConcurrentDictionary ImagesByNameItemCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
        /// 
        /// Generically retrieves an IBN item
        /// 
        private Task GetImagesByNameItem(string path, string name)
            where T : BaseEntity, new()
        {
            name = FileData.GetValidFilename(name);
            string key = Path.Combine(path, name);
            // Look for it in the cache, if it's not there, create it
            if (!ImagesByNameItemCache.ContainsKey(key))
            {
                ImagesByNameItemCache[key] = CreateImagesByNameItem(path, name);
            }
            return ImagesByNameItemCache[key] as Task;
        }
        /// 
        /// Creates an IBN item based on a given path
        /// 
        private async Task CreateImagesByNameItem(string path, string name)
            where T : BaseEntity, new()
        {
            T item = new T();
            item.Name = name;
            item.Id = Kernel.GetMD5(path);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            item.DateCreated = Directory.GetCreationTimeUtc(path);
            item.DateModified = Directory.GetLastWriteTimeUtc(path);
            ItemResolveEventArgs args = new ItemResolveEventArgs();
            args.FileInfo = FileData.GetFileData(path);
            args.FileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray();
            await Kernel.Instance.ExecuteMetadataProviders(item, args).ConfigureAwait(false);
            return item;
        }
    }
}