#ifndef _zio_hpp_z_
#define _zio_hpp_z_

#ifdef _MSC_VER
 #include <WinSock2.h>
 #include <Windows.h>
#else
 #include <unistd.h>
 #include <fcntl.h>          
 #include <sys/types.h>          
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
 #include <errno.h>
#endif

#include <string.h>
#include <string>

#include "log.h"
struct zio
{
private:
	template<typename T> 
	static int setoption(int fd,int level,int name,const T&opt)
	{
		return setsockopt(fd,level,name,(char*)&opt,sizeof(T));
	}

	static sockaddr* build_addr(sockaddr_in*addr,const char*ip,int port)
	{
		return build_addr(addr,ntohl(inet_addr(ip)),port);
	}

	static sockaddr* build_addr(sockaddr_in*addr,int ip,int port)
	{
		memset(addr, 0, sizeof(sockaddr_in));

		addr->sin_family = AF_INET; 
		addr->sin_addr.s_addr = htonl(ip); 
		addr->sin_port = htons(port); 

		return (sockaddr*)addr;
	}

	static int bind(int fd,sockaddr*sa)
	{
		return ::bind(fd,sa,sizeof(sockaddr));
	}

	static int bind(int fd,sockaddr_in*sa)
	{
		return zio::bind(fd,(sockaddr*)sa);
	}

	static int connect(int fd,sockaddr*sa)
	{
		int rc=::connect(fd,sa,sizeof(sockaddr));
		if(rc<0)
		{
			char name[128];
			sprintf(name,"%s:%d",inet_ntoa(((sockaddr_in*)sa)->sin_addr),ntohs(((sockaddr_in*)sa)->sin_port));
			log_errno("zio:socket connect to %s",name);
		}

		return rc;
	}

	static int connect(int fd,sockaddr_in*sa)
	{
		return zio::connect(fd, (sockaddr*)sa);
	}

	static int bind(int fd,const char* ip,int port)
	{
		sockaddr_in si;
		return zio::bind(fd,build_addr(&si,ip,port));
	}

	static int bind(int fd, int inaddr,int port)
	{
		sockaddr_in si;
		return zio::bind(fd,build_addr(&si,inaddr,port));
	}

	static int connect(int fd,int inaddr,int port)
	{
		sockaddr_in si;
		return zio::connect(fd,build_addr(&si,inaddr,port));
	}
public:

	static int connect(int fd,const char*ip,int port)
	{
		sockaddr_in si;
		return zio::connect(fd,build_addr(&si,ip,port));
	}

	static int listen(int fd,const char*ip,int port,int backlog=1024)
	{
		setnodelay(fd);
		setreuseaddr(fd);

		if(zio::bind(fd,ip,port)<0)
		{
			log_errno("zio:socket bind (%d,'%s',%d)",fd,ip,port);
			return -1;
		}

		return ::listen(fd,backlog);
	}

	static int setnodelay (int fd, bool nodelay = true)
	{
		int v = nodelay ? 1 : 0;

		return setoption (fd,IPPROTO_TCP, TCP_NODELAY, v);
	}

	static 	int setiobuf (int fd, int send_size,int recv_size)
	{
		if(send_size && setoption (fd, SOL_SOCKET, SO_SNDBUF, send_size)<0)
		{
			log_errno("zio:setoption(%d,SO_SNDBUF,%d)",fd,send_size);
			return -1;
		}

		if(recv_size && setoption (fd, SOL_SOCKET, SO_RCVBUF, recv_size)<0)
		{
			log_errno("zio:setoption(%d,SO_RECBUF,%d)",fd,recv_size);
			return -1;
		}

		return 0;
	}

	static 	int setreuseaddr (int fd, bool reuse = true)
	{
		int v = reuse ? 1 : 0;

		return setoption (fd, SOL_SOCKET, SO_REUSEADDR, v);
	}

	static int setrecvtimeo (int fd, int tmout_ms)
	{
#ifdef _MSC_VER
		int tv = tmout_ms;
#else
		struct timeval tv = { tmout_ms / 1000, tmout_ms % 1000 * 1000 };
#endif

		return setoption (fd,SOL_SOCKET, SO_RCVTIMEO, tv);
	}

	static int setsendtimeo (int fd, int tmout_ms)
	{
#ifdef _MSC_VER
		int tv = tmout_ms;
#else
		struct timeval tv = { tmout_ms / 1000, tmout_ms % 1000 * 1000 };
#endif

		return setoption (fd,SOL_SOCKET, SO_SNDTIMEO, tv);
	}

	static int setblocking (int fd, bool b=true)
	{
#ifdef _MSC_VER
#else
		int opts = fcntl (fd, F_GETFL);
		if (opts < 0)
			return -1;

		opts |= b ?(opts& ~O_NONBLOCK) :(opts|O_NONBLOCK);

		if (fcntl (fd, F_SETFL, opts) < 0)
			return -1;

		return 0;
#endif
	}

	static int close(int fd)
	{
		if(fd==-1)
			return 0;

#ifdef _MSC_VER
		return ::closesocket(fd);
#else
		return ::close(fd);
#endif
	}

	static int accept(int serv_fd,char *name)
	{
		struct sockaddr_in addr={0};
		int fd=-1;
		for(;;)
		{
			socklen_t addrlen=sizeof(struct sockaddr_in);
			fd=(int)::accept(serv_fd,(sockaddr*)&addr,&addrlen);
			if(fd<0)
			{
#ifdef _MSC_VER
#else
				if (errno == EINTR || errno == ECONNABORTED)
					continue;
#endif
			}

			if(fd>=0)
			{
				sprintf(name,"%s:%d",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
				log_info("zio:client %d(%s) connected",fd,name);
			}
			break;
		}
		return fd;
	}

	static int build_stream()
	{
		return (int)::socket(AF_INET,SOCK_STREAM,0);
	}

	static int write(int fd, const void *buff, int count)
	{
		const char*buf=(const char*)buff;
		for(;;)
		{
			int rc  = ::write(fd,buf,count);
			if (rc == -1) 
			{
				switch(errno)
				{
#ifndef _MSC_VER
					case EINTR:
						continue;
#endif
					case EAGAIN:
#ifdef _MSC_VER
					case EWOULDBLOCK:
#endif
						return -2;
					default:
						log_errno("zio:socket %d write",fd);
						return -1;
				}
			}

			return rc;
		}
	}

	static int read(int fd, void *buff, int count)
	{
		char*buf=(char*)buff;
		for(;;)
		{
			int rc = ::read(fd,buf,count);
			if (rc == -1)
			{
				switch(errno)
				{
#ifndef _MSC_VER
					case EINTR:
						continue;
#endif
					case EAGAIN:
#ifdef _MSC_VER
					case EWOULDBLOCK:
#endif
						return -2;
					default:
						log_errno("zio:socket %d read",fd);
						return -1;
				}
			}

			return rc;
		}
	}
	static int writev(int fd, const void*buff, int count)
	{
		const char*buf=(const char*)buff;
		int rc, pos = 0;
		while(pos != count) 
		{
			if((rc=zio::write(fd,buf,count-pos))>0)
			{
				pos += rc;
				buf += rc;
				continue;
			}

			if(rc==-1) 
				break;
		}

		return pos==0?-1:pos;
	}

	static int readv(int fd, void *buff, int count)
	{
		char*buf=(char*)buff;
		int rc, pos = 0;
		while(pos != count) 
		{
			if((rc=zio::read(fd,buf,count-pos))>0)
			{
				pos += rc;
				buf += rc;
				continue;
			}

			if(rc==0)
				return pos>0?pos:0;

			if(rc==-1) 
				break;
		}

		return pos==0?-1:pos;
	}

	static int listen_on(int port)
	{
		int fd=zio::build_stream();
		zio::setreuseaddr(fd,true);
		int rc=zio::listen(fd,"0.0.0.0",port,1024);
		if(rc<0)
		{
			zio::close(fd);
			return -1;
		}

		return fd;
	}
};

#endif

