/*
 * TSM - Selection Copy Tests
 *
 * Copyright (c) 2022 Andreas Heck <aheck@gmx.de>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdio.h>
#include "test_common.h"
#include "libtsm.h"
#include "libtsm-int.h"

static void write_string(struct tsm_screen *screen, const char *str)
{
	struct tsm_screen_attr attr;
	int i;

	memset(&attr, 0, sizeof(attr));
	attr.fccode = 37; /* white */
	attr.bccode = 40; /* black */

	for (i = 0; str[i]; i++) {
		tsm_screen_write(screen, str[i], &attr);
	}
}

START_TEST(test_screen_copy_incomplete)
{
	struct tsm_screen *screen;
	int r;
	char *str = NULL;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	tsm_screen_newline(screen);
	write_string(screen, "   Hello World!");

	/* select start but don't end selection and copy it */
	tsm_screen_selection_start(screen, 3, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_int_eq(1, r); // strlen == 1?
	ck_assert_str_eq("H", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}

START_TEST(test_screen_copy_one_cell)
{
	struct tsm_screen *screen;
	int r;
	char *str = NULL;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	tsm_screen_newline(screen);
	write_string(screen, "   Hello World!");

	/* select start, end selection on same cell and copy it */
	tsm_screen_selection_start(screen, 3, 1);
	tsm_screen_selection_target(screen, 3, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_int_eq(1, r); // strlen == 1?
	ck_assert_str_eq("H", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}

START_TEST(test_screen_copy_line)
{
	struct tsm_screen *screen;
	int r;
	char *str = NULL;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	tsm_screen_newline(screen);
	write_string(screen, "   Hello World!");
	tsm_screen_newline(screen);
	write_string(screen, "Filler Text");
	tsm_screen_newline(screen);

	/* select "Hello World!" from left to right and copy it */
	tsm_screen_selection_start(screen, 3, 1);
	tsm_screen_selection_target(screen, 14, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	const char *expected = "Hello World!";
	ck_assert_int_eq(strlen(expected), r);
	ck_assert_str_eq(expected, str);
	free(str);
	str = NULL;

	/* select "Hello" from left to right and copy it */
	tsm_screen_selection_reset(screen);
	tsm_screen_selection_start(screen, 3, 1);
	tsm_screen_selection_target(screen, 7, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello", str);
	free(str);
	str = NULL;

	/* select "   Hello" from left to right and copy it */
	tsm_screen_selection_reset(screen);
	tsm_screen_selection_start(screen, 0, 1);
	tsm_screen_selection_target(screen, 7, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("   Hello", str);
	free(str);
	str = NULL;

	/* select "Hello World!" from right to left and copy it */
	tsm_screen_selection_reset(screen);
	tsm_screen_selection_start(screen, 14, 1);
	tsm_screen_selection_target(screen, 3, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!", str);
	free(str);
	str = NULL;

	/* select "Hello" from right to left and copy it */
	tsm_screen_selection_reset(screen);
	tsm_screen_selection_start(screen, 7, 1);
	tsm_screen_selection_target(screen, 3, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}
END_TEST

START_TEST(test_screen_copy_line_scrolled)
{
	struct tsm_screen *screen;
	int r, i;
	char *str = NULL;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	for (i = 0; i < 39; i++) {
		tsm_screen_newline(screen);
	}

	write_string(screen, "   Hello World!");

	/* select "Hello World!" from left to right and copy it */
	tsm_screen_selection_start(screen, 3, 39);
	tsm_screen_selection_target(screen, 14, 39);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_int_eq(screen->sel_end.x, 14);

	/* force the selected text to scroll up */
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_int_eq(screen->sel_end.x, 14);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}
END_TEST

START_TEST(test_screen_copy_lines)
{
	struct tsm_screen *screen;
	int r;
	char *str = NULL;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	tsm_screen_newline(screen);
	write_string(screen, "   Hello World!");
	tsm_screen_newline(screen);
	write_string(screen, "This is a copy test");
	tsm_screen_newline(screen);
	write_string(screen, "for a selection with multiple lines.");
	tsm_screen_newline(screen);
	write_string(screen, "All of them are on screen (not in the sb).------");
	tsm_screen_newline(screen);

	/* Select all text excluding the first 3 spaces and the trailing '-' chars from top left to bottom right and copy it */
	tsm_screen_selection_start(screen, 3, 1);
	tsm_screen_selection_target(screen, 41, 4);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	const char *expected = "Hello World!\nThis is a copy test\nfor a selection with multiple lines.\nAll of them are on screen (not in the sb).";
	ck_assert_int_eq(strlen(expected), r);
	ck_assert_str_eq(expected, str);
	free(str);
	str = NULL;

	/* Select "This is a copy test\nfor a selection" from top left and to bottom right copy it */
	tsm_screen_selection_start(screen, 0, 2);
	tsm_screen_selection_target(screen, 14, 3);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("This is a copy test\nfor a selection", str);
	free(str);
	str = NULL;

	/* Select all text excluding the first 3 spaces and the trailing '-' chars from bottom right to top left and copy it */
	tsm_screen_selection_start(screen, 41, 4);
	tsm_screen_selection_target(screen, 3, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!\nThis is a copy test\nfor a selection with multiple lines.\nAll of them are on screen (not in the sb).", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}
END_TEST

START_TEST(test_screen_copy_lines_scrolled)
{
	struct tsm_screen *screen;
	int r, i;
	char *str = NULL;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	for (i = 0; i < 37; i++) {
		tsm_screen_newline(screen);
	}
	tsm_screen_newline(screen);

	write_string(screen, "   Hello World!");
	tsm_screen_newline(screen);
	write_string(screen, "Line 2");
	tsm_screen_newline(screen);
	write_string(screen, "Line 3");

	/* select "Hello World!" from left to right and copy it */
	tsm_screen_selection_start(screen, 3, 37);
	tsm_screen_selection_target(screen, 5, 39);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_int_eq(screen->sel_end.x, 5);

	/* force the selected text to scroll up */
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_int_eq(screen->sel_end.x, 5);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!\nLine 2\nLine 3", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}
END_TEST

START_TEST(test_screen_copy_line_sb)
{
	struct tsm_screen *screen;
	int r;
	char *str = NULL;
	int i;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	tsm_screen_set_max_sb(screen, 10);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	tsm_screen_newline(screen);
	write_string(screen, "   Hello World!");
	tsm_screen_newline(screen);
	write_string(screen, "Filler Text");
	tsm_screen_newline(screen);

	for (i = 0; i < 40; i++) {
		tsm_screen_newline(screen);
	}

	tsm_screen_sb_up(screen, 4);

	/* select "Hello World!" from left to right and copy it */
	tsm_screen_selection_start(screen, 3, 1);
	tsm_screen_selection_target(screen, 14, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!", str);
	free(str);
	str = NULL;

	/* select "Hello" from left to right and copy it */
	tsm_screen_selection_reset(screen);
	tsm_screen_selection_start(screen, 3, 1);
	tsm_screen_selection_target(screen, 7, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello", str);
	free(str);
	str = NULL;

	/* select "   Hello" from left to right and copy it */
	tsm_screen_selection_reset(screen);
	tsm_screen_selection_start(screen, 0, 1);
	tsm_screen_selection_target(screen, 7, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("   Hello", str);
	free(str);
	str = NULL;

	/* select "Hello World!" from right to left and copy it */
	tsm_screen_selection_reset(screen);
	tsm_screen_selection_start(screen, 14, 1);
	tsm_screen_selection_target(screen, 3, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!", str);
	free(str);
	str = NULL;

	/* select "Hello" from right to left and copy it */
	tsm_screen_selection_reset(screen);
	tsm_screen_selection_start(screen, 7, 1);
	tsm_screen_selection_target(screen, 3, 1);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}
END_TEST

START_TEST(test_screen_copy_line_sb_scrolled)
{
	struct tsm_screen *screen;
	int r;
	char *str = NULL;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	/*
	 * Create a selection of text in one line on the main screen, scroll it
	 * into the sb and copy it.
	 */

	tsm_screen_set_max_sb(screen, 10);

	write_string(screen, "   Hello World!");

	/* select "Hello World!" from left to right */
	tsm_screen_selection_start(screen, 3, 0);
	tsm_screen_selection_target(screen, 14, 0);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_int_eq(screen->sel_end.x, 14);

	/* force the selected text to scroll up */
	for (int i = 0; i < 40; i++) {
		tsm_screen_newline(screen);
	}

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_ptr_ne(screen->sel_start.line, NULL);
	ck_assert_int_eq(screen->sel_end.x, 14);
	ck_assert_ptr_ne(screen->sel_end.line, NULL);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!", str);
	free(str);
	str = NULL;

	/*
	 * Create a selection of the text in the sb, scroll it further into the
	 * sb and copy it.
	 */

	tsm_screen_selection_reset(screen);
	tsm_screen_sb_up(screen, 1);

	tsm_screen_selection_start(screen, 3, 0);
	tsm_screen_selection_target(screen, 14, 0);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_ptr_ne(screen->sel_start.line, NULL);
	ck_assert_int_eq(screen->sel_end.x, 14);
	ck_assert_ptr_ne(screen->sel_end.line, NULL);

	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_ptr_ne(screen->sel_start.line, NULL);
	ck_assert_int_eq(screen->sel_end.x, 14);
	ck_assert_ptr_ne(screen->sel_end.line, NULL);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}
END_TEST

START_TEST(test_screen_copy_line_sb_scrolled_invalid)
{
	struct tsm_screen *screen;
	int r, i;
	char *str = NULL;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	/*
	 * Disable the sb. If a selection is moved out of the sb it becomes invalid.
	 * Copying it should return an empty string.
	 */
	tsm_screen_set_max_sb(screen, 0);

	write_string(screen, "   Hello World!");

	/* select "Hello World!" from left to right */
	tsm_screen_selection_start(screen, 3, 0);
	tsm_screen_selection_target(screen, 14, 0);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_int_eq(screen->sel_end.x, 14);

	/* force the selected text to scroll up */
	for (i = 0; i < 40; i++) {
		tsm_screen_newline(screen);
	}

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_ptr_eq(screen->sel_start.line, NULL);
	ck_assert_int_eq(screen->sel_end.x, 14);
	ck_assert_ptr_eq(screen->sel_end.line, NULL);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}
END_TEST

START_TEST(test_screen_copy_lines_sb)
{
	struct tsm_screen *screen;
	int r;
	char *str = NULL;
	int i;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	tsm_screen_set_max_sb(screen, 10);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	tsm_screen_newline(screen);
	write_string(screen, "   Hello World!");
	tsm_screen_newline(screen);
	write_string(screen, "This is a copy test");
	tsm_screen_newline(screen);
	write_string(screen, "for a selection with multiple lines.");
	tsm_screen_newline(screen);
	write_string(screen, "All of them are on screen (not in the sb).------");
	tsm_screen_newline(screen);

	write_string(screen, "Text not in SB");
	tsm_screen_newline(screen);
	write_string(screen, "More Text not in SB");
	tsm_screen_newline(screen);


	for (i = 0; i < 38; i++) {
		tsm_screen_newline(screen);
	}

	tsm_screen_sb_up(screen, 6);

	/* Select all text excluding the first 3 spaces and the trailing '-' chars from top left to bottom right and copy it */
	tsm_screen_selection_start(screen, 3, 1);
	tsm_screen_selection_target(screen, 41, 4);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!\nThis is a copy test\nfor a selection with multiple lines.\nAll of them are on screen (not in the sb).", str);
	free(str);
	str = NULL;

	/* Select "This is a copy test\nfor a selection" from top left and to bottom right copy it */
	tsm_screen_selection_start(screen, 0, 2);
	tsm_screen_selection_target(screen, 14, 3);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("This is a copy test\nfor a selection", str);
	free(str);
	str = NULL;

	/* Select all text excluding the first 3 spaces and the trailing '-' chars from bottom right to top left and copy it */
	tsm_screen_selection_start(screen, 41, 4);
	tsm_screen_selection_target(screen, 3, 1);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_int_eq(screen->sel_end.x, 41);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!\nThis is a copy test\nfor a selection with multiple lines.\nAll of them are on screen (not in the sb).", str);
	free(str);
	str = NULL;

	/* Select from scroll back buffer and the screen from top left to bottom right and copy it */
	tsm_screen_selection_start(screen, 0, 4);
	tsm_screen_selection_target(screen, 18, 6);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("All of them are on screen (not in the sb).------\nText not in SB\nMore Text not in SB", str);
	free(str);
	str = NULL;

	/* Select from scroll back buffer and the screen from bottom right to top left and copy it */
	tsm_screen_selection_start(screen, 18, 6);
	tsm_screen_selection_target(screen, 0, 4);

	ck_assert_int_eq(screen->sel_start.x, 0);
	ck_assert_int_eq(screen->sel_end.x, 18);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("All of them are on screen (not in the sb).------\nText not in SB\nMore Text not in SB", str);
	free(str);
	str = NULL;

	/* Select from scroll back buffer and the screen from bottom right to top left and copy it */
	tsm_screen_selection_start(screen, 8, 6);
	tsm_screen_selection_target(screen, 7, 4);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("them are on screen (not in the sb).------\nText not in SB\nMore Text", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}
END_TEST

START_TEST(test_screen_copy_lines_sb_scrolled)
{
	struct tsm_screen *screen;
	int r, i;
	char *str = NULL;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	/*
	 * Create a selection of text on the main screen, scroll it into the sb
	 * and copy it.
	 */

	tsm_screen_set_max_sb(screen, 10);

	write_string(screen, "   Hello World!");
	tsm_screen_newline(screen);
	write_string(screen, "Line 2");
	tsm_screen_newline(screen);
	write_string(screen, "Line 3");

	/* select "Hello World!" from left to right */
	tsm_screen_selection_start(screen, 3, 0);
	tsm_screen_selection_target(screen, 5, 2);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_int_eq(screen->sel_end.x, 5);

	/* force the selected text to scroll into the sb */
	for (i = 0; i < 40; i++) {
		tsm_screen_newline(screen);
	}

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_ptr_ne(screen->sel_start.line, NULL);
	ck_assert_int_eq(screen->sel_end.x, 5);
	ck_assert_ptr_ne(screen->sel_end.line, NULL);
	ck_assert_ptr_ne(screen->sel_start.line, screen->sel_end.line);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!\nLine 2\nLine 3", str);
	free(str);
	str = NULL;

	/*
	 * Create a selection of the text in the sb, scroll it further into
	 * the sb and copy it.
	 */

	tsm_screen_selection_reset(screen);
	tsm_screen_sb_up(screen, 3);

	tsm_screen_selection_start(screen, 3, 0);
	tsm_screen_selection_target(screen, 5, 2);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_int_eq(screen->sel_end.x, 5);

	tsm_screen_newline(screen);
	tsm_screen_newline(screen);
	tsm_screen_newline(screen);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_ptr_ne(screen->sel_start.line, NULL);
	ck_assert_int_eq(screen->sel_end.x, 5);
	ck_assert_ptr_ne(screen->sel_end.line, NULL);
	ck_assert_ptr_ne(screen->sel_start.line, screen->sel_end.line);

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Hello World!\nLine 2\nLine 3", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}
END_TEST

START_TEST(test_screen_copy_lines_sb_scrolled_cut_off)
{
	struct tsm_screen *screen;
	int r, i;
	char *str = NULL;

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	/*
	 * Disable the sb. If a part of a selection is moved out of the sb it is
	 * cut off. Copying it should return the remaining part of the selection.
	 */
	tsm_screen_set_max_sb(screen, 0);

	write_string(screen, "   Hello World!");
	tsm_screen_newline(screen);
	write_string(screen, "Line 2");
	tsm_screen_newline(screen);
	write_string(screen, "Line 3");

	/* select "Hello World!" from left to right */
	tsm_screen_selection_start(screen, 3, 0);
	tsm_screen_selection_target(screen, 5, 2);

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert_int_eq(screen->sel_end.x, 5);

	/* force the selected text to scroll up */
	for (i = 0; i < 39; i++) {
		tsm_screen_newline(screen);
	}

	ck_assert_int_eq(screen->sel_start.x, 3);
	ck_assert(!is_in_scrollback(&screen->sel_start));
	ck_assert_int_eq(screen->sel_end.x, 5);
	ck_assert(!is_in_scrollback(&screen->sel_end));

	r = tsm_screen_selection_copy(screen, &str);
	ck_assert_ptr_ne(NULL, str);
	ck_assert_str_eq("Line 3", str);
	free(str);
	str = NULL;

	tsm_screen_unref(screen);
	screen = NULL;
}
END_TEST

static void check_sb_pos(struct tsm_screen *screen)
{
	struct line *line = shl_dlist_first(&screen->sb.list, struct line, list);
	int count = 0;

	if (!screen->sb.pos) {
		ck_assert_int_eq(screen->sb.pos_num, screen->sb.count);
		return;
	}

	ck_assert_int_le(screen->sb.pos_num, screen->sb.count);

	while (line && line != screen->sb.pos) {
		count++;
		line = shl_dlist_next(line, &screen->sb.list, list);
	}

	ck_assert_int_eq(count, screen->sb.pos_num);
}

static void write_random_string(struct tsm_screen *screen, int count)
{
	char str[201];
	int len = rand() % 100;
	int i, c;

	for (c = 0; c < count; c++) {
		for (i = 0; i < len; i++)
			str[i] = ' ' + rand() % 95;
		str[len] = '\0';
		write_string(screen, str);
		tsm_screen_newline(screen);
	}
}

START_TEST(test_screen_robustness)
{
	struct tsm_screen *screen;
	int sb_size = 50;
	int size_x, size_y;
	int r, i, j;
	char *str = NULL;


	srand(0x12345678);

	r = tsm_screen_new(&screen, NULL, NULL);
	ck_assert_int_eq(r, 0);

	r = tsm_screen_resize(screen, 80, 40);
	ck_assert_int_eq(r, 0);

	tsm_screen_set_max_sb(screen, sb_size);

	write_random_string(screen, 60);

	check_sb_pos(screen);

	for (i = 0; i < 100; i++) {
		size_x =  1 + rand() % 100;
		size_y =  1 + rand() % 100;
		r = tsm_screen_resize(screen, size_x, size_y);
		ck_assert_int_eq(r, 0);
		check_sb_pos(screen);
		tsm_screen_sb_up(screen, rand() % sb_size);
		check_sb_pos(screen);
		tsm_screen_sb_down(screen, rand() % sb_size);
		check_sb_pos(screen);
		tsm_screen_selection_start(screen, rand() % size_x, rand() % size_y);
		tsm_screen_selection_target(screen, rand() % size_x, rand() % size_y);
		for (j = 0; j < 20; j++) {
			r = tsm_screen_selection_copy(screen, &str);
			ck_assert_int_ge(r, 0);
			if (str) {
				free(str);
				str = NULL;
			}
			tsm_screen_sb_up(screen, sb_size );
			check_sb_pos(screen);
			tsm_screen_sb_down(screen, rand() % sb_size);
			check_sb_pos(screen);
		}
		write_random_string(screen, rand() % 100);
	}

	tsm_screen_unref(screen);
	screen = NULL;
}
END_TEST

TEST_DEFINE_CASE(misc)
	TEST(test_screen_copy_incomplete)
	TEST(test_screen_copy_one_cell)
	TEST(test_screen_copy_line)
	TEST(test_screen_copy_line_scrolled)
	TEST(test_screen_copy_lines)
	TEST(test_screen_copy_lines_scrolled)
	TEST(test_screen_copy_line_sb)
	TEST(test_screen_copy_line_sb_scrolled)
	TEST(test_screen_copy_line_sb_scrolled_invalid)
	TEST(test_screen_copy_lines_sb)
	TEST(test_screen_copy_lines_sb_scrolled)
	TEST(test_screen_copy_lines_sb_scrolled_cut_off)
	TEST(test_screen_robustness)
TEST_END_CASE

TEST_DEFINE(
	TEST_SUITE(selection,
		TEST_CASE(misc),
		TEST_END
	)
)
