/**
 * Copyright (c) 2017 IpTronix
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define LOG_MODULE_NAME wifi_winc1500
#define LOG_LEVEL CONFIG_WIFI_LOG_LEVEL

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME);

#include <zephyr/kernel.h>
#include <zephyr/debug/stack.h>
#include <zephyr/device.h>
#include <string.h>
#include <errno.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_l2.h>
#include <zephyr/net/net_context.h>
#include <zephyr/net/net_offload.h>
#include <zephyr/net/wifi_mgmt.h>

#include <zephyr/sys/printk.h>

/* We do not need <socket/include/socket.h>
 * It seems there is a bug in ASF side: if OS is already defining sockaddr
 * and all, ASF will not need to define it. Unfortunately its socket.h does
 * but also defines some NM API functions there (??), so we need to redefine
 * those here.
 */
#define __SOCKET_H__
#define HOSTNAME_MAX_SIZE	(64)

#include <driver/include/m2m_wifi.h>
#include <socket/include/m2m_socket_host_if.h>

NMI_API void socketInit(void);
typedef void (*tpfAppSocketCb) (SOCKET sock, uint8 u8Msg, void *pvMsg);
typedef void (*tpfAppResolveCb) (uint8 *pu8DomainName, uint32 u32ServerIP);
NMI_API void registerSocketCallback(tpfAppSocketCb socket_cb,
				    tpfAppResolveCb resolve_cb);
NMI_API SOCKET socket(uint16 u16Domain, uint8 u8Type, uint8 u8Flags);
NMI_API sint8 bind(SOCKET sock, struct sockaddr *pstrAddr, uint8 u8AddrLen);
NMI_API sint8 listen(SOCKET sock, uint8 backlog);
NMI_API sint8 accept(SOCKET sock, struct sockaddr *addr, uint8 *addrlen);
NMI_API sint8 connect(SOCKET sock, struct sockaddr *pstrAddr, uint8 u8AddrLen);
NMI_API sint16 recv(SOCKET sock, void *pvRecvBuf,
		    uint16 u16BufLen, uint32 u32Timeoutmsec);
NMI_API sint16 send(SOCKET sock, void *pvSendBuffer,
		    uint16 u16SendLength, uint16 u16Flags);
NMI_API sint16 sendto(SOCKET sock, void *pvSendBuffer,
		      uint16 u16SendLength, uint16 flags,
		      struct sockaddr *pstrDestAddr, uint8 u8AddrLen);
NMI_API sint8 winc1500_close(SOCKET sock);

enum socket_errors {
	SOCK_ERR_NO_ERROR = 0,
	SOCK_ERR_INVALID_ADDRESS = -1,
	SOCK_ERR_ADDR_ALREADY_IN_USE = -2,
	SOCK_ERR_MAX_TCP_SOCK = -3,
	SOCK_ERR_MAX_UDP_SOCK = -4,
	SOCK_ERR_INVALID_ARG = -6,
	SOCK_ERR_MAX_LISTEN_SOCK = -7,
	SOCK_ERR_INVALID = -9,
	SOCK_ERR_ADDR_IS_REQUIRED = -11,
	SOCK_ERR_CONN_ABORTED = -12,
	SOCK_ERR_TIMEOUT = -13,
	SOCK_ERR_BUFFER_FULL = -14,
};

enum socket_messages {
	SOCKET_MSG_BIND	= 1,
	SOCKET_MSG_LISTEN,
	SOCKET_MSG_DNS_RESOLVE,
	SOCKET_MSG_ACCEPT,
	SOCKET_MSG_CONNECT,
	SOCKET_MSG_RECV,
	SOCKET_MSG_SEND,
	SOCKET_MSG_SENDTO,
	SOCKET_MSG_RECVFROM,
};

typedef struct {
	sint8	status;
} tstrSocketBindMsg;

typedef struct {
	sint8	status;
} tstrSocketListenMsg;

typedef struct {
	SOCKET			sock;
	struct sockaddr_in	strAddr;
} tstrSocketAcceptMsg;

typedef struct {
	SOCKET	sock;
	sint8	s8Error;
} tstrSocketConnectMsg;

typedef struct {
	uint8			*pu8Buffer;
	sint16			s16BufferSize;
	uint16			u16RemainingSize;
	struct sockaddr_in	strRemoteAddr;
} tstrSocketRecvMsg;

#include <driver/include/m2m_wifi.h>
#include <socket/include/m2m_socket_host_if.h>

#if defined(CONFIG_WIFI_WINC1500_REGION_NORTH_AMERICA)
#define WINC1500_REGION		NORTH_AMERICA
#elif defined(CONFIG_WIFI_WINC1500_REGION_EUROPE)
#define WINC1500_REGION		EUROPE
#elif defined(CONFIG_WIFI_WINC1500_REGION_ASIA)
#define WINC1500_REGION		ASIA
#endif

#define WINC1500_BIND_TIMEOUT K_MSEC(500)
#define WINC1500_LISTEN_TIMEOUT K_MSEC(500)
#define WINC1500_BUF_TIMEOUT K_MSEC(100)

NET_BUF_POOL_DEFINE(winc1500_tx_pool, CONFIG_WIFI_WINC1500_BUF_CTR,
		    CONFIG_WIFI_WINC1500_MAX_PACKET_SIZE, 0, NULL);
NET_BUF_POOL_DEFINE(winc1500_rx_pool, CONFIG_WIFI_WINC1500_BUF_CTR,
		    CONFIG_WIFI_WINC1500_MAX_PACKET_SIZE, 0, NULL);

K_KERNEL_STACK_MEMBER(winc1500_stack, CONFIG_WIFI_WINC1500_THREAD_STACK_SIZE);
struct k_thread winc1500_thread_data;

struct socket_data {
	struct net_context		*context;
	net_context_connect_cb_t	connect_cb;
	net_tcp_accept_cb_t		accept_cb;
	net_context_recv_cb_t		recv_cb;
	void				*connect_user_data;
	void				*recv_user_data;
	void				*accept_user_data;
	struct net_pkt			*rx_pkt;
	struct net_buf			*pkt_buf;
	int				ret_code;
	struct k_sem			wait_sem;
};

struct winc1500_data {
	struct socket_data socket_data[
		CONFIG_WIFI_WINC1500_OFFLOAD_MAX_SOCKETS];
	struct net_if *iface;
	unsigned char mac[6];
	scan_result_cb_t scan_cb;
	uint8_t scan_result;
	bool connecting;
	bool connected;
};

static struct winc1500_data w1500_data;

#if LOG_LEVEL > LOG_LEVEL_OFF
static void stack_stats(void)
{
	log_stack_usage(&winc1500_thread_data);
}
#endif /* LOG_LEVEL > LOG_LEVEL_OFF */

static char *socket_error_string(int8_t err)
{
	switch (err) {
	case SOCK_ERR_NO_ERROR:
		return "Successful socket operation";
	case SOCK_ERR_INVALID_ADDRESS:
		return "Socket address is invalid."
			"The socket operation cannot be completed successfully"
			" without specifying a specific address. "
			"For example: bind is called without specifying"
			" a port number";
	case SOCK_ERR_ADDR_ALREADY_IN_USE:
		return "Socket operation cannot bind on the given address."
			" With socket operations, only one IP address per "
			"socket is permitted. Any attempt for a new socket "
			"to bind with an IP address already bound to another "
			"open socket, will return the following error code. "
			"States that bind operation failed.";
	case SOCK_ERR_MAX_TCP_SOCK:
		return "Exceeded the maximum number of TCP sockets."
			"A maximum number of TCP sockets opened simultaneously"
			" is defined through TCP_SOCK_MAX. It is not permitted"
			" to exceed that number at socket creation."
			" Identifies that @ref socket operation failed.";
	case SOCK_ERR_MAX_UDP_SOCK:
		return "Exceeded the maximum number of UDP sockets."
			"A maximum number of UDP sockets opened simultaneously"
			" is defined through UDP_SOCK_MAX. It is not permitted"
			" to exceed that number at socket creation."
			" Identifies that socket operation failed";
	case SOCK_ERR_INVALID_ARG:
		return "An invalid argument is passed to a function.";
	case SOCK_ERR_MAX_LISTEN_SOCK:
		return "Exceeded the maximum number of TCP passive listening "
			"sockets. Identifies Identifies that listen operation"
			" failed.";
	case SOCK_ERR_INVALID:
		return "The requested socket operation is not valid in the "
			"current socket state. For example: @ref accept is "
			"called on a TCP socket before bind or listen.";
	case SOCK_ERR_ADDR_IS_REQUIRED:
		return "Destination address is required. Failure to provide "
			"the socket address required for the socket operation "
			"to be completed. It is generated as an error to the "
			"sendto function when the address required to send the"
			" data to is not known.";
	case SOCK_ERR_CONN_ABORTED:
		return "The socket is closed by the peer. The local socket is "
			"also closed.";
	case SOCK_ERR_TIMEOUT:
		return "The socket pending operation has  timedout.";
	case SOCK_ERR_BUFFER_FULL:
		return "No buffer space available to be used for the requested"
			" socket operation.";
	default:
		return "Unknown";
	}
}

static char *wifi_cb_msg_2_str(uint8_t message_type)
{
	switch (message_type) {
	case M2M_WIFI_RESP_CURRENT_RSSI:
		return "M2M_WIFI_RESP_CURRENT_RSSI";
	case M2M_WIFI_REQ_WPS:
		return "M2M_WIFI_REQ_WPS";
	case M2M_WIFI_RESP_IP_CONFIGURED:
		return "M2M_WIFI_RESP_IP_CONFIGURED";
	case M2M_WIFI_RESP_IP_CONFLICT:
		return "M2M_WIFI_RESP_IP_CONFLICT";
	case M2M_WIFI_RESP_CLIENT_INFO:
		return "M2M_WIFI_RESP_CLIENT_INFO";

	case M2M_WIFI_RESP_GET_SYS_TIME:
		return "M2M_WIFI_RESP_GET_SYS_TIME";
	case M2M_WIFI_REQ_SEND_ETHERNET_PACKET:
		return "M2M_WIFI_REQ_SEND_ETHERNET_PACKET";
	case M2M_WIFI_RESP_ETHERNET_RX_PACKET:
		return "M2M_WIFI_RESP_ETHERNET_RX_PACKET";

	case M2M_WIFI_RESP_CON_STATE_CHANGED:
		return "M2M_WIFI_RESP_CON_STATE_CHANGED";
	case M2M_WIFI_REQ_DHCP_CONF:
		return "Response indicating that IP address was obtained.";
	case M2M_WIFI_RESP_SCAN_DONE:
		return "M2M_WIFI_RESP_SCAN_DONE";
	case M2M_WIFI_RESP_SCAN_RESULT:
		return "M2M_WIFI_RESP_SCAN_RESULT";
	case M2M_WIFI_RESP_PROVISION_INFO:
		return "M2M_WIFI_RESP_PROVISION_INFO";
	default:
		return "UNKNOWN";
	}
}

static char *socket_message_to_string(uint8_t message)
{
	switch (message) {
	case SOCKET_MSG_BIND:
		return "Bind socket event.";
	case SOCKET_MSG_LISTEN:
		return "Listen socket event.";
	case SOCKET_MSG_DNS_RESOLVE:
		return "DNS Resolution event.";
	case SOCKET_MSG_ACCEPT:
		return "Accept socket event.";
	case SOCKET_MSG_CONNECT:
		return "Connect socket event.";
	case SOCKET_MSG_RECV:
		return "Receive socket event.";
	case SOCKET_MSG_SEND:
		return "Send socket event.";
	case SOCKET_MSG_SENDTO:
		return "Sendto socket event.";
	case SOCKET_MSG_RECVFROM:
		return "Recvfrom socket event.";
	default:
		return "UNKNOWN.";
	}
}

/**
 * This function is called when the socket is to be opened.
 */
static int winc1500_get(sa_family_t family,
			enum net_sock_type type,
			enum net_ip_protocol ip_proto,
			struct net_context **context)
{
	struct socket_data *sd;
	SOCKET sock;

	if (family != AF_INET) {
		LOG_ERR("Only AF_INET is supported!");
		return -1;
	}

	/* winc1500 atmel uses AF_INET 2 instead of zephyrs AF_INET 1
	 * we have checked if family is AF_INET so we can hardcode this
	 * for now.
	 */
	sock = socket(2, type, 0);
	if (sock < 0) {
		LOG_ERR("socket error!");
		return -1;
	}

	(*context)->offload_context = (void *)(intptr_t)sock;
	sd = &w1500_data.socket_data[sock];

	k_sem_init(&sd->wait_sem, 0, 1);

	sd->context = *context;

	return 0;
}

/**
 * This function is called when user wants to bind to local IP address.
 */
static int winc1500_bind(struct net_context *context,
			 const struct sockaddr *addr,
			 socklen_t addrlen)
{
	SOCKET socket = (int)context->offload_context;
	int ret;

	/* FIXME atmel winc1500 don't support bind on null port */
	if (net_sin(addr)->sin_port == 0U) {
		return 0;
	}

	ret = bind((int)context->offload_context, (struct sockaddr *)addr,
		   addrlen);
	if (ret) {
		LOG_ERR("bind error %d %s!",
			ret, socket_message_to_string(ret));
		return ret;
	}

	if (k_sem_take(&w1500_data.socket_data[socket].wait_sem,
		       WINC1500_BIND_TIMEOUT)) {
		LOG_ERR("bind error timeout expired");
		return -ETIMEDOUT;
	}

	return w1500_data.socket_data[socket].ret_code;
}

/**
 * This function is called when user wants to mark the socket
 * to be a listening one.
 */
static int winc1500_listen(struct net_context *context, int backlog)
{
	SOCKET socket = (int)context->offload_context;
	int ret;

	ret = listen((int)context->offload_context, backlog);
	if (ret) {
		LOG_ERR("listen error %d %s!",
			ret, socket_error_string(ret));
		return ret;
	}

	if (k_sem_take(&w1500_data.socket_data[socket].wait_sem,
		       WINC1500_LISTEN_TIMEOUT)) {
		return -ETIMEDOUT;
	}

	return w1500_data.socket_data[socket].ret_code;
}

/**
 * This function is called when user wants to create a connection
 * to a peer host.
 */
static int winc1500_connect(struct net_context *context,
			    const struct sockaddr *addr,
			    socklen_t addrlen,
			    net_context_connect_cb_t cb,
			    int32_t timeout,
			    void *user_data)
{
	SOCKET socket = (int)context->offload_context;
	int ret;

	w1500_data.socket_data[socket].connect_cb = cb;
	w1500_data.socket_data[socket].connect_user_data = user_data;
	w1500_data.socket_data[socket].ret_code = 0;

	ret = connect(socket, (struct sockaddr *)addr, addrlen);
	if (ret) {
		LOG_ERR("connect error %d %s!",
			ret, socket_error_string(ret));
		return ret;
	}

	if (timeout != 0 &&
	    k_sem_take(&w1500_data.socket_data[socket].wait_sem, K_MSEC(timeout))) {
		return -ETIMEDOUT;
	}

	return w1500_data.socket_data[socket].ret_code;
}

/**
 * This function is called when user wants to accept a connection
 * being established.
 */
static int winc1500_accept(struct net_context *context,
			   net_tcp_accept_cb_t cb,
			   int32_t timeout,
			   void *user_data)
{
	SOCKET socket = (int)context->offload_context;
	int ret;

	w1500_data.socket_data[socket].accept_cb = cb;
	w1500_data.socket_data[socket].accept_user_data = user_data;

	ret = accept(socket, NULL, 0);
	if (ret) {
		LOG_ERR("accept error %d %s!",
			ret, socket_error_string(ret));
		return ret;
	}

	if (timeout) {
		if (k_sem_take(&w1500_data.socket_data[socket].wait_sem,
			       K_MSEC(timeout))) {
			return -ETIMEDOUT;
		}
	}

	return w1500_data.socket_data[socket].ret_code;
}

/**
 * This function is called when user wants to send data to peer host.
 */
static int winc1500_send(struct net_pkt *pkt,
			 net_context_send_cb_t cb,
			 int32_t timeout,
			 void *user_data)
{
	struct net_context *context = pkt->context;
	SOCKET socket = (int)context->offload_context;
	int ret = 0;
	struct net_buf *buf;

	buf = net_buf_alloc(&winc1500_tx_pool, WINC1500_BUF_TIMEOUT);
	if (!buf) {
		return -ENOBUFS;
	}

	if (net_pkt_read(pkt, buf->data, net_pkt_get_len(pkt))) {
		ret = -ENOBUFS;
		goto out;
	}

	net_buf_add(buf, net_pkt_get_len(pkt));

	ret = send(socket, buf->data, buf->len, 0);
	if (ret) {
		LOG_ERR("send error %d %s!", ret, socket_error_string(ret));
		goto out;
	}

	net_pkt_unref(pkt);
out:
	net_buf_unref(buf);

	return ret;
}

/**
 * This function is called when user wants to send data to peer host.
 */
static int winc1500_sendto(struct net_pkt *pkt,
			   const struct sockaddr *dst_addr,
			   socklen_t addrlen,
			   net_context_send_cb_t cb,
			   int32_t timeout,
			   void *user_data)
{
	struct net_context *context = pkt->context;
	SOCKET socket = (int)context->offload_context;
	int ret = 0;
	struct net_buf *buf;

	buf = net_buf_alloc(&winc1500_tx_pool, WINC1500_BUF_TIMEOUT);
	if (!buf) {
		return -ENOBUFS;
	}

	if (net_pkt_read(pkt, buf->data, net_pkt_get_len(pkt))) {
		ret = -ENOBUFS;
		goto out;
	}

	net_buf_add(buf, net_pkt_get_len(pkt));

	ret = sendto(socket, buf->data, buf->len, 0,
		     (struct sockaddr *)dst_addr, addrlen);
	if (ret) {
		LOG_ERR("sendto error %d %s!", ret, socket_error_string(ret));
		goto out;
	}

	net_pkt_unref(pkt);
out:
	net_buf_unref(buf);

	return ret;
}

/**
 */
static int prepare_pkt(struct socket_data *sock_data)
{
	/* Get the frame from the buffer */
	sock_data->rx_pkt = net_pkt_rx_alloc_on_iface(w1500_data.iface,
						      K_NO_WAIT);
	if (!sock_data->rx_pkt) {
		LOG_ERR("Could not allocate rx packet");
		return -1;
	}

	/* Reserve a data buffer to receive the frame */
	sock_data->pkt_buf = net_buf_alloc(&winc1500_rx_pool, K_NO_WAIT);
	if (!sock_data->pkt_buf) {
		LOG_ERR("Could not allocate data buffer");
		net_pkt_unref(sock_data->rx_pkt);
		return -1;
	}

	net_pkt_append_buffer(sock_data->rx_pkt, sock_data->pkt_buf);

	return 0;
}

/**
 * This function is called when user wants to receive data from peer
 * host.
 */
static int winc1500_recv(struct net_context *context,
			 net_context_recv_cb_t cb,
			 int32_t timeout,
			 void *user_data)
{
	SOCKET socket = (int) context->offload_context;
	int ret;

	w1500_data.socket_data[socket].recv_cb = cb;
	w1500_data.socket_data[socket].recv_user_data = user_data;
	if (!cb) {
		return 0;
	}

	ret = prepare_pkt(&w1500_data.socket_data[socket]);
	if (ret) {
		LOG_ERR("Could not reserve packet buffer");
		return -ENOMEM;
	}


	ret = recv(socket, w1500_data.socket_data[socket].pkt_buf->data,
		   CONFIG_WIFI_WINC1500_MAX_PACKET_SIZE, timeout);
	if (ret) {
		LOG_ERR("recv error %d %s!",
			ret, socket_error_string(ret));
		return ret;
	}

	return 0;
}

/**
 * This function is called when user wants to close the socket.
 */
static int winc1500_put(struct net_context *context)
{
	SOCKET sock = (int) context->offload_context;
	struct socket_data *sd = &w1500_data.socket_data[sock];
	int ret;

	memset(&(context->remote), 0, sizeof(struct sockaddr_in));
	context->flags &= ~NET_CONTEXT_REMOTE_ADDR_SET;
	ret = winc1500_close(sock);

	net_pkt_unref(sd->rx_pkt);

	memset(sd, 0, sizeof(struct socket_data));

	return ret;
}

static struct net_offload winc1500_offload = {
	.get		= winc1500_get,
	.bind		= winc1500_bind,
	.listen		= winc1500_listen,
	.connect	= winc1500_connect,
	.accept		= winc1500_accept,
	.send		= winc1500_send,
	.sendto		= winc1500_sendto,
	.recv		= winc1500_recv,
	.put		= winc1500_put,
};

static void handle_wifi_con_state_changed(void *pvMsg)
{
	tstrM2mWifiStateChanged *pstrWifiState =
		(tstrM2mWifiStateChanged *)pvMsg;

	switch (pstrWifiState->u8CurrState) {
	case M2M_WIFI_DISCONNECTED:
		LOG_DBG("Disconnected (%u)", pstrWifiState->u8ErrCode);

		if (w1500_data.connecting) {
			wifi_mgmt_raise_connect_result_event(w1500_data.iface,
					pstrWifiState->u8ErrCode ? -EIO : 0);
			w1500_data.connecting = false;

			break;
		}

		w1500_data.connected = false;
		wifi_mgmt_raise_disconnect_result_event(w1500_data.iface, 0);

		break;
	case M2M_WIFI_CONNECTED:
		LOG_DBG("Connected (%u)", pstrWifiState->u8ErrCode);

		w1500_data.connected = true;
		wifi_mgmt_raise_connect_result_event(w1500_data.iface, 0);

		break;
	case M2M_WIFI_UNDEF:
		/* TODO status undefined*/
		LOG_DBG("Undefined?");
		break;
	}
}

static void handle_wifi_dhcp_conf(void *pvMsg)
{
	uint8_t *pu8IPAddress = (uint8_t *)pvMsg;
	struct in_addr addr;
	uint8_t i;

	/* Connected and got IP address*/
	LOG_DBG("Wi-Fi connected, IP is %u.%u.%u.%u",
		pu8IPAddress[0], pu8IPAddress[1],
		pu8IPAddress[2], pu8IPAddress[3]);

	/* TODO at this point the standby mode should be enable
	 * status = WiFi connected IP assigned
	 */
	for (i = 0U; i < 4; i++) {
		addr.s4_addr[i] = pu8IPAddress[i];
	}

	/* TODO fill in net mask, gateway and lease time */

	net_if_ipv4_addr_add(w1500_data.iface, &addr, NET_ADDR_DHCP, 0);
}

static void reset_scan_data(void)
{
	w1500_data.scan_cb = NULL;
	w1500_data.scan_result = 0U;
}

static void handle_scan_result(void *pvMsg)
{
	tstrM2mWifiscanResult *pstrScanResult = (tstrM2mWifiscanResult *)pvMsg;
	struct wifi_scan_result result;

	if (!w1500_data.scan_cb) {
		return;
	}

	if (pstrScanResult->u8AuthType == M2M_WIFI_SEC_OPEN) {
		result.security = WIFI_SECURITY_TYPE_NONE;
	} else if (pstrScanResult->u8AuthType == M2M_WIFI_SEC_WPA_PSK) {
		result.security = WIFI_SECURITY_TYPE_PSK;
	} else {
		LOG_DBG("Security %u not supported",
			pstrScanResult->u8AuthType);
		goto out;
	}

	memcpy(result.ssid, pstrScanResult->au8SSID, WIFI_SSID_MAX_LEN);
	result.ssid_length = strlen(result.ssid);

	result.channel = pstrScanResult->u8ch;
	result.rssi = pstrScanResult->s8rssi;

	w1500_data.scan_cb(w1500_data.iface, 0, &result);

	k_yield();

out:
	if (w1500_data.scan_result < m2m_wifi_get_num_ap_found()) {
		m2m_wifi_req_scan_result(w1500_data.scan_result);
		w1500_data.scan_result++;
	} else {
		w1500_data.scan_cb(w1500_data.iface, 0, NULL);
		reset_scan_data();
	}
}

static void handle_scan_done(void *pvMsg)
{
	tstrM2mScanDone	*pstrInfo = (tstrM2mScanDone *)pvMsg;

	if (!w1500_data.scan_cb) {
		return;
	}

	if (pstrInfo->s8ScanState != M2M_SUCCESS) {
		w1500_data.scan_cb(w1500_data.iface, -EIO, NULL);
		reset_scan_data();

		LOG_ERR("Scan failed.");

		return;
	}

	w1500_data.scan_result = 0U;

	if (pstrInfo->u8NumofCh >= 1) {
		LOG_DBG("Requesting results (%u)",
			m2m_wifi_get_num_ap_found());

		m2m_wifi_req_scan_result(w1500_data.scan_result);
		w1500_data.scan_result++;
	} else {
		LOG_DBG("No AP found");

		w1500_data.scan_cb(w1500_data.iface, 0, NULL);
		reset_scan_data();
	}
}

static void winc1500_wifi_cb(uint8_t message_type, void *pvMsg)
{
	LOG_DBG("Msg Type %d %s",
		message_type, wifi_cb_msg_2_str(message_type));

	switch (message_type) {
	case M2M_WIFI_RESP_CON_STATE_CHANGED:
		handle_wifi_con_state_changed(pvMsg);
		break;
	case M2M_WIFI_REQ_DHCP_CONF:
		handle_wifi_dhcp_conf(pvMsg);
		break;
	case M2M_WIFI_RESP_SCAN_RESULT:
		handle_scan_result(pvMsg);
		break;
	case M2M_WIFI_RESP_SCAN_DONE:
		handle_scan_done(pvMsg);
		break;
	default:
		break;
	}
#if LOG_LEVEL > LOG_LEVEL_OFF
	stack_stats();
#endif /* LOG_LEVEL > LOG_LEVEL_OFF */
}

static void handle_socket_msg_connect(struct socket_data *sd, void *pvMsg)
{
	tstrSocketConnectMsg *strConnMsg = (tstrSocketConnectMsg *)pvMsg;

	LOG_ERR("CONNECT: socket %d error %d",
		strConnMsg->sock, strConnMsg->s8Error);

	if (!strConnMsg->s8Error) {
		net_context_set_state(sd->context, NET_CONTEXT_CONNECTED);
	}

	if (sd->connect_cb) {
		sd->connect_cb(sd->context,
			       strConnMsg->s8Error,
			       sd->connect_user_data);
	}

	sd->ret_code = strConnMsg->s8Error;
}


static bool handle_socket_msg_recv(SOCKET sock,
				   struct socket_data *sd, void *pvMsg)
{
	tstrSocketRecvMsg *pstrRx = (tstrSocketRecvMsg *)pvMsg;

	if ((pstrRx->pu8Buffer != NULL) && (pstrRx->s16BufferSize > 0)) {
		net_buf_add(sd->pkt_buf, pstrRx->s16BufferSize);
		net_pkt_cursor_init(sd->rx_pkt);

		if (sd->recv_cb) {
			sd->recv_cb(sd->context,
				    sd->rx_pkt,
				    NULL, NULL,
				    0,
				    sd->recv_user_data);
			}
	} else if (pstrRx->pu8Buffer == NULL) {
		if (pstrRx->s16BufferSize == SOCK_ERR_CONN_ABORTED) {
			net_pkt_unref(sd->rx_pkt);
			return false;
		}
	}
	return true;
}

static void handle_socket_msg_bind(struct socket_data *sd, void *pvMsg)
{
	tstrSocketBindMsg *bind_msg = (tstrSocketBindMsg *)pvMsg;

	/* Holding a value of ZERO for a successful bind or otherwise
	 * a negative error code corresponding to the type of error.
	 */

	if (bind_msg->status) {
		LOG_ERR("BIND: error %d %s",
			bind_msg->status,
			socket_message_to_string(bind_msg->status));
		sd->ret_code = bind_msg->status;
	}
}

static void handle_socket_msg_listen(struct socket_data *sd, void *pvMsg)
{
	tstrSocketListenMsg *listen_msg = (tstrSocketListenMsg *)pvMsg;

	/* Holding a value of ZERO for a successful listen or otherwise
	 * a negative error code corresponding to the type of error.
	 */

	if (listen_msg->status) {
		LOG_ERR("winc1500_socket_cb:LISTEN: error %d %s",
			listen_msg->status,
			socket_message_to_string(listen_msg->status));
		sd->ret_code = listen_msg->status;
	}
}

static void handle_socket_msg_accept(struct socket_data *sd, void *pvMsg)
{
	tstrSocketAcceptMsg *accept_msg = (tstrSocketAcceptMsg *)pvMsg;

	/* On a successful accept operation, the return information is
	 * the socket ID for the accepted connection with the remote peer.
	 * Otherwise a negative error code is returned to indicate failure
	 * of the accept operation.
	 */

	LOG_DBG("ACCEPT: from %d.%d.%d.%d:%d, new socket is %d",
		accept_msg->strAddr.sin_addr.s4_addr[0],
		accept_msg->strAddr.sin_addr.s4_addr[1],
		accept_msg->strAddr.sin_addr.s4_addr[2],
		accept_msg->strAddr.sin_addr.s4_addr[3],
		ntohs(accept_msg->strAddr.sin_port),
		accept_msg->sock);

	if (accept_msg->sock < 0) {
		LOG_ERR("ACCEPT: error %d %s",
			accept_msg->sock,
			socket_message_to_string(accept_msg->sock));
		sd->ret_code = accept_msg->sock;
	}

	if (sd->accept_cb) {
		struct socket_data *a_sd;
		int ret;

		a_sd = &w1500_data.socket_data[accept_msg->sock];

		memcpy(a_sd, sd, sizeof(struct socket_data));

		ret = net_context_get(AF_INET, SOCK_STREAM,
				      IPPROTO_TCP, &a_sd->context);
		if (ret < 0) {
			LOG_ERR("Cannot get new net context for ACCEPT");
			return;
		}
		/* We get a new socket from accept_msg but we need a new
		 * context as well. The new context gives us another socket
		 * so we have to close that one first.
		 */
		winc1500_close((int)a_sd->context->offload_context);

		a_sd->context->offload_context =
				(void *)((int)accept_msg->sock);
		/** The iface is reset when getting a new context. */
		a_sd->context->iface = sd->context->iface;

		/** Setup remote */
		a_sd->context->remote.sa_family = AF_INET;
		net_sin(&a_sd->context->remote)->sin_port =
			accept_msg->strAddr.sin_port;
		net_sin(&a_sd->context->remote)->sin_addr.s_addr =
			accept_msg->strAddr.sin_addr.s_addr;
		a_sd->context->flags |= NET_CONTEXT_REMOTE_ADDR_SET;

		sd->accept_cb(a_sd->context,
			      (struct sockaddr *)&accept_msg->strAddr,
			      sizeof(struct sockaddr_in),
			      (accept_msg->sock > 0) ?
			      0 : accept_msg->sock,
			      sd->accept_user_data);
	}
}

static void winc1500_socket_cb(SOCKET sock, uint8 message, void *pvMsg)
{
	struct socket_data *sd = &w1500_data.socket_data[sock];

	if (message != 6) {
		LOG_DBG("sock %d Msg %d %s",
			sock, message, socket_message_to_string(message));
	}

	sd->ret_code = 0;

	switch (message) {
	case SOCKET_MSG_CONNECT:
		handle_socket_msg_connect(sd, pvMsg);
		k_sem_give(&sd->wait_sem);

		break;
	case SOCKET_MSG_SEND:
		break;
	case SOCKET_MSG_RECV:
		if (!handle_socket_msg_recv(sock, sd, pvMsg)) {
			return;
		}

		break;
	case SOCKET_MSG_BIND:
		handle_socket_msg_bind(sd, pvMsg);
		k_sem_give(&sd->wait_sem);

		break;
	case SOCKET_MSG_LISTEN:
		handle_socket_msg_listen(sd, pvMsg);
		k_sem_give(&sd->wait_sem);

		break;
	case SOCKET_MSG_ACCEPT:
		handle_socket_msg_accept(sd, pvMsg);

		break;
	}
#if LOG_LEVEL > LOG_LEVEL_OFF
	stack_stats();
#endif /* LOG_LEVEL > LOG_LEVEL_OFF */
}

static void winc1500_thread(void)
{
	while (1) {
		while (m2m_wifi_handle_events(NULL) != 0) {
		}

		k_sleep(K_MSEC(1));
	}
}

static int winc1500_mgmt_scan(const struct device *dev,
			      struct wifi_scan_params *params,
			      scan_result_cb_t cb)
{
	ARG_UNUSED(params);

	if (w1500_data.scan_cb) {
		return -EALREADY;
	}

	w1500_data.scan_cb = cb;

	if (m2m_wifi_request_scan(M2M_WIFI_CH_ALL)) {
		w1500_data.scan_cb = NULL;
		LOG_ERR("Failed to request scan");
		return -EIO;
	}

	return 0;
}

static int winc1500_mgmt_connect(const struct device *dev,
				 struct wifi_connect_req_params *params)
{
	uint8_t ssid[M2M_MAX_SSID_LEN];
	tuniM2MWifiAuth psk;
	uint8_t security;
	uint16_t channel;
	void *auth;

	memcpy(ssid, params->ssid, params->ssid_length);
	ssid[params->ssid_length] = '\0';

	if (params->security == WIFI_SECURITY_TYPE_PSK) {
		memcpy(psk.au8PSK, params->psk, params->psk_length);
		psk.au8PSK[params->psk_length] = '\0';
		auth = &psk;

		security = M2M_WIFI_SEC_WPA_PSK;
	} else {
		auth = NULL;
		security = M2M_WIFI_SEC_OPEN;
	}

	if (params->channel == WIFI_CHANNEL_ANY) {
		channel = M2M_WIFI_CH_ALL;
	} else {
		channel = params->channel;
	}

	LOG_DBG("Connecting to %s (%u) on %s %u %s security (%s)",
		ssid, params->ssid_length,
		channel == M2M_WIFI_CH_ALL ? "channel unknown" : "channel",
		channel,
		security == M2M_WIFI_SEC_OPEN ? "without" : "with",
		params->psk ? (char *)psk.au8PSK : "");

	if (m2m_wifi_connect((char *)ssid, params->ssid_length,
			     security, auth, channel)) {
		return -EIO;
	}

	w1500_data.connecting = true;

	return 0;
}

static int winc1500_mgmt_disconnect(const struct device *dev)
{
	if (!w1500_data.connected) {
		return -EALREADY;
	}

	if (m2m_wifi_disconnect()) {
		return -EIO;
	}

	return 0;
}

static int winc1500_mgmt_ap_enable(const struct device *dev,
			      struct wifi_connect_req_params *params)
{
	tstrM2MAPConfig strM2MAPConfig;

	memset(&strM2MAPConfig, 0x00, sizeof(tstrM2MAPConfig));
	strcpy((char *)&strM2MAPConfig.au8SSID, params->ssid);
	strM2MAPConfig.u8ListenChannel = params->channel;
	/** security is hardcoded as open for now */
	strM2MAPConfig.u8SecType = M2M_WIFI_SEC_OPEN;
	/** DHCP: 192.168.1.1 */
	strM2MAPConfig.au8DHCPServerIP[0] = 0xC0;
	strM2MAPConfig.au8DHCPServerIP[1] = 0xA8;
	strM2MAPConfig.au8DHCPServerIP[2] = 0x01;
	strM2MAPConfig.au8DHCPServerIP[3] = 0x01;

	if (m2m_wifi_enable_ap(&strM2MAPConfig) != M2M_SUCCESS) {
		return -EIO;
	}

	return 0;
}

static int winc1500_mgmt_ap_disable(const struct device *dev)
{
	if (m2m_wifi_disable_ap() != M2M_SUCCESS) {
		return -EIO;
	}

	return 0;
}

static void winc1500_iface_init(struct net_if *iface)
{
	LOG_DBG("eth_init:net_if_set_link_addr:"
		"MAC Address %02X:%02X:%02X:%02X:%02X:%02X",
		w1500_data.mac[0], w1500_data.mac[1], w1500_data.mac[2],
		w1500_data.mac[3], w1500_data.mac[4], w1500_data.mac[5]);

	net_if_set_link_addr(iface, w1500_data.mac, sizeof(w1500_data.mac),
			     NET_LINK_ETHERNET);

	iface->if_dev->offload = &winc1500_offload;

	w1500_data.iface = iface;
}

static enum offloaded_net_if_types winc1500_get_wifi_type(void)
{
	return L2_OFFLOADED_NET_IF_TYPE_WIFI;
}

static const struct wifi_mgmt_ops winc1500_mgmt_ops = {
	.scan		= winc1500_mgmt_scan,
	.connect	= winc1500_mgmt_connect,
	.disconnect	= winc1500_mgmt_disconnect,
	.ap_enable	= winc1500_mgmt_ap_enable,
	.ap_disable	= winc1500_mgmt_ap_disable,
};
static const struct net_wifi_mgmt_offload winc1500_api = {
	.wifi_iface.iface_api.init = winc1500_iface_init,
	.wifi_iface.get_type = winc1500_get_wifi_type,
	.wifi_mgmt_api = &winc1500_mgmt_ops,
};

static int winc1500_init(const struct device *dev)
{
	tstrWifiInitParam param = {
		.pfAppWifiCb = winc1500_wifi_cb,
	};
	unsigned char is_valid;
	int ret;

	ARG_UNUSED(dev);

	w1500_data.connecting = false;
	w1500_data.connected = false;

	ret = m2m_wifi_init(&param);
	if (ret != M2M_SUCCESS) {
		LOG_ERR("m2m_wifi_init return error!(%d)", ret);
		return -EIO;
	}

	socketInit();
	registerSocketCallback(winc1500_socket_cb, NULL);

	if (m2m_wifi_get_otp_mac_address(w1500_data.mac, &is_valid) != M2M_SUCCESS) {
		LOG_ERR("Failed to get MAC address");
	}

	LOG_DBG("WINC1500 MAC Address from OTP (%d) "
		"%02X:%02X:%02X:%02X:%02X:%02X",
		is_valid,
		w1500_data.mac[0], w1500_data.mac[1], w1500_data.mac[2],
		w1500_data.mac[3], w1500_data.mac[4], w1500_data.mac[5]);

	if (m2m_wifi_set_scan_region(WINC1500_REGION) != M2M_SUCCESS) {
		LOG_ERR("Failed set scan region");
	}

	if (m2m_wifi_set_power_profile(PWR_LOW1) != M2M_SUCCESS) {
		LOG_ERR("Failed set power profile");
	}

	if (m2m_wifi_set_tx_power(TX_PWR_LOW) != M2M_SUCCESS) {
		LOG_ERR("Failed set tx power");
	}

	/* monitoring thread for winc wifi callbacks */
	k_thread_create(&winc1500_thread_data, winc1500_stack,
			CONFIG_WIFI_WINC1500_THREAD_STACK_SIZE,
			(k_thread_entry_t)winc1500_thread, NULL, NULL, NULL,
			K_PRIO_COOP(CONFIG_WIFI_WINC1500_THREAD_PRIO),
			0, K_NO_WAIT);
	k_thread_name_set(&winc1500_thread_data, "WINC1500");

	LOG_DBG("WINC1500 driver Initialized");

	return 0;
}

NET_DEVICE_OFFLOAD_INIT(winc1500, CONFIG_WIFI_WINC1500_NAME,
			winc1500_init, NULL, &w1500_data, NULL,
			CONFIG_WIFI_INIT_PRIORITY, &winc1500_api,
			CONFIG_WIFI_WINC1500_MAX_PACKET_SIZE);
