#include <stic.h>

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

#include <stddef.h> /* NULL */
#include <stdio.h> /* EOF FILE fclose() fgetc() fopen() remove() */
#include <stdlib.h> /* free() */
#include <string.h> /* strcpy() */

#include <test-utils.h>

#include "../../src/cfg/config.h"
#include "../../src/engine/keys.h"
#include "../../src/modes/modes.h"
#include "../../src/modes/wk.h"
#include "../../src/ui/column_view.h"
#include "../../src/ui/statusbar.h"
#include "../../src/ui/ui.h"
#include "../../src/utils/fs.h"
#include "../../src/compare.h"
#include "../../src/event_loop.h"
#include "../../src/filelist.h"
#include "../../src/ops.h"
#include "../../src/status.h"

static void column_line_print(const char buf[], int offset, AlignType align,
		const char full_column[], const format_info_t *info);
static int files_are_identical(const char a[], const char b[]);

static char *saved_cwd;

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

	view_setup(&lwin);
	view_setup(&rwin);

	modes_init();
	opt_handlers_setup();

	cfg.use_system_calls = 1;
	cfg.sizefmt.base = 1;

	undo_setup();

	saved_cwd = save_cwd();
}

TEARDOWN()
{
	restore_cwd(saved_cwd);

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

	vle_keys_reset();
	opt_handlers_teardown();

	undo_teardown();
}

TEST(moving_does_not_work_in_non_diff)
{
	strcpy(lwin.curr_dir, TEST_DATA_PATH "/compare/a");
	strcpy(rwin.curr_dir, TEST_DATA_PATH "/compare/b");
	(void)compare_one_pane(&lwin, CT_CONTENTS, LT_ALL, CF_NONE);

	(void)compare_move(&lwin, &rwin);

	assert_true(files_are_identical(
				TEST_DATA_PATH "/compare/a/same-content-different-name-1",
				TEST_DATA_PATH "/compare/b/same-content-different-name-1"));
}

TEST(moving_fake_entry_removes_the_other_file)
{
	strcpy(rwin.curr_dir, SANDBOX_PATH);
	strcpy(lwin.curr_dir, TEST_DATA_PATH "/compare/b");

	create_file(SANDBOX_PATH "/empty");

	(void)compare_two_panes(CT_CONTENTS, LT_ALL, CF_GROUP_PATHS | CF_SHOW);
	rwin.list_pos = 0;
	lwin.list_pos = 0;
	(void)compare_move(&lwin, &rwin);

	assert_failure(remove(SANDBOX_PATH "/empty"));
}

TEST(moving_to_fake_entry_creates_the_other_file_and_entry_is_updated)
{
	make_abs_path(rwin.curr_dir, sizeof(rwin.curr_dir), SANDBOX_PATH, "",
			saved_cwd);
	make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), TEST_DATA_PATH,
			"compare/b", saved_cwd);

	(void)compare_two_panes(CT_CONTENTS, LT_ALL, CF_GROUP_PATHS | CF_SHOW);
	rwin.list_pos = 3;
	lwin.list_pos = 3;
	(void)compare_move(&lwin, &rwin);

	columns_setup_column(SK_BY_NAME);
	columns_setup_column(SK_BY_ID);
	columns_set_line_print_func(&column_line_print);
	lwin.columns = columns_create();
	rwin.columns = columns_create();
	lwin.column_count = 1;
	rwin.column_count = 1;
	curr_stats.load_stage = 2;

	assert_true(process_scheduled_updates_of_view(&lwin));
	assert_true(process_scheduled_updates_of_view(&rwin));

	curr_stats.load_stage = 0;
	columns_teardown();

	assert_true(lwin.dir_entry[3].id == rwin.dir_entry[3].id);
	assert_true(lwin.dir_entry[3].size == rwin.dir_entry[3].size);
	assert_true(lwin.dir_entry[3].mtime == rwin.dir_entry[3].mtime);

	assert_success(remove(SANDBOX_PATH "/same-name-same-content"));
}

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

TEST(moving_mismatched_entry_makes_files_equal)
{
	make_abs_path(rwin.curr_dir, sizeof(rwin.curr_dir), SANDBOX_PATH, "",
			saved_cwd);
	make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), TEST_DATA_PATH,
			"compare/b", saved_cwd);

	copy_file(TEST_DATA_PATH "/compare/a/same-name-different-content",
			SANDBOX_PATH "/same-name-different-content");

	assert_false(files_are_identical(SANDBOX_PATH "/same-name-different-content",
				TEST_DATA_PATH "/compare/b/same-name-different-content"));

	(void)compare_two_panes(CT_CONTENTS, LT_ALL, CF_GROUP_PATHS | CF_SHOW);
	rwin.list_pos = 2;
	lwin.list_pos = 2;
	(void)compare_move(&lwin, &rwin);

	assert_true(files_are_identical(SANDBOX_PATH "/same-name-different-content",
				TEST_DATA_PATH "/compare/b/same-name-different-content"));

	assert_success(remove(SANDBOX_PATH "/same-name-different-content"));
}

TEST(moving_equal_does_nothing)
{
	strcpy(rwin.curr_dir, SANDBOX_PATH);
	strcpy(lwin.curr_dir, TEST_DATA_PATH "/compare/b");

	copy_file(TEST_DATA_PATH "/compare/a/same-name-same-content",
			SANDBOX_PATH "/same-name-same-content");

	assert_true(files_are_identical(SANDBOX_PATH "/same-name-same-content",
				TEST_DATA_PATH "/compare/b/same-name-same-content"));

	(void)compare_two_panes(CT_CONTENTS, LT_ALL, CF_GROUP_PATHS | CF_SHOW);

	/* Replace file unbeknownst to main code. */
	copy_file(TEST_DATA_PATH "/compare/a/same-name-different-content",
			SANDBOX_PATH "/same-name-same-content");
	assert_false(files_are_identical(SANDBOX_PATH "/same-name-same-content",
				TEST_DATA_PATH "/compare/b/same-name-same-content"));

	rwin.list_pos = 3;
	lwin.list_pos = 3;
	(void)compare_move(&lwin, &rwin);

	/* Check that file wasn't replaced. */
	assert_false(files_are_identical(SANDBOX_PATH "/same-name-same-content",
				TEST_DATA_PATH "/compare/b/same-name-same-content"));

	assert_success(remove(SANDBOX_PATH "/same-name-same-content"));
}

TEST(can_move_selection)
{
	strcpy(lwin.curr_dir, TEST_DATA_PATH "/compare/a");
	strcpy(rwin.curr_dir, SANDBOX_PATH);

	(void)compare_two_panes(CT_CONTENTS, LT_ALL, CF_GROUP_PATHS | CF_SHOW);

	int i;
	for(i = 0; i < lwin.list_rows; ++i)
	{
		if(fentry_is_valid(&lwin.dir_entry[i]))
		{
			lwin.dir_entry[i].selected = 1;
			++lwin.selected_files;
		}
	}

	(void)compare_move(&lwin, &rwin);
	assert_int_equal(0, lwin.selected_files);

	remove_file(SANDBOX_PATH "/same-content-different-name-1");
	remove_file(SANDBOX_PATH "/same-name-different-content");
	remove_file(SANDBOX_PATH "/same-name-same-content");
}

TEST(diff_stats_are_correct_and_stay_correct)
{
	strcpy(lwin.curr_dir, TEST_DATA_PATH "/compare/a");
	strcpy(rwin.curr_dir, TEST_DATA_PATH "/compare/b");

	ui_sb_msg("");
	(void)compare_two_panes(CT_CONTENTS, LT_ALL,
			CF_SHOW_UNIQUE_LEFT | CF_SHOW_IDENTICAL);
	modes_statusbar_update();
	assert_string_equal("(on compare) +identical: 2, +/-unique: 1/2",
			ui_sb_last());
	(void)vle_keys_exec_timed_out(WK_C_w WK_x);
	curr_stats.save_msg = 0;
	modes_statusbar_update();
	assert_string_equal("(on compare) +identical: 2, -/+unique: 2/1",
			ui_sb_last());

	/* Repeat the same, but with grouping by paths. */
	ui_sb_msg("");
	(void)compare_two_panes(CT_CONTENTS, LT_ALL,
			CF_SHOW_UNIQUE_LEFT | CF_SHOW_IDENTICAL | CF_GROUP_PATHS);
	curr_stats.save_msg = 0;
	modes_statusbar_update();
	assert_string_equal(
			"(on compare) +identical: 2, -different: 1, +/-unique: 1/0",
			ui_sb_last());
	(void)vle_keys_exec_timed_out(WK_C_w WK_x);
	curr_stats.save_msg = 0;
	modes_statusbar_update();
	assert_string_equal(
			"(on compare) +identical: 2, -different: 1, -/+unique: 0/1",
			ui_sb_last());
}

TEST(file_id_is_not_updated_on_failed_move, IF(regular_unix_user))
{
	make_abs_path(rwin.curr_dir, sizeof(rwin.curr_dir), SANDBOX_PATH, "",
			saved_cwd);
	make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), TEST_DATA_PATH,
			"compare/b", saved_cwd);

	copy_file(TEST_DATA_PATH "/compare/a/same-name-different-content",
			SANDBOX_PATH "/same-name-different-content");

	assert_false(files_are_identical(SANDBOX_PATH "/same-name-different-content",
				TEST_DATA_PATH "/compare/b/same-name-different-content"));

	(void)compare_two_panes(CT_CONTENTS, LT_ALL, CF_GROUP_PATHS | CF_SHOW);
	rwin.list_pos = 2;
	lwin.list_pos = 2;

	assert_success(chmod(SANDBOX_PATH, 0000));
	(void)compare_move(&lwin, &rwin);
	assert_success(chmod(SANDBOX_PATH, 0777));

	assert_false(files_are_identical(SANDBOX_PATH "/same-name-different-content",
				TEST_DATA_PATH "/compare/b/same-name-different-content"));
	assert_false(lwin.dir_entry[2].id == rwin.dir_entry[2].id);

	assert_success(remove(SANDBOX_PATH "/same-name-different-content"));
}

static int
files_are_identical(const char a[], const char b[])
{
	FILE *const a_file = fopen(a, "rb");
	FILE *const b_file = fopen(b, "rb");
	int a_data, b_data;

	if(a_file == NULL || b_file == NULL)
	{
		if(a_file != NULL)
		{
			fclose(a_file);
		}
		if(b_file != NULL)
		{
			fclose(b_file);
		}
		return 0;
	}

	do
	{
		a_data = fgetc(a_file);
		b_data = fgetc(b_file);
	}
	while(a_data != EOF && b_data != EOF);

	fclose(b_file);
	fclose(a_file);

	return a_data == b_data && a_data == EOF;
}

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