/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*  transform.c
 *	Copyright (C) 2006 Armin Burgmeier
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program 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 General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "transform.h"

#include <string.h>
#include <ctype.h>

typedef struct _CgTransformParamGuess CgTransformParamGuess;
struct _CgTransformParamGuess
{
	const gchar *gtype;
	const gchar *paramspec;
};

typedef struct _CgTransformGTypeGuess CgTransformGTypeGuess;
struct _CgTransformGTypeGuess
{
	const gchar *ctype;
	const gchar *gtype_prefix;
	const gchar *gtype_name;
};

/* This function looks up a flag with the given abbrevation (which has not
 * to be null-terminated) in the given flag list. */
static const CgElementEditorFlags *
cg_transform_lookup_flag (const CgElementEditorFlags *flags,
                          gchar *abbrevation,
                          size_t len)
{
	const CgElementEditorFlags *flag;
	for (flag = flags; flag->name != NULL; ++ flag)
	{
		if (strncmp (flag->abbrevation, abbrevation, len) == 0)
		{
			if(flag->abbrevation[len] == '\0')
			{
				return flag;
			}
		}
	}
	
	return NULL;
}

/* This function tries to convert a native C Type like int, float or
 * unsigned long into a GType like G_TYPE_INT, G_TYPE_FLOAT, G_TYPE_ULONG. */
gboolean
cg_transform_default_c_type_to_g_type (const gchar *c_type,
                                       const gchar **g_type_prefix,
                                       const gchar **g_type_name)
{
	static const CgTransformGTypeGuess DEFAULT_TYPES[] =
	{
		{ "int", "G", "INT" },
		{ "gint", "G", "INT" },
		{ "unsigned int", "G", "UINT" },
		{ "guint", "G", "UINT" },
		{ "char", "G", "CHAR" },
		{ "gchar", "G", "CHAR" },
		{ "unsigned char", "G", "UCHAR" },
		{ "guchar", "G", "UCHAR" },
		{ "long", "G", "LONG" },
		{ "glong", "G", "LONG" },
		{ "unsigned long", "G", "ULONG" },
		{ "gulong", "G", "ULONG" },
		{ "gint64", "G", "INT64" },
		{ "guint64", "G", "UINT64" },
		{ "float", "G", "FLOAT" },
		{ "double", "G", "DOUBLE" },
		{ "char*", "G", "STRING" },
		{ "gchar*", "G", "STRING" },
		{ "char *", "G", "STRING" },
		{ "gchar *", "G", "STRING" },
		{ "const char*", "G", "STRING" },
		{ "const gchar*", "G", "STRING" },
		{ "const char *", "G", "STRING" },
		{ "const gchar *", "G", "STRING" },
		{ "gpointer", "G", "POINTER" },
		{ "void*", "G", "POINTER" },
		{ "void *", "G", "POINTER" },
		{ "gconstpointer", "G", "POINTER" },
		{ "const void*", "G", "POINTER" },
		{ "const void *", "G", "POINTER" },
		{ "void", "G", "NONE" },
		{ "gboolean", "G", "BOOLEAN" },
		{ "GParamSpec*", "G", "PARAM" },
		{ "GParamSpec *", "G", "PARAM" },
		{ NULL, NULL, NULL }
	};
	
	const CgTransformGTypeGuess *guess;
	
	for (guess = DEFAULT_TYPES; guess->ctype != NULL; ++ guess)
	{
		if (strcmp (guess->ctype, c_type) == 0)
		{
			*g_type_prefix = guess->gtype_prefix;
			*g_type_name = guess->gtype_name;
			return TRUE;
		}
	}
	
	return FALSE;
}

/* This function tries to convert a c_type like GtkTreeIter to add a separator 
 * between each word, then convert to upper case or lower case. */
gchar *
cg_transform_custom_c_type (const gchar *c_type,
                            gboolean upper_case,
                            gchar separator)
{
	GString *str;
	const gchar *pos;
	gchar (*tocase_func) (gchar);

	if (upper_case)
		tocase_func = g_ascii_toupper;
	else
		tocase_func = g_ascii_tolower;

	str = g_string_sized_new (128);
	for (pos = c_type; *pos != '\0'; ++ pos)
	{
		if (!g_ascii_isalnum (*pos))
			continue;

		if (isupper (*pos) &&				/* Upper case only */
		    pos > c_type &&					/* Can't be first character, to check the previous character */
		    !isupper (*(pos-1)))			/* Previous character is not upper case */
		{
			/* This will add the separator if the previous character is
			 * not upper case and the current character is upper case */
			g_string_append_c (str, separator);
		}
		else if (isupper (*pos) &&			/* Upper case only */
		         pos-1 == c_type &&			/* Second character */
		         *(pos+1) != '\0' &&		/* Next character shouldn't be the last */
		         !isupper (*(pos+1)))		/* Next of next character is not upper case */
		{
			/* This will add the separator if the prefix is single character,
			 * the current character is upper case and the next character is not
			 * upper case
			 * In summary, this will catch single character prefixes
			 * Example: GObject -> G_OBJECT where O is the current character */
			g_string_append_c (str, separator);
		}
		else if (isupper (*pos) &&			/* Upper case only */
		         pos-1 > c_type &&			/* Can't be first two characters, to check the two previous characters */
		         isupper (*(pos-1)) &&		/* Previous character is upper case */
		         isupper (*(pos-2)) &&		/* Previous of previous character is upper case */
		         *(pos+1) != '\0' &&		/* Next character shouldn't be the last */
		         !isupper (*(pos+1)))		/* Next of next character is not upper case */
		{
			/* This will add the separator if there are three or more upper case
			 * (the last one is this character) and the next one is not upper case */
			/* Example: GtkUIManager -> GTK_UI_MANAGER where M is the current character */
			g_string_append_c (str, separator);
		}

		g_string_append_c (str, tocase_func (*pos));
	}

	return g_string_free (str, FALSE);
}

/* This function tries to convert a custom c_type like GtkTreeIter to
 * a gobject type like GTK_TYPE_TREE_ITER. It does this by parsing the C type.
 * The code is mostly borrowed from old action-callbacks.c by Dave
 * Huseby and Massimo Cora'. */
void
cg_transform_custom_c_type_to_g_type (const gchar *c_type,
                                      gchar **g_type_prefix,
                                      gchar **g_type_name,
                                      gchar **g_func_prefix)
{
	gchar *c_type_transformed, **c_type_split;

	c_type_transformed = cg_transform_custom_c_type (c_type, TRUE, '_');

	if (g_type_prefix != NULL || g_type_name != NULL)
	{
		c_type_split = g_strsplit (c_type_transformed, "_", 2);

		/* If c_type is empty string, first string is NULL */
		if (c_type_split[0])
		{
			if (g_type_prefix != NULL)
				*g_type_prefix = c_type_split[0];
			else
				g_free (c_type_split[0]);

			/* If prefix only, second string is NULL */
			if (c_type_split[1])
			{
				if (g_type_name != NULL)
					*g_type_name = c_type_split[1];
				else
					g_free (c_type_split[1]);
			}
			else if (g_type_name != NULL)
				*g_type_name = g_strdup ("");
		}
		else
		{
			if (g_type_prefix != NULL)
				*g_type_prefix = g_strdup ("");

			if (g_type_name != NULL)
				*g_type_name = g_strdup ("");
		}

		/* Free only the array */
		g_free (c_type_split);
	}

	if (g_func_prefix != NULL)
		*g_func_prefix = g_ascii_strdown (c_type_transformed, -1);

	g_free (c_type_transformed);
}

/* This function tries to convert any possible C type to its corresponding
 * GObject type. First, it looks whether the C type is a default type. If not,
 * it strips leading const or a trailing * away (because it does not matter
 * whether we have const GtkTreeIter* or GtkTreeIter, it all results in the
 * same gobject type, namely GTK_TYPE_TREE_ITER) and calls
 * cg_transform_custom_c_type_to_g_type. */
void
cg_transform_c_type_to_g_type (const gchar *c_type,
                               gchar **g_type_prefix,
                               gchar **g_type_name)
{
	const gchar *default_prefix;
	const gchar *default_name;
	gchar *plain_c_type;
	gboolean result;
	size_t len;
	
	result = cg_transform_default_c_type_to_g_type (c_type, &default_prefix,
	                                                &default_name);

	if (result == TRUE)
	{
		*g_type_prefix = g_strdup (default_prefix);
		*g_type_name = g_strdup (default_name);
	}
	else
	{
		if (strncmp (c_type, "const ", 6) == 0)
			plain_c_type = g_strdup (c_type + 6);
		else
			plain_c_type = g_strdup (c_type);

		len = strlen (plain_c_type);
		if (plain_c_type[len - 1] == '*')
		{
			plain_c_type[len - 1] = '\0';
			g_strchomp (plain_c_type);
		}

		cg_transform_custom_c_type_to_g_type (plain_c_type, g_type_prefix,
		                                      g_type_name, NULL);

		g_free (plain_c_type);
	}
}

/* Looks up the given index in the hash table and removes enclosing quotes,
 * if any. Those are added again by the autogen template. */
void
cg_transform_string (GHashTable *table,
                     const gchar *index)
{
	gchar *str;
	gchar *unescaped;
	size_t len;

	str = g_hash_table_lookup (table, index);

	if (str != NULL)
	{
		len = strlen (str);
		if (len >= 2 && str[0] == '\"' && str[len - 1] == '\"')
		{
			/* Unescape string because it was most likely already escaped
			 * by the user because s/he also added quotes around it. */
			str = g_strndup (str + 1, len - 2);
			unescaped = g_strcompress (str);
			g_free (str);

			g_hash_table_insert (table, (gpointer) index, unescaped);
		}
	}
}

/* Looks up the given index in the hash table which is assumed to be a string
 * with '|'-separated abbrevations of the given flags. This function replaces
 * the abbrevations by the full names. If no flags are set, it produces "0". */
void
cg_transform_flags (GHashTable *table,
                    const gchar *index,
                    const CgElementEditorFlags *flags)
{
	const CgElementEditorFlags *flag;
	GString *res_str;
	gchar *flags_str;
	gchar *prev;
	gchar *pos;

	flags_str = g_hash_table_lookup (table, index);
	res_str = g_string_sized_new (128);

	if (flags_str != NULL)
	{
		prev = flags_str;
		pos = flags_str;
		
		while (*prev != '\0')
		{
			while (*pos != '|' && *pos != '\0')
				++ pos;

			flag = cg_transform_lookup_flag (flags, prev, pos - prev);
			g_assert (flag != NULL);
			
			if (res_str->len > 0) g_string_append (res_str, " | ");
			g_string_append (res_str, flag->name);

			if (*pos != '\0') ++ pos;
			prev = pos;
		}
	}
	
	if (res_str->len == 0) g_string_append_c (res_str, '0');
	g_hash_table_insert (table, (gpointer) index,
	                     g_string_free (res_str, FALSE));
}

/* Looks up the given param_index in the hash table. If it contains
 * guess_entry, the value is replaced by guessing the param spec from
 * the GObject type stored in type_index. */
void
cg_transform_guess_paramspec (GHashTable *table,
                              const gchar *param_index,
                              const gchar *type_index,
                              const gchar *guess_entry)
{
	const CgTransformParamGuess GUESS_TABLE[] =
	{
		{ "G_TYPE_BOOLEAN", "g_param_spec_boolean" },
		{ "G_TYPE_BOXED", "g_param_spec_boxed" },
		{ "G_TYPE_CHAR", "g_param_spec_char" },
		{ "G_TYPE_DOUBLE", "g_param_spec_double" },
		{ "G_TYPE_ENUM", "g_param_spec_enum" },
		{ "G_TYPE_FLAGS", "g_param_spec_flags" },
		{ "G_TYPE_FLOAT", "g_param_spec_float" },
		{ "G_TYPE_INT", "g_param_spec_int" },
		{ "G_TYPE_INT64", "g_param_spec_int64" },
		{ "G_TYPE_LONG", "g_param_spec_long" },
		{ "G_TYPE_OBJECT", "g_param_spec_object" },
		{ "G_TYPE_PARAM", "g_param_spec_param" },
		{ "G_TYPE_POINTER", "g_param_spec_pointer" },
		{ "G_TYPE_STRING", "g_param_spec_string" },
		{ "G_TYPE_UCHAR", "g_param_spec_uchar" },
		{ "G_TYPE_UINT", "g_param_spec_uint" },
		{ "G_TYPE_UINT64", "g_param_spec_uint64" },
		{ "G_TYPE_ULONG", "g_param_spec_ulong" },
		{ "G_TYPE_UNICHAR", "g_param_spec_unichar" },
		{ NULL, NULL }
	};

	const CgTransformParamGuess *guess;
	gchar *paramspec;
	gchar *type;
	
	paramspec = g_hash_table_lookup (table, param_index);

	if (paramspec != NULL && strcmp (paramspec, guess_entry) == 0)
	{
		type = g_hash_table_lookup (table, type_index);
		if (type != NULL)
		{
			for (guess = GUESS_TABLE; guess->gtype != NULL; ++ guess)
			{
				if (strcmp (type, guess->gtype) == 0)
				{
					paramspec = g_strdup (guess->paramspec);
					break;
				}
			}

			/* Not in list, so assume it is an object */
			if (guess->gtype == NULL)
				paramspec = g_strdup ("g_param_spec_object");
		
			g_hash_table_insert (table, (gpointer) param_index, paramspec);
		}
	}
}

/* This function looks up index in the given hash table and encloses it by
 * surrounding parenthesis if they do not already exist and the field is
 * non-empty. If make_void is TRUE and the arguments are only "()" it makes
 * "(void)" out of it to stay ANSI C compliant. */
void
cg_transform_arguments (GHashTable *table,
                        const gchar *index,
                        gboolean make_void)
{
	gchar *arg_res;
	gchar *arguments;
	size_t len;

	arguments = g_hash_table_lookup (table, index);
	
	if (arguments != NULL)
	{
		g_strstrip (arguments);
		len = strlen (arguments);

		/* Do nothing if the field was left empty */
		if (len > 0)
		{
			/* Surround arguments with paranthesis if they do
			 * not already exist. */
			if (arguments[0] != '(' && arguments[len - 1] != ')')
				arg_res = g_strdup_printf ("(%s)", arguments);
			else if (arguments[0] != '(')
				arg_res = g_strdup_printf ("(%s", arguments);
			else if (arguments[len - 1] != ')')
				arg_res = g_strdup_printf ("%s)", arguments);
			else
				arg_res = NULL;

			/* Set arguments to the transformed result, if a transformation
			 * has happend. */
			if (arg_res != NULL)
				arguments = arg_res;

			if (make_void == TRUE)
			{
				/* Make "(void)" out of "()" if make_void is set. If this is
				 * the case, we do not need to store arg_res in the hash
				 * table lateron, so we delete it right here. */
				if (strcmp (arguments, "()") == 0)
				{
					g_hash_table_insert (table, (gpointer) index,
					                     g_strdup ("(void)"));

					g_free (arg_res);
					arg_res = NULL;
				}
			}

			if (arg_res != NULL)
				g_hash_table_insert (table, (gpointer) index, arg_res);
		}
	}
}

/** 
 * Add a self reference to the arguments list, if necessary. Python only.
 */
void
cg_transform_python_arguments (GHashTable *table,
			       const gchar *index)
{
	gchar *arg_res;
	gchar *arguments;
	size_t len;

	arguments = g_hash_table_lookup (table, index);

	arg_res = NULL;
	if (arguments != NULL)
	{
		g_strstrip (arguments);
		len = strlen (arguments);
		/* Do nothing if the field was left empty */
		if (len > 0)
		{
			if (arguments[0] != '(')
			{
				/* Check if self is in arguments. If yes,
				 * cg_transform_arguments will take care of the
				 * rest, if not then add the self argument. */
				if (g_strcmp0 (arguments, "self") != 0)
				{
					g_hash_table_insert (table, (gpointer) index,
							     g_strdup_printf ("(self, %s)", arguments));

					g_free (arg_res);
					arg_res = NULL;
				}
			}
			else
			{
				if (g_strcmp0 (arguments, "()") == 0)
				{
					g_hash_table_insert (table, (gpointer) index,
							     g_strdup ("(self)"));

					g_free (arg_res);
					arg_res = NULL;
				}
			}
		}
		else
		{
			g_hash_table_insert (table, (gpointer) index,
						     g_strdup_printf ("%s", "(self)"));
			g_free (arg_res);
			arg_res = NULL;
		}
	}

	cg_transform_arguments (table, index, FALSE);
}

/* This function makes a valid C identifier out of a string. It does this
 * by ignoring anything but digits, letters, hyphens and underscores. Digits
 * at the beginning of the string are also ignored. Hpyhens are transformed
 * to underscores. */
void
cg_transform_string_to_identifier (GHashTable *table,
                                   const gchar *string_index,
                                   const gchar *identifier_index)
{
	gchar *name;
	gchar *identifier_name;
	size_t name_len;
	size_t i, j;

	name = g_hash_table_lookup (table, "Name");
	if (name != NULL)
	{
		name_len = strlen (name);
		identifier_name = g_malloc ((name_len + 1) * sizeof(gchar));

		for (i = 0, j = 0; i < name_len; ++ i)
		{
			if (isupper (name[i]) || islower (name[i]))
				identifier_name[j ++] = name[i];
			else if (isdigit (name[i]) && j > 0)
				identifier_name[j ++] = name[i];
			else if (isspace (name[i]) || name[i] == '-' || name[i] == '_')
				identifier_name[j ++] = '_';
		}

		identifier_name[j] = '\0';

		g_hash_table_insert (table, (gpointer) identifier_index,
		                     identifier_name);

		/* Ownership is given to hash table, so no g_free here. */
	}
}

/* This function looks up the given index in the hash table and expects an
 * argument list like cg_transform_arguments generates it. It then checks
 * whether the first argument is of the given type. If it is, it does
 * nothing, otherwise it adds the first argument to the argument list
 * and writes the result back into the hash table. */
void
cg_transform_first_argument (GHashTable *table,
                             const gchar *index,
                             const gchar *type)
{
	gchar *arguments;
	guint pointer_count;
	guint arg_pointer_count;
	size_t type_len;
	const gchar *type_pos;
	gchar *arg_pos;
	gchar *pointer_str;
	gboolean arg_present;
	guint i;

	arguments = g_hash_table_lookup (table, index);

	/* Count the length of the basic type */
	type_len = 0;
	while (isalnum (type[type_len])) ++ type_len;
	type_pos = type + type_len;

	/* Count pointer indicators */
	pointer_count = 0;
	for (type_pos = type + type_len; *type_pos != '\0'; ++ type_pos)
		if (*type_pos == '*') ++ pointer_count;

	/* Build a string of all pointer indicators that we can append to the
	 * basic type to get the final type. */
	pointer_str = g_malloc ((pointer_count + 2) * sizeof(gchar));
	pointer_str[0] = ' ';
	pointer_str[pointer_count + 1] = '\0';
	for (i = 0; i < pointer_count; ++ i) pointer_str[i + 1] = '*';
	
	/* We do not just prepend type to the argument string because then
	 * we would have to fiddle around where spaces and pointer indicators
	 * belong. We can now always just build a string like
	 * BasicType+PointerStr+Name to get a fully qualified parameter like
	 * GtkTreeIter *iter, gint the_int or gchar **dest. */

	if (arguments == NULL || *arguments == '\0')
	{
		arguments = g_strdup_printf ("(%.*s%sself)", (int)type_len,
		                             type, pointer_str);

		g_hash_table_insert (table, (gpointer) index, arguments);
	}
	else
	{
		g_assert (arguments[0] == '(');

		/* Step over '(' and any leading whitespace */
		++ arguments;
		while (isspace (*arguments)) ++ arguments;

		arg_present = FALSE;
		if (strncmp (arguments, type, type_len) == 0)
		{
			/* We cannot just check (via string comparison) whether arguments
			 * begins with the whole type because the pointer indicator might
			 * be directly behind the basic type, but there might as well exist
			 * an arbitrary amount of spaces inbetween.
			 * GtkTreeIter* self vs. GtkTreeIter *self vs. 
			 * GtkTreeIter     *self. */
			arg_pointer_count = 0;
			for (arg_pos = arguments + type_len;
			     isspace (*arg_pos) || *arg_pos == '*';
			     ++ arg_pos)
			{
				if (*arg_pos == '*') ++ arg_pointer_count;
			}

			/* Type matches */
			if (arg_pointer_count == pointer_count)
				arg_present = TRUE;
		}

		/* The argument list does not contain the specified type as first
		 * argument, so add it. */
		if (arg_present == FALSE)
		{
			arguments = g_strdup_printf ("(%.*s%sself, %s", (int) type_len,
			                             type, pointer_str, arguments);
			g_hash_table_insert (table, (gpointer) index, arguments);
		}
	}
	
	g_free (pointer_str);
}

/* This function looks up the given arguments_index in the hash table and
 * expects an argument list with at least one argument like
 * cg_transform_first_argument generates it. Then, it makes a new
 * comma-separated list of the corresponding GTypes and writes it to
 * gtypes_index. It returns the amount of arguments. The first
 * argument is not part of the output.
 *
 * Additionally, if the arguments field was left empty, it makes (void) out
 * of it. Normally, the arguments field may be left empty to indicate that
 * the whole thing is a variable rather than a function. However, if someone
 * requires a list of gtypes of the arguments, it is most likely a function
 * and only a function. */
guint
cg_transform_arguments_to_gtypes (GHashTable *table,
                                  const gchar *arguments_index,
                                  const gchar *gtypes_index)
{
	guint arg_count;
	GString *arg_str;
	gchar *arguments;

	gchar *arg_prev;
	gchar *arg_pos;
	gchar *arg_type = NULL;
	
	gchar *argtype_prefix;
	gchar *argtype_suffix;

	arg_count = 0;
	arg_str = g_string_sized_new (128);
	arguments = g_hash_table_lookup (table, arguments_index);
	
	g_assert (arguments != NULL && *arguments != '\0');

	/* Step over '(' */
	arg_prev = arguments + 1;

	/* Ignore first argument */
	while (*arg_prev != ',' && *arg_prev != ')') ++ arg_prev;

	/* Step over ',' */
	if (*arg_prev == ',') ++ arg_prev;

	while (isspace (*arg_prev)) ++ arg_prev;
	arg_pos = arg_prev;

	while (*arg_prev != ')')
	{
		++ arg_count;

		/* Advance to end of this argument. */
		while (*arg_pos != ',' && *arg_pos != ')')
			++ arg_pos;

		/* Try to find argument type by going back the last identifier
		 * which should be the argument name. */
		if (arg_pos > arg_prev)
		{
			arg_type = arg_pos - 1;
			while (isspace (*arg_type)) -- arg_type;
		}

		while ((isalnum (*arg_type) || *arg_type == '_') &&
		      arg_type > arg_prev)
		{
			-- arg_type;
		}

		/* If the name is everything in this arguments this is most
		 * probably the type and the name has been omitted. If a type was
		 * given that ends on a special character (i.e. '*' because it is
		 * a pointer) we also want to get that character. */
		if (arg_type == arg_prev || !isspace (*arg_type))
			arg_type = arg_pos;

		/* Go back any whitespace to find end of type. Note that
		 * *(arg_type - 1) is always valid because arg_prev is at
		 * a character that is not a whitespace and arg_type is
		 * always >= arg_prev. */
		if (arg_type > arg_prev)
			while (isspace(*(arg_type - 1)))
				-- arg_type;

		/* The arguments type should now be enclosed by arg_prev and
		 * arg_type. */
		arg_type = g_strndup (arg_prev, arg_type - arg_prev);
		cg_transform_c_type_to_g_type (arg_type, &argtype_prefix,
		                               &argtype_suffix);
		g_free (arg_type);
		
		if (arg_str->len > 0) g_string_append (arg_str, ", ");
		g_string_append (arg_str, argtype_prefix);
		g_string_append (arg_str, "_TYPE_");
		g_string_append (arg_str, argtype_suffix);
		
		g_free (argtype_prefix);
		g_free (argtype_suffix);

		if (*arg_pos != ')')
		{
			/* Step over comma and following whitespace */
			++ arg_pos;
			while (isspace (*arg_pos)) ++ arg_pos;
		}

		arg_prev = arg_pos;
	}

	g_hash_table_insert (table, (gpointer) gtypes_index,
	                     g_string_free (arg_str, FALSE));

	return arg_count;
}
