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

GapBuffer::GapBuffer(Encoding::Type encoding)
{
    m_buffer = (uint8_t *)System::alloc_pages(1u);
    m_buffer_size = System::page_size;
    m_size = 0u;
    m_gap_position = 0u;
    m_encoding = encoding;
    tabstop = 4u;
}

GapBuffer::GapBuffer(uint8_t * buffer, size_t buffer_size, size_t size, Encoding::Type encoding)
{
    m_buffer = buffer;
    m_buffer_size = buffer_size;
    m_size = size;
    m_gap_position = size;
    m_encoding = encoding;
    tabstop = 4u;
}

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

std::shared_ptr<GapBuffer::Cursor> GapBuffer::add_cursor()
{
    std::shared_ptr<Cursor> cursor = std::make_shared<Cursor>(this);
    m_cursors.push_back(cursor);
    return cursor;
}

void GapBuffer::compact()
{
    if (m_gap_position < m_size)
    {
        memmove(&m_buffer[m_gap_position], &m_buffer[m_gap_position + gap_size()], m_size - m_gap_position);
        m_gap_position = m_size;
    }
}


void GapBuffer::Iterator::go_forward()
{
    if (valid())
    {
        m_offset += Encoding::num_bytes_in_code_point(m_gap_buffer->m_encoding, address());
    }
}

bool GapBuffer::Iterator::check_go_forward()
{
    if (valid())
    {
        Iterator i2 = *this;
        i2.go_forward();
        if (i2.valid())
        {
            m_offset = i2.m_offset;
            return true;
        }
    }
    return false;
}

void GapBuffer::Iterator::go_back()
{
    if (valid())
    {
        if (m_offset == 0u)
        {
            m_offset = 0xFFFFFFFFu;
        }
        else
        {
            const uint8_t * a = m_gap_buffer->address(m_offset - 1u);
            const uint8_t * beginning_of_code_point = Encoding::beginning_of_code_point(m_gap_buffer->m_encoding, a);
            m_offset -= ((a - beginning_of_code_point) + 1u);
        }
    }
}

bool GapBuffer::Iterator::check_go_back()
{
    if (valid())
    {
        Iterator i2 = *this;
        i2.go_back();
        if (i2.valid())
        {
            m_offset = i2.m_offset;
            return true;
        }
    }
    return false;
}


bool GapBuffer::Cursor::is_start_of_line()
{
    Iterator i2 = m_iterator;
    if (!i2.check_go_back())
    {
        /* Iterator cannot go backwards so it is at beginning of buffer. */
        return true;
    }
    return *i2 == '\n';
}

bool GapBuffer::Cursor::is_end_of_line(bool allow_eol)
{
    if (*m_iterator == '\n')
    {
        return true;
    }
    if (!allow_eol)
    {
        Iterator i2 = m_iterator;
        i2.go_forward();
        if (*i2 == '\n')
        {
            return true;
        }
    }
    return false;
}

bool GapBuffer::Cursor::go_start_of_line()
{
    bool moved = false;
    Iterator i2 = m_iterator;
    for (;;)
    {
        i2.go_back();
        if (i2.valid() && (*i2 != '\n'))
        {
            moved = true;
            m_iterator = i2;
        }
        else
        {
            break;
        }
    }
    if (moved)
    {
        init_column();
    }
    return moved;
}

bool GapBuffer::Cursor::go_end_of_line(bool allow_eol)
{
    bool moved = false;
    if (go_right(allow_eol))
    {
        moved = true;
    }
    while (go_right(allow_eol))
    {
        ;
    }
    return moved;
}

bool GapBuffer::Cursor::go_left()
{
    if (!is_start_of_line())
    {
        uint32_t chr = *m_iterator;
        if (m_iterator.check_go_back())
        {
            if (chr == '\t')
            {
                calculate_column();
            }
            else
            {
                /* TODO: handle multi-column characters */
                m_column--;
            }
            return true;
        }
    }
    return false;
}

bool GapBuffer::Cursor::go_right(bool allow_eol)
{
    if (!is_end_of_line(allow_eol))
    {
        if (m_iterator.check_go_forward())
        {
            uint32_t chr = *m_iterator;
            if (chr == '\t')
            {
                uint8_t tabstop = m_iterator.gap_buffer()->tabstop;
                m_column += tabstop - (m_column + 1u) % tabstop;
            }
            else
            {
                /* TODO: handle multi-column characters */
                m_column++;
            }
            return true;
        }
    }
    return false;
}

bool GapBuffer::Cursor::go_up(size_t target_column)
{
    Cursor c2 = *this;
    c2.go_start_of_line();
    if (!c2.m_iterator.check_go_back())
    {
        return false;
    }
    c2.go_start_of_line();
    *this = c2;
    m_line--;
    m_column = 0u;
    forward_to_column(target_column);
    return true;
}

bool GapBuffer::Cursor::go_down(size_t target_column)
{
    Cursor c2 = *this;
    c2.go_end_of_line(true);
    if (!c2.m_iterator.check_go_forward())
    {
        return false;
    }
    *this = c2;
    m_line++;
    m_column = 0u;
    forward_to_column(target_column);
    return true;
}

void GapBuffer::Cursor::init_column()
{
    if (*m_iterator == '\t')
    {
        m_column = m_iterator.gap_buffer()->tabstop - 1u;
    }
    else
    {
        m_column = 0u;
    }
}

void GapBuffer::Cursor::calculate_column()
{
    Cursor tmp_cursor = *this;
    tmp_cursor.go_start_of_line();
    while (tmp_cursor < *this)
    {
        tmp_cursor.go_right(true);
    }
    m_column = tmp_cursor.m_column;
}

void GapBuffer::Cursor::forward_to_column(size_t target_column)
{
    int32_t diff = abs((int32_t)(target_column - m_column));
    if (diff == 0)
        return;
    while (go_right(false))
    {
        int32_t new_diff = abs((int32_t)(target_column - m_column));
        if (new_diff > diff)
        {
            go_left();
            return;
        }
        diff = new_diff;
    }
}
