/* abstract base class for all vips objects
 *
 * Edited from nip's base class, 15/10/08
 *
 * 29/5/18
 * 	- added vips_argument_get_id()
 */

/*

    Copyright (C) 1991-2003 The National Gallery

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301  USA

 */

/*

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

 */

/*
#define DEBUG
#define VIPS_DEBUG
#define DEBUG_REF
 */

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

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

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

#include <gobject/gvaluecollector.h>

#include "vipsmarshal.h"

/**
 * SECTION: object
 * @short_description: the VIPS base object class
 * @stability: Stable
 * @see_also: <link linkend="VipsOperation">operation</link>
 * @include: vips/vips.h
 *
 * The #VipsObject class and associated types and macros.
 *
 * #VipsObject is the base class for all objects in libvips. It has the
 * following major features:
 *
 * <emphasis>Functional class creation</emphasis> Vips objects have a very 
 * regular lifecycle: initialise, build, use, destroy. They behave rather like
 * function calls and are free of side-effects.
 *
 * <emphasis>Run-time introspection</emphasis> Vips objects can be fully 
 * introspected at run-time. There is no need for separate source-code 
 * analysis.
 *
 * <emphasis>Command-line interface</emphasis> Any vips object can be run from
 * the command-line with the `vips` driver program. 
 *
 * ## The #VipsObject lifecycle
 *
 * #VipsObject s have a strictly defined lifecycle, split broadly as construct
 * and then use. In detail, the stages are:
 *
 * 1. g_object_new(). The #VipsObject is created with g_object_new(). Objects
 * in this state are blank slates and need to have their various parameters
 * set.
 *
 * 2. g_object_set(). You loop over the #VipsArgument that the object has
 * defined with vips_argument_map(). Arguments have a set of flags attached to
 * them for required, optional, input, output, type, and so on. You must set
 * all required arguments. 
 *
 * 3. vips_object_build(). Call this to construct the object and get it ready
 * for use. Building an object happens in four stages, see below.
 *
 * 4. g_object_get(). The object has now been built. You can read out any 
 * computed values.  
 *
 * 5. g_object_unref(). When you are done with an object, you can unref it.
 * See the section on reference counting for an explanation of the convention
 * that #VipsObject uses. When the last ref to an object is released, the
 * object is closed. Objects close in three stages, see below.
 *
 * The stages inside vips_object_build() are:
 *
 * 1. Chain up through the object's @build class methods. At each stage,
 * each class does any initial setup and checking, then chains up to its
 * superclass.
 *
 * 2. The innermost @build method inside #VipsObject itself checks that all 
 * input arguments have been set and then returns. 
 *
 * 3. All object @build methods now finish executing, from innermost to
 * outermost. They know all input arguments have been checked and supplied, so
 * now they set all output arguments. 
 *
 * 4. vips_object_build() finishes the process by checking that all output
 * objects have been set, and then triggering the #VipsObject::postbuild
 * signal. #VipsObject::postbuild only runs if the object has constructed
 * successfuly.
 *
 * #VipsOperation has a cache of recent operation objects, see that class for
 * an explanation of vips_cache_operation_build(). 
 *
 * Finally the stages inside close are:
 *
 * 1. #VipsObject::preclose. This is emitted at the start of
 * the #VipsObject dispose. The object is still functioning. 
 *
 * 2. #VipsObject::close. This runs just after all #VipsArgument held by
 * the object have been released.
 *
 * 3. #VipsObject::postclose. This runs right at the end. The object
 * pointer is still valid, but nothing else is. 
 *
 * ## #VipsArgument
 *
 * libvips has a simple mechanism for automating at least some aspects of
 * %GObject properties. You add a set of macros to your _class_init() which
 * describe the arguments, and set the get and set functions to the vips ones.
 *
 * See <link linkend="extending">extending</link> for a complete example. 
 *
 * ## The #VipsObject reference counting convention
 *
 * #VipsObject has a set of conventions to simplify reference counting.
 *
 * 1. All input %GObject have a ref added to them, owned by the object. When a
 * #VipsObject is unreffed, all of these refs to input objects are
 * automatically dropped.
 *
 * 2. All output %GObject hold a ref to the object. When a %GObject which is an
 * output of a #VipsObject is disposed, it must drop this reference.
 * #VipsObject which are outputs of other #VipsObject will do this
 * automatically. 
 *
 * See #VipsOperation for an example of #VipsObject reference counting. 
 *
 */

/** 
 * VipsArgumentFlags:
 * @VIPS_ARGUMENT_NONE: no flags
 * @VIPS_ARGUMENT_REQUIRED: must be set in the constructor
 * @VIPS_ARGUMENT_CONSTRUCT: can only be set in the constructor
 * @VIPS_ARGUMENT_SET_ONCE: can only be set once
 * @VIPS_ARGUMENT_SET_ALWAYS: don't do use-before-set checks
 * @VIPS_ARGUMENT_INPUT: is an input argument (one we depend on)
 * @VIPS_ARGUMENT_OUTPUT: is an output argument (depends on us)
 * @VIPS_ARGUMENT_DEPRECATED: just there for back-compat, hide 
 * @VIPS_ARGUMENT_MODIFY: the input argument will be modified
 *
 * Flags we associate with each object argument.
 *
 * Have separate input & output flags. Both set is an error; neither set is OK.
 *
 * Input gobjects are automatically reffed, output gobjects automatically ref
 * us. We also automatically watch for "destroy" and unlink.
 *
 * @VIPS_ARGUMENT_SET_ALWAYS is handy for arguments which are set from C. For
 * example, VipsImage::width is a property that gives access to the Xsize
 * member of struct _VipsImage. We default its 'assigned' to TRUE
 * since the field is always set directly by C.
 *
 * @VIPS_ARGUMENT_DEPRECATED arguments are not shown in help text, are not
 * looked for if required, are not checked for "have-been-set". You can
 * deprecate a required argument, but you must obviously add a new required
 * argument if you do.
 *
 * Input args with @VIPS_ARGUMENT_MODIFY will be modified by the operation.
 * This is used for things like the in-place drawing operations. 
 */

/* Our signals. 
 */
enum {
	SIG_POSTBUILD,		
	SIG_PRECLOSE,		
	SIG_CLOSE,		
	SIG_POSTCLOSE,		
	SIG_LAST
};

/* Table of all objects, handy for debugging.
 */
static GHashTable *vips__object_all = NULL;
static GMutex *vips__object_all_lock = NULL;

static guint vips_object_signals[SIG_LAST] = { 0 };

/* This has to be externally visible for compatibility with older libvipses.
 */
int _vips__argument_id = 1;

/* Keep a cache of nickname -> GType lookups.
 */
static GHashTable *vips__object_nickname_table = NULL;

G_DEFINE_ABSTRACT_TYPE( VipsObject, vips_object, G_TYPE_OBJECT );

/**
 * vips_argument_get_id: (skip)
 *
 * Allocate a new property id. See g_object_class_install_property().
 *
 * Returns: a new property id > 0
 */
int 
vips_argument_get_id( void )
{
	int id;

	/* We probably don't need to lock: glib seems to single-thread class
	 * creation.
	 */
	id = _vips__argument_id++;

	return( id );
}

/* Don't call this directly, see vips_object_build().
 */
static int
vips_object_postbuild( VipsObject *object )
{
	int result;

#ifdef DEBUG
	printf( "vips_object_postbuild: " );
	vips_object_print_name( object );
	printf( "\n" );
#endif /*DEBUG*/

	g_signal_emit( object, vips_object_signals[SIG_POSTBUILD], 0, &result );

	return( result ); 
}

void
vips_object_preclose( VipsObject *object )
{
	if( !object->preclose ) {
		object->preclose = TRUE;

#ifdef DEBUG
		printf( "vips_object_preclose: " );
		vips_object_print_name( object );
		printf( "\n" );
#endif /*DEBUG*/

		g_signal_emit( object, vips_object_signals[SIG_PRECLOSE], 0 );
	}
}

static void
vips_object_close( VipsObject *object )
{
	if( !object->close ) {
		object->close = TRUE;

#ifdef DEBUG
		printf( "vips_object_close: " );
		vips_object_print_name( object );
		printf( "\n" );
#endif /*DEBUG*/

		g_signal_emit( object, vips_object_signals[SIG_CLOSE], 0 );
	}
}

static void
vips_object_postclose( VipsObject *object )
{
	if( !object->postclose ) {
		object->postclose = TRUE;

#ifdef DEBUG
		printf( "vips_object_postclose: " );
		vips_object_print_name( object );
		printf( "\n" );
#endif /*DEBUG*/

		g_signal_emit( object, vips_object_signals[SIG_POSTCLOSE], 0 );
	}
}

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

	int *result = (int *) a;
	VipsArgumentFlags *iomask = (VipsArgumentFlags *) b;

	VIPS_DEBUG_MSG( "vips_object_check_required: %s\n", 
		g_param_spec_get_name( pspec ) );
	VIPS_DEBUG_MSG( "\trequired: %d\n", 
		argument_class->flags & VIPS_ARGUMENT_REQUIRED );
	VIPS_DEBUG_MSG( "\tconstruct: %d\n", 
		argument_class->flags & VIPS_ARGUMENT_CONSTRUCT ); 
	VIPS_DEBUG_MSG( "\tinput: %d\n", 
		argument_class->flags & VIPS_ARGUMENT_INPUT ); 
	VIPS_DEBUG_MSG( "\toutput: %d\n", 
		argument_class->flags & VIPS_ARGUMENT_OUTPUT ); 
	VIPS_DEBUG_MSG( "\tassigned: %d\n", 
		argument_instance->assigned );

	if( (argument_class->flags & VIPS_ARGUMENT_REQUIRED) &&
		(argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) &&
		!(argument_class->flags & VIPS_ARGUMENT_DEPRECATED) &&
		(argument_class->flags & *iomask) &&
		!argument_instance->assigned ) {
		vips_error( class->nickname, 
			_( "parameter %s not set" ),
			g_param_spec_get_name( pspec ) );
		*result = -1;
	}

	return( NULL );
}

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

	/* Input and output args must both be set.
	 */
	VipsArgumentFlags iomask = 
		VIPS_ARGUMENT_INPUT | VIPS_ARGUMENT_OUTPUT;

	int result;

#ifdef DEBUG
	printf( "vips_object_build: " );
	vips_object_print_name( object );
	printf( "\n" );
#endif /*DEBUG*/

	if( object_class->build( object ) )
		return( -1 );

	/* 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, &iomask );

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

	/* Only postbuild on success.
	 */
	if( !result )
		result = vips_object_postbuild( object );

	return( result );
}

/**
 * vips_object_summary_class: (skip)
 * @klass: class to summarise
 * @buf: write summary here
 *
 * Generate a human-readable summary for a class. 
 */
void
vips_object_summary_class( VipsObjectClass *klass, VipsBuf *buf )
{
	klass->summary_class( klass, buf );
}

/**
 * vips_object_summary: (skip)
 * @object: object to summarise
 * @buf: write summary here
 *
 * Generate a human-readable summary for an object. 
 */
void
vips_object_summary( VipsObject *object, VipsBuf *buf )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	class->summary( object, buf );
}

/**
 * vips_object_dump: (skip)
 * @object: object to dump
 * @buf: write dump here
 *
 * Dump everything that vips knows about an object to a string.
 */
void
vips_object_dump( VipsObject *object, VipsBuf *buf )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	class->dump( object, buf );
}

void
vips_object_print_summary_class( VipsObjectClass *class )
{
	char str[2048];
	VipsBuf buf = VIPS_BUF_STATIC( str );

	vips_object_summary_class( class, &buf );
	printf( "%s\n", vips_buf_all( &buf ) );
}

void
vips_object_print_summary( VipsObject *object )
{
	char str[2048];
	VipsBuf buf = VIPS_BUF_STATIC( str );

	vips_object_summary( object, &buf );
	printf( "%s\n", vips_buf_all( &buf ) );
}

void
vips_object_print_dump( VipsObject *object )
{
	char str[32768];
	VipsBuf buf = VIPS_BUF_STATIC( str );

	vips_object_dump( object, &buf );
	printf( "%s\n", vips_buf_all( &buf ) );
}

void
vips_object_print_name( VipsObject *object )
{
	printf( "%s (%p)", G_OBJECT_TYPE_NAME( object ), object );
}

gboolean
vips_object_sanity( VipsObject *object )
{
	VipsObjectClass *class;
	char str[1000];
	VipsBuf buf = VIPS_BUF_STATIC( str );

	if( !object ) {
		printf( "vips_object_sanity: null object\n" );

		return( FALSE );
	}

	class = VIPS_OBJECT_GET_CLASS( object );
	class->sanity( object, &buf );
	if( !vips_buf_is_empty( &buf ) ) {
		printf( "sanity failure: " );
		vips_object_print_name( object );
		printf( " %s\n", vips_buf_all( &buf ) );

		return( FALSE );
	}

	return( TRUE );
}

/* On a rewind, we dispose the old contents of the object and
 * reconstruct. This is used in things like im_pincheck() where a "w"
 * image has to be rewound and become a "p" image.
 *
 * Override in subclasses if you want to preserve some fields, see image.c.
 */
void
vips_object_rewind( VipsObject *object )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	class->rewind( object );
}

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

static void
vips_argument_instance_detach( VipsArgumentInstance *argument_instance )
{
	VipsObject *object = argument_instance->object;
	VipsArgumentClass *argument_class = argument_instance->argument_class;

	if( argument_instance->close_id ) {
		/* If close_id is set, the argument must be a gobject of some
		 * sort, so we can fetch it.
		 */
		GObject *member = G_STRUCT_MEMBER( GObject *, object,
			argument_class->offset );

		if( g_signal_handler_is_connected( member,
			argument_instance->close_id ) )
			g_signal_handler_disconnect( member,
				argument_instance->close_id );
		argument_instance->close_id = 0;
	}

	if( argument_instance->invalidate_id ) {
		GObject *member = G_STRUCT_MEMBER( GObject *, object,
			argument_class->offset );

		if( g_signal_handler_is_connected( member,
			argument_instance->invalidate_id ) )
			g_signal_handler_disconnect( member,
				argument_instance->invalidate_id );
		argument_instance->invalidate_id = 0;
	}
}

/* Free a VipsArgumentInstance ... VipsArgumentClass can just be g_free()d.
 */
static void
vips_argument_instance_free( VipsArgumentInstance *argument_instance )
{
	vips_argument_instance_detach( argument_instance );
	g_free( argument_instance );
}

VipsArgument *
vips__argument_table_lookup( VipsArgumentTable *table, GParamSpec *pspec )
{
	VipsArgument *argument;

	g_mutex_lock( vips__global_lock );
	argument = (VipsArgument *) g_hash_table_lookup( table, pspec );
	g_mutex_unlock( vips__global_lock );

	return( argument );
}

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 );
}

/**
 * vips_argument_map: (skip)
 * @object: object whose args should be enumerated
 * @fn: call this function for every argument
 * @a: client data
 * @b: client data
 *
 * Loop over the vips_arguments to an object. Stop when @fn returns non-%NULL
 * and return that value. 
 *
 * Returns: %NULL if @fn returns %NULL for all arguments, otherwise the first
 * non-%NULL value from @fn.
 */
void *
vips_argument_map( VipsObject *object,
	VipsArgumentMapFn fn, void *a, void *b )
{
	/* Make sure we can't go during the loop. This can happen if eg. we
	 * flush an arg that refs us.
	 */
	g_object_ref( object ); 

	VIPS_ARGUMENT_FOR_ALL( object, 
		pspec, argument_class, argument_instance ) { 
		void *result;

		/* argument_instance should not be NULL.
		 */
		g_assert( argument_instance );

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

	g_object_unref( object ); 

	return( NULL );
}

/**
 * vips_argument_class_map: (skip)
 *
 * And loop over a class. Same as ^^, but with no VipsArgumentInstance.
 */
void *
vips_argument_class_map( VipsObjectClass *object_class,
	VipsArgumentClassMapFn fn, void *a, void *b )
{
	GSList *p; 
 
	for( p = object_class->argument_table_traverse; p; p = p->next ) { 
		VipsArgumentClass *arg_class = 
			(VipsArgumentClass *) p->data; 
		VipsArgument *argument = (VipsArgument *) arg_class; 
		GParamSpec *pspec = argument->pspec; 

		void *result;

		if( (result = 
			fn( object_class, pspec, arg_class, a, b )) )
			return( result );
	}

	return( NULL );
}

/* Does an vipsargument need an argument to write to? For example, an image
 * output needs a filename, a double output just prints.
 */
gboolean
vips_argument_class_needsstring( VipsArgumentClass *argument_class )
{
	GParamSpec *pspec = ((VipsArgument *) argument_class)->pspec;

	GType otype;
	VipsObjectClass *oclass;

	if( G_IS_PARAM_SPEC_BOOLEAN( pspec ) ) 
		/* Bools, input or output, don't need args.
		 */
		return( FALSE );

	if( argument_class->flags & VIPS_ARGUMENT_INPUT ) 
		/* All other inputs need something.
		 */
		return( TRUE );

	/* Just output objects.
	 */

	if( (otype = G_PARAM_SPEC_VALUE_TYPE( pspec )) &&
		g_type_is_a( otype, VIPS_TYPE_OBJECT ) &&
		(oclass = g_type_class_ref( otype )) )
		/* For now, only vipsobject subclasses can ask for args.
		 */
		return( oclass->output_needs_arg );
	else
		return( FALSE );
}

/* 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 ) {
#ifdef DEBUG
		printf( "vips_argument_init: " );
		vips_object_print_name( object );
		printf( "\n" );
#endif /*DEBUG*/

		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. We can't use vips_argument_map() since that does
		 * some sanity checks that won't pass until all arg instance
		 * are built.
		 */
		VIPS_ARGUMENT_FOR_ALL( object, 
			pspec, argument_class, argument_instance ) {
#ifdef DEBUG
			printf( "vips_argument_init: "
				"adding instance argument for %s\n",
				g_param_spec_get_name( pspec ) );
#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 );

			((VipsArgument *) argument_instance)->pspec = pspec;
			argument_instance->argument_class = argument_class;
			argument_instance->object = object;
			/* SET_ALWAYS args default to assigned.
			 */
			argument_instance->assigned = 
				argument_class->flags & 
					VIPS_ARGUMENT_SET_ALWAYS;
			argument_instance->close_id = 0;
			argument_instance->invalidate_id = 0;

			vips_argument_table_replace( object->argument_table, 
				(VipsArgument *) argument_instance );
		} VIPS_ARGUMENT_FOR_ALL_END
	}
}

/**
 * vips__argument_get_instance: (skip)
 *
 * 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 ) );
}

/**
 * vips_object_get_argument: (skip)
 * @object: the object to fetch the args from
 * @name: arg to fetch
 * @pspec: (transfer none): the pspec for this arg
 * @argument_class: (transfer none): the argument_class for this arg
 * @argument_instance: (transfer none): the argument_instance for this arg
 *
 * Look up the three things you need to work with a vips argument.
 *
 * Returns: 0 on success, or -1 on error.
 */
int
vips_object_get_argument( VipsObject *object, const char *name,
	GParamSpec **pspec,
	VipsArgumentClass **argument_class,
	VipsArgumentInstance **argument_instance )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	if( !(*pspec = g_object_class_find_property( 
		G_OBJECT_CLASS( class ), name )) ) {
		vips_error( class->nickname, 
			_( "no property named `%s'" ), name );
		return( -1 );
	}

	if( !(*argument_class = (VipsArgumentClass *)
		vips__argument_table_lookup( class->argument_table, 
		*pspec )) ) {
		vips_error( class->nickname, 
			_( "no vips argument named `%s'" ), name );
		return( -1 );
	}
	if( !(*argument_instance = vips__argument_get_instance( 
		*argument_class, object )) ) {
		vips_error( class->nickname, 
			_( "argument `%s' has no instance" ), name );
		return( -1 );
	}

	return( 0 );
}

/**
 * vips_object_argument_isset: 
 * @object: the object to fetch the args from
 * @name: arg to fetch
 *
 * Convenience: has an argument been assigned. Useful for bindings.
 *
 * Returns: %TRUE if the argument has been assigned.
 */
gboolean
vips_object_argument_isset( VipsObject *object, const char *name )
{
	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

	if( vips_object_get_argument( object, name,
		&pspec, &argument_class, &argument_instance ) )
		return( FALSE );

	return( argument_instance->assigned );
}

/**
 * vips_object_get_argument_flags: 
 * @object: the object to fetch the args from
 * @name: arg to fetch
 *
 * Convenience: get the flags for an argument. Useful for bindings.
 *
 * Returns: The #VipsArgumentFlags for this argument.
 */
VipsArgumentFlags
vips_object_get_argument_flags( VipsObject *object, const char *name )
{
	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

	if( vips_object_get_argument( object, name,
		&pspec, &argument_class, &argument_instance ) )
		return( 0 );

	return( argument_class->flags );
}

/**
 * vips_object_get_argument_priority: 
 * @object: the object to fetch the args from
 * @name: arg to fetch
 *
 * Convenience: get the priority for an argument. Useful for bindings.
 *
 * Returns: The priority of this argument.
 */
int
vips_object_get_argument_priority( VipsObject *object, const char *name )
{
	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

	if( vips_object_get_argument( object, name,
		&pspec, &argument_class, &argument_instance ) )
		return( 0 );

	return( argument_class->priority );
}

static void
vips_object_clear_member( VipsArgumentInstance *argument_instance )
{
	VipsObject *object = argument_instance->object;
	VipsArgumentClass *argument_class = argument_instance->argument_class;
	GObject **member = &G_STRUCT_MEMBER( GObject *, object,
		argument_class->offset );

	vips_argument_instance_detach( argument_instance );

	if( *member ) {
		if( argument_class->flags & VIPS_ARGUMENT_INPUT ) {
#ifdef DEBUG_REF
			printf( "vips_object_clear_member: vips object: " );
			vips_object_print_name( 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_member: gobject %s (%p)\n",
				G_OBJECT_TYPE_NAME( *member ), *member );
			printf( "  no longer refers to vips object: " );
			vips_object_print_name( object );
			printf( "  count down to %d\n",
				G_OBJECT( object )->ref_count - 1 );
#endif /*DEBUG_REF*/

			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 )
{
	g_assert( ((VipsArgument *) argument_class)->pspec == pspec );
	g_assert( ((VipsArgument *) argument_instance)->pspec == pspec );

	if( G_IS_PARAM_SPEC_OBJECT( pspec ) ||
		G_IS_PARAM_SPEC_BOXED( pspec ) ) {
#ifdef DEBUG
		printf( "vips_object_dispose_argument: " );
		vips_object_print_name( object );
		printf( ".%s\n", g_param_spec_get_name( pspec ) ); 
#endif /*DEBUG*/

		g_object_set( object, 
			g_param_spec_get_name( pspec ), NULL, 
			NULL );
	}

	return( NULL );
}

/* Free all args on this object which may be holding resources.
 *
 * Note that this is not the same as vips_object_unref_outputs(). That 
 * looks for output objects which may have been created during _build() which
 * hold refs to this object and unrefs them. 
 *
 * This function looks for objects which this object holds refs to and which
 * may be holding sub-resources and zaps them.
 */
static void
vips_argument_dispose_all( VipsObject *object )
{
#ifdef DEBUG
	printf( "vips_argument_dispose_all: " );
	vips_object_print_name( object );
	printf( "\n" );
#endif /*DEBUG*/

	vips_argument_map( object, vips_object_dispose_argument, NULL, NULL );
}

/* Free any args which are holding memory.
 */
static void *
vips_object_free_argument( VipsObject *object, GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b )
{
	g_assert( ((VipsArgument *) argument_class)->pspec == pspec );
	g_assert( ((VipsArgument *) argument_instance)->pspec == pspec );

	if( G_IS_PARAM_SPEC_STRING( pspec ) ) {
#ifdef DEBUG
		printf( "vips_object_free_argument: " );
		vips_object_print_name( object );
		printf( ".%s\n", g_param_spec_get_name( pspec ) ); 
#endif /*DEBUG*/

		g_object_set( object, 
			g_param_spec_get_name( pspec ), NULL, 
			NULL );
	}

	return( NULL );
}

/* Free args which hold memory. Things like strings need to be freed right at
 * the end in case anyone is still using them.
 */
static void
vips_argument_free_all( VipsObject *object )
{
#ifdef DEBUG
	printf( "vips_argument_free_all: " );
	vips_object_print_name( object );
	printf( "\n" );
#endif /*DEBUG*/

	vips_argument_map( object, vips_object_free_argument, NULL, NULL );
}

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

#ifdef DEBUG
	printf( "vips_object_dispose: " );
	vips_object_print_name( object );
	printf( "\n" );
#endif /*DEBUG*/

	/* Our subclasses should have already called this. Run it again, just
	 * in case.
	 */
#ifdef DEBUG
	if( !object->preclose ) 
		printf( "vips_object_dispose: pre-close missing!\n" );
#endif /*DEBUG*/
	vips_object_preclose( object );

	/* Clear all our arguments: they may be holding resources we should 
	 * drop.
	 */
	vips_argument_dispose_all( object );

	vips_object_close( object );

	vips_object_postclose( object );

	vips_argument_free_all( object );

	VIPS_FREEF( vips_argument_table_destroy, object->argument_table );

	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_name( object );
	printf( "\n" );
#endif /*DEBUG*/

	/* I'd like to have post-close in here, but you can't emit signals
	 * from finalize, sadly.
	 */

	g_mutex_lock( vips__object_all_lock );
	g_hash_table_remove( vips__object_all, object );
	g_mutex_unlock( vips__object_all_lock );

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

static void
vips_object_arg_invalidate( GObject *argument,
	VipsArgumentInstance *argument_instance )
{
	/* Image @argument has signalled "invalidate" ... resignal on our
	 * operation.
	 */
	if( VIPS_IS_OPERATION( argument_instance->object ) )
		vips_operation_invalidate( 
			VIPS_OPERATION( argument_instance->object ) ); 
}

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

	/* Argument had reffed us ... now it's being closed, so we NULL out
	 * the pointer to unref.
	 */
	g_object_set( object, 
		g_param_spec_get_name( pspec ), NULL,
		NULL );
}

/* Set a member to an object. Handle the ref counts and signal
 * connect/disconnect.
 */
void
vips__object_set_member( VipsObject *object, GParamSpec *pspec,
	GObject **member, 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 );
	GType otype = G_PARAM_SPEC_VALUE_TYPE( pspec );

	g_assert( argument_instance );

	vips_object_clear_member( argument_instance );

	g_assert( !*member );
	*member = argument;

	if( *member ) {
		if( argument_class->flags & VIPS_ARGUMENT_INPUT ) {
#ifdef DEBUG_REF
			printf( "vips__object_set_member: vips object: " );
			vips_object_print_name( 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_member: gobject %s (%p)\n",
				G_OBJECT_TYPE_NAME( *member ), *member );
			printf( "  refers to vips object: " );
			vips_object_print_name( object );
			printf( "  count up to %d\n",
				G_OBJECT (object)->ref_count );
#endif /*DEBUG_REF*/

			/* The argument reffs us.
			 */
			g_object_ref( object );
		}
	}

	if( *member &&
		g_type_is_a( otype, VIPS_TYPE_IMAGE ) ) { 
		if( argument_class->flags & VIPS_ARGUMENT_INPUT ) {
			g_assert( !argument_instance->invalidate_id );

			argument_instance->invalidate_id =
				g_signal_connect( *member, "invalidate",
					G_CALLBACK( 
						vips_object_arg_invalidate ),
					argument_instance );
		}
		else if( argument_class->flags & VIPS_ARGUMENT_OUTPUT ) {
			g_assert( !argument_instance->close_id );

			argument_instance->close_id =
				g_signal_connect( *member, "close",
					G_CALLBACK( vips_object_arg_close ),
					argument_instance );
		}
	}
}

/* Is a value NULL? We allow multiple sets of NULL so props can be cleared.
 * The pspec gives the value type, for consistency with the way value types
 * are detected in set and get.
 */
gboolean
vips_value_is_null( GParamSpec *pspec, const GValue *value )
{
	if( G_IS_PARAM_SPEC_STRING( pspec ) && 
		!g_value_get_string( value ) )
		return( TRUE );
	if( G_IS_PARAM_SPEC_OBJECT( pspec ) &&
		!g_value_get_object( value ) )
		return( TRUE );
	if( G_IS_PARAM_SPEC_POINTER( pspec ) &&
		!g_value_get_pointer( value ) )
		return( TRUE );
	if( G_IS_PARAM_SPEC_BOXED( pspec ) &&
		!g_value_get_boxed( value ) )
		return( TRUE );

	return( FALSE );
}

/* 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 );

	g_assert( argument_instance );

#ifdef DEBUG
	printf( "vips_object_set_property: " );
	vips_object_print_name( object );
	printf( ".%s\n", g_param_spec_get_name( pspec ) );

	/* This can crash horribly with some values, have it as a separate
	 * chunk so we can easily comment it out.
	 */
{
	char *str_value;

	str_value = g_strdup_value_contents( value );
	printf( "\t%s\n", 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 &&
		!vips_value_is_null( pspec, value ) ) {
		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 &&
		!vips_value_is_null( pspec, value ) ) {
		g_warning( "%s: %s can only assign '%s' once",
			G_STRLOC,
			G_OBJECT_TYPE_NAME( gobject ),
			g_param_spec_get_name( pspec ) );
		return;
	}

	/* We can't use a switch since some param specs don't have fundamental
	 * types and are hence not compile-time constants, argh.
	 */
	if( G_IS_PARAM_SPEC_STRING( pspec ) ) {
		char **member = &G_STRUCT_MEMBER( char *, object,
			argument_class->offset );

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

		vips__object_set_member( object, pspec, member,
			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_UINT64( pspec ) ) {
		guint64 *member = &G_STRUCT_MEMBER( guint64, object,
			argument_class->offset );

		*member = g_value_get_uint64( 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_FLAGS( pspec ) ) {
		int *member = &G_STRUCT_MEMBER( int, object,
			argument_class->offset );

		*member = g_value_get_flags( 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.%s unimplemented property type %s",
			G_STRLOC,
			G_OBJECT_TYPE_NAME( gobject ),
			g_param_spec_get_name( pspec ),
			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 );
	VipsArgumentInstance *argument_instance =
		vips__argument_get_instance( argument_class, object );

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

	if( !argument_instance->assigned ) {
		/* Set the value to the default. Things like Ruby
		 * gobject-introspection will walk objects during GC, and we
		 * can find ourselves fetching object values between init and
		 * build.
		 */
		g_param_value_set_default( pspec, value );
		return;
	}

	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_UINT64( pspec ) ) {
		guint64 *member = &G_STRUCT_MEMBER( guint64, object,
			argument_class->offset );

		g_value_set_uint64( 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_FLAGS( pspec ) ) {
		int *member = &G_STRUCT_MEMBER( int, object,
			argument_class->offset );

		g_value_set_flags( 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.%s unimplemented property type %s",
			G_STRLOC,
			G_OBJECT_TYPE_NAME( gobject ),
			g_param_spec_get_name( pspec ),
			g_type_name( G_PARAM_SPEC_VALUE_TYPE( pspec ) ) );
	}
}

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

	/* Only test input args, output ones can be set by our subclasses as
	 * they build. See vips_object_build() above.
	 */
	VipsArgumentFlags iomask = VIPS_ARGUMENT_INPUT;

	int result;

#ifdef DEBUG
	printf( "vips_object_real_build: " ); 
	vips_object_print_name( object );
	printf( "\n" );
#endif /*DEBUG*/

	g_assert( !object->constructed );

	/* 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 );

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

	return( result );
}

static int
vips_object_real_postbuild( VipsObject *object, void *data )
{
#ifdef DEBUG
	printf( "vips_object_real_postbuild: " ); 
	vips_object_print_name( object );
	printf( "\n" );
#endif /*DEBUG*/

	g_assert( object->constructed ); 

	return( 0 ); 
}

static void
vips_object_real_summary_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_summary( VipsObject *object, VipsBuf *buf )
{
}

static void
vips_object_real_dump( VipsObject *object, VipsBuf *buf )
{
	vips_buf_appendf( buf, " %s (%p) count=%d", 
		G_OBJECT_TYPE_NAME( object ), 
		object, 
		G_OBJECT( object )->ref_count );

	if( object->local_memory )
		vips_buf_appendf( buf, " %zd bytes", object->local_memory ); 
}

static void
vips_object_real_sanity( VipsObject *object, VipsBuf *buf )
{
}

static void
vips_object_real_rewind( VipsObject *object )
{
#ifdef DEBUG
	printf( "vips_object_real_rewind\n" );
	vips_object_print_name( object );
	printf( "\n" );
#endif /*DEBUG*/

	g_object_run_dispose( G_OBJECT( object ) );

	object->constructed = FALSE;
	object->preclose = FALSE;
	object->close = FALSE;
	object->postclose = FALSE;
}

static VipsObject *
vips_object_real_new_from_string( const char *string )
{
	GType type;

	vips_check_init();

	/* The main arg selects the subclass.
	 */
	if( !(type = vips_type_find( NULL, string )) ) {
		vips_error( "VipsObject", 
			_( "class \"%s\" not found" ), string );
		return( NULL );
	}

	return( VIPS_OBJECT( g_object_new( type, NULL ) ) );
}

static void 
vips_object_real_to_string( VipsObject *object, VipsBuf *buf )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	/* Just "bicubic" or whatever.
	 */
	vips_buf_appends( buf, class->nickname );
}

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 *class )
{
	GObjectClass *gobject_class = G_OBJECT_CLASS( class );

	if( !vips__object_all ) {
		vips__object_all = g_hash_table_new( 
			g_direct_hash, g_direct_equal );
		vips__object_all_lock = vips_g_mutex_new();
	}

	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;

	class->build = vips_object_real_build;
	class->postbuild = vips_object_real_postbuild;
	class->summary_class = vips_object_real_summary_class;
	class->summary = vips_object_real_summary;
	class->dump = vips_object_real_dump;
	class->sanity = vips_object_real_sanity;
	class->rewind = vips_object_real_rewind;
	class->new_from_string = vips_object_real_new_from_string;
	class->to_string = vips_object_real_to_string;
	class->nickname = "object";
	class->description = _( "base class" );

	/* Table of VipsArgumentClass ... we can just g_free() them.
	 */
	class->argument_table = g_hash_table_new_full(
		g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_free );
	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 );

	VIPS_ARG_STRING( class, "nickname", 1, 
		_( "Nickname" ),
		_( "Class nickname" ),
		VIPS_ARGUMENT_SET_ONCE,
		G_STRUCT_OFFSET( VipsObject, nickname ), 
		"" );

	VIPS_ARG_STRING( class, "description", 2, 
		_( "Description" ),
		_( "Class description" ),
		VIPS_ARGUMENT_SET_ONCE,
		G_STRUCT_OFFSET( VipsObject, description ), 
		"" );

	/**
	 * VipsObject::postbuild:
	 * @object: the object that has been built
	 *
	 * The ::postbuild signal is emitted once just after successful object
	 * construction. Return non-zero to cause object construction to fail. 
	 */
	vips_object_signals[SIG_POSTBUILD] = g_signal_new( "postbuild",
		G_TYPE_FROM_CLASS( class ),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET( VipsObjectClass, postbuild ), 
		NULL, NULL,
		vips_INT__VOID,
		G_TYPE_INT, 0 );

	/**
	 * VipsObject::preclose:
	 * @object: the object that is to close
	 *
	 * The ::preclose signal is emitted once just before object close
	 * starts. The oject is still alive.
	 */
	vips_object_signals[SIG_PRECLOSE] = g_signal_new( "preclose",
		G_TYPE_FROM_CLASS( class ),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET( VipsObjectClass, preclose ), 
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0 );

	/**
	 * VipsObject::close:
	 * @object: the object that is closing
	 *
	 * The ::close signal is emitted once during object close. The object
	 * is dying and may not work. 
	 */
	vips_object_signals[SIG_CLOSE] = g_signal_new( "close",
		G_TYPE_FROM_CLASS( class ),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET( VipsObjectClass, close ), 
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0 );

	/**
	 * VipsObject::postclose:
	 * @object: the object that has closed
	 *
	 * The ::postclose signal is emitted once after object close. The 
	 * object pointer is still valid, but nothing else. 
	 */
	vips_object_signals[SIG_POSTCLOSE] = g_signal_new( "postclose",
		G_TYPE_FROM_CLASS( class ),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET( VipsObjectClass, postclose ), 
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0 );

}

static void
vips_object_init( VipsObject *object )
{
#ifdef DEBUG
	printf( "vips_object_init: " );
	vips_object_print_name( object );
	printf( "\n" );
#endif /*DEBUG*/

	g_mutex_lock( vips__object_all_lock );
	g_hash_table_insert( vips__object_all, object, object );
	g_mutex_unlock( vips__object_all_lock );
}

static void *
traverse_find_required_priority( void *data, void *a, void *b )
{
	VipsArgumentClass *argument_class = (VipsArgumentClass *) data;
	int priority = GPOINTER_TO_INT( a ); 

	if( (argument_class->flags & VIPS_ARGUMENT_REQUIRED) && 
		!(argument_class->flags & VIPS_ARGUMENT_DEPRECATED) && 
		argument_class->priority == priority )
		return( argument_class );
	
	return( NULL ); 
}

static gint
traverse_sort( gconstpointer a, gconstpointer b, void *user_data )
{
	VipsArgumentClass *class1 = (VipsArgumentClass *) a;
	VipsArgumentClass *class2 = (VipsArgumentClass *) b;

	return( class1->priority - class2->priority );
}

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

	GSList *argument_table_traverse;
	VipsArgumentClass *ac;

#ifdef DEBUG
	printf( "vips_object_class_install_argument: %p %s %s\n", 
		object_class,
		g_type_name( G_TYPE_FROM_CLASS( object_class ) ),
		g_param_spec_get_name( pspec ) );
#endif /*DEBUG*/

	/* object_class->argument* is shared, so we must lock.
	 */
	g_mutex_lock( vips__global_lock );

	/* Must be a new one.
	 */
	g_assert( !g_hash_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->priority = priority;
	argument_class->offset = offset;

	vips_argument_table_replace( object_class->argument_table,
		(VipsArgument *) argument_class );

	/* If this is the first argument for a new subclass, we need to clone
	 * the traverse list we inherit.
	 */
	if( object_class->argument_table_traverse_gtype != 
		G_TYPE_FROM_CLASS( object_class ) ) {
#ifdef DEBUG
		printf( "vips_object_class_install_argument: "
			"cloning traverse\n" ); 
#endif /*DEBUG*/

		object_class->argument_table_traverse = 
			g_slist_copy( object_class->argument_table_traverse );
		object_class->argument_table_traverse_gtype = 
			G_TYPE_FROM_CLASS( object_class );
	}

	/* We read argument_table_traverse without a lock (eg. see 
	 * vips_argument_map()), so we must be very careful updating it.
	 */
	argument_table_traverse = 
		g_slist_copy( object_class->argument_table_traverse );

	/* We keep traverse sorted by priority, so we musn't have duplicate
	 * priority values in required args. 
	 */
	if( (flags & VIPS_ARGUMENT_REQUIRED) && 
		!(flags & VIPS_ARGUMENT_DEPRECATED) && 
		(ac = vips_slist_map2( argument_table_traverse,
			traverse_find_required_priority, 
			GINT_TO_POINTER( priority ), NULL )) ) 
		g_warning( "vips_object_class_install_argument: "
			"%s.%s, %s.%s duplicate priority", 
			g_type_name( G_TYPE_FROM_CLASS( object_class ) ),
			g_param_spec_get_name( pspec ),
			g_type_name( G_TYPE_FROM_CLASS( ac->object_class ) ),
			g_param_spec_get_name( ((VipsArgument *) ac)->pspec ) );

	/* Warn about optional boolean args which default TRUE. These won't
	 * work from the CLI, since simple GOption switches don't allow
	 * `=false`.
	 */
	if( !(flags & VIPS_ARGUMENT_REQUIRED) && 
		!(flags & VIPS_ARGUMENT_DEPRECATED) && 
		G_IS_PARAM_SPEC_BOOLEAN( pspec ) &&
		G_PARAM_SPEC_BOOLEAN( pspec )->default_value ) 
		g_warning( "vips_object_class_install_argument: "
			"default TRUE BOOL arg %s.%s", 
			g_type_name( G_TYPE_FROM_CLASS( object_class ) ),
			g_param_spec_get_name( pspec ) );

	argument_table_traverse = g_slist_prepend(
		argument_table_traverse, argument_class );
	argument_table_traverse = g_slist_sort(
		argument_table_traverse, (GCompareFunc) traverse_sort );
	VIPS_SWAP( GSList *, 
		argument_table_traverse, 
		object_class->argument_table_traverse ); 

	g_slist_free( argument_table_traverse );  

#ifdef DEBUG
{
	GSList *p;

	printf( "%d items on traverse %p\n", 
		g_slist_length( object_class->argument_table_traverse ),
		&object_class->argument_table_traverse );
	for( p = object_class->argument_table_traverse; p; p = p->next ) {
		VipsArgumentClass *argument_class = 
			(VipsArgumentClass *) p->data;

		printf( "\t%p %s\n", 
			argument_class, 
			g_param_spec_get_name( 
				((VipsArgument *) argument_class)->pspec ) );
	}
}
#endif /*DEBUG*/

	g_mutex_unlock( vips__global_lock );
}

static void
vips_object_no_value( VipsObject *object, const char *name )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

	if( vips_object_get_argument( object, name,
		&pspec, &argument_class, &argument_instance ) )
		g_assert_not_reached();

	if( strcmp( name, g_param_spec_get_name( pspec ) ) == 0 )
		vips_error( class->nickname,
			_( "no value supplied for argument '%s'" ), name );
	else
		vips_error( class->nickname,
			_( "no value supplied for argument '%s' ('%s')" ), 
			name,
			g_param_spec_get_name( pspec ) );
}

/* Set a named arg from a string.
 */
int
vips_object_set_argument_from_string( VipsObject *object, 
	const char *name, const char *value )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;
	VipsObjectClass *oclass;
	GType otype;

	GValue gvalue = { 0 };

	VIPS_DEBUG_MSG( "vips_object_set_argument_from_string: %s = %s\n", 
		name, value );

	if( vips_object_get_argument( object, name,
		&pspec, &argument_class, &argument_instance ) )
		return( -1 );

	otype = G_PARAM_SPEC_VALUE_TYPE( pspec );

	g_assert( argument_class->flags & VIPS_ARGUMENT_INPUT );

	if( g_type_is_a( otype, VIPS_TYPE_IMAGE ) ) { 
		VipsImage *out;
		VipsOperationFlags flags;
		VipsAccess access;
		char filename[VIPS_PATH_MAX];
		char option_string[VIPS_PATH_MAX];

		flags = 0;
		if( VIPS_IS_OPERATION( object ) )
			flags = vips_operation_get_flags( 
				VIPS_OPERATION( object ) );

		if( flags & 
			(VIPS_OPERATION_SEQUENTIAL_UNBUFFERED | 
			 VIPS_OPERATION_SEQUENTIAL) ) 
			access = VIPS_ACCESS_SEQUENTIAL;
		else
			access = VIPS_ACCESS_RANDOM; 

		if( !value ) {
			vips_object_no_value( object, name );
			return( -1 );
		}
		vips__filename_split8( value, filename, option_string );

		if( strcmp( "stdin", filename ) == 0 ) {
			VipsSource *source;

			if( !(source = 
				vips_source_new_from_descriptor( 0 )) )
				return( -1 );
			if( !(out = vips_image_new_from_source( source, 
				option_string, 
				"access", access,
				NULL )) ) {
				VIPS_UNREF( source );
				return( -1 );
			}
			VIPS_UNREF( source );
		}
		else {
			if( !(out = vips_image_new_from_file( value, 
				"access", access,
				NULL )) )
				return( -1 );
		}

		g_value_init( &gvalue, VIPS_TYPE_IMAGE );
		g_value_set_object( &gvalue, out );

		/* Setting gvalue will have upped @out's count again,
		 * go back to 1 so that gvalue has the only ref.
		 */
		g_object_unref( out );
	}
	else if( g_type_is_a( otype, VIPS_TYPE_SOURCE ) ) { 
		VipsSource *source;

		if( !value ) {
			vips_object_no_value( object, name );
			return( -1 );
		}

		if( !(source = vips_source_new_from_options( value )) )
			return( -1 );
	
		g_value_init( &gvalue, VIPS_TYPE_SOURCE );
		g_value_set_object( &gvalue, source );

		/* Setting gvalue will have upped @out's count again,
		 * go back to 1 so that gvalue has the only ref.
		 */
		g_object_unref( source );
	}
	else if( g_type_is_a( otype, VIPS_TYPE_ARRAY_IMAGE ) ) { 
		/* We have to have a special case for this, we can't just rely
		 * on transform_g_string_array_image(), since we need to be
		 * able to set the access hint on the image.
		 */
		VipsArrayImage *array_image;
		VipsOperationFlags flags;
		VipsAccess access;

		if( !value ) {
			vips_object_no_value( object, name );
			return( -1 );
		}

		flags = 0;
		if( VIPS_IS_OPERATION( object ) )
			flags = vips_operation_get_flags( 
				VIPS_OPERATION( object ) );

		if( flags & (VIPS_OPERATION_SEQUENTIAL_UNBUFFERED |
			     VIPS_OPERATION_SEQUENTIAL) ) 
			access = VIPS_ACCESS_SEQUENTIAL;
		else
			access = VIPS_ACCESS_RANDOM; 

		if( !(array_image = 
			vips_array_image_new_from_string( value, access )) )
			return( -1 ); 

		g_value_init( &gvalue, VIPS_TYPE_ARRAY_IMAGE );
		g_value_set_boxed( &gvalue, array_image );

		/* Setting gvalue will have upped @array_image's count again,
		 * go back to 1 so that gvalue has the only ref.
		 */
		vips_area_unref( VIPS_AREA( array_image ) );
	}
	else if( g_type_is_a( otype, VIPS_TYPE_OBJECT ) &&
		(oclass = g_type_class_ref( otype )) ) { 
		VipsObject *new_object;

		if( !value ) {
			vips_object_no_value( object, name );
			return( -1 );
		}

		if( !(new_object = 
			vips_object_new_from_string( oclass, value )) )
			return( -1 );

		/* Not necessarily a VipsOperation subclass so we don't use
		 * the cache. We could have a separate case for this.
		 */
		if( vips_object_build( new_object ) ) {
			g_object_unref( new_object );
			return( -1 );
		}

		g_value_init( &gvalue, G_TYPE_OBJECT );
		g_value_set_object( &gvalue, new_object );

		/* The GValue now has a ref, we can drop ours.
		 */
		g_object_unref( new_object );
	}
	else if( G_IS_PARAM_SPEC_BOOLEAN( pspec ) ) {
		gboolean b;

		b = TRUE;
		if( value &&
			(g_ascii_strcasecmp( value, "false" ) == 0 ||
			g_ascii_strcasecmp( value, "no" ) == 0 ||
			strcmp( value, "0" ) == 0) )
			b = FALSE;

		g_value_init( &gvalue, G_TYPE_BOOLEAN );
		g_value_set_boolean( &gvalue, b );
	}
	else if( G_IS_PARAM_SPEC_INT( pspec ) ) {
		int i;

		if( !value ) {
			vips_object_no_value( object, name );
			return( -1 );
		}

		if( sscanf( value, "%d", &i ) != 1 ) {
			vips_error( class->nickname,
				_( "'%s' is not an integer" ), value );
			return( -1 );
		}

		g_value_init( &gvalue, G_TYPE_INT );
		g_value_set_int( &gvalue, i );
	}
	else if( G_IS_PARAM_SPEC_UINT64( pspec ) ) {
		/* Not allways the same as guint64 :-( argh.
		 */
		long long l;

		if( !value ) {
			vips_object_no_value( object, name );
			return( -1 );
		}

		if( sscanf( value, "%lld", &l ) != 1 ) {
			vips_error( class->nickname,
				_( "'%s' is not an integer" ), value );
			return( -1 );
		}

		g_value_init( &gvalue, G_TYPE_UINT64 );
		g_value_set_uint64( &gvalue, l );
	}
	else if( G_IS_PARAM_SPEC_DOUBLE( pspec ) ) {
		double d;

		if( !value ) {
			vips_object_no_value( object, name );
			return( -1 );
		}

		if( sscanf( value, "%lg", &d ) != 1 ) {
			vips_error( class->nickname,
				_( "'%s' is not a double" ), value );
			return( -1 );
		}

		g_value_init( &gvalue, G_TYPE_DOUBLE );
		g_value_set_double( &gvalue, d );
	}
	else if( G_IS_PARAM_SPEC_ENUM( pspec ) ) {
		int i;

		if( !value ) {
			vips_object_no_value( object, name );
			return( -1 );
		}

		if( (i = vips_enum_from_nick( class->nickname, 
			otype, value )) < 0 ) 
			return( -1 );

		g_value_init( &gvalue, otype );
		g_value_set_enum( &gvalue, i );
	}
	else if( G_IS_PARAM_SPEC_FLAGS( pspec ) ) {
		/* Allow a symbolic name, or an int. 
		 */
		int i;

		if( !value ) {
			vips_object_no_value( object, name );
			return( -1 );
		}

		if( sscanf( value, "%d", &i ) != 1 &&
			(i = vips_flags_from_nick( class->nickname, 
			otype, value )) < 0 ) {
			vips_error( class->nickname,
				_( "'%s' is not an integer" ), value );
			return( -1 );
		}

		g_value_init( &gvalue, otype );
		g_value_set_flags( &gvalue, i );
	}
	else {
		if( !value ) {
			vips_object_no_value( object, name );
			return( -1 );
		}

		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 );
}

/* Does an vipsargument need an argument to write to? For example, an image
 * output needs a filename, a double output just prints.
 */
gboolean
vips_object_argument_needsstring( VipsObject *object, const char *name )
{
	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

#ifdef DEBUG
	printf( "vips_object_argument_needsstring: %s\n", name );
#endif /*DEBUG*/

	if( vips_object_get_argument( object, name,
		&pspec, &argument_class, &argument_instance ) )
		return( -1 );

	return( vips_argument_class_needsstring( argument_class ) ); 
}

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 );
}

/* Is a filename a target, ie. it is of the form ".jpg". Any trailing options 
 * have already been stripped. Watch out for cases like "./x.jpg".
 */
static gboolean
vips_filename_istarget( const char *filename )
{
	const char *p;

	return( (p = strrchr( filename, '.' )) && p == filename );
}

/* Write a named arg to the string. If the arg does not need a string (see
 * above), arg will be NULL.
 */
int
vips_object_get_argument_to_string( VipsObject *object, 
	const char *name, const char *arg )
{
	GParamSpec *pspec;
	GType otype;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;
	VipsObjectClass *oclass;

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

	if( vips_object_get_argument( object, name,
		&pspec, &argument_class, &argument_instance ) )
		return( -1 );

	otype = G_PARAM_SPEC_VALUE_TYPE( pspec );

	g_assert( argument_class->flags & VIPS_ARGUMENT_OUTPUT );

	if( g_type_is_a( otype, VIPS_TYPE_IMAGE ) ) { 
		VipsImage *in;
		char filename[VIPS_PATH_MAX];
		char option_string[VIPS_PATH_MAX];

		vips__filename_split8( arg, filename, option_string );

		if( vips_filename_istarget( filename ) ) {
			VipsTarget *target;

			if( !(target = vips_target_new_to_descriptor( 1 )) )
				return( -1 );
			g_object_get( object, name, &in, NULL );
			if( vips_image_write_to_target( in, 
				arg, target, NULL ) ) {
				VIPS_UNREF( in );
				VIPS_UNREF( target );
				return( -1 );
			}
			VIPS_UNREF( in );
			VIPS_UNREF( target );
		}
		else {
			/* Pull out the image and write it.
			 */
			g_object_get( object, name, &in, NULL );
			if( vips_image_write_to_file( in, arg, NULL ) ) {
				VIPS_UNREF( in );
				return( -1 );
			}
			VIPS_UNREF( in );
		}
	}
	else if( g_type_is_a( otype, VIPS_TYPE_OBJECT ) &&
		(oclass = g_type_class_ref( otype )) &&
		oclass->output_to_arg ) {
		VipsObject *value;

		g_object_get( object, name, &value, NULL );
		if( oclass->output_to_arg( value, arg ) ) {
			g_object_unref( value );
			return( -1 );
		}
		g_object_unref( value );
	}
	else {
		char str[1000];
		VipsBuf buf = VIPS_BUF_STATIC( str );

		vips_object_print_arg( object, pspec, &buf );
		printf( "%s\n", vips_buf_all( &buf ) );
	}

	return( 0 );
}

static void *
vips_argument_is_required( 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_class->flags & VIPS_ARGUMENT_INPUT) &&
		!argument_instance->assigned )
		return( pspec );

	return( NULL );
}

/* Find the first unassigned required input arg.
 */
static GParamSpec *
vips_object_find_required( VipsObject *object )
{
	return( (GParamSpec *) vips_argument_map( object,
		vips_argument_is_required, NULL, NULL ) );
}

typedef struct _VipsNameFlagsPair {
	const char **names;
	int *flags;
} VipsNameFlagsPair;

static void *
vips_object_find_args( VipsObject *object,
	GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b )
{
	VipsNameFlagsPair *pair = (VipsNameFlagsPair *) a;
	int *i = (int *) b;

	pair->names[*i] = g_param_spec_get_name( pspec );
	pair->flags[*i] = (int) argument_class->flags;

	*i += 1;

	return( NULL );
}

/**
 * vips_object_get_args: (skip)
 * @object: object whose args should be retrieved
 * @names: (transfer none) (array length=n_args) (allow-none): output array of %GParamSpec names
 * @flags: (transfer none) (array length=n_args) (allow-none): output array of #VipsArgumentFlags
 * @n_args: (allow-none): length of output arrays
 *
 * Get all %GParamSpec names and #VipsArgumentFlags for an object.
 *
 * This is handy for language bindings. From C, it's usually more convenient to
 * use vips_argument_map().
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_object_get_args( VipsObject *object,
	const char ***names, int **flags, int *n_args )
{
	VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS( object );
	int n = g_slist_length( object_class->argument_table_traverse );

	VipsNameFlagsPair pair;
	int i;

	pair.names = VIPS_ARRAY( object, n, const char * );
	pair.flags = VIPS_ARRAY( object, n, int );
	if( !pair.names ||
		!pair.flags )
		return( -1 );

	i = 0;
	(void) vips_argument_map( object,
		vips_object_find_args, &pair, &i );

	if( names )
		*names = pair.names;
	if( flags )
		*flags = pair.flags;
	if( n_args )
		*n_args = n;

	return( 0 );
}

/**
 * vips_object_new: (skip)
 * @type: object to create
 * @set: set arguments with this
 * @a: client data
 * @b: client data
 *
 * g_object_new() the object, set any arguments with @set, call
 * vips_object_build() and return the complete object.
 *
 * Returns: the new object
 */
VipsObject *
vips_object_new( GType type, VipsObjectSetArguments set, void *a, void *b )
{
	VipsObject *object;

	vips_check_init();

	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 );
}

/**
 * vips_object_set_valist:
 * @object: object to set arguments on
 * @ap: %NULL-terminated list of argument/value pairs
 *
 * See vips_object_set().
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_object_set_valist( VipsObject *object, va_list ap )
{
	char *name;

	VIPS_DEBUG_MSG( "vips_object_set_valist:\n" );

	for( name = va_arg( ap, char * ); name; name = va_arg( ap, char * ) ) {
		GParamSpec *pspec;
		VipsArgumentClass *argument_class;
		VipsArgumentInstance *argument_instance;

		VIPS_DEBUG_MSG( "\tname = '%s' (%p)\n", name, name );

		if( vips_object_get_argument( VIPS_OBJECT( object ), name,
			&pspec, &argument_class, &argument_instance ) )
			return( -1 );

		VIPS_ARGUMENT_COLLECT_SET( pspec, argument_class, ap );

		g_object_set_property( G_OBJECT( object ), name, &value );

		VIPS_ARGUMENT_COLLECT_GET( pspec, argument_class, ap );

		VIPS_ARGUMENT_COLLECT_END
	}

	return( 0 );
}

/**
 * vips_object_set:
 * @object: object to set arguments on
 * @...: %NULL-terminated list of argument/value pairs
 *
 * Set a list of vips object arguments. For example:
 *
 * |[
 * vips_object_set (operation,
 *   "input", in,
 *   "output", &out,
 *   NULL);
 * ]|
 *
 * Input arguments are given in-line, output arguments are given as pointers
 * to where the output value should be written.
 *
 * See also: vips_object_set_valist(), vips_object_set_from_string(). 
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_object_set( VipsObject *object, ... )
{
	va_list ap;
	int result;

	va_start( ap, object );
	result = vips_object_set_valist( object, ap );
	va_end( ap );

	return( result );
}

/* Set object args from a string. @p should be the initial left bracket and
 * there should be no tokens after the matching right bracket. @p is modified. 
 */
static int
vips_object_set_args( VipsObject *object, const char *p )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	VipsToken token;
	char string[VIPS_PATH_MAX];
	char string2[VIPS_PATH_MAX];
	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

	if( !(p = vips__token_need( p, VIPS_TOKEN_LEFT, 
		string, VIPS_PATH_MAX )) )
		return( -1 );

	if( !(p = vips__token_segment( p, &token, string, VIPS_PATH_MAX )) )
		return( -1 );

	for(;;) {
		if( token == VIPS_TOKEN_RIGHT )
			break;
		if( token != VIPS_TOKEN_STRING ) {
			vips_error( class->nickname,
				_( "expected string or ), saw %s" ), 
				vips_enum_nick( VIPS_TYPE_TOKEN, token ) );
			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_segment( p, &token, 
			string2, VIPS_PATH_MAX )) )
			return( -1 );
		if( token == VIPS_TOKEN_EQUALS ) {
			if( !(p = vips__token_segment_need( p, VIPS_TOKEN_STRING,
				string2, VIPS_PATH_MAX )) )
				return( -1 );
			if( vips_object_set_argument_from_string( object, 
				string, string2 ) )
				return( -1 );

			if( !(p = vips__token_must( p, &token,
				string2, VIPS_PATH_MAX )) )
				return( -1 );
		}
		else if( g_object_class_find_property( 
			G_OBJECT_GET_CLASS( object ), string ) &&
			!vips_object_get_argument( object, string, 
				&pspec, &argument_class, &argument_instance ) &&
			(argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) &&
			(argument_class->flags & VIPS_ARGUMENT_INPUT) &&
			G_IS_PARAM_SPEC_BOOLEAN( pspec ) ) {
			/* The string is the name of an optional
			 * input boolean ... set it!
			 */
			if( !argument_instance->assigned )
				g_object_set( object, string, TRUE, NULL );
		}
		else if( (pspec = vips_object_find_required( object )) ) {
			if( vips_object_set_argument_from_string( object, 
				g_param_spec_get_name( pspec ), string ) ) 
				return( -1 );
		}
		else {
			vips_error( class->nickname,
				_( "unable to set '%s'" ), string );
			return( -1 );
		}

		/* Now must be a , or a ). 
		 */
		if( token == VIPS_TOKEN_COMMA ) {
			if( !(p = vips__token_must( p, &token, 
				string, VIPS_PATH_MAX )) )
				return( -1 );
		}
		else if( token != VIPS_TOKEN_RIGHT ) {
			vips_error( class->nickname,
				"%s", _( "not , or ) after parameter" ) );
			return( -1 );
		}
	}

	if( (p = vips__token_get( p, &token, string, VIPS_PATH_MAX )) ) {
		vips_error( class->nickname,
			"%s", _( "extra tokens after ')'" ) );
		return( -1 );
	}

	return( 0 );
}

/**
 * vips_object_set_from_string:
 * @object: object to set arguments on
 * @string: arguments as a string
 *
 * Set object arguments from a string. The string can be something like
 * "a=12", or "a = 12, b = 13", or "fred". The string can optionally be
 * enclosed in brackets. 
 *
 * You'd typically use this between creating the object and building it. 
 *
 * See also: vips_object_set(), vips_object_build(),
 * vips_cache_operation_buildp(). 
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_object_set_from_string( VipsObject *object, const char *string )
{
	const char *q;
	VipsToken token;
	char buffer[VIPS_PATH_MAX];
	char str[VIPS_PATH_MAX];

	vips_strncpy( buffer, string, VIPS_PATH_MAX );

	/* Does string start with a bracket? If it doesn't, enclose the whole
	 * thing in [].
	 */
	if( !(q = vips__token_get( buffer, &token, str, VIPS_PATH_MAX )) ||
		token != VIPS_TOKEN_LEFT )
		vips_snprintf( buffer, VIPS_PATH_MAX, "[%s]", string );
	else
		vips_strncpy( buffer, string, VIPS_PATH_MAX );

	return( vips_object_set_args( object, buffer ) ); 
}

VipsObject *
vips_object_new_from_string( VipsObjectClass *object_class, const char *p )
{
	const char *q;
	char str[VIPS_PATH_MAX];
	VipsObject *object;

	g_assert( object_class );
	g_assert( object_class->new_from_string );

	/* Find the start of the optional args on the end of the string, take
	 * everything before that as the principal arg for the constructor.
	 */
	if( (q = vips__find_rightmost_brackets( p )) )
		vips_strncpy( str, p, VIPS_MIN( VIPS_PATH_MAX, q - p + 1 ) );
	else
		vips_strncpy( str, p, VIPS_PATH_MAX );
	if( !(object = object_class->new_from_string( str )) )
		return( NULL );

	/* More tokens there? Set any other args.
	 */
	if( q && 
		vips_object_set_from_string( object, q ) ) {
		g_object_unref( object );
		return( NULL );
	}

	return( object ); 
}

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 );
}

/**
 * vips_object_to_string: 
 * @object: object to stringify
 * @buf: write string here
 *
 * The inverse of vips_object_new_from_string(): turn @object into eg.
 * "VipsInterpolateSnohalo1(blur=.333333)".
 */
void
vips_object_to_string( VipsObject *object, VipsBuf *buf )
{
	VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS( object );

	gboolean first;

	g_assert( object_class->to_string );

	/* Nicknames are not guaranteed to be unique, so use the full type
	 * name.
	 */
	object_class->to_string( object, buf );
	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, ")" );
}

typedef struct {
	VipsSListMap2Fn fn;
	void *a;
	void *b;
	void *result;
} VipsObjectMapArgs;

static void
vips_object_map_sub( VipsObject *key, VipsObject *value, 
	VipsObjectMapArgs *args )
{
	if( !args->result )
		args->result = args->fn( key, args->a, args->b );
}

/**
 * vips_object_map: (skip)
 * @fn: function to call for all objects
 * @a: client data
 * @b: client data
 *
 * Call a function for all alive objects.
 * Stop when @fn returns non-%NULL and return that value. 
 *
 * Returns: %NULL if @fn returns %NULL for all arguments, otherwise the first
 * non-%NULL value from @fn.
 */
void *
vips_object_map( VipsSListMap2Fn fn, void *a, void *b )
{
	VipsObjectMapArgs args;

	args.fn = fn;
	args.a = a;
	args.b = b;
	args.result = NULL;

	/* We must test vips__object_all before we lock because the lock is
	 * only created when the first object is created.
	 */
	if( vips__object_all ) {
		g_mutex_lock( vips__object_all_lock );
		g_hash_table_foreach( vips__object_all, 
			(GHFunc) vips_object_map_sub, &args );
		g_mutex_unlock( vips__object_all_lock );
	}

	return( args.result );
}

/**
 * vips_type_map: (skip)
 * @base: base type
 * @fn: call this function for every type
 * @a: client data
 * @b: client data
 *
 * Map over a type's children. Stop when @fn returns non-%NULL
 * and return that value. 
 *
 * Returns: %NULL if @fn returns %NULL for all arguments, otherwise the first
 * non-%NULL value from @fn.
 */
void *
vips_type_map( GType base, VipsTypeMap2Fn fn, void *a, void *b )
{
	GType *child;
	guint n_children;
	unsigned int i;
	void *result;

	child = g_type_children( base, &n_children );
	result = NULL;
	for( i = 0; i < n_children && !result; i++ )
		result = fn( child[i], a, b );
	g_free( child );

	return( result );
}

/**
 * vips_type_map_all: (skip)
 * @base: base type
 * @fn: call this function for every type
 * @a: client data
 *
 * Map over a type's children, direct and indirect. Stop when @fn returns 
 * non-%NULL and return that value. 
 *
 * Returns: %NULL if @fn returns %NULL for all arguments, otherwise the first
 * non-%NULL value from @fn.
 */
void *
vips_type_map_all( GType base, VipsTypeMapFn fn, void *a )
{
	void *result;

	if( !(result = fn( base, a )) )
		result = vips_type_map( base, 
			(VipsTypeMap2Fn) vips_type_map_all, fn, a );

	return( result );
}

/**
 * vips_class_map_all: (skip) 
 * @type: base type
 * @fn: call this function for every type
 * @a: client data
 *
 * Loop over all the subclasses of @type. Non-abstract classes only.
 * Stop when @fn returns 
 * non-%NULL and return that value. 
 *
 * Returns: %NULL if @fn returns %NULL for all arguments, otherwise the first
 * non-%NULL value from @fn.
 */
void *
vips_class_map_all( GType type, VipsClassMapFn fn, void *a )
{
	void *result;

	/* Avoid abstract classes. Use type_map_all for them.
	 */
	if( !G_TYPE_IS_ABSTRACT( type ) ) {
		/* We never unref this ref, but we never unload classes
		 * anyway, so so what.
		 */
		if( (result = fn( 
			VIPS_OBJECT_CLASS( g_type_class_ref( type ) ), a )) )
			return( result );
	}

	if( (result = vips_type_map( type, 
		(VipsTypeMap2Fn) vips_class_map_all, fn, a )) )
		return( result );

	return( NULL );
}

/* How deeply nested is a class ... used to indent class lists.
 */
int
vips_type_depth( GType type )
{
	int depth;

	depth = 0;
	while( type != VIPS_TYPE_OBJECT && 
		(type = g_type_parent( type )) )
		depth += 1;

	return( depth );
}

static void *
test_name( VipsObjectClass *class, const char *nickname )
{
	if( g_ascii_strcasecmp( class->nickname, nickname ) == 0 )
		return( class );

	/* Check the class name too, why not.
	 */
	if( g_ascii_strcasecmp( G_OBJECT_CLASS_NAME( class ), nickname ) == 0 )
		return( class );

	return( NULL );
}

/**
 * vips_class_find: 
 * @basename: name of base class
 * @nickname: search for a class with this nickname
 *
 * Search below @basename, return the first class whose name or @nickname
 * matches.
 *
 * See also: vips_type_find()
 *
 * Returns: (transfer none): the found class.
 */
const VipsObjectClass *
vips_class_find( const char *basename, const char *nickname )
{
	const char *classname = basename ? basename : "VipsObject";

	VipsObjectClass *class;
	GType base;

	if( !(base = g_type_from_name( classname )) )
		return( NULL );
	class = vips_class_map_all( base, 
		(VipsClassMapFn) test_name, (void *) nickname );

	return( class );
}

/* What we store for each nickname. We can't just store the type with
 * GINT_TO_POINTER() since GType is 64 bits on some platforms.
 */
typedef struct _NicknameGType {
	const char *nickname;
	GType type;
	gboolean duplicate;
} NicknameGType;

static void *
vips_class_add_hash( VipsObjectClass *class, GHashTable *table )
{
	GType type = G_OBJECT_CLASS_TYPE( class );
	NicknameGType *hit;

	hit = (NicknameGType *) 
		g_hash_table_lookup( table, (void *) class->nickname );

	/* If this is not a unique name, mark as a duplicate. In this case
	 * we'll need to fall back to a search.
	 */
	if( hit ) 
		hit->duplicate = TRUE;
	else {
		hit = g_new( NicknameGType, 1 );
		hit->nickname = class->nickname;
		hit->type = type;
		hit->duplicate = FALSE;
		g_hash_table_insert( table, (void *) hit->nickname, hit );
	}

	return( NULL ); 
}

static void *
vips_class_build_hash_cb( void *dummy )
{
	GType base;

	vips__object_nickname_table = 
		g_hash_table_new( g_str_hash, g_str_equal );

	base = g_type_from_name( "VipsObject" );
	g_assert( base ); 

	vips_class_map_all( base, 
		(VipsClassMapFn) vips_class_add_hash, 
		(void *) vips__object_nickname_table );

	return( NULL );
}

/**
 * vips_type_find:
 * @basename: name of base class
 * @nickname: search for a class with this nickname
 *
 * Search below @basename, return the %GType of the class whose name or 
 * @nickname matches, or 0 for not found. 
 * If @basename is NULL, the whole of #VipsObject is searched.
 *
 * This function uses a cache, so it should be quick. 
 *
 * See also: vips_class_find()
 *
 * Returns: the %GType of the class, or 0 if the class is not found.
 */
GType
vips_type_find( const char *basename, const char *nickname )
{
	static GOnce once = G_ONCE_INIT;

	const char *classname = basename ? basename : "VipsObject";

	NicknameGType *hit;
	GType base;
	GType type;

	VIPS_ONCE( &once, vips_class_build_hash_cb, NULL ); 

	hit = (NicknameGType *) 
		g_hash_table_lookup( vips__object_nickname_table, 
			(void *) nickname );

	/* We must only search below basename ... check that the cache hit is
	 * in the right part of the tree.
	 */
	if( !(base = g_type_from_name( classname )) )
		return( 0 );
	if( hit &&
		!hit->duplicate &&
		g_type_is_a( hit->type, base ) ) 
		type = hit->type;
	else {
		const VipsObjectClass *class;

		if( !(class = vips_class_find( basename, nickname )) )
			return( 0 );

		type = G_OBJECT_CLASS_TYPE( class );
	}

	return( type );
}

/**
 * vips_nickname_find:
 * @type: #GType to search for
 *
 * Return the VIPS nickname for a %GType. Handy for language bindings. 
 *
 * Returns: (transfer none): the class nickname. 
 */
const char *
vips_nickname_find( GType type )
{
	gpointer p;
	VipsObjectClass *class;

	if( type &&
		(p = g_type_class_ref( type )) &&
		VIPS_IS_OBJECT_CLASS( p ) &&
		(class = VIPS_OBJECT_CLASS( p )) )
		return( class->nickname );

	return( NULL );
}

/* The vips_object_local() macro uses this as its callback.
 */
void
vips_object_local_cb( VipsObject *vobject, GObject *gobject )
{
	VIPS_FREEF( g_object_unref, gobject );
}

typedef struct {
	VipsObject **array;
	int n;
} VipsObjectLocal;

static void
vips_object_local_array_cb( VipsObject *parent, VipsObjectLocal *local )
{
	int i;

	for( i = 0; i < local->n; i++ )
		VIPS_FREEF( g_object_unref, local->array[i] );

	VIPS_FREEF( g_free, local->array );
	VIPS_FREEF( g_free, local );
}

/** 
 * vips_object_local_array: (skip)
 * @parent: objects unref when this object unrefs
 * @n: array size
 *
 * Make an array of NULL VipsObject pointers. When @parent closes, every
 * non-NULL pointer in the array will be unreffed and the array will be
 * freed. Handy for creating a set of temporary images for a function.
 *
 * The array is NULL-terminated, ie. contains an extra NULL element at the
 * end. 
 *
 * Example:
 *
 * |[
 * VipsObject **t;
 *
 * t = vips_object_local_array( parent, 5 );
 * if( 
 *   vips_add( a, b, &t[0], NULL ) ||
 *   vips_invert( t[0], &t[1], NULL ) ||
 *   vips_add( t[1], t[0], &t[2], NULL ) ||
 *   vips_costra( t[2], out, NULL ) )
 *   return( -1 );
 * ]|
 *
 * See also: vips_object_local().
 *
 * Returns: an array of NULL pointers of length @n
 */
VipsObject **
vips_object_local_array( VipsObject *parent, int n )
{
	VipsObjectLocal *local;

	local = g_new( VipsObjectLocal, 1 );
	local->n = n;
	/* Make the array 1 too long so we can be sure there's a NULL 
	 * terminator.
	 */
	local->array = g_new0( VipsObject *, n + 1 );

	g_signal_connect( parent, "close", 
		G_CALLBACK( vips_object_local_array_cb ), local );

	return( local->array );
}

void 
vips_object_set_static( VipsObject *object, gboolean static_object )
{
	object->static_object = static_object;
}

static void *
vips_object_n_static_cb( VipsObject *object, int *n, void *b )
{
	if( object->static_object )
		*n += 1;

	return( NULL );
}

static int
vips_object_n_static( void )
{
	int n;

	n = 0;
	vips_object_map( 
		(VipsSListMap2Fn) vips_object_n_static_cb, &n, NULL );

	return( n );
}

static void *
vips_object_print_all_cb( VipsObject *object, int *n, void *b )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	char str[32768];
	VipsBuf buf = VIPS_BUF_STATIC( str );

	fprintf( stderr, "%d) %s (%p)", 
		*n, G_OBJECT_TYPE_NAME( object ), object );
	if( object->local_memory )
		fprintf( stderr, " %zd bytes", object->local_memory ); 
	fprintf( stderr, ", count=%d", G_OBJECT( object )->ref_count ); 
	fprintf( stderr, "\n" ); 

	vips_object_summary_class( class, &buf );
	vips_buf_appends( &buf, ", " );
	vips_object_summary( object, &buf ); 
	fprintf( stderr, "%s\n", vips_buf_all( &buf ) );

	*n += 1;

	return( NULL );
}

int
vips__object_leak( void )
{
	int n_leaks;

	n_leaks = 0;

	/* Don't count static objects.
	 */
	if( vips__object_all &&
		g_hash_table_size( vips__object_all ) > 
			vips_object_n_static() ) {
		fprintf( stderr, "%d objects alive:\n", 
			g_hash_table_size( vips__object_all ) ); 

		vips_object_map( 
			(VipsSListMap2Fn) vips_object_print_all_cb, 
			&n_leaks, NULL );
	}

	return( n_leaks );
}

void
vips_object_print_all( void )
{
	(void) vips__object_leak();
	(void) vips__type_leak();
}

static void *
vips_object_sanity_all_cb( VipsObject *object, void *a, void *b )
{
	(void) vips_object_sanity( object );

	return( NULL );
}

void
vips_object_sanity_all( void )
{
	vips_object_map( 
		(VipsSListMap2Fn) vips_object_sanity_all_cb, NULL, NULL );
}

static void *
vips_object_unref_outputs_sub( VipsObject *object,
	GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b )
{
	if( (argument_class->flags & VIPS_ARGUMENT_OUTPUT) &&
		G_IS_PARAM_SPEC_OBJECT( pspec ) &&
		argument_instance->assigned ) {
		GObject *value;

		g_object_get( object, 
			g_param_spec_get_name( pspec ), &value, NULL );

		/* Doing the get refs the object, so unref the get, then unref
		 * again since this an an output object of the operation.
		 */
		g_object_unref( value );
		g_object_unref( value );
	}

	return( NULL );
}

/**
 * vips_object_unref_outputs:
 * @object: object to drop output refs from
 *
 * Unref all assigned output objects. Useful for language bindings. 
 *
 * After an object is built, all output args are owned by the caller. If
 * something goes wrong before then, we have to unref the outputs that have
 * been made so far. This function can also be useful for callers when
 * they've finished processing outputs themselves.
 *
 * See also: vips_cache_operation_build().
 */
void
vips_object_unref_outputs( VipsObject *object )
{
	(void) vips_argument_map( object,
		vips_object_unref_outputs_sub, NULL, NULL );
}

/**
 * vips_object_get_description:
 * @object: object to fetch description from
 *
 * Fetch the object description. Useful for language bindings. 
 *
 * @object.description is only avaliable after _build(), which can be too
 * late. This function fetches from the instance, if possible, but falls back
 * to the class description if we are too early. 
 * 
 * Returns: the object description
 */
const char *
vips_object_get_description( VipsObject *object )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	if( object->description ) 
		return( object->description ) ;
	else
		return( class->description ) ;
}
