using SkmXmlApi;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using VideoNext;
using VideoNext.WindowsForms.Player;

namespace PlayerTestNET
{
    internal enum PlayerSource
    {
        Live,
        Archive,
        File
    }

    public partial class PlayerForm : Form
    {
        private Camera m_Camera;
        private SkmVideoPlayer m_Player;
        private PlayerSource m_Source = PlayerSource.Live;
        private string m_Url;
        private string m_FileName;
        private double[] m_Speeds = new double[] { 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 10.0 };
        private double m_Speed = 1.0;
        private string m_TimeFormat;

        public PlayerForm()
        {
            InitializeComponent();

            foreach (double speed in m_Speeds)
            {
                cbSpeed.Items.Add(string.Format("x{0}", speed));
            }
            cbSpeed.SelectedIndex = 3;
        }

        public void Show(Camera camera)
        {
            m_Camera = camera;

            Text = string.Format("[{0}] {1}", m_Camera.Id, m_Camera.Name);
            dtStartTime.Value = DateTime.Now.AddMinutes(-10);
            dtEndTime.Value = DateTime.Now.AddMinutes(-5);

            // An Skm session must be created first and login performed.
            SkmSession session = SkmSession.NewSkmSession(MainForm.ServerUrl);
            session.Login(MainForm.Username, MainForm.Password);

            // Get global settings and put to camera data to record this additional metadata in a local video file.
            var iac = IdentityAttributesCommand.NewIdentityAttributesCommand(session);
            m_TimeFormat = iac.TimeFormat;
            m_Camera.CameraData["SystemName"] = iac.Attributes.NAME;
            m_Camera.CameraData["SystemLocation"] = iac.Attributes.LOC;
            m_Camera.CameraData["ContactInfo"] = iac.Attributes.ADMIN_EMAIL;

            // Add player control to the container.
            m_Player = CreatePlayer();
            pnPlayer.Controls.Add(m_Player);

            UpdatePlayerSourceState();
            UpdateRecordingState();

            Show();
        }

        private void PlayerForm_Shown(object sender, EventArgs e)
        {
            Play();
        }

        private SkmVideoPlayer CreatePlayer()
        {
            var player = new SkmVideoPlayer();
            player.Transport = RtpTransport.Tcp;
            player.Dock = DockStyle.Fill;
            player.Location = new Point(0, 0);
            player.Size = new Size(320, 240);
            player.Name = "SkmVideoPlayer";
            player.CameraData = m_Camera.CameraData; // Copy camera data to player to record correct metadata in a local video file.
            player.TimeFormat = m_TimeFormat; // Set player time format to the format obtained from the global settings.
            player.TimeZone = m_Camera.TimeZone; // Set player time zone to the camera time zone.
            player.CurrentStateChanged += Player_CurrentStateChanged;
            player.CurrentTimeChanged += Player_CurrentTimeChanged;
            player.MessageLog += Player_MessageLog;
            player.StreamAdded += Player_StreamAdded;
            player.StreamRemoved += Player_StreamRemoved;
            player.RecordingStatusChanged += Player_RecordingStatusChanged;
            return player;
        }

        private void DestroyPlayer()
        {
            if (m_Player != null)
            {
                pnPlayer.Controls.Remove(m_Player);
                m_Player.CurrentStateChanged -= Player_CurrentStateChanged;
                m_Player.CurrentTimeChanged -= Player_CurrentTimeChanged;
                m_Player.MessageLog -= Player_MessageLog;
                m_Player.StreamAdded -= Player_StreamAdded;
                m_Player.StreamRemoved -= Player_StreamRemoved;
                m_Player.RecordingStatusChanged -= Player_RecordingStatusChanged;
                m_Player.Stop();
                m_Player.Dispose();
                m_Player = null;
            }
        }

        private string GetUrl()
        {
            // An Skm session must be created first and login performed.
            SkmSession session = SkmSession.NewSkmSession(MainForm.ServerUrl);
            session.Login(MainForm.Username, MainForm.Password);

            // Create authorization command to get Live or Archive URL.
            var ac = AuthorizationCommand.NewAuthorizationCommand(session);
            switch (m_Source)
            {
                case PlayerSource.Live:
                    return ac.GetLiveUrl(m_Camera.Id.ToString(), cbAnalytics.Checked);
                case PlayerSource.Archive:
                    return ac.GetArchiveUrl(m_Camera.Id.ToString(), dtStartTime.Value.ToUniversalTime(), dtEndTime.Value.ToUniversalTime(), cbSkipGaps.Checked);
                case PlayerSource.File:
                    return "file://" + m_FileName;
                default:
                    return null;
            }
        }

        private void Play()
        {
            m_Player.Stop();
            m_Url = GetUrl();
            m_Player.CameraData = m_Camera.CameraData; // Copy camera data to player to record correct metadata in a local video file.
            m_Player.TimeZone = m_Camera.TimeZone; // Set player time zone to the camera time zone.
            m_Player.Play(m_Url); // Start playback.
            m_Player.VisibleMetadata = cbMetadata.Checked ? new string[] { "UAS2", "UAS3", "UAS4", "UAS5", "UAS6", "UAS7" } : new string[] { };
            Text = m_Source != PlayerSource.File ? string.Format("[{0}] {1}", m_Camera.Id, m_Camera.Name) :
                string.Format("[{0}] {1}; File: {2}", m_Camera.Id, m_Camera.Name, Path.GetFileName(m_FileName));
        }

        private void Player_MessageLog(object sender, MessageLogEventArgs e)
        {
            Console.WriteLine(e.Message);
        }

        private void Player_CurrentTimeChanged(object sender, ValueChangedEventArgs<DateTime> e)
        {
            try
            {
                OnCurrentTimeChanged(e);
            }
            catch (ObjectDisposedException) { }
        }

        private void OnCurrentTimeChanged(ValueChangedEventArgs<DateTime> e)
        {
            if (InvokeRequired)
            {
                BeginInvoke((ThreadStart) delegate { OnCurrentTimeChanged(e); });
                return;
            }
            lbTime.Text = e.NewValue.ToLocalTime().ToString("dd/MM/yy hh:mm:ss");
        }

        private void Player_CurrentStateChanged(object sender, StreamStateChangedEventArgs e)
        {
            OnCurrentStateChanged(e);
        }

        private void OnCurrentStateChanged(StreamStateChangedEventArgs e)
        {
            if (InvokeRequired)
            {
                BeginInvoke((ThreadStart) delegate { OnCurrentStateChanged(e); });
                return;
            }

            Console.WriteLine(string.Format("Player state changed from {0} to {1}", e.OldValue.ToString(), e.NewValue.ToString()));
            lbPlayerStatus.Text = e.NewValue.ToString();

            bool hasError = e.ErrorCode != 0 || !string.IsNullOrEmpty(e.ErrorMessage);
            if (hasError)
            {
                Console.WriteLine(string.Format("Error code: {0}, message: {1}", e.ErrorCode, e.ErrorMessage));
                lbError.Text = string.Format("[{0}] {1}", e.ErrorCode, e.ErrorMessage);
            }
            lbErrorText.Visible = hasError;
            lbError.Visible = hasError;

            switch (e.NewValue)
            {
                case StreamState.Idle:
                case StreamState.Stopped:
                    btPlay.Enabled = true;
                    btStop.Enabled = false;
                    break;
                case StreamState.Opening:
                case StreamState.Opened:
                case StreamState.PlayFromServer:
                case StreamState.PlayFromBuffer:
                case StreamState.Buffering:
                    btPlay.Enabled = false;
                    btStop.Enabled = true;
                    break;
            }

            UpdatePausedState();
            UpdateRecordingState();

            bool wasPlaying = MediaReciever.IsPlayingState(e.OldValue);

            if (m_Source != PlayerSource.Live && !wasPlaying && m_Player.IsPlaying)
            {
                SetPlaybackSpeed(); // Restore playback speed selected by a user after playback restart.
            }
        }

        private void Player_StreamAdded(object sender, StreamEventArgs e)
        {
            Console.WriteLine("Stream added. Type: {0}, Id: {1}, Duration: {2} ms, RTP Codec: {3}", e.Info.Type.ToString(), e.Info.StreamId, e.Info.Duration, e.Info.RtpCodec);

            // Get video stream metadata and put it to player. Video stream metadata are available for a local recorded file playback.
            if (e.Info.Metadata != null && e.Info.Metadata.Count > 0)
            {
                Console.WriteLine("Metadata found.");
                foreach (KeyValuePair<string, string> kvp in e.Info.Metadata)
                {
                    Console.WriteLine("{0}: {1}", kvp.Key, kvp.Value);
                }

                m_Player.CameraData = e.Info.Metadata;
                string timeZone;
                if (e.Info.Metadata.TryGetValue("TimeZone", out timeZone) && !string.IsNullOrEmpty(timeZone))
                {
                    m_Player.TimeZone = timeZone; // Set player time zone to the recorded file time zone.
                    Console.WriteLine("Player time zone set to {0}", timeZone);
                }
                Console.WriteLine();
            }
        }

        private void Player_StreamRemoved(object sender, StreamEventArgs e)
        {
            Console.WriteLine("Stream removed. Type: {0}, Id: {1}, Duration: {2} ms, RTP Codec: {3}", e.Info.Type.ToString(), e.Info.StreamId, e.Info.Duration, e.Info.RtpCodec);
        }

        private void Player_RecordingStatusChanged(object sender, RecordingStatusEventArgs e)
        {
            UpdateRecordingState(e);
        }

        private void SetPlaybackSpeed()
        {
            Task.Run(() =>
            {
                Thread.Sleep(500);
                m_Player?.SetPlaybackSpeed(m_Speed);
            });
        }

        private void UpdatePlayerSourceState()
        {
            btArchive.Enabled = m_Source != PlayerSource.Archive;
            btLive.Enabled = m_Source != PlayerSource.Live;
            cbSpeed.Enabled = m_Source != PlayerSource.Live;
            lbLiveArchive.Text = m_Source.ToString();
        }

        private void UpdatePausedState()
        {
            btPauseResume.Enabled = m_Player.IsPlaying;
            btPauseResume.Text = m_Player.IsPaused ? "Resume" : "Pause";
        }

        private void UpdateRecordingState(RecordingStatusEventArgs ea = null)
        {
            if (InvokeRequired)
            {
                BeginInvoke((ThreadStart) delegate { UpdateRecordingState(ea); });
                return;
            }

            statusRecording.Visible = m_Player.RecordingStatus != RecordingStatus.Idle;
            lbRecStatus.Text = m_Player.RecordingStatus.ToString();
            if (ea != null)
            {
                Console.WriteLine("Recording status changed to {0}", ea.Status.ToString());
                if (!string.IsNullOrEmpty(ea.ErrorMessage))
                {
                    Console.WriteLine("Recording error: {0}", ea.ErrorMessage);
                }
                pbRecProgress.Value = ea.Progress;
                pbRecProgress.Visible = ea.Progress != 0;
                lbRecErrorText.Visible = lbRecError.Visible = !string.IsNullOrEmpty(ea.ErrorMessage);
                lbRecError.Text = ea.ErrorMessage;
            }
            btStartRecord.Enabled = m_Player.IsPlaying && !m_Player.IsPaused &&
                m_Player.RecordingStatus != RecordingStatus.Started && m_Player.RecordingStatus != RecordingStatus.Ending;
            btStopRecord.Enabled = m_Player.RecordingStatus == RecordingStatus.Started;
        }

        private void btArchive_Click(object sender, EventArgs e)
        {
            m_Source = PlayerSource.Archive;
            UpdatePlayerSourceState();
            Play();
        }

        private void btLive_Click(object sender, EventArgs e)
        {
            m_Source = PlayerSource.Live;
            UpdatePlayerSourceState();
            Play();
        }

        private void btFile_Click(object sender, EventArgs e)
        {
            using (var openFileDlg = new OpenFileDialog())
            {
                openFileDlg.Filter = "QuickTime Video (*.mov)|*.mov";
                if (openFileDlg.ShowDialog() != DialogResult.OK)
                {
                    return;
                }
                m_FileName = openFileDlg.FileName;
                m_Source = PlayerSource.File;
                UpdatePlayerSourceState();
                Play();
            }
        }

        private void btPlay_Click(object sender, EventArgs e)
        {
            Play();
        }

        private void btPauseResume_Click(object sender, EventArgs e)
        {
            if (m_Player.IsPlaying)
            {
                if (!m_Player.IsPaused)
                {
                    m_Player.Pause();
                }
                else
                {
                    m_Player.Resume(PlaybackDirection.Forwards);
                }
            }
            UpdatePausedState();
            UpdateRecordingState();
        }

        private void btStop_Click(object sender, EventArgs e)
        {
            m_Player.Stop();
        }

        private void btSnapshot_Click(object sender, EventArgs e)
        {
            m_Player.SaveSnapshotUI();
        }

        private void btSaveVideo_Click(object sender, EventArgs e)
        {
            using (SaveVideoDialog dialog = new SaveVideoDialog())
            {
                dialog.ShowSaveDialog(m_Camera.Id.ToString());
            }
        }

        private void PlayerForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            DestroyPlayer();
        }

        private void cbSpeed_SelectedIndexChanged(object sender, EventArgs e)
        {
            m_Speed = m_Speeds[cbSpeed.SelectedIndex];
            SetPlaybackSpeed();
        }

        private void cbOnScreenInfo_CheckedChanged(object sender, EventArgs e)
        {
            m_Player.OnScreenDisplayPosition = cbOnScreenInfo.Checked ? OnScreenDisplayPosition.Bottom : OnScreenDisplayPosition.None;
        }

        private void btStartRecord_Click(object sender, EventArgs e)
        {
            m_Player.StartRecordingUI();
        }

        private void btStopRecord_Click(object sender, EventArgs e)
        {
            m_Player.EndRecording();
        }
    }
}
