#include <windows.h>
#include <winsock.h>
#include <stdio.h>

/*
 * List of file descriptors and their Tcl-DP types.
 * This lists is used to intercept f* type calls (fprintf, feof, etc)
 * and replace them with Winsock calls.  The file descriptor needs to
 * be registered as a socket before it is properly handled.
 */

#define set_ferror(_stream) ((_stream)->_flag |= _IOERR)
#define set_feof(_stream) ((_stream)->_flag |= _IOEOF)

#define MAX_OPEN_FILES 1024
static unsigned char fdtype[MAX_OPEN_FILES];
static SOCKET fdToSocket[MAX_OPEN_FILES];

#define SOCKTYPE 1

/*
 *----------------------------------------------------------------------
 * Function: winio_registerSocket
 *----------------------------------------------------------------------
 */
void
winio_registerSocket(int fd, SOCKET socket)
{
    fdtype[fd] = SOCKTYPE;
    fdToSocket[fd] = socket;
}

/*
 *----------------------------------------------------------------------
 * Function: winio_unregisterSocket
 *----------------------------------------------------------------------
 */
void
winio_unregisterSocket(int fd)
{
    fdtype[fd] = 0;
}

#define TK_READABLE  1
#define TK_WRITABLE  2
#define TK_EXCEPTION 4

/*
 *--------------------------------------------------------------
 *
 * BlockSocket --
 *
 *	This function is needed under Windows NT to get proper
 *	blocking behavior before with calls to recv().  This
 *	is because the call to WSAAsyncSelect() in tkEvent.c
 *	causes the socket to become non-blocking.  dpsh.exe does
 *	not have this problem because it uses an event loop that
 *	does not need to call WSAAsyncSelect().  The call to
 *	select() will only occur if the socket is a blocking socket.
 *	if the socket is not blocking, this function just returns.
 *
 * Results: None
 *
 * Side Effects:
 *	This function will return when an event occurs on the
 *	socket
 *
 *--------------------------------------------------------------
 */
static __inline void
BlockSocket(socket, mask)
    SOCKET socket;
    int mask;
{
#ifdef _WINDOWS
    /* This next piece of code gets around a problem that occurs with
     * WSAAsyncSelect(): the socket becomes non-blocking when this call
     * is used.  Wait for select() to tell us something is available
     */
    fd_set ready[3];
    fd_set *readPtr, *writePtr, *exceptPtr;

    FD_ZERO(&ready[0]);
    FD_ZERO(&ready[1]);
    FD_ZERO(&ready[2]);

    if (mask & TK_READABLE) {
	FD_SET(socket, &ready[0]);
	readPtr = &ready[0];
    } else {
	readPtr = NULL;
    }

    if (mask & TK_WRITABLE) {
	FD_SET(socket, &ready[1]);
	writePtr = &ready[1];
    } else {
	writePtr = NULL;
    }

    if (mask & TK_WRITABLE) {
	FD_SET(socket, &ready[2]);
	exceptPtr = &ready[2];
    } else {
	exceptPtr = NULL;
    }

    select(1, readPtr, writePtr, exceptPtr, NULL);
#endif /* ifdef _WINDOWS */
}

static char *io_buffer = NULL;
static int io_bufsize = 0;

int
winio_fputs(const char *s, FILE *f)
{
    int ret, len;
    int fd;

    fd = fileno(f);

    switch (fdtype[fd]) {
    case SOCKTYPE:
	BlockSocket(fdToSocket[fd], TK_WRITABLE|TK_EXCEPTION);
	len = strlen(s) + 1;
	ret = send(fdToSocket[fd], s, len, 0);
	if (ret != len) {
	    set_ferror(f);
	    return EOF;
	}
	return ret;
    default:
	return (fputs(s, f));
    }
}

int
winio_fgetc(FILE *f)
{
    int fd, ret;
    char c;

    fd = fileno(f);
    switch (fdtype[fd]) {
    case SOCKTYPE:
	BlockSocket(fdToSocket[fd], TK_READABLE|TK_EXCEPTION);
	ret = recv(fdToSocket[fd], &c, 1, 0);
	if (ret == 0) {
	    set_feof(f);
	    return EOF;
	} else if (ret != 1) {
	    set_ferror(f);
	    return EOF;
	}
	return (int) c;
    default:
	return getc(f);
    }
}

int
winio_fputc(int ch, FILE *f)
{
    int ret, error, fd;
    char c;

    fd = fileno(f);
    switch (fdtype[fd]) {
    case SOCKTYPE:
	BlockSocket(fdToSocket[fd], TK_WRITABLE|TK_EXCEPTION);
	c = (char) ch;
	ret = send(fdToSocket[fd], &c, 1, 0);
	if (ret == 1) {
	    return ch;
	} else {
	    error = WSAGetLastError();
	    if (error != WSAEINTR && error != WSAEWOULDBLOCK) {
		set_feof(f);
	    } else {
		set_ferror(f);
	    }
	    return EOF;
	}
    default:
	return fputc(ch, f);
    }
}


#if 0
/* ===================================================================  */
/* the interface functions themselves.....                              */
/* ===================================================================  */

char *gets(char *pchTmp)
{
    BYTE	*pch = pchTmp;
    int	c;

    return fgets(pch, 1024, stdin);
}

int printf(const char *fmt, ...)
{
    va_list	marker;
    va_start(marker, fmt);
    return vfprintf(stdout, fmt, marker);
}

int vprintf(const char *fmt, va_list marker)
{
    static BYTE	s[1024];
    int		len;

    return vfprintf(stdout, fmt, marker);
}

#undef fprintf
int
winio_fprintf(FILE *f, const char *fmt, ...)
{
    va_list marker;
    int ret;
    int fd;
    SOCKET socket;

    if (f == NULL)
	return 0;
    va_start(marker, fmt);
    if ((f == stdout) || (f == stderr)) {
	ret = vfprintf(f, fmt, marker);
	return ret;
    } else {
	fd = fileno(f);

	switch (fdtype[fd]) {
	case SOCKTYPE:
	    if (io_bufsize == 0) {
		io_bufsize = 8192;
		io_buffer = (char *) malloc(io_bufsize);
		if (io_buffer == NULL) {
		    io_bufsize = 0;
		    SetLastError(ENOMEM);
		    set_ferror(f);
		    return -1;
		}
	    }
	    do {
		ret = _vsnprintf(io_buffer, io_bufsize, fmt, marker);
		if (ret == -1) {
		    io_bufsize *= 2;
		    free(io_buffer);
		    io_buffer = (char *) malloc(io_bufsize);
		    if (io_buffer == NULL) {
			io_bufsize = 0;
			SetLastError(ENOMEM);
			set_ferror(f);
			return -1;
		    }
		} else {
		    ret++;
		    socket = fdToSocket[fd];
		    if (socket != INVALID_SOCKET) {
			BlockSocket(socket, TK_WRITABLE|TK_EXCEPTION);
			ret = send(socket, io_buffer, io_bufsize, 0);
			if (ret != io_bufsize) {
			    set_ferror(f);
			    return -1;
			}
			return ret;
		    } else {
			set_ferror(f);
			return -1;
		    }
		}

	    } while (1);
	default:
	    return vfprintf(f, fmt, marker);
	}
    }
}


#undef _filbuf
int
winio__filbuf(FILE *f)
{
    int fd;
    int ret;
    char c;
    int size;
    SOCKET socket;

    fd = fileno(f);
    if (fdtype[fd] != SOCKTYPE) {
	return _filbuf(f);
    }
    socket = fdToSocket[fd];
    if (f->_flag & _IONBF) {
	BlockSocket(socket, TK_READABLE|TK_EXCEPTION);
	ret = recv(socket, &c, 1, 0);
	if (ret == 0) {
	    set_feof(f);
	    return EOF;
	} else if (ret != 1) {
	    set_ferror(f);
	    return EOF;
	}
	return (int) c;
    }

    if (f->_base == NULL) {
	f->_base = (char *) malloc(f->_bufsiz);
	if (f->_base == NULL) {
	    set_ferror(f);
	    return EOF;
	}
    }

    if (ioctlsocket(socket, FIONREAD, &size)) {
	set_ferror(f);
	return EOF;
    }
    if (size == 0) {
	size = 1;
    }
    if (size > f->_bufsiz) {
	size = f->_bufsiz;
    }
    BlockSocket(socket, TK_READABLE|TK_EXCEPTION);
    ret = recv(socket, f->_base, size, 0);
    if (ret == 0) {
	set_feof(f);
	return EOF;
    } else if (ret == -1) {
	set_ferror(f);
	return EOF;
    }
    f->_cnt = ret;
    f->_ptr = f->_base;
    return getc(f);
}
    
#undef fgets
char *
winio_fgets(char *buf, int len, FILE *f)
{
    int fd;
    SOCKET socket;
    int i;
    int c;

    if (f == NULL)
	return NULL;
    if (f == stdin)
	return gets(buf);

    fd = fileno(f);
    switch (fdtype[fd]) {
    case SOCKTYPE:
	socket = fdToSocket[fd];

	for (i = 0; i < len - 1; ) {
	    c = getc(f);

	    switch (c) {

	    case '\b': if (i > 0) i--; break;
	    case '\n': buf[i] = '\0'; i++; break;
	    case 0x1b: i = 0; break;
	    case EOF: return NULL;
	    default: buf[i] = c; i++;

	    }
	}
	buf[i] = '\0';
	return buf;
    default:
	return (char *) fgets(buf, len, f);
    }
}

#undef _flsbuf
int
winio__flsbuf(int ch, FILE *f)
{
    int fd;
    int ret;
    int error;
    SOCKET socket;

    fd = fileno(f);
    if (fdtype[fd] != SOCKTYPE) {
	return _flsbuf(ch, f);
    }
    if (f->_base == NULL) {
	f->_base = (char *) malloc(f->_bufsiz);
	if (f->_base == NULL) {
	    set_ferror(f);
	    return EOF;
	}
	if (f->_bufsiz > 2) {
	    f->_cnt = f->_bufsiz - 2;
	    f->_ptr = f->_base;
	    return putc(ch, f);
	}
    }
    socket = fdToSocket[fd];
    *f->_ptr = (char) ch;
    f->_ptr++;
    BlockSocket(socket, TK_WRITABLE|TK_EXCEPTION);
    ret = send(socket, f->_base, (f->_ptr - f->_base), 0);
    f->_cnt = f->_bufsiz - 2;
    f->_ptr = f->_base;

    if (ret == 1) {
	return ch;
    } else {
	error = WSAGetLastError();
	if (error != WSAEINTR && error != WSAEWOULDBLOCK) {
	    set_feof(f);
	} else {
	    set_ferror(f);
	}
	return EOF;
    }
}
	
#endif /* if 0 */

