using MediaBrowser.Common.Implementations.HttpServer;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using ServiceStack.ServiceHost;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.Api.UserLibrary
{
    /// 
    /// Class GetItems
    /// 
    [Route("/Users/{UserId}/Items", "GET")]
    public class GetItems : IReturn
    {
        /// 
        /// Gets or sets the user id.
        /// 
        /// The user id.
        public Guid UserId { get; set; }
        /// 
        /// Specify this to localize the search to a specific item or folder. Omit to use the root.
        /// 
        /// The parent id.
        public string ParentId { get; set; }
        /// 
        /// Skips over a given number of items within the results. Use for paging.
        /// 
        /// The start index.
        public int? StartIndex { get; set; }
        /// 
        /// The maximum number of items to return
        /// 
        /// The limit.
        public int? Limit { get; set; }
        /// 
        /// Whether or not to perform the query recursively
        /// 
        /// true if recursive; otherwise, false.
        public bool Recursive { get; set; }
        /// 
        /// Limit results to items containing a specific person
        /// 
        /// The person.
        public string Person { get; set; }
        /// 
        /// If the Person filter is used, this can also be used to restrict to a specific person type
        /// 
        /// The type of the person.
        public string PersonType { get; set; }
        /// 
        /// Search characters used to find items
        /// 
        /// The index by.
        public string SearchTerm { get; set; }
        /// 
        /// The dynamic, localized index function name
        /// 
        /// The index by.
        public string IndexBy { get; set; }
        /// 
        /// The dynamic, localized sort function name
        /// 
        /// The dynamic sort by.
        public string DynamicSortBy { get; set; }
        /// 
        /// What to sort the results by
        /// 
        /// The sort by.
        public string SortBy { get; set; }
        /// 
        /// The sort order to return results with
        /// 
        /// The sort order.
        public string SortOrder { get; set; }
        /// 
        /// Filters to apply to the results
        /// 
        /// The filters.
        public string Filters { get; set; }
        /// 
        /// Fields to return within the items, in addition to basic information
        /// 
        /// The fields.
        public string Fields { get; set; }
        /// 
        /// Limit results to items containing specific genres
        /// 
        /// The genres.
        public string Genres { get; set; }
        /// 
        /// Limit results to items containing specific studios
        /// 
        /// The studios.
        public string Studios { get; set; }
        /// 
        /// Gets or sets the exclude item types.
        /// 
        /// The exclude item types.
        public string ExcludeItemTypes { get; set; }
        /// 
        /// Gets or sets the include item types.
        /// 
        /// The include item types.
        public string IncludeItemTypes { get; set; }
        /// 
        /// Limit results to items containing specific years
        /// 
        /// The years.
        public string Years { get; set; }
        /// 
        /// Gets or sets the image types.
        /// 
        /// The image types.
        public string ImageTypes { get; set; }
    }
    /// 
    /// Class ItemsService
    /// 
    public class ItemsService : BaseRestService
    {
        /// 
        /// The _user manager
        /// 
        private readonly IUserManager _userManager;
        /// 
        /// The _library manager
        /// 
        private readonly ILibraryManager _libraryManager;
        
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The user manager.
        public ItemsService(IUserManager userManager, ILibraryManager libraryManager)
        {
            _userManager = userManager;
            _libraryManager = libraryManager;
        }
        /// 
        /// Gets the specified request.
        /// 
        /// The request.
        /// System.Object.
        public object Get(GetItems request)
        {
            var result = GetItems(request).Result;
            return ToOptimizedResult(result);
        }
        /// 
        /// Gets the items.
        /// 
        /// The request.
        /// Task{ItemsResult}.
        private async Task GetItems(GetItems request)
        {
            var user = _userManager.GetUserById(request.UserId);
            var items = GetItemsToSerialize(request, user);
            // Apply filters
            // Run them starting with the ones that are likely to reduce the list the most
            foreach (var filter in GetFilters(request).OrderByDescending(f => (int)f))
            {
                items = ApplyFilter(items, filter, user);
            }
            items = ApplyAdditionalFilters(request, items);
            items = ApplySearchTerm(request, items);
            items = ApplySortOrder(request, items, user);
            var itemsArray = items.ToArray();
            var pagedItems = ApplyPaging(request, itemsArray);
            var fields = GetItemFields(request).ToList();
            var dtoBuilder = new DtoBuilder(Logger);
            var returnItems = await Task.WhenAll(pagedItems.Select(i => dtoBuilder.GetDtoBaseItem(i, user, fields, _libraryManager))).ConfigureAwait(false);
            return new ItemsResult
            {
                TotalRecordCount = itemsArray.Length,
                Items = returnItems
            };
        }
        /// 
        /// Gets the items to serialize.
        /// 
        /// The request.
        /// The user.
        /// IEnumerable{BaseItem}.
        /// 
        private IEnumerable GetItemsToSerialize(GetItems request, User user)
        {
            var item = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.ParentId, _userManager, _libraryManager, user.Id);
            // Default list type = children
            if (request.Recursive)
            {
                return ((Folder)item).GetRecursiveChildren(user);
            }
            return ((Folder)item).GetChildren(user, request.IndexBy, request.DynamicSortBy, GetSortOrder(request));
        }
        /// 
        /// Applies sort order
        /// 
        /// The request.
        /// The items.
        /// The user.
        /// IEnumerable{BaseItem}.
        private IEnumerable ApplySortOrder(GetItems request, IEnumerable items, User user)
        {
            var isFirst = true;
            var descending = (GetSortOrder(request) ?? SortOrder.Ascending) == SortOrder.Descending;
            IOrderedEnumerable orderedItems = null;
            foreach (var orderBy in GetOrderBy(request).Select(o => GetComparer(o, user)))
            {
                if (isFirst)
                {
                    orderedItems = descending ? items.OrderByDescending(i => i, orderBy) : items.OrderBy(i => i, orderBy);
                }
                else
                {
                    orderedItems = descending ? orderedItems.ThenByDescending(i => i, orderBy) : orderedItems.ThenBy(i => i, orderBy);
                }
                isFirst = false;
            }
            return orderedItems ?? items;
        }
        /// 
        /// Gets the comparer.
        /// 
        /// The sort by.
        /// The user.
        /// IComparer{BaseItem}.
        /// 
        private IComparer GetComparer(ItemSortBy sortBy, User user)
        {
            switch (sortBy)
            {
                case ItemSortBy.Album:
                    return new AlbumComparer();
                case ItemSortBy.AlbumArtist:
                    return new AlbumArtistComparer();
                case ItemSortBy.Artist:
                    return new ArtistComparer();
                case ItemSortBy.Random:
                    return new RandomComparer();
                case ItemSortBy.DateCreated:
                    return new DateCreatedComparer();
                case ItemSortBy.SortName:
                    return new SortNameComparer();
                case ItemSortBy.PremiereDate:
                    return new PremiereDateComparer();
                case ItemSortBy.DatePlayed:
                    return new DatePlayedComparer { User = user };
                default:
                    throw new ArgumentException();
            }
        }
        /// 
        /// Applies filtering
        /// 
        /// The items.
        /// The filter.
        /// The user.
        /// IEnumerable{BaseItem}.
        private IEnumerable ApplyFilter(IEnumerable items, ItemFilter filter, User user)
        {
            switch (filter)
            {
                case ItemFilter.IsFavorite:
                    return items.Where(item =>
                    {
                        var userdata = item.GetUserData(user, false);
                        return userdata != null && userdata.IsFavorite;
                    });
                case ItemFilter.IsRecentlyAdded:
                    return items.Where(item => item.IsRecentlyAdded(user));
                case ItemFilter.IsResumable:
                    return items.Where(item =>
                    {
                        var userdata = item.GetUserData(user, false);
                        return userdata != null && userdata.PlaybackPositionTicks > 0;
                    });
                case ItemFilter.IsPlayed:
                    return items.Where(item =>
                    {
                        var userdata = item.GetUserData(user, false);
                        return userdata != null && userdata.PlayCount > 0;
                    });
                case ItemFilter.IsRecentlyPlayed:
                    return items.Where(item => item.IsRecentlyPlayed(user));
                case ItemFilter.IsUnplayed:
                    return items.Where(item =>
                    {
                        var userdata = item.GetUserData(user, false);
                        return userdata == null || userdata.PlayCount == 0;
                    });
                case ItemFilter.IsFolder:
                    return items.Where(item => item.IsFolder);
                case ItemFilter.IsNotFolder:
                    return items.Where(item => !item.IsFolder);
            }
            return items;
        }
        /// 
        /// Applies the additional filters.
        /// 
        /// The request.
        /// The items.
        /// IEnumerable{BaseItem}.
        private IEnumerable ApplyAdditionalFilters(GetItems request, IEnumerable items)
        {
            var imageTypes = GetImageTypes(request).ToArray();
            if (imageTypes.Length > 0)
            {
                items = items.Where(item => imageTypes.Any(imageType => HasImage(item, imageType)));
            }
            // Exclude item types
            var excludeItemTypes = request.ExcludeItemTypes;
            if (!string.IsNullOrEmpty(excludeItemTypes))
            {
                var vals = excludeItemTypes.Split(',');
                items = items.Where(f => !vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase));
            }
            var includeItemTypes = request.IncludeItemTypes;
            if (!string.IsNullOrEmpty(includeItemTypes))
            {
                var vals = includeItemTypes.Split(',');
                items = items.Where(f => vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase));
            }
            var genres = request.Genres;
            // Apply genre filter
            if (!string.IsNullOrEmpty(genres))
            {
                var vals = genres.Split(',');
                items = items.Where(f => f.Genres != null && vals.Any(v => f.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)));
            }
            var studios = request.Studios;
            // Apply studio filter
            if (!string.IsNullOrEmpty(studios))
            {
                var vals = studios.Split(',');
                items = items.Where(f => f.Studios != null && vals.Any(v => f.Studios.Contains(v, StringComparer.OrdinalIgnoreCase)));
            }
            var years = request.Years;
            // Apply year filter
            if (!string.IsNullOrEmpty(years))
            {
                var vals = years.Split(',').Select(int.Parse);
                items = items.Where(f => f.ProductionYear.HasValue && vals.Contains(f.ProductionYear.Value));
            }
            var personName = request.Person;
            // Apply person filter
            if (!string.IsNullOrEmpty(personName))
            {
                var personType = request.PersonType;
                items = !string.IsNullOrEmpty(personType)
                            ? items.Where(item => item.People != null && item.People.Any(p => p.Name.Equals(personName, StringComparison.OrdinalIgnoreCase) && p.Type.Equals(personType, StringComparison.OrdinalIgnoreCase)))
                            : items.Where(item => item.People != null && item.People.Any(p => p.Name.Equals(personName, StringComparison.OrdinalIgnoreCase)));
            }
            return items;
        }
        /// 
        /// Determines whether the specified item has image.
        /// 
        /// The item.
        /// Type of the image.
        /// true if the specified item has image; otherwise, false.
        private bool HasImage(BaseItem item, ImageType imageType)
        {
            if (imageType == ImageType.Backdrop)
            {
                return item.BackdropImagePaths != null && item.BackdropImagePaths.Count > 0;
            }
            if (imageType == ImageType.Screenshot)
            {
                return item.ScreenshotImagePaths != null && item.ScreenshotImagePaths.Count > 0;
            }
            if (imageType == ImageType.ChapterImage)
            {
                var video = item as Video;
                if (video != null)
                {
                    return video.Chapters != null && video.Chapters.Any(c => !string.IsNullOrEmpty(c.ImagePath));
                }
                return false;
            }
            return item.HasImage(imageType);
        }
        /// 
        /// Applies the search term.
        /// 
        /// The request.
        /// The items.
        /// IEnumerable{BaseItem}.
        private IEnumerable ApplySearchTerm(GetItems request, IEnumerable items)
        {
            var term = request.SearchTerm;
            if (!string.IsNullOrEmpty(term))
            {
                items = items.Where(i => i.Name.StartsWith(term, StringComparison.OrdinalIgnoreCase));
            }
            return items;
        }
        /// 
        /// Applies the paging.
        /// 
        /// The request.
        /// The items.
        /// IEnumerable{BaseItem}.
        private IEnumerable ApplyPaging(GetItems request, IEnumerable items)
        {
            // Start at
            if (request.StartIndex.HasValue)
            {
                items = items.Skip(request.StartIndex.Value);
            }
            // Return limit
            if (request.Limit.HasValue)
            {
                items = items.Take(request.Limit.Value);
            }
            return items;
        }
        /// 
        /// Gets the sort order.
        /// 
        /// The request.
        /// System.Nullable{SortOrder}.
        private SortOrder? GetSortOrder(GetItems request)
        {
            if (string.IsNullOrEmpty(request.SortOrder))
            {
                return null;
            }
            return (SortOrder)Enum.Parse(typeof(SortOrder), request.SortOrder, true);
        }
        /// 
        /// Gets the filters.
        /// 
        /// The request.
        /// IEnumerable{ItemFilter}.
        private IEnumerable GetFilters(GetItems request)
        {
            var val = request.Filters;
            if (string.IsNullOrEmpty(val))
            {
                return new ItemFilter[] { };
            }
            return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
        }
        /// 
        /// Gets the item fields.
        /// 
        /// The request.
        /// IEnumerable{ItemFields}.
        private IEnumerable GetItemFields(GetItems request)
        {
            var val = request.Fields;
            if (string.IsNullOrEmpty(val))
            {
                return new ItemFields[] { };
            }
            return val.Split(',').Select(v => (ItemFields)Enum.Parse(typeof(ItemFields), v, true));
        }
        /// 
        /// Gets the order by.
        /// 
        /// The request.
        /// IEnumerable{ItemSortBy}.
        private IEnumerable GetOrderBy(GetItems request)
        {
            var val = request.SortBy;
            if (string.IsNullOrEmpty(val))
            {
                return new ItemSortBy[] { };
            }
            return val.Split(',').Select(v => (ItemSortBy)Enum.Parse(typeof(ItemSortBy), v, true));
        }
        /// 
        /// Gets the image types.
        /// 
        /// The request.
        /// IEnumerable{ImageType}.
        private IEnumerable GetImageTypes(GetItems request)
        {
            var val = request.ImageTypes;
            if (string.IsNullOrEmpty(val))
            {
                return new ImageType[] { };
            }
            return val.Split(',').Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true));
        }
    }
    /// 
    /// Class DateCreatedComparer
    /// 
    public class DateCreatedComparer : IComparer
    {
        /// 
        /// Compares the specified x.
        /// 
        /// The x.
        /// The y.
        /// System.Int32.
        public int Compare(BaseItem x, BaseItem y)
        {
            return x.DateCreated.CompareTo(y.DateCreated);
        }
    }
    /// 
    /// Class RandomComparer
    /// 
    public class RandomComparer : IComparer
    {
        /// 
        /// Compares the specified x.
        /// 
        /// The x.
        /// The y.
        /// System.Int32.
        public int Compare(BaseItem x, BaseItem y)
        {
            return Guid.NewGuid().CompareTo(Guid.NewGuid());
        }
    }
    /// 
    /// Class SortNameComparer
    /// 
    public class SortNameComparer : IComparer
    {
        /// 
        /// Compares the specified x.
        /// 
        /// The x.
        /// The y.
        /// System.Int32.
        public int Compare(BaseItem x, BaseItem y)
        {
            return string.Compare(x.SortName, y.SortName, StringComparison.CurrentCultureIgnoreCase);
        }
    }
    /// 
    /// Class AlbumArtistComparer
    /// 
    public class AlbumArtistComparer : IComparer
    {
        /// 
        /// Compares the specified x.
        /// 
        /// The x.
        /// The y.
        /// System.Int32.
        public int Compare(BaseItem x, BaseItem y)
        {
            return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
        }
        /// 
        /// Gets the value.
        /// 
        /// The x.
        /// System.String.
        private string GetValue(BaseItem x)
        {
            var audio = x as Audio;
            return audio == null ? string.Empty : audio.AlbumArtist;
        }
    }
    /// 
    /// Class AlbumComparer
    /// 
    public class AlbumComparer : IComparer
    {
        /// 
        /// Compares the specified x.
        /// 
        /// The x.
        /// The y.
        /// System.Int32.
        public int Compare(BaseItem x, BaseItem y)
        {
            return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
        }
        /// 
        /// Gets the value.
        /// 
        /// The x.
        /// System.String.
        private string GetValue(BaseItem x)
        {
            var audio = x as Audio;
            return audio == null ? string.Empty : audio.Album;
        }
    }
    /// 
    /// Class ArtistComparer
    /// 
    public class ArtistComparer : IComparer
    {
        /// 
        /// Compares the specified x.
        /// 
        /// The x.
        /// The y.
        /// System.Int32.
        public int Compare(BaseItem x, BaseItem y)
        {
            return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
        }
        /// 
        /// Gets the value.
        /// 
        /// The x.
        /// System.String.
        private string GetValue(BaseItem x)
        {
            var audio = x as Audio;
            return audio == null ? string.Empty : audio.Artist;
        }
    }
    /// 
    /// Class PremiereDateComparer
    /// 
    public class PremiereDateComparer : IComparer
    {
        /// 
        /// Compares the specified x.
        /// 
        /// The x.
        /// The y.
        /// System.Int32.
        public int Compare(BaseItem x, BaseItem y)
        {
            return GetDate(x).CompareTo(GetDate(y));
        }
        /// 
        /// Gets the date.
        /// 
        /// The x.
        /// DateTime.
        private DateTime GetDate(BaseItem x)
        {
            if (x.PremiereDate.HasValue)
            {
                return x.PremiereDate.Value;
            }
            if (x.ProductionYear.HasValue)
            {
                return new DateTime(x.ProductionYear.Value, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            }
            return DateTime.MaxValue;
        }
    }
    /// 
    /// Class DatePlayedComparer
    /// 
    public class DatePlayedComparer : IComparer
    {
        /// 
        /// Gets or sets the user.
        /// 
        /// The user.
        public User User { get; set; }
        /// 
        /// Compares the specified x.
        /// 
        /// The x.
        /// The y.
        /// System.Int32.
        public int Compare(BaseItem x, BaseItem y)
        {
            return GetDate(x).CompareTo(GetDate(y));
        }
        /// 
        /// Gets the date.
        /// 
        /// The x.
        /// DateTime.
        private DateTime GetDate(BaseItem x)
        {
            var userdata = x.GetUserData(User, false);
            if (userdata != null && userdata.LastPlayedDate.HasValue)
            {
                return userdata.LastPlayedDate.Value;
            }
            return DateTime.MinValue;
        }
    }
}