#include <f4ll/ringbuffer.h>

#include <cstring>

namespace f4ll {

ringbuffer_ext::ringbuffer_ext(uint8_t *bptr, size_type bsize)
    : m_buffer(bptr),
      m_bsize(bsize)
{
}

iringbuffer::size_type ringbuffer_ext::put(uint8_t const *data, size_type len)
{
    size_type chunk1 = 0;
    size_type chunk2 = 0;

    if (!data || !len) {
        return 0;
    }

    size_type max_len = unused();
    len = len < max_len ? len : max_len;

    chunk1 = m_bsize - m_head_shadow;
    if (chunk1 >= len) {
        chunk1 = len;
    } else {
        chunk2 = len - chunk1;
    }

    std::memcpy(m_buffer + m_head_shadow, data, chunk1);
    m_head_shadow += chunk1;
    if (m_head_shadow == m_bsize) {
        m_head_shadow = 0;
    }

    if (chunk2) {
        std::memcpy(m_buffer, data + chunk1, chunk2);
        m_head_shadow += chunk2;
        if (m_head_shadow == m_bsize) {
            m_head_shadow = 0;
        }
    }

    return len;
}

void ringbuffer_ext::commit()
{
    m_head = m_head_shadow;
}

bool ringbuffer_ext::get_chunk(uint8_t const *&data, size_type &len) const
{
    size_type head = m_head;
    size_type tail = m_tail;
    size_type chunk_size = head >= tail ? head - tail : m_bsize - tail;

    if (!chunk_size) {
        len = 0;
        return tail != head;
    }

    data = m_buffer + tail;
    len = chunk_size;

    tail += chunk_size;
    if (tail == m_bsize) {
        tail = 0;
    }

    return tail != head;
}

void ringbuffer_ext::consumed(size_type len)
{
    if (!len) {
        return;
    }
    m_tail += len;
    if (m_tail == m_bsize) {
        m_tail = 0;
    }
}

iringbuffer::size_type ringbuffer_ext::max_chunk_len() const
{
    if (m_tail <= m_head_shadow) {
        return m_bsize - m_head_shadow - (m_tail ? 0 : 1);
    } else {
        return m_tail - m_head_shadow - 1;
    }
}

uint8_t *ringbuffer_ext::get_free_chunk(size_type &len)
{
    len = max_chunk_len();

    return m_buffer + m_head_shadow;
}

bool ringbuffer_ext::produced(size_type len)
{
    size_type max_len = max_chunk_len();
    if (len > max_len) {
        return false;
    }
    m_head_shadow += len;
    if (m_head_shadow == m_bsize) {
        m_head_shadow = 0;
    }
    return true;
}

iringbuffer::size_type ringbuffer_ext::uncommited() const
{
    return marker_diff(m_head_shadow, m_head);
}

iringbuffer::size_type ringbuffer_ext::commited() const
{
    return marker_diff(m_head, m_tail);
}

void ringbuffer_ext::discard()
{
    m_head_shadow = m_head;
}

iringbuffer::size_type ringbuffer_ext::size() const
{
    return m_bsize;
}

iringbuffer::size_type ringbuffer_ext::used() const
{
    return marker_diff(m_head_shadow, m_tail);
}

iringbuffer::size_type ringbuffer_ext::unused() const
{
    return m_bsize - used() - 1;
}

iringbuffer::size_type ringbuffer_ext::marker_diff(size_type m1, size_type m2) const
{
    return (m1 >= m2) ? (m1 - m2) : (m_bsize - m2 + m1);
}

//
}
