#include "ANSMJPEG.h"
#include "ANSMatRegistry.h"
#include "ANSGpuFrameOps.h"
#include "ANSCVVendorGate.h"  // anscv_vendor_gate::IsNvidiaGpuAvailable()
#include "ANSLicense.h"       // ANS_DBG macro
#include <memory>
#include <cstdint>
#include "media_codec.h"
#include <cuda_runtime.h>
#if defined(_WIN32)
#include <dxgi1_2.h>
#pragma comment(lib, "dxgi.lib")
#elif defined(__linux__)
#include <dirent.h>
#include <fstream>
#include <sstream>
#endif
extern "C"
{
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/frame.h>
}
// Note: per-instance thread safety is handled by ANSMJPEGClient::_mutex
// Mat registry thread safety is handled by anscv_mat_replace's internal registry_mutex
static bool ansmjpegLicenceValid = false;
// Global once_flag to protect license checking
static std::once_flag ansmjpegLicenseOnceFlag;
static std::once_flag mjpegHwDecoderAutoConfigOnceFlag;
namespace ANSCENTER {
    ANSMJPEGClient::ANSMJPEGClient() {
        _useFullURL = false;
        _username = "";
        _password = "";
        _url = "";
        _imageWidth = 0;
        _imageHeight = 0;
        _pts = 0;
        _lastJpegImage = "";
        _isPlaying = false;

        // Auto-configure HW decoder pool on first client creation.
        std::call_once(mjpegHwDecoderAutoConfigOnceFlag, []() {
            AutoConfigureHWDecoders(0);
        });
    }
    ANSMJPEGClient::~ANSMJPEGClient() noexcept {
		Destroy();
    }
    void ANSMJPEGClient::Destroy() {
        decltype(_playerClient) clientToClose;
        {
            std::lock_guard<std::recursive_mutex> lock(_mutex);
            if (_playerClient) {
                if (_isPlaying) {
                    _playerClient->stop();
                    _isPlaying = false;
                }
            }
            clientToClose = std::move(_playerClient);
        }
        // reset() runs the destructor which calls close() exactly once.
        // Do NOT call close() explicitly before reset() — second close would
        // re-enter decoder Stop/flush on already-torn-down state.
        if (clientToClose) {
            clientToClose.reset();
        }
    }
    static void VerifyGlobalANSMJPEGLicense(const std::string& licenseKey) {
        try {
            ansmjpegLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1007, "ANSCV");//Default productId=1005
            if (!ansmjpegLicenceValid) { // we also support ANSTS license
                ansmjpegLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1003, "ANSVIS");//Default productId=1003 (ANSVIS)
            }
            if (!ansmjpegLicenceValid) { // we also support ANSTS license
                ansmjpegLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1008, "ANSTS");//Default productId=1008 (ANSTS)
            }
        }
        catch (std::exception& e) {
            ansmjpegLicenceValid = false;
        }
    }
    void ANSMJPEGClient::CheckLicense() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            // Check once globally
            std::call_once(ansmjpegLicenseOnceFlag, [this]() {
                VerifyGlobalANSMJPEGLicense(_licenseKey);
                });

            // Update this instance's local license flag
            _licenseValid = ansmjpegLicenceValid;
        }
        catch (const std::exception& e) {
            this->_logger.LogFatal("ANSMJPEGClient::CheckLicense. Error:", e.what(), __FILE__, __LINE__);
        }
    }
    bool ANSMJPEGClient::Init(std::string licenseKey, std::string url) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _licenseKey = licenseKey;
        CheckLicense();
        if (!_licenseValid) {
            this->_logger.LogError("ANSMJPEGClient::Init.", "Invalid license", __FILE__, __LINE__);
            return false;
        }
        _url = url;
        _useFullURL = true;
        return Setup();
    }

    bool ANSMJPEGClient::Init(std::string licenseKey, std::string username, std::string password, std::string url) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _licenseKey = licenseKey;
        CheckLicense();
        if (!_licenseValid) {
            this->_logger.LogError("ANSMJPEGClient::Init.", "Invalid license", __FILE__, __LINE__);
            return false;
        }
        _url = url;
        _username = username;
        _password = password;
        _useFullURL = false;
        return Setup();
    }
    void ANSMJPEGClient::SetBBox(cv::Rect bbox) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _playerClient->setBbox(bbox);
    }
    void ANSMJPEGClient::SetCrop(bool crop) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _playerClient->setCrop(crop);
    }
    bool ANSMJPEGClient::Setup() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        if (_useFullURL) {
            return _playerClient->open(_url);
        }
        else {
            return _playerClient->open(_username, _password, _url);
        }
    }
    bool ANSMJPEGClient::Reconnect() {
        {
            std::lock_guard<std::recursive_mutex> lock(_mutex);
            _isPlaying = false;
        }
        // CHttpMjpegPlayer::open() calls close() internally at the top —
        // no need to close explicitly (would be a double-close).
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        Setup();
        _isPlaying = _playerClient->play();
        return _isPlaying;
    }

    bool ANSMJPEGClient::Start() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        Setup();
        _isPlaying = _playerClient->play();
        return _isPlaying;
    }
    bool ANSMJPEGClient::Stop() {
        decltype(_playerClient.get()) player = nullptr;
        {
            std::lock_guard<std::recursive_mutex> lock(_mutex);
            if (_isPlaying) {
                _isPlaying = false;
                player = _playerClient.get();
            }
        }
        if (player) {
            player->stop();
        }
        return true;
    }
	bool ANSMJPEGClient::Pause() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
		_isPlaying = false;
        return _playerClient->pause();
	}

    bool ANSMJPEGClient::IsPaused() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        return _playerClient->isPaused();
    }
    bool ANSMJPEGClient::IsPlaying() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        return _playerClient->isPlaying();
    }

    bool ANSMJPEGClient::IsRecording() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        return _playerClient->isRecording();
    }
    std::string ANSMJPEGClient::GetJpegImage(int& width, int& height, int64_t& pts) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        // If the player is playing, process the frame
        if (_isPlaying) {
            // Get a new frame from the player client
            _lastJpegImage = _playerClient->getJpegImage(width, height, pts);
            // Update internal state variables
            _pts = pts;
            _imageWidth = width;
            _imageHeight = height;
            // Return the frame
            return _lastJpegImage;
        }
        // If the player is not playing, return the last known frame
        else {
            width = _imageWidth;
            height = _imageHeight;
            pts = _pts;
            return _lastJpegImage;
        }
    }

    bool ANSMJPEGClient::areImagesIdentical(const cv::Mat& img1, const cv::Mat& img2) {
        double ageMs = _playerClient->getLastFrameAgeMs();
        if (ageMs > 5000.0) return true;
        if (ageMs > 0.0) return false;

        if (img1.empty() && img2.empty()) return true;
        if (img1.empty() || img2.empty()) return false;
        if (img1.size() != img2.size() || img1.type() != img2.type()) return false;
        if (img1.data == img2.data) return true;

        if (img1.isContinuous() && img2.isContinuous()) {
            const size_t totalBytes = img1.total() * img1.elemSize();
            return std::memcmp(img1.data, img2.data, totalBytes) == 0;
        }
        const size_t rowSize = img1.cols * img1.elemSize();
        for (int i = 0; i < img1.rows; i++) {
            if (std::memcmp(img1.ptr(i), img2.ptr(i), rowSize) != 0) return false;
        }
        return true;
    }
    cv::Mat ANSMJPEGClient::GetImage(int& width, int& height, int64_t& pts) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);

        // Return last known frame if not playing
        if (!_isPlaying) {
            width = _imageWidth;
            height = _imageHeight;
            pts = _pts;
            return _pLastFrame;  // Shallow copy (fast)
        }

        // Early stale-out: if the decoder hasn't produced a frame in 5s the
        // source is dead. Skip _playerClient->getImage() entirely and return
        // the cached frame with unchanged _pts so LabVIEW sees STALE PTS one
        // poll earlier and triggers reconnect.
        if (!_pLastFrame.empty()) {
            double ageMs = _playerClient->getLastFrameAgeMs();
            if (ageMs >= 5000.0) {
                ANS_DBG("MJPEG_GetImage",
                        "EARLY STALE: ageMs=%.1f pts=%lld url=%s — skipping getImage()",
                        ageMs, (long long)_pts, _url.c_str());
                width = _imageWidth;
                height = _imageHeight;
                pts = _pts;
                return _pLastFrame;
            }
        }

        int imageW = 0, imageH = 0;
        int64_t currentPts = 0;

        // Get image directly without intermediate assignment
        cv::Mat currentImage = _playerClient->getImage(imageW, imageH, currentPts);

        // Check if image is valid first (cheaper than comparison)
        if (currentImage.empty()) {
            width = _imageWidth;
            height = _imageHeight;
            pts = _pts;
            return _pLastFrame;
        }

        // Use PTS to detect duplicate frames (much faster than pixel comparison)
        if (currentPts == _pts && !_pLastFrame.empty()) {
            width = _imageWidth;
            height = _imageHeight;
            pts = _pts;
            return _pLastFrame;
        }

        // Update PTS first
        _pts = currentPts;
        pts = currentPts;

        // Handle non-rotated case
        if (_imageRotateDeg == 0) {
            // Apply display resize if configured
            if (_displayWidth > 0 && _displayHeight > 0 &&
                (currentImage.cols != _displayWidth || currentImage.rows != _displayHeight)) {
                cv::Mat displayResult;
                cv::resize(currentImage, displayResult, cv::Size(_displayWidth, _displayHeight),
                    0, 0, cv::INTER_LINEAR);
                currentImage = displayResult;
                imageW = _displayWidth;
                imageH = _displayHeight;
            }
            _imageWidth = imageW;
            _imageHeight = imageH;
            width = imageW;
            height = imageH;
            _pLastFrame = currentImage;
            return currentImage;
        }

        // Handle rotation case
        int rotatedWidth, rotatedHeight;
        double absAngle = std::abs(_imageRotateDeg);

        if (absAngle == 90.0 || absAngle == 270.0) {
            rotatedWidth = imageH;
            rotatedHeight = imageW;
        }
        else {
            rotatedWidth = imageW;
            rotatedHeight = imageH;
        }

        cv::Point2f center(imageW / 2.0f, imageH / 2.0f);
        cv::Mat rotationMatrix = cv::getRotationMatrix2D(center, _imageRotateDeg, 1.0);

        cv::Mat rotatedImage;
        cv::warpAffine(currentImage, rotatedImage, rotationMatrix,
            cv::Size(rotatedWidth, rotatedHeight),
            cv::INTER_LINEAR,
            cv::BORDER_CONSTANT,
            cv::Scalar(0, 0, 0));

        // Apply display resize if configured
        if (_displayWidth > 0 && _displayHeight > 0 &&
            (rotatedImage.cols != _displayWidth || rotatedImage.rows != _displayHeight)) {
            cv::Mat displayResult;
            cv::resize(rotatedImage, displayResult, cv::Size(_displayWidth, _displayHeight),
                0, 0, cv::INTER_LINEAR);
            rotatedImage = displayResult;
            rotatedWidth = _displayWidth;
            rotatedHeight = _displayHeight;
        }

        // Update dimensions - use calculated dimensions, not cols/rows
        _imageWidth = rotatedWidth;
        _imageHeight = rotatedHeight;
        width = rotatedWidth;
        height = rotatedHeight;

        // Store and return rotated image
        _pLastFrame = rotatedImage;
        return rotatedImage;
    }

    void ANSMJPEGClient::EnableAudio(bool status) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _playerClient->enableAudio(status);
    }
    void ANSMJPEGClient::SetAudioVolume(int volume) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _playerClient->setVolume(volume);

    }

    void ANSMJPEGClient::SetMaxHWDecoders(int maxDecoders) {
        if (maxDecoders >= 0) {
            g_hw_decoder_max = static_cast<uint32>(maxDecoders);
        }
    }
    // NVDEC hardware cap: fixed-function ASIC with ~32 concurrent session limit.
    static constexpr int NVDEC_HW_SESSION_CAP = 32;

    static int estimateMaxSessions(int smCount, size_t totalVramBytes) {
        int smBased;
        if (smCount >= 80)       smBased = 64;
        else if (smCount >= 50)  smBased = 48;
        else if (smCount >= 30)  smBased = 32;
        else if (smCount >= 20)  smBased = 16;
        else                     smBased = 8;

        size_t totalVramMB = totalVramBytes / (1024 * 1024);
        size_t reservedMB = 1024;
        size_t availableMB = (totalVramMB > reservedMB) ? (totalVramMB - reservedMB) : 256;
        int vramBased = static_cast<int>(availableMB / 80);
        if (vramBased < 4) vramBased = 4;

        int result = (smBased < vramBased) ? smBased : vramBased;
        if (result > NVDEC_HW_SESSION_CAP) result = NVDEC_HW_SESSION_CAP;
        return result;
    }

    static int estimateSessionsByVendor(uint32_t vendorId, size_t dedicatedMB, size_t sharedMB) {
        if (vendorId == 0x10DE) {
            size_t availMB = (dedicatedMB > 1024) ? (dedicatedMB - 1024) : 512;
            int sessions = static_cast<int>(availMB / 80);
            if (sessions < 8) sessions = 8;
            if (sessions > 64) sessions = 64;
            return sessions;
        }
        else if (vendorId == 0x8086) {
            size_t effectiveMB = (dedicatedMB > 0) ? dedicatedMB : (sharedMB / 4);
            if (effectiveMB >= 2048)     return 16;
            else if (effectiveMB >= 512) return 12;
            else                         return 8;
        }
        else if (vendorId == 0x1002) {
            if (dedicatedMB >= 4096)      return 32;
            else if (dedicatedMB >= 1024) return 16;
            else                          return 8;
        }
        return 4;
    }

    static const char* vendorIdToName(uint32_t vendorId) {
        if (vendorId == 0x10DE) return "NVIDIA";
        if (vendorId == 0x8086) return "Intel";
        if (vendorId == 0x1002) return "AMD";
        return "Unknown";
    }

#if defined(_WIN32)
    static int AutoConfigureHWDecoders_Platform_MJPEG() {
        IDXGIFactory1* pFactory = nullptr;
        HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&pFactory);
        if (FAILED(hr) || !pFactory) {
            g_hw_decoder_max = 4;
            fprintf(stderr, "[HWDecode] MJPEG AutoConfigure: DXGI unavailable, defaulting to %d sessions\n",
                g_hw_decoder_max);
            return g_hw_decoder_max;
        }

        IDXGIAdapter1* pAdapter = nullptr;
        int totalSessions = 0;
        bool foundHwGpu = false;

        for (UINT i = 0; pFactory->EnumAdapters1(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; i++) {
            DXGI_ADAPTER_DESC1 desc;
            pAdapter->GetDesc1(&desc);
            pAdapter->Release();

            if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) continue;

            char gpuName[128] = {};
            wcstombs(gpuName, desc.Description, sizeof(gpuName) - 1);

            uint32_t vendorId = desc.VendorId;
            size_t dedicatedMB = desc.DedicatedVideoMemory / (1024 * 1024);
            size_t sharedMB = desc.SharedSystemMemory / (1024 * 1024);

            int maxSessions = estimateSessionsByVendor(vendorId, dedicatedMB, sharedMB);

            fprintf(stderr, "[HWDecode] MJPEG AutoConfigure: GPU[%d] \"%s\" Vendor=%s(0x%04X) VRAM=%zuMB Shared=%zuMB -> max %d decode sessions\n",
                i, gpuName, vendorIdToName(vendorId), vendorId, dedicatedMB, sharedMB, maxSessions);

            totalSessions += maxSessions;
            foundHwGpu = true;
        }

        pFactory->Release();

        if (!foundHwGpu) {
            g_hw_decoder_max = 4;
            fprintf(stderr, "[HWDecode] MJPEG AutoConfigure: No hardware GPU found, defaulting to %d sessions (software fallback)\n",
                g_hw_decoder_max);
            return g_hw_decoder_max;
        }

        g_hw_decoder_max = static_cast<uint32>(totalSessions);
        fprintf(stderr, "[HWDecode] MJPEG AutoConfigure: Total %d decode sessions across all GPUs\n", totalSessions);
        return totalSessions;
    }

#elif defined(__linux__)
    static int AutoConfigureHWDecoders_Platform_MJPEG() {
        int totalSessions = 0;
        bool foundHwGpu = false;
        int gpuIndex = 0;

        for (int cardNum = 0; cardNum < 16; cardNum++) {
            char vendorPath[256];
            snprintf(vendorPath, sizeof(vendorPath),
                "/sys/class/drm/card%d/device/vendor", cardNum);

            std::ifstream vendorFile(vendorPath);
            if (!vendorFile.is_open()) continue;

            uint32_t vendorId = 0;
            vendorFile >> std::hex >> vendorId;
            vendorFile.close();

            if (vendorId == 0) continue;

            char gpuName[128] = "Unknown GPU";
            char ueventPath[256];
            snprintf(ueventPath, sizeof(ueventPath),
                "/sys/class/drm/card%d/device/uevent", cardNum);
            std::ifstream ueventFile(ueventPath);
            if (ueventFile.is_open()) {
                std::string line;
                while (std::getline(ueventFile, line)) {
                    if (line.find("PCI_SLOT_NAME=") == 0) {
                        snprintf(gpuName, sizeof(gpuName), "PCI %s", line.substr(14).c_str());
                        break;
                    }
                }
                ueventFile.close();
            }

            size_t dedicatedMB = 0;
            size_t sharedMB = 0;

            char resourcePath[256];
            snprintf(resourcePath, sizeof(resourcePath),
                "/sys/class/drm/card%d/device/resource", cardNum);
            std::ifstream resourceFile(resourcePath);
            if (resourceFile.is_open()) {
                std::string line;
                if (std::getline(resourceFile, line)) {
                    unsigned long long start = 0, end = 0;
                    if (sscanf(line.c_str(), "%llx %llx", &start, &end) == 2 && end > start) {
                        dedicatedMB = (end - start + 1) / (1024 * 1024);
                    }
                }
                resourceFile.close();
            }

            if (vendorId == 0x8086 && dedicatedMB < 512) {
                std::ifstream meminfo("/proc/meminfo");
                if (meminfo.is_open()) {
                    std::string line;
                    while (std::getline(meminfo, line)) {
                        if (line.find("MemTotal:") == 0) {
                            unsigned long totalKB = 0;
                            sscanf(line.c_str(), "MemTotal: %lu", &totalKB);
                            sharedMB = totalKB / 1024;
                            break;
                        }
                    }
                    meminfo.close();
                }
            }

            int maxSessions = estimateSessionsByVendor(vendorId, dedicatedMB, sharedMB);

            fprintf(stderr, "[HWDecode] MJPEG AutoConfigure: GPU[%d] \"%s\" Vendor=%s(0x%04X) VRAM=%zuMB Shared=%zuMB -> max %d VAAPI decode sessions\n",
                gpuIndex, gpuName, vendorIdToName(vendorId), vendorId, dedicatedMB, sharedMB, maxSessions);

            totalSessions += maxSessions;
            foundHwGpu = true;
            gpuIndex++;
        }

        if (!foundHwGpu) {
            g_hw_decoder_max = 4;
            fprintf(stderr, "[HWDecode] MJPEG AutoConfigure: No hardware GPU found in /sys/class/drm, defaulting to %d sessions (software fallback)\n",
                g_hw_decoder_max);
            return g_hw_decoder_max;
        }

        g_hw_decoder_max = static_cast<uint32>(totalSessions);
        fprintf(stderr, "[HWDecode] MJPEG AutoConfigure: Total %d VAAPI decode sessions across all GPUs\n", totalSessions);
        return totalSessions;
    }

#else
    static int AutoConfigureHWDecoders_Platform_MJPEG() {
        g_hw_decoder_max = 4;
        fprintf(stderr, "[HWDecode] MJPEG AutoConfigure: Unsupported platform, defaulting to %d sessions\n",
            g_hw_decoder_max);
        return g_hw_decoder_max;
    }
#endif

    int ANSMJPEGClient::AutoConfigureHWDecoders(int maxPerGpuOverride) {
        // Skip the CUDA probe on non-NVIDIA hardware — the Platform fallback
        // handles Intel/AMD auto configuration.  See ANSCVVendorGate.h.
        if (!anscv_vendor_gate::IsNvidiaGpuAvailable()) {
            return AutoConfigureHWDecoders_Platform_MJPEG();
        }

        int gpuCount = 0;
        cudaError_t err = cudaGetDeviceCount(&gpuCount);
        if (err != cudaSuccess || gpuCount <= 0) {
            return AutoConfigureHWDecoders_Platform_MJPEG();
        }

        std::vector<int> maxPerGpuList(gpuCount);
        int total = 0;

        for (int i = 0; i < gpuCount; i++) {
            cudaDeviceProp prop;
            cudaGetDeviceProperties(&prop, i);
            int smCount = prop.multiProcessorCount;

            if (maxPerGpuOverride > 0) {
                maxPerGpuList[i] = maxPerGpuOverride;
            } else {
                maxPerGpuList[i] = estimateMaxSessions(smCount, prop.totalGlobalMem);
            }

            total += maxPerGpuList[i];
            size_t vramMB = prop.totalGlobalMem / (1024 * 1024);
            fprintf(stderr, "[HWDecode] MJPEG AutoConfigure: GPU[%d] \"%s\" SM=%d VRAM=%zuMB -> max %d decode sessions\n",
                i, prop.name, smCount, vramMB, maxPerGpuList[i]);
        }

        HWDecoderPool::instance().configure(maxPerGpuList);

        return total;
    }
    void ANSMJPEGClient::SetHWDecoding(int hwMode, int preferredGpu) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _playerClient->setHWDecoding(hwMode, preferredGpu);
    }
    bool ANSMJPEGClient::IsHWDecodingActive() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        return _playerClient->isHWDecodingActive();
    }
    int ANSMJPEGClient::GetHWDecodingGpuIndex() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        return _playerClient->getHWDecodingGpuIndex();
    }
    void ANSMJPEGClient::SetDisplayResolution(int width, int height) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _displayWidth = width;
        _displayHeight = height;
    }
    void ANSMJPEGClient::SetImageQuality(int mode) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _playerClient->setImageQuality(mode);  // 0=fast (AI), 1=quality (display)
    }
    void ANSMJPEGClient::SetTargetFPS(double intervalMs) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _playerClient->setTargetFPS(intervalMs);  // 0=no limit, 100=~10FPS, 200=~5FPS
    }
    void ANSMJPEGClient::SetNV12FastPath(bool enable) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _useNV12FastPath = enable;
    }
    AVFrame* ANSMJPEGClient::GetNV12Frame() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        return _playerClient->getNV12Frame();  // Returns clone, caller must av_frame_free
    }
    AVFrame* ANSMJPEGClient::GetCudaHWFrame() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        return _playerClient->getCudaHWFrame();
    }
    bool ANSMJPEGClient::IsCudaHWAccel() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        return _playerClient->isCudaHWAccel();
    }
    std::string ANSMJPEGClient::MatToBinaryData(const cv::Mat& image) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        if (!image.empty()) {
            if ((image.data != nullptr) && (image.u != nullptr)) {
                try {
                    // Encode the image to a memory buffer
                    std::vector<uchar> imageData;
                    bool success = cv::imencode(".jpg", image, imageData);
                    if (!success) {
                        this->_logger.LogError("ANSMJPEGClient::MatToBinaryData. Error:", "Failed to encode the image.", __FILE__, __LINE__);
                        return "";
                    }
                    std::string binaryData(imageData.begin(), imageData.end());
                    return binaryData;
                }
                catch (const std::exception& e) {
                    this->_logger.LogFatal("ANSMJPEGClient::MatToBinaryData. Error:", e.what(), __FILE__, __LINE__);
                    return "";
                }
                catch (...) {
                    this->_logger.LogFatal("ANSMJPEGClient::MatToBinaryData. Error:", "Caught unknown exception!", __FILE__, __LINE__);
                    return "";
                }
            }
            else return "";
        }
        else return "";
    }

}

extern "C" __declspec(dllexport) int CreateANSMJPEGHandle(ANSCENTER::ANSMJPEGClient * *Handle, const char* licenseKey, const char* username, const char* password, const char* url) {
    if (!Handle || !licenseKey || !url) return -1;
    try {
        auto ptr = std::make_unique<ANSCENTER::ANSMJPEGClient>();
        std::string _username = username ? username : "";
        std::string _password = password ? password : "";
        bool result = false;
        if (_username.empty() && _password.empty()) result = ptr->Init(licenseKey, url);
        else result = ptr->Init(licenseKey, username, password, url);
        if (result) {
            *Handle = ptr.release();
            extern void anscv_unregister_handle(void*);
            extern void anscv_register_handle(void*, void(*)(void*));
            anscv_register_handle(*Handle, [](void* p) {
                auto* h = static_cast<ANSCENTER::ANSMJPEGClient*>(p);
                try { h->Stop(); } catch (...) {}
                try { h->Destroy(); } catch (...) {}
                try { delete h; } catch (...) {}
            });
            return 1;
        }
        *Handle = nullptr;
        return 0;
    } catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ReleaseANSMJPEGHandle(ANSCENTER::ANSMJPEGClient * *Handle) {
    if (Handle == nullptr || *Handle == nullptr) return -1;
    try {
        extern void anscv_unregister_handle(void*);
        anscv_unregister_handle(*Handle);
        // unique_ptr destructor calls ~ANSMJPEGClient which calls Destroy()
        std::unique_ptr<ANSCENTER::ANSMJPEGClient> ptr(*Handle);
        *Handle = nullptr;
        return 0;
    } catch (...) {
        if (Handle) *Handle = nullptr;
        return -1;
    }
}
extern "C" __declspec(dllexport) int GetMJPEGStrImage(ANSCENTER::ANSMJPEGClient * *Handle, int& width, int& height, int64_t & timeStamp, std::string & jpegImage) {
    if (Handle == nullptr || *Handle == nullptr) return -1;
    try {
        jpegImage = (*Handle)->GetJpegImage(width, height, timeStamp);
        if (!jpegImage.empty()) return 1;
        else return 0;
    }
	catch (const std::exception& e) {
		std::cerr << "Error getting MJPEG image: " << e.what() << std::endl;
		return 0;
	}
	catch (...) {
		std::cerr << "Error getting MJPEG image: Unknown exception." << std::endl;
		return 0;
	}
}
extern "C" __declspec(dllexport) int GetMJPEGImage(ANSCENTER::ANSMJPEGClient * *Handle, int& width, int& height, int64_t & timeStamp, LStrHandle jpegImage) {
    if (Handle == nullptr || *Handle == nullptr) return -1;
    try {
    std::string jpegString = (*Handle)->GetJpegImage(width, height, timeStamp);
    int size = jpegString.length();
    if (size > 0) {
        MgErr error;
        error = DSSetHandleSize(jpegImage, sizeof(int32) + size * sizeof(uChar));
        if (error == noErr) {
            (*jpegImage)->cnt = size;
            memcpy((*jpegImage)->str, jpegString.c_str(), size);
            return 1;
        }
        else {
            std::cerr << "Error resizing jpegImage handle: " << error << std::endl;
            return 0;
        }
    }
    else {
        std::cerr << "No image data retrieved from MJPEG client." << std::endl;
        return 0;
    }
    }
	catch (const std::exception& e) {
		std::cerr << "Error getting MJPEG image: " << e.what() << std::endl;
		return 0;
	}
	catch (...) {
		std::cerr << "Error getting MJPEG image: Unknown exception." << std::endl;
		return 0;
	}
}

extern "C" __declspec(dllexport) int GetMJPEGCVImage(ANSCENTER::ANSMJPEGClient** Handle, int& width, int& height, int64_t& timeStamp, cv::Mat** image) {
    if (!Handle || !*Handle || !image) {
        std::cerr << "Error: Invalid input parameters in GetMJPEGCVImage" << std::endl;
        return -1;
    }

    try {
        cv::Mat img = (*Handle)->GetImage(width, height, timeStamp);

        if (img.empty()) {
            return 0;
        }

        // Thread-safe Mat pointer swap (anscv_mat_replace has its own internal lock)
        anscv_mat_replace(image, std::move(img));

        // NV12 GPU fast path (optional — disabled by default for stability)
        if ((*Handle)->IsNV12FastPath()) {
            int gpuIdx = (*Handle)->GetHWDecodingGpuIndex();
            AVFrame* cudaHW = (*Handle)->GetCudaHWFrame();
            if (cudaHW) {
                AVFrame* cpuNV12 = (*Handle)->GetNV12Frame();
                gpu_frame_attach_cuda(*image, cudaHW, gpuIdx, timeStamp, cpuNV12);
            } else {
                AVFrame* nv12 = (*Handle)->GetNV12Frame();
                if (nv12) {
                    gpu_frame_attach(*image, nv12, gpuIdx, timeStamp);
                }
            }
        }

        return 1;
    }
    catch (const cv::Exception& e) {
        std::cerr << "OpenCV exception in GetMJPEGCVImage: " << e.what() << std::endl;
        return -2;
    }
    catch (const std::exception& e) {
        std::cerr << "Exception in GetMJPEGCVImage: " << e.what() << std::endl;
        return -2;
    }
    catch (...) {
        std::cerr << "Unknown exception in GetMJPEGCVImage" << std::endl;
        return -2;
    }
}
extern "C" __declspec(dllexport) int StartMJPEG(ANSCENTER::ANSMJPEGClient * *Handle) {
    if (Handle == nullptr || *Handle == nullptr) return -1;
    try {
        bool result = (*Handle)->Start();
        if (result) return 1;
        else return 0;
    }
	catch (const std::exception& e) {
		std::cerr << "Error starting MJPEG client: " << e.what() << std::endl;
		return 0;
	}
	catch (...) {
		std::cerr << "Error starting MJPEG client: Unknown exception." << std::endl;
		return 0;
	}
}
extern "C" __declspec(dllexport) int ReconnectMJPEG(ANSCENTER::ANSMJPEGClient * *Handle) {
    if (Handle == nullptr || *Handle == nullptr) return -1;
    try {
        bool result = (*Handle)->Reconnect();
        if (result) return 1;
        else return 0;
    }
	catch (const std::exception& e) {
		std::cerr << "Error reconnecting MJPEG client: " << e.what() << std::endl;
		return 0;
	}
	catch (...) {
		std::cerr << "Error reconnecting MJPEG client: Unknown exception." << std::endl;
		return 0;
	}
}
extern "C" __declspec(dllexport) int StopMJPEG(ANSCENTER::ANSMJPEGClient * *Handle) {
    if (Handle == nullptr || *Handle == nullptr) return -1;
    try {
        bool result = (*Handle)->Stop();
        if (result) return 1;
        else return 0;
    }
	catch (const std::exception& e) {
		std::cerr << "Error stopping MJPEG client: " << e.what() << std::endl;
		return 0;
	}
	catch (...) {
		std::cerr << "Error stopping MJPEG client: Unknown exception." << std::endl;
		return 0;
	}
}
extern "C" __declspec(dllexport) int PauseMJPEG(ANSCENTER::ANSMJPEGClient** Handle) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		bool result = (*Handle)->Pause();
		if (result) return 1;
		else return 0;
	} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int IsMJPEGPaused(ANSCENTER::ANSMJPEGClient * *Handle) {
    if (Handle == nullptr || *Handle == nullptr) return -1;
    try {
        bool result = (*Handle)->IsPaused();
        if (result) return 1;
        else return 0;
    } catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int IsMJPEGRunning(ANSCENTER::ANSMJPEGClient * *Handle) {
    if (Handle == nullptr || *Handle == nullptr) return -1;
    try {
        bool result = (*Handle)->IsPlaying();
        if (result) return 1;
        else return 0;
    } catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int IsMJPEGRecording(ANSCENTER::ANSMJPEGClient * *Handle) {
    if (Handle == nullptr || *Handle == nullptr) return -1;
    try {
        bool result = (*Handle)->IsRecording();
        if (result) return 1;
        else return 0;
    } catch (...) { return -1; }
}

extern "C" __declspec(dllexport) void SetMJPEGAudioVolume(ANSCENTER::ANSMJPEGClient * *Handle, int volume) {
    if (Handle == nullptr || *Handle == nullptr) return;
    try {
        (*Handle)->SetAudioVolume(volume);
    } catch (...) { }
}
extern "C" __declspec(dllexport) void EnableMJPEGAudioVolume(ANSCENTER::ANSMJPEGClient * *Handle, int status) {
    if (Handle == nullptr || *Handle == nullptr) return;
    try {
        bool audioStatus = false;
        if (status == 1)audioStatus = true;
        (*Handle)->EnableAudio(audioStatus);
    } catch (...) { }
}

extern "C" __declspec(dllexport) void SetMJPEGImageRotation(ANSCENTER::ANSMJPEGClient * *Handle, double rotationAngle) {
    if (Handle == nullptr || *Handle == nullptr) return;
    try {
        (*Handle)->SetImageRotate(rotationAngle);
    } catch (...) { }
}

extern "C" __declspec(dllexport) int SetBBoxMJPEG(ANSCENTER::ANSMJPEGClient** Handle, int x, int y, int width, int height) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Rect bbox(x, y, width, height);
		(*Handle)->SetBBox(bbox);
		return 1;
	} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int SetCropFlagMJPEG(ANSCENTER::ANSMJPEGClient** Handle, int cropFlag) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		bool crop = false;
		if (cropFlag == 1) crop = true;
		(*Handle)->SetCrop(crop);
		return 1;
	} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) void SetMJPEGMaxHWDecoders(int maxDecoders) {
    ANSCENTER::ANSMJPEGClient::SetMaxHWDecoders(maxDecoders);
}
extern "C" __declspec(dllexport) int AutoConfigureMJPEGHWDecoders(int maxPerGpuOverride) {
    return ANSCENTER::ANSMJPEGClient::AutoConfigureHWDecoders(maxPerGpuOverride);
}
extern "C" __declspec(dllexport) void SetMJPEGHWDecoding(ANSCENTER::ANSMJPEGClient** Handle, int hwMode, int preferredGpu) {
    if (Handle == nullptr || *Handle == nullptr) return;
    try {
        (*Handle)->SetHWDecoding(hwMode, preferredGpu);
    } catch (...) { }
}
extern "C" __declspec(dllexport) int IsMJPEGHWDecodingActive(ANSCENTER::ANSMJPEGClient** Handle) {
    if (Handle == nullptr || *Handle == nullptr) return -1;
    try {
        return (*Handle)->IsHWDecodingActive() ? 1 : 0;
    } catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int GetMJPEGHWDecodingGpuIndex(ANSCENTER::ANSMJPEGClient** Handle) {
    if (Handle == nullptr || *Handle == nullptr) return -1;
    try {
        return (*Handle)->GetHWDecodingGpuIndex();
    } catch (...) { return -1; }
}
extern "C" __declspec(dllexport) void SetMJPEGImageQuality(ANSCENTER::ANSMJPEGClient** Handle, int mode) {
    if (Handle == nullptr || *Handle == nullptr) return;
    try {
        (*Handle)->SetImageQuality(mode);
    } catch (...) { }
}
extern "C" __declspec(dllexport) void SetMJPEGDisplayResolution(ANSCENTER::ANSMJPEGClient** Handle, int width, int height) {
    if (Handle == nullptr || *Handle == nullptr) return;
    try {
        (*Handle)->SetDisplayResolution(width, height);
    } catch (...) { }
}
extern "C" __declspec(dllexport) void SetMJPEGTargetFPS(ANSCENTER::ANSMJPEGClient** Handle, double intervalMs) {
    if (Handle == nullptr || *Handle == nullptr) return;
    try {
        (*Handle)->SetTargetFPS(intervalMs);
    } catch (...) { }
}
extern "C" __declspec(dllexport) void SetMJPEGNV12FastPath(ANSCENTER::ANSMJPEGClient** Handle, int enable) {
    if (Handle == nullptr || *Handle == nullptr) return;
    try {
        (*Handle)->SetNV12FastPath(enable != 0);
    } catch (...) { }
}

// ============================================================================
// V2 entry points — accept handle as uint64_t by value (LabVIEW safe)
// ============================================================================

extern "C" __declspec(dllexport) int GetMJPEGImage_V2(uint64_t handleVal, int& width, int& height, int64_t& timeStamp, LStrHandle jpegImage) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        std::string jpegString = h->GetJpegImage(width, height, timeStamp);
        int size = jpegString.length();
        if (size > 0) {
            MgErr error;
            error = DSSetHandleSize(jpegImage, sizeof(int32) + size * sizeof(uChar));
            if (error == noErr) {
                (*jpegImage)->cnt = size;
                memcpy((*jpegImage)->str, jpegString.c_str(), size);
                return 1;
            }
            else {
                std::cerr << "Error resizing jpegImage handle: " << error << std::endl;
                return 0;
            }
        }
        else {
            std::cerr << "No image data retrieved from MJPEG client." << std::endl;
            return 0;
        }
    }
    catch (const std::exception& e) {
        std::cerr << "Error getting MJPEG image: " << e.what() << std::endl;
        return 0;
    }
    catch (...) {
        std::cerr << "Error getting MJPEG image: Unknown exception." << std::endl;
        return 0;
    }
}

extern "C" __declspec(dllexport) int GetMJPEGCVImage_V2(uint64_t handleVal, int& width, int& height, int64_t& timeStamp, cv::Mat** image) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    if (!image) return -1;
    try {
        cv::Mat img = h->GetImage(width, height, timeStamp);
        if (img.empty()) {
            return 0;
        }
        anscv_mat_replace(image, std::move(img));

        // Attach NV12 frame for GPU fast-path inference (side-table registry)
        int gpuIdx = h->GetHWDecodingGpuIndex();
        AVFrame* cudaHW = h->GetCudaHWFrame();
        if (cudaHW) {
            AVFrame* cpuNV12 = h->GetNV12Frame();
            gpu_frame_attach_cuda(*image, cudaHW, gpuIdx, timeStamp, cpuNV12);
        } else {
            AVFrame* nv12 = h->GetNV12Frame();
            if (nv12) {
                gpu_frame_attach(*image, nv12, gpuIdx, timeStamp);
            }
        }

        return 1;
    }
    catch (const cv::Exception& e) {
        std::cerr << "OpenCV exception in GetMJPEGCVImage_V2: " << e.what() << std::endl;
        return -2;
    }
    catch (const std::exception& e) {
        std::cerr << "Exception in GetMJPEGCVImage_V2: " << e.what() << std::endl;
        return -2;
    }
    catch (...) {
        std::cerr << "Unknown exception in GetMJPEGCVImage_V2" << std::endl;
        return -2;
    }
}

extern "C" __declspec(dllexport) int StartMJPEG_V2(uint64_t handleVal) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        bool result = h->Start();
        if (result) return 1;
        else return 0;
    }
    catch (const std::exception& e) {
        std::cerr << "Error starting MJPEG client: " << e.what() << std::endl;
        return 0;
    }
    catch (...) {
        std::cerr << "Error starting MJPEG client: Unknown exception." << std::endl;
        return 0;
    }
}

extern "C" __declspec(dllexport) int ReconnectMJPEG_V2(uint64_t handleVal) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        bool result = h->Reconnect();
        if (result) return 1;
        else return 0;
    }
    catch (const std::exception& e) {
        std::cerr << "Error reconnecting MJPEG client: " << e.what() << std::endl;
        return 0;
    }
    catch (...) {
        std::cerr << "Error reconnecting MJPEG client: Unknown exception." << std::endl;
        return 0;
    }
}

extern "C" __declspec(dllexport) int StopMJPEG_V2(uint64_t handleVal) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        bool result = h->Stop();
        if (result) return 1;
        else return 0;
    }
    catch (const std::exception& e) {
        std::cerr << "Error stopping MJPEG client: " << e.what() << std::endl;
        return 0;
    }
    catch (...) {
        std::cerr << "Error stopping MJPEG client: Unknown exception." << std::endl;
        return 0;
    }
}

extern "C" __declspec(dllexport) int PauseMJPEG_V2(uint64_t handleVal) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        bool result = h->Pause();
        if (result) return 1;
        else return 0;
    } catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int IsMJPEGPaused_V2(uint64_t handleVal) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        bool result = h->IsPaused();
        if (result) return 1;
        else return 0;
    } catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int IsMJPEGRunning_V2(uint64_t handleVal) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        bool result = h->IsPlaying();
        if (result) return 1;
        else return 0;
    } catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int IsMJPEGRecording_V2(uint64_t handleVal) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        bool result = h->IsRecording();
        if (result) return 1;
        else return 0;
    } catch (...) { return -1; }
}

extern "C" __declspec(dllexport) void SetMJPEGAudioVolume_V2(uint64_t handleVal, int volume) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return;
    try {
        h->SetAudioVolume(volume);
    } catch (...) { }
}

extern "C" __declspec(dllexport) void EnableMJPEGAudioVolume_V2(uint64_t handleVal, int status) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return;
    try {
        bool audioStatus = false;
        if (status == 1) audioStatus = true;
        h->EnableAudio(audioStatus);
    } catch (...) { }
}

extern "C" __declspec(dllexport) void SetMJPEGImageRotation_V2(uint64_t handleVal, double rotationAngle) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return;
    try {
        h->SetImageRotate(rotationAngle);
    } catch (...) { }
}

extern "C" __declspec(dllexport) int SetBBoxMJPEG_V2(uint64_t handleVal, int x, int y, int width, int height) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        cv::Rect bbox(x, y, width, height);
        h->SetBBox(bbox);
        return 1;
    } catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int SetCropFlagMJPEG_V2(uint64_t handleVal, int cropFlag) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        bool crop = false;
        if (cropFlag == 1) crop = true;
        h->SetCrop(crop);
        return 1;
    } catch (...) { return -1; }
}

extern "C" __declspec(dllexport) void SetMJPEGHWDecoding_V2(uint64_t handleVal, int hwMode, int preferredGpu) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return;
    try {
        h->SetHWDecoding(hwMode, preferredGpu);
    } catch (...) { }
}

extern "C" __declspec(dllexport) int IsMJPEGHWDecodingActive_V2(uint64_t handleVal) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        return h->IsHWDecodingActive() ? 1 : 0;
    } catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int GetMJPEGHWDecodingGpuIndex_V2(uint64_t handleVal) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
    try {
        return h->GetHWDecodingGpuIndex();
    } catch (...) { return -1; }
}

extern "C" __declspec(dllexport) void SetMJPEGImageQuality_V2(uint64_t handleVal, int mode) {
    auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return;
    try {
        h->SetImageQuality(mode);
    } catch (...) { }
}