/* Copyright (c) 1998, 1999, 2001 John E. Davis
 * This file is part of the S-Lang library.
 *
 * You may distribute under the terms of either the GNU General Public
 * License or the Perl Artistic License.
 */
#include "slinclud.h"

#include "slang.h"
#include "_slang.h"

struct _SLang_BString_Type
{
   unsigned int num_refs;
   unsigned int len;
   int ptr_type;
#define IS_SLSTRING		1
#define IS_MALLOCED		2
#define IS_NOT_TO_BE_FREED	3
   union
     {
	unsigned char bytes[1];
	unsigned char *ptr;
     }
   v;
};

#define BS_GET_POINTER(b) ((b)->ptr_type ? (b)->v.ptr : (b)->v.bytes)

static SLang_BString_Type *create_bstring_of_type (char *bytes, unsigned int len, int type)
{
   SLang_BString_Type *b;
   unsigned int size;

   size = sizeof(SLang_BString_Type);
   if (type == 0)
     size += len;

   if (NULL == (b = (SLang_BString_Type *)SLmalloc (size)))
     return NULL;

   b->len = len;
   b->num_refs = 1;
   b->ptr_type = type;

   switch (type)
     {
      case 0:
	if (bytes != NULL) memcpy ((char *) b->v.bytes, bytes, len);
	/* Now \0 terminate it because we want to also use it as a C string
	 * whenever possible.  Note that sizeof(SLang_BString_Type) includes
	 * space for 1 character and we allocated len extra bytes.  Thus, it is
	 * ok to add a \0 to the end.
	 */
	b->v.bytes[len] = 0;
	break;

      case IS_SLSTRING:
	if (NULL == (b->v.ptr = (unsigned char *)SLang_create_nslstring (bytes, len)))
	  {
	     SLfree ((char *) b);
	     return NULL;
	  }
	break;

      case IS_MALLOCED:
      case IS_NOT_TO_BE_FREED:
	b->v.ptr = (unsigned char *)bytes;
	bytes [len] = 0;	       /* NULL terminate */
	break;
     }

   return b;
}

SLang_BString_Type *
SLbstring_create (unsigned char *bytes, unsigned int len)
{
   return create_bstring_of_type ((char *)bytes, len, 0);
}

/* Note that ptr must be len + 1 bytes long for \0 termination */
SLang_BString_Type *
SLbstring_create_malloced (unsigned char *ptr, unsigned int len, int free_on_error)
{
   SLang_BString_Type *b;

   if (ptr == NULL)
     return NULL;

   if (NULL == (b = create_bstring_of_type ((char *)ptr, len, IS_MALLOCED)))
     {
	if (free_on_error)
	  SLfree ((char *) ptr);
     }
   return b;
}

SLang_BString_Type *SLbstring_create_slstring (char *s)
{
   if (s == NULL)
     return NULL;

   return create_bstring_of_type (s, strlen (s), IS_SLSTRING);
}

SLang_BString_Type *SLbstring_dup (SLang_BString_Type *b)
{
   if (b != NULL)
     b->num_refs += 1;

   return b;
}

unsigned char *SLbstring_get_pointer (SLang_BString_Type *b, unsigned int *len)
{
   if (b == NULL)
     {
	*len = 0;
	return NULL;
     }
   *len = b->len;
   return BS_GET_POINTER(b);
}

void SLbstring_free (SLang_BString_Type *b)
{
   if (b == NULL)
     return;

   if (b->num_refs > 1)
     {
	b->num_refs -= 1;
	return;
     }

   switch (b->ptr_type)
     {
      case 0:
      case IS_NOT_TO_BE_FREED:
      default:
	break;

      case IS_SLSTRING:
	SLang_free_slstring ((char *)b->v.ptr);
	break;

      case IS_MALLOCED:
	SLfree ((char *)b->v.ptr);
	break;
     }

   SLfree ((char *) b);
}

int SLang_pop_bstring (SLang_BString_Type **b)
{
   return SLclass_pop_ptr_obj (SLANG_BSTRING_TYPE, (VOID_STAR *)b);
}

int SLang_push_bstring (SLang_BString_Type *b)
{
   if (b == NULL)
     return SLang_push_null ();

   b->num_refs += 1;

   if (0 == SLclass_push_ptr_obj (SLANG_BSTRING_TYPE, (VOID_STAR)b))
     return 0;

   b->num_refs -= 1;
   return -1;
}

static int
bstring_bstring_bin_op_result (int op, unsigned char a, unsigned char b,
			       unsigned char *c)
{
   (void) a;
   (void) b;
   switch (op)
     {
      default:
	return 0;

      case SLANG_PLUS:
	*c = SLANG_BSTRING_TYPE;
	break;

      case SLANG_GT:
      case SLANG_GE:
      case SLANG_LT:
      case SLANG_LE:
      case SLANG_EQ:
      case SLANG_NE:
	*c = SLANG_CHAR_TYPE;
	break;
     }
   return 1;
}

static int compare_bstrings (SLang_BString_Type *a, SLang_BString_Type *b)
{
   unsigned int len;
   int ret;

   len = a->len;
   if (b->len < len) len = b->len;

   ret = memcmp ((char *)BS_GET_POINTER(b), (char *)BS_GET_POINTER(a), len);
   if (ret != 0)
     return ret;

   if (a->len > b->len)
     return 1;
   if (a->len == b->len)
     return 0;

   return -1;
}

static SLang_BString_Type *
concat_bstrings (SLang_BString_Type *a, SLang_BString_Type *b)
{
   unsigned int len;
   SLang_BString_Type *c;
   char *bytes;

   len = a->len + b->len;

   if (NULL == (c = SLbstring_create (NULL, len)))
     return NULL;

   bytes = (char *)BS_GET_POINTER(c);

   memcpy (bytes, (char *)BS_GET_POINTER(a), a->len);
   memcpy (bytes + a->len, (char *)BS_GET_POINTER(b), b->len);

   return c;
}

static void free_n_bstrings (SLang_BString_Type **a, unsigned int n)
{
   unsigned int i;

   if (a == NULL) return;

   for (i = 0; i < n; i++)
     {
	SLbstring_free (a[i]);
	a[i] = NULL;
     }
}

static int
bstring_bstring_bin_op (int op,
			unsigned char a_type, VOID_STAR ap, unsigned int na,
			unsigned char b_type, VOID_STAR bp, unsigned int nb,
			VOID_STAR cp)
{
   char *ic;
   SLang_BString_Type **a, **b, **c;
   unsigned int n, n_max;
   unsigned int da, db;

   (void) a_type;
   (void) b_type;

   if (na == 1) da = 0; else da = 1;
   if (nb == 1) db = 0; else db = 1;

   if (na > nb) n_max = na; else n_max = nb;

   a = (SLang_BString_Type **) ap;
   b = (SLang_BString_Type **) bp;
   for (n = 0; n < n_max; n++)
     {
	if ((*a == NULL) || (*b == NULL))
	  {
	     SLang_verror (SL_VARIABLE_UNINITIALIZED,
			   "Binary string element[%u] not initialized for binary operation", n);
	     return -1;
	  }
	a += da; b += db;
     }

   a = (SLang_BString_Type **) ap;
   b = (SLang_BString_Type **) bp;
   ic = (char *) cp;
   c = NULL;

   switch (op)
     {
       case SLANG_PLUS:
	/* Concat */
	c = (SLang_BString_Type **) cp;
	for (n = 0; n < n_max; n++)
	  {
	     if (NULL == (c[n] = concat_bstrings (*a, *b)))
	       goto return_error;

	     a += da; b += db;
	  }
	break;

      case SLANG_NE:
	for (n = 0; n < n_max; n++)
	  {
	     ic [n] = (0 != compare_bstrings (*a, *b));
	     a += da;
	     b += db;
	  }
	break;
      case SLANG_GT:
	for (n = 0; n < n_max; n++)
	  {
	     ic [n] = (compare_bstrings (*a, *b) > 0);
	     a += da;
	     b += db;
	  }
	break;
      case SLANG_GE:
	for (n = 0; n < n_max; n++)
	  {
	     ic [n] = (compare_bstrings (*a, *b) >= 0);
	     a += da;
	     b += db;
	  }
	break;
      case SLANG_LT:
	for (n = 0; n < n_max; n++)
	  {
	     ic [n] = (compare_bstrings (*a, *b) < 0);
	     a += da;
	     b += db;
	  }
	break;
      case SLANG_LE:
	for (n = 0; n < n_max; n++)
	  {
	     ic [n] = (compare_bstrings (*a, *b) <= 0);
	     a += da;
	     b += db;
	  }
	break;
      case SLANG_EQ:
	for (n = 0; n < n_max; n++)
	  {
	     ic [n] = (compare_bstrings (*a, *b) == 0);
	     a += da;
	     b += db;
	  }
	break;
     }
   return 1;

   return_error:
   if (c != NULL)
     {
	free_n_bstrings (c, n);
	while (n < n_max)
	  {
	     c[n] = NULL;
	     n++;
	  }
     }
   return -1;
}

/* If preserve_ptr, then use a[i] as the bstring data.  See how this function
 * is called by the binary op routines for why.
 */
static SLang_BString_Type **
make_n_bstrings (SLang_BString_Type **b, char **a, unsigned int n, int ptr_type)
{
   unsigned int i;
   int malloc_flag;

   malloc_flag = 0;
   if (b == NULL)
     {
	b = (SLang_BString_Type **) SLmalloc ((n + 1) * sizeof (SLang_BString_Type *));
	if (b == NULL)
	  return NULL;
	malloc_flag = 1;
     }

   for (i = 0; i < n; i++)
     {
	char *s = a[i];

	if (s == NULL)
	  {
	     b[i] = NULL;
	     continue;
	  }

	if (NULL == (b[i] = create_bstring_of_type (s, strlen(s), ptr_type)))
	  {
	     free_n_bstrings (b, i);
	     if (malloc_flag) SLfree ((char *) b);
	     return NULL;
	  }
     }

   return b;
}

static int
bstring_string_bin_op (int op,
		       unsigned char a_type, VOID_STAR ap, unsigned int na,
		       unsigned char b_type, VOID_STAR bp, unsigned int nb,
		       VOID_STAR cp)
{
   SLang_BString_Type **b;
   int ret;

   if (NULL == (b = make_n_bstrings (NULL, (char **)bp, nb, IS_NOT_TO_BE_FREED)))
     return -1;

   b_type = SLANG_BSTRING_TYPE;
   ret = bstring_bstring_bin_op (op,
				 a_type, ap, na,
				 b_type, (VOID_STAR) b, nb,
				 cp);
   free_n_bstrings (b, nb);
   SLfree ((char *) b);
   return ret;
}

static int
string_bstring_bin_op (int op,
		       unsigned char a_type, VOID_STAR ap, unsigned int na,
		       unsigned char b_type, VOID_STAR bp, unsigned int nb,
		       VOID_STAR cp)
{
   SLang_BString_Type **a;
   int ret;

   if (NULL == (a = make_n_bstrings (NULL, (char **)ap, na, IS_NOT_TO_BE_FREED)))
     return -1;

   a_type = SLANG_BSTRING_TYPE;
   ret = bstring_bstring_bin_op (op,
				 a_type, (VOID_STAR) a, na,
				 b_type, bp, nb,
				 cp);
   free_n_bstrings (a, na);
   SLfree ((char *) a);

   return ret;
}

static void bstring_destroy (unsigned char unused, VOID_STAR s)
{
   (void) unused;
   SLbstring_free (*(SLang_BString_Type **) s);
}

static int bstring_push (unsigned char unused, VOID_STAR sptr)
{
   (void) unused;

   return SLang_push_bstring (*(SLang_BString_Type **) sptr);
}

static int string_to_bstring (unsigned char a_type, VOID_STAR ap, unsigned int na,
			      unsigned char b_type, VOID_STAR bp)
{
   char **s;
   SLang_BString_Type **b;

   (void) a_type;
   (void) b_type;

   s = (char **) ap;
   b = (SLang_BString_Type **) bp;

   if (NULL == make_n_bstrings (b, s, na, IS_SLSTRING))
     return -1;

   return 1;
}

static int bstring_to_string (unsigned char a_type, VOID_STAR ap, unsigned int na,
			      unsigned char b_type, VOID_STAR bp)
{
   char **s;
   unsigned int i;
   SLang_BString_Type **a;

   (void) a_type;
   (void) b_type;

   s = (char **) bp;
   a = (SLang_BString_Type **) ap;

   for (i = 0; i < na; i++)
     {
	SLang_BString_Type *ai = a[i];

	if (ai == NULL)
	  {
	     s[i] = NULL;
	     continue;
	  }

	if (NULL == (s[i] = SLang_create_slstring ((char *)BS_GET_POINTER(ai))))
	  {
	     while (i != 0)
	       {
		  i--;
		  SLang_free_slstring (s[i]);
		  s[i] = NULL;
	       }
	     return -1;
	  }
     }

   return 1;
}

static char *bstring_string (unsigned char type, VOID_STAR v)
{
   SLang_BString_Type *s;
   unsigned char buf[128];
   unsigned char *bytes, *bytes_max;
   unsigned char *b, *bmax;

   (void) type;

   s = *(SLang_BString_Type **) v;
   bytes = BS_GET_POINTER(s);
   bytes_max = bytes + s->len;

   b = buf;
   bmax = buf + (sizeof (buf) - 4);

   while (bytes < bytes_max)
     {
	unsigned char ch = *bytes;

	if ((ch < 32) || (ch >= 127) || (ch == '\\'))
	  {
	     if (b + 4 > bmax)
	       break;

	     sprintf ((char *) b, "\\%03o", ch);
	     b += 4;
	  }
	else
	  {
	     if (b == bmax)
	       break;

	     *b++ = ch;
	  }

	bytes++;
     }

   if (bytes < bytes_max)
     {
	*b++ = '.';
	*b++ = '.';
	*b++ = '.';
     }
   *b = 0;

   return SLmake_string ((char *)buf);
}

static unsigned int bstrlen_cmd (SLang_BString_Type *b)
{
   return b->len;
}

static SLang_Intrin_Fun_Type BString_Table [] = /*{{{*/
{
   MAKE_INTRINSIC_1("bstrlen",  bstrlen_cmd, SLANG_UINT_TYPE, SLANG_BSTRING_TYPE),
   MAKE_INTRINSIC_0("pack", _SLpack, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_2("unpack", _SLunpack, SLANG_VOID_TYPE, SLANG_STRING_TYPE, SLANG_BSTRING_TYPE),
   MAKE_INTRINSIC_1("pad_pack_format", _SLpack_pad_format, SLANG_VOID_TYPE, SLANG_STRING_TYPE),
   MAKE_INTRINSIC_1("sizeof_pack", _SLpack_compute_size, SLANG_UINT_TYPE, SLANG_STRING_TYPE),
   SLANG_END_INTRIN_FUN_TABLE
};

int _SLang_init_bstring (void)
{
   SLang_Class_Type *cl;

   if (NULL == (cl = SLclass_allocate_class ("BString_Type")))
     return -1;
   (void) SLclass_set_destroy_function (cl, bstring_destroy);
   (void) SLclass_set_push_function (cl, bstring_push);
   (void) SLclass_set_string_function (cl, bstring_string);

   if (-1 == SLclass_register_class (cl, SLANG_BSTRING_TYPE, sizeof (char *),
				     SLANG_CLASS_TYPE_PTR))
     return -1;

   if ((-1 == SLclass_add_typecast (SLANG_BSTRING_TYPE, SLANG_STRING_TYPE, bstring_to_string, 1))
       || (-1 == SLclass_add_typecast (SLANG_STRING_TYPE, SLANG_BSTRING_TYPE, string_to_bstring, 1))
       || (-1 == SLclass_add_binary_op (SLANG_BSTRING_TYPE, SLANG_BSTRING_TYPE, bstring_bstring_bin_op, bstring_bstring_bin_op_result))
       || (-1 == SLclass_add_binary_op (SLANG_STRING_TYPE, SLANG_BSTRING_TYPE, string_bstring_bin_op, bstring_bstring_bin_op_result))
       || (-1 == SLclass_add_binary_op (SLANG_BSTRING_TYPE, SLANG_STRING_TYPE, bstring_string_bin_op, bstring_bstring_bin_op_result)))

     return -1;

   if (-1 == SLadd_intrin_fun_table (BString_Table, NULL))
     return -1;

   return 0;
}