/*
   Copyright 2018 alexrayne <alexraynepe196@gmail.com>

   Licensed 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.
*/

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

#include "hal_ringbuffer.h"

HAL_RingBuffer::HAL_RingBuffer(void* buf, unsigned buffer_size, unsigned data_size) 
{
    u8* buffer = (u8*)buf;
    this->buffer = NULL;
    this->buffer_size = 0;
    tail = buffer;
    Clear();
    if (buffer == NULL || data_size + 1 > buffer_size) return;
    this->buffer = (u8*)buffer;
    this->buffer_size = buffer_size;
    tail = buffer;
    head = buffer + data_size; 
}

/*!
    \brief Размер данных в объекте

    \return Возвращает количество данных в буфере объекта
*/
u32 HAL_RingBuffer::DataSize() const {
  int tmp = head - tail;
  if (tmp < 0)
    return tmp + buffer_size;
  assert(unsigned(tmp) <= buffer_size);
  return tmp;
  //return (buffer_size + head - tail) % buffer_size;
};

/*!
	\brief Записать данные в объект

	\details Функция записывает в объект указанное количество данных и возвращает количество записаных данных
	
	\param [in] data Указатель на данные, которые нужно записать в объект
	\param [in] data_size Размер записываемых данных
	\param [out] writed_data_size Количество по факту записанных данных (будет меньше data_size если в буфере не хватит свободного места)
*/
unsigned HAL_RingBuffer::WriteToRingBuffer(const void *data, const u32 data_size) {
   	// if nothing to do
    assert(data != NULL);
   	if (data_size == 0) {
        return 0;
   	}
     // if not enough space
   	if (data_size > FreeSpaceSize()) {
      return 0;
   	}

    // write data to buffer
   	if (data_size < CalcSpaceFromHeadToEnd()) {
        // all data can be placed into the end of buffer
   		memcpy(head, data, data_size);
		head += data_size;
   	}
   	else {
        // one part of data can be placed into the end of buffer and another part can be placed into the beginning of buffer
        u32 bytes_to_write1 = CalcSpaceFromHeadToEnd();
   		u32 bytes_to_write2 = data_size - bytes_to_write1;
   		memcpy(head, data, bytes_to_write1);
   		head = buffer;
		memcpy(head, ((u8*)data) + bytes_to_write1, bytes_to_write2);
   		head += bytes_to_write2;
   	}

   return data_size;
}

/*!
	\brief Прочитать данные из объекта

	\details Функция читает из объекта указанное количество данных и возвращает количество прочитанных данных
	
	\param [in] data Указатель на буфер в который нужно записать прочитанные из объекта данные
	\param [in] data_size Размер буфера, в который нужно записать прочитанные из объекта данные
	\param [out] readed_data_size Количество по факту прочитанных (записаных в data) данных
*/
unsigned HAL_RingBuffer::ReadFromRingBuffer(void *data, const u32 data_size) const {
	// check: if buffer is empty or nothing to do
    assert(data != NULL);
   	if (data_size == 0) {
   		return 0;
	}

    u32 used_space_size = DataSize();
    if (used_space_size == 0)
        return 0;

   	// how many bytes should we read?
    u32 bytes_to_read = 0;
    if (data_size < used_space_size) {
    	bytes_to_read = data_size;
    }
    else {
		bytes_to_read = used_space_size;
    }

   	// read data from buffer
    u32 bytes_to_read1 = CalcSpaceFromTailToEnd();
	if (bytes_to_read < bytes_to_read1) {
   		memcpy(data, tail, bytes_to_read);
   	}
   	else {
   		u32 bytes_to_read2 = bytes_to_read - CalcSpaceFromTailToEnd();
   		memcpy(data, tail, bytes_to_read1);
   		memcpy(((u8*)data) + bytes_to_read1, buffer, bytes_to_read2);
   	}

    return bytes_to_read;
}

/*!
	\brief Записать данные в объект

	\details Функция записывает в объект указанное количество данных, если в буфере объекта достаточно свободного места
	
	\param [in] data Указатель на данные, которые нужно записать в объект
	\param [in] data_size Размер записываемых данных
	
	\return Возвращает true если в буфере хватило свободного места для записи указанного количества данных.
	Возвращает false, если в буфере не хватило свободного места для записи указанного количества данных.
*/
int HAL_RingBuffer::WriteData_len(const void *data, u32 data_size) {
    assert(data != NULL);
    if (buffer == NULL) {
        return 0;
    }
    unsigned avail = FreeSpaceSize();
    if (avail == 0)
      return 0;
    if (data_size > avail)
      data_size = avail;
		// return true if success, else false
   	u32 writed_data_size = WriteToRingBuffer(data, data_size);
   	return writed_data_size;
}

bool HAL_RingBuffer::WriteData(const void* data, const u32 data_size){
   	return (WriteData_len(data, data_size) == (int)data_size);
}



/*!
	\brief Записать строку в объект

	\details Функция записывает в объект строку, если в буфере объекта достаточно свободного места
	
	\param [in] data Указатель на строку, которую нужно записать в объект
	
	\return Возвращает true если в буфере хватило свободного места для записи строки.
	Возвращает false, если в буфере не хватило свободного места для записи строки.
*/
bool HAL_RingBuffer::WriteData(const char* data) {
   	return WriteData((u8*)data, strlen(data));
}

int  HAL_RingBuffer::WriteData_len(const char* data){
  int len = strlen(data);
  return (WriteData((u8*)data, len) == len);
}

/*!
	\brief Прочитать данные из объекта

	\details Функция читает из объекта указанное количество данных, если в буфере объекта имеется указанное количество данных
	
	\param [in] data Указатель на буфер в который нужно записать прочитанные из объекта данные
	\param [in] data_size Количество данных, которые нужно прочитать из буфера объекта
	
	\return Возвращает true если в буфере объекта имеется указанное в data_size количество данных, иначе false
*/
bool HAL_RingBuffer::ReadData(void *data, const u32 data_size) const {
    assert(data != NULL);
    if (buffer == NULL || (data_size == 0) ) {
        return false;
    }
    if (data_size > DataSize())
        return false;
    // return true if success, else false
    unsigned readed_data_size = this->ReadFromRingBuffer(data, data_size);
    return ( readed_data_size == data_size );
}

/*!
	\brief Прочитать данные из объекта

	\details Функция читает и удаляет из объекта указанное количество данных, если в буфере объекта имеется указанное количество данных
	
	\param [in] data Указатель на буфер в который нужно записать прочитанные из объекта данные
	\param [in] data_size Количество данных, которые нужно прочитать и удалить из буфера объекта
	
	\return Возвращает true если в буфере объекта имеется указанное в data_size количество данных, иначе false
*/
bool HAL_RingBuffer::ReadAndDeleteData(void *data, const u32 data_size) {
    assert(data != NULL);
    if ( (buffer == NULL) || (data_size == 0) ) {
        return false;
    }
    if (data_size > DataSize())
        return false;

	// return true if success, else false
    u32 readed_data_size = this->ReadFromRingBuffer(data, data_size);
    DeleteData(readed_data_size);
	return (readed_data_size  == data_size);
}

/*!
	\brief Удалить данные из объекта

	\details Функция  удаляет из объекта указанное количество данных, если в буфере объекта имеется указанное количество данных
	
	\param [in] data_size Количество данных, которые нужно удалить из буфера объекта
	
	\return Возвращает true если в буфере объекта имеется указанное в data_size количество данных, иначе false
*/
bool HAL_RingBuffer::DeleteData(u32 data_size) {
    if (buffer == NULL) {
        return false;
    }
    // check: if buffer is empty or nothing to do
	if (data_size == 0) {
   		return false;
   	}

	u32 bytes_to_delete = DataSize();
    if (bytes_to_delete < data_size) {
        return false;
    }

	// how many bytes should we delete?
   	if (data_size < bytes_to_delete) {
   	    bytes_to_delete = data_size;
    }
    suredDeleteData(bytes_to_delete);
    return true;
}

//* продвигает позицию head на data_size
void HAL_RingBuffer::AcceptData(u32 data_size){
    // if nothing to do
    if (data_size == 0) {
        return;
    }
     // if not enough space
    if (data_size > FreeSpaceSize()) {
      return;
    }

    // write data to buffer
    if (data_size < CalcSpaceFromHeadToEnd()) {
        // all data can be placed into the end of buffer
        head += data_size;
    }
    else {
        // one part of data can be placed into the end of buffer and another part can be placed into the beginning of buffer
        u32 bytes_to_write1 = CalcSpaceFromHeadToEnd();
        u32 bytes_to_write2 = data_size - bytes_to_write1;
        head = buffer + bytes_to_write2;
    }
}

//* \return - >= 0 - data char
//*           < 0  - no data
int HAL_RingBuffer::pop_char(){
  if (buffer == NULL || isEmpty())
    return -1;
  int res = *tail;
  suredDeleteData(1);
  return res;
}

int HAL_RingBuffer::drop_until_char(int x){
  if (buffer == NULL || isEmpty())
    return -1;

  u8* tmp = head;
  if (tail > head){
      tmp = StoreLimit();
      for (; tail < tmp; tail++)
          if (*tail == x)
              return 1;
      if (tail >= StoreLimit())
        tail = buffer;
  }
  for (; tail < head; tail++)
      if (*tail == x)
          return 1;
  return 0;
}




//* \return amount of pushed data
int HAL_RingBuffer::push_char(int x){
  if (buffer == NULL || (FreeSpaceSize() == 0) )
    return 0;
  *head = u8(x);
  u8* tmp = head+1;
  if ((tmp - buffer) < buffer_size)
    head = tmp;
  else
    head = buffer;
  return 1;
}

void HAL_RingBuffer::FreeBufAssign(u8* x){
    assert( (x >= buffer) && ((x - buffer) < buffer_size));
    if (head >= tail) {
        if ( (x < head) && (x > tail) )
                tail = x+1;
    }
    else {
        if ((x < head) || (x >= tail))
            tail = x+1;
    }
    head = x;
}

//* \return длинна куска буфера на записи
u32 HAL_RingBuffer::FreeBufSize() const {
  int tmp = tail - head;
  if (tmp < 0)
      return (buffer + buffer_size) - head;
  else
      return tmp;
};

u32 HAL_RingBuffer::DataBufSize() const {
  int tmp = head - tail;
  if (tmp > 0)
      return tmp;
  else
      return (buffer + buffer_size) - tail;
};
