#include "gskxmlrpc.h"
#include "../gskerror.h"
#include "../gskmacros.h"
#include "../common/gskbase64.h"
#include "../common/gskdate.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#define DEBUG_XMLRPC_PARSER	0

#define RESPONSE_MAGIC	0x3524de1a
#define REQUEST_MAGIC	0x3524de2b

/**
 * gsk_xmlrpc_struct_new:
 *
 * Allocate a new structure, with no members.
 *
 * returns: the newly allocated structure.
 */
GskXmlrpcStruct *gsk_xmlrpc_struct_new         (void)
{
  GskXmlrpcStruct *structure = g_new0 (GskXmlrpcStruct, 1);
  return structure;
}

static void
gsk_xmlrpc_value_destruct (GskXmlrpcValue *value)
{
  switch (value->type)
    {
    case GSK_XMLRPC_STRING:
      g_free (value->data.v_string);
      break;
    case GSK_XMLRPC_BINARY_DATA:
      g_byte_array_free (value->data.v_binary_data, TRUE);
      break;
    case GSK_XMLRPC_STRUCT:
      gsk_xmlrpc_struct_free (value->data.v_struct);
      break;
    case GSK_XMLRPC_ARRAY:
      gsk_xmlrpc_array_free (value->data.v_array);
      break;
    default:
      break;
    }
}

/**
 * gsk_xmlrpc_struct_free:
 * @structure: the structure to free.
 *
 * Free memory associated with an XMLRPC struct.
 */
void             gsk_xmlrpc_struct_free        (GskXmlrpcStruct *structure)
{
  unsigned i;
  for (i = 0; i < structure->n_members; i++)
    {
      g_free (structure->members[i].name);
      gsk_xmlrpc_value_destruct (&structure->members[i].value);
    }
}
static void
gsk_xmlrpc_struct_add_value_steal_name (GskXmlrpcStruct *structure,
                             char      *member_name,
			     GskXmlrpcValue  *value)
{
  if (structure->n_members == structure->alloced)
    {
      unsigned new_alloced = structure->alloced;
      if (new_alloced == 0)
	new_alloced = 16;
      else
	new_alloced += new_alloced;
      structure->members = g_renew (GskXmlrpcNamedValue, structure->members, new_alloced);
      structure->alloced = new_alloced;
    }
  structure->members[structure->n_members].name = member_name;
  structure->members[structure->n_members].value = *value;
  ++(structure->n_members);
}


static inline void
gsk_xmlrpc_struct_add_value (GskXmlrpcStruct *structure,
                             const char      *member_name,
			     GskXmlrpcValue  *value)
{
  gsk_xmlrpc_struct_add_value_steal_name (structure, g_strdup (member_name), value);
}

/**
 * gsk_xmlrpc_struct_add_int32:
 * @structure: the structure to append to.
 * @member_name: name of the new int32 member.
 * @value: value of the new int32 member.
 *
 * Add a single int32 member to the given struct.
 */
void             gsk_xmlrpc_struct_add_int32   (GskXmlrpcStruct *structure,
                                                const char      *member_name,
                                                gint32           value)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_INT32;
  v.data.v_int32 = value;
  gsk_xmlrpc_struct_add_value (structure, member_name, &v);
}

/**
 * gsk_xmlrpc_struct_add_boolean:
 * @structure: the structure to append to.
 * @member_name: name of the new boolean member.
 * @value: value of the new boolean member.
 *
 * Add a single boolean member to the given struct.
 */
void             gsk_xmlrpc_struct_add_boolean (GskXmlrpcStruct *structure,
                                                const char      *member_name,
                                                gboolean         value)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_BOOLEAN;
  v.data.v_boolean = value;
  gsk_xmlrpc_struct_add_value (structure, member_name, &v);
}

/**
 * gsk_xmlrpc_struct_add_double:
 * @structure: the structure to append to.
 * @member_name: name of the new double member.
 * @value: value of the new double member.
 *
 * Add a single double member to the given struct.
 */
void             gsk_xmlrpc_struct_add_double  (GskXmlrpcStruct *structure,
                                                const char      *member_name,
                                                gdouble          value)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_DOUBLE;
  v.data.v_double = value;
  gsk_xmlrpc_struct_add_value (structure, member_name, &v);
}

/**
 * gsk_xmlrpc_struct_add_string:
 * @structure: the structure to append to.
 * @member_name: name of the new string member.
 * @value: value of the new string member.
 *
 * Add a single string member to the given struct.
 */
void             gsk_xmlrpc_struct_add_string  (GskXmlrpcStruct *structure,
                                                const char      *member_name,
                                                const char      *value)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_STRING;
  v.data.v_string = g_strdup (value);
  gsk_xmlrpc_struct_add_value (structure, member_name, &v);
}

/**
 * gsk_xmlrpc_struct_add_date:
 * @structure: the structure to append to.
 * @member_name: name of the new date member.
 * @value: value of the new date member.
 *
 * Add a single date member to the given struct.
 */
void             gsk_xmlrpc_struct_add_date    (GskXmlrpcStruct *structure,
                                                const char      *member_name,
                                                gulong           value)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_DATE;
  v.data.v_date = value;
  gsk_xmlrpc_struct_add_value (structure, member_name, &v);
}

/**
 * gsk_xmlrpc_struct_add_data:
 * @structure: the structure to append to.
 * @member_name: name of the new date member.
 * @data: binary data to add, which will be freed by the structure when
 * it is freed.
 *
 * Add a single data member to the given struct.
 */
void             gsk_xmlrpc_struct_add_data    (GskXmlrpcStruct *structure,
                                                const char      *member_name,
                                                GByteArray      *data)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_BINARY_DATA;
  v.data.v_binary_data = data;
  gsk_xmlrpc_struct_add_value (structure, member_name, &v);
}

/**
 * gsk_xmlrpc_struct_add_struct:
 * @structure: the structure to append to.
 * @member_name: name of the new struct member.
 * @substructure: substructure to add, which will be freed by @structure when
 * it is freed.
 *
 * Add a structure as a member of another structure.
 */
void             gsk_xmlrpc_struct_add_struct  (GskXmlrpcStruct *structure,
                                                const char      *member_name,
                                                GskXmlrpcStruct *substructure)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_STRUCT;
  v.data.v_struct = substructure;
  gsk_xmlrpc_struct_add_value (structure, member_name, &v);
}

/**
 * gsk_xmlrpc_struct_add_array:
 * @structure: the structure to append to.
 * @member_name: name of the new struct member.
 * @array: subarray to add, which will be freed by @structure when
 * it is freed.
 *
 * Add an array as a member of a structure.
 */
void             gsk_xmlrpc_struct_add_array   (GskXmlrpcStruct *structure,
                                                const char      *member_name,
                                                GskXmlrpcArray  *array)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_ARRAY;
  v.data.v_array = array;
  gsk_xmlrpc_struct_add_value (structure, member_name, &v);
}

static GskXmlrpcValue *
gsk_xmlrpc_struct_peek_any (GskXmlrpcStruct *structure,
			    const char      *member_name,
			    GskXmlrpcType    type)
{
  guint i;
  for (i = 0; i < structure->n_members; i++)
    if (strcmp (member_name, structure->members[i].name) == 0
     && structure->members[i].value.type == type)
      return &structure->members[i].value;
  return NULL;
}

/**
 * gsk_xmlrpc_struct_peek_int32:
 * @structure: the structure to lookup the member of.
 * @member_name: the value to retrieve.
 * @out: place to store the result.
 *
 * Lookup a named int32 member of a structure,
 * storing the result, if any, in @out.
 *
 * returns: whether there was an int32 member named @member_name.
 */
gboolean         gsk_xmlrpc_struct_peek_int32  (GskXmlrpcStruct *structure,
                                                const char      *member_name,
                                                gint32          *out)
{
  GskXmlrpcValue *value = gsk_xmlrpc_struct_peek_any (structure, member_name, GSK_XMLRPC_INT32);
  if (value)
    *out = value->data.v_int32;
  return value != NULL;
}

/**
 * gsk_xmlrpc_struct_peek_boolean:
 * @structure: the structure to lookup the member of.
 * @member_name: the value to retrieve.
 * @out: place to store the result.
 *
 * Lookup a named boolean member of a structure,
 * storing the result, if any, in @out.
 *
 * returns: whether there was an boolean member named @member_name.
 */
gboolean         gsk_xmlrpc_struct_peek_boolean(GskXmlrpcStruct *structure,
                                                const char      *member_name,
                                                gboolean        *out)
{
  GskXmlrpcValue *value = gsk_xmlrpc_struct_peek_any (structure, member_name, GSK_XMLRPC_BOOLEAN);
  if (value)
    *out = value->data.v_boolean;
  return value != NULL;
}

/**
 * gsk_xmlrpc_struct_peek_double:
 * @structure: the structure to lookup the member of.
 * @member_name: the value to retrieve.
 * @out: place to store the result.
 *
 * Lookup a named double member of a structure,
 * storing the result, if any, in @out.
 *
 * returns: whether there was an double member named @member_name.
 */
gboolean         gsk_xmlrpc_struct_peek_double (GskXmlrpcStruct *structure,
                                                const char      *member_name,
                                                double          *out)
{
  GskXmlrpcValue *value = gsk_xmlrpc_struct_peek_any (structure, member_name, GSK_XMLRPC_DOUBLE);
  if (value)
    *out = value->data.v_double;
  return value != NULL;
}


/**
 * gsk_xmlrpc_struct_peek_string:
 * @structure: the structure to lookup the member of.
 * @member_name: the value to retrieve.
 *
 * Lookup a named string member of a structure,
 * returning the result, or #NULL.
 * 
 * returns: the string value, or NULL.
 */
const char *     gsk_xmlrpc_struct_peek_string (GskXmlrpcStruct *structure,
                                                const char      *member_name)
{
  GskXmlrpcValue *value = gsk_xmlrpc_struct_peek_any (structure, member_name, GSK_XMLRPC_STRING);
  return value ? value->data.v_string : NULL;
}

/**
 * gsk_xmlrpc_struct_peek_date:
 * @structure: the structure to lookup the member of.
 * @member_name: the value to retrieve.
 * @out: place to store the result.
 *
 * Lookup a named date member of a structure,
 * storing the result, if any, in @out.
 *
 * returns: whether there was an date member named @member_name.
 */
gboolean         gsk_xmlrpc_struct_peek_date   (GskXmlrpcStruct *structure,
                                                const char      *member_name,
						gulong          *out)
{
  GskXmlrpcValue *value = gsk_xmlrpc_struct_peek_any (structure, member_name, GSK_XMLRPC_DATE);
  if (value)
    *out = value->data.v_date;
  return value != NULL;
}
/**
 * gsk_xmlrpc_struct_peek_data:
 * @structure: the structure to lookup the member of.
 * @member_name: the value to retrieve.
 *
 * Lookup a named binary-data member of a structure,
 * returning a reference to the #GByteArray result, or #NULL.
 * 
 * returns: a reference (not a copy!) to the binary data, or #NULL.
 */
const GByteArray*gsk_xmlrpc_struct_peek_data   (GskXmlrpcStruct *structure,
                                                const char      *member_name)
{
  GskXmlrpcValue *value = gsk_xmlrpc_struct_peek_any (structure, member_name, GSK_XMLRPC_BINARY_DATA);
  return value ? value->data.v_binary_data : NULL;
}
/**
 * gsk_xmlrpc_struct_peek_struct:
 * @structure: the structure to lookup the member of.
 * @member_name: the value to retrieve.
 *
 * Lookup a named substructure member of a structure,
 * returning a reference to the #GskXmlrpcStruct result, or #NULL.
 * 
 * returns: a reference (not a copy!) to the struct, or #NULL.
 */
GskXmlrpcStruct *gsk_xmlrpc_struct_peek_struct (GskXmlrpcStruct *structure,
                                                const char      *member_name)
{
  GskXmlrpcValue *value = gsk_xmlrpc_struct_peek_any (structure, member_name, GSK_XMLRPC_STRUCT);
  return value ? value->data.v_struct : NULL;
}
/**
 * gsk_xmlrpc_struct_peek_array:
 * @structure: the structure to lookup the member of.
 * @member_name: the value to retrieve.
 *
 * Lookup a named subarray member of a structure,
 * returning a reference to the #GskXmlrpcArray result, or #NULL.
 * 
 * returns: a reference (not a copy!) to the array, or #NULL.
 */
GskXmlrpcArray  *gsk_xmlrpc_struct_peek_array  (GskXmlrpcStruct *structure,
                                                const char      *member_name)
{
  GskXmlrpcValue *value = gsk_xmlrpc_struct_peek_any (structure, member_name, GSK_XMLRPC_ARRAY);
  return value ? value->data.v_array : NULL;
}


/**
 * gsk_xmlrpc_array_new:
 *
 * Allocate a new, empty array.*
 *
 * returns: the newly allocated arrays.
 */
GskXmlrpcArray  *gsk_xmlrpc_array_new          (void)
{
  return g_new0 (GskXmlrpcArray, 1);
}

/**
 * gsk_xmlrpc_array_free:
 * @array: the array to free.
 *
 * Free the array and all its values.
 */
void             gsk_xmlrpc_array_free         (GskXmlrpcArray  *array)
{
  unsigned i;
  for (i = 0; i < array->len; i++)
    gsk_xmlrpc_value_destruct (array->values + i);
  g_free (array->values);
  g_free (array);
}

static void
gsk_xmlrpc_array_add_value (GskXmlrpcArray *array,
			    GskXmlrpcValue  *value)
{
  if (array->len == array->alloced)
    {
      unsigned new_alloced = array->alloced;
      if (new_alloced == 0)
	new_alloced = 16;
      else
	new_alloced += new_alloced;
      array->values = g_renew (GskXmlrpcValue, array->values, new_alloced);
      array->alloced = new_alloced;
    }
  array->values[array->len] = *value;
  ++(array->len);
}

/**
 * gsk_xmlrpc_array_add_int32:
 * @array: array to which to append a value.
 * @value: integer value to append.
 *
 * Append an integer to an XMLRPC array.
 */
void             gsk_xmlrpc_array_add_int32    (GskXmlrpcArray  *array,
                                                gint32           value)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_INT32;
  v.data.v_int32 = value;
  gsk_xmlrpc_array_add_value (array, &v);
}

/**
 * gsk_xmlrpc_array_add_boolean:
 * @array: array to which to append a value.
 * @value: boolean value to append.
 *
 * Append a boolean to an XMLRPC array.
 */
void             gsk_xmlrpc_array_add_boolean  (GskXmlrpcArray  *array,
                                                gboolean         value)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_BOOLEAN;
  v.data.v_boolean = value;
  gsk_xmlrpc_array_add_value (array, &v);
}

/**
 * gsk_xmlrpc_array_add_double:
 * @array: array to which to append a value.
 * @value: double value to append.
 *
 * Append a double-precision floating-pointer value to an XMLRPC array.
 */
void             gsk_xmlrpc_array_add_double   (GskXmlrpcArray  *array,
                                                gdouble          value)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_DOUBLE;
  v.data.v_double = value;
  gsk_xmlrpc_array_add_value (array, &v);
}

/**
 * gsk_xmlrpc_array_add_string:
 * @array: array to which to append a value.
 * @value: string value to append.
 * This value is copied: we do not take ownership.
 *
 * Append a string to an XMLRPC array.
 */
void             gsk_xmlrpc_array_add_string   (GskXmlrpcArray  *array,
                                                const char      *value)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_STRING;
  v.data.v_string = g_strdup (value);
  gsk_xmlrpc_array_add_value (array, &v);
}

/**
 * gsk_xmlrpc_array_add_date:
 * @array: array to which to append a value.
 * @value: date/time value to append.
 *
 * Append a string to an XMLRPC array.
 */
void             gsk_xmlrpc_array_add_date     (GskXmlrpcArray  *array,
                                                gulong           value)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_DATE;
  v.data.v_date = value;
  gsk_xmlrpc_array_add_value (array, &v);
}


/**
 * gsk_xmlrpc_array_add_data:
 * @array: array to which to append a value.
 * @data: a byte array containing arbitrary binary-data.
 * The XMLRPC array takes ownership of the data.
 *
 * Append a binary-data element to an XMLRPC array.
 * This take ownership of @data.
 */
void             gsk_xmlrpc_array_add_data     (GskXmlrpcArray  *array,
                                                GByteArray      *data)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_BINARY_DATA;
  v.data.v_binary_data = data;
  gsk_xmlrpc_array_add_value (array, &v);
}

/**
 * gsk_xmlrpc_array_add_struct:
 * @array: array to which to append a value.
 * @substructure: structure to append to @array.
 * It will be freed by the @array.
 *
 * Append a binary-data element to an XMLRPC array.
 * This take ownership of @substructure.
 */
void             gsk_xmlrpc_array_add_struct   (GskXmlrpcArray  *array,
                                                GskXmlrpcStruct *substructure)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_STRUCT;
  v.data.v_struct = substructure;
  gsk_xmlrpc_array_add_value (array, &v);
}

/**
 * gsk_xmlrpc_array_add_array:
 * @array: array to which to append a value.
 * @subarray: array to append to @array.
 * It will be freed by the @array.
 *
 * Append a binary-data element to an XMLRPC array.
 * This take ownership of @subarray.
 */
void             gsk_xmlrpc_array_add_array    (GskXmlrpcArray  *array,
                                                GskXmlrpcArray  *subarray)
{
  GskXmlrpcValue v;
  v.type = GSK_XMLRPC_ARRAY;
  v.data.v_array = subarray;
  gsk_xmlrpc_array_add_value (array, &v);
}


/**
 * gsk_xmlrpc_request_new:
 *
 * Allocate a new request.
 * At a very minimum, it should have a method name set with
 * gsk_xmlrpc_request_set_name().
 *
 * returns: a newly allocated request.
 */
GskXmlrpcRequest *
gsk_xmlrpc_request_new(GskXmlrpcStream *xmlrpc_stream)
{
  GskXmlrpcRequest *request = g_new (GskXmlrpcRequest, 1);
  request->magic = REQUEST_MAGIC;
  request->ref_count = 1;
  request->method_name = NULL;
  request->params = gsk_xmlrpc_array_new ();
  if (xmlrpc_stream != NULL)
    request->xmlrpc_stream = g_object_ref (xmlrpc_stream);
  else
    request->xmlrpc_stream = NULL;
  return request;
}

/**
 * gsk_xmlrpc_request_ref:
 * @request: the request to reference.
 *
 * Increase the reference count on @request.
 *
 * returns: the @request, for convenience.
 */
GskXmlrpcRequest   *gsk_xmlrpc_request_ref        (GskXmlrpcRequest   *request)
{
  g_assert (request->ref_count > 0);
  g_assert (request->magic == REQUEST_MAGIC);
  ++(request->ref_count);
  return request;
}

/**
 * gsk_xmlrpc_request_unref:
 * @request: the request to stop referencing.
 *
 * Decrease the reference count on @request,
 * and free it if the count reached 0.
 */
void             gsk_xmlrpc_request_unref      (GskXmlrpcRequest   *request)
{
  g_assert (request->ref_count > 0);
  g_assert (request->magic == REQUEST_MAGIC);
  --(request->ref_count);
  if (request->ref_count == 0)
    {
      if (request->xmlrpc_stream != NULL)
	g_object_unref (request->xmlrpc_stream);
      gsk_xmlrpc_array_free (request->params);
      g_free (request->method_name);
      request->magic = 0;
      g_free (request);
    }
}

/**
 * gsk_xmlrpc_request_set_name:
 * @request: the request whose method-name should be set.
 * @name: the name of the method to invoke.
 *
 * Set the method name for this request.
 */
void             gsk_xmlrpc_request_set_name   (GskXmlrpcRequest   *request,
                                                const char      *name)
{
  char *nname = g_strdup (name);
  g_free (request->method_name);
  request->method_name = nname;
}

/**
 * gsk_xmlrpc_request_add_int32:
 * @request: request to whose parameters a value shall be appended.
 * @value: integer value to append.
 *
 * Append an integer to an XMLRPC request.
 */
void             gsk_xmlrpc_request_add_int32  (GskXmlrpcRequest *request,
                                                gint32           value)
{
  gsk_xmlrpc_array_add_int32 (request->params, value);
}

/**
 * gsk_xmlrpc_request_add_boolean:
 * @request: request to whose parameters a value shall be appended.
 * @value: integer value to append.
 *
 * Append a boolean to an XMLRPC request.
 */
void             gsk_xmlrpc_request_add_boolean  (GskXmlrpcRequest *request,
                                                gboolean         value)
{
  gsk_xmlrpc_array_add_boolean (request->params, value);
}
/**
 * gsk_xmlrpc_request_add_double:
 * @request: request to whose parameters a value shall be appended.
 * @value: double value to append.
 *
 * Append a double to an XMLRPC request.
 */
void             gsk_xmlrpc_request_add_double (GskXmlrpcRequest *request,
                                                gdouble          value)
{
  gsk_xmlrpc_array_add_double (request->params, value);
}
/**
 * gsk_xmlrpc_request_add_string:
 * @request: request to whose parameters a value shall be appended.
 * @value: string value to append.
 *
 * Append a string to an XMLRPC request.
 */
void             gsk_xmlrpc_request_add_string (GskXmlrpcRequest *request,
                                                const char      *value)
{
  gsk_xmlrpc_array_add_string (request->params, value);
}
/**
 * gsk_xmlrpc_request_add_date:
 * @request: request to whose parameters a value shall be appended.
 * @value: date value to append.
 *
 * Append a date to an XMLRPC request.
 */
void             gsk_xmlrpc_request_add_date   (GskXmlrpcRequest *request,
                                                gulong           value)
{
  gsk_xmlrpc_array_add_date (request->params, value);
}
/**
 * gsk_xmlrpc_request_add_data:
 * @request: request to whose parameters a value shall be appended.
 * @data: GByteArray to append: it shall be freed by the request,
 * that is, there is a transfer of ownership.
 *
 * Append binary data to an XMLRPC request (it will be base64 encoded transparently).
 */
void             gsk_xmlrpc_request_add_data (GskXmlrpcRequest *request,
                                              GByteArray      *data)
{
  gsk_xmlrpc_array_add_data (request->params, data);
}
/**
 * gsk_xmlrpc_request_add_struct:
 * @request: request to whose parameters a value shall be appended.
 * @substructure: structure to append to @array.
 * This will be freed by the @request automatically: a transfer of ownership
 * occurs in this function.
 *
 * Add a structure as a parameter to the request.
 */
void             gsk_xmlrpc_request_add_struct(GskXmlrpcRequest *request,
                                              GskXmlrpcStruct *substructure)
{
  gsk_xmlrpc_array_add_struct (request->params, substructure);
}
/**
 * gsk_xmlrpc_request_add_array:
 * @request: request to whose parameters a value shall be appended.
 * @array: array to append to @request's parmeter list.
 * This will be freed by the @request automatically: a transfer of ownership
 * occurs in this function.
 *
 * Add a structure as a parameter to the request.
 */
void             gsk_xmlrpc_request_add_array(GskXmlrpcRequest *request,
                                              GskXmlrpcArray  *array)
{
  gsk_xmlrpc_array_add_array (request->params, array);
}

/**
 * gsk_xmlrpc_response_new:
 * 
 * Allocate a new response.
 *
 * returns: the newly allocated response which has no parameters
 * and no fault.
 */
GskXmlrpcResponse   *gsk_xmlrpc_response_new        (void)
{
  GskXmlrpcResponse *response = g_new (GskXmlrpcResponse, 1);
  response->magic = RESPONSE_MAGIC;
  response->ref_count = 1;
  response->params = gsk_xmlrpc_array_new ();
  response->has_fault = FALSE;
  return response;
}

/**
 * gsk_xmlrpc_response_ref:
 * @response: the response to reference.
 *
 * Increase the reference count on @response.
 *
 * returns: the @response, for convenience.
 */
GskXmlrpcResponse   *gsk_xmlrpc_response_ref        (GskXmlrpcResponse   *response)
{
  g_assert (response->ref_count > 0);
  g_assert (response->magic == RESPONSE_MAGIC);
  ++(response->ref_count);
  return response;
}

/**
 * gsk_xmlrpc_response_unref:
 * @response: the response to stop referencing.
 *
 * Decrease the reference count on @response,
 * and free it if the count reached 0.
 */
void             gsk_xmlrpc_response_unref      (GskXmlrpcResponse   *response)
{
  g_assert (response->ref_count > 0);
  g_assert (response->magic == RESPONSE_MAGIC);
  --(response->ref_count);
  if (response->ref_count == 0)
    {
      gsk_xmlrpc_array_free (response->params);
      if (response->has_fault)
	gsk_xmlrpc_value_destruct (&response->fault);
      response->magic = 0;
      g_free (response);
    }
}

/**
 * gsk_xmlrpc_response_add_int32:
 * @response: response to whose parameters a value shall be appended.
 * @value: integer value to append.
 *
 * Append an integer to an XMLRPC response.
 */
void             gsk_xmlrpc_response_add_int32  (GskXmlrpcResponse *response,
                                                gint32           value)
{
  gsk_xmlrpc_array_add_int32 (response->params, value);
}
/**
 * gsk_xmlrpc_response_add_boolean:
 * @response: response to whose parameters a value shall be appended.
 * @value: integer value to append.
 *
 * Append a boolean to an XMLRPC response.
 */
void             gsk_xmlrpc_response_add_boolean  (GskXmlrpcResponse *response,
                                                gboolean           value)
{
  gsk_xmlrpc_array_add_boolean (response->params, value);
}
/**
 * gsk_xmlrpc_response_add_double:
 * @response: response to whose parameters a value shall be appended.
 * @value: double value to append.
 *
 * Append a double to an XMLRPC response.
 */
void             gsk_xmlrpc_response_add_double (GskXmlrpcResponse *response,
                                                gdouble          value)
{
  gsk_xmlrpc_array_add_double (response->params, value);
}
/**
 * gsk_xmlrpc_response_add_string:
 * @response: response to whose parameters a value shall be appended.
 * @value: string value to append.
 *
 * Append a string to an XMLRPC response.
 */
void             gsk_xmlrpc_response_add_string (GskXmlrpcResponse *response,
                                                const char      *value)
{
  gsk_xmlrpc_array_add_string (response->params, value);
}
/**
 * gsk_xmlrpc_response_add_date:
 * @response: response to whose parameters a value shall be appended.
 * @value: date value to append.
 *
 * Append a date to an XMLRPC response.
 */
void             gsk_xmlrpc_response_add_date   (GskXmlrpcResponse *response,
                                                gulong           value)
{
  gsk_xmlrpc_array_add_date (response->params, value);
}

/**
 * gsk_xmlrpc_response_add_data:
 * @response: response to whose parameters a value shall be appended.
 * @data: GByteArray to append: it shall be freed by the response,
 * that is, there is a transfer of ownership.
 *
 * Append binary data to an XMLRPC response (it will be base64 encoded transparently).
 */
void             gsk_xmlrpc_response_add_data (GskXmlrpcResponse *response,
                                              GByteArray      *data)
{
  gsk_xmlrpc_array_add_data (response->params, data);
}
/**
 * gsk_xmlrpc_response_add_struct:
 * @response: response to whose parameters a value shall be appended.
 * @substructure: structure to append to @array.
 * This will be freed by the @response automatically: a transfer of ownership
 * occurs in this function.
 *
 * Add a structure as a parameter to the response.
 */
void             gsk_xmlrpc_response_add_struct(GskXmlrpcResponse *response,
                                              GskXmlrpcStruct *substructure)
{
  gsk_xmlrpc_array_add_struct (response->params, substructure);
}
/**
 * gsk_xmlrpc_response_add_array:
 * @response: response to whose parameters a value shall be appended.
 * @array: array to append to @response's parmeter list.
 * This will be freed by the @response automatically: a transfer of ownership
 * occurs in this function.
 *
 * Add a structure as a parameter to the response.
 */
void             gsk_xmlrpc_response_add_array(GskXmlrpcResponse *response,
                                              GskXmlrpcArray  *array)
{
  gsk_xmlrpc_array_add_array (response->params, array);
}


#if 0
void             gsk_xmlrpc_response_fault_int32(GskXmlrpcResponse *response,
                                              gint32           value)
{
  if (response->has_fault)
    gsk_xmlrpc_value_destruct (&response->fault);
  response->has_fault = TRUE;
  response->fault.type = GSK_XMLRPC_INT32;
  response->fault.data.v_int32 = value;
}

void             gsk_xmlrpc_response_fault_double(GskXmlrpcResponse *response,
                                              gdouble          value)
{
  if (response->has_fault)
    gsk_xmlrpc_value_destruct (&response->fault);
  response->has_fault = TRUE;
  response->fault.type = GSK_XMLRPC_DOUBLE;
  response->fault.data.v_int32 = value;
}
void             gsk_xmlrpc_response_fault_string(GskXmlrpcResponse *response,
                                              const char      *value)
{
  if (response->has_fault)
    gsk_xmlrpc_value_destruct (&response->fault);
  response->has_fault = TRUE;
  response->fault.type = GSK_XMLRPC_STRING;
  response->fault.data.v_string = g_strdup (value);
}
void             gsk_xmlrpc_response_fault_date (GskXmlrpcResponse *response,
                                              gulong           value)
{
  if (response->has_fault)
    gsk_xmlrpc_value_destruct (&response->fault);
  response->has_fault = TRUE;
  response->fault.type = GSK_XMLRPC_DATE;
  response->fault.data.v_date = value;
}

/* these take ownership of second argument */
void             gsk_xmlrpc_response_fault_data (GskXmlrpcResponse *response,
                                              GByteArray      *data)
{
  if (response->has_fault)
    gsk_xmlrpc_value_destruct (&response->fault);
  response->has_fault = TRUE;
  response->fault.type = GSK_XMLRPC_BINARY_DATA;
  response->fault.data.v_binary_data = data;
}
void             gsk_xmlrpc_response_fault_array(GskXmlrpcResponse *response,
                                              GskXmlrpcArray  *array)
{
  if (response->has_fault)
    gsk_xmlrpc_value_destruct (&response->fault);
  response->has_fault = TRUE;
  response->fault.type = GSK_XMLRPC_ARRAY;
  response->fault.data.v_array = array;
}
#endif
/**
 * gsk_xmlrpc_response_fault:
 * @response: the response to affect.
 * @structure: the fault information.
 * This should be a struct with
 * at most an integer 'faultCode'
 * and a string 'faultString'.
 *
 * Indicate that an error occurred trying to
 * process the XMLRPC request.
 */
void             gsk_xmlrpc_response_fault   (GskXmlrpcResponse *response,
                                              GskXmlrpcStruct *structure)
{
  if (response->has_fault)
    gsk_xmlrpc_value_destruct (&response->fault);
  response->has_fault = TRUE;
  response->fault.type = GSK_XMLRPC_STRUCT;
  response->fault.data.v_struct = structure;
}

/* --- parsing --- */
/* States for a nesting level in the ValueStack.
 * These states apply to 'is_structure' ValueStacks. */
enum
{
  STRUCT_STATE_OUTER,
  STRUCT_STATE_IN_MEMBER,
  STRUCT_STATE_IN_MEMBERNAME,
  STRUCT_STATE_IN_VALUE,
  STRUCT_STATE_IN_TYPED_VALUE
};

/* States for a nesting level in the ValueStack.
 * These states apply to '!is_structure' ValueStacks. */
enum
{
  ARRAY_STATE_OUTER,
  ARRAY_STATE_IN_DATA,
  ARRAY_STATE_IN_VALUE,
  ARRAY_STATE_IN_TYPED_VALUE
};

/* ValueStacks: handle one nesting level of either
   an array or a structure. */
typedef struct _ValueStack ValueStack;
struct _ValueStack
{
  gboolean is_structure;	/* if not, it's an array */
  gpointer cur;		/* either GskXmlrpcArray or GskXmlrpcStruct */

  guint state;

  /* for structures only: the current member-name */
  char *name;

  gboolean got_value;
  GskXmlrpcValue value;

  ValueStack *up;
};

enum
{
  OUTER,

  REQUEST_IN_METHODCALL,
  REQUEST_IN_METHODNAME,
  REQUEST_IN_PARAMS,
  REQUEST_IN_PARAM,
  REQUEST_IN_PARAM_VALUE,
  REQUEST_IN_PARAM_TYPED_VALUE,

  RESPONSE_IN_METHODRESPONSE,
  RESPONSE_IN_PARAMS,
  RESPONSE_IN_PARAM,
  RESPONSE_IN_PARAM_VALUE,
  RESPONSE_IN_PARAM_TYPED_VALUE,
  RESPONSE_IN_FAULT,
  RESPONSE_IN_FAULT_VALUE,
  RESPONSE_IN_FAULT_TYPED_VALUE,
};

struct _GskXmlrpcParser
{
  ValueStack *stack;
  /* This is so the request & response 
   * can ref the stream so it does not
   * get shut down
   */
  GskXmlrpcStream *xmlrpc_stream;
  guint state;
  gboolean got_cur_param;
  GskXmlrpcValue cur_param;
  gpointer cur_message;

  GMarkupParseContext *context;
  GQueue *messages;
};

static gboolean
deal_with_stack_and_type (GskXmlrpcParser *parser,
			  const char *element_name,
			  GskXmlrpcValue *value_init_out,
			  GError **error)
{
  ValueStack *old = parser->stack;

  memset (value_init_out, 0, sizeof (GskXmlrpcValue));

  if (strcmp (element_name, "i4") == 0
   || strcmp (element_name, "int") == 0)
    value_init_out->type = GSK_XMLRPC_INT32;
  else if (strcmp (element_name, "boolean") == 0)
    value_init_out->type = GSK_XMLRPC_BOOLEAN;
  else if (strcmp (element_name, "double") == 0)
    value_init_out->type = GSK_XMLRPC_DOUBLE;
  else if (strcmp (element_name, "dateTime.iso8601") == 0)
    value_init_out->type = GSK_XMLRPC_DATE;
  else if (strcmp (element_name, "base64") == 0)
    value_init_out->type = GSK_XMLRPC_BINARY_DATA;
  else if (strcmp (element_name, "string") == 0)
    value_init_out->type = GSK_XMLRPC_STRING;
  else if (strcmp (element_name, "struct") == 0)
    value_init_out->type = GSK_XMLRPC_STRUCT;
  else if (strcmp (element_name, "array") == 0)
    value_init_out->type = GSK_XMLRPC_ARRAY;
  else
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN,
		   GSK_ERROR_BAD_FORMAT,
		   _("in XML-RPC, expected type tag in <value> tag, got <%s>"),
		   element_name);
    }

  if (value_init_out->type == GSK_XMLRPC_STRUCT
   || value_init_out->type == GSK_XMLRPC_ARRAY)
    {
      parser->stack = g_new (ValueStack, 1);
      parser->stack->up = old;
      parser->stack->name = NULL;
      parser->stack->got_value = FALSE;
      if (value_init_out->type == GSK_XMLRPC_STRUCT)
	{
	  parser->stack->is_structure = TRUE;
	  parser->stack->cur = gsk_xmlrpc_struct_new ();
	  parser->stack->state = STRUCT_STATE_OUTER;
	  value_init_out->data.v_struct = parser->stack->cur;
	}
      else /*if (*type_out == GSK_XMLRPC_ARRAY)*/
	{
	  parser->stack->is_structure = FALSE;
	  parser->stack->cur = gsk_xmlrpc_array_new ();
	  parser->stack->state = ARRAY_STATE_OUTER;
	  value_init_out->data.v_array = parser->stack->cur;
	}
    }
  return TRUE;
}

#if DEBUG_XMLRPC_PARSER
static char *
get_state_string (GskXmlrpcParser *parser)
{
  GString *str = g_string_new ("");
  switch (parser->state)
    {
#define ST(st) case st: g_string_append(str, #st); break;
  ST(OUTER)
  ST(REQUEST_IN_METHODCALL)
  ST(REQUEST_IN_METHODNAME)
  ST(REQUEST_IN_PARAMS)
  ST(REQUEST_IN_PARAM)
  ST(REQUEST_IN_PARAM_VALUE)
  ST(REQUEST_IN_PARAM_TYPED_VALUE)
  ST(RESPONSE_IN_METHODRESPONSE)
  ST(RESPONSE_IN_PARAMS)
  ST(RESPONSE_IN_PARAM)
  ST(RESPONSE_IN_PARAM_VALUE)
  ST(RESPONSE_IN_PARAM_TYPED_VALUE)
  ST(RESPONSE_IN_FAULT)
  ST(RESPONSE_IN_FAULT_VALUE)
  ST(RESPONSE_IN_FAULT_TYPED_VALUE)
    default: g_assert_not_reached();
    }

  ValueStack *stack = parser->stack;
  while (stack)
    {
      g_string_append(str, " << ");
      if (stack->is_structure)
	{
	  switch (stack->state)
	    {
	    ST(STRUCT_STATE_OUTER)
	    ST(STRUCT_STATE_IN_MEMBER)
	    ST(STRUCT_STATE_IN_MEMBERNAME)
	    ST(STRUCT_STATE_IN_VALUE)
	    ST(STRUCT_STATE_IN_TYPED_VALUE)
	    default: g_assert_not_reached();
	    }
	}
      else
	{
	  switch (stack->state)
	    {
            ST(ARRAY_STATE_OUTER)
            ST(ARRAY_STATE_IN_DATA)
            ST(ARRAY_STATE_IN_VALUE)
            ST(ARRAY_STATE_IN_TYPED_VALUE)
	    default: g_assert_not_reached();
	    }
	}
      stack = stack->up;
    }
#undef ST
  return g_string_free (str, FALSE);
}
#endif	 /* DEBUG_XMLRPC_PARSER */

static void 
parser_start_element   (GMarkupParseContext *context,
			const gchar         *element_name,
			const gchar        **attribute_names,
			const gchar        **attribute_values,
			gpointer             user_data,
                        GError             **error)
{
  GskXmlrpcParser *parser = user_data;
#if DEBUG_XMLRPC_PARSER
    {
      char *state = get_state_string(parser);
      g_message ("<%s>: %s",element_name,state);
      g_free (state);
    }
#endif
  switch (parser->state)
    {
    case OUTER:
      if (strcmp (element_name, "methodCall") == 0)
	{
	  parser->state = REQUEST_IN_METHODCALL;
	  /* The GskXmlrpcStream is needed so we can ref it
	   * and prevent a stream shutdown 
	   */
	  parser->cur_message = gsk_xmlrpc_request_new (parser->xmlrpc_stream);
	}
      else if (strcmp (element_name, "methodResponse") == 0)
	{
	  parser->state = RESPONSE_IN_METHODRESPONSE;
	  parser->cur_message = gsk_xmlrpc_response_new ();
	}
      else
	g_set_error (error, GSK_G_ERROR_DOMAIN,
		     GSK_ERROR_BAD_FORMAT,
		     _("root element in XML-RPC must be methodCall, got %s"),
		     element_name);
      return;

    case REQUEST_IN_METHODCALL:
      if (strcmp (element_name, "methodName") == 0)
	parser->state = REQUEST_IN_METHODNAME;
      else if (strcmp (element_name, "params") == 0)
	parser->state = REQUEST_IN_PARAMS;
      else
	g_set_error (error, GSK_G_ERROR_DOMAIN,
		     GSK_ERROR_BAD_FORMAT,
		     _("in XML-RPC, <methodCall> only contains <methodName> and <params>, got %s"),
		     element_name);
      return;
    case REQUEST_IN_METHODNAME:
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		   _("in XML-RPC, <methodName> cannot contain subtags, got <%s>"),
		   element_name);
      return;
    case RESPONSE_IN_METHODRESPONSE:
      if (strcmp (element_name, "params") == 0)
	parser->state = RESPONSE_IN_PARAMS;
      else if (strcmp (element_name, "fault") == 0)
	parser->state = RESPONSE_IN_FAULT;
      else
	{
	  g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		       _("expected <fault> or <params>, got <%s>"),
		       element_name);
	}
      return;
    case RESPONSE_IN_PARAMS:
    case REQUEST_IN_PARAMS:
      if (strcmp (element_name, "param") == 0)
	parser->state = parser->state == REQUEST_IN_PARAMS ? REQUEST_IN_PARAM
	                                                   : RESPONSE_IN_PARAM;
      else
	g_set_error (error, GSK_G_ERROR_DOMAIN,
		     GSK_ERROR_BAD_FORMAT,
		     _("in XML-RPC, <params> only contains <param> tags, got <%s>"),
		     element_name);
      return;
    case RESPONSE_IN_PARAM:
    case REQUEST_IN_PARAM:
      if (strcmp (element_name, "value") == 0)
	parser->state
	  = (parser->state == REQUEST_IN_PARAM) ? REQUEST_IN_PARAM_VALUE
					        : RESPONSE_IN_PARAM_VALUE;
      else
	g_set_error (error, GSK_G_ERROR_DOMAIN,
		     GSK_ERROR_BAD_FORMAT,
		     _("in XML-RPC, <param> only contains <value> tags, got <%s>"),
		     element_name);
      return;
    case RESPONSE_IN_PARAM_VALUE:
    case REQUEST_IN_PARAM_VALUE:
    case RESPONSE_IN_FAULT_VALUE:
      {
	GskXmlrpcValue *value = (parser->state == RESPONSE_IN_FAULT_VALUE)
	                      ? &((GskXmlrpcResponse*)parser->cur_message)->fault
			      : &parser->cur_param;
	if (deal_with_stack_and_type (parser, element_name, value, error))
	  {
	    if (parser->state == REQUEST_IN_PARAM_VALUE)
	      parser->state = REQUEST_IN_PARAM_TYPED_VALUE;
	    else if (parser->state == RESPONSE_IN_PARAM_VALUE)
	      parser->state = RESPONSE_IN_PARAM_TYPED_VALUE;
	    else
	      parser->state = RESPONSE_IN_FAULT_TYPED_VALUE;
	  }
	return;
      }
    case REQUEST_IN_PARAM_TYPED_VALUE:
    case RESPONSE_IN_PARAM_TYPED_VALUE:
    case RESPONSE_IN_FAULT_TYPED_VALUE:
      if (parser->stack == NULL)
	{
	  g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		       _("in XMLRPC, subtags not allowed in unstructured type, got <%s>"),
		       element_name);
	  return;
	}
      if (parser->stack->is_structure)
	{
	  switch (parser->stack->state)
	    {
	    case STRUCT_STATE_OUTER:
	      if (strcmp (element_name, "member") == 0)
		parser->stack->state = STRUCT_STATE_IN_MEMBER;
	      else
		g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			     _("in XMLRPC, got <%s> where <member> expected"),
			     element_name);
	      return;
	    case STRUCT_STATE_IN_MEMBER:
	      if (strcmp (element_name, "value") == 0)
		parser->stack->state = STRUCT_STATE_IN_VALUE;
	      else if (strcmp (element_name, "name") == 0)
		parser->stack->state = STRUCT_STATE_IN_MEMBERNAME;
	      else
		g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			     _("in XMLRPC, got <%s> where <value> or <name> expected"),
			     element_name);
	      return;
	    case STRUCT_STATE_IN_VALUE:
	      {
		ValueStack *cur_stack = parser->stack;

		if (!deal_with_stack_and_type (parser, element_name, 
					       &cur_stack->value, error))
		  return;
		cur_stack->state = STRUCT_STATE_IN_TYPED_VALUE;
		return;
	      }

	    case STRUCT_STATE_IN_TYPED_VALUE:
	    case STRUCT_STATE_IN_MEMBERNAME:
	      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			   _("got <%s> where only text expected"),
			   element_name);
	      return;
	    }
	}
      else /* it's an array */
	{
	  switch (parser->stack->state)
	    {
	    case ARRAY_STATE_OUTER:
	      if (strcmp (element_name, "data") == 0)
		parser->stack->state = ARRAY_STATE_IN_DATA;
	      else
		g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			     _("in XMLRPC, got <%s> where <data> expected"),
			     element_name);
	      return;
	    case ARRAY_STATE_IN_DATA:
	      if (strcmp (element_name, "value") == 0)
		parser->stack->state = ARRAY_STATE_IN_VALUE;
	      else
		g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			     _("in XMLRPC, got <%s> where <value> expected"),
			     element_name);
	      return;
	    case ARRAY_STATE_IN_VALUE:
	      {
		ValueStack *cur_stack = parser->stack;
		if (!deal_with_stack_and_type (parser, element_name, 
					       &cur_stack->value, error))
		  return;
		cur_stack->state = ARRAY_STATE_IN_TYPED_VALUE;
		return;
	      }

		/* no open tags allowed in these states */
	    case ARRAY_STATE_IN_TYPED_VALUE:
	      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			   _("got <%s> where only text expected"),
			   element_name);
	      return;
	    }
	}
      break;
    case RESPONSE_IN_FAULT:
      if (strcmp (element_name, "value") == 0)
	parser->state = RESPONSE_IN_FAULT_VALUE;
      else
	g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		     _("only <value> allowed under <fault>, got <%s>"),
		     element_name);
      return;
    default:
      g_assert_not_reached ();
    }
}

#define ASSERT_ELEMENT_NAME(name) g_assert(strcmp(element_name,name)==0)

static void 
parser_end_element     (GMarkupParseContext *context,
			const gchar         *element_name,
			gpointer             user_data,
			GError             **error)
{
  GskXmlrpcParser *parser = user_data;
#if DEBUG_XMLRPC_PARSER
    {
      char *state = get_state_string(parser);
      g_message ("</%s>: %s",element_name,state);
      g_free (state);
    }
#endif
  switch (parser->state)
    {
    case OUTER:
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		   _("got unexpected closing tag at outer scope (</%s>)"),
		   element_name);
      return;
    case REQUEST_IN_METHODCALL:
    case RESPONSE_IN_METHODRESPONSE:
      g_queue_push_tail (parser->messages, parser->cur_message);
      parser->cur_message = NULL;
      parser->state = OUTER;
      return;
    case REQUEST_IN_METHODNAME:
      parser->state = REQUEST_IN_METHODCALL;
      ASSERT_ELEMENT_NAME("methodName");
      break;
    case REQUEST_IN_PARAMS:
      parser->state = REQUEST_IN_METHODCALL;
      ASSERT_ELEMENT_NAME("params");
      break;
    case RESPONSE_IN_PARAMS:
      parser->state = RESPONSE_IN_METHODRESPONSE;
      ASSERT_ELEMENT_NAME("params");
      break;
    case REQUEST_IN_PARAM:
    case RESPONSE_IN_PARAM:
      if (!parser->got_cur_param)
	{
	  g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		       _("missing <value> tag in <param> tag"));
	  return;
	}

      /* Append param to set */
      {
	GskXmlrpcArray *array = (parser->state == REQUEST_IN_PARAM)
	                         ? ((GskXmlrpcRequest*)(parser->cur_message))->params
	                         : ((GskXmlrpcResponse*)(parser->cur_message))->params;
	gsk_xmlrpc_array_add_value (array, &parser->cur_param);
      }
      parser->got_cur_param = FALSE;

      parser->state = (parser->state == REQUEST_IN_PARAM)
	              ? REQUEST_IN_PARAMS : RESPONSE_IN_PARAMS;
      ASSERT_ELEMENT_NAME("param");
      break;
    case REQUEST_IN_PARAM_VALUE:
    case RESPONSE_IN_PARAM_VALUE:
      if (!parser->got_cur_param)
	{
	  // XXX: default to string????
	  g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		       _("missing 'type' tag in 'value'"));
	  return;
	}
      ASSERT_ELEMENT_NAME("value");
      parser->state = (parser->state == REQUEST_IN_PARAM_VALUE)
	               ? REQUEST_IN_PARAM : RESPONSE_IN_PARAM;
      break;
    case REQUEST_IN_PARAM_TYPED_VALUE:
    case RESPONSE_IN_PARAM_TYPED_VALUE:
    case RESPONSE_IN_FAULT_TYPED_VALUE:
      {
	ValueStack *orig_stack = parser->stack;
	if (parser->stack != NULL)
	  {
	    if (parser->stack->is_structure)
	      {
		switch (parser->stack->state)
		  {
		  case STRUCT_STATE_OUTER:
		    /* Ok, pop stack. */
		    {
		      ValueStack *to_kill = parser->stack;
		      parser->stack = to_kill->up;
		      g_assert (to_kill->got_value == FALSE);
		      g_assert (to_kill->name == NULL);
		      g_free (to_kill);
		      ASSERT_ELEMENT_NAME("struct");
		    }
		    break;
		  case STRUCT_STATE_IN_MEMBER:
		    {
		      if (parser->stack->name == NULL
		       || parser->stack->got_value == FALSE)
			{
			  g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
				       _("required field (<name> or <value>) missing in struct's member"));
			  return;
			}
		      gsk_xmlrpc_struct_add_value_steal_name (parser->stack->cur, parser->stack->name, &parser->stack->value);
		      parser->stack->got_value = FALSE;
		      parser->stack->name = NULL;
		      parser->stack->state = STRUCT_STATE_OUTER;
		      ASSERT_ELEMENT_NAME("member");
		    }
		    return;
		  case STRUCT_STATE_IN_MEMBERNAME:
		    if (parser->stack->name == NULL)
		      {
			g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
				     _("missing text in <name> tag"));
			return;
		      }
		    parser->stack->state = STRUCT_STATE_IN_MEMBER;
		    ASSERT_ELEMENT_NAME("name");
		    return;
		  case STRUCT_STATE_IN_VALUE:
		    if (parser->stack->got_value == FALSE)
		      {
			parser->stack->got_value = TRUE;
			parser->stack->value.type = GSK_XMLRPC_STRING;
			parser->stack->value.data.v_string = g_strdup ("");
		      }
		    ASSERT_ELEMENT_NAME("value");
		    parser->stack->state = STRUCT_STATE_IN_MEMBER;
		    return;
		  case STRUCT_STATE_IN_TYPED_VALUE:
		    parser->stack->got_value = TRUE;
		    parser->stack->state = STRUCT_STATE_IN_VALUE;
		    return;
		  default:
		      g_assert_not_reached ();
		  }
	      }
	    else
	      {
		switch (parser->stack->state)
		  {
		  case ARRAY_STATE_OUTER:
		    {
		      ValueStack *to_kill = parser->stack;
		      parser->stack = to_kill->up;
		      g_assert (to_kill->got_value == FALSE);
		      g_free (to_kill);
		    }
		    ASSERT_ELEMENT_NAME("array");
		    break;
		  case ARRAY_STATE_IN_DATA:
		    parser->stack->state = ARRAY_STATE_OUTER;
		    ASSERT_ELEMENT_NAME("data");
		    break;
		  case ARRAY_STATE_IN_VALUE:
		    if (parser->stack->got_value == FALSE)
		      {
			parser->stack->got_value = TRUE;
			parser->stack->value.type = GSK_XMLRPC_STRING;
			parser->stack->value.data.v_string = g_strdup ("");
			return;
		      }
		    gsk_xmlrpc_array_add_value (parser->stack->cur, &parser->stack->value);
		    parser->stack->got_value = FALSE;
		    parser->stack->state = ARRAY_STATE_IN_DATA;
		    ASSERT_ELEMENT_NAME("value");
		    return;
		  case ARRAY_STATE_IN_TYPED_VALUE:
		    parser->stack->got_value = TRUE;
		    parser->stack->state = ARRAY_STATE_IN_VALUE;
		    return;
		  default:
		      g_assert_not_reached ();
		  }
	      }
	  }
	if (parser->stack == NULL)
	  {
	    parser->got_cur_param = TRUE;
	    parser->state = (parser->state == REQUEST_IN_PARAM_TYPED_VALUE) ? REQUEST_IN_PARAM_VALUE :
			    (parser->state == RESPONSE_IN_PARAM_TYPED_VALUE) ?  RESPONSE_IN_PARAM_VALUE :
			    RESPONSE_IN_FAULT_VALUE;


	    // XXX: NULL string => "" conversion?  or next case?
	  }
	else if (parser->stack != orig_stack)
	  {
	    if (parser->stack->is_structure)
	      {
		g_assert (parser->stack->state == STRUCT_STATE_IN_TYPED_VALUE);
		parser->stack->state = STRUCT_STATE_IN_VALUE;
	      }
	    else
	      {
		g_assert (parser->stack->state == ARRAY_STATE_IN_TYPED_VALUE);
		parser->stack->state = ARRAY_STATE_IN_VALUE;
	      }
	    parser->stack->got_value = TRUE;
	  }
      }
      return;
    case RESPONSE_IN_FAULT:
      parser->state = RESPONSE_IN_METHODRESPONSE;
      ASSERT_ELEMENT_NAME("fault");
      return;
    case RESPONSE_IN_FAULT_VALUE:
      ((GskXmlrpcResponse*)(parser->cur_message))->has_fault = TRUE;
      parser->state = RESPONSE_IN_FAULT;
      ASSERT_ELEMENT_NAME("value");
      return;
    default: g_assert_not_reached ();
    }
}
static gboolean
is_whitespace (const char *txt, unsigned len)
{
  while (len--)
    {
      if (!isspace (*txt++))
	return FALSE;
    }
  return TRUE;
}

static gboolean
parse_value_from_string (const char *text, gsize text_len,
			 GskXmlrpcValue *val, GError **error)
{
  switch (val->type)
    {
    case GSK_XMLRPC_INT32:
      {
	char *tmp;
	char *end;
	if (text_len > 1000)
	  {
	    g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			 _("integer value way too long"));
	    return FALSE;
	  }
	tmp = g_alloca (text_len + 1);
	memcpy (tmp, text, text_len);
	tmp[text_len] = 0;
	val->data.v_int32 = strtol (tmp, &end, 10);
	if (tmp == end)
	  {
	    g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			 _("error parsing int from '%.*s'"), text_len, text);
	    return FALSE;
	  }
	break;
      }
    case GSK_XMLRPC_BOOLEAN:
      if (text_len != 1
       || (text[0] != '0' && text[0] != '1'))
	{
	  g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		       _("unexpected boolean value '%.*s'"), text_len, text);
	  return FALSE;
	}
      val->data.v_boolean = (text[0] == '1');
      break;
    case GSK_XMLRPC_DOUBLE:
      {
	char *tmp;
	char *end;
	if (text_len > 1000)
	  {
	    g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			 _("double value way too long"));
	    return FALSE;
	  }
	tmp = g_alloca (text_len + 1);
	memcpy (tmp, text, text_len);
	tmp[text_len] = 0;
	val->data.v_double = strtod (tmp, &end);
	if (tmp == end)
	  {
	    g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			 _("error parsing double from '%.*s'"), text_len, text);
	    return FALSE;
	  }
	break;
      }
      break;
    case GSK_XMLRPC_STRING:
      val->data.v_string = g_strndup (text, text_len);
      break;
    case GSK_XMLRPC_DATE:
      {
	time_t t;
	char *tmp = g_alloca (text_len + 1);
	memcpy (tmp, text, text_len);
	tmp[text_len] = 0;
	if (!gsk_date_parse_timet (tmp, &t, GSK_DATE_FORMAT_ISO8601))
	  {
	    g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			 _("error parsing ISO8601 date"));
	    return FALSE;
	  }
	val->data.v_date = t;
      }
      break;
    case GSK_XMLRPC_BINARY_DATA:
      {
	GByteArray *array = g_byte_array_new ();
	g_byte_array_set_size (array, GSK_BASE64_GET_MAX_DECODED_LEN (text_len));
	g_byte_array_set_size (array, gsk_base64_decode ((char*)array->data, array->len, text, text_len));
	val->data.v_binary_data = array;
	break;
      }
    case GSK_XMLRPC_STRUCT:
    case GSK_XMLRPC_ARRAY:
      if (!is_whitespace (text, text_len))
	g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		     _("struct/array had raw text inside it"));
      return FALSE;
    }
  return TRUE;
}

/* Called for character data */
/* text is not nul-terminated */
static void 
parser_text            (GMarkupParseContext *context,
			const gchar         *text,
			gsize                text_len,  
			gpointer             user_data,
			GError             **error)
{
  GskXmlrpcParser *parser = user_data;
  gboolean got_implicit_string = FALSE;
#if DEBUG_XMLRPC_PARSER
    {
      char *state = get_state_string(parser);
      g_message ("%s: '%.*s'", state, text_len, text);
      g_free (state);
    }
#endif
  switch (parser->state)
    {
    case OUTER:
      if (!is_whitespace (text, text_len))
	g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		     _("did not expect non-whitespace text at outer scope"));
      return;
    case REQUEST_IN_METHODCALL:
      if (!is_whitespace (text, text_len))
	g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		     _("did not expect non-whitespace text in <methodCall>"));
      return;
    case RESPONSE_IN_METHODRESPONSE:
      if (!is_whitespace (text, text_len))
	g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		     _("did not expect non-whitespace text in <methodResponse>"));
      return;
    case REQUEST_IN_METHODNAME:
      {
	GskXmlrpcRequest *req = parser->cur_message;
	g_free (req->method_name);
	req->method_name = g_strndup (text, text_len);
	return;
      }
    case RESPONSE_IN_PARAMS:
    case REQUEST_IN_PARAMS:
      if (!is_whitespace (text, text_len))
	g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		     _("did not expect non-whitespace text in <params>"));
      return;
    case REQUEST_IN_PARAM:
    case RESPONSE_IN_PARAM:
      if (!is_whitespace (text, text_len))
	g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		     _("did not expect non-whitespace text in <param>"));
      return;
    case RESPONSE_IN_FAULT:
    case RESPONSE_IN_FAULT_VALUE:
      if (!is_whitespace (text, text_len))
	g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
		     _("did not expect non-whitespace text in <fault>"));
      return;
    case RESPONSE_IN_PARAM_VALUE:
    case REQUEST_IN_PARAM_VALUE:
      if (is_whitespace (text, text_len))
	return;
      {
	GskXmlrpcValue *value = (parser->state == RESPONSE_IN_FAULT_VALUE)
			      ? &((GskXmlrpcResponse*)parser->cur_message)->fault
			      : &parser->cur_param;
	if (parser->state == REQUEST_IN_PARAM_VALUE)
	  parser->state = REQUEST_IN_PARAM_TYPED_VALUE;
	else if (parser->state == RESPONSE_IN_PARAM_VALUE)
	  parser->state = RESPONSE_IN_PARAM_TYPED_VALUE;

	value->type = GSK_XMLRPC_STRING;
	/* fall-through */
	got_implicit_string = TRUE;
      }
    case REQUEST_IN_PARAM_TYPED_VALUE:
    case RESPONSE_IN_PARAM_TYPED_VALUE:
    case RESPONSE_IN_FAULT_TYPED_VALUE:
      {
	GskXmlrpcValue *value = NULL;
	gboolean *got_value;
	if (parser->stack)
	  {
	    ValueStack *st = parser->stack;
	    if (st->is_structure && st->state == STRUCT_STATE_IN_VALUE)
	      {
		if (is_whitespace (text, text_len))
		  return;
		got_implicit_string = TRUE;
		st->state = STRUCT_STATE_IN_TYPED_VALUE;
	      }
	    else if (!st->is_structure && st->state == ARRAY_STATE_IN_VALUE)
	      {
		if (is_whitespace (text, text_len))
		  return;
		got_implicit_string = TRUE;
		st->state = ARRAY_STATE_IN_TYPED_VALUE;
	      }
	    if ((st->is_structure && st->state == STRUCT_STATE_IN_TYPED_VALUE)
             || (!st->is_structure && st->state == ARRAY_STATE_IN_TYPED_VALUE))
	      {
		value = &parser->stack->value;
		got_value = &parser->stack->got_value;
		if (got_implicit_string)
		  value->type = GSK_XMLRPC_STRING;
	      }
	    else if (st->is_structure && st->state == STRUCT_STATE_IN_MEMBERNAME)
	      {
		g_free (st->name);
		st->name = g_strndup (text, text_len);
		return;
	      }
	  }
	else if (parser->state == REQUEST_IN_PARAM_TYPED_VALUE
              || parser->state == RESPONSE_IN_PARAM_TYPED_VALUE)
	  {
	    value = &parser->cur_param;
	    got_value = &parser->got_cur_param;
	  }
	else if (parser->state == RESPONSE_IN_FAULT_TYPED_VALUE)
	  {
	    GskXmlrpcResponse *res = parser->cur_message;
	    value = &res->fault;
	    got_value = &res->has_fault;
	  }
	else
	  g_assert_not_reached ();

	if (value == NULL)
	  {
	    if (!is_whitespace (text, text_len))
	      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			   _("got text where none expected (text='%.*s'"),
			   text_len, text);
	    return;
	  }

	if (*got_value)
	  {
	    g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
			 _("already got value (text='%.*s'"),
			 text_len, text);
	    return;
	  }
	if (!parse_value_from_string (text, text_len, value, error))
	  return;
	*got_value = TRUE;
	if (got_implicit_string)
	  {
	    if (parser->stack)
	      {
		ValueStack *st = parser->stack;
		if (st->is_structure && st->state == STRUCT_STATE_IN_TYPED_VALUE)
		  st->state = STRUCT_STATE_IN_VALUE;
		else if (!st->is_structure && st->state == ARRAY_STATE_IN_TYPED_VALUE)
		  st->state = ARRAY_STATE_IN_VALUE;
		else
		  g_warning ("unexpected state");
	      }
	    else if (parser->state == RESPONSE_IN_PARAM_TYPED_VALUE)
	      parser->state = RESPONSE_IN_PARAM_VALUE;
	    else if (parser->state == REQUEST_IN_PARAM_TYPED_VALUE)
	      parser->state = REQUEST_IN_PARAM_VALUE;
	    else
	      g_assert_not_reached ();
	  }
	return;
      }
    default: g_assert_not_reached ();
    }
}

static void 
parser_passthrough     (GMarkupParseContext *context,
			const gchar         *passthrough_text,
			gsize                text_len,  
			gpointer             user_data,
                        GError             **error)
{
  /* do nothing */
}

static void
parser_error           (GMarkupParseContext *context,
			GError              *error,
			gpointer             user_data)
{
  /* do nothing */
  //g_message ("got parser error '%s'", error->message);
}

static GMarkupParser parser_funcs =
{
  parser_start_element,
  parser_end_element,
  parser_text,
  parser_passthrough,
  parser_error
};

/**
 * gsk_xmlrpc_parser_new:
 *
 * Allocate a new XMLRPC parser. A parser can parse both requests and responses.
 *
 * returns: the newly allocated parser.
 */
GskXmlrpcParser *gsk_xmlrpc_parser_new (GskXmlrpcStream *stream)
{
  GskXmlrpcParser *parser = g_new0 (GskXmlrpcParser, 1);
  parser->xmlrpc_stream = stream;
  parser->state = OUTER;
  parser->messages = g_queue_new ();
  parser->context = g_markup_parse_context_new (&parser_funcs, 0, parser, NULL);
  return parser;
}

/**
 * gsk_xmlrpc_parser_feed:
 * @parser: the parser to give data.
 * @text: the data to parse (it may just be a partial request).
 * @len: the length of @text, or -1 to use NUL-termination.
 * @error: place to put the error object if the parsing has a problem.
 *
 * Pass data into the XMLRPC parser.
 *
 * If this works, gsk_xmlrpc_parser_get_request() and/or
 * gsk_xmlrpc_parser_get_response() should be called as appropriate
 * until there are no more messages to dequeue.
 *
 * returns: TRUE if processing did not encounter an error.
 */
gboolean   gsk_xmlrpc_parser_feed   (GskXmlrpcParser *parser,
				     const char              *text,
				     gssize                   len,
				     GError                 **error)
{
  gboolean rv = g_markup_parse_context_parse(parser->context, text, len, error);
  return rv;
}

static gpointer
gsk_xmlrpc_parser_get_either (GskXmlrpcParser *parser,
			      guint            magic)
{
  GskXmlrpcRequest *rv;
  if (parser->messages->head == NULL)
    return NULL;
  rv = parser->messages->head->data;
  if (* (guint*) parser->messages->head->data == magic)
    return g_queue_pop_head (parser->messages);
  return NULL;
}

/**
 * gsk_xmlrpc_parser_get_request:
 * @parser: parser to try and get a parsed request from.
 *
 * Get a parsed request from the list maintained by
 * the parser.
 *
 * returns: a reference to the request; the caller
 * must call gsk_xmlrpc_request_unref() eventually.
 */
GskXmlrpcRequest *
gsk_xmlrpc_parser_get_request (GskXmlrpcParser *parser)
{
  return gsk_xmlrpc_parser_get_either (parser, REQUEST_MAGIC);
}

/**
 * gsk_xmlrpc_parser_get_response:
 * @parser: parser to try and get a parsed response from.
 *
 * Get a parsed response from the list maintained by
 * the parser.
 *
 * returns: a reference to the response; the caller
 * must call gsk_xmlrpc_response_unref() eventually.
 */
GskXmlrpcResponse *
gsk_xmlrpc_parser_get_response (GskXmlrpcParser *parser)
{
  return gsk_xmlrpc_parser_get_either (parser, RESPONSE_MAGIC);
}

static void
gsk_xmlrpc_either_unref (gpointer data)
{
  const guint *magic = data;
  if (*magic == REQUEST_MAGIC)
    gsk_xmlrpc_request_unref (data);
  else if (*magic == RESPONSE_MAGIC)
    gsk_xmlrpc_request_unref (data);
  else
    g_assert_not_reached ();
}

static void
value_stack_destroy_all (ValueStack *stack)
{
  while (stack)
    {
      ValueStack *kill = stack;
      stack = stack->up;

      if (kill->is_structure)
	{
	  gsk_xmlrpc_struct_free (kill->cur);
	  g_free (kill->name);
	}
      else
	gsk_xmlrpc_array_free (kill->cur);
      if (kill->got_value)
	gsk_xmlrpc_value_destruct (&kill->value);
      g_free (kill);
    }
}

/**
 * gsk_xmlrpc_parser_free:
 * @parser: the parser to free.
 *
 * Free the memory associated with the parser.
 */
void gsk_xmlrpc_parser_free (GskXmlrpcParser *parser)
{
  g_list_foreach (parser->messages->head, (GFunc) gsk_xmlrpc_either_unref, NULL);
  g_queue_free (parser->messages);
  g_markup_parse_context_free (parser->context);
  value_stack_destroy_all (parser->stack);
  if (parser->got_cur_param)
    gsk_xmlrpc_value_destruct (&parser->cur_param);
  g_free (parser);
}



syntax highlighted by Code2HTML, v. 0.9.1