/*
    zsock.c - socket utilities
    Copyright (C) 2011-2020 Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include <libziaint.h>

#include <zsock.h>

#include <zdebug.h>
#include <zerror.h>

#include <errno.h>
#include <glib.h>
#include <string.h>

#ifdef Z_HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#ifdef Z_HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef Z_HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef Z_HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#ifdef Z_HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef Z_HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef Z_MSC
#pragma warning(disable : 4996)
#endif

int z_sock_nonblock(int sock, int nonblock){
#ifdef Z_UNIX_CYGWIN_ANDROID
	return fcntl(sock, F_SETFL, nonblock ? O_NONBLOCK : 0); 
#else	
	u_long iMode = 0;
	// If iMode = 0, blocking is enabled; 
    // If iMode != 0, non-blocking mode is enabled.
	iMode = nonblock;
	if (ioctlsocket(sock, FIONBIO, &iMode) != NO_ERROR) return -1;
    return 0;
#endif
} 

int z_sock_reuse(int sock, int reuseaddr){
#ifdef Z_MSC_MINGW
    return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuseaddr, sizeof(reuseaddr));
#else
    return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
#endif
}

int z_sock_broadcast(int sock, int broadcast){
#ifdef Z_MSC_MINGW
    return setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast));
#else
    return setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
#endif
}

int z_sock_wouldblock(int err){
#ifdef Z_MSC_MINGW
        if (err == WSAEWOULDBLOCK) return 1;
		return 0;
#else
        if (err==EWOULDBLOCK || err == EALREADY || err == EINPROGRESS) return 1;
		return 0;
#endif
}

int z_sock_error(int sock){
    socklen_t optlen;
    int ret, err;

    optlen = sizeof(err);
#ifdef Z_MSC_MINGW
    ret = getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&err, &optlen);
#else
    ret = getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *)&err, &optlen);
#endif
    //dbg("getsockopt(SO_ERROR) returns %d err is %d\n", ret, err);
    if (ret != 0) return errno;
    if (err != 0) return err;
    return 0;
}

void *z_sockadr_get_addr(union zsockaddr *sa){
	switch (sa->sa.sa_family){
		case AF_INET:
			return &sa->in.sin_addr;
#ifdef AF_INET6
		case AF_INET6:
			return &sa->in6.sin6_addr;
#endif
		default:
			return &sa->sa.sa_data;
	}
}

int z_sockadr_get_len(union zsockaddr *sa){
	switch (sa->sa.sa_family){
		case AF_INET:
			return sizeof(struct sockaddr_in);
#ifdef AF_INET6
		case AF_INET6:
			return sizeof(struct sockaddr_in6);
#endif
		default:
			return sizeof(struct sockaddr);
	}
}

char *z_sock_ntoa(GString *gs, int family, union zsockaddr *sa){
	char buf[256];
#ifdef Z_MSC_MINGW
	DWORD size = sizeof(buf);

	if (WSAAddressToString(&sa->sa, sizeof(union zsockaddr), NULL, buf, &size)){
		g_string_append_printf(gs, "[Unknown address, family=%d, error=%d]", family, z_sock_errno);
		return gs->str;
	}
	g_string_append_printf(gs, "%s", buf);
#else
	unsigned short port = 0;

	if (!inet_ntop(family, z_sockadr_get_addr(sa), buf, sizeof(buf))){		 // inet_ntop is not on XP
		g_string_append_printf(gs, "[Unknown address, family=%d, error=%d]", family, z_sock_errno);
		return gs->str;
	}
	g_string_append_printf(gs, "%s", buf);
	switch (family){
		case AF_INET:
			port = ntohs(sa->in.sin_port);
			break;
#ifdef AF_INET6
		case AF_INET6:
			port = ntohs(sa->in6.sin6_port);
			break;
#endif
	}
	if (port) g_string_append_printf(gs, ":%u", port);
#endif
	return gs->str;
}

int z_sock_printf(int fd, const char *m, ...){
	va_list l;
	char *c;
	int ret;

    va_start(l, m);
	c = g_strdup_vprintf(m, l);
	ret = send(fd, c, strlen(c), 0);
    va_end(l);
	return ret;
}



int z_pipe(int fds[2]){
//#ifndef Z_HAVE_UNISTD_H
#ifdef WIN32
	int port, master;
    struct sockaddr_in sin;
    
	master = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (master == INVALID_SOCKET) {
        zinternal("pipe: invalid master socket %d", WSAGetLastError());
        return -1;
    }

	for (port = 2000; port < 2500; port++){

		if (port == 2500 - 1){
			shutdown(master, SD_BOTH);
            zinternal("pipe: no free socket");
			return -1;
		}
		sin.sin_family = AF_INET;
		sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
		sin.sin_port = htons(port);
		if (bind(master, (struct sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR){
			//if (WSAGetLastError() == WSAEADDRINUSE) continue;
			continue;
		}
		break;
	}

	if (listen(master, 5) == SOCKET_ERROR){
		shutdown(master, SD_BOTH);
        zinternal("pipe: listen socket error");
		return -1;
	}

	fds[0] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (fds[0] == INVALID_SOCKET){
		shutdown(master, SD_BOTH);
        zinternal("pipe: invalid socket 0");
		return -1;
	}

	if (z_sock_nonblock(fds[0], 1)){
		shutdown(master, SD_BOTH);
		shutdown(fds[0], SD_BOTH);
        zinternal("pipe: can't set O_NONBLOCK");
		return -1;
	}

	if (connect(fds[0], (struct sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR){
		int err = WSAGetLastError();
		if (err != WSAEWOULDBLOCK){
			shutdown(master, SD_BOTH);
			shutdown(fds[0], SD_BOTH);
            zinternal("pipe: can't connect");
			return -1;
		}
	}

	if (z_sock_nonblock(fds[0], 0)){
		shutdown(master, SD_BOTH);
		shutdown(fds[0], SD_BOTH);
        zinternal("pipe: can't clear O_NONBLOCK");
		return -1;
	}

	fds[1] = accept(master, NULL, NULL);
	if (fds[1] == INVALID_SOCKET){
		shutdown(master, SD_BOTH);
		shutdown(fds[0], SD_BOTH);
        zinternal("pipe: invalid socket accepted");
		return -1;
	}

	shutdown(master, SD_BOTH);	
	return 0;
#else
	return pipe(fds);
#endif
}

int z_pipe_write(int fd, const void *buf, int count){
#ifdef Z_MSC_MINGW
	return send(fd, (char *)buf, count, 0);
#else
	return write(fd, buf, count);
#endif
}

int z_pipe_read(int fd, void *buf, int count){
#ifdef Z_MSC_MINGW
	return recv(fd, (char *)buf, count, 0);
#else
	return read(fd, buf, count);
#endif
}

int z_pipe_close(int fd){
#ifdef Z_MSC_MINGW
	if ((SOCKET)fd == INVALID_SOCKET || (SOCKET)fd == SOCKET_ERROR) return 0;
	return closesocket((SOCKET)fd);	
#else
	return close(fd);
#endif
}

#ifdef Z_MSC_MINGW
int inet_aton(const char *cp, struct in_addr *inp){
	unsigned long addr = inet_addr(cp);
	if (addr == INADDR_NONE) return 0; // invalid
	inp->S_un.S_addr = addr;
	return 1; // valid
}
#endif

int z_sock_aton(const char *hostname, int port, union zsockaddr *sa){
    int ret;

    ret = inet_aton(hostname, &(sa->in.sin_addr));
    if (ret) { //valid 
        sa->in.sin_family = AF_INET;
        sa->in.sin_port = htons(port);
    }else{
#ifdef Z_MSC_MINGW
		char *rwhost = g_strdup(hostname);
		int len = sizeof(struct sockaddr_in6);
		ret = !WSAStringToAddress(rwhost, AF_INET6, NULL, (struct sockaddr *)&sa->in6, &len); // 0 success, SOCKET_ERROR failed
		g_free(rwhost);
#else
		ret = inet_pton(AF_INET6, hostname, &(sa->in6.sin6_addr));
#endif
        if (ret) { // valid
            sa->in6.sin6_family = AF_INET6;
            sa->in6.sin6_port = htons(port);
        }else{
            sa->in.sin_family = -1;
            return -1;
        }
    }
    return 0;
}

void z_sock_set_errno(int err){
#ifdef Z_MSC_MINGW
    WSASetLastError(err);
#else
    errno = err;
#endif
}

int z_sock_connect(int sock, union zsockaddr *sa, int timeout_ms){
    int ret, err;
    struct sockaddr *xy = &(sa->sa);

	ret = z_sock_nonblock(sock, 1);
	if (ret < 0) return ret;

    ret = connect(sock, xy, z_sockadr_get_len(sa));
    err = z_sock_errno;
    dbg("connect=%d errno=%d xy=%p\n", ret, err, xy);
    if (ret < 0 && z_sock_wouldblock(err)){
        fd_set fds;
        struct timeval tv;
        
        FD_ZERO(&fds);
        FD_SET(sock, &fds);
        tv.tv_sec = timeout_ms / 1000;
        tv.tv_usec = (timeout_ms % 1000) * 1000;
        ret = select(sock + 1, NULL, &fds, NULL, &tv);
        if (ret < 0) return ret; // error
        if (ret == 0) { // timeout
#ifdef Z_MSC_MINGW
            z_sock_set_errno(WSAETIMEDOUT);
#else
            z_sock_set_errno(ETIMEDOUT);    
#endif
            return -1;
        }
        // socket available for writing - connected or rejected
        if (z_sock_error(sock) != 0){
            // keep errno/WSAGetLastError
            return -1;
        }
        return 0;
    }
    return ret;
}


