# Copyright 2022-2023 Free Software Foundation, Inc.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Unit-test Term, the testsuite's terminal implementation that is used to test
# the TUI.

tuiterm_env

# Validate the cursor position.
#
# EXPECTED_CUR_COL and EXPECTED_CUR_ROW are the expected cursor column and row
# positions.

proc check_cursor_position { test expected_cur_col expected_cur_row } {
    with_test_prefix $test {
	gdb_assert {$expected_cur_col == ${Term::_cur_col}} "column"
	gdb_assert {$expected_cur_row == ${Term::_cur_row}} "row"
    }
}

# Validate the terminal contents and cursor position.
#
# EXPECTED_CONTENTS must be a list of strings, one element for each terminal
# line.
#
# EXPECTED_CUR_COL and EXPECTED_CUR_ROW are passed to check_cursor_position.

proc check { test expected_contents expected_cur_col expected_cur_row } {
    with_test_prefix $test {
	# Check term contents.
	set regexp "^"

	foreach line $expected_contents {
	    append regexp $line
	    append regexp "\n"
	}

	append regexp "$"
	Term::check_contents "contents" $regexp
    }

    check_cursor_position $test $expected_cur_col $expected_cur_row
}

proc setup_terminal { cols rows } {
    setenv TERM ansi
    Term::_setup $rows $cols
}

# Most tests are fine with a small terminal.  This proc initializes the terminal
# with 8 columns and 4 rows, with the following content:
#
#   abcdefgh
#   ijklmnop
#   qrstuvwx
#   yz01234
#
# The bottom right cell is left blank: trying to write to it using _insert
# would move the cursor past the screen, causing a scroll, but scrolling is
# not implemented at the moment.

proc setup_small {} {
    setup_terminal 8 4

    Term::_insert "abcdefgh"
    Term::_insert "ijklmnop"
    Term::_insert "qrstuvwx"
    Term::_insert "yz01234"

    check "check after setup" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 7 3
}

# Some tests require a larger terminal.  This proc initializes the terminal with
# 80 columns and 25 rows, but leaves the content empty.

proc setup_large {} {
    setup_terminal 80 25
}

# Each proc below tests a control character or sequence individually.

proc test_backspace {} {
    # Note: the backspace (BS) control character only moves the cursor left,
    # it does not delete characters.

    Term::_move_cursor 1 2

    Term::_ctl_0x08
    check "backspace one" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 2

    # Cursor should not move if it is already at column 0.
    Term::_ctl_0x08
    check "backspace 2" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 2
}

proc test_linefeed { } {
    Term::_move_cursor 1 2
    Term::_ctl_0x0a
    check "linefeed" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 1 3
}

proc test_carriage_return { } {
    Term::_move_cursor 1 2
    Term::_ctl_0x0d
    check "carriage return 1" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 2

    Term::_ctl_0x0d
    check "carriage return 2" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 2
}

proc test_insert_characters { } {
    Term::_move_cursor 1 2

    Term::_csi_@
    check "insert characters 1" {
	"abcdefgh"
	"ijklmnop"
	"q rstuvw"
	"yz01234 "
    } 1 2

    Term::_csi_@ 20
    check "insert characters 2" {
	"abcdefgh"
	"ijklmnop"
	"q       "
	"yz01234 "
    } 1 2

    Term::_move_cursor 0 1
    Term::_csi_@ 6
    check "insert characters 3" {
	"abcdefgh"
	"      ij"
	"q       "
	"yz01234 "
    } 0 1
}

proc test_pan_down { } {
    Term::_move_cursor 1 2
    Term::_csi_S
    check "pan down, default arg" {
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
	"        "
    } 1 2

    Term::_csi_S 2
    check "pan down, explicit arg" {
	"yz01234 "
	"        "
	"        "
	"        "
    } 1 2

    Term::_csi_S 100
    check "pan down, excessive arg" {
	"        "
	"        "
	"        "
	"        "
    } 1 2
}

proc test_pan_up { } {
    Term::_move_cursor 1 2
    Term::_csi_T
    check "pan down, default arg" {
	"        "
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
    } 1 2

    Term::_csi_T 2
    check "pan down, explicit arg" {
	"        "
	"        "
	"        "
	"abcdefgh"
    } 1 2

    Term::_csi_T 100
    check "pan down, excessive arg" {
	"        "
	"        "
	"        "
	"        "
    } 1 2
}

proc test_cursor_up { } {
    Term::_move_cursor 2 3

    Term::_csi_A
    check "cursor up 1" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 2 2

    Term::_csi_A 2
    check "cursor up 2" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 2 0

    Term::_csi_A 1
    check "cursor up 3" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 2 0
}

proc test_cursor_down { } {
    Term::_move_cursor 1 0

    Term::_csi_B
    check "cursor down 1" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 1 1

    Term::_csi_B 2
    check "cursor down 2" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 1 3

    Term::_csi_B 1
    check "cursor down 3" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 1 3
}

proc test_cursor_forward { } {
    Term::_move_cursor 0 1

    Term::_csi_C
    check "cursor forward 1" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 1 1

    Term::_csi_C 6
    check "cursor forward 2" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 7 1

    Term::_csi_C 1
    check "cursor forward 3" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 7 1
}

proc test_cursor_backward { } {
    Term::_move_cursor 7 1

    Term::_csi_D
    check "cursor backward 1" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 6 1

    Term::_csi_D 6
    check "cursor backward 2" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 1

    Term::_csi_D 1
    check "cursor backward 3" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 1
}

proc test_cursor_next_line { } {
    Term::_move_cursor 2 0

    Term::_csi_E
    check "cursor next line 1" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 1

    Term::_move_cursor 2 1
    Term::_csi_E 2
    check "cursor next line 2" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 3

    Term::_move_cursor 2 3
    Term::_csi_E 1
    check "cursor next line 3" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 3
}

proc test_cursor_previous_line { } {
    Term::_move_cursor 2 3

    Term::_csi_F
    check "cursor previous line 1" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 2

    Term::_move_cursor 2 2
    Term::_csi_F 2
    check "cursor previous line 2" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 0

    Term::_move_cursor 2 0
    Term::_csi_F 1
    check "cursor previous line 3" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 0
}

proc test_horizontal_absolute { } {
    Term::_move_cursor 2 2
    Term::_csi_G
    check "cursor horizontal absolute 1" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 0 2

    Term::_move_cursor 2 2
    Term::_csi_G 4
    check "cursor horizontal absolute 2" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 3 2
}

proc test_cursor_position { } {
    Term::_move_cursor 1 1

    Term::_csi_H 3 5
    check "cursor horizontal absolute 2" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 4 2
}

proc test_cursor_horizontal_forward_tabulation { } {
    Term::_move_cursor 5 2
    Term::_csi_I
    check_cursor_position "default param" 8 2

    Term::_csi_I 2
    check_cursor_position "explicit param" 24 2

    Term::_move_cursor 77 2
    Term::_csi_I 5
    check_cursor_position "try to go past the end" 79 2
}

proc test_erase_in_display { } {
    Term::_move_cursor 5 2
    Term::_csi_J
    check "erase in display, cursor to end with default param" {
	"abcdefgh"
	"ijklmnop"
	"qrstu   "
	"        "
    } 5 2

    Term::_move_cursor 3 2
    Term::_csi_J 0
    check "erase in display, cursor to end with explicit param" {
	"abcdefgh"
	"ijklmnop"
	"qrs     "
	"        "
    } 3 2

    Term::_move_cursor 2 1
    Term::_csi_J 1
    check "erase in display, beginning to cursor" {
	"        "
	"   lmnop"
	"qrs     "
	"        "
    } 2 1

    Term::_move_cursor 5 1
    Term::_csi_J 2
    check "erase in display, entire display" {
	"        "
	"        "
	"        "
	"        "
    } 5 1
}

proc test_erase_in_line { } {
    Term::_move_cursor 5 2
    Term::_csi_K
    check "erase in line, cursor to end with default param" {
	"abcdefgh"
	"ijklmnop"
	"qrstu   "
	"yz01234 "
    } 5 2

    Term::_move_cursor 3 2
    Term::_csi_K 0
    check "erase in line, cursor to end with explicit param" {
	"abcdefgh"
	"ijklmnop"
	"qrs     "
	"yz01234 "
    } 3 2

    Term::_move_cursor 3 1
    Term::_csi_K 1
    check "erase in line, beginning to cursor" {
	"abcdefgh"
	"    mnop"
	"qrs     "
	"yz01234 "
    } 3 1

    Term::_move_cursor 3 0
    Term::_csi_K 2
    check "erase in line, entire line" {
	"        "
	"    mnop"
	"qrs     "
	"yz01234 "
    } 3 0
}

proc test_delete_line { } {
    Term::_move_cursor 3 2
    Term::_csi_M
    check "delete line, default param" {
	"abcdefgh"
	"ijklmnop"
	"yz01234 "
	"        "
    } 3 2

    Term::_move_cursor 3 0
    Term::_csi_M 2
    check "delete line, explicit param" {
	"yz01234 "
	"        "
	"        "
	"        "
    } 3 0
}

proc test_delete_character { } {
    Term::_move_cursor 2 1

    Term::_csi_P
    check "delete character, default param" {
	"abcdefgh"
	"ijlmnop "
	"qrstuvwx"
	"yz01234 "
    } 2 1

    Term::_csi_P 3
    check "delete character, explicit param" {
	"abcdefgh"
	"ijop    "
	"qrstuvwx"
	"yz01234 "
    } 2 1

    Term::_csi_P 12
    check "delete character, more than number of columns" {
	"abcdefgh"
	"ij      "
	"qrstuvwx"
	"yz01234 "
    } 2 1
}

proc test_erase_character { } {
    Term::_move_cursor 3 2
    Term::_csi_X
    check "erase character, default param" {
	"abcdefgh"
	"ijklmnop"
	"qrs uvwx"
	"yz01234 "
    } 3 2

    Term::_move_cursor 1 3
    Term::_csi_X 4
    check "erase character, explicit param" {
	"abcdefgh"
	"ijklmnop"
	"qrs uvwx"
	"y    34 "
    } 1 3
}

proc test_cursor_backward_tabulation { } {
    Term::_move_cursor 77 2
    Term::_csi_Z
    check_cursor_position "default param" 72 2

    Term::_csi_Z 2
    check_cursor_position "explicit param" 56 2

    Term::_move_cursor 6 2
    Term::_csi_Z 12
    check_cursor_position "try to go past the beginning" 0 2
}

proc test_repeat { } {
    Term::_move_cursor 2 1
    set Term::_last_char X

    Term::_csi_b 3
    check "repeat" {
	"abcdefgh"
	"ijXXXnop"
	"qrstuvwx"
	"yz01234 "
    } 5 1
}

proc test_vertical_line_position_absolute { } {
    Term::_move_cursor 2 1

    Term::_csi_d
    check "default param" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 2 0

    Term::_csi_d 3
    check "explicit param" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 2 2

    Term::_csi_d 100
    check "try to move off-display" {
	"abcdefgh"
	"ijklmnop"
	"qrstuvwx"
	"yz01234 "
    } 2 3
}

proc test_insert_line { } {
    Term::_move_cursor 2 1
    Term::_csi_L
    check "insert line, default param" {
	"abcdefgh"
	"        "
	"ijklmnop"
	"qrstuvwx"
    } 2 1

    Term::_move_cursor 2 0
    Term::_csi_L 2
    check "insert line, explicit param" {
	"        "
	"        "
	"abcdefgh"
	"        "
    } 2 0

    Term::_csi_L 12
    check "insert line, insert more lines than display has" {
	"        "
	"        "
	"        "
	"        "
    } 2 0
}

# Run proc TEST_PROC_NAME with a "small" terminal.

proc run_one_test_small { test_proc_name } {
    save_vars { env(TERM) stty_init } {
	setup_small
	eval $test_proc_name
    }
}

# Run proc TEST_PROC_NAME with a "large" terminal.

proc run_one_test_large { test_proc_name } {
    save_vars { env(TERM) stty_init } {
	setup_large
	eval $test_proc_name
    }
}

foreach_with_prefix test {
    test_backspace
    test_linefeed
    test_carriage_return
    test_insert_characters
    test_cursor_up
    test_cursor_down
    test_cursor_forward
    test_cursor_backward
    test_cursor_next_line
    test_cursor_previous_line
    test_horizontal_absolute
    test_cursor_position
    test_erase_in_display
    test_erase_in_line
    test_delete_line
    test_delete_character
    test_erase_character
    test_repeat
    test_vertical_line_position_absolute
    test_insert_line
    test_pan_up
    test_pan_down
} {
    run_one_test_small $test
}

foreach_with_prefix test {
    test_cursor_horizontal_forward_tabulation
    test_cursor_backward_tabulation
} {
    run_one_test_large $test
}