/*
   Copyright 2019 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.
*/

/*!
	\file
	\brief Файл с описанием класса HAL_RingBuffer и шаблона EMP_TemplateRingBuffer
	\details HAL_RingBuffer обеспечивает функционал кольцевого буфера, но не содержит в себе буфера для хранения данных, а работает через указатель на буфер.
	Рабочие объекты создаем как экземпляры EMP_TemplateRingBuffer с указанием размера буфера.
*/

#ifndef hal_ringbufferH
#define hal_ringbufferH

#include "hal_types.h"

/*!
	Класс HAL_RingBuffer. Реализует функционал кольцевого буфера,
	    но не содержит в себе буфера для хранения данных, а работает через указатель на буфер.
*/
class HAL_RingBuffer {
public:
  typedef u8  value_t;
  typedef u8* value_p;
protected:
	u8 *buffer; ///< указатель на буфер для хранения данных
	u32 buffer_size; ///< размер буфера
	u8 *head; ///< first free byte
	u8 *tail; ///< last used byte

	/*!
		\brief Функция вычисляет количество байт между головой и концом буфера
		
		\return Возвращает количество байт между головой и концом буфера
	*/
    u32 CalcSpaceFromHeadToEnd() const {
	    return buffer_size + buffer - head;
    }

	/*!
		\brief Функция вычисляет количество байт между головой и концом буфера

		\return Возвращает количество байт между головой и концом буфера
	*/
    u32 CalcSpaceFromTailToEnd() const {
	    return buffer_size + buffer - tail;
    }

    //* расчитывает на валидность буфера и данных
    void suredDeleteData(u32 data_size){
      // delete data from buffer
      value_p tmp = tail + data_size;
      if ((tmp - buffer) >= buffer_size)
        tmp -= buffer_size;
      tail = tmp;
    }

    //* эта функция записывает только если data_size помещается целиком
    //* \return - 0 - data not fit in buffer
    unsigned WriteToRingBuffer(const void *data, const u32 data_size);

    //* \return - 0 - empty buffer
    unsigned ReadFromRingBuffer(void *data, const u32 data_size) const;

    //* \return - >= 0 - data char
    //*           < 0  - no data
    int ReadChar_FromRingBuffer(void);
    int WriteChar_ToRingBuffer(char x);

public:
	HAL_RingBuffer() {
        buffer = NULL;
        buffer_size = 0;
        tail = buffer;
        Clear();
    }
	
	HAL_RingBuffer(void* buf, unsigned buffer_size, unsigned data_size);

	// отбрасывает уазатель записи к концу прочитаных данных
	void Clear() {
        head = tail;
    }
    // сбрасывает уазатель чтения к началу буфера записи
    void Drop() {
        tail = head;
    }
	void Assign(const HAL_RingBuffer& x){
	    buffer      = x.buffer;
	    buffer_size = x.buffer_size;
	    head        = x.head;
	    tail        = x.tail;
	}

	bool WriteData(const void *data, const u32 data_size); // TODO: rename to push
	bool WriteData(const char* data); // TODO: rename to push
	//* TODO should rename bool WriteData -> WriteDataok or isWriteData
  int  WriteData_len(const void *data, u32 data_size);
  int  WriteData_len(const char* data);
    bool ReadData(void *data, const u32 data_size) const ; // TODO: rename to peek
	bool ReadAndDeleteData(void *data, const u32 data_size); // TODO: rename to pop
    bool DeleteData(u32 data_size);
    //* продвигает позицию head на data_size
    void AcceptData(u32 data_size);

    //* \return - >= 0 - data char
    //*           < 0  - no data
    int pop_char();
    //* \return amount of pushed data
    int push_char(int x);

    int touch_char() const {
        if (!isEmpty())
            return *tail;
        return -1;
    };

    // \return < 0 - fail, no buffer
    //         = 0 - buffer empty
    //         = 1 - buffer starts at x
    int drop_until_char(int x);

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

		\return Возвращает количество данных в буфере объекта
	*/
    u32 DataSize() const;

	/*!
		\brief Пустой ли буфер объекта?

		\return Возвращает true если объект не содержит данных (буфер объекта пуст), иначе false
	*/
	bool isEmpty() const {
		return (head == tail);
	};

	/*!
		\brief Получить размер свободного места в буфере объекта

		\return Возвращает количество свободного места в буфере объекта
	*/
	u32 FreeSpaceSize() const {
	  return buffer_size-1 - DataSize();
		//return (buffer_size - 1 + tail - head) % buffer_size;
	};


public:
    u8* Store() const {return (u8*)buffer;};
    unsigned StoreSize() const {return buffer_size;};
    u8* StoreLimit() const {return ((u8*)buffer)+buffer_size;};

	//* \return длинна куска буфера на записи
	u32 FreeBufSize() const;
	u8* FreeBuf(){return head;};
    void FreeBuf(u8* x){head = x;};
    void FreeBufAssign(u8* x);

	//* \return длинна куска буфера на записи
    u32 DataBufSize() const;
    u8* DataBuf(){return tail;};
    void DataBuf(u8* x){tail = x;};
};

/*!
	Шаблон HAL_RingStore. Используется для объявления кольцевых буферов фиксированного размера
	    (в связи с ограничением на использование динамического выделения памяти).
	for example use HAL_RingStore <EMP_Max_Packet_Size> rb;
*/
template < u32 set_buffer_size >
class HAL_RingStore : public HAL_RingBuffer {
protected:
    static const unsigned limit = set_buffer_size+1;
	u8 sbuffer[set_buffer_size]; ///< буфер для хранения данных

public:
	HAL_RingStore(): HAL_RingBuffer(sbuffer, set_buffer_size, 0 ) { };
};

template <u32 set_buffer_size, typename T>
class EMP_RingRecords
    : public HAL_RingStore<set_buffer_size>
{
    public:
        typedef HAL_RingStore<set_buffer_size> inherited;
        bool Write(const T& ev){
            return inherited::WriteData((u8*)&ev, sizeof(T));
        };
};


#endif

