#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>

#ifdef _WIN32
#include <basetsd.h>
typedef SSIZE_T ssize_t;
#else
#include <unistd.h>
#endif

#include "tenkinet.h"
#include "socket.h"
#include "portable_endian.h"
#include "chip.h"
#include "buffer.h"
#include "list.h"
#include "stream.h"
#include "str_helper.h"

////////////////////////////////////////////////////////////////
// Types
////////////////////////////////////////////////////////////////

typedef enum State {

    STATE_DISCONNECTED = 0,
    STATE_CONNECTING,
    STATE_HELLO,
    STATE_READY,
    STATE_RECEIVING_LIST,
    STATE_PINGING,

} State;

typedef struct Context {

    struct sockaddr_in server_address;
    socklen_t server_address_len;
    char server_address_str[INET_ADDRSTRLEN];
    uint16_t server_port;

    char server_serial_number[TENKINET_SERIAL_NUMBER_LEN + 1];
    char server_name[TENKINET_SERVER_NAME_LEN + 1];
    Version server_version;
    uint8_t protocol;

    State state;
    TenkinetError error;
    socket_t sock;
    int64_t timestamp;

    Buffer read_buffer;
    Buffer write_buffer;

    List cached_devices;
    List sub_devices;

    size_t alive_device_count;
    size_t alive_channel_count;

    size_t list_msg_device_count;
    size_t list_msg_device_idx;

    TenkinetCallbacks callbacks;

} Context;

#define CONTEXT(client) \
    Context *context = ((Context*)(client));


////////////////////////////////////////////////////////////////
// Constants
////////////////////////////////////////////////////////////////

const char TENKINET_SERVER_PORT_STR[] = STR(TENKINET_SERVER_PORT);

static const unsigned char HELLO[] = {
    REQUEST_HELLO,
    (unsigned char)(TENKINET_MAGIC_NUMBER >> 8),
    (unsigned char)(TENKINET_MAGIC_NUMBER & 0xff),
    TENKINET_PROTOCOL,
    TENKINET_PROTOCOL
};

#define CONNECTION_TIMEOUT_S   7
#define CONNECTION_TIMEOUT_US  (CONNECTION_TIMEOUT_S * TIMESTAMP_ONE_SECOND)

#define IDLE_TIMEOUT_S  30
#define IDLE_TIMEOUT_US (IDLE_TIMEOUT_S * TIMESTAMP_ONE_SECOND)


////////////////////////////////////////////////////////////////
// Static variables
////////////////////////////////////////////////////////////////

static List contexts;
static select_helper_data *seldata = NULL;


////////////////////////////////////////////////////////////////
// Static function declarations
////////////////////////////////////////////////////////////////

static void context_close(Context *context);
static void context_process_connecting(Context *context);
static void context_process_connected(Context *context);
static ssize_t context_send(Context *context);
static ssize_t context_stream_data_handler(char *data, size_t len, void *user_data);
static int handle_connection(Context *context);
static int handle_device_header(Context *context, char *data, size_t len, Device **result);


////////////////////////////////////////////////////////////////
// Message Handlers (Tenkinet Protocol)
////////////////////////////////////////////////////////////////

// Arguments expected by each handler.
#define MESSAGE_HANDLER_ARGS \
    Context *context, char *data, size_t len

// Macro to conveniently declare several handlers.
#define MESSAGE_HANDLER(name) static int handle_ ## name(MESSAGE_HANDLER_ARGS)

/**
 * Handler function type definition.
 *
 * The return value is an integer corresponding to the number of bytes processed.
 * Zero indicates that there wasn't enough data to process (incomplete data).
 * A negative value indicates an error (illegal data) which will result in immediate termination
 * of the context connection.
 */
typedef int (*message_handler)(MESSAGE_HANDLER_ARGS);

MESSAGE_HANDLER(hello);
MESSAGE_HANDLER(list);
MESSAGE_HANDLER(subscribe);
MESSAGE_HANDLER(unsubscribe);
MESSAGE_HANDLER(data);
MESSAGE_HANDLER(device_connect);
MESSAGE_HANDLER(device_disconnect);
MESSAGE_HANDLER(pong);

static message_handler MESSAGE_HANDLERS[] = {
    [MESSAGE_HELLO] = handle_hello,
    [MESSAGE_LIST] = handle_list,
    [MESSAGE_SUBSCRIBE] = handle_subscribe,
    [MESSAGE_UNSUBSCRIBE] = handle_unsubscribe,
    [MESSAGE_DATA] = handle_data,
    [MESSAGE_DEVICE_CONNECT] = handle_device_connect,
    [MESSAGE_DEVICE_DISCONNECT] = handle_device_disconnect,
    [MESSAGE_PONG] = handle_pong,
};

#define MESSAGE_HANDLERS_COUNT (sizeof(MESSAGE_HANDLERS) / sizeof(message_handler))


////////////////////////////////////////////////////////////////
// Function definitions (public)
////////////////////////////////////////////////////////////////

int tenkinet_init(select_helper_data *_seldata) {

    #ifdef _WIN32
        WORD winsock_version = MAKEWORD(2, 2);
        WSADATA wsa_data;
        int winsock_error = WSAStartup(winsock_version, &wsa_data);
        if (winsock_error) {
            return winsock_error;
        }
    #endif

    seldata = _seldata;

    return 0;

}

void tenkinet_exit() {

    if (seldata == NULL) {
        return;
    }

    LIST_FOR(&contexts) {
        Context *context = LIST_CUR(Context);
        context_close(context);
    }

    list_clear(&contexts);

    #ifdef _WIN32
        WSACleanup();
    #endif

}

void tenkinet_process() {

    LIST_FOR(&contexts) {

        Context *context = LIST_CUR(Context);

        switch (context->state) {

            case STATE_DISCONNECTED:
                break;

            case STATE_CONNECTING:
                context_process_connecting(context);
                break;

            default:
                context_process_connected(context);
                break;

        }

    }

}

TenkinetClient tenkinet_client_new(const char *host, const char *port, TenkinetCallbacks *cb) {

    // Resolve host name

    struct addrinfo hints;
    struct addrinfo *info;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;

    // TODO error codes

    if (getaddrinfo(host, port, &hints, &info) || info == NULL) {
        return NULL;
    }

    Context *context = malloc(sizeof(Context));

    struct sockaddr_in *addr = (struct sockaddr_in *)(info->ai_addr);
    socklen_t addrlen = info->ai_addrlen;

    memcpy(&context->server_address, addr, addrlen);
    context->server_address_len = addrlen;

#ifdef _WIN32
    // !!! WARNING !!!
    // For simplicity, we're using inet_ntoa(), but it only works with IPv4 addresses.
    // Before switching to to WSAAddressToString(), which is more modern and flexible,
    // be aware that it copies BOTH the address AND the port number to the result string!
    // So instead of "127.0.0.1" you would get "127.0.0.1:10395". This behaviour differs
    // from the *nix implementations of similar functions. Keep that in mind !
    char *s = inet_ntoa(addr->sin_addr);
    strncpy(context->server_address_str, s, sizeof(context->server_address_str));
    context->server_address_str[sizeof(context->server_address_str) - 1] = '\0';
#else
    inet_ntop(
        AF_INET,
        &(addr->sin_addr),
        context->server_address_str,
        sizeof(context->server_address_str)
    );
#endif

    context->server_port = be16toh(addr->sin_port);

    freeaddrinfo(info);

    context->server_serial_number[0] = 0;
    context->server_name[0] = 0;
    memset(&context->server_version, 0, sizeof(Version));
    context->protocol = TENKINET_PROTOCOL_NONE;

    context->state = STATE_DISCONNECTED;
    context->error = TENKINET_SUCCESS;
    context->sock = -1;

    buffer_init(&context->read_buffer);
    buffer_grow(&context->read_buffer, 1024);
    buffer_init(&context->write_buffer);
    buffer_grow(&context->write_buffer, 256);

    list_init(&context->cached_devices);
    list_init(&context->sub_devices);

    context->alive_device_count = 0;
    context->alive_channel_count = 0;

    context->list_msg_device_count = 0;
    context->list_msg_device_idx = 0;

    if (cb) {
        context->callbacks = *cb;
    }
    else {
        memset(&context->callbacks, 0, sizeof(TenkinetCallbacks));
    }

    list_add(&contexts, context);

    return (TenkinetClient)context;

}

void tenkinet_client_delete(TenkinetClient client) {

    CONTEXT(client);

    context_close(context);
    list_remove(&contexts, context);

}

int tenkinet_connect(TenkinetClient client) {

    CONTEXT(client);

    if (context->state != STATE_DISCONNECTED) {
        context->error = TENKINET_ERROR_INTERNAL;
        return -1;
    }

    // Open TCP socket

    socket_t sock = socket_open_nonblocking(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        context->error = TENKINET_ERROR_INTERNAL;
        return -1;
    }

    // Set TCP options
    //
    // Nagle's algorithm:
    //     OFF (i.e. TCP_NODELAY is ON)
    //
    // Keepalive:
    //     OFF since it is unreliable on Windows.
    //     We have our own keepalive mechanism instead.
    //
    // Connection timeout:
    //     Windows: Set via TCP_MAXRT
    //     Apple:   Set via TCP_CONNECTIONTIMEOUT
    //     Linux:   Set via TCP_SYNCNT
    //     * We also have our own connection timeout mechanism in case this one fails.

    if (socket_set_tcp_options(sock, 1, 0, 0, 0, 0)) {
        socket_close(sock);
        context->error = TENKINET_ERROR_INTERNAL;
        return -1;
    }

    #if defined _WIN32
        #ifndef TCP_MAXRT
            #define TCP_MAXRT 5
        #endif
        DWORD maxrt = CONNECTION_TIMEOUT_S;
        if (setsockopt(sock, IPPROTO_TCP, TCP_MAXRT, (const char *)&maxrt, sizeof(DWORD)) < 0) {
            context->error = TENKINET_ERROR_INTERNAL;
            socket_close(sock);
            return -1;
        }
    #elif defined __APPLE__
        int timeout = CONNECTION_TIMEOUT_S;
        if (setsockopt(sock, IPPROTO_TCP, TCP_CONNECTIONTIMEOUT, (const char *)&timeout, sizeof(int)) < 0) {
            context->error = TENKINET_ERROR_INTERNAL;
            socket_close(sock);
            return -1;
        }
    #else
        #if CONNECTION_TIMEOUT_S != 7
            // On Linux, connection timeout is defined in terms of number of retries, not duration.
            // This sanity check exists to prevent modification of the connection timeout constant
            // without a corresponding modification to the TCP_SYNCNT value (number of retries).
            #error Mismatch between defined connection timeout and TCP_SYNCNT value
        #endif
        int syncnt = 2;  // 2 retries:  1s + 2s + 4s = 7s  (next step would be +8s = 15s)
        if (setsockopt(sock, IPPROTO_TCP, TCP_SYNCNT, (const char *)&syncnt, sizeof(int)) < 0) {
            context->error = TENKINET_ERROR_INTERNAL;
            socket_close(sock);
            return -1;
        }
    #endif

    // Initiate non-blocking connection

    int error = socket_connect_nonblocking(
        sock,
        (struct sockaddr *)(&context->server_address),
        context->server_address_len
    );

    if (error) {
        socket_close(sock);
        context->error = TENKINET_ERROR_INTERNAL;
        return -1;
    }

    FD_SET(sock, &seldata->writefds);
    #ifdef _WIN32
    FD_SET(sock, &seldata->exceptfds);
    #endif

    select_helper_nfds_update(sock, seldata);

    context->state = STATE_CONNECTING;
    context->sock = sock;
    context->timestamp = timestamp_now();

    return 0;

}

int tenkinet_is_connected(TenkinetClient client) {

    CONTEXT(client);
    return (context->state > STATE_HELLO);

}

void tenkinet_disconnect(TenkinetClient client) {

    CONTEXT(client);

    if (context->state == STATE_DISCONNECTED) {
        return;
    }

    FD_CLR(context->sock, &seldata->readfds);
    FD_CLR(context->sock, &seldata->writefds);
    #ifdef _WIN32
    FD_CLR(context->sock, &seldata->exceptfds);
    #endif

    socket_close(context->sock);

    context->sock = -1;
    context->state = STATE_DISCONNECTED;

    buffer_reset(&context->read_buffer);
    buffer_reset(&context->write_buffer);

    LIST_FOR(&context->cached_devices) {
        Device *device = LIST_CUR(Device);
        device_invalidate(device);
    }

    if (context->callbacks.device_status_cb) {
        int64_t now = timestamp_now();
        LIST_FOR(&context->sub_devices) {
            Device *device = LIST_CUR(Device);
            if (device_flag_check(device, DEVICE_FLAG_ALIVE)) {
                device_flag_clear(device, DEVICE_FLAG_ALIVE);
                context->callbacks.device_status_cb(
                    client,
                    now,
                    device,
                    context->callbacks.user_data
                );
            }
        }
    }

    context->alive_device_count = 0;
    context->alive_channel_count = 0;

    context->list_msg_device_count = 0;
    context->list_msg_device_idx = 0;

    if (context->callbacks.connection_cb) {
        context->callbacks.connection_cb(
            client,
            TENKINET_ERROR_DISCONNECTED,  // TODO more specific error
            context->callbacks.user_data
        );
    }

}

void tenkinet_req_list(TenkinetClient client) {

    if (!tenkinet_is_connected(client)) {
        return;
    }

    CONTEXT(client);
    Buffer *buf = &context->write_buffer;
    buffer_reserve(buf, 1);

    buffer_put_char(buf, REQUEST_LIST);

}

void tenkinet_req_subscribe(TenkinetClient client, Device *device) {

    if (!tenkinet_is_connected(client)) {
        return;
    }

    CONTEXT(client);

    Buffer *buf = &context->write_buffer;
    buffer_reserve(buf, 1 + DEVICE_SERIAL_NUMBER_LEN);
    buffer_put_char(buf, REQUEST_SUBSCRIBE);
    buffer_put(buf, device->serial_number, DEVICE_SERIAL_NUMBER_LEN);

}

void tenkinet_req_unsubscribe(TenkinetClient client, Device *device) {

    if (!tenkinet_is_connected(client)) {
        return;
    }

    CONTEXT(client);

    Buffer *buf = &context->write_buffer;
    buffer_reserve(buf, 1 + DEVICE_SERIAL_NUMBER_LEN);
    buffer_put_char(buf, REQUEST_UNSUBSCRIBE);
    buffer_put(buf, device->serial_number, DEVICE_SERIAL_NUMBER_LEN);

}

void tenkinet_req_subscribe_all(TenkinetClient client) {

    if (!tenkinet_is_connected(client)) {
        return;
    }

    CONTEXT(client);

    Buffer *buf = &context->write_buffer;
    List *devices = &context->cached_devices;

    buffer_reserve(buf, devices->size * (1 + DEVICE_SERIAL_NUMBER_LEN));

    LIST_FOR(devices) {
        Device *device = LIST_CUR(Device);
        buffer_put_char(buf, REQUEST_SUBSCRIBE);
        buffer_put(buf, device->serial_number, DEVICE_SERIAL_NUMBER_LEN);
    }

}

void tenkinet_req_unsubscribe_all(TenkinetClient client) {

    if (!tenkinet_is_connected(client)) {
        return;
    }

    CONTEXT(client);

    Buffer *buf = &context->write_buffer;
    List *devices = &context->cached_devices;

    buffer_reserve(buf, devices->size * (1 + DEVICE_SERIAL_NUMBER_LEN));

    LIST_FOR(devices) {
        Device *device = LIST_CUR(Device);
        buffer_put_char(buf, REQUEST_UNSUBSCRIBE);
        buffer_put(buf, device->serial_number, DEVICE_SERIAL_NUMBER_LEN);
    }

}

void tenkinet_req_data(TenkinetClient client) {

    if (!tenkinet_is_connected(client)) {
        return;
    }

    CONTEXT(client);
    Buffer *buf = &context->write_buffer;
    buffer_reserve(buf, 1);

    buffer_put_char(buf, REQUEST_DATA);

}

void tenkinet_req_poll(TenkinetClient client, uint32_t interval) {

    if (!tenkinet_is_connected(client)) {
        return;
    }

    CONTEXT(client);
    Buffer *buf = &context->write_buffer;
    buffer_reserve(buf, 1 + sizeof(uint32_t));

    buffer_put_char(buf, REQUEST_POLL);
    interval = htobe32(interval);
    buffer_put(buf, &interval, sizeof(interval));
}

void tenkinet_req_align(TenkinetClient client, int64_t timestamp) {

    if (!tenkinet_is_connected(client)) {
        return;
    }

    CONTEXT(client);
    Buffer *buf = &context->write_buffer;
    buffer_reserve(buf, 1 + sizeof(uint64_t));

    buffer_put_char(buf, REQUEST_ALIGN);
    uint64_t t = htobe64((uint64_t)timestamp);
    buffer_put(buf, &t, sizeof(uint64_t));

}

void tenkinet_send(TenkinetClient client) {

    if (!tenkinet_is_connected(client)) {
        return;
    }

    CONTEXT(client);
    context_send(context);

}

const char *tenkinet_get_server_address(TenkinetClient client) {

    CONTEXT(client);

    return context->server_address_str;

}

uint16_t tenkinet_get_server_port(TenkinetClient client) {

    CONTEXT(client);

    return context->server_port;

}

const char *tenkinet_get_server_serial_number(TenkinetClient client) {

    CONTEXT(client);

    return context->server_serial_number;

}

const char *tenkinet_get_server_name(TenkinetClient client) {

    CONTEXT(client);

    return context->server_name;

}

Version tenkinet_get_server_version(TenkinetClient client) {

    CONTEXT(client);

    return context->server_version;

}

uint8_t tenkinet_get_server_protocol(TenkinetClient client) {

    CONTEXT(client);

    return context->protocol;

}

TenkinetError tenkinet_get_server_error(TenkinetClient client) {

    CONTEXT(client);

    return context->error;

}

size_t tenkinet_get_devices(TenkinetClient client, List *dst) {

    CONTEXT(client);

    List *src = &context->cached_devices;
    const size_t n = src->size;
    list_grow(dst, n + dst->size);

    LIST_FOR(src) {
        Device *device = LIST_CUR(Device);
        list_add(dst, device);
    }

    return n;

}

Device *tenkinet_find_device(TenkinetClient client, const char *serial_number) {

    CONTEXT(client);

    return device_find(&context->cached_devices, serial_number, 0);

}


////////////////////////////////////////////////////////////////
// Function definitions (static)
////////////////////////////////////////////////////////////////

static void context_close(Context *context) {

    // Disable callbacks before disconnecting
    memset(&context->callbacks, 0, sizeof(TenkinetCallbacks));
    tenkinet_disconnect((TenkinetClient)context);

    LIST_FOR(&context->cached_devices) {
        Device *device = LIST_CUR(Device);
        device_delete(device);
    }

    list_clear(&context->cached_devices);
    list_clear(&context->sub_devices);

    buffer_clear(&context->read_buffer);
    buffer_clear(&context->write_buffer);

    free(context);

}

static void context_process_connecting(Context *context) {

    socket_t sock = context->sock;

    if (FD_ISSET(sock, &seldata->writefds_ready)) {
        if (handle_connection(context)) {
            tenkinet_disconnect((TenkinetClient)context);
        }
        else {
            context->timestamp = seldata->timestamp;
        }
        return;
    }

    #ifdef _WIN32
        // On Windows, a non-blocking connection failure is expressed via exceptfds.
        if (FD_ISSET(sock, &seldata->exceptfds_ready)) {
            tenkinet_disconnect((TenkinetClient)context);
            return;
        }
    #endif

    // No incoming socket activity. How long have we been idle?
    int64_t idle = seldata->timestamp - context->timestamp;

    // If it's been too long, disconnect.
    if (idle >= CONNECTION_TIMEOUT_US) {
        tenkinet_disconnect((TenkinetClient)context);
    }

}

static void context_process_connected(Context *context) {

    socket_t sock = context->sock;

    if (FD_ISSET(sock, &seldata->readfds_ready)) {
        ssize_t n = stream_receive(sock, &context->read_buffer, context_stream_data_handler, context);
        if (n > 0) {
            context->timestamp = seldata->timestamp;
        }
        else if (n < 0) {
            tenkinet_disconnect((TenkinetClient)context);
            return;
        }
    }

    // No incoming socket activity. How long have we been idle?
    int64_t idle = seldata->timestamp - context->timestamp;

    // If it's been too long, disconnect.
    if (idle >= IDLE_TIMEOUT_US) {
        tenkinet_disconnect((TenkinetClient)context);
        return;
    }

    // After half the idle timeout, send a ping.
    if (idle >= (IDLE_TIMEOUT_US / 2) && context->state == STATE_READY) {
        context->state = STATE_PINGING;
        Buffer *buf = &context->write_buffer;
        buffer_reserve(buf, 1);
        buffer_put_char(buf, REQUEST_PING);
    }

    // Send requests
    if (context_send(context) < 0) {
        tenkinet_disconnect((TenkinetClient)context);
    }

}

static ssize_t context_send(Context *context) {

    Buffer *buf = &context->write_buffer;
    size_t size = buffer_size(buf);

    if (size == 0) {
        return 0;
    }

    socket_t sock = context->sock;
    ssize_t n = stream_send(sock, buf);

    // Add socket to writefds on partial send

    if (n == size) {
        FD_CLR(sock, &seldata->writefds);
    }
    else {
        FD_SET(sock, &seldata->writefds);
    }

    return n;

}

static ssize_t context_stream_data_handler(char *data, size_t len, void *user_data) {

    Context *context = (Context*)user_data;
    ssize_t n;

    // If a device list transmission is in progress, continue it
    if (context->state == STATE_RECEIVING_LIST) {
        n = handle_list(context, data, len);
    }
    else {
        uint8_t message = data[0];
        if (message >= MESSAGE_HANDLERS_COUNT) {
            context->error = TENKINET_ERROR_INTERNAL;
            return -1;  // invalid message code
        }
        n = MESSAGE_HANDLERS[message](context, data, len);
    }

    return n;

}

static int handle_connection(Context *context) {

    int error;
    socklen_t errorlen = sizeof(error);

    // No error means connection was successful
    int res = getsockopt(context->sock, SOL_SOCKET, SO_ERROR, (char *)(&error), &errorlen);
    if (res || error) {
        context->error = TENKINET_ERROR_INTERNAL;
        return -1;
    }

    // Ready for reading
    FD_SET(context->sock, &seldata->readfds);

    #ifdef _WIN32
    // We no longer need to monitor exceptions now that we're connected
    FD_CLR(context->sock, &seldata->exceptfds);
    #endif

    context->state = STATE_HELLO;

    Buffer *buf = &context->write_buffer;
    buffer_reserve(buf, sizeof(HELLO));
    buffer_put(buf, HELLO, sizeof(HELLO));

    context_send(context);

    return 0;

}

static int handle_device_header(Context *context, char *data, size_t len, Device **result) {

    // LEN                       TYPE           DESCRIPTION
    // DEVICE_SERIAL_NUMBER_LEN  char[]         device serial number
    //   1                       uint8_t        device version, major
    //   1                       uint8_t        device version, minor
    //   1                       uint8_t        device port
    //   4                       uint32_t       device flags
    //   1                       uint8_t        device channel count
    //   1                       uint8_t        device name length (n)
    //   n                       char[]         device name, not null-terminated
    //   ---- one block per channel --------------------------------------------
    //   2                       uint16_t       channel chip ID
    //   ---- if DEVICE_FLAG_CALPOINTS is set ----------------------------------
    //   CAL_POINTS_COUNT * 16   Point[CAL_POINTS_COUNT]

    const size_t OFFSET_SERIAL_NUMBER = 0;
    const size_t OFFSET_VERSION = OFFSET_SERIAL_NUMBER + DEVICE_SERIAL_NUMBER_LEN;
    const size_t OFFSET_PORT = OFFSET_VERSION + 2;
    const size_t OFFSET_FLAGS = OFFSET_PORT + 1;
    const size_t OFFSET_CHANNEL_COUNT = OFFSET_FLAGS + 4;
    const size_t OFFSET_NAME_LEN = OFFSET_CHANNEL_COUNT + 1;
    const size_t OFFSET_NAME = OFFSET_NAME_LEN + 1;

    char *p = data;
    char *end = data + len;
    size_t remaining = end - p;

    if (remaining < OFFSET_NAME) { return 0; }

    uint32_t flags = p[OFFSET_FLAGS];
    memcpy(&flags, p + OFFSET_FLAGS, 4);
    flags = be32toh(flags);

    size_t channel_block_len = 2;
    if (flags & DEVICE_FLAG_CALPOINTS) {
        channel_block_len += (CAL_POINTS_COUNT * 16);
    }

    size_t name_len = p[OFFSET_NAME_LEN];
    if (name_len > DEVICE_PRODUCT_NAME_LEN) { return -1; }

    size_t channel_count = p[OFFSET_CHANNEL_COUNT];
    size_t device_block_len = OFFSET_NAME + name_len + (channel_count * channel_block_len);

    if (remaining < device_block_len) { return 0; }

    Device *device = device_find(&context->cached_devices, p + OFFSET_SERIAL_NUMBER, 0);

    if (device) {
        if (device->channels.size != channel_count) { return -1; }
        flags |= (device->flags & DEVICE_FLAG_TENKINET_SUBSCRIBED);  // restore subscribed state
    }
    else {
        device = device_new();
        device_add_channels(device, channel_count);
        list_add(&context->cached_devices, device);
    }

    *result = device;

    device->flags = (flags & ~DEVICE_FLAG_LOCAL) | DEVICE_FLAG_TENKINET;

    memcpy(device->serial_number, p + OFFSET_SERIAL_NUMBER, DEVICE_SERIAL_NUMBER_LEN);
    device->serial_number[DEVICE_SERIAL_NUMBER_LEN] = '\0';

    memcpy(device->product_name, p + OFFSET_NAME, name_len);
    device->product_name[name_len] = '\0';

    device->version.major = p[OFFSET_VERSION];
    device->version.minor = p[OFFSET_VERSION + 1];
    device->port = p[OFFSET_PORT];

    p += (OFFSET_NAME + name_len);

    LIST_FOR(&device->channels) {

        Channel *channel = LIST_CUR(Channel);

        memcpy(&channel->chip_id, p, 2);
        channel->chip_id = be16toh(channel->chip_id);
        p += 2;

        if (!(flags & DEVICE_FLAG_CALPOINTS)) {
            channel->calpoints_count = 0;
            continue;
        }

        channel->calpoints_count = CAL_POINTS_COUNT;

        for (Point *point = channel->calpoints; point < channel->calpoints + CAL_POINTS_COUNT; point++) {
            memcpy(&point->x_bits, p, sizeof(uint64_t));
            point->x_bits = be64toh(point->x_bits);
            p += sizeof(uint64_t);
            memcpy(&point->y_bits, p, sizeof(uint64_t));
            point->y_bits = be64toh(point->y_bits);
            p += sizeof(uint64_t);
        }

    }

    return p - data;

}

MESSAGE_HANDLER(hello) {

    // LEN  TYPE      DESCRIPTION
    //   1  uint8_t   MESSAGE_HELLO
    //   1  uint8_t   protocol number
    //   1  uint8_t   server version major
    //   1  uint8_t   server version minor
    //   1  uint8_t   server version revision
    //   7  char[]    server serial number
    //   1  uint8_t   server name length (n)
    //   n  char[]    server name

    if (context->state != STATE_HELLO) {
        context->error = TENKINET_ERROR_INTERNAL;
        return -1;
    }

    const int HEADER_LEN = 13;
    if (len < HEADER_LEN) {
        return 0;
    }

    uint8_t server_name_len = data[12];
    if (server_name_len > TENKINET_SERVER_NAME_LEN) {
        return -1;
    }

    const int N = HEADER_LEN + server_name_len;
    if (len < N) {
        return 0;
    }

    context->protocol = data[1];
    context->server_version.major = data[2];
    context->server_version.minor = data[3];
    context->server_version.revision = data[4];
    strncpy(context->server_serial_number, data + 5, TENKINET_SERIAL_NUMBER_LEN);
    context->server_serial_number[TENKINET_SERIAL_NUMBER_LEN] = '\0';
    strncpy(context->server_name, data + HEADER_LEN, server_name_len);
    context->server_name[server_name_len] = '\0';

    if (context->protocol != 1) {
        context->error = TENKINET_ERROR_UNSUPPORTED;
        return -1;
    }

    context->state = STATE_READY;

    LIST_FOR(&context->sub_devices) {
        Device *device = LIST_CUR(Device);
        tenkinet_req_subscribe((TenkinetClient)context, device);
    }

    if (context->callbacks.connection_cb) {
        context->callbacks.connection_cb(
            (TenkinetClient)context,
            TENKINET_SUCCESS,
            context->callbacks.user_data
        );
    }

    return N;

}

MESSAGE_HANDLER(list) {

    // LEN                                 TYPE           DESCRIPTION
    //   1                                 uint8_t        MESSAGE_LIST
    //   1                                 uint8_t        number of devices
    //   ---- one device header block per device --------------------------

    char *p = data;
    char *end = data + len;

    size_t device_count;
    size_t device_idx;

    if (context->state == STATE_RECEIVING_LIST) {
        device_count = context->list_msg_device_count;
        device_idx = context->list_msg_device_idx;
    }
    else {
        if (len < 2) { return 0; }
        p++;  // skip message code
        device_count = *p++;
        device_idx = 0;
    }

    Device *device;

    while (device_idx < device_count) {

        int n = handle_device_header(context, p, end - p, &device);

        if (n < 0) {
            return -1;
        }

        if (n == 0) {
            break;
        }

        p += n;
        device_idx++;

    }

    // Finalize list of devices

    if (device_idx == device_count) {

        context->state = STATE_READY;
        context->list_msg_device_count = 0;
        context->list_msg_device_idx = 0;

        if (context->callbacks.list_cb) {
            context->callbacks.list_cb(
                (TenkinetClient)context,
                context->callbacks.user_data
            );
        }

    }
    else {
        context->state = STATE_RECEIVING_LIST;
        context->list_msg_device_count = device_count;
        context->list_msg_device_idx = device_idx;
    }

    return p - data;

}

MESSAGE_HANDLER(subscribe) {

    // LEN                       TYPE      DESCRIPTION
    // 1                         uint8_t   MESSAGE_SUBSCRIBE
    // 1                         uint8_t   error code
    // DEVICE_SERIAL_NUMBER_LEN  char[]    serial number

    const int N = 2 + DEVICE_SERIAL_NUMBER_LEN;

    if (len < N) {
        return 0;
    }

    //uint8_t error = data[1];
    char *serial_number = &data[2];
    Device *device = device_find(&context->cached_devices, serial_number, 0);

    if (device) {
        device_flag_set(device, DEVICE_FLAG_TENKINET_SUBSCRIBED);
        list_add_unique(&context->sub_devices, device);
    }

    return N;

}

MESSAGE_HANDLER(unsubscribe) {

    // LEN                       TYPE      DESCRIPTION
    // 1                         uint8_t   MESSAGE_UNSUBSCRIBE
    // 1                         uint8_t   error code
    // DEVICE_SERIAL_NUMBER_LEN  char[]    serial number

    const int N = 2 + DEVICE_SERIAL_NUMBER_LEN;

    if (len < N) {
        return 0;
    }

    // Regardless of error, remove device from subscriptions

    char *serial_number = &data[2];
    Device *device = device_find(&context->sub_devices, serial_number, 1);

    if (!device) {
        context->error = TENKINET_ERROR_INTERNAL;
        return -1;  // should never happen
    }

    device_invalidate(device);
    device_flag_clear(device, DEVICE_FLAG_TENKINET_SUBSCRIBED | DEVICE_FLAG_ALIVE);

    if (context->callbacks.device_status_cb) {
        context->callbacks.device_status_cb(
            (TenkinetClient)context,
            seldata->timestamp,
            device,
            context->callbacks.user_data
        );
    }

    return N;

}

MESSAGE_HANDLER(data) {

    // LEN                         TYPE       DESCRIPTION
    //   1                         uint8_t    MESSAGE_DATA
    //   8                         int64_t    timestamp
    //   ---- one block per device ---------------------------------
    //   DEVICE_SERIAL_NUMBER_LEN  char[]     serial number
    //   ---- one block per channel --------------------------------
    //   1                         uint8_t    quantity->unit
    //   1                         uint8_t    quantity->type
    //   4                         uint32_t   quantity->value_uint32

    const int N =
        9 +
        (context->alive_device_count * DEVICE_SERIAL_NUMBER_LEN) +
        (context->alive_channel_count * 6) ;

    if (len < N) {
        return 0;
    }

    data++;  // skip message code

    int64_t timestamp;
    memcpy(&timestamp, data, sizeof(int64_t));
    timestamp = (int64_t) be64toh((uint64_t)(timestamp));
    data += sizeof(int64_t);

    for (size_t i = 0; i < context->alive_device_count; i++) {

        Device *device = device_find(&context->sub_devices, data, 0);
        if (!device) { return -1; }
        data += DEVICE_SERIAL_NUMBER_LEN;

        LIST_FOR(&device->channels) {
            Channel *channel = LIST_CUR(Channel);
            channel->quantity.unit = *data++;
            channel->quantity.type = *data++;
            memcpy(&channel->quantity.value_uint32, data, sizeof(uint32_t));
            channel->quantity.value_uint32 = be32toh(channel->quantity.value_uint32);
            data += sizeof(uint32_t);
        }

    }

    if (context->callbacks.data_cb) {
        context->callbacks.data_cb(
            (TenkinetClient)context,
            timestamp,
            context->callbacks.user_data
        );
    }

    return N;

}

MESSAGE_HANDLER(device_connect) {

    // LEN                       TYPE      DESCRIPTION
    // 1                         uint8_t   MESSAGE_DEVICE_CONNECT
    // 8                         int64_t   timestamp
    // ---- one device header block -----------------------------

    const size_t N = 9;

    if (len < N) {
        return 0;
    }

    Device *device;
    int n = handle_device_header(context, (data + N), (len - N), &device);

    if (n <= 0) {
        return n;
    }

    if (!device_flag_check(device, DEVICE_FLAG_TENKINET_SUBSCRIBED)) {
        // We subscribed to the serial number before having an actual Device.
        // Now that we have one, add it to our subscription list.
        list_add_unique(&context->sub_devices, device);
    }

    device_flag_set(device, DEVICE_FLAG_ALIVE | DEVICE_FLAG_TENKINET_SUBSCRIBED);
    context->alive_device_count++;
    context->alive_channel_count += device->channels.size;

    // Invoke callback

    int64_t timestamp;
    memcpy(&timestamp, data + 1, sizeof(timestamp));
    timestamp = (int64_t) be64toh((uint64_t)timestamp);

    if (context->callbacks.device_status_cb) {
        context->callbacks.device_status_cb(
            (TenkinetClient)context,
            timestamp,
            device,
            context->callbacks.user_data
        );
    }

    return N + n;

}

MESSAGE_HANDLER(device_disconnect) {

    // LEN                       TYPE      DESCRIPTION
    // 1                         uint8_t   MESSAGE_DEVICE_DISCONNECT
    // 8                         int64_t   timestamp
    // DEVICE_SERIAL_NUMBER_LEN  char[]    serial number

    const int N = 9 + DEVICE_SERIAL_NUMBER_LEN;

    if (len < N) {
        return 0;
    }

    char *serial_number = data + 9;
    Device *device = device_find(&context->cached_devices, serial_number, 0);

    if (!device) {
        context->error = TENKINET_ERROR_INTERNAL;
        return -1;  // should never happen
    }

    device_flag_clear(device, DEVICE_FLAG_ALIVE);
    context->alive_device_count--;
    context->alive_channel_count -= device->channels.size;

    device_invalidate(device);

    int64_t timestamp;
    memcpy(&timestamp, data + 1, sizeof(timestamp));
    timestamp = (int64_t) be64toh((uint64_t)timestamp);

    if (context->callbacks.device_status_cb) {
        context->callbacks.device_status_cb(
            (TenkinetClient)context,
            timestamp,
            device,
            context->callbacks.user_data
        );
    }

    return N;

}

MESSAGE_HANDLER(pong) {

    // LEN                       TYPE      DESCRIPTION
    // 1                         uint8_t   MESSAGE_PONG

    const int N = 1;

    // State should always be PINGING here, but who knows...
    if (context->state == STATE_PINGING) {
        context->state = STATE_READY;
    }

    return N;

}
