/*
 * SPDX-FileCopyrightText: 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com>
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 */

#include "recording-channel-configuration.hpp"
#include "ust-app.hpp"
#include "ust-event-class.hpp"
#include "ust-stream-class.hpp"

#include <common/error.hpp>
#include <common/exception.hpp>
#include <common/format.hpp>
#include <common/hashtable/utils.hpp>
#include <common/make-unique-wrapper.hpp>
#include <common/make-unique.hpp>
#include <common/urcu.hpp>

#include <utility>

namespace lst = lttng::sessiond::trace;
namespace lsu = lttng::sessiond::ust;

namespace {
bool is_max_event_id(lttng::sessiond::ust::event_id id)
{
	return id == UINT32_MAX;
}

unsigned long ht_hash_event(const void *_key, unsigned long seed)
{
	uint64_t hashed_key;
	const lttng::sessiond::ust::event_class *key = (lttng::sessiond::ust::event_class *) _key;

	LTTNG_ASSERT(key);

	hashed_key = (uint64_t) hash_key_str(key->name.c_str(), seed);

	return hash_key_u64(&hashed_key, seed);
}

/*
 * Hash table match function for event in the registry.
 */
int ht_match_event(struct cds_lfht_node *node, const void *_key)
{
	const lttng::sessiond::ust::event_class *key;
	lttng::sessiond::ust::event_class *event;

	LTTNG_ASSERT(node);
	LTTNG_ASSERT(_key);

	event = lttng::utils::container_of(node, &lttng::sessiond::ust::event_class::_node);
	key = (lttng::sessiond::ust::event_class *) _key;

	/* It has to be a perfect match. First, compare the event names. */
	if (event->name != key->name) {
		goto no_match;
	}

	/* Compare log levels. */
	if (event->log_level != key->log_level) {
		goto no_match;
	}

	/* Compare the arrays of fields. */
	if (*event->payload != *key->payload) {
		goto no_match;
	}

	/* Compare model URI. */
	if (event->model_emf_uri != key->model_emf_uri) {
		goto no_match;
	}

	/* Match */
	return 1;

no_match:
	return 0;
}

lst::type::cuptr create_event_header(const lst::abi& trace_abi,
				     lst::stream_class::header_type header_type)
{
	using range_t = lst::unsigned_enumeration_type::mapping::range_t;

	static constexpr auto compact_opt_name = "compact";
	static constexpr auto extended_opt_name = "extended";
	lst::structure_type::fields event_header_fields;

	if (header_type == lst::stream_class::header_type::COMPACT) {
		auto enum_mappings = std::make_shared<lst::unsigned_enumeration_type::mappings>();
		lst::unsigned_enumeration_type::mapping compact_mapping{ { range_t{ 0, 30 } } };
		lst::unsigned_enumeration_type::mapping extended_mapping{ { range_t{ 31 } } };

		enum_mappings->emplace(compact_opt_name, compact_mapping);
		enum_mappings->emplace(extended_opt_name, extended_mapping);

		lst::type::cuptr choice_enum = lttng::make_unique<lst::unsigned_enumeration_type>(
			1,
			trace_abi.byte_order,
			5,
			lst::integer_type::base::DECIMAL,
			std::move(enum_mappings),
			std::initializer_list<lst::integer_type::role>(
				{ lst::integer_type::role::EVENT_RECORD_CLASS_ID }));

		lst::variant_type<range_t::range_integer_t>::choices variant_choices;

		lst::structure_type::fields compact_fields;
		compact_fields.emplace_back(lttng::make_unique<lst::field>(
			"timestamp",
			lttng::make_unique<lst::integer_type>(
				1,
				trace_abi.byte_order,
				27,
				lst::integer_type::signedness::UNSIGNED,
				lst::integer_type::base::DECIMAL,
				std::initializer_list<lst::integer_type::role>(
					{ lst::integer_type::role::DEFAULT_CLOCK_TIMESTAMP }))));

		auto compact_type =
			lttng::make_unique<lst::structure_type>(0, std::move(compact_fields));
		variant_choices.emplace_back(
			compact_opt_name, std::move(compact_mapping), std::move(compact_type));

		lst::structure_type::fields extended_fields;
		extended_fields.emplace_back(lttng::make_unique<lst::field>(
			"id",
			lttng::make_unique<lst::integer_type>(
				trace_abi.uint32_t_alignment,
				trace_abi.byte_order,
				32,
				lst::integer_type::signedness::UNSIGNED,
				lst::integer_type::base::DECIMAL,
				std::initializer_list<lst::integer_type::role>(
					{ lst::integer_type::role::EVENT_RECORD_CLASS_ID }))));
		extended_fields.emplace_back(lttng::make_unique<lst::field>(
			"timestamp",
			lttng::make_unique<lst::integer_type>(
				trace_abi.uint64_t_alignment,
				trace_abi.byte_order,
				64,
				lst::integer_type::signedness::UNSIGNED,
				lst::integer_type::base::DECIMAL,
				std::initializer_list<lst::integer_type::role>(
					{ lst::integer_type::role::DEFAULT_CLOCK_TIMESTAMP }))));

		lst::type::cuptr extended_type =
			lttng::make_unique<lst::structure_type>(0, std::move(extended_fields));
		variant_choices.emplace_back(
			extended_opt_name, std::move(extended_mapping), std::move(extended_type));

		auto variant = lttng::make_unique<lst::variant_type<range_t::range_integer_t>>(
			0,
			lst::field_location(lst::field_location::root::EVENT_RECORD_HEADER,
					    { "id" }),
			std::move(variant_choices));

		event_header_fields.emplace_back(
			lttng::make_unique<lst::field>("id", std::move(choice_enum)));
		event_header_fields.emplace_back(
			lttng::make_unique<lst::field>("v", std::move(variant)));
	} else {
		auto enum_mappings = std::make_shared<lst::unsigned_enumeration_type::mappings>();
		lst::unsigned_enumeration_type::mapping compact_mapping{ { range_t{ 0, 65534 } } };
		lst::unsigned_enumeration_type::mapping extended_mapping{ { range_t{ 65535 } } };
		enum_mappings->emplace(compact_opt_name, compact_mapping);
		enum_mappings->emplace(extended_opt_name, extended_mapping);

		auto choice_enum = lttng::make_unique<lst::unsigned_enumeration_type>(
			trace_abi.uint16_t_alignment,
			trace_abi.byte_order,
			16,
			lst::integer_type::base::DECIMAL,
			std::move(enum_mappings),
			std::initializer_list<lst::integer_type::role>(
				{ lst::integer_type::role::EVENT_RECORD_CLASS_ID }));

		lst::variant_type<range_t::range_integer_t>::choices variant_choices;

		lst::structure_type::fields compact_fields;
		compact_fields.emplace_back(lttng::make_unique<lst::field>(
			"timestamp",
			lttng::make_unique<lst::integer_type>(
				trace_abi.uint32_t_alignment,
				trace_abi.byte_order,
				32,
				lst::integer_type::signedness::UNSIGNED,
				lst::integer_type::base::DECIMAL,
				std::initializer_list<lst::integer_type::role>(
					{ lst::integer_type::role::DEFAULT_CLOCK_TIMESTAMP }))));

		lst::type::cuptr compact_type =
			lttng::make_unique<lst::structure_type>(0, std::move(compact_fields));
		variant_choices.emplace_back(
			compact_opt_name, std::move(compact_mapping), std::move(compact_type));

		lst::structure_type::fields extended_fields;
		extended_fields.emplace_back(lttng::make_unique<lst::field>(
			"id",
			lttng::make_unique<lst::integer_type>(
				trace_abi.uint32_t_alignment,
				trace_abi.byte_order,
				32,
				lst::integer_type::signedness::UNSIGNED,
				lst::integer_type::base::DECIMAL,
				std::initializer_list<lst::integer_type::role>(
					{ lst::integer_type::role::EVENT_RECORD_CLASS_ID }))));
		extended_fields.emplace_back(lttng::make_unique<lst::field>(
			"timestamp",
			lttng::make_unique<lst::integer_type>(
				trace_abi.uint64_t_alignment,
				trace_abi.byte_order,
				64,
				lst::integer_type::signedness::UNSIGNED,
				lst::integer_type::base::DECIMAL,
				std::initializer_list<lst::integer_type::role>(
					{ lst::integer_type::role::DEFAULT_CLOCK_TIMESTAMP }))));

		auto extended_type =
			lttng::make_unique<lst::structure_type>(0, std::move(extended_fields));
		variant_choices.emplace_back(
			extended_opt_name, std::move(extended_mapping), std::move(extended_type));

		auto variant = lttng::make_unique<lst::variant_type<range_t::range_integer_t>>(
			0,
			lst::field_location(lst::field_location::root::EVENT_RECORD_HEADER,
					    { "id" }),
			std::move(variant_choices));

		event_header_fields.emplace_back(
			lttng::make_unique<lst::field>("id", std::move(choice_enum)));
		event_header_fields.emplace_back(
			lttng::make_unique<lst::field>("v", std::move(variant)));
	}

	return lttng::make_unique<lst::structure_type>(0, std::move(event_header_fields));
}

lst::type::cuptr create_packet_context(
	const lst::abi& trace_abi,
	lttng::sessiond::config::recording_channel_configuration::buffer_allocation_policy_t
		buffer_allocation_policy)
{
	lst::structure_type::fields packet_context_fields;

	/* uint64_t timestamp_begin */
	packet_context_fields.emplace_back(lttng::make_unique<lst::field>(
		"timestamp_begin",
		lttng::make_unique<lst::integer_type>(
			trace_abi.uint64_t_alignment,
			trace_abi.byte_order,
			64,
			lst::integer_type::signedness::UNSIGNED,
			lst::integer_type::base::DECIMAL,
			std::initializer_list<lst::integer_type::role>(
				{ lst::integer_type::role::DEFAULT_CLOCK_TIMESTAMP }))));

	/* uint64_t timestamp_end */
	packet_context_fields.emplace_back(lttng::make_unique<lst::field>(
		"timestamp_end",
		lttng::make_unique<lst::integer_type>(
			trace_abi.uint64_t_alignment,
			trace_abi.byte_order,
			64,
			lst::integer_type::signedness::UNSIGNED,
			lst::integer_type::base::DECIMAL,
			std::initializer_list<lst::integer_type::role>(
				{ lst::integer_type::role::PACKET_END_DEFAULT_CLOCK_TIMESTAMP }))));

	/* uint64_t content_size */
	packet_context_fields.emplace_back(lttng::make_unique<lst::field>(
		"content_size",
		lttng::make_unique<lst::integer_type>(
			trace_abi.uint64_t_alignment,
			trace_abi.byte_order,
			64,
			lst::integer_type::signedness::UNSIGNED,
			lst::integer_type::base::DECIMAL,
			std::initializer_list<lst::integer_type::role>(
				{ lst::integer_type::role::PACKET_CONTENT_LENGTH }))));

	/* uint64_t packet_size */
	packet_context_fields.emplace_back(lttng::make_unique<lst::field>(
		"packet_size",
		lttng::make_unique<lst::integer_type>(
			trace_abi.uint64_t_alignment,
			trace_abi.byte_order,
			64,
			lst::integer_type::signedness::UNSIGNED,
			lst::integer_type::base::DECIMAL,
			std::initializer_list<lst::integer_type::role>(
				{ lst::integer_type::role::PACKET_TOTAL_LENGTH }))));

	/* uint64_t packet_seq_num */
	packet_context_fields.emplace_back(lttng::make_unique<lst::field>(
		"packet_seq_num",
		lttng::make_unique<lst::integer_type>(
			trace_abi.uint64_t_alignment,
			trace_abi.byte_order,
			64,
			lst::integer_type::signedness::UNSIGNED,
			lst::integer_type::base::DECIMAL,
			std::initializer_list<lst::integer_type::role>(
				{ lst::integer_type::role::PACKET_SEQUENCE_NUMBER }))));

	/* unsigned long events_discarded */
	packet_context_fields.emplace_back(lttng::make_unique<lst::field>(
		"events_discarded",
		lttng::make_unique<lst::integer_type>(
			trace_abi.long_alignment,
			trace_abi.byte_order,
			trace_abi.bits_per_long,
			lst::integer_type::signedness::UNSIGNED,
			lst::integer_type::base::DECIMAL,
			std::initializer_list<lst::integer_type::role>(
				{ lst::integer_type::role::
					  DISCARDED_EVENT_RECORD_COUNTER_SNAPSHOT }))));

	/*
	 * The cpu_id is part of the packet header with channels of that use a
	 * 'per-cpu' allocation policy. Essentially, this is free because the
	 * CPU ID needs to be determined in the fast-path of the instrumentation
	 * to select the stream to write to. Might as well add it to the packet
	 * header and interpret it as an implicit context.
	 *
	 * However, the story is different for other types of channel. For
	 * example, channel with the per-channel allocation policy have a single
	 * stream of data, that is a single buffer shared by all CPUs.
	 * Therefore, to avoid extra time overhead in the instrumentation
	 * fast-path, and to avoid wasted space in the buffer, the CPU ID is not
	 * queried and emitted by default for these channels.
	 *
	 * For the reasons mentioned above, it is important, for correct decoding
	 * of the trace, to not expect this field in the packet header if the
	 * channel is not of the type 'per-cpu'.
	 */
	if (buffer_allocation_policy ==
	    lttng::sessiond::config::recording_channel_configuration::buffer_allocation_policy_t::
		    PER_CPU) {
		/* uint32_t cpu_id */
		packet_context_fields.emplace_back(lttng::make_unique<lst::field>(
			"cpu_id",
			lttng::make_unique<lst::integer_type>(
				trace_abi.uint32_t_alignment,
				trace_abi.byte_order,
				32,
				lst::integer_type::signedness::UNSIGNED,
				lst::integer_type::base::DECIMAL)));
	}

	return lttng::make_unique<lst::structure_type>(0, std::move(packet_context_fields));
}
}; /* namespace */

lsu::stream_class::stream_class(
	unsigned int channel_id,
	lttng::sessiond::config::recording_channel_configuration::buffer_allocation_policy_t
		buffer_allocation_policy,
	const lst::abi& trace_abi,
	std::string in_default_clock_class_name,
	lsu::stream_class::registered_listener_fn channel_registered_listener,
	lsu::stream_class::event_added_listener_fn event_added_listener) :
	lst::stream_class(channel_id,
			  lst::stream_class::header_type::LARGE,
			  std::move(in_default_clock_class_name)),
	_key{ -1ULL },
	_consumer_key{ -1ULL },
	_next_event_id{ 0 },
	_is_registered_listener{ std::move(channel_registered_listener) },
	_event_added_listener{ std::move(event_added_listener) },
	_is_registered{ false }
{
	_events = lttng_ht_new(0, LTTNG_HT_TYPE_STRING);
	if (!_events) {
		LTTNG_THROW_POSIX("Failed to allocate urcu events hash table", ENOMEM);
	}

	/* Set custom match function. */
	_events->match_fct = ht_match_event;
	_events->hash_fct = ht_hash_event;

	_rcu_head = {};
	/*
	 * Node's key is initialized by the channel's parent session. Its value is irrelevant to the
	 * channel object itself.
	 */
	_node = {};

	_packet_context = create_packet_context(trace_abi, buffer_allocation_policy);
	_event_header = create_event_header(trace_abi, header_type_);
}

void lsu::stream_class::add_event(int session_objd,
				  int channel_objd,
				  std::string name,
				  std::string signature,
				  std::vector<lst::field::cuptr> event_fields,
				  int loglevel_value,
				  nonstd::optional<std::string> model_emf_uri,
				  lttng_buffer_type buffer_type,
				  const lsu::app& app,
				  lsu::event_id& out_event_id)
{
	lsu::event_id event_id;
	struct cds_lfht_node *nptr;
	const lttng::urcu::read_lock_guard read_lock_guard;

	/*
	 * This should not happen but since it comes from the UST tracer, an
	 * external party, don't assert and simply validate values.
	 */
	if (session_objd < 0) {
		LTTNG_THROW_INVALID_ARGUMENT_ERROR(lttng::format(
			"Invalid session object descriptor provided by application: session descriptor = {}, app = {}",
			session_objd,
			app));
	}

	if (channel_objd < 0) {
		LTTNG_THROW_INVALID_ARGUMENT_ERROR(lttng::format(
			"Invalid channel object descriptor provided by application: channel descriptor = {}, app = {}",
			channel_objd,
			app));
	}

	/* Check if we've reached the maximum possible id. */
	if (is_max_event_id(_next_event_id)) {
		LTTNG_THROW_ERROR(lttng::format(
			"Failed to allocate new event id (id would overflow): app = {}", app));
	}

	auto event = lttng::make_unique_wrapper<lsu::event_class, event_class_destroy>(
		new lsu::event_class(_next_event_id,
				     id,
				     session_objd,
				     channel_objd,
				     std::move(name),
				     std::move(signature),
				     std::move(event_fields),
				     loglevel_value,
				     std::move(model_emf_uri)));

	DBG3("%s", lttng::format("UST trace class creating event: event = {}", *event).c_str());

	/*
	 * This is an add unique with a custom match function for event. The node
	 * are matched using the event name and signature.
	 */
	nptr = cds_lfht_add_unique(_events->ht,
				   _events->hash_fct(event.get(), lttng_ht_seed),
				   _events->match_fct,
				   event.get(),
				   &event->_node);
	if (nptr != &event->_node) {
		if (buffer_type == LTTNG_BUFFER_PER_UID) {
			/*
			 * This is normal, we just have to send the event id of the
			 * returned node.
			 */
			const auto existing_event = lttng::utils::container_of(
				nptr, &lttng::sessiond::ust::event_class::_node);
			event_id = existing_event->id;
		} else {
			LTTNG_THROW_INVALID_ARGUMENT_ERROR(lttng::format(
				"UST trace class create event add unique failed for event: event = {}",
				*event));
		}
	} else {
		/* Ownership transferred to _events hash table. */
		const auto *const event_ptr = event.release();

		/* Request next event id if the node was successfully added. */
		event_id = event_ptr->id;

		/*
		 * Only increment the next id here since we don't want to waste an ID when the event
		 * matches an existing one.
		 */
		_next_event_id++;
		_event_added_listener(*this, *event_ptr);
	}

	out_event_id = event_id;
}

lsu::stream_class::~stream_class()
{
	lttng_ht_destroy(_events);
}

const lttng::sessiond::trace::type *lsu::stream_class::event_context() const
{
	LTTNG_ASSERT(_is_registered);
	return lst::stream_class::event_context();
}

void lsu::stream_class::event_context(lttng::sessiond::trace::type::cuptr context)
{
	/* Must only be set once, on the first channel registration provided by an application. */
	LTTNG_ASSERT(!_event_context);
	_event_context = std::move(context);
}

bool lsu::stream_class::is_registered() const
{
	return _is_registered;
}

void lsu::stream_class::set_as_registered()
{
	if (!_is_registered) {
		_is_registered = true;
		_is_registered_listener(*this);
	}
}

void lsu::stream_class::_accept_on_event_classes(
	lttng::sessiond::trace::trace_class_visitor& visitor) const
{
	const lttng::urcu::lfht_iteration_adapter<lsu::event_class,
						  decltype(lsu::event_class::_node),
						  &lsu::event_class::_node>
		events_view(*_events->ht);

	/* Copy the event ptrs from the _events ht to this vector which we'll sort. */
	std::vector<const lttng::sessiond::ust::event_class *> sorted_event_classes(
		events_view.begin(), events_view.end());

	std::sort(sorted_event_classes.begin(),
		  sorted_event_classes.end(),
		  [](const lttng::sessiond::ust::event_class *a,
		     const lttng::sessiond::ust::event_class *b) { return a->id < b->id; });

	for (const auto event : sorted_event_classes) {
		event->accept(visitor);
	}
}
