/****************************************************************************
 * libs/libc/misc/lib_circbuf.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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.
 *
 ****************************************************************************/

/* Note about locking: There is no locking required while only one reader
 * and one writer is using the circular buffer.
 * For multiple writer and one reader there is only a need to lock the
 * writer. And vice versa for only one writer and multiple reader there is
 * only a need to lock the reader.
 */

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

#include <nuttx/config.h>

#include <assert.h>

#include <nuttx/circbuf.h>
#include <nuttx/lib/lib.h>

/****************************************************************************
 * Private Types
 ****************************************************************************/

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

/****************************************************************************
 * Name: circbuf_init
 *
 * Description:
 *   Initialize a circular buffer.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 *   base  - A pointer to circular buffer's internal buffer. It can be
 *           provided by caller because sometimes the creation of buffer
 *           is special or needs to preallocated, eg: DMA buffer.
 *           If NULL, a buffer of the given size will be allocated.
 *   bytes - The size of the internal buffer.
 *
 * Returned Value:
 *   Zero on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

int circbuf_init(FAR struct circbuf_s *circ, FAR void *base, size_t bytes)
{
  DEBUGASSERT(circ);
  DEBUGASSERT(!base || bytes);

  circ->external = !!base;

  if (!base && bytes)
    {
      base = lib_malloc(bytes);
      if (!base)
        {
          return -ENOMEM;
        }
    }

  circ->base = base;
  circ->size = bytes;
  circ->head = 0;
  circ->tail = 0;

  return 0;
}

/****************************************************************************
 * Name: circbuf_resize
 *
 * Description:
 *   Resize a circular buffer (change buffer size).
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 *   bytes - The size of the internal buffer.
 *
 * Returned Value:
 *   Zero on success; A negated errno value is returned on any failure.
 *
 ****************************************************************************/

int circbuf_resize(FAR struct circbuf_s *circ, size_t bytes)
{
  FAR void *tmp = NULL;
  size_t len = 0;

  DEBUGASSERT(circ);
  DEBUGASSERT(!circ->external);
  if (bytes == circ->size)
    {
      return 0;
    }

  if (bytes)
    {
      tmp = lib_malloc(bytes);
      if (!tmp)
        {
          return -ENOMEM;
        }

      len = circbuf_used(circ);
      if (bytes < len)
        {
          circbuf_skip(circ, len - bytes);
          len = bytes;
        }

      circbuf_read(circ, tmp, len);
    }

  lib_free(circ->base);

  circ->base = tmp;
  circ->size = bytes;
  circ->head = len;
  circ->tail = 0;

  return 0;
}

/****************************************************************************
 * Name: circbuf_reset
 *
 * Description:
 *   Remove the entire circular buffer content.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 ****************************************************************************/

void circbuf_reset(FAR struct circbuf_s *circ)
{
  DEBUGASSERT(circ);
  circ->head = circ->tail = 0;
}

/****************************************************************************
 * Name: circbuf_uninit
 *
 * Description:
 *   Free the circular buffer.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 ****************************************************************************/

void circbuf_uninit(FAR struct circbuf_s *circ)
{
  DEBUGASSERT(circ);

  if (!circ->external)
    {
      lib_free(circ->base);
    }

  memset(circ, 0, sizeof(*circ));
}

/****************************************************************************
 * Name: circbuf_size
 *
 * Description:
 *   Return size of the circular buffer.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 ****************************************************************************/

size_t circbuf_size(FAR struct circbuf_s *circ)
{
  DEBUGASSERT(circ);
  return circ->size;
}

/****************************************************************************
 * Name: circbuf_used
 *
 * Description:
 *   Return the used bytes of the circular buffer.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 ****************************************************************************/

size_t circbuf_used(FAR struct circbuf_s *circ)
{
  DEBUGASSERT(circ);
  return circ->head - circ->tail;
}

/****************************************************************************
 * Name: circbuf_space
 *
 * Description:
 *   Return the remaining space of the circular buffer.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 ****************************************************************************/

size_t circbuf_space(FAR struct circbuf_s *circ)
{
  return circbuf_size(circ) - circbuf_used(circ);
}

/****************************************************************************
 * Name: circbuf_is_init
 *
 * Description:
 *   Return true if the circular buffer had been initialized.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 ****************************************************************************/

bool circbuf_is_init(FAR struct circbuf_s *circ)
{
  return !!circ->base;
}

/****************************************************************************
 * Name: circbuf_is_empty
 *
 * Description:
 *   Return true if the circular buffer is empty.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 ****************************************************************************/

bool circbuf_is_empty(FAR struct circbuf_s *circ)
{
  return !circbuf_used(circ);
}

/****************************************************************************
 * Name: circbuf_is_full
 *
 * Description:
 *   Return true if the circular buffer is full.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 ****************************************************************************/

bool circbuf_is_full(FAR struct circbuf_s *circ)
{
  return !circbuf_space(circ);
}

/****************************************************************************
 * Name: circbuf_peekat
 *
 * Description:
 *   Get data specified position from the circular buffer without removing
 *
 * Note :
 *   That with only one concurrent reader and one concurrent writer,
 *   you don't need extra locking to use these api.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 *   pos   - Position to read.
 *   dst   - Address where to store the data.
 *   bytes - Number of bytes to get.
 *
 * Returned Value:
 *   The bytes of get data is returned if the peek data is successful;
 *   A negated errno value is returned on any failure.
 ****************************************************************************/

ssize_t circbuf_peekat(FAR struct circbuf_s *circ, size_t pos,
                       FAR void *dst, size_t bytes)
{
  size_t len;
  size_t off;

  DEBUGASSERT(circ);

  if (!circ->size)
    {
      return 0;
    }

  if (circ->head - pos > circ->head - circ->tail)
    {
      pos = circ->tail;
    }

  len = circ->head - pos;
  off = pos % circ->size;

  if (bytes > len)
    {
      bytes = len;
    }

  len = circ->size - off;
  if (bytes < len)
    {
      len = bytes;
    }

  memcpy(dst, (FAR char *)circ->base + off, len);
  memcpy((FAR char *)dst + len, circ->base, bytes - len);

  return bytes;
}

/****************************************************************************
 * Name: circbuf_peek
 *
 * Description:
 *   Get data from the circular buffer without removing
 *
 * Note :
 *   That with only one concurrent reader and one concurrent writer,
 *   you don't need extra locking to use these api.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 *   dst   - Address where to store the data.
 *   bytes - Number of bytes to get.
 *
 * Returned Value:
 *   The bytes of get data is returned if the peek data is successful;
 *   A negated errno value is returned on any failure.
 ****************************************************************************/

ssize_t circbuf_peek(FAR struct circbuf_s *circ,
                     FAR void *dst, size_t bytes)
{
  return circbuf_peekat(circ, circ->tail, dst, bytes);
}

/****************************************************************************
 * Name: circbuf_read
 *
 * Description:
 *   Get data from the circular buffer.
 *
 * Note :
 *   That with only one concurrent reader and one concurrent writer,
 *   you don't need extra locking to use these api.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 *   dst   - Address where to store the data.
 *   bytes - Number of bytes to get.
 *
 * Returned Value:
 *   The bytes of get data is returned if the read data is successful;
 *   A negated errno value is returned on any failure.
 ****************************************************************************/

ssize_t circbuf_read(FAR struct circbuf_s *circ,
                     FAR void *dst, size_t bytes)
{
  DEBUGASSERT(circ);
  DEBUGASSERT(dst || !bytes);

  bytes = circbuf_peek(circ, dst, bytes);
  circ->tail += bytes;

  return bytes;
}

/****************************************************************************
 * Name: circbuf_skip
 *
 * Description:
 *   Skip data from the circular buffer.
 *
 * Note :
 *   That with only one concurrent reader and one concurrent writer,
 *   you don't need extra locking to use these api.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 *   bytes - Number of bytes to skip.
 *
 * Returned Value:
 *   The bytes of get data is returned if the skip data is successful;
 *   A negated errno value is returned on any failure.
 ****************************************************************************/

ssize_t circbuf_skip(FAR struct circbuf_s *circ, size_t bytes)
{
  size_t len;

  DEBUGASSERT(circ);

  len = circbuf_used(circ);

  if (bytes > len)
    {
      bytes = len;
    }

  circ->tail += bytes;

  return bytes;
}

/****************************************************************************
 * Name: circbuf_write
 *
 * Description:
 *   Write data to the circular buffer.
 *
 * Note :
 *   That with only one concurrent reader and one concurrent writer,
 *   you don't need extra locking to use these api.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 *   src   - The data to be added.
 *   bytes - Number of bytes to be added.
 *
 * Returned Value:
 *   The bytes of get data is returned if the write data is successful;
 *   A negated errno value is returned on any failure.
 ****************************************************************************/

ssize_t circbuf_write(FAR struct circbuf_s *circ,
                      FAR const void *src, size_t bytes)
{
  size_t space;
  size_t off;

  DEBUGASSERT(circ);
  DEBUGASSERT(src || !bytes);

  if (!circ->size)
    {
      return 0;
    }

  space = circbuf_space(circ);
  off = circ->head % circ->size;
  if (bytes > space)
    {
      bytes = space;
    }

  space = circ->size - off;
  if (bytes < space)
    {
      space = bytes;
    }

  memcpy((FAR char *)circ->base + off, src, space);
  memcpy(circ->base, (FAR char *)src + space, bytes - space);
  circ->head += bytes;

  return bytes;
}

/****************************************************************************
 * Name: circbuf_overwrite
 *
 * Description:
 *   Write data to the circular buffer. It can overwrite old data when
 *   circular buffer don't have enough space to store data.
 *
 * Note:
 *   Usage circbuf_overwrite () is dangerous. It should be only called
 *   when the buffer is exclusived locked or when it is secured that no
 *   other thread is accessing the buffer.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 *   src   - The data to be added.
 *   bytes - Number of bytes to be added.
 *
 * Returned Value:
 *   The bytes length of overwrite is returned if it's successful;
 *   A negated errno value is returned on any failure.
 ****************************************************************************/

ssize_t circbuf_overwrite(FAR struct circbuf_s *circ,
                          FAR const void *src, size_t bytes)
{
  size_t overwrite = 0;
  size_t skip = 0;
  size_t space;
  size_t off;

  DEBUGASSERT(circ);
  DEBUGASSERT(src || !bytes);

  if (!circ->size)
    {
      return 0;
    }

  if (bytes > circ->size)
    {
      skip = bytes - circ->size;
      src = (FAR const void *)((FAR char *)src + skip);
      bytes = circ->size;
    }

  space = circbuf_space(circ);
  if (bytes > space)
    {
      overwrite = bytes - space + skip;
    }

  circ->head += skip;
  off = circ->head % circ->size;
  space = circ->size - off;
  if (bytes < space)
    {
      space = bytes;
    }

  memcpy((FAR char *)circ->base + off, src, space);
  memcpy(circ->base, (FAR char *)src + space, bytes - space);
  circ->head += bytes;
  circ->tail += overwrite;

  return overwrite;
}

/****************************************************************************
 * Name: circbuf_get_writeptr
 *
 * Description:
 *   Get the write pointer of the circbuf.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 *   size  - Returns the maximum size that can be written consecutively
 *
 * Returned Value:
 *   The write pointer of the circbuf.
 *
 ****************************************************************************/

FAR void *circbuf_get_writeptr(FAR struct circbuf_s *circ, FAR size_t *size)
{
  size_t off;

  DEBUGASSERT(circ);

  *size = circbuf_space(circ);
  off = circ->head % circ->size;
  if (off + *size > circ->size)
    {
      *size = circ->size - off;
    }

  return (FAR char *)circ->base + off;
}

/****************************************************************************
 * Name: circbuf_get_readptr
 *
 * Description:
 *   Get the read pointer of the circbuf.
 *
 * Input Parameters:
 *   circ  - Address of the circular buffer to be used.
 *   size  - Returns the maximum size that can be read consecutively.
 *
 * Returned Value:
 *   The read pointer of the circbuf.
 *
 ****************************************************************************/

FAR void *circbuf_get_readptr(FAR struct circbuf_s *circ, size_t *size)
{
  size_t off;

  DEBUGASSERT(circ);

  *size = circbuf_used(circ);
  off = circ->tail % circ->size;
  if (off + *size > circ->size)
    {
      *size = circ->size - off;
    }

  return (FAR char *)circ->base + off;
}

/****************************************************************************
 * Name: circbuf_writecommit
 *
 * Description:
 *   After writing data using the buf returned by circbuf_writebuf,
 *   you need to use this function to update the internal structure
 *   of cricbuf.
 *
 * Input Parameters:
 *   circ        - Address of the circular buffer to be used.
 *   writtensize - The data that has been written to the buffer.
 *
 ****************************************************************************/

void circbuf_writecommit(FAR struct circbuf_s *circ, size_t writtensize)
{
  DEBUGASSERT(circ);
  circ->head += writtensize;
}

/****************************************************************************
 * Name: circbuf_readcommit
 *
 * Description:
 *   After reading data using the buf returned by circbuf_readbuf,
 *   you need to use this function to update the internal structure
 *   of cricbuf.
 *
 * Input Parameters:
 *   circ     - Address of the circular buffer to be used.
 *   readsize - The data that has been read to the buffer.
 *
 ****************************************************************************/

void circbuf_readcommit(FAR struct circbuf_s *circ, size_t readsize)
{
  DEBUGASSERT(circ);
  circ->tail += readsize;
}
