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

#ifndef LTTNG_SESSIOND_MAP_CHANNEL_HPP
#define LTTNG_SESSIOND_MAP_CHANNEL_HPP

#include "key-registry.hpp"
#include "map-group-identity.hpp"
#include "shared-group.hpp"

#include <algorithm>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <utility>

struct lttng_event_rule;
struct lttng_action;

namespace lttng {
namespace sessiond {

namespace config {
class map_channel_configuration;
} /* namespace config */

namespace map {

class group;

/*
 * Identifies a counter-event rule registered against a map channel by the
 * (event_rule, increment-map-value action) pair of the trigger that produced
 * it. Both pointers are stable for the trigger's registered lifetime, which
 * matches the window during which the orchestrator holds rule state.
 */
using event_rule_action_key = std::pair<const lttng_event_rule *, const lttng_action *>;

/*
 * Find the record of the rule that `event_rule` and `incr_map_value_action`
 * identify in a channel's `_rules`, or `rules.end()` when the channel holds no
 * such rule.
 *
 * The rules are keyed by user token to keep them in registration order, so
 * this looks up by scanning. A channel holds one rule per trigger targeting
 * it, which is few enough for this to be irrelevant.
 */
template <typename RulesType>
typename RulesType::iterator find_rule(RulesType& rules,
				       const lttng_event_rule& event_rule,
				       const lttng_action& incr_map_value_action) noexcept
{
	return std::find_if(rules.begin(),
			    rules.end(),
			    [&event_rule, &incr_map_value_action](
				    const typename RulesType::value_type& rule_entry) {
				    return rule_entry.second.key ==
					    event_rule_action_key{ &event_rule,
								   &incr_map_value_action };
			    });
}

/*
 * Per-channel runtime owned by the domain orchestrator. Aggregates the
 * channel's `key_registry`, its `shared_group`, and (in the domain
 * subclasses) its tracer-backed group(s).
 *
 * The base provides the registry / shared accessors and the
 * `for_each_element_of` helper that walks a group through the
 * channel's registered keys. Domain-specific group storage and
 * iteration live in the subclasses (`modules::map_channel`,
 * `ust::map_channel`).
 */
class map_channel {
public:
	using uptr = std::unique_ptr<map_channel>;

	/*
	 * Visitor signature for `for_each_element_of`. Invoked once per
	 * (key, index) pair registered on the channel, with the value
	 * read from the supplied group.
	 */
	using element_visitor = std::function<void(
		lttng::c_string_view key, std::uint64_t index, const element_value& value)>;

	/*
	 * Visitor signature for `for_each_group`. Invoked once per group of
	 * the channel with its description and the group itself.
	 */
	using group_visitor = std::function<void(const group_description& description,
						 const group& visited_group)>;

	/*
	 * `registry` may be null for INDEX-keyed channels: those
	 * channels reference indices directly and do not maintain a
	 * string registry. Callers that walk a group's elements via
	 * `for_each_element_of` therefore must check `has_registry()`
	 * before iterating.
	 */
	map_channel(const config::map_channel_configuration& configuration,
		    key_registry::uptr registry);

	virtual ~map_channel() = default;
	map_channel(const map_channel&) = delete;
	map_channel(map_channel&&) = delete;
	map_channel& operator=(const map_channel&) = delete;
	map_channel& operator=(map_channel&&) = delete;

	const config::map_channel_configuration& configuration() const noexcept;

	bool has_registry() const noexcept;

	key_registry& registry() noexcept;
	const key_registry& registry() const noexcept;

	/*
	 * A weak observer of the channel's registry, for consumers that
	 * run outside the recording session lock and must therefore guard
	 * against the channel being torn down concurrently. The
	 * application-notification thread resolves a key-registration
	 * request to a registry through this observer: it upgrades the
	 * weak_ptr with `lock()`, which both keeps the registry alive for
	 * the resolution and reports (by returning empty) that the owning
	 * channel has been destroyed.
	 *
	 * Returns an empty observer for INDEX-keyed channels (no
	 * registry); see the constructor's note on `has_registry()`.
	 */
	std::weak_ptr<key_registry> registry_observer() const noexcept;

	shared_group& shared() noexcept;
	const shared_group& shared() const noexcept;

	/*
	 * Invoke `visitor` on every group of the channel, whatever the
	 * domain and buffer-ownership model: the single system-wide group of
	 * a kernel channel; the per-(user, bitness) or per-process groups of
	 * a user space channel followed by its shared group. This mirrors,
	 * group by group, what the public lttng_map_channel_get_groups()
	 * exposes.
	 */
	virtual void for_each_group(const group_visitor& visitor) const = 0;

	/*
	 * Walk the channel's registry and emit
	 * `(key, index, target_group.aggregate_element(index))` for every
	 * key registered, in registry-determined order.
	 *
	 * Invoking this on an INDEX-keyed channel (no registry) throws,
	 * since iteration over flat indices is the caller's
	 * responsibility in that case.
	 */
	void for_each_element_of(const group& target_group, const element_visitor& visitor) const;

	/*
	 * Reset to 0 every counter of every group of this channel: each
	 * tracer-backed group followed by the channel-wide
	 * shared group.
	 *
	 * Preserves the keys themselves (doesn't deallocate their
	 * indices): clearing resets values, it doesn't remove counters
	 * from the map.
	 *
	 * Used by the recording session clear
	 * path (lttng_clear_session()).
	 */
	void clear();

	/*
	 * Resolve `key` in this channel's registry (allocating if needed) and add
	 * `delta` to the shared group value at that index, with modular arithmetic.
	 *
	 * Used by the session-daemon execution path for increment-map-value actions
	 * that are not handled directly by a tracer event hit.
	 *
	 * Throws if this channel has no registry (INDEX-keyed) or if its registry
	 * cannot allocate from sessiond (for example, kernel-backed registries).
	 */
	void increment_shared_value(const std::string& key, std::int64_t delta);

	/*
	 * Allocate the next user_token for a rule about to be registered on
	 * this channel. The token is a monotonic 64-bit counter that is never
	 * recycled: at 2^64 distinct tokens, recycling on rule removal would
	 * add complexity without buying anything useful.
	 */
	std::uint64_t allocate_user_token() noexcept;

protected:
	/*
	 * Reset to 0 every counter of `target_group` registered in the
	 * key registry of this channel.
	 *
	 * A no-op for an index-keyed channel, which has no registry
	 * to walk.
	 *
	 * Helper for subclasses (_clear_tracer_groups()).
	 */
	void _clear_group_elements(group& target_group);

	/*
	 * Reset to 0 the counters of every tracer-backed group of
	 * this channel.
	 *
	 * The base clear() handles the shared group; subclasses only
	 * deal with their own domain group storage.
	 */
	virtual void _clear_tracer_groups() = 0;

	std::uint64_t _next_user_token = 0;

private:
	const config::map_channel_configuration& _configuration;
	key_registry::sptr _registry;
	shared_group _shared;
};

} /* namespace map */
} /* namespace sessiond */
} /* namespace lttng */

#endif /* LTTNG_SESSIOND_MAP_CHANNEL_HPP */
