/*
 * Copyright (c) 2003 by Emmanuele Bassi (see the file AUTHORS)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the 
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 
 * Boston, MA  02111-1307  USA.
 */

#include "gconfperl.h"

/*
 * GConfValue is a dynamic type container used by GConf, in many ways similar
 * to GValue.  It should be accessed only via methods, so GConf doesn't export
 * it as a registered type.  Hence, I decided to translate it into a Perl data
 * structure completely transparent to the programmer; GConfValues are used
 * only internally: what a programmer will always see or use will be an hashref
 * containing the "type" field, which is used to store the symbolic type of the
 * key which GConfValue is bound to, and the payload, that is the value bound
 * to the key.  Fundamental types will have the "value" field filled with the
 * corresponding perl scalar; lists of fundamental types will have the "value"
 * field filled with an arrayref of scalars; pairs of fundamental types will
 * have two fields, "car" (for the first value of the pair) and "cdr" (for the
 * second value of the pair), each one containing an hashref corresponding to a
 * GConfValue of their fundamental types. This may seems a little obfuscated,
 * but, since we're not providing any accessor methods to gather the data
 * inside GConfValue, I've decided to keep the semantics as much as similar to
 * the corresponding C one. (ebassi)
 * 
 */

/* gconfperl_sv_from_value returns the correct SV for the fundamental types
 * stored inside a GConfValue.  gconfperl_value_from_sv is used the other way
 * around, to fill an already initialized GConfValue from a SV.
 */
static SV *
gconfperl_sv_from_value (GConfValue * v)
{
	SV * sv;
	
	switch (v->type) {
		case GCONF_VALUE_BOOL:
			sv = newSViv (gconf_value_get_bool (v));
                        break;
		case GCONF_VALUE_FLOAT:
			sv = newSVnv (gconf_value_get_float (v));
                        break;
		case GCONF_VALUE_INT:
			sv = newSViv (gconf_value_get_int (v));
                        break;
		case GCONF_VALUE_STRING:
			sv = newSVGChar (gconf_value_get_string (v));
                        break;
		case GCONF_VALUE_SCHEMA:
                        sv = newSVGConfSchema (gconf_value_get_schema (v));
                        break;
		case GCONF_VALUE_INVALID:
		default:
			sv = NULL;
                        break;
	}
	
	return sv;
}

static void
gconfperl_value_from_sv (SV * sv, GConfValue * v)
{
	switch (v->type) {
		case GCONF_VALUE_BOOL:
			gconf_value_set_bool (v, SvIV (sv));
                        break;
		case GCONF_VALUE_FLOAT:
			gconf_value_set_float (v, SvNV (sv));
                        break;
		case GCONF_VALUE_INT:
			gconf_value_set_int (v, SvIV (sv));
                        break;
		case GCONF_VALUE_STRING:
			gconf_value_set_string (v, SvGChar (sv));
                        break;
                case GCONF_VALUE_SCHEMA:
                        gconf_value_set_schema (v, SvGConfSchema (sv));
                        break;
		case GCONF_VALUE_INVALID:
		default:
			break;
	}
}

/*
 * Create a SV from a GConfValue.
 * The hash has this form:
 * 
 * fundamentals = { 'int' | 'bool' | 'float' | 'string' | 'schema' }
 * 
 * iff <type> := <fundamentals>
 * 	{ type => <type>, value => { <scalar> | <arrayref> } }
 * 
 * iff <type> := 'pair'
 * 	{
 * 		type => 'pair',
 * 		car  => { type => <fundamentals>, value => <scalar> },
 * 		cdr  => { type => <fundamentals>, value => <scalar> }
 * 	}
 * 
 * a schema is a fundamental type because we have a type for it, like
 * we do have types for integer, boolean, floating and string values.
 */ 
SV *
newSVGConfValue (GConfValue * v)
{
	HV * h;
	SV * sv;
	HV * stash;
	
	if (! v)
		return newSVsv(&PL_sv_undef);

	h = newHV ();
	sv = newRV_noinc ((SV *) h);	/* safe */
	
	switch (v->type) {
		case GCONF_VALUE_STRING:
		case GCONF_VALUE_INT:
		case GCONF_VALUE_FLOAT:
		case GCONF_VALUE_BOOL:
                case GCONF_VALUE_SCHEMA:
			/* these are fundamental types, so store type and value
			 * directly inside the hashref; for the type, use the
			 * 'stringyfied' version.
			 */
			hv_store (h, "type", 4, gperl_convert_back_enum (GCONF_TYPE_VALUE_TYPE, v->type), 0);
			hv_store (h, "value", 5, gconfperl_sv_from_value (v), 0);
			break;
		case GCONF_VALUE_PAIR:
			/* a pair consists of two fundamental types, stored as
			 * car and cdr (damned LISP lovers).  We do not supply
			 * accessor methods for GConfValue, so we try to
			 * reflect the storage as much as we can; thus, we
			 * create two keys, 'car' and 'cdr', instead of the
			 * usual 'value' one.  The programmer must be warned,
			 * so we leave type as 'pair' (also because car and cdr
			 * may be of two different types, so we need a marker
			 * for this situation).
			 */
			{
			SV * car, * cdr;
			hv_store (h, "type", 4,
				gperl_convert_back_enum (GCONF_TYPE_VALUE_TYPE, v->type), 0);
			
			car = newSVGConfValue (gconf_value_get_car (v));
			cdr = newSVGConfValue (gconf_value_get_cdr (v));
			hv_store (h, "car", 3, newSVsv (car), 0);
			hv_store (h, "cdr", 3, newSVsv (cdr), 0);
			}
			break;
		case GCONF_VALUE_LIST:
			/* lists are handled like arrayrefs; the type is the
			 * list type, in order to mask the special 'list' type
			 * from the programmer.
			 */
			{
			AV * a;
			SV * r;
			GSList * l, * tmp;
			GConfValueType t = gconf_value_get_list_type (v);
			
			a = newAV ();
			r = newRV_noinc ((SV *) a);	/* safe */
			l = gconf_value_get_list (v);
			for (tmp = l; tmp != NULL; tmp = tmp->next)
				av_push (a, gconfperl_sv_from_value ((GConfValue *) tmp->data));
			
			hv_store (h, "type", 4, gperl_convert_back_enum (GCONF_TYPE_VALUE_TYPE, t), 0);
			hv_store (h, "value", 5, newSVsv (r), 0);
			}
			break;
		case GCONF_VALUE_INVALID:
			/* this is used only for error handling */
		default:
			croak ("newSVGConfValue: invalid type found");
                        break;
	}

	stash = gv_stashpv ("Gnome2::GConf::Value", TRUE);
	sv_bless (sv, stash);
	
	return sv;
}

/* Create a GConfValue from a SV. */
GConfValue *
SvGConfValue (SV * data)
{
	HV * h;
	SV ** s;
	GConfValue * v;
	GConfValueType t;
	int n;
		
	if ((!data) || (!SvOK(data)) || (!SvRV(data)) || (SvTYPE(SvRV(data)) != SVt_PVHV))
		croak ("SvGConfValue: value must be an hashref");

	h = (HV *) SvRV (data);
	
	/* retrieve the type */
	if (! ((s = hv_fetch (h, "type", 4, 0)) && SvOK (*s)))
		croak ("SvGConfValue: 'type' key is needed");
	
	/* if it is an integer, just assign it... */
	if (looks_like_number (*s))
		t = SvIV (*s);
	
	/* otherwise, try to convert it from the enum */
	if (!gperl_try_convert_enum (GCONF_TYPE_VALUE_TYPE, *s, &n))
		croak ("SvGConfValue: 'type' should be either a GConfValueType or an integer");
	t = (GConfValueType) n;
	
	/* set GConfValue using the right setter method */
	switch (t) {
		case GCONF_VALUE_STRING:
		case GCONF_VALUE_INT:
		case GCONF_VALUE_FLOAT:
		case GCONF_VALUE_BOOL:
                case GCONF_VALUE_SCHEMA:
			if (! ((s = hv_fetch (h, "value", 5, 0)) && SvOK (*s)))
				croak ("SvGConfValue: fundamental types require a value key");
			
			/* the argument is not a reference, so convert it */
			if (!SvROK (*s)) {
				v = gconf_value_new (t);
				gconfperl_value_from_sv (*s, v);
			}
			else if (SvROK (*s) || SvTYPE (SvRV (*s)) == SVt_PVAV) {
				/* the argument is an array, so fill the list */
				AV * av = (AV*) SvRV (*s);
				GSList * list = NULL;
				int i;
				
				v = gconf_value_new (GCONF_VALUE_LIST);
				gconf_value_set_list_type (v, t);
				
				for (i = av_len (av) ; i >= 0 ; i--) {
					GConfValue * v = gconf_value_new (t);
					gconfperl_value_from_sv (*av_fetch (av, i, FALSE), v);
					list = g_slist_prepend (list, v);
				}
				gconf_value_set_list_nocopy (v, list);
			}
			else
				croak ("SvGConfValue: value must be either a "
				       "scalar or an array reference");
			break;
		case GCONF_VALUE_PAIR:
			{
			GConfValue * car, * cdr;
			
			v = gconf_value_new (GCONF_VALUE_PAIR);
			
			/* build up the first value of the pair */	
			if (! ((s = hv_fetch (h, "car", 3, 0)) && SvOK (*s)))
				croak ("SvGConfValue: 'pair' type requires a 'car' key");
			
			car = SvGConfValue (*s);
			gconf_value_set_car_nocopy (v, car);
			
			/* and then the second value */
			if (! ((s = hv_fetch (h, "cdr", 3, 0)) && SvOK (*s)))
				croak ("SvGConfValue: 'pair' type requires a 'cdr' key");
			
			cdr = SvGConfValue (*s);
			gconf_value_set_cdr_nocopy (v, cdr);
			}
			break;	
		case GCONF_VALUE_LIST:
			/* handled above, this should never be passed */
		case GCONF_VALUE_INVALID:
			/* used for error situations */
		default:
			croak ("SvGConfValue: invalid type found.");
	}
	
	return v;
}

MODULE = Gnome2::GConf::Value	PACKAGE = Gnome2::GConf::Value


=for object Gnome2::GConf::Value Opaque datatype for generic values
=cut

=for position SYNOPSIS

=head1 SYNOPSIS

  $client = Gnome2::GConf::Client->get_default;
  $client->set($config_key,
      {
        type  => 'string',
	value => 'Hello, World',
      });
  print "The Meaning of Life." if ($client->get($another_key)->{value} == 42);

=cut

=for position DESCRIPTION

=head1 DESCRIPTION

C<GConfValue> is a dynamic type similar to C<GValue>,  and represents a value
that can be obtained from or stored in the configuration database; it contains
the value bound to a key, and its type.

In perl, it's an hashref containing these keys:

=over

=item B<type>

The type of the data.  Fundamental types are 'string', 'int', 'float' and
'bool'.  Lists are handled by passing an arrayref as the payload of the C<value>
key:
	
	$client->set($key, { type => 'string', value => 'some string' });
	$client->set($key, { type => 'float',  value => 0.5           });
	$client->set($key, { type => 'bool',   value => FALSE         });
	$client->set($key, { type => 'int',    value => [0..15]       });
	
Pairs are handled by using the special type 'pair', and passing, in place
of the C<value> key, the C<car> and the C<cdr> keys, each containing an hashref
representing a GConfValue:

	$client->set($key, {
			type => 'pair',
			car  => { type => 'string', value => 'some string' },
			cdr  => { type => 'int',    value => 42            },
		});

This is needed since pairs might have different types; lists, instead, are of
the same type.

=item B<value>

The payload, containing the value of type C<type>.  It is used only for
fundamental types (scalars or lists).

=item B<car>, B<cdr>

Special keys, that must be used only when working with the 'pair' type.

=back

=cut

=for see_also

=head1 SEE ALSO

L<Gnome2::GConf>(3pm), L<Gnome2::GConf::Entry>(3pm), L<Gnome2::GConf::Schema>(3pm),
L<Gnome2::GConf::ChangeSet>(3pm).

=cut

##/* we need to provide a real DESTROY function because GConfValue
## * objects are dynamically allocated
## */

void
DESTROY (value)
        SV * value
    CODE:
        gconf_value_free (SvGConfValue (value));        

#if GCONF_CHECK_VERSION (2, 13, 1)

gint
compare (value_a, value_b)
	GConfValue * value_a
	GConfValue * value_b
    CODE:
    	RETVAL = gconf_value_compare (value_a, value_b);
    OUTPUT:
    	RETVAL

#endif /* GCONF_CHECK_VERSION (2, 13, 1) */
	
gchar_own *
to_string (value)
	GConfValue * value
    CODE:
    	RETVAL = gconf_value_to_string (value);
    OUTPUT:
    	RETVAL