using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Entities
{
    /// 
    /// Class BaseItem
    /// 
    public abstract class BaseItem : IHasProviderIds, ILibraryItem
    {
        protected BaseItem()
        {
            Genres = new List();
            Studios = new List();
            People = new List();
            BackdropImagePaths = new List();
            Images = new Dictionary();
            ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
            LockedFields = new List();
            ImageSources = new List();
        }
        /// 
        /// The supported image extensions
        /// 
        public static readonly string[] SupportedImageExtensions = new[] { ".png", ".jpg", ".jpeg", ".tbn" };
        /// 
        /// 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 XbmcTrailerFileSuffix = "-trailer";
        public bool IsInMixedFolder { get; set; }
        private string _name;
        /// 
        /// Gets or sets the name.
        /// 
        /// The name.
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
                // lazy load this again
                _sortName = null;
            }
        }
        /// 
        /// Gets or sets the id.
        /// 
        /// The id.
        public Guid Id { 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.
        [IgnoreDataMember]
        public virtual Guid DisplayPreferencesId
        {
            get
            {
                var thisType = GetType();
                return thisType == typeof(Folder) ? Id : thisType.FullName.GetMD5();
            }
        }
        /// 
        /// Gets or sets the path.
        /// 
        /// The path.
        public virtual string Path { get; set; }
        [IgnoreDataMember]
        protected internal bool IsOffline { get; set; }
        /// 
        /// Gets or sets the type of the location.
        /// 
        /// The type of the location.
        [IgnoreDataMember]
        public virtual LocationType LocationType
        {
            get
            {
                if (IsOffline)
                {
                    return LocationType.Offline;
                }
                if (string.IsNullOrEmpty(Path))
                {
                    return LocationType.Virtual;
                }
                return System.IO.Path.IsPathRooted(Path) ? LocationType.FileSystem : LocationType.Remote;
            }
        }
        /// 
        /// This is just a helper for convenience
        /// 
        /// The primary image path.
        [IgnoreDataMember]
        public string PrimaryImagePath
        {
            get { return GetImage(ImageType.Primary); }
            set { SetImage(ImageType.Primary, value); }
        }
        /// 
        /// Gets or sets the images.
        /// 
        /// The images.
        public Dictionary Images { get; set; }
        /// 
        /// Gets or sets the date created.
        /// 
        /// The date created.
        public DateTime DateCreated { get; set; }
        /// 
        /// Gets or sets the date modified.
        /// 
        /// The date modified.
        public DateTime DateModified { get; set; }
        public DateTime DateLastSaved { 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; }
        /// 
        /// Returns a  that represents this instance.
        /// 
        /// A  that represents this instance.
        public override string ToString()
        {
            return Name;
        }
        /// 
        /// Returns true if this item should not attempt to fetch metadata
        /// 
        /// true if [dont fetch meta]; otherwise, false.
        public bool DontFetchMeta { get; set; }
        /// 
        /// Gets or sets the locked fields.
        /// 
        /// The locked fields.
        public List LockedFields { get; set; }
        /// 
        /// Should be overridden to return the proper folder where metadata lives
        /// 
        /// The meta location.
        [IgnoreDataMember]
        public virtual string MetaLocation
        {
            get
            {
                return Path ?? "";
            }
        }
        /// 
        /// Gets the type of the media.
        /// 
        /// The type of the media.
        [IgnoreDataMember]
        public virtual string MediaType
        {
            get
            {
                return null;
            }
        }
        /// 
        /// The _resolve args
        /// 
        private ItemResolveArgs _resolveArgs;
        /// 
        /// We attach these to the item so that we only ever have to hit the file system once
        /// (this includes the children of the containing folder)
        /// 
        /// The resolve args.
        [IgnoreDataMember]
        public ItemResolveArgs ResolveArgs
        {
            get
            {
                if (_resolveArgs == null)
                {
                    try
                    {
                        _resolveArgs = CreateResolveArgs();
                    }
                    catch (IOException ex)
                    {
                        Logger.ErrorException("Error creating resolve args for {0}", ex, Path);
                        IsOffline = true;
                        throw;
                    }
                }
                return _resolveArgs;
            }
            set
            {
                _resolveArgs = value;
            }
        }
        /// 
        /// Resets the resolve args.
        /// 
        /// The path info.
        public void ResetResolveArgs(FileSystemInfo pathInfo)
        {
            ResetResolveArgs(CreateResolveArgs(pathInfo));
        }
        /// 
        /// Resets the resolve args.
        /// 
        public void ResetResolveArgs()
        {
            _resolveArgs = null;
        }
        /// 
        /// Resets the resolve args.
        /// 
        /// The args.
        public void ResetResolveArgs(ItemResolveArgs args)
        {
            _resolveArgs = args;
        }
        /// 
        /// Creates ResolveArgs on demand
        /// 
        /// The path info.
        /// ItemResolveArgs.
        /// Unable to retrieve file system info for  + path
        protected internal virtual ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
        {
            var path = Path;
            var locationType = LocationType;
            if (locationType == LocationType.Remote ||
                locationType == LocationType.Virtual)
            {
                return new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager);
            }
            var isDirectory = false;
            if (UseParentPathToCreateResolveArgs)
            {
                path = System.IO.Path.GetDirectoryName(path);
                isDirectory = true;
            }
            pathInfo = pathInfo ?? (isDirectory ? new DirectoryInfo(path) : FileSystem.GetFileSystemInfo(path));
            if (pathInfo == null || !pathInfo.Exists)
            {
                throw new IOException("Unable to retrieve file system info for " + path);
            }
            var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
            {
                FileInfo = pathInfo,
                Path = path,
                Parent = Parent
            };
            // Gather child folder and files
            if (args.IsDirectory)
            {
                var isPhysicalRoot = args.IsPhysicalRoot;
                // When resolving the root, we need it's grandchildren (children of user views)
                var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
                args.FileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
                // Need to remove subpaths that may have been resolved from shortcuts
                // Example: if \\server\movies exists, then strip out \\server\movies\action
                if (isPhysicalRoot)
                {
                    var paths = args.FileSystemDictionary.Keys.ToList();
                    foreach (var subPath in paths
                        .Where(subPath => !subPath.EndsWith(":\\", StringComparison.OrdinalIgnoreCase) && paths.Any(i => subPath.StartsWith(i.TrimEnd(System.IO.Path.DirectorySeparatorChar) + System.IO.Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))))
                    {
                        Logger.Info("Ignoring duplicate path: {0}", subPath);
                        args.FileSystemDictionary.Remove(subPath);
                    }
                }
            }
            //update our dates
            EntityResolutionHelper.EnsureDates(FileSystem, this, args, false);
            IsOffline = false;
            return args;
        }
        /// 
        /// Some subclasses will stop resolving at a directory and point their Path to a file within. This will help ensure the on-demand resolve args are identical to the
        /// original ones.
        /// 
        /// true if [use parent path to create resolve args]; otherwise, false.
        [IgnoreDataMember]
        protected virtual bool UseParentPathToCreateResolveArgs
        {
            get
            {
                return false;
            }
        }
        /// 
        /// Gets or sets the name of the forced sort.
        /// 
        /// The name of the forced sort.
        public string ForcedSortName { get; set; }
        private string _sortName;
        /// 
        /// Gets or sets the name of the sort.
        /// 
        /// The name of the sort.
        [IgnoreDataMember]
        public string SortName
        {
            get
            {
                if (!string.IsNullOrEmpty(ForcedSortName))
                {
                    return ForcedSortName;
                }
                return _sortName ?? (_sortName = CreateSortName());
            }
        }
        /// 
        /// 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
            var sortable = Name.Trim().ToLower();
            sortable = ConfigurationManager.Configuration.SortRemoveCharacters.Aggregate(sortable, (current, search) => current.Replace(search.ToLower(), string.Empty));
            sortable = ConfigurationManager.Configuration.SortReplaceCharacters.Aggregate(sortable, (current, search) => current.Replace(search.ToLower(), " "));
            foreach (var search in ConfigurationManager.Configuration.SortRemoveWords)
            {
                var searchLower = search.ToLower();
                // Remove from beginning if a space follows
                if (sortable.StartsWith(searchLower + " "))
                {
                    sortable = sortable.Remove(0, searchLower.Length + 1);
                }
                // Remove from middle if surrounded by spaces
                sortable = sortable.Replace(" " + searchLower + " ", " ");
                // Remove from end if followed by a space
                if (sortable.EndsWith(" " + searchLower))
                {
                    sortable = sortable.Remove(sortable.Length - (searchLower.Length + 1));
                }
            }
            return sortable;
        }
        /// 
        /// Gets or sets the parent.
        /// 
        /// The parent.
        [IgnoreDataMember]
        public Folder Parent { get; set; }
        [IgnoreDataMember]
        public IEnumerable Parents
        {
            get
            {
                var parent = Parent;
                while (parent != null)
                {
                    yield return parent;
                    parent = parent.Parent;
                }
            }
        }
        /// 
        /// When the item first debuted. For movies this could be premiere date, episodes would be first aired
        /// 
        /// The premiere date.
        public DateTime? PremiereDate { get; set; }
        /// 
        /// Gets or sets the end date.
        /// 
        /// The end date.
        public DateTime? EndDate { get; set; }
        /// 
        /// Gets or sets the display type of the media.
        /// 
        /// The display type of the media.
        public string DisplayMediaType { get; set; }
        /// 
        /// Gets or sets the backdrop image paths.
        /// 
        /// The backdrop image paths.
        public List BackdropImagePaths { get; set; }
        /// 
        /// Gets or sets the backdrop image sources.
        /// 
        /// The backdrop image sources.
        public List ImageSources { get; set; }
        /// 
        /// Gets or sets the official rating.
        /// 
        /// The official rating.
        public string OfficialRating { get; set; }
        /// 
        /// Gets or sets the official rating description.
        /// 
        /// The official rating description.
        public string OfficialRatingDescription { get; set; }
        /// 
        /// Gets or sets the custom rating.
        /// 
        /// The custom rating.
        public string CustomRating { get; set; }
        /// 
        /// Gets or sets the overview.
        /// 
        /// The overview.
        public string Overview { get; set; }
        /// 
        /// Gets or sets the people.
        /// 
        /// The people.
        public List People { get; set; }
        /// 
        /// Override this if you need to combine/collapse person information
        /// 
        /// All people.
        [IgnoreDataMember]
        public virtual IEnumerable AllPeople
        {
            get { return People; }
        }
        [IgnoreDataMember]
        public virtual IEnumerable AllStudios
        {
            get { return Studios; }
        }
        [IgnoreDataMember]
        public virtual IEnumerable AllGenres
        {
            get { return Genres; }
        }
        /// 
        /// Gets or sets the studios.
        /// 
        /// The studios.
        public List Studios { get; set; }
        /// 
        /// Gets or sets the genres.
        /// 
        /// The genres.
        public List Genres { get; set; }
        /// 
        /// Gets or sets the home page URL.
        /// 
        /// The home page URL.
        public string HomePageUrl { get; set; }
        /// 
        /// Gets or sets the community rating.
        /// 
        /// The community rating.
        public float? CommunityRating { get; set; }
        /// 
        /// Gets or sets the community rating vote count.
        /// 
        /// The community rating vote count.
        public int? VoteCount { get; set; }
        /// 
        /// Gets or sets the run time ticks.
        /// 
        /// The run time ticks.
        public long? RunTimeTicks { get; set; }
        /// 
        /// Gets or sets the original run time ticks.
        /// 
        /// The original run time ticks.
        public long? OriginalRunTimeTicks { get; set; }
        /// 
        /// Gets or sets the production year.
        /// 
        /// The production year.
        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.
        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.
        public int? ParentIndexNumber { get; set; }
        [IgnoreDataMember]
        public virtual string OfficialRatingForComparison
        {
            get { return OfficialRating; }
        }
        [IgnoreDataMember]
        public virtual string CustomRatingForComparison
        {
            get { return CustomRating; }
        }
        /// 
        /// Loads local trailers from the file system
        /// 
        /// List{Video}.
        private IEnumerable LoadLocalTrailers()
        {
            ItemResolveArgs resolveArgs;
            try
            {
                resolveArgs = ResolveArgs;
                if (!resolveArgs.IsDirectory)
                {
                    return new List();
                }
            }
            catch (IOException ex)
            {
                Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
                return new List();
            }
            var files = new List();
            var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName);
            // Path doesn't exist. No biggie
            if (folder != null)
            {
                try
                {
                    files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
                }
                catch (IOException ex)
                {
                    Logger.ErrorException("Error loading trailers for {0}", ex, Name);
                }
            }
            // Support xbmc trailers (-trailer suffix on video file names)
            files.AddRange(resolveArgs.FileSystemChildren.Where(i =>
            {
                try
                {
                    if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
                    {
                        if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
                        {
                            return true;
                        }
                    }
                }
                catch (IOException ex)
                {
                    Logger.ErrorException("Error accessing path {0}", ex, i.FullName);
                }
                return false;
            }));
            return LibraryManager.ResolvePaths(files, null).Select(video =>
            {
                // Try to retrieve it from the db. If we don't find it, use the resolved version
                var dbItem = LibraryManager.RetrieveItem(video.Id) as Trailer;
                if (dbItem != null)
                {
                    dbItem.ResetResolveArgs(video.ResolveArgs);
                    video = dbItem;
                }
                return video;
            }).ToList();
        }
        /// 
        /// Loads the theme songs.
        /// 
        /// List{Audio.Audio}.
        private IEnumerable LoadThemeSongs()
        {
            ItemResolveArgs resolveArgs;
            try
            {
                resolveArgs = ResolveArgs;
                if (!resolveArgs.IsDirectory)
                {
                    return new List();
                }
            }
            catch (IOException ex)
            {
                Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
                return new List();
            }
            var files = new List();
            var folder = resolveArgs.GetFileSystemEntryByName(ThemeSongsFolderName);
            // Path doesn't exist. No biggie
            if (folder != null)
            {
                try
                {
                    files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
                }
                catch (IOException ex)
                {
                    Logger.ErrorException("Error loading theme songs for {0}", ex, Name);
                }
            }
            // Support plex/xbmc convention
            files.AddRange(resolveArgs.FileSystemChildren
                .Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsAudioFile(i.Name))
                );
            return LibraryManager.ResolvePaths(files, null).Select(audio =>
            {
                // Try to retrieve it from the db. If we don't find it, use the resolved version
                var dbItem = LibraryManager.RetrieveItem(audio.Id) as Audio.Audio;
                if (dbItem != null)
                {
                    dbItem.ResetResolveArgs(audio.ResolveArgs);
                    audio = dbItem;
                }
                return audio;
            }).ToList();
        }
        /// 
        /// Loads the video backdrops.
        /// 
        /// List{Video}.
        private IEnumerable