/*
    Copyright (C) 1995-1997
	Rainer Schnitker, Heeper Str. 283, 33607 Bielefeld
	email: rainer@mathematik.uni-bielefeld.de

    All rights reserved
*/

#include <rsxnt.h>
#include <rsxntso.h>
#include <string.h>

static int _rsxnt_socket_close	(int);
static int _rsxnt_socket_ioctl	(int, int, int);
static int _rsxnt_socket_read	(int, void *, size_t);
static int _rsxnt_socket_write	(int, const void *, size_t);
static int _rsxnt_socket_fcntl	(int, int, int);
static int _rsxnt_socket_select (int, struct _fd_set *, struct _fd_set *, struct _fd_set *, const struct timeval *);

#ifdef USE_WINSOCK2

static const char * winsock_dll = "WS2_32.DLL";
static const char * pszEnumProtocol = "WSAEnumProtocolsA";
static const int iWinsockVersionMajor = 2;
static const int iWinsockVersionMinor = 0;

#else /* WINSOCK 1.1 */

static const char * winsock_dll = "WSOCK32.DLL";
static const char * pszEnumProtocol = "EnumProtocolsA";
static const int iWinsockVersionMajor = 1;
static const int iWinsockVersionMinor = 1;

#endif /* USE_WINSOCK2 */

typedef int WINAPI (*LPFN_WSAFDISSET) (SOCKET, win_fd_set *);

LPFN_WSASTARTUP 	 wsa_WSAStartup       ;
LPFN_WSACLEANUP 	 wsa_WSACleanup       ;
LPFN_WSAGETLASTERROR	 wsa_WSAGetLastError  ;
LPFN_GETSOCKOPT 	 wsa_getsockopt       ;
LPFN_CLOSESOCKET	 wsa_closesocket      ;
LPFN_RECV		 wsa_recv	      ;
LPFN_SEND		 wsa_send	      ;
LPFN_IOCTLSOCKET	 wsa_ioctlsocket      ;
LPFN_SOCKET		 wsa_socket	      ;
LPFN_BIND		 wsa_bind	      ;
LPFN_LISTEN		 wsa_listen	      ;
LPFN_ACCEPT		 wsa_accept	      ;
LPFN_SELECT		 wsa_select	      ;
LPFN_CONNECT		 wsa_connect	      ;
LPFN_SETSOCKOPT 	 wsa_setsockopt       ;
LPFN_GETSOCKNAME	 wsa_getsockname      ;
LPFN_GETPEERNAME	 wsa_getpeername      ;
LPFN_GETHOSTBYNAME	 wsa_gethostbyname    ;
LPFN_GETHOSTBYADDR	 wsa_gethostbyaddr    ;
LPFN_GETSERVBYNAME	 wsa_getservbyname    ;
LPFN_GETSERVBYPORT	 wsa_getservbyport    ;
LPFN_GETPROTOBYNAME	 wsa_getprotobyname   ;
LPFN_GETPROTOBYNUMBER	 wsa_getprotobynumber ;
LPFN_GETHOSTNAME	 wsa_gethostname      ;
LPFN_SHUTDOWN		 wsa_shutdown	      ;
LPFN_WSAENUMPROTOCOLS	 wsa_WSAEnumProtocols ;
static LPFN_WSAFDISSET	 wsa_WSAFDIsSet;

static BOOL do_load_winsock (BOOL loadit)
{
    static HMODULE hWSA = NULL;

    if (loadit == FALSE) {
	if (hWSA)
	    if (FreeLibrary (hWSA) != FALSE)
		hWSA = NULL;
	return TRUE;
    }
    else {
	if (!hWSA) {
	    hWSA = LoadLibrary (winsock_dll);
	    if (!hWSA)
		return FALSE;
	}

	if (   !(wsa_WSAStartup       = (LPFN_WSASTARTUP)      GetProcAddress (hWSA, "WSAStartup"))
	    || !(wsa_WSACleanup       = (LPFN_WSACLEANUP)      GetProcAddress (hWSA, "WSACleanup"))
	    || !(wsa_WSAGetLastError  = (LPFN_WSAGETLASTERROR) GetProcAddress (hWSA, "WSAGetLastError"))
	    || !(wsa_getsockopt       = (LPFN_GETSOCKOPT)      GetProcAddress (hWSA, "getsockopt"))
	    || !(wsa_closesocket      = (LPFN_CLOSESOCKET)     GetProcAddress (hWSA, "closesocket"))
	    || !(wsa_recv	      = (LPFN_RECV)	       GetProcAddress (hWSA, "recv"))
	    || !(wsa_send	      = (LPFN_SEND)	       GetProcAddress (hWSA, "send"))
	    || !(wsa_ioctlsocket      = (LPFN_IOCTLSOCKET)     GetProcAddress (hWSA, "ioctlsocket"))
	    || !(wsa_socket	      = (LPFN_SOCKET)	       GetProcAddress (hWSA, "socket"))
	    || !(wsa_bind	      = (LPFN_BIND)	       GetProcAddress (hWSA, "bind"))
	    || !(wsa_listen	      = (LPFN_LISTEN)	       GetProcAddress (hWSA, "listen"))
	    || !(wsa_select	      = (LPFN_SELECT)	       GetProcAddress (hWSA, "select"))
	    || !(wsa_accept	      = (LPFN_ACCEPT)	       GetProcAddress (hWSA, "accept"))
	    || !(wsa_connect	      = (LPFN_CONNECT)	       GetProcAddress (hWSA, "connect"))
	    || !(wsa_setsockopt       = (LPFN_SETSOCKOPT)      GetProcAddress (hWSA, "setsockopt"))
	    || !(wsa_getsockname      = (LPFN_GETSOCKNAME)     GetProcAddress (hWSA, "getsockname"))
	    || !(wsa_getpeername      = (LPFN_GETPEERNAME)     GetProcAddress (hWSA, "getpeername"))
	    || !(wsa_gethostbyname    = (LPFN_GETHOSTBYNAME)   GetProcAddress (hWSA, "gethostbyname"))
	    || !(wsa_gethostbyaddr    = (LPFN_GETHOSTBYADDR)   GetProcAddress (hWSA, "gethostbyaddr"))
	    || !(wsa_getservbyname    = (LPFN_GETSERVBYNAME)   GetProcAddress (hWSA, "getservbyname"))
	    || !(wsa_getservbyport    = (LPFN_GETSERVBYPORT)   GetProcAddress (hWSA, "getservbyport"))
	    || !(wsa_getprotobyname   = (LPFN_GETPROTOBYNAME)  GetProcAddress (hWSA, "getprotobyname"))
	    || !(wsa_getprotobynumber = (LPFN_GETPROTOBYNUMBER)GetProcAddress (hWSA, "getprotobynumber"))
	    || !(wsa_gethostname      = (LPFN_GETHOSTNAME)     GetProcAddress (hWSA, "gethostname"))
	    || !(wsa_shutdown	      = (LPFN_SHUTDOWN)        GetProcAddress (hWSA, "shutdown"))
	    || !(wsa_WSAEnumProtocols = (LPFN_WSAENUMPROTOCOLS)GetProcAddress (hWSA, pszEnumProtocol))
	    || !(wsa_WSAFDIsSet       = (LPFN_WSAFDISSET)      GetProcAddress (hWSA, "__WSAFDIsSet"))
	   )
	    return FALSE;
	else
	    return TRUE;
    }
}

int _rsxnt_socket_inited = 0;

void _rsxnt_exit_sockets ()
{
    if (_rsxnt_socket_inited) {
	(*wsa_WSACleanup)();
	_rsxnt_socket_inited = 0;
	do_load_winsock (FALSE);
    }
}

void _rsxnt_init_sockets ()
{
    WORD wVersionRequested;
    WSADATA wsaData;

    if (do_load_winsock (TRUE) == FALSE)
	return;

    wVersionRequested = MAKEWORD(iWinsockVersionMajor, iWinsockVersionMinor);
    if ((*wsa_WSAStartup)(wVersionRequested, &wsaData) != 0) {
	do_load_winsock (FALSE);
	return;
    }

    /* check expexted version */
    if (LOBYTE(wVersionRequested) < iWinsockVersionMajor ||
	  (LOBYTE(wVersionRequested) == iWinsockVersionMajor
	  && HIBYTE(wVersionRequested) < iWinsockVersionMinor))
    {
	_rsxnt_exit_sockets();
	return;
    }

    /* SYNCHRONOUS Sockets ***
    if (do_sync) {
	int optionValue = SO_SYNCHRONOUS_ALERT;
	int optionLength = sizeof(originalValue);

	(*wsa_getsockopt)(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE,
	    (char *)&originalValue, optionLength);

	(*wsa_setsockopt)(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE,
	    (char *)&optionValue, sizeof(optionValue));
    }
    *** */

    _rsxnt_socket_inited = 1;

    _rsxnt_socket_fop.read = _rsxnt_socket_read;
    _rsxnt_socket_fop.write = _rsxnt_socket_write;
    _rsxnt_socket_fop.ioctl = _rsxnt_socket_ioctl;
    _rsxnt_socket_fop.close = _rsxnt_socket_close;
    _rsxnt_socket_fop.fcntl = _rsxnt_socket_fcntl;
    _rsxnt_socket_fop.select = _rsxnt_socket_select;
}

int _rsxnt_socket_errno()
{
    int r, err = (*wsa_WSAGetLastError)() - WSABASEERR;

    switch (err) {
	case WSAEWOULDBLOCK-WSABASEERR:
	    r = EWOULDBLOCK;
	    break;
	case WSAEINPROGRESS-WSABASEERR:
	    r = EINPROGRESS;
	    break;
	case WSAEALREADY-WSABASEERR:
	    r = EALREADY;
	    break;
	case WSAENOTSOCK-WSABASEERR:
	    r = ENOTSOCK;
	    break;
	case WSAEDESTADDRREQ-WSABASEERR:
	    r = EDESTADDRREQ;
	    break;
	case WSAEMSGSIZE-WSABASEERR:
	    r = EMSGSIZE;
	    break;
	case WSAEPROTOTYPE-WSABASEERR:
	    r = EPROTOTYPE;
	    break;
	case WSAENOPROTOOPT-WSABASEERR:
	    r = ENOPROTOOPT;
	    break;
	case WSAEPROTONOSUPPORT-WSABASEERR:
	    r = EPROTONOSUPPORT;
	    break;
	case WSAESOCKTNOSUPPORT-WSABASEERR:
	    r = ESOCKTNOSUPPORT;
	    break;
	case WSAEOPNOTSUPP-WSABASEERR:
	    r = EOPNOTSUPP;
	    break;
	case WSAEPFNOSUPPORT-WSABASEERR:
	    r = EPFNOSUPPORT;
	    break;
	case WSAEAFNOSUPPORT-WSABASEERR:
	    r = EAFNOSUPPORT;
	    break;
	case WSAEADDRINUSE-WSABASEERR:
	    r = EADDRINUSE;
	    break;
	case WSAEADDRNOTAVAIL-WSABASEERR:
	    r = EADDRNOTAVAIL;
	    break;
	case WSAENETDOWN-WSABASEERR:
	    r = ENETDOWN;
	    break;
	case WSAENETUNREACH-WSABASEERR:
	    r = ENETUNREACH;
	    break;
	case WSAENETRESET-WSABASEERR:
	    r = ENETRESET;
	    break;
	case WSAECONNABORTED-WSABASEERR:
	    r = ECONNABORTED;
	    break;
	case WSAECONNRESET-WSABASEERR:
	    r = ECONNRESET;
	    break;
	case WSAENOBUFS-WSABASEERR:
	    r = ENOBUFS;
	    break;
	case WSAEISCONN-WSABASEERR:
	    r = EISCONN;
	    break;
	case WSAENOTCONN-WSABASEERR:
	    r = ENOTCONN;
	    break;
	case WSAESHUTDOWN-WSABASEERR:
	    r = ESHUTDOWN;
	    break;
	case WSAETOOMANYREFS-WSABASEERR:
	    r = ETOOMANYREFS;
	    break;
	case WSAETIMEDOUT-WSABASEERR:
	    r = ETIMEDOUT;
	    break;
	case WSAECONNREFUSED-WSABASEERR:
	    r = ECONNREFUSED;
	    break;
	case WSAELOOP-WSABASEERR:
	    r = ELOOP;
	    break;
	case WSAENAMETOOLONG-WSABASEERR:
	    r = ENAMETOOLONG;
	    break;
	case WSAEHOSTDOWN-WSABASEERR:
	    r = EHOSTDOWN;
	    break;
	case WSAEHOSTUNREACH-WSABASEERR:
	    r = EHOSTUNREACH;
	    break;
	case WSAENOTEMPTY-WSABASEERR:
	    r = ENOTEMPTY;
	    break;

	default:
	    r = EAGAIN;
    };
#ifdef EMX_SYS_LIB
  errno = r;
  return -1;
#else
  return -(r);
#endif
}

static int _rsxnt_socket_close (int s)
{
    if (!_rsxnt_socket_inited)
      return _rsxnt_errno(ENETDOWN);

    if ((*wsa_closesocket)(s) == SOCKET_ERROR)
	return _rsxnt_socket_errno();

    return 0;
}

static int _rsxnt_socket_read (int s, void *buf, size_t len)
{
    int bytes;

    if (!_rsxnt_socket_inited)
      return _rsxnt_errno(ENETDOWN);

    if ((bytes = (*wsa_recv)(s, buf, len, 0)) == SOCKET_ERROR)
	return _rsxnt_socket_errno();
    else
	return bytes;
}

static int _rsxnt_socket_write (int s, const void *buf, size_t len)
{
    int bytes;

    if (!_rsxnt_socket_inited)
      return _rsxnt_errno(ENETDOWN);

    if ((bytes = (*wsa_send)(s, buf, len, 0)) == SOCKET_ERROR)
	return _rsxnt_socket_errno();
    else
	return bytes;
}

#define IFNAMSIZ	16
struct	ifreq {
    char    ifr_name[IFNAMSIZ]; 	    /* if name, e.g. "en0" */
    union {
	struct	sockaddr ifru_addr;
	struct	sockaddr ifru_dstaddr;
	struct	sockaddr ifru_broadaddr;
	short	ifru_flags;
	int	ifru_metric;
	caddr_t ifru_data;
    } ifr_ifru;
};

struct	ifconf {
    int ifc_len;
    union {
	caddr_t ifcu_buf;
	struct	ifreq *ifcu_req;
    } u;
};

#define MAX_PROTOCOLS 16

#ifdef USE_WINSOCK2

#define XPROTOCOL_INFO WSAPROTOCOL_INFO
#define GET_NAME(x) ((x)->szProtocol)

#else /* WINSOCK 1.1 */

#define XPROTOCOL_INFO PROTOCOL_INFO
#define GET_NAME(x) ((x)->lpProtocol)

#endif /* USE_WINSOCK2 */

static int enum_config(struct ifconf *ifc)
{
    char pszTempBuffer[2048];
    int iProtocolCount;
    struct ifreq *ifreq;
    int i;
    unsigned long dwBytesRequired = sizeof(pszTempBuffer);
    XPROTOCOL_INFO *pProtocolInfo = (XPROTOCOL_INFO *) pszTempBuffer;

    iProtocolCount = (*wsa_WSAEnumProtocols) (NULL, pProtocolInfo, &dwBytesRequired);
    if (iProtocolCount <= 0)
      return -1;

    ifreq = ifc->u.ifcu_req;
    ifc->ifc_len = 0;

    for (i = 0; i < iProtocolCount; i++, ifreq++, pProtocolInfo++) {
	char * substr = strchr(GET_NAME(pProtocolInfo), '[');
	if (!substr)
	  substr = GET_NAME(pProtocolInfo);

	ifc->ifc_len += sizeof (struct ifreq);
	lstrcpyn (ifreq->ifr_name, substr, IFNAMSIZ);
	ifreq->ifr_name[IFNAMSIZ] = 0;
	memset (& ifreq->ifr_ifru.ifru_addr, 0, sizeof(struct sockaddr));
	ifreq->ifr_ifru.ifru_addr.sa_family = pProtocolInfo->iAddressFamily;
    }
    return 0;
}

static int _rsxnt_socket_ioctl (int s, int request, int arg)
{
    if (!_rsxnt_socket_inited)
      return _rsxnt_errno(ENETDOWN);

    switch (request) {

	case FGETHTYPE:
	    *(int *) arg = HT_SOCKET;
	    return 0;

	case FIONREAD:
	    if ((*wsa_ioctlsocket)(s, FIONREAD, (unsigned long *) arg) == SOCKET_ERROR)
		return _rsxnt_socket_errno();
	    else
		return 0;

	case FIONBIO:
	    if ((*wsa_ioctlsocket)(s, FIONBIO, (unsigned long *) arg) == SOCKET_ERROR)
		return _rsxnt_socket_errno();
	    /*
	    if (*(int *)arg)
		handle_flags[handle] |= HF_NDELAY;
	    else
		handle_flags[handle] &= ~HF_NDELAY;
	    */
	    return 0;

	case SIOCATMARK:
	    if ((*wsa_ioctlsocket)(s, SIOCATMARK, (unsigned long *) arg) == SOCKET_ERROR)
		return _rsxnt_socket_errno();
	    else
		return 0;

	case SIOCGIFCONF:
	{
	    struct ifconf *ifc = (struct ifconf *) arg;
	    if (enum_config(ifc) == 0)
	      return 0;
	    else
	      return _rsxnt_socket_errno();
	}

	default:
	    return -EINVAL;
    }
}

static int _rsxnt_socket_fcntl (int s, int request, int arg)
{
    unsigned long bits;

    if (!_rsxnt_socket_inited)
      return _rsxnt_errno(ENETDOWN);

    bits = arg & O_NONBLOCK;

    if ((*wsa_ioctlsocket) (s, FIONBIO, &bits) == SOCKET_ERROR)
	return _rsxnt_socket_errno();
    else
	return 0;
}

static int _rsxnt_socket_select (int n, struct _fd_set *emx_in, struct _fd_set *emx_out, struct _fd_set *emx_exp, const struct timeval *tv)
{
    EMXPROCESS *p = _rsxnt_get_process_ptr();
    win_fd_set win_in;
    win_fd_set win_out;
    win_fd_set win_exp;
    int i, nhandles;

    if (!_rsxnt_socket_inited)
      return _rsxnt_errno(ENETDOWN);

    if (n > 64)
	n = 64;

    win_in.fd_count = 0;
    win_out.fd_count = 0;
    win_exp.fd_count = 0;
    memset (&win_in, 0, sizeof(win_fd_set));
    memset (&win_out, 0, sizeof(win_fd_set));
    memset (&win_exp, 0, sizeof(win_fd_set));

    /* set all win fd_set sockets */
    for (i = 0; i <= n; ++i) {
        if (p->file[i].f_mode == HT_SOCKET && FD_ISSET(i, emx_in))
	    win_in.fd_array[win_in.fd_count++] = (SOCKET) p->file[i].f_handle;

        if (p->file[i].f_mode == HT_SOCKET && FD_ISSET(i, emx_out))
	    win_out.fd_array[win_out.fd_count++] = (SOCKET) p->file[i].f_handle;

        if (p->file[i].f_mode == HT_SOCKET && FD_ISSET(i, emx_exp))
	    win_exp.fd_array[win_exp.fd_count++] = (SOCKET) p->file[i].f_handle;
    }

    if ((nhandles = (*wsa_select)(n, &win_in, &win_out, &win_exp, tv)) == SOCKET_ERROR)
	return _rsxnt_socket_errno();

    /* clear emx socket bits, if they are not ready */
    for (i = 0; i <= n; ++i) {
	if (FD_ISSET(i, emx_in))
            if (p->file[i].f_mode != HT_SOCKET ||
                    (*wsa_WSAFDIsSet) ((SOCKET) p->file[i].f_handle, &win_in) == 0)
		FD_CLR(i, emx_in);

	if (FD_ISSET(i, emx_out)) {
            if (p->file[i].f_mode != HT_SOCKET ||
                    (*wsa_WSAFDIsSet) ((SOCKET) p->file[i].f_handle, &win_out) == 0)
		FD_CLR(i, emx_out);
	}
	if (FD_ISSET(i, emx_exp)) {
            if (p->file[i].f_mode != HT_SOCKET ||
                    (*wsa_WSAFDIsSet) ((SOCKET) p->file[i].f_handle, &win_exp) == 0)
		FD_CLR(i, emx_exp);
	}
    }
    return nhandles;
}

#ifdef EMX_SYS_LIB
__asm__ ("\t.stabs \"___CTOR_LIST__\", 23, 0, 0, __rsxnt_init_sockets");
__asm__ ("\t.stabs \"___DTOR_LIST__\", 23, 0, 0, __rsxnt_exit_sockets");
#endif
