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

#ifndef LTTNG_SESSIOND_RECORDING_CHANNEL_CONFIGURATION_HPP
#define LTTNG_SESSIOND_RECORDING_CHANNEL_CONFIGURATION_HPP

#include "channel-configuration.hpp"
#include "context-configuration.hpp"
#include "event-rule-configuration.hpp"

#include <common/ctl/memory.hpp>
#include <common/exception.hpp>
#include <common/format.hpp>
#include <common/make-unique.hpp>

#include <lttng/event-rule/event-rule-internal.hpp>

#include <vendor/optional.hpp>

#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

namespace lttng {
namespace sessiond {
namespace config {

#define LTTNG_THROW_EVENT_RULE_CONFIGURATION_NOT_FOUND_ERROR(event_rule)                     \
	throw lttng::sessiond::config::exceptions::event_rule_configuration_not_found_error( \
		event_rule, LTTNG_SOURCE_LOCATION())
#define LTTNG_THROW_CONTEXT_CONFIGURATION_ALREADY_PRESENT_ERROR(context_configuration)          \
	throw lttng::sessiond::config::exceptions::context_configuration_already_present_error( \
		context_configuration, LTTNG_SOURCE_LOCATION())

namespace exceptions {
/*
 * @class event_rule_configuration_not_found_error
 * @brief Represents a event-rule-configuration-not-found error.
 */
class event_rule_configuration_not_found_error : public lttng::runtime_error {
public:
	explicit event_rule_configuration_not_found_error(
		const lttng_event_rule& event_rule, const lttng::source_location& source_location);
};

/*
 * @class context_configuration_already_present_error
 * @brief Represents a duplicate-context error.
 */
class context_configuration_already_present_error : public lttng::runtime_error {
public:
	explicit context_configuration_already_present_error(
		const context_configuration& context_configuration,
		const lttng::source_location& source_location);
};
} /* namespace exceptions */

/*
 * A recording channel configuration represents the configuration of a recording session's
 * channel at a given point in time. It belongs to a single recording session.
 */
class recording_channel_configuration final : public channel_configuration {
public:
	using uptr = std::unique_ptr<recording_channel_configuration>;

	enum class buffer_allocation_policy_t {
		PER_CPU,
		PER_CHANNEL,
	};

	enum class buffer_preallocation_policy_t {
		PREALLOCATE,
		ON_DEMAND,
	};

	using ownership_model_t = lttng::sessiond::config::ownership_model_t;

	struct consumption_blocking_policy {
		enum class mode {
			NONE,
			TIMED,
			UNBOUNDED,
		};

		explicit consumption_blocking_policy(
			mode mode,
			const nonstd::optional<timer_period_us>& timeout_us = nonstd::nullopt);

		const mode mode_;
		/* Only set in TIMED mode. */
		const nonstd::optional<timer_period_us> timeout_us;
	};

	recording_channel_configuration(
		bool is_enabled,
		std::string name,
		buffer_full_policy_t buffer_full_policy,
		buffer_consumption_backend_t buffer_consumption_backend,
		buffer_allocation_policy_t buffer_allocation_policy,
		buffer_preallocation_policy_t buffer_preallocation_policy,
		std::uint64_t subbuffer_size_bytes,
		unsigned int subbuffer_count,
		const nonstd::optional<timer_period_us>& switch_timer_period_us,
		const nonstd::optional<timer_period_us>& read_timer_period_us,
		const nonstd::optional<timer_period_us>& live_timer_period_us,
		const nonstd::optional<timer_period_us>& monitor_timer_period_us,
		const nonstd::optional<timer_period_us>& watchdog_timer_period_us,
		const nonstd::optional<std::chrono::microseconds>&
			automatic_memory_reclamation_maximal_age_us,
		consumption_blocking_policy consumption_blocking_policy,
		const nonstd::optional<std::uint64_t>& trace_file_size_limit_bytes,
		const nonstd::optional<unsigned int>& trace_file_count_limit);

	~recording_channel_configuration() = default;
	recording_channel_configuration(recording_channel_configuration&&) = delete;
	recording_channel_configuration(const recording_channel_configuration&) = delete;
	recording_channel_configuration& operator=(const recording_channel_configuration&) = delete;
	recording_channel_configuration& operator=(recording_channel_configuration&&) = delete;

	channel_type_t channel_type() const noexcept override
	{
		return channel_type_t::DATA;
	}

	void enable() noexcept
	{
		set_enabled(true);
	}

	void disable() noexcept
	{
		set_enabled(false);
	}

	void set_enabled(bool enable) noexcept;

	template <typename... Args>
	void add_event_rule_configuration(Args&&...args)
	{
		auto config =
			lttng::make_unique<event_rule_configuration>(std::forward<Args>(args)...);

		event_rules.emplace(std::cref(*config->event_rule), std::move(config));
	}

	const event_rule_configuration&
	get_event_rule_configuration(const lttng_event_rule& matching_event_rule_to_lookup) const;
	event_rule_configuration&
	get_event_rule_configuration(const lttng_event_rule& matching_event_rule_to_lookup);

	const context_configuration& add_context(context_configuration::uptr context);
	const std::vector<context_configuration::uptr>& get_contexts() const noexcept;

	const buffer_allocation_policy_t buffer_allocation_policy;
	const buffer_preallocation_policy_t buffer_preallocation_policy;
	const nonstd::optional<timer_period_us> live_timer_period_us;
	const nonstd::optional<timer_period_us> monitor_timer_period_us;
	const nonstd::optional<timer_period_us> watchdog_timer_period_us;
	/* Age at 0 means "continuously reclaimed". */
	const nonstd::optional<std::chrono::microseconds> automatic_memory_reclamation_maximal_age;
	const consumption_blocking_policy consumption_blocking_policy_;
	const nonstd::optional<std::uint64_t> trace_file_size_limit_bytes;
	const nonstd::optional<unsigned int> trace_file_count_limit;

	bool is_enabled;

	std::unordered_map<std::reference_wrapper<const lttng_event_rule>,
			   event_rule_configuration::uptr,
			   std::hash<std::reference_wrapper<const lttng_event_rule>>,
			   lttng_event_rule_ref_equal>
		event_rules;

private:
	/*
	 * Contexts attached to this channel. Contexts add supplementary information
	 * (e.g., PID, TID, perf counters) to each event record.
	 *
	 * Order is preserved as it affects the layout of context fields in the trace.
	 */
	std::vector<context_configuration::uptr> _contexts;
};

} /* namespace config */
} /* namespace sessiond */
} /* namespace lttng */

/*
 * Specialize fmt::formatter for consumption_blocking_policy's mode.
 *
 * Due to a bug in g++ < 7.1, this specialization must be enclosed in the fmt namespace,
 * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480.
 */
namespace fmt {
template <>
struct formatter<
	lttng::sessiond::config::recording_channel_configuration::consumption_blocking_policy::mode>
	: formatter<std::string> {
	/* Format function to convert enum to string. */
	template <typename FormatContextType>
	typename FormatContextType::iterator
	format(lttng::sessiond::config::recording_channel_configuration::
		       consumption_blocking_policy::mode mode,
	       FormatContextType& ctx) const
	{
		auto name = "UNKNOWN";

		switch (mode) {
		case lttng::sessiond::config::recording_channel_configuration::
			consumption_blocking_policy::mode::NONE:
			name = "NONE";
			break;
		case lttng::sessiond::config::recording_channel_configuration::
			consumption_blocking_policy::mode::UNBOUNDED:
			name = "UNBOUNDED";
			break;
		case lttng::sessiond::config::recording_channel_configuration::
			consumption_blocking_policy::mode::TIMED:
			name = "TIMED";
			break;
		}

		/* Write the string representation to the format context output iterator. */
		return format_to(ctx.out(), name);
	}
};

template <>
struct formatter<lttng::sessiond::config::recording_channel_configuration::buffer_allocation_policy_t>
	: formatter<std::string> {
	template <typename FormatContextType>
	typename FormatContextType::iterator
	format(lttng::sessiond::config::recording_channel_configuration::buffer_allocation_policy_t
		       policy,
	       FormatContextType& ctx) const
	{
		auto name = "UNKNOWN";

		switch (policy) {
		case lttng::sessiond::config::recording_channel_configuration::
			buffer_allocation_policy_t::PER_CPU:
			name = "PER_CPU";
			break;
		case lttng::sessiond::config::recording_channel_configuration::
			buffer_allocation_policy_t::PER_CHANNEL:
			name = "PER_CHANNEL";
			break;
		}

		return format_to(ctx.out(), name);
	}
};

template <>
struct formatter<
	lttng::sessiond::config::recording_channel_configuration::buffer_preallocation_policy_t>
	: formatter<std::string> {
	template <typename FormatContextType>
	typename FormatContextType::iterator
	format(lttng::sessiond::config::recording_channel_configuration::buffer_preallocation_policy_t
		       policy,
	       FormatContextType& ctx) const
	{
		auto name = "UNKNOWN";

		switch (policy) {
		case lttng::sessiond::config::recording_channel_configuration::
			buffer_preallocation_policy_t::PREALLOCATE:
			name = "PREALLOCATE";
			break;
		case lttng::sessiond::config::recording_channel_configuration::
			buffer_preallocation_policy_t::ON_DEMAND:
			name = "ON_DEMAND";
			break;
		}

		return format_to(ctx.out(), name);
	}
};

/*
 * Specialize fmt::formatter for recording_channel_configuration.
 *
 * Due to a bug in g++ < 7.1, this specialization must be enclosed in the fmt namespace,
 * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480.
 */
template <>
struct formatter<lttng::sessiond::config::recording_channel_configuration>
	: formatter<std::string> {
	template <typename FormatContextType>
	typename FormatContextType::iterator
	format(const lttng::sessiond::config::recording_channel_configuration& channel,
	       FormatContextType& ctx) const
	{
		return format_to(
			ctx.out(),
			"`{}` (enabled: {}, buffer_full_policy: {}, buffer_consumption_backend: {}, "
			"buffer_allocation_policy: {}, buffer_preallocation_policy: {}, "
			"subbuffer_size_bytes: {}, subbuffer_count: {}, "
			"switch_timer_period_us: {}, read_timer_period_us: {}, "
			"live_timer_period_us: {}, monitor_timer_period_us: {}, "
			"trace_file_size_limit_bytes: {}, trace_file_count_limit: {}, "
			"consumption_blocking_policy: {}, event_rules: {})",
			channel.name,
			channel.is_enabled,
			channel.buffer_full_policy,
			channel.buffer_consumption_backend,
			channel.buffer_allocation_policy,
			channel.buffer_preallocation_policy,
			channel.subbuffer_size_bytes,
			channel.subbuffer_count,
			channel.switch_timer_period_us,
			channel.read_timer_period_us,
			channel.live_timer_period_us,
			channel.monitor_timer_period_us,
			channel.trace_file_size_limit_bytes,
			channel.trace_file_count_limit,
			channel.consumption_blocking_policy_.mode_,
			channel.event_rules.size());
	}
};
} /* namespace fmt */

#endif /* LTTNG_SESSIOND_RECORDING_CHANNEL_CONFIGURATION_HPP */
