/*############################################################################
  # Copyright (C) Intel Corporation
  #
  # SPDX-License-Identifier: MIT
  ############################################################################*/

#include <assert.h>
#include <dlfcn.h>
#include <string.h>
#include <unistd.h>

#include <algorithm>
#include <list>
#include <memory>
#include <mutex>
#include <utility>
#include <vector>

#include "vpl/mfxvideo.h"

// internal implementation of mfxConfigInterface
#include "src/mfx_config_interface/mfx_config_interface.h"

#include "src/linux/device_ids.h"
#include "src/linux/mfxloader.h"

namespace MFX {

#if defined(__x86_64__) || (INTPTR_MAX == INT64_MAX)
    #define LIBMFXSW "libmfxsw64.so.1"
    #define LIBMFXHW "libmfxhw64.so.1"

    #define ONEVPLSW "libvplswref64.so.1"
    #define ONEVPLHW "libmfx-gen.so.1.2"
#elif defined(__i386__) || (INTPTR_MAX == INT32_MAX)
    #define LIBMFXSW "libmfxsw32.so.1"
    #define LIBMFXHW "libmfxhw32.so.1"

    #define ONEVPLSW "libvplswref32.so.1"
    #define ONEVPLHW "libmfx-gen.so.1.2"
#else
    #error Unsupported architecture
#endif

#undef FUNCTION
#define FUNCTION(return_value, func_name, formal_param_list, actual_param_list) e##func_name,

enum Function {
    eMFXInit,
    eMFXInitEx,
    eMFXClose,
    eMFXJoinSession,
    eMFXVideoCORE_GetHandle,
#include "src/linux/mfxvideo_functions.h"
    eFunctionsNum,
    eNoMoreFunctions = eFunctionsNum
};

// new functions for API 2.x
enum Function2 {
    // 2.0
    eMFXQueryImplsDescription = 0,
    eMFXReleaseImplDescription,
    eMFXMemory_GetSurfaceForVPP,
    eMFXMemory_GetSurfaceForEncode,
    eMFXMemory_GetSurfaceForDecode,
    eMFXInitialize,

    // 2.1
    eMFXMemory_GetSurfaceForVPPOut,
    eMFXVideoDECODE_VPP_Init,
    eMFXVideoDECODE_VPP_DecodeFrameAsync,
    eMFXVideoDECODE_VPP_Reset,
    eMFXVideoDECODE_VPP_GetChannelParam,
    eMFXVideoDECODE_VPP_Close,
    eMFXVideoVPP_ProcessFrameAsync,

    eFunctionsNum2,
};

struct FunctionsTable {
    Function id;
    const char *name;
    mfxVersion version;
};

struct FunctionsTable2 {
    Function2 id;
    const char *name;
    mfxVersion version;
};

#define VERSION(major, minor) \
    {                         \
        { minor, major }      \
    }

#undef FUNCTION
#define FUNCTION(return_value, func_name, formal_param_list, actual_param_list) \
    { e##func_name, #func_name, API_VERSION },

static const FunctionsTable g_mfxFuncTable[] = {
    { eMFXInit, "MFXInit", VERSION(1, 0) },
    { eMFXInitEx, "MFXInitEx", VERSION(1, 14) },
    { eMFXClose, "MFXClose", VERSION(1, 0) },
    { eMFXJoinSession, "MFXJoinSession", VERSION(1, 1) },
    { eMFXVideoCORE_GetHandle, "MFXVideoCORE_GetHandle", VERSION(1, 0) },
#include "src/linux/mfxvideo_functions.h" // NOLINT(build/include)
    { eNoMoreFunctions }
};

static const FunctionsTable2 g_mfxFuncTable2[] = {
    { eMFXQueryImplsDescription, "MFXQueryImplsDescription", VERSION(2, 0) },
    { eMFXReleaseImplDescription, "MFXReleaseImplDescription", VERSION(2, 0) },
    { eMFXMemory_GetSurfaceForVPP, "MFXMemory_GetSurfaceForVPP", VERSION(2, 0) },
    { eMFXMemory_GetSurfaceForEncode, "MFXMemory_GetSurfaceForEncode", VERSION(2, 0) },
    { eMFXMemory_GetSurfaceForDecode, "MFXMemory_GetSurfaceForDecode", VERSION(2, 0) },
    { eMFXInitialize, "MFXInitialize", VERSION(2, 0) },

    { eMFXMemory_GetSurfaceForVPPOut, "MFXMemory_GetSurfaceForVPPOut", VERSION(2, 1) },
    { eMFXVideoDECODE_VPP_Init, "MFXVideoDECODE_VPP_Init", VERSION(2, 1) },
    { eMFXVideoDECODE_VPP_DecodeFrameAsync, "MFXVideoDECODE_VPP_DecodeFrameAsync", VERSION(2, 1) },
    { eMFXVideoDECODE_VPP_Reset, "MFXVideoDECODE_VPP_Reset", VERSION(2, 1) },
    { eMFXVideoDECODE_VPP_GetChannelParam, "MFXVideoDECODE_VPP_GetChannelParam", VERSION(2, 1) },
    { eMFXVideoDECODE_VPP_Close, "MFXVideoDECODE_VPP_Close", VERSION(2, 1) },
    { eMFXVideoVPP_ProcessFrameAsync, "MFXVideoVPP_ProcessFrameAsync", VERSION(2, 1) },
};

class LoaderCtx {
public:
    mfxStatus Init(mfxInitParam &par,
                   mfxInitializationParam &vplParam,
                   mfxU16 *pDeviceID,
                   char *dllName,
                   bool bCloneSession = false);
    mfxStatus Close();

    inline void *getFunction(Function func) const {
        return m_table[func];
    }

    inline void *getFunction2(Function2 func) const {
        return m_table2[func];
    }

    inline mfxSession getSession() const {
        return m_session;
    }

    inline mfxIMPL getImpl() const {
        return m_implementation;
    }

    inline mfxVersion getVersion() const {
        return m_version;
    }

    inline void *getHandle() const {
        return m_dlh.get();
    }

    inline const char *getLibPath() const {
        return m_libToLoad.c_str();
    }

    // special operations to set session pointer and version from MFXCloneSession()
    inline void setSession(const mfxSession session) {
        m_session = session;
    }

    inline void setVersion(const mfxVersion version) {
        m_version = version;
    }

private:
    std::shared_ptr<void> m_dlh;
    mfxVersion m_version{};
    mfxIMPL m_implementation{};
    mfxSession m_session = nullptr;
    void *m_table[eFunctionsNum]{};
    void *m_table2[eFunctionsNum2]{};
    std::string m_libToLoad;
};

std::shared_ptr<void> make_dlopen(const char *filename, int flags) {
    return std::shared_ptr<void>(dlopen(filename, flags), [](void *handle) {
        if (handle)
            dlclose(handle);
    });
}

mfxStatus LoaderCtx::Init(mfxInitParam &par,
                          mfxInitializationParam &vplParam,
                          mfxU16 *pDeviceID,
                          char *dllName,
                          bool bCloneSession) {
    mfxStatus mfx_res = MFX_ERR_NONE;

    std::vector<std::string> libs;
    std::vector<Device> devices;
    eMFXHWType msdk_platform;

    // query graphics device_id
    // if it is found on list of legacy devices, load MSDK RT
    // otherwise load Intel® Video Processing Library (Intel® VPL) RT
    mfxU16 deviceID = 0;
    mfx_res         = get_devices(devices);
    if (mfx_res == MFX_ERR_NOT_FOUND) {
        // query failed
        msdk_platform = MFX_HW_UNKNOWN;
    }
    else {
        // query succeeded:
        //   may be a valid platform from listLegalDevIDs[] or MFX_HW_UNKNOWN
        //   if underlying device_id is unrecognized (i.e. new platform)
        msdk_platform = devices[0].platform;
        deviceID      = devices[0].device_id;
    }

    if (pDeviceID)
        *pDeviceID = deviceID;

    if (dllName) {
        // attempt to load only this DLL, fail if unsuccessful
        // this may also be used later by MFXCloneSession()
        m_libToLoad = dllName;
        libs.emplace_back(m_libToLoad);
    }
    else {
        mfxIMPL implType = MFX_IMPL_BASETYPE(par.Implementation);
        // add HW lib
        if (implType == MFX_IMPL_AUTO || implType == MFX_IMPL_AUTO_ANY ||
            (implType & MFX_IMPL_HARDWARE) || (implType & MFX_IMPL_HARDWARE_ANY)) {
            if (msdk_platform == MFX_HW_UNKNOWN) {
                // if not on list of known MSDK platforms, prefer Intel® VPL
                libs.emplace_back(ONEVPLHW);
                libs.emplace_back(MFX_MODULES_DIR "/" ONEVPLHW);
            }

            // use MSDK (fallback if Intel® VPL is not installed)
            libs.emplace_back(LIBMFXHW);
            libs.emplace_back(MFX_MODULES_DIR "/" LIBMFXHW);
        }

        // add SW lib (Intel® VPL only)
        if (implType == MFX_IMPL_AUTO || implType == MFX_IMPL_AUTO_ANY ||
            (implType & MFX_IMPL_SOFTWARE)) {
            libs.emplace_back(ONEVPLSW);
            libs.emplace_back(MFX_MODULES_DIR "/" ONEVPLSW);
        }
    }

    // fail if libs is empty (invalid Implementation)
    mfx_res = MFX_ERR_UNSUPPORTED;

    for (auto &lib : libs) {
        std::shared_ptr<void> hdl = make_dlopen(lib.c_str(), RTLD_LOCAL | RTLD_NOW);
        if (hdl) {
            do {
                /* Loading functions table */
                bool wrong_version = false;
                for (int i = 0; i < eFunctionsNum; ++i) {
                    assert(i == g_mfxFuncTable[i].id);
                    m_table[i] = dlsym(hdl.get(), g_mfxFuncTable[i].name);
                    if (!m_table[i] && ((g_mfxFuncTable[i].version <= par.Version))) {
                        wrong_version = true;
                        break;
                    }
                }

                // if version >= 2.0, load these functions as well
                if (par.Version.Major >= 2) {
                    for (int i = 0; i < eFunctionsNum2; ++i) {
                        assert(i == g_mfxFuncTable2[i].id);
                        m_table2[i] = dlsym(hdl.get(), g_mfxFuncTable2[i].name);
                        if (!m_table2[i] && (g_mfxFuncTable2[i].version <= par.Version)) {
                            wrong_version = true;
                            break;
                        }
                    }
                }

                if (wrong_version) {
                    mfx_res = MFX_ERR_UNSUPPORTED;
                    break;
                }

                if (bCloneSession == true) {
                    // success - exit loop since caller will create session with MFXCloneSession()
                    mfx_res = MFX_ERR_NONE;
                    break;
                }

                if (par.Version.Major >= 2) {
                    // for API >= 2.0 call MFXInitialize instead of MFXInitEx
                    mfx_res =
                        ((decltype(MFXInitialize) *)m_table2[eMFXInitialize])(vplParam, &m_session);
                }
                else {
                    if (m_table[eMFXInitEx]) {
                        // initialize with MFXInitEx if present (API >= 1.14)
                        mfx_res = ((decltype(MFXInitEx) *)m_table[eMFXInitEx])(par, &m_session);
                    }
                    else {
                        // initialize with MFXInit for API < 1.14
                        mfx_res = ((decltype(MFXInit) *)m_table[eMFXInit])(par.Implementation,
                                                                           &(par.Version),
                                                                           &m_session);
                    }
                }

                if (MFX_ERR_NONE != mfx_res) {
                    break;
                }

                // Below we just get some data and double check that we got what we have expected
                // to get. Some of these checks are done inside mediasdk init function
                mfx_res =
                    ((decltype(MFXQueryVersion) *)m_table[eMFXQueryVersion])(m_session, &m_version);
                if (MFX_ERR_NONE != mfx_res) {
                    break;
                }

                if (m_version < par.Version) {
                    mfx_res = MFX_ERR_UNSUPPORTED;
                    break;
                }

                mfx_res = ((decltype(MFXQueryIMPL) *)m_table[eMFXQueryIMPL])(m_session,
                                                                             &m_implementation);
                if (MFX_ERR_NONE != mfx_res) {
                    mfx_res = MFX_ERR_UNSUPPORTED;
                    break;
                }
            } while (false);

            if (MFX_ERR_NONE == mfx_res) {
                m_dlh = std::move(hdl);
                break;
            }
            else {
                Close();
            }
        }
    }

    return mfx_res;
}

mfxStatus LoaderCtx::Close() {
    auto proc         = (decltype(MFXClose) *)m_table[eMFXClose];
    mfxStatus mfx_res = (proc) ? (*proc)(m_session) : MFX_ERR_NONE;

    m_implementation = {};
    m_version        = {};
    m_session        = nullptr;
    std::fill(std::begin(m_table), std::end(m_table), nullptr);
    return mfx_res;
}

} // namespace MFX

// internal function - load a specific DLL, return unsupported if it fails
// vplParam is required for API >= 2.0 (load via MFXInitialize)
mfxStatus MFXInitEx2(mfxVersion version,
                     mfxInitializationParam vplParam,
                     mfxIMPL hwImpl,
                     mfxSession *session,
                     mfxU16 *deviceID,
                     char *dllName) {
    if (!session)
        return MFX_ERR_NULL_PTR;

    *deviceID = 0;

    // fill minimal 1.x parameters for Init to choose correct initialization path
    mfxInitParam par = {};
    par.Version      = version;

    // select first adapter if not specified
    // only relevant for MSDK-via-MFXLoad path
    if (!hwImpl)
        hwImpl = MFX_IMPL_HARDWARE;

    switch (vplParam.AccelerationMode) {
        case MFX_ACCEL_MODE_NA:
            par.Implementation = MFX_IMPL_SOFTWARE;
            break;
        case MFX_ACCEL_MODE_VIA_D3D9:
            par.Implementation = hwImpl | MFX_IMPL_VIA_D3D9;
            break;
        case MFX_ACCEL_MODE_VIA_D3D11:
            par.Implementation = hwImpl | MFX_IMPL_VIA_D3D11;
            break;
        case MFX_ACCEL_MODE_VIA_VAAPI:
            par.Implementation = hwImpl | MFX_IMPL_VIA_VAAPI;
            break;
        default:
            par.Implementation = hwImpl;
            break;
    }

    // also pass extBuf array (if any) to MFXInitEx for 1.x API
    par.NumExtParam = vplParam.NumExtParam;
    par.ExtParam    = (vplParam.NumExtParam ? vplParam.ExtParam : nullptr);

#ifdef ONEVPL_EXPERIMENTAL
    // if GPUCopy is enabled via MFXSetConfigProperty(DeviceCopy), set corresponding
    //   flag in mfxInitParam for legacy RTs
    par.GPUCopy = vplParam.DeviceCopy;
#endif

    try {
        std::unique_ptr<MFX::LoaderCtx> loader;

        loader.reset(new MFX::LoaderCtx{});

        mfxStatus mfx_res = loader->Init(par, vplParam, deviceID, dllName);
        if (MFX_ERR_NONE == mfx_res) {
            *session = (mfxSession)loader.release();
        }
        else {
            *session = nullptr;
        }

        return mfx_res;
    }
    catch (...) {
        return MFX_ERR_MEMORY_ALLOC;
    }
}

#ifdef __cplusplus
extern "C" {
#endif

mfxStatus MFXInit(mfxIMPL impl, mfxVersion *ver, mfxSession *session) {
    mfxInitParam par{};

    par.Implementation = impl;
    if (ver) {
        par.Version = *ver;
    }
    else {
        par.Version = VERSION(MFX_VERSION_MAJOR, MFX_VERSION_MINOR);
    }

    return MFXInitEx(par, session);
}

mfxStatus MFXInitEx(mfxInitParam par, mfxSession *session) {
    if (!session)
        return MFX_ERR_NULL_PTR;

    const mfxIMPL implMethod        = par.Implementation & (MFX_IMPL_VIA_ANY - 1);
    mfxInitializationParam vplParam = {};
    if (implMethod == MFX_IMPL_SOFTWARE) {
        vplParam.AccelerationMode = MFX_ACCEL_MODE_NA;
    }
    else {
        vplParam.AccelerationMode = MFX_ACCEL_MODE_VIA_VAAPI;
    }

    try {
        std::unique_ptr<MFX::LoaderCtx> loader;

        loader.reset(new MFX::LoaderCtx{});

        mfxStatus mfx_res = loader->Init(par, vplParam, nullptr, nullptr);
        if (MFX_ERR_NONE == mfx_res) {
            *session = (mfxSession)loader.release();
        }
        else {
            *session = nullptr;
        }

        return mfx_res;
    }
    catch (...) {
        return MFX_ERR_MEMORY_ALLOC;
    }
}

mfxStatus MFXClose(mfxSession session) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    try {
        std::unique_ptr<MFX::LoaderCtx> loader((MFX::LoaderCtx *)session);
        mfxStatus mfx_res = loader->Close();

        if (mfx_res == MFX_ERR_UNDEFINED_BEHAVIOR) {
            // It is possible, that there is an active child session.
            // Can't unload library in this case.
            loader.release();
        }
        return mfx_res;
    }
    catch (...) {
        return MFX_ERR_MEMORY_ALLOC;
    }
}

// passthrough functions to implementation
mfxStatus MFXMemory_GetSurfaceForVPP(mfxSession session, mfxFrameSurface1 **surface) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

    auto proc = (decltype(MFXMemory_GetSurfaceForVPP) *)loader->getFunction2(
        MFX::eMFXMemory_GetSurfaceForVPP);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession(), surface);
}

mfxStatus MFXMemory_GetSurfaceForVPPOut(mfxSession session, mfxFrameSurface1 **surface) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

    auto proc = (decltype(MFXMemory_GetSurfaceForVPPOut) *)loader->getFunction2(
        MFX::eMFXMemory_GetSurfaceForVPPOut);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession(), surface);
}

mfxStatus MFXMemory_GetSurfaceForEncode(mfxSession session, mfxFrameSurface1 **surface) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

    auto proc = (decltype(MFXMemory_GetSurfaceForEncode) *)loader->getFunction2(
        MFX::eMFXMemory_GetSurfaceForEncode);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession(), surface);
}

mfxStatus MFXMemory_GetSurfaceForDecode(mfxSession session, mfxFrameSurface1 **surface) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

    auto proc = (decltype(MFXMemory_GetSurfaceForDecode) *)loader->getFunction2(
        MFX::eMFXMemory_GetSurfaceForDecode);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession(), surface);
}

mfxStatus MFXVideoDECODE_VPP_Init(mfxSession session,
                                  mfxVideoParam *decode_par,
                                  mfxVideoChannelParam **vpp_par_array,
                                  mfxU32 num_vpp_par) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

    auto proc =
        (decltype(MFXVideoDECODE_VPP_Init) *)loader->getFunction2(MFX::eMFXVideoDECODE_VPP_Init);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession(), decode_par, vpp_par_array, num_vpp_par);
}

mfxStatus MFXVideoDECODE_VPP_DecodeFrameAsync(mfxSession session,
                                              mfxBitstream *bs,
                                              mfxU32 *skip_channels,
                                              mfxU32 num_skip_channels,
                                              mfxSurfaceArray **surf_array_out) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

    auto proc = (decltype(MFXVideoDECODE_VPP_DecodeFrameAsync) *)loader->getFunction2(
        MFX::eMFXVideoDECODE_VPP_DecodeFrameAsync);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession(), bs, skip_channels, num_skip_channels, surf_array_out);
}

mfxStatus MFXVideoDECODE_VPP_Reset(mfxSession session,
                                   mfxVideoParam *decode_par,
                                   mfxVideoChannelParam **vpp_par_array,
                                   mfxU32 num_vpp_par) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

    auto proc =
        (decltype(MFXVideoDECODE_VPP_Reset) *)loader->getFunction2(MFX::eMFXVideoDECODE_VPP_Reset);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession(), decode_par, vpp_par_array, num_vpp_par);
}

mfxStatus MFXVideoDECODE_VPP_GetChannelParam(mfxSession session,
                                             mfxVideoChannelParam *par,
                                             mfxU32 channel_id) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

    auto proc = (decltype(MFXVideoDECODE_VPP_GetChannelParam) *)loader->getFunction2(
        MFX::eMFXVideoDECODE_VPP_GetChannelParam);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession(), par, channel_id);
}

mfxStatus MFXVideoDECODE_VPP_Close(mfxSession session) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

    auto proc =
        (decltype(MFXVideoDECODE_VPP_Close) *)loader->getFunction2(MFX::eMFXVideoDECODE_VPP_Close);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession());
}

mfxStatus MFXVideoVPP_ProcessFrameAsync(mfxSession session,
                                        mfxFrameSurface1 *in,
                                        mfxFrameSurface1 **out) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

    auto proc = (decltype(MFXVideoVPP_ProcessFrameAsync) *)loader->getFunction2(
        MFX::eMFXVideoVPP_ProcessFrameAsync);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession(), in, out);
}

// implement as a non-passthrough function so that we can catch dispatcher-level interface query requests
mfxStatus MFXVideoCORE_GetHandle(mfxSession session, mfxHandleType type, mfxHDL *hdl) {
    if (!session)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

    // first check if handle type points to an interface implemented inside the dispatcher
    if (type == MFX_HANDLE_CONFIG_INTERFACE) {
        if (!hdl)
            return MFX_ERR_NULL_PTR;

        *hdl = (mfxHDL)(&(MFX_CONFIG_INTERFACE::g_dispatcher_mfxConfigInterface));

        return MFX_ERR_NONE;
    }

    // passthrough to runtime
    auto proc =
        (decltype(MFXVideoCORE_GetHandle) *)loader->getFunction(MFX::eMFXVideoCORE_GetHandle);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession(), type, hdl);
}

mfxStatus MFXJoinSession(mfxSession session, mfxSession child_session) {
    if (!session || !child_session) {
        return MFX_ERR_INVALID_HANDLE;
    }

    MFX::LoaderCtx *loader       = (MFX::LoaderCtx *)session;
    MFX::LoaderCtx *child_loader = (MFX::LoaderCtx *)child_session;

    if (loader->getVersion().Version != child_loader->getVersion().Version) {
        return MFX_ERR_INVALID_HANDLE;
    }

    auto proc = (decltype(MFXJoinSession) *)loader->getFunction(MFX::eMFXJoinSession);
    if (!proc) {
        return MFX_ERR_INVALID_HANDLE;
    }

    return (*proc)(loader->getSession(), child_loader->getSession());
}

static mfxStatus AllocateCloneLoader(MFX::LoaderCtx *parentLoader, MFX::LoaderCtx **cloneLoader) {
    // initialization param structs are not used when bCloneSession == true
    mfxInitParam par                = {};
    mfxInitializationParam vplParam = {};
    mfxU16 deviceID                 = 0;

    // initialization extBufs are not saved at this level
    // the RT should save these when the parent session is created and may use
    //   them when creating the cloned session
    par.NumExtParam = 0;

    try {
        std::unique_ptr<MFX::LoaderCtx> cl;

        cl.reset(new MFX::LoaderCtx{});

        mfxStatus mfx_res =
            cl->Init(par, vplParam, &deviceID, (char *)parentLoader->getLibPath(), true);
        if (MFX_ERR_NONE == mfx_res) {
            *cloneLoader = cl.release();
        }
        else {
            *cloneLoader = nullptr;
        }

        return mfx_res;
    }
    catch (...) {
        return MFX_ERR_MEMORY_ALLOC;
    }
}

mfxStatus MFXCloneSession(mfxSession session, mfxSession *clone) {
    if (!session || !clone)
        return MFX_ERR_INVALID_HANDLE;

    MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
    mfxVersion version     = loader->getVersion();
    *clone                 = nullptr;

    // initialize the clone session
    // for runtimes with 1.x API, call MFXInit followed by MFXJoinSession
    // for runtimes with 2.x API, use RT implementation of MFXCloneSession (passthrough)
    if (version.Major == 1) {
        mfxStatus mfx_res = MFXInit(loader->getImpl(), &version, clone);
        if (MFX_ERR_NONE != mfx_res) {
            return mfx_res;
        }

        // join the sessions
        mfx_res = MFXJoinSession(session, *clone);
        if (MFX_ERR_NONE != mfx_res) {
            MFXClose(*clone);
            *clone = nullptr;
            return mfx_res;
        }
    }
    else if (version.Major == 2) {
        MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;

        // MFXCloneSession not included in function pointer search during init
        // for bwd-compat, check for it here and fail gracefully if missing
        void *libHandle = loader->getHandle();
        auto proc       = (decltype(MFXCloneSession) *)(dlsym(libHandle, "MFXCloneSession"));
        if (!proc)
            return MFX_ERR_UNSUPPORTED;

        // allocate new dispatcher-level session object and copy
        //   state from parent session (function pointer tables, impl type, etc.)
        MFX::LoaderCtx *cloneLoader;
        mfxStatus mfx_res = AllocateCloneLoader(loader, &cloneLoader);
        if (mfx_res != MFX_ERR_NONE)
            return mfx_res;

        // call RT implementation of MFXCloneSession
        mfxSession cloneRT;
        mfx_res = (*proc)(loader->getSession(), &cloneRT);

        if (mfx_res != MFX_ERR_NONE || cloneRT == NULL) {
            // RT call failed, delete cloned loader (no valid session created)
            delete cloneLoader;
            return MFX_ERR_UNSUPPORTED;
        }
        cloneLoader->setSession(cloneRT);

        // get version of cloned session
        mfxVersion cloneVersion = {};
        mfx_res                 = MFXQueryVersion((mfxSession)cloneLoader, &cloneVersion);
        cloneLoader->setVersion(cloneVersion);
        if (mfx_res != MFX_ERR_NONE) {
            MFXClose((mfxSession)cloneLoader);
            return mfx_res;
        }

        *clone = (mfxSession)cloneLoader;
    }
    else {
        return MFX_ERR_UNSUPPORTED;
    }

    return MFX_ERR_NONE;
}

#undef FUNCTION
#define FUNCTION(return_value, func_name, formal_param_list, actual_param_list)    \
    return_value MFX_CDECL func_name formal_param_list {                           \
        /* get the function's address and make a call */                           \
        if (!session)                                                              \
            return MFX_ERR_INVALID_HANDLE;                                         \
                                                                                   \
        MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;                        \
                                                                                   \
        auto proc = (decltype(func_name) *)loader->getFunction(MFX::e##func_name); \
        if (!proc)                                                                 \
            return MFX_ERR_INVALID_HANDLE;                                         \
                                                                                   \
        /* get the real session pointer */                                         \
        session = loader->getSession();                                            \
        /* pass down the call */                                                   \
        return (*proc)actual_param_list;                                           \
    }

#include "src/linux/mfxvideo_functions.h" // NOLINT(build/include)

#ifdef __cplusplus
}
#endif
