/*  -*- Mode: C -*-  */

/* filament.c --- a bit like a string, but different =)O|
 * Copyright (C) 1999 Gary V. Vaughan
 * Originally by Gary V. Vaughan, 1999
 * This file is part of Snprintfv
 *
 * Snprintfv is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * Snprintfv 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 GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses>.
 *
 * As a special exception to the GNU General Public License, if you
 * distribute this file as part of a program that also links with and
 * uses the libopts library from AutoGen, you may include it under
 * the same distribution terms used by the libopts library.
 */

/* Commentary:
 *
 * Try to exploit usage patterns to optimise string handling, and
 * as a happy consequence handle NUL's embedded in strings properly.
 *
 * o Since finding the length of a (long) string is time consuming and
 *   requires careful coding to cache the result in local scope: We
 *   keep count of the length of a Filament all the time, so finding the
 *   length is O(1) at the expense of a little bookkeeping while
 *   manipulating the Filament contents.
 *
 * o Constantly resizing a block of memory to hold a string is memory
 *   efficient, but slow:  Filaments are only ever expanded in size,
 *   doubling at each step to minimise the number of times the block
 *   needs to be reallocated and the contents copied (this problem is
 *   especially poignant for very large strings).
 *
 * o Most strings tend to be either relatively small and short-lived,
 *   or else long-lived but growing in asymptotically in size: To
 *   care for the former case, Filaments start off with a modest static
 *   buffer for the string contents to avoid any mallocations (except
 *   the initial one to get the structure!); the latter case is handled
 *   gracefully by the resizing algorithm in the previous point.
 *
 * o Extracting a C-style NUL terminated string from the Filament is
 *   an extremely common operation:  We ensure there is always a
 *   terminating NUL character after the last character in the string
 *   so that the conversion can be performed quickly.
 *
 * In summary, Filaments are good where you need to do a lot of length
 * calculations with your strings and/or gradually append more text
 * onto existing strings.  Filaments are also an easy way to get 8-bit
 * clean strings is a more lightweight approach isn't required.
 *
 * They probably don't buy much if you need to do insertions and partial
 * deletions, but optimising for that is a whole other problem!
 */

/* Code: */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#ifdef WITH_DMALLOC
#  include <dmalloc.h>
#endif

#include "mem.h"
#include "filament.h"



/**
 * filnew: constructor
 * @init: address of the first byte to copy into the new object.
 * @len:  the number of bytes to copy into the new object.
 *
 * Create a new Filament object, initialised to hold a copy of the
 * first @len bytes starting at address @init.  If @init is NULL, or
 * @len is 0 (or less), then the initialised Filament will return the
 * empty string, "", if its value is queried.
 *
 * Return value:
 * A newly created Filament object is returned.
 **/
Filament *
filnew (const char *const init, size_t len)
{
  Filament *new;

  new = snv_new (Filament, 1);

  new->value = new->buffer;
  new->length = 0;
  new->size = FILAMENT_BUFSIZ;

  return (init || len) ? filinit (new, init, len) : new;
}

/**
 * filinit:
 * @fil: The Filament object to initialise.
 * @init: address of the first byte to copy into the new object.
 * @len:  the number of bytes to copy into the new object.
 *
 * Initialise a Filament object to hold a copy of the first @len bytes
 * starting at address @init.  If @init is NULL, or @len is 0 (or less),
 * then the Filament will be reset to hold the empty string, "".
 *
 * Return value:
 * The initialised Filament object is returned.
 **/
Filament *
filinit (Filament *fil, const char *const init, size_t len)
{
  if (init == NULL || len < 1)
    {
      /* Recycle any dynamic memory assigned to the previous
         contents of @fil, and point back into the static buffer. */
      if (fil->value != fil->buffer)
	snv_delete (fil->value);

      fil->value = fil->buffer;
      fil->length = 0;
      fil->size = FILAMENT_BUFSIZ;
    }
  else
    {
      if (len < FILAMENT_BUFSIZ)
	{
	  /* We have initialisation data which will easily fit into
	     the static buffer: recycle any memory already assigned
	     and initialise in the static buffer. */
	  if (fil->value != fil->buffer)
	    {
	      snv_delete (fil->value);
	      fil->value = fil->buffer;
	      fil->size = FILAMENT_BUFSIZ;
	    }
	}
      else
	{
	  /* If we get to here then we never try to shrink the already
	     allocated dynamic buffer (if any), we just leave it in
	     place all ready to expand into later... */
	  fil_maybe_extend (fil, len, false);
	}

      snv_assert (len < fil->size);

      fil->length = len;
      memcpy (fil->value, init, len);
    }

  return fil;
}

/**
 * fildelete: destructor
 * @fil: The Filament object for recycling.
 *
 * The memory being used by @fil is recycled.
 *
 * Return value:
 * The original contents of @fil are converted to a null terminated
 * string which is returned, either to be freed itself or else used
 * as a normal C string.  The entire Filament contents are copied into
 * this string including any embedded nulls.
 **/
char *
fildelete (Filament *fil)
{
  char *value;

  if (fil->value == fil->buffer)
    {
      value = memcpy (snv_new (char, 1 + fil->length),
		      fil->buffer, 1 + fil->length);
      value[fil->length] = '\0';
    }
  else
    value = filval (fil);

  snv_delete (fil);

  return value;
}

/**
 * _fil_extend:
 * @fil: The Filament object which may need more string space.
 * @len: The length of the data to be stored in @fil.
 * @copy: whether to copy data from the static buffer on reallocation.
 *
 * This function will will assign a bigger block of memory to @fil
 * considering the space left in @fil and @len, the length required
 * for the prospective contents.
 */
void
_fil_extend (Filament *fil, size_t len, bool copy)
{
  /* Usually we will simply double the amount of space previously
     allocated, but if the extra data is larger than the current
     size it *still* won't fit, so in that case we allocate enough
     room plus some we leave the current free space to expand into. */
  fil->size += MAX (len, fil->size);

  if (fil->value == fil->buffer)
    {
      fil->value = snv_new (char, fil->size);
      if (copy)
	memcpy (fil->value, fil->buffer, fil->length);
    }
  else
    fil->value = snv_renew (char, fil->value, fil->size);
}

/*
 * Local Variables:
 * mode: C
 * c-file-style: "gnu"
 * indent-tabs-mode: nil
 * End:
 * end of snprintfv/filament.c */
