/****************************************************************************
 * apps/system/nxcodec/nxcodec_context.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <sys/ioctl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <nuttx/nuttx.h>

#include "nxcodec_context.h"
#include "nxcodec.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define NXCODEC_CONTEXT_BUFNUMBER 3

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static inline FAR nxcodec_t *
nxcodec_context_to_nxcodec(FAR nxcodec_context_t *ctx)
{
  return V4L2_TYPE_IS_OUTPUT(ctx->type) ?
         container_of(ctx, nxcodec_t, output) :
         container_of(ctx, nxcodec_t, capture);
}

static FAR nxcodec_context_buf_t *
nxcodec_context_dequeue_buf(FAR nxcodec_context_t *ctx)
{
  FAR nxcodec_t *codec = nxcodec_context_to_nxcodec(ctx);
  struct v4l2_buffer buf;
  int ret;

  memset(&buf, 0, sizeof(buf));
  buf.memory = V4L2_MEMORY_MMAP;
  buf.type = ctx->type;

  ret = ioctl(codec->fd, VIDIOC_DQBUF, &buf);
  if (ret < 0)
    {
      if (errno != EAGAIN)
        printf("nxcodec %s VIDIOC_DQBUF - %s\n",
               V4L2_TYPE_IS_OUTPUT(ctx->type) ? "output" : "capture",
               strerror(errno));
      return NULL;
    }

  ctx->buf[buf.index].free = true;
  ctx->buf[buf.index].buf = buf;

  return &ctx->buf[buf.index];
}

static FAR nxcodec_context_buf_t *
nxcodec_context_get_freebuf(FAR nxcodec_context_t *ctx)
{
  int i;

  if (V4L2_TYPE_IS_OUTPUT(ctx->type))
    {
      while (nxcodec_context_dequeue_buf(ctx));
    }

  for (i = 0; i < ctx->nbuffers; i++)
    {
      if (ctx->buf[i].free)
        {
          return &ctx->buf[i];
        }
    }

  return NULL;
}

static int nxcodec_context_write_data(FAR nxcodec_context_t *ctx,
                                      FAR const char *buf, int size)
{
  return write(ctx->fd, buf, size) < 0 ? -errno : 0;
}

static int nxcodec_context_read_yuv_data(FAR nxcodec_context_t *ctx,
                                         FAR char *buf,
                                         FAR uint32_t *bytesused)
{
  size_t buflen = ctx->format.fmt.pix.width *
                  ctx->format.fmt.pix.height * 3 / 2;
  ssize_t ret;

  ret = read(ctx->fd, buf, buflen);
  if (ret <= 0)
    {
      return -errno;
    }

  *bytesused = ret;
  return 0;
}

static int nxcodec_context_read_h264_data(FAR nxcodec_context_t *ctx,
                                          FAR char *buf,
                                          FAR uint32_t *bytesused)
{
  char start_code[4];
  ssize_t ret;
  int size;

  memset(start_code, 0, 4);

  ret = read(ctx->fd, buf, 4);
  if (ret <= 0)
    {
      return -errno;
    }

  if (buf[0] == 0x00 && buf[1] == 0x00 &&
      buf[2] == 0x00 && buf[3] == 0x01)
    {
      size = 4;
      while (1)
        {
          ret = read(ctx->fd, buf + size, 1);
          if (ret < 0)
            {
              return -errno;
            }
          else if (ret == 0)
            {
              break;
            }

          start_code[0] = start_code[1];
          start_code[1] = start_code[2];
          start_code[2] = start_code[3];
          start_code[3] = *(buf + size);
          size++;

          if (start_code[0] == 0x00 && start_code[1] == 0x00 &&
              start_code[2] == 0x00 && start_code[3] == 0x01)
            {
              size -= 4;
              lseek(ctx->fd, -4, SEEK_CUR);
              break;
            }
        }
    }
  else
    {
      return -EINVAL;
    }

  *bytesused = size;

  return 0;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int nxcodec_context_set_status(FAR nxcodec_context_t *ctx, uint32_t cmd)
{
  FAR nxcodec_t *codec = nxcodec_context_to_nxcodec(ctx);

  return ioctl(codec->fd, cmd, &ctx->type) < 0 ? -errno : 0;
}

int nxcodec_context_enqueue_frame(FAR nxcodec_context_t *ctx)
{
  FAR nxcodec_t *codec = nxcodec_context_to_nxcodec(ctx);
  FAR nxcodec_context_buf_t *buf;
  int ret;

  buf = nxcodec_context_get_freebuf(ctx);
  if (!buf)
    {
      return -EAGAIN;
    }

  if (ctx->format.fmt.pix.pixelformat == V4L2_PIX_FMT_H264)
    {
      ret = nxcodec_context_read_h264_data(ctx,
                                           buf->addr,
                                           &buf->buf.bytesused);
      if (ret < 0)
        {
          return ret;
        }
    }
  else if (ctx->format.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420)
    {
      ret = nxcodec_context_read_yuv_data(ctx,
                                          buf->addr,
                                          &buf->buf.bytesused);
      if (ret < 0)
        {
          return ret;
        }
    }

  ret = ioctl(codec->fd, VIDIOC_QBUF, &buf->buf);
  if (ret < 0)
    {
      return -errno;
    }

  buf->free = false;
  return 0;
}

int nxcodec_context_dequeue_frame(FAR nxcodec_context_t *ctx)
{
  FAR nxcodec_t *codec = nxcodec_context_to_nxcodec(ctx);
  FAR nxcodec_context_buf_t *buf;
  int ret;

  buf = nxcodec_context_dequeue_buf(ctx);
  if (!buf)
    {
      return -EAGAIN;
    }

  if (buf->buf.length > 0)
    {
      nxcodec_context_write_data(ctx, buf->addr, buf->buf.bytesused);
    }

  ret = ioctl(codec->fd, VIDIOC_QBUF, &buf->buf);
  if (ret < 0)
    {
      return -errno;
    }

  buf->free = false;
  return 0;
}

int nxcodec_context_get_format(FAR nxcodec_context_t *ctx)
{
  FAR nxcodec_t *codec = nxcodec_context_to_nxcodec(ctx);
  struct v4l2_fmtdesc fdesc;
  int ret;

  fdesc.type = ctx->type;
  while (true)
    {
      ret = ioctl(codec->fd, VIDIOC_ENUM_FMT, &fdesc);
      if (ret < 0)
        {
          printf("nxcodec %s enum_fmt error: %d\n",
                 V4L2_TYPE_IS_OUTPUT(ctx->type) ? "output" : "capture",
                 errno);
          return -errno;
        }

      if (fdesc.pixelformat == ctx->format.fmt.pix.pixelformat)
        {
          break;
        }

      fdesc.index++;
    }

  ctx->format.type = ctx->type;
  return ioctl(codec->fd, VIDIOC_TRY_FMT, &ctx->format) < 0 ? -errno : 0;
}

int nxcodec_context_set_format(FAR nxcodec_context_t *ctx)
{
  FAR nxcodec_t *codec = nxcodec_context_to_nxcodec(ctx);

  printf("nxcodec %s VIDIOC_S_FMT\n",
         V4L2_TYPE_IS_OUTPUT(ctx->type) ? "output" : "capture");

  return ioctl(codec->fd, VIDIOC_S_FMT, &ctx->format) < 0 ? -errno : 0;
}

int nxcodec_context_init(FAR nxcodec_context_t *ctx)
{
  FAR nxcodec_t *codec = nxcodec_context_to_nxcodec(ctx);
  struct v4l2_requestbuffers req;
  int ret;
  int i;

  memset(&req, 0, sizeof(req));
  req.count = NXCODEC_CONTEXT_BUFNUMBER;
  req.memory = V4L2_MEMORY_MMAP;
  req.type = ctx->type;

  ret = ioctl(codec->fd, VIDIOC_REQBUFS, &req);
  if (ret < 0)
    {
      printf("nxcodec type: %s, VIDIOC_REQBUFS failed: %s\n",
             V4L2_TYPE_IS_OUTPUT(ctx->type) ? "output" : "capture",
             strerror(errno));
      return -errno;
    }

  ctx->nbuffers = req.count;
  ctx->buf = calloc(ctx->nbuffers, sizeof(nxcodec_context_buf_t));
  if (!ctx->buf)
    {
      printf("nxcodec type: %s, alloc memory error\n",
             V4L2_TYPE_IS_OUTPUT(ctx->type) ? "output" : "capture");
      return -ENOMEM;
    }

  for (i = 0; i < ctx->nbuffers; i++)
    {
      FAR nxcodec_context_buf_t *buf = &ctx->buf[i];

      buf->buf.memory = V4L2_MEMORY_MMAP;
      buf->buf.type = ctx->type;
      buf->buf.index = i;

      ret = ioctl(codec->fd, VIDIOC_QUERYBUF, &buf->buf);
      if (ret < 0)
        {
          goto error;
        }

      buf->length = buf->buf.length;
      buf->addr = mmap(NULL,
                       buf->buf.length,
                       PROT_READ | PROT_WRITE,
                       MAP_SHARED,
                       codec->fd,
                       buf->buf.m.offset);

      if (buf->addr == MAP_FAILED)
        {
          goto error;
        }

      buf->free = true;

      if (V4L2_TYPE_IS_OUTPUT(ctx->type))
        {
          continue;
        }

      ret = ioctl(codec->fd, VIDIOC_QBUF, &buf->buf);
      if (ret < 0)
        {
          munmap(buf->addr, buf->length);
          goto error;
        }

      buf->free = false;
    }

  return 0;

error:
  free(ctx->buf);
  return -errno;
}

void nxcodec_context_uninit(FAR nxcodec_context_t *ctx)
{
  int i;

  if (!ctx->buf)
    {
      return;
    }

  for (i = 0; i < ctx->nbuffers; i++)
    {
      FAR nxcodec_context_buf_t *buf = &ctx->buf[i];

      if (buf->addr && buf->length)
        {
          if (munmap(buf->addr, buf->length) < 0)
            {
              printf("nxcodec type: %s, unmap plane (%s))\n",
                     V4L2_TYPE_IS_OUTPUT(ctx->type) ? "output" : "capture",
                     strerror(errno));
            }
        }
    }

  free(ctx->buf);
}