#ifndef _tenkinet_h__
#define _tenkinet_h__

/** Tenkinet **
 * 
 * Client module of the Tenkinet protocol (TCP/IP)
 * 
 * A Tenkinet client connects to a Tenkinet server and subscribes to remote devices in order
 * to stream sensor data in real time.
 * 
 * This module performs asynchronous I/O using the `select_helper` module.
 * 
 * Usage summary:
 * 
 *   1. Initialize the module with tenkinet_init().
 * 
 *   2. Create a client with tenkinet_client_new().
 * 
 *   3. Initiate a connection with tenkinet_connect().
 * 
 *   4. Invoke select_helper() to wait for a response.
 * 
 *   5. After waking up, call tenkinet_process() to finalize the connection.
 *      The connection callback is invoked.
 * 
 *   6. You may now prepare requests to the server using the various tenkinet_req_*()
 *      functions. Call tenkinet_send() to actually send the prepared requests.
 *      You may also skip this step entirely if you don't need to send any requests.
 * 
 *   7. Invoke select_helper() to wait for server messages.
 * 
 *   8. After waking up, call tenkinet_process() to handle received messages.
 *      Appropriate callbacks are invoked, depending on the type of messages received.
 * 
 *   9. Repeat steps 6 to 8 as needed.
 * 
 *   10. When finished, terminate the module with tenkinet_exit().
 * 
 * At any point, you may disconnect a client by calling tenkinet_disconnect(). The client
 * remains valid; you can connect again later with tenkinet_connect().
 * 
 * If a client is no longer needed, delete it with tenkinet_client_delete().
 * 
 * Considerations:
 * 
 *   - Typically, the first request to send after connection is LIST. This will populate
 *     the list of remote Devices. Without this request, the client assumes the list is
 *     empty, i.e. that the server has no Devices.
 * 
 *   - Updates to Device status and data are only received for subscribed devices. There are
 *     no hotplug notifications for unsubscribed devices. Therefore, make sure to subscribe
 *     to the devices for which you require updates.
 * 
 *   - The DATA request will result in an immediate single reading of all subscribed devices,
 *     i.e. a single DATA message. Immediate means as soon as the server receives the request.
 * 
 *   - The POLL request will trigger periodic readings of all subscribed devices at a
 *     desired interval. Timestamps of readings are aligned with respect to an "origin"
 *     timestamp. This is like "snapping" to fixed points on the time axis. For example,
 *     assuming an origin timestamp T and a poll interval of 1 second, the server will send
 *     data updates at T + multiples of 1 second, i.e.:
 * 
 *       T
 *       T + 1s
 *       T + 2s
 *       ...
 * 
 *   - You may explicitly set the origin timestamp with the ALIGN request. If you do not send
 *     this request, the server will arbitrarily choose the origin time.
 * 
 *   - Your local system clock differs from the server clock. Even if both clocks are
 *     relatively accurate, they won't be exactly the same. All timestamps in this module
 *     are expressed in the server clock's frame of reference.
 */

#include <stdint.h>

#include "protocol.h"
#include "device.h"
#include "socket.h"
#include "select_helper.h"


#ifdef __cplusplus
extern "C" {
#endif


/**
 * Default Tenkinet port as a string.
 */
extern const char TENKINET_SERVER_PORT_STR[];

/**
 * Tenkinet client handle (opaque type)
 */
typedef void *TenkinetClient;

/**
 * Connection callback function.
 * 
 * Invoked by tenkinet_process() upon completing a connection attempt.
 * 
 * If connection was unsuccessful, it is possible to try connecting again.
 * 
 * Arguments:
 * 
 *   client: client handle
 *   status: TENKINET_SUCCESS or TENKINET_ERROR_DISCONNECTED
 *   user_data: arbitrary user data
 */
typedef void (*tenkinet_connection_callback_fn)
    (TenkinetClient client, int status, void *user_data);

/**
 * LIST message callback function.
 * 
 * Invoked by tenkinet_process() upon receiving a LIST message.
 * 
 * The list of remote Devices has been updated. It can be accessed via tenkinet_get_devices().
 * 
 * Arguments:
 * 
 *   client: client handle
 *   user_data: arbitrary user data
 */
typedef void (*tenkinet_list_callback_fn)
    (TenkinetClient client, void *user_data);

/**
 * DATA message callback function.
 * 
 * Invoked by tenkinet_process() upon receiving a DATA message.
 * 
 * The data of subscribed Devices has been updated.
 * 
 * This message is only sent for subscribed devices.
 * 
 * Arguments:
 * 
 *   client: client handle
 *   timestamp: server time (microseconds since Epoch)
 *   user_data: arbitrary user data
 */
typedef void (*tenkinet_data_callback_fn)
    (TenkinetClient client, int64_t timestamp, void *user_data);

/**
 * Device status callback function.
 * 
 * Invoked by tenkinet_process() upon receiving a DEVICE_CONNECT or DEVICE_DISCONNECT message.
 * 
 * This is a notification that a subscribed Device is now connected (alive) or disconnected.
 * This status is indicated in device->flags by the DEVICE_FLAG_ALIVE bit, i.e.
 * 
 *   device_flag_check(device, DEVICE_FLAG_ALIVE) is true if connected, false if disconnected
 * 
 * This message is only sent for subscribed devices.
 * 
 * Arguments:
 * 
 *   client: client handle
 *   timestamp: server time (microseconds since Epoch)
 *   device: subscribed Device
 *   user_data: arbitrary user data
 */
typedef void (*tenkinet_device_status_callback_fn)
    (TenkinetClient client, int64_t timestamp, Device *device, void *user_data);

/**
 * Struct containing callback functions to be invoked by tenkinet_process().
 * 
 * Any callback can be disabled by setting it to NULL.
 * 
 * Arbitrary user_data can be provided to be passed back to the callback functions.
 */
typedef struct TenkinetCallbacks {

    tenkinet_connection_callback_fn connection_cb;
    tenkinet_list_callback_fn list_cb;
    tenkinet_data_callback_fn data_cb;
    tenkinet_device_status_callback_fn device_status_cb;
    void *user_data;

} TenkinetCallbacks;

/**
 * Initialize the Tenkinet module.
 * 
 * This module performs asynchronous I/O using the `select_helper` module.
 * 
 * Return: 0 on success, non-zero on error
 */
int tenkinet_init(select_helper_data *seldata);

/**
 * Perform clean-up and terminate the Tenkinet module.
 * 
 * It is not necessary to disconnect or delete clients beforehand.
 */
void tenkinet_exit();

/**
 * Handle server messages.
 * 
 * Must be called periodically, ideally after select_helper() has returned.
 * 
 * This function may trigger callbacks.
 */
void tenkinet_process();

/**
 * Create a Tenkinet client for a single server identified by host and port strings.
 * 
 * Arguments:
 * 
 *   host: host name or IP address of the Tenkinet server
 *   port: port of the Tenkinet server, expressed as a string
 *   cb: pointer to TenkinetCallbacks struct, or NULL to disable all callbacks
 * 
 * Return: client handle, or NULL if the host could not be resolved
 * 
 * The TenkinetCallbacks struct is copied internally, so you are free to modify your struct
 * and re-use it for other calls to this function, as needed.
 */
TenkinetClient tenkinet_client_new(const char *host, const char *port, TenkinetCallbacks *cb);

/**
 * Delete a Tenkinet client from the module.
 * 
 * After calling this function, the handle becomes invalid, as well as all of this client's
 * remote Devices.
 */
void tenkinet_client_delete(TenkinetClient client);

/**
 * Begin a connection attempt. Has no effect if the client is already connected.
 * 
 * A later call to tenkinet_process() will finalize the connection, triggering a callback
 * if one is configured in the client.
 * 
 * Return: 0 on success, non-zero on error or if the client is already connected.
 */
int tenkinet_connect(TenkinetClient client);

/**
 * Disconnect a client. Has no effect if the client is disconnected.
 * 
 * The handle remains valid and can be used to connect again later.
 */
void tenkinet_disconnect(TenkinetClient client);

/**
 * Prepare a LIST request. Has no effect if the client is disconnected.
 * 
 * Use this request to refresh the list of remote Devices.
 * 
 * Remember to call tenkinet_send() to actually send all prepared requests.
 */
void tenkinet_req_list(TenkinetClient client);

/**
 * Prepare a SUBSCRIBE request. Has no effect if the client is disconnected.
 * 
 * Use this request to subscribe to a single Device.
 * 
 * Remember to call tenkinet_send() to actually send all prepared requests.
 */
void tenkinet_req_subscribe(TenkinetClient client, Device *device);

/**
 * Prepare an UNSUBSCRIBE request. Has no effect if the client is disconnected.
 * 
 * Use this request to unsubscribe from a single Device.
 * 
 * Remember to call tenkinet_send() to actually send all prepared requests.
 */
void tenkinet_req_unsubscribe(TenkinetClient client, Device *device);

/**
 * Prepare SUBSCRIBE requests for all remote Devices. Has no effect if the client is
 * disconnected, or if the list of remote Devices is empty.
 * 
 * Use this function to subscribe to all Devices in the list. It only makes sense to
 * call this function after the list of Devices was populated.
 * 
 * Remember to call tenkinet_send() to actually send all prepared requests.
 */
void tenkinet_req_subscribe_all(TenkinetClient client);

/**
 * Prepare UNSUBSCRIBE requests for all remote Devices. Has no effect if the client is
 * disconnected, or if the list of remote Devices is empty.
 * 
 * Use this function to unsubscribe from all Devices in the list. It only makes sense to
 * call this function after the list of Devices was populated.
 * 
 * Remember to call tenkinet_send() to actually send all prepared requests.
 */
void tenkinet_req_unsubscribe_all(TenkinetClient client);

/**
 * Prepare a DATA request. Has no effect if the client is disconnected.
 * 
 * Use this request to trigger an immediate single reading of all subscribed Devices.
 * Immediate means as soon as the server receives the request.
 * 
 * Remember to call tenkinet_send() to actually send all prepared requests.
 */
void tenkinet_req_data(TenkinetClient client);

/**
 * Prepare a POLL request. Has no effect if the client is disconnected.
 * 
 * Use this request to trigger periodic readings of all subscribed devices at a desired
 * interval expressed in microseconds. Timestamps of readings are aligned with respect
 * to the "origin" timestamp.
 * 
 * This request overwrites any previous POLL requests, i.e. it can be used to change the
 * interval of an ongoing poll, or stop it completely if interval == 0.
 * 
 * Remember to call tenkinet_send() to actually send all prepared requests.
 */
void tenkinet_req_poll(TenkinetClient client, uint32_t interval);

/**
 * Prepare an ALIGN request. Has no effect if the client is disconnected.
 * 
 * Use this request to set the origin timestamp to which periodic POLL readings are aligned.
 * 
 * This request overwrites any previous ALIGN requests, i.e. it can be used to change the
 * origin timestamp.
 * 
 * Remember to call tenkinet_send() to actually send all prepared requests.
 */
void tenkinet_req_align(TenkinetClient client, int64_t timestamp);

/**
 * Send all requests previously prepared with the tenkinet_req_*() functions.
 * 
 * A later call to tenkinet_process() will handle any resulting messages from the server,
 * triggering any applicable callbacks.
 */
void tenkinet_send(TenkinetClient client);

/**
 * Return whether a client is connected to the server.
 */
int tenkinet_is_connected(TenkinetClient client);

/**
 * Return the IP address of the server corresponding to a client.
 */
const char *tenkinet_get_server_address(TenkinetClient client);

/**
 * Return the port number of the server corresponding to a client.
 */
uint16_t tenkinet_get_server_port(TenkinetClient client);

/**
 * Return the serial number of the server corresponding to a client.
 * This information becomes available after a successful connection.
 */
const char *tenkinet_get_server_serial_number(TenkinetClient client);

/**
 * Return the name of the server.
 * This information becomes available after a successful connection.
 */
const char *tenkinet_get_server_name(TenkinetClient client);

/**
 * Return the version of the server.
 * This information becomes available after a successful connection.
 */
Version tenkinet_get_server_version(TenkinetClient client);


/**
 * Return the protocol used by the server.
 * This information becomes available after a successful connection.
 */
uint8_t tenkinet_get_server_protocol(TenkinetClient client);

/**
 * Return the server error.
 */
TenkinetError tenkinet_get_server_error(TenkinetClient client);


/**
 * Append all of a client's remote Devices to the provided List.
 * 
 * Before a successful LIST request, the client is assumed to have zero remote Devices.
 * Therefore it only makes sense to call this function after a LIST message has been received.
 * 
 * The client retains ownership of all appended Devices. They are NOT copies. You must not
 * modify them or free them. You are responsible for copying Device data elsewhere as needed.
 * 
 * Deleting a client also deletes all of its Devices, and any pointers to them become invalid.
 */
size_t tenkinet_get_devices(TenkinetClient client, List *devices);

/**
 * Find a remote Device by serial number.
 */
Device *tenkinet_find_device(TenkinetClient client, const char *serial_number);


#ifdef __cplusplus
}
#endif

#endif
