#include <stic.h>

#include <stdio.h> /* remove() */
#include <stdlib.h> /* free() */
#include <string.h> /* strcmp() */

#include <test-utils.h>

#include "../../src/int/file_magic.h"
#include "../../src/utils/fs.h"
#include "../../src/utils/matcher.h"
#include "../../src/utils/str.h"

static void check_glob(matcher_t *m);
static void check_fast_globs(matcher_t *m);
static void check_regexp(matcher_t *m);
static int has_mime_type_detection(void);
static int has_mime_type_detection_and_symlinks(void);

TEST(empty_matcher_can_be_created)
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("", 0, 0, "", &error));
	assert_true(matcher_is_empty(m));

	assert_false(matcher_matches(m, ""));
	assert_false(matcher_matches(m, "a"));

	matcher_free(m);
}

TEST(empty_matcher_matches_nothing_can_be_created)
{
	char *error;
	matcher_t *m = matcher_alloc("", 0, 0, "", &error);
	assert_true(matcher_is_empty(m));
	assert_string_equal(NULL, error);

	matcher_free(m);
}

TEST(glob)
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("{*.ext}", 0, 1, "", &error));
	assert_null(error);

	check_glob(m);

	matcher_free(m);
}

TEST(regexp)
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("/^x*$/", 0, 1, "", &error));
	assert_null(error);

	check_regexp(m);

	matcher_free(m);
}

TEST(defaulted_glob)
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("*.ext", 0, 1, "", &error));
	assert_null(error);

	check_glob(m);

	matcher_free(m);
}

TEST(defaulted_regexp)
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("^x*$", 0, 0, "", &error));
	assert_null(error);

	check_regexp(m);

	matcher_free(m);
}

TEST(full_path_glob)
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("{{/tmp/[^/].ext}}", 0, 1, "", &error));
	assert_null(error);

	assert_true(matcher_matches(m, "/tmp/a.ext"));
	assert_true(matcher_matches(m, "/tmp/b.ext"));

	assert_false(matcher_matches(m, "/tmp/a,ext"));
	assert_false(matcher_matches(m, "/tmp/prog/a.ext"));
	assert_false(matcher_matches(m, "/tmp/.ext"));
	assert_false(matcher_matches(m, "/tmp/ab.ext"));

	matcher_free(m);
}

TEST(full_path_regexp)
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("//^/tmp/[^/]+\\.ext$//", 0, 1, "",
				&error));
	assert_null(error);

	assert_true(matcher_matches(m, "/tmp/a.ext"));
	assert_true(matcher_matches(m, "/tmp/b.ext"));
	assert_true(matcher_matches(m, "/tmp/ab.ext"));

	assert_false(matcher_matches(m, "/tmp/a,ext"));
	assert_false(matcher_matches(m, "/tmp/prog/a.ext"));
	assert_false(matcher_matches(m, "/tmp/.ext"));

	matcher_free(m);
}

TEST(matcher_negation)
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("!{*.ext}", 0, 1, "", &error));
	assert_null(error);
	assert_true(matcher_matches(m, "file.ext2"));
	assert_false(matcher_matches(m, "name.ext"));
	matcher_free(m);

	assert_non_null(m = matcher_alloc("!/^x*$/", 0, 1, "", &error));
	assert_null(error);
	assert_true(matcher_matches(m, "axxxxx"));
	assert_false(matcher_matches(m, "xxxxx"));
	matcher_free(m);

	assert_non_null(m = matcher_alloc("!*.ext", 0, 1, "", &error));
	assert_null(error);
	assert_true(matcher_matches(m, "!abc.ext"));
	assert_false(matcher_matches(m, "!abc.ext2"));
	matcher_free(m);

	assert_non_null(m = matcher_alloc("!x*$", 0, 0, "", &error));
	assert_null(error);
	assert_true(matcher_matches(m, "a!xx"));
	assert_false(matcher_matches(m, "xx"));
	matcher_free(m);

	assert_non_null(m = matcher_alloc("!{{/tmp/[^/].ext}}", 0, 1, "", &error));
	assert_null(error);
	assert_true(matcher_matches(m, "/tmp/a.ext1"));
	assert_false(matcher_matches(m, "/tmp/a.ext"));
	matcher_free(m);

	assert_non_null(m = matcher_alloc("!//^/tmp/[^/]+\\.ext$//", 0, 1, "",
				&error));
	assert_null(error);
	assert_true(matcher_matches(m, "/bin/ab.ext"));
	assert_false(matcher_matches(m, "/tmp/ab.ext"));
	matcher_free(m);
}

TEST(empty_regexp)
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("", 0, 0, ".*\\.ext", &error));
	assert_null(error);
	assert_true(matcher_matches(m, "/tmp/a.ext"));
	assert_false(matcher_matches(m, "/tmp/a.axt"));
	assert_string_equal(".*\\.ext", matcher_get_expr(m));
	assert_string_equal(".*\\.ext", matcher_get_undec(m));
	matcher_free(m);

	assert_non_null(m = matcher_alloc("//", 0, 1, ".*\\.ext", &error));
	assert_null(error);
	assert_true(matcher_matches(m, "/tmp/a.ext"));
	assert_false(matcher_matches(m, "/tmp/a.axt"));
	assert_string_equal("/.*\\.ext/", matcher_get_expr(m));
	assert_string_equal(".*\\.ext", matcher_get_undec(m));
	matcher_free(m);

	assert_non_null(m = matcher_alloc("//i", 0, 1, ".*\\.ext", &error));
	assert_null(error);
	assert_true(matcher_matches(m, "/tmp/a.Ext"));
	assert_false(matcher_matches(m, "/tmp/a.axt"));
	assert_string_equal("/.*\\.ext/i", matcher_get_expr(m));
	assert_string_equal(".*\\.ext", matcher_get_undec(m));
	matcher_free(m);

	assert_non_null(m = matcher_alloc("//Iii", 0, 1, ".*\\.ext", &error));
	assert_null(error);
	assert_true(matcher_matches(m, "/tmp/a.Ext"));
	assert_false(matcher_matches(m, "/tmp/a.axt"));
	assert_string_equal("/.*\\.ext/Iii", matcher_get_expr(m));
	assert_string_equal(".*\\.ext", matcher_get_undec(m));
	matcher_free(m);

	assert_non_null(m = matcher_alloc("////I", 0, 1, "tmp/.*\\.Ext", &error));
	assert_null(error);
	assert_true(matcher_matches(m, "/tmp/a.Ext"));
	assert_false(matcher_matches(m, "/tmp/a.axt"));
	assert_string_equal("//tmp/.*\\.Ext//I", matcher_get_expr(m));
	assert_string_equal("tmp/.*\\.Ext", matcher_get_undec(m));
	matcher_free(m);
}

TEST(wrong_regex_flag)
{
	char *error;
	matcher_t *m;

	assert_null(m = matcher_alloc("/reg/x", 0, 1, ".*\\.ext", &error));
	assert_non_null(error);
	free(error);
}

TEST(expr_includes_itself)
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("*.c", 0, 1, "", &error));
	assert_null(error);

	assert_true(matcher_includes(m, m));

	matcher_free(m);
}

TEST(different_exprs_match_inclusion)
{
	char *error;
	matcher_t *m1, *m2;

	assert_non_null(m1 = matcher_alloc("*.c", 0, 1, "", &error));
	assert_null(error);
	assert_non_null(m2 = matcher_alloc("/.*\\.c/", 0, 1, "", &error));
	assert_null(error);

	assert_false(matcher_includes(m1, m2));

	matcher_free(m2);
	matcher_free(m1);
}

TEST(global_match_inclusion)
{
	char *error;
	matcher_t *m1, *m2;

	assert_non_null(m1 = matcher_alloc("*.cpp,*.c", 0, 1, "", &error));
	assert_null(error);
	assert_non_null(m2 = matcher_alloc("*.c", 0, 1, "", &error));
	assert_null(error);

	assert_true(matcher_includes(m1, m2));

	matcher_free(m2);
	matcher_free(m1);
}

TEST(global_match_no_inclusion)
{
	char *error;
	matcher_t *m1, *m2;

	assert_non_null(m1 = matcher_alloc("*.cpp,*.c", 0, 1, "", &error));
	assert_null(error);
	assert_non_null(m2 = matcher_alloc("*.hpp", 0, 1, "", &error));
	assert_null(error);

	assert_false(matcher_includes(m1, m2));

	matcher_free(m2);
	matcher_free(m1);
}

TEST(regex_inclusion_case_is_taken_into_account)
{
	char *error;
	matcher_t *m1, *m2;

	assert_non_null(m1 = matcher_alloc("/a/I", 0, 1, "", &error));
	assert_null(error);
	assert_non_null(m2 = matcher_alloc("/A/I", 0, 1, "", &error));
	assert_null(error);

	assert_false(matcher_includes(m1, m2));

	matcher_free(m2);
	matcher_free(m1);
}

TEST(globs_are_cloned)
{
	char *error;
	matcher_t *m, *clone;

	assert_non_null(m = matcher_alloc("{*.ext}", 0, 1, "", &error));
	assert_null(error);
	assert_non_null(clone = matcher_clone(m));

	check_glob(m);
	matcher_free(m);

	check_glob(clone);
	matcher_free(clone);
}

TEST(globs_are_case_insensitive)
{
	char *error;
	matcher_t *m, *clone;

	assert_non_null(m = matcher_alloc("{*.ExT}", 0, 1, "", &error));
	assert_null(error);
	assert_non_null(clone = matcher_clone(m));

	check_glob(m);
	matcher_free(m);

	check_glob(clone);
	matcher_free(clone);
}

TEST(fast_globs)
{
	char *error;
	matcher_t *m;

	m = matcher_alloc("{*suffix,prefix*,mid*dle,literal}", 0, 1, "", &error);
	assert_true(matcher_is_fast(m));
	assert_non_null(m);
	assert_null(error);
	check_fast_globs(m);
	matcher_free(m);

	/* Control against equivalent non-fast-globs. */
	m = matcher_alloc("{*[s]uffix,[p]refix*,[m]id*dle,[l]iteral}", 0, 1, "",
			&error);
	assert_false(matcher_is_fast(m));
	assert_non_null(m);
	assert_null(error);
	check_fast_globs(m);
	matcher_free(m);

	m = matcher_alloc("{mid\\*dle}", 0, 1, "", &error);
	assert_true(matcher_is_fast(m));
	assert_true(matcher_matches(m, "mid*dle"));
	assert_false(matcher_matches(m, "middle"));
	matcher_free(m);
}

TEST(regexps_are_cloned)
{
	char *error;
	matcher_t *m, *clone;

	assert_non_null(m = matcher_alloc("/^x*$/", 0, 1, "", &error));
	assert_null(error);
	assert_non_null(clone = matcher_clone(m));

	check_regexp(m);
	matcher_free(m);

	check_regexp(clone);
	matcher_free(clone);
}

TEST(comma_escaping)
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("{a,,b,*.ext,c,,d}", 0, 1, "", &error));
	assert_null(error);

	check_glob(m);
	assert_false(matcher_matches(m, "a"));
	assert_false(matcher_matches(m, "b"));
	assert_true(matcher_matches(m, "a,b"));
	assert_true(matcher_matches(m, "c,d"));

	matcher_free(m);
}

TEST(forced_glob_matcher_does_not_strip_decorations)
{
	char *error;
	matcher_t *m = matcher_alloc_glob("{abc}", &error);
	assert_null(error);

	assert_string_equal("{abc}", matcher_get_undec(m));
	assert_false(matcher_matches(m, "abc"));
	assert_true(matcher_matches(m, "{abc}"));

	matcher_free(m);
}

TEST(empty_forced_glob_matcher_matches_nothing)
{
	char *error;
	matcher_t *m = matcher_alloc_glob("", &error);
	assert_string_equal(NULL, error);

	assert_string_equal("", matcher_get_undec(m));
	assert_false(matcher_matches(m, ""));
	matcher_free(m);
}

TEST(mime_type_pattern, IF(has_mime_type_detection))
{
	char *error;
	matcher_t *m;

	assert_non_null(m = matcher_alloc("<text/plain>", 0, 1, "", &error));
	assert_null(error);
	assert_true(matcher_matches(m, TEST_DATA_PATH "/read/dos-line-endings"));
	assert_false(matcher_matches(m, TEST_DATA_PATH "/read/binary-data"));
	matcher_free(m);

	assert_non_null(m = matcher_alloc("<text/*>", 0, 1, "", &error));
	assert_null(error);
	assert_true(matcher_matches(m, TEST_DATA_PATH "/read/dos-line-endings"));
	assert_false(matcher_matches(m, TEST_DATA_PATH "/read/binary-data"));
	matcher_free(m);
}

TEST(mime_type_inclusion, IF(has_mime_type_detection))
{
	char *error;
	matcher_t *m, *m1, *m2;

	assert_non_null(m = matcher_alloc("<a/b,c/Dd>", 0, 1, "", &error));
	assert_null(error);
	assert_non_null(m1 = matcher_alloc("<c/dd>", 0, 1, "", &error));
	assert_null(error);
	assert_non_null(m2 = matcher_alloc("<c/d>", 0, 1, "", &error));
	assert_null(error);

	assert_true(matcher_includes(m, m));
	assert_true(matcher_includes(m, m1));
	assert_false(matcher_includes(m, m2));

	matcher_free(m2);
	matcher_free(m1);
	matcher_free(m);
}

TEST(mime_type_of_link_is_that_of_its_ultimate_target,
		IF(has_mime_type_detection_and_symlinks))
{
	char *error;
	matcher_t *m;

	assert_success(make_symlink(".", "link"));
	assert_success(make_symlink("link", "link2"));

	char *expr = format_str("<%s>", get_mimetype(SANDBOX_PATH, 0));

	assert_non_null(m = matcher_alloc(expr, 0, 1, "", &error));
	assert_null(error);
	assert_true(matcher_matches(m, "link"));
	assert_true(matcher_matches(m, "link2"));
	matcher_free(m);

	free(expr);

	assert_success(remove("link"));
	assert_success(remove("link2"));
}

static void
check_glob(matcher_t *m)
{
	assert_true(matcher_matches(m, "{.ext"));
	assert_true(matcher_matches(m, "}.ext"));
	assert_true(matcher_matches(m, "name.ext"));

	assert_false(matcher_matches(m, "{,ext"));
}

static void
check_fast_globs(matcher_t *m)
{
	assert_false(matcher_matches(m, "suffix"));
	assert_true(matcher_matches(m, "0suffix"));
	assert_true(matcher_matches(m, "00suffix"));
	assert_false(matcher_matches(m, "suffix0"));
	assert_false(matcher_matches(m, "0suffix0"));

	assert_true(matcher_matches(m, "prefix"));
	assert_true(matcher_matches(m, "prefix0"));
	assert_true(matcher_matches(m, "prefix00"));
	assert_false(matcher_matches(m, "0prefix"));
	assert_false(matcher_matches(m, "0prefix0"));

	assert_true(matcher_matches(m, "literal"));
	assert_false(matcher_matches(m, "0literal"));
	assert_false(matcher_matches(m, "literal0"));
	assert_false(matcher_matches(m, "0literal0"));

	assert_true(matcher_matches(m, "middle"));
	assert_true(matcher_matches(m, "mid123dle"));
	assert_false(matcher_matches(m, "0middle"));
	assert_false(matcher_matches(m, "middle0"));
	assert_false(matcher_matches(m, "0middle0"));
}

static void
check_regexp(matcher_t *m)
{
	assert_true(matcher_matches(m, "x"));
	assert_true(matcher_matches(m, "xx"));
	assert_true(matcher_matches(m, "xxx"));

	assert_false(matcher_matches(m, "y"));
	assert_false(matcher_matches(m, "xy"));
	assert_false(matcher_matches(m, "yx"));
}

static int
has_mime_type_detection(void)
{
	return get_mimetype(TEST_DATA_PATH "/read/dos-line-endings", 0) != NULL
	    && strcmp(get_mimetype(TEST_DATA_PATH "/read/binary-data", 0),
	                           "text/plain") != 0;
}

static int
has_mime_type_detection_and_symlinks(void)
{
	return has_mime_type_detection()
	    && symlinks_available();
}

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