/* Copyright (c) Dovecot authors, see top-level COPYING file */

#include "lib.h"
#include "array.h"
#include "settings.h"
#include "test-common.h"

/*
 * settings_get()
 */

struct test2_fruit_settings {
	pool_t pool;

	const char *name;
	bool eat;
	unsigned int preference;
};

struct test2_settings {
	pool_t pool;

	const char *title;
	ARRAY_TYPE(const_string) fruits;
	ARRAY_TYPE(const_string) attrs;
};

#undef DEF
#define DEF(type, name) \
	SETTING_DEFINE_STRUCT_##type("test2_fruit_"#name, name, \
				     struct test2_fruit_settings)

static const struct setting_define test2_fruit_setting_defines[] = {
	DEF(STR, name),
	DEF(BOOL, eat),
	DEF(UINT, preference),
	SETTING_DEFINE_LIST_END
};

static struct test2_fruit_settings test2_fruit_default_settings = {
	.name = "",
	.eat = FALSE,
	.preference = UINT_MAX,
};

static const struct setting_parser_info test2_fruit_setting_parser_info = {
	.name = "test2_fruit",

	.defines = test2_fruit_setting_defines,
	.defaults = &test2_fruit_default_settings,

	.struct_size = sizeof(struct test2_fruit_settings),
	.pool_offset1 = 1 + offsetof(struct test2_fruit_settings, pool),
};

#undef DEF
#define DEF(type, name) \
	SETTING_DEFINE_STRUCT_##type("test2_"#name, name, struct test2_settings)

static struct setting_define test2_setting_defines[] = {
	DEF(STR, title),

	{ .type = SET_FILTER_ARRAY, .key = "test2_fruit",
	   .offset = offsetof(struct test2_settings, fruits),
	   .filter_array_field_name = "test2_fruit_name" },
	DEF(STRLIST, attrs),
	SETTING_DEFINE_LIST_END
};

static struct test2_settings test2_default_settings = {
	.title = "",
	.fruits = ARRAY_INIT,
	.attrs = ARRAY_INIT,
};

static const struct setting_parser_info test2_setting_parser_info = {
	.name = "test2",

	.defines = test2_setting_defines,
	.defaults = &test2_default_settings,

	.struct_size = sizeof(struct test2_settings),
	.pool_offset1 = 1 + offsetof(struct test2_settings, pool),
};

static void test_settings_get_scenario(
	const char *scenario_name,
	const struct setting_filter_array_order *order, const char *result[])
{
	static const char *const settings[] = {
		"test2_title=Fruit Preferences",
		"test2_fruit+=Orange",
		"test2_fruit/Orange/eat=no",
		"test2_fruit/Orange/preference=1",
		"test2_fruit+=Apple",
		"test2_fruit/Apple/eat=yes",
		"test2_fruit/Apple/preference=2",
		"test2_fruit+=Grapefruit",
		"test2_fruit/Grapefruit/eat=no",
		"test2_fruit/Grapefruit/preference=0",
		"test2_fruit+=Mulberry",
		"test2_fruit/Mulberry/eat=yes",
		"test2_fruit/Mulberry/preference=6",
		"test2_fruit+=Lemon",
		"test2_fruit/Lemon/eat=yes",
		"test2_fruit/Lemon/preference=4",
		"test2_fruit+=Fig",
		"test2_fruit/Fig/eat=no",
		"test2_fruit/Fig/preference=5",
		"test2_fruit+=PineApple",
		"test2_fruit/PineApple/eat=yes",
		"test2_fruit/PineApple/preference=3",
		NULL
	};
	const char *error = NULL;
	pool_t pool;
	static struct test2_settings *set;
	static struct settings_root *set_root;
	int ret;

	/* Modified like this only for testing; normally this is all const */
	test2_setting_defines[1].filter_array_order = order;

	test_begin(t_strdup_printf("settings_get - %s", scenario_name));

	pool = pool_alloconly_create(MEMPOOL_GROWING"test2 pool", 2048);

	set_root = settings_root_init();
	for (unsigned int i = 0; settings[i] != NULL; i++) {
		const char *key, *value;
		t_split_key_value_eq(settings[i], &key, &value);
		settings_root_override(set_root, key, value,
				       SETTINGS_OVERRIDE_TYPE_CODE);
	}
	struct event *event = event_create(NULL);
	event_set_ptr(event, SETTINGS_EVENT_ROOT, set_root);

	ret = settings_get(event, &test2_setting_parser_info,
			   (order != NULL ?
			    SETTINGS_GET_FLAG_SORT_FILTER_ARRAYS : 0),
			   &set, &error);
	test_assert(ret == 0);
	test_assert(error == NULL);
	if (error != NULL)
		i_error("%s", error);
	if (ret == 0) {
		test_assert(array_is_created(&set->fruits));
		test_assert_strcmp(set->title, "Fruit Preferences");
	}
	if (ret == 0 && array_is_created(&set->fruits)) {
		unsigned int count, i;
		const char *const *fruits = array_get(&set->fruits, &count);
		for (i = 0; i < count; i++) {
			struct test2_fruit_settings *fruit_set;
			ret = settings_get_filter(
				event, "test2_fruit", fruits[i],
				&test2_fruit_setting_parser_info, 0,
				&fruit_set, &error);
			test_assert(ret == 0);
			test_assert(error == NULL);

			test_assert_strcmp(fruit_set->name, result[i]);

			settings_free(fruit_set);
		}
	}

	settings_free(set);
	settings_root_deinit(&set_root);
	pool_unref(&pool);
	event_unref(&event);

	test_end();
}

static void test_settings_get(void)
{
	static const struct setting_filter_array_order order_by_preference = {
		.info = &test2_fruit_setting_parser_info,
		.field_name = "test2_fruit_preference",
	};
	static struct setting_filter_array_order order_by_name = {
		.info = &test2_fruit_setting_parser_info,
		.field_name = "test2_fruit_name",
	};
	static const struct setting_filter_array_order
		order_by_preference_reverse = {
		.info = &test2_fruit_setting_parser_info,
		.field_name = "test2_fruit_preference",
		.reverse = TRUE,
	};
	static struct setting_filter_array_order order_by_name_reverse = {
		.info = &test2_fruit_setting_parser_info,
		.field_name = "test2_fruit_name",
		.reverse = TRUE,
	};
	static const char *result_unsorted[] = {
		"Orange",
		"Apple",
		"Grapefruit",
		"Mulberry",
		"Lemon",
		"Fig",
		"PineApple",
	};
	static const char *result_sorted_preference[] = {
		"Grapefruit",
		"Orange",
		"Apple",
		"PineApple",
		"Lemon",
		"Fig",
		"Mulberry",
	};
	static const char *result_sorted_name[] = {
		"Apple",
		"Fig",
		"Grapefruit",
		"Lemon",
		"Mulberry",
		"Orange",
		"PineApple",
	};
	static const char *result_sorted_preference_reverse[] = {
		"Mulberry",
		"Fig",
		"Lemon",
		"PineApple",
		"Apple",
		"Orange",
		"Grapefruit",
	};
	static const char *result_sorted_name_reverse[] = {
		"PineApple",
		"Orange",
		"Mulberry",
		"Lemon",
		"Grapefruit",
		"Fig",
		"Apple",
	};

	test_settings_get_scenario("not sorted", NULL, result_unsorted);
	test_settings_get_scenario("sort by preference", &order_by_preference,
				   result_sorted_preference);
	test_settings_get_scenario("sort by name", &order_by_name,
				   result_sorted_name);
	test_settings_get_scenario("sort by preference (reverse)",
				   &order_by_preference_reverse,
				   result_sorted_preference_reverse);
	test_settings_get_scenario("sort by name (reverse)",
				   &order_by_name_reverse,
				   result_sorted_name_reverse);
}

static int
test_var_expand_hierarchy_key1(const char *field_name ATTR_UNUSED,
			       const char **value_r, void *context,
			       const char **error_r ATTR_UNUSED)
{
	test_assert_strcmp(context, "context1");
	*value_r = "key1_value";
	return 0;
}

static int
test_var_expand_hierarchy_key2(const char *key ATTR_UNUSED,
			       const char **value_r, void *context,
			       const char **error_r ATTR_UNUSED)
{
	test_assert_strcmp(context, "context2");
	*value_r = "key2_value";
	return 0;
}

static void test_var_expand_hierarchy(void)
{
	test_begin("settings_get - hierarchical event");
	char *context1 = "context1";
	char *context2 = "context2";
	struct var_expand_table tab1[] = {
		{ .key = "key1", .func = test_var_expand_hierarchy_key1 },
		{ .key = NULL }
	};
	struct var_expand_params params1 = {
		.table = tab1,
		.context = context1,
	};

	struct var_expand_provider prov2[] = {
		{ "key2", test_var_expand_hierarchy_key2 },
		{ NULL, NULL }
	};
	struct var_expand_params params2 = {
		.providers = prov2,
		.context = context2,
	};

	struct var_expand_params params3;
	i_zero(&params3);
	params3.tables_arr = (const struct var_expand_table *const[]) {
		tab1,
		tab1,
		NULL
	};
	params3.contexts = (void *const[]) {
		context1,
		context1,
		VAR_EXPAND_CONTEXTS_END,
	};

	struct settings_root *set_root = settings_root_init();
	settings_root_override(set_root, "test2_title", "%{key1} and %{key2:foo}",
			       SETTINGS_OVERRIDE_TYPE_DEFAULT);

	struct event *root = event_create(NULL);
	event_set_ptr(root, SETTINGS_EVENT_ROOT, set_root);
	event_set_ptr(root, SETTINGS_EVENT_VAR_EXPAND_PARAMS, &params1);

	struct event *child = event_create(root);
	event_set_ptr(child, SETTINGS_EVENT_VAR_EXPAND_PARAMS, &params2);

	struct test2_settings *set;
	const char *error;
	test_assert(settings_get(child, &test2_setting_parser_info, 0,
				 &set, &error) == 0);
	test_assert_strcmp(set->title, "key1_value and key2_value");
	settings_free(set);

	struct event *child2 = event_create(child);
	event_set_ptr(child2, SETTINGS_EVENT_VAR_EXPAND_PARAMS, &params3);

	test_assert(settings_get(child2, &test2_setting_parser_info, 0,
				 &set, &error) == 0);
	test_assert_strcmp(set->title, "key1_value and key2_value");
	settings_free(set);


	event_unref(&child2);
	event_unref(&child);
	event_unref(&root);
	settings_root_deinit(&set_root);
	test_end();
}

static void test_var_expand_strlist_key(void)
{
	test_begin("settings_get - strlist key expansion");

	char *context1 = "context1";
	struct var_expand_table tab1[] = {
		{ .key = "key1", .func = test_var_expand_hierarchy_key1 },
		{ .key = NULL }
	};
	struct var_expand_params params1 = {
		.table = tab1,
		.context = context1,
	};

	struct settings_root *set_root = settings_root_init();
	/* key with a %variable - must be expanded, but its raw form is the
	   list identity used for dedup/override */
	settings_root_override(set_root, "test2_attrs/%{key1}", "v-%{key1}",
			       SETTINGS_OVERRIDE_TYPE_DEFAULT);
	/* a second, distinct raw key that must survive alongside the first */
	settings_root_override(set_root, "test2_attrs/plain", "%{key1}",
			       SETTINGS_OVERRIDE_TYPE_DEFAULT);

	struct event *event = event_create(NULL);
	event_set_ptr(event, SETTINGS_EVENT_ROOT, set_root);
	event_set_ptr(event, SETTINGS_EVENT_VAR_EXPAND_PARAMS, &params1);

	struct test2_settings *set;
	const char *error;
	test_assert(settings_get(event, &test2_setting_parser_info, 0,
				 &set, &error) == 0);

	unsigned int count;
	const char *const *attrs = array_get(&set->attrs, &count);
	/* both raw keys survived dedup (no collision on raw identity);
	   overrides are filled in reverse order */
	test_assert(count == 4);
	if (count == 4) {
		/* literal key kept, value expanded */
		test_assert_strcmp(attrs[0], "plain");
		test_assert_strcmp(attrs[1], "key1_value");
		/* key and value both expanded */
		test_assert_strcmp(attrs[2], "key1_value");
		test_assert_strcmp(attrs[3], "v-key1_value");
	}
	settings_free(set);

	event_unref(&event);
	settings_root_deinit(&set_root);
	test_end();
}

static void test_strlist_key_no_override_expand(void)
{
	test_begin("settings_get - strlist key not expanded for overrides");

	char *context1 = "context1";
	struct var_expand_table tab1[] = {
		{ .key = "key1", .func = test_var_expand_hierarchy_key1 },
		{ .key = NULL }
	};
	struct var_expand_params params1 = {
		.table = tab1,
		.context = context1,
	};

	struct settings_root *set_root = settings_root_init();
	/* -o style and userdb overrides must NOT have their keys or values
	   expanded, the same way override values are not expanded. */
	settings_root_override(set_root, "test2_attrs/%{key1}", "%{key1}",
			       SETTINGS_OVERRIDE_TYPE_CLI_PARAM);
	settings_root_override(set_root, "test2_attrs/u-%{key1}", "%{key1}",
			       SETTINGS_OVERRIDE_TYPE_USERDB);

	struct event *event = event_create(NULL);
	event_set_ptr(event, SETTINGS_EVENT_ROOT, set_root);
	event_set_ptr(event, SETTINGS_EVENT_VAR_EXPAND_PARAMS, &params1);

	struct test2_settings *set;
	const char *error;
	test_assert(settings_get(event, &test2_setting_parser_info, 0,
				 &set, &error) == 0);

	unsigned int count;
	const char *const *attrs = array_get(&set->attrs, &count);
	test_assert(count == 4);
	if (count == 4) {
		/* keys and values stay literal */
		test_assert_strcmp(attrs[0], "%{key1}");    /* -o key   */
		test_assert_strcmp(attrs[1], "%{key1}");    /* -o value */
		test_assert_strcmp(attrs[2], "u-%{key1}");  /* userdb key   */
		test_assert_strcmp(attrs[3], "%{key1}");    /* userdb value */
	}
	settings_free(set);

	event_unref(&event);
	settings_root_deinit(&set_root);
	test_end();
}

struct test_empty_default_settings {
	pool_t pool;

	unsigned int num;
	unsigned int interval;
	const char *str;
};

#undef DEF
#define DEF(type, name) \
	SETTING_DEFINE_STRUCT_##type("test_empty_default_"#name, name, \
				     struct test_empty_default_settings)

static const struct setting_define test_empty_default_setting_defines[] = {
	DEF(UINT, num),
	DEF(TIME, interval),
	DEF(STR, str),
	SETTING_DEFINE_LIST_END
};

static const struct test_empty_default_settings
test_empty_default_default_settings = {
	.num = 42,
	.interval = 30,
	.str = "default",
};

/* An empty built-in default for a setting that can't be empty (num, interval)
   must be treated as "no default" and inherited from the struct default,
   rather than failing to parse. An empty default for an emptyable setting
   (str) is applied normally. */
static const struct setting_keyvalue
test_empty_default_default_settings_keyvalue[] = {
	{ "test_empty_default_num", "" },
	{ "test_empty_default_interval", "" },
	{ "test_empty_default_str", "" },
	{ NULL, NULL }
};

static const struct setting_parser_info test_empty_default_setting_parser_info = {
	.name = "test_empty_default",

	.defines = test_empty_default_setting_defines,
	.defaults = &test_empty_default_default_settings,
	.default_settings = test_empty_default_default_settings_keyvalue,

	.struct_size = sizeof(struct test_empty_default_settings),
	.pool_offset1 = 1 + offsetof(struct test_empty_default_settings, pool),
};

static void test_settings_empty_default(void)
{
	test_begin("settings_get - empty built-in default means no default");

	struct settings_root *set_root = settings_root_init();
	struct event *event = event_create(NULL);
	event_set_ptr(event, SETTINGS_EVENT_ROOT, set_root);

	struct test_empty_default_settings *set;
	const char *error = NULL;
	int ret = settings_get(event, &test_empty_default_setting_parser_info,
			       0, &set, &error);
	test_assert(ret == 0);
	test_assert(error == NULL);
	if (error != NULL)
		i_error("%s", error);
	if (ret == 0) {
		/* non-emptyable settings inherit their struct defaults */
		test_assert(set->num == 42);
		test_assert(set->interval == 30);
		/* emptyable setting keeps the empty default */
		test_assert_strcmp(set->str, "");
		settings_free(set);
	}

	event_unref(&event);
	settings_root_deinit(&set_root);
	test_end();
}

int main(void)
{
	static void (*const test_functions[])(void) = {
		test_settings_get,
		test_var_expand_hierarchy,
		test_var_expand_strlist_key,
		test_strlist_key_no_override_expand,
		test_settings_empty_default,
		NULL
	};
	return test_run(test_functions);
}
