#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "circular_buffer.h"
#include "priv_malloc.h"

/**
 * @brief     Check if Num is power of 2
 *
 * @param[in] Num   the number to check
 *
 * @return          1 if Num is power of 2
 */
unsigned long long IsPowerOf2(unsigned long long Num) {
  return (Num > 0 && !(Num & (Num - 1)));
}

/**
 * @brief     calculate the minimum number that round up to the next power of 2
 *
 * @param[in] Num   the number to calculate
 *
 * @return          the number that round up to the next power of 2 (0x100 if Num is 0xf0, 0x81, 0xa3 ... )
 */
unsigned long RoundUp_PowerOf2(unsigned long Num) {
  unsigned long result = 1;

  if (IsPowerOf2(Num) || Num == 0)
    return Num;
  else if (Num > LONG_MAX)
    return (LONG_MAX ^ ULONG_MAX);   // WARN: if Num biger than (LONG_MAX+1) then result will equals to (LONG_MAX+1)

  while (Num) {
    Num >>= 1;
    result <<= 1;
  }

  return result;
}

/**
 * @brief     calculate the minimum number that round down to the next power of 2
 *
 * @param[] Num the number to check
 *
 * @return    the number that round up to the last power of 2 (4 if Num is 5,6,7, 8 if Num is 9,10,11 ... )
 */
unsigned long RoundDown_PowerOf2(unsigned long Num) {
  unsigned long result = 1;

  if (IsPowerOf2(Num) || Num == 0)
    return Num;
  else if (Num > LONG_MAX)
    return (LONG_MAX ^ ULONG_MAX);   // WARN: if Num biger than (LONG_MAX+1) then result will equals to (LONG_MAX+1)

  while (Num) {
    Num >>= 1;
    result <<= 1;
  }

  return result >> 1;
}

/**
 * @brief     Init the Circular buffer with a array
 *
 * @param[in] CBuf      The circular buffer to initial
 * @param[in] Buff      the buffer for circular buffer to store data
 * @param[in] Size      the size of buffer
 *
 * @return      the Round Down(Power Of 2) size that the circular  buffer to be used
 */
int CircBuf_Init(CircBuf_t *CBuf, unsigned char *Buff, unsigned int Size) {
  CBuf->Buffer = Buff;

  if(!IsPowerOf2(Size)) {
    if (Size > INT_MAX)
      Size = (INT_MAX ^ UINT_MAX);
    else
      Size = (int) RoundDown_PowerOf2(Size);
  }

  CBuf->Size = Size;
  CBuf->Tailer = 0;
  CBuf->Header = 0;

  return Size;
}

/**
 * @brief     Circular Buffer initialization
 *
 * @param[in] CBuf      the circular buffer to initialization
 * @param[in] Size      size of the circular buffer
 *
 * @return    1 if memery allocation success
 *            0 if fail
 */
int CircBuf_Alloc(CircBuf_t *CBuf, unsigned int Size) {
  int result = 0;

  if(!IsPowerOf2(Size)) {
    if(Size > INT_MAX)
      Size = (INT_MAX ^ UINT_MAX);
    else
      Size = (int)RoundUp_PowerOf2(Size);
  }
  CBuf->Buffer = (unsigned char *) board_calloc(Size);    // Buffer will set to 0

  CBuf->Tailer = 0;
  CBuf->Header = 0;

  if(CBuf->Buffer != NULL) {
    CBuf->Size = Size;
    result = 1;
  }

  return result;
}

/**
 * @brief     delete circular buffer and release the memery
 *
 * @param[in] CBuf  the circular buffer to delete
 */
void CircBuf_Free(CircBuf_t *CBuf) {
  free(CBuf->Buffer);
  CBuf = NULL;
}

/**
 * @brief     put data into the circular buffer
 *
 * @param[in] CBuf      the circular buffer that will store the data
 * @param[in] data      the data to store into circular buffer
 * @param[in] LenToPush  the length of data to store into circular buffer
 *
 * @return      the actual size stored into circular buffer
 */
unsigned int CircBuf_Push(CircBuf_t *CBuf, unsigned char *data, unsigned int LenToPush) {
  unsigned int len;

  LenToPush = MIN(LenToPush, (CBuf->Size - (CBuf->Header - CBuf->Tailer)));

  len = MIN(LenToPush, CBuf->Size - (CBuf->Header & (CBuf->Size - 1)));

  memcpy(CBuf->Buffer + (CBuf->Header & CBuf->Size - 1), data, len);
  memcpy(CBuf->Buffer, data + len, LenToPush - len);

  CBuf->Header += LenToPush;

  return LenToPush;
}

/**
 * @brief     get data from circular buffer
 *
 * @param[in] CBuf      the circular buffer that stored data
 * @param[in] data      target buffer that will store the data that from circular buffer
 * @param[in] LenToPop  the length that wan't to get from circular buffer
 *
 * @return      actual length that get from circular buffer
 */
unsigned int CircBuf_Pop(CircBuf_t *CBuf, unsigned char *data, unsigned int LenToPop) {
  unsigned int len;

  LenToPop = MIN(LenToPop, CBuf->Header - CBuf->Tailer);

  len = MIN(LenToPop, CBuf->Size - (CBuf->Tailer & (CBuf->Size - 1)));

  memcpy(data, CBuf->Buffer + (CBuf->Tailer & (CBuf->Size - 1)), len);
  memcpy(data + len, CBuf->Buffer, LenToPop - len);

  CBuf->Tailer += LenToPop;

  return LenToPop;
}

/**
 * @brief     get one char from circular buffer
 *
 * @param[in] CBuf      the circular buffer that stored data
 * @param[n] data       target buffer that will store the data that from circular buffer
 *
 * @return              actual length that get from circular buffer
 */
unsigned int CircBuf_PopOneChar(CircBuf_t *CBuf, unsigned char *data) {
  return CircBuf_Pop(CBuf, data, 1);
}

/**
 * @brief     for access data at Tailer + offset
 *
 * @param[in] CBuf      the circular buffer that stored data
 * @param[in] offset    the offset of Tailer
 *
 * @return              the data at Buffer[Tailer + offset]
 */
unsigned char CircBuf_At(CircBuf_t *CBuf, unsigned int offset) {
  unsigned int index = (CBuf->Tailer + offset) & (CBuf->Size - 1);
  return CBuf->Buffer[index];
}

/**
 * @brief     get data from circular buffer
 *
 * @param[in] CBuf      the circular buffer that stored data
 * @param[in] data      target buffer that will store the data that from circular buffer
 * @param[in] LenToRead  the length that wan't to get from circular buffer
 *
 * @return      actual length that get from circular buffer
 */
unsigned int CircBuf_Read(CircBuf_t *CBuf, unsigned char *data, unsigned int LenToRead) {
  unsigned int len;

  LenToRead = MIN(LenToRead, CBuf->Header - CBuf->Tailer);

  len = MIN(LenToRead, CBuf->Size - (CBuf->Tailer & (CBuf->Size - 1)));

  memcpy(data, CBuf->Buffer + (CBuf->Tailer & (CBuf->Size - 1)), len);
  memcpy(data + len, CBuf->Buffer, LenToRead - len);

  return LenToRead;
}

/**
 * @brief     drop the the size of data at tailer
 *
 * @param[in] CBuf          the circular buffer that stored data
 * @param[in] LenToDrop     the size of data at tailer of circular_buffer to drop
 */
void CircBuf_Drop(CircBuf_t *CBuf, unsigned int LenToDrop) {
  if((CBuf->Tailer + LenToDrop) <= CBuf->Header )
    CBuf->Tailer += LenToDrop;
  else
    CBuf->Tailer = CBuf->Header;
}

/**
 * @brief     get the Available memery size of circular buffer
 *
 * @param[in] CBuf  the circular buffer to get size
 *
 * @return          Available size of the circular buffer
 */
unsigned int CircBuf_GetAvalaibleSize(CircBuf_t *CBuf) {
  return ((CBuf->Size > 0) ? (CBuf->Size - (CBuf->Header - CBuf->Tailer)) : 0);
}

/**
* @brief     get the used memery size of circular buffer
*
* @param[in] CBuf  the circular buffer to get size
*
* @return          used size of the circular buffer
*/
unsigned int CircBuf_GetUsedSize(CircBuf_t *CBuf) {
  return (CBuf->Header - CBuf->Tailer);
}

/**
 * @brief     check if the circular buffer is empty
 *
 * @param[in] CBuf  the circular buffer to check
 *
 * @return          1   if no data stored in the circular buffer
 *                  0   if the size of circular buffer equals to 0
 *                      or some data stored in the circular buffer
 */
unsigned int CircBuf_IsEmpty(CircBuf_t *CBuf) {
  return ((CBuf->Size > 0) && (CBuf->Header == CBuf->Tailer));
}

/**
 * @brief     check if the circular buffer is full
 *
 * @param[in] CBuf  the circular buffer to check
 *
 * @return      1 if the size of circular buffer equals to 0
 *                  or no Available space of circular buffer
 */
unsigned int CircBuf_IsFull(CircBuf_t *CBuf) {
  return ((CBuf->Size == 0) || (CBuf->Size == (CBuf->Header - CBuf->Tailer)));
}
