/* ``The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved via the world wide web at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
 * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
 * AB. All Rights Reserved.''
 * 
 *     $Id$
 */
#ifdef __WIN32__
#include <winsock2.h>
#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <errno.h>

static unsigned long param_zero = 0;
static unsigned long param_one = 1;
#define SET_BLOCKING(Sock) ioctlsocket((Sock),FIONBIO,&param_zero)
#define SET_NONBLOCKING(Sock) ioctlsocket((Sock),FIONBIO,&param_one)

#define ERROR_WOULDBLOCK WSAEWOULDBLOCK
#define ERROR_TIMEDOUT WSAETIMEDOUT 
#define ERROR_INPROGRESS WSAEINPROGRESS
#define GET_SOCKET_ERROR() WSAGetLastError()
#define MEANS_SOCKET_ERROR(Ret) ((Ret == SOCKET_ERROR))
#define IS_INVALID_SOCKET(Sock) ((Sock) == INVALID_SOCKET)

#elif VXWORKS
#include <vxWorks.h>
#include <hostLib.h>
#include <ifLib.h>
#include <sockLib.h>
#include <taskLib.h>
#include <inetLib.h>
#include <selectLib.h>
#include <sys/types.h>
#include <ioLib.h>
#include <unistd.h>

static unsigned long param_zero = 0;
static unsigned long param_one = 1;
#define SET_BLOCKING(Sock) ioctl((Sock),FIONBIO,(int)&param_zero)
#define SET_NONBLOCKING(Sock) ioctl((Sock),FIONBIO,(int)&param_one)
#define ERROR_WOULDBLOCK EWOULDBLOCK
#define ERROR_TIMEDOUT ETIMEDOUT 
#define ERROR_INPROGRESS EINPROGRESS
#define GET_SOCKET_ERROR() (errno)
#define MEANS_SOCKET_ERROR(Ret) ((Ret) == ERROR)
#define IS_INVALID_SOCKET(Sock) ((Sock) < 0)

#else /* other unix */
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#ifndef EWOULDBLOCK
#define ERROR_WOULDBLOCK EAGAIN
#else
#define ERROR_WOULDBLOCK EWOULDBLOCK
#endif
#define SET_BLOCKING(fd)  fcntl((fd), F_SETFL, \
				fcntl((fd), F_GETFL, 0) & ~O_NONBLOCK)
#define SET_NONBLOCKING(fd) fcntl((fd), F_SETFL, \
				  fcntl((fd), F_GETFL, 0) | O_NONBLOCK)
#define ERROR_TIMEDOUT ETIMEDOUT 
#define ERROR_INPROGRESS EINPROGRESS
#define GET_SOCKET_ERROR() (errno)
#define MEANS_SOCKET_ERROR(Ret) ((Ret) < 0)
#define IS_INVALID_SOCKET(Sock) ((Sock) < 0)

#endif

/* common includes */
#include "eidef.h"

#include <stdio.h> 
#include <stdlib.h>
#include <string.h>
#include "ei_portio.h"
#include "ei_internal.h"

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif

#ifdef HAVE_WRITEV
static int ei_writev_t(int fd,  struct  iovec  *iov,  int iovcnt, unsigned ms)
{
    int res;
    if (ms != 0) {
	fd_set writemask;
	struct timeval tv;
	tv.tv_sec = (time_t) (ms / 1000U);
	ms %= 1000U;
	tv.tv_usec = (time_t) (ms * 1000U);
	FD_ZERO(&writemask);
	FD_SET(fd,&writemask);
	switch (select(fd+1, NULL, &writemask, NULL, &tv)) {
	case -1 : 
	    return -1; /* i/o error */
	case 0:
	    return -2; /* timeout */
	default:
	    if (!FD_ISSET(fd, &writemask)) {
		return -1; /* Other error */
	    }
	}
    }
    res = writev(fd, iov, iovcnt);
    return (res < 0) ? -1 : res;
}

int ei_writev_fill_t(int fd,  const  struct  iovec  *iov,  int iovcnt, unsigned ms)
{
    int i;
    int done;
    struct iovec *iov_base = NULL;
    struct iovec *current_iov;
    int current_iovcnt;
    int sum;

    for (sum = 0, i = 0; i < iovcnt; ++i) {
	sum += iov[i].iov_len;
    }
    if (ms != 0U) {
	SET_NONBLOCKING(fd);
    } 
    current_iovcnt = iovcnt;
    current_iov = (struct iovec *) iov;
    done = 0;
    for (;;) {
	i = ei_writev_t(fd, current_iov, current_iovcnt, ms);
	if (i <= 0) { /* ei_writev_t should always return at least 1 */ 
	    if (ms != 0U) {
		SET_BLOCKING(fd);
	    }    
	    if (iov_base != NULL) {
		free(iov_base);
	    }
	    return (i);
	}
	done += i;

	if (done < sum) {
	    if (iov_base == NULL) {
		iov_base = malloc(sizeof(struct iovec) * iovcnt);
		memcpy(iov_base, iov, sizeof(struct iovec) * iovcnt);
		current_iov = iov_base;
	    }
	    while (i > 0) {
		if (i < current_iov[0].iov_len) {
		    current_iov[0].iov_len -= i;
		    i = 0;
		} else {
		    i -= current_iov[0].iov_len;
		    current_iov++;
		    current_iovcnt--;
		}
	    }
	} else {
	    break;
	}
    } 
    if (ms != 0U) {
	SET_BLOCKING(fd);
    }
    if (iov_base != NULL) {
	free(iov_base);
    }
    return (sum);
}


#endif

int ei_connect_t(int fd, void *sinp, int sin_siz, unsigned ms)
{
    int res;
    int error;
    int s_res;
    struct timeval tv;
    fd_set writefds;
    fd_set exceptfds;

    if (ms == 0) {
	res = connect(fd, sinp, sin_siz);
	return (res < 0) ? -1 : res;
    } else {
	SET_NONBLOCKING(fd);
	res = connect(fd, sinp, sin_siz);
	error = GET_SOCKET_ERROR();
	SET_BLOCKING(fd);
	if (!MEANS_SOCKET_ERROR(res)) {
	    return (res < 0) ? -1 : res;
	} else {
	    if (error != ERROR_WOULDBLOCK && 
		error != ERROR_INPROGRESS) {
		return -1;
	    } else {
		tv.tv_sec = (long) (ms/1000U);
		ms %= 1000U;
		tv.tv_usec = (long) (ms * 1000U);
		FD_ZERO(&writefds);
		FD_SET(fd,&writefds);
		FD_ZERO(&exceptfds);
		FD_SET(fd,&exceptfds);
		s_res = select(fd + 1, NULL, &writefds, &exceptfds, &tv);
		switch (s_res) {
		case 0:
		    return -2;
		case 1:
		    if (FD_ISSET(fd, &exceptfds)) {
			return  -1;
		    } else {
			return 0; /* Connect completed */
		    }
		default:
		    return -1;
		}
	    }
	} 
    }
}

int ei_accept_t(int fd, void  *addr,   void  *addrlen, unsigned ms)
{
    int res;
    if (ms != 0) {
	fd_set readmask;
	struct timeval tv;
	tv.tv_sec = (time_t) (ms / 1000U);
	ms %= 1000U;
	tv.tv_usec = (time_t) (ms * 1000U);
	FD_ZERO(&readmask);
	FD_SET(fd,&readmask);
	switch (select(fd+1, &readmask, NULL, NULL, &tv)) {
	case -1 : 
	    return -1; /* i/o error */
	case 0:
	    return -2; /* timeout */
	default:
	    if (!FD_ISSET(fd, &readmask)) {
		return -1; /* Other error */
	    }
	}
    }
    res = (int) accept(fd,addr,addrlen);
    return (res < 0) ? -1 : res;
}
    


static int ei_read_t(int fd, char* buf, int len, unsigned  ms)
{
    int res;
    if (ms != 0) {
	fd_set readmask;
	struct timeval tv;
	tv.tv_sec = (time_t) (ms / 1000U);
	ms %= 1000U;
	tv.tv_usec = (time_t) (ms * 1000U);
	FD_ZERO(&readmask);
	FD_SET(fd,&readmask);
	switch (select(fd+1, &readmask, NULL, NULL, &tv)) {
	case -1 : 
	    return -1; /* i/o error */
	case 0:
	    return -2; /* timeout */
	default:
	    if (!FD_ISSET(fd, &readmask)) {
		return -1; /* Other error */
	    }
	}
    }
    res = readsocket(fd, buf, len);
    return (res < 0) ? -1 : res;
}

static int ei_write_t(int fd, const char* buf, int len, unsigned  ms)
{
    int res;
    if (ms != 0) {
	fd_set writemask;
	struct timeval tv;
	tv.tv_sec = (time_t) (ms / 1000U);
	ms %= 1000U;
	tv.tv_usec = (time_t) (ms * 1000U);
	FD_ZERO(&writemask);
	FD_SET(fd,&writemask);
	switch (select(fd+1, NULL, &writemask, NULL, &tv)) {
	case -1 : 
	    return -1; /* i/o error */
	case 0:
	    return -2; /* timeout */
	default:
	    if (!FD_ISSET(fd, &writemask)) {
		return -1; /* Other error */
	    }
	}
    }
    res =  writesocket(fd, buf, len);
    return (res < 0) ? -1 : res;
}
	
/* 
 * Fill buffer, return buffer length, 0 for EOF, < 0 (and sets errno)
 * for error.  */
int ei_read_fill_t(int fd, char* buf, int len, unsigned  ms)
{
    int i,got=0;

    do {
	i = ei_read_t(fd, buf+got, len-got, ms);
	if (i <= 0)
	    return (i);
	got += i;
    } while (got < len);
    return (len);
    
} /* read_fill */

int ei_read_fill(int fd, char* buf, int len)
{
    return ei_read_fill_t(fd, buf, len, 0);
}

/* write entire buffer on fd  or fail (setting errno)
 */
int ei_write_fill_t(int fd, char *buf, int len, unsigned ms)
{
    int i,done=0;
    if (ms != 0U) {
	SET_NONBLOCKING(fd);
    }    
    do {
	i = ei_write_t(fd, buf+done, len-done, ms);
	if (i <= 0) {
	    if (ms != 0U) {
		SET_BLOCKING(fd);
	    }    
	    return (i);
	}
	done += i;
    } while (done < len);
    if (ms != 0U) {
	SET_BLOCKING(fd);
    }
    return (len);
}

int ei_write_fill(int fd, char *buf, int len) 
{
    return ei_write_fill_t(fd, buf, len, 0);
}

