#include "Buffer.h"
#include <stdio.h>
#include "System.h"
#include "File.h"
#include "TextLoader.h"
#include <cassert>
#include <string.h>

Buffer::Buffer()
{
    common_pre_load_initialization();
    load_empty_buffer();
    common_post_load_initialization();
}

Buffer::Buffer(const char * filename)
{
    common_pre_load_initialization();
    if (!load_from_file(filename))
    {
        load_empty_buffer();
    }
    common_post_load_initialization();
}

Buffer::Buffer(const uint8_t * data, size_t data_length)
{
    common_pre_load_initialization();
    if (!load_from_memory(data, data_length))
    {
        load_empty_buffer();
    }
    common_post_load_initialization();
}

void Buffer::common_pre_load_initialization()
{
    m_tabstop = 4u;
    m_change_buffer = std::make_shared<GapBuffer>();
    m_operation_level = 0;
    m_current_change_operation_index = INVALID_CHANGE_OPERATION_INDEX;
    m_encoding = Encoding::UTF_8;
}

void Buffer::common_post_load_initialization()
{
    m_eof_iterator = add_iterator();
    while (m_eof_iterator->valid())
    {
        m_eof_iterator->go_forward();
    }
}

void Buffer::load_empty_buffer()
{
    m_gap_buffer = std::make_shared<GapBuffer>();
    m_eol_at_eof = true;
    m_line_endings = LineEndings::LF;
}

bool Buffer::load_from_file(const char * filename)
{
    File file;
    if (!file.open(filename))
    {
        return false;
    }

    size_t file_size = file.get_size();
    if (file_size < 0)
    {
        return false;
    }

    size_t buffer_size = ((unsigned long)file_size + System::page_size + (64u * 1024u)) & System::page_base_mask;
    uint8_t * buffer = (uint8_t *)System::alloc_pages(buffer_size >> System::page_size_log);
    if (buffer == NULL)
    {
        return false;
    }

    if (!file.read(buffer, file_size))
    {
        System::free_pages(buffer, buffer_size >> System::page_size_log);
        return false;
    }

    load_text_in_buffer(buffer, buffer_size, file_size);
    m_filename = std::make_shared<std::string>(filename);

    return true;
}

bool Buffer::load_from_memory(const uint8_t * data, size_t data_length)
{
    size_t buffer_size = ((unsigned long)data_length + 2u * System::page_size) & System::page_base_mask;
    uint8_t * buffer = (uint8_t *)System::alloc_pages(buffer_size >> System::page_size_log);
    if (buffer == NULL)
    {
        return false;
    }

    memcpy(buffer, data, data_length);

    load_text_in_buffer(buffer, buffer_size, data_length);

    return true;
}

void Buffer::load_text_in_buffer(uint8_t * buffer, size_t buffer_size, size_t data_length)
{
    TextLoader text_loader;
    size_t loaded_size;
    text_loader.load_buffer(buffer, data_length, &loaded_size);

    m_gap_buffer = std::make_shared<GapBuffer>(buffer, buffer_size, loaded_size);
    m_encoding = text_loader.get_encoding();
    m_eol_at_eof = text_loader.get_eol_at_eof();
    m_line_endings = text_loader.get_line_endings();
}

bool Buffer::write_to_file(const char * filename)
{
    File file;
    if (!file.open(filename, true))
    {
        return false;
    }

    m_gap_buffer->compact();

    Iterator start_of_line(this);
    size_t bytes_written = 0u;

    while (start_of_line.valid())
    {
        Iterator iterator = start_of_line;
        iterator.go_end_of_line(true);
        size_t len = iterator.address() - start_of_line.address();
        if (len > 0u)
        {
            if (!file.write(start_of_line.address(), len))
            {
                return false;
            }
            bytes_written += len;
        }
        bool moved_down = iterator.go_next_line();
        if (moved_down || m_eol_at_eof)
        {
            if (!file.write(LineEndings::spans[m_line_endings]))
            {
                return false;
            }
            bytes_written += LineEndings::spans[m_line_endings].length;
        }
        if (!moved_down)
        {
            break;
        }
        start_of_line = iterator;
    }

    return true;
}

void Buffer::enter_insert_mode()
{
    m_insert_mode = true;
    push_operation();
}

void Buffer::exit_insert_mode()
{
    m_insert_mode = false;
    pop_operation();
}

void Buffer::insert_code_point(const Buffer::Iterator & position, uint32_t code_point)
{
    push_operation();
    uint8_t encoded[Encoding::MAX_CODE_POINT_SIZE * 2];
    uint8_t bytes = Encoding::encode(code_point, m_encoding, encoded);
    if ((size() == 0u) && (code_point != '\n'))
    {
        bytes += Encoding::encode('\n', m_encoding, &encoded[bytes]);
    }
    insert_data(position.offset(), encoded, bytes, true);
    pop_operation();
}

void Buffer::insert_data(size_t offset, const uint8_t data[], size_t length, bool do_record_change)
{
    m_gap_buffer->insert(offset, data, length);
    if (do_record_change)
    {
        record_change(offset, length, true);
    }
    warp_iterators_after_insert(offset, length);
    post_warp_cursors();
}

void Buffer::erase_code_point(const Buffer::Iterator & position)
{
    if (position.valid())
    {
        push_operation();
        erase_data(position.offset(),
                   Encoding::num_bytes_in_code_point(m_encoding, position.address()),
                   true);
        pop_operation();
    }
}

void Buffer::erase_range(const Buffer::Range & range)
{
    if (range.valid() && (range.size() > 0u))
    {
        push_operation();
        erase_data(range.start_offset(), range.size(), true);
        pop_operation();
    }
}

void Buffer::erase_data(size_t offset, size_t length, bool do_record_change)
{
    warp_iterators_before_delete(offset, length);
    if (do_record_change)
    {
        record_change(offset, length, false);
    }
    m_gap_buffer->erase(offset, length);
    post_warp_cursors();
}

size_t Buffer::lines_in_data(size_t offset, size_t length)
{
    size_t lines = 0u;
    for (size_t i = 0u; i < length; )
    {
        uint8_t bytes;
        uint32_t c = Encoding::decode(m_encoding, m_gap_buffer->address(offset + i), &bytes);
        if (c == '\n')
        {
            lines++;
        }
        i += bytes;
    }
    return lines;
}

void Buffer::post_warp_cursors()
{
    if (size() > 0u)
    {
        for (auto cursor : m_cursors)
        {
            if (*cursor == *m_eof_iterator)
            {
                cursor->go_back();
            }
            if ((!insert_mode()) && (**cursor == '\n'))
            {
                cursor->go_left_in_line();
            }
        }
    }
}

void Buffer::warp_iterators_after_insert(size_t offset, size_t length)
{
    size_t lines = lines_in_data(offset, length);
    for (auto iterator : m_iterators)
    {
        if (iterator->offset() >= offset)
        {
            iterator->warp(length, lines);
        }
    }
}

void Buffer::warp_iterators_before_delete(size_t offset, size_t length)
{
    size_t lines = lines_in_data(offset, length);
    for (auto iterator : m_iterators)
    {
        /* Move any iterators within the chunk to be deleted to after it. */
        while ((iterator->offset() >= offset) &&
                (iterator->offset() < (offset + length)))
        {
            iterator->go_forward();
            if (!iterator->valid())
            {
                break;
            }
        }
        if (iterator->offset() >= offset)
        {
            iterator->warp(-(ssize_t)length, -(ssize_t)lines);
        }
    }
}

void Buffer::pop_operation()
{
    if (m_operation_level > 0)
    {
        m_operation_level--;
        if (m_operation_level == 0)
        {
            save_current_operation();
        }
    }
}

void Buffer::record_change(size_t offset, size_t length, bool insert)
{
    if (!m_current_change_operation)
    {
        m_current_change_operation = std::make_shared<ChangeOperation>();
        m_current_change_operation->parent = m_current_change_operation_index;
    }
    if (m_current_change_operation->changes.size() > 0)
    {
        /* There is a previous change unit in progress. See if it can be
         * modified to encompass the new change. */
        ChangeUnit & cu = *m_current_change_operation->changes.rbegin();
        if (insert && cu.insert &&
                ((offset + length) >= cu.buffer_position) &&
                (offset <= (cu.buffer_position + cu.length)))
        {
            /* The new insertion can extend the previous insertion change unit. */
            size_t change_buffer_offset = cu.change_buffer_offset + offset - cu.buffer_position;
            m_gap_buffer->copy_to(offset, length, *m_change_buffer, change_buffer_offset);
            cu.length += length;
            cu.change_buffer_offset = std::min(cu.change_buffer_offset, change_buffer_offset);
            cu.buffer_position = std::min(cu.buffer_position, offset);
            return;
        }
        else if ((!insert) && (!cu.insert) &&
                 ((offset == cu.buffer_position) ||
                  ((offset + length) == cu.buffer_position)))
        {
            /* The new deletion can extend the previous deletion change unit. */
            if (offset == cu.buffer_position)
            {
                /* The new deletion immediately follows the previous. */
                m_gap_buffer->copy_to(offset, length, *m_change_buffer, cu.change_buffer_offset + cu.length);
            }
            else
            {
                /* The new deletion immediately precedes the previous. */
                m_gap_buffer->copy_to(offset, length, *m_change_buffer, cu.change_buffer_offset);
                cu.buffer_position = offset;
            }
            cu.length += length;
            return;
        }
        else if ((!insert) && cu.insert &&
                 (offset >= cu.buffer_position) &&
                 ((offset + length) <= (cu.buffer_position + cu.length)))
        {
            /* The deletion is removing from the previous insertion change unit. */
            m_change_buffer->erase(cu.change_buffer_offset + (offset - cu.buffer_position), length);
            if (cu.length <= length)
            {
                m_current_change_operation->changes.erase(--m_current_change_operation->changes.end());
            }
            else
            {
                cu.length -= length;
            }
            return;
        }
    }
    /* Start a new change unit. */
    size_t change_buffer_offset = m_change_buffer->size();
    m_gap_buffer->copy_to(offset, length, *m_change_buffer, change_buffer_offset);
    ChangeUnit cu;
    cu.change_buffer_offset = change_buffer_offset;
    cu.length = length;
    cu.buffer_position = offset;
    cu.insert = insert;
    m_current_change_operation->changes.push_back(cu);
}

void Buffer::save_current_operation()
{
    if (m_current_change_operation)
    {
        if (m_current_change_operation->changes.size() > 0u)
        {
            size_t new_change_operation_index = m_change_operations.size();
            if (m_current_change_operation_index < new_change_operation_index)
            {
                m_change_operations[m_current_change_operation_index]->children.push_back(new_change_operation_index);
            }
            m_change_operations.push_back(m_current_change_operation);
            m_current_change_operation_index = new_change_operation_index;
        }
        m_current_change_operation = nullptr;
    }
}

void Buffer::apply_change_operation(const ChangeOperation & change_operation, bool forward)
{
    if (forward)
    {
        for (auto change_unit : change_operation.changes)
        {
            apply_change_unit(change_unit, forward);
        }
    }
    else
    {
        for (auto i = change_operation.changes.rbegin();
             i != change_operation.changes.rend();
             i++)
        {
            apply_change_unit(*i, forward);
        }
    }
}

void Buffer::apply_change_unit(const ChangeUnit & change_unit, bool forward)
{
    if (forward == change_unit.insert)
    {
        m_change_buffer->compact();
        insert_data(change_unit.buffer_position,
                m_change_buffer->address(change_unit.change_buffer_offset),
                change_unit.length,
                false);
    }
    else
    {
        erase_data(change_unit.buffer_position,
                   change_unit.length,
                   false);
    }
}

void Buffer::undo()
{
    if (m_current_change_operation_index != INVALID_CHANGE_OPERATION_INDEX)
    {
        apply_change_operation(*m_change_operations[m_current_change_operation_index], false);
        m_current_change_operation_index = m_change_operations[m_current_change_operation_index]->parent;
    }
}

void Buffer::redo()
{
    size_t child_index = INVALID_CHANGE_OPERATION_INDEX;
    if (m_current_change_operation_index == INVALID_CHANGE_OPERATION_INDEX)
    {
        if (m_change_operations.size() > 0u)
        {
            child_index = 0u;
        }
    }
    else if (m_change_operations[m_current_change_operation_index]->children.size() > 0u)
    {
        /* TODO: support redoing a child of the undo tree other than the first */
        child_index = *m_change_operations[m_current_change_operation_index]->children.begin();
    }

    if (child_index != INVALID_CHANGE_OPERATION_INDEX)
    {
        apply_change_operation(*m_change_operations[child_index], true);
        m_current_change_operation_index = child_index;
    }
}

void Buffer::clear()
{
    m_gap_buffer->clear();
    for (auto iterator : m_iterators)
    {
        auto b = begin();
        *iterator = b;
    }
}

EncodedString Buffer::get_string()
{
    m_gap_buffer->compact();
    return EncodedString(m_gap_buffer->address(0u), m_gap_buffer->size(), m_encoding);
}
