/*
 * Copyright (C) 2021 Jérémie Galarneau <jeremie.galarneau@efficios.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-only
 *
 */

#include <lttng/action/path-internal.h>

struct lttng_action_path_comm {
	uint32_t index_count;
	uint64_t indexes[];
} LTTNG_PACKED;

struct lttng_action_path *lttng_action_path_create(
		const uint64_t *indexes, size_t index_count)
{
	int ret;
	size_t i;
	struct lttng_action_path *path = NULL;

	if (!indexes && index_count > 0) {
		goto error;
	}

	path = zmalloc(sizeof(*path));
	if (!path) {
		goto error;
	}

	lttng_dynamic_array_init(&path->indexes, sizeof(uint64_t), NULL);

	for (i = 0; i < index_count; i++) {
		ret = lttng_dynamic_array_add_element(
				&path->indexes, &indexes[i]);
		if (ret) {
			goto error;
		}
	}

	goto end;
error:
	lttng_action_path_destroy(path);
	path = NULL;
end:
	return path;
}

enum lttng_action_path_status lttng_action_path_get_index_count(
		const struct lttng_action_path *path, size_t *index_count)
{
	enum lttng_action_path_status status;

	if (!path || !index_count) {
		status = LTTNG_ACTION_PATH_STATUS_INVALID;
		goto end;
	}

	*index_count = lttng_dynamic_array_get_count(&path->indexes);
	status = LTTNG_ACTION_PATH_STATUS_OK;
end:
	return status;
}

enum lttng_action_path_status lttng_action_path_get_index_at_index(
		const struct lttng_action_path *path,
		size_t path_index,
		uint64_t *out_index)
{
	enum lttng_action_path_status status;

	if (!path || !out_index ||
			path_index >= lttng_dynamic_array_get_count(
				&path->indexes)) {
		status = LTTNG_ACTION_PATH_STATUS_INVALID;
		goto end;
	}

	*out_index = *((typeof(out_index)) lttng_dynamic_array_get_element(
			&path->indexes, path_index));
	status = LTTNG_ACTION_PATH_STATUS_OK;
end:
	return status;
}

void lttng_action_path_destroy(struct lttng_action_path *action_path)
{
	if (!action_path) {
		goto end;
	}

	lttng_dynamic_array_reset(&action_path->indexes);
	free(action_path);
end:
	return;
}

LTTNG_HIDDEN
int lttng_action_path_copy(const struct lttng_action_path *src,
		struct lttng_action_path *dst)
{
	int ret;
	size_t i, src_count;

	assert(src);
	assert(dst);

	lttng_dynamic_array_init(&dst->indexes, sizeof(uint64_t), NULL);
	src_count = lttng_dynamic_array_get_count(&src->indexes);

	for (i = 0; i < src_count; i++) {
		const void *index = lttng_dynamic_array_get_element(
				&src->indexes, i);

		ret = lttng_dynamic_array_add_element(&dst->indexes, index);
		if (ret) {
			goto error;
		}
	}

	ret = 0;
	goto end;
error:
	lttng_dynamic_array_reset(&dst->indexes);
end:
	return ret;
}

LTTNG_HIDDEN
ssize_t lttng_action_path_create_from_payload(
		struct lttng_payload_view *view,
		struct lttng_action_path **_action_path)
{
	ssize_t consumed_size = 0, ret = -1;
	const struct lttng_action_path_comm *header;
	struct lttng_action_path *action_path = NULL;
	const struct lttng_payload_view header_view =
			lttng_payload_view_from_view(view, 0, sizeof(*header));

	if (!lttng_payload_view_is_valid(&header_view)) {
		goto end;
	}

	header = (typeof(header)) header_view.buffer.data;
	consumed_size += header_view.buffer.size;

	/*
	 * An action path of size 0 can exist and represents a trigger with a
	 * single non-list action. Handle it differently since a payload view of
	 * size 0 is considered invalid.
	 */
	if (header->index_count != 0)
	{
		const struct lttng_payload_view indexes_view =
				lttng_payload_view_from_view(view,
						consumed_size,
						header->index_count *
								sizeof(uint64_t));

		if (!lttng_payload_view_is_valid(&indexes_view)) {
			goto end;
		}

		consumed_size += indexes_view.buffer.size;
		action_path = lttng_action_path_create(
				(const uint64_t *) indexes_view.buffer.data,
				header->index_count);
		if (!action_path) {
			goto end;
		}
	} else {
		action_path = lttng_action_path_create(NULL, 0);
		if (!action_path) {
			goto end;
		}
	}

	ret = consumed_size;
	*_action_path = action_path;
end:
	return ret;
}

LTTNG_HIDDEN
int lttng_action_path_serialize(const struct lttng_action_path *action_path,
		struct lttng_payload *payload)
{
	int ret;
	size_t index_count, i;
	enum lttng_action_path_status status;

	status = lttng_action_path_get_index_count(action_path, &index_count);
	if (status != LTTNG_ACTION_PATH_STATUS_OK) {
		ret = -1;
		goto end;
	}

	ret = lttng_dynamic_buffer_append(&payload->buffer,
			&((struct lttng_action_path_comm) {
				.index_count = index_count
			}),
			sizeof(struct lttng_action_path_comm));

	for (i = 0; i < index_count; i++) {
		uint64_t path_index;

		status = lttng_action_path_get_index_at_index(
				action_path, i, &path_index);
		if (status != LTTNG_ACTION_PATH_STATUS_OK) {
			ret = -1;
			goto end;
		}

		ret = lttng_dynamic_buffer_append(&payload->buffer, &path_index,
				sizeof(path_index));
		if (ret) {
			goto end;
		}
	}

	ret = 0;
end:
	return ret;
}
