using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Common.Events;
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
        #region Item Events
        /// 
        /// Called when an item is being created.
        /// This should be used to fill item values, such as metadata
        /// 
        public event EventHandler> ItemCreating;
        /// 
        /// Called when an item has been created.
        /// This should be used to process or modify item values.
        /// 
        public event EventHandler> ItemCreated;
        #endregion
        /// 
        /// Called when an item has been created
        /// 
        private void OnItemCreated(BaseItem item, Folder parent)
        {
            GenericItemEventArgs args = new GenericItemEventArgs { Item = item };
            if (ItemCreating != null)
            {
                ItemCreating(this, args);
            }
            if (ItemCreated != null)
            {
                ItemCreated(this, args);
            }
        }
        private void FireCreateEventsRecursive(Folder folder, Folder parent)
        {
            OnItemCreated(folder, parent);
            int count = folder.Children.Length;
            Parallel.For(0, count, i =>
            {
                BaseItem item = folder.Children[i];
                Folder childFolder = item as Folder;
                if (childFolder != null)
                {
                    FireCreateEventsRecursive(childFolder, folder);
                }
                else
                {
                    OnItemCreated(item, folder);
                }
            });
        }
        private BaseItem ResolveItem(ItemResolveEventArgs args)
        {
            // If that didn't pan out, try the slow ones
            foreach (IBaseItemResolver resolver in Kernel.Instance.EntityResolvers)
            {
                var item = resolver.ResolvePath(args);
                if (item != null)
                {
                    return item;
                }
            }
            return null;
        }
        /// 
        /// Resolves a path into a BaseItem
        /// 
        public BaseItem GetItem(string path)
        {
            return GetItem(null, path);
        }
        /// 
        /// Resolves a path into a BaseItem
        /// 
        public BaseItem GetItem(Folder parent, string path)
        {
            BaseItem item = GetItemInternal(parent, path, File.GetAttributes(path));
            if (item != null)
            {
                var folder = item as Folder;
                if (folder != null)
                {
                    FireCreateEventsRecursive(folder, parent);
                }
                else
                {
                    OnItemCreated(item, parent);
                }
            }
            return item;
        }
        /// 
        /// Resolves a path into a BaseItem
        /// 
        private BaseItem 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 = ResolveItem(args);
            var folder = item as Folder;
            if (folder != null)
            {
                // If it's a folder look for child entities
                AttachChildren(folder, fileSystemChildren);
            }
            return item;
        }
        /// 
        /// Finds child BaseItems for a given Folder
        /// 
        private void AttachChildren(Folder folder, IEnumerable> fileSystemChildren)
        {
            List baseItemChildren = new List();
            int count = fileSystemChildren.Count();
            // Resolve the child folder paths into entities
            Parallel.For(0, count, i =>
            {
                KeyValuePair child = fileSystemChildren.ElementAt(i);
                BaseItem item = GetItemInternal(folder, child.Key, child.Value);
                if (item != null)
                {
                    lock (baseItemChildren)
                    {
                        baseItemChildren.Add(item);
                    }
                }
            });
            // Sort them
            folder.Children = baseItemChildren.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;
        }
        public Person GetPerson(string name)
        {
            // not yet implemented
            return null;
        }
        public Studio GetStudio(string name)
        {
            // not yet implemented
            return null;
        }
        public Genre GetGenre(string name)
        {
            // not yet implemented
            return null;
        }
        public Year GetYear(int value)
        {
            // not yet implemented
            return null;
        }
    }
}