#include <stic.h>

#include <stdarg.h> /* va_list va_start() va_arg() va_end() */

#include <test-utils.h>

#include "../../src/cfg/config.h"
#include "../../src/compat/fs_limits.h"
#include "../../src/compat/os.h"
#include "../../src/ui/column_view.h"
#include "../../src/ui/ui.h"
#include "../../src/utils/fs.h"
#include "../../src/utils/str.h"
#include "../../src/event_loop.h"
#include "../../src/filelist.h"
#include "../../src/filtering.h"
#include "../../src/running.h"
#include "../../src/sort.h"

#include "utils.h"

static void column_line_print(const char buf[], int offset, AlignType align,
		const char full_column[], const format_info_t *info);
static int build_custom_view(view_t *view, ...);
static void toggle_fold_and_update(view_t *view);

static char cwd[PATH_MAX + 1];

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

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

	view_setup(&lwin);
	lwin.columns = columns_create();

	columns_set_line_print_func(&column_line_print);
}

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

	columns_set_line_print_func(NULL);
}

TEST(no_folding_in_non_cv)
{
	make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), TEST_DATA_PATH, "", cwd);
	populate_dir_list(&lwin, /*reload=*/1);
	assert_int_equal(11, lwin.list_rows);

	flist_toggle_fold(&lwin);
	assert_int_equal(11, lwin.list_rows);
	populate_dir_list(&lwin, /*reload=*/1);
	assert_int_equal(11, lwin.list_rows);
}

TEST(no_folding_for_non_dirs)
{
	assert_true(build_custom_view(&lwin,
				"tree/dir1/dir2",
				"tree/dir1/file4",
				(const char *)NULL) == 0);

	assert_success(load_tree(&lwin, SANDBOX_PATH, cwd));
	assert_int_equal(3, lwin.list_rows);

	lwin.list_pos = 2;
	assert_string_equal("file4", lwin.dir_entry[lwin.list_pos].name);

	flist_toggle_fold(&lwin);
	assert_int_equal(3, lwin.list_rows);
	populate_dir_list(&lwin, /*reload=*/1);
	assert_int_equal(3, lwin.list_rows);
}

TEST(folding_of_directories)
{
	assert_success(os_mkdir(SANDBOX_PATH "/nested-dir", 0700));
	create_file(SANDBOX_PATH "/nested-dir/a");

	assert_success(load_tree(&lwin, SANDBOX_PATH, cwd));
	assert_int_equal(2, lwin.list_rows);

	toggle_fold_and_update(&lwin);

	assert_int_equal(1, lwin.list_rows);
	assert_string_equal("nested-dir", lwin.dir_entry[0].name);
	assert_true(lwin.dir_entry[0].folded);

	toggle_fold_and_update(&lwin);

	assert_int_equal(2, lwin.list_rows);
	assert_string_equal("nested-dir", lwin.dir_entry[0].name);
	assert_string_equal("a", lwin.dir_entry[1].name);
	assert_false(lwin.dir_entry[0].folded);

	assert_success(remove(SANDBOX_PATH "/nested-dir/a"));
	assert_success(rmdir(SANDBOX_PATH "/nested-dir"));
}

TEST(folding_two_tree_out_of_cv)
{
	assert_true(build_custom_view(&lwin,
				"tree/dir1/dir2/dir4",
				"tree/dir1/dir2/dir4/file3",
				(const char *)NULL) == 0);

	assert_success(load_tree(&lwin, SANDBOX_PATH, cwd));
	assert_int_equal(2, lwin.list_rows);

	toggle_fold_and_update(&lwin);
	assert_int_equal(1, lwin.list_rows);
	assert_string_equal("dir4", lwin.dir_entry[0].name);
	assert_true(lwin.dir_entry[0].folded);

	toggle_fold_and_update(&lwin);
	assert_int_equal(2, lwin.list_rows);
	assert_string_equal("dir4", lwin.dir_entry[0].name);
	assert_string_equal("file3", lwin.dir_entry[1].name);
	assert_false(lwin.dir_entry[0].folded);
}

TEST(unfolding_accounts_for_sorting)
{
	assert_true(build_custom_view(&lwin,
				"tree/dir1/dir2/dir3",
				"tree/dir1/dir2/dir3/file1",
				"tree/dir1/dir2/dir3/file2",
				(const char *)NULL) == 0);

	assert_success(load_tree(&lwin, SANDBOX_PATH, cwd));
	assert_int_equal(3, lwin.list_rows);
	assert_string_equal("dir3", lwin.dir_entry[0].name);
	assert_string_equal("file1", lwin.dir_entry[1].name);
	assert_string_equal("file2", lwin.dir_entry[2].name);

	toggle_fold_and_update(&lwin);
	assert_int_equal(1, lwin.list_rows);
	assert_string_equal("dir3", lwin.dir_entry[0].name);
	assert_true(lwin.dir_entry[0].folded);

	lwin.sort[0] = -SK_BY_NAME;
	sort_view(&lwin);

	toggle_fold_and_update(&lwin);
	assert_int_equal(3, lwin.list_rows);
	assert_string_equal("dir3", lwin.dir_entry[0].name);
	assert_string_equal("file2", lwin.dir_entry[1].name);
	assert_string_equal("file1", lwin.dir_entry[2].name);
	assert_false(lwin.dir_entry[0].folded);
}

TEST(folding_five_tree_out_of_cv)
{
	assert_true(build_custom_view(&lwin,
				"tree/dir1/dir2/dir3",
				"tree/dir1/dir2/dir3/file1",
				"tree/dir1/dir2/dir3/file2",
				"tree/dir1/dir2/dir4",
				"tree/dir1/dir2/dir4/file3",
				(const char *)NULL) == 0);

	assert_success(load_tree(&lwin, TEST_DATA_PATH, cwd));
	assert_int_equal(6, lwin.list_rows);

	assert_string_equal("dir2", lwin.dir_entry[0].name);
	assert_string_equal("dir3", lwin.dir_entry[1].name);
	assert_string_equal("file1", lwin.dir_entry[2].name);
	assert_string_equal("file2", lwin.dir_entry[3].name);
	assert_string_equal("dir4", lwin.dir_entry[4].name);
	assert_string_equal("file3", lwin.dir_entry[5].name);

	lwin.list_pos = 1;
	toggle_fold_and_update(&lwin);
	assert_int_equal(4, lwin.list_rows);

	toggle_fold_and_update(&lwin);
	assert_int_equal(6, lwin.list_rows);
}

TEST(folds_of_custom_tree_are_not_lost_on_filtering)
{
	assert_true(build_custom_view(&lwin,
				"tree/dir1/dir2/dir3",
				"tree/dir1/dir2/dir3/file1",
				"tree/dir1/dir2/dir3/file2",
				"tree/dir1/dir2/dir4",
				"tree/dir1/dir2/dir4/file3",
				(const char *)NULL) == 0);

	assert_success(load_tree(&lwin, TEST_DATA_PATH, cwd));
	assert_int_equal(6, lwin.list_rows);

	/* fold */
	lwin.list_pos = 1;
	assert_string_equal("dir3", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	lwin.list_pos = 2;
	assert_string_equal("dir4", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(3, lwin.list_rows);

	/* filter */
	assert_int_equal(0, local_filter_set(&lwin, "[34]"));
	local_filter_accept(&lwin, /*update_history=*/1);
	assert_int_equal(2, lwin.list_rows);

	/* unfold */
	lwin.list_pos = 0;
	assert_string_equal("dir3", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);

	/* remove filter */
	local_filter_remove(&lwin);
	curr_stats.load_stage = 2;
	assert_true(process_scheduled_updates_of_view(&lwin));
	curr_stats.load_stage = 0;
	assert_int_equal(5, lwin.list_rows);
}

TEST(local_filter_respects_tree_folds)
{
	assert_success(load_tree(&lwin, TEST_DATA_PATH "/tree/dir1/dir2", cwd));
	assert_int_equal(5, lwin.list_rows);

	lwin.list_pos = 0;
	assert_string_equal("dir3", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(3, lwin.list_rows);

	local_filter_apply(&lwin, "2");
	populate_dir_list(&lwin, /*reload=*/1);

	assert_int_equal(1, lwin.list_rows);
	assert_string_equal("..", lwin.dir_entry[0].name);
}

TEST(local_filter_respects_custom_tree_folds)
{
	assert_true(build_custom_view(&lwin,
				"tree/dir1",
				"tree/dir1/file4",
				(const char *)NULL) == 0);

	assert_success(load_tree(&lwin, TEST_DATA_PATH, cwd));
	assert_int_equal(2, lwin.list_rows);

	lwin.list_pos = 0;
	assert_string_equal("dir1", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(1, lwin.list_rows);

	local_filter_apply(&lwin, "4");
	populate_dir_list(&lwin, /*reload=*/1);

	assert_int_equal(1, lwin.list_rows);
	assert_string_equal("..", lwin.dir_entry[0].name);
}

TEST(folding_parent_does_not_mess_up_children)
{
	assert_true(build_custom_view(&lwin,
				"tree/dir1",
				"tree/dir1/file4",
				"tree/dir5",
				"tree/dir5/file5",
				(const char *)NULL) == 0);

	assert_success(load_tree(&lwin, TEST_DATA_PATH "/tree", cwd));
	assert_int_equal(5, lwin.list_rows);

	lwin.list_pos = 1;
	assert_string_equal("dir1", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(4, lwin.list_rows);

	lwin.list_pos = 1;
	assert_string_equal("dir1", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(5, lwin.list_rows);

	lwin.list_pos = 0;
	assert_string_equal("tree", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(1, lwin.list_rows);

	lwin.list_pos = 0;
	assert_string_equal("tree", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(5, lwin.list_rows);

	lwin.list_pos = 1;
	assert_string_equal("dir1", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(4, lwin.list_rows);

	lwin.list_pos = 1;
	assert_string_equal("dir1", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(5, lwin.list_rows);
}

/* This test mixes different trees and does reloading to verify resource uses
 * and tree reloading. */
TEST(folding_grind)
{
	assert_true(build_custom_view(&lwin,
				"tree/dir1",
				"tree/dir1/file4",
				"tree/dir1/dir2",
				"tree/dir1/dir2/dir3",
				"tree/dir1/dir2/dir3/file1",
				"tree/dir1/dir2/dir3/file2",
				"tree/dir1/dir2/dir4",
				"tree/dir1/dir2/dir4/file3",
				"tree/dir5",
				"tree/dir5/.nested_hidden",
				"tree/dir5/file5",
				"tree/.hidden",
				(const char *)NULL) == 0);

	assert_success(load_tree(&lwin, TEST_DATA_PATH "/tree", cwd));
	assert_int_equal(CV_CUSTOM_TREE, lwin.custom.type);
	assert_int_equal(13, lwin.list_rows);

	lwin.list_pos = 3;
	assert_string_equal("dir3", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	lwin.list_pos = 4;
	assert_string_equal("dir4", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	lwin.list_pos = 2;
	assert_string_equal("dir2", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	lwin.list_pos = 4;
	assert_string_equal("dir5", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);

	assert_int_equal(6, lwin.list_rows);

	lwin.list_pos = 2;
	assert_string_equal("dir2", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);

	assert_int_equal(8, lwin.list_rows);

	/* Not a custom tree below. */

	assert_success(load_tree(&lwin, TEST_DATA_PATH "/tree", cwd));
	assert_int_equal(CV_TREE, lwin.custom.type);
	assert_int_equal(12, lwin.list_rows);

	lwin.list_pos = 2;
	toggle_fold_and_update(&lwin);
	lwin.list_pos = 3;
	assert_string_equal("dir4", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	lwin.list_pos = 1;
	assert_string_equal("dir2", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	lwin.list_pos = 3;
	assert_string_equal("dir5", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);

	assert_int_equal(5, lwin.list_rows);

	lwin.list_pos = 1;
	assert_string_equal("dir2", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);

	assert_int_equal(7, lwin.list_rows);
}

TEST(lazy_unfolding)
{
	assert_success(load_limited_tree(&lwin, TEST_DATA_PATH "/tree", cwd, 0));
	assert_int_equal(3, lwin.list_rows);

	lwin.list_pos = 0;
	assert_string_equal("dir1", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(5, lwin.list_rows);

	lwin.list_pos = 1;
	assert_string_equal("dir2", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(7, lwin.list_rows);

	lwin.list_pos = 1;
	assert_string_equal("dir2", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(5, lwin.list_rows);

	lwin.list_pos = 1;
	assert_string_equal("dir2", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(7, lwin.list_rows);

	lwin.list_pos = 0;
	assert_string_equal("dir1", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(3, lwin.list_rows);

	lwin.list_pos = 0;
	assert_string_equal("dir1", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(7, lwin.list_rows);
}

TEST(lazy_unfolding_and_filtering)
{
	(void)filter_set(&lwin.local_filter.filter, "^[^1]+$");

	assert_success(load_limited_tree(&lwin, TEST_DATA_PATH "/tree", cwd, 0));
	assert_int_equal(2, lwin.list_rows);

	lwin.list_pos = 0;
	assert_string_equal("dir5", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(4, lwin.list_rows);

	lwin.list_pos = 0;
	assert_string_equal("dir5", lwin.dir_entry[lwin.list_pos].name);
	toggle_fold_and_update(&lwin);
	assert_int_equal(2, lwin.list_rows);
}

TEST(folding_is_reset_on_leaving_tree)
{
	assert_success(load_limited_tree(&lwin, TEST_DATA_PATH "/tree", cwd,
				/*depth=*/1));
	assert_true(flist_custom_active(&lwin));
	assert_int_equal(7, lwin.list_rows);

	lwin.list_pos = 0;
	assert_string_equal("dir1", lwin.dir_entry[lwin.list_pos].name);
	rn_open(&lwin, FHE_RUN);
	assert_false(flist_custom_active(&lwin));

	/* Should do nothing, specifically shouldn't crash. */
	flist_toggle_fold(&lwin);
}

static void
column_line_print(const char buf[], int offset, AlignType align,
		const char full_column[], const format_info_t *info)
{
	/* Do nothing. */
}

static int
build_custom_view(view_t *view, ...)
{
	va_list ap;
	va_start(ap, view);

	flist_custom_start(view, "test");

	const char *rel_path;
	while((rel_path = va_arg(ap, const char *)) != NULL)
	{
		char path[PATH_MAX + 1];
		make_abs_path(path, sizeof(path), TEST_DATA_PATH, rel_path, cwd);
		flist_custom_add(view, path);
	}

	va_end(ap);

	return flist_custom_finish(view, CV_REGULAR, 0);
}

static void
toggle_fold_and_update(view_t *view)
{
	flist_toggle_fold(view);

	validate_tree(&lwin);

	curr_stats.load_stage = 2;
	assert_true(process_scheduled_updates_of_view(view));
	curr_stats.load_stage = 0;

	validate_tree(&lwin);
}

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