/*
    (C) 1995-96 AROS - The Amiga Replacement OS
    $Id: bufferingfuncs.c,v 1.3 1997/02/07 14:32:39 digulla Exp $

    Desc: Funtions needed for buffering writes
    Lang: English.
*/
#include "iffparse_intern.h"



/****************/
/* AllocBuffer	*/
/****************/

struct BufferList *AllocBuffer(ULONG bufnodesize,
    struct IFFParseBase_intern * IFFParseBase)
{
    struct BufferList *buflist;
    struct BufferNode *bufnode;


    /* First allocate memory for the BufferList structure */

    buflist = AllocMem(sizeof (struct BufferList), MEMF_ANY);

    if (buflist)
    {
	/* Initialize teh bufferlist */
	NewList( (struct List*)&(buflist->bl_ListHeader) );
	buflist->bl_BufferNodeSize = bufnodesize;


	/* The bufferlist should always contain at least one buffer */

	bufnode = AllocBufferNode(buflist, IFFParseBase);

	if (bufnode)
	{
	    buflist->bl_CurrentNode	 = bufnode;
	    buflist->bl_CurrentOffset	 =  0;
	    buflist->bl_BufferSize	=  0;
	    buflist->bl_CurrentNodeNum	= 1;

	    return (buflist);
	}

	FreeMem(buflist, sizeof (struct BufferList) );
    }

    return (FALSE);
}


/**************/
/* FreeBuffer  */
/**************/

/* Frees all the buffers and buffernodes inside a bufferlist */

VOID FreeBuffer(struct BufferList *buflist,
    struct IFFParseBase_intern * IFFParseBase)
{
    /* Frees all the buffernodes in a bufferlist */
    struct BufferNode *node, *nextnode;

    /* first free all the nodes */

    node = (struct BufferNode*)buflist->bl_ListHeader.mlh_Head;

    while ((nextnode=(struct BufferNode*)node->bn_Node.mln_Succ))
    {

	FreeMem(node->bn_BufferPtr, buflist->bl_BufferNodeSize);

	FreeMem(node,sizeof(struct BufferNode));
	node = nextnode;
    }

    /* Free the bufferlist itself */

    FreeMem(buflist, sizeof (struct BufferList));
    return;
}


/********************/
/* AllocBufferNode  */
/********************/

/* Allocate a new buffernode,
  a new buffer, and add the buffernode to the TAIL of the bufferlist
*/
struct BufferNode *AllocBufferNode(struct BufferList *buflist,
    struct IFFParseBase_intern * IFFParseBase)
{
    /* New buffernodes are added at the HEAD of the list */

    struct BufferNode *n;

    /* first allocate node */
    if ((n=(struct BufferNode*)AllocMem(sizeof(struct BufferNode),MEMF_ANY)))
    {
	/* Then allocate buffer */
	if ((n->bn_BufferPtr=(UBYTE*)AllocMem(buflist->bl_BufferNodeSize, MEMF_ANY)))
	{
	    AddTail((struct List*)buflist,(struct Node*)n);

	    return (n);
	}

	FreeMem(n,sizeof(struct BufferNode));
    }
    return (FALSE);
}

/********************/
/* WriteToBuffer    */
/********************/

/* Copies a number of bytes into the buffer at the current position */

LONG WriteToBuffer( struct BufferList *buflist, UBYTE *mem, LONG size,
    struct IFFParseBase_intern * IFFParseBase)
{
    /* The buffernode that the routine currently will buffer to */
    struct BufferNode *bufnode;


    /* Total number of bytes to be written */
    ULONG   bytes2write;

    /* Bytes left in bufnode that may be written into */
    ULONG  bytesleft;
	  /* The offset int bufnode that the routine will start buffering at */
    ULONG  bufoffset;

    ULONG  bufnodenum;
    /*
      This is the offset in bytes into the whole buffer, not obly into the
      current node
    */
    ULONG offset_into_bigbuf;



    bytes2write = size;

    /* Get pointer to current buffer in list */
    bufnode   = buflist->bl_CurrentNode;
    bufoffset  = buflist->bl_CurrentOffset;

    bufnodenum = buflist->bl_CurrentNodeNum;

    /* Calculate the offset into the buffer as a whole */
    offset_into_bigbuf	=  (bufnodenum  - 1) * buflist->bl_BufferNodeSize + bufoffset;

    while (bytes2write)
    {

	/* Find out how many bytes we can write into the current node */

	bytesleft = buflist->bl_BufferNodeSize - bufoffset;


	if (bytes2write > bytesleft)
	{
	    /* Copy into the old buffer all that there is place for */
	    CopyMem(mem,bufnode->bn_BufferPtr + bufoffset, bytesleft);

	    /* We have written bytesleft bytes */
	    bytes2write -= bytesleft;
	    mem += bytesleft;

	    /* No more space in this buffernode */
	    bytesleft = 0;

	    /* Go to the next buffer in the list */
	    bufnode = (struct BufferNode*)bufnode->bn_Node.mln_Succ;

	    bufnodenum ++;
	    /* We go to the start of the current buffernode */
	    bufoffset = 0;

	    if (!bufnode->bn_Node.mln_Succ)
	    {
		/* No more nodes in the list.	We have to allocate and add a new one */


		bufnode = AllocBufferNode(buflist, IFFParseBase);

		/* Did the allocation succeed ? */
		if (!bufnode)
		    /* Return number of bytes written so far */
		    return (size - bytes2write);


	    }

	}
	else
	{
	    /* There is place enough to write the remaining bytes into the current buffer */
	    CopyMem
	    (
		mem,
		bufnode->bn_BufferPtr + bufoffset,
		bytes2write
	    );

	    bufoffset		  += bytes2write;

	    bytes2write 	=   0;
	}

    }  /* End of while */

    /* If we have reached here, we can be sure that bytes written == size
       (everything has been succesfull) */

    /* Update some stuff */
    buflist->bl_CurrentNode	 = bufnode;
    buflist->bl_CurrentOffset	 = bufoffset;
    buflist->bl_CurrentNodeNum	 = bufnodenum;

    /* Update the offset into buffer as a whole */
    offset_into_bigbuf	+= size;

    /* Have we expanded the buffer ? */
    if (offset_into_bigbuf > buflist->bl_BufferSize)
	 /* Update the size of the buffer */
	 buflist->bl_BufferSize = offset_into_bigbuf;


    return (size);
}

/****************/
/* SeekBuffer	 */
/****************/


BOOL SeekBuffer
(
    struct BufferList *buflist,
    LONG offset

)
{
    struct BufferNode *bufnode;

    /*	The number of bytes we potentially can seek in this buffernode's buffer
    */
    ULONG  left2seekinbuf,
	  /* The size of each buffer. (same for all buffers) */
	  bufnodesize,
	  bufoffset,
	  bufnodenum;

    LONG offset_into_bigbuf,
	  new_offset_into_bigbuf;

    /* Get the size of the buffers */
    bufnodesize  = buflist->bl_BufferNodeSize;
    bufnode	 = buflist->bl_CurrentNode;
    bufoffset	= buflist->bl_CurrentOffset;
    bufnodenum	= buflist->bl_CurrentNodeNum;

    offset_into_bigbuf	=  (bufnodenum - 1) * bufnodesize + bufoffset;

    /* Calculate the new offset into whole buffer. Remember: If the offset is negative,
       this will be substarction.*/
    new_offset_into_bigbuf = offset_into_bigbuf + offset;

    /* Are we seeking forwards, backwords or not at all ? */
    if (offset > 0)
    {
	/* Forwards seek */

	/* Are we trying to seek outside the bounds of the buffer */
	if (new_offset_into_bigbuf > buflist->bl_BufferSize)
	    return (FALSE);

	/* How much is there left to seek towards the end of the first buf ? */
	left2seekinbuf	= bufnodesize - bufoffset;

	while (offset) /* While there are more bytes to seek */
	{
	    if (offset > left2seekinbuf)
	    {
		/* We have to jump to the next buffer in the list */
		bufnode = (struct BufferNode*)bufnode->bn_Node.mln_Succ;

#if 0
		if (!bufnode->bn_Node.mln_Succ)
		    /* Oops !! No more buffers to seek in */
		    return (FALSE);
#endif

		offset -= left2seekinbuf;
		bufnodenum ++;

		/* Current offset is at start of the buffer */
		bufoffset  =  0;

		/* The number of bytes written to this buffer
		is the number we can potentially seek.
		 */
		left2seekinbuf = bufnodesize;

	    }
	    else
	    {
		/* We are at the last buffernode we have to seek. */
		bufoffset += offset;

		/* not more to seek */
		offset	 = 0;
	    }
	}  /* End of while */
    }

    else if (offset < 0)
    {

	/* Backwards seek */

	/* Are we trying to seek outside the bounds of the buffer */
	if (new_offset_into_bigbuf < 0)
	    return (FALSE);

	/* For simplicity we take the absolute value of offset */
	offset = abs(offset);

	/* How much is there left to seek towards the start of the first buf ? */
	left2seekinbuf	= bufoffset;

	while (offset) /* While there are more bytes to seek */
	{
	    if (offset > left2seekinbuf)
	    {

		/* We have to jump to the next buffer in the list */
		bufnode = (struct BufferNode*)bufnode->bn_Node.mln_Pred;

#if 0
		if (bufnode->bn_Node.mln_Pred == NULL )
		    /* Oops !! No more buffers to seek in */
		    return (FALSE);
#endif

		offset -= left2seekinbuf;
		bufnodenum --;

		/* Current offset is at end of the buffer */
		bufoffset  =  bufnodesize;

		/* The number of bytes written to this buffer
		is the number we can potentially seek.
		 */
		left2seekinbuf = bufoffset;

	    }
	    else
	    {
		/* We are at the last buffernode we have to seek. */
		bufoffset -= offset;
		offset	  = 0;

		/* not more to seek */

	    }
	}  /* End of while */
    }

    /* if offset is 0, we are finished seeking */

    buflist->bl_CurrentNode	= bufnode;
    buflist->bl_CurrentOffset	 = bufoffset;
    buflist->bl_CurrentNodeNum	= bufnodenum;

    return (TRUE);

}

/******************/
/* BufferToStream  */
/******************/

/* Writes a whole buffer to a stream.
It is VERY important that intiff->BufferStartDepth is 0, when this
function is called. Else, WriteStream will buffer the writes, and we will
get a nice loop allocating ALL the memory of the machine. (imagine that with VM ;-)

Well, I guess I'll set that to 0 at the start of this function, just for safety.

*/

BOOL BufferToStream
(
    struct BufferList *buflist,
    struct IFFHandle   *iff,
    struct IFFParseBase_intern * IFFParseBase
)
{
    struct BufferNode *node,
		      *nextnode;


    /* Number of bytes axtually writen to the stream,
      or if negative: an IFFERR_.. error */

    LONG   byteswritten,
	  bytes2write,
	  numbytes;

    numbytes = buflist->bl_BufferSize;
    /* For safety. Read at the function header. */
    GetIntIH(iff)->iff_BufferStartDepth = 0;

    nextnode = (struct BufferNode*)buflist->bl_ListHeader.mlh_Head;

    while (numbytes)
    {
	node = nextnode;

	/* Should we write more than the first buffer ? */

	if (numbytes > buflist->bl_BufferNodeSize)
	{

	    nextnode = (struct BufferNode*)node->bn_Node.mln_Succ;
	    /* Check if there are enough buffers to write numbytesf */
	    if (!nextnode) return (FALSE);;


	    bytes2write = buflist->bl_BufferNodeSize;

	}
	else
	{
	    /* We are at the last buffer to write */

	    bytes2write = numbytes;
	}

	/* Write a buffernode to the stream */
	byteswritten = WriteStream
	(
	    iff,
	    node->bn_BufferPtr,
	    bytes2write,
	    IFFParseBase
	);

	/* Do we have a IFFERR_.. ? */
	if (byteswritten < 0) return (FALSE);


	/* There is bytes2write less to write */

	numbytes -= bytes2write;
    }


    return (byteswritten);
}

/*****************************************/
/* BufferStream initialization & cleanup */
/*****************************************/

/* Put in own functions just to make code tidier */

LONG  InitBufferedStream(struct IFFHandle *iff,
    struct IFFParseBase_intern * IFFParseBase)
{
    if
    (
	!(GetIntIH(iff)->iff_BufferStartDepth)
    )
    {
	/* Preserve the stream */
	GetIntIH(iff)->iff_PreservedStream = iff->iff_Stream;

	/* Allocate buffers that WriteStream can buffer its output to */
	if
	(
	    !( iff->iff_Stream = (IPTR)AllocBuffer
		(
		    BUFFERNODESIZE,
		    IFFParseBase
		)
	    )
	)
	      return (IFFERR_NOMEM);

	/* To inform WriteStream that it should buffer all input */
	/* + 1 because we have not PushContextNode()ed yet */

	GetIntIH(iff)->iff_BufferStartDepth = iff->iff_Depth + 1;

	/* Preserve the old hook and the old flags */

	GetIntIH(iff)->iff_PreservedHandler = GetIntIH(iff)->iff_StreamHandler;


	GetIntIH(iff)->iff_PreservedFlags = iff->iff_Flags;


	/* Insert the BufStreamHandler into the hook instead */
	InitIFF(iff, IFFF_RSEEK, &IFFParseBase->bufhook);

    }

    return (NULL);
}

LONG ExitBufferedStream(struct IFFHandle *iff,
    struct IFFParseBase_intern * IFFParseBase)
{
    /*
      If we have come to the chunk that started internal buffering, then we should do the following.

	    - Turn it off, so that WriteStream again writes to the REAL stream.
	    - Write the whole buffer to this stream.
	    - Free the buffer/
    */
    LONG err;

    struct BufferList *buflist;

    /* Turn off buffering */
    GetIntIH(iff)->iff_BufferStartDepth = 0;

      /* Read out the bufferlist stream pointer */
    buflist = (struct BufferList*)iff->iff_Stream;


    /* Now we can reinstall the old StreamHandler */
    GetIntIH(iff)->iff_StreamHandler =   GetIntIH(iff)->iff_PreservedHandler;

    /* Reinstall the old flags */
    iff->iff_Flags = GetIntIH(iff)->iff_PreservedFlags;


    /* Reinstall the old stream */
    iff->iff_Stream = GetIntIH(iff)->iff_PreservedStream;


    /* Write all the buffers into the old stream */
    if
    (
	!BufferToStream
	(
	    buflist,
	    iff,
	    IFFParseBase
	)
    )
	err = IFFERR_WRITE;



    FreeBuffer(buflist, IFFParseBase);

    return (err);
}

/**********************/
/* BufStreamHandler    */
/**********************/

#define IFFParseBase	    (IPB(hook->h_Data))

ULONG BufStreamHandler
(
    struct Hook 	*hook,
    struct IFFHandle	*iff,
    struct IFFStreamCmd  *cmd
)
{

    LONG error;

    switch (cmd->sc_Command)
    {

	case IFFCMD_READ:

	    /* It should NEVER be needed to read a buffered stream.
	      To output the buffered stream to the real stream,
	      we have the routne BufferToStream which is MUCH more effective
	    */

	    error = TRUE;
	    break;

	case IFFCMD_WRITE:
	    error =
	    (
		WriteToBuffer
		(
		    (struct BufferList*)iff->iff_Stream,
		    cmd->sc_Buf,
		    cmd->sc_NBytes,
		    IFFParseBase
		)
	    !=
		cmd->sc_NBytes
	    );

	    break;

	case IFFCMD_SEEK:

	    error =
	    (!
		SeekBuffer
		(
		    (struct BufferList*)iff->iff_Stream,
		    cmd->sc_NBytes
		)
	    );

	    break;

	case IFFCMD_INIT:
	case IFFCMD_CLEANUP:

	    error = NULL;
	    break;

    }

    return (error);
}

