#ifndef RSPAMD_UTIL_H
#define RSPAMD_UTIL_H

#include "config.h"
#include "mem_pool.h"
#include "radix.h"
#include "statfile.h"
#include "printf.h"
#include "fstring.h"

struct config_file;
struct rspamd_main;
struct workq;
struct statfile;
struct classifier_config;

/**
 * Union that is used for storing sockaddrs
 */
union sa_union {
	struct sockaddr_storage ss;
	struct sockaddr sa;
	struct sockaddr_in s4;
	struct sockaddr_in6 s6;
	struct sockaddr_un su;
};

/*
 * Create socket and bind or connect it to specified address and port
 */
gint make_tcp_socket (struct addrinfo *, gboolean is_server, gboolean async);
/*
 * Create socket and bind or connect it to specified address and port
 */
gint make_udp_socket (struct addrinfo *, gboolean is_server, gboolean async);
/*
 * Accept from socket
 */
gint accept_from_socket (gint listen_sock, struct sockaddr *addr, socklen_t *len);
/*
 * Create and bind or connect unix socket
 */
gint make_unix_socket (const gchar *, struct sockaddr_un *, gint type, gboolean is_server, gboolean async);

/**
 * Make a universal socket
 * @param credits host, ip or path to unix socket
 * @param port port (used for network sockets)
 * @param type type of socket (SO_STREAM or SO_DGRAM)
 * @param async make this socket asynced
 * @param is_server make this socket as server socket
 * @param try_resolve try name resolution for a socket (BLOCKING)
 */
gint make_universal_socket (const gchar *credits, guint16 port, gint type,
		gboolean async, gboolean is_server, gboolean try_resolve);

/**
 * Make a universal sockets
 * @param credits host, ip or path to unix socket (several items may be separated by ',')
 * @param port port (used for network sockets)
 * @param type type of socket (SO_STREAM or SO_DGRAM)
 * @param async make this socket asynced
 * @param is_server make this socket as server socket
 * @param try_resolve try name resolution for a socket (BLOCKING)
 */
GList* make_universal_sockets_list (const gchar *credits, guint16 port, gint type,
		gboolean async, gboolean is_server, gboolean try_resolve);
/*
 * Create socketpair
 */
gint make_socketpair (gint pair[2]);

/*
 * Write pid to file
 */
gint write_pid (struct rspamd_main *);

/*
 * Make specified socket non-blocking
 */
gint make_socket_nonblocking (gint);
/*
 * Make specified socket blocking
 */
gint make_socket_blocking (gint);

/*
 * Poll a sync socket for specified events
 */
gint poll_sync_socket (gint fd, gint timeout, short events);

/*
 * Init signals
 */
#ifdef HAVE_SA_SIGINFO
void init_signals (struct sigaction *sa, void (*sig_handler)(gint, siginfo_t *, void *));
#else
void init_signals (struct sigaction *sa, void (*sig_handler)(gint));
#endif

/*
 * Send specified signal to each worker
 */
void pass_signal_worker (GHashTable *, gint );
/*
 * Convert string to lowercase
 */
void convert_to_lowercase (gchar *str, guint size);

#ifndef HAVE_SETPROCTITLE
/*
 * Process title utility functions
 */
gint init_title(gint argc, gchar *argv[], gchar *envp[]);
gint setproctitle(const gchar *fmt, ...);
#endif

#ifndef HAVE_PIDFILE
/*
 * Pidfile functions from FreeBSD libutil code
 */
typedef struct rspamd_pidfh_s {
	gint pf_fd;
#ifdef HAVE_PATH_MAX
	gchar    pf_path[PATH_MAX + 1];
#elif defined(HAVE_MAXPATHLEN)
	gchar    pf_path[MAXPATHLEN + 1];
#else
	gchar    pf_path[1024 + 1];
#endif
 	dev_t pf_dev;
 	ino_t   pf_ino;
} rspamd_pidfh_t;
rspamd_pidfh_t *rspamd_pidfile_open(const gchar *path, mode_t mode, pid_t *pidptr);
gint rspamd_pidfile_write(rspamd_pidfh_t *pfh);
gint rspamd_pidfile_close(rspamd_pidfh_t *pfh);
gint rspamd_pidfile_remove(rspamd_pidfh_t *pfh);
#else
typedef struct pidfh rspamd_pidfh_t;
#define rspamd_pidfile_open pidfile_open
#define rspamd_pidfile_write pidfile_write
#define rspamd_pidfile_close pidfile_close
#define rspamd_pidfile_remove pidfile_remove
#endif

/*
 * Replace %r with rcpt value and %f with from value, new string is allocated in pool
 */
gchar* resolve_stat_filename (memory_pool_t *pool, gchar *pattern, gchar *rcpt, gchar *from);
#ifdef HAVE_CLOCK_GETTIME
/*
 * Calculate check time with specified resolution of timer
 */
const gchar* calculate_check_time (struct timeval *tv, struct timespec *begin, gint resolution, guint32 *scan_ms);
#else
const gchar* calculate_check_time (struct timeval *begin, gint resolution, guint32 *scan_ms);
#endif

/*
 * File locking functions
 */
gboolean lock_file (gint fd, gboolean async);
gboolean unlock_file (gint fd, gboolean async);

/*
 * Hash table utility functions for case insensitive hashing
 */
guint rspamd_strcase_hash (gconstpointer key);
gboolean rspamd_strcase_equal (gconstpointer v, gconstpointer v2);

/*
 * Hash table utility functions for case sensitive hashing
 */
guint rspamd_str_hash (gconstpointer key);
gboolean rspamd_str_equal (gconstpointer v, gconstpointer v2);


/*
 * Hash table utility functions for hashing fixed strings
 */
guint fstr_strcase_hash (gconstpointer key);
gboolean fstr_strcase_equal (gconstpointer v, gconstpointer v2);

/*
 * Google perf-tools initialization function
 */
void gperf_profiler_init (struct config_file *cfg, const gchar *descr);

/*
 * Workarounds for older versions of glib
 */
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 22))
void g_ptr_array_unref (GPtrArray *array);
#endif
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 14))
void g_queue_clear (GQueue *queue);
#endif


/**
 * Copy src to dest limited to len, in compare with standart strlcpy(3) rspamd strlcpy does not
 * traverse the whole string and it is possible to use it for non NULL terminated strings. This is
 * more like memccpy(dst, src, size, '\0')
 *
 * @param dst destination string
 * @param src source string
 * @param siz length of destination buffer
 * @return bytes copied
 */
gsize rspamd_strlcpy (gchar *dst, const gchar *src, gsize siz);

/**
 * Lowercase strlcpy variant
 * @param dst
 * @param src
 * @param siz
 * @return
 */
gsize rspamd_strlcpy_tolower (gchar *dst, const gchar *src, gsize siz);

/*
 * Strip <> from email address
 */
gchar * escape_braces_addr_fstr (memory_pool_t *pool, f_str_t *in);

/*
 * Convert milliseconds to timeval fields
 */
#define msec_to_tv(msec, tv) do { (tv)->tv_sec = (msec) / 1000; (tv)->tv_usec = ((msec) - (tv)->tv_sec * 1000) * 1000; } while(0)
#define double_to_tv(dbl, tv) do { (tv)->tv_sec = (int)(dbl); (tv)->tv_usec = ((dbl) - (int)(dbl))*1000*1000; } while(0)
#define tv_to_msec(tv) (tv)->tv_sec * 1000 + (tv)->tv_usec / 1000

/* Compare two emails for building emails tree */
gint compare_email_func (gconstpointer a, gconstpointer b);

/* Compare two urls for building emails tree */
gint compare_url_func (gconstpointer a, gconstpointer b);

/*
 * Find string find in string s ignoring case
 */
gchar* rspamd_strncasestr (const gchar *s, const gchar *find, gint len);

/*
 * Try to convert string of length to long
 */
gboolean rspamd_strtol (const gchar *s, gsize len, glong *value);

/*
 * Try to convert string of length to unsigned long
 */
gboolean rspamd_strtoul (const gchar *s, gsize len, gulong *value);

/**
 * Try to allocate a file on filesystem (using fallocate or posix_fallocate)
 * @param fd descriptor
 * @param offset offset of file
 * @param len length to allocate
 * @return -1 in case of failure
 */
gint rspamd_fallocate (gint fd, off_t offset, off_t len);

/**
 * Return worker's control structure by its type
 * @param type
 * @return worker's control structure or NULL
 */
extern worker_t* get_worker_by_type (GQuark type);

/**
 * Utils for working with threads to be compatible with all glib versions
 */
typedef struct rspamd_mutex_s {
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
	GMutex mtx;
#else
	GStaticMutex mtx;
#endif
} rspamd_mutex_t;

typedef struct rspamd_rwlock_s {
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
	GRWLock rwlock;
#else
	GStaticRWLock rwlock;
#endif
} rspamd_rwlock_t;


/**
 * Create new mutex
 * @return mutex or NULL
 */
rspamd_mutex_t* rspamd_mutex_new (void);

/**
 * Lock mutex
 * @param mtx
 */
void rspamd_mutex_lock (rspamd_mutex_t *mtx);

/**
 * Unlock mutex
 * @param mtx
 */
void rspamd_mutex_unlock (rspamd_mutex_t *mtx);

/**
 * Clear rspamd mutex
 * @param mtx
 */
void rspamd_mutex_free (rspamd_mutex_t *mtx);

/**
 * Create new rwloc
 * @return
 */
rspamd_rwlock_t* rspamd_rwlock_new (void);

/**
 * Lock rwlock for writing
 * @param mtx
 */
void rspamd_rwlock_writer_lock (rspamd_rwlock_t *mtx);

/**
 * Lock rwlock for reading
 * @param mtx
 */
void rspamd_rwlock_reader_lock (rspamd_rwlock_t *mtx);

/**
 * Unlock rwlock from writing
 * @param mtx
 */
void rspamd_rwlock_writer_unlock (rspamd_rwlock_t *mtx);

/**
 * Unlock rwlock from reading
 * @param mtx
 */
void rspamd_rwlock_reader_unlock (rspamd_rwlock_t *mtx);

/**
 * Free rwlock
 * @param mtx
 */
void rspamd_rwlock_free (rspamd_rwlock_t *mtx);

static inline void
rspamd_cond_wait (GCond *cond, rspamd_mutex_t *mtx)
{
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
	g_cond_wait (cond, &mtx->mtx);
#else
	g_cond_wait (cond, g_static_mutex_get_mutex (&mtx->mtx));
#endif
}

/**
 * Create new named thread
 * @param name name pattern
 * @param func function to start
 * @param data data to pass to function
 * @param err error pointer
 * @return new thread object that can be joined
 */
GThread* rspamd_create_thread (const gchar *name, GThreadFunc func, gpointer data, GError **err);

/**
 * Return 32bit murmur hash value for specified input
 * @param in input data
 * @param len length of the input data
 * @code
 *  MurmurHash3 was created by Austin Appleby  in 2008. The cannonical
 *  implementations are in C++ and placed in the public.
 *
 *    https://sites.google.com/site/murmurhash/
 *
 *  Seungyoung Kim has ported it's cannonical implementation to C language
 *  in 2012 and published it as a part of qLibc component.
 * @endcode
 * @return
 */
guint32 murmur32_hash (const guint8 *in, gsize len);

/**
 * Return 32bit murmur hash value for specified input
 * @param in input data
 * @param len length of the input data
 * @param out array of 2 guint64 variables
 * @code
 *  MurmurHash3 was created by Austin Appleby  in 2008. The cannonical
 *  implementations are in C++ and placed in the public.
 *
 *    https://sites.google.com/site/murmurhash/
 *
 *  Seungyoung Kim has ported it's cannonical implementation to C language
 *  in 2012 and published it as a part of qLibc component.
 * @endcode
 * @return
 */
void murmur128_hash (const guint8 *in, gsize len, guint64 out[]);

/**
 * Deep copy of one hash table to another
 * @param src source hash
 * @param dst destination hash
 * @param key_copy_func function called to copy or modify keys (or NULL)
 * @param value_copy_func function called to copy or modify values (or NULL)
 * @param ud user data for copy functions
 */
void rspamd_hash_table_copy (GHashTable *src, GHashTable *dst,
		gpointer (*key_copy_func)(gconstpointer data, gpointer ud),
		gpointer (*value_copy_func)(gconstpointer data, gpointer ud),
		gpointer ud);

/**
 * Utility function to provide mem_pool copy for rspamd_hash_table_copy function
 * @param data string to copy
 * @param ud memory pool to use
 * @return
 */
gpointer rspamd_str_pool_copy (gconstpointer data, gpointer ud);

/**
 * Parse ipv4 address with optional mask in CIDR format
 * @param line cidr notation of ipv4 address
 * @param ina destination address
 * @param mask destination mask
 * @return
 */
gboolean parse_ipmask_v4 (const char *line, struct in_addr *ina, int *mask);

/**
 * Read passphrase from tty
 * @param buf buffer to fill with a password
 * @param size size of the buffer
 * @param rwflag unused flag
 * @param key unused key
 * @return size of password read
 */
gint rspamd_read_passphrase (gchar *buf, gint size, gint rwflag, gpointer key);

/**
 * Seed glib prng using openssl if possible
 */
void rspamd_prng_seed (void);

/**
 * Generate random bytes using the most suitable generator
 * @param buf
 * @param buflen
 */
void rspamd_random_bytes (gchar *buf, gsize buflen);

/**
 * Check whether specified ip is valid (not INADDR_ANY or INADDR_NONE) for ipv4 or ipv6
 * @param ptr pointer to struct in_addr or struct in6_addr
 * @param af address family (AF_INET or AF_INET6)
 * @return TRUE if the address is valid
 */
gboolean rspamd_ip_is_valid (void *ptr, int af);

#endif
