##! Functions to create and manipulate Bloom filters.

%%{

// TODO: This is currently included from the top-level src directory, hence
// paths are relative to there. We need a better mechanisms to pull in
// BiFs defined in sub directories.
#include "zeek/probabilistic/BloomFilter.h"
#include "zeek/OpaqueVal.h"

using namespace zeek::probabilistic;

%%}

module GLOBAL;

## Creates a basic Bloom filter.
##
## fp: The desired false-positive rate.
##
## capacity: the maximum number of elements that guarantees a false-positive
##           rate of *fp*.
##
## name: A name that uniquely identifies and seeds the Bloom filter. If empty,
##       the filter will use :zeek:id:`global_hash_seed` if that's set, and
##       otherwise use a local seed tied to the current Zeek process. Only
##       filters with the same seed can be merged with
##       :zeek:id:`bloomfilter_merge`.
##
## Returns: A Bloom filter handle.
##
## .. zeek:see:: bloomfilter_basic_init2 bloomfilter_counting_init bloomfilter_add
##    bloomfilter_lookup bloomfilter_clear bloomfilter_merge global_hash_seed
function bloomfilter_basic_init%(fp: double, capacity: count,
                                 name: string &default=""%): opaque of bloomfilter
	%{
	if ( fp < 0.0 || fp > 1.0 )
		{
		reporter->Error("false-positive rate must take value between 0 and 1");
		return nullptr;
		}

	size_t cells = zeek::probabilistic::BasicBloomFilter::M(fp, capacity);
	size_t optimal_k = zeek::probabilistic::BasicBloomFilter::K(cells, capacity);
	zeek::probabilistic::detail::Hasher::seed_t seed =
		zeek::probabilistic::detail::Hasher::MakeSeed(name->Len() > 0 ? name->Bytes() : 0, name->Len());
	const zeek::probabilistic::detail::Hasher* h = new zeek::probabilistic::detail::DoubleHasher(optimal_k, seed);

	return zeek::make_intrusive<zeek::BloomFilterVal>(new zeek::probabilistic::BasicBloomFilter(h, cells));
	%}

## Creates a basic Bloom filter. This function serves as a low-level
## alternative to :zeek:id:`bloomfilter_basic_init` where the user has full
## control over the number of hash functions and cells in the underlying bit
## vector.
##
## k: The number of hash functions to use.
##
## cells: The number of cells of the underlying bit vector.
##
## name: A name that uniquely identifies and seeds the Bloom filter. If empty,
##       the filter will use :zeek:id:`global_hash_seed` if that's set, and
##       otherwise use a local seed tied to the current Zeek process. Only
##       filters with the same seed can be merged with
##       :zeek:id:`bloomfilter_merge`.
##
## Returns: A Bloom filter handle.
##
## .. zeek:see:: bloomfilter_basic_init bloomfilter_counting_init  bloomfilter_add
##    bloomfilter_lookup bloomfilter_clear bloomfilter_merge global_hash_seed
function bloomfilter_basic_init2%(k: count, cells: count,
                                  name: string &default=""%): opaque of bloomfilter
	%{
	if ( k == 0 )
		{
		reporter->Error("number of hash functions must be non-negative");
		return nullptr;
		}
	if ( cells == 0 )
		{
		reporter->Error("number of cells must be non-negative");
		return nullptr;
		}

	zeek::probabilistic::detail::Hasher::seed_t seed =
		zeek::probabilistic::detail::Hasher::MakeSeed(name->Len() > 0 ? name->Bytes() : 0, name->Len());
	const zeek::probabilistic::detail::Hasher* h = new zeek::probabilistic::detail::DoubleHasher(k, seed);

	return zeek::make_intrusive<zeek::BloomFilterVal>(new zeek::probabilistic::BasicBloomFilter(h, cells));
	%}

## Creates a counting Bloom filter.
##
## k: The number of hash functions to use.
##
## cells: The number of cells of the underlying counter vector. As there's
##        no single answer to what's the best parameterization for a
##        counting Bloom filter, we refer to the Bloom filter literature
##        here for choosing an appropiate value.
##
## max: The maximum counter value associated with each element
##      described by *w = ceil(log_2(max))* bits. Each bit in the underlying
##      counter vector becomes a cell of size *w* bits.
##
## name: A name that uniquely identifies and seeds the Bloom filter. If empty,
##       the filter will use :zeek:id:`global_hash_seed` if that's set, and
##       otherwise use a local seed tied to the current Zeek process. Only
##       filters with the same seed can be merged with
##       :zeek:id:`bloomfilter_merge`.
##
## Returns: A Bloom filter handle.
##
## .. zeek:see:: bloomfilter_basic_init bloomfilter_basic_init2 bloomfilter_add
##    bloomfilter_lookup bloomfilter_clear bloomfilter_merge global_hash_seed
function bloomfilter_counting_init%(k: count, cells: count, max: count,
				    name: string &default=""%): opaque of bloomfilter
	%{
	if ( max == 0 )
		{
		reporter->Error("max counter value must be greater than 0");
		return nullptr;
		}

	zeek::probabilistic::detail::Hasher::seed_t seed =
		zeek::probabilistic::detail::Hasher::MakeSeed(name->Len() > 0 ? name->Bytes() : 0, name->Len());

	const zeek::probabilistic::detail::Hasher* h = new zeek::probabilistic::detail::DefaultHasher(k, seed);

	uint16_t width = 1;
	while ( max >>= 1 )
		++width;

	return zeek::make_intrusive<zeek::BloomFilterVal>(new zeek::probabilistic::CountingBloomFilter(h, cells, width));
	%}

## Adds an element to a Bloom filter. For counting bloom filters, the counter is incremented.
##
## bf: The Bloom filter handle.
##
## x: The element to add.
##
## .. zeek:see:: bloomfilter_basic_init bloomfilter_basic_init2
##    bloomfilter_counting_init bloomfilter_lookup bloomfilter_clear
##    bloomfilter_merge bloomfilter_decrement
function bloomfilter_add%(bf: opaque of bloomfilter, x: any%): any
	%{
	auto* bfv = static_cast<BloomFilterVal*>(bf);

	if ( ! bfv->Type() && ! bfv->Typify(x->GetType()) )
		reporter->Error("failed to set Bloom filter type");

	else if ( ! same_type(bfv->Type(), x->GetType()) )
		reporter->Error("incompatible Bloom filter types");

	else
		bfv->Add(x);

	return nullptr;
	%}

## Decrements the counter for an element that was added to a counting bloom filter in the past.
##
## Note that decrement operations can lead to false negatives if used on a counting bloom-filter
## that exceeded the width of its counter.
##
## bf: The coubting bloom filter handle.
##
## x: The element to decrement
##
## Returns: True on success
##
## .. zeek:see:: bloomfilter_basic_init bloomfilter_basic_init2
##    bloomfilter_counting_init bloomfilter_lookup bloomfilter_clear
##    bloomfilter_merge
function bloomfilter_decrement%(bf: opaque of bloomfilter, x: any%): bool
	%{
	auto* bfv = static_cast<BloomFilterVal*>(bf);

	if ( ! bfv->Type() && ! bfv->Typify(x->GetType()) )
		reporter->Error("failed to set Bloom filter type");

	else if ( ! same_type(bfv->Type(), x->GetType()) )
		reporter->Error("incompatible Bloom filter types");

	if ( bfv->Decrement(x) )
		return val_mgr->True();
	else
		return val_mgr->False();
	%}
#

## Retrieves the counter for a given element in a Bloom filter.
##
## For a basic bloom filter, this is 0 when the element is not part of the bloom filter, or 1
## if it is part of the bloom filter.
##
## For a counting bloom filter, this is the estimate of how often an element was added.
##
## bf: The Bloom filter handle.
##
## x: The element to count.
##
## Returns: the counter associated with *x* in *bf*.
##
## .. zeek:see:: bloomfilter_basic_init bloomfilter_basic_init2
##    bloomfilter_counting_init bloomfilter_add bloomfilter_clear
##    bloomfilter_merge
function bloomfilter_lookup%(bf: opaque of bloomfilter, x: any%): count
	%{
	const auto* bfv = static_cast<const BloomFilterVal*>(bf);

	if ( ! bfv->Type() )
		return zeek::val_mgr->Count(0);

	else if ( ! same_type(bfv->Type(), x->GetType()) )
		reporter->Error("incompatible Bloom filter types");

	else
		return zeek::val_mgr->Count(static_cast<uint64_t>(bfv->Count(x)));

	return zeek::val_mgr->Count(0);
	%}

## Removes all elements from a Bloom filter. This function resets all bits in
## the underlying bitvector back to 0 but does not change the parameterization
## of the Bloom filter, such as the element type and the hasher seed.
##
## bf: The Bloom filter handle.
##
## .. zeek:see:: bloomfilter_basic_init bloomfilter_basic_init2
##    bloomfilter_counting_init bloomfilter_add bloomfilter_lookup
##    bloomfilter_merge
function bloomfilter_clear%(bf: opaque of bloomfilter%): any
	%{
	auto* bfv = static_cast<BloomFilterVal*>(bf);

	if ( bfv->Type() ) // Untyped Bloom filters are already empty.
		bfv->Clear();

	return nullptr;
	%}

## Merges two Bloom filters.
##
## bf1: The first Bloom filter handle.
##
## bf2: The second Bloom filter handle.
##
## Returns: The union of *bf1* and *bf2*.
##
## .. zeek:see:: bloomfilter_basic_init bloomfilter_basic_init2
##    bloomfilter_counting_init bloomfilter_add bloomfilter_lookup
##    bloomfilter_clear bloomfilter_merge
function bloomfilter_merge%(bf1: opaque of bloomfilter,
			    bf2: opaque of bloomfilter%): opaque of bloomfilter
	%{
	const auto* bfv1 = static_cast<const BloomFilterVal*>(bf1);
	const auto* bfv2 = static_cast<const BloomFilterVal*>(bf2);

	if ( bfv1->Type() && // any one 0 is ok here
	     bfv2->Type() &&
	     ! same_type(bfv1->Type(), bfv2->Type()) )
		{
		reporter->Error("incompatible Bloom filter types");
		return nullptr;
		}

	return BloomFilterVal::Merge(bfv1, bfv2);
	%}

## Intersects two Bloom filters.
##
## The resulting Bloom filter returns true when queried for elements
## that were contained in both bloom filters. Note that intersected Bloom
## filters have a slightly higher probability of false positives than
## Bloom filters created from scratch.
##
## Please note that, while this function works with basic and with counting
## bloom filters, the result always is a basic bloom filter. So - intersecting
## two counting bloom filters will result in a basic bloom filter. The reason
## for this is that there is no reasonable definition of how to handle counters
## during intersection.
##
## bf1: The first Bloom filter handle.
##
## bf2: The second Bloom filter handle.
##
## Returns: The intersection of *bf1* and *bf2*.
##
## .. zeek:see:: bloomfilter_basic_init bloomfilter_basic_init2
##    bloomfilter_counting_init bloomfilter_add bloomfilter_lookup
##    bloomfilter_clear bloomfilter_merge
function bloomfilter_intersect%(bf1: opaque of bloomfilter,
			    bf2: opaque of bloomfilter%): opaque of bloomfilter
	%{
	const auto* bfv1 = static_cast<const BloomFilterVal*>(bf1);
	const auto* bfv2 = static_cast<const BloomFilterVal*>(bf2);

	if ( bfv1->Type() && // any one 0 is ok here
	     bfv2->Type() &&
	     ! same_type(bfv1->Type(), bfv2->Type()) )
		{
		reporter->Error("incompatible Bloom filter types");
		return nullptr;
		}

	return BloomFilterVal::Intersect(bfv1, bfv2);
	%}

## Returns a string with a representation of a Bloom filter's internal
## state. This is for debugging/testing purposes only.
##
## bf: The Bloom filter handle.
##
## Returns: a string with a representation of a Bloom filter's internal state.
function bloomfilter_internal_state%(bf: opaque of bloomfilter%): string
	%{
	auto* bfv = static_cast<BloomFilterVal*>(bf);
	return zeek::make_intrusive<zeek::StringVal>(bfv->InternalState());
	%}
