#include "gtest/gtest.h"
#include "Buffer.h"
#include "TestSupport.h"
#include "Path.h"
#include <string>

#define C(x) ((uint32_t)(x))
#define ss(s) std::string(s)

TEST(BufferTest, writes_an_identical_file_to_what_is_loaded_if_no_changes_were_performed)
{
    const char * files_to_test[] = {
        "test/files/empty.txt",
        "test/files/one_eol.txt",
        "test/files/two_eol.txt",
        "test/files/line_endings/cr_format.txt",
        "test/files/line_endings/crlf_format.txt",
        "test/files/line_endings/lf_format.txt",
        "test/files/no_eol_at_eof.txt",
        "test/files/no_eol_at_eof_crlf.txt",
    };
    for (auto e : files_to_test)
    {
        Buffer b(e);
        ASSERT_TRUE(b.write_to_file("test/tmp/f"));
        ASSERT_TRUE(Path::is_file("test/tmp/f"));
        TestSupport::compare_files(e, "test/tmp/f");
    }
}

TEST(BufferTest, writes_an_empty_file_for_an_empty_buffer)
{
    Buffer b;
    ASSERT_TRUE(b.write_to_file("test/tmp/f"));
    TestSupport::compare_files("test/files/empty.txt", "test/tmp/f");
}

TEST(BufferTest, allows_navigating_using_iterators)
{
    Buffer b("test/files/line_endings/lf_format.txt");
    auto iterator = b.add_cursor();

    EXPECT_EQ(0u, iterator->line());
    ASSERT_EQ(C('H'), **iterator);

    EXPECT_FALSE(iterator->go_previous_line());
    EXPECT_EQ(0u, iterator->line());
    ASSERT_EQ(C('H'), **iterator);

    EXPECT_TRUE(iterator->go_right_in_line(false));
    EXPECT_EQ(0u, iterator->line());
    ASSERT_EQ(C('e'), **iterator);

    EXPECT_TRUE(iterator->go_next_line());
    EXPECT_EQ(1u, iterator->line());
    ASSERT_EQ(C('T'), **iterator);

    EXPECT_FALSE(iterator->go_left_in_line());
    EXPECT_EQ(1u, iterator->line());
    ASSERT_EQ(C('T'), **iterator);

    EXPECT_TRUE(iterator->go_right_in_line(true));
    EXPECT_EQ(1u, iterator->line());
    ASSERT_EQ(C('h'), **iterator);

    EXPECT_FALSE(iterator->go_next_line());
    EXPECT_EQ(1u, iterator->line());
    ASSERT_EQ(C('h'), **iterator);

    EXPECT_TRUE(iterator->go_previous_line());
    EXPECT_EQ(0u, iterator->line());
    ASSERT_EQ(C('H'), **iterator);
}

TEST(BufferTest, allows_navigating_using_iterators2)
{
    Buffer b("test/files/blank_lines.txt");
    auto iterator = b.add_cursor();

    EXPECT_EQ(0u, iterator->line());
    EXPECT_EQ(C('a'), **iterator);

    EXPECT_TRUE(iterator->go_next_line());
    EXPECT_EQ(1u, iterator->line());
    EXPECT_EQ(C('\n'), **iterator);

    EXPECT_FALSE(iterator->go_start_of_line());
    EXPECT_EQ(1u, iterator->line());
    EXPECT_EQ(C('\n'), **iterator);

    EXPECT_FALSE(iterator->go_end_of_line(false));
    EXPECT_EQ(1u, iterator->line());
    EXPECT_EQ(C('\n'), **iterator);

    EXPECT_FALSE(iterator->go_end_of_line(true));
    EXPECT_EQ(1u, iterator->line());
    EXPECT_EQ(C('\n'), **iterator);

    EXPECT_TRUE(iterator->go_next_line());
    EXPECT_EQ(2u, iterator->line());
    EXPECT_EQ(C('d'), **iterator);

    EXPECT_TRUE(iterator->go_previous_line());
    EXPECT_EQ(1u, iterator->line());
    EXPECT_EQ(C('\n'), **iterator);

    EXPECT_TRUE(iterator->go_previous_line());
    EXPECT_EQ(0u, iterator->line());
    EXPECT_EQ(C('a'), **iterator);
}

TEST(BufferTest, allows_inserting_and_erasing_characters)
{
    std::string s("abc\ndef\nghi\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_cursor();

    b.insert_code_point(*it, C('1'));
    EXPECT_EQ(ss("1abc\ndef\nghi\n"), b.get_string());
    EXPECT_EQ(C('a'), **it);

    b.enter_insert_mode();

    it->go_end_of_line(true);
    b.insert_code_point(*it, C('$'));
    EXPECT_EQ(ss("1abc$\ndef\nghi\n"), b.get_string());
    EXPECT_EQ(C('\n'), **it);

    b.erase_code_point(*it);
    EXPECT_EQ(ss("1abc$def\nghi\n"), b.get_string());
    EXPECT_EQ(C('d'), **it);

    b.exit_insert_mode();
}

TEST(BufferTest, allows_erasing_ranges)
{
    std::string s("abc\ndef\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_iterator();
    auto it2 = b.add_iterator();
    it2->go_next_line();

    /* Invalid tests. */
    b.erase_range(Buffer::Range(b.endp(), b.endp()));
    EXPECT_EQ(ss("abc\ndef\n"), b.get_string());
    b.erase_range(Buffer::Range(it, b.beginp()));
    EXPECT_EQ(ss("abc\ndef\n"), b.get_string());

    /* Zero-length test. */
    b.erase_range(Buffer::Range(it2, it2));
    EXPECT_EQ(ss("abc\ndef\n"), b.get_string());

    EXPECT_EQ(C('a'), **it);
    EXPECT_EQ(C('d'), **it2);
    b.erase_range(Buffer::Range(it, it2));
    EXPECT_EQ(ss("def\n"), b.get_string());
    EXPECT_EQ(*it, *it2);
    EXPECT_EQ(C('d'), **it);
    while (it2->valid())
    {
        it2->go_forward();
    }
    EXPECT_EQ(C('d'), **it);
    b.erase_range(Buffer::Range(it, it2));
    EXPECT_EQ(ss(""), b.get_string());
    EXPECT_FALSE(it->valid());
    EXPECT_FALSE(it2->valid());
}

TEST(BufferTest, adds_a_newline_after_inserted_character_when_inserting_in_an_empty_buffer)
{
    Buffer b;
    auto it = b.add_cursor();
    EXPECT_EQ(0u, it->offset());
    EXPECT_FALSE(it->valid());

    b.enter_insert_mode();
    b.insert_code_point(*it, C('J'));
    EXPECT_EQ(ss("J\n"), b.get_string());
    EXPECT_EQ(C('\n'), **it);
    EXPECT_TRUE(it->valid());
    EXPECT_EQ(0u, it->line());
    b.exit_insert_mode();
}

TEST(BufferTest, allows_undo_and_redo_of_inserts)
{
    std::string s("abc\ndef\nghi\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_cursor();

    b.enter_insert_mode();
    b.insert_code_point(*it, C('1'));
    b.insert_code_point(*it, C('2'));
    b.exit_insert_mode();
    EXPECT_EQ(ss("12abc\ndef\nghi\n"), b.get_string());
    EXPECT_EQ(C('a'), **it);
    EXPECT_EQ(1u, b.m_change_operations.size());

    it->go_next_line();
    it->go_right_in_line(false);
    b.enter_insert_mode();
    b.insert_code_point(*it, C('3'));
    b.exit_insert_mode();
    EXPECT_EQ(ss("12abc\nd3ef\nghi\n"), b.get_string());
    EXPECT_EQ(C('e'), **it);
    EXPECT_EQ(2u, b.m_change_operations.size());

    it->go_end_of_line(true);
    b.insert_code_point(*it, C('$'));
    EXPECT_EQ(ss("12abc\nd3ef$\nghi\n"), b.get_string());
    EXPECT_EQ(C('$'), **it); /* cursor moved back because not in insert mode */
    EXPECT_EQ(3u, b.m_change_operations.size());

    it->go_start_of_line();
    EXPECT_EQ(C('d'), **it);

    b.undo();
    EXPECT_EQ(ss("12abc\nd3ef\nghi\n"), b.get_string());
    EXPECT_EQ(C('d'), **it);

    b.undo();
    EXPECT_EQ(ss("12abc\ndef\nghi\n"), b.get_string());
    EXPECT_EQ(C('d'), **it);

    b.undo();
    EXPECT_EQ(ss("abc\ndef\nghi\n"), b.get_string());
    EXPECT_EQ(C('d'), **it);

    b.redo();
    EXPECT_EQ(ss("12abc\ndef\nghi\n"), b.get_string());
    EXPECT_EQ(C('d'), **it);

    b.redo();
    EXPECT_EQ(ss("12abc\nd3ef\nghi\n"), b.get_string());
    EXPECT_EQ(C('d'), **it);

    b.redo();
    EXPECT_EQ(ss("12abc\nd3ef$\nghi\n"), b.get_string());
    EXPECT_EQ(C('d'), **it);

    /* undo/redo do not create new operations */
    EXPECT_FALSE(b.m_current_change_operation);
    EXPECT_EQ(3u, b.m_change_operations.size());
}

TEST(BufferTest, allows_undo_and_redo_of_erases)
{
    std::string s("abc\ndef\nghi\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_cursor();

    b.erase_code_point(*it);
    EXPECT_EQ(ss("bc\ndef\nghi\n"), b.get_string());
    EXPECT_EQ(C('b'), **it);
    EXPECT_EQ(1u, b.m_change_operations.size());

    it->go_next_line();
    it->go_right_in_line(false);
    b.erase_code_point(*it);
    EXPECT_EQ(ss("bc\ndf\nghi\n"), b.get_string());
    EXPECT_EQ(C('f'), **it);
    EXPECT_EQ(2u, b.m_change_operations.size());

    it->go_end_of_line(true);
    b.erase_code_point(*it);
    EXPECT_EQ(ss("bc\ndfghi\n"), b.get_string());
    EXPECT_EQ(C('g'), **it);
    EXPECT_EQ(3u, b.m_change_operations.size());

    it->go_left_in_line();
    EXPECT_EQ(C('f'), **it);

    b.undo();
    EXPECT_EQ(ss("bc\ndf\nghi\n"), b.get_string());
    EXPECT_EQ(C('f'), **it);

    b.undo();
    EXPECT_EQ(ss("bc\ndef\nghi\n"), b.get_string());
    EXPECT_EQ(C('f'), **it);

    b.undo();
    EXPECT_EQ(ss("abc\ndef\nghi\n"), b.get_string());
    EXPECT_EQ(C('f'), **it);

    b.redo();
    EXPECT_EQ(ss("bc\ndef\nghi\n"), b.get_string());
    EXPECT_EQ(C('f'), **it);

    b.redo();
    EXPECT_EQ(ss("bc\ndf\nghi\n"), b.get_string());
    EXPECT_EQ(C('f'), **it);

    b.redo();
    EXPECT_EQ(ss("bc\ndfghi\n"), b.get_string());
    EXPECT_EQ(C('f'), **it);

    /* undo/redo do not create new operations */
    EXPECT_FALSE(b.m_current_change_operation);
    EXPECT_EQ(3u, b.m_change_operations.size());
}

TEST(BufferTest, erase_chunks_are_coalesced_within_a_change_operation)
{
    std::string s("abcdef\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_cursor();

    b.enter_insert_mode();
    it->go_right_in_line(false);
    it->go_right_in_line(false); /* erase just after previous erase */
    b.erase_code_point(*it);
    b.erase_code_point(*it);
    it->go_left_in_line();
    b.erase_code_point(*it); /* erase just before previous erase */
    b.exit_insert_mode();
    EXPECT_EQ(C('e'), **it);
    EXPECT_EQ(ss("aef\n"), b.get_string());
    ASSERT_EQ(1u, b.m_change_operations.size());
    ASSERT_EQ(1u, b.m_change_operations[0]->changes.size());

    b.undo();
    EXPECT_EQ(C('e'), **it);
    EXPECT_EQ(ss("abcdef\n"), b.get_string());
}

TEST(BufferTest, erases_in_uncommitted_insert_units_modify_the_insert_units)
{
    std::string s("abcdef\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_cursor();

    it->go_right_in_line(false);
    b.enter_insert_mode();
    b.insert_code_point(*it, C('1'));
    b.insert_code_point(*it, C('2'));
    b.insert_code_point(*it, C('3'));
    it->go_left_in_line();
    b.erase_code_point(*it);
    b.exit_insert_mode();
    EXPECT_EQ(1u, b.m_change_operations.size());
    EXPECT_EQ(ss("a12bcdef\n"), b.get_string());
    EXPECT_EQ(C('b'), **it);

    b.undo();
    EXPECT_EQ(ss("abcdef\n"), b.get_string());
    EXPECT_EQ(C('b'), **it);

    b.redo();
    EXPECT_EQ(ss("a12bcdef\n"), b.get_string());
    EXPECT_EQ(C('b'), **it);
}

TEST(BufferTest, erases_outside_uncommitted_insert_units_start_new_units_within_the_same_operation)
{
    std::string s("abcdef\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_cursor();

    it->go_right_in_line(false);
    b.enter_insert_mode();
    b.insert_code_point(*it, C('1'));
    b.insert_code_point(*it, C('2'));
    b.erase_code_point(*it);
    b.exit_insert_mode();
    EXPECT_EQ(1u, b.m_change_operations.size());
    EXPECT_EQ(ss("a12cdef\n"), b.get_string());
    EXPECT_EQ(C('c'), **it);

    b.undo();
    EXPECT_EQ(ss("abcdef\n"), b.get_string());
    EXPECT_EQ(C('c'), **it);

    b.redo();
    EXPECT_EQ(ss("a12cdef\n"), b.get_string());
    EXPECT_EQ(C('c'), **it);

    b.enter_insert_mode();
    b.insert_code_point(*it, C('3'));
    it->go_start_of_line();
    b.erase_code_point(*it);
    b.exit_insert_mode();
    EXPECT_EQ("123cdef\n", b.get_string());
    EXPECT_EQ(C('1'), **it);

    b.undo();
    EXPECT_EQ("a12cdef\n", b.get_string());
    EXPECT_EQ(C('1'), **it);

    b.undo();
    EXPECT_EQ("abcdef\n", b.get_string());
    EXPECT_EQ(C('b'), **it);

    b.redo();
    EXPECT_EQ("a12cdef\n", b.get_string());
    EXPECT_EQ(C('c'), **it);

    b.redo();
    EXPECT_EQ("123cdef\n", b.get_string());
    EXPECT_EQ(C('c'), **it);
}

TEST(BufferTest, a_insert_change_unit_is_dropped_if_everything_in_it_is_erased_before_committing)
{
    std::string s("abc\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_cursor();

    it->go_right_in_line(false);
    b.enter_insert_mode();
    b.insert_code_point(*it, C('1'));
    b.insert_code_point(*it, C('2'));
    it->go_left_in_line();
    b.erase_code_point(*it); /* backspace */
    it->go_left_in_line();
    b.erase_code_point(*it); /* backspace */
    b.exit_insert_mode();
    EXPECT_EQ(ss("abc\n"), b.get_string());
    EXPECT_EQ(0u, b.m_change_operations.size());
    EXPECT_EQ(C('b'), **it);
}

TEST(BufferTest, non_adjacent_inserts_in_insert_mode_create_a_single_change_operation)
{
    std::string s("abc\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_cursor();

    b.enter_insert_mode();
    b.insert_code_point(*it, C('1'));
    it->go_right_in_line(false);
    b.insert_code_point(*it, C('2'));
    b.exit_insert_mode();
    EXPECT_EQ(ss("1a2bc\n"), b.get_string());

    b.undo();
    EXPECT_EQ(ss("abc\n"), b.get_string());

    it->go_right_in_line(false);
    b.enter_insert_mode();
    b.insert_code_point(*it, C('3'));
    it->go_start_of_line();
    b.insert_code_point(*it, C('4'));
    b.exit_insert_mode();
    EXPECT_EQ(ss("4ab3c\n"), b.get_string());

    b.undo();
    EXPECT_EQ("abc\n", b.get_string());
}

TEST(BufferTest, erase_then_insert_creates_single_change_operation)
{
    std::string s("abc\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_cursor();

    b.enter_insert_mode();
    b.erase_code_point(*it);
    b.insert_code_point(*it, C('1'));
    b.exit_insert_mode();
    EXPECT_EQ("1bc\n", b.get_string());

    b.undo();
    EXPECT_EQ("abc\n", b.get_string());
}

TEST(BufferTest, non_adjacent_erases_create_a_single_change_operation)
{
    std::string s("abc\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_cursor();

    b.enter_insert_mode();
    b.erase_code_point(*it);
    it->go_right_in_line(false);
    b.erase_code_point(*it);
    b.exit_insert_mode();
    EXPECT_EQ("b\n", b.get_string());

    b.undo();
    EXPECT_EQ(ss("abc\n"), b.get_string());
}

TEST(BufferTest, inserting_at_beginning_and_end_of_line_in_insert_mode_creates_proper_undo_operation)
{
    std::string s("abc\ndef\n");
    Buffer b((const uint8_t *)&s[0], s.size());
    auto it = b.add_cursor();

    b.enter_insert_mode();
    b.insert_code_point(*it, C('1'));
    b.insert_code_point(*it, C('2'));
    it->go_end_of_line(true);
    b.insert_code_point(*it, C('3'));
    b.insert_code_point(*it, C('4'));
    b.exit_insert_mode();
    EXPECT_EQ("12abc34\ndef\n", b.get_string());

    b.undo();
    EXPECT_EQ("abc\ndef\n", b.get_string());

    b.redo();
    EXPECT_EQ("12abc34\ndef\n", b.get_string());
}

TEST(BufferTest, inserts_and_undoes_properly_starting_from_empty_buffer)
{
    Buffer b;
    auto it = b.add_cursor();

    b.enter_insert_mode();
    EXPECT_FALSE(it->valid());
    b.insert_code_point(*it, C('1'));
    EXPECT_TRUE(it->valid());
    EXPECT_EQ(0u, it->line());
    b.insert_code_point(*it, C('\n'));
    EXPECT_EQ(1u, it->line());
    b.insert_code_point(*it, C('2'));
    EXPECT_EQ(1u, it->line());
    b.insert_code_point(*it, C('\n'));
    EXPECT_EQ(2u, it->line());
    b.insert_code_point(*it, C('3'));
    EXPECT_EQ(2u, it->line());
    b.exit_insert_mode();

    EXPECT_EQ(2u, it->line());
    EXPECT_EQ("1\n2\n3\n", b.get_string());

    b.undo();
    EXPECT_EQ("", b.get_string());
    EXPECT_EQ(0u, it->line());
}
