using MediaBrowser.Common.Extensions;
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.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Entities
{
    /// 
    /// Class BaseItem
    /// 
    public abstract class BaseItem : IHasProviderIds
    {
        protected BaseItem()
        {
            Genres = new List();
            TrailerUrls = new List();
            Studios = new List();
            People = new List();
            Taglines = new List();
            ScreenshotImagePaths = new List();
            BackdropImagePaths = new List();
            ProductionLocations = new List();
            Images = new Dictionary();
            ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
            Tags = new List();
            ThemeSongIds = new List();
            ThemeVideoIds = new List();
            LocalTrailerIds = new List();
        }
        /// 
        /// The trailer folder name
        /// 
        public const string TrailerFolderName = "trailers";
        public const string ThemeSongsFolderName = "theme-music";
        public const string ThemeVideosFolderName = "backdrops";
        private string _name;
        /// 
        /// Gets or sets the name.
        /// 
        /// The name.
        public virtual string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
                // lazy load this again
                _sortName = null;
            }
        }
        /// 
        /// Gets or sets the id.
        /// 
        /// The id.
        public virtual Guid Id { get; set; }
        /// 
        /// Gets or sets the path.
        /// 
        /// The path.
        public virtual string Path { get; set; }
        /// 
        /// Gets or sets the type of the location.
        /// 
        /// The type of the location.
        public virtual LocationType LocationType
        {
            get
            {
                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 virtual 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; }
        /// 
        /// 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; }
        /// 
        /// 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.
        [IgnoreDataMember]
        public virtual bool DontFetchMeta
        {
            get
            {
                if (Path != null)
                {
                    return Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1;
                }
                return false;
            }
        }
        /// 
        /// Determines whether the item has a saved local image of the specified name (jpg or png).
        /// 
        /// The name.
        /// true if [has local image] [the specified item]; otherwise, false.
        /// name
        public bool HasLocalImage(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }
            return ResolveArgs.ContainsMetaFileByName(name + ".jpg") ||
                ResolveArgs.ContainsMetaFileByName(name + ".png");
        }
        /// 
        /// Should be overridden to return the proper folder where metadata lives
        /// 
        /// The meta location.
        [IgnoreDataMember]
        public virtual string MetaLocation
        {
            get
            {
                return Path ?? "";
            }
        }
        /// 
        /// The _provider data
        /// 
        private Dictionary _providerData;
        /// 
        /// Holds persistent data for providers like last refresh date.
        /// Providers can use this to determine if they need to refresh.
        /// The BaseProviderInfo class can be extended to hold anything a provider may need.
        /// Keyed by a unique provider ID.
        /// 
        /// The provider data.
        public Dictionary ProviderData
        {
            get
            {
                return _providerData ?? (_providerData = new Dictionary());
            }
            set
            {
                _providerData = value;
            }
        }
        /// 
        /// The _file system stamp
        /// 
        private Guid? _fileSystemStamp;
        /// 
        /// Gets a directory stamp, in the form of a string, that can be used for
        /// comparison purposes to determine if the file system entries for this item have changed.
        /// 
        /// The file system stamp.
        [IgnoreDataMember]
        public Guid FileSystemStamp
        {
            get
            {
                if (!_fileSystemStamp.HasValue)
                {
                    _fileSystemStamp = GetFileSystemStamp();
                }
                return _fileSystemStamp.Value;
            }
        }
        /// 
        /// Gets the type of the media.
        /// 
        /// The type of the media.
        [IgnoreDataMember]
        public virtual string MediaType
        {
            get
            {
                return null;
            }
        }
        /// 
        /// Gets a directory stamp, in the form of a string, that can be used for
        /// comparison purposes to determine if the file system entries for this item have changed.
        /// 
        /// Guid.
        private Guid GetFileSystemStamp()
        {
            // If there's no path or the item is a file, there's nothing to do
            if (LocationType != LocationType.FileSystem || !ResolveArgs.IsDirectory)
            {
                return Guid.Empty;
            }
            var sb = new StringBuilder();
            // Record the name of each file 
            // Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order
            foreach (var file in ResolveArgs.FileSystemChildren
                .Where(i => !i.Attributes.HasFlag(FileAttributes.System))
                .OrderBy(f => f.Name))
            {
                sb.Append(file.Name);
            }
            foreach (var file in ResolveArgs.MetadataFiles.OrderBy(f => f.Name))
            {
                sb.Append(file.Name);
            }
            return sb.ToString().GetMD5();
        }
        /// 
        /// The _resolve args
        /// 
        private ItemResolveArgs _resolveArgs;
        /// 
        /// The _resolve args initialized
        /// 
        private bool _resolveArgsInitialized;
        /// 
        /// The _resolve args sync lock
        /// 
        private object _resolveArgsSyncLock = new object();
        /// 
        /// 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)
        /// Use ResolveArgs.FileSystemDictionary to check for the existence of files instead of File.Exists
        /// 
        /// The resolve args.
        [IgnoreDataMember]
        public ItemResolveArgs ResolveArgs
        {
            get
            {
                try
                {
                    LazyInitializer.EnsureInitialized(ref _resolveArgs, ref _resolveArgsInitialized, ref _resolveArgsSyncLock, () => CreateResolveArgs());
                }
                catch (IOException ex)
                {
                    Logger.ErrorException("Error creating resolve args for ", ex, Path);
                    throw;
                }
                return _resolveArgs;
            }
            set
            {
                _resolveArgs = value;
                _resolveArgsInitialized = value != null;
                // Null this out so that it can be lazy loaded again
                _fileSystemStamp = null;
            }
        }
        /// 
        /// Resets the resolve args.
        /// 
        /// The path info.
        public void ResetResolveArgs(FileSystemInfo pathInfo)
        {
            ResolveArgs = CreateResolveArgs(pathInfo);
        }
        /// 
        /// 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;
            // non file-system entries will not have a path
            if (LocationType != LocationType.FileSystem || string.IsNullOrEmpty(path))
            {
                return new ItemResolveArgs(ConfigurationManager.ApplicationPaths);
            }
            if (UseParentPathToCreateResolveArgs)
            {
                path = System.IO.Path.GetDirectoryName(path);
            }
            pathInfo = pathInfo ?? 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)
            {
                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, Logger, flattenFolderDepth: flattenFolderDepth, args: args, resolveShortcuts: isPhysicalRoot || args.IsVf);
            }
            //update our dates
            EntityResolutionHelper.EnsureDates(this, args);
            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
            {
                return ForcedSortName ?? _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; }
        /// 
        /// Gets the collection folder parent.
        /// 
        /// The collection folder parent.
        [IgnoreDataMember]
        public Folder CollectionFolder
        {
            get
            {
                if (this is AggregateFolder)
                {
                    return null;
                }
                if (IsFolder)
                {
                    var iCollectionFolder = this as ICollectionFolder;
                    if (iCollectionFolder != null)
                    {
                        return (Folder)this;
                    }
                }
                var parent = Parent;
                while (parent != null)
                {
                    var iCollectionFolder = parent as ICollectionFolder;
                    if (iCollectionFolder != null)
                    {
                        return parent;
                    }
                    parent = parent.Parent;
                }
                return null;
            }
        }
        /// 
        /// 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 virtual string DisplayMediaType { get; set; }
        /// 
        /// Gets or sets the backdrop image paths.
        /// 
        /// The backdrop image paths.
        public List BackdropImagePaths { get; set; }
        /// 
        /// Gets or sets the screenshot image paths.
        /// 
        /// The screenshot image paths.
        public List ScreenshotImagePaths { get; set; }
        /// 
        /// Gets or sets the official rating.
        /// 
        /// The official rating.
        public virtual string OfficialRating { get; set; }
        /// 
        /// Gets or sets the custom rating.
        /// 
        /// The custom rating.
        public virtual string CustomRating { get; set; }
        /// 
        /// Gets or sets the language.
        /// 
        /// The language.
        public string Language { get; set; }
        /// 
        /// Gets or sets the overview.
        /// 
        /// The overview.
        public string Overview { get; set; }
        /// 
        /// Gets or sets the taglines.
        /// 
        /// The taglines.
        public List Taglines { get; set; }
        /// 
        /// Gets or sets the people.
        /// 
        /// The people.
        public List People { get; set; }
        /// 
        /// Gets or sets the tags.
        /// 
        /// The tags.
        public List Tags { get; set; }
        /// 
        /// Override this if you need to combine/collapse person information
        /// 
        /// All people.
        [IgnoreDataMember]
        public virtual IEnumerable AllPeople
        {
            get { return People; }
        }
        /// 
        /// Gets or sets the studios.
        /// 
        /// The studios.
        public virtual List Studios { get; set; }
        /// 
        /// Gets or sets the genres.
        /// 
        /// The genres.
        public virtual List Genres { get; set; }
        /// 
        /// Gets or sets the home page URL.
        /// 
        /// The home page URL.
        public string HomePageUrl { get; set; }
        /// 
        /// Gets or sets the budget.
        /// 
        /// The budget.
        public double? Budget { get; set; }
        /// 
        /// Gets or sets the revenue.
        /// 
        /// The revenue.
        public double? Revenue { get; set; }
        /// 
        /// Gets or sets the production locations.
        /// 
        /// The production locations.
        public List ProductionLocations { get; set; }
        /// 
        /// Gets or sets the critic rating.
        /// 
        /// The critic rating.
        public float? CriticRating { get; set; }
        /// 
        /// Gets or sets the critic rating summary.
        /// 
        /// The critic rating summary.
        public string CriticRatingSummary { get; set; }
        /// 
        /// Gets or sets the community rating.
        /// 
        /// The community rating.
        public float? CommunityRating { 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 aspect ratio.
        /// 
        /// The aspect ratio.
        public string AspectRatio { get; set; }
        /// 
        /// Gets or sets the production year.
        /// 
        /// The production year.
        public virtual 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; }
        public List ThemeSongIds { get; set; }
        public List ThemeVideoIds { get; set; }
        public List LocalTrailerIds { get; set; }
        /// 
        /// Loads local trailers from the file system
        /// 
        /// List{Video}.
        private IEnumerable LoadLocalTrailers()
        {
            if (LocationType != LocationType.FileSystem)
            {
                return new List();
            }
            ItemResolveArgs resolveArgs;
            try
            {
                resolveArgs = ResolveArgs;
            }
            catch (IOException ex)
            {
                Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
                return new List();
            }
            if (!resolveArgs.IsDirectory)
            {
                return new List();
            }
            var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName);
            // Path doesn't exist. No biggie
            if (folder == null)
            {
                return new List();
            }
            IEnumerable files;
            try
            {
                files = new DirectoryInfo(folder.FullName).EnumerateFiles();
            }
            catch (IOException ex)
            {
                Logger.ErrorException("Error loading trailers for {0}", ex, Name);
                return new List();
            }
            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.ResolveArgs = video.ResolveArgs;
                    video = dbItem;
                }
                return video;
            }).ToList();
        }
        /// 
        /// Loads the theme songs.
        /// 
        /// List{Audio.Audio}.
        private IEnumerable LoadThemeSongs()
        {
            if (LocationType != LocationType.FileSystem)
            {
                return new List();
            }
            ItemResolveArgs resolveArgs;
            try
            {
                resolveArgs = ResolveArgs;
            }
            catch (IOException ex)
            {
                Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
                return new List();
            }
            if (!resolveArgs.IsDirectory)
            {
                return new List();
            }
            var folder = resolveArgs.GetFileSystemEntryByName(ThemeSongsFolderName);
            // Path doesn't exist. No biggie
            if (folder == null)
            {
                return new List();
            }
            IEnumerable files;
            try
            {
                files = new DirectoryInfo(folder.FullName).EnumerateFiles();
            }
            catch (IOException ex)
            {
                Logger.ErrorException("Error loading theme songs for {0}", ex, Name);
                return new List();
            }
            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.ResolveArgs = audio.ResolveArgs;
                    audio = dbItem;
                }
                return audio;
            }).ToList();
        }
        /// 
        /// Loads the video backdrops.
        /// 
        /// List{Video}.
        private IEnumerable