/*
 * Copyright (c) 2024 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef ZEPHYR_INCLUDE_SHELL_WEBSOCKET_H_
#define ZEPHYR_INCLUDE_SHELL_WEBSOCKET_H_

#include <zephyr/net/socket.h>
#include <zephyr/net/http/server.h>
#include <zephyr/net/http/service.h>
#include <zephyr/shell/shell.h>

#ifdef __cplusplus
extern "C" {
#endif

#define SHELL_WEBSOCKET_SERVICE_COUNT CONFIG_SHELL_WEBSOCKET_BACKEND_COUNT

/** Line buffer structure. */
struct shell_websocket_line_buf {
	/** Line buffer. */
	char buf[CONFIG_SHELL_WEBSOCKET_LINE_BUF_SIZE];

	/** Current line length. */
	uint16_t len;
};

/** WEBSOCKET-based shell transport. */
struct shell_websocket {
	/** Handler function registered by shell. */
	shell_transport_handler_t shell_handler;

	/** Context registered by shell. */
	void *shell_context;

	/** Buffer for outgoing line. */
	struct shell_websocket_line_buf line_out;

	/** Array for sockets used by the websocket service. */
	struct zsock_pollfd fds[1];

	/** Input buffer. */
	uint8_t rx_buf[CONFIG_SHELL_CMD_BUFF_SIZE];

	/** Number of data bytes within the input buffer. */
	size_t rx_len;

	/** Mutex protecting the input buffer access. */
	struct k_mutex rx_lock;

	/** The delayed work is used to send non-lf terminated output that has
	 *  been around for "too long". This will prove to be useful
	 *  to send the shell prompt for instance.
	 */
	struct k_work_delayable send_work;
	struct k_work_sync work_sync;

	/** If set, no output is sent to the WEBSOCKET client. */
	bool output_lock;
};

extern const struct shell_transport_api shell_websocket_transport_api;
extern int shell_websocket_setup(int ws_socket, void *user_data);
extern int shell_websocket_enable(const struct shell *sh);

#define GET_WS_NAME(_service) ws_ctx_##_service
#define GET_WS_SHELL_NAME(_name) shell_websocket_##_name
#define GET_WS_TRANSPORT_NAME(_service) transport_shell_ws_##_service
#define GET_WS_DETAIL_NAME(_service) ws_res_detail_##_service

#define SHELL_WEBSOCKET_DEFINE(_service)					\
	static struct shell_websocket GET_WS_NAME(_service);			\
	static struct shell_transport GET_WS_TRANSPORT_NAME(_service) = {	\
		.api = &shell_websocket_transport_api,				\
		.ctx = &GET_WS_NAME(_service),					\
	}

#define SHELL_WS_PORT_NAME(_service)	http_service_##_service
#define SHELL_WS_BUF_NAME(_service)	ws_recv_buffer_##_service
#define SHELL_WS_TEMP_RECV_BUF_SIZE 256

#define DEFINE_WEBSOCKET_HTTP_SERVICE(_service)					\
	uint8_t SHELL_WS_BUF_NAME(_service)[SHELL_WS_TEMP_RECV_BUF_SIZE];	\
	struct http_resource_detail_websocket					\
	    GET_WS_DETAIL_NAME(_service) = {					\
		.common = {							\
			.type = HTTP_RESOURCE_TYPE_WEBSOCKET,			\
										\
			/* We need HTTP/1.1 GET method for upgrading */		\
			.bitmask_of_supported_http_methods = BIT(HTTP_GET),	\
		},								\
		.cb = shell_websocket_setup,					\
		.data_buffer = SHELL_WS_BUF_NAME(_service),			\
		.data_buffer_len = sizeof(SHELL_WS_BUF_NAME(_service)),		\
		.user_data = &GET_WS_NAME(_service),				\
	};									\
	HTTP_RESOURCE_DEFINE(ws_resource_##_service, _service,			\
			     "/" CONFIG_SHELL_WEBSOCKET_ENDPOINT_URL,		\
			     &GET_WS_DETAIL_NAME(_service))

#define DEFINE_WEBSOCKET_SERVICE(_service)					\
	SHELL_WEBSOCKET_DEFINE(_service);					\
	SHELL_DEFINE(shell_websocket_##_service,				\
		     CONFIG_SHELL_WEBSOCKET_PROMPT,				\
		     &GET_WS_TRANSPORT_NAME(_service),				\
		     CONFIG_SHELL_WEBSOCKET_LOG_MESSAGE_QUEUE_SIZE,		\
		     CONFIG_SHELL_WEBSOCKET_LOG_MESSAGE_QUEUE_TIMEOUT,		\
		     SHELL_FLAG_OLF_CRLF);					\
	DEFINE_WEBSOCKET_HTTP_SERVICE(_service)

#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
/* Use a secure connection only for Websocket. */
#define WEBSOCKET_CONSOLE_DEFINE(_service, _sec_tag_list, _sec_tag_list_size) \
	static uint16_t SHELL_WS_PORT_NAME(_service) =			  \
		CONFIG_SHELL_WEBSOCKET_PORT;				  \
	HTTPS_SERVICE_DEFINE(_service,					  \
			     CONFIG_SHELL_WEBSOCKET_IP_ADDR,		  \
			     &SHELL_WS_PORT_NAME(_service),		  \
			     SHELL_WEBSOCKET_SERVICE_COUNT,		  \
			     SHELL_WEBSOCKET_SERVICE_COUNT,		  \
			     NULL,					  \
			     _sec_tag_list,				  \
			     _sec_tag_list_size);			  \
	DEFINE_WEBSOCKET_SERVICE(_service);				  \


#else /* CONFIG_NET_SOCKETS_SOCKOPT_TLS */
/* TLS not possible so define only normal HTTP service */
#define WEBSOCKET_CONSOLE_DEFINE(_service, _sec_tag_list, _sec_tag_list_size) \
	static uint16_t SHELL_WS_PORT_NAME(_service) =			\
		CONFIG_SHELL_WEBSOCKET_PORT;				\
	HTTP_SERVICE_DEFINE(_service,					\
			    CONFIG_SHELL_WEBSOCKET_IP_ADDR,		\
			    &SHELL_WS_PORT_NAME(_service),		\
			    SHELL_WEBSOCKET_SERVICE_COUNT,		\
			    SHELL_WEBSOCKET_SERVICE_COUNT,		\
			    NULL);					\
	DEFINE_WEBSOCKET_SERVICE(_service)

#endif /* CONFIG_NET_SOCKETS_SOCKOPT_TLS */

#define WEBSOCKET_CONSOLE_ENABLE(_service)				\
	(void)shell_websocket_enable(&GET_WS_SHELL_NAME(_service))

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_SHELL_WEBSOCKET_H_ */
