/*
 * Copyright 1993-2015 NVIDIA Corporation.  All rights reserved.
 *
 * Please refer to the NVIDIA end user license agreement (EULA) associated
 * with this source code for terms and conditions that govern your use of
 * this software. Any use, reproduction, disclosure, or distribution of
 * this software and related documentation outside the terms of the EULA
 * is strictly prohibited.
 *
 */


#include <stdio.h>
#include "dynlink_nvcuvid.h"

tcuvidCreateVideoSource               *cuvidCreateVideoSource;
tcuvidCreateVideoSourceW              *cuvidCreateVideoSourceW;
tcuvidDestroyVideoSource              *cuvidDestroyVideoSource;
tcuvidSetVideoSourceState             *cuvidSetVideoSourceState;
tcuvidGetVideoSourceState             *cuvidGetVideoSourceState;
tcuvidGetSourceVideoFormat            *cuvidGetSourceVideoFormat;
tcuvidGetSourceAudioFormat            *cuvidGetSourceAudioFormat;

tcuvidCreateVideoParser               *cuvidCreateVideoParser;
tcuvidParseVideoData                  *cuvidParseVideoData;
tcuvidDestroyVideoParser              *cuvidDestroyVideoParser;

tcuvidCreateDecoder                   *cuvidCreateDecoder;
tcuvidDestroyDecoder                  *cuvidDestroyDecoder;
tcuvidDecodePicture                   *cuvidDecodePicture;

tcuvidMapVideoFrame                   *cuvidMapVideoFrame;
tcuvidUnmapVideoFrame                 *cuvidUnmapVideoFrame;

#if defined(WIN64) || defined(_WIN64) || defined(__x86_64) || defined(AMD64) || defined(_M_AMD64)
tcuvidMapVideoFrame64                 *cuvidMapVideoFrame64;
tcuvidUnmapVideoFrame64               *cuvidUnmapVideoFrame64;
#endif

//tcuvidGetVideoFrameSurface            *cuvidGetVideoFrameSurface;
tcuvidCtxLockCreate                   *cuvidCtxLockCreate;
tcuvidCtxLockDestroy                  *cuvidCtxLockDestroy;
tcuvidCtxLock                         *cuvidCtxLock;
tcuvidCtxUnlock                       *cuvidCtxUnlock;


// Auto-lock helper for C++ applications
CCtxAutoLock::CCtxAutoLock(CUvideoctxlock ctx) 
    : m_ctx(ctx) 
{
    cuvidCtxLock(m_ctx, 0); 
}
CCtxAutoLock::~CCtxAutoLock()
{ 
    cuvidCtxUnlock(m_ctx, 0); 
}



#define STRINGIFY(X) #X

#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
#include <Windows.h>

#ifdef UNICODE
   static LPCWSTR __DriverLibName = L"nvcuvid.dll";
#else
   static LPCSTR __DriverLibName = "nvcuvid.dll";
#endif


typedef HMODULE DLLDRIVER;

static CUresult LOAD_LIBRARY(DLLDRIVER *pInstance)
{
    *pInstance = LoadLibrary(__DriverLibName);

    if (*pInstance == NULL)
    {
        printf("LoadLibrary \"%s\" failed!\n", __DriverLibName);
        return CUDA_ERROR_UNKNOWN;
    }

    return CUDA_SUCCESS;
}

#define GET_PROC_EX(name, alias, required)                     \
    alias = (t##name *)GetProcAddress(DriverLib, #name);               \
    if (alias == NULL && required) {                                    \
        printf("Failed to find required function \"%s\" in %s\n",       \
               #name, __DriverLibName);                                  \
        return CUDA_ERROR_UNKNOWN;                                      \
    }

#define GET_PROC_EX_V2(name, alias, required)                           \
    alias = (t##name *)GetProcAddress(DriverLib, STRINGIFY(name##_v2));\
    if (alias == NULL && required) {                                    \
        printf("Failed to find required function \"%s\" in %s\n",       \
               STRINGIFY(name##_v2), __DriverLibName);                       \
        return CUDA_ERROR_UNKNOWN;                                      \
    }

#elif defined(__unix__) || defined(__APPLE__) || defined(__MACOSX)

#include <dlfcn.h>

static char __DriverLibName[] = "libnvcuvid.so";

typedef void *DLLDRIVER;

static CUresult LOAD_LIBRARY(DLLDRIVER *pInstance)
{
    *pInstance = dlopen(__DriverLibName, RTLD_NOW);

    if (*pInstance == NULL)
    {
        printf("dlopen \"%s\" failed!\n", __DriverLibName);
        return CUDA_ERROR_UNKNOWN;
    }

    return CUDA_SUCCESS;
}

#define GET_PROC_EX(name, alias, required)                              \
    alias = (t##name *)dlsym(DriverLib, #name);                        \
    if (alias == NULL && required) {                                    \
        printf("Failed to find required function \"%s\" in %s\n",       \
               #name, __DriverLibName);                                  \
        return CUDA_ERROR_UNKNOWN;                                      \
    }

#define GET_PROC_EX_V2(name, alias, required)                           \
    alias = (t##name *)dlsym(DriverLib, STRINGIFY(name##_v2));         \
    if (alias == NULL && required) {                                    \
        printf("Failed to find required function \"%s\" in %s\n",       \
               STRINGIFY(name##_v2), __DriverLibName);                    \
        return CUDA_ERROR_UNKNOWN;                                      \
    }

#else
#error unsupported platform
#endif

#define CHECKED_CALL(call)              \
    do {                                \
        CUresult result = (call);       \
        if (CUDA_SUCCESS != result) {   \
            return result;              \
        }                               \
    } while(0)

#define GET_PROC_REQUIRED(name) GET_PROC_EX(name,name,1)
#define GET_PROC_OPTIONAL(name) GET_PROC_EX(name,name,0)
#define GET_PROC(name)          GET_PROC_REQUIRED(name)
#define GET_PROC_V2(name)       GET_PROC_EX_V2(name,name,1)

CUresult CUDAAPI cuvidInit(unsigned int Flags)
{
    DLLDRIVER DriverLib;

    CHECKED_CALL(LOAD_LIBRARY(&DriverLib));

    // fetch all function pointers
    GET_PROC(cuvidCreateVideoSource);
    GET_PROC(cuvidCreateVideoSourceW);
    GET_PROC(cuvidDestroyVideoSource);
    GET_PROC(cuvidSetVideoSourceState);
    GET_PROC(cuvidGetVideoSourceState);
    GET_PROC(cuvidGetSourceVideoFormat);
    GET_PROC(cuvidGetSourceAudioFormat);

    GET_PROC(cuvidCreateVideoParser);
    GET_PROC(cuvidParseVideoData);
    GET_PROC(cuvidDestroyVideoParser);

    GET_PROC(cuvidCreateDecoder);
    GET_PROC(cuvidDestroyDecoder);
    GET_PROC(cuvidDecodePicture);

#if defined(WIN64) || defined(_WIN64) || defined(__x86_64) || defined(AMD64) || defined(_M_AMD64)
    GET_PROC(cuvidMapVideoFrame64);
    GET_PROC(cuvidUnmapVideoFrame64);
    cuvidMapVideoFrame   = cuvidMapVideoFrame64;
    cuvidUnmapVideoFrame = cuvidUnmapVideoFrame64;
#else
    GET_PROC(cuvidMapVideoFrame);
    GET_PROC(cuvidUnmapVideoFrame);
#endif

//    GET_PROC(cuvidGetVideoFrameSurface);
    GET_PROC(cuvidCtxLockCreate);
    GET_PROC(cuvidCtxLockDestroy);
    GET_PROC(cuvidCtxLock);
    GET_PROC(cuvidCtxUnlock);

    return CUDA_SUCCESS;
}
