/* Copyright (c) 2025 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "str.h"
#include "imap-seqset.h"
#include "imap-util.h"
#include "seq-range-array.h"
#include "test-common.h"

static void imap_write_seq_range_star_one(string_t *dest, uint32_t seq)
{
	if (seq == UINT32_MAX)
		str_append_c(dest, '*');
	else
		str_printfa(dest, "%u", seq);
}

static void
imap_write_seq_range_star(string_t *dest, const ARRAY_TYPE(seq_range) *array)
{
	const struct seq_range *range;
	unsigned int i, count;

	range = array_get(array, &count);
	for (i = 0; i < count; i++) {
		if (i > 0)
			str_append_c(dest, ',');
		imap_write_seq_range_star_one(dest, range[i].seq1);
		if (range[i].seq1 != range[i].seq2) {
			str_append_c(dest, ':');
			imap_write_seq_range_star_one(dest, range[i].seq2);
		}
	}
}

static void test_imap_seq_set_parse(void)
{
	static const struct {
		const char *input;
		const char *output;
		int ret;
	} tests[] = {
		{ "0", "", -1 },
		{ "1", "1", 0 },
		{ "2:4", "2:4", 0 },
		{ "5:*", "5:*", 0 },
		{ "1,3,5", "1,3,5", 0 },
		{ "1,3:5,7:*", "1,3:5,7:*", 0 },
		{ "1,2,3,4,5", "1:5", 0 },
		{ "1,2,4,5", "1:2,4:5", 0 },
		{ "1,3,2,5,4", "1:5", 0 },
		/* Comma at the end is not actually valid, but for now at least
		   we allow it. At least imapc used to send UID STORE commands
		   with uidsets ending with comma. */
		{ "1,2,", "1:2", 0 },
		{ "4294967296", "", -1 },
		{ "4294967297", "", -1 },
		{ "4294967295", "4294967294", 0 },
		{ "4294967294:4294967295", "4294967294", 0 },
		{ "4294967293:4294967295", "4294967293:4294967294", 0 },
		{ "", "", -1 },
		{ ",", "", -1 },
		{ "1,,5", "", -1 },
		{ "1:2,3,,5", "", -1 },
		{ "a", "", -1 },
		{ "1:a", "", -1 },
		{ "1:2a", "", -1 },
	};
	ARRAY_TYPE(seq_range) ranges;

	test_begin("imap_seq_set_parse()");
	t_array_init(&ranges, 4);

	for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
		array_clear(&ranges);
		int ret = imap_seq_set_parse(tests[i].input, &ranges);
		test_assert_idx(ret == tests[i].ret, i);
		if (ret == 0) {
			string_t *str = t_str_new(128);
			imap_write_seq_range_star(str, &ranges);
			test_assert_strcmp_idx(str_c(str), tests[i].output, i);
		}
	}
	test_end();
}

static void test_imap_seq_set_nostar_parse(void)
{
	static const struct {
		const char *input;
		const char *output;
		int ret;
	} tests[] = {
		{ "1", "1", 0 },
		{ "2:4", "2:4", 0 },
		{ "1,3,5", "1,3,5", 0 },
		{ "1,3:5", "1,3:5", 0 },
		{ "1,2,3,4,5", "1:5", 0 },
		{ "1,2,4,5", "1:2,4:5", 0 },
		{ "1,3,2,5,4", "1:5", 0 },
		{ "5:*", "", -1 },
		{ "1,3:5,7:*", "", -1 },
		{ "*", "", -1 },
	};
	ARRAY_TYPE(seq_range) ranges;

	test_begin("imap_seq_set_nostar_parse()");
	t_array_init(&ranges, 4);

	for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
		array_clear(&ranges);
		int ret = imap_seq_set_nostar_parse(tests[i].input, &ranges);
		test_assert_idx(ret == tests[i].ret, i);
		if (ret == 0) {
			string_t *str = t_str_new(128);
			imap_write_seq_range_star(str, &ranges);
			test_assert_strcmp_idx(str_c(str), tests[i].output, i);
		}
	}
	test_end();
}

static void test_imap_seq_range_parse(void)
{
	static const struct {
		const char *input;
		uint32_t seq1, seq2;
		int ret;
	} tests[] = {
		{ "1", 1, 1, 0 },
		{ "2:4", 2, 4, 0 },
		{ "5:*", 5, (uint32_t)-1, 0 },
		{ "*", (uint32_t)-1, (uint32_t)-1, 0 },
		{ "0", 0, 0, -1 },
		{ "1:0", 0, 0, -1 },
		{ "", 0, 0, -1 },
		{ ":", 0, 0, -1 },
		{ "1:", 0, 0, -1 },
		{ ":5", 0, 0, -1 },
		{ "a", 0, 0, -1 },
		{ "1a", 0, 0, -1 },
		{ "1:a", 0, 0, -1 },
	};
	uint32_t seq1, seq2;

	test_begin("imap_seq_range_parse()");

	for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
		int ret = imap_seq_range_parse(tests[i].input, &seq1, &seq2);
		test_assert_idx(ret == tests[i].ret, i);
		if (ret == 0) {
			test_assert_idx(seq1 == tests[i].seq1 &&
					seq2 == tests[i].seq2, i);
		}
	}
	test_end();
}

static void test_imap_seq_set_ordered_parse(void)
{
	static const struct {
		const char *input;
		const char *output;
		int ret;
	} tests[] = {
		{ "", "", -1 },
		{ "0", "", -1 },
		{ "1,", "", -1 },
		{ "1:", "", -1 },
		{ ":1", "", -1 },
		{ "0:1", "", -1 },
		{ "*", "", -1 },
		{ "1:*", "", -1 },
		{ "1", "1", 0 },
		{ "3:4,1:2,5,700,500:503", "3 4 1 2 5 700 500 501 502 503", 0 },
	};
	ARRAY_TYPE(uint32_t) dest;
	string_t *str = t_str_new(64);

	test_begin("imap_seq_set_ordered_parse()");

	t_array_init(&dest, 8);
	for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
		array_clear(&dest);
		int ret = imap_seq_set_ordered_parse(tests[i].input, &dest);
		test_assert_idx(ret == tests[i].ret, i);
		if (ret == 0) {
			str_truncate(str, 0);
			const uint32_t *seqp;
			array_foreach(&dest, seqp)
				str_printfa(str, " %u", *seqp);
			test_assert_strcmp_idx(str_c(str) + 1, tests[i].output, i);
		}
	}
	test_end();
}

int main(void)
{
	static void (*const test_functions[])(void) = {
		test_imap_seq_set_parse,
		test_imap_seq_set_nostar_parse,
		test_imap_seq_range_parse,
		test_imap_seq_set_ordered_parse,
		NULL
	};
	return test_run(test_functions);
}
