/* File:      varstring.c
** Author(s): kifer
** Contact:   xsb-contact@cs.sunysb.edu
** 
** Copyright (C) The Research Foundation of SUNY, 1999
** 
** XSB is free software; you can redistribute it and/or modify it under the
** terms of the GNU Library General Public License as published by the Free
** Software Foundation; either version 2 of the License, or (at your option)
** any later version.
** 
** XSB 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 Library General Public License for
** more details.
** 
** You should have received a copy of the GNU Library General Public License
** along with XSB; if not, write to the Free Software Foundation,
** Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
** $Id: varstring.c,v 1.13 2001/07/24 15:36:49 dwarren Exp $
** 
*/


/*
  Usage:

  XSB_StrDefine(foo);  // Declare foo as a variable length string
  XSB_StrDefine(boo);  // Declare boo as a variable length string
  VarString *goo;    // Declare a pointer to a varstring.

  foo.op->set(&foo,"abc");        // or XSB_StrSet(&foo, "abc");
  foo.op->append(&foo,"123");     // or XSB_StrAppend(&foo, "123");
  // shrink foo, set the increment to 5
  foo.op->shrink(&foo,5);         // or XSB_StrShrink(&foo,5);
  foo.op->prepend(&foo,"098");    // or XSB_StrPrepend(&foo,"098");

  boo.op->prepend("pasddsf");

  goo = &boo;
  goo->op->strcmp(goo, "jdshdd"); // or XSB_StrStrCmp(goo,"jdshdd");

  if (foo.length > 0)
     printf("%s   %d\n", foo.string, foo.length);

  printf("boo: %s\n", goo->string);

*/


/* To test: define the string below and compile: cc varstring.c; a.out */
#undef DEBUG_VARSTRING


#include "xsb_config.h"

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <string.h>

#include "wind2unix.h"

#include "auxlry.h"
#include "cell_xsb.h"
#ifndef DEBUG_VARSTRING
#include "error_xsb.h"
#endif

#include "varstring_xsb.h"


#define DEFAULT_VARSTR_INCREMENT     128
#define NEWLENGTH(vstr,additional_size) \
    	    	    	    	vstr->length + additional_size +1


static void  vs_init(VarString*, int);
static void  vs_set(VarString*, char*);
static void  vs_setv(VarString*, VarString*);
static void  vs_append(VarString*, char*);
static void  vs_prepend(VarString*, char*);
static inline void  vs_appendv(VarString*, VarString*);
static inline void  vs_prependv(VarString*, VarString*);
static inline int   vs_compare(VarString*, VarString*);
static inline int   vs_strcmp(VarString*, char*);
static inline void  vs_destroy(VarString*);
static inline void  vs_shrink(VarString*, int);
static inline void  vs_ensure_size(VarString*, int);

/* add char to the end of string, don't null-terminate */
static void  vs_appendblk(VarString *vstr,char*,int);
static void  vs_prependblk(VarString *vstr,char*,int);
static void  vs_adjust_size(VarString *vstr, int add_size);
static inline void vs_null_terminate(VarString *vstr);

DllExport struct varstr_ops VarStrOps = {vs_set,vs_setv,
					 vs_append,vs_prepend, 
					 vs_appendv,vs_prependv,
					 vs_compare,vs_strcmp,
					 vs_appendblk,vs_prependblk,
					 vs_null_terminate,
					 vs_ensure_size,
					 vs_shrink,vs_destroy};


DllExport void call_conv varstring_init(VarString *vstr)
{
  vstr->size = 0;
  vstr->increment = 0;
  vstr->length =0;
  vstr->string = NULL;
  vstr->op = &VarStrOps;
}

DllExport void call_conv varstring_create(VarString **vstr)
{
  *vstr = (VarString *) malloc(sizeof(VarString));
  varstring_init(*vstr);
}


/* initialize a var string. This is the only function that isn't a member of
   the data structure, because somebody must assign functions to the function
   pointers inside struct varstr */
static void vs_init(VarString *vstr, int increment)
{
  if (vstr->string != NULL)
    return;

  if (increment < 1)
    increment = DEFAULT_VARSTR_INCREMENT;

  if (NULL == (vstr->string = (char *)calloc(1, increment))) {
#ifdef DEBUG_VARSTRING
    fprintf(stderr, "Cannot allocate memory for a variable-length string\n");
    return;
#else
    xsb_abort("Cannot allocate memory for a variable-length string");
#endif
  }

  vstr->increment   = increment;
  vstr->size        = increment;
  vstr->length      = 0;
  vstr->string[0]   = '\0';
}


/* change the increment and also shrink the allocated space to the minimum */
static inline void vs_shrink(VarString *vstr, int increment)
{
  if (vstr->string == NULL)
    vs_init(vstr,increment);
  else { /* already initialized */
    vstr->increment = increment;
    /* make sure we don't clobber existing stuff */
    vs_adjust_size(vstr, vstr->length+1);
  }
}


/* ensure that the VarString has room for minsize bytes */
static inline void vs_ensure_size(VarString *vstr, int minsize)
{
  vs_init(vstr,0);

  vs_adjust_size(vstr, max(vstr->length,minsize)+1);
}


static void vs_set(VarString *vstr, char *str)
{
  int newlength;

  vs_init(vstr,0); /* conditional init */

  if (str == NULL) {
#ifdef DEBUG_VARSTRING
    fprintf(stderr, "Assigning a NULL pointer to a variable-length string\n");
    return;
#else
    xsb_bug("Assigning a NULL pointer to a variable-length string");
#endif
  }

  newlength = strlen(str);

  vs_adjust_size(vstr, newlength+1);

  strcpy(vstr->string, str);
  vstr->length=newlength;
}

static void vs_setv(VarString *vstr, VarString *vstr1)
{
  vs_set(vstr, vstr1->string);
}

static void vs_append(VarString *vstr, char *str)
{
  if (str == NULL) {
#ifdef DEBUG_VARSTRING
    fprintf(stderr, "Appending a NULL string\n");
    return;
#else
    xsb_bug("Appending a NULL string");
#endif
  }
  vs_appendblk(vstr, str, strlen(str));
  vs_null_terminate(vstr);
}

static void vs_prepend(VarString *vstr, char *str)
{
  if (str == NULL) {
#ifdef DEBUG_VARSTRING
    fprintf(stderr, "Appending a NULL string\n");
    return;
#else
    xsb_bug("Appending a NULL string");
#endif
  }
  vs_prependblk(vstr, str, strlen(str));
}

static inline void vs_appendv(VarString *vstr, VarString *vstr1)
{
  vs_append(vstr, vstr1->string);
}

static inline void vs_prependv(VarString *vstr, VarString *vstr1)
{
  vs_prepend(vstr, vstr1->string);
}

static inline int vs_compare(VarString *vstr, VarString *vstr1)
{
  /* conditional init */
  vs_init(vstr,0);
  vs_init(vstr1,0);

  return strcmp(vstr->string, vstr1->string);
}

static inline int vs_strcmp(VarString *vstr, char *str)
{
  vs_init(vstr,0); /* conditional init */

  if (str == NULL) {
#ifdef DEBUG_VARSTRING
  fprintf(stderr, "Comparing string with a NULL pointer\n");
  return 0;
#else
  xsb_bug("Comparing string with a NULL pointer");
#endif
  }

  return strcmp(vstr->string, str);
}

/* destruction is necessary for automatic VarString's,
   or else there will be a memory leak */
static inline void  vs_destroy(VarString *vstr)
{
  if (vstr->string == NULL) {
#ifdef DEBUG_VARSTRING
    fprintf(stderr,
	    "Attempt to deallocate uninitialized variable-length string\n");
    return;
#else
    xsb_bug("Attempt to deallocate uninitialized variable-length string");
#endif
  }
#ifdef DEBUG_VARSTRING
  fprintf(stderr,
	    "Deallocating a variable-length string\n");
#endif
  free(vstr->string);
  vstr->string    = NULL;
  vstr->size        = 0;
  vstr->length      = 0;
  vstr->increment   = 0;
}

/* append block of chars, don't NULL-terminate */
static void vs_appendblk(VarString *vstr, char *blk, int blk_size)
{
  int newlength;

  vs_init(vstr,0); /* conditional init */

  if (blk == NULL) {
#ifdef DEBUG_VARSTRING
    fprintf(stderr, "Appending a NULL string\n");
    return;
#else
    xsb_bug("Appending a NULL string");
#endif
  }

  newlength = NEWLENGTH(vstr,blk_size);

  if (newlength > vstr->size)
    vs_adjust_size(vstr, newlength);

  strncpy(vstr->string+vstr->length, blk, blk_size);
  vstr->length=vstr->length + blk_size;
}


/* append block of chars */
static void vs_prependblk(VarString *vstr, char *blk, int blk_size)
{
  int newlength;

  vs_init(vstr,0); /* conditional init */

  if (blk == NULL) {
#ifdef DEBUG_VARSTRING
    fprintf(stderr, "Prepending a NULL string\n");
    return;
#else
    xsb_bug("Prepending a NULL string");
#endif
  }

  newlength = NEWLENGTH(vstr,blk_size);

  if (newlength > vstr->size)
    vs_adjust_size(vstr, newlength);

  memmove(vstr->string+blk_size, vstr->string, vstr->length+1);
  strncpy(vstr->string, blk, blk_size);
  vstr->length=vstr->length + blk_size;
}


static inline void vs_null_terminate(VarString *vstr)
{
  int newlength;

  vs_init(vstr,0); /* conditional init */

  newlength = NEWLENGTH(vstr,0);

  if (newlength > vstr->size)
    vs_adjust_size(vstr, newlength);

  vstr->string[vstr->length] = '\0';
}


/* Adjust size to the next multiple of the increment after minsize;
   This can enlarge as well as shrink a VarString. 
   The caller must make sure that the existing data isn't clobbered by
   shrinking. */
static void vs_adjust_size(VarString *vstr, int minsize)
{
  int newsize;

  vs_init(vstr,0); /* conditional init */

  newsize = (minsize/vstr->increment +1) * (vstr->increment);

  if (NULL == (vstr->string = (char *)realloc(vstr->string, newsize))) {
#ifdef DEBUG_VARSTRING
    fprintf(stderr, "No room to expand a variable-length string\n");
    return;
#else
    vstr->size        = 0;
    vstr->length      = 0;
    xsb_abort("No room to expand a variable-length string");
#endif
  }

#ifdef DEBUG_VARSTRING
  if (newsize > vstr->size)
    fprintf(stderr, "Expanding a VarString from %d to %d\n",
	    vstr->size, newsize);
  else if (newsize < vstr->size)
    fprintf(stderr, "Shrinking a VarString from %d to %d\n",
	    vstr->size, newsize);
#endif

  vstr->size = newsize;
}



#ifdef DEBUG_VARSTRING

int main (int argc, char** argv)
{
  XSB_StrDefine(foo);
  XSB_StrDefine(boo);
  XSB_StrDefine(goo);
  VarString *loo;

  XSB_StrSet(&foo, "abc");
  printf("foo1: %s   %d/%d\n", foo.string, foo.length, foo.size);
  foo.op->append(&foo, "123");
  printf("foo2: %s   %d/%d\n", foo.string, foo.length, foo.size);
  foo.op->prepend(&foo, "098");
  printf("foo3: %s   %d/%d\n", foo.string, foo.length, foo.size);

  boo.op->prepend(&boo, "booooooo");
  printf("boo: %s     %d/%d\n", boo.string, boo.length, boo.size);

  boo.op->shrink(&boo, 3);
  boo.op->appendv(&boo, &foo);

  loo = &boo;
  printf("boo2: %s     %d/%d\n", loo->string, boo.length, boo.size);

  foo.op->shrink(&foo,60);
  if (foo.length > 0)
      printf("foo4: %s   %d/%d\n", foo.string, foo.length, foo.size);

  boo.op->set(&boo,"123");
  XSB_StrAppend(loo,"---");
  printf("boo3: %s     %d/%d\n", loo->string, boo.length, boo.size);

  boo.op->append(&boo,"4567");
  printf("boo4: %s     %d/%d\n", loo->string, boo.length, boo.size);

  printf("initialized: boo=%p  foo=%p  goo=%p\n",
	 boo.string, foo.string, goo.string);

  XSB_StrSetV(loo, &foo);
  printf("boo5: %s     %d/%d\n", loo->string, boo.length, boo.size);

  XSB_StrEnsureSize(loo, 1000);

  XSB_StrAppendBlk(loo,NULL,5);
  XSB_StrAppend(loo,NULL);

  XSB_StrDestroy(&foo);
  XSB_StrDestroy(loo);
  goo.op->destroy(&goo);

}

#endif
