/* Copyright 2013 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Test console editing and history.
 */

#include "common.h"
#include "console.h"
#include "test_util.h"
#include "timer.h"
#include "util.h"

static int cmd_1_call_cnt;
static int cmd_2_call_cnt;

static int command_test_1(int argc, char **argv)
{
	cmd_1_call_cnt++;
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(test1, command_test_1, NULL, NULL);

static int command_test_2(int argc, char **argv)
{
	cmd_2_call_cnt++;
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(test2, command_test_2, NULL, NULL);

/*****************************************************************************/
/* Test utilities */

enum arrow_key_t {
	ARROW_UP = 0,
	ARROW_DOWN,
	ARROW_RIGHT,
	ARROW_LEFT,
};

static void arrow_key(enum arrow_key_t k, int repeat)
{
	static char seq[4] = {0x1B, '[', 0, 0};
	seq[2] = 'A' + k;
	while (repeat--)
		UART_INJECT(seq);
}

static void delete_key(void)
{
	UART_INJECT("\x1b[3~");
}

static void home_key(void)
{
	UART_INJECT("\x1b[1~");
}

static void end_key(void)
{
	UART_INJECT("\x1bOF");
}

static void ctrl_key(char c)
{
	static char seq[2] = {0, 0};
	seq[0] = c - '@';
	UART_INJECT(seq);
}

/*
 * Helper function to compare multiline strings. When comparing, CR's are
 * ignored.
 */
static int compare_multiline_string(const char *s1, const char *s2)
{
	do {
		while (*s1 == '\r')
			++s1;
		while (*s2 == '\r')
			++s2;
		if (*s1 != *s2)
			return 1;
		if (*s1 == 0 && *s2 == 0)
			break;
		++s1;
		++s2;
	} while (1);

	return 0;
}

/*****************************************************************************/
/* Tests */

static int test_backspace(void)
{
	cmd_1_call_cnt = 0;
	UART_INJECT("testx\b1\n");
	msleep(30);
	TEST_CHECK(cmd_1_call_cnt == 1);
}

static int test_insert_char(void)
{
	cmd_1_call_cnt = 0;
	UART_INJECT("tet1");
	arrow_key(ARROW_LEFT, 2);
	UART_INJECT("s\n");
	msleep(30);
	TEST_CHECK(cmd_1_call_cnt == 1);
}

static int test_delete_char(void)
{
	cmd_1_call_cnt = 0;
	UART_INJECT("testt1");
	arrow_key(ARROW_LEFT, 1);
	UART_INJECT("\b\n");
	msleep(30);
	TEST_CHECK(cmd_1_call_cnt == 1);
}

static int test_insert_delete_char(void)
{
	cmd_1_call_cnt = 0;
	UART_INJECT("txet1");
	arrow_key(ARROW_LEFT, 4);
	delete_key();
	arrow_key(ARROW_RIGHT, 1);
	UART_INJECT("s\n");
	msleep(30);
	TEST_CHECK(cmd_1_call_cnt == 1);
}

static int test_home_end_key(void)
{
	cmd_1_call_cnt = 0;
	UART_INJECT("est");
	home_key();
	UART_INJECT("t");
	end_key();
	UART_INJECT("1\n");
	msleep(30);
	TEST_CHECK(cmd_1_call_cnt == 1);
}

static int test_ctrl_k(void)
{
	cmd_1_call_cnt = 0;
	UART_INJECT("test123");
	arrow_key(ARROW_LEFT, 2);
	ctrl_key('K');
	UART_INJECT("\n");
	msleep(30);
	TEST_CHECK(cmd_1_call_cnt == 1);
}

static int test_history_up(void)
{
	cmd_1_call_cnt = 0;
	UART_INJECT("test1\n");
	msleep(30);
	arrow_key(ARROW_UP, 1);
	UART_INJECT("\n");
	msleep(30);
	TEST_CHECK(cmd_1_call_cnt == 2);
}

static int test_history_up_up(void)
{
	cmd_1_call_cnt = 0;
	cmd_2_call_cnt = 0;
	UART_INJECT("test1\n");
	msleep(30);
	UART_INJECT("test2\n");
	msleep(30);
	arrow_key(ARROW_UP, 2);
	UART_INJECT("\n");
	msleep(30);
	TEST_CHECK(cmd_1_call_cnt == 2 && cmd_2_call_cnt == 1);
}

static int test_history_up_up_down(void)
{
	cmd_1_call_cnt = 0;
	cmd_2_call_cnt = 0;
	UART_INJECT("test1\n");
	msleep(30);
	UART_INJECT("test2\n");
	msleep(30);
	arrow_key(ARROW_UP, 2);
	arrow_key(ARROW_DOWN, 1);
	UART_INJECT("\n");
	msleep(30);
	TEST_CHECK(cmd_1_call_cnt == 1 && cmd_2_call_cnt == 2);
}

static int test_history_edit(void)
{
	cmd_1_call_cnt = 0;
	cmd_2_call_cnt = 0;
	UART_INJECT("test1\n");
	msleep(30);
	arrow_key(ARROW_UP, 1);
	UART_INJECT("\b2\n");
	msleep(30);
	TEST_CHECK(cmd_1_call_cnt == 1 && cmd_2_call_cnt == 1);
}

static int test_history_stash(void)
{
	cmd_1_call_cnt = 0;
	cmd_2_call_cnt = 0;
	UART_INJECT("test1\n");
	msleep(30);
	UART_INJECT("test");
	arrow_key(ARROW_UP, 1);
	arrow_key(ARROW_DOWN, 1);
	UART_INJECT("2\n");
	msleep(30);
	TEST_CHECK(cmd_1_call_cnt == 1 && cmd_2_call_cnt == 1);
}

static int test_history_list(void)
{
	const char *exp_output = "history\n" /* Input command */
				 "test3\n"   /* Output 4 last commands */
				 "test4\n"
				 "test5\n"
				 "history\n"
				 "> ";

	UART_INJECT("test1\n");
	UART_INJECT("test2\n");
	UART_INJECT("test3\n");
	UART_INJECT("test4\n");
	UART_INJECT("test5\n");
	msleep(30);
	test_capture_console(1);
	UART_INJECT("history\n");
	msleep(30);
	test_capture_console(0);
	TEST_ASSERT(compare_multiline_string(test_get_captured_console(),
					     exp_output) == 0);

	return EC_SUCCESS;
}

static int test_output_channel(void)
{
	UART_INJECT("chan save\n");
	msleep(30);
	UART_INJECT("chan 0\n");
	msleep(30);
	test_capture_console(1);
	cprintf(CC_SYSTEM, "shouldn't see this\n");
	cputs(CC_TASK, "shouldn't see this either\n");
	cflush();
	test_capture_console(0);
	TEST_ASSERT(compare_multiline_string(test_get_captured_console(),
					     "") == 0);
	UART_INJECT("chan restore\n");
	msleep(30);
	test_capture_console(1);
	cprintf(CC_SYSTEM, "see me\n");
	cputs(CC_TASK, "me as well\n");
	cflush();
	test_capture_console(0);
	TEST_ASSERT(compare_multiline_string(test_get_captured_console(),
					     "see me\nme as well\n") == 0);

	return EC_SUCCESS;
}

void run_test(void)
{
	test_reset();

	RUN_TEST(test_backspace);
	RUN_TEST(test_insert_char);
	RUN_TEST(test_delete_char);
	RUN_TEST(test_insert_delete_char);
	RUN_TEST(test_home_end_key);
	RUN_TEST(test_ctrl_k);
	RUN_TEST(test_history_up);
	RUN_TEST(test_history_up_up);
	RUN_TEST(test_history_up_up_down);
	RUN_TEST(test_history_edit);
	RUN_TEST(test_history_stash);
	RUN_TEST(test_history_list);
	RUN_TEST(test_output_channel);

	test_print_result();
}
