/*
 * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  * Neither the name of NVIDIA CORPORATION nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "NvVideoConverter.h"
#include "NvLogging.h"

#include <cstring>
#include <errno.h>

#define CONVERTER_DEV "/dev/nvhost-vic"
#define CAT_NAME "NVVIDCONV"

#define CHECK_V4L2_RETURN(ret, str)        \
  if (ret < 0) {                           \
    COMP_SYS_ERROR_MSG(str << ": failed"); \
    return -1;                             \
  } else {                                 \
    COMP_DEBUG_MSG(str << ": success");    \
    return 0;                              \
  }

#define RETURN_ERROR_IF_FORMATS_SET()                               \
  if (output_plane_pixfmt != 0 || capture_plane_pixfmt != 0) {      \
    COMP_ERROR_MSG("Should be called before setting plane formats") \
    return -1;                                                      \
  }

#define RETURN_ERROR_IF_BUFFERS_REQUESTED()                                      \
  if (output_plane.getNumBuffers() != 0 && capture_plane.getNumBuffers() != 0) { \
    COMP_ERROR_MSG("Should be called before requesting buffers on either plane") \
    return -1;                                                                   \
  }

#define RETURN_ERROR_IF_FORMATS_NOT_SET()                          \
  if (output_plane_pixfmt == 0 || capture_plane_pixfmt == 0) {     \
    COMP_ERROR_MSG("Should be called after setting plane formats") \
    return -1;                                                     \
  }

using namespace std;

NvVideoConverter::NvVideoConverter(const char *name, int flags)
    : NvV4l2Element(name, CONVERTER_DEV, flags, valid_fields) {}

NvVideoConverter *NvVideoConverter::createVideoConverter(const char *name, int flags) {
  NvVideoConverter *conv = new NvVideoConverter(name, flags);
  if (conv->isInError()) {
    delete conv;
    return NULL;
  }
  return conv;
}

NvVideoConverter::~NvVideoConverter() {}

int NvVideoConverter::setCapturePlaneFormat(uint32_t pixfmt, uint32_t width, uint32_t height,
                                            enum v4l2_nv_buffer_layout type) {
  struct v4l2_format format;
  uint32_t num_bufferplanes;
  NvBuffer::NvBufferPlaneFormat planefmts[MAX_PLANES];

  if (setCapturePlaneBufferLayout(type) < 0) {
    return -1;
  }

  capture_plane_pixfmt = pixfmt;
  NvBuffer::fill_buffer_plane_format(&num_bufferplanes, planefmts, width, height, pixfmt);
  capture_plane.setBufferPlaneFormat(num_bufferplanes, planefmts);

  memset(&format, 0, sizeof(struct v4l2_format));
  format.type = capture_plane.getBufType();
  format.fmt.pix_mp.width = width;
  format.fmt.pix_mp.height = height;
  format.fmt.pix_mp.pixelformat = pixfmt;
  format.fmt.pix_mp.num_planes = num_bufferplanes;

  CHECK_V4L2_RETURN(capture_plane.setFormat(format), "Setting capture plane format");
}

int NvVideoConverter::setOutputPlaneFormat(uint32_t pixfmt, uint32_t width, uint32_t height,
                                           enum v4l2_nv_buffer_layout type) {
  struct v4l2_format format;
  uint32_t num_bufferplanes;
  NvBuffer::NvBufferPlaneFormat planefmts[MAX_PLANES];

  if (setOutputPlaneBufferLayout(type) < 0) {
    return -1;
  }

  output_plane_pixfmt = pixfmt;
  NvBuffer::fill_buffer_plane_format(&num_bufferplanes, planefmts, width, height, pixfmt);
  output_plane.setBufferPlaneFormat(num_bufferplanes, planefmts);

  memset(&format, 0, sizeof(struct v4l2_format));
  format.type = output_plane.getBufType();
  format.fmt.pix_mp.width = width;
  format.fmt.pix_mp.height = height;
  format.fmt.pix_mp.pixelformat = pixfmt;
  format.fmt.pix_mp.num_planes = num_bufferplanes;

  CHECK_V4L2_RETURN(output_plane.setFormat(format), "Setting output plane format");
}

int NvVideoConverter::waitForIdle(uint32_t max_wait_ms) {
  struct timespec timeToWait;
  struct timeval now;
  int return_val = 0;

  gettimeofday(&now, NULL);

  timeToWait.tv_nsec = (now.tv_usec + (max_wait_ms % 1000) * 1000L) * 1000L;
  timeToWait.tv_sec = now.tv_sec + max_wait_ms / 1000 + timeToWait.tv_nsec / 1000000000L;
  timeToWait.tv_nsec = timeToWait.tv_nsec % 1000000000L;

  pthread_mutex_lock(&capture_plane.plane_lock);
  while (output_plane.getTotalQueuedBuffers() > capture_plane.getTotalDequeuedBuffers()) {
    if (!capture_plane.getStreamStatus()) {
      return_val = -2;
      break;
    }
    int ret = pthread_cond_timedwait(&capture_plane.plane_cond, &capture_plane.plane_lock, &timeToWait);
    if (ret == ETIMEDOUT) {
      return_val = -1;
      break;
    }
  }
  pthread_mutex_unlock(&capture_plane.plane_lock);

  return return_val;
}

int NvVideoConverter::setOutputPlaneBufferLayout(enum v4l2_nv_buffer_layout type) {
  struct v4l2_ext_control ctl;
  struct v4l2_ext_controls controls;

  RETURN_ERROR_IF_BUFFERS_REQUESTED();

  memset(&ctl, 0, sizeof(ctl));
  memset(&controls, 0, sizeof(controls));

  ctl.id = V4L2_CID_VIDEO_CONVERT_OUTPUT_PLANE_LAYOUT;
  ctl.value = (uint32_t)type;
  controls.controls = &ctl;
  controls.count = 1;

  CHECK_V4L2_RETURN(setExtControls(controls), "Setting output plane buffer layout to " << type);
}

int NvVideoConverter::setCapturePlaneBufferLayout(enum v4l2_nv_buffer_layout type) {
  struct v4l2_ext_control ctl;
  struct v4l2_ext_controls controls;

  RETURN_ERROR_IF_BUFFERS_REQUESTED();

  memset(&ctl, 0, sizeof(ctl));
  memset(&controls, 0, sizeof(controls));

  ctl.id = V4L2_CID_VIDEO_CONVERT_CAPTURE_PLANE_LAYOUT;
  ctl.value = (uint32_t)type;
  controls.controls = &ctl;
  controls.count = 1;

  CHECK_V4L2_RETURN(setExtControls(controls), "Setting capture plane buffer layout to " << type);
}

int NvVideoConverter::setInterpolationMethod(enum v4l2_interpolation_method method) {
  struct v4l2_ext_control ctl;
  struct v4l2_ext_controls controls;

  RETURN_ERROR_IF_BUFFERS_REQUESTED();

  memset(&ctl, 0, sizeof(ctl));
  memset(&controls, 0, sizeof(controls));

  ctl.id = V4L2_CID_VIDEO_CONVERT_INTERPOLATION_METHOD;
  ctl.value = (uint32_t)method;
  controls.controls = &ctl;
  controls.count = 1;

  CHECK_V4L2_RETURN(setExtControls(controls), "Setting interpolation method to " << method);
}

int NvVideoConverter::setFlipMethod(enum v4l2_flip_method method) {
  struct v4l2_ext_control ctl;
  struct v4l2_ext_controls controls;

  RETURN_ERROR_IF_BUFFERS_REQUESTED();

  memset(&ctl, 0, sizeof(ctl));
  memset(&controls, 0, sizeof(controls));

  ctl.id = V4L2_CID_VIDEO_CONVERT_FLIP_METHOD;
  ctl.value = (uint32_t)method;
  controls.controls = &ctl;
  controls.count = 1;

  CHECK_V4L2_RETURN(setExtControls(controls), "Setting flip method to " << method);
}

int NvVideoConverter::setTnrAlgorithm(enum v4l2_tnr_algorithm algorithm) {
  struct v4l2_ext_control ctl;
  struct v4l2_ext_controls controls;

  RETURN_ERROR_IF_BUFFERS_REQUESTED();

  memset(&ctl, 0, sizeof(ctl));
  memset(&controls, 0, sizeof(controls));

  ctl.id = V4L2_CID_VIDEO_CONVERT_TNR_ALGORITHM;
  ctl.value = (uint32_t)algorithm;
  controls.controls = &ctl;
  controls.count = 1;

  CHECK_V4L2_RETURN(setExtControls(controls), "Setting TNR algorithm to " << algorithm);
}

int NvVideoConverter::setCropRect(uint32_t left, uint32_t top, uint32_t width, uint32_t height) {
  struct v4l2_rect rect;

  rect.left = left;
  rect.top = top;
  rect.width = width;
  rect.height = height;

  CHECK_V4L2_RETURN(
      capture_plane.setSelection(V4L2_SEL_TGT_CROP, 0, rect),
      "Setting crop rectangle to left=" << left << ", top=" << top << ", width=" << width << ", height=" << height);
}
