using System;
using System.Drawing;
using System.Globalization;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using TweetDuck.Video.Controls;
using TweetLib.Communication;
using WMPLib;

namespace TweetDuck.Video{
    sealed partial class FormPlayer : Form{
        protected override bool ShowWithoutActivation => true;

        private readonly IntPtr ownerHandle;
        private readonly string videoUrl;
        private readonly DuplexPipe pipe;
        
        private readonly ControlWMP player;
        private bool wasCursorInside;
        private bool isPaused;
        private bool isDragging;

        private WindowsMediaPlayer Player => player.Ocx;

        public FormPlayer(IntPtr handle, int volume, string url, string token){
            InitializeComponent();

            this.ownerHandle = handle;
            this.videoUrl = url;
            this.pipe = DuplexPipe.CreateClient(token);
            this.pipe.DataIn += pipe_DataIn;
            
            player = new ControlWMP{
                Dock = DockStyle.Fill
            };

            player.BeginInit();
            Controls.Add(player);
            player.EndInit();

            Player.enableContextMenu = false;
            Player.uiMode = "none";
            Player.settings.autoStart = false;
            Player.settings.enableErrorDialogs = false;
            Player.settings.setMode("loop", true);

            Player.PlayStateChange += player_PlayStateChange;
            Player.MediaError += player_MediaError;
            
            trackBarVolume.Value = volume; // changes player volume too if non-default

            labelTooltip.AttachTooltip(progressSeek, true, args => {
                IWMPMedia media = Player.currentMedia;
                int progress = (int)(media.duration*progressSeek.GetProgress(args.X));

                Marshal.ReleaseComObject(media);

                return $"{(progress/60).ToString("00")}:{(progress%60).ToString("00")}";
            });
            
            labelTooltip.AttachTooltip(trackBarVolume, false, args => $"Volume : {trackBarVolume.Value}%");

            labelTooltip.AttachTooltip(imageClose, false, "Close");
            labelTooltip.AttachTooltip(imageDownload, false, "Download");
            labelTooltip.AttachTooltip(imageResize, false, "Fullscreen");

            Application.AddMessageFilter(new MessageFilter(this));
        }

        // Events

        private void FormPlayer_Load(object sender, EventArgs e){
            Player.URL = videoUrl;
        }

        private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
            Invoke(new Action(() => {
                switch(e.Key){
                    case "key":
                        HandleKey((Keys)int.Parse(e.Data, NumberStyles.Integer));
                        break;

                    case "die":
                        StopVideo();
                        break;
                }
            }));
        }

        private void player_PlayStateChange(int newState){
            WMPPlayState state = (WMPPlayState)newState;

            if (state == WMPPlayState.wmppsReady){
                Player.controls.play();
            }
            else if (state == WMPPlayState.wmppsPlaying){
                Player.PlayStateChange -= player_PlayStateChange;
                
                timerSync.Start();
                NativeMethods.SetWindowOwner(Handle, ownerHandle);
                Cursor.Current = Cursors.Default;
            }
        }

        private void player_MediaError(object pMediaObject){
            IWMPErrorItem error = ((IWMPMedia2)pMediaObject).Error;
            Console.Out.WriteLine($"Media Error {error.errorCode}: {error.errorDescription}");

            Marshal.ReleaseComObject(error);
            Marshal.ReleaseComObject(pMediaObject);
            Environment.Exit(Program.CODE_MEDIA_ERROR);
        }

        [HandleProcessCorruptedStateExceptions]
        private void timerSync_Tick(object sender, EventArgs e){
            if (NativeMethods.GetWindowRect(ownerHandle, out NativeMethods.RECT rect)){
                int width = rect.Right-rect.Left+1;
                int height = rect.Bottom-rect.Top+1;

                IWMPMedia media = Player.currentMedia;
                IWMPControls controls = Player.controls;

                bool isCursorInside = ClientRectangle.Contains(PointToClient(Cursor.Position));

                ClientSize = new Size(Math.Max(MinimumSize.Width, Math.Min(media.imageSourceWidth, width*3/4)), Math.Max(MinimumSize.Height, Math.Min(media.imageSourceHeight, height*3/4)));
                Location = new Point(rect.Left+(width-ClientSize.Width)/2, rect.Top+(height-ClientSize.Height+SystemInformation.CaptionHeight)/2);

                tablePanel.Visible = isCursorInside || isDragging;

                if (tablePanel.Visible){
                    labelTime.Text = $"{controls.currentPositionString} / {media.durationString}";

                    int value = (int)Math.Round(progressSeek.Maximum*controls.currentPosition/media.duration);

                    if (value >= progressSeek.Maximum){
                        progressSeek.Value = progressSeek.Maximum;
                        progressSeek.Value = progressSeek.Maximum-1;
                        progressSeek.Value = progressSeek.Maximum;
                    }
                    else{
                        progressSeek.Value = value+1;
                        progressSeek.Value = value;
                    }
                }

                if (controls.currentPosition > media.duration){ // pausing near the end of the video causes WMP to play beyond the end of the video wtf
                    try{
                        controls.stop();
                        controls.currentPosition = 0;
                        controls.play();
                    }catch(AccessViolationException){
                        // something is super retarded here because shit gets disposed between the start of this method and
                        // the controls.play() call even though it runs on the UI thread
                    }
                }
                
                if (isCursorInside && !wasCursorInside){
                    wasCursorInside = true;
                }
                else if (!isCursorInside && wasCursorInside){
                    wasCursorInside = false;

                    if (!Player.fullScreen && Handle == NativeMethods.GetForegroundWindow()){
                        NativeMethods.SetForegroundWindow(ownerHandle);
                    }
                }

                Marshal.ReleaseComObject(media);
                Marshal.ReleaseComObject(controls);
            }
            else{
                Environment.Exit(Program.CODE_OWNER_GONE);
            }
        }

        private void timerData_Tick(object sender, EventArgs e){
            timerData.Stop();
            pipe.Write("vol", trackBarVolume.Value.ToString());
        }

        private void progressSeek_MouseDown(object sender, MouseEventArgs e){
            if (e.Button == MouseButtons.Left){
                IWMPMedia media = Player.currentMedia;
                IWMPControls controls = Player.controls;

                controls.currentPosition = media.duration*progressSeek.GetProgress(progressSeek.PointToClient(Cursor.Position).X);

                Marshal.ReleaseComObject(media);
                Marshal.ReleaseComObject(controls);
            }
        }

        private void trackBarVolume_ValueChanged(object sender, EventArgs e){
            IWMPSettings settings = Player.settings;
            settings.volume = trackBarVolume.Value;
            
            Marshal.ReleaseComObject(settings);

            if (timerSync.Enabled){
                timerData.Stop();
                timerData.Start();
            }
        }

        private void trackBarVolume_MouseDown(object sender, MouseEventArgs e){
            isDragging = true;
        }

        private void trackBarVolume_MouseUp(object sender, MouseEventArgs e){
            isDragging = false;
        }

        private void imageClose_Click(object sender, EventArgs e){
            StopVideo();
        }

        private void imageDownload_Click(object sender, EventArgs e){
            pipe.Write("download");
        }

        private void imageResize_Click(object sender, EventArgs e){
            Player.fullScreen = true;
        }

        // Controls & messages

        private bool HandleKey(Keys key){
            switch(key){
                case Keys.Space:
                    TogglePause();
                    return true;

                case Keys.Escape:
                    if (Player.fullScreen){
                        Player.fullScreen = false;
                        NativeMethods.SetForegroundWindow(ownerHandle);
                        return true;
                    }
                    else{
                        StopVideo();
                        return true;
                    }
                    
                default:
                    return false;
            }
        }

        private void TogglePause(){
            IWMPControls controls = Player.controls;

            if (isPaused){
                controls.play();
            }
            else{
                controls.pause();
            }

            isPaused = !isPaused;
            Marshal.ReleaseComObject(controls);
        }

        private void StopVideo(){
            timerSync.Stop();
            Visible = false;
            pipe.Write("rip");
            
            Player.close();
            Close();
        }

        internal sealed class MessageFilter : IMessageFilter{
            private readonly FormPlayer form;

            private bool IsCursorOverVideo{
                get{
                    Point cursor = form.PointToClient(Cursor.Position);
                    return cursor.Y < form.tablePanel.Location.Y;
                }
            }

            public MessageFilter(FormPlayer form){
                this.form = form;
            }

            bool IMessageFilter.PreFilterMessage(ref Message m){
                if (m.Msg == 0x0201){ // WM_LBUTTONDOWN
                    if (IsCursorOverVideo){
                        form.TogglePause();
                        return true;
                    }
                }
                else if (m.Msg == 0x0203){ // WM_LBUTTONDBLCLK
                    if (IsCursorOverVideo){
                        form.TogglePause();
                        form.Player.fullScreen = !form.Player.fullScreen;
                        return true;
                    }
                }
                else if (m.Msg == 0x0100){ // WM_KEYDOWN
                    return form.HandleKey((Keys)m.WParam.ToInt32());
                }
                else if (m.Msg == 0x020B && ((m.WParam.ToInt32() >> 16) & 0xFFFF) == 1){ // WM_XBUTTONDOWN
                    NativeMethods.SetForegroundWindow(form.ownerHandle);
                    Environment.Exit(Program.CODE_USER_REQUESTED);
                }

                return false;
            }
        }
    }
}