#ifndef QDB_API_CLIENT_H
#define QDB_API_CLIENT_H

//! \file client.h
//! \defgroup client General client functions

/*
 *
 * Official quasardb C API 2.0.02.0.1
 * c64b446 2017-10-09 20:00:48 +0000
 *
 * Copyright (c) 2009-2017, quasardb SAS
 * 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.
 *    * Neither the name of quasardb nor the names of its contributors may
 *      be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY QUASARDB AND CONTRIBUTORS ``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 THE REGENTS AND CONTRIBUTORS 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 "error.h"

#include <stddef.h>
#include <time.h>

/*!

\mainpage quasardb C API documentation
\author quasardb SAS
\copyright quasardb SAS All rights reserved

This is a comprehensive reference documentation of the C API. The whole quasardb
documentation is available online at https://doc.quasardb.net/

Function categories:
- \ref client
- \ref error
- \ref batch
- \ref blob
- \ref deque
- \ref error
- \ref hset
- \ref integer
- \ref iterator
- \ref log
- \ref node
- \ref stream
- \ref prefix
- \ref suffix
*/

#ifdef __cplusplus
extern "C" {
#endif

//! \ingroup client
//! \typedef qdb_time_t
//! \brief A cross-platform type that represents a time value.
//! \see qdb_expires_at
//! \remark ::qdb_time_t MUST be 64-bit large. The API will probably not link
//! otherwise.
#ifdef _MSC_VER
typedef __time64_t qdb_time_t;
#else
#include <stdint.h>
typedef time_t qdb_time_t;
#endif

//! \ingroup client
//! \typedef qdb_size_t
//! \brief An alias for size_t
typedef size_t qdb_size_t;

//! \ingroup client
//! \typedef qdb_int_t
//! \brief A cross-platform type that represents a signed 64-bit integer.
#ifdef _MSC_VER
typedef __int64 qdb_int_t;
typedef unsigned __int64 qdb_uint_t;
#else
#include <stdint.h>
typedef int64_t qdb_int_t;
typedef uint64_t qdb_uint_t;
#endif

//! \ingroup client
//! \def qdb_never_expires
//! \brief An arbitrary time value representing the "no expiration" time value
//! \see qdb_time_t
#define qdb_never_expires ((qdb_time_t)0u)
//! \ingroup client
//! \def qdb_preserve_expiration
//! \brief An arbitrary time value representing the "preserve existing
//! expiration" time value
//! \see qdb_time_t
#define qdb_preserve_expiration ((qdb_time_t)0xFFFFFFFFFFFFFFFFull)

//! \ingroup client
//! \enum qdb_limits
//! \brief An enumeration of API limits

//! \ingroup client
//! \typedef qdb_limits_t
//! \brief A type alias for enum ::qdb_limits
typedef enum qdb_limits {
    //! The maximum allowed length for aliases
    qdb_l_alias_max_length = 1024,
} qdb_limits_t;

//! \ingroup client
//! \enum qdb_compression
//! \brief An enumeration of compression parameters
//! \see qdb_option_set_compression

//! \ingroup client
//! \typedef qdb_compression_t
//! \brief A type alias for enum ::qdb_compression
//! \see qdb_option_set_compression
typedef enum qdb_compression {
    //! No compression
    qdb_comp_none = 0,
    //! Maximum compression speed, potentially minimum compression ratio.
    //! This is currently the default.
    qdb_comp_fast = 1,
    //! Maximum compression ratio, potentially minimum compression speed.
    //! This is currently not implemented.
    qdb_comp_best = 2
} qdb_compression_t;

//! \ingroup client
//! \enum qdb_protocol
//! \brief An enumeration of allowed network protocols.
//! \see qdb_open

//! \ingroup client
//! \typedef qdb_protocol_t
//! \brief A type alias for enum ::qdb_protocol
//! \see qdb_open
typedef enum qdb_protocol {
    //! Uses TCP/IP to communicate with the cluster.
    //! This is currently the only supported network protocol.
    qdb_p_tcp = 0
} qdb_protocol_t;

//! \ingroup client
//! \typedef qdb_handle_t
//! \brief An opaque handle to internal API-allocated structures needed for
//! maintaining connection to a cluster.
typedef struct qdb_handle_internal * qdb_handle_t;

/* explicit packing will prevent some incompatibility cases */
#pragma pack(push)
#pragma pack(16)

//! \ingroup client
//! \struct qdb_timespec_t
//! \brief A structure representing an elapsed time since epoch
//!        (cross-platform equivalent of timespec structure)
typedef struct
{
    //! number of whole seconds of elapsed time
    qdb_time_t tv_sec;
    //! rest of the elapsed time, represented as a number of nanoseconds,
    //! always less than one billion
    qdb_time_t tv_nsec;
} qdb_timespec_t;

#if defined(_WIN64) || defined(__x86_64__) || defined(__aarch64__) \
    || defined(__ARM_ARCH_ISA_A64) || defined(__ppc64__)
#define QDB_PLATFORM_32 0
#define QDB_PLATFORM_64 1
#else
#define QDB_PLATFORM_32 1
#define QDB_PLATFORM_64 0
#endif

//! \ingroup client
//! \struct qdb_remote_node_t
//! \brief A structure representing the address of a quasardb node.
typedef struct
{
    //! A pointer to a null-terminated string representing the
    //! address of the remote node
    const char * address;
#if QDB_PLATFORM_32
    char _padding0[8 - sizeof(const char *)];
#endif
    //! The port of the remote node
    unsigned short port;
#if QDB_PLATFORM_32
    char _padding1[8 - sizeof(const char *)];
#endif
} qdb_remote_node_t;

//! \ingroup client
//! \enum qdb_entry_type
//! \brief A enumeration representing possible entries type.

//! \ingroup client
//! \typedef qdb_entry_type_t
//! \brief A type alias for enum ::qdb_entry_type
typedef enum qdb_entry_type {
    //! Uninitialized value.
    qdb_entry_uninitialized = -1,
    //! A binary large object (blob)
    qdb_entry_blob = 0,
    //! A signed 64-bit integer
    qdb_entry_integer = 1,
    //! A distributed hash set
    qdb_entry_hset = 2,
    //! A tag
    qdb_entry_tag = 3,
    //! A distributed double-entry queue (deque)
    qdb_entry_deque = 4,
    //! A distributed binary stream
    qdb_entry_stream = 5
} qdb_entry_type_t;

//! \ingroup client
//! \struct qdb_id_t
//! \brief A cluster-wide unique identifier.
typedef struct
{
    qdb_int_t data[4];
} qdb_id_t;

//! \ingroup client
//! \struct qdb_entry_metadata_t
//! \brief A structure representing the metadata of an entry in the database.
typedef struct
{
    //! unique identifier
    qdb_id_t reference;
    //! The type of the entry
    qdb_entry_type_t type;
    //! content size for blobs and integers, N/A for other types
    qdb_uint_t size;

    //! last time the entry was modified
    //! May not reflect the actual modification time for distributed containers
    //! such as deque and hset.
    qdb_timespec_t modification_time;
    //! The expiry time of the entry, if any.
    qdb_timespec_t expiry_time;
} qdb_entry_metadata_t;

#pragma pack(pop)

//! \ingroup client
//! \brief Returns a null-terminated string describing the API version
//!
//! \return A static, null-terminated string describing the API version.
//! The buffer is API managed and should not be freed or written to by the
//! caller.
QDB_API_LINKAGE const char * qdb_version(void);

//! \ingroup client
//! \brief Returns a null-terminated string describing the exact API build
//!
//! \return A static, null-terminated string describing the exact API build.
//! The buffer is API managed and should not be freed or written to by the
//! caller.
QDB_API_LINKAGE const char * qdb_build(void);

//! \ingroup client
//! \brief Creates a ::qdb_handle_t. No connection will be established.
//! \param handle A pointer to a ::qdb_handle_t that will be ready to connect to a
//! cluster.
//! \param proto The protocol to use.
//! \return A ::qdb_error_t code indicating success or failure.
//! \see qdb_connect, qdb_open_tcp, qdb_protocol_t
QDB_API_LINKAGE qdb_error_t qdb_open(qdb_handle_t * handle,
                                     qdb_protocol_t proto);

//! \ingroup client
//! \brief Creates a TCP/IP qdb_handle_t. No connection will be established.
//! \return An initialized qdb_handle_t, ready to connect, in case of success,
//! NULL in case of failure.
//! \see qdb_connect, qdb_open
QDB_API_LINKAGE qdb_handle_t qdb_open_tcp(void);

//! \ingroup client
//! \brief Sets the timeout of all network operations.
//! \param handle The qdb_handle_t that was initialized with ::qdb_open or
//! ::qdb_open_tcp
//! \param timeout_ms The timeout of network operation, in milliseconds.
//! \return A ::qdb_error_t code indicating success or failure.
//! \remark The lower the timeout, the higher the risk of having timeout errors.
QDB_API_LINKAGE qdb_error_t qdb_option_set_timeout(qdb_handle_t handle,
                                                   int timeout_ms);

//! \ingroup client
//! \brief Set the compression level for all future messages emitted by the
//! specified handle. Regardless of this parameter, the API will
//!        be able to read whatever compression the server uses.
//! \param handle The handle on which to set the compression level
//! \param comp_level The compression level to use
//! \return A ::qdb_error_t code indicating success or failure.
//! \see qdb_compression_t
//! \remark For better performance, consider disabling compression if your data
//! is already compressed.
QDB_API_LINKAGE qdb_error_t
qdb_option_set_compression(qdb_handle_t handle, qdb_compression_t comp_level);

//! \ingroup client
//! \brief Binds the client instance to a quasardb cluster and connect to at
//! least one node within.
//!
//! Quasardb URI are in the form `qdb://<address>:<port>` where `<address>` is either
//! an IPv4 or IPv6 (surrounded with square brackets), or a domain name.
//! It is recommended to specify multiple addresses should the designated node
//! be unavailable.
//!
//! URI examples:
//!   - `qdb://myserver.org:2836` - Connects to myserver.org on the port 2836
//!   - `qdb://127.0.0.1:2836` - Connects to the local IPv4 loopback on the port
//!   2836
//!   - `qdb://myserver1.org:2836,myserver2.org:2836` - Connects to myserver1.org
//!   or myserver2.org on the port 2836
//!   - `qdb://[::1]:2836` - Connects to the local IPv6 loopback on the port 2836
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \param uri A pointer to a null-terminated UTF-8 string representing the URI
//! of the quasardb cluster to connect to.
//! \return A ::qdb_error_t code indicating success or failure.
//! \see qdb_open, qdb_open_tcp
//! \attention Make sure all the addresses in the URI belong to the same cluster
QDB_API_LINKAGE qdb_error_t qdb_connect(qdb_handle_t handle, const char * uri);

//! \ingroup client
//! \brief Closes the handle previously opened with ::qdb_open or ::qdb_open_tcp.
//!
//! This results in terminating all connections and releasing all internal
//! buffers, including buffers which may
//! have been allocated as or a result of batch operations or get operations.
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \return A ::qdb_error_t code indicating success or failure.
//! \attention API-allocated buffers may be released by this call. For example,
//! the buffer allocated by ::qdb_blob_get may be released by this call.
QDB_API_LINKAGE qdb_error_t qdb_close(qdb_handle_t handle);

//! \ingroup client
//! \brief Creates a clone of a buffer using API's high-performance memory
//! allocator.
//!
//! The allocated buffer has to be released later with ::qdb_free_buffer.
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \param source_buffer A pointer to a buffer to clone
//! \param source_buffer_size The size of the buffer to clone
//! \param dest_buffer A pointer to a a pointer of an API-allocated buffer whose
//! content will be a copy of the source buffer
//! \return A ::qdb_error_t code indicating success or failure.
//! \see qdb_free_buffer
QDB_API_LINKAGE qdb_error_t qdb_copy_alloc_buffer(qdb_handle_t handle,
                                                  const void * source_buffer,
                                                  qdb_size_t source_buffer_size,
                                                  const void ** dest_buffer);

//! \ingroup client
//! \brief Releases an API-allocated buffer.
//!
//! Failure to properly call this function may result in excessive memory usage.
//! Most operations that return a content
//! (e.g. batch operations, ::qdb_blob_get, ::qdb_blob_get_and_update,
//! ::qdb_blob_compare_and_swap...) will allocate a buffer
//! for the content and will not release the allocated buffer until you either
//! call this function or close the handle.
//!
//! For API functions that returns a collection of results, use
//! ::qdb_free_results.
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \param buffer A pointer to an API-allocated buffer to release. The behavior
//! for non API buffers is undefined.
//! \return A ::qdb_error_t code indicating success or failure.
//! \see qdb_free_results
QDB_API_LINKAGE void qdb_free_buffer(qdb_handle_t handle, const void * buffer);

//! \ingroup client
//! \brief Results a collection of API-allocated results.
//!
//! Functions that returns a list of results expect the API-allocated buffers to
//! be released with ::qdb_free_results.
//!
//! Example of such functions:
//!  - ::qdb_prefix_get
//!  - ::qdb_get_tagged
//!  - ::qdb_get_tags
//!
//! For API functions that return a single content, use ::qdb_free_buffer.
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \param results A collection of results returned by a quasardb API call.
//! \param result_count The number of results returned by the quasardb API
//! call.
//! \return A ::qdb_error_t code indicating success or failure.
//! \see qdb_free_buffer
QDB_API_LINKAGE void qdb_free_results(qdb_handle_t handle,
                                      const char ** results,
                                      qdb_size_t result_count);

//! \ingroup client
//! \brief Removes an entry from the cluster, regardless of its type.
//!
//! This call will remove the entry, whether it is a blob, integer, deque,
//! stream or hset. It will properly untag the entry.
//! If the entry spawns on multiple entries or nodes (deques, hsets and streams)
//! all blocks will be properly removed.
//!
//! The call is ACID, regardless of the type of the entry and a transaction will
//! be created if need be.
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \param alias A pointer to a null-terminated UTF-8 string representing the
//! alias of the entry.
//! \return A ::qdb_error_t code indicating success or failure.
QDB_API_LINKAGE qdb_error_t qdb_remove(qdb_handle_t handle, const char * alias);

//! \ingroup client
//! \brief Sets the absolute expiration time of an entry, if the type supports
//! expiration.
//!
//! Blobs and integers can have an expiration time and will be automatically
//! removed by the cluster when they expire.
//!
//! The absolute expiration time is the Unix epoch, that is, the number of
//! milliseconds since 1 January 1970, 00:00::00 UTC.
//! To use a relative expiration time (that is expiration relative to the time
//! of the call), use ::qdb_expires_from_now.
//!
//! To remove the expiration time of an entry, specify the value
//! `qdb_never_expires` as \p expiry_time parameter.
//!
//! Values in the past are refused, but the cluster will have a certain
//! tolerance to account for clock skews.
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \param alias A pointer to a null-terminated UTF-8 string representing the
//! alias of the entry.
//! \param expiry_time The new, absolute UTC expiration time.
//! \return A ::qdb_error_t code indicating success or failure.
//! \attention It is an error to specify an expiration in the past, although the
//! cluster has a certain tolerance to account for clock synchronization
//! \see qdb_expires_from_now
QDB_API_LINKAGE qdb_error_t qdb_expires_at(qdb_handle_t handle,
                                           const char * alias,
                                           qdb_time_t expiry_time);

//! \ingroup client
//! \brief Sets the expiration time of an entry, relative to the current time of
//! the client, if the type supports expiration.
//!
//! Blobs and integers can have an expiration time and will automatically be
//! removed by the cluster when they expire.
//!
//! The expiration is relative to the current time of the machine.
//!
//! To remove the expiration time of an entry or to use an absolute expiration
//! time use ::qdb_expires_at.
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \param alias A pointer to a null-terminated UTF-8 string representing the
//! alias of the entry.
//! \param expiry_delta The number of milliseconds, relative to the current time,
//! after which the entry should expire.
//! \return A ::qdb_error_t code indicating success or failure.
//! \see qdb_expires_at
QDB_API_LINKAGE qdb_error_t qdb_expires_from_now(qdb_handle_t handle,
                                                 const char * alias,
                                                 qdb_time_t expiry_delta);

//! \ingroup client
//! \brief Retrieves the absolute expiration time of the given entry.
//!
//! The returned expiration time is the Unix epoch, UTC.
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \param alias A pointer to a null-terminated UTF-8 string representing the
//! alias of the entry.
//! \param expiry_time A pointer to an expiry time which will be set to the
//! expiration of the entry if the call is successful.
//!
//! \return A ::qdb_error_t code indicating success or failure.
//!
//! \see qdb_expires_from_now
//! \see qdb_expires_at
//! \see qdb_get_metadata
QDB_DEPRECATED("use qdb_get_metadata")
QDB_API_LINKAGE qdb_error_t qdb_get_expiry_time(qdb_handle_t handle,
                                                const char * alias,
                                                qdb_time_t * expiry_time);

//! \ingroup client
//! \brief Returns the primary node of an entry.
//!
//! The exact location of an entry should be assumed random and users should not
//! bother about its location as the API will
//! transparently locate the best node for the requested operation.
//!
//! This function is intended for higher level APIs that need to optimize
//! transfers and potentially push computation close to the data.
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \param alias A pointer to a null-terminated UTF-8 string representing the
//! alias of the entry.
//! \param location A pointer to a ::qdb_remote_node_t structure that will
//! receive the address of the primary node of the entry if
//! successful
//!
//! \return A ::qdb_error_t code indicating success or failure.
QDB_API_LINKAGE qdb_error_t qdb_get_location(qdb_handle_t handle,
                                             const char * alias,
                                             qdb_remote_node_t * location);

//! \ingroup client
//! \brief Gets the type of an entry, if it exists.
//!
//! \param handle         A valid handle previously initialized by ::qdb_open or
//!                       ::qdb_open_tcp.
//! \param alias A pointer to a null-terminated UTF-8 string representing the
//! alias of the entry.
//! \param entry_type A pointer to a ::qdb_entry_type_t that will receive the
//! type of the entry if successful.
//!
//! \return A ::qdb_error_t code indicating success or failure.
//!
//! \see qdb_get_metadata
QDB_DEPRECATED("use qdb_get_metadata")
QDB_API_LINKAGE qdb_error_t qdb_get_type(qdb_handle_t handle,
                                         const char * alias,
                                         qdb_entry_type_t * entry_type);

//! \ingroup client
//! \brief Gets the meta-information about an entry, if it exists.
//!
//! \param handle         A valid handle previously initialized by ::qdb_open or
//!                       ::qdb_open_tcp.
//! \param alias          A pointer to a null-terminated UTF-8 string
//!                       representing the alias of the entry.
//! \param entry_metadata A pointer to a ::qdb_entry_metadata_t that will
//!                       receive the metadata of the entry if successful.
//!
//! \return A ::qdb_error_t code indicating success or failure.
QDB_API_LINKAGE qdb_error_t
qdb_get_metadata(qdb_handle_t handle,
                 const char * alias,
                 qdb_entry_metadata_t * entry_metadata);

//! \ingroup client
//! \brief Removes irremediably all data from all the nodes of the cluster.
//!
//! This function is useful when quasardb is used as a cache and is not the
//! golden source.
//!
//! This call is not atomic: if the command cannot be dispatched on the whole
//! cluster, it will be dispatched
//! on as many nodes as possible and the function will return with a `qdb_e_ok`
//! code.
//!
//! By default cluster does not allow this operation and the function returns
//! a `qdb_e_operation_disabled` error.
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \param timeout_ms A timeout value, in milliseconds.
//!
//! \return A ::qdb_error_t code indicating success or failure.
//! \attention Use this function at your own risk. This function causes
//! irremediable data loss.
QDB_API_LINKAGE qdb_error_t qdb_purge_all(qdb_handle_t handle, int timeout_ms);

//! \ingroup client
//! \brief Trims all data on all the nodes of the cluster.
//!
//! quasardb uses Multi-Version Concurrency Control (MVCC) as a foundation of
//! its transaction engine. It will
//! automatically clean up old versions as entries are accessed.
//!
//! This call is not atomic: if the command cannot be dispatched on the whole
//! cluster, it will be dispatched
//! on as many nodes as possible and the function will return with a `qdb_e_ok`
//! code.
//!
//! Entries that are not accessed may not be cleaned up, resulting in increasing
//! disk usage.
//!
//! This function will request each nodes to trim all entries, release unused
//! memory and compact files on disk.
//! Because this operation is I/O and CPU intensive, it is not recommended to
//! run it when the cluster is
//! heavily used.
//!
//! \param handle A valid handle previously initialized by ::qdb_open or
//! ::qdb_open_tcp.
//! \param timeout_ms A timeout value, in milliseconds.
//!
//! \return A ::qdb_error_t code indicating success or failure.
//! \attention This function impacts the performance of the cluster.
QDB_API_LINKAGE qdb_error_t qdb_trim_all(qdb_handle_t handle, int timeout_ms);

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* QDB_API_CLIENT_H */
