#ifndef DQLITE_H
#define DQLITE_H

#include <sqlite3.h>
#include <stddef.h>
#include <stdint.h>

/**
 * Version.
 */
#define DQLITE_VERSION_MAJOR    1
#define DQLITE_VERSION_MINOR    9
#define DQLITE_VERSION_RELEASE  0
#define DQLITE_VERSION_NUMBER (DQLITE_VERSION_MAJOR *100*100 + DQLITE_VERSION_MINOR *100 + DQLITE_VERSION_RELEASE)

int dqlite_version_number (void);

/**
 * Error codes.
 */
#define DQLITE_ERROR 1  /* Generic error */
#define DQLITE_MISUSE 2 /* Library used incorrectly */
#define DQLITE_NOMEM 3  /* A malloc() failed */

/**
 * Dqlite node handle.
 *
 * Opaque handle to a single dqlite node that can serve database requests from
 * connected clients and exchanges data replication messages with other dqlite
 * nodes.
 */
typedef struct dqlite_node dqlite_node;

/**
 * Hold the value of a dqlite node ID. Guaranteed to be at least 64-bit long.
 */
typedef unsigned long long dqlite_node_id;

/**
 * Create a new dqlite node object.
 *
 * The @id argument a is positive number that identifies this particular dqlite
 * node in the cluster. Each dqlite node part of the same cluster must be
 * created with a different ID. The very first node, used to bootstrap a new
 * cluster, must have ID #1. Every time a node is started again, it must be
 * passed the same ID.
 *
 * The @address argument is the network address that clients or other nodes in
 * the cluster must use to connect to this dqlite node. If no custom connect
 * function is going to be set using dqlite_node_set_connect_func(), then the
 * format of the string must be "<HOST>:<PORT>", where <HOST> is an IPv4/IPv6
 * address or a DNS name, and <PORT> is a port number. Otherwise if a custom
 * connect function is used, then the format of the string must by whatever the
 * custom connect function accepts.
 *
 * The @data_dir argument the file system path where the node should store its
 * durable data, such as Raft log entries containing WAL frames of the SQLite
 * databases being replicated.
 *
 * No reference to the memory pointed to by @address and @data_dir is kept by
 * the dqlite library, so any memory associated with them can be released after
 * the function returns.
 */
int dqlite_node_create(dqlite_node_id id,
		       const char *address,
		       const char *data_dir,
		       dqlite_node **n);

/**
 * Destroy a dqlite node object.
 *
 * This will release all memory that was allocated by the node. If
 * dqlite_node_start() was successfully invoked, then dqlite_node_stop() must be
 * invoked before destroying the node.
 */
void dqlite_node_destroy(dqlite_node *n);

/**
 * Instruct the dqlite node to bind a network address when starting, and
 * listening for incoming client connections.
 *
 * The given address might match the one passed to @dqlite_node_create or be a
 * different one (for example if the application wants to proxy it).
 *
 * The format of the @address argument must be either "<HOST>:<PORT>", where
 * <HOST> is an IPv4/IPv6 address or a DNS name and <PORT> is a port number, or
 * "@<PATH>", where <PATH> is an abstract Unix socket path. The special string
 * "@" can be used to automatically select an available abstract Unix socket
 * path, which can then be retrieved with dqlite_node_get_bind_address().

 * If an abstract Unix socket is used the dqlite node will accept only
 * connections originating from the same process.
 *
 * No reference to the memory pointed to by @address is kept, so any memory
 * associated with them can be released after the function returns.
 *
 * This function must be called before calling dqlite_node_start().
 */
int dqlite_node_set_bind_address(dqlite_node *n, const char *address);

/**
 * Get the network address that the dqlite node is using to accept incoming
 * connections.
 */
const char *dqlite_node_get_bind_address(dqlite_node *n);

/**
 * Set a custom connect function.
 *
 * The function should block until a network connection with the dqlite node at
 * the given @address is established, or an error occurs.
 *
 * In case of success, the file descriptor of the connected socket must be saved
 * into the location pointed by the @fd argument. The socket must be either a
 * TCP or a Unix socket.
 *
 * This function must be called before calling dqlite_node_start().
 */
int dqlite_node_set_connect_func(dqlite_node *n,
				 int (*f)(void *arg,
					  const char *address,
					  int *fd),
				 void *arg);

/**
 * DEPRECATED - USE `dqlite_node_set_network_latency_ms`
 * Set the average one-way network latency, expressed in nanoseconds.
 *
 * This value is used internally by dqlite to decide how frequently the leader
 * node should send heartbeats to other nodes in order to maintain its
 * leadership, and how long other nodes should wait before deciding that the
 * leader has died and initiate a failover.
 *
 * This function must be called before calling dqlite_node_start().
 */
int dqlite_node_set_network_latency(dqlite_node *n,
				    unsigned long long nanoseconds);


/**
 * Set the average one-way network latency, expressed in milliseconds.
 *
 * This value is used internally by dqlite to decide how frequently the leader
 * node should send heartbeats to other nodes in order to maintain its
 * leadership, and how long other nodes should wait before deciding that the
 * leader has died and initiate a failover.
 *
 * This function must be called before calling dqlite_node_start().
 *
 * Latency should not be 0 or larger than 3600000 milliseconds.
 */
int dqlite_node_set_network_latency_ms(dqlite_node *t, unsigned milliseconds);

/**
 * Set the failure domain associated with this node.
 *
 * This is effectively a tag applied to the node and that can be inspected later
 * with the "Describe node" client request.
 */
int dqlite_node_set_failure_domain(dqlite_node *n, unsigned long long code);

/**
 * Set the snapshot parameters for this node.
 *
 * This function determines how frequently a node will snapshot the state
 * of the database and how many raft log entries will be kept around after
 * a snapshot has been taken.
 *
 * `snapshot_threshold` : Determines the frequency of taking a snapshot, the
 * lower the number, the higher the frequency.
 *
 * `snapshot_trailing` : Determines the amount of log entries kept around after
 * taking a snapshot. Lowering this number decreases disk and memory footprint
 * but increases the chance of having to send a full snapshot (instead of a
 * number of log entries to a node that has fallen behind.
 *
 * This function must be called before calling dqlite_node_start().
 */
int dqlite_node_set_snapshot_params(dqlite_node *n, unsigned snapshot_threshold,
                                    unsigned snapshot_trailing);

/**
 * Start a dqlite node.
 *
 * A background thread will be spawned which will run the node's main loop. If
 * this function returns successfully, the dqlite node is ready to accept new
 * connections.
 */
int dqlite_node_start(dqlite_node *n);

/**
 * Stop a dqlite node.
 *
 * The background thread running the main loop will be notified and the node
 * will not accept any new client connections. Once inflight requests are
 * completed, open client connections get closed and then the thread exits.
 */
int dqlite_node_stop(dqlite_node *n);

struct dqlite_node_info
{
	dqlite_node_id id;
	const char *address;
};
typedef struct dqlite_node_info dqlite_node_info;

/* Defined to be an extensible struct, future additions to this struct should be
 * 64-bits wide and 0 should not be used as a valid value. */
struct dqlite_node_info_ext
{
        uint64_t size; /* The size of this struct */
        uint64_t id; /* dqlite_node_id */
        uint64_t address;
        uint64_t dqlite_role;
};
typedef struct dqlite_node_info_ext dqlite_node_info_ext;
#define DQLITE_NODE_INFO_EXT_SZ_ORIG 32U /* (4 * 64) / 8 */

/**
 * !!! Deprecated, use `dqlite_node_recover_ext` instead which also includes
 * dqlite roles. !!!
 *
 * Force recovering a dqlite node which is part of a cluster whose majority of
 * nodes have died, and therefore has become unavailable.
 *
 * In order for this operation to be safe you must follow these steps:
 *
 * 1. Make sure no dqlite node in the cluster is running.
 *
 * 2. Identify all dqlite nodes that have survived and that you want to be part
 *    of the recovered cluster.
 *
 * 3. Among the survived dqlite nodes, find the one with the most up-to-date
 *    raft term and log.
 *
 * 4. Invoke @dqlite_node_recover exactly one time, on the node you found in
 *    step 3, and pass it an array of #dqlite_node_info filled with the IDs and
 *    addresses of the survived nodes, including the one being recovered.
 *
 * 5. Copy the data directory of the node you ran @dqlite_node_recover on to all
 *    other non-dead nodes in the cluster, replacing their current data
 *    directory.
 *
 * 6. Restart all nodes.
 */
int dqlite_node_recover(dqlite_node *n, dqlite_node_info infos[], int n_info);

/**
 * Force recovering a dqlite node which is part of a cluster whose majority of
 * nodes have died, and therefore has become unavailable.
 *
 * In order for this operation to be safe you must follow these steps:
 *
 * 1. Make sure no dqlite node in the cluster is running.
 *
 * 2. Identify all dqlite nodes that have survived and that you want to be part
 *    of the recovered cluster.
 *
 * 3. Among the survived dqlite nodes, find the one with the most up-to-date
 *    raft term and log.
 *
 * 4. Invoke @dqlite_node_recover_ext exactly one time, on the node you found in
 *    step 3, and pass it an array of #dqlite_node_info filled with the IDs,
 *    addresses and roles of the survived nodes, including the one being recovered.
 *
 * 5. Copy the data directory of the node you ran @dqlite_node_recover_ext on to all
 *    other non-dead nodes in the cluster, replacing their current data
 *    directory.
 *
 * 6. Restart all nodes.
 */
int dqlite_node_recover_ext(dqlite_node *n, dqlite_node_info_ext infos[], int n_info);

/**
 * Return a human-readable description of the last error occurred.
 */
const char *dqlite_node_errmsg(dqlite_node *n);

/**
 * Generate a unique ID for the given address.
 */
dqlite_node_id dqlite_generate_node_id(const char *address);

/**
 * Initialize the given SQLite VFS interface object with dqlite's custom
 * implementation, which can be used for replication.
 */
int dqlite_vfs_init(sqlite3_vfs *vfs, const char *name);

/**
 * Release all memory used internally by a SQLite VFS object that was
 * initialized using @qlite_vfs_init.
 */
void dqlite_vfs_close(sqlite3_vfs *vfs);

/**
 * A single WAL frame to be replicated.
 */
struct dqlite_vfs_frame
{
	unsigned long page_number; /* Database page number. */
	void *data;                /* Content of the database page. */
};
typedef struct dqlite_vfs_frame dqlite_vfs_frame;

/**
 * Check if the last call to sqlite3_step() has triggered a write transaction on
 * the database with the given filename. In that case acquire a WAL write lock
 * to prevent further write transactions, and return all new WAL frames
 * generated by the transaction. These frames are meant to be replicated across
 * nodes and then actually added to the WAL with dqlite_vfs_apply() once a
 * quorum is reached. If a quorum is not reached within a given time, then
 * dqlite_vfs_abort() can be used to abort and release the WAL write lock.
 */
int dqlite_vfs_poll(sqlite3_vfs *vfs,
		    const char *filename,
		    dqlite_vfs_frame **frames,
		    unsigned *n);

/**
 * Add to the WAL all frames that were generated by a write transaction
 * triggered by sqlite3_step() and that were obtained via dqlite_vfs_poll().
 *
 * This interface is designed to match the typical use case of a node receiving
 * the frames by sequentially reading a byte stream from a network socket and
 * passing the data to this routine directly without any copy or futher
 * allocation, possibly except for integer encoding/decoding.
 */
int dqlite_vfs_apply(sqlite3_vfs *vfs,
		     const char *filename,
		     unsigned n,
		     unsigned long *page_numbers,
		     void *frames);

/**
 * Abort a pending write transaction that was triggered by sqlite3_step() and
 * whose frames were obtained via dqlite_vfs_poll().
 *
 * This should be called if the transaction could not be safely replicated. In
 * particular it will release the write lock acquired by dqlite_vfs_poll().
 */
int dqlite_vfs_abort(sqlite3_vfs *vfs, const char *filename);

/**
 * Return a snapshot of the main database file and of the WAL file.
 */
int dqlite_vfs_snapshot(sqlite3_vfs *vfs,
			const char *filename,
			void **data,
			size_t *n);

/**
 * Restore a snapshot of the main database file and of the WAL file.
 */
int dqlite_vfs_restore(sqlite3_vfs *vfs,
		       const char *filename,
		       const void *data,
		       size_t n);

#endif /* DQLITE_H */
