/* abstract base class for all vips objects
 *
 * Edited from nip's base class, 15/10/08
 */

/*

    Copyright (C) 1991-2003 The National Gallery

    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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

/*

    These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/*
#define DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>

#include <stdio.h>
#include <string.h>

#include <vips/vips.h>
#include <vips/internal.h>

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/

/* Properties.
 */
enum {
	PROP_NICKNAME = 1,/* Class properties as object props */
	PROP_DESCRIPTION,
	PROP_LAST
};

G_DEFINE_ABSTRACT_TYPE( VipsObject, vips_object, G_TYPE_OBJECT );

int
vips_object_build( VipsObject *object )
{
	VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS( object );

#ifdef DEBUG
	printf( "vips_object_build: " );
	vips_object_print( object );
#endif /*DEBUG*/

	return( object_class->build( object ) );
}

void
vips_object_print_class( VipsObjectClass *class )
{
	VipsBuf buf;
	char str[1000];

	vips_buf_init_static( &buf, str, 1000 );
	class->print_class( class, &buf );
	printf( "%s\n", vips_buf_all( &buf ) );
}

void
vips_object_print( VipsObject *object )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
	VipsBuf buf;
	char str[1000];

	vips_object_print_class( class );
	vips_buf_init_static( &buf, str, 1000 );
	class->print( object, &buf );
	printf( "\n%s (%p)\n", vips_buf_all( &buf ), object );
}

/* Extra stuff we track for properties to do our argument handling.
 */

/* Free a VipsArgumentInstance ... VipsArgumentClass can just be g_free()d.
 */
static void
vips_argument_instance_free( VipsArgumentInstance *argument_instance )
{
	if( argument_instance->destroy_id ) {
		g_signal_handler_disconnect( argument_instance->object,
			argument_instance->destroy_id );
		argument_instance->destroy_id = 0;
	}
	g_free( argument_instance );
}

VipsArgument *
vips__argument_table_lookup( VipsArgumentTable *table, GParamSpec *pspec )
{
	return( g_hash_table_lookup( table, pspec ) );
}

static void
vips_argument_table_replace( VipsArgumentTable *table, VipsArgument *argument )
{
	g_hash_table_replace( table, argument->pspec, argument );
}

static void
vips_argument_table_destroy( VipsArgumentTable *table )
{
	g_hash_table_destroy( table );
}

/* Loop over the vips_arguments to an object.
 */
void *
vips_argument_map( VipsObject *object,
	VipsArgumentMapFn fn, void *a, void *b )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
	GSList *p;

	for( p = class->argument_table_traverse; p; p = p->next ) {
		VipsArgumentClass *argument_class =
			(VipsArgumentClass *) p->data;
		VipsArgument *argument = (VipsArgument *) argument_class;
		GParamSpec *pspec = argument->pspec;
		VipsArgumentInstance *argument_instance =
			vips__argument_get_instance( argument_class, object );

		/* We have many props on the arg table ... filter out the ones
		 * for this class.
		 */
		if( g_object_class_find_property( G_OBJECT_CLASS( class ),
			pspec->name ) == pspec ) {
			void *result;

			if( (result = fn( object, pspec,
				argument_class, argument_instance, a, b )) )
				return( result );
		}
	}

	return( NULL );
}

static void *
vips_argument_init2( VipsObject *object, GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b )
{
	VipsArgument *argument;

#ifdef DEBUG
	printf( "vips_argument_init_sub: adding instance argument for %s\n",
		pspec->name );
#endif /*DEBUG*/

	/* argument_instance should be NULL since we've not set it yet.
	 */
	g_assert( argument_instance == NULL );

	argument_instance = g_new( VipsArgumentInstance, 1 );
	argument = (VipsArgument *) argument_instance;

	argument->pspec = ((VipsArgument *) argument_class)->pspec;
	argument_instance->object = object;
	argument_instance->assigned = FALSE;
	argument_instance->destroy_id = 0;

	vips_argument_table_replace( object->argument_table, argument );

	return( NULL );
}

/* Create a VipsArgumentInstance for each installed argument property. Ideally
 * we'd do this during _init() but g_object_class_find_property() does not seem
 * to work then :-( so we have to delay it until first access. See
 * vips__argument_get_instance().
 */
static void
vips_argument_init( VipsObject *object )
{
	if( !object->argument_table ) {
		object->argument_table = g_hash_table_new_full( g_direct_hash,
			g_direct_equal, NULL,
			(GDestroyNotify) vips_argument_instance_free );

		/* Make a VipsArgumentInstance for each installed argument
		 * property.
		 */
		vips_argument_map( object, vips_argument_init2, NULL, NULL );
	}
}

/* Convenience ... given the VipsArgumentClass, get the VipsArgumentInstance.
 */
VipsArgumentInstance *
vips__argument_get_instance( VipsArgumentClass *argument_class,
	VipsObject *object )
{
	/* Make sure the instance args are built.
	 */
	vips_argument_init( object );

	return( (VipsArgumentInstance *)
		vips__argument_table_lookup( object->argument_table,
			((VipsArgument *) argument_class)->pspec ) );
}

static void
vips_object_clear_object( VipsObject *object, GParamSpec *pspec )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
	VipsArgumentClass *argument_class = (VipsArgumentClass *)
		vips__argument_table_lookup( class->argument_table, pspec );
	VipsArgumentInstance *argument_instance =
		vips__argument_get_instance( argument_class, object );
	GObject **member = &G_STRUCT_MEMBER( GObject *, object,
		argument_class->offset );

	if( *member ) {
		if( argument_class->flags & VIPS_ARGUMENT_INPUT ) {
#ifdef DEBUG_REF
			printf( "vips_object_clear_object: vips object: " );
			vips_object_print( object );
			printf( "  no longer refers to gobject %s (%p)\n",
				G_OBJECT_TYPE_NAME( *member ), *member );
			printf( "  count down to %d\n",
				G_OBJECT( *member )->ref_count - 1 );
#endif /*DEBUG_REF*/

			/* We reffed the object.
			 */
			g_object_unref( *member );
		}
		else if( argument_class->flags & VIPS_ARGUMENT_OUTPUT ) {
#ifdef DEBUG_REF
			printf( "vips_object_clear_object: gobject %s (%p)\n",
				G_OBJECT_TYPE_NAME( *member ), *member );
			printf( "  no longer refers to vips object: " );
			vips_object_print( object );
			printf( "  count down to %d\n",
				G_OBJECT( object )->ref_count - 1 );
#endif /*DEBUG_REF*/

			/* The object reffed us. Stop listening link to the
			 * object's "destroy" signal. We can come here from
			 * object being destroyed, in which case the handler
			 * will already have been disconnected for us.
			 */
			if( g_signal_handler_is_connected( object,
				argument_instance->destroy_id ) )
				g_signal_handler_disconnect( object,
					argument_instance->destroy_id );
			argument_instance->destroy_id = 0;
			*member = NULL;

			g_object_unref( object );
		}

		*member = NULL;
	}
}

/* Free any args which are holding resources.
 */
static void *
vips_object_dispose_argument( VipsObject *object, GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b )
{
#ifdef DEBUG
	printf( "vips_object_dispose_argument: " );
	vips_object_print( object );
	printf( ".%s\n", pspec->name );
#endif /*DEBUG*/

	g_assert( ((VipsArgument *) argument_class)->pspec == pspec );
	g_assert( ((VipsArgument *) argument_instance)->pspec == pspec );

	if( G_IS_PARAM_SPEC_STRING( pspec ) ) {
		char **member = &G_STRUCT_MEMBER( char *, object,
			argument_class->offset );

		IM_FREE( *member );
	}
	else if( G_IS_PARAM_SPEC_OBJECT( pspec ) )
		vips_object_clear_object( object, pspec );
	else if( G_IS_PARAM_SPEC_BOXED( pspec ) ) {
		gpointer *member = &G_STRUCT_MEMBER( gpointer, object,
			argument_class->offset );

		if( *member ) {
			g_boxed_free( G_PARAM_SPEC_VALUE_TYPE( pspec ),
				*member );
			*member = NULL;
		}
	}

	return( NULL );
}

static void
vips_object_dispose( GObject *gobject )
{
	VipsObject *object = VIPS_OBJECT( gobject );

#ifdef DEBUG
	printf( "vips_object_dispose: " );
	vips_object_print( object );
#endif /*DEBUG*/

	/* Clear all our arguments: they may be holding refs we should drop.
	 */
	vips_argument_map( object, vips_object_dispose_argument, NULL, NULL );

	G_OBJECT_CLASS( vips_object_parent_class )->dispose( gobject );
}

static void
vips_object_finalize( GObject *gobject )
{
	VipsObject *object = VIPS_OBJECT( gobject );

#ifdef DEBUG
	printf( "vips_object_finalize: " );
	vips_object_print( object );
#endif /*DEBUG*/

	IM_FREEF( vips_argument_table_destroy, object->argument_table );

	G_OBJECT_CLASS( vips_object_parent_class )->finalize( gobject );
}

static void
vips_object_arg_destroy( GObject *argument,
	VipsArgumentInstance *argument_instance )
{
	VipsObject *object = argument_instance->object;
	GParamSpec *pspec = ((VipsArgument *) argument_instance)->pspec;

	/* Argument had reffed us ... now it's being destroyed, so we unref.
	 */
	vips_object_clear_object( object, pspec );
}

static void
vips_object_set_object( VipsObject *object, GParamSpec *pspec,
	GObject *argument )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
	VipsArgumentClass *argument_class = (VipsArgumentClass *)
		vips__argument_table_lookup( class->argument_table, pspec );
	VipsArgumentInstance *argument_instance =
		vips__argument_get_instance( argument_class, object );
	GObject **member = &G_STRUCT_MEMBER( GObject *, object,
		argument_class->offset );

	g_assert( !*member );

	*member = argument;

	if( *member ) {
		if( argument_class->flags & VIPS_ARGUMENT_INPUT ) {
#ifdef DEBUG_REF
			printf( "vips_object_set_object: vips object: " );
			vips_object_print( object );
			printf( "  refers to gobject %s (%p)\n",
				G_OBJECT_TYPE_NAME( *member ), *member );
			printf( "  count up to %d\n",
				G_OBJECT( *member )->ref_count );
#endif /*DEBUG_REF*/

			/* Ref the argument.
			 */
			g_object_ref( *member );
		}
		else if( argument_class->flags & VIPS_ARGUMENT_OUTPUT ) {
#ifdef DEBUG_REF
			printf( "vips_object_set_object: gobject %s (%p)\n",
				G_OBJECT_TYPE_NAME( *member ), *member );
			printf( "  refers to vips object: " );
			vips_object_print( object );
			printf( "  count up to %d\n",
				G_OBJECT (object)->ref_count );
#endif /*DEBUG_REF*/

			/* The argument reffs us.
			 */
			g_object_ref( object );
			argument_instance->destroy_id =
				g_signal_connect( *member, "destroy",
					G_CALLBACK( vips_object_arg_destroy ),
					argument_instance );
		}
	}
}

/* Also used by subclasses, so not static.
 */
void
vips_object_set_property( GObject *gobject,
	guint property_id, const GValue *value, GParamSpec *pspec )
{
	VipsObject *object = VIPS_OBJECT( gobject );
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gobject );
	VipsArgumentClass *argument_class = (VipsArgumentClass *)
		vips__argument_table_lookup( class->argument_table, pspec );
	VipsArgumentInstance *argument_instance =
		vips__argument_get_instance( argument_class, object );

	if( !argument_class ) {
		G_OBJECT_WARN_INVALID_PROPERTY_ID( gobject,
			property_id, pspec );
		return;
	}

#ifdef DEBUG
{
	char *str_value;

	str_value = g_strdup_value_contents( value );
	printf( "vips_object_set_property: " );
	vips_object_print( object );
	printf( ".%s = %s\n", g_param_spec_get_name( pspec ), str_value );
	g_free( str_value );
}
#endif /*DEBUG*/

	g_assert( ((VipsArgument *) argument_class)->pspec == pspec );
	g_assert( ((VipsArgument *) argument_instance)->pspec == pspec );

	/* If this is a construct-only argument, we can only set before we've
	 * built.
	 */
	if( argument_class->flags & VIPS_ARGUMENT_CONSTRUCT &&
		object->constructed ) {
		g_warning( "%s: %s can't assign '%s' after construct",
			G_STRLOC,
			G_OBJECT_TYPE_NAME( gobject ),
			g_param_spec_get_name( pspec ) );
		return;
	}

	/* If this is a set-once argument, check we've not set it before.
	 */
	if( argument_class->flags & VIPS_ARGUMENT_SET_ONCE &&
		argument_instance->assigned ) {
		g_warning( "%s: %s can only assign '%s' once",
			G_STRLOC,
			G_OBJECT_TYPE_NAME( gobject ),
			g_param_spec_get_name( pspec ) );
		return;
	}

	if( G_IS_PARAM_SPEC_STRING( pspec ) ) {
		char **member = &G_STRUCT_MEMBER( char *, object,
			argument_class->offset );

		IM_SETSTR( *member, g_value_get_string( value ) );
	}
	else if( G_IS_PARAM_SPEC_OBJECT( pspec ) ) {
		/* Remove any old object.
		 */
		vips_object_clear_object( object, pspec );

		/* Install the new object.
		 */
		vips_object_set_object( object, pspec,
			g_value_get_object( value ) );
	}
	else if( G_IS_PARAM_SPEC_INT( pspec ) ) {
		int *member = &G_STRUCT_MEMBER( int, object,
			argument_class->offset );

		*member = g_value_get_int( value );
	}
	else if( G_IS_PARAM_SPEC_BOOLEAN( pspec ) ) {
		gboolean *member = &G_STRUCT_MEMBER( gboolean, object,
			argument_class->offset );

		*member = g_value_get_boolean( value );
	}
	else if( G_IS_PARAM_SPEC_ENUM( pspec ) ) {
		int *member = &G_STRUCT_MEMBER( int, object,
			argument_class->offset );

		*member = g_value_get_enum( value );
	}
	else if( G_IS_PARAM_SPEC_POINTER( pspec ) ) {
		gpointer *member = &G_STRUCT_MEMBER( gpointer, object,
			argument_class->offset );

		*member = g_value_get_pointer( value );
	}
	else if( G_IS_PARAM_SPEC_DOUBLE( pspec ) ) {
		double *member = &G_STRUCT_MEMBER( double, object,
			argument_class->offset );

		*member = g_value_get_double( value );
	}
	else if( G_IS_PARAM_SPEC_BOXED( pspec ) ) {
		gpointer *member = &G_STRUCT_MEMBER( gpointer, object,
			argument_class->offset );

		if( *member ) {
			g_boxed_free( G_PARAM_SPEC_VALUE_TYPE( pspec ),
				*member );
			*member = NULL;
		}

		/* Copy the boxed into our pointer (will use eg.
		 * vips__object_vector_dup ()).
		 */
		*member = g_value_dup_boxed( value );
	}
	else {
		g_warning( "%s: %s unimplemented property type %s",
			G_STRLOC,
			G_OBJECT_TYPE_NAME( gobject ),
			g_type_name( G_PARAM_SPEC_VALUE_TYPE( pspec ) ) );
	}

	/* Note that it's now been set.
	 */
	argument_instance->assigned = TRUE;
}

/* Also used by subclasses, so not static.
 */
void
vips_object_get_property( GObject *gobject,
	guint property_id, GValue *value, GParamSpec *pspec )
{
	VipsObject *object = VIPS_OBJECT( gobject );
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gobject );
	VipsArgumentClass *argument_class = (VipsArgumentClass *)
		vips__argument_table_lookup( class->argument_table, pspec );

	if( !argument_class ) {
		G_OBJECT_WARN_INVALID_PROPERTY_ID( gobject,
			property_id, pspec );
		return;
	}

	g_assert( ((VipsArgument *) argument_class)->pspec == pspec );

	if( G_IS_PARAM_SPEC_STRING( pspec ) ) {
		char *member = G_STRUCT_MEMBER( char *, object,
			argument_class->offset );

		g_value_set_string( value, member );
	}
	else if( G_IS_PARAM_SPEC_OBJECT( pspec ) ) {
		GObject **member = &G_STRUCT_MEMBER( GObject *, object,
			argument_class->offset );

		g_value_set_object( value, *member );
	}
	else if( G_IS_PARAM_SPEC_INT( pspec ) ) {
		int *member = &G_STRUCT_MEMBER( int, object,
			argument_class->offset );

		g_value_set_int( value, *member );
	}
	else if( G_IS_PARAM_SPEC_BOOLEAN( pspec ) ) {
		gboolean *member = &G_STRUCT_MEMBER( gboolean, object,
			argument_class->offset );

		g_value_set_boolean( value, *member );
	}
	else if( G_IS_PARAM_SPEC_ENUM( pspec ) ) {
		int *member = &G_STRUCT_MEMBER( int, object,
			argument_class->offset );

		g_value_set_enum( value, *member );
	}
	else if( G_IS_PARAM_SPEC_POINTER( pspec ) ) {
		gpointer *member = &G_STRUCT_MEMBER( gpointer, object,
			argument_class->offset );

		g_value_set_pointer( value, *member );
	}
	else if( G_IS_PARAM_SPEC_DOUBLE( pspec ) ) {
		double *member = &G_STRUCT_MEMBER( double, object,
			argument_class->offset );

		g_value_set_double( value, *member );
	}
	else if( G_IS_PARAM_SPEC_BOXED( pspec ) ) {
		gpointer *member = &G_STRUCT_MEMBER( gpointer, object,
			argument_class->offset );

		/* Copy the boxed into our pointer (will use eg.
		 * vips__object_vector_dup ()).
		 */
		g_value_set_boxed( value, *member );
	}
	else {
		g_warning( "%s: %s unimplemented property type %s",
			G_STRLOC,
			G_OBJECT_TYPE_NAME( gobject ),
			g_type_name( G_PARAM_SPEC_VALUE_TYPE( pspec ) ) );
	}
}

static void *
vips_object_check_required( VipsObject *object, GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b )
{
	int *result = (int *) a;

	if( (argument_class->flags & VIPS_ARGUMENT_REQUIRED) &&
		(argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) &&
		!argument_instance->assigned ) {
		im_error( "check_required",
			_( "required construct param %s to %s not set" ),
			g_param_spec_get_name( pspec ),
			G_OBJECT_TYPE_NAME( object ) );
		*result = -1;
	}

	return( NULL );
}

static int
vips_object_real_build( VipsObject *object )
{
	VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS( object );

	int result;

	g_assert( !object->constructed );

	/* Check all required arguments have been supplied, don't stop on 1st
	 * error.
	 */
	result = 0;
	(void) vips_argument_map( object,
		vips_object_check_required, &result, NULL );

	/* ... more checks go here.
	 */
	object->constructed = TRUE;

	/* It'd be nice if this just copied a pointer rather than did a
	 * strdup(). Set these here rather than in object_init, so that the
	 * class gets a chance to set them.
	 */
	g_object_set( object,
		"nickname", object_class->nickname,
		"description", object_class->description, NULL );

	return( result );
}

static void
vips_object_real_print_class( VipsObjectClass *class, VipsBuf *buf )
{
	vips_buf_appendf( buf, "%s", G_OBJECT_CLASS_NAME( class ) );
	if( class->nickname )
		vips_buf_appendf( buf, " (%s)", class->nickname );
	if( class->description )
		vips_buf_appendf( buf, ", %s", class->description );
}

static void
vips_object_real_print( VipsObject *object, VipsBuf *buf )
{
	vips_buf_appendf( buf, " (%p)", object );
}

static void
transform_string_double( const GValue *src_value, GValue *dest_value )
{
	g_value_set_double( dest_value,
		g_ascii_strtod( g_value_get_string( src_value ), NULL ) );
}

static void
vips_object_class_init( VipsObjectClass *object_class )
{
	GObjectClass *gobject_class = G_OBJECT_CLASS( object_class );

	GParamSpec *pspec;

	gobject_class->dispose = vips_object_dispose;
	gobject_class->finalize = vips_object_finalize;
	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->build = vips_object_real_build;
	object_class->print_class = vips_object_real_print_class;
	object_class->print = vips_object_real_print;
	object_class->nickname = "object";
	object_class->description = _( "VIPS base class" );

	/* Table of VipsArgumentClass ... we can just g_free() them.
	 */
	object_class->argument_table = g_hash_table_new_full(
		g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_free );
	object_class->argument_table_traverse = NULL;

	/* For setting double arguments from the command-line.
	 */
	g_value_register_transform_func( G_TYPE_STRING, G_TYPE_DOUBLE,
		transform_string_double );

	/* Create properties.
	 */
	pspec = g_param_spec_string( "nickname",
		_( "Nickname" ),
		_( "Class nickname" ),
		"",
		(GParamFlags) G_PARAM_READWRITE );
	g_object_class_install_property( gobject_class,
		PROP_NICKNAME, pspec );
	vips_object_class_install_argument( object_class, pspec,
		VIPS_ARGUMENT_SET_ONCE,
		G_STRUCT_OFFSET( VipsObject, nickname ) );

	pspec = g_param_spec_string( "description",
		_( "Description" ),
		_( "Class description" ),
		"",
		(GParamFlags) G_PARAM_READWRITE );
	g_object_class_install_property( gobject_class,
		PROP_DESCRIPTION, pspec );
	vips_object_class_install_argument( object_class, pspec,
		VIPS_ARGUMENT_SET_ONCE,
		G_STRUCT_OFFSET( VipsObject, description ) );

}

static void
vips_object_init( VipsObject *object )
{
#ifdef DEBUG
	printf( "vips_object_init: " );
	vips_object_print( object );
#endif /*DEBUG*/
}

/* Add a vipsargument ... automate some stuff with this.
 */
void
vips_object_class_install_argument( VipsObjectClass *object_class,
	GParamSpec *pspec, VipsArgumentFlags flags, guint offset )
{
	VipsArgumentClass *argument_class = g_new( VipsArgumentClass, 1 );

#ifdef DEBUG
	printf( "vips_object_class_install_argument: %s\n", pspec->name );
#endif /*DEBUG*/

	/* Must be a new one.
	 */
	g_assert( !vips__argument_table_lookup( object_class->argument_table,
		pspec ) );

	/* Mustn't have INPUT and OUTPUT both set.
	 */
	g_assert( !(
		(flags & VIPS_ARGUMENT_INPUT) &&
		(flags & VIPS_ARGUMENT_OUTPUT)) );

	((VipsArgument *) argument_class)->pspec = pspec;
	argument_class->object_class = object_class;
	argument_class->flags = flags;
	argument_class->offset = offset;

	vips_argument_table_replace( object_class->argument_table,
		(VipsArgument *) argument_class );
	object_class->argument_table_traverse = g_slist_append(
		object_class->argument_table_traverse, argument_class );
}

/* Set a named arg from a string.
 */
static int
vips_object_set_arg( VipsObject *object, const char *name, const char *value )
{
	GValue gvalue = { 0 };

	g_value_init( &gvalue, G_TYPE_STRING );
	g_value_set_string( &gvalue, value );
	g_object_set_property( G_OBJECT( object ), name, &gvalue );
	g_value_unset( &gvalue );

	return( 0 );
}

static void *
vips_object_set_required_test( VipsObject *object,
	GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b )
{
	if( (argument_class->flags & VIPS_ARGUMENT_REQUIRED) &&
		(argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) &&
		!argument_instance->assigned )
		return( pspec );

	return( NULL );
}

/* Set the first unassigned required arg to the string.
 */
static int
vips_object_set_required( VipsObject *object, const char *value )
{
	GParamSpec *pspec;

	if( !(pspec = vips_argument_map( object,
		vips_object_set_required_test, NULL, NULL )) ) {
		im_error( "vips_object_set_required",
			_( "no unset required arguments for %s" ), value );
		return( -1 );
	}

	if( vips_object_set_arg( object, pspec->name, value ) )
		return( -1 );

	return( 0 );
}

/* Set object args from a string. We've seen the '(', we need to check for the
 * closing ')' and make sure there's no extra stuff.
 */
static int
vips_object_set_args( VipsObject *object, const char *p )
{
	VipsToken token;
	char string[PATH_MAX];
	char string2[PATH_MAX];

	do {
		if( !(p = vips__token_need( p, VIPS_TOKEN_STRING,
			string, PATH_MAX )) )
			return( -1 );

		/* We have to look for a '=', ')' or a ',' to see if string is
		 * a param name or a value.
		 */
		if( !(p = vips__token_must( p, &token, string2, PATH_MAX )) )
			return( -1 );
		if( token == VIPS_TOKEN_EQUALS ) {
			if( !(p = vips__token_need( p, VIPS_TOKEN_STRING,
				string2, PATH_MAX )) )
				return( -1 );
			if( vips_object_set_arg( object, string, string2 ) )
				return( -1 );
			if( !(p = vips__token_must( p, &token,
				string2, PATH_MAX )) )
				return( -1 );
		}
		else {
			if( vips_object_set_required( object, string ) )
				return( -1 );
		}

		/* Now must be a , or a ).
		 */
		if( token != VIPS_TOKEN_RIGHT && token != VIPS_TOKEN_COMMA ) {
			im_error( "set_args", "%s",
				_( "not , or ) after parameter" ) );
			return( -1 );
		}
	} while( token != VIPS_TOKEN_RIGHT );

	if( (p = vips__token_get( p, &token, string, PATH_MAX )) ) {
		im_error( "set_args", "%s",
			_( "extra tokens after ')'" ) );
		return( -1 );
	}

	return( 0 );
}

VipsObject *
vips_object_new( GType type, VipsObjectSetArguments set, void *a, void *b )
{
	VipsObject *object;

	object = VIPS_OBJECT( g_object_new( type, NULL ) );

	if( set && set( object, a, b ) ) {
		g_object_unref( object );
		return( NULL );
	}

	if( vips_object_build( object ) ) {
		g_object_unref( object );
		return( NULL );
	}

	return( object );
}

static void *
vips_object_new_from_string_set( VipsObject *object, void *a, void *b )
{
	const char *p = (const char *) a;
	VipsToken token;
	char string[PATH_MAX];

	if( (p = vips__token_get( p, &token, string, PATH_MAX )) ) {
		if( token == VIPS_TOKEN_LEFT &&
			vips_object_set_args( object, p ) ) {
			im_error( "object_new", "%s",
				_( "bad object arguments" ) );
			return( object );
		}
	}

	return( NULL );
}

VipsObject *
vips_object_new_from_string( const char *basename, const char *p )
{
	char string[PATH_MAX];
	GType type;

	if( !(p = vips__token_need( p, VIPS_TOKEN_STRING, string, PATH_MAX )) ||
		!(type = vips_type_find( basename, string )) )
		return( NULL );

	return( vips_object_new( type,
		vips_object_new_from_string_set, (void *) p, NULL ) );
}

static void
vips_object_print_arg( VipsObject *object, GParamSpec *pspec, VipsBuf *buf )
{
	GType type = G_PARAM_SPEC_VALUE_TYPE( pspec );
	const char *name = g_param_spec_get_name( pspec );
	GValue value = { 0 };
	char *str_value;

	g_value_init( &value, type );
	g_object_get_property( G_OBJECT( object ), name, &value );
	str_value = g_strdup_value_contents( &value );
	vips_buf_appends( buf, str_value );
	g_free( str_value );
	g_value_unset( &value );

}

static void *
vips_object_to_string_required( VipsObject *object,
	GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b )
{
	VipsBuf *buf = (VipsBuf *) a;
	gboolean *first = (gboolean *) b;

	if( (argument_class->flags & VIPS_ARGUMENT_REQUIRED) ) {
		if( *first ) {
			vips_buf_appends( buf, "(" );
			*first = FALSE;
		}
		else {
			vips_buf_appends( buf, "," );
		}

		vips_object_print_arg( object, pspec, buf );
	}

	return( NULL );
}

static void *
vips_object_to_string_optional( VipsObject *object,
	GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b )
{
	VipsBuf *buf = (VipsBuf *) a;
	gboolean *first = (gboolean *) b;

	if( !(argument_class->flags & VIPS_ARGUMENT_REQUIRED) &&
		argument_instance->assigned ) {
		if( *first ) {
			vips_buf_appends( buf, "(" );
			*first = FALSE;
		}
		else {
			vips_buf_appends( buf, "," );
		}

		vips_buf_appends( buf, g_param_spec_get_name( pspec ) );
		vips_buf_appends( buf, "=" );
		vips_object_print_arg( object, pspec, buf );
	}

	return( NULL );
}

/* The inverse of vips_object_new_from_string(): turn an object into eg.
 * "VipsInterpolateSnohalo1(blur=.333333)".
 */
void
vips_object_to_string( VipsObject *object, VipsBuf *buf )
{
	gboolean first;

	/* Nicknames are not guaranteed to be unique, so use the full type
	 * name.
	 */
	vips_buf_appends( buf, G_OBJECT_TYPE_NAME( object ) );
	first = TRUE;
	(void) vips_argument_map( object,
		vips_object_to_string_required, buf, &first );
	(void) vips_argument_map( object,
		vips_object_to_string_optional, buf, &first );
	if( !first )
		vips_buf_appends( buf, ")" );
}
