using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Streaming
{
    /// 
    /// Class BaseHlsPlaylistHandler
    /// 
    /// The type of the T base item type.
    public abstract class BaseHlsPlaylistHandler : BaseStreamingHandler
        where TBaseItemType : BaseItem, IHasMediaStreams, new()
    {
        /// 
        /// Gets the audio arguments.
        /// 
        /// System.String.
        protected abstract string GetAudioArguments();
        /// 
        /// Gets the video arguments.
        /// 
        /// System.String.
        protected abstract string GetVideoArguments();
        /// 
        /// Gets the type of the transcoding job.
        /// 
        /// The type of the transcoding job.
        protected override TranscodingJobType TranscodingJobType
        {
            get { return TranscodingJobType.Hls; }
        }
        /// 
        /// This isn't needed because we're going to override the whole flow using ProcessRequest
        /// 
        /// The stream.
        /// The response info.
        /// Length of the content.
        /// Task.
        /// 
        protected override Task WriteResponseToOutputStream(Stream stream, ResponseInfo responseInfo, long? contentLength)
        {
            throw new NotImplementedException();
        }
        /// 
        /// Gets the segment file extension.
        /// 
        /// The segment file extension.
        protected abstract string SegmentFileExtension { get; }
        /// 
        /// Processes the request.
        /// 
        /// The CTX.
        /// Task.
        public override async Task ProcessRequest(HttpListenerContext ctx)
        {
            HttpListenerContext = ctx;
            var playlist = OutputFilePath;
            var isPlaylistNewlyCreated = false;
            // If the playlist doesn't already exist, startup ffmpeg
            if (!File.Exists(playlist))
            {
                isPlaylistNewlyCreated = true;
                await StartFFMpeg(playlist).ConfigureAwait(false);
            }
            else
            {
                Plugin.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
            }
            // Get the current playlist text and convert to bytes
            var playlistText = await GetPlaylistFileText(playlist, isPlaylistNewlyCreated).ConfigureAwait(false);
            var content = Encoding.UTF8.GetBytes(playlistText);
            var stream = new MemoryStream(content);
            try
            {
                // Dump the stream off to the static file handler to serve statically
                await new StaticFileHandler(Kernel) { ContentType = MimeTypes.GetMimeType("playlist.m3u8"), SourceStream = stream }.ProcessRequest(ctx);
            }
            finally
            {
                Plugin.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
            }
        }
        /// 
        /// Gets the current playlist text
        /// 
        /// The path to the playlist
        /// Whether or not we should wait until it contains three segments
        /// Task{System.String}.
        private async Task GetPlaylistFileText(string playlist, bool waitForMinimumSegments)
        {
            string fileText;
            while (true)
            {
                // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
                using (var fileStream = new FileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
                {
                    using (var reader = new StreamReader(fileStream))
                    {
                        fileText = await reader.ReadToEndAsync().ConfigureAwait(false);
                    }
                }
                if (!waitForMinimumSegments || CountStringOccurrences(fileText, "#EXTINF:") >= 3)
                {
                    break;
                }
                await Task.Delay(25).ConfigureAwait(false);
            }
            // The segement paths within the playlist are phsyical, so strip that out to make it relative
            fileText = fileText.Replace(Path.GetDirectoryName(playlist) + Path.DirectorySeparatorChar, string.Empty);
            // Even though we specify target duration of 9, ffmpeg seems unable to keep all segments under that amount
            fileText = fileText.Replace("#EXT-X-TARGETDURATION:9", "#EXT-X-TARGETDURATION:10");
            // It's considered live while still encoding (EVENT). Once the encoding has finished, it's video on demand (VOD).
            var playlistType = fileText.IndexOf("#EXT-X-ENDLIST", StringComparison.OrdinalIgnoreCase) == -1 ? "EVENT" : "VOD";
            // Add event type at the top
            fileText = fileText.Replace("#EXT-X-ALLOWCACHE", "#EXT-X-PLAYLIST-TYPE:" + playlistType + Environment.NewLine + "#EXT-X-ALLOWCACHE");
            return fileText;
        }
        /// 
        /// Count occurrences of strings.
        /// 
        /// The text.
        /// The pattern.
        /// System.Int32.
        private static int CountStringOccurrences(string text, string pattern)
        {
            // Loop through all instances of the string 'text'.
            var count = 0;
            var i = 0;
            while ((i = text.IndexOf(pattern, i, StringComparison.OrdinalIgnoreCase)) != -1)
            {
                i += pattern.Length;
                count++;
            }
            return count;
        }
        /// 
        /// Gets all command line arguments to pass to ffmpeg
        /// 
        /// The playlist output path
        /// The iso mount.
        /// System.String.
        protected override string GetCommandLineArguments(string outputPath, IIsoMount isoMount)
        {
            var segmentOutputPath = Path.GetDirectoryName(outputPath);
            var segmentOutputName = HlsSegmentHandler.SegmentFilePrefix + Path.GetFileNameWithoutExtension(outputPath);
            segmentOutputPath = Path.Combine(segmentOutputPath, segmentOutputName + "%03d." + SegmentFileExtension.TrimStart('.'));
            var probeSize = Kernel.FFMpegManager.GetProbeSizeArgument(LibraryItem);
            return string.Format("{0} {1} -i {2}{3} -threads 0 {4} {5} {6} -f ssegment -segment_list_flags +live -segment_time 9 -segment_list \"{7}\" \"{8}\"",
                probeSize,
                FastSeekCommandLineParameter,
                GetInputArgument(isoMount),
                SlowSeekCommandLineParameter,
                MapArgs,
                GetVideoArguments(),
                GetAudioArguments(),
                outputPath,
                segmentOutputPath
                ).Trim();
        }
        /// 
        /// Deletes the partial stream files.
        /// 
        /// The playlist file path.
        protected override void DeletePartialStreamFiles(string playlistFilePath)
        {
            var directory = Path.GetDirectoryName(playlistFilePath);
            var name = Path.GetFileNameWithoutExtension(playlistFilePath);
            var filesToDelete = Directory.EnumerateFiles(directory, "*", SearchOption.TopDirectoryOnly)
                .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1)
                .ToList();
            foreach (var file in filesToDelete)
            {
                try
                {
                    //Logger.Info("Deleting HLS file {0}", file);
                    File.Delete(file);
                }
                catch (IOException ex)
                {
                    //Logger.ErrorException("Error deleting HLS file {0}", ex, file);
                }
            }
        }
    }
}