#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
{
    /// 
    /// Class BaseItem.
    /// 
    public abstract class BaseItem : IHasProviderIds, IHasLookupInfo, IEquatable
    {
        /// 
        /// The supported image extensions.
        /// 
        public static readonly string[] SupportedImageExtensions
            = new[] { ".png", ".jpg", ".jpeg", ".tbn", ".gif" };
        private static readonly List _supportedExtensions = new List(SupportedImageExtensions)
        {
            ".nfo",
            ".xml",
            ".srt",
            ".vtt",
            ".sub",
            ".idx",
            ".txt",
            ".edl",
            ".bif",
            ".smi",
            ".ttml"
        };
        protected BaseItem()
        {
            Tags = Array.Empty();
            Genres = Array.Empty();
            Studios = Array.Empty();
            ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
            LockedFields = Array.Empty();
            ImageInfos = Array.Empty();
            ProductionLocations = Array.Empty();
            RemoteTrailers = Array.Empty();
            ExtraIds = Array.Empty();
        }
        public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
        public static char SlugChar = '-';
        /// 
        /// The trailer folder name.
        /// 
        public const string TrailerFolderName = "trailers";
        public const string ThemeSongsFolderName = "theme-music";
        public const string ThemeSongFilename = "theme";
        public const string ThemeVideosFolderName = "backdrops";
        public const string ExtrasFolderName = "extras";
        public const string BehindTheScenesFolderName = "behind the scenes";
        public const string DeletedScenesFolderName = "deleted scenes";
        public const string InterviewFolderName = "interviews";
        public const string SceneFolderName = "scenes";
        public const string SampleFolderName = "samples";
        public const string ShortsFolderName = "shorts";
        public const string FeaturettesFolderName = "featurettes";
        public static readonly string[] AllExtrasTypesFolderNames = {
            ExtrasFolderName,
            BehindTheScenesFolderName,
            DeletedScenesFolderName,
            InterviewFolderName,
            SceneFolderName,
            SampleFolderName,
            ShortsFolderName,
            FeaturettesFolderName
        };
        [JsonIgnore]
        public Guid[] ThemeSongIds
        {
            get
            {
                if (_themeSongIds == null)
                {
                    _themeSongIds = GetExtras()
                        .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong)
                        .Select(song => song.Id)
                        .ToArray();
                }
                return _themeSongIds;
            }
            private set
            {
                _themeSongIds = value;
            }
        }
        [JsonIgnore]
        public Guid[] ThemeVideoIds
        {
            get
            {
                if (_themeVideoIds == null)
                {
                    _themeVideoIds = GetExtras()
                        .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo)
                        .Select(song => song.Id)
                        .ToArray();
                }
                return _themeVideoIds;
            }
            private set
            {
                _themeVideoIds = value;
            }
        }
        [JsonIgnore]
        public string PreferredMetadataCountryCode { get; set; }
        [JsonIgnore]
        public string PreferredMetadataLanguage { get; set; }
        public long? Size { get; set; }
        public string Container { get; set; }
        [JsonIgnore]
        public string Tagline { get; set; }
        [JsonIgnore]
        public virtual ItemImageInfo[] ImageInfos { get; set; }
        [JsonIgnore]
        public bool IsVirtualItem { get; set; }
        /// 
        /// Gets or sets the album.
        /// 
        /// The album.
        [JsonIgnore]
        public string Album { get; set; }
        /// 
        /// Gets or sets the channel identifier.
        /// 
        /// The channel identifier.
        [JsonIgnore]
        public Guid ChannelId { get; set; }
        [JsonIgnore]
        public virtual bool SupportsAddingToPlaylist => false;
        [JsonIgnore]
        public virtual bool AlwaysScanInternalMetadataPath => false;
        /// 
        /// Gets a value indicating whether this instance is in mixed folder.
        /// 
        /// true if this instance is in mixed folder; otherwise, false.
        [JsonIgnore]
        public bool IsInMixedFolder { get; set; }
        [JsonIgnore]
        public virtual bool SupportsPlayedStatus => false;
        [JsonIgnore]
        public virtual bool SupportsPositionTicksResume => false;
        [JsonIgnore]
        public virtual bool SupportsRemoteImageDownloading => true;
        private string _name;
        /// 
        /// Gets or sets the name.
        /// 
        /// The name.
        [JsonIgnore]
        public virtual string Name
        {
            get => _name;
            set
            {
                _name = value;
                // lazy load this again
                _sortName = null;
            }
        }
        [JsonIgnore]
        public bool IsUnaired => PremiereDate.HasValue && PremiereDate.Value.ToLocalTime().Date >= DateTime.Now.Date;
        [JsonIgnore]
        public int? TotalBitrate { get; set; }
        [JsonIgnore]
        public ExtraType? ExtraType { get; set; }
        [JsonIgnore]
        public bool IsThemeMedia => ExtraType.HasValue && (ExtraType.Value == Model.Entities.ExtraType.ThemeSong || ExtraType.Value == Model.Entities.ExtraType.ThemeVideo);
        [JsonIgnore]
        public string OriginalTitle { get; set; }
        /// 
        /// Gets or sets the id.
        /// 
        /// The id.
        [JsonIgnore]
        public Guid Id { get; set; }
        [JsonIgnore]
        public Guid OwnerId { get; set; }
        /// 
        /// Gets or sets the audio.
        /// 
        /// The audio.
        [JsonIgnore]
        public ProgramAudio? Audio { get; set; }
        /// 
        /// Return the id that should be used to key display prefs for this item.
        /// Default is based on the type for everything except actual generic folders.
        /// 
        /// The display prefs id.
        [JsonIgnore]
        public virtual Guid DisplayPreferencesId
        {
            get
            {
                var thisType = GetType();
                return thisType == typeof(Folder) ? Id : thisType.FullName.GetMD5();
            }
        }
        /// 
        /// Gets or sets the path.
        /// 
        /// The path.
        [JsonIgnore]
        public virtual string Path { get; set; }
        [JsonIgnore]
        public virtual SourceType SourceType
        {
            get
            {
                if (!ChannelId.Equals(Guid.Empty))
                {
                    return SourceType.Channel;
                }
                return SourceType.Library;
            }
        }
        /// 
        /// Returns the folder containing the item.
        /// If the item is a folder, it returns the folder itself.
        /// 
        [JsonIgnore]
        public virtual string ContainingFolderPath
        {
            get
            {
                if (IsFolder)
                {
                    return Path;
                }
                return System.IO.Path.GetDirectoryName(Path);
            }
        }
        /// 
        /// Gets or sets the name of the service.
        /// 
        /// The name of the service.
        [JsonIgnore]
        public string ServiceName { get; set; }
        /// 
        /// If this content came from an external service, the id of the content on that service.
        /// 
        [JsonIgnore]
        public string ExternalId { get; set; }
        [JsonIgnore]
        public string ExternalSeriesId { get; set; }
        /// 
        /// Gets or sets the etag.
        /// 
        /// The etag.
        [JsonIgnore]
        public string ExternalEtag { get; set; }
        [JsonIgnore]
        public virtual bool IsHidden => false;
        public BaseItem GetOwner()
        {
            var ownerId = OwnerId;
            return ownerId.Equals(Guid.Empty) ? null : LibraryManager.GetItemById(ownerId);
        }
        /// 
        /// Gets or sets the type of the location.
        /// 
        /// The type of the location.
        [JsonIgnore]
        public virtual LocationType LocationType
        {
            get
            {
                // if (IsOffline)
                //{
                //    return LocationType.Offline;
                //}
                var path = Path;
                if (string.IsNullOrEmpty(path))
                {
                    if (SourceType == SourceType.Channel)
                    {
                        return LocationType.Remote;
                    }
                    return LocationType.Virtual;
                }
                return FileSystem.IsPathFile(path) ? LocationType.FileSystem : LocationType.Remote;
            }
        }
        [JsonIgnore]
        public MediaProtocol? PathProtocol
        {
            get
            {
                var path = Path;
                if (string.IsNullOrEmpty(path))
                {
                    return null;
                }
                return MediaSourceManager.GetPathProtocol(path);
            }
        }
        public bool IsPathProtocol(MediaProtocol protocol)
        {
            var current = PathProtocol;
            return current.HasValue && current.Value == protocol;
        }
        [JsonIgnore]
        public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File);
        [JsonIgnore]
        public bool HasPathProtocol => PathProtocol.HasValue;
        [JsonIgnore]
        public virtual bool SupportsLocalMetadata
        {
            get
            {
                if (SourceType == SourceType.Channel)
                {
                    return false;
                }
                return IsFileProtocol;
            }
        }
        [JsonIgnore]
        public virtual string FileNameWithoutExtension
        {
            get
            {
                if (IsFileProtocol)
                {
                    return System.IO.Path.GetFileNameWithoutExtension(Path);
                }
                return null;
            }
        }
        [JsonIgnore]
        public virtual bool EnableAlphaNumericSorting => true;
        private List> GetSortChunks(string s1)
        {
            var list = new List>();
            int thisMarker = 0;
            while (thisMarker < s1.Length)
            {
                char thisCh = s1[thisMarker];
                var thisChunk = new StringBuilder();
                bool isNumeric = char.IsDigit(thisCh);
                while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric)
                {
                    thisChunk.Append(thisCh);
                    thisMarker++;
                    if (thisMarker < s1.Length)
                    {
                        thisCh = s1[thisMarker];
                    }
                }
                list.Add(new Tuple(thisChunk, isNumeric));
            }
            return list;
        }
        /// 
        /// This is just a helper for convenience.
        /// 
        /// The primary image path.
        [JsonIgnore]
        public string PrimaryImagePath => this.GetImagePath(ImageType.Primary);
        public virtual bool CanDelete()
        {
            if (SourceType == SourceType.Channel)
            {
                return ChannelManager.CanDelete(this);
            }
            return IsFileProtocol;
        }
        public virtual bool IsAuthorizedToDelete(User user, List allCollectionFolders)
        {
            if (user.HasPermission(PermissionKind.EnableContentDeletion))
            {
                return true;
            }
            var allowed = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders);
            if (SourceType == SourceType.Channel)
            {
                return allowed.Contains(ChannelId.ToString(""), StringComparer.OrdinalIgnoreCase);
            }
            else
            {
                var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders);
                foreach (var folder in collectionFolders)
                {
                    if (allowed.Contains(folder.Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
        public bool CanDelete(User user, List allCollectionFolders)
        {
            return CanDelete() && IsAuthorizedToDelete(user, allCollectionFolders);
        }
        public bool CanDelete(User user)
        {
            var allCollectionFolders = LibraryManager.GetUserRootFolder().Children.OfType().ToList();
            return CanDelete(user, allCollectionFolders);
        }
        public virtual bool CanDownload()
        {
            return false;
        }
        public virtual bool IsAuthorizedToDownload(User user)
        {
            return user.HasPermission(PermissionKind.EnableContentDownloading);
        }
        public bool CanDownload(User user)
        {
            return CanDownload() && IsAuthorizedToDownload(user);
        }
        /// 
        /// Gets or sets the date created.
        /// 
        /// The date created.
        [JsonIgnore]
        public DateTime DateCreated { get; set; }
        /// 
        /// Gets or sets the date modified.
        /// 
        /// The date modified.
        [JsonIgnore]
        public DateTime DateModified { get; set; }
        public DateTime DateLastSaved { get; set; }
        [JsonIgnore]
        public DateTime DateLastRefreshed { get; set; }
        /// 
        /// The logger.
        /// 
        public static ILogger Logger { get; set; }
        public static ILibraryManager LibraryManager { get; set; }
        public static IServerConfigurationManager ConfigurationManager { get; set; }
        public static IProviderManager ProviderManager { get; set; }
        public static ILocalizationManager LocalizationManager { get; set; }
        public static IItemRepository ItemRepository { get; set; }
        public static IFileSystem FileSystem { get; set; }
        public static IUserDataManager UserDataManager { get; set; }
        public static IChannelManager ChannelManager { get; set; }
        public static IMediaSourceManager MediaSourceManager { get; set; }
        /// 
        /// Returns a  that represents this instance.
        /// 
        /// A  that represents this instance.
        public override string ToString()
        {
            return Name;
        }
        [JsonIgnore]
        public bool IsLocked { get; set; }
        /// 
        /// Gets or sets the locked fields.
        /// 
        /// The locked fields.
        [JsonIgnore]
        public MetadataField[] LockedFields { get; set; }
        /// 
        /// Gets the type of the media.
        /// 
        /// The type of the media.
        [JsonIgnore]
        public virtual string MediaType => null;
        [JsonIgnore]
        public virtual string[] PhysicalLocations
        {
            get
            {
                if (!IsFileProtocol)
                {
                    return Array.Empty();
                }
                return new[] { Path };
            }
        }
        private string _forcedSortName;
        /// 
        /// Gets or sets the name of the forced sort.
        /// 
        /// The name of the forced sort.
        [JsonIgnore]
        public string ForcedSortName
        {
            get => _forcedSortName;
            set { _forcedSortName = value; _sortName = null; }
        }
        private string _sortName;
        private Guid[] _themeSongIds;
        private Guid[] _themeVideoIds;
        /// 
        /// Gets the name of the sort.
        /// 
        /// The name of the sort.
        [JsonIgnore]
        public string SortName
        {
            get
            {
                if (_sortName == null)
                {
                    if (!string.IsNullOrEmpty(ForcedSortName))
                    {
                        // Need the ToLower because that's what CreateSortName does
                        _sortName = ModifySortChunks(ForcedSortName).ToLowerInvariant();
                    }
                    else
                    {
                        _sortName = CreateSortName();
                    }
                }
                return _sortName;
            }
            set => _sortName = value;
        }
        public string GetInternalMetadataPath()
        {
            var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath;
            return GetInternalMetadataPath(basePath);
        }
        protected virtual string GetInternalMetadataPath(string basePath)
        {
            if (SourceType == SourceType.Channel)
            {
                return System.IO.Path.Combine(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture));
            }
            ReadOnlySpan idString = Id.ToString("N", CultureInfo.InvariantCulture);
            basePath = System.IO.Path.Combine(basePath, "library");
            return System.IO.Path.Join(basePath, idString.Slice(0, 2), idString);
        }
        /// 
        /// Creates the name of the sort.
        /// 
        /// System.String.
        protected virtual string CreateSortName()
        {
            if (Name == null)
            {
                return null; // some items may not have name filled in properly
            }
            if (!EnableAlphaNumericSorting)
            {
                return Name.TrimStart();
            }
            var sortable = Name.Trim().ToLowerInvariant();
            foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters)
            {
                sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal);
            }
            foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters)
            {
                sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal);
            }
            foreach (var search in ConfigurationManager.Configuration.SortRemoveWords)
            {
                // Remove from beginning if a space follows
                if (sortable.StartsWith(search + " ", StringComparison.Ordinal))
                {
                    sortable = sortable.Remove(0, search.Length + 1);
                }
                // Remove from middle if surrounded by spaces
                sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal);
                // Remove from end if followed by a space
                if (sortable.EndsWith(" " + search, StringComparison.Ordinal))
                {
                    sortable = sortable.Remove(sortable.Length - (search.Length + 1));
                }
            }
            return ModifySortChunks(sortable);
        }
        private string ModifySortChunks(string name)
        {
            var chunks = GetSortChunks(name);
            var builder = new StringBuilder();
            foreach (var chunk in chunks)
            {
                var chunkBuilder = chunk.Item1;
                // This chunk is numeric
                if (chunk.Item2)
                {
                    while (chunkBuilder.Length < 10)
                    {
                        chunkBuilder.Insert(0, '0');
                    }
                }
                builder.Append(chunkBuilder);
            }
            // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString());
            return builder.ToString().RemoveDiacritics();
        }
        [JsonIgnore]
        public bool EnableMediaSourceDisplay
        {
            get
            {
                if (SourceType == SourceType.Channel)
                {
                    return ChannelManager.EnableMediaSourceDisplay(this);
                }
                return true;
            }
        }
        [JsonIgnore]
        public Guid ParentId { get; set; }
        /// 
        /// Gets or sets the parent.
        /// 
        /// The parent.
        [JsonIgnore]
        public Folder Parent
        {
            get => GetParent() as Folder;
            set
            {
            }
        }
        public void SetParent(Folder parent)
        {
            ParentId = parent == null ? Guid.Empty : parent.Id;
        }
        public BaseItem GetParent()
        {
            var parentId = ParentId;
            if (!parentId.Equals(Guid.Empty))
            {
                return LibraryManager.GetItemById(parentId);
            }
            return null;
        }
        public IEnumerable GetParents()
        {
            var parent = GetParent();
            while (parent != null)
            {
                yield return parent;
                parent = parent.GetParent();
            }
        }
        /// 
        /// Finds a parent of a given type.
        /// 
        /// 
        /// ``0.
        public T FindParent()
            where T : Folder
        {
            foreach (var parent in GetParents())
            {
                var item = parent as T;
                if (item != null)
                {
                    return item;
                }
            }
            return null;
        }
        [JsonIgnore]
        public virtual Guid DisplayParentId
        {
            get
            {
                var parentId = ParentId;
                return parentId;
            }
        }
        [JsonIgnore]
        public BaseItem DisplayParent
        {
            get
            {
                var id = DisplayParentId;
                if (id.Equals(Guid.Empty))
                {
                    return null;
                }
                return LibraryManager.GetItemById(id);
            }
        }
        /// 
        /// When the item first debuted. For movies this could be premiere date, episodes would be first aired
        /// 
        /// The premiere date.
        [JsonIgnore]
        public DateTime? PremiereDate { get; set; }
        /// 
        /// Gets or sets the end date.
        /// 
        /// The end date.
        [JsonIgnore]
        public DateTime? EndDate { get; set; }
        /// 
        /// Gets or sets the official rating.
        /// 
        /// The official rating.
        [JsonIgnore]
        public string OfficialRating { get; set; }
        [JsonIgnore]
        public int InheritedParentalRatingValue { get; set; }
        /// 
        /// Gets or sets the critic rating.
        /// 
        /// The critic rating.
        [JsonIgnore]
        public float? CriticRating { get; set; }
        /// 
        /// Gets or sets the custom rating.
        /// 
        /// The custom rating.
        [JsonIgnore]
        public string CustomRating { get; set; }
        /// 
        /// Gets or sets the overview.
        /// 
        /// The overview.
        [JsonIgnore]
        public string Overview { get; set; }
        /// 
        /// Gets or sets the studios.
        /// 
        /// The studios.
        [JsonIgnore]
        public string[] Studios { get; set; }
        /// 
        /// Gets or sets the genres.
        /// 
        /// The genres.
        [JsonIgnore]
        public string[] Genres { get; set; }
        /// 
        /// Gets or sets the tags.
        /// 
        /// The tags.
        [JsonIgnore]
        public string[] Tags { get; set; }
        [JsonIgnore]
        public string[] ProductionLocations { get; set; }
        /// 
        /// Gets or sets the home page URL.
        /// 
        /// The home page URL.
        [JsonIgnore]
        public string HomePageUrl { get; set; }
        /// 
        /// Gets or sets the community rating.
        /// 
        /// The community rating.
        [JsonIgnore]
        public float? CommunityRating { get; set; }
        /// 
        /// Gets or sets the run time ticks.
        /// 
        /// The run time ticks.
        [JsonIgnore]
        public long? RunTimeTicks { get; set; }
        /// 
        /// Gets or sets the production year.
        /// 
        /// The production year.
        [JsonIgnore]
        public int? ProductionYear { get; set; }
        /// 
        /// If the item is part of a series, this is it's number in the series.
        /// This could be episode number, album track number, etc.
        /// 
        /// The index number.
        [JsonIgnore]
        public int? IndexNumber { get; set; }
        /// 
        /// For an episode this could be the season number, or for a song this could be the disc number.
        /// 
        /// The parent index number.
        [JsonIgnore]
        public int? ParentIndexNumber { get; set; }
        [JsonIgnore]
        public virtual bool HasLocalAlternateVersions => false;
        [JsonIgnore]
        public string OfficialRatingForComparison
        {
            get
            {
                var officialRating = OfficialRating;
                if (!string.IsNullOrEmpty(officialRating))
                {
                    return officialRating;
                }
                var parent = DisplayParent;
                if (parent != null)
                {
                    return parent.OfficialRatingForComparison;
                }
                return null;
            }
        }
        [JsonIgnore]
        public string CustomRatingForComparison
        {
            get
            {
                var customRating = CustomRating;
                if (!string.IsNullOrEmpty(customRating))
                {
                    return customRating;
                }
                var parent = DisplayParent;
                if (parent != null)
                {
                    return parent.CustomRatingForComparison;
                }
                return null;
            }
        }
        /// 
        /// Gets the play access.
        /// 
        /// The user.
        /// PlayAccess.
        public PlayAccess GetPlayAccess(User user)
        {
            if (!user.HasPermission(PermissionKind.EnableMediaPlayback))
            {
                return PlayAccess.None;
            }
            // if (!user.IsParentalScheduleAllowed())
            //{
            //    return PlayAccess.None;
            //}
            return PlayAccess.Full;
        }
        public virtual List GetMediaStreams()
        {
            return MediaSourceManager.GetMediaStreams(new MediaStreamQuery
            {
                ItemId = Id
            });
        }
        protected virtual bool IsActiveRecording()
        {
            return false;
        }
        public virtual List GetMediaSources(bool enablePathSubstitution)
        {
            if (SourceType == SourceType.Channel)
            {
                var sources = ChannelManager.GetStaticMediaSources(this, CancellationToken.None)
                           .ToList();
                if (sources.Count > 0)
                {
                    return sources;
                }
            }
            var list = GetAllItemsForMediaSources();
            var result = list.Select(i => GetVersionInfo(enablePathSubstitution, i.Item1, i.Item2)).ToList();
            if (IsActiveRecording())
            {
                foreach (var mediaSource in result)
                {
                    mediaSource.Type = MediaSourceType.Placeholder;
                }
            }
            return result.OrderBy(i =>
            {
                if (i.VideoType == VideoType.VideoFile)
                {
                    return 0;
                }
                return 1;
            }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
            .ThenByDescending(i =>
            {
                var stream = i.VideoStream;
                return stream == null || stream.Width == null ? 0 : stream.Width.Value;
            })
            .ToList();
        }
        protected virtual List> GetAllItemsForMediaSources()
        {
            return new List>();
        }
        private MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, BaseItem item, MediaSourceType type)
        {
            if (item == null)
            {
                throw new ArgumentNullException(nameof(item));
            }
            var protocol = item.PathProtocol;
            var info = new MediaSourceInfo
            {
                Id = item.Id.ToString("N", CultureInfo.InvariantCulture),
                Protocol = protocol ?? MediaProtocol.File,
                MediaStreams = MediaSourceManager.GetMediaStreams(item.Id),
                MediaAttachments = MediaSourceManager.GetMediaAttachments(item.Id),
                Name = GetMediaSourceName(item),
                Path = enablePathSubstitution ? GetMappedPath(item, item.Path, protocol) : item.Path,
                RunTimeTicks = item.RunTimeTicks,
                Container = item.Container,
                Size = item.Size,
                Type = type
            };
            if (string.IsNullOrEmpty(info.Path))
            {
                info.Type = MediaSourceType.Placeholder;
            }
            if (info.Protocol == MediaProtocol.File)
            {
                info.ETag = item.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N", CultureInfo.InvariantCulture);
            }
            var video = item as Video;
            if (video != null)
            {
                info.IsoType = video.IsoType;
                info.VideoType = video.VideoType;
                info.Video3DFormat = video.Video3DFormat;
                info.Timestamp = video.Timestamp;
                if (video.IsShortcut)
                {
                    info.IsRemote = true;
                    info.Path = video.ShortcutPath;
                    info.Protocol = MediaSourceManager.GetPathProtocol(info.Path);
                }
                if (string.IsNullOrEmpty(info.Container))
                {
                    if (video.VideoType == VideoType.VideoFile || video.VideoType == VideoType.Iso)
                    {
                        if (protocol.HasValue && protocol.Value == MediaProtocol.File)
                        {
                            info.Container = System.IO.Path.GetExtension(item.Path).TrimStart('.');
                        }
                    }
                }
            }
            if (string.IsNullOrEmpty(info.Container))
            {
                if (protocol.HasValue && protocol.Value == MediaProtocol.File)
                {
                    info.Container = System.IO.Path.GetExtension(item.Path).TrimStart('.');
                }
            }
            if (info.SupportsDirectStream && !string.IsNullOrEmpty(info.Path))
            {
                info.SupportsDirectStream = MediaSourceManager.SupportsDirectStream(info.Path, info.Protocol);
            }
            if (video != null && video.VideoType != VideoType.VideoFile)
            {
                info.SupportsDirectStream = false;
            }
            info.Bitrate = item.TotalBitrate;
            info.InferTotalBitrate();
            return info;
        }
        private string GetMediaSourceName(BaseItem item)
        {
            var terms = new List();
            var path = item.Path;
            if (item.IsFileProtocol && !string.IsNullOrEmpty(path))
            {
                if (HasLocalAlternateVersions)
                {
                    var displayName = System.IO.Path.GetFileNameWithoutExtension(path)
                        .Replace(System.IO.Path.GetFileName(ContainingFolderPath), string.Empty, StringComparison.OrdinalIgnoreCase)
                        .TrimStart(new char[] { ' ', '-' });
                    if (!string.IsNullOrEmpty(displayName))
                    {
                        terms.Add(displayName);
                    }
                }
                if (terms.Count == 0)
                {
                    var displayName = System.IO.Path.GetFileNameWithoutExtension(path);
                    terms.Add(displayName);
                }
            }
            if (terms.Count == 0)
            {
                terms.Add(item.Name);
            }
            var video = item as Video;
            if (video != null)
            {
                if (video.Video3DFormat.HasValue)
                {
                    terms.Add("3D");
                }
                if (video.VideoType == VideoType.BluRay)
                {
                    terms.Add("Bluray");
                }
                else if (video.VideoType == VideoType.Dvd)
                {
                    terms.Add("DVD");
                }
                else if (video.VideoType == VideoType.Iso)
                {
                    if (video.IsoType.HasValue)
                    {
                        if (video.IsoType.Value == IsoType.BluRay)
                        {
                            terms.Add("Bluray");
                        }
                        else if (video.IsoType.Value == IsoType.Dvd)
                        {
                            terms.Add("DVD");
                        }
                    }
                    else
                    {
                        terms.Add("ISO");
                    }
                }
            }
            return string.Join("/", terms.ToArray());
        }
        /// 
        /// Loads the theme songs.
        /// 
        /// List{Audio.Audio}.
        private static Audio.Audio[] LoadThemeSongs(List fileSystemChildren, IDirectoryService directoryService)
        {
            var files = fileSystemChildren.Where(i => i.IsDirectory)
                .Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
                .SelectMany(i => FileSystem.GetFiles(i.FullName))
                .ToList();
            // Support plex/xbmc convention
            files.AddRange(fileSystemChildren
                .Where(i => !i.IsDirectory && string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase)));
            return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
                .OfType()
                .Select(audio =>
                {
                    // Try to retrieve it from the db. If we don't find it, use the resolved version
                    var dbItem = LibraryManager.GetItemById(audio.Id) as Audio.Audio;
                    if (dbItem != null)
                    {
                        audio = dbItem;
                    }
                    else
                    {
                        // item is new
                        audio.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeSong;
                    }
                    return audio;
                    // Sort them so that the list can be easily compared for changes
                }).OrderBy(i => i.Path).ToArray();
        }
        /// 
        /// Loads the video backdrops.
        /// 
        /// List{Video}.
        private static Video[] LoadThemeVideos(IEnumerable fileSystemChildren, IDirectoryService directoryService)
        {
            var files = fileSystemChildren.Where(i => i.IsDirectory)
                .Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
                .SelectMany(i => FileSystem.GetFiles(i.FullName));
            return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
                .OfType