#ifndef BUFFERVIEW_H
#define BUFFERVIEW_H

#include "Buffer.h"
#include "CharacterWidthDeterminer.h"
#include <functional>
#include "BufferLineWalker.h"
#include <vector>

/**
 * Tracks a "view" of a buffer, which is a two-dimensional grid of characters
 * that displays a section of a buffer's contents.
 */
class BufferView
{
public:
    enum class CursorMovement : uint8_t
    {
        LEFT,
        RIGHT,
        UP,
        DOWN,
        SOL,
        EOL,
        FIRST_LINE,
        LAST_LINE,
        SCREEN_ROW_UP,
        SCREEN_ROW_DOWN,
        FORWARD_UP_TO_CHAR,
        FORWARD_ON_TO_CHAR,
        BACK_UP_TO_CHAR,
        BACK_ON_TO_CHAR,
    };

    class Iterator
    {
    public:
        Iterator(BufferView & bv) : m_buffer_view(bv)
        {
            m_index = 0u;
        }
        void operator++()
        {
            (*this)++;
        }
        void operator++(int unused)
        {
            m_index++;
        }
        bool is_valid()
        {
            return m_index < m_buffer_view.m_lines.size();
        }
        int row_offset() const
        {
            return m_buffer_view.m_lines[m_index].row_offset;
        }
        int n_rows() const
        {
            return m_buffer_view.m_lines[m_index].n_rows;
        }
        std::shared_ptr<Buffer::Iterator> iterator() const
        {
            return m_buffer_view.m_lines[m_index].line;
        }

    protected:
        BufferView & m_buffer_view;
        size_t m_index;
    };

    BufferView(std::shared_ptr<Buffer> buffer,
            std::shared_ptr<Buffer::Iterator> iterator,
            CharacterWidthDeterminer & character_width_determiner);
    void resize(int width, int height);
    void set_scroll_offset(int scroll_offset);
    void update();
    Iterator vert_iter();
    BufferLineWalker horiz_iter(std::shared_ptr<Buffer::Iterator> start_of_line)
    {
        return BufferLineWalker(m_buffer, start_of_line, m_width, m_character_width_determiner);
    }
    bool cursor_move(CursorMovement which, uint32_t c, bool allow_eol);
    int cursor_screen_row() const { return m_cursor_screen_row; }
    int cursor_screen_column() const { return m_cursor_screen_column; }
    int cursor_virtual_column() const { return m_cursor_virtual_column; }
    void set_cursor_screen_row(int cursor_screen_row)
    {
        m_cursor_screen_row = cursor_screen_row;
    }
    void scroll_view_up(int n_lines, bool allow_eol);
    void scroll_view_down(int n_lines, bool allow_eol);

protected:
    struct LineDescriptor
    {
        int row_offset;
        int n_rows;
        std::shared_ptr<Buffer::Iterator> line;
    };

    std::shared_ptr<Buffer> m_buffer;
    std::shared_ptr<Buffer::Iterator> m_iterator;
    std::vector<LineDescriptor> m_lines;
    CharacterWidthDeterminer & m_character_width_determiner;
    int m_width;
    int m_height;
    int m_scroll_offset;
    int m_cursor_screen_row;
    int m_cursor_screen_column;
    int m_cursor_virtual_column;
    int m_cursor_row_offset;
    int m_rows_in_cursor_line;
    int m_target_screen_column;
    int m_target_virtual_column;
    bool m_update_target_column;

    int effective_scroll_offset()
    {
        return std::min(m_scroll_offset, (m_height - 1) / 2);
    }
    void determine_new_cursor_screen_row();
    int calculate_rows_in_line(std::shared_ptr<Buffer::Iterator> start_of_line);
    int calculate_rows_in_cursor_line(std::shared_ptr<Buffer::Iterator> start_of_line);
    int screen_rows_below_line(std::shared_ptr<Buffer::Iterator> line);
    int screen_rows_above_line(
            std::shared_ptr<Buffer::Iterator> line,
            std::list<std::pair<int, std::shared_ptr<Buffer::Iterator>>> & backward_lines);
    void move_forward_to_column(int to_column, bool allow_eol);
    bool move_cursor_screen_row_up(bool allow_eol);
    bool move_cursor_screen_row_down(bool allow_eol);
    void move_forward_to_screen_position(
            std::shared_ptr<Buffer::Iterator> line, int target_row_offset,
            bool allow_eol);
    bool move_cursor_forward_up_to_char(uint32_t c);
    bool move_cursor_forward_on_to_char(uint32_t c);
    bool move_cursor_backward_up_to_char(uint32_t c);
    bool move_cursor_backward_on_to_char(uint32_t c);
    int calculate_rows_above_screen(int stop_at);
    int calculate_rows_below_screen(int stop_at);
};

#endif
