using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.IO;
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, WIN32_FIND_DATA fileData)
        {
            PreBeginResolveEventArgs args = new PreBeginResolveEventArgs()
            {
                Path = path,
                Parent = parent,
                FileData = fileData,
                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 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)
        {
            WIN32_FIND_DATA fileData = fileInfo ?? FileData.GetFileData(path);
            if (!OnPreBeginResolvePath(parent, path, fileData))
            {
                return null;
            }
            KeyValuePair[] fileSystemChildren;
            // Gather child folder and files
            if (fileData.IsDirectory)
            {
                fileSystemChildren = ConvertFileSystemEntries(Directory.GetFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly));
                bool isVirtualFolder = parent != null && parent.IsRoot;
                fileSystemChildren = FilterChildFileSystemEntries(fileSystemChildren, isVirtualFolder);
            }
            else
            {
                fileSystemChildren = new KeyValuePair[] { };
            }
            ItemResolveEventArgs args = new ItemResolveEventArgs()
            {
                Path = path,
                FileSystemChildren = fileSystemChildren,
                FileData = fileData,
                Parent = parent,
                Cancel = false
            };
            // 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);
                var folder = item as Folder;
                if (folder != null)
                {
                    // If it's a folder look for child entities
                    await AttachChildren(folder, fileSystemChildren).ConfigureAwait(false);
                }
            }
            return item;
        }
        /// 
        /// Finds child BaseItems for a given Folder
        /// 
        private async Task AttachChildren(Folder folder, KeyValuePair[] fileSystemChildren)
        {
            int count = fileSystemChildren.Length;
            Task[] tasks = new Task[count];
            for (int i = 0; i < count; i++)
            {
                var child = fileSystemChildren[i];
                tasks[i] = GetItem(child.Key, folder, child.Value);
            }
            BaseItem[] baseItemChildren = await Task.WhenAll(tasks).ConfigureAwait(false);
            // Sort them
            folder.Children = baseItemChildren.Where(i => i != null).OrderBy(f =>
            {
                return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName;
            });
        }
        /// 
        /// Transforms shortcuts into their actual paths
        /// 
        private KeyValuePair[] FilterChildFileSystemEntries(KeyValuePair[] fileSystemChildren, bool flattenShortcuts)
        {
            KeyValuePair[] returnArray = new KeyValuePair[fileSystemChildren.Length];
            List> resolvedShortcuts = new List>();
            for (int i = 0; i < fileSystemChildren.Length; i++)
            {
                KeyValuePair file = fileSystemChildren[i];
                if (file.Value.IsDirectory)
                {
                    returnArray[i] = file;
                }
                // If it's a shortcut, resolve it
                else if (Shortcut.IsShortcut(file.Key))
                {
                    string newPath = Shortcut.ResolveShortcut(file.Key);
                    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;
                            KeyValuePair[] newChildren = ConvertFileSystemEntries(Directory.GetFileSystemEntries(newPath, "*", SearchOption.TopDirectoryOnly));
                            resolvedShortcuts.AddRange(FilterChildFileSystemEntries(newChildren, false));
                        }
                        else
                        {
                            returnArray[i] = new KeyValuePair(newPath, newPathData);
                        }
                    }
                    else
                    {
                        returnArray[i] = new KeyValuePair(newPath, newPathData);
                    }
                }
                else
                {
                    returnArray[i] = file;
                }
            }
            if (resolvedShortcuts.Count > 0)
            {
                resolvedShortcuts.InsertRange(0, returnArray);
                return resolvedShortcuts.ToArray();
            }
            else
            {
                return returnArray;
            }
        }
        /// 
        /// Gets a Person
        /// 
        public async Task GetPerson(string name)
        {
            string path = Path.Combine(Kernel.Instance.ApplicationPaths.PeoplePath, name);
            return await GetImagesByNameItem(path, name).ConfigureAwait(false);
        }
        /// 
        /// Gets a Studio
        /// 
        public async Task GetStudio(string name)
        {
            string path = Path.Combine(Kernel.Instance.ApplicationPaths.StudioPath, name);
            return await GetImagesByNameItem(path, name).ConfigureAwait(false);
        }
        /// 
        /// Gets a Genre
        /// 
        public async Task GetGenre(string name)
        {
            string path = Path.Combine(Kernel.Instance.ApplicationPaths.GenrePath, name);
            return await GetImagesByNameItem(path, name).ConfigureAwait(false);
        }
        /// 
        /// Gets a Year
        /// 
        public async Task GetYear(int value)
        {
            string path = Path.Combine(Kernel.Instance.ApplicationPaths.YearPath, value.ToString());
            return await GetImagesByNameItem(path, value.ToString()).ConfigureAwait(false);
        }
        private ConcurrentDictionary ImagesByNameItemCache = new ConcurrentDictionary();
        /// 
        /// Generically retrieves an IBN item
        /// 
        private async Task 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))
            {
                T obj = await CreateImagesByNameItem(path, name).ConfigureAwait(false);
                ImagesByNameItemCache[key] = obj;
                return obj;
            }
            return ImagesByNameItemCache[key] as T;
        }
        /// 
        /// 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.GetCreationTime(path);
            item.DateModified = Directory.GetLastAccessTime(path);
            ItemResolveEventArgs args = new ItemResolveEventArgs();
            args.Path = path;
            args.FileData = FileData.GetFileData(path);
            args.FileSystemChildren = ConvertFileSystemEntries(Directory.GetFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly));
            await Kernel.Instance.ExecuteMetadataProviders(item, args).ConfigureAwait(false);
            return item;
        }
        private KeyValuePair[] ConvertFileSystemEntries(string[] files)
        {
            KeyValuePair[] items = new KeyValuePair[files.Length];
            for (int i = 0; i < files.Length; i++)
            {
                string file = files[i];
                items[i] = new KeyValuePair(file, FileData.GetFileData(file));
            }
            return items;
        }
    }
}