/* 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/. */

#ifndef MOZ_DMABUF_LIB_WRAPPER_H_
#define MOZ_DMABUF_LIB_WRAPPER_H_

#include "mozilla/StaticMutex.h"
#include "mozilla/widget/DMABufFormats.h"
#include <gbm.h>

#undef LOGDMABUF
#ifdef MOZ_LOGGING
#  include "mozilla/Logging.h"
#  include "nsTArray.h"
#  include "Units.h"
extern mozilla::LazyLogModule gDmabufLog;
#  define LOGDMABUF(args) MOZ_LOG(gDmabufLog, mozilla::LogLevel::Debug, args)
#else
#  define LOGDMABUF(args)
#endif /* MOZ_LOGGING */

namespace mozilla {
namespace widget {

typedef struct gbm_device* (*CreateDeviceFunc)(int);
typedef void (*DestroyDeviceFunc)(struct gbm_device*);
typedef struct gbm_bo* (*CreateFunc)(struct gbm_device*, uint32_t, uint32_t,
                                     uint32_t, uint32_t);
typedef struct gbm_bo* (*CreateWithModifiersFunc)(struct gbm_device*, uint32_t,
                                                  uint32_t, uint32_t,
                                                  const uint64_t*,
                                                  const unsigned int);
typedef struct gbm_bo* (*CreateWithModifiers2Func)(struct gbm_device*, uint32_t,
                                                   uint32_t, uint32_t,
                                                   const uint64_t*,
                                                   const unsigned int,
                                                   const uint32_t);
typedef uint64_t (*GetModifierFunc)(struct gbm_bo*);
typedef uint32_t (*GetStrideFunc)(struct gbm_bo*);
typedef int (*GetFdFunc)(struct gbm_bo*);
typedef void (*DestroyFunc)(struct gbm_bo*);
typedef void* (*MapFunc)(struct gbm_bo*, uint32_t, uint32_t, uint32_t, uint32_t,
                         uint32_t, uint32_t*, void**);
typedef void (*UnmapFunc)(struct gbm_bo*, void*);
typedef int (*GetPlaneCountFunc)(struct gbm_bo*);
typedef union gbm_bo_handle (*GetHandleForPlaneFunc)(struct gbm_bo*, int);
typedef uint32_t (*GetStrideForPlaneFunc)(struct gbm_bo*, int);
typedef uint32_t (*GetOffsetFunc)(struct gbm_bo*, int);
typedef int (*DeviceIsFormatSupportedFunc)(struct gbm_device*, uint32_t,
                                           uint32_t);
typedef int (*DrmPrimeHandleToFDFunc)(int, uint32_t, uint32_t, int*);
typedef struct gbm_surface* (*CreateSurfaceFunc)(struct gbm_device*, uint32_t,
                                                 uint32_t, uint32_t, uint32_t);
typedef void (*DestroySurfaceFunc)(struct gbm_surface*);

class GbmLib {
 public:
  static bool IsAvailable() { return sLoaded || Load(); }
  static bool IsModifierAvailable();

  static struct gbm_device* CreateDevice(int fd) { return sCreateDevice(fd); };
  static void DestroyDevice(struct gbm_device* gdm) {
    if (gdm) {
      sDestroyDevice(gdm);
    }
  };
  static struct gbm_bo* Create(struct gbm_device* gbm, uint32_t width,
                               uint32_t height, uint32_t format,
                               uint32_t flags) {
    if (!gbm) {
      return nullptr;
    }
    return sCreate(gbm, width, height, format, flags);
  }
  static void Destroy(struct gbm_bo* bo) {
    if (bo) {
      sDestroy(bo);
    }
  }
  static uint32_t GetStride(struct gbm_bo* bo) {
    if (!bo) {
      return 0;
    }
    return sGetStride(bo);
  }
  static int GetFd(struct gbm_bo* bo) {
    if (!bo) {
      return -1;
    }
    return sGetFd(bo);
  }
  static void* Map(struct gbm_bo* bo, uint32_t x, uint32_t y, uint32_t width,
                   uint32_t height, uint32_t flags, uint32_t* stride,
                   void** map_data) {
    if (!bo) {
      return nullptr;
    }
    return sMap(bo, x, y, width, height, flags, stride, map_data);
  }
  static void Unmap(struct gbm_bo* bo, void* map_data) {
    if (bo) {
      sUnmap(bo, map_data);
    }
  }
  static struct gbm_bo* CreateWithModifiers(struct gbm_device* gbm,
                                            uint32_t width, uint32_t height,
                                            uint32_t format,
                                            const uint64_t* modifiers,
                                            const unsigned int count) {
    if (!gbm) {
      return nullptr;
    }
    return sCreateWithModifiers(gbm, width, height, format, modifiers, count);
  }
  static struct gbm_bo* CreateWithModifiers2(struct gbm_device* gbm,
                                             uint32_t width, uint32_t height,
                                             uint32_t format,
                                             const uint64_t* modifiers,
                                             const unsigned int count,
                                             const uint32_t flags) {
    if (!gbm) {
      return nullptr;
    }
    if (!sCreateWithModifiers2) {
      // CreateWithModifiers2 is optional and present in new GBM lib,
      // if missing just fallback to old CreateWithModifiers as
      // we want to anyway.
      return sCreateWithModifiers(gbm, width, height, format, modifiers, count);
    }
    return sCreateWithModifiers2(gbm, width, height, format, modifiers, count,
                                 flags);
  }
  static uint64_t GetModifier(struct gbm_bo* bo) {
    if (!bo) {
      return 0;
    }
    return sGetModifier(bo);
  }
  static int GetPlaneCount(struct gbm_bo* bo) {
    if (!bo) {
      return 0;
    }
    return sGetPlaneCount(bo);
  }
  static union gbm_bo_handle GetHandleForPlane(struct gbm_bo* bo, int plane) {
    if (!bo) {
      union gbm_bo_handle ret;
      ret.ptr = nullptr;
      return ret;
    }
    return sGetHandleForPlane(bo, plane);
  }
  static uint32_t GetStrideForPlane(struct gbm_bo* bo, int plane) {
    if (!bo) {
      return 0;
    }
    return sGetStrideForPlane(bo, plane);
  }
  static uint32_t GetOffset(struct gbm_bo* bo, int plane) {
    if (!bo) {
      return 0;
    }
    return sGetOffset(bo, plane);
  }
  static int DeviceIsFormatSupported(struct gbm_device* gbm, uint32_t format,
                                     uint32_t usage) {
    if (!gbm) {
      return 0;
    }
    return sDeviceIsFormatSupported(gbm, format, usage);
  }
  static int DrmPrimeHandleToFD(int fd, uint32_t handle, uint32_t flags,
                                int* prime_fd) {
    return sDrmPrimeHandleToFD(fd, handle, flags, prime_fd);
  }
  static struct gbm_surface* CreateSurface(struct gbm_device* gbm,
                                           uint32_t width, uint32_t height,
                                           uint32_t format, uint32_t flags) {
    if (!gbm) {
      return nullptr;
    }
    return sCreateSurface(gbm, width, height, format, flags);
  }
  static void DestroySurface(struct gbm_surface* surface) {
    return sDestroySurface(surface);
  }

 private:
  static bool Load();
  static bool IsLoaded();

  static CreateDeviceFunc sCreateDevice;
  static DestroyDeviceFunc sDestroyDevice;
  static CreateFunc sCreate;
  static CreateWithModifiersFunc sCreateWithModifiers;
  static CreateWithModifiers2Func sCreateWithModifiers2;
  static GetModifierFunc sGetModifier;
  static GetStrideFunc sGetStride;
  static GetFdFunc sGetFd;
  static DestroyFunc sDestroy;
  static MapFunc sMap;
  static UnmapFunc sUnmap;
  static GetPlaneCountFunc sGetPlaneCount;
  static GetHandleForPlaneFunc sGetHandleForPlane;
  static GetStrideForPlaneFunc sGetStrideForPlane;
  static GetOffsetFunc sGetOffset;
  static DeviceIsFormatSupportedFunc sDeviceIsFormatSupported;
  static DrmPrimeHandleToFDFunc sDrmPrimeHandleToFD;
  static CreateSurfaceFunc sCreateSurface;
  static DestroySurfaceFunc sDestroySurface;
  static bool sLoaded;

  static void* sGbmLibHandle;
  static void* sXf86DrmLibHandle;
};

class DRMFormat;
class DMABufDeviceLock;

class DMABufDevice final {
  friend class DMABufDeviceLock;

 public:
  bool Init();

  gbm_device* GetDevice(DMABufDeviceLock* aDMABufDeviceLock);

  int OpenDRMFd();
  int GetDmabufFD(uint32_t aGEMHandle);
  bool IsEnabled(nsACString& aFailureId);

  // Use dmabuf for WebGL content
  static bool IsDMABufWebGLEnabled();
  static void DisableDMABufWebGL();

 private:
  ~DMABufDevice();

  void LoadFormatModifiers();
  void SetModifiersToGfxVars();
  void GetModifiersFromGfxVars();

  // Formats passed to RDD process to WebGL process
  // where we can't get formats/modifiers from Wayland display.
  // RGBA formats are mandatory, YUV ones are optional.
  RefPtr<DRMFormat> mFormatRGBA;
  RefPtr<DRMFormat> mFormatRGBX;
  RefPtr<DRMFormat> mFormatP010;
  RefPtr<DRMFormat> mFormatNV12;

  int mDRMFd = -1;
  gbm_device* mGbmDevice = nullptr;
  const char* mFailureId = nullptr;
  nsAutoCString mDrmRenderNode;
};

class MOZ_RAII DMABufDeviceLock final {
 public:
  DMABufDeviceLock();
  ~DMABufDeviceLock();

  static void Shutdown();

  gbm_device* GetGBMDevice() {
    sMutex.AssertCurrentThreadOwns();
    return mGBMDevice;
  }

  DMABufDevice* GetDMABufDevice() { return mDMABufDevice; }
  operator gbm_device*() { return GetGBMDevice(); }

 private:
  static DMABufDevice* EnsureDMABufDevice();

  DMABufDevice* mDMABufDevice = nullptr;
  gbm_device* mGBMDevice = nullptr;

  static StaticMutex sMutex;
  static DMABufDevice* sDMABufDevice;
};

}  // namespace widget
}  // namespace mozilla

#endif  // MOZ_DMABUF_LIB_WRAPPER_H_
