// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include "ANSCVVendorGate.h"  // anscv_vendor_gate::IsNvidiaGpuAvailable()
#include "ANSLicense.h"        // ANSCENTER::EngineType, CheckHardwareInformation
#include <mutex>
#include <unordered_map>
#include <iostream>
#include <functional>

// ── Global handle registry for shutdown cleanup ──────────────────────────
// Streaming handle creators register a stop-callback on creation.
// DLL_PROCESS_DETACH invokes all callbacks so threads exit before DLL unmaps.

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

static std::unordered_map<void*, std::function<void()>>& shutdownRegistry() {
    static std::unordered_map<void*, std::function<void()>> r;
    return r;
}

// Called by Create*Handle functions to register a cleanup callback
extern "C" void anscv_register_handle(void* handle, void(*stopFn)(void*)) {
    if (!handle || !stopFn) return;
    std::lock_guard<std::mutex> lk(shutdownMutex());
    shutdownRegistry()[handle] = [=]() { stopFn(handle); };
}

// Called by Release*Handle functions to unregister (handle properly cleaned up)
extern "C" void anscv_unregister_handle(void* handle) {
    if (!handle) return;
    std::lock_guard<std::mutex> lk(shutdownMutex());
    shutdownRegistry().erase(handle);
}

// Stop all leaked handles
static void stopAllHandles() {
    std::unordered_map<void*, std::function<void()>> copy;
    {
        std::lock_guard<std::mutex> lk(shutdownMutex());
        copy.swap(shutdownRegistry());
    }
    for (auto& [ptr, fn] : copy) {
        try { fn(); } catch (...) {}
    }
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     ) noexcept
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        // Pin the DLL so it is never unmapped from the process address space.
        // ANSCV creates background threads (RTSP receive, timers) that may
        // still be running when LabVIEW's CLR/COM shutdown unloads the DLL.
        // If the DLL is unmapped while threads are executing its code, the
        // threads crash with an access violation at <Unloaded_ANSCV.dll>.
        // Pinning keeps the code pages mapped; the OS kills all threads when
        // the process exits, so this is safe and is Microsoft's recommended
        // pattern for DLLs that own threads.
        //
        // CRITICAL: do NOT call CheckHardwareInformation() or
        // anscv_vendor_gate::IsNvidiaGpuAvailable() here.  DllMain holds the
        // OS loader lock (LdrpLoaderLock).  CheckHardwareInformation touches
        // hwinfo → DXGI / WMI / COM which internally call LoadLibrary; doing
        // that while holding the loader lock causes a classic loader-lock
        // deadlock (confirmed by stress-test hang).  The vendor gate will
        // lazy-initialise on the first real call from worker code, which
        // runs with the loader lock released.
        {
            HMODULE hSelf = nullptr;
            GetModuleHandleExW(
                GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
                GET_MODULE_HANDLE_EX_FLAG_PIN,
                reinterpret_cast<LPCWSTR>(&DllMain),
                &hSelf);
        }
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        // CRITICAL: Do NOT call stopAllHandles() here.
        //
        // DllMain holds the OS loader lock (LdrpLoaderLock).  Destroying
        // streaming handles (CRtspPlayer → CVideoPlayer → CWAudioPlay)
        // calls dsound!CThread::Terminate which waits for the DirectSound
        // admin thread to exit.  That thread needs the loader lock to shut
        // down → classic deadlock.  (Confirmed by crash dump analysis:
        // Thread 0 holds LdrpLoaderLock and waits for dsound thread 331,
        // which is blocked on LdrpLoaderLock.)
        //
        // If lpReserved is non-NULL the process is terminating — the OS
        // will tear down all threads and reclaim resources; cleanup is
        // unnecessary and dangerous.  If lpReserved is NULL (dynamic
        // FreeLibrary), LabVIEW should have called Release*Handle first.
        // Log leaked handles for diagnostics but do NOT stop them here.
        if (lpReserved == nullptr) {
            // Dynamic unload — warn about leaked handles (non-blocking)
            try {
                std::lock_guard<std::mutex> lk(shutdownMutex());
                if (!shutdownRegistry().empty()) {
                    std::cerr << "[ANSCV] WARNING: " << shutdownRegistry().size()
                              << " streaming handle(s) leaked at DLL unload."
                              << " Call Release*Handle before FreeLibrary." << std::endl;
                }
            } catch (...) {}
        }
        break;
    }
    return TRUE;
}
