using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using FastBitmapLib;
using IPCamLib;

namespace IPCamAppBar
{
    public partial class CameraView : UserControl, IRetryableCameraObserver
    {
        private readonly ICamera camera;
        private readonly bool overlayDateTime;
        private Bitmap viewBitmap;
        private CancellationTokenSource streamCancellationTokenSource;

        public CameraView(ICamera camera, bool overlayDateTime)
        {
            this.camera = camera;
            this.overlayDateTime = overlayDateTime;
            InitializeComponent();

            StartCamera();
            
            Disposed += (_, _) =>
            {
                StopCamera();
            };
        }

        private void StartCamera()
        {
            streamCancellationTokenSource = new CancellationTokenSource();
            
            Task.Run(async () =>
            {
                var retryableCamera = new RetryableCamera(camera);
                await retryableCamera.Fetch(this, streamCancellationTokenSource.Token);
            });
        }


        private void StopCamera()
        {
            streamCancellationTokenSource.Cancel();
        }


        private Bitmap resizedBitmap;
        
        public Task OnFrame(Image image)
        {
            if (streamCancellationTokenSource.IsCancellationRequested)
                return Task.CompletedTask;
            
            if (overlayDateTime || image is not Bitmap bitmap || image.Width != Width || image.Height != Height)
            {
                resizedBitmap ??= new Bitmap(Width, Height);
                
                using var graphics = Graphics.FromImage(resizedBitmap);
                graphics.SmoothingMode = SmoothingMode.AntiAlias;
                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

                graphics.DrawImage(image, 0, 0, resizedBitmap.Width, resizedBitmap.Height);

                if (overlayDateTime)
                {
                    using (var path = new GraphicsPath())
                    {
                        path.AddString(
                            DateTime.Now.ToString("G"),
                            FontFamily.GenericSansSerif,
                            (int) FontStyle.Regular,
                            graphics.DpiY * 14 / 72,
                            Rectangle.Inflate(new Rectangle(0, 0, resizedBitmap.Width, resizedBitmap.Height), -4, -4),
                            new StringFormat
                            {
                                Alignment = StringAlignment.Far,
                                LineAlignment = StringAlignment.Far
                            });

                        graphics.DrawPath(new Pen(Color.Black, 3), path);
                        graphics.FillPath(Brushes.White, path);
                    }
                }

                graphics.Flush();
                bitmap = resizedBitmap;
            }

            Invoke(new Action(() => { InvokeFrame(bitmap); }));
            return Task.CompletedTask;
        }

        
        public Task OnFetch()
        {
            Invoke(new Action(InvokeFetch));
            return Task.CompletedTask;
        }


        public Task OnDisconnected(Exception exception, TimeSpan retryDelay)
        {
            Invoke(new Action(() => { InvokeDisconnected(exception, retryDelay); }));
            return Task.CompletedTask;
        }


        private void InvokeFrame(Bitmap bitmap)
        {
            if (IsDisposed)
                return;

            ConnectingLabel.Visible = false;
            StreamView.Visible = true;
            IssueLabel.Visible = false;
            PausedOverlay.Visible = false;


            if (viewBitmap == null)
            {
                viewBitmap = new Bitmap(Width, Height);
                StreamView.Image = viewBitmap;
            }

            using (var fastViewBitmap = viewBitmap.FastLock()) 
            {
                fastViewBitmap.CopyRegion(bitmap, new Rectangle(Point.Empty, bitmap.Size), new Rectangle(Point.Empty, viewBitmap.Size));
            }

            StreamView.Invalidate();
        }
        
        
        private void InvokeFetch()
        {
            if (IsDisposed)
                return;

            IssueLabel.Text = "Connecting...";
            IssueLabel.Visible = true;
            IssueLabel.BringToFront();
        }
        
        
        private void InvokeDisconnected(Exception exception, TimeSpan retryDelay)
        {
            if (IsDisposed || streamCancellationTokenSource.IsCancellationRequested)
                return;

            IssueLabel.Text = (exception?.Message ?? "Camera disconnected") + $", retrying in {retryDelay.TotalSeconds} seconds";
            IssueLabel.Visible = true;
            IssueLabel.BringToFront();
        }

        
        public void SetPaused(bool pause)
        {
            if (pause)
            {
                StopCamera();

                PausedOverlay.Left = (Width - PausedOverlay.Width) / 2;
                PausedOverlay.Top = (Height - PausedOverlay.Height) / 2;
                PausedOverlay.Visible = true;
                PausedOverlay.BringToFront();
            }
            else
                StartCamera();
        }
    }
}
