#include <stic.h>

#include <sys/stat.h> /* stat */
#include <unistd.h> /* stat() */

#include <stdio.h> /* fclose() fopen() fprintf() remove() */
#include <stdlib.h> /* free() */

#include <test-utils.h>

#include "../../src/cfg/config.h"
#include "../../src/cfg/info.h"
#include "../../src/cfg/info_chars.h"
#include "../../src/engine/keys.h"
#include "../../src/ui/column_view.h"
#include "../../src/ui/ui.h"
#include "../../src/utils/matcher.h"
#include "../../src/utils/matchers.h"
#include "../../src/utils/parson.h"
#include "../../src/utils/str.h"
#include "../../src/cmd_core.h"
#include "../../src/filetype.h"
#include "../../src/flist_hist.h"
#include "../../src/instance.h"
#include "../../src/opt_handlers.h"
#include "../../src/status.h"

static char *saved_locale;

SETUP_ONCE()
{
	make_abs_path(cfg.config_dir, sizeof(cfg.config_dir), SANDBOX_PATH, "", NULL);
	saved_locale = drop_locale();
}

TEARDOWN_ONCE()
{
	cfg.config_dir[0] = '\0';
	restore_locale(saved_locale);
}

SETUP()
{
	view_setup(&lwin);
	view_setup(&rwin);
	curr_view = &lwin;
	other_view = &rwin;

	cfg_resize_histories(10);

	cfg.vifm_info = 0;
}

TEARDOWN()
{
	curr_view = NULL;
	other_view = NULL;

	cfg_resize_histories(0);

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

	cfg.vifm_info = 0;
}

TEST(view_sorting_is_read_from_vifminfo)
{
	FILE *const f = fopen(SANDBOX_PATH "/vifminfo", "w");
	fprintf(f, "%c%d,%d", LINE_TYPE_LWIN_SORT, SK_BY_NAME, -SK_BY_DIR);
	fclose(f);

	/* ls-like view blocks view column updates. */
	lwin.ls_view = 1;
	state_load(1);
	lwin.ls_view = 0;

	opt_handlers_setup();

	copy_str(lwin.curr_dir, sizeof(lwin.curr_dir), "fake/path");
	assert_int_equal(SK_BY_NAME, lwin.sort[0]);
	assert_int_equal(-SK_BY_DIR, lwin.sort[1]);
	assert_int_equal(SK_NONE, lwin.sort[2]);

	opt_handlers_teardown();

	assert_success(remove(SANDBOX_PATH "/vifminfo"));
}

TEST(filetypes_are_deduplicated)
{
	struct stat first, second;
	char *error;
	matchers_t *ms;

	cfg.vifm_info = VINFO_FILETYPES;
	cmds_init();

	/* Add a filetype. */
	ms = matchers_alloc("*.c", 0, 1, "", &error);
	assert_non_null(ms);
	ft_set_programs(ms, "{Description}com,,mand,{descr2}cmd", 0, 1);

	/* Write it first time. */
	write_info_file();
	/* And remember size of the file. */
	assert_success(stat(SANDBOX_PATH "/vifminfo.json", &first));

	/* Add filetype again (as if it was read from vifmrc). */
	ms = matchers_alloc("*.c", 0, 1, "", &error);
	assert_non_null(ms);
	ft_set_programs(ms, "{Description}com,,mand,{descr2}cmd", 0, 1);

	/* Update vifminfo second time. */
	write_info_file();
	/* Check that size hasn't changed. */
	assert_success(stat(SANDBOX_PATH "/vifminfo.json", &second));
	assert_true(first.st_size == second.st_size);

	assert_success(remove(SANDBOX_PATH "/vifminfo.json"));
	vle_cmds_reset();
}

TEST(correct_manual_filters_are_read_from_vifminfo)
{
	FILE *const f = fopen(SANDBOX_PATH "/vifminfo", "w");
	fprintf(f, "%c%s\n", LINE_TYPE_LWIN_FILT, "abc");
	fprintf(f, "%c%s\n", LINE_TYPE_RWIN_FILT, "cba");
	fclose(f);

	state_load(1);

	assert_string_equal("abc", lwin.prev_manual_filter);
	assert_string_equal("abc", matcher_get_expr(lwin.manual_filter));
	assert_string_equal("cba", rwin.prev_manual_filter);
	assert_string_equal("cba", matcher_get_expr(rwin.manual_filter));

	assert_success(remove(SANDBOX_PATH "/vifminfo"));
}

TEST(incorrect_manual_filters_in_vifminfo_are_cleared)
{
	FILE *const f = fopen(SANDBOX_PATH "/vifminfo", "w");
	fprintf(f, "%c%s\n", LINE_TYPE_LWIN_FILT, "*");
	fprintf(f, "%c%s\n", LINE_TYPE_RWIN_FILT, "?");
	fclose(f);

	state_load(1);

	assert_string_equal("", lwin.prev_manual_filter);
	assert_string_equal("", matcher_get_expr(lwin.manual_filter));
	assert_string_equal("", rwin.prev_manual_filter);
	assert_string_equal("", matcher_get_expr(rwin.manual_filter));

	assert_success(remove(SANDBOX_PATH "/vifminfo"));
}

TEST(file_with_newline_and_dash_in_history_does_not_cause_abort)
{
	FILE *const f = fopen(SANDBOX_PATH "/vifminfo", "w");
	fputs("d/dev/shm/TEES/out\n", f);
	fputs("\tAVxXQ1xDFJDzUCRLgIob\n-log.txt\n", f);
	fputs("10\n", f);
	fclose(f);

	cfg_resize_histories(1);

	state_load(1);

	assert_success(remove(SANDBOX_PATH "/vifminfo"));
}

TEST(optional_number_should_not_be_preceded_by_a_whitespace)
{
	FILE *const f = fopen(SANDBOX_PATH "/vifminfo", "w");
	fputs("d/dev/shm/TEES/out1\n", f);
	fputs("\tAVxXQ1xDFJDzUCRLgIob.log\n", f);
	fputs("11\n", f);
	fputs("d/dev/shm/TEES/out\n", f);
	fputs("\tAVxXQ1xDFJDzUCRLgIob-log.txt\n", f);
	fputs(" 10\n", f);
	fclose(f);

	state_load(1);

	assert_int_equal(1, lwin.history_pos);
	/* First entry is correct. */
	assert_int_equal(11, lwin.history[0].rel_pos);
	/* Second entry is not read in full. */
	assert_int_equal(0, lwin.history[1].rel_pos);

	assert_success(remove(SANDBOX_PATH "/vifminfo"));
}

TEST(history_is_automatically_extended)
{
	FILE *const f = fopen(SANDBOX_PATH "/vifminfo", "w");
	fputs("d/path1\n\tfile1\n", f);
	fputs("d/path2\n\tfile2\n", f);
	fputs("d/path1\n\tfile1\n", f);
	fputs("d/path2\n\tfile2\n", f);
	fputs("d/path1\n\tfile1\n", f);
	fputs("d/path2\n\tfile2\n", f);
	fputs("d/path1\n\tfile1\n", f);
	fputs("d/path2\n\tfile2\n", f);
	fputs("d/path1\n\tfile1\n", f);
	fputs("d/path2\n\tfile2\n", f);
	fputs("d/path1\n\tfile1\n", f);
	fputs("d/path2\n\tfile2\n", f);
	fclose(f);

	state_load(1);

	assert_int_equal(12, lwin.history_num);

	assert_success(remove(SANDBOX_PATH "/vifminfo"));
}

TEST(empty_vifminfo_option_produces_empty_state)
{
	JSON_Value *value = serialize_state(0);
	char *as_string = json_serialize_to_string(value);

	assert_string_equal("{\"gtabs\":["
	                         "{\"panes\":[{\"ptabs\":[{}]},{\"ptabs\":[{}]}]}"
	                       "]"
	                    "}", as_string);

	free(as_string);
	json_value_free(value);
}

TEST(histories_are_merged_correctly)
{
	cfg.vifm_info = VINFO_CHISTORY | VINFO_SHISTORY | VINFO_PHISTORY
	              | VINFO_FHISTORY | VINFO_EHISTORY | VINFO_MCHISTORY;

	hist_add(&curr_stats.cmd_hist, "command0", 0);
	hist_add(&curr_stats.cmd_hist, "command2", 2);
	hist_add(&curr_stats.search_hist, "search0", 0);
	hist_add(&curr_stats.search_hist, "search2", 2);
	hist_add(&curr_stats.prompt_hist, "prompt0", 0);
	hist_add(&curr_stats.prompt_hist, "prompt2", 2);
	hist_add(&curr_stats.filter_hist, "lfilter0", 0);
	hist_add(&curr_stats.filter_hist, "lfilter2", 2);
	hist_add(&curr_stats.exprreg_hist, "exprreg0", 0);
	hist_add(&curr_stats.exprreg_hist, "exprreg2", 2);
	hist_add(&curr_stats.menucmd_hist, "menucmd0", 0);
	hist_add(&curr_stats.menucmd_hist, "menucmd2", 2);

	/* First time, no merging is necessary. */
	write_info_file();

	hist_add(&curr_stats.cmd_hist, "command1", 1);
	hist_add(&curr_stats.search_hist, "search1", 1);
	hist_add(&curr_stats.prompt_hist, "prompt1", 1);
	hist_add(&curr_stats.filter_hist, "lfilter1", 1);
	hist_add(&curr_stats.exprreg_hist, "exprreg1", 1);
	hist_add(&curr_stats.menucmd_hist, "menucmd1", 1);

	/* Second time, touched vifminfo.json file, merging is necessary. */
	reset_timestamp(SANDBOX_PATH "/vifminfo.json");
	write_info_file();

	/* Clear histories. */
	cfg_resize_histories(0);
	cfg_resize_histories(10);

	state_load(0);

	assert_int_equal(3, curr_stats.cmd_hist.size);
	assert_int_equal(3, curr_stats.search_hist.size);
	assert_int_equal(3, curr_stats.prompt_hist.size);
	assert_int_equal(3, curr_stats.filter_hist.size);
	assert_int_equal(3, curr_stats.exprreg_hist.size);
	assert_int_equal(3, curr_stats.menucmd_hist.size);
	assert_string_equal("command2", curr_stats.cmd_hist.items[0].text);
	assert_int_equal(2, curr_stats.cmd_hist.items[0].timestamp);
	assert_string_equal("command1", curr_stats.cmd_hist.items[1].text);
	assert_int_equal(1, curr_stats.cmd_hist.items[1].timestamp);
	assert_string_equal("command0", curr_stats.cmd_hist.items[2].text);
	assert_int_equal(0, curr_stats.cmd_hist.items[2].timestamp);
	assert_string_equal("search2", curr_stats.search_hist.items[0].text);
	assert_int_equal(2, curr_stats.search_hist.items[0].timestamp);
	assert_string_equal("search1", curr_stats.search_hist.items[1].text);
	assert_int_equal(1, curr_stats.search_hist.items[1].timestamp);
	assert_string_equal("search0", curr_stats.search_hist.items[2].text);
	assert_int_equal(0, curr_stats.search_hist.items[2].timestamp);
	assert_string_equal("prompt2", curr_stats.prompt_hist.items[0].text);
	assert_int_equal(2, curr_stats.prompt_hist.items[0].timestamp);
	assert_string_equal("prompt1", curr_stats.prompt_hist.items[1].text);
	assert_int_equal(1, curr_stats.prompt_hist.items[1].timestamp);
	assert_string_equal("prompt0", curr_stats.prompt_hist.items[2].text);
	assert_int_equal(0, curr_stats.prompt_hist.items[2].timestamp);
	assert_string_equal("lfilter2", curr_stats.filter_hist.items[0].text);
	assert_int_equal(2, curr_stats.filter_hist.items[0].timestamp);
	assert_string_equal("lfilter1", curr_stats.filter_hist.items[1].text);
	assert_int_equal(1, curr_stats.filter_hist.items[1].timestamp);
	assert_string_equal("lfilter0", curr_stats.filter_hist.items[2].text);
	assert_int_equal(0, curr_stats.filter_hist.items[2].timestamp);
	assert_string_equal("exprreg2", curr_stats.exprreg_hist.items[0].text);
	assert_int_equal(2, curr_stats.exprreg_hist.items[0].timestamp);
	assert_string_equal("exprreg1", curr_stats.exprreg_hist.items[1].text);
	assert_int_equal(1, curr_stats.exprreg_hist.items[1].timestamp);
	assert_string_equal("exprreg0", curr_stats.exprreg_hist.items[2].text);
	assert_int_equal(0, curr_stats.exprreg_hist.items[2].timestamp);
	assert_string_equal("menucmd2", curr_stats.menucmd_hist.items[0].text);
	assert_int_equal(2, curr_stats.menucmd_hist.items[0].timestamp);
	assert_string_equal("menucmd1", curr_stats.menucmd_hist.items[1].text);
	assert_int_equal(1, curr_stats.menucmd_hist.items[1].timestamp);
	assert_string_equal("menucmd0", curr_stats.menucmd_hist.items[2].text);
	assert_int_equal(0, curr_stats.menucmd_hist.items[2].timestamp);

	assert_success(remove(SANDBOX_PATH "/vifminfo.json"));
}

TEST(view_sorting_round_trip)
{
	cfg.vifm_info = VINFO_TUI;

	opt_handlers_setup();
	lwin.columns = columns_create();
	rwin.columns = columns_create();
	columns_setup_column(SK_BY_NAME);
	columns_setup_column(SK_BY_SIZE);
	columns_setup_column(SK_BY_NITEMS);
	columns_setup_column(SK_BY_EXTENSION);
	columns_setup_column(SK_BY_DIR);
	columns_setup_column(SK_BY_FILEEXT);
	columns_setup_column(SK_BY_TARGET);
	columns_setup_column(SK_BY_TYPE);
	columns_setup_column(SK_BY_INAME);
	columns_setup_column(SK_BY_TIME_CHANGED);

	write_info_file();
	view_set_sort(lwin.sort_g, SK_NONE, SK_NONE);
	view_set_sort(rwin.sort_g, SK_NONE, SK_NONE);
	state_load(0);

	assert_int_equal(SK_BY_NAME, lwin.sort_g[0]);
	assert_int_equal(SK_BY_NAME, rwin.sort_g[0]);

	lwin.sort_g[0] = SK_BY_NITEMS;
	lwin.sort_g[1] = -SK_BY_EXTENSION;
	lwin.sort_g[2] = SK_BY_SIZE;
	lwin.sort_g[3] = -SK_BY_NAME;
	lwin.sort_g[4] = SK_BY_DIR;

	rwin.sort_g[0] = -SK_BY_TIME_CHANGED;
	rwin.sort_g[1] = SK_BY_TARGET;
	rwin.sort_g[2] = SK_BY_INAME;
	rwin.sort_g[3] = SK_BY_FILEEXT;
	rwin.sort_g[4] = -SK_BY_TYPE;
	rwin.sort_g[5] = -SK_BY_NAME;

	write_info_file();
	view_set_sort(lwin.sort_g, SK_NONE, SK_NONE);
	view_set_sort(rwin.sort_g, SK_NONE, SK_NONE);
	state_load(0);

	assert_int_equal(SK_BY_NITEMS, lwin.sort_g[0]);
	assert_int_equal(-SK_BY_EXTENSION, lwin.sort_g[1]);
	assert_int_equal(SK_BY_SIZE, lwin.sort_g[2]);
	assert_int_equal(-SK_BY_NAME, lwin.sort_g[3]);
	assert_int_equal(SK_BY_DIR, lwin.sort_g[4]);

	assert_int_equal(-SK_BY_TIME_CHANGED, rwin.sort_g[0]);
	assert_int_equal(SK_BY_TARGET, rwin.sort_g[1]);
	assert_int_equal(SK_BY_INAME, rwin.sort_g[2]);
	assert_int_equal(SK_BY_FILEEXT, rwin.sort_g[3]);
	assert_int_equal(-SK_BY_TYPE, rwin.sort_g[4]);
	assert_int_equal(-SK_BY_NAME, rwin.sort_g[5]);

	opt_handlers_teardown();
	columns_teardown();

	assert_success(remove(SANDBOX_PATH "/vifminfo.json"));
}

TEST(view_filters_round_trip)
{
	cfg.vifm_info = VINFO_STATE;
	opt_handlers_setup();

	lwin.invert = 0;
	lwin.hide_dot = 1;
	assert_success(filter_set(&lwin.auto_filter, "lauto-value"));
	assert_success(replace_matcher(&lwin.manual_filter, "^lmanual-value$"));
	rwin.invert = 1;
	rwin.hide_dot = 0;
	assert_success(filter_set(&rwin.auto_filter, "rauto-value"));
	assert_success(replace_matcher(&rwin.manual_filter, "^rmanual-value$"));

	write_info_file();
	lwin.invert = -1;
	lwin.hide_dot = -1;
	assert_success(filter_set(&lwin.auto_filter, "auto-clear"));
	assert_success(replace_matcher(&lwin.manual_filter, "^manual-clear$"));
	rwin.invert = -1;
	rwin.hide_dot = -1;
	assert_success(filter_set(&rwin.auto_filter, "auto-clear"));
	assert_success(replace_matcher(&rwin.manual_filter, "^manual-clear$"));
	state_load(0);

	assert_int_equal(0, lwin.invert);
	assert_int_equal(1, lwin.hide_dot);
	assert_string_equal("lauto-value", lwin.auto_filter.raw);
	assert_string_equal("^lmanual-value$", matcher_get_expr(lwin.manual_filter));
	assert_int_equal(1, rwin.invert);
	assert_int_equal(0, rwin.hide_dot);
	assert_string_equal("rauto-value", rwin.auto_filter.raw);
	assert_string_equal("^rmanual-value$", matcher_get_expr(rwin.manual_filter));

	opt_handlers_teardown();
}

TEST(savedirs_works_on_its_own)
{
	cfg.vifm_info = VINFO_SAVEDIRS;

	copy_str(lwin.curr_dir, sizeof(lwin.curr_dir), "/ldir");
	copy_str(rwin.curr_dir, sizeof(rwin.curr_dir), "/rdir");

	write_info_file();
	lwin.curr_dir[0] = '\0';
	rwin.curr_dir[0] = '\0';
	state_load(0);

	assert_string_equal("/ldir", lwin.curr_dir);
	assert_string_equal("/rdir", rwin.curr_dir);
}

TEST(dhistory_is_merged_correctly)
{
	cfg.vifm_info = VINFO_DHISTORY;

	flist_hist_setup(&lwin, "/dir1", "file5", 5, 5);
	flist_hist_setup(&lwin, "/dir2", "file6", 6, 6);

	/* First time, no merging is necessary. */
	write_info_file();

	/* Clear histories. */
	cfg_resize_histories(0);
	cfg_resize_histories(10);

	flist_hist_setup(&lwin, "/dir3", "file3", 3, 3);
	flist_hist_setup(&lwin, "/dir1", "file4", 4, 4);

	/* Second time, touched vifminfo.json file, merging is necessary. */
	reset_timestamp(SANDBOX_PATH "/vifminfo.json");
	write_info_file();

	/* Clear histories. */
	cfg_resize_histories(0);
	cfg_resize_histories(10);

	state_load(0);

	assert_int_equal(2, lwin.history_pos);
	assert_int_equal(3, lwin.history_num);

	assert_string_equal("/dir3", lwin.history[0].dir);
	assert_string_equal("file3", lwin.history[0].file);
	assert_int_equal(3, lwin.history[0].timestamp);
	assert_int_equal(3, lwin.history[0].rel_pos);
	assert_string_equal("/dir1", lwin.history[1].dir);
	assert_string_equal("file5", lwin.history[1].file);
	assert_int_equal(5, lwin.history[1].timestamp);
	assert_int_equal(5, lwin.history[1].rel_pos);
	assert_string_equal("/dir2", lwin.history[2].dir);
	assert_string_equal("file6", lwin.history[2].file);
	assert_int_equal(6, lwin.history[2].timestamp);
	assert_int_equal(6, lwin.history[2].rel_pos);

	assert_success(remove(SANDBOX_PATH "/vifminfo.json"));
}

TEST(size_of_dhistory_is_limited)
{
	cfg.vifm_info = VINFO_DHISTORY;

	/* Short history to fill it easily. */
	cfg.history_len = 2;
	opt_handlers_setup();
	cmds_init();

	/* Adding 3 items to make sure the limit is in effect. */
	flist_hist_setup(&lwin, "/dir1", "file1", 1, 1);
	flist_hist_setup(&lwin, "/dir2", "file2", 2, 2);
	flist_hist_setup(&lwin, "/dir3", "file3", 3, 3);
	assert_int_equal(2, lwin.history_num);

	/* Write the history to a file. */
	state_store();
	/* Reload the state and config (which should be empty in tests). */
	instance_start_restart(RT_MOST);
	state_load(0);
	instance_finish_restart();
	/* Verify that history was trimmed to the set limit. */
	assert_int_equal(2, lwin.history_num);

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

TEST(things_missing_from_vifminfo_option_are_dropped)
{
	cfg.vifm_info = VINFO_CS;
	write_info_file();

	reset_timestamp(SANDBOX_PATH "/vifminfo.json");

	cfg.vifm_info = VINFO_TUI | VINFO_STATE;
	write_info_file();

	char *locale = drop_locale();
	JSON_Value *json = json_parse_file(SANDBOX_PATH "/vifminfo.json");
	if(json != NULL)
	{
		assert_false(json_object_has_value(json_object(json), "color-scheme"));
		json_value_free(json);
	}

	restore_locale(locale);

	assert_success(remove(SANDBOX_PATH "/vifminfo.json"));
}

TEST(active_pane_is_respected_both_ways)
{
	curr_view = &lwin;
	other_view = &rwin;

	make_file(SANDBOX_PATH "/vifminfo.json", "{\"gtabs\":[{\"active-pane\":1}]}");
	state_load(0);
	assert_true(curr_view == &rwin);
	assert_true(other_view == &lwin);

	make_file(SANDBOX_PATH "/vifminfo.json", "{\"gtabs\":[{\"active-pane\":0}]}");
	state_load(0);
	assert_true(curr_view == &lwin);
	assert_true(other_view == &rwin);

	remove_file(SANDBOX_PATH "/vifminfo.json");
}

TEST(options_round_trip)
{
	opt_handlers_setup();
	cfg.vifm_info = VINFO_OPTIONS;

	assert_success(process_set_args("numberwidth=1", /*global=*/1, /*local=*/0));
	assert_success(process_set_args("numberwidth=11", /*global=*/0, /*local=*/1));
	assert_int_equal(1, curr_view->num_width_g);
	assert_int_equal(11, curr_view->num_width);
	assert_success(process_set_args("grepprg=111", /*global=*/1, /*local=*/0));
	assert_string_equal("111", cfg.grep_prg);

	write_info_file();
	assert_success(process_set_args("numberwidth=2", /*global=*/1, /*local=*/0));
	assert_success(process_set_args("numberwidth=22", /*global=*/0, /*local=*/1));
	assert_success(process_set_args("grepprg=222", /*global=*/1, /*local=*/0));

	state_load(0);
	/* Only global values of local options is stored and restored. */
	assert_int_equal(1, curr_view->num_width_g);
	assert_int_equal(1, curr_view->num_width);
	assert_string_equal("111", cfg.grep_prg);

	opt_handlers_teardown();
}

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