/*
   <p>Circular buffer handling functions.</p>

   <kbd>
   <b>environ - C programming support functions</b><br>
   Copyright (C) 2005  Martin C Gregorie
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the Lesser GNU General Public License.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
  
   You should have received a copy of the Lesser GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   </kbd>
  
   <p>Contact Martin Gregorie at <i>martin@gregorie.org</i></p>
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <poll.h>
#include <fdlist.h>
#include <environ.h>

static int debug = 0;

/*
   Set debugging for the circular buffer function. 0 is off, 1 traces
   function calls and higher values trace internal workings of functions.
*/
void buff_debug(int dbg)
{
   if (dbg < 1)
      debug = 0;
   else
      debug = dbg - 1;
}

/*
   Create and initialise a circular buffer.
   The size of the buffer is set by the <u>size</u>
   argument and, once set, cannot be changed without
   recreating the buffer.
*/
CIRC_BUFF *buff_create(int size)
{
   CIRC_BUFF *b;

   b = get_space(sizeof(CIRC_BUFF));
   b->size = size;
   b->buff = get_space(size);
   buff_reset(b);

   if (debug)
      logmsg("%p = buff_create(%d)", b, size);
      
   return b;
}

/*
   Return the size of a circular buffer
*/
int buff_size(CIRC_BUFF *b)
{
   if (debug)
      logmsg("%d = buff_size(%p)", b->size, b);

   return b->size;
}

/*
   Initialise (reset) a circular buffer.
*/
void buff_reset(CIRC_BUFF *b)
{
   memset(b->buff, 0, b->size);

   /*
      Detecting the buffer full condition depends on the
      buffer write pointer always being logically ahead
      of the buffer read pointer, hence the initialisation
      values for these pointers.
   */ 
   b->read_ptr = 0;
   b->write_ptr = 1;

   if (debug)
      logmsg("buff_reset(%p)", b);
}

/*
   Release the space occupied by a circular buffer.
   Returns NULL.
*/
CIRC_BUFF *buff_delete(CIRC_BUFF *b)
{
   release_space(b->buff);
   release_space(b);
   
   if (debug)
      logmsg("buff_delete(%p)", b);

   return 0;
}
/*
   Returns the available space in the circular buffer*/
int buff_freespace(CIRC_BUFF *b)
{
   int n;

   if (b->write_ptr > b->read_ptr)
      n = (b->size -1) - b->write_ptr + b->read_ptr;
   else
   if (b->write_ptr == b->read_ptr)
         n = 0;
   else
   if (b->write_ptr < b->read_ptr)
      n = b->read_ptr - b->write_ptr;
   
   if (debug)
      logmsg("%d = buff_freespace() r=%d, w=%d s=%d",
             n,
             b->read_ptr,
             b->write_ptr,
             b->size);
      
   return n;
}

/*
   Returns the occupied space in the circular buffer.
*/
int buff_usedspace(CIRC_BUFF *b)
{
   int n = b->size - buff_freespace(b) - 2;
   
   if (debug)
      logmsg("%d = buff_usedspace() r=%d, w=%d s=%d",
             n,
             b->read_ptr,
             b->write_ptr,
             b->size);
      
   return n;
}

/*
   Add a byte <u>c</u> into the circular buffer <u>b</u>.
   Returns 1 if a byte was added.
   If the buffer is full zero is returned.
*/
int buff_add_one(CIRC_BUFF *b, char c)
{
   int   rs = 1;

   if (buff_freespace(b) < 1)
      rs = 0;                       /* No space in buffer */
   else
   {
      b->buff[b->write_ptr] = c;
      b->write_ptr = (b->write_ptr + 1) % b->size;
   }

   if (debug)
      logmsg("%d = buff_add_one(%p,%s)", rs, b, byteout(c));

   return rs;
}

/*
   Move <u>n</u> characters from <u>s</u> into a circular buffer, <u>b</u>.
   You should call <i>buff_freespace()</i> first to check that there is
   enough space in the buffer to hold the string.
*/
void buff_add_string(CIRC_BUFF *b, char *s, int n)
{
   int i;
   
   for (i = 0; i < n; i++)
   {
      buff_add_one(b, s[i]);
   }

   if (debug)
      logmsg("buff_add_string(%p, %s, %d) r=%d, w=%d s=%d",
             b,
             s,
             n,
             b->read_ptr,
             b->write_ptr,
             b->size);
}   

/*
   Return the next byte from the circular buffer <u>b</u>.
   Returns zero if the buffer is empty and -1 for errors.
*/
int buff_extract_one(CIRC_BUFF *b)
{
   char  c;

   if ((b->read_ptr + 1) == b->write_ptr)
   {
      c = 0;
   }
   else
   {
      b->read_ptr = (b->read_ptr + 1 ) % b->size;
      c = b->buff[b->read_ptr];
   }

   if (debug)
      logmsg("%d(%s) = buff_extract_one(%p)", c, byteout(c), b);

   return c;
}

/*
   Pull up to <u>n</u> characters out of a circular buffer, <u>b</u>,
   and put them in the reply area, <u>s</u>.
   Use the separator in <u>sep_marker</u>, if <u>sep_active</u> is true,
   as the string terminator.
   Separators are discarded (<u>sep_extern</u> is true) or included
   in the string (<u>sep_extern</u> is false).
   Returns the number of bytes extracted.
*/
int buff_extract_string(CIRC_BUFF *b,
                        char *s,
                        int  n,
                        int  sep_active,
                        int  sep_extern,
                        char sep_marker)
{
   int   i;
   int   c;
   
   for (i = 0; i < n; i++)
   {
      c = buff_extract_one(b);
      if (!c)
         break;

      if (!sep_active)                    /* Sort out line separators */
      {
         s[i] = c;                        /* No separator - just copy */
      }
      else
      {                                   /* Separators used */
         if (c != sep_marker)             /* Not a separator */
         {
            s[i] = c;                     /* Copy it         */
         }
         else
         {                                /* Separator found */
            if (sep_extern)
            {                             /* External separator in use */
               break;                     /* Ignore it but stop        */
            }
            else
            {                             /* Internal separator in use */
               s[i] = c;                  /* Copy it */
               i++;                       /* Let the caller know that a */
               break;                     /* separator is included and stop */
	    }
         }
      }
   }

   s[i] = 0;
   
   if (debug)
      logmsg("%d = buff_extract_string(%p,%s,%d,%s,%s,%s) r=%d, w=%d s=%d",
             i,
             b,
             s,
             n,
             (sep_active ? "Sep_act" : "Sep_inact"),
             (sep_extern ? "Sep_ext" : "Sep_int"),
             byteout(sep_marker),
             b->read_ptr,
             b->write_ptr,
             b->size);
         
   return i;
}   
