// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2024 Robin Jarry

#pragma once

#include <assert.h>
#include <stdalign.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>

// Must be bumped when making non-backward compatible changes in API headers
#define GR_API_VERSION 3

// API request header.
struct gr_api_request {
	uint32_t id; // Auto-generated by client
	uint32_t type;
	uint32_t payload_len; // max GR_API_MAX_MSG_LEN
};

// API response header.
struct gr_api_response {
	uint32_t for_id; // matches gr_api_request.id (for out-of-order handling)
	uint32_t status; // errno values, 0 for success
	uint32_t payload_len; // max GR_API_MAX_MSG_LEN
};

#define GR_API_MAX_MSG_LEN (128 * 1024)

#define GR_MSG_TYPE(module, id) (((uint32_t)(0xffff & module) << 16) | (0xffff & id))
#define GR_EVENT_ALL UINT32_C(0xffffffff)

#define GR_DEFAULT_SOCK_PATH "/run/grout.sock"

// Opaque API client handle (NOT thread-safe, use one per thread).
struct gr_api_client;

// Connect to the API server.
// Automatically sends GR_HELLO with version negotiation.
// Returns NULL on failure (check errno for details).
struct gr_api_client *gr_api_client_connect(const char *sock_path);

// Disconnect from the API server.
// Automatically unsubscribes from all events.
int gr_api_client_disconnect(struct gr_api_client *);

// Send an API request.
// Returns request ID on success, negative errno on failure.
// Client handles out-of-order responses automatically.
long int
gr_api_client_send(struct gr_api_client *, uint32_t req_type, size_t tx_len, const void *tx_data);

// Receive an API response with minimum payload size validation.
// Caller must free(*rx_data) after use.
// Returns 0 on success, negative errno on failure.
// Returns -EMSGSIZE if payload is non-empty but smaller than min_resp_size.
int gr_api_client_recv(struct gr_api_client *, uint32_t req_type, uint32_t for_id, void **rx_data);

// Send a request and receive the response.
// Validates response payload size against GR_REQ-declared type.
// Caller must free(*rx_data) after use.
// Returns 0 on success, negative errno on failure.
static inline int gr_api_client_send_recv(
	struct gr_api_client *client,
	uint32_t req_type,
	size_t tx_len,
	const void *tx_data,
	void **rx_data
) {
	long int ret = gr_api_client_send(client, req_type, tx_len, tx_data);
	if (ret < 0)
		return ret;
	return gr_api_client_recv(client, req_type, ret, rx_data);
}

// internal, called when interrupting gr_api_client_stream_foreach()
int __gr_api_client_stream_drain(struct gr_api_client *, uint32_t req_type, uint32_t for_id);

// Send a request and iterate over the received stream of responses.
//
// @param obj Iterator variable (const pointer to response object type).
// @param ret Final return code of the operation.
// @param client API client handle.
// @param req_type Request type code.
// @param tx_len Request payload size (0 if tx_data is NULL).
// @param tx_data Request payload (NULL if tx_len is 0).
//
// This should be used like a for loop, e.g.:
//
//     struct gr_iface_list_req req = {.type = GR_IFACE_TYPE_UNDEF};
//     const struct gr_iface *iface;
//     int ret;
//
//     gr_api_client_stream_foreach (iface, ret, client, GR_IFACE_LIST, sizeof(req), &req)
//         printf("Interface: %s\n", iface->name);
//     if (ret < 0)
//         handle_error(ret);
//
// The loop can be interrupted with break which drains remaining responses.
//
// XXX: Interrupting the loop with an early return will cause memory
// leaks and will leave messages hanging in the socket buffer.
#define gr_api_client_stream_foreach(obj, ret, client, req_type, tx_len, tx_data)                  \
	for (long int __id = gr_api_client_send(client, req_type, tx_len, tx_data),                \
		      __first = 1,                                                                 \
		      __done = 0;                                                                  \
	     ({                                                                                    \
		     if (__first == 1) {                                                           \
			     ret = __id;                                                           \
		     } else if (!__done && __id >= 0) {                                            \
			     free((void *)obj);                                                    \
			     ret = __gr_api_client_stream_drain(client, req_type, __id);           \
		     }                                                                             \
		     __id >= 0 && __first; /* statement expression value */                        \
	     });                                                                                   \
	     __first = 0)                                                                          \
		for (void *__ptr = NULL; ({                                                        \
			     bool more = false;                                                    \
			     ret = gr_api_client_recv(client, req_type, __id, &__ptr);             \
			     if (ret < 0) {                                                        \
				     free(__ptr);                                                  \
				     __ptr = NULL;                                                 \
				     __done = 1;                                                   \
			     } else if (__ptr != NULL) {                                           \
				     obj = __ptr;                                                  \
				     more = true;                                                  \
			     } else {                                                              \
				     __done = 1;                                                   \
			     }                                                                     \
			     more; /* statement expression value */                                \
		     });                                                                           \
		     free(__ptr), __ptr = NULL)

const char *gr_api_message_name(uint32_t type);

#ifndef GR_REQ
#define GR_REQ(code, req, resp)                                                                    \
	enum {                                                                                     \
		code##_REQ_SIZE = sizeof(req),                                                     \
		code##_RESP_SIZE = sizeof(resp)                                                    \
	}
#endif
// Stream responses are always terminated by an empty response (header only with payload_len=0).
#ifndef GR_REQ_STREAM
#define GR_REQ_STREAM(code, req, resp)                                                             \
	enum {                                                                                     \
		code##_REQ_SIZE = sizeof(req),                                                     \
		code##_RESP_SIZE = sizeof(resp)                                                    \
	}
#endif
#ifndef GR_EVENT
#define GR_EVENT(code, obj)                                                                        \
	enum {                                                                                     \
		code##_OBJ_SIZE = sizeof(obj)                                                      \
	}
#endif
#ifndef GR_API_INLINE
#define GR_API_INLINE static inline
#endif

struct gr_empty { };

#define GR_MAIN_MODULE 0xcafe

enum gr_main_requests : uint32_t {
	GR_HELLO = GR_MSG_TYPE(GR_MAIN_MODULE, 0x1981),
	GR_LOG_PACKETS_SET,
	GR_LOG_LEVEL_LIST,
	GR_LOG_LEVEL_SET,
	GR_EVENT_SUBSCRIBE,
	GR_EVENT_UNSUBSCRIBE,
};

// Client handshake with API version negotiation.
// Must be the first request sent by any client.
// The api_version must match the server's GR_API_VERSION.
struct gr_hello_req {
	uint32_t api_version; // must match GR_API_VERSION
	char version[128]; // NUL-terminated
};

GR_REQ(GR_HELLO, struct gr_hello_req, struct gr_empty);

// Enable/disable packet ingress/egress logging.
struct gr_log_packets_set_req {
	bool enabled;
};

GR_REQ(GR_LOG_PACKETS_SET, struct gr_log_packets_set_req, struct gr_empty);

struct gr_log_level_list_req {
	bool show_all;
};

struct gr_log_entry {
	char name[64];
	uint32_t level;
};

GR_REQ_STREAM(GR_LOG_LEVEL_LIST, struct gr_log_level_list_req, struct gr_log_entry);

#define GR_LOG_LEVEL_MIN 1
#define GR_LOG_LEVEL_MAX 8

struct gr_log_level_set_req {
	char pattern[64];
	uint32_t level;
};

GR_REQ(GR_LOG_LEVEL_SET, struct gr_log_level_set_req, struct gr_empty);

// Subscribe to events of a given type.
// Use GR_EVENT_ALL to subscribe to all event types.
// Multiple subscriptions to same type update suppression flag.
struct gr_event_subscribe_req {
	// Suppress events originating from API messages made by the same PID
	// as the subscriber socket.
	bool suppress_self_events;
	uint32_t ev_type;
};
GR_REQ(GR_EVENT_SUBSCRIBE, struct gr_event_subscribe_req, struct gr_empty);

// Unsubscribe from all events.
// Removes ALL subscriptions for this client, not just specific types.
// Automatically called on client disconnection.
GR_REQ(GR_EVENT_UNSUBSCRIBE, struct gr_empty, struct gr_empty);

struct gr_api_event {
	uint32_t ev_type;
	size_t payload_len;
};

// Receive an event notification.
// Caller must free(*event) after use.
// Returns 0 on success, negative errno on failure.
int gr_api_client_event_recv(const struct gr_api_client *, struct gr_api_event **);
