package com.termux.terminal;

import java.io.UnsupportedEncodingException;

public class TerminalTest extends TerminalTestCase {

	public void testCursorPositioning() throws Exception {
		withTerminalSized(10, 10).placeCursorAndAssert(1, 2).placeCursorAndAssert(3, 5).placeCursorAndAssert(2, 2).enterString("A")
				.assertCursorAt(2, 3);
	}

	public void testScreen() throws UnsupportedEncodingException {
		withTerminalSized(3, 3);
		assertLinesAre("   ", "   ", "   ");

		assertEquals("", mTerminal.getScreen().getTranscriptText());
		enterString("hi").assertLinesAre("hi ", "   ", "   ");
		assertEquals("hi", mTerminal.getScreen().getTranscriptText());
		enterString("\r\nu");
		assertEquals("hi\nu", mTerminal.getScreen().getTranscriptText());
		mTerminal.reset();
		assertEquals("hi\nu", mTerminal.getScreen().getTranscriptText());

		withTerminalSized(3, 3).enterString("hello");
		assertEquals("hello", mTerminal.getScreen().getTranscriptText());
		enterString("\r\nworld");
		assertEquals("hello\nworld", mTerminal.getScreen().getTranscriptText());
	}

	public void testScrollDownInAltBuffer() {
		withTerminalSized(3, 3).enterString("\033[?1049h");
		enterString("\033[38;5;111m1\r\n");
		enterString("\033[38;5;112m2\r\n");
		enterString("\033[38;5;113m3\r\n");
		enterString("\033[38;5;114m4\r\n");
		enterString("\033[38;5;115m5");
		assertLinesAre("3  ", "4  ", "5  ");
		assertForegroundColorAt(0, 0, 113);
		assertForegroundColorAt(1, 0, 114);
		assertForegroundColorAt(2, 0, 115);
	}

	public void testMouseClick() throws Exception {
		withTerminalSized(10, 10);
		assertFalse(mTerminal.isMouseTrackingActive());
		enterString("\033[?1000h");
		assertTrue(mTerminal.isMouseTrackingActive());
		enterString("\033[?1000l");
		assertFalse(mTerminal.isMouseTrackingActive());
		enterString("\033[?1000h");
		assertTrue(mTerminal.isMouseTrackingActive());

		enterString("\033[?1006h");
		mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 3, 4, true);
		assertEquals("\033[<0;3;4M", mOutput.getOutputAndClear());
		mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 3, 4, false);
		assertEquals("\033[<0;3;4m", mOutput.getOutputAndClear());

		// When the client says that a click is outside (which could happen when pixels are outside
		// the terminal area, see https://github.com/termux/termux-app/issues/501) the terminal
		// sends a click at the edge.
		mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 0, 0, true);
		assertEquals("\033[<0;1;1M", mOutput.getOutputAndClear());
		mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 11, 11, false);
		assertEquals("\033[<0;10;10m", mOutput.getOutputAndClear());
	}

	public void testNormalization() throws UnsupportedEncodingException {
		// int lowerCaseN = 0x006E;
		// int combiningTilde = 0x0303;
		// int combined = 0x00F1;
		withTerminalSized(3, 3).assertLinesAre("   ", "   ", "   ");
		enterString("\u006E\u0303");
		assertEquals(1, WcWidth.width("\u006E\u0303".toCharArray(), 0));
		// assertEquals("\u00F1  ", new String(mTerminal.getScreen().getLine(0)));
		assertLinesAre("\u006E\u0303  ", "   ", "   ");
	}

	/** On "\e[18t" xterm replies with "\e[8;${HEIGHT};${WIDTH}t" */
	public void testReportTerminalSize() throws Exception {
		withTerminalSized(5, 5);
		assertEnteringStringGivesResponse("\033[18t", "\033[8;5;5t");
		for (int width = 3; width < 12; width++) {
			for (int height = 3; height < 12; height++) {
				resize(width, height);
				assertEnteringStringGivesResponse("\033[18t", "\033[8;" + height + ";" + width + "t");
			}
		}
	}

	/** Device Status Report (DSR) and Report Cursor Position (CPR). */
	public void testDeviceStatusReport() throws Exception {
		withTerminalSized(5, 5);
		assertEnteringStringGivesResponse("\033[5n", "\033[0n");

		assertEnteringStringGivesResponse("\033[6n", "\033[1;1R");
		enterString("AB");
		assertEnteringStringGivesResponse("\033[6n", "\033[1;3R");
		enterString("\r\n");
		assertEnteringStringGivesResponse("\033[6n", "\033[2;1R");
	}

	/** Test the cursor shape changes using DECSCUSR. */
	public void testSetCursorStyle() throws Exception {
		withTerminalSized(5, 5);
		assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
		enterString("\033[3 q");
		assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
		enterString("\033[5 q");
		assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR, mTerminal.getCursorStyle());
		enterString("\033[0 q");
		assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
		enterString("\033[6 q");
		assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR, mTerminal.getCursorStyle());
		enterString("\033[4 q");
		assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
		enterString("\033[1 q");
		assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
		enterString("\033[4 q");
		assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
		enterString("\033[2 q");
		assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
	}

	public void testPaste() {
		withTerminalSized(5, 5);
		mTerminal.paste("hi");
		assertEquals("hi", mOutput.getOutputAndClear());

		enterString("\033[?2004h");
		mTerminal.paste("hi");
		assertEquals("\033[200~" + "hi" + "\033[201~", mOutput.getOutputAndClear());

		enterString("\033[?2004l");
		mTerminal.paste("hi");
		assertEquals("hi", mOutput.getOutputAndClear());
	}

	public void testSelectGraphics() {
		selectGraphicsTestRun(';');
		selectGraphicsTestRun(':');
	}

	public void selectGraphicsTestRun(char separator) {
		withTerminalSized(5, 5);
		enterString("\033[31m");
		assertEquals(mTerminal.mForeColor, 1);
		enterString("\033[32m");
		assertEquals(mTerminal.mForeColor, 2);
		enterString("\033[43m");
		assertEquals(2, mTerminal.mForeColor);
		assertEquals(3, mTerminal.mBackColor);

		// SGR 0 should reset both foreground and background color.
		enterString("\033[0m");
		assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
		assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);

		// Test CSI resetting to default if sequence starts with ; or has sequential ;;
        // Check TerminalEmulator.parseArg()
        enterString("\033[31m\033[m");
        assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
        enterString("\033[31m\033[;m".replace(';', separator));
        assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
        enterString("\033[31m\033[0m");
        assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
        enterString("\033[31m\033[0;m".replace(';', separator));
        assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
        enterString("\033[31;;m");
        assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
        enterString("\033[31::m");
        assertEquals(1, mTerminal.mForeColor);
        enterString("\033[31;m");
        assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
        enterString("\033[31:m");
        assertEquals(1, mTerminal.mForeColor);
        enterString("\033[31;;41m");
        assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
        assertEquals(1, mTerminal.mBackColor);
        enterString("\033[0m");
        assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);

		// 256 colors:
		enterString("\033[38;5;119m".replace(';', separator));
		assertEquals(119, mTerminal.mForeColor);
		assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
		enterString("\033[48;5;129m".replace(';', separator));
		assertEquals(119, mTerminal.mForeColor);
		assertEquals(129, mTerminal.mBackColor);

		// Invalid parameter:
		enterString("\033[48;8;129m".replace(';', separator));
		assertEquals(119, mTerminal.mForeColor);
		assertEquals(129, mTerminal.mBackColor);

		// Multiple parameters at once:
		enterString("\033[38;5;178".replace(';', separator) + ";" + "48;5;179m".replace(';', separator));
		assertEquals(178, mTerminal.mForeColor);
		assertEquals(179, mTerminal.mBackColor);

		// Omitted parameter means zero:
		enterString("\033[38;5;m".replace(';', separator));
		assertEquals(0, mTerminal.mForeColor);
		assertEquals(179, mTerminal.mBackColor);
		enterString("\033[48;5;m".replace(';', separator));
		assertEquals(0, mTerminal.mForeColor);
		assertEquals(0, mTerminal.mBackColor);

		// 24 bit colors:
		enterString(("\033[0m")); // Reset fg and bg colors.
		enterString("\033[38;2;255;127;2m".replace(';', separator));
		int expectedForeground = 0xff000000 | (255 << 16) | (127 << 8) | 2;
		assertEquals(expectedForeground, mTerminal.mForeColor);
		assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
		enterString("\033[48;2;1;2;254m".replace(';', separator));
		int expectedBackground = 0xff000000 | (1 << 16) | (2 << 8) | 254;
		assertEquals(expectedForeground, mTerminal.mForeColor);
		assertEquals(expectedBackground, mTerminal.mBackColor);

		// 24 bit colors, set fg and bg at once:
		enterString(("\033[0m")); // Reset fg and bg colors.
		assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
		assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
		enterString("\033[38;2;255;127;2".replace(';', separator) + ";" + "48;2;1;2;254m".replace(';', separator));
		assertEquals(expectedForeground, mTerminal.mForeColor);
		assertEquals(expectedBackground, mTerminal.mBackColor);

		// 24 bit colors, invalid input:
		enterString("\033[38;2;300;127;2;48;2;1;300;254m".replace(';', separator));
		assertEquals(expectedForeground, mTerminal.mForeColor);
		assertEquals(expectedBackground, mTerminal.mBackColor);

		// 24 bit colors, omitted parameter means zero:
		enterString("\033[38;2;255;127;m".replace(';', separator));
		expectedForeground = 0xff000000 | (255 << 16) | (127 << 8);
		assertEquals(expectedForeground, mTerminal.mForeColor);
		assertEquals(expectedBackground, mTerminal.mBackColor);
		enterString("\033[38;2;123;;77m".replace(';', separator));
		expectedForeground = 0xff000000 | (123 << 16) | 77;
		assertEquals(expectedForeground, mTerminal.mForeColor);
		assertEquals(expectedBackground, mTerminal.mBackColor);

		// 24 bit colors, extra sub-parameters are skipped:
		expectedForeground = 0xff000000 | (255 << 16) | (127 << 8) | 2;
		enterString("\033[0;38:2:255:127:2:48:2:1:2:254m");
		assertEquals(expectedForeground, mTerminal.mForeColor);
		assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
	}

	public void testBackgroundColorErase() {
		final int rows = 3;
		final int cols = 3;
		withTerminalSized(cols, rows);
		for (int r = 0; r < rows; r++) {
			for (int c = 0; c < cols; c++) {
				long style = getStyleAt(r, c);
				assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.decodeForeColor(style));
				assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, TextStyle.decodeBackColor(style));
			}
		}
		// Foreground color to 119:
		enterString("\033[38;5;119m");
		// Background color to 129:
		enterString("\033[48;5;129m");
		// Clear with ED, Erase in Display:
		enterString("\033[2J");
		for (int r = 0; r < rows; r++) {
			for (int c = 0; c < cols; c++) {
				long style = getStyleAt(r, c);
				assertEquals(119, TextStyle.decodeForeColor(style));
				assertEquals(129, TextStyle.decodeBackColor(style));
			}
		}
		// Background color to 139:
		enterString("\033[48;5;139m");
		// Insert two blank lines.
		enterString("\033[2L");
		for (int r = 0; r < rows; r++) {
			for (int c = 0; c < cols; c++) {
				long style = getStyleAt(r, c);
				assertEquals((r == 0 || r == 1) ? 139 : 129, TextStyle.decodeBackColor(style));
			}
		}

		withTerminalSized(cols, rows);
		// Background color to 129:
		enterString("\033[48;5;129m");
		// Erase two characters, filling them with background color:
		enterString("\033[2X");
		assertEquals(129, TextStyle.decodeBackColor(getStyleAt(0, 0)));
		assertEquals(129, TextStyle.decodeBackColor(getStyleAt(0, 1)));
		assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, TextStyle.decodeBackColor(getStyleAt(0, 2)));
	}

	public void testParseColor() {
		assertEquals(0xFF0000FA, TerminalColors.parse("#0000FA"));
		assertEquals(0xFF000000, TerminalColors.parse("#000000"));
		assertEquals(0xFF000000, TerminalColors.parse("#000"));
		assertEquals(0xFF000000, TerminalColors.parse("#000000000"));
		assertEquals(0xFF53186f, TerminalColors.parse("#53186f"));

		assertEquals(0xFFFF00FF, TerminalColors.parse("rgb:F/0/F"));
		assertEquals(0xFF0000FA, TerminalColors.parse("rgb:00/00/FA"));
		assertEquals(0xFF53186f, TerminalColors.parse("rgb:53/18/6f"));

		assertEquals(0, TerminalColors.parse("invalid_0000FA"));
		assertEquals(0, TerminalColors.parse("#3456"));
	}

	/** The ncurses library still uses this. */
	public void testLineDrawing() {
		// 016 - shift out / G1. 017 - shift in / G0. "ESC ) 0" - use line drawing for G1
		withTerminalSized(4, 2).enterString("q\033)0q\016q\017q").assertLinesAre("qq─q", "    ");
		// "\0337", saving cursor should save G0, G1 and invoked charset and "ESC 8" should restore.
		withTerminalSized(4, 2).enterString("\033)0\016qqq\0337\017\0338q").assertLinesAre("────", "    ");
	}

	public void testSoftTerminalReset() {
		// See http://vt100.net/docs/vt510-rm/DECSTR and https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=650304
		// "\033[?7l" is DECRST to disable wrap-around, and DECSTR ("\033[!p") should reset it.
		withTerminalSized(3, 3).enterString("\033[?7lABCD").assertLinesAre("ABD", "   ", "   ");
		enterString("\033[!pEF").assertLinesAre("ABE", "F  ", "   ");
	}

	public void testBel() {
		withTerminalSized(3, 3);
		assertEquals(0, mOutput.bellsRung);
		enterString("\07");
		assertEquals(1, mOutput.bellsRung);
		enterString("hello\07");
		assertEquals(2, mOutput.bellsRung);
		enterString("\07hello");
		assertEquals(3, mOutput.bellsRung);
		enterString("hello\07world");
		assertEquals(4, mOutput.bellsRung);
	}

	public void testAutomargins() throws UnsupportedEncodingException {
		withTerminalSized(3, 3).enterString("abc").assertLinesAre("abc", "   ", "   ").assertCursorAt(0, 2);
		enterString("d").assertLinesAre("abc", "d  ", "   ").assertCursorAt(1, 1);

		withTerminalSized(3, 3).enterString("abc\r ").assertLinesAre(" bc", "   ", "   ").assertCursorAt(0, 1);
	}

	public void testTab() {
		withTerminalSized(11, 2).enterString("01234567890\r\tXX").assertLinesAre("01234567XX0", "           ");
		withTerminalSized(11, 2).enterString("01234567890\033[44m\r\tXX").assertLinesAre("01234567XX0", "           ");
	}

}
