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{
        private bool IsCursorOverVideo{
            get{
                Point cursor = PointToClient(Cursor.Position);
                return cursor.Y < (tablePanelFull.Enabled ? tablePanelFull.Location.Y : tablePanelCompactTop.Location.Y);
            }
        }

        protected override bool ShowWithoutActivation => true;

        private readonly IntPtr ownerHandle;
        private readonly float ownerDpi;
        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 dpi, int volume, string url, string token){
            InitializeComponent();

            this.ownerHandle = handle;
            this.ownerDpi = dpi / 100F;
            this.videoUrl = url;
            this.pipe = DuplexPipe.CreateClient(token);
            this.pipe.DataIn += pipe_DataIn;

            if (NativeMethods.GetWindowRect(ownerHandle, out NativeMethods.RECT rect)){
                ClientSize = new Size(0, 0);
                Location = new Point((rect.Left+rect.Right)/2, (rect.Top+rect.Bottom)/2);
                Opacity = 0;
            }
            
            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 => {
                if (args.X < 0 || args.Y < 0 || args.X >= progressSeek.Width || args.Y >= progressSeek.Height){
                    return null;
                }

                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));
        }

        // Layout

        private int DpiScaled(int value){
            return (int)Math.Round(value*ownerDpi);
        }

        private void RefreshControlPanel(){
            bool useCompactLayout = ClientSize.Width < DpiScaled(480);
            bool needsUpdate = !timerSync.Enabled || (useCompactLayout ? tablePanelFull.Enabled : tablePanelCompactBottom.Enabled);

            if (needsUpdate){
                void Disable(TableLayoutPanel panel){
                    panel.Controls.Clear();
                    panel.Visible = false;
                    panel.Enabled = false;
                }

                tablePanelFull.SuspendLayout();
                tablePanelCompactBottom.SuspendLayout();
                tablePanelCompactTop.SuspendLayout();

                if (useCompactLayout){
                    Disable(tablePanelFull);

                    tablePanelCompactBottom.Enabled = true;
                    tablePanelCompactBottom.Controls.Add(imageClose, 0, 0);
                    tablePanelCompactBottom.Controls.Add(trackBarVolume, 2, 0);
                    tablePanelCompactBottom.Controls.Add(imageDownload, 3, 0);
                    tablePanelCompactBottom.Controls.Add(imageResize, 4, 0);

                    tablePanelCompactTop.Enabled = true;
                    tablePanelCompactTop.Controls.Add(progressSeek, 0, 0);
                    tablePanelCompactTop.Controls.Add(labelTime, 1, 0);
                }
                else{
                    Disable(tablePanelCompactBottom);
                    Disable(tablePanelCompactTop);

                    tablePanelFull.Enabled = true;
                    tablePanelFull.Controls.Add(imageClose, 0, 0);
                    tablePanelFull.Controls.Add(progressSeek, 1, 0);
                    tablePanelFull.Controls.Add(labelTime, 2, 0);
                    tablePanelFull.Controls.Add(trackBarVolume, 3, 0);
                    tablePanelFull.Controls.Add(imageDownload, 4, 0);
                    tablePanelFull.Controls.Add(imageResize, 5, 0);
                }
                
                tablePanelFull.ResumeLayout();
                tablePanelCompactBottom.ResumeLayout();
                tablePanelCompactTop.ResumeLayout();
            }
        }

        // 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;
                
                NativeMethods.SetWindowOwner(Handle, ownerHandle);
                Cursor.Current = Cursors.Default;

                SuspendLayout();
                timerSync_Tick(timerSync, EventArgs.Empty);
                timerSync.Start();
                Opacity = 1;
                ResumeLayout(true);
            }
        }

        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)){
                IWMPMedia media = Player.currentMedia;
                IWMPControls controls = Player.controls;

                int ownerLeft = rect.Left;
                int ownerTop = rect.Top;
                int ownerWidth = rect.Right-rect.Left+1;
                int ownerHeight = rect.Bottom-rect.Top+1;
                
                // roughly matches MinimumSize for client bounds, adjusted a bit for weirdness with higher DPI
                int minWidth = DpiScaled(356);
                int minHeight = DpiScaled(386);

                if (NativeMethods.GetClientRect(ownerHandle, out NativeMethods.RECT clientSize)){
                    minWidth = Math.Min(minWidth, clientSize.Right);
                    minHeight = Math.Min(minHeight, clientSize.Bottom);
                }

                int maxWidth = Math.Min(DpiScaled(media.imageSourceWidth), ownerWidth*3/4);
                int maxHeight = Math.Min(DpiScaled(media.imageSourceHeight), ownerHeight*3/4);
                
                bool isCursorInside = ClientRectangle.Contains(PointToClient(Cursor.Position));
                
                Size newSize = new Size(Math.Max(minWidth+2, maxWidth), Math.Max(minHeight+2, maxHeight));
                Point newLocation = new Point(ownerLeft+(ownerWidth-newSize.Width)/2, ownerTop+(ownerHeight-newSize.Height+SystemInformation.CaptionHeight)/2);
                
                if (ClientSize != newSize || Location != newLocation){
                    ClientSize = newSize;
                    Location = newLocation;
                    RefreshControlPanel();
                }
                
                if (isCursorInside || isDragging){
                    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 (tablePanelFull.Enabled){
                        tablePanelFull.Visible = true;
                    }
                    else{
                        tablePanelCompactBottom.Visible = true;
                        tablePanelCompactTop.Visible = true;
                    }
                }
                else{
                    tablePanelFull.Visible = false;
                    tablePanelCompactBottom.Visible = false;
                    tablePanelCompactTop.Visible = false;
                }
                
                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;

                    if (IsCursorOverVideo){
                        Cursor.Current = Cursors.Default;
                    }
                }
                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;

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

            bool IMessageFilter.PreFilterMessage(ref Message m){
                if (m.Msg == 0x0201){ // WM_LBUTTONDOWN
                    if (form.IsCursorOverVideo){
                        form.TogglePause();
                        return true;
                    }
                }
                else if (m.Msg == 0x0203){ // WM_LBUTTONDBLCLK
                    if (form.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;
            }
        }
    }
}