#ifndef GAPBUFFER_H
#define GAPBUFFER_H

#include <stdint.h>
#include <stdlib.h>
#ifdef ENABLE_TESTING
#include <string>
#endif

class GapBuffer
{
public:
    /**
     * Create a new GapBuffer object.
     *
     * The GapBuffer will allocate its own memory.
     */
    GapBuffer();

    /**
     * Create a GapBuffer object that points to preallocated memory.
     *
     * The GapBuffer object will take over ownership of the allocated memory.
     *
     * @param buffer
     *   Pointer to buffer allocated by System::alloc_pages().
     * @param buffer_size
     *   Size of the buffer allocated by System::alloc_pages().
     * @param size
     *   Size of the data in the buffer.
     */
    GapBuffer(uint8_t * buffer, size_t buffer_size, size_t size);

    /**
     * Destroy the GapBuffer object and free its memory.
     */
    ~GapBuffer();

    /**
     * Insert data into the gap buffer at the given position.
     *
     * @param position
     *   Position in the gap buffer to insert the code point at.
     * @param data
     *   Pointer to the data to insert.
     * @param length
     *   Length of the data to insert.
     */
    void insert(size_t position, const uint8_t * data, size_t length);

    /**
     * Erase data from the gap buffer.
     *
     * @param position
     *   Position in the gap buffer to erase data from.
     * @param length
     *   Length of the data to erase.
     */
    void erase(size_t position, size_t length);

    /**
     * Erase all data from the gap buffer.
     */
    void clear();

    /**
     * Get the size of the data stored within the buffer.
     */
    size_t size() const
    {
        return m_size;
    }

    /**
     * Get the memory address of a buffer location.
     *
     * @param offset
     *   The index into the gap buffer data.
     */
    uint8_t * address(size_t offset) const
    {
        if (offset < m_gap_position)
        {
            return &m_buffer[offset];
        }
        else
        {
            return &m_buffer[offset + gap_size()];
        }
    }

    /**
     * Move buffer gap to the end of the buffer so the contents are congruent
     * at the beginning of the buffer.
     */
    void compact()
    {
        move_gap(m_size);
    }

    /**
     * Copy a range of data from this GapBuffer to another.
     *
     * @param source_position
     *   Position in the gap buffer containing the data to copy.
     * @param length
     *   Length of the data to copy.
     * @param other
     *   Destination GapBuffer to copy the data into.
     * @param destination_position
     *   Position in the destination GapBuffer to copy the data to.
     */
    void copy_to(size_t source_position, size_t length, GapBuffer & other, size_t destination_position);

#ifdef ENABLE_TESTING
    /**
     * Get the contents of the gap buffer as a string.
     *
     * This makes a copy of the entire buffer in a std::string. This method is
     * intended for use by unit tests.
     */
    std::string get_string();

    /**
     * Replace the buffer contents with a string.
     */
    void set_string(const std::string & s);
#endif

protected:
    /** Address of the allocated memory buffer. */
    uint8_t * m_buffer;

    /** Size of the allocated memory buffer. */
    size_t m_buffer_size;

    /** Size of the data stored in the buffer. */
    size_t m_size;

    /** Index of the gap in the memory buffer. */
    size_t m_gap_position;

    /**
     * Get the gap size.
     */
    size_t gap_size() const
    {
        return m_buffer_size - m_size;
    }

    /**
     * Verify that there is enough free space in the gap, and if not, grow the
     * gap and move data.
     *
     * @param length
     *   The number of bytes that must be free.
     */
    void ensure_free(size_t length);

    /**
     * Move the gap buffer gap position to the requested position.
     *
     * @param position
     *   New gap position. Must be <= m_size.
     */
    void move_gap(size_t position);
};

#endif
