// ============================================================================
// OvLoader.cpp — OpenVINO DLL discovery and pre-loading
//
// Loading order (dependency chain):
//   1. Intel TBB (tbb12.dll / tbbmalloc.dll)  — required by openvino.dll
//   2. openvino.dll                            — core runtime
//   3. openvino_c.dll                          — C API (optional, version query)
//
// Plugins (openvino_intel_cpu_plugin, openvino_intel_gpu_plugin, etc.) and
// frontends (openvino_ir_frontend, openvino_onnx_frontend, etc.) are loaded
// on-demand by OpenVINO's internal plugin system.  They are discovered
// automatically because the shared directory is injected into the DLL
// search path by ANSLibsLoader before OvLoader::Initialize() runs.
// ============================================================================

#include "OvLoader.h"
#include "DynLibUtils.h"

#include <iostream>
#include <mutex>

using namespace ANSCENTER::DynLib;

// ─────────────────────────────────────────────────────────────────────────────
// Static member definitions
// ─────────────────────────────────────────────────────────────────────────────
std::mutex OvLoader::s_mutex;
bool       OvLoader::s_initialized = false;
OvInfo     OvLoader::s_info;
LibHandle  OvLoader::s_hTbb       = nullptr;
LibHandle  OvLoader::s_hTbbMalloc = nullptr;
LibHandle  OvLoader::s_hOvCore    = nullptr;
LibHandle  OvLoader::s_hOvC       = nullptr;

// ─────────────────────────────────────────────────────────────────────────────
// Candidate lists — search ANSCENTER shared dir first, then system PATH
// ─────────────────────────────────────────────────────────────────────────────

std::vector<std::string> OvLoader::TbbCandidates(const std::string& shared_dir)
{
    std::vector<std::string> v;

    // ── Priority 1: ANSCENTER shared directory ────────────────────────────────
#ifdef _WIN32
    v.push_back(JoinPath(shared_dir, "tbb12.dll"));
    v.push_back(JoinPath(shared_dir, "tbb.dll"));
#else
    v.push_back(JoinPath(shared_dir, "libtbb.so.12"));
    v.push_back(JoinPath(shared_dir, "libtbb.so.2"));
    v.push_back(JoinPath(shared_dir, "libtbb.so"));
#endif

    // ── Priority 2: System PATH / LD_LIBRARY_PATH ─────────────────────────────
#ifdef _WIN32
    v.push_back("tbb12.dll");
    v.push_back("tbb.dll");
#else
    v.push_back("libtbb.so.12");
    v.push_back("libtbb.so.2");
    v.push_back("libtbb.so");
#endif

    return v;
}

std::vector<std::string> OvLoader::TbbMallocCandidates(const std::string& shared_dir)
{
    std::vector<std::string> v;

#ifdef _WIN32
    v.push_back(JoinPath(shared_dir, "tbbmalloc.dll"));
    v.push_back("tbbmalloc.dll");
#else
    v.push_back(JoinPath(shared_dir, "libtbbmalloc.so.2"));
    v.push_back(JoinPath(shared_dir, "libtbbmalloc.so"));
    v.push_back("libtbbmalloc.so.2");
    v.push_back("libtbbmalloc.so");
#endif

    return v;
}

std::vector<std::string> OvLoader::OvCoreCandidates(const std::string& shared_dir)
{
    std::vector<std::string> v;

#ifdef _WIN32
    v.push_back(JoinPath(shared_dir, "openvino.dll"));
    v.push_back("openvino.dll");
#else
    v.push_back(JoinPath(shared_dir, "libopenvino.so"));
    v.push_back("libopenvino.so");
#endif

    return v;
}

std::vector<std::string> OvLoader::OvCApiCandidates(const std::string& shared_dir)
{
    std::vector<std::string> v;

#ifdef _WIN32
    v.push_back(JoinPath(shared_dir, "openvino_c.dll"));
    v.push_back("openvino_c.dll");
#else
    v.push_back(JoinPath(shared_dir, "libopenvino_c.so"));
    v.push_back("libopenvino_c.so");
#endif

    return v;
}

// ─────────────────────────────────────────────────────────────────────────────
// Version extraction via OpenVINO C API
//
// The C API exports ov_get_openvino_version() which fills an ov_version_t
// struct with a buildNumber string (e.g. "2024.6.0-17404-4c0f47d2335").
// We replicate the struct layout locally to avoid including OpenVINO headers.
// ─────────────────────────────────────────────────────────────────────────────
std::string OvLoader::TryGetVersion(LibHandle hOvC)
{
    if (!hOvC) return {};

    // Replicate the ov_version_t layout from openvino/c/ov_common.h
    struct ov_version_local {
        const char* buildNumber;
        const char* description;
    };

    using fn_get_version  = int  (*)(ov_version_local*);
    using fn_free_version = void (*)(ov_version_local*);

    fn_get_version  pfnGet  = nullptr;
    fn_free_version pfnFree = nullptr;

    Bind(hOvC, "ov_get_openvino_version", pfnGet);
    Bind(hOvC, "ov_version_free",         pfnFree);

    if (!pfnGet) return {};

    ov_version_local ver = {};
    if (pfnGet(&ver) == 0 && ver.buildNumber) {
        std::string result = ver.buildNumber;
        if (pfnFree) pfnFree(&ver);
        return result;
    }
    return {};
}

// ─────────────────────────────────────────────────────────────────────────────
// Initialize
// ─────────────────────────────────────────────────────────────────────────────
const OvInfo& OvLoader::Initialize(const std::string& shared_dir, bool verbose)
{
    std::lock_guard<std::mutex> lock(s_mutex);
    if (s_initialized) return s_info;

    // Clean up any partially-loaded handles from a previous failed attempt
    if (s_hOvC)       { FreeLib(s_hOvC);       s_hOvC       = nullptr; }
    if (s_hOvCore)    { FreeLib(s_hOvCore);    s_hOvCore    = nullptr; }
    if (s_hTbbMalloc) { FreeLib(s_hTbbMalloc); s_hTbbMalloc = nullptr; }
    if (s_hTbb)       { FreeLib(s_hTbb);       s_hTbb       = nullptr; }

    if (verbose) {
        std::cout << "==================================================" << std::endl;
        std::cout << "[OvLoader] Discovering OpenVINO libraries..."       << std::endl;
        std::cout << "  Shared dir : " << shared_dir                      << std::endl;
        std::cout << "==================================================" << std::endl;
    }

    // ── STEP 1: Inject shared dir into search path ───────────────────────────
    // Ensures plugins/frontends are discoverable by OpenVINO's internal loader.
    InjectDllSearchPath(shared_dir);

    // ── STEP 2: Pre-load Intel TBB (dependency of openvino.dll) ──────────────
    std::string tbbPath;
    s_hTbb = FindAndLoad(TbbCandidates(shared_dir), tbbPath, verbose);
    if (s_hTbb) {
        if (verbose)
            std::cout << "[OvLoader] TBB loaded: " << tbbPath << std::endl;
    } else if (verbose) {
        std::cout << "[OvLoader] WARNING: TBB not found "
                  << "(openvino.dll may fail to load)." << std::endl;
    }

    std::string tbbMallocPath;
    s_hTbbMalloc = FindAndLoad(TbbMallocCandidates(shared_dir),
                               tbbMallocPath, verbose);
    if (!s_hTbbMalloc && verbose) {
        std::cout << "[OvLoader] tbbmalloc not found (optional)." << std::endl;
    }

    // ── STEP 3: Pre-load openvino.dll (core runtime) ─────────────────────────
    std::string corePath;
    s_hOvCore = FindAndLoad(OvCoreCandidates(shared_dir), corePath, verbose);
    if (s_hOvCore) {
        s_info.dllPath = corePath;
        s_info.loaded  = true;

        if (verbose)
            std::cout << "[OvLoader] OpenVINO core loaded: " << corePath
                      << std::endl;
    } else {
        std::cout << "[OvLoader] WARNING: openvino.dll not found in "
                  << shared_dir << " or system PATH." << std::endl;
        std::cout << "[OvLoader] OpenVINO will rely on import-lib resolution "
                  << "at load time." << std::endl;
    }

    // ── STEP 4: Pre-load openvino_c.dll + query version ──────────────────────
    std::string cApiPath;
    s_hOvC = FindAndLoad(OvCApiCandidates(shared_dir), cApiPath, verbose);
    if (s_hOvC) {
        s_info.version = TryGetVersion(s_hOvC);
        if (!s_info.version.empty() && verbose) {
            std::cout << "[OvLoader] OpenVINO version = " << s_info.version
                      << std::endl;
        }
    } else if (verbose) {
        std::cout << "[OvLoader] openvino_c.dll not found "
                  << "(version detection skipped)." << std::endl;
    }

    s_initialized = true;

    if (verbose) {
        std::cout << "[OvLoader] "
                  << (s_info.loaded
                        ? "initialised successfully."
                        : "WARNING — openvino.dll not pre-loaded.")
                  << std::endl;
        std::cout << "==================================================" << std::endl;
    }
    return s_info;
}

// ─────────────────────────────────────────────────────────────────────────────
// Shutdown
// ─────────────────────────────────────────────────────────────────────────────
void OvLoader::Shutdown()
{
    std::lock_guard<std::mutex> lock(s_mutex);

    // Release in reverse load order
    if (s_hOvC)       { FreeLib(s_hOvC);       s_hOvC       = nullptr; }
    if (s_hOvCore)    { FreeLib(s_hOvCore);    s_hOvCore    = nullptr; }
    if (s_hTbbMalloc) { FreeLib(s_hTbbMalloc); s_hTbbMalloc = nullptr; }
    if (s_hTbb)       { FreeLib(s_hTbb);       s_hTbb       = nullptr; }

    s_info = OvInfo{};
    s_initialized = false;
}
