/*****************************************************************
 * i/o functions
 *
 *   WriteToClient, ReadRequestFromClient
 *
 *****************************************************************/

#include <stdio.h>
#include "Xos.h"
#include "Xmd.h"
#include <errno.h>
#include "X.h"
#include "Xproto.h"
#include "os.h"
#include "opaque.h"
#include "dixstruct.h"
#include "osdep.h"
#include "misc.h"

typedef struct _connection {
    struct _connection _near *next;
    char *buffer;               /* contains current client input */
    char *ptr;		/* pointer to start of request */
    int  count;                /* count of bytes in buffer */
    int size;
    int lastRequestSize;
} ConnectionRec;

#define	con_buffer(con)	((con)->buffer)

static int timesThisConnection = 0;
int _near OutputBufferSize = BUFSIZ;
static ConnectionPtr FreeInputs = (ConnectionPtr)NULL;
static ConnectionPtr FreeOutputs = (ConnectionPtr)NULL;
static OsCommPtr AvailableInput = (OsCommPtr)NULL;
static Bool CriticalOutputPending;
Bool _near AnyClientsWriteBlocked;
fd_set _near WriteBlockedSockets;
Bool _near NewOutputPending;
extern fd_set _near AllSockets;

extern char _near isItTimeToYield;

extern Bool near mouse_moved;

extern OsCommPtr near MyPrivate[NPORTS];

extern int AutoResetServer();

static ConnectionPtr _near AllocateBuffer(size_t);
static void _fastcall _near osXfer(OsCommPtr oc, ConnectionPtr oci);

int near lastRequest;

#define request_length(req, cli) ((cli->swapped ? \
	lswaps((req)->length) : (req)->length) << 2)
#define MAX_TIMES_PER         5

/*****************************************************************
 * ReadRequestFromClient
 *    Returns one request in client->requestBuffer.  Return status is:
 *
 *    > 0  if  successful, specifies length in bytes of the request
 *    = 0  if  entire request is not yet available
 *    < 0  if  client should be terminated
 *
 *    The request returned must be contiguous so that it can be
 *    cast in the dispatcher to the correct request type.  Because requests
 *    are variable length, ReadRequestFromClient() must look at the first 4
 *    bytes of a request to determine the length (the request length is
 *    always the 3rd and 4th bytes of the request).  
 *
 *****************************************************************/

#define YieldControl()				\
        { isItTimeToYield = TRUE;		\
	  mouseBlockHandler();			\
	  timesThisConnection = 0; }
#define YieldControlDeath()			\
        { timesThisConnection = 0; }

/* for debug */
extern int (_fastcall _near * _far InitialVector[3]) (ClientPtr);

int _near max_request_size = MAXBUFSIZE >> 2;

int
ReadRequestFromClient(client)
    ClientPtr client;
{

    OsCommPtr oc = (OsCommPtr)client->osPrivate;
    ConnectionPtr oci = oc->input;
    int fd = oc->fd;
    int needed, gotnow, result;
    xReq *request;

    if (AvailableInput)
    {
	if (AvailableInput != oc)
	{
	    AvailableInput->input->next = FreeInputs;
	    FreeInputs = AvailableInput->input;
	    AvailableInput->input = (ConnectionPtr)NULL;
	}
	AvailableInput = (OsCommPtr)NULL;
    }
    if (!oci)
    {
	if (oci = FreeInputs)
	{
	    oci->ptr = con_buffer(oci);
	    oci->count = 0;
	    FreeInputs = oci->next;
	}
	else if (!(oci = AllocateBuffer(OUTBUFSIZE)))
	{
	    YieldControlDeath();
	    return -1;
	}
	oc->input = oci;
	oci->lastRequestSize = 0;
    } else if (oc->flags & OS_REQUEST) {
	/* If this flag is set, then we have already processed the request
	 * in the buffer.  Remove it from the buffer now.
	 */
	oci->count -= oci->lastRequestSize;
	if (oci->count == 0)
	    oci->ptr = con_buffer(oci);
	else
	    oci->ptr += oci->lastRequestSize;
    }
    oc->flags &= ~OS_REQUEST;

    /* Make sure we have room for a header. */
    if (oci->size - (oci->ptr - con_buffer(oci)) < sizeof (xReq)) {
	memcpy(con_buffer(oci), oci->ptr, oci->count);
	oci->ptr = con_buffer(oci);
    }

    /* Get an X request header so we know how much more we need to read. */
    if (oci->count < sizeof (xReq)) {
	result = soread(fd, oci->ptr + oci->count,
	    (con_buffer(oci) + oci->size) - (oci->ptr + oci->count));
	if (result <= 0) {
	    if (result == 0 || errno != EWOULDBLOCK) {
		YieldControlDeath();
		return -1;
	    }
	} else
	    oci->count += result;
	if (oci->count < sizeof (xReq)) {
	    oc->flags &= ~OS_MORE;
	    YieldControl();
	    return 0;
	}
    }
    needed = request_length((xReq *)oci->ptr, client);

    if (needed == 0)
	needed = sizeof (xReq);
    if (needed > MAXBUFSIZE || needed < sizeof (xReq)) {
printf("needed=%u\n", needed);
	/* Naughty client */
	YieldControlDeath();
	return -1;
    }

    /* If we need more memory for this request, allocate it. */
    if (oci->ptr + needed > con_buffer(oci) + oci->size) {
	/* Shift the request to the beginning of the buffer. */
	if (oci->ptr != con_buffer(oci)) {
	    memcpy(con_buffer(oci), oci->ptr, oci->count);
	    oci->ptr = con_buffer(oci);
	}
	/* see if we have enough memory */
	if (needed > oci->size)
	{
	    char *buf;

	    buf = xrealloc(oci->buffer, needed);
	    if (!buf) {
		/* Try freeing some other memory */
		FlushAllOutput();
		ResetOsBuffers();
		buf = xrealloc(oci->buffer, needed);
		if (!buf) {
		    YieldControlDeath();
		    return -1;
		}
	    }
	    oci->buffer = buf;
	    oci->size = needed;
	    oci->ptr = con_buffer(oci);
	}
    }

    if (oci->count < needed) {
	result = soread(fd, oci->ptr + oci->count,
	    (con_buffer(oci) + oci->size) - (oci->ptr + oci->count));
	if (result <= 0) {
	    if (result == 0 || errno != EWOULDBLOCK) {
		YieldControlDeath();
		return -1;
	    }
	} else
	    oci->count += result;
	if (oci->count < needed) {
	    oc->flags &= ~OS_MORE;
	    YieldControl();
	    return 0;
	}
    }

/*
printf("request=%d, client=%d len=%d, seq=%d\n", request->reqType, client->index, needed, client->sequence);
*/
    oc->flags |= OS_REQUEST;

    oci->lastRequestSize = needed;
    if (oci->count == needed) {
	AvailableInput = oc;
	if (con_buffer(oci) + oci->size != oci->ptr + needed) {
	    oc->flags &= ~OS_MORE;
	    YieldControl();
	}
    } else
	oc->flags |= OS_MORE;

lastRequest = ((xReq *)oci->ptr)->reqType;
/*
if (client->requestVector == InitialVector)
printf("=");
*/
    if (++timesThisConnection >= MAX_TIMES_PER)
	YieldControl()
    else if (mouse_moved)
	mouseBlockHandler();

    client->requestBuffer = (pointer)oci->ptr;
    return needed;
}

/*****************************************************************
 * InsertFakeRequest
 *    Splice a consed up (possibly partial) request in as the next request.
 * This routine will only be called once per session and it will be called
 * before ReadRequestFromClient is called for this client.  Therefore,
 * we know the buffer will be empty.
 *
 * I am assuming this code will only be called from NextAvalibleClient.
 * If it's called from other places, certain modifications must be made.
 *
 **********************/

Bool
InsertFakeRequest(client, data, count)
    ClientPtr client;
    char *data;
    int count;
{
    OsCommPtr oc = (OsCommPtr)client->osPrivate;
    ConnectionPtr oci = oc->input;
    int size;

    if (AvailableInput)
    {
	if (AvailableInput != oc)
	{
	    AvailableInput->input->next = FreeInputs;
	    FreeInputs = AvailableInput->input;
	    AvailableInput->input = (ConnectionPtr)NULL;
	}
	AvailableInput = (OsCommPtr)NULL;
    }
    if (!oci)
    {
	if ((oci = FreeInputs) != 0) {
	    FreeInputs = oci->next;
	    oci->ptr = con_buffer(oci);
	    oci->count = 0;
	} else if ((oci = AllocateBuffer(OUTBUFSIZE)) == 0)
	    return FALSE;
	oc->input = oci;
    } else if (oc->flags & OS_REQUEST) {
	/* If this flag is set, then we have already processed the request
	 * in the buffer.  Remove it from the buffer now.
	 */
	size = request_length((xReq *)oci->ptr, client);
	oci->count -= size;
	if (oci->count == 0)
	    oci->ptr = con_buffer(oci);
	else
	    oci->ptr += size;
    }
    oc->flags &= ~OS_REQUEST;
    memcpy(oci->ptr, data, count);
    oci->count += count;
    return TRUE;
}

/*****************************************************************
 * ResetRequestFromClient
 *    Reset to reexecute the current request, and yield.
 *
 **********************/

ResetCurrentRequest(client)
    ClientPtr client;
{
    OsCommPtr oc = (OsCommPtr)client->osPrivate;
    xReq *request = (xReq *)oc->input->ptr;

    if (AvailableInput == oc)
	AvailableInput = (OsCommPtr)NULL;
    oc->flags &= ~OS_REQUEST;
    /* From the place where this code is called, oc->input is never NULL. */
    if (oc->input->count >= request_length(request, client))
	oc->flags |= OS_MORE;		/* just re-use this request */
    else
	YieldControl();
}

 /********************
 * FlushClient()
 *    If the client isn't keeping up with us, then we try to continue
 *    buffering the data and set the apropriate bit in ClientsWritable
 *    (which is used by WaitFor in the select).  If the connection yields
 *    a permanent error, or we can't allocate any more space, we then
 *    close the connection.
 *
 **********************/

int near
FlushClient(oc, extraBuf, extraCount)
    OsCommPtr oc;
    char *extraBuf;
    int extraCount;
{
    int connection = oc->fd, n, i, notWritten;
    ConnectionPtr oco = oc->output;

    if (!oco)
	return 0;
    i = extraCount;
    extraCount += -extraCount & 3;
/*
printf("flush client %d: %d %d.\n", connection, oc->count, extraCount);
*/
    if (oco->count)
    {
	char *buf = oco->buffer;
	do {
	    n = sowrite(connection, buf, min(1024, oco->count));
	    if (n <= 0)
		if (n < 0 && errno != EWOULDBLOCK)
		    goto bad;
		else
		    break;
	    oco->count -= n;
	    buf += n;
	} while (oco->count);
	notWritten = oco->count;
	if (notWritten)
	    (void)memcpy(oco->buffer, buf, notWritten);
    } else
	notWritten = 0;
    if (extraCount) {
	if (notWritten)
	    notWritten += extraCount;
	else {
	    do {
		n = sowrite(connection, extraBuf, min(1024, extraCount));
		if (n <= 0)
		    if (n < 0 && errno != EWOULDBLOCK)
			goto bad;
		    else
			break;
		extraCount -= n;
		extraBuf += n;
	    } while (extraCount);
	    notWritten = extraCount;
	}
    }

    if (notWritten == 0) {
	/* everything was flushed out */
	if (oco->size > OUTBUFSIZE * 2) {
	    xfree(oco->buffer);
	    _nfree(oco);
	} else {
	    oco->next = FreeOutputs;
	    FreeOutputs = oco;
	}
	oc->output = 0;
	return i; /* return only the amount explicitly requested */
    }

/*
printf("flush client %d: extraCount %d notWritten %d n %d.\n", connection, extraCount, notWritten, n);
*/
    /* If we've arrived here, then the client is stuffed to the gills
       and not ready to accept more.  Make a note of it and buffer
       the rest. */
    AnyClientsWriteBlocked = TRUE;
    FD_SET(oc->fd, &WriteBlockedSockets);

    if (notWritten > oco->size)
    {
	char *buf;

	if (notWritten > MAXOUTBUF) {
		goto bad;
	}
	/* allocate at least enough to contain it plus one
	   OutputBufferSize */
	n = notWritten + OutputBufferSize;
	buf = (unsigned char *)realloc(oco->buffer, n);
	if (buf == NULL)
	{
printf("Out of memory! notWritten=%d.\n", notWritten);
	    goto bad;
	}
	oco->size = n;
	oco->buffer = buf;
    }

    if (extraCount)
	memcpy ( (char *)oco->buffer + oco->count, extraBuf, extraCount);

    oco->count = notWritten; /* this will include the pad */

    return i; /* return only the amount explicitly requested */

bad:
    MarkClientException(clients[oc->client]);
    return(-1);
}

 /********************
 * FlushAllOutput()
 *    Flush all clients with output.  However, if some client still
 *    has input in the queue (more requests), then don't flush.  This
 *    will prevent the output queue from being flushed every time around
 *    the round robin queue.  Now, some say that it SHOULD be flushed
 *    every time around, but...
 *
 * This routine is called from dix and WaitForSomething().
 *
 **********************/

void
FlushAllOutput()
{
    int i;
    OsCommPtr oc;

    CriticalOutputPending = FALSE;
    NewOutputPending = FALSE;

    for (i = 0; i < NPORTS; i++) {
	oc = MyPrivate[i];
	if (!oc || !oc->output || !oc->output->count)
		continue;
	FlushClient(oc, (char *)NULL, 0);
    }
}

void
FlushIfCriticalOutputPending()
{
    if (CriticalOutputPending)
	FlushAllOutput();
}

void
SetCriticalOutputPending()
{
    CriticalOutputPending = TRUE;
}

/*****************
 * WriteToClient
 *    Copies buf into ClientPtr.buf if it fits (with padding), else
 *    flushes ClientPtr.buf and buf to client.  As of this writing,
 *    every use of WriteToClient is cast to void, and the result
 *    is ignored.  Potentially, this could be used by requests
 *    that are sending several chunks of data and want to break
 *    out of a loop on error.  Thus, we will leave the type of
 *    this routine as int.
 *****************/

int
WriteToClient (client, count, buf)
    ClientPtr client;
    int count;
    char *buf;
{
    OsCommPtr oc = (OsCommPtr)client->osPrivate;
    ConnectionPtr oco = oc->output;
    int padBytes;

    if (oc->fd < 0) 
    {
	ErrorF("WriteToClient: %d fd %d flags %#x\n", client->index, oc->fd, oc->flags);
	ErrorF("clientGone=%d\n", client->clientGone);
	return(-1);
    }

    if (!oco)
    {
	if (oco = FreeOutputs)
	{
	    FreeOutputs = oco->next;
	}
	else if (!(oco = AllocateBuffer(OutputBufferSize)))
	{
	    MarkClientException(client);
	    return -1;
	}
	oc->output = oco;
    }

    padBytes = -count & 3;
    if (oco->count + count + padBytes > oco->size) {
	return FlushClient(oc, buf, count);
    }

    NewOutputPending = TRUE;
    memcpy((char *)oco->buffer + oco->count, buf, count);
    oco->count += count + padBytes;

    return count;
}

static ConnectionPtr _near
AllocateBuffer(size)
size_t size;
{
    register ConnectionPtr occ;

    occ = (ConnectionPtr)_nmalloc(sizeof(ConnectionRec));
    if (!occ)
{
printf("AllocateBuffer: nmalloc failed\n");
	return (ConnectionPtr)NULL;
}
    occ->buffer = (char *)xalloc(size);
    if (!occ->buffer)
    {
printf("AllocateBuffer: xalloc failed\n");
	_nfree(occ);
	return (ConnectionPtr)NULL;
    }
    occ->ptr = con_buffer(occ);
    occ->size = size;
    occ->count = 0;
    return occ;
}

void _fastcall _near
CloseDownFileDescriptor(oc)
    OsCommPtr oc;
{
    ConnectionPtr occ;

    soclose(oc->fd);
    FD_CLR(oc->fd, &WriteBlockedSockets);
    FD_CLR(oc->fd, &AllSockets);
    if (AvailableInput == oc)
	AvailableInput = (OsCommPtr)NULL;
    if (occ = oc->input)
    {
	oc->input = 0;
	if (FreeInputs)
	{
	    xfree(occ->buffer);
	    _nfree(occ);
	}
	else
	{
	    FreeInputs = occ;
	    occ->next = (ConnectionPtr)NULL;
	    occ->count = 0;
	}
    }
    if (occ = oc->output)
    {
	oc->output = 0;
	if (FreeOutputs)
	{
	    xfree(occ->buffer);
	    _nfree(occ);
	}
	else
	{
	    FreeOutputs = occ;
	    occ->next = (ConnectionPtr)NULL;
	    occ->count = 0;
	}
    }
}

void _near
ResetOsBuffers()
{
    ConnectionPtr occ;
    int i;

    while (occ = FreeInputs)
    {
	FreeInputs = occ->next;
	xfree(occ->buffer);
	_nfree(occ);
    }
    while (occ = FreeOutputs)
    {
	FreeOutputs = occ->next;
	xfree(occ->buffer);
	_nfree(occ);
    }
}
