#ifndef BUFFER_H
#define BUFFER_H

#include <list>
#include <memory>
#include "LineEndings.h"
#include "Encoding.h"
#include "GapBuffer.h"
#include <string>
#include "ChangeOperation.h"
#include <vector>
#include "EncodedString.h"

class Buffer
{
public:
    enum : uint32_t
    {
        INVALID_CHANGE_OPERATION_INDEX = 0xFFFFFFFFu,
    };

    struct Range;

    class Iterator
    {
    public:
        enum : uint32_t
        {
            INVALID_CODE_POINT = 0xFFFFFFFFu,
        };

        Iterator(const Buffer * buffer)
        {
            m_buffer = buffer;
            m_offset = 0u;
            m_line = 0u;
        }
        bool valid() const
        {
            return m_offset < m_buffer->size();
        }
        bool is_start_of_line();
        bool is_end_of_line(bool eol_only);
        bool go_back();
        bool go_forward();
        bool go_start_of_line();
        bool go_end_of_line(bool allow_eol);
        bool go_left_in_line();
        bool go_right_in_line(bool allow_eol);
        bool go_previous_line();
        bool go_next_line();
        Iterator prev();
        Iterator next();
        void warp(ssize_t offset_offset, ssize_t line_offset)
        {
            m_offset += offset_offset;
            m_line += line_offset;
        }
        void warp_to_offset(size_t offset);
        uint8_t * address() const
        {
            return m_buffer->address(m_offset);
        }
        uint32_t operator*() const
        {
            if (valid())
            {
                return Encoding::decode(m_buffer->encoding(), address());
            }
            else
            {
                return INVALID_CODE_POINT;
            }
        }
        bool operator==(const Iterator & other) const
        {
            return m_offset == other.m_offset;
        }
        bool operator!=(const Iterator & other) const
        {
            return m_offset != other.m_offset;
        }
        bool operator<(const Iterator & other) const
        {
            return m_offset < other.m_offset;
        }
        bool operator<=(const Iterator & other) const
        {
            return m_offset <= other.m_offset;
        }
        bool operator>(const Iterator & other) const
        {
            return m_offset > other.m_offset;
        }
        bool operator>=(const Iterator & other) const
        {
            return m_offset >= other.m_offset;
        }
        size_t line() const { return m_line; }
        size_t offset() const { return m_offset; }

    protected:
        friend struct Range;

        const Buffer * m_buffer;
        size_t m_offset;
        size_t m_line;
    };

    struct Range
    {
        std::shared_ptr<Iterator> start;
        std::shared_ptr<Iterator> end;

        Range(std::shared_ptr<Iterator> _start, std::shared_ptr<Iterator> _end)
            : start(_start), end(_end)
        {
        }

        bool valid() const
        {
            return start->valid() && (!end->valid() || (*start <= *end));
        }

        size_t start_offset() const
        {
            return start->offset();
        }

        size_t end_offset() const
        {
            if (end->valid())
            {
                return end->offset();
            }
            else
            {
                return start->m_buffer->size();
            }
        }

        size_t size() const
        {
            return end_offset() - start_offset();
        }
    };

    Buffer();
    Buffer(const char * filename);
    Buffer(const uint8_t * data, size_t data_length);
    bool write_to_file(const char * filename);

    std::shared_ptr<Iterator> add_iterator()
    {
        std::shared_ptr<Iterator> iterator = std::make_shared<Iterator>(this);
        m_iterators.push_back(iterator);
        return iterator;
    }
    std::shared_ptr<Iterator> add_cursor()
    {
        auto iterator = add_iterator();
        m_cursors.push_back(iterator);
        return iterator;
    }
    void clear();
    EncodedString get_string();
    size_t size() const { return m_gap_buffer->size(); }
    uint8_t * address(size_t offset) const { return m_gap_buffer->address(offset); }
    Encoding::Type encoding() const { return m_encoding; }
    uint8_t tabstop() const { return m_tabstop; }
    void enter_insert_mode();
    void exit_insert_mode();
    bool insert_mode() const { return m_insert_mode; }
    void insert_code_point(const Buffer::Iterator & position, uint32_t code_point);
    void erase_code_point(const Buffer::Iterator & position);
    void erase_range(const Buffer::Range & range);
    std::shared_ptr<std::string> filename() const { return m_filename; }
    Iterator begin() const { return Iterator(this); }
    std::shared_ptr<Iterator> beginp() const { return std::make_shared<Iterator>(this); }
    Iterator end() const { return *m_eof_iterator; }
    std::shared_ptr<Iterator> endp() const { return std::make_shared<Iterator>(*m_eof_iterator); }
    void push_operation() { m_operation_level++; }
    void pop_operation();
    void undo();
    void redo();

#ifndef ENABLE_TESTING
protected:
#endif
    bool m_eol_at_eof;
    LineEndings::Type m_line_endings;
    std::shared_ptr<GapBuffer> m_gap_buffer;
    std::shared_ptr<GapBuffer> m_change_buffer;
    Encoding::Type m_encoding;
    uint8_t m_tabstop;
    bool m_insert_mode;
    std::list<std::shared_ptr<Iterator>> m_iterators;
    std::list<std::shared_ptr<Iterator>> m_cursors;
    std::shared_ptr<Iterator> m_eof_iterator;
    std::shared_ptr<std::string> m_filename;
    std::shared_ptr<ChangeOperation> m_current_change_operation;
    int m_operation_level;
    size_t m_current_change_operation_index;
    std::vector<std::shared_ptr<ChangeOperation>> m_change_operations;

    void common_pre_load_initialization();
    void common_post_load_initialization();
    bool load_from_file(const char * filename);
    void load_empty_buffer();
    bool load_from_memory(const uint8_t * data, size_t data_length);
    void load_text_in_buffer(uint8_t * buffer, size_t buffer_size, size_t data_length);
    void record_change(size_t offset, size_t length, bool insert);
    void save_current_operation();
    void warp_iterators_after_insert(size_t offset, size_t length);
    void warp_iterators_before_delete(size_t offset, size_t length);
    void insert_data(size_t offset, const uint8_t data[], size_t length, bool do_record_change);
    void erase_data(size_t offset, size_t length, bool do_record_change);
    size_t lines_in_data(size_t offset, size_t length);
    void post_warp_cursors();
    void apply_change_operation(const ChangeOperation & change_operation, bool forward);
    void apply_change_unit(const ChangeUnit & change_unit, bool forward);
};

#endif
