/*
 * bufQueue.c --
 *
 *	Implementation of a queue out of buffers.
 *
 * Copyright (c) 2000 by Andreas Kupries <a.kupries@westend.com>
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: bufQueue.c,v 1.2 2002/04/25 06:29:48 andreas_kupries Exp $
 */

#include "buf.h"

/*
 * Internal structures used to hold the buffers in the queue.
 */

/*
 * Structure of a node in the queue.
 */

typedef struct QNode_ {
  Buf_Buffer     buf;     /* The buffer managed by the node */
  struct QNode_* nextPtr; /* Reference to the next node/buffer */
} QNode;

/*
 * Structure of the whole queue.
 */

typedef struct Queue_ {
  QNode*    firstNode;  /* Head of the queue */
  QNode*    lastNode;   /* Last node/buffer in the queue */
  int       size;       /* Number of bytes stored in the queue */
#if GT81
  Tcl_Mutex lock;       /* mutex to serialize access to the
			 * queue when more than one thread
			 * is trying to access it. */
#endif
} Queue;

/*
 * Declaration of size to use for new buffers when
 * extending the queue
 */

#define BUF_SIZE (1024)


/*
 *------------------------------------------------------*
 *
 *	Buf_NewQueue --
 *
 *	Creates a new, empty queue.
 *
 *	Sideeffects:
 *		Allocates and initializes memory.
 *
 *	Result:
 *		A queue token.
 *
 *------------------------------------------------------*
 */

Buf_BufferQueue
Buf_NewQueue ()
{
  Queue* q = (Queue*) Tcl_Alloc (sizeof (Queue));

  q->firstNode = (QNode*) NULL;
  q->lastNode  = (QNode*) NULL;
  q->size      = 0;
#if GT81
  q->lock      = (Tcl_Mutex) NULL;
#endif
  return (Buf_BufferQueue) q;
}

/*
 *------------------------------------------------------*
 *
 *	Buf_FreeQueue --
 *
 *	Deletes the specified queue.
 *
 *	Sideeffects:
 *		Deallocates the memory which was
 *		allocated in Buf_NewQueue.
 *
 *	Result:
 *		None.
 *
 *------------------------------------------------------*
 */

void
Buf_FreeQueue (queue)
     Buf_BufferQueue queue;
{
  Queue* q = (Queue*) queue;
  QNode* n = q->firstNode;
  QNode* tmp;

#if GT81
  Tcl_MutexLock (&q->lock);
#endif

  while (n != (QNode*) NULL) {
    Buf_DecrRefcount (n->buf);
    tmp = n->nextPtr;
    Tcl_Free ((char*) n);
    n = tmp;
  }

#if GT81
  Tcl_MutexUnlock   (&q->lock);
  Tcl_MutexFinalize (&q->lock);
#endif
  Tcl_Free((char*) q);
  return;
}

/*
 *------------------------------------------------------*
 *
 *	Buf_QueueRead --
 *
 *	Reads information from the queue. The read data
 *	is deleted from the queue.
 *
 *	Sideeffects:
 *		May deallocate memory. Moves the access
 *		pointer in the queue buffers.
 *
 *	Result:
 *		Returns the number of bytes actually read.
 *
 *------------------------------------------------------*
 */

int
Buf_QueueRead (queue, outbuf, size)
     Buf_BufferQueue queue;
     char*           outbuf;
     int             size;
{
  Queue* q = (Queue*) queue;
  QNode* n;
  int    got, read;

#if GT81
  Tcl_MutexLock (&q->lock);
#endif

  n = q->firstNode;

  if ((size <= 0) || (n == (QNode*) NULL)) {
#if GT81
    Tcl_MutexUnlock (&q->lock);
#endif
    return 0;
  }

  read = 0;
  while ((size > 0) && (n != (QNode*) NULL)) {
    got = Buf_Read (n->buf, outbuf, size);

    if (got > 0) {
      read   += got;
      outbuf += got;
      size   -= got;
    }

    if (size > 0) {
      Buf_DecrRefcount (n->buf);
      q->firstNode = n->nextPtr;
      Tcl_Free ((char*) n);
      n = q->firstNode;
    }
  }

  if (n == (QNode*) NULL) {
    q->lastNode = (QNode*) NULL;
  }

  q->size -= read;

#if GT81
  Tcl_MutexUnlock (&q->lock);
#endif

  return read;
}

/*
 *------------------------------------------------------*
 *
 *	Buf_QueueWrite --
 *
 *	Writes information to the queue. The written data
 *	is appended at the end of the queue.
 *
 *	Sideeffects:
 *		May allocate memory. Moves the access
 *		pointer in the queue buffers.
 *
 *	Result:
 *		Returns the number of bytes actually written.
 *
 *------------------------------------------------------*
 */

int
Buf_QueueWrite (queue, inbuf, size)
Buf_BufferQueue queue;
const char*     inbuf;
int             size;
{
  Queue* q = (Queue*) queue;
  QNode* n;
  int    done, written;

  if ((size <= 0)) {
    return 0;
  }

#if GT81
  Tcl_MutexLock (&q->lock);
#endif

  n       = q->firstNode;
  written = 0;

  while (size > 0) {
    if (n == (QNode*) NULL) {
      n = (QNode*) Tcl_Alloc (sizeof (QNode));
      n->nextPtr = (QNode*) NULL;
      n->buf     = Buf_CreateFixedBuffer (BUF_SIZE);

      if (q->lastNode == (QNode*) NULL) {
	q->firstNode = n;
      } else {
	q->lastNode->nextPtr = n;
      }

      q->lastNode = n;
    }

    done = Buf_Write (n->buf, inbuf, size);

    if (done > 0) {
      written += done;
      inbuf   += done;
      size    -= done;
    }
    if (size > 0) {
      n = (QNode*) NULL;
    }
  }

  q->size += written;

#if GT81
  Tcl_MutexUnlock (&q->lock);
#endif

  return written;
}

/*
 *------------------------------------------------------*
 *
 *	BufQueue_Append --
 *
 *	Appends a range containing the information
 *	not yet read from the specified buffer to the queue.
 *
 *	Sideeffects:
 *		Creates a range buffer, allocates memory.
 *
 *	Result:
 *		None.
 *
 *------------------------------------------------------*
 */

void
Buf_QueueAppend (queue, buf)
     Buf_BufferQueue queue;
     Buf_Buffer      buf;
{
  /* Not the buffer is appended, but a range containing
   * the rest of the data to read from it.
   *
   * Allows external usage of the buffer without affecting
   * the queue. Writing (s.a.) is no problem, as ranges
   * always return that nothing was written and thus force
   * the system to append a new fixed-size buffer behind them.
   */

  Queue* q = (Queue*) queue;
  QNode* n;

#if GT81
  Tcl_MutexLock (&q->lock);
#endif

  buf = Buf_CreateRange (buf, Buf_Size (buf));

  n = (QNode*) Tcl_Alloc (sizeof (QNode));
  n->nextPtr = (QNode*) NULL;
  n->buf     = buf;

  if (q->lastNode == (QNode*) NULL) {
    q->firstNode = n;
  } else {
    q->lastNode->nextPtr = n;
  }

  q->lastNode = n;

  q->size += Buf_Size (buf);

#if GT81
  Tcl_MutexUnlock (&q->lock);
#endif
  return;
}

/*
 *------------------------------------------------------*
 *
 *	BufQueue_Size --
 *
 *	Returns the current number of bytes stored in the queue.
 *
 *	Sideeffects:
 *		None.
 *
 *	Result:
 *		None.
 *
 *------------------------------------------------------*
 */

int
Buf_QueueSize (queue)
     Buf_BufferQueue queue;
{
  Queue* q = (Queue*) queue;
  int size;

#if GT81
  Tcl_MutexLock (&q->lock);
#endif

  size = q->size;

#if GT81
  Tcl_MutexUnlock (&q->lock);
#endif
  return size;
}
