#include "GapBuffer.h"
#include "System.h"
#include <string.h>

GapBuffer::GapBuffer()
{
    int num_pages = (64u * 1024u + System::page_size - 1u) >> System::page_size_log;
    m_buffer = (uint8_t *)System::alloc_pages(num_pages);
    m_buffer_size = System::page_size;
    m_size = 0u;
    m_gap_position = 0u;
}

GapBuffer::GapBuffer(uint8_t * buffer, size_t buffer_size, size_t size)
{
    m_buffer = buffer;
    m_buffer_size = buffer_size;
    m_size = size;
    m_gap_position = size;
}

GapBuffer::~GapBuffer()
{
    System::free_pages(m_buffer, m_buffer_size >> System::page_size_log);
}

void GapBuffer::insert(size_t position, const uint8_t * data, size_t length)
{
    ensure_free(length);
    move_gap(position);
    memcpy(&m_buffer[m_gap_position], data, length);
    m_gap_position += length;
    m_size += length;
}

void GapBuffer::erase(size_t position, size_t length)
{
    if ((position < m_size) && ((position + length) <= m_size))
    {
        if ((position + length) == m_gap_position)
        {
            m_gap_position -= length;
            m_size -= length;
        }
        else
        {
            move_gap(position);
            m_size -= length;
        }
    }
}

void GapBuffer::clear()
{
    m_size = 0u;
    m_gap_position = 0u;
}

#ifdef ENABLE_TESTING
std::string GapBuffer::get_string()
{
    compact();
    return std::string((char *)m_buffer, m_size);
}

void GapBuffer::set_string(const std::string & s)
{
    clear();
    insert(0u, (const uint8_t *)s.c_str(), s.size());
}
#endif

void GapBuffer::ensure_free(size_t length)
{
    if (gap_size() < length)
    {
        /* We're out of space. Allocate more and move data. */
        size_t new_size = (m_buffer_size + (128u * 1024u)) & System::page_base_mask;
        size_t new_num_pages = new_size >> System::page_size_log;
        uint8_t * new_buffer = (uint8_t *)System::alloc_pages(new_num_pages);
        memcpy(new_buffer, m_buffer, m_gap_position);
        memcpy(&new_buffer[m_gap_position], &m_buffer[m_gap_position + gap_size()], m_size - m_gap_position);

        /* Free the old buffer */
        System::free_pages(m_buffer, m_buffer_size >> System::page_size_log);

        m_buffer = new_buffer;
        m_buffer_size = new_size;
        m_gap_position = m_size;
    }
}

void GapBuffer::move_gap(size_t position)
{
    if (position != m_gap_position)
    {
        if (position < m_gap_position)
        {
            memmove(&m_buffer[position + gap_size()], &m_buffer[position], m_gap_position - position);
        }
        else
        {
            memmove(&m_buffer[m_gap_position], &m_buffer[m_gap_position + gap_size()], position - m_gap_position);
        }
        m_gap_position = position;
    }
}

void GapBuffer::copy_to(size_t source_position, size_t length, GapBuffer & other, size_t destination_position)
{
    if ((source_position < m_size) &&
            (length > 0u) &&
            ((source_position + length) <= m_size))
    {
        if ((m_gap_position <= source_position) ||
                (m_gap_position >= (source_position + length)))
        {
            /* The gap is before or after the range of data to copy. */
            other.insert(destination_position, address(source_position), length);
        }
        else
        {
            /* The gap is in the middle of the range of data to copy. */
            size_t pre_gap_size = m_gap_position - source_position;
            other.insert(destination_position, address(source_position), pre_gap_size);
            other.insert(destination_position + pre_gap_size, address(source_position + pre_gap_size), length - pre_gap_size);
        }
    }
}
