/* gserialize.h
 * Copyright (C) 1998 Jay Cox <jaycox@earthlink.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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 Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <string.h>

#include "gserialize.h"

struct _GSerialDescription
{
  char *struct_name;
  GSList *list;
};


struct _GSerialItem
{
  GSerialType type;          /* the type of this data member                 */
  gulong      offset;        /* the offset into the struct of this item      */
  glong       length;        /* the number of elements (if this is an array) */
		             /* or -1 if this is a variable length array     */
  gulong      length_offset; /* offset to the length of the array            */
};

#define g_serial_copy_from_n g_serial_copy_to_n

static long g_serial_copy_to_n(char *dest, char *source, long data_size,
			       long n_items)
{
  int i;
  int length = n_items*data_size;
#if G_BYTE_ORDER == G_BIG_ENDIAN
  memcpy(dest, source, length);
#else
  switch (data_size)
  {
   case 1: memcpy(dest, source, n_items);
     break;
   case 2: 
     for (i = 0; i < length; i+=2)
     {
       dest[i]   = source[i + 1];
       dest[i+1] = source[i];
     } break;
   case 4: 
     for (i = 0; i < length; i+=4)
     {
       dest[i]   = source[i + 3];
       dest[i+1] = source[i + 2];
       dest[i+2] = source[i + 1];
       dest[i+3] = source[i];
     } break;
   case 8: 
     for (i = 0; i < length; i+=8)
     {
       dest[i]   = source[i+7];
       dest[i+1] = source[i+6];
       dest[i+2] = source[i+5];
       dest[i+3] = source[i+4];
       dest[i+4] = source[i+3];
       dest[i+5] = source[i+2];
       dest[i+6] = source[i+1];
       dest[i+7] = source[i];
     } break;
   default:
     g_assert_not_reached();
  }
#endif /* G_BYTE_ORDER != G_BIG_ENDIAN */
  return length;
}

GSerialItem *g_new_serial_item(GSerialType type, gulong offset,
			       gint32 length, gulong length_offset)
{
  GSerialItem *item = g_new(GSerialItem, 1);
  item->type          = type;
  item->offset        = offset;
  item->length        = length;
  item->length_offset = length_offset;
  return item;
}

GSerialDescription *g_new_serial_description(char *name, ...)
{
  va_list argp;
  void *tmp;
  GSerialDescription *d = g_new(GSerialDescription, 1);

  d->struct_name = g_strdup(name);
  d->list = 0;

  va_start(argp, name);
  while ((tmp =  va_arg (argp, void*)))
    d->list = g_slist_append(d->list, tmp);
  va_end(argp);
  return d;
}

void g_free_serial_description(GSerialDescription *d)
{
  g_free (d->struct_name);
  while (d->list)
  {
    g_free(d->list->data);
    d->list = g_slist_remove_link(d->list, d->list);
  }
  g_free (d);
}

static int g_serial_item_is_array(GSerialItem *item)
{
  if (item->type < GSERIAL_STRING)
    return 0;
  return 1;
}

static long g_serial_item_data_size(GSerialItem *item)
{
  static long sizes[] = {0, 1, 2, 4, 4, 8, 1, 1, 2, 4, 4, 8};
  if (item->type >= GSERIAL_STRING)
    return sizes[item->type - 95];
  return sizes[item->type];
}

static long g_serial_item_n_items(GSerialItem *item, void *struct_data)
{
  if (item->type < GSERIAL_STRING)
    return 1;
  if (item->type == GSERIAL_STRING)
    return (strlen(G_STRUCT_MEMBER(char*, struct_data, item->offset)) + 1);
  if (item->length >= 0)
    return item->length;
  return (G_STRUCT_MEMBER(gint32, struct_data, item->length_offset));
}

static long g_serial_item_compute_length(GSerialItem *item, void *struct_data)
{
  int length;
  length = g_serial_item_n_items(item, struct_data) * g_serial_item_data_size(item);
  length += g_serial_item_is_array(item) * 4;
  return length + 1;
}

static long g_serial_description_compute_length(GSerialDescription *d,
						void *struct_data)
{
  long length = 0;
  GSList *list;
  list = d->list;
  while (list)
  {
    length += g_serial_item_compute_length((GSerialItem *) list->data, struct_data);
    list = list->next;
  }
  return length;
}

static long g_serial_item_serialize(GSerialItem *item, char *buffer,
				    void *struct_data)
{
  char *buf = buffer;
  gint32 tmp;
  if (item->type >= GSERIAL_LAST_TYPE ||
      (item->type > GSERIAL_DOUBLE && item->type < GSERIAL_STRING))
  {
    g_warning("Error serializing: Unknown serial item type.\n");
    return 0;
  }
  *buf++ = item->type;
  if (g_serial_item_is_array(item))
  {
    tmp = g_serial_item_n_items(item, struct_data);
    buf += g_serial_copy_to_n(buf, (char *)&tmp, 4, 1);
    buf += g_serial_copy_to_n(buf,
			      G_STRUCT_MEMBER(char*, struct_data,item->offset),
			      g_serial_item_data_size(item),
			      tmp);
  }
  else
  {
    buf += g_serial_copy_to_n(buf,
			      G_STRUCT_MEMBER_P(struct_data, item->offset),
			      g_serial_item_data_size(item), 1);
  }
  return (buf - buffer);
}

static long g_serial_item_deserialize(GSerialItem *item, void *struct_data, 
				      char *buffer)
{
  char *buf = buffer;
  gint32 n_items;
  if (*buf != item->type)
  {
    g_warning("Error deserializing: item types do not match: %d vs %d.\n",
	      *buf, item->type);
    return 0;
  }
  buf++;
  if (g_serial_item_is_array(item))
  {
    buf += g_serial_copy_from_n((char *)&n_items, buf, 4, 1);
    if (item->length < 0)
      G_STRUCT_MEMBER(gint32, struct_data, item->length_offset) = n_items;
    G_STRUCT_MEMBER(void*, struct_data, item->offset) 
      = g_malloc(n_items*g_serial_item_data_size(item));
    buf += g_serial_copy_from_n(G_STRUCT_MEMBER(void *, struct_data,
						item->offset),
				buf,
				g_serial_item_data_size(item),
				n_items);
  }
  else
  {
    buf += g_serial_copy_from_n(G_STRUCT_MEMBER_P(struct_data, item->offset),
				buf,
				g_serial_item_data_size(item),
				1);
  }
  return (buf - buffer);
}

long g_serialize(GSerialDescription *d,  void **output, void *struct_data)
{
  int length = g_serial_description_compute_length(d, struct_data);
  char *outbuf;
  GSList *list;
  long item_length;
  outbuf = (char *)g_malloc(length);
  *output = outbuf;

  list = d->list;
  while (list)
  {
    item_length = g_serial_item_serialize((GSerialItem *)list->data, outbuf,
					  struct_data);
    if (item_length == 0)
      g_error("Error serializing %s\n", d->struct_name);
    outbuf += item_length;
    list = list->next;
  }
  return length;
}

long g_deserialize(GSerialDescription *d,  void *struct_data, void *serial)
{
  GSList *list;
  char *in_buf = serial;
  char *out_buf = struct_data;
  list = d->list;
  while (list)
  {
    in_buf += g_serial_item_deserialize((GSerialItem *)list->data, 
					out_buf, in_buf);
    list = list->next;
  }
  return (in_buf - (char *)serial);
}
