/* -*- mode: c; indent-tabs-mode: t; c-basic-offset: 8; -*- */

static void
store_fields (HV *fields, GIBaseInfo *info, GIInfoType info_type)
{
	const gchar *namespace;
	AV *av;
	gint i;

	namespace = g_base_info_get_name (info);
	av = newAV ();

	switch (info_type) {
	    case GI_INFO_TYPE_BOXED:
	    case GI_INFO_TYPE_STRUCT:
	    {
		gint n_fields = g_struct_info_get_n_fields (
		                   (GIStructInfo *) info);
		for (i = 0; i < n_fields; i++) {
			GIFieldInfo *field_info;
			const gchar *field_name;
			field_info = g_struct_info_get_field ((GIStructInfo *) info, i);
			field_name = g_base_info_get_name ((GIBaseInfo *) field_info);
			av_push (av, newSVpv (field_name, PL_na));
			g_base_info_unref ((GIBaseInfo *) field_info);
		}
		break;
	    }

	    case GI_INFO_TYPE_UNION:
	    {
                gint n_fields = g_union_info_get_n_fields ((GIUnionInfo *) info);
                for (i = 0; i < n_fields; i++) {
                        GIFieldInfo *field_info;
                        const gchar *field_name;
                        field_info = g_union_info_get_field ((GIUnionInfo *) info, i);
                        field_name = g_base_info_get_name ((GIBaseInfo *) field_info);
                        av_push (av, newSVpv (field_name, PL_na));
                        g_base_info_unref ((GIBaseInfo *) field_info);
                }
                break;
	    }

	    default:
		ccroak ("store_fields: unsupported info type %d", info_type);
	}

	gperl_hv_take_sv (fields, namespace, strlen (namespace),
	                  newRV_noinc ((SV *) av));
}

static SV *
get_field (GIFieldInfo *field_info, gpointer mem, GITransfer transfer)
{
	GITypeInfo *field_type;
	GIBaseInfo *interface_info;
	GIArgument value;
	SV *sv = NULL;

	field_type = g_field_info_get_type (field_info);
	interface_info = g_type_info_get_interface (field_type);

	/* This case is not handled by g_field_info_set_field. */
	if (!g_type_info_is_pointer (field_type) &&
	    g_type_info_get_tag (field_type) == GI_TYPE_TAG_INTERFACE &&
	    g_base_info_get_type (interface_info) == GI_INFO_TYPE_STRUCT)
	{
		gsize offset;
		offset = g_field_info_get_offset (field_info);
		value.v_pointer = mem + offset;
		sv = arg_to_sv (&value,
		                field_type,
		                GI_TRANSFER_NOTHING,
		                NULL);
	} else if (g_field_info_get_field (field_info, mem, &value)) {
		sv = arg_to_sv (&value,
		                field_type,
		                transfer,
		                NULL);
	} else {
		ccroak ("Could not get field '%s'",
		        g_base_info_get_name (field_info));
	}

	if (interface_info)
		g_base_info_unref (interface_info);
	g_base_info_unref ((GIBaseInfo *) field_type);

	return sv;
}

static void
set_field (GIFieldInfo *field_info, gpointer mem, GITransfer transfer, SV *value)
{
	GITypeInfo *field_type;
	GIBaseInfo *interface_info;
	GIArgument arg;

	field_type = g_field_info_get_type (field_info);
	interface_info = g_type_info_get_interface (field_type);

	/* FIXME: No GIArgInfo and no
	 * GPerlI11nInvocationInfo here.  What if the
	 * struct contains an object pointer, or a
	 * callback field?  And is it OK to always
	 * allow undef? */

	/* This case is not handled by g_field_info_set_field. */
	if (!g_type_info_is_pointer (field_type) &&
	    g_type_info_get_tag (field_type) == GI_TYPE_TAG_INTERFACE &&
	    g_base_info_get_type (interface_info) == GI_INFO_TYPE_STRUCT)
	{
		gsize offset;
		gssize size;
		/* Enforce GI_TRANSFER_NOTHING since we will copy into the
		 * memory that has already been allocated inside 'mem' */
		sv_to_arg (value, &arg, NULL, field_type,
		           GI_TRANSFER_NOTHING, TRUE, NULL);
		offset = g_field_info_get_offset (field_info);
		size = g_struct_info_get_size (interface_info);
		g_memmove (mem + offset, arg.v_pointer, size);
	} else {
		sv_to_arg (value, &arg, NULL, field_type,
		           transfer, TRUE, NULL);
		if (!g_field_info_set_field (field_info, mem, &arg))
			ccroak ("Could not set field '%s'",
			        g_base_info_get_name (field_info));
	}

	if (interface_info)
		g_base_info_unref (interface_info);
	g_base_info_unref (field_type);
}
