using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Naming.Common;
using Emby.Naming.ExternalFiles;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
{
    /// 
    /// Resolves external files for .
    /// 
    public abstract class MediaInfoResolver
    {
        /// 
        /// The  instance.
        /// 
        private readonly ExternalPathParser _externalPathParser;
        /// 
        /// The  instance.
        /// 
        private readonly IMediaEncoder _mediaEncoder;
        private readonly ILogger _logger;
        private readonly IFileSystem _fileSystem;
        /// 
        /// The  instance.
        /// 
        private readonly NamingOptions _namingOptions;
        /// 
        /// The  of the files this resolver should resolve.
        /// 
        private readonly DlnaProfileType _type;
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The logger.
        /// The localization manager.
        /// The media encoder.
        /// The file system.
        /// The  object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.
        /// The  of the parsed file.
        protected MediaInfoResolver(
            ILogger logger,
            ILocalizationManager localizationManager,
            IMediaEncoder mediaEncoder,
            IFileSystem fileSystem,
            NamingOptions namingOptions,
            DlnaProfileType type)
        {
            _logger = logger;
            _mediaEncoder = mediaEncoder;
            _fileSystem = fileSystem;
            _namingOptions = namingOptions;
            _type = type;
            _externalPathParser = new ExternalPathParser(namingOptions, localizationManager, _type);
        }
        /// 
        /// Retrieves the external streams for the provided video.
        /// 
        /// The  object to search external streams for.
        /// The stream index to start adding external streams at.
        /// The directory service to search for files.
        /// True if the directory service cache should be cleared before searching.
        /// The cancellation token.
        /// The external streams located.
        public async Task> GetExternalStreamsAsync(
            Video video,
            int startIndex,
            IDirectoryService directoryService,
            bool clearCache,
            CancellationToken cancellationToken)
        {
            if (!video.IsFileProtocol)
            {
                return Array.Empty();
            }
            var pathInfos = GetExternalFiles(video, directoryService, clearCache);
            if (!pathInfos.Any())
            {
                return Array.Empty();
            }
            var mediaStreams = new List();
            foreach (var pathInfo in pathInfos)
            {
                if (!pathInfo.Path.AsSpan().EndsWith(".strm", StringComparison.OrdinalIgnoreCase))
                {
                    try
                    {
                        var mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false);
                        if (mediaInfo.MediaStreams.Count == 1)
                        {
                            MediaStream mediaStream = mediaInfo.MediaStreams[0];
                            if ((mediaStream.Type == MediaStreamType.Audio && _type == DlnaProfileType.Audio)
                                || (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
                            {
                                mediaStream.Index = startIndex++;
                                mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
                                mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
                                mediaStream.IsHearingImpaired = pathInfo.IsHearingImpaired || mediaStream.IsHearingImpaired;
                                mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
                            }
                        }
                        else
                        {
                            foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
                            {
                                if ((mediaStream.Type == MediaStreamType.Audio && _type == DlnaProfileType.Audio)
                                    || (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
                                {
                                    mediaStream.Index = startIndex++;
                                    mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Error getting external streams from {Path}", pathInfo.Path);
                        continue;
                    }
                }
            }
            return mediaStreams.AsReadOnly();
        }
        /// 
        /// Returns the external file infos for the given video.
        /// 
        /// The  object to search external files for.
        /// The directory service to search for files.
        /// True if the directory service cache should be cleared before searching.
        /// The external file paths located.
        public IReadOnlyList GetExternalFiles(
            Video video,
            IDirectoryService directoryService,
            bool clearCache)
        {
            if (!video.IsFileProtocol)
            {
                return Array.Empty();
            }
            // Check if video folder exists
            string folder = video.ContainingFolderPath;
            if (!_fileSystem.DirectoryExists(folder))
            {
                return Array.Empty();
            }
            var files = directoryService.GetFilePaths(folder, clearCache, true).ToList();
            files.Remove(video.Path);
            var internalMetadataPath = video.GetInternalMetadataPath();
            if (_fileSystem.DirectoryExists(internalMetadataPath))
            {
                files.AddRange(directoryService.GetFilePaths(internalMetadataPath, clearCache, true));
            }
            if (files.Count == 0)
            {
                return Array.Empty();
            }
            var externalPathInfos = new List();
            ReadOnlySpan prefix = video.FileNameWithoutExtension;
            foreach (var file in files)
            {
                var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file.AsSpan());
                if (fileNameWithoutExtension.Length >= prefix.Length
                    && prefix.Equals(fileNameWithoutExtension[..prefix.Length], StringComparison.OrdinalIgnoreCase)
                    && (fileNameWithoutExtension.Length == prefix.Length || _namingOptions.MediaFlagDelimiters.Contains(fileNameWithoutExtension[prefix.Length])))
                {
                    var externalPathInfo = _externalPathParser.ParseFile(file, fileNameWithoutExtension[prefix.Length..].ToString());
                    if (externalPathInfo is not null)
                    {
                        externalPathInfos.Add(externalPathInfo);
                    }
                }
            }
            return externalPathInfos;
        }
        /// 
        /// Returns the media info of the given file.
        /// 
        /// The path to the file.
        /// The .
        /// The cancellation token to cancel operation.
        /// The media info for the given file.
        private Task GetMediaInfo(string path, DlnaProfileType type, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return _mediaEncoder.GetMediaInfo(
                new MediaInfoRequest
                {
                    MediaType = type,
                    MediaSource = new MediaSourceInfo
                    {
                        Path = path,
                        Protocol = MediaProtocol.File
                    }
                },
                cancellationToken);
        }
        /// 
        /// Merges path metadata into stream metadata.
        /// 
        /// The  object.
        /// The  object.
        /// The modified mediaStream.
        private MediaStream MergeMetadata(MediaStream mediaStream, ExternalPathParserResult pathInfo)
        {
            mediaStream.Path = pathInfo.Path;
            mediaStream.IsExternal = true;
            mediaStream.Title = string.IsNullOrEmpty(mediaStream.Title) ? (string.IsNullOrEmpty(pathInfo.Title) ? null : pathInfo.Title) : mediaStream.Title;
            mediaStream.Language = string.IsNullOrEmpty(mediaStream.Language) ? (string.IsNullOrEmpty(pathInfo.Language) ? null : pathInfo.Language) : mediaStream.Language;
            return mediaStream;
        }
    }
}