#include "BufferPane.h"
#include <cmath>

int BufferPane::Cwd::operator()(uint32_t character)
{
    if (character < 0x20u)
    {
        return 2;
    }
    else if (character < 0x80u)
    {
        return 1;
    }
    else
    {
        return std::max(1, m_window->font()->get_glyph(character)->get_advance() / m_window->font()->get_advance());
    }
}

BufferPane::BufferPane(Window * window, std::shared_ptr<Buffer> buffer)
    : m_window(window), m_buffer(buffer), m_cwd(window)
{
    m_iterator = buffer->add_cursor();
    m_buffer_view = std::make_shared<BufferView>(buffer, m_iterator, m_cwd);
    m_buffer_view->set_scroll_offset(5);
    m_buffer_view->update();
    m_show_status_bar = true;
    m_show_line_number_gutter = true;
    m_command_mode = false;
    m_focused = false;
    m_rows = 0;
    m_columns = 0;
}

void BufferPane::resize(int width, int height)
{
    Pane::resize(width, height);
    resize_buffer_view();
}

void BufferPane::resize_buffer_view()
{
    if (m_show_line_number_gutter)
    {
        int max_line_possible = 1;
        if (m_iterator->valid())
        {
            max_line_possible = std::min(m_iterator->line() + m_rows - m_buffer_view->cursor_screen_row(), m_buffer->end().line());
        }
        m_line_number_gutter_width = log10(max_line_possible) + 1;
    }
    else
    {
        m_line_number_gutter_width = 0;
    }
    int columns = std::max(1, (m_width - col_x(0)) / m_window->font()->get_advance());
    int height_subtract = 0;
    if (m_show_status_bar)
    {
        height_subtract = m_window->font()->get_line_height() + 1;
    }
    int rows = std::max(1, (m_height - height_subtract) / m_window->font()->get_line_height());
    if ((rows != m_rows) || (columns != m_columns))
    {
        m_rows = rows;
        m_columns = columns;
        m_buffer_view->resize(m_columns, m_rows);
        m_buffer_view->update();
    }
}

void BufferPane::draw()
{
    resize_buffer_view();
    if (m_iterator->valid())
    {
        for (auto line_iterator = m_buffer_view->vert_iter();
                line_iterator.is_valid();
                line_iterator++)
        {
            draw_buffer_line(line_iterator.row_offset(), line_iterator.iterator());
        }
    }
    else
    {
        draw_cursor(col_x(0), row_y(0), 0, 1);
    }
    if (m_show_line_number_gutter)
    {
        m_window->gl()->draw_rect(win_x(m_line_number_gutter_width * m_window->font()->get_advance() + 1), win_y(0), 1, m_height, 0.5, 0.5, 0.5, 1.0);
    }
    if (m_show_status_bar)
    {
        draw_status_bar();
    }
}

void BufferPane::scroll_window_up(Window::ScrollMode scroll_mode)
{
    int lines_to_scroll = calculate_lines_to_scroll(scroll_mode);
    m_buffer_view->scroll_view_up(lines_to_scroll, insert_mode());
    m_buffer_view->update();
    m_window->request_redraw();
}

void BufferPane::scroll_window_down(Window::ScrollMode scroll_mode)
{
    int lines_to_scroll = calculate_lines_to_scroll(scroll_mode);
    m_buffer_view->scroll_view_down(lines_to_scroll, insert_mode());
    m_buffer_view->update();
    m_window->request_redraw();
}


void BufferPane::draw_buffer_line(int screen_row, std::shared_ptr<Buffer::Iterator> start_of_line)
{
    if (m_show_line_number_gutter)
    {
        draw_line_number(screen_row, start_of_line->line());
    }
    int last_drawn_crosshair_row = -1;
    for (auto it = m_buffer_view->horiz_iter(start_of_line);
            it.is_valid();
            it++)
    {
        int draw_row = screen_row + it.row_offset();
        int cwidth = std::max(it.character_width(), 1);
        if ((draw_row < 0) || (draw_row > m_rows))
            continue;
        if (it.iterator()->line() == m_iterator->line())
        {
            if ((draw_row > last_drawn_crosshair_row) &&
                    ((!it.is_eol()) || (it.row_offset() == 0)))
            {
                draw_crosshair(col_x(0), row_y(draw_row), col_x(m_columns));
                last_drawn_crosshair_row = draw_row;
            }
        }
        else if ((m_buffer_view->cursor_virtual_column() >= it.virtual_column()) &&
                (m_buffer_view->cursor_virtual_column() < (it.virtual_column() + cwidth)))
        {
            int col = it.screen_column() + (m_buffer_view->cursor_virtual_column() - it.virtual_column());
            int row = draw_row;
            while (col >= m_columns)
            {
                col -= m_columns;
                row++;
            }
            draw_crosshair(col_x(col), row_y(row));
        }
        if (*it.iterator() == *m_iterator)
        {
            bool wrap = (it.code_point() == '\t');
            int row = draw_row;
            int col = it.screen_column();
            for (int i = 0; i < cwidth; i++)
            {
                draw_cursor(col_x(col), row_y(row), i, cwidth);
                col++;
                if (wrap && (col >= m_columns) && (i < (cwidth - 1)))
                {
                    row++;
                    col = 0;
                    /* We draw another crosshair row here so that it can be
                     * drawn before drawing the cursor over top of it. If we
                     * did not do this then the draw_crosshair for the next
                     * character on the new row would overwrite the wrapped
                     * part of the cursor. */
                    draw_crosshair(col_x(0), row_y(row), col_x(m_columns));
                    last_drawn_crosshair_row = row;
                }
            }
        }
        if (it.is_eol())
        {
            if ((m_buffer_view->cursor_virtual_column() > it.virtual_column()) &&
                    (m_buffer_view->cursor_virtual_column() < (it.virtual_column() + (m_columns - it.screen_column()))))
            {
                draw_crosshair(col_x(it.screen_column() + (m_buffer_view->cursor_virtual_column() - it.virtual_column())),
                        row_y(draw_row));
            }
        }
        else
        {
            draw_buffer_character(it.code_point(), draw_row, it.screen_column());
        }
    }
}

void BufferPane::draw_buffer_character(uint32_t character, int screen_row, int screen_column)
{
    if ((character != ' ') &&
        (character != '\t') &&
        (character != '\n') &&
        (character != Buffer::Iterator::INVALID_CODE_POINT))
    {
        int x = col_x(screen_column);
        int y = row_y(screen_row);
        if (character < 0x20u)
        {
            m_window->gl()->draw_rect(win_x(x + 2), win_y(y),
                    m_window->font()->get_advance() * 2 - 4, 1,
                    0, 0.7, 1.0, 1.0);
            m_window->gl()->draw_character(win_x(x), win_y(y), '^', *m_window->font(), 0.0, 0.7, 1.0, 1.0);
            m_window->gl()->draw_character(win_x(col_x(screen_column + 1)), win_y(y), character | 0x40u, *m_window->font(), 0.0, 0.7, 1.0, 1.0);
        }
        else
        {
            m_window->gl()->draw_character(win_x(x), win_y(y), character, *m_window->font(), 1.0, 1.0, 1.0, 1.0);
        }
    }
}

void BufferPane::draw_cursor(int x, int y, int i, int columns)
{
    if (m_command_mode && (!m_focused))
    {
        return;
    }
    int width = m_window->font()->get_advance();
    int height = m_window->font()->get_line_height();
    if (insert_mode())
    {
        if (i == 0)
        {
            m_window->gl()->draw_rect(win_x(x), win_y(y), 3, height, 1.0, 0.2, 1.0, 1.0);
        }
    }
    else if (m_focused)
    {
        m_window->gl()->draw_rect(win_x(x), win_y(y), width, height, 1.0, 0.2, 1.0, 1.0);
    }
    else
    {
        m_window->gl()->draw_rect(win_x(x), win_y(y), width, 1, 1.0, 0.2, 1.0, 1.0);
        m_window->gl()->draw_rect(win_x(x), win_y(y) + height - 1, width, 1, 1.0, 0.2, 1.0, 1.0);
        if (i == 0)
        {
            m_window->gl()->draw_rect(win_x(x), win_y(y), 1, height, 1.0, 0.2, 1.0, 1.0);
        }
        if (i == (columns - 1))
        {
            m_window->gl()->draw_rect(win_x(x) + width - 1, win_y(y), 1, height, 1.0, 0.2, 1.0, 1.0);
        }
    }
}

void BufferPane::draw_crosshair(int x, int y, int width, int height)
{
    if (!m_command_mode)
    {
        if (width < 0)
            width = m_window->font()->get_advance();
        if (height < 0)
            height = m_window->font()->get_line_height();
        m_window->gl()->draw_rect(win_x(x), win_y(y), width, height, 0.1, 0.1, 0.1, 1.0);
    }
}

void BufferPane::draw_line_number(int screen_row, size_t line_number)
{
    if (screen_row >= 0)
    {
        char line_number_string[20];
        int length = snprintf(line_number_string, sizeof(line_number_string), "%lu", line_number + 1u);
        int x = (m_line_number_gutter_width - length) * m_window->font()->get_advance();
        int y = row_y(screen_row);
        float r, g;
        if (line_number == m_iterator->line())
        {
            r = 1.0;
            g = 1.0;
        }
        else
        {
            r = 0.6;
            g = 0.6;
        }
        for (int i = 0; i < length; i++)
        {
            m_window->gl()->draw_character(win_x(x), win_y(y), line_number_string[i], *m_window->font(), r, g, 0.0, 1.0);
            x += m_window->font()->get_advance();
        }
    }
}

void BufferPane::exit_insert_mode()
{
    if (insert_mode())
    {
        m_buffer->exit_insert_mode();
        if (!m_iterator->is_start_of_line())
        {
            m_iterator->go_back();
        }
        m_buffer_view->update();
        m_window->request_redraw();
    }
}

void BufferPane::insert_code_point(uint32_t code_point)
{
    if (insert_mode())
    {
        if (code_point == '\b') /* Backspace */
        {
            Buffer::Iterator i = *m_iterator;
            i.go_back();
            if (i.valid())
            {
                m_buffer->erase_code_point(i);
            }
        }
        else if (code_point == 127) /* Delete */
        {
            m_buffer->erase_code_point(*m_iterator);
        }
        else
        {
            /* TODO: adjust cursor screen row in m_buffer_view. */
            m_buffer->insert_code_point(*m_iterator, code_point);
        }
        m_buffer_view->update();
        m_window->request_redraw();
    }
}

void BufferPane::kill_character_forward()
{
    if (**m_iterator != '\n')
    {
        m_buffer->erase_code_point(*m_iterator);
        if (**m_iterator == '\n')
        {
            m_iterator->go_left_in_line();
        }
        m_buffer_view->update();
        m_window->request_redraw();
    }
}

void BufferPane::kill_character_backward()
{
    Buffer::Iterator it = *m_iterator;
    if (it.go_left_in_line())
    {
        m_buffer->erase_code_point(it);
        m_buffer_view->update();
        m_window->request_redraw();
    }
}

size_t BufferPane::display_column() const
{
    if (!m_iterator->valid())
    {
        return 0u;
    }
    if ((**m_iterator == '\n') && (!insert_mode()))
    {
        Buffer::Iterator i = *m_iterator;
        if (!i.go_left_in_line())
        {
            return 0u;
        }
    }
    return m_buffer_view->cursor_virtual_column() + 1u;
}

void BufferPane::cursor_move(BufferPane::CursorMovement which, uint32_t c)
{
    bool moved = m_buffer_view->cursor_move(which, c, insert_mode());
    if (moved)
    {
        m_buffer_view->update();
        m_window->request_redraw();
    }
}

void BufferPane::enter_insert_mode(Window::EnterInsertModeMode which)
{
    if (insert_mode())
        return;

    switch (which)
    {
    case Window::EnterInsertModeMode::START_OF_CHAR:
        m_buffer->enter_insert_mode();
        break;
    case Window::EnterInsertModeMode::END_OF_CHAR:
        if (**m_iterator != '\n')
        {
            m_iterator->go_forward();
        }
        m_buffer->enter_insert_mode();
        break;
    case Window::EnterInsertModeMode::START_OF_LINE:
        m_iterator->go_start_of_line();
        enter_insert_mode(Window::EnterInsertModeMode::START_OF_CHAR);
        break;
    case Window::EnterInsertModeMode::END_OF_LINE:
        m_iterator->go_end_of_line(false);
        enter_insert_mode(Window::EnterInsertModeMode::END_OF_CHAR);
        break;
    case Window::EnterInsertModeMode::NEW_LINE_BEFORE:
        m_iterator->go_start_of_line();
        m_buffer->enter_insert_mode();
        m_buffer->insert_code_point(*m_iterator, '\n');
        m_buffer->exit_insert_mode();
        cursor_move(CursorMovement::UP, 0u);
        enter_insert_mode(Window::EnterInsertModeMode::START_OF_CHAR);
        break;
    case Window::EnterInsertModeMode::NEW_LINE_AFTER:
        m_iterator->go_end_of_line(true);
        m_buffer->enter_insert_mode();
        m_buffer->insert_code_point(*m_iterator, '\n');
        break;
    }

    m_buffer_view->update();
    m_window->request_redraw();
}

int BufferPane::calculate_lines_to_scroll(Window::ScrollMode scroll_mode)
{
    switch (scroll_mode)
    {
    case Window::ScrollMode::ONE_LINE:
        return 1;
        break;
    case Window::ScrollMode::WHEEL:
        return 3;
        break;
    case Window::ScrollMode::HALF_SCREEN:
        return m_rows / 2;
        break;
    case Window::ScrollMode::WHOLE_SCREEN:
        return m_rows - 1;
        break;
    }
    return 0;
}

void BufferPane::draw_status_bar()
{
    m_window->gl()->draw_rect(win_x(0), win_y(m_window->font()->get_line_height()), m_width, 1, 0.5, 0.5, 0.5, 1.0);
    m_window->gl()->draw_rect(win_x(0), win_y(0), m_width, m_window->font()->get_line_height(), 0.0, 0.0, 0.0, 1.0);
    char cursor_position[20];
    sprintf(cursor_position, "%zu, %zu", display_line(), display_column());
    int cursor_position_length = strlen(cursor_position);
    int x = m_width - m_window->font()->get_advance() * cursor_position_length;
    m_window->gl()->draw_rect(win_x(x - 2), win_y(0), 1, m_window->font()->get_line_height(), 0.5, 0.5, 0.5, 1.0);
    std::string filename = m_buffer->filename() ? *m_buffer->filename() : "[No Name]";
    int filename_x = std::min(0, x - 3 - (int)filename.size() * m_window->font()->get_advance());
    for (int i = 0; i < cursor_position_length; i++)
    {
        m_window->gl()->draw_character(win_x(x), win_y(0), cursor_position[i], *m_window->font(), 1.0, 1.0, 1.0, 1.0);
        x += m_window->font()->get_advance();
    }
    for (size_t i = 0; i < filename.size(); i++)
    {
        m_window->gl()->draw_character(win_x(filename_x), win_y(0), filename[i], *m_window->font(), 1.0, 1.0, 1.0, 1.0);
        filename_x += m_window->font()->get_advance();
    }
}

void BufferPane::undo()
{
    m_buffer->undo();
    m_buffer_view->update();
    m_window->request_redraw();
}

void BufferPane::redo()
{
    m_buffer->redo();
    m_buffer_view->update();
    m_window->request_redraw();
}

void BufferPane::set_command_mode()
{
    m_command_mode = true;
    set_show_status_bar(false);
    set_show_line_number_gutter(false);
    enter_insert_mode(Window::EnterInsertModeMode::START_OF_CHAR);
}

void BufferPane::clear()
{
    m_buffer->clear();
    *m_iterator = m_buffer->begin();
    m_buffer_view->update();
}
