#include <stic.h>

#include <string.h> /* strdup() */

#include "../../src/cfg/config.h"
#include "../../src/engine/keys.h"
#include "../../src/engine/mode.h"
#include "../../src/int/file_magic.h"
#include "../../src/lua/vlua.h"
#include "../../src/modes/cmdline.h"
#include "../../src/modes/modes.h"
#include "../../src/modes/visual.h"
#include "../../src/modes/wk.h"
#include "../../src/ui/tabs.h"
#include "../../src/ui/ui.h"
#include "../../src/utils/dynarray.h"
#include "../../src/utils/str.h"
#include "../../src/filelist.h"

#include <test-utils.h>

#include "asserts.h"

static int has_mime_type_detection(void);
static int has_no_mime_type_detection(void);

static vlua_t *vlua;
static line_stats_t *stats;

SETUP_ONCE()
{
	stub_colmgr();

	stats = get_line_stats();
}

TEARDOWN_ONCE()
{
	curr_view = NULL;
	other_view = NULL;
}

SETUP()
{
	vlua = vlua_init();

	view_setup(&lwin);
	strcpy(lwin.curr_dir, "/lwin");
	lwin.list_rows = 2;
	lwin.list_pos = 1;
	lwin.top_line = 0;
	lwin.dir_entry = dynarray_cextend(NULL,
			lwin.list_rows*sizeof(*lwin.dir_entry));
	lwin.dir_entry[0].name = strdup("file0");
	lwin.dir_entry[0].origin = &lwin.curr_dir[0];
	lwin.dir_entry[1].name = strdup("file1");
	lwin.dir_entry[1].origin = &lwin.curr_dir[0];

	view_setup(&rwin);
	strcpy(rwin.curr_dir, "/rwin");
	rwin.list_rows = 1;
	rwin.list_pos = 0;
	rwin.top_line = 0;
	rwin.dir_entry = dynarray_cextend(NULL,
			rwin.list_rows*sizeof(*rwin.dir_entry));
	rwin.dir_entry[0].name = strdup("ofile0");
	rwin.dir_entry[0].origin = &rwin.curr_dir[0];

	curr_view = &lwin;
	other_view = &rwin;
}

TEARDOWN()
{
	view_teardown(&lwin);
	view_teardown(&rwin);

	vlua_finish(vlua);
}

TEST(vifmview_properties)
{
	GLUA_EQ(vlua, "2", "print(vifm.currview().entrycount)");
	GLUA_EQ(vlua, "2", "print(vifm.currview().currententry)");
	GLUA_EQ(vlua, "/lwin", "print(vifm.currview().cwd)");
}

TEST(vifmview_entry)
{
	GLUA_EQ(vlua, "nil", "print(vifm.currview():entry(0))");
	GLUA_EQ(vlua, "file0", "print(vifm.currview():entry(1).name)");
	GLUA_EQ(vlua, "file1", "print(vifm.currview():entry(2).name)");
	GLUA_EQ(vlua, "ofile0", "print(vifm.otherview():entry(1).name)");
	GLUA_EQ(vlua, "nil", "print(vifm.currview():entry(3))");
}

TEST(vifmview_cursor)
{
	GLUA_EQ(vlua, "2", "print(vifm.currview().cursor.pos)");
	GLUA_EQ(vlua, "file1", "print(vifm.currview().cursor:entry().name)");
	GLUA_EQ(vlua, "nil", "print(vifm.currview().cursor.nosuchfield)");
	GLUA_EQ(vlua, "", "vifm.currview().cursor.nosuchfield = 10");
}

TEST(vifmview_set_cursor_pos_in_normal_mode)
{
	GLUA_EQ(vlua, "", "vifm.currview().cursor.pos = 1");
	GLUA_EQ(vlua, "1", "print(vifm.currview().cursor.pos)");

	GLUA_EQ(vlua, "", "vifm.currview().cursor.pos = 10");
	GLUA_EQ(vlua, "2", "print(vifm.currview().cursor.pos)");

	GLUA_EQ(vlua, "", "vifm.currview().cursor.pos = 0");
	GLUA_EQ(vlua, "1", "print(vifm.currview().cursor.pos)");

	BLUA_ENDS(vlua,
			"bad argument #3 to 'newindex' (number has no integer representation)",
			"vifm.currview().cursor.pos = 1.5");
}

TEST(vifmview_set_cursor_pos_in_visual_mode)
{
	modes_init();
	opt_handlers_setup();

	modvis_enter(VS_NORMAL);

	GLUA_EQ(vlua, "", "vifm.currview().cursor.pos = 1");

	assert_true(vle_mode_is(VISUAL_MODE));
	assert_int_equal(0, lwin.list_pos);
	assert_true(lwin.dir_entry[0].selected);
	assert_true(lwin.dir_entry[1].selected);

	GLUA_EQ(vlua, "1", "print(vifm.currview().cursor.pos)");

	modvis_leave(/*save_msg=*/0, /*goto_top=*/1, /*clear_selection=*/1);

	(void)vle_keys_exec_timed_out(WK_C_c);
	vle_keys_reset();
	opt_handlers_teardown();
}

TEST(vifmview_set_cursor_pos_in_cmdline_mode)
{
	modes_init();
	opt_handlers_setup();

	modcline_enter(CLS_COMMAND, "");

	GLUA_EQ(vlua, "", "vifm.currview().cursor.pos = 1");

	assert_true(vle_mode_is(CMDLINE_MODE));
	assert_int_equal(0, lwin.list_pos);

	GLUA_EQ(vlua, "1", "print(vifm.currview().cursor.pos)");

	(void)vle_keys_exec_timed_out(WK_C_c);
	vle_keys_reset();
	opt_handlers_teardown();
}

TEST(vifmview_set_cursor_pos_during_incsearch_from_normal_mode)
{
	modes_init();
	opt_handlers_setup();
	conf_setup();
	cfg.inc_search = 1;

	(void)vle_keys_exec_timed_out(WK_SLASH);

	assert_true(vle_mode_is(CMDLINE_MODE));
	assert_int_equal(1, lwin.list_pos);
	assert_int_equal(0, stats->old_top);
	assert_int_equal(1, stats->old_pos);

	GLUA_EQ(vlua, "", "vifm.currview().cursor.pos = 1");

	assert_true(vle_mode_is(CMDLINE_MODE));
	assert_int_equal(0, lwin.list_pos);
	assert_int_equal(0, stats->old_top);
	assert_int_equal(0, stats->old_pos);

	(void)vle_keys_exec_timed_out(WK_C_c);
	cfg.inc_search = 0;
	conf_teardown();
	vle_keys_reset();
	opt_handlers_teardown();
}

TEST(vifmview_set_cursor_pos_during_incsearch_from_visual_mode)
{
	modes_init();
	opt_handlers_setup();
	conf_setup();
	cfg.inc_search = 1;

	(void)vle_keys_exec_timed_out(WK_v WK_SLASH);

	assert_true(vle_mode_is(CMDLINE_MODE));
	assert_true(vle_primary_mode_is(VISUAL_MODE));
	assert_int_equal(1, lwin.list_pos);
	assert_int_equal(0, stats->old_top);
	assert_int_equal(1, stats->old_pos);
	assert_false(lwin.dir_entry[0].selected);
	assert_true(lwin.dir_entry[1].selected);

	GLUA_EQ(vlua, "", "vifm.currview().cursor.pos = 1");

	assert_true(vle_mode_is(CMDLINE_MODE));
	assert_true(vle_primary_mode_is(VISUAL_MODE));
	assert_int_equal(0, lwin.list_pos);
	assert_int_equal(0, stats->old_top);
	assert_int_equal(0, stats->old_pos);
	assert_true(lwin.dir_entry[0].selected);
	assert_true(lwin.dir_entry[1].selected);

	(void)vle_keys_exec_timed_out(WK_C_c);
	(void)vle_keys_exec_timed_out(WK_C_c);
	cfg.inc_search = 0;
	conf_teardown();
	vle_keys_reset();
	opt_handlers_teardown();
}

TEST(vifmview_custom)
{
	GLUA_EQ(vlua, "nil", "print(vifm.currview().custom)");

	make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), "", "", NULL);
	flist_custom_start(&lwin, "vifmview_custom");
	flist_custom_add(&lwin, TEST_DATA_PATH "/existing-files/a");
	assert_success(flist_custom_finish(&lwin, CV_REGULAR, /*allow_empty=*/0));

	GLUA_EQ(vlua, "vifmview_custom", "print(vifm.currview().custom.title)");
	GLUA_EQ(vlua, "custom", "print(vifm.currview().custom.type)");
}

TEST(vifmview_select)
{
	assert_false(lwin.dir_entry[0].selected);
	assert_false(lwin.dir_entry[1].selected);

	GLUA_EQ(vlua, "0",
			"print(vifm.currview():select({ indexes = { 0, 1.5, 10 } }))");
	assert_false(lwin.dir_entry[0].selected);
	assert_false(lwin.dir_entry[1].selected);

	GLUA_EQ(vlua, "1",
			"print(vifm.currview():select({ indexes = { 2 } }))");
	assert_false(lwin.dir_entry[0].selected);
	assert_true(lwin.dir_entry[1].selected);

	GLUA_EQ(vlua, "1",
			"print(vifm.currview():select({ indexes = { 1, 2 } }))");
	assert_true(lwin.dir_entry[0].selected);
	assert_true(lwin.dir_entry[1].selected);
}

TEST(vifmview_select_in_visual_mode)
{
	modes_init();
	opt_handlers_setup();

	/* Does nothing in visual non-amend mode. */

	modvis_enter(VS_NORMAL);

	assert_false(lwin.dir_entry[0].selected);
	assert_true(lwin.dir_entry[1].selected);

	GLUA_EQ(vlua, "0", "print(vifm.currview():select({ indexes = { 1 } }))");
	assert_false(lwin.dir_entry[0].selected);
	assert_true(lwin.dir_entry[1].selected);

	/* Selects in visual amend mode. */

	modvis_enter(VS_AMEND);

	assert_false(lwin.dir_entry[0].selected);
	assert_true(lwin.dir_entry[1].selected);

	GLUA_EQ(vlua, "1", "print(vifm.currview():select({ indexes = { 1 } }))");
	assert_true(lwin.dir_entry[0].selected);
	assert_true(lwin.dir_entry[1].selected);

	modvis_leave(/*save_msg=*/0, /*goto_top=*/1, /*clear_selection=*/1);

	(void)vle_keys_exec_timed_out(WK_C_c);
	vle_keys_reset();
	opt_handlers_teardown();
}

TEST(vifmview_unselect)
{
	lwin.dir_entry[0].selected = 1;
	lwin.dir_entry[1].selected = 1;

	GLUA_EQ(vlua, "0",
			"print(vifm.currview():unselect({ indexes = { 0, 1.5, 10 } }))");
	assert_true(lwin.dir_entry[0].selected);
	assert_true(lwin.dir_entry[1].selected);

	GLUA_EQ(vlua, "1",
			"print(vifm.currview():unselect({ indexes = { 2 } }))");
	assert_true(lwin.dir_entry[0].selected);
	assert_false(lwin.dir_entry[1].selected);

	GLUA_EQ(vlua, "0",
			"print(vifm.currview():unselect({ indexes = 1 }))");
	assert_true(lwin.dir_entry[0].selected);
	assert_false(lwin.dir_entry[1].selected);

	GLUA_EQ(vlua, "1",
			"print(vifm.currview():unselect({ indexes = { 1, 2 } }))");
	assert_false(lwin.dir_entry[0].selected);
	assert_false(lwin.dir_entry[1].selected);
}

TEST(vifmview_selected)
{
	lwin.dir_entry[0].selected = 1;
	lwin.dir_entry[1].selected = 1;

	GLUA_EQ(vlua, "", "selected = vifm.currview():selected()");
	GLUA_EQ(vlua, "file0", "print(selected().name)");
	GLUA_EQ(vlua, "file1", "print(selected().name)");
	GLUA_EQ(vlua, "nil", "print(selected())");

	lwin.dir_entry[0].selected = 1;
	lwin.dir_entry[1].selected = 0;

	GLUA_EQ(vlua, "", "selected = vifm.currview():selected()");
	GLUA_EQ(vlua, "file0", "print(selected().name)");
	GLUA_EQ(vlua, "nil", "print(selected())");

	lwin.dir_entry[0].selected = 0;
	lwin.dir_entry[1].selected = 1;

	GLUA_EQ(vlua, "", "selected = vifm.currview():selected()");
	GLUA_EQ(vlua, "file1", "print(selected().name)");
	GLUA_EQ(vlua, "nil", "print(selected())");

	lwin.dir_entry[0].selected = 0;
	lwin.dir_entry[1].selected = 0;

	GLUA_EQ(vlua, "", "selected = vifm.currview():selected()");
	GLUA_EQ(vlua, "nil", "print(selected())");
}

TEST(vifmview_entries)
{
	GLUA_EQ(vlua, "", "entries = vifm.currview():entries()");
	GLUA_EQ(vlua, "", "entry = entries()");
	GLUA_EQ(vlua, "file0", "print(entry.name)");
	GLUA_EQ(vlua, "", "entry = entries()");
	GLUA_EQ(vlua, "file1", "print(entry.name)");
	GLUA_EQ(vlua, "", "entry = entries()");
	GLUA_EQ(vlua, "nil", "print(entry)");
}

TEST(vifmview_focus)
{
	opt_handlers_setup();
	columns_setup_column(SK_BY_NAME);
	columns_setup_column(SK_BY_SIZE);

	GLUA_EQ(vlua, "", "curr_tab1 = vifm.currview()");
	int curr_id = curr_view->id;

	tabs_new(NULL, NULL);
	GLUA_EQ(vlua, "", "other_tab2 = vifm.otherview()");
	int other_id = other_view->id;

	GLUA_EQ(vlua, "true", "print(curr_tab1:focus())");
	assert_int_equal(curr_view->id, curr_id);

	GLUA_EQ(vlua, "true", "print(other_tab2:focus())");
	assert_int_equal(curr_view->id, other_id);

	tabs_only(&lwin);

	columns_teardown();
	opt_handlers_teardown();
}

TEST(vifmview_gotopath)
{
	conf_setup();
	make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), TEST_DATA_PATH, "", NULL);

	GLUA_EQ(vlua, "false", "print(vifm.currview():gotopath('rename/wrong'))");

	GLUA_EQ(vlua, "true", "print(vifm.currview():gotopath('rename/a'))");
	assert_string_equal("a", lwin.dir_entry[lwin.list_pos].name);

	GLUA_EQ(vlua, "true", "print(vifm.currview():gotopath('aa'))");
	assert_string_equal("aa", lwin.dir_entry[lwin.list_pos].name);

	conf_teardown();
}

TEST(vifmview_loadcustom)
{
	opt_handlers_setup();
	make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), TEST_DATA_PATH, "rename",
			NULL);

	/* Missing required keys. */
	BLUA_ENDS(vlua, ": `title` key is mandatory",
			"print(vifm.currview():loadcustom({ }))");
	assert_false(flist_custom_active(&lwin));
	BLUA_ENDS(vlua, ": `paths` key is mandatory",
			"print(vifm.currview():loadcustom({ title = 'title' }))");
	assert_false(flist_custom_active(&lwin));

	/* Invalid type field. */
	BLUA_ENDS(vlua, ": Unknown type of custom view: 'bla'",
			"print(vifm.currview():loadcustom({ title = 'title',"
			                                   "paths = { },"
			                                   "type = 'bla' }))");
	assert_false(flist_custom_active(&lwin));

	/* Good invocation. */
	GLUA_ENDS(vlua, "",
			"print(vifm.currview():loadcustom({ title = 'title',"
			                                   "paths = { 'a', 'aa' } }))");
	assert_true(flist_custom_active(&lwin));
	assert_int_equal(CV_REGULAR, lwin.custom.type);
	assert_int_equal(2, lwin.list_rows);
	assert_string_equal("a", lwin.dir_entry[0].name);
	assert_string_equal("aa", lwin.dir_entry[1].name);

	/* Bad invocation doesn't ruin current custom view. */
	BLUA_ENDS(vlua, "", "print(vifm.currview():loadcustom({ title = 'title' }))");
	assert_true(flist_custom_active(&lwin));
	assert_int_equal(CV_REGULAR, lwin.custom.type);
	assert_int_equal(2, lwin.list_rows);
	assert_string_equal("a", lwin.dir_entry[0].name);
	assert_string_equal("aa", lwin.dir_entry[1].name);

	/* Invalid type is filtered out. */
	GLUA_ENDS(vlua, "",
			"print(vifm.currview():loadcustom({ title = 'title',"
			                                   "paths = { 'a', {} } }))");
	assert_true(flist_custom_active(&lwin));
	assert_int_equal(CV_REGULAR, lwin.custom.type);
	assert_int_equal(1, lwin.list_rows);
	assert_string_equal("a", lwin.dir_entry[0].name);

	/* Good invocation for unsorted view. */
	GLUA_ENDS(vlua, "",
			"print(vifm.currview():loadcustom({ title = 'title',"
			                                   "type = 'very-custom',"
			                                   "paths = { 'aa', 'a' } }))");
	assert_true(flist_custom_active(&lwin));
	assert_int_equal(CV_VERY, lwin.custom.type);
	assert_int_equal(2, lwin.list_rows);
	assert_string_equal("aa", lwin.dir_entry[0].name);
	assert_string_equal("a", lwin.dir_entry[1].name);

	/* Good invocation for regular view requested explicitly.  Missing file is
	 * silently ignored. */
	GLUA_ENDS(vlua, "",
			"print(vifm.currview():loadcustom({ title = 'title',"
			                                   "type = 'custom',"
			                                   "paths = { 'aa', 'a', 'x' } }))");
	assert_true(flist_custom_active(&lwin));
	assert_int_equal(CV_REGULAR, lwin.custom.type);
	assert_int_equal(2, lwin.list_rows);
	assert_string_equal("a", lwin.dir_entry[0].name);
	assert_string_equal("aa", lwin.dir_entry[1].name);

	/* List of files can be empty. */
	GLUA_ENDS(vlua, "",
			"print(vifm.currview():loadcustom({ title = 'title',"
			                                   "paths = { 'nosuchfile' } }))");
	assert_true(flist_custom_active(&lwin));
	assert_int_equal(CV_REGULAR, lwin.custom.type);
	assert_int_equal(1, lwin.list_rows);
	assert_string_equal("..", lwin.dir_entry[0].name);

	opt_handlers_teardown();
}

TEST(vifmview_entry_mimetype_unavailable, IF(has_no_mime_type_detection))
{
	GLUA_EQ(vlua, "nil", "print(vifm.currview():entry(2):mimetype())");
}

TEST(vifmview_entry_mimetype, IF(has_mime_type_detection))
{
	make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), SANDBOX_PATH, "", NULL);
	lwin.dir_entry[1].origin = SANDBOX_PATH;
	copy_file(TEST_DATA_PATH "/read/very-long-line", SANDBOX_PATH "/file1");

	GLUA_EQ(vlua, "text/plain", "print(vifm.currview():entry(2):mimetype())");

	remove_file(SANDBOX_PATH "/file1");
}

static int
has_mime_type_detection(void)
{
	return get_mimetype(TEST_DATA_PATH "/read/dos-line-endings", 0) != NULL;
}

static int
has_no_mime_type_detection(void)
{
	return has_mime_type_detection() == 0;
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 : */
