using Quasar.Client.Helper;
using Quasar.Common.Enums;
using Quasar.Common.Messages;
using Quasar.Common.Networking;
using Quasar.Common.Video;
using Quasar.Common.Video.Codecs;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Windows.Forms;

namespace Quasar.Client.Messages
{
    public class RemoteDesktopHandler : NotificationMessageProcessor, IDisposable
    {
        private UnsafeStreamCodec _streamCodec;
        private bool _isStreaming;
        private Thread _streamThread;
        private ISender _currentClient;
        private int _fpsCap = 60;
        private readonly object _streamLock = new object();

        public override bool CanExecute(IMessage message) => message is GetDesktop ||
                                                             message is DoMouseEvent ||
                                                             message is DoKeyboardEvent ||
                                                             message is GetMonitors;

        public override bool CanExecuteFrom(ISender sender) => true;

        public override void Execute(ISender sender, IMessage message)
        {
            switch (message)
            {
                case GetDesktop msg:      Execute(sender, msg); break;
                case DoMouseEvent msg:    Execute(sender, msg); break;
                case DoKeyboardEvent msg: Execute(sender, msg); break;
                case GetMonitors msg:     Execute(sender, msg); break;
            }
        }

        private void Execute(ISender client, GetDesktop message)
        {
            if (message.StopStreaming)
            {
                lock (_streamLock)
                {
                    _isStreaming = false;
                    _currentClient = null;
                }
                return;
            }

            if (message.CreateNew)
            {
                lock (_streamLock)
                {
                    var monitorBounds = ScreenHelper.GetBounds(message.DisplayIndex);
                    var resolution = new Resolution { Height = monitorBounds.Height, Width = monitorBounds.Width };

                    _streamCodec?.Dispose();
                    _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution);

                    _isStreaming = true;
                    _currentClient = client;
                    _fpsCap = message.FpsCap > 0 ? message.FpsCap : 60;

                    OnReport("Remote desktop streaming started");
                }

                if (_streamThread == null || !_streamThread.IsAlive)
                {
                    _streamThread = new Thread(StreamingLoop)
                    {
                        IsBackground = true,
                        Priority = ThreadPriority.AboveNormal
                    };
                    _streamThread.Start();
                }
                return;
            }

            // Legacy single-frame mode
            var bounds = ScreenHelper.GetBounds(message.DisplayIndex);
            var res = new Resolution { Height = bounds.Height, Width = bounds.Width };

            if (_streamCodec == null || _streamCodec.ImageQuality != message.Quality ||
                _streamCodec.Monitor != message.DisplayIndex || _streamCodec.Resolution != res)
            {
                lock (_streamLock)
                {
                    _streamCodec?.Dispose();
                    _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, res);
                }
            }

            SendSingleFrame(client, message.DisplayIndex);
        }

        private void StreamingLoop()
        {
            while (true)
            {
                ISender client;
                UnsafeStreamCodec codec;
                int display;
                int fpsCap;

                lock (_streamLock)
                {
                    if (!_isStreaming || _currentClient == null || _streamCodec == null)
                        break;

                    client = _currentClient;
                    codec = _streamCodec;
                    display = codec.Monitor;
                    fpsCap = _fpsCap;
                }

                try
                {
                    SendSingleFrame(client, display);
                }
                catch { }

                int delayMs = 1000 / fpsCap;
                Thread.Sleep(delayMs);
            }
        }

        private void SendSingleFrame(ISender client, int displayIndex)
        {
            BitmapData desktopData = null;
            Bitmap desktop = null;
            try
            {
                desktop = ScreenHelper.CaptureScreen(displayIndex);
                desktopData = desktop.LockBits(new Rectangle(0, 0, desktop.Width, desktop.Height),
                    ImageLockMode.ReadWrite, desktop.PixelFormat);

                using (MemoryStream stream = new MemoryStream())
                {
                    if (_streamCodec == null) throw new Exception("StreamCodec can not be null.");
                    _streamCodec.CodeImage(desktopData.Scan0,
                        new Rectangle(0, 0, desktop.Width, desktop.Height),
                        new Size(desktop.Width, desktop.Height),
                        desktop.PixelFormat, stream);
                    client.Send(new GetDesktopResponse
                    {
                        Image = stream.ToArray(),
                        Quality = _streamCodec.ImageQuality,
                        Monitor = _streamCodec.Monitor,
                        Resolution = _streamCodec.Resolution
                    });
                }
            }
            catch (Exception)
            {
                if (_streamCodec != null)
                {
                    client.Send(new GetDesktopResponse
                    {
                        Image = null,
                        Quality = _streamCodec.ImageQuality,
                        Monitor = _streamCodec.Monitor,
                        Resolution = _streamCodec.Resolution
                    });
                }
                _streamCodec = null;
            }
            finally
            {
                if (desktop != null)
                {
                    if (desktopData != null)
                        try { desktop.UnlockBits(desktopData); } catch { }
                    desktop.Dispose();
                }
            }
        }

        private void Execute(ISender sender, DoMouseEvent message)
        {
            try
            {
                Screen[] allScreens = Screen.AllScreens;
                int offsetX = allScreens[message.MonitorIndex].Bounds.X;
                int offsetY = allScreens[message.MonitorIndex].Bounds.Y;
                Point p = new Point(message.X + offsetX, message.Y + offsetY);

                switch (message.Action)
                {
                    case MouseAction.LeftDown:
                    case MouseAction.LeftUp:
                    case MouseAction.RightDown:
                    case MouseAction.RightUp:
                    case MouseAction.MoveCursor:
                        if (NativeMethodsHelper.IsScreensaverActive())
                            NativeMethodsHelper.DisableScreensaver();
                        break;
                }

                switch (message.Action)
                {
                    case MouseAction.LeftDown:
                    case MouseAction.LeftUp:
                        NativeMethodsHelper.DoMouseLeftClick(p, message.IsMouseDown);
                        break;
                    case MouseAction.RightDown:
                    case MouseAction.RightUp:
                        NativeMethodsHelper.DoMouseRightClick(p, message.IsMouseDown);
                        break;
                    case MouseAction.MoveCursor:
                        NativeMethodsHelper.DoMouseMove(p);
                        break;
                    case MouseAction.ScrollDown:
                        NativeMethodsHelper.DoMouseScroll(p, true);
                        break;
                    case MouseAction.ScrollUp:
                        NativeMethodsHelper.DoMouseScroll(p, false);
                        break;
                }
            }
            catch { }
        }

        private void Execute(ISender sender, DoKeyboardEvent message)
        {
            if (NativeMethodsHelper.IsScreensaverActive())
                NativeMethodsHelper.DisableScreensaver();
            NativeMethodsHelper.DoKeyPress(message.Key, message.KeyDown);
        }

        private void Execute(ISender client, GetMonitors message)
        {
            client.Send(new GetMonitorsResponse { Number = Screen.AllScreens.Length });
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                lock (_streamLock)
                {
                    _isStreaming = false;
                }

                if (_streamThread != null && _streamThread.IsAlive)
                    _streamThread.Join(1000);

                _streamCodec?.Dispose();
            }
        }
    }
}
