#include <stic.h>

#include <limits.h> /* INT_MAX */
#include <stdlib.h> /* free() */
#include <string.h> /* strcpy() strdup() */

#include <test-utils.h>

#include "../../src/cfg/config.h"
#include "../../src/compat/fs_limits.h"
#include "../../src/engine/keys.h"
#include "../../src/modes/modes.h"
#include "../../src/ui/ui.h"
#include "../../src/utils/dynarray.h"
#include "../../src/utils/filter.h"
#include "../../src/utils/fs.h"
#include "../../src/utils/matcher.h"
#include "../../src/utils/str.h"
#include "../../src/cmd_core.h"
#include "../../src/filelist.h"
#include "../../src/filtering.h"
#include "../../src/opt_handlers.h"
#include "../../src/sort.h"
#include "../../src/status.h"

#define assert_hidden(view, name, is_dir) \
	assert_false( \
			filters_file_is_visible(&view, flist_get_dir(&view), name, is_dir, 1))

#define assert_visible(view, name, is_dir) \
	assert_true( \
			filters_file_is_visible(&view, flist_get_dir(&view), name, is_dir, 1))

static char cwd[PATH_MAX + 1];

SETUP_ONCE()
{
	assert_non_null(get_cwd(cwd, sizeof(cwd)));
}

SETUP()
{
	update_string(&cfg.fuse_home, "no");
	update_string(&cfg.slow_fs_list, "");

	cfg.filter_inverted_by_default = 1;

	view_setup(&lwin);

	lwin.list_rows = 7;
	lwin.list_pos = 2;
	strcpy(lwin.curr_dir, "/some/path");
	lwin.dir_entry = dynarray_cextend(NULL,
			lwin.list_rows*sizeof(*lwin.dir_entry));
	lwin.dir_entry[0].name = strdup("with(round)");
	lwin.dir_entry[0].origin = &lwin.curr_dir[0];
	lwin.dir_entry[1].name = strdup("with[square]");
	lwin.dir_entry[1].origin = &lwin.curr_dir[0];
	lwin.dir_entry[2].name = strdup("with{curly}");
	lwin.dir_entry[2].origin = &lwin.curr_dir[0];
	lwin.dir_entry[3].name = strdup("with<angle>");
	lwin.dir_entry[3].origin = &lwin.curr_dir[0];
	lwin.dir_entry[4].name = strdup("withSPECS+*^$?|\\");
	lwin.dir_entry[4].origin = &lwin.curr_dir[0];
	lwin.dir_entry[5].name = strdup("with....dots");
	lwin.dir_entry[5].origin = &lwin.curr_dir[0];
	lwin.dir_entry[6].name = strdup("withnonodots");
	lwin.dir_entry[6].origin = &lwin.curr_dir[0];

	lwin.dir_entry[0].selected = 1;
	lwin.dir_entry[1].selected = 1;
	lwin.dir_entry[2].selected = 1;
	lwin.dir_entry[3].selected = 1;
	lwin.dir_entry[4].selected = 1;
	lwin.dir_entry[5].selected = 1;
	lwin.dir_entry[6].selected = 0;
	lwin.selected_files = 6;

	lwin.invert = cfg.filter_inverted_by_default;

	lwin.column_count = 1;

	view_setup(&rwin);

	rwin.list_rows = 8;
	rwin.list_pos = 2;
	rwin.dir_entry = dynarray_cextend(NULL,
			rwin.list_rows*sizeof(*rwin.dir_entry));
	rwin.dir_entry[0].name = strdup("dir1.d");
	rwin.dir_entry[0].origin = &rwin.curr_dir[0];
	rwin.dir_entry[1].name = strdup("dir2.d");
	rwin.dir_entry[1].origin = &rwin.curr_dir[0];
	rwin.dir_entry[2].name = strdup("dir3.d");
	rwin.dir_entry[2].origin = &rwin.curr_dir[0];
	rwin.dir_entry[3].name = strdup("file1.d");
	rwin.dir_entry[3].origin = &rwin.curr_dir[0];
	rwin.dir_entry[4].name = strdup("file2.d");
	rwin.dir_entry[4].origin = &rwin.curr_dir[0];
	rwin.dir_entry[5].name = strdup("file3.d");
	rwin.dir_entry[5].origin = &rwin.curr_dir[0];
	rwin.dir_entry[6].name = strdup("withnonodots");
	rwin.dir_entry[6].origin = &rwin.curr_dir[0];
	rwin.dir_entry[7].name = strdup("somedir");
	rwin.dir_entry[7].origin = &rwin.curr_dir[0];

	rwin.dir_entry[0].selected = 0;
	rwin.dir_entry[1].selected = 0;
	rwin.dir_entry[2].selected = 0;
	rwin.dir_entry[3].selected = 0;
	rwin.dir_entry[4].selected = 0;
	rwin.dir_entry[5].selected = 0;
	rwin.dir_entry[6].selected = 0;
	rwin.dir_entry[7].selected = 0;
	rwin.selected_files = 0;

	rwin.invert = cfg.filter_inverted_by_default;

	rwin.column_count = 1;

	update_string(&lwin.prev_manual_filter, "");
	update_string(&lwin.prev_auto_filter, "");
}

TEARDOWN()
{
	update_string(&cfg.slow_fs_list, NULL);
	update_string(&cfg.fuse_home, NULL);

	view_teardown(&lwin);
	view_teardown(&rwin);

	update_string(&lwin.prev_manual_filter, NULL);
	update_string(&lwin.prev_auto_filter, NULL);
}

TEST(filtering)
{
	assert_int_equal(7, lwin.list_rows);
	name_filters_add_active(&lwin);
	assert_int_equal(1, lwin.list_rows);
	assert_int_equal(0, lwin.selected_files);

	assert_string_equal("withnonodots", lwin.dir_entry[0].name);
	assert_visible(lwin, lwin.dir_entry[0].name, 0);
}

TEST(filtering_file_does_not_filter_dir)
{
	char *const name = strdup(rwin.dir_entry[6].name);

	rwin.dir_entry[6].selected = 1;
	rwin.selected_files = 1;

	assert_int_equal(8, rwin.list_rows);
	name_filters_add_active(&rwin);
	assert_int_equal(7, rwin.list_rows);
	assert_int_equal(0, rwin.selected_files);

	assert_hidden(rwin, name, 0);
	assert_visible(rwin, name, 1);

	free(name);
}

TEST(filtering_dir_does_not_filter_file)
{
	char *const name = strdup(rwin.dir_entry[6].name);

	rwin.dir_entry[6].selected = 1;
	rwin.dir_entry[6].type = FT_DIR;
	rwin.selected_files = 1;

	assert_int_equal(8, rwin.list_rows);
	name_filters_add_active(&rwin);
	assert_int_equal(7, rwin.list_rows);
	assert_int_equal(0, rwin.selected_files);

	assert_hidden(rwin, name, 1);
	assert_visible(rwin, name, 0);

	free(name);
}

TEST(filtering_files_does_not_filter_dirs)
{
	(void)replace_matcher(&rwin.manual_filter, "^.*\\.d$");

	assert_visible(rwin, rwin.dir_entry[0].name, 1);
	assert_visible(rwin, rwin.dir_entry[1].name, 1);
	assert_visible(rwin, rwin.dir_entry[2].name, 1);
	assert_hidden(rwin, rwin.dir_entry[3].name, 0);
	assert_hidden(rwin, rwin.dir_entry[4].name, 0);
	assert_hidden(rwin, rwin.dir_entry[5].name, 0);
	assert_visible(rwin, rwin.dir_entry[6].name, 0);
	assert_visible(rwin, rwin.dir_entry[7].name, 1);
	assert_int_equal(8, rwin.list_rows);
}

TEST(filtering_dirs_does_not_filter_files)
{
	(void)replace_matcher(&rwin.manual_filter, "^.*\\.d/$");

	assert_hidden(rwin, rwin.dir_entry[0].name, 1);
	assert_hidden(rwin, rwin.dir_entry[1].name, 1);
	assert_hidden(rwin, rwin.dir_entry[2].name, 1);
	assert_visible(rwin, rwin.dir_entry[3].name, 0);
	assert_visible(rwin, rwin.dir_entry[4].name, 0);
	assert_visible(rwin, rwin.dir_entry[5].name, 0);
	assert_visible(rwin, rwin.dir_entry[6].name, 0);
	assert_visible(rwin, rwin.dir_entry[7].name, 1);
	assert_int_equal(8, rwin.list_rows);
}

TEST(filtering_files_and_dirs)
{
	(void)replace_matcher(&rwin.manual_filter, "^.*\\.d/?$");

	assert_hidden(rwin, rwin.dir_entry[0].name, 1);
	assert_hidden(rwin, rwin.dir_entry[1].name, 1);
	assert_hidden(rwin, rwin.dir_entry[2].name, 1);
	assert_hidden(rwin, rwin.dir_entry[3].name, 0);
	assert_hidden(rwin, rwin.dir_entry[4].name, 0);
	assert_hidden(rwin, rwin.dir_entry[5].name, 0);
	assert_visible(rwin, rwin.dir_entry[6].name, 0);
	assert_visible(rwin, rwin.dir_entry[7].name, 1);
	assert_int_equal(8, rwin.list_rows);
}

TEST(file_after_directory_is_hidden)
{
	char path[PATH_MAX + 1];

	view_teardown(&lwin);
	view_setup(&lwin);

	flist_custom_start(&lwin, "test");
	make_abs_path(path, sizeof(path), TEST_DATA_PATH, "read", cwd);
	flist_custom_add(&lwin, path);
	make_abs_path(path, sizeof(path), TEST_DATA_PATH, "read/very-long-line", cwd);
	flist_custom_add(&lwin, path);
	assert_true(flist_custom_finish(&lwin, CV_REGULAR, 0) == 0);

	lwin.dir_entry[1].selected = 1;
	lwin.selected_files = 1;
	name_filters_add_active(&lwin);

	assert_int_equal(1, lwin.list_rows);
	assert_int_equal(0, lwin.selected_files);
}

TEST(global_local_nature_of_normal_zo)
{
	view_teardown(&lwin);
	view_setup(&lwin);

	view_teardown(&rwin);
	view_setup(&rwin);

	curr_view = &lwin;
	other_view = &rwin;

	lwin.hide_dot_g = lwin.hide_dot = 0;
	rwin.hide_dot_g = rwin.hide_dot = 1;

	opt_handlers_setup();
	load_view_options(curr_view);

	modes_init();
	cmds_init();

	curr_stats.global_local_settings = 1;

	assert_success(cmds_dispatch("normal zo", &lwin, CIT_COMMAND));
	assert_false(lwin.hide_dot_g);
	assert_false(lwin.hide_dot);
	assert_false(rwin.hide_dot_g);
	assert_false(rwin.hide_dot);

	curr_stats.global_local_settings = 0;

	vle_keys_reset();
	vle_cmds_reset();
	opt_handlers_teardown();
}

TEST(cursor_is_not_moved_from_parent_dir_initially)
{
	char path[PATH_MAX + 1];

	cfg.dot_dirs = DD_NONROOT_PARENT;

	flist_custom_start(&lwin, "test");
	make_abs_path(path, sizeof(path), TEST_DATA_PATH, "read/very-long-line", cwd);
	flist_custom_add(&lwin, path);
	assert_true(flist_custom_finish(&lwin, CV_REGULAR, 0) == 0);

	lwin.list_pos = 0;
	assert_int_equal(0, local_filter_set(&lwin, ""));
	local_filter_update_view(&lwin, 0);
	assert_int_equal(0, lwin.list_pos);

	assert_int_equal(0, local_filter_set(&lwin, "l"));
	local_filter_update_view(&lwin, 0);
	assert_int_equal(1, lwin.list_pos);
	local_filter_cancel(&lwin);

	cfg.dot_dirs = 0;
}

TEST(cursor_is_moved_to_nearest_neighbour)
{
	char path[PATH_MAX + 1];

	flist_custom_start(&lwin, "test");
	make_abs_path(path, sizeof(path), TEST_DATA_PATH, "read/binary-data", cwd);
	flist_custom_add(&lwin, path);
	make_abs_path(path, sizeof(path), TEST_DATA_PATH, "read/dos-eof", cwd);
	flist_custom_add(&lwin, path);
	make_abs_path(path, sizeof(path), TEST_DATA_PATH, "read/two-lines", cwd);
	flist_custom_add(&lwin, path);
	make_abs_path(path, sizeof(path), TEST_DATA_PATH, "read/very-long-line", cwd);
	flist_custom_add(&lwin, path);
	assert_true(flist_custom_finish(&lwin, CV_REGULAR, 0) == 0);

	lwin.list_pos = 1;
	assert_int_equal(0, local_filter_set(&lwin, "l"));
	local_filter_update_view(&lwin, 0);
	assert_int_equal(0, lwin.list_pos);
	local_filter_cancel(&lwin);
}

TEST(removed_filename_filter_is_stored)
{
	assert_success(filter_set(&lwin.auto_filter, "a"));
	assert_success(replace_matcher(&lwin.manual_filter, "b"));

	name_filters_remove(&lwin);

	assert_string_equal("a", lwin.prev_auto_filter);
	assert_string_equal("b", lwin.prev_manual_filter);
}

TEST(filename_filter_can_removed_at_most_once)
{
	assert_success(filter_set(&lwin.auto_filter, "a"));
	assert_success(replace_matcher(&lwin.manual_filter, "b"));

	name_filters_remove(&lwin);
	name_filters_remove(&lwin);

	assert_string_equal("a", lwin.prev_auto_filter);
	assert_string_equal("b", lwin.prev_manual_filter);
}

TEST(filename_filter_can_be_cleared)
{
	assert_success(filter_set(&lwin.auto_filter, "a"));
	assert_success(replace_matcher(&lwin.manual_filter, "b"));

	name_filters_drop(&lwin);

	assert_true(filter_is_empty(&lwin.auto_filter));
	assert_true(matcher_is_empty(lwin.manual_filter));

	assert_true(name_filters_empty(&lwin));
}

TEST(filename_filter_can_be_restored)
{
	assert_success(filter_set(&lwin.auto_filter, "a"));
	assert_success(replace_matcher(&lwin.manual_filter, "b"));

	name_filters_remove(&lwin);
	name_filters_restore(&lwin);

	assert_string_equal("a", lwin.auto_filter.raw);
	assert_string_equal("b", matcher_get_expr(lwin.manual_filter));
}

TEST(filename_filter_is_not_restored_from_empty_state)
{
	assert_success(filter_set(&lwin.auto_filter, "a"));
	assert_success(replace_matcher(&lwin.manual_filter, "b"));

	name_filters_restore(&lwin);

	assert_string_equal("a", lwin.auto_filter.raw);
	assert_string_equal("b", matcher_get_expr(lwin.manual_filter));
}

TEST(filename_filter_can_match_full_paths)
{
	assert_success(replace_matcher(&lwin.manual_filter, "///some/path/b$//"));
	assert_visible(lwin, "a", 0);
	assert_visible(lwin, "a", 0);
	assert_hidden(lwin, "b", 0);
	assert_visible(lwin, "b", 1);

	assert_success(replace_matcher(&lwin.manual_filter, "{{/some/path/b}}"));
	assert_visible(lwin, "a", 0);
	assert_visible(lwin, "a", 0);
	assert_hidden(lwin, "b", 0);
	assert_visible(lwin, "b", 1);

	assert_success(replace_matcher(&lwin.manual_filter, "///other/path/b//"));
	assert_visible(lwin, "b", 0);
	assert_visible(lwin, "b", 1);

	assert_success(replace_matcher(&lwin.manual_filter, "{{/other/path/b}}"));
	assert_visible(lwin, "b", 0);
	assert_visible(lwin, "b", 1);

	assert_success(replace_matcher(&lwin.manual_filter,
				"//^/some/path/[^/]*\\.png$//"));
	assert_hidden(lwin, "a.png", 0);
	assert_visible(lwin, "a.png", 1);
	strcat(lwin.curr_dir, "/nested");
	assert_visible(lwin, "a.png", 0);
	assert_visible(lwin, "a.png", 1);
}

TEST(custom_tree_can_restore_files_after_local_filter_interactive)
{
	char test_data[PATH_MAX + 1];
	char path[PATH_MAX + 1];

	make_abs_path(test_data, sizeof(test_data), TEST_DATA_PATH, "", cwd);

	flist_custom_start(&lwin, "test");
	snprintf(path, sizeof(path), "%s/%s", test_data, "compare");
	flist_custom_add(&lwin, path);
	snprintf(path, sizeof(path), "%s/%s", test_data, "read");
	flist_custom_add(&lwin, path);
	snprintf(path, sizeof(path), "%s/%s", test_data, "rename");
	flist_custom_add(&lwin, path);
	snprintf(path, sizeof(path), "%s/%s", test_data, "tree");
	flist_custom_add(&lwin, path);
	assert_true(flist_custom_finish(&lwin, CV_REGULAR, 0) == 0);

	assert_success(flist_load_tree(&lwin, test_data, INT_MAX));
	assert_int_equal(5, lwin.list_rows);

	assert_int_equal(0, local_filter_set(&lwin, "t"));
	local_filter_accept(&lwin, /*update_history=*/1);
	assert_int_equal(2, lwin.list_rows);

	assert_int_equal(0, local_filter_set(&lwin, ""));
	local_filter_accept(&lwin, /*update_history=*/1);
	assert_int_equal(5, lwin.list_rows);
}

TEST(custom_tree_can_restore_files_after_local_filter_non_interactive)
{
	char test_data[PATH_MAX + 1];
	char path[PATH_MAX + 1];

	make_abs_path(test_data, sizeof(test_data), TEST_DATA_PATH, "", cwd);

	flist_custom_start(&lwin, "test");
	snprintf(path, sizeof(path), "%s/%s", test_data, "compare");
	flist_custom_add(&lwin, path);
	snprintf(path, sizeof(path), "%s/%s", test_data, "read");
	flist_custom_add(&lwin, path);
	snprintf(path, sizeof(path), "%s/%s", test_data, "rename");
	flist_custom_add(&lwin, path);
	snprintf(path, sizeof(path), "%s/%s", test_data, "tree");
	flist_custom_add(&lwin, path);
	assert_true(flist_custom_finish(&lwin, CV_REGULAR, 0) == 0);

	assert_success(flist_load_tree(&lwin, test_data, INT_MAX));
	assert_int_equal(5, lwin.list_rows);

	local_filter_apply(&lwin, "t");
	load_dir_list(&lwin, 1);
	assert_int_equal(2, lwin.list_rows);

	local_filter_apply(&lwin, "");
	load_dir_list(&lwin, 1);
	assert_int_equal(5, lwin.list_rows);
}

TEST(sorting_tree_preserves_parent_dot_dir)
{
	char test_data[PATH_MAX + 1];
	char path[PATH_MAX + 1];

	cfg.dot_dirs = DD_NONROOT_PARENT;

	make_abs_path(test_data, sizeof(test_data), TEST_DATA_PATH, "", cwd);

	flist_custom_start(&lwin, "test");
	snprintf(path, sizeof(path), "%s/%s", test_data, "compare");
	flist_custom_add(&lwin, path);
	snprintf(path, sizeof(path), "%s/%s", test_data, "read");
	flist_custom_add(&lwin, path);
	assert_true(flist_custom_finish(&lwin, CV_REGULAR, 0) == 0);

	assert_success(flist_load_tree(&lwin, test_data, INT_MAX));
	assert_int_equal(4, lwin.list_rows);

	local_filter_apply(&lwin, "/");
	load_dir_list(&lwin, 1);
	assert_int_equal(4, lwin.list_rows);

	assert_string_equal("..", lwin.dir_entry[0].name);
	sort_view(&lwin);
	assert_string_equal("..", lwin.dir_entry[0].name);

	cfg.dot_dirs = 0;
}

TEST(case_is_ignored_by_noninteractive_local_filter)
{
	cfg.ignore_case = 1;

	make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), TEST_DATA_PATH, "", cwd);

	flist_custom_start(&lwin, "test");
	flist_custom_add(&lwin, "compare");
	assert_true(flist_custom_finish(&lwin, CV_REGULAR, 0) == 0);

	local_filter_apply(&lwin, "CoMpArE");
	load_dir_list(&lwin, 1);

	assert_string_equal("compare", lwin.dir_entry[0].name);

	cfg.ignore_case = 0;
}

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