using MediaBrowser.Common.Extensions;
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.Persistence;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MoreLinq;
namespace MediaBrowser.Controller.Dto
{
    /// 
    /// Generates DTO's from domain entities
    /// 
    public class DtoBuilder
    {
        /// 
        /// The index folder delimeter
        /// 
        const string IndexFolderDelimeter = "-index-";
        private readonly ILogger _logger;
        private readonly ILibraryManager _libraryManager;
        private readonly IUserDataRepository _userDataRepository;
        private readonly IItemRepository _itemRepo;
        public DtoBuilder(ILogger logger, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepo)
        {
            _logger = logger;
            _libraryManager = libraryManager;
            _userDataRepository = userDataRepository;
            _itemRepo = itemRepo;
        }
        /// 
        /// Converts a BaseItem to a DTOBaseItem
        /// 
        /// The item.
        /// The fields.
        /// The user.
        /// The owner.
        /// Task{DtoBaseItem}.
        /// item
        public async Task GetBaseItemDto(BaseItem item, List fields, User user = null, BaseItem owner = null)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            if (fields == null)
            {
                throw new ArgumentNullException("fields");
            }
            var dto = new BaseItemDto();
            var tasks = new List();
            if (fields.Contains(ItemFields.Studios))
            {
                tasks.Add(AttachStudios(dto, item));
            }
            if (fields.Contains(ItemFields.People))
            {
                tasks.Add(AttachPeople(dto, item));
            }
            if (fields.Contains(ItemFields.PrimaryImageAspectRatio))
            {
                try
                {
                    await AttachPrimaryImageAspectRatio(dto, item, _logger).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
                    _logger.ErrorException("Error generating PrimaryImageAspectRatio for {0}", ex, item.Name);
                }
            }
            if (user != null)
            {
                AttachUserSpecificInfo(dto, item, user, fields);
            }
            AttachBasicFields(dto, item, owner, fields);
            if (fields.Contains(ItemFields.SoundtrackIds))
            {
                var series = item as Series;
                if (series != null)
                {
                    AttachSoundtrackIds(dto, series, user);
                }
                var movie = item as Movie;
                if (movie != null)
                {
                    AttachSoundtrackIds(dto, movie, user);
                }
                var album = item as MusicAlbum;
                if (album != null)
                {
                    AttachSoundtrackIds(dto, album, user);
                }
                var game = item as Game;
                if (game != null)
                {
                    AttachSoundtrackIds(dto, game, user);
                }
            }
            
            // Make sure all the tasks we kicked off have completed.
            if (tasks.Count > 0)
            {
                await Task.WhenAll(tasks).ConfigureAwait(false);
            }
            return dto;
        }
        private void AttachSoundtrackIds(BaseItemDto dto, Movie item, User user)
        {
            var tmdb = item.GetProviderId(MetadataProviders.Tmdb);
            if (string.IsNullOrEmpty(tmdb))
            {
                return;
            }
            var recursiveChildren = user == null
                                        ? _libraryManager.RootFolder.RecursiveChildren
                                        : user.RootFolder.GetRecursiveChildren(user);
            dto.SoundtrackIds = recursiveChildren
                .Where(i =>
                {
                    if (!string.IsNullOrEmpty(tmdb) &&
                        string.Equals(tmdb, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase) &&
                        i is MusicAlbum)
                    {
                        return true;
                    }
                    return false;
                })
                .Select(GetClientItemId)
                .ToArray();
        }
        private void AttachSoundtrackIds(BaseItemDto dto, Series item, User user)
        {
            var tvdb = item.GetProviderId(MetadataProviders.Tvdb);
            if (string.IsNullOrEmpty(tvdb))
            {
                return;
            }
            var recursiveChildren = user == null
                                        ? _libraryManager.RootFolder.RecursiveChildren
                                        : user.RootFolder.GetRecursiveChildren(user);
            dto.SoundtrackIds = recursiveChildren
                .Where(i =>
                {
                    if (!string.IsNullOrEmpty(tvdb) &&
                        string.Equals(tvdb, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase) &&
                        i is MusicAlbum)
                    {
                        return true;
                    }
                    return false;
                })
                .Select(GetClientItemId)
                .ToArray();
        }
        private void AttachSoundtrackIds(BaseItemDto dto, Game item, User user)
        {
            var gamesdb = item.GetProviderId(MetadataProviders.Gamesdb);
            if (string.IsNullOrEmpty(gamesdb))
            {
                return;
            }
            var recursiveChildren = user == null
                                        ? _libraryManager.RootFolder.RecursiveChildren
                                        : user.RootFolder.GetRecursiveChildren(user);
            dto.SoundtrackIds = recursiveChildren
                .Where(i =>
                {
                    if (!string.IsNullOrEmpty(gamesdb) &&
                        string.Equals(gamesdb, i.GetProviderId(MetadataProviders.Gamesdb), StringComparison.OrdinalIgnoreCase) &&
                        i is MusicAlbum)
                    {
                        return true;
                    }
                    return false;
                })
                .Select(GetClientItemId)
                .ToArray();
        }
        private void AttachSoundtrackIds(BaseItemDto dto, MusicAlbum item, User user)
        {
            var tmdb = item.GetProviderId(MetadataProviders.Tmdb);
            var tvdb = item.GetProviderId(MetadataProviders.Tvdb);
            var gamesdb = item.GetProviderId(MetadataProviders.Gamesdb);
            if (string.IsNullOrEmpty(tmdb) && string.IsNullOrEmpty(tvdb) && string.IsNullOrEmpty(gamesdb))
            {
                return;
            }
            var recursiveChildren = user == null
                                        ? _libraryManager.RootFolder.RecursiveChildren
                                        : user.RootFolder.GetRecursiveChildren(user);
            dto.SoundtrackIds = recursiveChildren
                .Where(i =>
                {
                    if (!string.IsNullOrEmpty(tmdb) && 
                        string.Equals(tmdb, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase) &&
                        i is Movie)
                    {
                        return true;
                    }
                    if (!string.IsNullOrEmpty(tvdb) &&
                        string.Equals(tvdb, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase) &&
                        i is Series)
                    {
                        return true;
                    }
                    if (!string.IsNullOrEmpty(gamesdb) &&
                        string.Equals(gamesdb, i.GetProviderId(MetadataProviders.Gamesdb), StringComparison.OrdinalIgnoreCase) &&
                        i is Game)
                    {
                        return true;
                    }
                    return false;
                })
                .Select(GetClientItemId)
                .ToArray();
        }
        /// 
        /// Attaches the user specific info.
        /// 
        /// The dto.
        /// The item.
        /// The user.
        /// The fields.
        private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List fields)
        {
            if (item.IsFolder && fields.Contains(ItemFields.DisplayPreferencesId))
            {
                dto.DisplayPreferencesId = ((Folder) item).DisplayPreferencesId.ToString("N");
            }
            if (item.IsFolder)
            {
                var hasItemCounts = fields.Contains(ItemFields.ItemCounts);
                if (hasItemCounts || fields.Contains(ItemFields.CumulativeRunTimeTicks))
                {
                    var folder = (Folder)item;
                    if (hasItemCounts)
                    {
                        dto.ChildCount = folder.GetChildren(user, true).Count();
                    }
                    SetSpecialCounts(folder, user, dto, _userDataRepository);
                }
            }
            var userData = _userDataRepository.GetUserData(user.Id, item.GetUserDataKey());
            dto.UserData = GetUserItemDataDto(userData);
            if (item.IsFolder)
            {
                dto.UserData.Played = dto.PlayedPercentage.HasValue && dto.PlayedPercentage.Value >= 100;
            }
        }
        /// 
        /// Attaches the primary image aspect ratio.
        /// 
        /// The dto.
        /// The item.
        /// The _logger.
        /// Task.
        internal static async Task AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item, ILogger logger)
        {
            var path = item.PrimaryImagePath;
            if (string.IsNullOrEmpty(path))
            {
                return;
            }
            var metaFileEntry = item.ResolveArgs.GetMetaFileByPath(path);
            // See if we can avoid a file system lookup by looking for the file in ResolveArgs
            var dateModified = metaFileEntry == null ? File.GetLastWriteTimeUtc(path) : metaFileEntry.LastWriteTimeUtc;
            ImageSize size;
            try
            {
                size = await Kernel.Instance.ImageManager.GetImageSize(path, dateModified).ConfigureAwait(false);
            }
            catch (FileNotFoundException)
            {
                logger.Error("Image file does not exist: {0}", path);
                return;
            }
            catch (Exception ex)
            {
                logger.ErrorException("Failed to determine primary image aspect ratio for {0}", ex, path);
                return;
            }
            dto.OriginalPrimaryImageAspectRatio = size.Width / size.Height;
            var supportedEnhancers = Kernel.Instance.ImageManager.ImageEnhancers.Where(i =>
            {
                try
                {
                    return i.Supports(item, ImageType.Primary);
                }
                catch (Exception ex)
                {
                    logger.ErrorException("Error in image enhancer: {0}", ex, i.GetType().Name);
                    return false;
                }
            }).ToList();
            foreach (var enhancer in supportedEnhancers)
            {
                try
                {
                    size = enhancer.GetEnhancedImageSize(item, ImageType.Primary, 0, size);
                }
                catch (Exception ex)
                {
                    logger.ErrorException("Error in image enhancer: {0}", ex, enhancer.GetType().Name);
                }
            }
            dto.PrimaryImageAspectRatio = size.Width / size.Height;
        }
        /// 
        /// Sets simple property values on a DTOBaseItem
        /// 
        /// The dto.
        /// The item.
        /// The owner.
        /// The fields.
        private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem owner, List fields)
        {
            if (fields.Contains(ItemFields.DateCreated))
            {
                dto.DateCreated = item.DateCreated;
            }
            if (fields.Contains(ItemFields.OriginalRunTimeTicks))
            {
                dto.OriginalRunTimeTicks = item.OriginalRunTimeTicks;
            }
            dto.DisplayMediaType = item.DisplayMediaType;
            if (fields.Contains(ItemFields.MetadataSettings))
            {
                dto.LockedFields = item.LockedFields;
                dto.EnableInternetProviders = !item.DontFetchMeta;
            }
            if (fields.Contains(ItemFields.Budget))
            {
                dto.Budget = item.Budget;
            }
            if (fields.Contains(ItemFields.Revenue))
            {
                dto.Revenue = item.Revenue;
            }
            dto.EndDate = item.EndDate;
            if (fields.Contains(ItemFields.HomePageUrl))
            {
                dto.HomePageUrl = item.HomePageUrl;
            }
            if (fields.Contains(ItemFields.Tags))
            {
                dto.Tags = item.Tags;
            }
            if (fields.Contains(ItemFields.ProductionLocations))
            {
                dto.ProductionLocations = item.ProductionLocations;
            }
            dto.AspectRatio = item.AspectRatio;
            dto.BackdropImageTags = GetBackdropImageTags(item);
            dto.ScreenshotImageTags = GetScreenshotImageTags(item);
            if (fields.Contains(ItemFields.Genres))
            {
                dto.Genres = item.Genres;
            }
            dto.ImageTags = new Dictionary();
            foreach (var image in item.Images)
            {
                var type = image.Key;
                var tag = GetImageCacheTag(item, type, image.Value);
                if (tag.HasValue)
                {
                    dto.ImageTags[type] = tag.Value;
                }
            }
            dto.Id = GetClientItemId(item);
            dto.IndexNumber = item.IndexNumber;
            dto.IsFolder = item.IsFolder;
            dto.Language = item.Language;
            dto.MediaType = item.MediaType;
            dto.LocationType = item.LocationType;
            dto.CriticRating = item.CriticRating;
            if (fields.Contains(ItemFields.CriticRatingSummary))
            {
                dto.CriticRatingSummary = item.CriticRatingSummary;
            }
            var localTrailerCount = item.LocalTrailerIds.Count;
            if (localTrailerCount > 0)
            {
                dto.LocalTrailerCount = localTrailerCount;
            }
            dto.Name = item.Name;
            dto.OfficialRating = item.OfficialRating;
            var hasOverview = fields.Contains(ItemFields.Overview);
            var hasHtmlOverview = fields.Contains(ItemFields.OverviewHtml);
            if (hasOverview || hasHtmlOverview)
            {
                var strippedOverview = string.IsNullOrEmpty(item.Overview) ? item.Overview : item.Overview.StripHtml();
                if (hasOverview)
                {
                    dto.Overview = strippedOverview;
                }
                // Only supply the html version if there was actually html content
                if (hasHtmlOverview)
                {
                    dto.OverviewHtml = item.Overview;
                }
            }
            // If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance
            if (dto.BackdropImageTags.Count == 0)
            {
                var parentWithBackdrop = GetParentBackdropItem(item, owner);
                if (parentWithBackdrop != null)
                {
                    dto.ParentBackdropItemId = GetClientItemId(parentWithBackdrop);
                    dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop);
                }
            }
            if (item.Parent != null && fields.Contains(ItemFields.ParentId))
            {
                dto.ParentId = GetClientItemId(item.Parent);
            }
            dto.ParentIndexNumber = item.ParentIndexNumber;
            // If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance
            if (!dto.HasLogo)
            {
                var parentWithLogo = GetParentImageItem(item, ImageType.Logo, owner);
                if (parentWithLogo != null)
                {
                    dto.ParentLogoItemId = GetClientItemId(parentWithLogo);
                    dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImage(ImageType.Logo));
                }
            }
            // If there is no art, indicate what parent has one in case the Ui wants to allow inheritance
            if (!dto.HasArtImage)
            {
                var parentWithImage = GetParentImageItem(item, ImageType.Art, owner);
                if (parentWithImage != null)
                {
                    dto.ParentArtItemId = GetClientItemId(parentWithImage);
                    dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImage(ImageType.Art));
                }
            }
            if (fields.Contains(ItemFields.Path))
            {
                dto.Path = item.Path;
            }
            dto.PremiereDate = item.PremiereDate;
            dto.ProductionYear = item.ProductionYear;
            if (fields.Contains(ItemFields.ProviderIds))
            {
                dto.ProviderIds = item.ProviderIds;
            }
            dto.RunTimeTicks = item.RunTimeTicks;
            if (fields.Contains(ItemFields.SortName))
            {
                dto.SortName = item.SortName;
            }
            if (fields.Contains(ItemFields.CustomRating))
            {
                dto.CustomRating = item.CustomRating;
            }
            if (fields.Contains(ItemFields.Taglines))
            {
                dto.Taglines = item.Taglines;
            }
            if (fields.Contains(ItemFields.RemoteTrailers))
            {
                dto.RemoteTrailers = item.RemoteTrailers;
            }
            dto.Type = item.GetType().Name;
            dto.CommunityRating = item.CommunityRating;
            if (item.IsFolder)
            {
                var folder = (Folder)item;
                if (fields.Contains(ItemFields.IndexOptions))
                {
                    dto.IndexOptions = folder.IndexByOptionStrings.ToArray();
                }
            }
            // Add audio info
            var audio = item as Audio;
            if (audio != null)
            {
                dto.Album = audio.Album;
                dto.AlbumArtist = audio.AlbumArtist;
                dto.Artists = new[] { audio.Artist };
                var albumParent = audio.FindParent();
                if (albumParent != null)
                {
                    dto.AlbumId = GetClientItemId(albumParent);
                    var imagePath = albumParent.PrimaryImagePath;
                    if (!string.IsNullOrEmpty(imagePath))
                    {
                        dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary, imagePath);
                    }
                }
            }
            var album = item as MusicAlbum;
            if (album != null)
            {
                var songs = album.RecursiveChildren.OfType