using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using FastBitmapLib;
using IPCamLib.FFMPEG;
using RtspClientSharp;
using RtspClientSharp.RawFrames.Video;

namespace IPCamLib.Concrete
{
    /// <summary>
    /// Implements the ICamera interface for IP cameras exposing an RTSP stream.
    /// </summary>
    public class RTSPStreamCamera : ICamera
    {
        private readonly Uri streamUri;
        private readonly Dictionary<FFmpegVideoCodecId, FFmpegVideoDecoder> videoDecodersMap = new();
        private readonly Bitmap bitmap;
        private readonly TransformParameters transformParameters;

        /// <param name="streamUri">The URI to the camera stream.
        /// Can include basic credentials in the standard 'username:password@' format.</param>
        /// <param name="width">The width of the viewport</param>
        /// <param name="height">The height of the viewport</param>
        public RTSPStreamCamera(Uri streamUri, int width, int height)
        {
            this.streamUri = streamUri;

            transformParameters = new TransformParameters(RectangleF.Empty, new Size(width, height),
                ScalingPolicy.Stretch, PixelFormat.Bgra32, ScalingQuality.FastBilinear);

            bitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            
            using var fastBitmap = bitmap.FastLock();
            fastBitmap.Clear(Color.Black);
        }


        /// <inheritdoc />
        public async Task Fetch(ICameraObserver observer, CancellationToken cancellationToken)
        {
            NetworkCredential credentials = null;
            
            if (!string.IsNullOrEmpty(streamUri.UserInfo))
            {
                var parts = streamUri.UserInfo.Split(':');
                credentials = new NetworkCredential(parts[0], parts.Length > 1 ? parts[1] : "");
            }
            else
                credentials = new NetworkCredential(null, (string)null);


            var connectionParameters = new ConnectionParameters(streamUri, credentials)
            {
                RtpTransport = RtpTransportProtocol.TCP
            };
            
            using var rtspClient = new RtspClient(connectionParameters);

            rtspClient.FrameReceived += (_, rawFrame) =>
            {
                if (rawFrame is not RawVideoFrame rawVideoFrame)
                    return;

                var decoder = GetDecoderForFrame(rawVideoFrame);
                var decodedFrame = decoder.TryDecode(rawVideoFrame);

                if (decodedFrame == null) 
                    return;

                using (var fastBitmap = bitmap.FastLock())
                {
                    decodedFrame.TransformTo(fastBitmap.Scan0, fastBitmap.StrideInBytes, transformParameters);
                }

                // TODO await
                observer.OnFrame(bitmap);
            };

            await rtspClient.ConnectAsync(cancellationToken);
            await rtspClient.ReceiveAsync(cancellationToken);
        }



        private FFmpegVideoDecoder GetDecoderForFrame(RawVideoFrame videoFrame)
        {
            var codecId = DetectCodecId(videoFrame);
            if (videoDecodersMap.TryGetValue(codecId, out var decoder)) 
                return decoder;
            
            decoder = FFmpegVideoDecoder.CreateDecoder(codecId);
            videoDecodersMap.Add(codecId, decoder);

            return decoder;
        }

        
        private static FFmpegVideoCodecId DetectCodecId(RawVideoFrame videoFrame)
        {
            return videoFrame switch
            {
                RawJpegFrame => FFmpegVideoCodecId.MJPEG,
                RawH264Frame => FFmpegVideoCodecId.H264,
                _ => throw new ArgumentOutOfRangeException(nameof(videoFrame))
            };
        }
    }
}
