/*
 * Copyright (c) 2023 Basalte bv
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/** @file
 * @brief CoAP Service API
 *
 * An API for applications to respond to CoAP requests
 */

#ifndef ZEPHYR_INCLUDE_NET_COAP_SERVICE_H_
#define ZEPHYR_INCLUDE_NET_COAP_SERVICE_H_

#include <zephyr/net/coap.h>
#include <zephyr/sys/iterable_sections.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief CoAP Service API
 * @defgroup coap_service CoAP service API
 * @ingroup networking
 * @{
 */

/**
 * @name CoAP Service configuration flags
 * @anchor COAP_SERVICE_FLAGS
 * @{
 */

/** Start the service on boot. */
#define COAP_SERVICE_AUTOSTART		BIT(0)

/** @} */

/** @cond INTERNAL_HIDDEN */

struct coap_service_data {
	int sock_fd;
	struct coap_observer observers[CONFIG_COAP_SERVICE_OBSERVERS];
	struct coap_pending pending[CONFIG_COAP_SERVICE_PENDING_MESSAGES];
};

struct coap_service {
	const char *name;
	const char *host;
	uint16_t *port;
	uint8_t flags;
	struct coap_resource *res_begin;
	struct coap_resource *res_end;
	struct coap_service_data *data;
};

#define __z_coap_service_define(_name, _host, _port, _flags, _res_begin, _res_end)		\
	static struct coap_service_data coap_service_data_##_name = {				\
		.sock_fd = -1,									\
	};											\
	const STRUCT_SECTION_ITERABLE(coap_service, _name) = {					\
		.name = STRINGIFY(_name),							\
		.host = _host,									\
		.port = (uint16_t *)(_port),							\
		.flags = _flags,								\
		.res_begin = (_res_begin),							\
		.res_end = (_res_end),								\
		.data = &coap_service_data_##_name,						\
	}

/** @endcond */

/**
 * @brief Define a static CoAP resource owned by the service named @p _service .
 *
 * @note The handlers registered with the resource can return a CoAP response code to reply with
 * an acknowledge without any payload, nothing is sent if the return value is 0 or negative.
 * As seen in the example.
 *
 * @code{.c}
 *     static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
 *
 *     static int led_put(struct coap_resource *resource, struct coap_packet *request,
 *                        struct sockaddr *addr, socklen_t addr_len)
 *     {
 *             const uint8_t *payload;
 *             uint16_t payload_len;
 *
 *             payload = coap_packet_get_payload(request, &payload_len);
 *             if (payload_len != 1) {
 *                     return COAP_RESPONSE_CODE_BAD_REQUEST;
 *             }
 *
 *             if (gpio_pin_set_dt(&led, payload[0]) < 0) {
 *                     return COAP_RESPONSE_CODE_INTERNAL_ERROR;
 *             }
 *
 *             return COAP_RESPONSE_CODE_CHANGED;
 *     }
 *
 *     COAP_RESOURCE_DEFINE(my_resource, my_service, {
 *             .put = led_put,
 *     });
 * @endcode
 *
 * @param _name Name of the resource.
 * @param _service Name of the associated service.
 */
#define COAP_RESOURCE_DEFINE(_name, _service, ...)						\
	STRUCT_SECTION_ITERABLE_ALTERNATE(coap_resource_##_service, coap_resource, _name)	\
		= __VA_ARGS__

/**
 * @brief Define a CoAP service with static resources.
 *
 * @note The @p _host parameter can be `NULL`. If not, it is used to specify an IP address either in
 * IPv4 or IPv6 format a fully-qualified hostname or a virtual host, otherwise the any address is
 * used.
 *
 * @note The @p _port parameter must be non-`NULL`. It points to a location that specifies the port
 * number to use for the service. If the specified port number is zero, then an ephemeral port
 * number will be used and the actual port number assigned will be written back to memory. For
 * ephemeral port numbers, the memory pointed to by @p _port must be writeable.
 *
 * @param _name Name of the service.
 * @param _host IP address or hostname associated with the service.
 * @param[inout] _port Pointer to port associated with the service.
 * @param _flags Configuration flags @see @ref COAP_SERVICE_FLAGS.
 */
#define COAP_SERVICE_DEFINE(_name, _host, _port, _flags)					\
	extern struct coap_resource _CONCAT(_coap_resource_##_name, _list_start)[];		\
	extern struct coap_resource _CONCAT(_coap_resource_##_name, _list_end)[];		\
	__z_coap_service_define(_name, _host, _port, _flags,					\
				&_CONCAT(_coap_resource_##_name, _list_start)[0],		\
				&_CONCAT(_coap_resource_##_name, _list_end)[0])

/**
 * @brief Count the number of CoAP services.
 *
 * @param[out] _dst Pointer to location where result is written.
 */
#define COAP_SERVICE_COUNT(_dst) STRUCT_SECTION_COUNT(coap_service, _dst)

/**
 * @brief Count CoAP service static resources.
 *
 * @param _service Pointer to a service.
 */
#define COAP_SERVICE_RESOURCE_COUNT(_service) ((_service)->res_end - (_service)->res_begin)

/**
 * @brief Check if service has the specified resource.
 *
 * @param _service Pointer to a service.
 * @param _resource Pointer to a resource.
 */
#define COAP_SERVICE_HAS_RESOURCE(_service, _resource)						\
	((_service)->res_begin <= _resource && _resource < (_service)->res_end)

/**
 * @brief Iterate over all CoAP services.
 *
 * @param _it Name of iterator (of type @ref coap_service)
 */
#define COAP_SERVICE_FOREACH(_it) STRUCT_SECTION_FOREACH(coap_service, _it)

/**
 * @brief Iterate over static CoAP resources associated with a given @p _service.
 *
 * @note This macro requires that @p _service is defined with @ref COAP_SERVICE_DEFINE.
 *
 * @param _service Name of CoAP service
 * @param _it Name of iterator (of type @ref coap_resource)
 */
#define COAP_RESOURCE_FOREACH(_service, _it)							\
	STRUCT_SECTION_FOREACH_ALTERNATE(coap_resource_##_service, coap_resource, _it)

/**
 * @brief Iterate over all static resources associated with @p _service .
 *
 * @note This macro is suitable for a @p _service defined with @ref COAP_SERVICE_DEFINE.
 *
 * @param _service Pointer to COAP service
 * @param _it Name of iterator (of type @ref coap_resource)
 */
#define COAP_SERVICE_FOREACH_RESOURCE(_service, _it)						\
	for (struct coap_resource *_it = (_service)->res_begin; ({				\
		__ASSERT(_it <= (_service)->res_end, "unexpected list end location");		\
		_it < (_service)->res_end;							\
	}); _it++)

/**
 * @brief Start the provided @p service .
 *
 * @note This function is suitable for a @p service defined with @ref COAP_SERVICE_DEFINE.
 *
 * @param service Pointer to CoAP service
 * @retval 0 in case of success.
 * @retval -EALREADY in case of an already running service.
 * @retval -ENOTSUP in case the server has no valid host and port configuration.
 */
int coap_service_start(const struct coap_service *service);

/**
 * @brief Stop the provided @p service .
 *
 * @note This function is suitable for a @p service defined with @ref COAP_SERVICE_DEFINE.
 *
 * @param service Pointer to CoAP service
 * @retval 0 in case of success.
 * @retval -EALREADY in case the service isn't running.
 */
int coap_service_stop(const struct coap_service *service);

/**
 * @brief Query the provided @p service running state.
 *
 * @note This function is suitable for a @p service defined with @ref COAP_SERVICE_DEFINE.
 *
 * @param service Pointer to CoAP service
 * @retval 1 if the service is running
 * @retval 0 if the service is stopped
 * @retval negative in case of an error.
 */
int coap_service_is_running(const struct coap_service *service);

/**
 * @brief Send a CoAP message from the provided @p service .
 *
 * @note This function is suitable for a @p service defined with @ref COAP_SERVICE_DEFINE.
 *
 * @param service Pointer to CoAP service
 * @param cpkt CoAP Packet to send
 * @param addr Peer address
 * @param addr_len Peer address length
 * @param params Pointer to transmission parameters structure or NULL to use default values.
 * @return 0 in case of success or negative in case of error.
 */
int coap_service_send(const struct coap_service *service, const struct coap_packet *cpkt,
		      const struct sockaddr *addr, socklen_t addr_len,
		      const struct coap_transmission_parameters *params);

/**
 * @brief Send a CoAP message from the provided @p resource .
 *
 * @note This function is suitable for a @p resource defined with @ref COAP_RESOURCE_DEFINE.
 *
 * @param resource Pointer to CoAP resource
 * @param cpkt CoAP Packet to send
 * @param addr Peer address
 * @param addr_len Peer address length
 * @param params Pointer to transmission parameters structure or NULL to use default values.
 * @return 0 in case of success or negative in case of error.
 */
int coap_resource_send(const struct coap_resource *resource, const struct coap_packet *cpkt,
		       const struct sockaddr *addr, socklen_t addr_len,
		       const struct coap_transmission_parameters *params);

/**
 * @brief Parse a CoAP observe request for the provided @p resource .
 *
 * @note This function is suitable for a @p resource defined with @ref COAP_RESOURCE_DEFINE.
 *
 * If the observe option value is equal to 0, an observer will be added, if the value is equal
 * to 1, an existing observer will be removed.
 *
 * @param resource Pointer to CoAP resource
 * @param request CoAP request to parse
 * @param addr Peer address
 * @return the observe option value in case of success or negative in case of error.
 */
int coap_resource_parse_observe(struct coap_resource *resource, const struct coap_packet *request,
				const struct sockaddr *addr);

/**
 * @brief Lookup an observer by address and remove it from the @p resource .
 *
 * @note This function is suitable for a @p resource defined with @ref COAP_RESOURCE_DEFINE.
 *
 * @param resource Pointer to CoAP resource
 * @param addr Peer address
 * @return 0 in case of success or negative in case of error.
 */
int coap_resource_remove_observer_by_addr(struct coap_resource *resource,
					  const struct sockaddr *addr);

/**
 * @brief Lookup an observer by token and remove it from the @p resource .
 *
 * @note This function is suitable for a @p resource defined with @ref COAP_RESOURCE_DEFINE.
 *
 * @param resource Pointer to CoAP resource
 * @param token Pointer to the token
 * @param token_len Length of valid bytes in the token
 * @return 0 in case of success or negative in case of error.
 */
int coap_resource_remove_observer_by_token(struct coap_resource *resource,
					   const uint8_t *token, uint8_t token_len);

/**
 * @}
 */

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_NET_COAP_SERVICE_H_ */
