#pragma once
// ANSMatRegistry.h — Thread-safe cv::Mat* pointer registry.
// Prevents double-free crashes when LabVIEW calls ReleaseImage on a pointer
// that a stream source (RTSP, VideoPlayer, etc.) has already freed and replaced.
//
// Every cv::Mat* allocated through these functions is tracked in a global set.
// Deletion only proceeds if the pointer is still in the set, guaranteeing each
// pointer is freed exactly once regardless of which thread calls delete first.
//
// Usage:
//   Allocate:  cv::Mat* p = anscv_mat_new(std::move(img));
//   Free:      anscv_mat_delete(&p);          // safe, no-op if already freed
//   Swap:      anscv_mat_replace(&p, std::move(newImg));  // atomic old-delete + new-alloc

#include <opencv2/core/mat.hpp>
#include <mutex>
#include <unordered_set>
#include "ANSGpuFrameOps.h"

namespace anscv {
namespace detail {

inline std::mutex& registry_mutex() {
    static std::mutex m;
    return m;
}

inline std::unordered_set<cv::Mat*>& registry() {
    static std::unordered_set<cv::Mat*> s;
    return s;
}

} // namespace detail
} // namespace anscv

// Allocate a new cv::Mat on the heap and register it.
inline cv::Mat* anscv_mat_new(cv::Mat&& src) {
    cv::Mat* p = new cv::Mat(std::move(src));
    {
        std::lock_guard<std::mutex> lk(anscv::detail::registry_mutex());
        anscv::detail::registry().insert(p);
    }
    return p;
}

inline cv::Mat* anscv_mat_new(const cv::Mat& src) {
    cv::Mat* p = new cv::Mat(src);
    {
        std::lock_guard<std::mutex> lk(anscv::detail::registry_mutex());
        anscv::detail::registry().insert(p);
    }
    return p;
}

// Delete a cv::Mat* only if it is still in the registry.
// Returns true if deleted, false if pointer was null or already freed.
// Thread-safe: the pointer is removed from the registry AND nulled AND deleted
// all inside the lock, so no other thread can race on the same pointer.
inline bool anscv_mat_delete(cv::Mat** pp) {
    if (!pp || !(*pp))
        return false;
    cv::Mat* toDelete = nullptr;
    {
        std::lock_guard<std::mutex> lk(anscv::detail::registry_mutex());
        // Re-check under lock — another thread may have nulled *pp
        if (!*pp)
            return false;
        auto& reg = anscv::detail::registry();
        auto it = reg.find(*pp);
        if (it == reg.end()) {
            // Not in registry — already freed by another thread.
            *pp = nullptr;
            return false;
        }
        toDelete = *pp;
        reg.erase(it);
        *pp = nullptr;  // Null the caller's pointer while still under lock
    }
    // Release GPU frame data (refcount--, frees NV12+GPU cache when refcount=0)
    gpu_frame_remove(toDelete);
    // Safe to delete outside lock: pointer is removed from registry and
    // *pp is nullptr, so no other thread can reach toDelete.
    delete toDelete;
    return true;
}

// Atomic replace: delete old image (if still registered), allocate new, assign to *pp.
// The old pointer is only deleted if it is in the registry (prevents double-free
// when LabVIEW already freed it via anscv_mat_delete).
inline void anscv_mat_replace(cv::Mat** pp, cv::Mat&& newImg) {
    cv::Mat* newPtr = new cv::Mat(std::move(newImg));
    cv::Mat* toDelete = nullptr;
    {
        std::lock_guard<std::mutex> lk(anscv::detail::registry_mutex());
        auto& reg = anscv::detail::registry();
        if (pp && *pp) {
            auto it = reg.find(*pp);
            if (it != reg.end()) {
                toDelete = *pp;
                reg.erase(it);
            }
            // If not in registry, another thread already freed it — skip delete
        }
        *pp = newPtr;
        reg.insert(newPtr);
    }
    if (toDelete) {
        // Release GPU frame ref for old Mat (safe with refcounting —
        // only frees NV12/GPU cache when refcount reaches 0)
        gpu_frame_remove(toDelete);
        delete toDelete;
    }

    // Periodically run TTL eviction (piggybacked on camera thread activity)
    gpu_frame_evict_stale();
}
