/*
 * Copyright (c) 2009-2012, Vsevolod Stakhov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "bloom.h"
#include "xxhash.h"

/* 4 bits are used for counting (implementing delete operation) */
#define SIZE_BIT 4

/* These macroes are for 4 bits for counting element */
#define INCBIT(a, n, acc) do {																\
	acc = a[n * SIZE_BIT / CHAR_BIT] & (0xF << (n % (CHAR_BIT / SIZE_BIT) * SIZE_BIT));		\
	acc ++;																					\
	acc &= 0xF;																				\
																							\
	a[n * SIZE_BIT / CHAR_BIT] &= (0xF << (4 - (n % (CHAR_BIT/SIZE_BIT) * SIZE_BIT)));		\
	a[n * SIZE_BIT / CHAR_BIT] |= (acc << (n % (CHAR_BIT/SIZE_BIT) * SIZE_BIT));			\
} while (0);

#define DECBIT(a, n, acc) do {																\
	acc = a[n * SIZE_BIT / CHAR_BIT] & (0xF << (n % (CHAR_BIT / SIZE_BIT) * SIZE_BIT));		\
	acc --;																					\
	acc &= 0xF;																				\
																							\
	a[n * SIZE_BIT / CHAR_BIT] &= (0xF << (4 - (n % (CHAR_BIT/SIZE_BIT) * SIZE_BIT)));		\
	a[n * SIZE_BIT / CHAR_BIT] |= (acc << (n % (CHAR_BIT/SIZE_BIT) * SIZE_BIT));			\
} while (0);

#define GETBIT(a, n) (a[n * SIZE_BIT / CHAR_BIT] & (0xF << (n % (CHAR_BIT/SIZE_BIT) * SIZE_BIT)))

/* Common hash functions */


rspamd_bloom_filter_t                 *
rspamd_bloom_create (size_t size, size_t nfuncs, ...)
{
	rspamd_bloom_filter_t                 *bloom;
	va_list                         l;
	gsize                           n;

	if (!(bloom = g_malloc (sizeof (rspamd_bloom_filter_t)))) {
		return NULL;
	}
	if (!(bloom->a = g_new0 (gchar,  (size + CHAR_BIT - 1) / CHAR_BIT * SIZE_BIT))) {
		g_free (bloom);
		return NULL;
	}
	if (!(bloom->seeds = g_new0 (guint32, nfuncs))) {
		g_free (bloom->a);
		g_free (bloom);
		return NULL;
	}

	va_start (l, nfuncs);
	for (n = 0; n < nfuncs; ++n) {
		bloom->seeds[n] = va_arg (l, guint32);
	}
	va_end (l);

	bloom->nfuncs = nfuncs;
	bloom->asize = size;

	return bloom;
}

void
rspamd_bloom_destroy (rspamd_bloom_filter_t * bloom)
{
	g_free (bloom->a);
	g_free (bloom->seeds);
	g_free (bloom);
}

gboolean
rspamd_bloom_add (rspamd_bloom_filter_t * bloom, const gchar *s)
{
	size_t                          n, len;
	u_char                          t;
	guint                           v;

	if (s == NULL) {
		return FALSE;
	}
	len = strlen (s);
	for (n = 0; n < bloom->nfuncs; ++n) {
		v = XXH32 (s, len, bloom->seeds[n]) % bloom->asize;
		INCBIT (bloom->a, v, t);
	}

	return TRUE;
}

gboolean
rspamd_bloom_del (rspamd_bloom_filter_t * bloom, const gchar *s)
{
	size_t                          n, len;
	u_char                          t;
	guint                           v;

	if (s == NULL) {
		return FALSE;
	}
	len = strlen (s);
	for (n = 0; n < bloom->nfuncs; ++n) {
		v = XXH32 (s, len, bloom->seeds[n]) % bloom->asize;
		DECBIT (bloom->a, v, t);
	}

	return TRUE;

}

gboolean
rspamd_bloom_check (rspamd_bloom_filter_t * bloom, const gchar *s)
{
	size_t                          n, len;
	guint                           v;

	if (s == NULL) {
		return FALSE;
	}
	len = strlen (s);
	for (n = 0; n < bloom->nfuncs; ++n) {
		v = XXH32 (s, len, bloom->seeds[n]) % bloom->asize;
		if (!(GETBIT (bloom->a, v))) {
			return FALSE;
		}
	}

	return TRUE;
}
