/*
**	BUFFERED TRANSPORT WRITER STREAM
**
**	(c) COPYRIGHT MIT 1995.
**	Please first read the full copyright statement in the file COPYRIGH.
**	@(#) $Id: HTBufWrt.c,v 2.5 1996/08/08 02:16:44 frystyk Exp $
**
**	A buffered output stream. This stream lets you write characters to a
**	stream without causing a write every time.  The data is first written
**	into a buffer. Data is written to the actual stream only when the
**	buffer is full, or when the stream is flushed.
*/

/* Library include files */
#include "sysdep.h"
#include "WWWUtil.h"
#include "WWWCore.h"
#include "HTNetMan.h"
#include "HTWriter.h"
#include "HTBufWrt.h"					 /* Implemented here */

struct _HTStream {
    const HTStreamClass *	isa;
    /* ... */
};

struct _HTOutputStream {
    const HTOutputStreamClass *	isa;
    HTOutputStream *		target;		 /* Target for outgoing data */
    HTChannel *			ch;
    HTNet *			net;
    int				size;			      /* Buffer size */
    int				bb;
    char *			block;
    char *			read;		       /* Position in 'data' */
    char *			data;				   /* buffer */
};

#define PUTBLOCK(b,l) (*me->target->isa->put_block)(me->target,(b),(l))

/* ------------------------------------------------------------------------- */

PRIVATE int HTBufferWriter_flush (HTOutputStream * me)
{
    int status = HT_OK;
    if (me->read > me->data) {
	if ((status = PUTBLOCK(me->data, me->read - me->data))==HT_WOULD_BLOCK)
	    return HT_WOULD_BLOCK;
	me->read = me->data;
	me->block = NULL;
    }
    return status;
}

PRIVATE int HTBufferWriter_free (HTOutputStream * me)
{
    return HTBufferWriter_flush(me);
}

PRIVATE int HTBufferWriter_abort (HTOutputStream * me, HTList * e)
{
    if (me->target) (*me->target->isa->abort)(me->target, e);
    if (PROT_TRACE) HTTrace("Buffer...... ABORTING...\n");
    return HT_ERROR;
}

PRIVATE int HTBufferWriter_write (HTOutputStream *me, const char *buf, int len)
{
    long total = len;
    int status;

    if (me->bb > 0) {
	len -= (me->block - buf);
	if ((status = PUTBLOCK(me->block, me->bb)) != HT_OK) return status;
	me->block += me->bb;
	len -= me->bb;
	me->bb = 0;
    } else {
	int available = me->data + me->size - me->read;

	/* Still room in buffer */
	if (len <= available) {
	    memcpy(me->read, buf, len);
	    me->read += len;
	    return HT_OK;
	}

	/* If already data in buffer then fill it and flush */
	if (me->read > me->data) {
	    memcpy(me->read, buf, available);
	    me->block = (char *) buf+available;
	    if ((status = PUTBLOCK(me->data, me->size))!=HT_OK) return status;
	}

	/* If more data then write n times buffer size */
	if (!me->block)
	    me->block = (char *) buf;
	else {
	    len -= (me->block - buf);
	}
	me->bb = len - len%me->size;
	if ((status = PUTBLOCK(me->block, me->bb)) != HT_OK) return status;
	me->block += me->bb;
	len -= me->bb;
	me->bb = 0;
    }

    /* If data is not aligned then save the rest in our buffer */
    if (len > 0) {
	memcpy(me->data, me->block, len);
	me->read = me->data + len;
    } else
	me->read = me->data;
    me->block = NULL;
    me->net->bytes_written += total;
    return HT_OK;
}

/*	Character handling
**	------------------
*/
PRIVATE int HTBufferWriter_put_character (HTOutputStream * me, char c)
{
    return HTBufferWriter_write(me, &c, 1);
}

/*	String handling
**	---------------
**
**	Strings must be smaller than this buffer size.
*/
PRIVATE int HTBufferWriter_put_string (HTOutputStream * me, const char * s)
{
    return HTBufferWriter_write(me, s, (int) strlen(s));
}
/*
**	The difference between the close and the free method is that we don't
**	close the connection in the free method - we only call the free method
**	of the target stream. That way, we can keep the output stream as long 
**	as the channel itself.
*/
PRIVATE int HTBufferWriter_close (HTOutputStream * me)
{
    if (me) {
	HTBufferWriter_flush(me);
	if (me->target) (*me->target->isa->close)(me->target);
	HT_FREE(me->data);
	HT_FREE(me);
    }
    return HT_OK;
}

PRIVATE const HTOutputStreamClass HTBufferWriter =
{		
    "SocketWriter",
    HTBufferWriter_flush,
    HTBufferWriter_free,
    HTBufferWriter_abort,
    HTBufferWriter_put_character,
    HTBufferWriter_put_string,
    HTBufferWriter_write,
    HTBufferWriter_close
}; 

PUBLIC HTOutputStream * HTBufferWriter_new (HTNet * net, HTChannel * ch,
					    void * param, int bufsize)
{
    if (net && ch) {
	HTOutputStream * me = HTChannel_output(ch);
	if (me == NULL) {
	    if (bufsize <= 0) bufsize = OUTPUT_BUFFER_SIZE;
	    if ((me = (HTOutputStream *) HT_CALLOC(1, sizeof(HTOutputStream))) == NULL ||
		(me->data = (char *) HT_MALLOC(bufsize)) == NULL)
		HT_OUTOFMEM("HTBufferWriter_new");
	    me->isa = &HTBufferWriter;
	    me->read = me->data;
	    me->size = bufsize;
	    me->target = HTWriter_new(net, ch, param, 0);
	    me->ch = ch;
	} else
	    HTWriter_set(me->target, net, ch, param, 0);
	me->net = net;
	return me;
    }
    return NULL;
}
