using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
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(Folder parent, string path, FileAttributes attributes)
        {
            PreBeginResolveEventArgs args = new PreBeginResolveEventArgs()
            {
                Path = path,
                Parent = parent,
                FileAttributes = attributes,
                Cancel = false
            };
            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 async Task ResolveItem(ItemResolveEventArgs args)
        {
            // If that didn't pan out, try the slow ones
            foreach (IBaseItemResolver resolver in Kernel.Instance.EntityResolvers)
            {
                var item = await resolver.ResolvePath(args);
                if (item != null)
                {
                    return item;
                }
            }
            return null;
        }
        /// 
        /// Resolves a path into a BaseItem
        /// 
        public async Task GetItem(Folder parent, string path)
        {
            return await GetItemInternal(parent, path, File.GetAttributes(path));
        }
        /// 
        /// Resolves a path into a BaseItem
        /// 
        private async Task GetItemInternal(Folder parent, string path, FileAttributes attributes)
        {
            if (!OnPreBeginResolvePath(parent, path, attributes))
            {
                return null;
            }
            IEnumerable> fileSystemChildren;
            // Gather child folder and files
            if (attributes.HasFlag(FileAttributes.Directory))
            {
                fileSystemChildren = Directory.GetFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly).Select(f => new KeyValuePair(f, File.GetAttributes(f)));
                bool isVirtualFolder = parent != null && parent.IsRoot;
                fileSystemChildren = FilterChildFileSystemEntries(fileSystemChildren, isVirtualFolder);
            }
            else
            {
                fileSystemChildren = new KeyValuePair[] { };
            }
            ItemResolveEventArgs args = new ItemResolveEventArgs()
            {
                Path = path,
                FileAttributes = attributes,
                FileSystemChildren = fileSystemChildren,
                Parent = parent,
                Cancel = false
            };
            // Fire BeginResolvePath to see if anyone wants to cancel this operation
            if (!OnBeginResolvePath(args))
            {
                return null;
            }
            BaseItem item = await ResolveItem(args);
            var folder = item as Folder;
            if (folder != null)
            {
                // If it's a folder look for child entities
                await AttachChildren(folder, fileSystemChildren);
            }
            return item;
        }
        /// 
        /// Finds child BaseItems for a given Folder
        /// 
        private async Task AttachChildren(Folder folder, IEnumerable> fileSystemChildren)
        {
            KeyValuePair[] fileSystemChildrenArray = fileSystemChildren.ToArray();
            int count = fileSystemChildrenArray.Length;
            Task[] tasks = new Task[count];
            for (int i = 0; i < count; i++)
            {
                var child = fileSystemChildrenArray[i];
                tasks[i] = GetItemInternal(folder, child.Key, child.Value);
            }
            BaseItem[] baseItemChildren = await Task.WhenAll(tasks);
            
            // Sort them
            folder.Children = baseItemChildren.Where(i => i != null).OrderBy(f =>
            {
                return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName;
            }).ToArray();
        }
        /// 
        /// Transforms shortcuts into their actual paths
        /// 
        private List> FilterChildFileSystemEntries(IEnumerable> fileSystemChildren, bool flattenShortcuts)
        {
            List> returnFiles = new List>();
            // Loop through each file
            foreach (KeyValuePair file in fileSystemChildren)
            {
                // Folders
                if (file.Value.HasFlag(FileAttributes.Directory))
                {
                    returnFiles.Add(file);
                }
                // If it's a shortcut, resolve it
                else if (Shortcut.IsShortcut(file.Key))
                {
                    string newPath = Shortcut.ResolveShortcut(file.Key);
                    FileAttributes newPathAttributes = File.GetAttributes(newPath);
                    // Find out if the shortcut is pointing to a directory or file
                    if (newPathAttributes.HasFlag(FileAttributes.Directory))
                    {
                        // If we're flattening then get the shortcut's children
                        if (flattenShortcuts)
                        {
                            IEnumerable> newChildren = Directory.GetFileSystemEntries(newPath, "*", SearchOption.TopDirectoryOnly).Select(f => new KeyValuePair(f, File.GetAttributes(f)));
                            returnFiles.AddRange(FilterChildFileSystemEntries(newChildren, false));
                        }
                        else
                        {
                            returnFiles.Add(new KeyValuePair(newPath, newPathAttributes));
                        }
                    }
                    else
                    {
                        returnFiles.Add(new KeyValuePair(newPath, newPathAttributes));
                    }
                }
                else
                {
                    returnFiles.Add(file);
                }
            }
            return returnFiles;
        }
        /// 
        /// Gets a Person
        /// 
        public Person GetPerson(string name)
        {
            string path = Path.Combine(Kernel.Instance.ApplicationPaths.PeoplePath, name);
            return GetImagesByNameItem(path, name);
        }
        /// 
        /// Gets a Studio
        /// 
        public Studio GetStudio(string name)
        {
            string path = Path.Combine(Kernel.Instance.ApplicationPaths.StudioPath, name);
            return GetImagesByNameItem(path, name);
        }
        /// 
        /// Gets a Genre
        /// 
        public Genre GetGenre(string name)
        {
            string path = Path.Combine(Kernel.Instance.ApplicationPaths.GenrePath, name);
            return GetImagesByNameItem(path, name);
        }
        /// 
        /// Gets a Year
        /// 
        public Year GetYear(int value)
        {
            string path = Path.Combine(Kernel.Instance.ApplicationPaths.YearPath, value.ToString());
            return GetImagesByNameItem(path, value.ToString());
        }
        private Dictionary ImagesByNameItemCache = new Dictionary();
        /// 
        /// Generically retrieves an IBN item
        /// 
        private T GetImagesByNameItem(string path, string name)
            where T : BaseEntity, new()
        {
            string key = path.ToLower();
            // 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 T;
        }
        /// 
        /// Creates an IBN item based on a given path
        /// 
        private T 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))
            {
                item.DateCreated = Directory.GetCreationTime(path);
                item.DateModified = Directory.GetLastAccessTime(path);
                if (File.Exists(Path.Combine(path, "folder.jpg")))
                {
                    item.PrimaryImagePath = Path.Combine(path, "folder.jpg");
                }
                else if (File.Exists(Path.Combine(path, "folder.png")))
                {
                    item.PrimaryImagePath = Path.Combine(path, "folder.png");
                }
            }
            else
            {
                DateTime now = DateTime.Now;
                item.DateCreated = now;
                item.DateModified = now;
            }
            return item;
        }
    }
}