/*
    Copyright (C) 2008-2024, Millistream Market Data <support@millistream.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#ifndef MDF_H
#define MDF_H 1

#ifndef _STDINT_H
# if _MSC_VER < 1600 && _MSC_VER > 0
#  define uint32_t unsigned __int32
#  define uint64_t unsigned __int64
# else
#  include <stdint.h>
# endif
#endif

#if defined _WIN32 || defined _WIN64 || defined __CYGWIN__
# ifdef LIBMDF_EXPORTS
#  ifdef __GNUC__
#   define LIBMDF_API __attribute__ ((dllexport))
#  else
#   define LIBMDF_API __declspec(dllexport)
#  endif
# else
#  ifdef __GNUC__
#   define LIBMDF_API __attribute__ ((dllimport))
#  else
#   define LIBMDF_API __declspec(dllimport)
#  endif
# endif
#else
# if __GNUC__ >= 4
#  define LIBMDF_API __attribute__ ((visibility ("default")))
# else
#  define LIBMDF_API
# endif
#endif

#ifdef __cplusplus
extern "C" {
#endif

typedef enum {
	MDF_OPT_FD,
	MDF_OPT_ERROR,
	MDF_OPT_RCV_BYTES,
	MDF_OPT_SENT_BYTES,
	MDF_OPT_DATA_CALLBACK_FUNCTION,
	MDF_OPT_DATA_CALLBACK_USERDATA,
	MDF_OPT_STATUS_CALLBACK_FUNCTION,
	MDF_OPT_STATUS_CALLBACK_USERDATA,
	MDF_OPT_CONNECT_TIMEOUT,
	MDF_OPT_HEARTBEAT_INTERVAL,
	MDF_OPT_HEARTBEAT_MAX_MISSED,
	MDF_OPT_TCP_NODELAY,
	MDF_OPT_NO_ENCRYPTION,
	MDF_OPT_TIME_DIFFERENCE,
	MDF_OPT_BIND_ADDRESS,
	MDF_OPT_TIME_DIFFERENCE_NS,
	MDF_OPT_CRYPT_DIGESTS,
	MDF_OPT_CRYPT_CIPHERS,
	MDF_OPT_CRYPT_DIGEST,
	MDF_OPT_CRYPT_CIPHER,
	MDF_OPT_TIMEOUT,
	MDF_OPT_HANDLE_DELAY,
	MDF_OPT_RBUF_SIZE,
	MDF_OPT_RBUF_MAXSIZE,
	MDF_OPT_CONNECTED_HOST,
	MDF_OPT_CONNECTED_IP
} MDF_OPTION;

typedef enum {
	MDF_MSG_OPT_UTF8,
	MDF_MSG_OPT_COMPRESSION,
	MDF_MSG_OPT_DELAY
} MDF_MSG_OPTION;

typedef enum {
	MDF_ERR_NO_ERROR,
	MDF_ERR_NO_MEM,
	MDF_ERR_MSG_OOB,
	MDF_ERR_TEMPLATE_OOB,
	MDF_ERR_UNKNOWN_TEMPLATE,
	MDF_ERR_ARGUMENT,
	MDF_ERR_CONNECTED,
	MDF_ERR_NOT_CONNECTED,
	MDF_ERR_CONNECT,
	MDF_ERR_MSG_TO_LARGE,
	MDF_ERR_CONNECTION_IDLE,
	MDF_ERR_DISCONNECTED,
	MDF_ERR_AUTHFAIL
} MDF_ERROR;

typedef enum {
	MDF_STATUS_LOOKUP = 0,
	MDF_STATUS_CONNECTING,
	MDF_STATUS_CONNECTED,
	MDF_STATUS_DISCONNECTED,
	MDF_STATUS_READYTOLOGON,
	MDF_STATUS_SND_HB_REQ,
	MDF_STATUS_RCV_HB_REQ,
	MDF_STATUS_RCV_HB_RES
} MDF_CONN_STATUS;

/* The opaque API handle */
typedef struct mdf_s *mdf_t;

/* Create a new API handle, this should be the first function
 * to call in your application. The handle is not thread-safe
 * but different threads can access different handles simultaneously.
 *
 * Please also note SIGPIPE will be disabled by this function, and
 * it will NOT be restored in mdf_destroy(). So if your application
 * depends upon SIGPIPE you will have to re set the signal after the
 * call to mdf_create(). This only happens on platforms that does not
 * have MSG_NOSIGNAL or SO_NOSIGPIPE but does have SIGPIPE. */
LIBMDF_API mdf_t mdf_create ();

/* Destroy the API handle, open connections will be automatically closed
 * and any remaining message chains will also be destroyed */
LIBMDF_API void mdf_destroy (mdf_t handle);

/* Proceeds to the next message in the stream and returns the 
 * message reference (mref) and the instrument reference (insref) of
 * that message.
 *
 * Returns true (1) if there was a message to return or false (0)
 * if there is no more messages in the stream. */
LIBMDF_API int mdf_get_next_message2 (mdf_t handle, uint16_t * const mref, uint64_t * const insref);

/* Return the message reference, class and instrument reference of the
 * current message in the stream. Also proceeds the internal position
 * to the next message in the stream.
 *
 * This is just a wrapper for mdf_get_next_message2 for compability with
 * older clients, the integer for message_class was too small for the
 * values currently in use and was also mostly unimportant for most clients.
 *
 * Returns true (1) if there was a message to return or false (0)
 * if there is no more messages in the stream. */
LIBMDF_API int mdf_get_next_message (mdf_t handle, int *message_reference, int *message_class, uint64_t *instrument_reference);

/* Return the message class of the current message, mdf_get_next_message
 * or mdf_get_next_message2 must be called before this function so it can
 * look at the correct message.
 *
 * Return MDF_MC_UNDEF (0) on error or if the message class is unknown. */
LIBMDF_API uint64_t mdf_get_mclass (mdf_t handle);

/* Returns the intended delay of the current message if delay-mode have been
 * activated by setting the MDF_OPT_HANDLE_DELAY option. Note that this is
 * the intended delay of the message and not necessarily the real delay,
 * network latency, server latency and so on are not included. */
LIBMDF_API uint8_t mdf_get_delay (mdf_t handle);

/* Return the tag and value of the current field in the message and
 * proceed the internal position to the next field in the message.
 *
 * Returns true (1) if there was a field to return or false (0)
 * if there is no more fields in the message. */
LIBMDF_API int mdf_get_next_field (mdf_t handle, uint32_t *tag, char **value);

/* Return the value of the specified option. The data value points to
 * will be filled in accordingly and can be relied upon only if the
 * function returns true (1) */
LIBMDF_API int mdf_get_property (mdf_t handle, MDF_OPTION option, ...);

/* Sets the specified option to the specified value. value can be set
 * to NULL in order to unset the option. */
LIBMDF_API int mdf_set_property (mdf_t handle, MDF_OPTION option, void *value);

/* If a callback function has been set, it will be called
 * whenever there is messages to consume */
typedef void (*mdf_data_callback)(void *userdata, mdf_t handle);

/* If a status callback function has been set, it will be called
 * whenever the status of the connection changes */
typedef void (*mdf_status_callback)(void *userdata, MDF_CONN_STATUS status, const char *host, const char *ip);

/* Connects to the first server in the servers string which can be a
 * comma separated list of "server:port" where server can be a host name or
 * a raw ip address (IPv6 must be enclosed in brackets "[address]", if the
 * first server does not respond in time, the next will be tried and so on
 * until there is no more servers in the list.
 *
 * Returns true (1) if a connection with a server was possible and false (0)
 * if connection failed with every server on the list */
LIBMDF_API int mdf_connect (mdf_t handle, const char *servers);

/* Disconnects a connected handle. Safe to call even if the connection
 * is already down. */
LIBMDF_API void mdf_disconnect (mdf_t handle);

/* Consumes any bytes from the server (if any), if a complete transaction
 * has been received, any defined callback function will be called.
 * timeout is the time in seconds to wait for data from the server, if
 * zero the function will return immediately if there is no data to consume.
 *
 * Returns 1 if there is messages to decode and no callback function
 * was defined, 0 if the function timeouts and -1 if there was an
 * error.
*/
LIBMDF_API int mdf_consume (mdf_t handle, const int timeout);

/* Proceeds to the next message in the stream and extracts it so the 
 * caller can later inject the message to another mdf handle. The 
 * message reference (mref) and the instrument reference (insref) of
 * the message is also returned for use by the caller (e.g to decide
 * to which other mdf handle to inject it).
 *
 * Returns a pointer to the message if there was a message to return
 * or NULL if there is no more messages in the stream. The caller must
 * make a copy of the returned data since the pointer is to the internal
 * state of the mdf handle.
*/
LIBMDF_API void *mdf_extract (mdf_t handle, uint16_t * const mref, uint64_t * const insref, size_t * const len);

/* Inject a message into a mdf handle previously extracted from another
 * mdf handle with the mdf_extract() function. Once called the user must
 * call mdf_get_next_message() / mdf_get_next_message2() as usual until it
 * returns false.
*/
LIBMDF_API int mdf_inject (mdf_t handle, const void * const ptr, const size_t len);

/* Returns a pointer to the start of the message to be decoded and a pointer to where
 * the message ends, can only be used after a successfull call to get_next_message()
 * and is for internal Millistream usage only.
*/
LIBMDF_API uint8_t *mdf_get_message_ptr (mdf_t handle, const uint8_t **endp);


/* Message Chains - Functions to send data into the Millistream system. */

/* Opaque handler to the message chain */
typedef struct mdf_message_s *mdf_message_t;

/* Create a new message chain */
LIBMDF_API mdf_message_t mdf_message_create ();

/* Sets the specified option to the specified value. 
 *
 * Returns true (1) if the option was changed and false (0) if it was not */
LIBMDF_API int mdf_message_set_property (mdf_message_t message, MDF_MSG_OPTION option, const int value);

/* Adds a new message to the message chain, if there is an empty message in the chain
 * that message will be reused and no memory will be allocated.
 * Use message_reference to define which message to send.
 *
 * Returns true (1) if a new message was added (or an old was reused) and 
 * false (0) if there was an error. */
LIBMDF_API int mdf_message_add (mdf_message_t message, const uint64_t instrument_reference, const int message_reference);

/* Adds a new message to the message chain, if there is an empty message in the chain
 * that message will be reused and no memory will be allocated.
 * Use message_reference to define which message to send.
 *
 * Returns true (1) if a new message was added (or an old was reused) and 
 * false (0) if there was an error. */
LIBMDF_API int mdf_message_add2 (mdf_message_t message, const uint64_t insref, const uint16_t mref, const uint8_t delay);

/* remove the current message from the message chain, i.e mark it to be reused.
 * The current message pointer will change to the previous message in the chain
 * if there is any, so repeated calls will reset the whole chain.
 *
 * Returns true (1) if there are more messages in the chain or false (0)
 * if the message chain is empty. */
LIBMDF_API int mdf_message_del (mdf_message_t message);

/* Resets the message chain so it can be reused. The same as
 * calling mdf_message_del() until it returns false (0). */
LIBMDF_API void mdf_message_reset (mdf_message_t message);

/* Serialise the message chain to a base64 encoded string and store the result in *result
 * it's the responsibility of the caller to free *result once the value is used.
 *
 * Returns true (1) if the serialization was successfull or false (0) if not */
LIBMDF_API int mdf_message_serialize (const mdf_message_t message, char **result);

/* Deserialize the message chain from the base64 encoded string and store the result
 * in message.
 *
 * Returns true(1) if the deserialization was successfull of false (0) if not */
LIBMDF_API int mdf_message_deserialize (const mdf_message_t message, const char * const data);

/* Destroys the message chain and frees all memory */
LIBMDF_API void mdf_message_destroy (mdf_message_t message);

/* Adds a space separated list of unsigned integers to the current message */
LIBMDF_API int mdf_message_add_list (mdf_message_t message, const uint32_t tag, const char *value);

/* Adds a numeric field to the current message */
LIBMDF_API int mdf_message_add_numeric (mdf_message_t message, const uint32_t tag, const char *value);

/* Adds a scaled unsigned integer numeric field to the current message */
LIBMDF_API int mdf_message_add_uint (mdf_message_t message, const uint32_t tag, uint64_t value, int decimals);

/* Adds a scaled signed integer numeric field to the current message */
LIBMDF_API int mdf_message_add_int (mdf_message_t message, const uint32_t tag, int64_t value, int decimals);

/* Adds a string field to the current message */
LIBMDF_API int mdf_message_add_string (mdf_message_t message, const uint32_t tag, const char * value);

/* Adds a string with defined length field to the current message */
LIBMDF_API int mdf_message_add_string2 (mdf_message_t message, const uint32_t tag, const char * value, size_t len);

/* Adds a date field to the current message */
LIBMDF_API int mdf_message_add_date (mdf_message_t message, const uint32_t tag, const char *value);

/* Adds a broken down date field to the current message */
LIBMDF_API int mdf_message_add_date2 (mdf_message_t message, const uint32_t tag, const int year, const int mon, const int day);

/* Adds a time field to the current message */
LIBMDF_API int mdf_message_add_time (mdf_message_t message, const uint32_t tag, const char *value);

/* Adds a broken down time with milliseconds field to the current message */
LIBMDF_API int mdf_message_add_time2 (mdf_message_t message, const uint32_t tag, const int hour, const int min, const int sec, int msec);

/* Adds a broken down time with nanoseconds field to the current message */
LIBMDF_API int mdf_message_add_time3 (mdf_message_t message, const uint32_t tag, const int hour, const int min, const int sec, int nsec);

/* Return the total number of messages in the chain */
LIBMDF_API int mdf_message_get_num (mdf_message_t message);

/* Return the number of active messages in the chain */
LIBMDF_API int mdf_message_get_num_active (mdf_message_t message);

/* Return the number of fields in the current message */
LIBMDF_API int mdf_message_get_num_fields (mdf_message_t message);

/* Change insref from insref_src to insref_dst, also move the messages from message_src to message_dst if they point to different messages */
LIBMDF_API int mdf_message_move (const mdf_message_t message_src, const mdf_message_t message_dst, const uint64_t insref_src, const uint64_t insref_dst);

/* Send the message chain to the server */
LIBMDF_API int mdf_message_send (mdf_t handle, mdf_message_t message);

#ifdef __cplusplus
}
#endif

#endif
