#include <stic.h>

#include <stdio.h> /* fclose() fopen() */

#include "../../src/cfg/config.h"
#include "../../src/engine/keys.h"
#include "../../src/engine/variables.h"
#include "../../src/lua/vlua.h"
#include "../../src/modes/modes.h"
#include "../../src/modes/wk.h"
#include "../../src/ui/ui.h"
#include "../../src/utils/str.h"
#include "../../src/utils/utils.h"
#include "../../src/cmd_core.h"
#include "../../src/event_loop.h"
#include "../../src/status.h"

#include <test-utils.h>

#include "asserts.h"

static vlua_t *vlua;

SETUP()
{
	make_abs_path(cfg.config_dir, sizeof(cfg.config_dir), SANDBOX_PATH, "", NULL);

	vlua = vlua_init();
	curr_view = &lwin;
	other_view = &rwin;

	engine_cmds_setup(/*real_completion=*/0);
}

TEARDOWN()
{
	vlua_finish(vlua);
	engine_cmds_teardown();
}

TEST(print_outputs_to_statusbar)
{
	GLUA_EQ(vlua, "arg1\targ2", "print('arg1', 'arg2')");
}

TEST(os_getenv_works)
{
	init_variables();

	GLUA_EQ(vlua, "nil", "print(os.getenv('VIFM_TEST'))");

	assert_success(let_variables("$VIFM_TEST='test'"));

	GLUA_EQ(vlua, "test", "print(os.getenv('VIFM_TEST'))");

	clear_variables();
}

TEST(vifm_errordialog)
{
	BLUA_ENDS(vlua,
			": bad argument #2 to 'errordialog' (string expected, got no value)",
			"vifm.errordialog('title')");
	GLUA_EQ(vlua, "", "vifm.errordialog('title', 'msg')");
}

TEST(fnamemodify)
{
	GLUA_EQ(vlua, "c",
			"print(vifm.fnamemodify('/a/b/c.d', ':t:r'))");
	GLUA_EQ(vlua, "/parent/c.d",
			"print(vifm.fnamemodify('c.d', ':p', '/parent'))");
}

TEST(vifm_escape)
{
	GLUA_EQ(vlua, get_env_type() == ET_UNIX ? "\\ " : "\" \"",
			"print(vifm.escape(' '))");
}

TEST(vifm_exists)
{
	GLUA_EQ(vlua, "", "testdata = '" TEST_DATA_PATH "'");

	GLUA_EQ(vlua, "n",
			"print(vifm.exists('/no/such/path!') and 'y' or 'n')");
	GLUA_EQ(vlua, "y",
			"print(vifm.exists(testdata) and 'y' or 'n')");
	GLUA_EQ(vlua, "y",
			"print(vifm.exists(testdata..'/existing-files/a') and 'y' or 'n')");
}

TEST(vifm_executable)
{
	const char *const exec_file = SANDBOX_PATH "/exec" EXE_SUFFIX;
	create_executable(exec_file);

	GLUA_EQ(vlua, "n",
			"print(vifm.executable('.') and 'y' or 'n')");
	GLUA_EQ(vlua, "n",
			"print(vifm.executable('" SANDBOX_PATH "') and 'y' or 'n')");
	GLUA_EQ(vlua, "y",
			"print(vifm.executable('" SANDBOX_PATH "/exec" EXE_SUFFIX "')"
			"      and 'y' or 'n')");
	GLUA_EQ(vlua, "n",
			"print(vifm.executable('" TEST_DATA_PATH "/read/two-lines')"
			"      and 'y' or 'n')");

	assert_success(remove(exec_file));
}

TEST(vifm_makepath)
{
	GLUA_EQ(vlua, "", "sandbox = '" SANDBOX_PATH "'");

	GLUA_EQ(vlua, "", "vifm.makepath(sandbox..'/dir')");
	remove_dir(SANDBOX_PATH "/dir");

	GLUA_EQ(vlua, "", "vifm.makepath(sandbox..'/dir1/dir2')");
	remove_dir(SANDBOX_PATH "/dir1/dir2");
	remove_dir(SANDBOX_PATH "/dir1");
}

TEST(vifm_expand)
{
	copy_str(curr_view->curr_dir, sizeof(curr_view->curr_dir), "/tst");

	GLUA_EQ(vlua, "/tst", "print(vifm.expand('%d'))");
	GLUA_EQ(vlua, "\\\\tst", "print(vifm.expand('%d:p:gs!/!\\\\\\\\!'))");
}

TEST(vifmview_cd)
{
	stub_colmgr();
	conf_setup();
	view_setup(curr_view);

	GLUA_EQ(vlua, "", "testdata = '" TEST_DATA_PATH "'");

	copy_str(curr_view->curr_dir, sizeof(curr_view->curr_dir), "/tst");

	GLUA_EQ(vlua, "y", "print(vifm.currview():cd(testdata) and 'y' or 'n')");

	view_teardown(curr_view);
	conf_teardown();
}

TEST(sb_info_outputs_to_statusbar)
{
	curr_stats.save_msg = 0;
	GLUA_EQ(vlua, "info", "vifm.sb.info 'info'");
	assert_int_equal(1, curr_stats.save_msg);
}

TEST(sb_error_outputs_to_statusbar)
{
	curr_stats.save_msg = 0;
	GLUA_EQ(vlua, "err", "vifm.sb.error 'err'");
	assert_int_equal(1, curr_stats.save_msg);
}

TEST(sb_quick_message_is_not_stored)
{
	curr_stats.save_msg = 0;
	GLUA_EQ(vlua, "", "vifm.sb.quick 'msg'");
	assert_int_equal(0, curr_stats.save_msg);
}

TEST(vifm_currview)
{
	cmds_init();

	conf_setup();
	cfg.pane_tabs = 0;
	view_setup(curr_view);
	view_setup(other_view);
	opt_handlers_setup();
	columns_setup_column(SK_BY_NAME);
	columns_setup_column(SK_BY_SIZE);

	GLUA_EQ(vlua, "", "l = vifm.currview()");
	swap_view_roles();
	GLUA_EQ(vlua, "", "r = vifm.currview()");

	/* Both non-nil and aren't equal. */
	GLUA_EQ(vlua, "y", "print(l and 'y' or 'n')");
	GLUA_EQ(vlua, "y", "print(r and 'y' or 'n')");
	GLUA_EQ(vlua, "y", "print(r ~= l and 'y' or 'n')");

	/* Can access visible views. */
	GLUA_EQ(vlua, "", "r:cd('/')");
	GLUA_EQ(vlua, "", "l:cd('/')");

	/* Can't access view that can't be found. */
	++curr_view->id;
	BLUA_ENDS(vlua, "Invalid VifmView object (associated view is dead)",
			"r:cd('bla')");

	assert_success(cmds_dispatch1("tabnew", curr_view, CIT_COMMAND));

	GLUA_EQ(vlua, "", "l:cd('/')");

	assert_success(cmds_dispatch1("tabonly", curr_view, CIT_COMMAND));

	columns_teardown();
	opt_handlers_teardown();
	view_teardown(curr_view);
	view_teardown(other_view);
	conf_teardown();
}

TEST(vifm_version)
{
	GLUA_EQ(vlua, "false", "print(vifm.version.api.has('feature'))");

	GLUA_EQ(vlua, "0\t2\t0",
			"print(vifm.version.api.major,"
			"      vifm.version.api.minor,"
			"      vifm.version.api.patch)");

	GLUA_EQ(vlua, "true", "print(vifm.version.api.atleast(0, 0, 0))");
	GLUA_EQ(vlua, "true", "print(vifm.version.api.atleast(0, 0, 1))");
	GLUA_EQ(vlua, "true", "print(vifm.version.api.atleast(0, 1))");

	GLUA_EQ(vlua, "", "v = vifm.version.api");
	GLUA_EQ(vlua, "false", "print(v.atleast(v.major, v.minor, v.patch + 1))");
	GLUA_EQ(vlua, "false", "print(v.atleast(v.major, v.minor + 1))");
	GLUA_EQ(vlua, "false", "print(v.atleast(v.major + 1))");

	GLUA_EQ(vlua, "false", "print(vifm.version.api.atleast(0, 99, 1))");
	GLUA_EQ(vlua, "false", "print(vifm.version.api.atleast(0, 99))");
	GLUA_EQ(vlua, "false", "print(vifm.version.api.atleast(99))");
}

TEST(vifm_run)
{
	conf_setup();

	BLUA_ENDS(vlua, ": Unrecognized value for `pause`: unknown",
			"print(vifm.run({ cmd = 'exit 3',"
			"                  usetermmux = false,"
			"                  pause = 'unknown' }))");

	/* This waits for user input on Windows. */
#ifndef _WIN32
	GLUA_EQ(vlua, "0",
			"print(vifm.run({ cmd = 'exit 0',"
			"                 usetermmux = false,"
			"                 pause = 'onerror' }))");
	GLUA_EQ(vlua, "1",
			"print(vifm.run({ cmd = 'exit 1',"
			"                 usetermmux = false,"
			"                 pause = 'always' }))");
#endif

	GLUA_EQ(vlua, "2",
			"print(vifm.run({ cmd = 'exit 2',"
			"                 usetermmux = false,"
			"                 pause = 'never' }))");

	conf_teardown();
}

TEST(vifm_input)
{
	view_setup(&lwin);
	view_setup(&rwin);

	cfg.timeout_len = 1;

	modes_init();

	/* Preparing input beforehand, because input() runs nested event loop. */
	feed_keys(L"def" WK_C_m);
	GLUA_EQ(vlua, "abcdef",
			"print(vifm.input({ prompt = 'write: ',"
			"                   initial = 'abc',"
			"                   complete = 'dir' }))");

	/* Preparing input beforehand, because input() runs nested event loop. */
	feed_keys(L"xyz" WK_C_m);
	GLUA_EQ(vlua, "xyz",
			"print(vifm.input({ prompt = 'write: ', complete = 'file' }))");

	/* Preparing input beforehand, because input() runs nested event loop. */
	feed_keys(WK_C_c);
	GLUA_EQ(vlua, "nil", "print(vifm.input({ prompt = 'write:' }))");

	BLUA_ENDS(vlua, ": Unrecognized value for `complete`: bla",
				"print(vifm.input({ prompt = 'write: ', complete = 'bla' }))");

	vle_keys_reset();

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

TEST(vifm_stdout)
{
	curr_stats.original_stdout = fopen(SANDBOX_PATH "/out", "w");
	assert_non_null(curr_stats.original_stdout);

	GLUA_EQ(vlua, "true",
			"print(vifm.stdout() ~= nil)");

	GLUA_EQ(vlua, "true",
			"stream = vifm.stdout()"
			"print(stream == vifm.stdout())");

	GLUA_EQ(vlua, "",
			"vifm.stdout():write('test')");

	fclose(curr_stats.original_stdout);
	curr_stats.original_stdout = NULL;

	const char *lines[] = { "test" };
	file_is(SANDBOX_PATH "/out", lines, 1);

	remove_file(SANDBOX_PATH "/out");
}

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