using Ayay.SerilogLogs;
using OpenCvSharp;
using Serilog;

namespace SHH.CameraSdk;

/// <summary>
/// [消费者] 专用渲染线程（零延迟设计）
/// 核心策略：
/// <para>1. 容量为1的阻塞队列：仅保留最新一帧，杜绝帧堆积</para>
/// <para>2. 非阻塞入队+主动丢帧：渲染慢时直接丢弃新帧，确保主线程不阻塞</para>
/// <para>3. 引用计数联动：丢帧时立即释放引用，内存自动回池复用</para>
/// </summary>
public class FrameConsumer : IDisposable
{
    #region --- 私有资源与状态 (Private Resources & States) ---

    private ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);

    /// <summary> 帧缓冲队列（容量1）：仅存储最新一帧，保证零延迟渲染 </summary>
    /// <remarks> BlockingCollection 封装线程安全操作，GetConsumingEnumerable 支持取消令牌 </remarks>
    private readonly BlockingCollection<SmartFrame> _frameBuffer = new(1);

    /// <summary> 取消令牌源：用于终止渲染循环 </summary>
    private readonly CancellationTokenSource _cts = new();

    /// <summary> 后台渲染任务 </summary>
    private Task? _renderTask;

    /// <summary> OpenCV 窗口名称 </summary>
    private readonly string _windowName;

    #endregion

    #region --- 构造与生命周期 (Constructor & Lifecycle) ---

    /// <summary>
    /// 初始化帧渲染消费者
    /// </summary>
    /// <param name="windowName">OpenCV 显示窗口名称</param>
    public FrameConsumer(string windowName = "Zero Latency Preview")
    {
        _windowName = windowName;
    }

    /// <summary>
    /// 启动渲染线程
    /// </summary>
    public void Start()
    {
        // 防止重复启动
        if (_renderTask != null) return;

        // 启动长期运行的渲染任务，提升线程调度优先级
        _renderTask = Task.Factory.StartNew(RenderLoop, TaskCreationOptions.LongRunning);
        _sysLog.Information($"[Consumer] 渲染线程启动成功，窗口名称: {_windowName}");
    }

    /// <summary>
    /// 停止渲染线程并清理资源
    /// </summary>
    public void Stop()
    {
        // 发送取消信号，终止渲染循环
        _cts.Cancel();
        // 标记队列完成添加，触发 GetConsumingEnumerable 退出遍历
        _frameBuffer.CompleteAdding();

        // 等待渲染任务结束（最多等待1秒，防止卡死）
        if (_renderTask != null)
        {
            Task.WaitAny(_renderTask, Task.Delay(1000));
            _renderTask = null;
        }

        // 清理队列残余帧：释放所有未消费帧的引用，防止内存泄漏
        while (_frameBuffer.TryTake(out var residualFrame))
        {
            residualFrame.Dispose();
        }

        _sysLog.Information($"[Consumer] 渲染线程已停止，窗口: {_windowName}");
    }

    #endregion

    #region --- 帧入队与渲染逻辑 (Frame Enqueue & Render Logic) ---

    /// <summary>
    /// [生产端入口] 接收帧并尝试入队（非阻塞）
    /// </summary>
    /// <param name="frame">待渲染的智能帧</param>
    public void Enqueue(SmartFrame frame)
    {
        // 防护：线程已停止，直接释放帧引用
        if (_cts.IsCancellationRequested)
        {
            frame.Dispose();
            return;
        }

        // 核心零延迟策略：非阻塞尝试入队
        // 队列满 → 上一帧未渲染完成 → 丢弃当前帧，释放引用
        if (!_frameBuffer.TryAdd(frame))
        {
            frame.Dispose();
            // Debug.WriteLine($"[Drop] 渲染线程[{_windowName}]处理过慢，丢弃一帧");
        }
        // 入队成功 → 帧由队列托管，等待渲染线程消费
    }

    /// <summary>
    /// 后台渲染循环（核心逻辑）
    /// </summary>
    private void RenderLoop()
    {
        bool isWindowCreated = false;

        try
        {
            // 我们不再使用简单的 foreach 阻塞等待数据，
            // 而是改用非阻塞模式或带有超时的读取，以保证 WaitKey 的活性
            while (!_cts.Token.IsCancellationRequested)
            {
                // 尝试在 30ms 内获取一帧数据（相当于 33 fps 的响应速度）
                if (_frameBuffer.TryTake(out var frame, 30, _cts.Token))
                {
                    try
                    {
                        if (frame.InternalMat != null && !frame.InternalMat.IsDisposed)
                        {
                            if (!isWindowCreated)
                            {
                                Cv2.NamedWindow(_windowName, WindowFlags.Normal);
                                isWindowCreated = true;
                            }

                            Cv2.ImShow(_windowName, frame.InternalMat);
                        }
                    }
                    finally
                    {
                        frame.Dispose();
                    }
                }

                // 【核心修复】无论有没有取到帧，都要执行 WaitKey
                // 只有这样，窗口在没视频时才能被拖动、最小化或手动点击 X 关闭
                if (isWindowCreated)
                {
                    // 1ms 的等待足以处理 Windows 窗口消息
                    int key = Cv2.WaitKey(1);

                    // 如果用户点击了 OpenCV 窗口右上角的 X (部分版本支持)
                    // 或者按下 ESC，可以根据需要在这里处理
                    if (key == 27) break;

                    // 检查窗口是否还存在（防止用户手动关掉窗口后报错）
                    // 如果窗口被手动关闭，我们标记为未创建，下次有流时重新弹窗
                    try
                    {
                        if (Cv2.GetWindowProperty(_windowName, WindowPropertyFlags.Visible) < 1)
                        {
                            isWindowCreated = false;
                        }
                    }
                    catch { isWindowCreated = false; }
                }
            }
        }
        catch (OperationCanceledException) { }
        catch { }
        finally
        {
            if (isWindowCreated)
            {
                Cv2.DestroyWindow(_windowName);
            }
        }
    }

    #endregion

    #region --- 资源释放 (Disposal) ---

    /// <summary>
    /// 释放所有资源
    /// </summary>
    public void Dispose()
    {
        Stop();
        _frameBuffer.Dispose();
        _cts.Dispose();
    }

    #endregion
}