using MediaBrowser.Common.Logging;
using MediaBrowser.Model.DTO;
using MediaBrowser.Model.Entities;
using MediaBrowser.UI.Configuration;
using MediaBrowser.UI.Controller;
using MediaBrowser.UI.Playback;
using MediaBrowser.UI.Playback.ExternalPlayer;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Plugins.MpcHc
{
    /// 
    /// Class GenericExternalPlayer
    /// 
    [Export(typeof(BaseMediaPlayer))]
    public class MpcHcMediaPlayer : BaseExternalPlayer
    {
        /// 
        /// The state sync lock
        /// 
        private object stateSyncLock = new object();
        /// 
        /// The MPC HTTP interface resource pool
        /// 
        private SemaphoreSlim MpcHttpInterfaceResourcePool = new SemaphoreSlim(1, 1);
        /// 
        /// Gets or sets the HTTP interface cancellation token.
        /// 
        /// The HTTP interface cancellation token.
        private CancellationTokenSource HttpInterfaceCancellationTokenSource { get; set; }
        /// 
        /// Gets or sets a value indicating whether this instance has started playing.
        /// 
        /// true if this instance has started playing; otherwise, false.
        private bool HasStartedPlaying { get; set; }
        /// 
        /// Gets or sets the status update timer.
        /// 
        /// The status update timer.
        private Timer StatusUpdateTimer { get; set; }
        /// 
        /// Gets a value indicating whether this instance can monitor progress.
        /// 
        /// true if this instance can monitor progress; otherwise, false.
        protected override bool CanMonitorProgress
        {
            get
            {
                return true;
            }
        }
        /// 
        /// The _current position ticks
        /// 
        private long? _currentPositionTicks;
        /// 
        /// Gets the current position ticks.
        /// 
        /// The current position ticks.
        public override long? CurrentPositionTicks
        {
            get
            {
                return _currentPositionTicks;
            }
        }
        /// 
        /// The _current playlist index
        /// 
        private int _currentPlaylistIndex;
        /// 
        /// Gets the index of the current playlist.
        /// 
        /// The index of the current playlist.
        public override int CurrentPlaylistIndex
        {
            get
            {
                return _currentPlaylistIndex;
            }
        }
        /// 
        /// Gets the name.
        /// 
        /// The name.
        public override string Name
        {
            get { return "MpcHc"; }
        }
        /// 
        /// Gets a value indicating whether this instance can close automatically.
        /// 
        /// true if this instance can close automatically; otherwise, false.
        protected override bool CanCloseAutomatically
        {
            get
            {
                return true;
            }
        }
        /// 
        /// Determines whether this instance can play the specified item.
        /// 
        /// The item.
        /// true if this instance can play the specified item; otherwise, false.
        public override bool CanPlay(DtoBaseItem item)
        {
            return item.IsVideo || item.IsAudio;
        }
        /// 
        /// Gets the command arguments.
        /// 
        /// The items.
        /// The options.
        /// The player configuration.
        /// System.String.
        protected override string GetCommandArguments(List items, PlayOptions options, PlayerConfiguration playerConfiguration)
        {
            var formatString = "{0} /play /fullscreen /close";
            var firstItem = items[0];
            var startTicks = Math.Max(options.StartPositionTicks, 0);
            if (startTicks > 0 && firstItem.IsVideo && firstItem.VideoType.HasValue && firstItem.VideoType.Value == VideoType.Dvd)
            {
                formatString += " /dvdpos 1#" + TimeSpan.FromTicks(startTicks).ToString("hh\\:mm\\:ss");
            }
            else
            {
                formatString += " /start " + TimeSpan.FromTicks(startTicks).TotalMilliseconds;
            }
            return GetCommandArguments(items, formatString);
        }
        /// 
        /// Gets the path for command line.
        /// 
        /// The item.
        /// System.String.
        protected override string GetPathForCommandLine(DtoBaseItem item)
        {
            var path = base.GetPathForCommandLine(item);
            if (item.IsVideo && item.VideoType.HasValue)
            {
                if (item.VideoType.Value == VideoType.Dvd)
                {
                    // Point directly to the video_ts path
                    // Otherwise mpc will play any other media files that might exist in the dvd top folder (e.g. video backdrops).
                    var videoTsPath = Path.Combine(path, "video_ts");
                    if (Directory.Exists(videoTsPath))
                    {
                        path = videoTsPath;
                    }
                }
                if (item.VideoType.Value == VideoType.BluRay)
                {
                    // Point directly to the bdmv path
                    var bdmvPath = Path.Combine(path, "bdmv");
                    if (Directory.Exists(bdmvPath))
                    {
                        path = bdmvPath;
                    }
                }
            }
            return FormatPath(path);
        }
        /// 
        /// Formats the path.
        /// 
        /// The path.
        /// System.String.
        private string FormatPath(string path)
        {
            if (path.EndsWith(":\\", StringComparison.OrdinalIgnoreCase))
            {
                path = path.TrimEnd('\\');
            }
            return path;
        }
        /// 
        /// Called when [external player launched].
        /// 
        protected override void OnExternalPlayerLaunched()
        {
            base.OnExternalPlayerLaunched();
            ReloadStatusUpdateTimer();
        }
        /// 
        /// Reloads the status update timer.
        /// 
        private void ReloadStatusUpdateTimer()
        {
            DisposeStatusTimer();
            HttpInterfaceCancellationTokenSource = new CancellationTokenSource();
            StatusUpdateTimer = new Timer(OnStatusUpdateTimerStopped, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
        }
        /// 
        /// Called when [status update timer stopped].
        /// 
        /// The state.
        private async void OnStatusUpdateTimerStopped(object state)
        {
            try
            {
                var token = HttpInterfaceCancellationTokenSource.Token;
                using (var stream = await UIKernel.Instance.HttpManager.Get(StatusUrl, MpcHttpInterfaceResourcePool, token).ConfigureAwait(false))
                {
                    token.ThrowIfCancellationRequested();
                    using (var reader = new StreamReader(stream))
                    {
                        token.ThrowIfCancellationRequested();
                        var result = await reader.ReadToEndAsync().ConfigureAwait(false);
                        token.ThrowIfCancellationRequested();
                        ProcessStatusResult(result);
                    }
                }
            }
            catch (HttpRequestException ex)
            {
                Logger.ErrorException("Error connecting to MpcHc status interface", ex);
            }
            catch (OperationCanceledException)
            {
                // Manually cancelled by us
                Logger.Info("Status request cancelled");
            }
        }
        /// 
        /// Processes the status result.
        /// 
        /// The result.
        private async void ProcessStatusResult(string result)
        {
            // Sample result
            // OnStatus('test.avi', 'Playing', 5292, '00:00:05', 1203090, '00:20:03', 0, 100, 'C:\test.avi')
            // 5292 = position in ms
            // 00:00:05 = position
            // 1203090 = duration in ms
            // 00:20:03 = duration
            var quoteChar = result.IndexOf(", \"", StringComparison.OrdinalIgnoreCase) == -1 ? '\'' : '\"';
            // Strip off the leading "OnStatus(" and the trailing ")"
            result = result.Substring(result.IndexOf(quoteChar));
            result = result.Substring(0, result.LastIndexOf(quoteChar));
            // Strip off the filename at the beginning
            result = result.Substring(result.IndexOf(string.Format("{0}, {0}", quoteChar), StringComparison.OrdinalIgnoreCase) + 3);
            // Find the last index of ", '" so that we can extract and then strip off the file path at the end.
            var lastIndexOfSeparator = result.LastIndexOf(", " + quoteChar, StringComparison.OrdinalIgnoreCase);
            // Get the current playing file path
            var currentPlayingFile = result.Substring(lastIndexOfSeparator + 2).Trim(quoteChar);
            // Strip off the current playing file path
            result = result.Substring(0, lastIndexOfSeparator);
            var values = result.Split(',').Select(v => v.Trim().Trim(quoteChar)).ToList();
            var currentPositionTicks = TimeSpan.FromMilliseconds(double.Parse(values[1])).Ticks;
            //var currentDurationTicks = TimeSpan.FromMilliseconds(double.Parse(values[3])).Ticks;
            var playstate = values[0];
            var playlistIndex = GetPlaylistIndex(currentPlayingFile);
            if (playstate.Equals("stopped", StringComparison.OrdinalIgnoreCase))
            {
                if (HasStartedPlaying)
                {
                    await ClosePlayer().ConfigureAwait(false);
                }
            }
            else
            {
                lock (stateSyncLock)
                {
                    if (_currentPlaylistIndex != playlistIndex)
                    {
                        OnMediaChanged(_currentPlaylistIndex, _currentPositionTicks, playlistIndex);
                    }
                    _currentPositionTicks = currentPositionTicks;
                    _currentPlaylistIndex = playlistIndex;
                }
                if (playstate.Equals("playing", StringComparison.OrdinalIgnoreCase))
                {
                    HasStartedPlaying = true;
                    PlayState = PlayState.Playing;
                }
                else if (playstate.Equals("paused", StringComparison.OrdinalIgnoreCase))
                {
                    HasStartedPlaying = true;
                    PlayState = PlayState.Paused;
                }
            }
        }
        /// 
        /// Gets the index of the playlist.
        /// 
        /// The now playing path.
        /// System.Int32.
        private int GetPlaylistIndex(string nowPlayingPath)
        {
            for (var i = 0; i < Playlist.Count; i++)
            {
                var item = Playlist[i];
                var pathArg = GetPathForCommandLine(item);
                if (pathArg.Equals(nowPlayingPath, StringComparison.OrdinalIgnoreCase))
                {
                    return i;
                }
                if (item.VideoType.HasValue)
                {
                    if (item.VideoType.Value == VideoType.BluRay || item.VideoType.Value == VideoType.Dvd || item.VideoType.Value == VideoType.HdDvd)
                    {
                        if (nowPlayingPath.StartsWith(pathArg, StringComparison.OrdinalIgnoreCase))
                        {
                            return i;
                        }
                    }
                }
            }
            return -1;
        }
        /// 
        /// Called when [player stopped internal].
        /// 
        protected override void OnPlayerStoppedInternal()
        {
            HttpInterfaceCancellationTokenSource.Cancel();
            DisposeStatusTimer();
            _currentPositionTicks = null;
            _currentPlaylistIndex = 0;
            HasStartedPlaying = false;
            HttpInterfaceCancellationTokenSource = null;
            base.OnPlayerStoppedInternal();
        }
        /// 
        /// Disposes the status timer.
        /// 
        private void DisposeStatusTimer()
        {
            if (StatusUpdateTimer != null)
            {
                StatusUpdateTimer.Dispose();
            }
        }
        /// 
        /// Releases unmanaged and - optionally - managed resources.
        /// 
        /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
        protected override void Dispose(bool dispose)
        {
            if (dispose)
            {
                DisposeStatusTimer();
                MpcHttpInterfaceResourcePool.Dispose();
            }
            base.Dispose(dispose);
        }
        /// 
        /// Seeks the internal.
        /// 
        /// The position ticks.
        /// Task.
        protected override Task SeekInternal(long positionTicks)
        {
            var additionalParams = new Dictionary();
            var time = TimeSpan.FromTicks(positionTicks);
            var timeString = time.Hours + ":" + time.Minutes + ":" + time.Seconds;
            additionalParams.Add("position", timeString);
            return SendCommandToPlayer("-1", additionalParams);
        }
        /// 
        /// Pauses the internal.
        /// 
        /// Task.
        protected override Task PauseInternal()
        {
            return SendCommandToPlayer("888", new Dictionary());
        }
        /// 
        /// Uns the pause internal.
        /// 
        /// Task.
        protected override Task UnPauseInternal()
        {
            return SendCommandToPlayer("887", new Dictionary());
        }
        /// 
        /// Stops the internal.
        /// 
        /// Task.
        protected override Task StopInternal()
        {
            return SendCommandToPlayer("890", new Dictionary());
        }
        /// 
        /// Closes the player.
        /// 
        /// Task.
        protected Task ClosePlayer()
        {
            return SendCommandToPlayer("816", new Dictionary());
        }
        /// 
        /// Sends a command to MPC using the HTTP interface
        /// http://www.autohotkey.net/~specter333/MPC/HTTP%20Commands.txt
        /// 
        /// The command number.
        /// The additional params.
        /// Task.
        /// commandNumber
        private async Task SendCommandToPlayer(string commandNumber, Dictionary additionalParams)
        {
            if (string.IsNullOrEmpty(commandNumber))
            {
                throw new ArgumentNullException("commandNumber");
            }
            if (additionalParams == null)
            {
                throw new ArgumentNullException("additionalParams");
            }
            var url = CommandUrl + "?wm_command=" + commandNumber;
            url = additionalParams.Keys.Aggregate(url, (current, name) => current + ("&" + name + "=" + additionalParams[name]));
            Logger.Info("Sending command to MPC: " + url);
            try
            {
                using (var stream = await UIKernel.Instance.HttpManager.Get(url, MpcHttpInterfaceResourcePool, HttpInterfaceCancellationTokenSource.Token).ConfigureAwait(false))
                {
                }
            }
            catch (HttpRequestException ex)
            {
                Logger.ErrorException("Error connecting to MpcHc command interface", ex);
            }
            catch (OperationCanceledException)
            {
                // Manually cancelled by us
                Logger.Info("Command request cancelled");
            }
        }
        /// 
        /// Gets a value indicating whether this instance can pause.
        /// 
        /// true if this instance can pause; otherwise, false.
        public override bool CanPause
        {
            get
            {
                return true;
            }
        }
        /// 
        /// Gets the server name that the http interface will be running on
        /// 
        /// The HTTP server.
        private string HttpServer
        {
            get
            {
                return "localhost";
            }
        }
        /// 
        /// Gets the port that the web interface will be running on
        /// 
        /// The HTTP port.
        private string HttpPort
        {
            get
            {
                return "13579";
            }
        }
        /// 
        /// Gets the url of that will be called to for status
        /// 
        /// The status URL.
        private string StatusUrl
        {
            get
            {
                return "http://" + HttpServer + ":" + HttpPort + "/status.html";
            }
        }
        /// 
        /// Gets the url of that will be called to send commands
        /// 
        /// The command URL.
        private string CommandUrl
        {
            get
            {
                return "http://" + HttpServer + ":" + HttpPort + "/command.html";
            }
        }
    }
}