using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Media;
using FFmpeg.AutoGen;

namespace SmartAquaViewer.Helper.FFHelper
{
    public sealed unsafe class VideoFrameConverter : IDisposable
    {
        private readonly IntPtr _convertedFrameBufferPtr;
        private readonly System.Windows.Size _destinationSize;
        private readonly byte_ptrArray4 _dstData;
        private readonly int_array4 _dstLinesize;
        private readonly SwsContext* _pConvertContext;
        private bool _disposed = false;

        public VideoFrameConverter(System.Windows.Size sourceSize, AVPixelFormat sourcePixelFormat,
            System.Windows.Size destinationSize, AVPixelFormat destinationPixelFormat)
        {
            try
            {
                _destinationSize = destinationSize;

                _pConvertContext = ffmpeg.sws_getContext(
                    (int)sourceSize.Width,
                    (int)sourceSize.Height,
                    sourcePixelFormat,
                    (int)destinationSize.Width,
                    (int)destinationSize.Height,
                    destinationPixelFormat,
                    ffmpeg.SWS_FAST_BILINEAR, null, null, null);

                if (_pConvertContext == null)
                    throw new ApplicationException("Could not initialize the conversion context.");

                var convertedFrameBufferSize = ffmpeg.av_image_get_buffer_size(destinationPixelFormat,
                    (int)destinationSize.Width, (int)destinationSize.Height, 1);
                _convertedFrameBufferPtr = Marshal.AllocHGlobal(convertedFrameBufferSize);
                _dstData = new byte_ptrArray4();
                _dstLinesize = new int_array4();

                ffmpeg.av_image_fill_arrays(
                    ref _dstData,
                    ref _dstLinesize,
                    (byte*)_convertedFrameBufferPtr,
                    destinationPixelFormat,
                    (int)destinationSize.Width,
                    (int)destinationSize.Height, 1);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("VideoFrameConverter() : " + ex.Message);
            }
        }

        public void Dispose()
        {
            if (!_disposed)
            {
                if (_convertedFrameBufferPtr != IntPtr.Zero)
                    Marshal.FreeHGlobal(_convertedFrameBufferPtr);

                if (_pConvertContext != null)
                    ffmpeg.sws_freeContext(_pConvertContext);

                _disposed = true;
            }
        }

        public AVFrame Convert(AVFrame sourceFrame)
        {
            ffmpeg.sws_scale(_pConvertContext,
                sourceFrame.data, sourceFrame.linesize, 0, sourceFrame.height, _dstData, _dstLinesize);

            var data = new byte_ptrArray8();
            data.UpdateFrom(_dstData);
            var linesize = new int_array8();
            linesize.UpdateFrom(_dstLinesize);

            return new AVFrame
            {
                data = data,
                linesize = linesize,
                width = (int)_destinationSize.Width,
                height = (int)_destinationSize.Height
            };
        }

        public unsafe Bitmap DeepCopyFrame(AVFrame sourceFrame)
        {
            if (_disposed)
                throw new ObjectDisposedException(nameof(VideoFrameConverter));

            AVFrame* dstFramePtr = ffmpeg.av_frame_alloc();
            if (dstFramePtr == null)
            {
                throw new Exception("Failed to allocate destination frame");
            }

            try
            {
                dstFramePtr->format = (int)AVPixelFormat.AV_PIX_FMT_BGR24;
                dstFramePtr->width = sourceFrame.width;
                dstFramePtr->height = sourceFrame.height;

                int ret = ffmpeg.av_frame_get_buffer(dstFramePtr, 32);
                if (ret < 0)
                {
                    throw new Exception($"Failed to allocate buffer for destination frame: {Helper.av_strerror(ret)}");
                }

                ret = ffmpeg.av_frame_make_writable(dstFramePtr);
                if (ret < 0)
                {
                    throw new Exception($"Failed to make frame writable: {Helper.av_strerror(ret)}");
                }

                ffmpeg.sws_scale(_pConvertContext,
                    sourceFrame.data, sourceFrame.linesize, 0, sourceFrame.height, dstFramePtr->data, dstFramePtr->linesize);

                ret = ffmpeg.av_frame_copy_props(dstFramePtr, &sourceFrame);
                if (ret < 0)
                {
                    throw new Exception($"Failed to copy frame properties: {Helper.av_strerror(ret)}");
                }

                dstFramePtr->pts = sourceFrame.pts;
                dstFramePtr->pkt_dts = sourceFrame.pkt_dts;
                dstFramePtr->best_effort_timestamp = sourceFrame.best_effort_timestamp;

                var dstDataArr = new byte_ptrArray8();
                dstDataArr.UpdateFrom(dstFramePtr->data);
                var dstLinesizeArr = new int_array8();
                dstLinesizeArr.UpdateFrom(dstFramePtr->linesize);

                AVFrame frame = new AVFrame
                {
                    data = dstDataArr,
                    linesize = dstLinesizeArr,
                    width = dstFramePtr->width,
                    height = dstFramePtr->height,
                    format = (int)AVPixelFormat.AV_PIX_FMT_BGR24,
                    pts = dstFramePtr->pts,
                    pkt_dts = dstFramePtr->pkt_dts,
                    best_effort_timestamp = dstFramePtr->best_effort_timestamp
                };

                Bitmap bitmap = new Bitmap(
                    dstFramePtr->width,
                    dstFramePtr->height,
                    dstFramePtr->linesize[0],
                    System.Drawing.Imaging.PixelFormat.Format24bppRgb,
                    (IntPtr)dstFramePtr->data[0]
                );

                // Bitmap의 데이터를 따로 복사
                Bitmap finalBitmap = new Bitmap(bitmap);
                bitmap.Dispose(); // 중간 비트맵 해제

                return finalBitmap; // 데이터 복사된 최종 비트맵 리턴
            }
            catch (Exception ex)
            {
                //Log4NetManager.GetLog().Error($"Exception during frame copy: {ex.Message}");
                throw;
            }
            finally
            {
                // 포인터 유효성 검사 후 안전하게 해제
                if (dstFramePtr != null)
                {
                    ffmpeg.av_frame_unref(dstFramePtr); // 참조 해제
                    ffmpeg.av_frame_free(&dstFramePtr); // 메모리 해제
                }
            }
        }
    }
}
