#include <string.h>
#include "gskxmlformat.h"
#include "gskxmlvaluewriter.h"

#define MAX_SIMPLE_TYPE_ATOM_LEN 64

/*
 * Helpers.
 */

static GHashTable *serialized_properties_map = NULL;

static GPtrArray *
get_serialized_properties (GObjectClass *object_class)
{
  GPtrArray *serialized_properties;
  GType type;

  g_return_val_if_fail (object_class, NULL);
  type = G_OBJECT_CLASS_TYPE (object_class);
  g_return_val_if_fail (type, NULL);

  if (serialized_properties_map == NULL)
    {
      serialized_properties_map = g_hash_table_new (g_direct_hash,
						    g_direct_equal);
      serialized_properties = NULL;
    }
  else
    {
      serialized_properties = g_hash_table_lookup (serialized_properties_map,
						   GUINT_TO_POINTER (type));
    }
  if (serialized_properties == NULL)
    {
      GParamSpec **pspecs;
      guint n_pspecs;

      serialized_properties = g_ptr_array_new ();
      pspecs = g_object_class_list_properties (object_class, &n_pspecs);
      if (n_pspecs > 0)
	{
	  guint i;

	  g_return_val_if_fail (pspecs, NULL);

	  /* Collect only readable properties that aren't ignored. */
	  for (i = 0; i < n_pspecs; ++i)
	    {
	      if (pspecs[i]->flags & G_PARAM_READABLE &&
		  !(pspecs[i]->flags & GSK_XML_FORMAT_PARAM_IGNORE))
		g_ptr_array_add (serialized_properties, pspecs[i]);
	    }
	}
      if (pspecs)
	g_free (pspecs);

      g_hash_table_insert (serialized_properties_map,
                           GUINT_TO_POINTER (type),
                           serialized_properties);
    }
  return serialized_properties;
}

/*
 *
 * GskXmlValueWriter
 *
 */

typedef struct _XmlStackFrame XmlStackFrame;

/* XXX: need another pair of states to handle top-level values
 * of simple type, <TYPE>TEXT</TYPE>.
 */

struct _XmlStackFrame
{
  enum
    {
      STATE_VALUE_START,
      STATE_OBJECT_BODY,
      STATE_PROPERTY_VALUE,
      STATE_PROPERTY_END_TAG,
      STATE_VALUE_ARRAY_BODY,
      STATE_VALUE_ARRAY_VALUE,
      STATE_VALUE_ARRAY_END_TAG,
      STATE_START_TAG_OPEN,
      STATE_START_TAG_NAME,
      STATE_START_TAG_CLOSE,
      STATE_END_TAG_OPEN,
      STATE_END_TAG_NAME,
      STATE_END_TAG_CLOSE
    }
  state;

  union
    {
      struct
	{
	  GValue value;
	  GPtrArray *properties; /* of GParamSpec */
	  guint property_index;
	}
      value_info;
      struct
	{
	  GValueArray *value_array;
	  const char *property_name;
	  guint element_index;
	}
      value_array_info;
      struct
	{
	  const char *tag_name;
	}
      tag_info;
    }
  info;

  XmlStackFrame *parent;
};

static GObjectClass *parent_class = NULL;

static inline XmlStackFrame *
xml_stack_frame_alloc (void)
{
  /* TODO: better allocator */
  return g_new0 (XmlStackFrame, 1);
}

static inline void
xml_stack_frame_free (XmlStackFrame *frame)
{
  g_free (frame);
}

static XmlStackFrame *
push_value (const GValue *value, XmlStackFrame *parent)
{
  XmlStackFrame *frame;

  frame = xml_stack_frame_alloc ();
  frame->state = STATE_VALUE_START;
  g_value_init (&frame->info.value_info.value, G_VALUE_TYPE (value));
  g_value_copy (value, &frame->info.value_info.value);
  frame->parent = parent;
  return frame;
}

static XmlStackFrame *
push_value_property (GObject *object, GParamSpec *pspec, XmlStackFrame *parent)
{
  XmlStackFrame *frame;

  frame = xml_stack_frame_alloc ();
  frame->state = STATE_VALUE_START;
  g_value_init (&frame->info.value_info.value, pspec->value_type);
  g_object_get_property (object, pspec->name, &frame->info.value_info.value);
  frame->parent = parent;
  return frame;
}

static inline XmlStackFrame *
push_start_tag (const char *tag_name, XmlStackFrame *parent)
{
  XmlStackFrame *frame;

  frame = xml_stack_frame_alloc ();
  frame->state = STATE_START_TAG_OPEN;
  frame->info.tag_info.tag_name = tag_name;
  frame->parent = parent;
  return frame;
}

static inline XmlStackFrame *
push_end_tag (const char *tag_name, XmlStackFrame *parent)
{
  XmlStackFrame *frame;

  frame = xml_stack_frame_alloc ();
  frame->state = STATE_END_TAG_OPEN;
  frame->info.tag_info.tag_name = tag_name;
  frame->parent = parent;
  return frame;
}

static inline XmlStackFrame *
push_value_array (GValueArray   *value_array,
		  const char    *property_name,
		  XmlStackFrame *parent)
{
  XmlStackFrame *frame;

  frame = xml_stack_frame_alloc ();
  frame->state = STATE_VALUE_ARRAY_BODY;
  frame->info.value_array_info.value_array = value_array;
  frame->info.value_array_info.property_name = property_name;
  frame->parent = parent;
  return frame;
}

static inline XmlStackFrame *
xml_stack_frame_pop (XmlStackFrame *frame)
{
  XmlStackFrame *parent = frame->parent;
  switch (frame->state)
    {
      case STATE_VALUE_START:
      case STATE_OBJECT_BODY:
      case STATE_PROPERTY_VALUE:
      case STATE_PROPERTY_END_TAG:
	g_value_unset (&frame->info.value_info.value);
	break;
      case STATE_VALUE_ARRAY_BODY:
      case STATE_VALUE_ARRAY_VALUE:
      case STATE_VALUE_ARRAY_END_TAG:
	g_value_array_free (frame->info.value_array_info.value_array);
	break;
      case STATE_START_TAG_OPEN:
      case STATE_START_TAG_NAME:
      case STATE_START_TAG_CLOSE:
      case STATE_END_TAG_OPEN:
      case STATE_END_TAG_NAME:
      case STATE_END_TAG_CLOSE:
	break;
      default:
	g_return_val_if_reached (NULL);
	break;
    }
  xml_stack_frame_free (frame);
  return parent;
}

static inline guint
simple_type_atom (const GValue *value,
		  char          atom[MAX_SIMPLE_TYPE_ATOM_LEN])
{
  switch (G_VALUE_TYPE (value))
    {
      case G_TYPE_CHAR:
	*atom = g_value_get_char (value);
	return 1;
      case G_TYPE_UCHAR:
	*atom = g_value_get_uchar (value);
	return 1;
      case G_TYPE_BOOLEAN:
	*atom = g_value_get_boolean (value) ? '1' : '0';
	return 1;

#define TYPE_CASE_PRINTF(T,t,fmt) \
      case G_TYPE_##T: \
	g_snprintf (atom, \
		    MAX_SIMPLE_TYPE_ATOM_LEN, \
		    fmt, \
		    g_value_get_##t (value)); \
	return (strlen (atom));

      TYPE_CASE_PRINTF (INT,     int,    "%d")
      TYPE_CASE_PRINTF (UINT,    uint,   "%u")
      TYPE_CASE_PRINTF (LONG,    long,   "%ld")
      TYPE_CASE_PRINTF (ULONG,   ulong,  "%lu")
      TYPE_CASE_PRINTF (INT64,   int64,  "%" G_GINT64_FORMAT)
      TYPE_CASE_PRINTF (UINT64,  uint64, "%" G_GUINT64_FORMAT)
      TYPE_CASE_PRINTF (FLOAT,   float,  "%.18e")
      TYPE_CASE_PRINTF (DOUBLE,  double, "%.18e")

      default:
        break;
    }
  g_return_val_if_reached (0);
  return 0;
}

static inline guint
string_atom (GValue      *value,
	     const char **atom_out,
	     gboolean    *free_atom)
{
  static const guint8 must_escape[256] = {
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,

    /*  SP !  "  #  $  %  &  '  (  )  *  +  ,  -  .  /  */
	1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,

    /*  0  1  2  3  4  5  6  7  8  9  :  ;  <  =  >  ?  */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,

    /*  @  A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

    /*  P  Q  R  S  T  U  V  W  X  Y  Z  [  \  ]  ^  _  */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

    /*  `  a  b  c  d  e  f  g  h  i  j  k  l  m  n  o  */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

    /*  p  q  r  s  t  u  v  w  x  y  z  {  |  }  ~  DEL */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,

	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
  };
  char *atom;
  guint8 *p;

  /* YHWH */
  atom = value->data[0].v_pointer;
/* XXX: no representation for NULL */
  g_return_val_if_fail (atom, 0);
  if (*atom == 0)
    {
      *atom_out = "<![CDATA[]]>";
      return 12;
    }
  for (p = atom; *p; ++p)
    {
      if (must_escape[*p])
	{
	  *atom_out = g_strdup_printf ("<![CDATA[%s]]>", atom);
	  *free_atom = TRUE;
	  /* XXX: should really use &#nnn; entities */
	  g_return_val_if_fail (strstr (atom, "]]>") == NULL, 0);
	  return strlen (*atom_out);
	}
    }
  if (!(value->data[1].v_uint & G_VALUE_NOCOPY_CONTENTS))
    *free_atom = TRUE;
  value->data[0].v_pointer = NULL;
  *atom_out = atom;
  return strlen (atom);
}

static inline guint
next_atom (GskXmlValueWriter  *self,
	   char                atom_buf[MAX_SIMPLE_TYPE_ATOM_LEN],
	   const char        **atom_out,
	   gboolean           *free_atom)
{
  XmlStackFrame *top = (XmlStackFrame *) self->stack;
  const char *atom = NULL;
  guint atom_len = 0;

  g_return_val_if_fail (atom_out, 0);
  g_return_val_if_fail (free_atom, 0);
  *free_atom = FALSE;

  do
    {
      if (top == NULL)
	return 0;
      switch (top->state)
	{
	  case STATE_VALUE_START:
	    {
	      GValue *value = &top->info.value_info.value;
	      GType value_type = G_VALUE_TYPE (value);
	      if (g_type_is_a (value_type, G_TYPE_OBJECT))
		{
		  GObject *object;
		  object = g_value_get_object (value);
		  g_return_val_if_fail (object && G_IS_OBJECT (object), 0);
		  top->state = STATE_OBJECT_BODY;
		  top = push_start_tag (g_type_name (G_OBJECT_TYPE (object)),
					top);
		}
	      else if (value_type == G_TYPE_STRING)
		{
		  atom_len = string_atom (value, &atom, free_atom);
		  if (atom_len == 0)
		    {
		      if (atom && free_atom)
			g_free ((char *) atom);
		      atom = NULL;
		    }
		  top = xml_stack_frame_pop (top);
		}
	      else
		{
		  atom = atom_buf;
		  atom_len = simple_type_atom (value, atom_buf);
		  top = xml_stack_frame_pop (top);
		}
	    }
	    break;
	  case STATE_OBJECT_BODY:
	    {
	      GValue *value = &top->info.value_info.value;
	      GPtrArray *properties = top->info.value_info.properties;
	      guint index = top->info.value_info.property_index;
	      GObject *object;

	      object = g_value_get_object (value);
	      g_return_val_if_fail (object && G_IS_OBJECT (object), 0);
	      if (properties == NULL)
		{
		  g_return_val_if_fail (index == 0, 0);
		  properties = top->info.value_info.properties =
		    get_serialized_properties (G_OBJECT_GET_CLASS (object));
		  g_return_val_if_fail (properties, 0);
		}
	      /* Loop until we get a non-default property or last property. */
	      for (;;)
		{
		  if (index >= properties->len)
		    {
		      const char *type_name;
		      /* We assume the type name doesn't go away
		       * when the object is unreferenced!
		       */
		      type_name = g_type_name (G_OBJECT_TYPE (object));
		      top = xml_stack_frame_pop (top);
		      top = push_end_tag (type_name, top);
		      break;
		    }
		  else
		    {
		      GParamSpec *pspec = g_ptr_array_index (properties, index);
		      GValue value = { 0, { { 0 }, { 0 } } };
		      g_value_init (&value, pspec->value_type);
		      g_object_get_property (object, pspec->name, &value);
		      if (G_VALUE_TYPE (&value) == G_TYPE_VALUE_ARRAY)
			{
			  GValueArray *value_array;
			  value_array = g_value_get_boxed (&value);
			  if (value_array)
			    {
			      top->state = STATE_OBJECT_BODY;
			      top->info.value_info.property_index = index + 1;
			      top = push_value_array (value_array,
						      pspec->name,
						      top);
			      /* Don't unset value; we steal value_array. */
			      break;
			    }
			}
		      else if (!g_param_value_defaults (pspec, &value))
			{
			  /* TODO: could push an extra value frame now? */
			  top->state = STATE_PROPERTY_VALUE;
			  top->info.value_info.property_index = index;
			  top = push_start_tag (pspec->name, top);
			  g_value_unset (&value);
			  break;
			}
		      g_value_unset (&value);
		      ++index;
		    }
		}
	    }
	    break;
	  case STATE_PROPERTY_VALUE:
	    {
	      GPtrArray *properties = top->info.value_info.properties;
	      guint index = top->info.value_info.property_index;
	      GParamSpec *pspec;
	      GObject *object;

	      g_return_val_if_fail (properties, 0);
	      g_return_val_if_fail (index < properties->len, 0);
	      pspec = g_ptr_array_index (properties, index);
	      object = g_value_get_object (&top->info.value_info.value);
	      g_return_val_if_fail (object && G_IS_OBJECT (object), 0);

	      top->state = STATE_PROPERTY_END_TAG;
	      top = push_value_property (object, pspec, top);
	    }
	    break;
	  case STATE_PROPERTY_END_TAG:
	    {
	      GPtrArray *properties = top->info.value_info.properties;
	      guint index = top->info.value_info.property_index;
	      GParamSpec *pspec;

	      g_return_val_if_fail (properties, 0);
	      g_return_val_if_fail (index < properties->len, 0);
	      pspec = g_ptr_array_index (properties, index);

	      top->state = STATE_OBJECT_BODY;
	      ++top->info.value_info.property_index;
	      top = push_end_tag (pspec->name, top);
	    }
	    break;
	  case STATE_VALUE_ARRAY_BODY:
	    {
	      GValueArray *value_array = top->info.value_array_info.value_array;
	      guint index = top->info.value_array_info.element_index;

	      if (index >= value_array->n_values)
		top = xml_stack_frame_pop (top);
	      else
		{
		  top->state = STATE_VALUE_ARRAY_VALUE;
		  top =
		    push_start_tag (top->info.value_array_info.property_name,
				    top);
		}
	    }
	    break;
	  case STATE_VALUE_ARRAY_VALUE:
	    {
	      GValueArray *value_array = top->info.value_array_info.value_array;
	      guint index = top->info.value_array_info.element_index;
	      GValue *element;

	      g_return_val_if_fail (index < value_array->n_values, 0);
	      element = g_value_array_get_nth (value_array, index);
	      g_return_val_if_fail (element, 0);
	      top->state = STATE_VALUE_ARRAY_END_TAG;
	      top = push_value (element, top);
	    }
	    break;
	  case STATE_VALUE_ARRAY_END_TAG:
	    {
	      const char *property_name =
		top->info.value_array_info.property_name;
	      g_return_val_if_fail (property_name, 0);
	      top = xml_stack_frame_pop (top);
	      top = push_end_tag (property_name, top);
	    }
	    break;
	  case STATE_START_TAG_OPEN:
	    {
	      atom = "<";
	      atom_len = 1;
	      top->state = STATE_START_TAG_NAME;
	    }
	    break;
	  case STATE_START_TAG_NAME:
	    {
	      atom = top->info.tag_info.tag_name;
	      atom_len = strlen (atom);
	      top->state = STATE_START_TAG_CLOSE;
	    }
	    break;
	  case STATE_END_TAG_OPEN:
	    {
	      atom = "</";
	      atom_len = 2;
	      top->state = STATE_END_TAG_NAME;
	    }
	    break;
	  case STATE_END_TAG_NAME:
	    {
	      atom = top->info.tag_info.tag_name;
	      atom_len = strlen (atom);
	      top->state = STATE_END_TAG_CLOSE;
	    }
	    break;
	  case STATE_START_TAG_CLOSE:
	  case STATE_END_TAG_CLOSE:
	    {
	      atom = ">";
	      atom_len = 1;
	      top = xml_stack_frame_pop (top);
	    }
	    break;
	}
      }
  while (atom == NULL);

  self->stack = top;
  *atom_out = atom;
  return atom_len;
}

static guint
gsk_xml_value_writer_raw_read (GskStream  *stream,
			       gpointer    data,
			       guint       length,
			       GError    **error)
{
  char atom_buf[MAX_SIMPLE_TYPE_ATOM_LEN];
  GskXmlValueWriter *self = GSK_XML_VALUE_WRITER (stream);
  char *out = data;
  guint num_written = 0;

  (void) error;

  if (length == 0)
    return 0;

  /* First write out anything already buffered. */
  if (self->buf.size > 0)
    {
      num_written = gsk_buffer_read (&self->buf, out, length);
      out += num_written;
      length -= num_written;
    }

  /* Then run the state machine until we fill up length. */
  while (length > 0)
    {
      const char *atom;
      gboolean free_atom;
      guint atom_len;

      atom_len = next_atom (self, atom_buf, &atom, &free_atom);
      if (atom_len == 0)
	{
	  gsk_io_notify_read_shutdown (stream);
	  break;
	}
      if (atom_len < length)
	{
	  strncpy (out, atom, atom_len);
	  out += atom_len;
	  num_written += atom_len;
	  length -= atom_len;
	}
      else
	{
	  strncpy (out, atom, length);
	  num_written += length;
	  gsk_buffer_append (&self->buf, atom + length, atom_len - length);
	  length = 0;
	}
      if (free_atom)
	g_free ((char *) atom);
    }
  return num_written;
}

static void
gsk_xml_value_writer_finalize (GObject *object)
{
  GskXmlValueWriter *self = GSK_XML_VALUE_WRITER (object);

  while (self->stack)
    self->stack = xml_stack_frame_pop ((XmlStackFrame *) self->stack);

  (*parent_class->finalize) (object);
}

static void
gsk_xml_value_writer_init (GskXmlValueWriter *xml_value_writer)
{
  gsk_stream_mark_is_readable (xml_value_writer);
  gsk_stream_mark_never_blocks_read (xml_value_writer);
}

static void
gsk_xml_value_writer_class_init (GskStreamClass *stream_class)
{
  parent_class = g_type_class_peek_parent (stream_class);
  G_OBJECT_CLASS (stream_class)->finalize = gsk_xml_value_writer_finalize;
  stream_class->raw_read = gsk_xml_value_writer_raw_read;
}

GType
gsk_xml_value_writer_get_type (void)
{
  static GType type = 0;
  if (G_UNLIKELY (type == 0))
    {
      static const GTypeInfo type_info =
	{
	  sizeof(GskXmlValueWriterClass),
	  (GBaseInitFunc) NULL,
	  (GBaseFinalizeFunc) NULL,
	  (GClassInitFunc) gsk_xml_value_writer_class_init,
	  NULL,		/* class_finalize */
	  NULL,		/* class_data */
	  sizeof (GskXmlValueWriter),
	  0,		/* n_preallocs */
	  (GInstanceInitFunc) gsk_xml_value_writer_init,
	  NULL		/* value_table */
	};
      type = g_type_register_static (GSK_TYPE_STREAM,
				     "GskXmlValueWriter",
				     &type_info,
				     0);
    }
  return type;
}

GskXmlValueWriter *
gsk_xml_value_writer_new (const GValue *value)
{
  GskXmlValueWriter *stream;

  stream = g_object_new (GSK_TYPE_XML_VALUE_WRITER, NULL);
  stream->stack = push_value (value, NULL);
  return stream;
}


syntax highlighted by Code2HTML, v. 0.9.1