/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
 */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#pragma GCC visibility push(default)
#include <va/va.h>
#pragma GCC visibility pop

#include "mozilla/Types.h"
#include <dlfcn.h>
#include <pthread.h>
#include <stdlib.h>

#define GET_FUNC(func, lib) (func##Fn = dlsym(lib, #func))

#define IS_FUNC_LOADED(func) (func##Fn != NULL)

static VAStatus (*vaDestroyBufferFn)(VADisplay dpy, VABufferID buffer_id);
static VAStatus (*vaBeginPictureFn)(VADisplay dpy, VAContextID context,
                                    VASurfaceID render_target);
static VAStatus (*vaEndPictureFn)(VADisplay dpy, VAContextID context);
static VAStatus (*vaRenderPictureFn)(VADisplay dpy, VAContextID context,
                                     VABufferID* buffers, int num_buffers);
static int (*vaMaxNumProfilesFn)(VADisplay dpy);
static VAStatus (*vaCreateContextFn)(VADisplay dpy, VAConfigID config_id,
                                     int picture_width, int picture_height,
                                     int flag, VASurfaceID* render_targets,
                                     int num_render_targets,
                                     VAContextID* context /* out */);
static VAStatus (*vaDestroyContextFn)(VADisplay dpy, VAContextID context);
static VAStatus (*vaCreateBufferFn)(VADisplay dpy, VAContextID context,
                                    VABufferType type,         /* in */
                                    unsigned int size,         /* in */
                                    unsigned int num_elements, /* in */
                                    void* data,                /* in */
                                    VABufferID* buf_id /* out */);
static VAStatus (*vaQuerySurfaceAttributesFn)(VADisplay dpy, VAConfigID config,
                                              VASurfaceAttrib* attrib_list,
                                              unsigned int* num_attribs);
static VAStatus (*vaQueryConfigProfilesFn)(VADisplay dpy,
                                           VAProfile* profile_list, /* out */
                                           int* num_profiles /* out */);
static const char* (*vaErrorStrFn)(VAStatus error_status);
static VAStatus (*vaCreateConfigFn)(VADisplay dpy, VAProfile profile,
                                    VAEntrypoint entrypoint,
                                    VAConfigAttrib* attrib_list,
                                    int num_attribs,
                                    VAConfigID* config_id /* out */);
static VAStatus (*vaDestroyConfigFn)(VADisplay dpy, VAConfigID config_id);
static int (*vaMaxNumImageFormatsFn)(VADisplay dpy);
static VAStatus (*vaQueryImageFormatsFn)(VADisplay dpy,
                                         VAImageFormat* format_list, /* out */
                                         int* num_formats /* out */);
static const char* (*vaQueryVendorStringFn)(VADisplay dpy);
static VAStatus (*vaDestroySurfacesFn)(VADisplay dpy, VASurfaceID* surfaces,
                                       int num_surfaces);
static VAStatus (*vaCreateSurfacesFn)(VADisplay dpy, unsigned int format,
                                      unsigned int width, unsigned int height,
                                      VASurfaceID* surfaces,
                                      unsigned int num_surfaces,
                                      VASurfaceAttrib* attrib_list,
                                      unsigned int num_attribs);
static VAStatus (*vaDeriveImageFn)(VADisplay dpy, VASurfaceID surface,
                                   VAImage* image /* out */);
static VAStatus (*vaDestroyImageFn)(VADisplay dpy, VAImageID image);
static VAStatus (*vaPutImageFn)(VADisplay dpy, VASurfaceID surface,
                                VAImageID image, int src_x, int src_y,
                                unsigned int src_width, unsigned int src_height,
                                int dest_x, int dest_y, unsigned int dest_width,
                                unsigned int dest_height);
static VAStatus (*vaSyncSurfaceFn)(VADisplay dpy, VASurfaceID render_target);
static VAStatus (*vaCreateImageFn)(VADisplay dpy, VAImageFormat* format,
                                   int width, int height,
                                   VAImage* image /* out */);
static VAStatus (*vaGetImageFn)(
    VADisplay dpy, VASurfaceID surface,
    int x,                     /* coordinates of the upper left source pixel */
    int y, unsigned int width, /* width and height of the region */
    unsigned int height, VAImageID image);
static VAStatus (*vaMapBufferFn)(VADisplay dpy, VABufferID buf_id, /* in */
                                 void** pbuf /* out */);
static VAStatus (*vaUnmapBufferFn)(VADisplay dpy, VABufferID buf_id /* in */);
static VAStatus (*vaTerminateFn)(VADisplay dpy);
static VAStatus (*vaInitializeFn)(VADisplay dpy, int* major_version, /* out */
                                  int* minor_version /* out */);
static VAStatus (*vaSetDriverNameFn)(VADisplay dpy, char* driver_name);
static int (*vaMaxNumEntrypointsFn)(VADisplay dpy);
static VAStatus (*vaQueryConfigEntrypointsFn)(VADisplay dpy, VAProfile profile,
                                              VAEntrypoint* entrypoint_list,
                                              int* num_entrypoints);
static VAMessageCallback (*vaSetErrorCallbackFn)(VADisplay dpy,
                                                 VAMessageCallback callback,
                                                 void* user_context);
static VAMessageCallback (*vaSetInfoCallbackFn)(VADisplay dpy,
                                                VAMessageCallback callback,
                                                void* user_context);
int LoadVALibrary() {
  static pthread_mutex_t sVALock = PTHREAD_MUTEX_INITIALIZER;
  static void* sVALib = NULL;
  static int sVAInitialized = 0;
  static int sVALoaded = 0;

  pthread_mutex_lock(&sVALock);

  if (!sVAInitialized) {
    sVAInitialized = 1;
    sVALib = dlopen("libva.so.2", RTLD_LAZY);
    if (!sVALib) {
      pthread_mutex_unlock(&sVALock);
      return 0;
    }
    GET_FUNC(vaDestroyBuffer, sVALib);
    GET_FUNC(vaBeginPicture, sVALib);
    GET_FUNC(vaEndPicture, sVALib);
    GET_FUNC(vaRenderPicture, sVALib);
    GET_FUNC(vaMaxNumProfiles, sVALib);
    GET_FUNC(vaCreateContext, sVALib);
    GET_FUNC(vaDestroyContext, sVALib);
    GET_FUNC(vaCreateBuffer, sVALib);
    GET_FUNC(vaQuerySurfaceAttributes, sVALib);
    GET_FUNC(vaQueryConfigProfiles, sVALib);
    GET_FUNC(vaErrorStr, sVALib);
    GET_FUNC(vaCreateConfig, sVALib);
    GET_FUNC(vaDestroyConfig, sVALib);
    GET_FUNC(vaMaxNumImageFormats, sVALib);
    GET_FUNC(vaQueryImageFormats, sVALib);
    GET_FUNC(vaQueryVendorString, sVALib);
    GET_FUNC(vaDestroySurfaces, sVALib);
    GET_FUNC(vaCreateSurfaces, sVALib);
    GET_FUNC(vaDeriveImage, sVALib);
    GET_FUNC(vaDestroyImage, sVALib);
    GET_FUNC(vaPutImage, sVALib);
    GET_FUNC(vaSyncSurface, sVALib);
    GET_FUNC(vaCreateImage, sVALib);
    GET_FUNC(vaGetImage, sVALib);
    GET_FUNC(vaMapBuffer, sVALib);
    GET_FUNC(vaUnmapBuffer, sVALib);
    GET_FUNC(vaTerminate, sVALib);
    GET_FUNC(vaInitialize, sVALib);
    GET_FUNC(vaSetDriverName, sVALib);
    GET_FUNC(vaMaxNumEntrypoints, sVALib);
    GET_FUNC(vaQueryConfigEntrypoints, sVALib);
    GET_FUNC(vaSetErrorCallback, sVALib);
    GET_FUNC(vaSetInfoCallback, sVALib);

    sVALoaded =
        (IS_FUNC_LOADED(vaDestroyBuffer) && IS_FUNC_LOADED(vaBeginPicture) &&
         IS_FUNC_LOADED(vaEndPicture) && IS_FUNC_LOADED(vaRenderPicture) &&
         IS_FUNC_LOADED(vaMaxNumProfiles) && IS_FUNC_LOADED(vaCreateContext) &&
         IS_FUNC_LOADED(vaDestroyContext) && IS_FUNC_LOADED(vaCreateBuffer) &&
         IS_FUNC_LOADED(vaQuerySurfaceAttributes) &&
         IS_FUNC_LOADED(vaQueryConfigProfiles) && IS_FUNC_LOADED(vaErrorStr) &&
         IS_FUNC_LOADED(vaCreateConfig) && IS_FUNC_LOADED(vaDestroyConfig) &&
         IS_FUNC_LOADED(vaMaxNumImageFormats) &&
         IS_FUNC_LOADED(vaQueryImageFormats) &&
         IS_FUNC_LOADED(vaQueryVendorString) &&
         IS_FUNC_LOADED(vaDestroySurfaces) &&
         IS_FUNC_LOADED(vaCreateSurfaces) && IS_FUNC_LOADED(vaDeriveImage) &&
         IS_FUNC_LOADED(vaDestroyImage) && IS_FUNC_LOADED(vaPutImage) &&
         IS_FUNC_LOADED(vaSyncSurface) && IS_FUNC_LOADED(vaCreateImage) &&
         IS_FUNC_LOADED(vaGetImage) && IS_FUNC_LOADED(vaMapBuffer) &&
         IS_FUNC_LOADED(vaUnmapBuffer) && IS_FUNC_LOADED(vaTerminate) &&
         IS_FUNC_LOADED(vaInitialize) && IS_FUNC_LOADED(vaSetDriverName) &&
         IS_FUNC_LOADED(vaMaxNumEntrypoints) &&
         IS_FUNC_LOADED(vaQueryConfigEntrypoints) &&
         IS_FUNC_LOADED(vaSetErrorCallback) &&
         IS_FUNC_LOADED(vaSetInfoCallback));
  }
  pthread_mutex_unlock(&sVALock);
  return sVALoaded;
}

#pragma GCC visibility push(default)

VAStatus vaDestroyBuffer(VADisplay dpy, VABufferID buffer_id) {
  if (LoadVALibrary()) {
    return vaDestroyBufferFn(dpy, buffer_id);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaBeginPicture(VADisplay dpy, VAContextID context,
                        VASurfaceID render_target) {
  if (LoadVALibrary()) {
    return vaBeginPictureFn(dpy, context, render_target);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaEndPicture(VADisplay dpy, VAContextID context) {
  if (LoadVALibrary()) {
    return vaEndPictureFn(dpy, context);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaRenderPicture(VADisplay dpy, VAContextID context,
                         VABufferID* buffers, int num_buffers) {
  if (LoadVALibrary()) {
    return vaRenderPictureFn(dpy, context, buffers, num_buffers);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

int vaMaxNumProfiles(VADisplay dpy) {
  if (LoadVALibrary()) {
    return vaMaxNumProfilesFn(dpy);
  }
  return 0;
}

VAStatus vaCreateContext(VADisplay dpy, VAConfigID config_id, int picture_width,
                         int picture_height, int flag,
                         VASurfaceID* render_targets, int num_render_targets,
                         VAContextID* context /* out */) {
  if (LoadVALibrary()) {
    return vaCreateContextFn(dpy, config_id, picture_width, picture_height,
                             flag, render_targets, num_render_targets, context);
  }
  *context = 0;
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaDestroyContext(VADisplay dpy, VAContextID context) {
  if (LoadVALibrary()) {
    return vaDestroyContextFn(dpy, context);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaCreateBuffer(VADisplay dpy, VAContextID context,
                        VABufferType type,         /* in */
                        unsigned int size,         /* in */
                        unsigned int num_elements, /* in */
                        void* data,                /* in */
                        VABufferID* buf_id /* out */) {
  if (LoadVALibrary()) {
    return vaCreateBufferFn(dpy, context, type, size, num_elements, data,
                            buf_id);
  }
  *buf_id = 0;
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaQuerySurfaceAttributes(VADisplay dpy, VAConfigID config,
                                  VASurfaceAttrib* attrib_list,
                                  unsigned int* num_attribs) {
  if (LoadVALibrary()) {
    return vaQuerySurfaceAttributesFn(dpy, config, attrib_list, num_attribs);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaQueryConfigProfiles(VADisplay dpy, VAProfile* profile_list, /* out */
                               int* num_profiles /* out */) {
  if (LoadVALibrary()) {
    return vaQueryConfigProfilesFn(dpy, profile_list, num_profiles);
  }
  *num_profiles = 0;
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

const char* vaErrorStr(VAStatus error_status) {
  if (LoadVALibrary()) {
    return vaErrorStrFn(error_status);
  }
  static char tmp[] = "Unimplemented";
  return tmp;
}

VAStatus vaCreateConfig(VADisplay dpy, VAProfile profile,
                        VAEntrypoint entrypoint, VAConfigAttrib* attrib_list,
                        int num_attribs, VAConfigID* config_id /* out */) {
  if (LoadVALibrary()) {
    return vaCreateConfigFn(dpy, profile, entrypoint, attrib_list, num_attribs,
                            config_id);
  }
  *config_id = 0;
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaDestroyConfig(VADisplay dpy, VAConfigID config_id) {
  if (LoadVALibrary()) {
    return vaDestroyConfigFn(dpy, config_id);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

int vaMaxNumImageFormats(VADisplay dpy) {
  if (LoadVALibrary()) {
    return vaMaxNumImageFormatsFn(dpy);
  }
  return 0;
}

VAStatus vaQueryImageFormats(VADisplay dpy, VAImageFormat* format_list,
                             int* num_formats) {
  if (LoadVALibrary()) {
    return vaQueryImageFormatsFn(dpy, format_list, num_formats);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

const char* vaQueryVendorString(VADisplay dpy) {
  if (LoadVALibrary()) {
    return vaQueryVendorStringFn(dpy);
  }
  return NULL;
}

VAStatus vaDestroySurfaces(VADisplay dpy, VASurfaceID* surfaces,
                           int num_surfaces) {
  if (LoadVALibrary()) {
    return vaDestroySurfacesFn(dpy, surfaces, num_surfaces);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaCreateSurfaces(VADisplay dpy, unsigned int format,
                          unsigned int width, unsigned int height,
                          VASurfaceID* surfaces, unsigned int num_surfaces,
                          VASurfaceAttrib* attrib_list,
                          unsigned int num_attribs) {
  if (LoadVALibrary()) {
    return vaCreateSurfacesFn(dpy, format, width, height, surfaces,
                              num_surfaces, attrib_list, num_attribs);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaDeriveImage(VADisplay dpy, VASurfaceID surface,
                       VAImage* image /* out */) {
  if (LoadVALibrary()) {
    return vaDeriveImageFn(dpy, surface, image);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaDestroyImage(VADisplay dpy, VAImageID image) {
  if (LoadVALibrary()) {
    return vaDestroyImageFn(dpy, image);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaPutImage(VADisplay dpy, VASurfaceID surface, VAImageID image,
                    int src_x, int src_y, unsigned int src_width,
                    unsigned int src_height, int dest_x, int dest_y,
                    unsigned int dest_width, unsigned int dest_height) {
  if (LoadVALibrary()) {
    return vaPutImageFn(dpy, surface, image, src_x, src_y, src_width,
                        src_height, dest_x, dest_y, dest_width, dest_height);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaSyncSurface(VADisplay dpy, VASurfaceID render_target) {
  if (LoadVALibrary()) {
    return vaSyncSurfaceFn(dpy, render_target);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaCreateImage(VADisplay dpy, VAImageFormat* format, int width,
                       int height, VAImage* image /* out */) {
  if (LoadVALibrary()) {
    return vaCreateImageFn(dpy, format, width, height, image);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaGetImage(VADisplay dpy, VASurfaceID surface,
                    int x, /* coordinates of the upper left source pixel */
                    int y,
                    unsigned int width, /* width and height of the region */
                    unsigned int height, VAImageID image) {
  if (LoadVALibrary()) {
    return vaGetImageFn(dpy, surface, x, y, width, height, image);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaMapBuffer(VADisplay dpy, VABufferID buf_id, /* in */
                     void** pbuf /* out */) {
  if (LoadVALibrary()) {
    return vaMapBufferFn(dpy, buf_id, pbuf);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaUnmapBuffer(VADisplay dpy, VABufferID buf_id /* in */) {
  if (LoadVALibrary()) {
    return vaUnmapBufferFn(dpy, buf_id);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaTerminate(VADisplay dpy) {
  if (LoadVALibrary()) {
    return vaTerminateFn(dpy);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaInitialize(VADisplay dpy, int* major_version, /* out */
                      int* minor_version /* out */) {
  if (LoadVALibrary()) {
    return vaInitializeFn(dpy, major_version, minor_version);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAStatus vaSetDriverName(VADisplay dpy, char* driver_name) {
  if (LoadVALibrary()) {
    return vaSetDriverNameFn(dpy, driver_name);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

int vaMaxNumEntrypoints(VADisplay dpy) {
  if (LoadVALibrary()) {
    return vaMaxNumEntrypointsFn(dpy);
  }
  return 0;
}

VAStatus vaQueryConfigEntrypoints(VADisplay dpy, VAProfile profile,
                                  VAEntrypoint* entrypoint_list,
                                  int* num_entrypoints) {
  if (LoadVALibrary()) {
    return vaQueryConfigEntrypointsFn(dpy, profile, entrypoint_list,
                                      num_entrypoints);
  }
  return VA_STATUS_ERROR_UNIMPLEMENTED;
}

VAMessageCallback vaSetErrorCallback(VADisplay dpy, VAMessageCallback callback,
                                     void* user_context) {
  if (LoadVALibrary()) {
    return vaSetErrorCallbackFn(dpy, callback, user_context);
  }
  return NULL;
}

VAMessageCallback vaSetInfoCallback(VADisplay dpy, VAMessageCallback callback,
                                    void* user_context) {
  if (LoadVALibrary()) {
    return vaSetInfoCallbackFn(dpy, callback, user_context);
  }
  return NULL;
}

#pragma GCC visibility pop