#include "gtest/gtest.h"
#include "BufferLineWalker.h"

class MyCharacterWidthDeterminer : public CharacterWidthDeterminer
{
    int operator()(uint32_t character)
    {
        if (character < 32)
        {
            return 2;
        }
        else
        {
            return 1;
        }
    }
} cld;

TEST(BufferLineWalkerTest, iterats_once_for_empty_line)
{
    static const uint8_t data[] = "\n";
    std::shared_ptr<Buffer> b = std::make_shared<Buffer>(data, 1u);
    std::shared_ptr<Buffer::Iterator> start_of_line = b->add_iterator();
    BufferLineWalker blw(b, start_of_line, 20, cld);
    int iter_count = 0;
    for (; blw.is_valid(); blw++)
    {
        EXPECT_EQ(0, blw.row_offset());
        EXPECT_EQ(0, blw.screen_column());
        EXPECT_EQ(0, blw.virtual_column());
        EXPECT_EQ(0, blw.character_width());
        EXPECT_TRUE(blw.is_eol());
        iter_count++;
    }
    EXPECT_EQ(1, iter_count);
    EXPECT_FALSE(blw.is_valid());
    EXPECT_FALSE(blw.is_eol());
    blw++;
    EXPECT_FALSE(blw.is_valid());
    EXPECT_FALSE(blw.is_eol());
}

TEST(BufferLineWalkerTest, walks_correctly_with_tabs_and_no_line_wrapping)
{
    static const char data[] = "\t1\t12\t123\t1234\t!";
    std::shared_ptr<Buffer> b = std::make_shared<Buffer>((const uint8_t *)data, strlen(data));
    std::shared_ptr<Buffer::Iterator> start_of_line = b->add_iterator();
    BufferLineWalker blw(b, start_of_line, 100, cld);

    static int expected_screen_columns[] = {
        0,   4, 5,   8, 9, 10,   12, 13, 14, 15,   16, 17, 18, 19, 20,   24, 25
    };
    static int expected_character_widths[] = {
        4,   1, 3,   1, 1, 2,    1, 1, 1, 1,       1, 1, 1, 1, 4,        1, 0
    };
    int iter_count = 0;
    for (; blw.is_valid(); blw++)
    {
        EXPECT_EQ(0, blw.row_offset());
        EXPECT_LT(iter_count, (int)(sizeof(expected_screen_columns)/sizeof(expected_screen_columns[0])));
        EXPECT_EQ(expected_screen_columns[iter_count], blw.screen_column());
        EXPECT_EQ(expected_screen_columns[iter_count], blw.virtual_column());
        EXPECT_EQ(expected_character_widths[iter_count], blw.character_width());
        iter_count++;
    }
    EXPECT_EQ((int)strlen(data) + 1, iter_count);
}

TEST(BufferLineWalkerTest, walks_correctly_with_tabs_and_line_wrapping)
{
    static const char data[] = "\t1\t12\t123\t1234\t\t!";
    std::shared_ptr<Buffer> b = std::make_shared<Buffer>((const uint8_t *)data, strlen(data));
    std::shared_ptr<Buffer::Iterator> start_of_line = b->add_iterator();
    BufferLineWalker blw(b, start_of_line, 5, cld);

    static int expected_row_offsets[] = {
        0,   0, 1,   1, 1, 2,   2, 2, 2, 3,   3, 3, 3, 3, 4,   4, 5, 5
    };
    static int expected_screen_columns[] = {
        0,   4, 0,   3, 4, 0,   2, 3, 4, 0,   1, 2, 3, 4, 0,   4, 3, 4
    };
    static int expected_virtual_columns[] = {
        0,   4, 5,   8, 9, 10,   12, 13, 14, 15,   16, 17, 18, 19, 20,   24, 28, 29
    };
    static int expected_character_widths[] = {
        4,   1, 3,   1, 1, 2,    1, 1, 1, 1,       1, 1, 1, 1, 4,        4, 1, 0
    };
    int iter_count = 0;
    for (; blw.is_valid(); blw++)
    {
        EXPECT_LT(iter_count, (int)(sizeof(expected_screen_columns)/sizeof(expected_screen_columns[0])));
        EXPECT_EQ(expected_row_offsets[iter_count], blw.row_offset());
        EXPECT_EQ(expected_screen_columns[iter_count], blw.screen_column());
        EXPECT_EQ(expected_virtual_columns[iter_count], blw.virtual_column());
        EXPECT_EQ(expected_character_widths[iter_count], blw.character_width());
        iter_count++;
    }
    EXPECT_EQ((int)strlen(data) + 1, iter_count);
}

TEST(BufferLineWalkerTest, walks_correctly_with_multi_column_characters_and_line_wrapping)
{
    static const char data[] = "ab\002xyz\003.";
    std::shared_ptr<Buffer> b = std::make_shared<Buffer>((const uint8_t *)data, strlen(data));
    std::shared_ptr<Buffer::Iterator> start_of_line = b->add_iterator();
    BufferLineWalker blw(b, start_of_line, 4, cld);

    static int expected_screen_columns[] = {
        0, 1, 2,  0, 1, 2,  0, 2,  3
    };
    static int expected_row_offsets[] = {
        0, 0, 0,  1, 1, 1,  2, 2,  2
    };
    static int expected_virtual_columns[] = {
        0, 1, 2,  4, 5, 6,  7, 9, 10
    };
    static int expected_character_widths[] = {
        1, 1, 2,  1, 1, 1,  2, 1, 0
    };
    int iter_count = 0;
    for (; blw.is_valid(); blw++)
    {
        EXPECT_LT(iter_count, (int)(sizeof(expected_screen_columns)/sizeof(expected_screen_columns[0])));
        EXPECT_EQ(expected_row_offsets[iter_count], blw.row_offset());
        EXPECT_EQ(expected_screen_columns[iter_count], blw.screen_column());
        EXPECT_EQ(expected_virtual_columns[iter_count], blw.virtual_column());
        EXPECT_EQ(expected_character_widths[iter_count], blw.character_width());
        iter_count++;
    }
    EXPECT_EQ((int)strlen(data) + 1, iter_count);
}

TEST(BufferLineWalkerTest, does_not_push_down_a_row_when_a_multi_column_character_is_wider_than_view_width)
{
    static const char data[] = "\002\003";
    std::shared_ptr<Buffer> b = std::make_shared<Buffer>((const uint8_t *)data, strlen(data));
    std::shared_ptr<Buffer::Iterator> start_of_line = b->add_iterator();
    BufferLineWalker blw(b, start_of_line, 1, cld);

    static int expected_screen_columns[] = {
        0, 0, 0
    };
    static int expected_row_offsets[] = {
        0, 1, 2
    };
    static int expected_virtual_columns[] = {
        0, 2, 4
    };
    static int expected_character_widths[] = {
        2, 2, 0
    };
    int iter_count = 0;
    for (; blw.is_valid(); blw++)
    {
        EXPECT_LT(iter_count, (int)(sizeof(expected_screen_columns)/sizeof(expected_screen_columns[0])));
        EXPECT_EQ(expected_row_offsets[iter_count], blw.row_offset());
        EXPECT_EQ(expected_screen_columns[iter_count], blw.screen_column());
        EXPECT_EQ(expected_virtual_columns[iter_count], blw.virtual_column());
        EXPECT_EQ(expected_character_widths[iter_count], blw.character_width());
        iter_count++;
    }
    EXPECT_EQ((int)strlen(data) + 1, iter_count);
}
