/*
 * ESPRSSIF MIT License
 *
 * Copyright (c) 2016 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
 *
 * Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP8266 only, in which case,
 * it is free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#ifndef ESP_SOCKET_H_
#define ESP_SOCKET_H_

#include "lwip/inet.h"
#include "lwip/opt.h"
#include "sys/ringbuf.h"

#if 0
#define ESP_LOG			os_printf
#else
#define ESP_LOG(...)
#endif

#define NUM_SOCKETS 3

#define ESP_OK          0    /* No error, everything OK. */
#define ESP_MEM        -1    /* Out of memory error.     */
#define ESP_TIMEOUT    -3    /* Timeout.                 */
#define ESP_RTE        -4    /* Routing problem.         */
#define ESP_INPROGRESS  -5   /* Operation in progress    */
#define ESP_MAXNUM		-7	 /* Total number exceeds the set maximum*/

#define ESP_ABRT       -8    /* Connection aborted.      */
#define ESP_RST        -9    /* Connection reset.        */
#define ESP_CLSD       -10   /* Connection closed.       */
#define ESP_CONN       -11   /* Not connected.           */

#define ESP_ARG        -12   /* Illegal argument.        */
#define ESP_IF		   -14	 /* Low_level error			 */
#define ESP_ISCONN     -15   /* Already connected.       */

typedef enum{
	NETCONN_STATE_NONE = 0,
	NETCONN_STATE_ESTABLISHED,
	NETCONN_STATE_WRITE,
	NETCONN_STATE_READ,
	NETCONN_STATE_CLOSED,
	NETCONN_STATE_ERROR,
	NETCONN_STATE_SUMNUM
}netconn_state;

extern int __attribute__((weak)) espconn_mbedtls_parse_internal(int socket, sint8 error);
extern int __attribute__((weak)) espconn_mbedtls_parse_thread(int socket, int event, int error);

#if (!defined(lwIP_unlikely))
#define lwIP_unlikely(Expression)	!!(Expression)
#endif

#define lwIP_ASSERT(Expression) do{if (!(Expression)) ESP_LOG("%d\n", __LINE__);}while(0)

#define lwIP_REQUIRE_ACTION(Expression,Label,Action) \
	do{\
		if (lwIP_unlikely(!(Expression))) \
		{\
			ESP_LOG("%d\n", __LINE__);\
			{Action;}\
			goto Label;\
		}\
	}while(0)

#define lwIP_REQUIRE_NOERROR(Expression,Label) \
	do{\
		int LocalError;\
		LocalError = (int)Expression;\
		if (lwIP_unlikely(LocalError != 0)) \
		{\
			ESP_LOG("%d 0x%x\n", __LINE__, LocalError);\
			goto Label;\
		}\
	}while(0)

#define lwIP_REQUIRE_NOERROR_ACTION(Expression,Label,Action) \
	do{\
		int LocalError;\
		LocalError = (int)Expression;\
		if (lwIP_unlikely(LocalError != 0)) \
		{\
			ESP_LOG("%d\n", __LINE__);\
			{Action;}\
			goto Label;\
		}\
	}while(0)

typedef enum{
	NETCONN_EVENT_NONE = 0,
	NETCONN_EVENT_ESTABLISHED = 1,
	NETCONN_EVENT_RECV = 2,
	NETCONN_EVENT_SEND = 3,
	NETCONN_EVENT_ERROR = 4,
	NETCONN_EVENT_CLOSE = 5,
	NETCONN_EVENT_SUMNUM = 6
}netconn_event;

typedef enum _netconn_type {
	NETCONN_INVALID = 0,
	/* ESPCONN_TCP Group */
	NETCONN_TCP = 0x10,
	/* ESPCONN_UDP Group */
	NETCONN_UDP = 0x20,
} netconn_type;

/* members are in network byte order */
struct sockaddr_in {
	u8_t sin_len;
	u8_t sin_family;
	u16_t sin_port;
	struct in_addr sin_addr;
	char sin_zero[8];
};

/** A netconn descriptor */
typedef struct _lwIP_netconn{
	/** flags blocking or nonblocking defines */
	uint8 flags;
	/** type of the lwIP_netconn (TCP, UDP) */
	netconn_type type;
	/** current state of the lwIP_netconn */
	netconn_state state;
	/** the lwIP internal protocol control block */
	struct tcp_pcb *tcp;

	/**the new socket is unknown and sync*/
	void *acceptmbox;

	/**the lwIP internal buffer control block*/
	ringbuf *readbuf;
	/** only used for socket layer */
	int socket;
}lwIP_netconn, *lwIPNetconn;

/** Contains all internal pointers and states used for a socket */
typedef struct _lwIP_sock{
	/** sockets currently are built on lwIP_netconn, each socket has one lwIP_netconn */
	lwIP_netconn *conn;
	/** data that was left from the previous read */
	u32_t recv_index;
	u32_t send_index;
	u32_t recv_data_len;
	void  *send_buffer;
}lwIP_sock;

typedef uint8 sa_family_t;

struct sockaddr {
	uint8 sa_len;
	sa_family_t sa_family;
	char sa_data[14];
};

typedef uint32 socklen_t;

#define NETCONNTYPE_GROUP(t)         ((t)&0xF0)
#define NETCONNTYPE_DATAGRAM(t)      ((t)&0xE0)

#define AF_UNSPEC       0
#define AF_INET         2

/* Socket protocol types (TCP/UDP/RAW) */
#define SOCK_STREAM     1
#define SOCK_DGRAM      2
#define SOCK_RAW        3

#define lwIPThreadPrio 25
#define lwIPThreadQueueLen 15

/*
 * Option flags per-socket. These must match the SOF_ flags in ip.h (checked in init.c)
 */
#define  SO_DEBUG       0x0001 /* Unimplemented: turn on debugging info recording */
#define  SO_ACCEPTCONN  0x0002 /* socket has had listen() */
#define  SO_REUSEADDR   0x0004 /* Allow local address reuse */
#define  SO_KEEPALIVE   0x0008 /* keep connections alive */
#define  SO_DONTROUTE   0x0010 /* Unimplemented: just use interface addresses */
#define  SO_BROADCAST   0x0020 /* permit to send and to receive broadcast messages (see IP_SOF_BROADCAST option) */
#define  SO_USELOOPBACK 0x0040 /* Unimplemented: bypass hardware when possible */
#define  SO_LINGER      0x0080 /* linger on close if data present */
#define  SO_OOBINLINE   0x0100 /* Unimplemented: leave received OOB data in line */
#define  SO_REUSEPORT   0x0200 /* Unimplemented: allow local address & port reuse */

/*
 * Additional options, not kept in so_options.
 */
#define SO_SNDBUF    0x1001    /* Unimplemented: send buffer size */
#define SO_RCVBUF    0x1002    /* receive buffer size */
#define SO_TYPE      0x1008    /* get socket type */

#define IPPROTO_IP      0
#define IPPROTO_TCP     6
#define IPPROTO_UDP     17

#define  SOL_SOCKET  0xfff    /* options for socket level */

#if LWIP_TCP
/*
 * Options for level IPPROTO_TCP
 */
#define TCP_NODELAY    0x01    /* don't delay send to coalesce packets */
#define TCP_KEEPALIVE  0x02    /* send KEEPALIVE probes when idle for pcb->keep_idle milliseconds */
#define TCP_KEEPIDLE   0x03    /* set pcb->keep_idle  - Same as TCP_KEEPALIVE, but use seconds for get/setsockopt */
#define TCP_KEEPINTVL  0x04    /* set pcb->keep_intvl - Use seconds for get/setsockopt */
#define TCP_KEEPCNT    0x05    /* set pcb->keep_cnt   - Use number of probes sent for get/setsockopt */
#endif /* LWIP_TCP */

/* commands for fnctl */
#ifndef F_GETFL
#define F_GETFL 3
#endif
#ifndef F_SETFL
#define F_SETFL 4
#endif

/* File status flags and file access modes for fnctl,
 these are bits in an int. */
#ifndef O_NONBLOCK
#define O_NONBLOCK  1 /* nonblocking I/O */
#endif
#ifndef O_NDELAY
#define O_NDELAY    1 /* same as O_NONBLOCK, for compatibility */
#endif

struct timeval {
	long tv_sec; /* seconds */
	long tv_usec; /* and microseconds */
};
/* Flags for struct netconn.flags (u8_t) */
/** TCP: when data passed to netconn_write doesn't fit into the send buffer,
 this temporarily stores whether to wake up the original application task
 if data couldn't be sent in the first try. */
#define NETCONN_FLAG_WRITE_DELAYED            0x01
/** Should this netconn avoid blocking? */
#define NETCONN_FLAG_NON_BLOCKING             0x02
/** Was the last connect action a non-blocking one? */
#define NETCONN_FLAG_IN_NONBLOCKING_CONNECT   0x04
/** If this is set, a TCP netconn must call netconn_recved() to update
 the TCP receive window (done automatically if not set). */
#define NETCONN_FLAG_NO_AUTO_RECVED           0x08
/** If a nonblocking write has been rejected before, poll_tcp needs to
 check if the netconn is writable again */
#define NETCONN_FLAG_CHECK_WRITESPACE         0x10

/** Set the blocking status of netconn calls (@todo: write/send is missing) */
#define netconn_set_nonblocking(conn, val)  do { if(val) { \
  (conn)->flags |= NETCONN_FLAG_NON_BLOCKING; \
} else { \
  (conn)->flags &= ~ NETCONN_FLAG_NON_BLOCKING; }} while(0)
/** Get the blocking status of netconn calls (@todo: write/send is missing) */
#define netconn_is_nonblocking(conn)        (((conn)->flags & NETCONN_FLAG_NON_BLOCKING) != 0)

/* FD_SET used for lwip_select */
#ifndef FD_SET
#undef  FD_SETSIZE
/* Make FD_SETSIZE match NUM_SOCKETS in socket.c */
#define FD_SETSIZE    NUM_SOCKETS
#define FD_SET(n, p)  ((p)->fd_bits[(n)/8] |=  (1 << ((n) & 7)))
#define FD_CLR(n, p)  ((p)->fd_bits[(n)/8] &= ~(1 << ((n) & 7)))
#define FD_ISSET(n,p) ((p)->fd_bits[(n)/8] &   (1 << ((n) & 7)))
#define FD_ZERO(p)    os_memset((void*)(p),0,sizeof(*(p)))

typedef struct fd_set {
	unsigned char fd_bits[(FD_SETSIZE + 7) / 8];
} fd_set;

#endif /* FD_SET */

void lwip_socket_init(void);

int lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen);
int lwip_bind(int s, const struct sockaddr *name, socklen_t namelen);
int lwip_shutdown(int s, int how);
int lwip_getpeername(int s, struct sockaddr *name, socklen_t *namelen);
int lwip_getsockname(int s, struct sockaddr *name, socklen_t *namelen);
int lwip_getsockopt(int s, int level, int optname, void *optval,socklen_t *optlen);
int lwip_setsockopt(int s, int level, int optname, const void *optval,socklen_t optlen);
int lwip_close(int s);
int lwip_connect(int s, const struct sockaddr *name, socklen_t namelen);
int lwip_listen(int s, int backlog);
int lwip_recv(int s, void *mem, size_t len, int flags);
int lwip_read(int s, void *mem, size_t len);
int lwip_recvfrom(int s, void *mem, size_t len, int flags,struct sockaddr *from, socklen_t *fromlen);
int lwip_send(int s, const void *dataptr, size_t size, int flags);
int lwip_sendto(int s, const void *dataptr, size_t size, int flags,const struct sockaddr *to, socklen_t tolen);
int lwip_socket(int domain, int type, int protocol);
int lwip_write(int s, const void *dataptr, size_t size);
int lwip_select(int maxfdp1, fd_set *readset, fd_set *writeset,fd_set *exceptset, struct timeval *timeout);
int lwip_ioctl(int s, long cmd, void *argp);
int lwip_fcntl(int s, int cmd, int val);
uint32_t lwip_getul(char *str);

#define accept(a,b,c)         lwip_accept(a,b,c)
#define bind(a,b,c)           lwip_bind(a,b,c)
#define shutdown(a,b)         lwip_shutdown(a,b)
#define closesocket(s)        lwip_close(s)
#define connect(a,b,c)        lwip_connect(a,b,c)
#define getsockname(a,b,c)    lwip_getsockname(a,b,c)
#define getpeername(a,b,c)    lwip_getpeername(a,b,c)
#define setsockopt(a,b,c,d,e) lwip_setsockopt(a,b,c,d,e)
#define getsockopt(a,b,c,d,e) lwip_getsockopt(a,b,c,d,e)
#define listen(a,b)           lwip_listen(a,b)
#define recv(a,b,c,d)         lwip_recv(a,b,c,d)
#define recvfrom(a,b,c,d,e,f) lwip_recvfrom(a,b,c,d,e,f)
#define send(a,b,c,d)         lwip_send(a,b,c,d)
#define sendto(a,b,c,d,e,f)   lwip_sendto(a,b,c,d,e,f)
#define socket(a,b,c)         lwip_socket(a,b,c)
#define select(a,b,c,d,e)     lwip_select(a,b,c,d,e)
#define ioctlsocket(a,b,c)    lwip_ioctl(a,b,c)

#define read(a,b,c)           lwip_read(a,b,c)
#define write(a,b,c)          lwip_write(a,b,c)
#define close(s)              lwip_close(s)
#define getul(s)			  lwip_getul(s)

extern int system_overclock(void);
extern int system_restoreclock(void);
extern char *sys_itoa(int n);

#endif /* ESPCONN_SOCKT_H_ */