/****************************************************************************
 * drivers/video/video_framebuff.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 <string.h>
#include <errno.h>

#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>

#include "video_framebuff.h"

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

static void init_buf_chain(video_framebuff_t *fbuf)
{
  vbuf_container_t *tmp;
  int i;

  fbuf->vbuf_empty = fbuf->vbuf_alloced;
  fbuf->vbuf_next  = NULL;
  fbuf->vbuf_top   = NULL;
  fbuf->vbuf_tail  = NULL;

  tmp = fbuf->vbuf_alloced;
  for (i = 1; i < fbuf->container_size; i++)
    {
      tmp->next = &tmp[1];
      tmp++;
    }
}

static inline bool is_last_one(video_framebuff_t *fbuf)
{
  return fbuf->vbuf_top == fbuf->vbuf_tail;
}

static inline vbuf_container_t *dequeue_vbuf_unsafe(video_framebuff_t *fbuf)
{
  vbuf_container_t *ret = fbuf->vbuf_top;

  if (is_last_one(fbuf))
    {
      fbuf->vbuf_top  = NULL;
      fbuf->vbuf_tail = NULL;
      fbuf->vbuf_next = NULL;
    }
  else
    {
      if (fbuf->mode == V4L2_BUF_MODE_RING)
        {
          fbuf->vbuf_tail->next = fbuf->vbuf_top->next;
        }

      fbuf->vbuf_top = fbuf->vbuf_top->next;
    }

  ret->next = NULL;
  return ret;
}

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

void video_framebuff_init(video_framebuff_t *fbuf)
{
  memset(fbuf, 0, sizeof(video_framebuff_t));
  fbuf->mode = V4L2_BUF_MODE_RING;
  nxmutex_init(&fbuf->lock_empty);
}

void video_framebuff_uninit(video_framebuff_t *fbuf)
{
  video_framebuff_realloc_container(fbuf, 0);
  nxmutex_destroy(&fbuf->lock_empty);
}

int video_framebuff_realloc_container(video_framebuff_t *fbuf, int sz)
{
  vbuf_container_t *vbuf;

  nxmutex_lock(&fbuf->lock_empty);
  if (fbuf->container_size == sz)
    {
      nxmutex_unlock(&fbuf->lock_empty);
      return OK;
    }

  if (sz > 0)
    {
      vbuf = kmm_realloc(fbuf->vbuf_alloced, sizeof(vbuf_container_t) * sz);
      if (vbuf != NULL)
        {
          memset(vbuf, 0, sizeof(vbuf_container_t) * sz);
          fbuf->vbuf_alloced = vbuf;
          fbuf->container_size = sz;
        }
      else
        {
          nxmutex_unlock(&fbuf->lock_empty);
          return -ENOMEM;
        }
    }
  else
    {
      kmm_free(fbuf->vbuf_alloced);
      fbuf->vbuf_alloced = NULL;
      fbuf->container_size = 0;
    }

  init_buf_chain(fbuf);
  nxmutex_unlock(&fbuf->lock_empty);

  return OK;
}

int video_framebuff_is_empty(video_framebuff_t *fbuf)
{
  return fbuf->vbuf_top == NULL || fbuf->vbuf_top == fbuf->vbuf_next;
}

vbuf_container_t *video_framebuff_get_container(video_framebuff_t *fbuf)
{
  vbuf_container_t *ret;

  nxmutex_lock(&fbuf->lock_empty);
  ret = fbuf->vbuf_empty;
  if (ret != NULL)
    {
      fbuf->vbuf_empty = ret->next;
      ret->next        = NULL;
    }

  nxmutex_unlock(&fbuf->lock_empty);
  return ret;
}

void video_framebuff_free_container(video_framebuff_t *fbuf,
                                    vbuf_container_t  *cnt)
{
  nxmutex_lock(&fbuf->lock_empty);
  cnt->next = fbuf->vbuf_empty;
  fbuf->vbuf_empty = cnt;
  nxmutex_unlock(&fbuf->lock_empty);
}

void video_framebuff_queue_container(video_framebuff_t *fbuf,
                                     vbuf_container_t  *tgt)
{
  irqstate_t flags;

  flags = spin_lock_irqsave(&fbuf->lock_queue);
  if (fbuf->vbuf_top != NULL)
    {
      fbuf->vbuf_tail->next = tgt;
      fbuf->vbuf_tail = tgt;
    }
  else
    {
      fbuf->vbuf_top = fbuf->vbuf_tail = tgt;
    }

  if (fbuf->vbuf_next == NULL)
    {
      fbuf->vbuf_next = tgt;
    }

  if (fbuf->mode == V4L2_BUF_MODE_RING)
    {
      tgt->next = fbuf->vbuf_top;
    }
  else  /* Case of V4L2_BUF_MODE_FIFO */
    {
      tgt->next = NULL;
    }

  spin_unlock_irqrestore(&fbuf->lock_queue, flags);
}

vbuf_container_t *video_framebuff_dq_valid_container(video_framebuff_t *fbuf)
{
  vbuf_container_t *ret = NULL;
  irqstate_t flags;

  flags = spin_lock_irqsave(&fbuf->lock_queue);
  if (fbuf->vbuf_top != NULL && fbuf->vbuf_top != fbuf->vbuf_next)
    {
      ret = dequeue_vbuf_unsafe(fbuf);
    }

  spin_unlock_irqrestore(&fbuf->lock_queue, flags);
  return ret;
}

vbuf_container_t *
video_framebuff_get_vacant_container(video_framebuff_t *fbuf)
{
  vbuf_container_t *ret;
  irqstate_t flags;

  flags = spin_lock_irqsave(&fbuf->lock_queue);
  ret = fbuf->vbuf_next;
  spin_unlock_irqrestore(&fbuf->lock_queue, flags);

  return ret;
}

void video_framebuff_capture_done(video_framebuff_t *fbuf)
{
  irqstate_t flags;

  flags = spin_lock_irqsave(&fbuf->lock_queue);
  if (fbuf->vbuf_next != NULL)
    {
      fbuf->vbuf_next = fbuf->vbuf_next->next;
      if (fbuf->vbuf_next == fbuf->vbuf_top)  /* RING mode case. */
        {
          fbuf->vbuf_top  = fbuf->vbuf_top->next;
          fbuf->vbuf_tail = fbuf->vbuf_tail->next;
        }
    }

  spin_unlock_irqrestore(&fbuf->lock_queue, flags);
}

void video_framebuff_change_mode(video_framebuff_t  *fbuf,
                                 enum v4l2_buf_mode mode)
{
  irqstate_t flags;

  flags = spin_lock_irqsave(&fbuf->lock_queue);
  if (fbuf->mode != mode)
    {
      if (fbuf->vbuf_tail)
        {
          if (mode == V4L2_BUF_MODE_RING)
            {
              fbuf->vbuf_tail->next = fbuf->vbuf_top;
            }
          else
            {
              fbuf->vbuf_tail->next = NULL;
            }
        }

      fbuf->vbuf_next = fbuf->vbuf_top;
      fbuf->mode = mode;
    }

  spin_unlock_irqrestore(&fbuf->lock_queue, flags);
}

vbuf_container_t *video_framebuff_pop_curr_container(video_framebuff_t *fbuf)
{
  vbuf_container_t *ret = NULL;
  irqstate_t flags;

  flags = spin_lock_irqsave(&fbuf->lock_queue);
  if (fbuf->vbuf_top != NULL)
    {
      ret = dequeue_vbuf_unsafe(fbuf);
    }

  spin_unlock_irqrestore(&fbuf->lock_queue, flags);
  return ret;
}