#ifndef SDB_H
#define SDB_H

#ifndef O_BINARY
#define O_BINARY 0
#endif

#ifdef __cplusplus
extern "C" {
#endif

#include "types.h"
#include "ht.h"
#include "ls.h"
#include "cdb.h"
#include "cdb_make.h"
#include "sdb_version.h"

#undef r_offsetof
#define r_offsetof(type, member) ((unsigned long) &((type*)0)->member)

/* Key value sizes */
#define SDB_MIN_VALUE 1
#define SDB_MAX_VALUE 0xffffff
#define SDB_MIN_KEY 1
#define SDB_MAX_KEY 0xff

#if __SDB_WINDOWS__ && !__CYGWIN__
#include <windows.h>
#include <fcntl.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <io.h>
extern void *__cdecl _aligned_malloc(size_t, size_t);
extern char *strdup (const char *);
//#define SDB_MODE 0
#define SDB_MODE _S_IWRITE | _S_IREAD
#else
#define SDB_MODE 0644
//#define SDB_MODE 0600
#endif

//#define SDB_RS '\x1e'
#define SDB_RS ','
#define SDB_SS ","
#define SDB_MAX_PATH 256
#define SDB_NUM_BASE 16
#define SDB_NUM_BUFSZ 64

#define SDB_OPTION_NONE 0
#define SDB_OPTION_ALL 0xff
#define SDB_OPTION_SYNC    (1<<0)
#define SDB_OPTION_NOSTAMP (1<<1)
#define SDB_OPTION_FS      (1<<2)
#define SDB_OPTION_JOURNAL (1<<3)

// This size implies trailing zero terminator, this is 254 chars + 0
#define SDB_KSZ 0xff

typedef struct sdb_kv {
	char *key;
	char *value;
	int value_len;
	ut32 cas;
	ut64 expire;
} SdbKv;

typedef struct sdb_t {
	char *dir; // path+name
	char *path;
	char *name;
	int fd;
	int refs; // reference counter
	int lock;
	int journal;
	struct cdb db;
	struct cdb_make m;
	SdbHash *ht;
	ut32 eod;
	ut32 pos;
	int fdump;
	char *ndump;
	ut64 expire;
	ut64 last; // timestamp of last change
	int options;
	int ns_lock; // TODO: merge into options?
	SdbList *ns;
	SdbList *hooks;
	SdbKv tmpkv;
} Sdb;

typedef struct sdb_ns_t {
	char *name;
	ut32 hash;
	Sdb *sdb;
} SdbNs;

SDB_API Sdb* sdb_new0 ();
Sdb* sdb_new (const char *path, const char *file, int lock);

int sdb_open (Sdb *s, const char *file);
void sdb_close (Sdb *s);

void sdb_config (Sdb *s, int options);
int  sdb_free (Sdb* s);
void sdb_file (Sdb* s, const char *dir);
bool sdb_merge (Sdb* d, Sdb *s);
int sdb_count (Sdb* s);
void sdb_reset (Sdb* s);
void sdb_setup (Sdb* s, int options);
void sdb_drain (Sdb*, Sdb*);
int sdb_stats(Sdb *s, ut32 *disk, ut32 *mem);
int sdb_dump_hasnext (Sdb* s);

typedef int (*SdbForeachCallback)(void *user, const char *k, const char *v);
int sdb_foreach (Sdb* s, SdbForeachCallback cb, void *user);
SdbList *sdb_foreach_list (Sdb* s);

int  sdb_query (Sdb* s, const char *cmd);
int  sdb_queryf (Sdb* s, const char *fmt, ...);
int  sdb_query_lines (Sdb *s, const char *cmd);
char *sdb_querys (Sdb* s, char *buf, size_t len, const char *cmd);
char *sdb_querysf (Sdb* s, char *buf, size_t buflen, const char *fmt, ...);
int  sdb_query_file(Sdb *s, const char* file);
int  sdb_exists (Sdb*, const char *key);
int  sdb_remove (Sdb*, const char *key, ut32 cas);
int  sdb_unset (Sdb*, const char *key, ut32 cas);
int  sdb_unset_like(Sdb *s, const char *k);
char** sdb_like(Sdb *s, const char *k, const char *v, SdbForeachCallback cb);
char *sdb_get (Sdb*, const char *key, ut32 *cas);
char *sdb_get_len (Sdb*, const char *key, int *vlen, ut32 *cas);
const char *sdb_const_get (Sdb*, const char *key, ut32 *cas);
const char *sdb_const_get_len (Sdb* s, const char *key, int *vlen, ut32 *cas);
int  sdb_set (Sdb*, const char *key, const char *data, ut32 cas);
int  sdb_set_owned (Sdb* s, const char *key, char *val, ut32 cas);
int  sdb_concat(Sdb *s, const char *key, const char *value, ut32 cas);
int  sdb_uncat(Sdb *s, const char *key, const char *value, ut32 cas);
int  sdb_add (Sdb* s, const char *key, const char *val, ut32 cas);
void sdb_list(Sdb*);
int  sdb_sync (Sdb*);
void sdb_kv_free (SdbKv *kv);

/* num.c */
int  sdb_num_exists (Sdb*, const char *key);
int  sdb_num_base (const char *s);
ut64 sdb_num_get  (Sdb* s, const char *key, ut32 *cas);
int  sdb_num_set  (Sdb* s, const char *key, ut64 v, ut32 cas);
int  sdb_num_add  (Sdb *s, const char *key, ut64 v, ut32 cas);
ut64 sdb_num_inc  (Sdb* s, const char *key, ut64 n, ut32 cas);
ut64 sdb_num_dec  (Sdb* s, const char *key, ut64 n, ut32 cas);
int  sdb_num_min  (Sdb* s, const char *key, ut64 v, ut32 cas);
int  sdb_num_max  (Sdb* s, const char *key, ut64 v, ut32 cas);

/* ptr */
int sdb_ptr_set(Sdb *db, const char *key, void *p, ut32 cas);
void* sdb_ptr_get(Sdb *db, const char *key, ut32 *cas);

/* create db */
int sdb_disk_create (Sdb* s);
int sdb_disk_insert (Sdb* s, const char *key, const char *val);
int sdb_disk_finish (Sdb* s);
bool sdb_disk_unlink (Sdb* s);

/* iterate */
void sdb_dump_begin (Sdb* s);
SdbKv *sdb_dump_next (Sdb* s);
int sdb_dump_dupnext (Sdb* s, char **key, char **value, int *_vlen);

/* journaling */
int sdb_journal_close(Sdb *s);
int sdb_journal_open(Sdb *s);
int sdb_journal_load(Sdb *s);
int sdb_journal_log(Sdb *s, const char *key, const char *val);
int sdb_journal_clear(Sdb *s);
int sdb_journal_unlink(Sdb *s);

/* numeric */
char *sdb_itoa (ut64 n, char *s, int base);
ut64  sdb_atoi (const char *s);

/* locking */
int sdb_lock(const char *s);
const char *sdb_lock_file(const char *f);
void sdb_unlock(const char *s);
SDB_API int sdb_unlink (Sdb* s);
SDB_API int sdb_lock_wait(const char *s UNUSED);

/* expiration */
int sdb_expire_set(Sdb* s, const char *key, ut64 expire, ut32 cas);
ut64 sdb_expire_get(Sdb* s, const char *key, ut32 *cas);
ut64 sdb_now (void);
ut64 sdb_unow (void);
ut32 sdb_hash (const char *key);
ut32 sdb_hash_len (const char *key, ut32 *len);

/* json api */
int sdb_isjson (const char *k);
char *sdb_json_get (Sdb* s, const char *key, const char *p, ut32 *cas);
int sdb_json_set (Sdb* s, const char *k, const char *p, const char *v, ut32 cas);
int sdb_json_num_get (Sdb* s, const char *k, const char *p, ut32 *cas);
int sdb_json_num_set (Sdb* s, const char *k, const char *p, int v, ut32 cas);
int sdb_json_num_dec(Sdb* s, const char *k, const char *p, int n, ut32 cas);
int sdb_json_num_inc(Sdb* s, const char *k, const char *p, int n, ut32 cas);

char *sdb_json_indent(const char *s);
char *sdb_json_unindent(const char *s);

typedef struct {
	char *buf;
	size_t blen;
	size_t len;
} SdbJsonString;

const char *sdb_json_format(SdbJsonString* s, const char *fmt, ...);
#define sdb_json_format_free(x) free ((x)->buf)

// namespace
Sdb* sdb_ns (Sdb *s, const char *name, int create);
Sdb *sdb_ns_path(Sdb *s, const char *path, int create);
void sdb_ns_init (Sdb* s);
void sdb_ns_free (Sdb* s);
void sdb_ns_lock(Sdb *s, int lock, int depth);
void sdb_ns_sync (Sdb* s);
int sdb_ns_set (Sdb *s, const char *name, Sdb *r);
bool sdb_ns_unset (Sdb *s, const char *name, Sdb *r);

// array
int sdb_array_contains (Sdb* s, const char *key, const char *val, ut32 *cas);
int sdb_array_contains_num(Sdb *s, const char *key, ut64 val, ut32 *cas);
int sdb_array_indexof(Sdb *s, const char *key, const char *val, ut32 cas);
int sdb_array_set (Sdb* s, const char *key, int idx, const char *val, ut32 cas);
int sdb_array_set_num (Sdb* s, const char *key, int idx, ut64 val, ut32 cas);
bool sdb_array_append(Sdb *s, const char *key, const char *val, ut32 cas);
bool sdb_array_append_num(Sdb *s, const char *key, ut64 val, ut32 cas);
int sdb_array_prepend(Sdb *s, const char *key, const char *val, ut32 cas);
int sdb_array_prepend_num(Sdb *s, const char *key, ut64 val, ut32 cas);
char *sdb_array_get (Sdb* s, const char *key, int idx, ut32 *cas);
ut64 sdb_array_get_num (Sdb* s, const char *key, int idx, ut32 *cas);
int sdb_array_get_idx (Sdb *s, const char *key, const char *val, ut32 cas); // agetv
int sdb_array_insert (Sdb* s, const char *key, int idx, const char *val, ut32 cas);
int sdb_array_insert_num (Sdb* s, const char *key, int idx, ut64 val, ut32 cas);
int sdb_array_unset (Sdb* s, const char *key, int n, ut32 cas); // leaves empty bucket
int sdb_array_delete (Sdb* s, const char *key, int n, ut32 cas);
void sdb_array_sort (Sdb* s, const char *key, ut32 cas);
void sdb_array_sort_num (Sdb* s, const char *key, ut32 cas);
// set
int sdb_array_add (Sdb* s, const char *key, const char *val, ut32 cas);
int sdb_array_add_num (Sdb* s, const char *key, ut64 val, ut32 cas);
int sdb_array_add_sorted (Sdb *s, const char *key, const char *val, ut32 cas);
int sdb_array_add_sorted_num (Sdb *s, const char *key, ut64 val, ut32 cas);
int sdb_array_remove (Sdb *s, const char *key, const char *val, ut32 cas);
int sdb_array_remove_num (Sdb* s, const char *key, ut64 val, ut32 cas);
// helpers
char *sdb_anext(char *str, char **next);
const char *sdb_const_anext(const char *str, const char **next);
int sdb_alen(const char *str);
int sdb_alen_ignore_empty(const char *str);
int sdb_array_size(Sdb* s, const char *key);
int sdb_array_length(Sdb* s, const char *key);

int sdb_array_list(Sdb* s, const char *key);
int sdb_array_push(Sdb *s, const char *key, const char *val, ut32 cas);
char *sdb_array_pop(Sdb *s, const char *key, ut32 *cas);
int sdb_array_push_num(Sdb *s, const char *key, ut64 num, ut32 cas);
ut64 sdb_array_pop_num(Sdb *s, const char *key, ut32 *cas);

char *sdb_array_pop_head(Sdb *s, const char *key, ut32 *cas);
char *sdb_array_pop_tail(Sdb *s, const char *key, ut32 *cas);

typedef void (*SdbHook)(Sdb *s, void *user, const char *k, const char *v);

void sdb_global_hook(SdbHook hook, void *user);
int sdb_hook(Sdb* s, SdbHook cb, void* user);
int sdb_unhook(Sdb* s, SdbHook h);
int sdb_hook_call(Sdb *s, const char *k, const char *v);
void sdb_hook_free(Sdb *s);
/* Util.c */
int sdb_check_value (const char *s);
bool sdb_check_key (const char *s);
int sdb_isnum (const char *s);

const char *sdb_type(const char *k);
bool sdb_match(const char *str, const char *glob);
int sdb_bool_set(Sdb *db, const char *str, bool v, ut32 cas);
bool sdb_bool_get(Sdb *db, const char *str, ut32 *cas);

// base64
ut8 *sdb_decode (const char *in, int *len);
char *sdb_encode(const ut8 *bin, int len);
void sdb_encode_raw(char *bout, const ut8 *bin, int len);
int sdb_decode_raw(ut8 *bout, const char *bin, int len);

// binfmt
char *sdb_fmt(int n, const char *fmt, ...);
int sdb_fmt_init (void *p, const char *fmt);
void sdb_fmt_free (void *p, const char *fmt);
int sdb_fmt_tobin(const char *_str, const char *fmt, void *stru);
char *sdb_fmt_tostr(void *stru, const char *fmt);
SDB_API char** sdb_fmt_array(const char *list);
SDB_API ut64* sdb_fmt_array_num(const char *list);

// raw array helpers
char *sdb_array_compact(char *p);
char *sdb_aslice(char *out, int from, int to);
#define sdb_aforeach(x,y) \
	{ char *next; \
	if (y) for (x=y;;) { \
		x = sdb_anext (x, &next);
#define sdb_aforeach_next(x) \
	if (!next) break; \
	*(next-1) = ','; \
	x = next; } }

#ifdef __cplusplus
}
#endif

#endif
