#ifndef _discovery_h__
#define _discovery_h__

/** discovery **
 * 
 * Discover Tenkinet servers on local networks via UDP broadcast.
 * 
 * Each discovered server is described in a struct TenkinetServerInfo.
 * 
 * Usage:
 * 
 *   - Initialize the module with tenkinet_discovery_init().
 * 
 *   - Enter the discovery loop with tenkinet_discovery_loop().
 *     If a callback function was provided, it will be invoked for each discovered server.
 * 
 *   - It's also possible to access all discovered servers after the loop has finished, by
 *     using tenkinet_discovery_results().
 * 
 *   - The loop function can be invoked any number of times. Newly discovered servers will be
 *     appended to previous results.
 * 
 *   - To clear the list of results, use tenkinet_discovery_results_free().
 * 
 *   - When finished, terminate the module with tenkinet_discovery_exit().
 * 
 * Note that discovery is not guaranteed to works on all networks. In particular, most VPNs
 * will not allow UDP broadcast packets. Various firewalls are also likely to block them.
 * Generally, discovery will work on a home LAN, but your mileage may vary on a corporate
 * or university network.
 * 
 */

#include <stdint.h>

#include "socket.h"
#include "tenkinet.h"

#ifdef __cplusplus
extern "C" {
#endif


// Discovery Response Packet Structure
//
// LEN  TYPE      DESCRIPTION
//   2  uint16_t  magic number
//   1  uint8_t   server protocol number
//   1  uint8_t   server version (major)
//   1  uint8_t   server version (minor)
//   1  uint8_t   server version (revision)
//   1  uint8_t   server device count
//   7  char[]    server serial number
//   1  uint8_t   server name len (n)
//   n  char[]    server name

// Discovery Response offset constants

#define TENKINET_DISCOVERY_RESPONSE_HEADER_LEN 15

#define TENKINET_DISCOVERY_RESPONSE_OFFSET_MAGIC 0
#define TENKINET_DISCOVERY_RESPONSE_OFFSET_PROTOCOL 2
#define TENKINET_DISCOVERY_RESPONSE_OFFSET_VERSION 3
#define TENKINET_DISCOVERY_RESPONSE_OFFSET_DEVICE_COUNT 6
#define TENKINET_DISCOVERY_RESPONSE_OFFSET_SERIAL_NUMBER 7
#define TENKINET_DISCOVERY_RESPONSE_OFFSET_NAME_LEN 14
#define TENKINET_DISCOVERY_RESPONSE_OFFSET_NAME TENKINET_DISCOVERY_RESPONSE_HEADER_LEN

#define TENKINET_DISCOVERY_RESPONSE_LEN_MAX \
    (TENKINET_DISCOVERY_RESPONSE_HEADER_LEN + TENKINET_SERVER_NAME_LEN)

/**
 * Description of a Tenkinet server. Expressed as a node in a linked list.
 * `next` is a pointer to the next element in the list, or NULL if there are no more.
 */
typedef struct TenkinetServerInfo {

    struct TenkinetServerInfo *next;  // linked list: pointer to next element, NULL if last

    struct sockaddr_in address;
    char address_str[INET_ADDRSTRLEN];  // IPv4 numerical address + terminating null char
    char port_str[6];      // IPv4 port + terminating null char
    Version version;
    uint8_t protocol;
    uint8_t device_count;
    char serial_number[TENKINET_SERIAL_NUMBER_LEN + 1];
    uint8_t name_len;
    char name_str[TENKINET_SERVER_NAME_LEN + 1];

} TenkinetServerInfo;

/**
 * Callback function to be invoked with each newly discovered server.
 */
typedef void (*tenkinet_discovery_callback_fn)(TenkinetServerInfo *info);

/**
 * Initialize the discovery module.
 * 
 * Arguments:
 * 
 *   port: destination port number for UDP broadcast packets
 *         usually the default port, i.e. TENKINET_SERVER_PORT
 *
 *   cb:   callback function, or NULL
 * 
 * Return: non-zero if the module cannot be initialized
 * 
 */
int tenkinet_discovery_init(uint16_t port, tenkinet_discovery_callback_fn cb);

/**
 * Perform clean-up and terminate the discovery module.
 * 
 * This function frees the results list and its elements.
 * Take care to copy the results elsewhere beforehand, if persistence is required.
 * 
 * It is not necessary to call tenkinet_discovery_results_free() before this function.
 */
void tenkinet_discovery_exit();

/**
 * Send UDP broadcast packets at a given interval and listen for replies.
 * 
 * If we receive a reply from a server we haven't discovered before, it is added to the
 * results and the callback function is invoked. See tenkinet_discovery_results().
 * 
 * This function can be called again after it has completed. The results are incremental.
 * 
 * Arguments:
 * 
 *   interval_ms: how long to wait (milliseconds) between discovery packet sends
 *   count: how many packets to send in total
 * 
 * This function roughly takes (count * interval_ms) milliseconds to execute.
 * 
 * If count == 0, nothing is done.
 * If count == 1, only a single packet is sent, listening for the specified interval only
 * once. This can be useful to avoid blocking the main application logic for too long.
 * 
 * Take care not to spam your local networks with a low interval and a large count!
 * 
 */
void tenkinet_discovery_loop(uint32_t interval_ms, uint32_t count);

/**
 * Get a linked list of TenkinetServerInfo structs.
 * 
 * Return: pointer to the first element in the list, or NULL if the list is empty
 * 
 * Typical usage might look like:
 * 
 *   TenkinetServerInfo *info = tenkinet_discovery_results();
 *   while (info) {
 *       do_some_work(info);  // do some work
 *       info = info->next;   // move to the next element
 *   }
 * 
 * This list does not contain duplicates, though if a server is reachable on more than one
 * network, it may appear multiple times in the list with different addresses.
 * 
 * Calling tenkinet_discovery_loop() can append new elements to this list.
 * 
 * To clear this list, call tenkinet_discovery_results_free().
 */
TenkinetServerInfo *tenkinet_discovery_results();

/**
 * Clear the results list.
 * 
 * This function frees the results list and its elements.
 * Take care to copy the results elsewhere beforehand, if persistence is required.
 */
void tenkinet_discovery_results_free();


#ifdef __cplusplus
}
#endif

#endif
