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

#include <errno.h>
#include <fcntl.h>
#include <cstdio>
#include <cstdlib>
#if defined(__NetBSD__) || defined(__OpenBSD__)
#  include <sys/videoio.h>
#elif defined(__sun)
#  include <sys/videodev2.h>
#else
#  include <linux/videodev2.h>
#endif
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>

#include "mozilla/ScopeExit.h"

#ifdef __SUNPRO_CC
#  include <stdio.h>
#endif

#include "mozilla/GfxInfoUtils.h"

// Convert an integer pixfmt to a 4-character string.  str must have a length
// of at least 5 to include null-termination.
static void v4l2_pixfmt_to_str(uint32_t pixfmt, char* str) {
  for (int i = 0; i < 4; i++) {
    str[i] = (pixfmt >> (i * 8)) & 0xff;
  }
  str[4] = 0;
}

// Enumerate the buffer formats supported on a V4L2 buffer queue.  aTypeStr
// is the queue type, i.e. CAPTURE or OUTPUT.
static void v4l2_enumfmt(int aFd, int aType, const char* aTypeStr) {
  struct v4l2_fmtdesc fmt{};
  char pix_fmt_str[5];
  fmt.type = aType;
  record_value("V4L2_%s_FMTS\n", aTypeStr);
  for (fmt.index = 0;; fmt.index++) {
    int result = ioctl(aFd, VIDIOC_ENUM_FMT, &fmt);
    if (result < 0) {
      break;
    }
    v4l2_pixfmt_to_str(fmt.pixelformat, pix_fmt_str);
    record_value(" %s", pix_fmt_str);
  }
  record_value("\n");
}

// Probe a V4L2 device to work out what it supports
static void v4l2_check_device(const char* aVideoDevice) {
  int fd = -1;
  int result = -1;

  log("v4l2test probing device '%s'\n", aVideoDevice);

  auto autoRelease = mozilla::MakeScopeExit([&] {
    if (fd >= 0) {
      close(fd);
    }
  });

  fd = open(aVideoDevice, O_RDWR | O_NONBLOCK, 0);
  if (fd < 0) {
    record_value("ERROR\nV4L2 failed to open device %s: %s\n", aVideoDevice,
                 strerror(errno));
    return;
  }

  struct v4l2_capability cap{};
  result = ioctl(fd, VIDIOC_QUERYCAP, &cap);
  if (result < 0) {
    record_value("ERROR\nV4L2 device %s failed to query capabilities\n",
                 aVideoDevice);
    return;
  }
  log("v4l2test driver %s card %s bus_info %s version %d\n", cap.driver,
      cap.card, cap.bus_info, cap.version);

  if (!(cap.capabilities & V4L2_CAP_DEVICE_CAPS)) {
    record_value("ERROR\nV4L2 device %s does not support DEVICE_CAPS\n",
                 aVideoDevice);
    return;
  }

  if (!(cap.device_caps & V4L2_CAP_STREAMING)) {
    record_value("ERROR\nV4L2 device %s does not support V4L2_CAP_STREAMING\n",
                 aVideoDevice);
    return;
  }

  // Work out whether the device supports planar or multiplaner bitbuffers and
  // framebuffers
  bool splane = cap.device_caps & V4L2_CAP_VIDEO_M2M;
  bool mplane = cap.device_caps & V4L2_CAP_VIDEO_M2M_MPLANE;
  if (!splane && !mplane) {
    record_value("ERROR\nV4L2 device %s does not support M2M modes\n",
                 aVideoDevice);
    // (It's probably a webcam!)
    return;
  }
  record_value("V4L2_SPLANE\n%s\n", splane ? "TRUE" : "FALSE");
  record_value("V4L2_MPLANE\n%s\n", mplane ? "TRUE" : "FALSE");

  // Now check the formats supported for CAPTURE and OUTPUT buffers.
  // For a V4L2-M2M decoder, OUTPUT is actually the bitbuffers we put in and
  // CAPTURE is the framebuffers we get out.
  v4l2_enumfmt(
      fd,
      mplane ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : V4L2_BUF_TYPE_VIDEO_CAPTURE,
      "CAPTURE");
  v4l2_enumfmt(
      fd,
      mplane ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE : V4L2_BUF_TYPE_VIDEO_OUTPUT,
      "OUTPUT");

  record_value("V4L2_SUPPORTED\nTRUE\n");
}

int v4l2test(const char* aDevice) {
  const char* env = getenv("MOZ_GFX_DEBUG");
  enable_logging = env && *env == '1';
  if (!enable_logging) {
    close_logging();
  }
  v4l2_check_device(aDevice);
  record_flush();
  return EXIT_SUCCESS;
}
