/*
 * Copyright (C) 2008 Search Solution Corporation. All rights reserved by Search Solution.
 *
 *   This program 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; version 2 of the 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 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
 *
 */

/*
 * variable_string.c : Flexible strings that allow unlimited appending, 
 *                     prepending, etc.
 */

#ident "$Id$"

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include "variable_string.h"
#include "error_code.h"

#define FUDGE		16
#define PREFIX_CUSHION	 8
#define VS_INC		32

static const char *EMPTY_STRING = "";

static int vs_cannot_hold (varstring * vstr, int n);
static int vs_grow (varstring * vstr, int n);
static int vs_do_sprintf (varstring * vstr, const char *fmt, va_list args);
static int vs_itoa (varstring * vstr, int n);

/*
 * vs_cannot_hold() - Check vstr can hold or not
 *   return: true if cannot hold, false if can hold
 *   vstr(in/out): the varstring to be grown
 *   n(in): the size
 */
static int
vs_cannot_hold (varstring * vstr, int n)
{
  return ((vstr->base == NULL) || (vstr->limit - vstr->end) < n);
}

/*
 * vs_grow() - Enlarge the buffer area of the given varstring by the
 *             given amount.
 *   return: -1 if failure, 0 if success
 *   vstr(in/out): the varstring to be grown
 *   n(in): the size to grow it by.
 */
static int
vs_grow (varstring * vstr, int n)
{
  if (vstr == NULL)
    {
      return ER_FAILED;
    }

  if (vstr->base)
    {
      int size = vstr->limit - vstr->base;
      int length = vstr->end - vstr->start;
      int offset = vstr->start - vstr->base;
      char *new_buf = (char *) malloc (sizeof (char) * (size + n));
      if (new_buf == NULL)
	{
	  return ER_FAILED;
	}

      /*
       * Don't use strcpy here; vs_grow() can be invoked in the middle
       * of string processing while the string isn't properly
       * null-terminated.
       */
      memcpy (new_buf, vstr->base, size);
      free (vstr->base);

      vstr->base = new_buf;
      vstr->limit = new_buf + size + n;
      vstr->start = new_buf + offset;
      vstr->end = vstr->start + length;
    }
  else
    {
      char *new_buf = (char *) malloc (sizeof (char) * n);

      if (new_buf == NULL)
	{
	  return ER_FAILED;
	}

      vstr->base = new_buf;
      vstr->limit = new_buf + n;
      vstr->start = new_buf + (n > PREFIX_CUSHION ? PREFIX_CUSHION : 0);
      vstr->end = vstr->start;
      *vstr->start = '\0';
    }

  return NO_ERROR;
}

/*
 * vs_do_sprintf() - Format the arguments into vstr according to fmt
 *   return: -1 if failure, 0 if success
 *   vstr(in/out): pointer to a varstring.
 *   fmt(in) : printf-style format string.
 *   args(in): stdarg-style package of arguments to be formatted.
 *
 * Note: Only understands %s and %d right now.
 */
static int
vs_do_sprintf (varstring * vstr, const char *fmt, va_list args)
{
  int c;
  char *p, *limit;

  if (vstr->base == NULL && vs_grow (vstr, VS_INC))
    {
      return ER_FAILED;
    }

  while (1)
    {
    restart:
      for (p = vstr->end, limit = vstr->limit; p < limit;)
	{
	  switch (c = *fmt++)
	    {
	    case '%':
	      switch (c = *fmt++)
		{
		case '%':
		  *p++ = '%';
		  continue;

		case 's':
		  vstr->end = p;
		  if (vs_strcat (vstr, va_arg (args, char *)))
		    {
		      return 1;
		    }
		  goto restart;

		case 'd':
		  vstr->end = p;
		  if (vs_itoa (vstr, va_arg (args, int)))
		    {
		      return 1;
		    }
		  goto restart;

		default:
		  break;
		}
	      /* Fall through */

	    case '\0':
	      *p = '\0';
	      vstr->end = p;
	      return NO_ERROR;

	    default:
	      *p++ = (char) c;
	      continue;
	    }
	}

      vstr->end = p;
      if (vs_grow (vstr, VS_INC))
	{
	  return ER_FAILED;
	}
    }

  return NO_ERROR;
}

/*
 * vs_itoa() -  Append the text representation of the given integer
 *   return: -1 if failure, 0 if success
 *   vstr(in/out)
 *   n(in)
 *
 * Note:
 */
static int
vs_itoa (varstring * vstr, int n)
{
  char buf[32];
  sprintf (buf, "%d", n);
  return vs_strcat (vstr, buf);
}

/*
 * vs_new() - Allocate (if necessary) and initialize a varstring.
 *   return: pointer to the vstr if success, NULL if failure
 *   vstr(in): pointer of varstring to be initialized, possibly NULL.
 */
varstring *
vs_new (varstring * vstr)
{
  if (vstr == NULL)
    {
      vstr = (varstring *) malloc (sizeof (varstring));
      vstr->heap_allocated = 1;
    }
  else
    {
      vstr->heap_allocated = 0;
    }

  if (vstr)
    {
      vstr->base = NULL;
      vstr->limit = NULL;
      vstr->start = NULL;
      vstr->end = NULL;
    }

  return vstr;
}

/*
 * vs_free() - free varstring if it was heap allocated.
 *   return: none
 *   vstr(in/out)
 */
void
vs_free (varstring * vstr)
{
  if (vstr == NULL)
    {
      return;
    }

  if (vstr->base)
    {
      free (vstr->base);
      vstr->base = NULL;
    }

  if (vstr->heap_allocated)
    {
      free (vstr);
    }
}

/*
 * vs_clear() - Reset the buffer pointers in varstring.
 *   return: none
 *   vstr(in/out)
 */
void
vs_clear (varstring * vstr)
{
  if (vstr == NULL || vstr->base == NULL)
    {
      return;
    }

  vstr->start = vstr->base +
    (vstr->limit - vstr->base < PREFIX_CUSHION ? 0 : PREFIX_CUSHION);

  vstr->end = vstr->start;
  *vstr->start = '\0';
}


/*
 * vs_append() - synonym for vs_strcat().
 *   return: -1 if failure, 0 if success.
 *   vstr(in/out)
 *   suffix(in)
 */
int
vs_append (varstring * vstr, const char *suffix)
{
  return vs_strcat (vstr, suffix);
}

/*
 * vs_prepend() - Prefix a string onto the varstring.
 *   return: -1 if failure, 0 if success
 *   vstr(in/out)
 *   prefix(in)
 *
 * Note:
 */
int
vs_prepend (varstring * vstr, const char *prefix)
{
  int n, available;

  if (vstr == NULL)
    {
      return ER_FAILED;
    }

  n = strlen (prefix);
  if (prefix == NULL || n == 0)
    {
      return NO_ERROR;
    }

  if (vstr->base == NULL && vs_grow (vstr, n + FUDGE))
    {
      return ER_FAILED;
    }

  available = vstr->start - vstr->base;
  if (available < n)
    {
      /*
       * Make room at the front of the string for the prefix.  If there
       * is enough slop at the end, shift the current string toward the
       * end without growing the string; if not, grow it and then do
       * the shift.
       */
      char *new_start;
      int length;

      if (vs_cannot_hold (vstr, PREFIX_CUSHION + (n - available))
	  && vs_grow (vstr, n + PREFIX_CUSHION + FUDGE))
	{
	  return ER_FAILED;
	}

      length = vstr->end - vstr->start;
      new_start = vstr->base + n + PREFIX_CUSHION;

      memmove (new_start, vstr->start, length);

      vstr->end = new_start + length;
      vstr->start = new_start;
      *vstr->end = '\0';
    }

  vstr->start -= n;
  memcpy (vstr->start, prefix, n);

  return NO_ERROR;
}

/*
 * vs_sprintf() - Perform a sprintf-style formatting into vstr
 *   return: -1 if failure, 0 if success.
 *   vstr(in/out)
 *   fmt(in)
 *
 * Note: only the %s and %d codes are supported.
 */
int
vs_sprintf (varstring * vstr, const char *fmt, ...)
{
  int status;
  va_list args;

  if (vstr == NULL || fmt == NULL)
    {
      return ER_FAILED;
    }

  va_start (args, fmt);
  status = vs_do_sprintf (vstr, fmt, args);
  va_end (args);

  return status;
}

/*
 * vs_strcat() - Concatenate string onto the buffer in varstring
 *   return: -1 if failure, 0 if success.
 *   vstr(in/out)
 *   str(in)
 */
int
vs_strcat (varstring * vstr, const char *str)
{
  int n;

  if (vstr == NULL)
    {
      return ER_FAILED;
    }

  if (str == NULL || (n = strlen (str)) == 0)
    {
      return NO_ERROR;
    }

  if (vs_cannot_hold (vstr, n) && vs_grow (vstr, n + FUDGE))
    {
      return ER_FAILED;
    }

  memcpy (vstr->end, str, n);
  vstr->end += n;

  return NO_ERROR;

}

/*
 * vs_strcatn() - Concatenate str onto the buffer in varstring for
 *                a given length
 *   return: -1 if failure, 0 if success.
 *   vstr(in/out)
 *   str(in)
 *   length(in)
 */
int
vs_strcatn (varstring * vstr, const char *str, int length)
{
  if (vstr == NULL)
    {
      return ER_FAILED;
    }

  if (str == NULL || length == 0)
    {
      return NO_ERROR;
    }

  if (vs_cannot_hold (vstr, length) && vs_grow (vstr, length + FUDGE))
    {
      return ER_FAILED;
    }

  memcpy (vstr->end, str, length);
  vstr->end += length;

  return NO_ERROR;
}

/*
 * vs_strcpy() - Initialize varstring with the contents of string.
 *   return: -1 if failure, 0 if success.
 *   vstr(in/out)
 *   str(in)
 */
int
vs_strcpy (varstring * vstr, const char *str)
{
  vs_clear (vstr);
  return vs_strcat (vstr, str);
}

/*
 * vs_putc() - Put a single character in varstring
 *   return: -1 if failure, 0 if success.
 *   vstr(in/out)
 *   ch(in)
 */
int
vs_putc (varstring * vstr, int ch)
{
  if (vstr == NULL)
    {
      return ER_FAILED;
    }

  if (vs_cannot_hold (vstr, 1) && vs_grow (vstr, FUDGE))
    {
      return ER_FAILED;
    }

  *vstr->end++ = (char) ch;

  return NO_ERROR;

}

/*
 * vs_str() - Return the prepared character string within varstring.
 *   return: the start pointer of varstring
 *   vstr(in/out)
 */
char *
vs_str (varstring * vstr)
{
  if (vstr == NULL || vstr->base == NULL)
    {
      return (char *) EMPTY_STRING;
    }
  else
    {
      /*
       * Make sure it's null-terminated by emitting a null character
       * and then backing up the end pointer.
       */
      if (vs_cannot_hold (vstr, 1) && vs_grow (vstr, FUDGE))
	{
	  return NULL;
	}

      *vstr->end = '\0';
      return vstr->start;
    }
}

/*
 * vs_strlen() - Return the length of the string managed by varstring.
 *   return: length of the varstring
 *   vstr(in)
 */
int
vs_strlen (const varstring * vstr)
{
  if (vstr == NULL || vstr->base == NULL)
    {
      return 0;
    }

  return (vstr->end - vstr->start);
}
