// ============================================================================
// CvLoader.cpp — OpenCV DLL discovery and pre-loading
// ============================================================================

#include "CvLoader.h"
#include "DynLibUtils.h"

#include <iostream>
#include <mutex>

using namespace ANSCENTER::DynLib;

// ─────────────────────────────────────────────────────────────────────────────
// Static member definitions
// ─────────────────────────────────────────────────────────────────────────────
std::mutex CvLoader::s_mutex;
bool       CvLoader::s_initialized = false;
CvInfo     CvLoader::s_info;
LibHandle  CvLoader::s_hCvWorld   = nullptr;
LibHandle  CvLoader::s_hCvImgHash = nullptr;

// ─────────────────────────────────────────────────────────────────────────────
// Helper — extract version string from version code (e.g. 4130 → "4.13.0")
// ─────────────────────────────────────────────────────────────────────────────
static std::string VersionFromCode(int code)
{
    // OpenCV version encoding: XYZZ where X=major, Y=minor, ZZ=patch
    //   4130 → major=4, minor=13, patch=0
    //   4100 → major=4, minor=10, patch=0
    //   490  → major=4, minor=9,  patch=0
    int major = code / 1000;
    int minor = (code / 10) % 100;
    int patch = code % 10;
    return std::to_string(major) + "." +
           std::to_string(minor) + "." +
           std::to_string(patch);
}

// ─────────────────────────────────────────────────────────────────────────────
// Helper — extract version code from DLL filename
//   "opencv_world4130.dll"  → 4130
//   "opencv_world4100.dll"  → 4100
//   "libopencv_world.so.413"→ 413 (Linux)
// ─────────────────────────────────────────────────────────────────────────────
static int ExtractVersionCode(const std::string& path)
{
#ifdef _WIN32
    // Windows: opencv_world<XYZZ>.dll — digits between "opencv_world" and ".dll"
    const std::string marker = "opencv_world";
    size_t pos = path.find(marker);
    if (pos == std::string::npos) return 0;
    pos += marker.size();
    size_t dot = path.find('.', pos);
    if (dot == std::string::npos) return 0;
    try { return std::stoi(path.substr(pos, dot - pos)); }
    catch (...) { return 0; }
#else
    // Linux: libopencv_world.so.<digits>
    size_t dot = path.find_last_of('.');
    if (dot == std::string::npos || dot + 1 >= path.size()) return 0;
    try { return std::stoi(path.substr(dot + 1)); }
    catch (...) { return 0; }
#endif
}

// ─────────────────────────────────────────────────────────────────────────────
// Candidate lists — search ANSCENTER shared dir first, then system PATH
// ─────────────────────────────────────────────────────────────────────────────
std::vector<std::string> CvLoader::CvWorldCandidates(const std::string& shared_dir)
{
    std::vector<std::string> v;

    // Versioned candidates, newest first (covers OpenCV 4.5 through 4.20+)
    // Version codes: 4200, 4190, 4180, ..., 4130, ..., 4100, ..., 450
    static const int versions[] = {
        4200, 4190, 4180, 4170, 4160, 4150, 4140, 4130, 4120, 4110, 4100,
        490,  480,  470,  460,  450
    };

    // ── Priority 1: ANSCENTER shared directory ────────────────────────────────
    for (int ver : versions) {
#ifdef _WIN32
        v.push_back(JoinPath(shared_dir,
            "opencv_world" + std::to_string(ver) + ".dll"));
#else
        v.push_back(JoinPath(shared_dir,
            "libopencv_world.so." + std::to_string(ver)));
#endif
    }

    // ── Priority 2: System PATH / LD_LIBRARY_PATH ─────────────────────────────
    for (int ver : versions) {
#ifdef _WIN32
        v.push_back("opencv_world" + std::to_string(ver) + ".dll");
#else
        v.push_back("libopencv_world.so." + std::to_string(ver));
#endif
    }

    return v;
}

std::vector<std::string> CvLoader::CvImgHashCandidates(const std::string& shared_dir)
{
    std::vector<std::string> v;

    static const int versions[] = {
        4200, 4190, 4180, 4170, 4160, 4150, 4140, 4130, 4120, 4110, 4100,
        490,  480,  470,  460,  450
    };

    // ── Priority 1: ANSCENTER shared directory ────────────────────────────────
    for (int ver : versions) {
#ifdef _WIN32
        v.push_back(JoinPath(shared_dir,
            "opencv_img_hash" + std::to_string(ver) + ".dll"));
#else
        v.push_back(JoinPath(shared_dir,
            "libopencv_img_hash.so." + std::to_string(ver)));
#endif
    }

    // ── Priority 2: System PATH / LD_LIBRARY_PATH ─────────────────────────────
    for (int ver : versions) {
#ifdef _WIN32
        v.push_back("opencv_img_hash" + std::to_string(ver) + ".dll");
#else
        v.push_back("libopencv_img_hash.so." + std::to_string(ver));
#endif
    }

    return v;
}

// ─────────────────────────────────────────────────────────────────────────────
// Initialize
// ─────────────────────────────────────────────────────────────────────────────
const CvInfo& CvLoader::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_hCvWorld)   { FreeLib(s_hCvWorld);   s_hCvWorld   = nullptr; }
    if (s_hCvImgHash) { FreeLib(s_hCvImgHash); s_hCvImgHash = nullptr; }

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

    // ── STEP 1: Inject shared dir into search path ───────────────────────────
    // This ensures dependent DLLs (e.g. opencv_videoio_ffmpeg) can also be
    // found when they are co-located with opencv_world in the shared dir.
    InjectDllSearchPath(shared_dir);

    // ── STEP 2: Pre-load opencv_world ────────────────────────────────────────
    std::string worldPath;
    s_hCvWorld = FindAndLoad(CvWorldCandidates(shared_dir), worldPath, verbose);
    if (s_hCvWorld) {
        s_info.dllPath     = worldPath;
        s_info.versionCode = ExtractVersionCode(worldPath);
        s_info.version     = VersionFromCode(s_info.versionCode);
        s_info.loaded      = true;

        if (verbose) {
            std::cout << "[CvLoader] OpenCV version = " << s_info.version
                      << " (code " << s_info.versionCode << ")" << std::endl;
        }
    } else {
        std::cout << "[CvLoader] WARNING: No opencv_world DLL found in "
                  << shared_dir << " or system PATH." << std::endl;
        std::cout << "[CvLoader] OpenCV will rely on import-lib resolution at load time."
                  << std::endl;
    }

    // ── STEP 3: Pre-load companion modules ───────────────────────────────────
    // These are optional — missing is a warning, not a failure.
    std::string imgHashPath;
    s_hCvImgHash = FindAndLoad(CvImgHashCandidates(shared_dir), imgHashPath, verbose);
    if (!s_hCvImgHash && verbose) {
        std::cout << "[CvLoader] opencv_img_hash not found (optional)." << std::endl;
    }

    s_initialized = true;

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

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

    // Release in reverse order
    if (s_hCvImgHash) { FreeLib(s_hCvImgHash); s_hCvImgHash = nullptr; }
    if (s_hCvWorld)   { FreeLib(s_hCvWorld);   s_hCvWorld   = nullptr; }

    s_info = CvInfo{};
    s_initialized = false;
}
