// SPDX-License-Identifier: BSD-3-Clause
#define _FILE_OFFSET_BITS 64

#include <errno.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#include <cmocka.h>

#include "pathbuf.h"

static void init_path(struct pathbuf *path, const char *s, bool set_len)
{
	size_t len = strlen(s);

	assert_true(len < sizeof(path->buf));
	memcpy(path->buf, s, len + 1);
	path->len = set_len ? len : 0;
}

static void assert_dirname(const char *in, const char *out, bool set_len)
{
	struct pathbuf path;

	init_path(&path, in, set_len);
	qdl_pathbuf_dirname(&path);

	assert_string_equal(path.buf, out);
	assert_int_equal(path.len, strlen(out));
}

static void test_dirname_posix_root(void **state)
{
	(void)state;
	assert_dirname("/", "/", true);
	assert_dirname("/", "/", false);
}

static void test_dirname_posix_absolute(void **state)
{
	(void)state;
	assert_dirname("/foo", "/", true);
	assert_dirname("/foo/bar", "/foo", true);
	assert_dirname("/foo/bar/", "/foo", true);
}

static void test_dirname_relative(void **state)
{
	(void)state;
	assert_dirname("foo", "foo", true);
	assert_dirname("foo/bar", "foo", true);
	assert_dirname("foo/bar/", "foo", true);
}

static void test_reset_and_dup(void **state)
{
	struct pathbuf src;
	struct pathbuf dst;

	(void)state;

	init_path(&src, "/tmp/example", true);
	qdl_pathbuf_dup(&dst, &src);
	assert_string_equal(dst.buf, src.buf);
	assert_int_equal(dst.len, src.len);

	qdl_pathbuf_reset(&dst);
	assert_string_equal(dst.buf, "");
	assert_int_equal(dst.len, 0);

	qdl_pathbuf_reset(NULL);
}

static void test_push_basic_behavior(void **state)
{
	struct pathbuf path = {0};
	int ret;

	(void)state;

	ret = qdl_pathbuf_push(&path, "foo");
	assert_int_equal(ret, 0);
	assert_string_equal(path.buf, "foo");
	assert_int_equal(path.len, strlen("foo"));

	ret = qdl_pathbuf_push(&path, "bar");
	assert_int_equal(ret, 0);
	assert_string_equal(path.buf, "foo/bar");
	assert_int_equal(path.len, strlen("foo/bar"));

	ret = qdl_pathbuf_push(&path, "");
	assert_int_equal(ret, 0);
	assert_string_equal(path.buf, "foo/bar");
	assert_int_equal(path.len, strlen("foo/bar"));

	ret = qdl_pathbuf_push(&path, "./baz");
	assert_int_equal(ret, 0);
	assert_string_equal(path.buf, "foo/bar/baz");
	assert_int_equal(path.len, strlen("foo/bar/baz"));

#ifdef _WIN32
	ret = qdl_pathbuf_push(&path, ".\\qux");
	assert_int_equal(ret, 0);
	assert_string_equal(path.buf, "foo/bar/baz/qux");
	assert_int_equal(path.len, strlen("foo/bar/baz/qux"));

	ret = qdl_pathbuf_push(&path, "nested\\leaf");
	assert_int_equal(ret, 0);
	assert_string_equal(path.buf, "foo/bar/baz/qux/nested/leaf");
	assert_int_equal(path.len, strlen("foo/bar/baz/qux/nested/leaf"));

	ret = qdl_pathbuf_push(&path, "C:/abs");
	assert_int_equal(ret, 0);
	assert_string_equal(path.buf, "C:/abs");
	assert_int_equal(path.len, strlen("C:/abs"));
#else
	ret = qdl_pathbuf_push(&path, "/abs");
	assert_int_equal(ret, 0);
	assert_string_equal(path.buf, "/abs");
	assert_int_equal(path.len, strlen("/abs"));
#endif
}

static void test_push_invalid_args(void **state)
{
	struct pathbuf path = {0};

	(void)state;

	assert_int_equal(qdl_pathbuf_push(NULL, "foo"), -EINVAL);
	assert_int_equal(qdl_pathbuf_push(&path, NULL), -EINVAL);
}

static void test_push_overflow_keeps_state(void **state)
{
	struct pathbuf path = {0};
	char before[PATH_MAX];
	size_t max_prefix_len;
	int ret;

	(void)state;

	max_prefix_len = sizeof(path.buf) - 1;
	memset(path.buf, 'a', max_prefix_len);
	path.buf[max_prefix_len] = '\0';
	path.len = max_prefix_len;
	memcpy(before, path.buf, sizeof(before));

	ret = qdl_pathbuf_push(&path, "b");
	assert_int_equal(ret, -ENAMETOOLONG);
	assert_string_equal(path.buf, before);
	assert_int_equal(path.len, max_prefix_len);
}

#ifdef _WIN32
static void test_dirname_windows_drive(void **state)
{
	(void)state;
	assert_dirname("C:\\", "C:\\", true);
	assert_dirname("C:\\foo", "C:\\", true);
	assert_dirname("C:\\foo\\bar", "C:\\foo", true);
	assert_dirname("C:/foo/bar", "C:/foo", true);
}

static void test_dirname_windows_unc(void **state)
{
	(void)state;
	assert_dirname("\\\\server\\share", "\\\\server\\share", true);
	assert_dirname("\\\\server\\share\\dir", "\\\\server\\share", true);
	assert_dirname("\\\\server\\share\\dir\\sub", "\\\\server\\share\\dir", true);
}
#endif

int main(void)
{
	const struct CMUnitTest tests[] = {
		cmocka_unit_test(test_dirname_posix_root),
		cmocka_unit_test(test_dirname_posix_absolute),
		cmocka_unit_test(test_dirname_relative),
		cmocka_unit_test(test_reset_and_dup),
		cmocka_unit_test(test_push_basic_behavior),
		cmocka_unit_test(test_push_invalid_args),
		cmocka_unit_test(test_push_overflow_keeps_state),
#ifdef _WIN32
		cmocka_unit_test(test_dirname_windows_drive),
		cmocka_unit_test(test_dirname_windows_unc),
#endif
	};

	return cmocka_run_group_tests(tests, NULL, NULL);
}
