
##! Functions for peering and various messaging patterns.

%%{
#include <set>
#include <span>
#include <string>

#include "zeek/broker/Manager.h"
#include "zeek/logging/Manager.h"

namespace {

using ArgsSpan = std::span<const zeek::ValPtr>;

}

static bool is_string_set(const zeek::Type* type)
	{
	if ( ! type->IsSet() )
		return false;

	const auto& index_types = type->AsTableType()->GetIndexTypes();

	if ( index_types.size() != 1 )
		return false;

	return index_types[0]->Tag() == zeek::TYPE_STRING;
	}

std::set<std::string> val_to_topic_set(zeek::Val* val)
	{
	std::set<std::string> rval;

	if ( val->GetType()->Tag() == zeek::TYPE_STRING )
		rval.emplace(val->AsString()->CheckString());
	else
		{
		const zeek::PDict<zeek::TableEntryVal>* tbl = val->AsTable();

		if ( tbl->Length() == 0 )
			return rval;

		for ( const auto& te : *tbl )
			{
			auto k = te.GetHashKey();

			auto index = val->AsTableVal()->RecreateIndex(*k);
			rval.emplace(index->Idx(0)->AsString()->CheckString());
			}
		}

	return rval;
	}

static bool publish_event_args(ArgsSpan args, const zeek::String* topic,
                               zeek::detail::Frame* frame)
	{
	zeek::Broker::Manager::ScriptScopeGuard ssg;
	zeek::ScriptLocationScope scope{frame};

	auto rval = false;

	if ( zeek::broker_mgr != zeek::cluster::backend && ! zeek::broker_mgr->Active() )
		zeek::reporter->Warning("Non-broker cluster backend configured and Broker manager inactive. "
                                        "Did you mean to use Cluster::publish() instead of Broker::publish()?");

	if ( args[0]->GetType()->Tag() == zeek::TYPE_RECORD )
		{
		auto* rv = args[0]->AsRecordVal();

		// same_type() should be fast if it's the type pointers are the same.
		if ( ! zeek::same_type(rv->GetType(), zeek::BifType::Record::Broker::Event) )
			{
			zeek::emit_builtin_error(zeek::util::fmt("expected Broker::Event, got %s",
			                                         zeek::obj_desc_short(rv->GetType()).c_str()));
			return false;
			}

		return zeek::broker_mgr->PublishEvent(topic->CheckString(), rv);
		}

	auto ev = zeek::broker_mgr->MakeEvent(args, frame);
	return zeek::broker_mgr->PublishEvent(topic->CheckString(), ev->AsRecordVal());
	}

static bool is_cluster_pool(zeek::Val* pool)
	{
	static zeek::RecordTypePtr pool_type = nullptr;

	if ( ! pool_type )
		pool_type = zeek::id::find_type<zeek::RecordType>("Cluster::Pool");

	return pool->GetType() == pool_type;
	}

%%}

module Broker;

type Broker::Event: record;

## Create a data structure that may be used to send a remote event via
## :zeek:see:`Broker::publish`.
##
## args: an event, followed by a list of argument values that may be used
##       to call it.
##
## Returns: opaque communication data that may be used to send a remote
##          event.
function Broker::make_event%(...%): Broker::Event
	%{
	zeek::Broker::Manager::ScriptScopeGuard ssg;

	auto ev = zeek::broker_mgr->MakeEvent(ArgsSpan{*@ARGS@}, frame);
	return zeek::cast_intrusive<RecordVal>(ev);
	%}

## Publishes an event at a given topic.
##
## topic: a topic associated with the event message.
##
## args: Either the event arguments as already made by
##       :zeek:see:`Broker::make_event` or the argument list to pass along
##       to it.
##
## Returns: true if the message is sent.
function Broker::publish%(topic: string, ...%): bool
	%{
	auto rval = publish_event_args(ArgsSpan{*@ARGS@}.subspan(1),
	                               topic->AsString(), frame);
	return zeek::val_mgr->Bool(rval);
	%}

function Broker::__flush_logs%(%): count
	%{
	auto rval = zeek::broker_mgr->FlushLogBuffers();
	return zeek::val_mgr->Count(static_cast<uint64_t>(rval));
	%}

function Broker::__publish_id%(topic: string, id: string%): bool
	%{
	zeek::Broker::Manager::ScriptScopeGuard ssg;
	auto rval = zeek::broker_mgr->PublishIdentifier(topic->CheckString(),
	                                                id->CheckString());
	return zeek::val_mgr->Bool(rval);
	%}

function Broker::__subscribe%(topic_prefix: string%): bool
	%{
	zeek::Broker::Manager::ScriptScopeGuard ssg;
	auto rval = zeek::broker_mgr->Subscribe(topic_prefix->CheckString());
	return zeek::val_mgr->Bool(rval);
	%}

function Broker::__forward%(topic_prefix: string%): bool
	%{
	zeek::Broker::Manager::ScriptScopeGuard ssg;
	auto rval = zeek::broker_mgr->Forward(topic_prefix->CheckString());
	return zeek::val_mgr->Bool(rval);
	%}

function Broker::__unsubscribe%(topic_prefix: string%): bool
	%{
	zeek::Broker::Manager::ScriptScopeGuard ssg;
	auto rval = zeek::broker_mgr->Unsubscribe(topic_prefix->CheckString());
	return zeek::val_mgr->Bool(rval);
	%}
