/*
 * ka_buffer.c
 * Copyright (C) 2005 KinoAmp team.
 *
 * This file is part of KinoAmp, a free video stream decoder.
 *
 * KinoAmp is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * KinoAmp is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "ka_buffer.h"

#include <assert.h>
#include <string.h>

#include "ka_error.h"
#include "ka_mem.h"
#include "ka_log.h"
#include "config.h"
#include "timer1.h"

struct ka_buffer_s
{
  // circular buffer
  uint8_t* pbuffer;         // ptr to allocated buffer
  uint8_t* pstart;          // ptr to normal start of the circular buffer
  uint8_t* pend;            // ptr to end of buffer
  const uint8_t* pread;     // ptr to first loaded byte
  uint8_t* pwrite;          // ptr to first free byte
  const uint8_t* pstacked;  // ptr to first non-stacked byte
  int      totalsize;       // total size of the circular buffer
  int      windowsize;      // size of buffer before the circular buffer
                            // reserved for data from the end of the buffer
  int      loadsize;        // amount of bytes of the next block to load
};

/**
 * Creates a new buffer.
 *
 * @param  windowsize  Size of the window before the circular buffer.
 *                     Used to move unprocessed data at the end of the circular buffer
 *                     to form again a continuous block of data.
 * @param  size        Size of the circular buffer, should be a multiple of blocksize.
 * @param  blocksize   Size of each data load.
 *
 * @returns New buffer.
 */
ka_buffer_t* ka_new_buffer(ka_error_t* pErrorBlock, int windowsize, int size, int blocksize)
{
  ka_buffer_t* buf = ka_mem_calloc(sizeof(*buf));

  if (!buf)
  {
    ka_error_fill(pErrorBlock, "error4:%s %d", "input", sizeof(*buf)); // (Cannot allocate input buffer)
    return NULL;
  }

  // Allocate main input buffer
  if ((buf->pbuffer = ka_mem_alloc(windowsize + size)) == 0)
  {
    ka_error_fill(pErrorBlock, "error4:%s %d", "input", windowsize + size); // (Cannot allocate input buffer)
    ka_mem_free(buf);
    return NULL;
  }

  buf->totalsize = size;
  buf->windowsize = windowsize;
  buf->loadsize = blocksize;
  ka_buffer_clear(buf);

  if (config.debug & cfg_printbufferstats)
    ka_log(ka_log_buffer, "Allocated buffer %p [start %p, end %p]"
                  , buf->pbuffer, buf->pstart, buf->pend);

  return buf;
}

/*
static void asserts(const ka_buffer_t* buf)
{
  assert(buf->pstart == buf->pbuffer + buf->windowsize);
  assert(buf->pend == buf->pstart + buf->totalsize);
  assert(buf->pstart <= buf->pwrite);
  assert(buf->pend > buf->pwrite);
  assert(buf->pbuffer <= buf->pread);
  assert(buf->pend >= buf->pread);
  assert(buf->pbuffer <= buf->pstacked);
  assert(buf->pend >= buf->pstacked);
}
*/

/**
 * Releases the buffer and all its data.
 *
 * @param  buf         Buffer to delete.
 */
void ka_delete_buffer(ka_buffer_t** pbuf)
{
  ka_buffer_t* buf = *pbuf;
  *pbuf = NULL;

  if (!buf)
    return;

  if (buf->pbuffer) ka_mem_free(buf->pbuffer);
  ka_mem_free(buf);
}

/**
 * Resets the buffer to an empty one.
 *
 * @param  buf         Buffer to handle.
 */
void ka_buffer_clear(ka_buffer_t* buf)
{
  // Reset all pointers
  buf->pstacked = buf->pread = buf->pwrite = buf->pstart = buf->pbuffer + buf->windowsize;
  buf->pend = buf->pstart + buf->totalsize;
}

/**
 * Returns the amount of bytes loaded in the buffer.
 *
 * @param  buf         Buffer to handle.
 *
 * @returns Amount of bytes loaded in the buffer.
 */
int ka_buffer_getByteCount(const ka_buffer_t* buf)
{
  int len = buf->pwrite - buf->pread;
  if (len < 0) len += buf->totalsize;

  return len;
}

/**
 * Returns the amount of unstacked bytes in the buffer.
 *
 * @param  buf         Buffer to handle.
 *
 * @returns Amount of unstacked bytes in the buffer.
 */
int ka_buffer_getUnstackedCount(const ka_buffer_t* buf)
{
  int len = buf->pwrite - buf->pstacked;
  if (len < 0) len += buf->totalsize;

  return len;
}

/**
 * Returns the amount of free bytes in the buffer.
 *
 * @param  buf         Buffer to handle.
 *
 * @returns Amount of free bytes in the buffer.
 */
int ka_buffer_getFreeCount(const ka_buffer_t* buf)
{
  int len = buf->pread - buf->pwrite;
  if (len <= 0) len += buf->totalsize;

  return len;
}

/**
 * Gets the next contiguous block of data to parse.
 *
 * @param  buf         Buffer to handle.
 * @param  ppstart     Pointer where to store start address of block.
 * @param  ppend       Pointer where to store end address of block.
 *
 * @returns Size of block or 0 if no block to parse.
 */
int ka_buffer_getUnstackedBlock(const ka_buffer_t* buf, const uint8_t** ppstart, const uint8_t** ppend)
{
  *ppstart = buf->pstacked;

  if (buf->pread > buf->pwrite)
  {
    if (*ppstart >= buf->pread)
      *ppend = buf->pend;
    else
      *ppend = buf->pwrite;
  }
  else
    *ppend = buf->pwrite;

  return (*ppend - *ppstart);
}

/**
 * Gets the next contiguous free block.
 *
 * @param  buf         Buffer to handle.
 * @param  ppstart     Pointer where to store start address of block.
 * @param  ppend       Pointer where to store end address of block.
 *
 * @returns Size of block or 0 if no free block.
 */
int ka_buffer_getFreeBlock(const ka_buffer_t* buf, uint8_t** ppstart, uint8_t** ppend)
{
  int free = ka_buffer_getFreeCount(buf);
  int len = buf->loadsize;

  if (free <= buf->loadsize)
    return 0;

  // Ask to fill most of the buffer on the first data load
  if (free == buf->totalsize)
    len = buf->totalsize - buf->loadsize;

  if ((buf->pwrite + len) > buf->pend)
    len = buf->pend - buf->pwrite;

  *ppstart = buf->pwrite;
  *ppend = buf->pwrite + len;

  return len;
}

/**
 * Retuns the first of the two pointer in the buffer.
 * Since the implementation uses a circular buffer it is not always the one with the lowest adress.
 *
 * @param  buf         Buffer to handle.
 *
 * @returns Pointer to first of the two blocks.
 */
const uint8_t* ka_buffer_firstBlock(const ka_buffer_t* buf, const uint8_t* p1, const uint8_t* p2)
{
  if (!p1) return p2;
  if (!p2) return p1;

  int32_t pos1 = p1 - buf->pread;
  int32_t pos2 = p2 - buf->pread;
  if (pos1 < 0) pos1 += buf->totalsize;
  if (pos2 < 0) pos2 += buf->totalsize;

  if (pos1 <= pos2)
    return p1;

  return p2;
}

/**
 * Move up the read pointer and mark all the data before it as free.
 *
 * @param  buf         Buffer to handle.
 * @param  pread       New address of the read limit
 *                     or NULL to mark all stacked data as read.
 */
void ka_buffer_setRead(ka_buffer_t* buf, const uint8_t* pread)
{
  if (pread)
  {
/*
    if (buf->pread > buf->pstacked)
    {
      // Would dangerous to check against pstacked since it could be < pstart
      if (pread >= buf->pread)
        assert((pread >= buf->pread) && (pread <= buf->pend));
      else
        assert((pread >= buf->pbuffer) && (pread <= buf->pstacked));
    }
    else
    {
      assert((pread >= buf->pread) && (pread <= buf->pstacked));
    }
*/
  }
  else
  {
    pread = buf->pstacked;
  }

  buf->pread = pread;
}

/**
 * Move up the limit of stacked data in the buffer.
 * The data between read and stacked must be kept till they are pulled
 * from the stack.
 *
 * @param  buf         Buffer to handle.
 * @param  pstacked    New address of the stack limit.
 */
void ka_buffer_setStacked(ka_buffer_t* buf, const uint8_t* pstacked)
{
  if (buf->pstacked != pstacked)
  {
/*    if (buf->pstacked > buf->pwrite)
      assert((pstacked >= buf->pstacked) && (pstacked <= buf->pend));
    else
      assert((pstacked >= buf->pstacked) && (pstacked <= buf->pwrite));
*/
    if (pstacked == buf->pend)
      pstacked = buf->pstart;

    buf->pstacked = pstacked;
  }
}

/**
 * Move up the write pointer after filling up the buffer.
 *
 * @param  buf         Buffer to handle.
 * @param  pwrite      New address of the write limit.
 */
void ka_buffer_setWritten(ka_buffer_t* buf, uint8_t* pwrite)
{
  if (buf->pwrite != pwrite)
  {
/*    if (buf->pwrite >= buf->pread)
    {
      if (pwrite >= buf->pwrite)
        assert((pwrite >= buf->pwrite) && (pwrite <= buf->pend));
      else
        assert((pwrite >= buf->pstart) && (pwrite < buf->pread));
    }
    else
      assert((pwrite >= buf->pwrite) && (pwrite < buf->pread));
*/
    buf->pwrite = pwrite;
    if (buf->pwrite >= buf->pend)
      buf->pwrite = buf->pstart;
  }
}

/**
 * When stacking is completed without filling the stack
 * either we have no more data to push or we are at the end
 * of the circular buffer and have to move the last remaining
 * bytes to just before the start of the buffer to form
 * again a contiguous block and restart pushing.
 *
 * @param  buf         Buffer to handle.
 * @param  end         End address of block we were processing.
 *
 * @returns 1 if stacking is complete, -1 on error, 0 otherwise.
 */
int ka_buffer_compressUnstacked(ka_buffer_t* buf, ka_error_t* pErrorBlock, const uint8_t* end)
{
  if (end == buf->pend)
  {
    // move the remaining of current buffer before circular buffer
    int len = buf->pend - buf->pstacked;

    if (len)
    {
      if (config.debug & cfg_printbufferstats)
        ka_log(ka_log_buffer, "Moving %x bytes before circular buffer", len);
      if (buf->pbuffer > buf->pstart - len)
      {
        // (video buffer overflow)
        ka_error_fill(pErrorBlock, "error6: %s %d %d", "Input Buffer", buf->pbuffer - buf->pstart, len);
        return -1;
      }
      memcpy(buf->pstart - len, buf->pstacked, len);
    }
    buf->pstacked = buf->pstart - len;

    return 0;
  }

  return 1;
}
