/* Copyright (c) 2011, 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 ''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.
 */

#ifndef KVSTORAGE_H_
#define KVSTORAGE_H_

#include "config.h"

struct rspamd_kv_cache;
struct rspamd_kv_backend;
struct rspamd_kv_storage;
struct rspamd_kv_expire;
struct rspamd_kv_element;

/* Locking definitions */
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
#define RW_R_LOCK g_rw_lock_reader_lock
#define RW_R_UNLOCK g_rw_lock_reader_unlock
#define RW_W_LOCK g_rw_lock_writer_lock
#define RW_W_UNLOCK g_rw_lock_writer_unlock
#else
#define RW_R_LOCK g_static_rw_lock_reader_lock
#define RW_R_UNLOCK g_static_rw_lock_reader_unlock
#define RW_W_LOCK g_static_rw_lock_writer_lock
#define RW_W_UNLOCK g_static_rw_lock_writer_unlock
#endif

/* Callbacks for cache */
typedef void (*cache_init)(struct rspamd_kv_cache *cache);
typedef struct rspamd_kv_element* (*cache_insert)(struct rspamd_kv_cache *cache,
		gpointer key, guint keylen, gpointer value, gsize len);
typedef gboolean (*cache_replace)(struct rspamd_kv_cache *cache, gpointer key, guint keylen,
		struct rspamd_kv_element *elt);
typedef struct rspamd_kv_element* (*cache_lookup)(struct rspamd_kv_cache *cache, gpointer key, guint keylen);
typedef struct rspamd_kv_element* (*cache_delete)(struct rspamd_kv_cache *cache, gpointer key, guint keylen);
typedef void (*cache_steal)(struct rspamd_kv_cache *cache, struct rspamd_kv_element* elt);
typedef void (*cache_destroy)(struct rspamd_kv_cache *cache);

/* Callbacks for backend */
typedef void (*backend_init)(struct rspamd_kv_backend *backend);
typedef gboolean (*backend_insert)(struct rspamd_kv_backend *backend, gpointer key, guint keylen,
		struct rspamd_kv_element *elt);
typedef gboolean (*backend_replace)(struct rspamd_kv_backend *backend, gpointer key, guint keylen,
		struct rspamd_kv_element *elt);
typedef struct rspamd_kv_element* (*backend_lookup)(struct rspamd_kv_backend *backend, gpointer key,
		guint keylen);
typedef void (*backend_delete)(struct rspamd_kv_backend *backend, gpointer key, guint keylen);
typedef gboolean (*backend_sync)(struct rspamd_kv_backend *backend);
typedef gboolean (*backend_incref)(struct rspamd_kv_backend *backend, gpointer key, guint keylen);
typedef void (*backend_destroy)(struct rspamd_kv_backend *backend);

/* Callbacks for expire */
typedef void (*expire_init)(struct rspamd_kv_expire *expire);
typedef void (*expire_insert)(struct rspamd_kv_expire *expire, struct rspamd_kv_element *elt);
typedef void (*expire_delete)(struct rspamd_kv_expire *expire, struct rspamd_kv_element *elt);
typedef gboolean (*expire_step)(struct rspamd_kv_expire *expire, struct rspamd_kv_storage *storage,
		time_t now, gboolean forced);
typedef void (*expire_destroy)(struct rspamd_kv_expire *expire);


/* Flags of element */
enum rspamd_kv_flags {
	KV_ELT_ARRAY = 1 << 0,
	KV_ELT_PERSISTENT = 1 << 1,
	KV_ELT_DIRTY = 1 << 2,
	KV_ELT_OUSTED = 1 << 3,
	KV_ELT_NEED_FREE = 1 << 4,
	KV_ELT_INTEGER = 1 << 5,
	KV_ELT_NEED_INSERT = 1 << 6,
	KV_ELT_NEED_EXPIRE = 1 << 7
};

#define ELT_DATA(elt) (gchar *)(elt)->data + (elt)->keylen + 1
#define ELT_LONG(elt) *((glong *)((elt)->data + (elt)->keylen + 1))
#define ELT_KEY(elt) (gchar *)(elt)->data
#define ELT_SIZE(elt) elt->size + sizeof(struct rspamd_kv_element) + elt->keylen + 1

/* Common structures description */

struct rspamd_kv_element {
	time_t age;									/*< age of element */
	guint32 expire;								/*< expire of element */
	gint flags;									/*< element flags  */
	gsize size;									/*< size of element */
	TAILQ_ENTRY (rspamd_kv_element) entry;		/*< list entry */
	guint keylen;								/*< length of key */

	gpointer p;									/*< pointer to data */
	gchar data[1];								/*< expandable data */
};

struct rspamd_kv_cache {
	cache_init init_func;						/*< this callback is called on kv storage initialization */
	cache_insert insert_func;					/*< this callback is called when element is inserted */
	cache_replace replace_func;					/*< this callback is called when element is replace */
	cache_lookup lookup_func;					/*< this callback is used for lookup of element */
	cache_delete delete_func;					/*< this callback is called when an element is deleted */
	cache_steal steal_func;						/*< this callback is used to replace duplicates in cache */
	cache_destroy destroy_func;					/*< this callback is used for destroying all elements inside cache */
};
struct rspamd_kv_backend {
	backend_init init_func;						/*< this callback is called on kv storage initialization */
	backend_insert insert_func;					/*< this callback is called when element is inserted */
	backend_replace replace_func;				/*< this callback is called when element is replaced */
	backend_lookup lookup_func;					/*< this callback is used for lookup of element */
	backend_delete delete_func;					/*< this callback is called when an element is deleted */
	backend_sync sync_func;						/*< this callback is called when backend need to be synced */
	backend_incref incref_func;					/*< this callback is called when element must be ref'd */
	backend_destroy destroy_func;				/*< this callback is used for destroying all elements inside backend */
};
struct rspamd_kv_expire {
	expire_init init_func;						/*< this callback is called on kv storage initialization */
	expire_insert insert_func;					/*< this callback is called when element is inserted */
	expire_step step_func;						/*< this callback is used when cache is full */
	expire_delete delete_func;					/*< this callback is called when an element is deleted */
	expire_destroy destroy_func;				/*< this callback is used for destroying all elements inside expire */
};

/* Main kv storage structure */

struct rspamd_kv_storage {
	struct rspamd_kv_cache *cache;
	struct rspamd_kv_backend *backend;
	struct rspamd_kv_expire *expire;

	gsize elts;									/*< current elements count in a storage */
	gsize max_elts;								/*< maximum number of elements in a storage */

	gsize memory;								/*< memory eaten */
	gsize max_memory;							/*< memory limit */

	gint id;									/* char ID */
	gchar *name;								/* numeric ID */

	gboolean no_overwrite;						/* do not overwrite data with the same keys */
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
	GRWLock rwlock;								/* rwlock in new glib */
#else
	GStaticRWLock rwlock;						/* rwlock for threaded access */
#endif
};

/** Create new kv storage */
struct rspamd_kv_storage *rspamd_kv_storage_new (gint id, const gchar *name,
		struct rspamd_kv_cache *cache, struct rspamd_kv_backend *backend,
		struct rspamd_kv_expire *expire,
		gsize max_elts, gsize max_memory, gboolean no_overwrite);

/** Insert new element to the kv storage */
gboolean rspamd_kv_storage_insert (struct rspamd_kv_storage *storage, gpointer key, guint keylen, gpointer data, gsize len, gint flags, guint expire);

/** Insert element only in cache */
gboolean rspamd_kv_storage_insert_cache (struct rspamd_kv_storage *storage, gpointer key, guint keylen,
		gpointer data, gsize len, gint flags, guint expire, struct rspamd_kv_element **pelt);

/** Replace an element in the kv storage */
gboolean rspamd_kv_storage_replace (struct rspamd_kv_storage *storage, gpointer key, guint keylen, struct rspamd_kv_element *elt);

/** Increment value in kvstorage */
gboolean rspamd_kv_storage_increment (struct rspamd_kv_storage *storage, gpointer key, guint keylen, glong *value);

/** Lookup an element inside kv storage */
struct rspamd_kv_element* rspamd_kv_storage_lookup (struct rspamd_kv_storage *storage, gpointer key, guint keylen, time_t now);

/** Expire an element from kv storage */
struct rspamd_kv_element* rspamd_kv_storage_delete (struct rspamd_kv_storage *storage, gpointer key, guint keylen);

/** Destroy kv storage */
void rspamd_kv_storage_destroy (struct rspamd_kv_storage *storage);

/** Insert array */
gboolean rspamd_kv_storage_insert_array (struct rspamd_kv_storage *storage, gpointer key, guint keylen, guint elt_size, gpointer data, gsize len, gint flags, guint expire);

/** Set element inside array */
gboolean rspamd_kv_storage_set_array (struct rspamd_kv_storage *storage, gpointer key, guint keylen, guint elt_num,
		gpointer data, gsize len, time_t now);

/** Get element inside array */
gboolean rspamd_kv_storage_get_array (struct rspamd_kv_storage *storage, gpointer key, guint keylen, guint elt_num,
		gpointer *data, gsize *len, time_t now);

/* Hash table functions */
guint kv_elt_hash_func (gconstpointer e);
gboolean kv_elt_compare_func (gconstpointer e1, gconstpointer e2);

/**
 * LRU expire
 */
struct rspamd_kv_expire* rspamd_lru_expire_new (void);

/**
 * Ordinary hash
 */
struct rspamd_kv_cache* rspamd_kv_hash_new (void);

/**
 * Radix tree
 */
struct rspamd_kv_cache* rspamd_kv_radix_new (void);

#ifdef WITH_JUDY
/**
 * Judy tree
 */
struct rspamd_kv_cache* rspamd_kv_judy_new (void);
#endif

#endif /* KVSTORAGE_H_ */
