using System.Globalization;
using System.IO;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using ServiceStack.ServiceHost;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Api.UserLibrary
{
    /// 
    /// Class GetItems
    /// 
    [Route("/Users/{UserId}/Items", "GET")]
    [Api(Description = "Gets items based on a query.")]
    public class GetItems : BaseItemsRequest, IReturn
    {
        /// 
        /// Gets or sets the user id.
        /// 
        /// The user id.
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
        public Guid UserId { get; set; }
        /// 
        /// Limit results to items containing a specific person
        /// 
        /// The person.
        [ApiMember(Name = "Person", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        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.
        [ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public string PersonTypes { get; set; }
        /// 
        /// Search characters used to find items
        /// 
        /// The index by.
        [ApiMember(Name = "SearchTerm", Description = "Optional. If specified, results will be filtered based on a search term.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public string SearchTerm { get; set; }
        /// 
        /// The dynamic, localized index function name
        /// 
        /// The index by.
        public string IndexBy { get; set; }
        /// 
        /// Limit results to items containing specific genres
        /// 
        /// The genres.
        [ApiMember(Name = "Genres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string Genres { get; set; }
        [ApiMember(Name = "AllGenres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string AllGenres { get; set; }
        /// 
        /// Limit results to items containing specific studios
        /// 
        /// The studios.
        [ApiMember(Name = "Studios", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string Studios { get; set; }
        /// 
        /// Gets or sets the studios.
        /// 
        /// The studios.
        [ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string Artists { get; set; }
        [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string Albums { get; set; }
        /// 
        /// Limit results to items containing specific years
        /// 
        /// The years.
        [ApiMember(Name = "Years", Description = "Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string Years { get; set; }
        /// 
        /// Gets or sets the item ids.
        /// 
        /// The item ids.
        [ApiMember(Name = "Ids", Description = "Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string Ids { get; set; }
        /// 
        /// Gets or sets the video types.
        /// 
        /// The video types.
        [ApiMember(Name = "VideoTypes", Description = "Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string VideoTypes { get; set; }
        /// 
        /// Gets or sets the video formats.
        /// 
        /// The video formats.
        [ApiMember(Name = "Is3D", Description = "Optional filter by items that are 3D, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
        public bool? Is3D { get; set; }
        /// 
        /// Gets or sets the series status.
        /// 
        /// The series status.
        [ApiMember(Name = "SeriesStatus", Description = "Optional filter by Series Status. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string SeriesStatus { get; set; }
        [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public string NameStartsWithOrGreater { get; set; }
        [ApiMember(Name = "AlbumArtistStartsWithOrGreater", Description = "Optional filter by items whose album artist is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public string AlbumArtistStartsWithOrGreater { get; set; }
        /// 
        /// Gets or sets the air days.
        /// 
        /// The air days.
        [ApiMember(Name = "AirDays", Description = "Optional filter by Series Air Days. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string AirDays { get; set; }
        /// 
        /// Gets or sets the min offical rating.
        /// 
        /// The min offical rating.
        [ApiMember(Name = "MinOfficialRating", Description = "Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public string MinOfficialRating { get; set; }
        /// 
        /// Gets or sets the max offical rating.
        /// 
        /// The max offical rating.
        [ApiMember(Name = "MaxOfficialRating", Description = "Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public string MaxOfficialRating { get; set; }
        [ApiMember(Name = "HasThemeSong", Description = "Optional filter by items with theme songs.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public bool? HasThemeSong { get; set; }
        [ApiMember(Name = "HasThemeVideo", Description = "Optional filter by items with theme videos.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public bool? HasThemeVideo { get; set; }
        [ApiMember(Name = "HasSubtitles", Description = "Optional filter by items with subtitles.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public bool? HasSubtitles { get; set; }
        [ApiMember(Name = "HasSpecialFeature", Description = "Optional filter by items with special features.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public bool? HasSpecialFeature { get; set; }
        [ApiMember(Name = "HasTrailer", Description = "Optional filter by items with trailers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public bool? HasTrailer { get; set; }
        [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
        public string AdjacentTo { get; set; }
        [ApiMember(Name = "MinIndexNumber", Description = "Optional filter by minimum index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
        public int? MinIndexNumber { get; set; }
        [ApiMember(Name = "MinPlayers", Description = "Optional filter by minimum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
        public int? MinPlayers { get; set; }
        [ApiMember(Name = "MaxPlayers", Description = "Optional filter by maximum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
        public int? MaxPlayers { get; set; }
        [ApiMember(Name = "ParentIndexNumber", Description = "Optional filter by parent index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
        public int? ParentIndexNumber { get; set; }
        [ApiMember(Name = "HasParentalRating", Description = "Optional filter by items that have or do not have a parental rating", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
        public bool? HasParentalRating { get; set; }
        [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
        public bool? IsHD { get; set; }
        [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string LocationTypes { get; set; }
        [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
        public string ExcludeLocationTypes { get; set; }
        public bool IncludeIndexContainers { get; set; }
        [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
        public bool? IsMissing { get; set; }
        [ApiMember(Name = "IsUnaired", Description = "Optional filter by items that are unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
        public bool? IsUnaired { get; set; }
        [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
        public bool? IsVirtualUnaired { get; set; }
        [ApiMember(Name = "MinCommunityRating", Description = "Optional filter by minimum community rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
        public double? MinCommunityRating { get; set; }
        [ApiMember(Name = "MinCriticRating", Description = "Optional filter by minimum critic rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
        public double? MinCriticRating { get; set; }
        [ApiMember(Name = "AiredDuringSeason", Description = "Gets all episodes that aired during a season, including specials.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
        public int? AiredDuringSeason { get; set; }
        [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
        public string MinPremiereDate { get; set; }
        [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
        public string MaxPremiereDate { get; set; }
        [ApiMember(Name = "HasOverview", Description = "Optional filter by items that have an overview or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
        public bool? HasOverview { get; set; }
        [ApiMember(Name = "HasImdbId", Description = "Optional filter by items that have an imdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
        public bool? HasImdbId { get; set; }
        [ApiMember(Name = "HasTmdbId", Description = "Optional filter by items that have a tmdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
        public bool? HasTmdbId { get; set; }
        [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
        public bool? HasTvdbId { get; set; }
        [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
        public bool? IsYearMismatched { get; set; }
    }
    /// 
    /// Class ItemsService
    /// 
    public class ItemsService : BaseApiService
    {
        /// 
        /// The _user manager
        /// 
        private readonly IUserManager _userManager;
        private readonly IUserDataManager _userDataRepository;
        /// 
        /// The _library manager
        /// 
        private readonly ILibraryManager _libraryManager;
        private readonly ILibrarySearchEngine _searchEngine;
        private readonly ILocalizationManager _localization;
        private readonly IDtoService _dtoService;
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The user manager.
        /// The library manager.
        /// The search engine.
        /// The user data repository.
        public ItemsService(IUserManager userManager, ILibraryManager libraryManager, ILibrarySearchEngine searchEngine, IUserDataManager userDataRepository, ILocalizationManager localization, IDtoService dtoService)
        {
            _userManager = userManager;
            _libraryManager = libraryManager;
            _searchEngine = searchEngine;
            _userDataRepository = userDataRepository;
            _localization = localization;
            _dtoService = dtoService;
        }
        /// 
        /// Gets the specified request.
        /// 
        /// The request.
        /// System.Object.
        public object Get(GetItems request)
        {
            var result = GetItems(request);
            return ToOptimizedResult(result);
        }
        /// 
        /// Gets the items.
        /// 
        /// The request.
        /// Task{ItemsResult}.
        private ItemsResult GetItems(GetItems request)
        {
            var user = _userManager.GetUserById(request.UserId);
            var items = GetItemsToSerialize(request, user);
            items = items.AsParallel();
            items = ApplyAdditionalFilters(request, items, user);
            // Apply filters
            // Run them starting with the ones that are likely to reduce the list the most
            foreach (var filter in request.GetFilters().OrderByDescending(f => (int)f))
            {
                items = ApplyFilter(items, filter, user, _userDataRepository);
            }
            items = FilterVirtualEpisodes(request, items, user);
            items = items.AsEnumerable();
            items = ApplySearchTerm(request, items);
            items = ApplySortOrder(request, items, user, _libraryManager);
            // This must be the last filter
            if (!string.IsNullOrEmpty(request.AdjacentTo))
            {
                items = FilterForAdjacency(items, request.AdjacentTo);
            }
            var itemsArray = items.ToList();
            var pagedItems = ApplyPaging(request, itemsArray);
            var fields = request.GetItemFields().ToList();
            var returnItems = pagedItems.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray();
            return new ItemsResult
            {
                TotalRecordCount = itemsArray.Count,
                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 : _dtoService.GetItemByDtoId(request.ParentId, user.Id);
            // Default list type = children
            IEnumerable items;
            if (!string.IsNullOrEmpty(request.Ids))
            {
                var idList = request.Ids.Split(',').ToList();
                items = idList.Select(i => _dtoService.GetItemByDtoId(i, user.Id));
            }
            else if (request.Recursive)
            {
                items = ((Folder)item).GetRecursiveChildren(user);
            }
            else
            {
                items = ((Folder)item).GetChildren(user, true, request.IndexBy);
            }
            if (request.IncludeIndexContainers)
            {
                var list = items.ToList();
                var containers = list.Select(i => i.IndexContainer)
                    .Where(i => i != null);
                list.AddRange(containers);
                return list.Distinct();
            }
            return items;
        }
        /// 
        /// Applies sort order
        /// 
        /// The request.
        /// The items.
        /// The user.
        /// The library manager.
        /// IEnumerable{BaseItem}.
        internal static IEnumerable ApplySortOrder(BaseItemsRequest request, IEnumerable items, User user, ILibraryManager libraryManager)
        {
            var orderBy = request.GetOrderBy().ToList();
            return orderBy.Count == 0 ? items : libraryManager.Sort(items, user, orderBy, request.SortOrder ?? SortOrder.Ascending);
        }
        /// 
        /// Applies filtering
        /// 
        /// The items.
        /// The filter.
        /// The user.
        /// The repository.
        /// IEnumerable{BaseItem}.
        internal static IEnumerable ApplyFilter(IEnumerable items, ItemFilter filter, User user, IUserDataManager repository)
        {
            switch (filter)
            {
                case ItemFilter.IsFavoriteOrLikes:
                    return items.Where(item =>
                    {
                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
                        if (userdata == null)
                        {
                            return false;
                        }
                        var likes = userdata.Likes ?? false;
                        var favorite = userdata.IsFavorite;
                        return likes || favorite;
                    });
                case ItemFilter.Likes:
                    return items.Where(item =>
                    {
                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
                        return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
                    });
                case ItemFilter.Dislikes:
                    return items.Where(item =>
                    {
                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
                        return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
                    });
                case ItemFilter.IsFavorite:
                    return items.Where(item =>
                    {
                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
                        return userdata != null && userdata.IsFavorite;
                    });
                case ItemFilter.IsRecentlyAdded:
                    return items.Where(item => item.IsRecentlyAdded());
                case ItemFilter.IsResumable:
                    return items.Where(item =>
                    {
                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
                        return userdata != null && userdata.PlaybackPositionTicks > 0;
                    });
                case ItemFilter.IsPlayed:
                    return items.Where(item =>
                    {
                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
                        return userdata != null && userdata.Played;
                    });
                case ItemFilter.IsUnplayed:
                    return items.Where(item =>
                    {
                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
                        return userdata == null || !userdata.Played;
                    });
                case ItemFilter.IsFolder:
                    return items.Where(item => item.IsFolder);
                case ItemFilter.IsNotFolder:
                    return items.Where(item => !item.IsFolder);
            }
            return items;
        }
        private IEnumerable FilterVirtualEpisodes(GetItems request, IEnumerable items, User user)
        {
            items = FilterVirtualSeasons(request, items, user);
            if (request.IsMissing.HasValue)
            {
                var val = request.IsMissing.Value;
                items = items.Where(i =>
                {
                    var e = i as Episode;
                    if (e != null)
                    {
                        return e.IsMissingEpisode == val;
                    }
                    return true;
                });
            }
            if (request.IsUnaired.HasValue)
            {
                var val = request.IsUnaired.Value;
                items = items.Where(i =>
                {
                    var e = i as Episode;
                    if (e != null)
                    {
                        return e.IsUnaired == val;
                    }
                    return true;
                });
            }
            if (request.IsVirtualUnaired.HasValue)
            {
                var val = request.IsVirtualUnaired.Value;
                items = items.Where(i =>
                {
                    var e = i as Episode;
                    if (e != null)
                    {
                        return e.IsVirtualUnaired == val;
                    }
                    return true;
                });
            }
            return items;
        }
        private IEnumerable FilterVirtualSeasons(GetItems request, IEnumerable items, User user)
        {
            if (request.IsMissing.HasValue && request.IsVirtualUnaired.HasValue)
            {
                var isMissing = request.IsMissing.Value;
                var isVirtualUnaired = request.IsVirtualUnaired.Value;
                if (!isMissing && !isVirtualUnaired)
                {
                    return items.Where(i =>
                    {
                        var e = i as Season;
                        if (e != null)
                        {
                            return !e.IsMissingOrVirtualUnaired;
                        }
                        return true;
                    });
                }
            }
            if (request.IsMissing.HasValue)
            {
                var val = request.IsMissing.Value;
                items = items.Where(i =>
                {
                    var e = i as Season;
                    if (e != null)
                    {
                        return e.IsMissingSeason == val;
                    }
                    return true;
                });
            }
            if (request.IsUnaired.HasValue)
            {
                var val = request.IsUnaired.Value;
                items = items.Where(i =>
                {
                    var e = i as Season;
                    if (e != null)
                    {
                        return e.IsUnaired == val;
                    }
                    return true;
                });
            }
            if (request.IsVirtualUnaired.HasValue)
            {
                var val = request.IsVirtualUnaired.Value;
                items = items.Where(i =>
                {
                    var e = i as Season;
                    if (e != null)
                    {
                        return e.IsVirtualUnaired == val;
                    }
                    return true;
                });
            }
            return items;
        }
        /// 
        /// Applies the additional filters.
        /// 
        /// The request.
        /// The items.
        /// IEnumerable{BaseItem}.
        private IEnumerable ApplyAdditionalFilters(GetItems request, IEnumerable items, User user)
        {
            if (request.MinCommunityRating.HasValue)
            {
                var val = request.MinCommunityRating.Value;
                items = items.Where(i => i.CommunityRating.HasValue && i.CommunityRating >= val);
            }
            if (request.MinCriticRating.HasValue)
            {
                var val = request.MinCriticRating.Value;
                items = items.Where(i =>
                {
                    var hasCriticRating = i as IHasCriticRating;
                    if (hasCriticRating != null)
                    {
                        return hasCriticRating.CriticRating.HasValue && hasCriticRating.CriticRating >= val;
                    }
                    return false;
                });
            }
            // Artists
            if (!string.IsNullOrEmpty(request.Artists))
            {
                var artists = request.Artists.Split('|');
                items = items.Where(i =>
                {
                    var audio = i as IHasArtist;
                    return audio != null && artists.Any(audio.HasArtist);
                });
            }
            // Albums
            if (!string.IsNullOrEmpty(request.Albums))
            {
                var albums = request.Albums.Split('|');
                items = items.Where(i =>
                {
                    var audio = i as Audio;
                    if (audio != null)
                    {
                        return albums.Any(a => string.Equals(a, audio.Album, StringComparison.OrdinalIgnoreCase));
                    }
                    var album = i as MusicAlbum;
                    if (album != null)
                    {
                        return albums.Any(a => string.Equals(a, album.Name, StringComparison.OrdinalIgnoreCase));
                    }
                    var musicVideo = i as MusicVideo;
                    if (musicVideo != null)
                    {
                        return albums.Any(a => string.Equals(a, musicVideo.Album, StringComparison.OrdinalIgnoreCase));
                    }
                    return false;
                });
            }
            // Min index number
            if (request.MinIndexNumber.HasValue)
            {
                items = items.Where(i => i.IndexNumber.HasValue && i.IndexNumber.Value >= request.MinIndexNumber.Value);
            }
            // Min official rating
            if (!string.IsNullOrEmpty(request.MinOfficialRating))
            {
                var level = _localization.GetRatingLevel(request.MinOfficialRating);
                if (level.HasValue)
                {
                    items = items.Where(i =>
                    {
                        var rating = i.CustomRating;
                        if (string.IsNullOrEmpty(rating))
                        {
                            rating = i.OfficialRating;
                        }
                        if (string.IsNullOrEmpty(rating))
                        {
                            return true;
                        }
                        var itemLevel = _localization.GetRatingLevel(rating);
                        return !itemLevel.HasValue || itemLevel.Value >= level.Value;
                    });
                }
            }
            // Max official rating
            if (!string.IsNullOrEmpty(request.MaxOfficialRating))
            {
                var level = _localization.GetRatingLevel(request.MaxOfficialRating);
                if (level.HasValue)
                {
                    items = items.Where(i =>
                    {
                        var rating = i.CustomRating;
                        if (string.IsNullOrEmpty(rating))
                        {
                            rating = i.OfficialRating;
                        }
                        if (string.IsNullOrEmpty(rating))
                        {
                            return true;
                        }
                        var itemLevel = _localization.GetRatingLevel(rating);
                        return !itemLevel.HasValue || itemLevel.Value <= level.Value;
                    });
                }
            }
            // Exclude item types
            if (!string.IsNullOrEmpty(request.ExcludeItemTypes))
            {
                var vals = request.ExcludeItemTypes.Split(',');
                items = items.Where(f => !vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase));
            }
            // Include item types
            if (!string.IsNullOrEmpty(request.IncludeItemTypes))
            {
                var vals = request.IncludeItemTypes.Split(',');
                items = items.Where(f => vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase));
            }
            // LocationTypes
            if (!string.IsNullOrEmpty(request.LocationTypes))
            {
                var vals = request.LocationTypes.Split(',');
                items = items.Where(f => vals.Contains(f.LocationType.ToString(), StringComparer.OrdinalIgnoreCase));
            }
            // ExcludeLocationTypes
            if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
            {
                var vals = request.ExcludeLocationTypes.Split(',');
                items = items.Where(f => !vals.Contains(f.LocationType.ToString(), StringComparer.OrdinalIgnoreCase));
            }
            if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater))
            {
                items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1);
            }
            if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater))
            {
                items = items.OfType()
                    .Where(i => string.Compare(request.AlbumArtistStartsWithOrGreater, i.AlbumArtist, StringComparison.CurrentCultureIgnoreCase) < 1)
                    .Cast();
            }
            // Filter by Series Status
            if (!string.IsNullOrEmpty(request.SeriesStatus))
            {
                var vals = request.SeriesStatus.Split(',');
                items = items.OfType().Where(i => i.Status.HasValue && vals.Contains(i.Status.Value.ToString(), StringComparer.OrdinalIgnoreCase));
            }
            // Filter by Series AirDays
            if (!string.IsNullOrEmpty(request.AirDays))
            {
                var days = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true));
                items = items.OfType().Where(i => i.AirDays != null && days.Any(d => i.AirDays.Contains(d)));
            }
            // Filter by Video3DFormat
            if (request.Is3D.HasValue)
            {
                items = items.OfType