using FoxTube.Controls;
using FoxTube.Controls.Adverts;
using FoxTube.Controls.Player;
using Google.Apis.YouTube.v3.Data;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Windows.ApplicationModel.Resources;
using Windows.Foundation;
using Windows.Graphics.Display;
using Windows.Media.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using YoutubeExplode;
using YoutubeExplode.Models.ClosedCaptions;
using YoutubeExplode.Models.MediaStreams;

namespace FoxTube
{
    public delegate void MinimodeChangedEventHandler(object sender, bool isOn);

    public enum PlayerDisplayState { Normal, Minimized, Compact }

    public class QualityComparer : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            string[] xArr = x.Split('p');
            string[] yArr = y.Split('p');

            int qualityA = int.Parse(xArr[0]);
            int qualityB = int.Parse(yArr[0]);
            int framerateA = 30;
            int framerateB = 30;

            if (!string.IsNullOrWhiteSpace(xArr[1]))
                framerateA = int.Parse(xArr[1]);
            if (!string.IsNullOrWhiteSpace(yArr[1]))
                framerateB = int.Parse(yArr[1]);

            if (qualityA > qualityB)
                return 1;
            else if (qualityA < qualityB)
                return -1;
            else
                return framerateA - framerateB > 0 ? 1 : -1;
        }
    }

    /// <summary>
    /// Custom controls for media player. MARKUP IS IN **Themes/Generic.xaml**!!!
    /// </summary>
    public sealed class PlayerControls : MediaTransportControls
    {
        public event RoutedEventHandler CloseRequested;
        public event MinimodeChangedEventHandler MiniModeChanged;
        public event RoutedEventHandler NextRequested;

        #region Controls variables
        Button minimize;
        Button close;
        Button miniview;
        Button play;
        Button next;
        Button volumeMenu;
        Button mute;
        Button live;
        Button fwd;
        Button bwd;
        Button captionsMenu;
        Button drag;

        TextBlock title;
        TextBlock channel;
        TextBlock elapsed;
        TextBlock remain;

        Slider volume;
        Slider seek;
        Slider playbackSpeed;
        ProgressBar seekIndicator;

        ComboBox captions;
        ComboBox quality;

        ToggleSwitch captionsSwitch;

        StackPanel rightFooter;
        StackPanel leftFooter;
        StackPanel rightHeader;
        StackPanel centerStack;
        Grid header;
        Grid footer;
        Grid center;
        Grid centerTrigger;
        #endregion

        PlayerDisplayState State { get; set; } = PlayerDisplayState.Normal;

        public MediaElement Player { get; set; }
        public PlayerAdvert Advert;
        public LiveCaptions Caption;

        TimeSpan timecodeBackup;
        bool needUpdateTimecode = false;

        public Video Meta { get; set; }

        public IReadOnlyList<ClosedCaptionTrackInfo> ClosedCaptions { get; set; }
        public MediaStreamInfoSet MediaStreams { get; set; }

        Queue<Action> queue = new Queue<Action>();
        bool isReady = false;

        public PlayerControls()
        {
            DefaultStyleKey = typeof(PlayerControls);
        }

        protected override void OnApplyTemplate()
        {
            AssignControls();

            isReady = true;

            minimize.Click += Minimize_Click;
            close.Click += Close_Click;
            miniview.Click += Miniview_Click;

            next.Click += Next_Click;
            volume.ValueChanged += Volume_ValueChanged;
            playbackSpeed.ValueChanged += PlaybackSpeed_ValueChanged;
            live.Click += Live_Click;

            captionsSwitch.Toggled += CaptionsSwitch_Toggled;
            captions.SelectionChanged += Captions_SelectionChanged;
            quality.SelectionChanged += Quality_SelectionChanged;
            seek.ValueChanged += Seek_ValueChanged;

            Player.Tapped += (s, e) =>
            {
                Rect view = new Rect(0, 0, centerTrigger.ActualWidth, centerTrigger.ActualHeight);
                Point p = e.GetPosition(centerTrigger);

                if (!view.Contains(p) || e.PointerDeviceType != Windows.Devices.Input.PointerDeviceType.Mouse || State != PlayerDisplayState.Normal)
                    return;

                if (Player.CurrentState == Windows.UI.Xaml.Media.MediaElementState.Playing)
                    Player.Pause();
                else if (Player.CurrentState == Windows.UI.Xaml.Media.MediaElementState.Paused)
                    Player.Play();
            };

            if (queue.Count > 0)
                foreach (Action i in queue)
                    i();
            
            base.OnApplyTemplate();
        }

        private void PlaybackSpeed_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
        {
            Player.PlaybackRate = playbackSpeed.Value;
        }

        void AssignControls()
        {
            minimize = GetTemplateChild("MinimizeButton") as Button;
            close = GetTemplateChild("CloseButton") as Button;
            miniview = GetTemplateChild("CompactOverlayButton") as Button;
            play = GetTemplateChild("PlayPauseButton") as Button;
            next = GetTemplateChild("NextButton") as Button;
            volumeMenu = GetTemplateChild("VolumeMenuButton") as Button;
            mute = GetTemplateChild("AudioMuteButton") as Button;
            live = GetTemplateChild("PlayLiveButton") as Button;
            fwd = GetTemplateChild("SkipForwardButton") as Button;
            bwd = GetTemplateChild("SkipBackwardButton") as Button;
            captionsMenu = GetTemplateChild("CaptionsMenuButton") as Button;
            drag = GetTemplateChild("drag") as Button;

            Advert = GetTemplateChild("AdvertControl") as PlayerAdvert;
            Caption = GetTemplateChild("CaptionControl") as LiveCaptions;

            title = GetTemplateChild("title") as TextBlock;
            channel = GetTemplateChild("channel") as TextBlock;
            elapsed = GetTemplateChild("TimeElapsedElement") as TextBlock;
            remain = GetTemplateChild("TimeRemainingElement") as TextBlock;

            volume = GetTemplateChild("VolumeSlider") as Slider;
            seek = GetTemplateChild("ProgressSlider") as Slider;
            playbackSpeed = GetTemplateChild("PlaybackSpeedSlider") as Slider;
            seekIndicator = GetTemplateChild("SeekIndicator") as ProgressBar;

            captions = GetTemplateChild("CaptionsSelector") as ComboBox;
            quality = GetTemplateChild("QualitySelector") as ComboBox;
            captionsSwitch = GetTemplateChild("CaptionsToggleSwitch") as ToggleSwitch;

            rightFooter = GetTemplateChild("RightFooterControls") as StackPanel;
            leftFooter = GetTemplateChild("LeftFooterControls") as StackPanel;
            rightHeader = GetTemplateChild("RightHeaderControls") as StackPanel;
            centerStack = GetTemplateChild("centerControls") as StackPanel;
            header = GetTemplateChild("header") as Grid;
            footer = GetTemplateChild("footer") as Grid;
            center = GetTemplateChild("center") as Grid;
            centerTrigger = GetTemplateChild("centerTrigger") as Grid;
        }

        private void Seek_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
        {
            seekIndicator.Value = seek.Value;
        }

        private async void Quality_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (Meta.Snippet.LiveBroadcastContent == "live")
                goto SetQuality;
            if(!needUpdateTimecode)
                timecodeBackup = Player.Position;
            needUpdateTimecode = true;
            Player.Pause();
            Player.Source = null;

            SetQuality:
            object info = (quality.SelectedItem as ComboBoxItem).Tag;
            if (info is MuxedStreamInfo)
                Player.SetPlaybackSource(MediaSource.CreateFromUri((info as MuxedStreamInfo).Url.ToUri()));
            else if (info is VideoStreamInfo || info == null)
                Player.SetPlaybackSource(MediaSource.CreateFromUri(await ManifestGenerator.GetManifest(Meta, info as VideoStreamInfo, MediaStreams)));
            else if (info is StreamQuality)
                Player.SetPlaybackSource(MediaSource.CreateFromUri((info as StreamQuality).Url));
        }

        private void Captions_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if(Caption.IsActive)
            {
                Caption.Close();
                Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
            }
        }

        private void CaptionsSwitch_Toggled(object sender, RoutedEventArgs e)
        {
            if(captionsSwitch.IsOn)
                Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
            else
                Caption.Close();
        }

        private void Live_Click(object sender, RoutedEventArgs e)
        {
            Player.Position = Player.NaturalDuration.TimeSpan;
        }

        private void Next_Click(object sender, RoutedEventArgs e)
        {
            NextRequested.Invoke(sender, e);
        }

        private void Miniview_Click(object sender, RoutedEventArgs e)
        {
            if (State == PlayerDisplayState.Compact)
                Maximize();
            else
                EnterMiniview();
        }

        public void UpdateVolumeIcon()
        {
            Volume_ValueChanged(this, null);
        }

        private void Volume_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
        {
            double v = volume.Value;
            if (v == 0 || Player.IsMuted)
                volumeMenu.Content = mute.Content = "\xE74F";
            else if (v <= 25 && v > 0)
                volumeMenu.Content = mute.Content = "\xE992";
            else if (v <= 50 && v > 25)
                volumeMenu.Content = mute.Content = "\xE993";
            else if (v <= 75 && v > 50)
                volumeMenu.Content = mute.Content = "\xE994";
            else if (v > 75)
                volumeMenu.Content = mute.Content = "\xE995";
        }

        private void Player_MediaOpened(object sender, RoutedEventArgs args)
        {
            if (!needUpdateTimecode)
                return;

            needUpdateTimecode = false;
            Player.Position = timecodeBackup;
        }

        private void Close_Click(object sender, RoutedEventArgs e)
        {
            CloseRequested?.Invoke(sender, e);
        }

        private void Minimize_Click(object sender, RoutedEventArgs e)
        {
            if (State == PlayerDisplayState.Normal)
                Minimize();
            else
                Maximize();
        }

        public void Minimize()
        {
            if (State == PlayerDisplayState.Minimized)
                return;

            header.Children.Remove(minimize);
            center.Children.Add(minimize);
            rightHeader.Children.Remove(close);
            center.Children.Add(close);
            leftFooter.Children.Remove(play);
            centerStack.Children.Add(play);
            rightFooter.Children.Remove(fwd);
            centerStack.Children.Add(fwd);
            rightFooter.Children.Remove(bwd);
            centerStack.Children.Insert(0, bwd);

            header.Visibility = Visibility.Collapsed;
            center.Visibility = Visibility.Visible;
            footer.Visibility = Visibility.Collapsed;

            minimize.Content = "\xE010";

            MiniModeChanged.Invoke(this, true);
            Caption.Minimize();

            State = PlayerDisplayState.Minimized;
        }

        public void Maximize()
        {
            if (State == PlayerDisplayState.Normal)
                return;

            if(State == PlayerDisplayState.Compact)
            {
                center.Children.Remove(miniview);
                rightHeader.Children.Add(miniview);
                centerStack.Children.Remove(play);
                leftFooter.Children.Insert(0, play);
                centerStack.Children.Remove(fwd);
                rightFooter.Children.Insert(0, fwd);
                centerStack.Children.Remove(bwd);
                rightFooter.Children.Insert(0, bwd);

                miniview.Margin = new Thickness();
                miniview.Width = 50;
                miniview.Height = 50;
            }
            else
            {
                center.Children.Remove(minimize);
                header.Children.Insert(0, minimize);
                center.Children.Remove(close);
                rightHeader.Children.Insert(0, close);
                centerStack.Children.Remove(play);
                leftFooter.Children.Insert(0, play);
                centerStack.Children.Remove(fwd);
                rightFooter.Children.Insert(0, fwd);
                centerStack.Children.Remove(bwd);
                rightFooter.Children.Insert(0, bwd);

                MiniModeChanged.Invoke(this, false);
            }

            drag.Visibility = Visibility.Collapsed;
            header.Visibility = Visibility.Visible;
            center.Visibility = Visibility.Collapsed;
            footer.Visibility = Visibility.Visible;
            miniview.Content = "\xE2B3";
            minimize.Content = "\xE011";
            Caption.Maximize();

            State = PlayerDisplayState.Normal;
        }

        public void EnterMiniview()
        {
            if (State == PlayerDisplayState.Compact)
                return;

            rightHeader.Children.Remove(miniview);
            center.Children.Add(miniview);
            leftFooter.Children.Remove(play);
            centerStack.Children.Add(play);
            rightFooter.Children.Remove(fwd);
            centerStack.Children.Add(fwd);
            rightFooter.Children.Remove(bwd);
            centerStack.Children.Insert(0, bwd);

            drag.Visibility = Visibility.Visible;
            header.Visibility = Visibility.Collapsed;
            center.Visibility = Visibility.Visible;
            footer.Visibility = Visibility.Collapsed;

            miniview.Margin = new Thickness(0, 32, 0, 0);
            miniview.Width = 47;
            miniview.Height = 47;

            miniview.Content = "\xE2B4";
            Caption.Minimize();

            State = PlayerDisplayState.Compact;
        }

        public async void Load(Video meta)
        {
            if(!isReady)
            {
                queue.Enqueue(() => Load(meta));
                return;
            }

            Player.MediaOpened += Player_MediaOpened;

            Meta = meta;
            title.Text = meta.Snippet.Title;
            channel.Text = meta.Snippet.ChannelTitle;

            MediaStreams = await new YoutubeClient().GetVideoMediaStreamInfosAsync(meta.Id);

            if (meta.Snippet.LiveBroadcastContent == "none")
            {
                ClosedCaptions = await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(meta.Id);

                uint screenHeight = DisplayInformation.GetForCurrentView().ScreenHeightInRawPixels;

                List<string> qualityList = MediaStreams.GetAllVideoQualityLabels().ToList();
                qualityList.Sort(new QualityComparer());
                qualityList.Reverse();

                quality.Items.Add(new ComboBoxItem
                {
                    Content = ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/auto")
                });

                foreach (string i in qualityList)
                {
                    object tag;
                    if (MediaStreams.Muxed.Any(m => m.VideoQualityLabel == i && m.Resolution.Height <= screenHeight))
                        tag = MediaStreams.Muxed.Find(m => m.VideoQualityLabel == i);
                    else if (MediaStreams.Video.Any(m => m.VideoQualityLabel == i && m.Resolution.Height <= screenHeight && m.Container.GetFileExtension() == "mp4"))
                        tag = MediaStreams.Video.Find(m => m.VideoQualityLabel == i && m.Container.GetFileExtension() == "mp4");
                    else
                        continue;

                    quality.Items.Add(new ComboBoxItem
                    {
                        Content = i,
                        Tag = tag
                    });
                }

                string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;

                if (quality.Items.Any(i => ((i as ComboBoxItem).Content as string).Contains(s)))
                    quality.SelectedItem = quality.Items.Find(i => ((i as ComboBoxItem).Content as string).Contains(s));
                else
                    quality.SelectedIndex = 0;


                if (ClosedCaptions.Count == 0)
                {
                    captionsMenu.Visibility = Visibility.Collapsed;
                    return;
                }

                foreach (ClosedCaptionTrackInfo i in ClosedCaptions)
                    captions.Items.Add(new ComboBoxItem
                    {
                        Content = string.Format("{0} {1}", CultureInfo.GetCultureInfo(i.Language.Code).DisplayName, i.IsAutoGenerated ? ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/generatedCaption") : ""),
                        Tag = i
                    });

                ClosedCaptionTrackInfo item = ClosedCaptions.Find(i => SettingsStorage.RelevanceLanguage.Contains(i.Language.Code)) ?? ClosedCaptions.Find(i => "en-US".Contains(i.Language.Code));
                if (item == null)
                    item = ClosedCaptions.First();

                captions.SelectedItem = captions.Items.Find(i => (i as ComboBoxItem).Tag as ClosedCaptionTrackInfo == item);

                Caption.Player = Player;
            }
            else
            {
                captionsMenu.Visibility = Visibility.Collapsed;
                seek.Visibility = Visibility.Collapsed;
                live.Visibility = Visibility.Visible;
                remain.Visibility = Visibility.Collapsed;
                elapsed.FontSize = 24;
                Grid.SetRow(elapsed, 0);
                Grid.SetRowSpan(elapsed, 2);
                elapsed.HorizontalAlignment = HorizontalAlignment.Right;
                fwd.Visibility = Visibility.Collapsed;
                bwd.Visibility = Visibility.Collapsed;

                List<StreamQuality> list = await ManifestGenerator.ResolveLiveSteream(MediaStreams.HlsLiveStreamUrl);

                foreach (StreamQuality i in list)
                    quality.Items.Add(new ComboBoxItem
                    {
                        Content = i.Resolution,
                        Tag = i
                    });

                string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;

                if (quality.Items.Any(i => (i as ComboBoxItem).Content as string == s))
                    quality.SelectedItem = quality.Items.Find(i => (i as ComboBoxItem).Content as string == s);
                else
                    quality.SelectedIndex = 0;
            }

            Focus(FocusState.Programmatic);
        }

        public void PushAdvert()
        {
            if(State == PlayerDisplayState.Normal)
                Advert.PushAdvert();
        }
    }
}
