#include "gpu_driver_specific.h"

#include "common/library.h"
#include "mtml.h"

struct FFMtmlData
{
    FF_LIBRARY_SYMBOL(mtmlDeviceCountGpuCores)
    FF_LIBRARY_SYMBOL(mtmlDeviceGetBrand)
    FF_LIBRARY_SYMBOL(mtmlDeviceGetIndex)
    FF_LIBRARY_SYMBOL(mtmlDeviceGetName)
    FF_LIBRARY_SYMBOL(mtmlDeviceGetPciInfo)
    FF_LIBRARY_SYMBOL(mtmlDeviceGetUUID)
    FF_LIBRARY_SYMBOL(mtmlDeviceInitGpu)
    FF_LIBRARY_SYMBOL(mtmlDeviceInitMemory)
    FF_LIBRARY_SYMBOL(mtmlGpuGetMaxClock)
    FF_LIBRARY_SYMBOL(mtmlGpuGetTemperature)
    FF_LIBRARY_SYMBOL(mtmlGpuGetUtilization)
    FF_LIBRARY_SYMBOL(mtmlLibraryCountDevice)
    FF_LIBRARY_SYMBOL(mtmlLibraryInitDeviceByIndex)
    FF_LIBRARY_SYMBOL(mtmlLibraryInitDeviceByPciSbdf)
    FF_LIBRARY_SYMBOL(mtmlLibraryInitSystem)
    FF_LIBRARY_SYMBOL(mtmlMemoryGetTotal)
    FF_LIBRARY_SYMBOL(mtmlMemoryGetUsed)
    FF_LIBRARY_SYMBOL(mtmlMemoryGetUtilization)
    FF_LIBRARY_SYMBOL(mtmlLibraryShutDown)

    bool inited;
    MtmlLibrary *lib;
    MtmlSystem *sys;
} mtmlData;

FF_MAYBE_UNUSED static void shutdownMtml(void)
{
    mtmlData.ffmtmlLibraryShutDown(mtmlData.lib);
}

const char *ffDetectMthreadsGpuInfo(const FFGpuDriverCondition *cond, FFGpuDriverResult result, const char *soName)
{
#ifndef FF_DISABLE_DLOPEN

    if (!mtmlData.inited)
    {
        mtmlData.inited = true;
        FF_LIBRARY_LOAD(libmtml, "dlopen mtml failed", soName, 1);
        FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libmtml, mtmlLibraryInit)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceCountGpuCores)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetBrand)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetIndex)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetName)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetPciInfo)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetUUID)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceInitGpu)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceInitMemory)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetMaxClock)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetTemperature)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetUtilization)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryCountDevice)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitDeviceByIndex)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitDeviceByPciSbdf)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitSystem)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetTotal)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetUsed)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetUtilization)
        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryShutDown)

        if (ffmtmlLibraryInit(&mtmlData.lib) != MTML_SUCCESS)
        {
            mtmlData.ffmtmlLibraryInitSystem = NULL;
            return "mtmlLibraryInit failed";
        }
        if (mtmlData.ffmtmlLibraryInitSystem(mtmlData.lib, &mtmlData.sys) != MTML_SUCCESS)
        {
            mtmlData.ffmtmlLibraryShutDown(mtmlData.lib);
            mtmlData.ffmtmlLibraryInitSystem = NULL;
            return "mtmlLibraryInitSystem failed";
        }
        atexit(shutdownMtml);
        libmtml = NULL; // don't close mtml
    }

    if (mtmlData.ffmtmlLibraryInitSystem == NULL)
        return "loading mtml library failed";

    MtmlDevice *device = NULL;
    if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID)
    {
        char pciBusIdStr[32];
        snprintf(pciBusIdStr, ARRAY_SIZE(pciBusIdStr) - 1, "%04x:%02x:%02x.%d", cond->pciBusId.domain, cond->pciBusId.bus, cond->pciBusId.device, cond->pciBusId.func);

        MtmlReturn ret = mtmlData.ffmtmlLibraryInitDeviceByPciSbdf(mtmlData.lib, pciBusIdStr, &device);
        if (ret != MTML_SUCCESS)
            return "mtmlLibraryInitDeviceByPciSbdf() failed";
    }
    else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID)
    {
        uint32_t count;
        if (mtmlData.ffmtmlLibraryCountDevice(mtmlData.lib, &count) != MTML_SUCCESS)
            return "mtmlLibraryCountDevice() failed";

        for (uint32_t i = 0; i < count; i++, device = NULL)
        {
            if (mtmlData.ffmtmlLibraryInitDeviceByIndex(mtmlData.lib, i, &device) != MTML_SUCCESS)
                continue;

            MtmlPciInfo pciInfo;
            if (mtmlData.ffmtmlDeviceGetPciInfo(device, &pciInfo) != MTML_SUCCESS)
                continue;

            if (pciInfo.pciDeviceId != ((cond->pciDeviceId.deviceId << 16u) | cond->pciDeviceId.vendorId) ||
                pciInfo.pciSubsystemId != cond->pciDeviceId.subSystemId)
                continue;

            break;
        }
        if (!device)
            return "Device not found";
    }
    else
    {
        return "Unknown condition type";
    }

    MtmlBrandType brand;
    if (mtmlData.ffmtmlDeviceGetBrand(device, &brand) == MTML_SUCCESS)
    {
        switch (brand)
        {
        case MTML_BRAND_MTT:
            *result.type = FF_GPU_TYPE_DISCRETE;
            break;
        default:
            break;
        }
    }

    if (result.index)
    {
        unsigned int value;
        if (mtmlData.ffmtmlDeviceGetIndex(device, &value) == MTML_SUCCESS)
            *result.index = value;
    }

    if (result.temp)
    {
        MtmlGpu *gpu = NULL;
        if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS)
        {
            uint32_t value;
            if (mtmlData.ffmtmlGpuGetTemperature(gpu, &value) == MTML_SUCCESS)
                *result.temp = value;
        }
    }

    if (result.memory)
    {
        MtmlMemory *mem = NULL;
        if (mtmlData.ffmtmlDeviceInitMemory(device, &mem) == MTML_SUCCESS)
        {
            unsigned long long total;
            if (mtmlData.ffmtmlMemoryGetTotal(mem, &total) == MTML_SUCCESS)
                result.memory->total = total;

            unsigned long long used;
            if (mtmlData.ffmtmlMemoryGetUsed(mem, &used) == MTML_SUCCESS)
                result.memory->used = used;
        }
    }

    if (result.coreCount)
        mtmlData.ffmtmlDeviceCountGpuCores(device, result.coreCount);

    if (result.frequency)
    {
        MtmlGpu *gpu = NULL;
        if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS)
        {
            uint32_t clockMHz;
            if (mtmlData.ffmtmlGpuGetMaxClock(gpu, &clockMHz) == MTML_SUCCESS)
                *result.frequency = clockMHz;
        }
    }

    if (result.coreUsage)
    {
        MtmlGpu *gpu = NULL;
        if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS)
        {
            unsigned int utilization;
            if (mtmlData.ffmtmlGpuGetUtilization(gpu, &utilization) == MTML_SUCCESS)
                *result.coreUsage = utilization;
        }
    }

    if (result.name)
    {
        char name[MTML_DEVICE_NAME_BUFFER_SIZE];
        if (mtmlData.ffmtmlDeviceGetName(device, name, ARRAY_SIZE(name)) == MTML_SUCCESS)
            ffStrbufSetS(result.name, name);
    }

    return NULL;

#else

    FF_UNUSED(cond, result, soName);
    return "dlopen is disabled";

#endif
}
