/* VIPS package handling.
 *
 * J. Cupitt, 8/4/93.
 *
 * 18/2/04 JC
 *	- now uses g_module_*() instead of dlopen()
 * 9/8/04
 *	- uses glib dir scanning stuff instead of dirent.h
 * 20/5/08
 * 	- note_dependencies() does IMAGEVEC as well as IMAGE
 * 5/8/08
 * 	- silent success in loading plugins if the dir isn't there
 */

/*

	This file is part of VIPS.

	VIPS 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 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 Lesser General Public License for more details.

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

 */

/*

	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 <glib/gi18n-lib.h>

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif /*HAVE_SYS_PARAM_H*/
#include <string.h>
#include <stdarg.h>
#include <limits.h>

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

/* Standard VIPS packages.
 */
extern im_package im__arithmetic;
extern im_package im__cimg;
extern im_package im__colour;
extern im_package im__conversion;
extern im_package im__convolution;
extern im_package im__deprecated;
extern im_package im__format;
extern im_package im__freq_filt;
extern im_package im__histograms_lut;
extern im_package im__inplace;
extern im_package im__mask;
extern im_package im__morphology;
extern im_package im__mosaicing;
extern im_package im__other;
extern im_package im__resample;
extern im_package im__video;

/* im_guess_prefix() args.
 */
static im_arg_desc guess_prefix_args[] = {
	IM_INPUT_STRING("argv0"),
	IM_INPUT_STRING("env_name"),
	IM_OUTPUT_STRING("PREFIX")
};

/* Call im_guess_prefix() via arg vector.
 */
static int
guess_prefix_vec(im_object *argv)
{
	const char *prefix = vips_guess_prefix(argv[0], argv[1]);

	if (!prefix) {
		argv[2] = NULL;
		return -1;
	}

	argv[2] = im_strdup(NULL, prefix);

	return 0;
}

/* Description of im_guess_prefix.
 */
static im_function guess_prefix_desc = {
	"im_guess_prefix",				/* Name */
	"guess install area",			/* Description */
	0,								/* Flags */
	guess_prefix_vec,				/* Dispatch function */
	VIPS_NUMBER(guess_prefix_args), /* Size of arg list */
	guess_prefix_args				/* Arg list */
};

/* im_guess_libdir() args.
 */
static im_arg_desc guess_libdir_args[] = {
	IM_INPUT_STRING("argv0"),
	IM_INPUT_STRING("env_name"),
	IM_OUTPUT_STRING("LIBDIR")
};

/* Call im_guess_libdir() via arg vector.
 */
static int
guess_libdir_vec(im_object *argv)
{
	const char *libdir = vips_guess_libdir(argv[0], argv[1]);

	if (!libdir) {
		argv[2] = NULL;
		return -1;
	}

	argv[2] = im_strdup(NULL, libdir);

	return 0;
}

/* Description of im_guess_libdir.
 */
static im_function guess_libdir_desc = {
	"im_guess_libdir",				/* Name */
	"guess library area",			/* Description */
	0,								/* Flags */
	guess_libdir_vec,				/* Dispatch function */
	VIPS_NUMBER(guess_libdir_args), /* Size of arg list */
	guess_libdir_args				/* Arg list */
};

/* im_header_int() args.
 */
static im_arg_desc header_int_args[] = {
	IM_INPUT_STRING("field"),
	IM_INPUT_IMAGE("image"),
	IM_OUTPUT_INT("value")
};

/* Call im_header_int() via arg vector.
 */
static int
header_int_vec(im_object *argv)
{
	return im_header_int((IMAGE *) argv[1], (const char *) argv[0],
		(int *) argv[2]);
}

/* Description of im_header_int().
 */
static im_function header_int_desc = {
	"im_header_int",				  /* Name */
	"extract int fields from header", /* Description */
	0,								  /* Flags */
	header_int_vec,					  /* Dispatch function */
	VIPS_NUMBER(header_int_args),	  /* Size of arg list */
	header_int_args					  /* Arg list */
};

/* im_header_get_typeof() args.
 */
static im_arg_desc header_get_typeof_args[] = {
	IM_INPUT_STRING("field"),
	IM_INPUT_IMAGE("image"),
	IM_OUTPUT_INT("gtype")
};

/* Call im_header_get_typeof() via arg vector.
 */
static int
header_get_typeof_vec(im_object *argv)
{
	int *out = (int *) argv[2];

	*out = im_header_get_typeof((IMAGE *) argv[1],
		(const char *) argv[0]);

	return 0;
}

/* Description of im_header_get_typeof().
 */
static im_function header_get_typeof_desc = {
	"im_header_get_typeof",				 /* Name */
	"return field type",				 /* Description */
	0,									 /* Flags */
	header_get_typeof_vec,				 /* Dispatch function */
	VIPS_NUMBER(header_get_typeof_args), /* Size of arg list */
	header_get_typeof_args				 /* Arg list */
};

/* im_header_double() args.
 */
static im_arg_desc header_double_args[] = {
	IM_INPUT_STRING("field"),
	IM_INPUT_IMAGE("image"),
	IM_OUTPUT_DOUBLE("value")
};

/* Call im_header_double() via arg vector.
 */
static int
header_double_vec(im_object *argv)
{
	return im_header_double((IMAGE *) argv[1], (const char *) argv[0],
		(double *) argv[2]);
}

/* Description of im_header_double().
 */
static im_function header_double_desc = {
	"im_header_double",					 /* Name */
	"extract double fields from header", /* Description */
	0,									 /* Flags */
	header_double_vec,					 /* Dispatch function */
	VIPS_NUMBER(header_double_args),	 /* Size of arg list */
	header_double_args					 /* Arg list */
};

/* im_header_string() args.
 */
static im_arg_desc header_string_args[] = {
	IM_INPUT_STRING("field"),
	IM_INPUT_IMAGE("image"),
	IM_OUTPUT_STRING("value")
};

/* Call im_header_string() via arg vector.
 */
static int
header_string_vec(im_object *argv)
{
	char **out = (char **) &argv[2];

	/* Actually, we call im_header_as_string(), so we can do any field and
	 * not just the string-valued ones.
	 */
	if (im_header_as_string((IMAGE *) argv[1],
			(const char *) argv[0], out))
		return -1;

	return 0;
}

/* Description of im_header_string().
 */
static im_function header_string_desc = {
	"im_header_string",						  /* Name */
	"extract fields from headers as strings", /* Description */
	0,										  /* Flags */
	header_string_vec,						  /* Dispatch function */
	VIPS_NUMBER(header_string_args),		  /* Size of arg list */
	header_string_args						  /* Arg list */
};

/* im_history_get() args.
 */
static im_arg_desc history_get_args[] = {
	IM_INPUT_IMAGE("image"),
	IM_OUTPUT_STRING("history")
};

/* Call im_history_get() via arg vector.
 */
static int
history_get_vec(im_object *argv)
{
	char **out = (char **) &argv[1];
	const char *str;

	if (!(str = im_history_get((IMAGE *) argv[0])) ||
		!(*out = im_strdup(NULL, str)))
		return -1;

	return 0;
}

/* Description of im_history_get().
 */
static im_function history_get_desc = {
	"im_history_get",						/* Name */
	"return the image history as a string", /* Description */
	0,										/* Flags */
	history_get_vec,						/* Dispatch function */
	VIPS_NUMBER(history_get_args),			/* Size of arg list */
	history_get_args						/* Arg list */
};

/* im_getext() args.
 */
static im_arg_desc getext_args[] = {
	IM_INPUT_IMAGE("image"),
	IM_OUTPUT_STRING("history")
};

/* Call im_getext() via arg vector.
 */
static int
getext_vec(im_object *argv)
{
	void **out = (void **) &argv[1];
	int size;

	/* void/char confusion is fine.
	 */
	if (!(*out = im__read_extension_block((IMAGE *) argv[0], &size)))
		return -1;

	return 0;
}

/* Description of im_getext().
 */
static im_function getext_desc = {
	"im_getext",								 /* Name */
	"return the image metadata XML as a string", /* Description */
	0,											 /* Flags */
	getext_vec,									 /* Dispatch function */
	VIPS_NUMBER(getext_args),					 /* Size of arg list */
	getext_args									 /* Arg list */
};

/* im_printdesc() args.
 */
static im_arg_desc printdesc_args[] = {
	IM_INPUT_IMAGE("image"),
};

/* Call im_printdesc() via arg vector.
 */
static int
printdesc_vec(im_object *argv)
{
	vips_object_print_dump(VIPS_OBJECT(argv[0]));

	return 0;
}

/* Description of im_printdesc().
 */
static im_function printdesc_desc = {
	"im_printdesc",					   /* Name */
	"print an image header to stdout", /* Description */
	0,								   /* Flags */
	printdesc_vec,					   /* Dispatch function */
	VIPS_NUMBER(printdesc_args),	   /* Size of arg list */
	printdesc_args					   /* Arg list */
};

/* im_version_string() args.
 */
static im_arg_desc version_string_args[] = {
	IM_OUTPUT_STRING("version")
};

/* Call im_version_string() via arg vector.
 */
static int
version_string_vec(im_object *argv)
{
	if (!(argv[0] = im_strdup(NULL, vips_version_string())))
		return -1;

	return 0;
}

/* Description of im_version_string.
 */
static im_function version_string_desc = {
	"im_version_string",			  /* Name */
	"VIPS version string",			  /* Description */
	0,								  /* Flags */
	version_string_vec,				  /* Dispatch function */
	VIPS_NUMBER(version_string_args), /* Size of arg list */
	version_string_args				  /* Arg list */
};

/* im_version() args.
 */
static im_arg_desc version_args[] = {
	IM_INPUT_INT("flag"),
	IM_OUTPUT_INT("version")
};

/* Call im_version() via arg vector.
 */
static int
version_vec(im_object *argv)
{
	int flag = *((int *) argv[0]);
	int *out = ((int *) argv[1]);

	int version = vips_version(flag);

	if (version < 0)
		return -1;

	*out = version;

	return 0;
}

/* Description of im_version.
 */
static im_function version_desc = {
	"im_version",			   /* Name */
	"VIPS version number",	   /* Description */
	0,						   /* Flags */
	version_vec,			   /* Dispatch function */
	VIPS_NUMBER(version_args), /* Size of arg list */
	version_args			   /* Arg list */
};

/* im_concurrency_get() args.
 */
static im_arg_desc concurrency_get_args[] = {
	IM_OUTPUT_INT("concurrency")
};

/* Call im_concurrency_get() via arg vector.
 */
static int
concurrency_get_vec(im_object *argv)
{
	int *out = ((int *) argv[0]);

	*out = vips_concurrency_get();

	return 0;
}

/* Description of im_concurrency_get.
 */
static im_function concurrency_get_desc = {
	"im_concurrency_get",			   /* Name */
	"get concurrency level",		   /* Description */
	0,								   /* Flags */
	concurrency_get_vec,			   /* Dispatch function */
	VIPS_NUMBER(concurrency_get_args), /* Size of arg list */
	concurrency_get_args			   /* Arg list */
};

/* im_cache() args.
 */
static im_arg_desc cache_args[] = {
	IM_INPUT_IMAGE("in"),
	IM_OUTPUT_IMAGE("out"),
	IM_INPUT_INT("tile_width"),
	IM_INPUT_INT("tile_height"),
	IM_INPUT_INT("max_tiles")
};

/* Call im_cache() via arg vector.
 */
static int
cache_vec(im_object *argv)
{
	int tile_width = *((int *) argv[2]);
	int tile_height = *((int *) argv[3]);
	int max_tiles = *((int *) argv[4]);

	return im_cache(argv[0], argv[1],
		tile_width, tile_height, max_tiles);
}

/* Description of im_cache.
 */
static im_function cache_desc = {
	"im_cache",						 /* Name */
	"cache results of an operation", /* Description */
	0,								 /* Flags */
	cache_vec,						 /* Dispatch function */
	VIPS_NUMBER(cache_args),		 /* Size of arg list */
	cache_args						 /* Arg list */
};

static int
tile_cache_random_vec(im_object *argv)
{
	int tile_width = *((int *) argv[2]);
	int tile_height = *((int *) argv[3]);
	int max_tiles = *((int *) argv[4]);

	return im_tile_cache_random(argv[0], argv[1],
		tile_width, tile_height, max_tiles);
}

/* Description of im_cache.
 */
static im_function tile_cache_random_desc = {
	"im_tile_cache_random",			 /* Name */
	"cache results of an operation", /* Description */
	0,								 /* Flags */
	tile_cache_random_vec,			 /* Dispatch function */
	VIPS_NUMBER(cache_args),		 /* Size of arg list */
	cache_args						 /* Arg list */
};

/* im_binfile() args.
 */
static im_arg_desc binfile_args[] = {
	IM_INPUT_STRING("filename"),
	IM_OUTPUT_IMAGE("out"),
	IM_INPUT_INT("width"),
	IM_INPUT_INT("height"),
	IM_INPUT_INT("bands"),
	IM_INPUT_INT("offset")
};

/* Call im_binfile() via arg vector.
 */
static int
binfile_vec(im_object *argv)
{
	int width = *((int *) argv[2]);
	int height = *((int *) argv[3]);
	int bands = *((int *) argv[4]);
	int offset = *((int *) argv[5]);
	VipsImage *im;

	if (!(im = vips_image_new_from_file_raw(argv[0],
			  width, height, bands, offset)))
		return -1;
	vips_object_local(argv[1], im);

	if (im_copy(im, argv[1]))
		return -1;

	return 0;
}

/* Description of im_binfile.
 */
static im_function binfile_desc = {
	"im_binfile",					 /* Name */
	"open a headerless binary file", /* Description */
	0,								 /* Flags */
	binfile_vec,					 /* Dispatch function */
	VIPS_NUMBER(binfile_args),		 /* Size of arg list */
	binfile_args					 /* Arg list */
};

/* Package up iofuncs functions.
 */
static im_function *iofuncs_list[] = {
	&binfile_desc,
	&cache_desc,
	&tile_cache_random_desc,
	&concurrency_get_desc,
	&getext_desc,
	&guess_prefix_desc,
	&guess_libdir_desc,
	&header_get_typeof_desc,
	&header_int_desc,
	&header_double_desc,
	&header_string_desc,
	&history_get_desc,
	&printdesc_desc,
	&version_desc,
	&version_string_desc
};

/* Package of io functions.
 */
static im_package im__iofuncs = {
	"iofuncs",
	VIPS_NUMBER(iofuncs_list),
	iofuncs_list
};

/* List of built-in VIPS packages.
 */
static im_package *built_in[] = {
	&im__arithmetic,
	&im__cimg,
	&im__colour,
	&im__conversion,
	&im__convolution,
	&im__deprecated,
	&im__format,
	&im__freq_filt,
	&im__histograms_lut,
	&im__inplace,
	&im__iofuncs,
	&im__mask,
	&im__morphology,
	&im__mosaicing,
	&im__other,
	&im__resample,
	&im__video
};

#ifdef ENABLE_MODULES
/* How we represent a loaded plugin.
 */
typedef struct _Plugin {
	GModule *module;  /* As loaded by g_module_open() */
	char *name;		  /* Name we loaded */
	im_package *pack; /* Package table */
} Plugin;

/* List of loaded plugins.
 */
static GSList *plugin_list = NULL;

/* Free a plugin.
 */
static int
plugin_free(Plugin *plug)
{
	char *name = plug->name ? plug->name : "<unknown>";

	if (plug->module) {
		if (!g_module_close(plug->module)) {
			vips_error("plugin",
				_("unable to close plugin \"%s\""), name);
			vips_error("plugin", "%s", g_module_error());
			return -1;
		}

		plug->module = NULL;
	}
	VIPS_FREE(plug->name);
	plug->pack = NULL;
	g_free(plug);

	plugin_list = g_slist_remove(plugin_list, plug);

	return 0;
}
#endif /*ENABLE_MODULES*/

/* Load a plugin.
 */
im_package *
im_load_plugin(const char *name)
{
#ifdef ENABLE_MODULES
	Plugin *plug;

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

	if (!g_module_supported()) {
		vips_error("plugin",
			"%s", _("plugins not supported on this platform"));
		return NULL;
	}

	/* Build a new plugin.
	 */
	plug = VIPS_NEW(NULL, Plugin);
	plug->module = NULL;
	plug->name = g_strdup(name);
	plug->pack = NULL;
	plugin_list = g_slist_prepend(plugin_list, plug);

	/* Open library.
	 */
	if (!(plug->module = g_module_open(name, 0))) {
		vips_error("plugin",
			_("unable to open plugin \"%s\""), name);
		vips_error("plugin", "%s", g_module_error());
		plugin_free(plug);

		return NULL;
	}

	/* Find package.
	 */
	/* Bizarre double-cast stops a bogus gcc 4.1 compiler warning.
	 */
	if (!g_module_symbol(plug->module,
			"package_table", (gpointer *) ((void *) &plug->pack))) {
		vips_error("plugin",
			_("unable to find symbol \"package_table\" "
			  "in plugin \"%s\""),
			name);
		vips_error("plugin", "%s", g_module_error());
		plugin_free(plug);

		return NULL;
	}

	/* Sanity check.
	 */
	if (!plug->pack->name || plug->pack->nfuncs < 0 ||
		plug->pack->nfuncs > 10000) {
		vips_error("plugin",
			_("corrupted package table in plugin \"%s\""), name);
		plugin_free(plug);

		return NULL;
	}

#ifdef DEBUG
	printf("added package \"%s\"\n", plug->pack->name);
#endif /*DEBUG*/

	return plug->pack;
#else  /*!ENABLE_MODULES*/
	vips_error("plugin",
		"%s", _("libvips built without modules support"));

	return NULL;
#endif /*ENABLE_MODULES*/
}

/* Load all plugins in a directory ... look for '.plg' suffix. Error if we had
 * any probs.
 */
int
im_load_plugins(const char *fmt, ...)
{
#ifdef ENABLE_MODULES
	va_list ap;
	char dir_name[VIPS_PATH_MAX];
	GDir *dir;
	const char *name;
	int result;

	/* Silently succeed if we can't do modules.
	 */
	if (!g_module_supported())
		return 0;

	va_start(ap, fmt);
	(void) im_vsnprintf(dir_name, VIPS_PATH_MAX - 1, fmt, ap);
	va_end(ap);

#ifdef DEBUG
	printf("im_load_plugins: searching \"%s\"\n", dir_name);
#endif /*DEBUG*/

	if (!(dir = g_dir_open(dir_name, 0, NULL)))
		/* Silent success for dir not there.
		 */
		return 0;

	result = 0;
	while ((name = g_dir_read_name(dir)))
		if (im_ispostfix(name, ".plg")) {
			char path[VIPS_PATH_MAX];

			im_snprintf(path, VIPS_PATH_MAX - 1,
				"%s" G_DIR_SEPARATOR_S "%s", dir_name, name);
			if (!im_load_plugin(path))
				result = -1;
		}
	g_dir_close(dir);

	return result;
#else  /*!ENABLE_MODULES*/
	/* Silently succeed if we can't do modules.
	 */
	return 0;
#endif /*ENABLE_MODULES*/
}

/* Close all loaded plugins.
 */
int
im_close_plugins(void)
{
#ifdef ENABLE_MODULES
	while (plugin_list)
		if (plugin_free((Plugin *) plugin_list->data))
			return -1;
#endif /*ENABLE_MODULES*/

	return 0;
}

#ifdef ENABLE_MODULES
/* Apply a user-function to a plugin package.
 */
static void *
apply_plugin(Plugin *plug, VSListMap2Fn fn, void *a)
{
	if (!plug->pack)
		return NULL;
	else
		return fn(plug->pack, a, NULL);
}
#endif /*ENABLE_MODULES*/

/* Map a function over all packages. Map over plugins first, to allow
 * overriding of VIPS functions.
 */
void *
im_map_packages(VSListMap2Fn fn, void *a)
{
#ifdef ENABLE_MODULES
	void *r = im_slist_map2(plugin_list,
		(VSListMap2Fn) apply_plugin, (void *) fn, a);
#else  /*!ENABLE_MODULES*/
	void *r = NULL;
#endif /*ENABLE_MODULES*/

	/* If not there, try main VIPS package list.
	 */
	if (!r) {
		int i;

		for (i = 0; i < VIPS_NUMBER(built_in); i++)
			if ((r = fn(built_in[i], a, NULL)))
				return r;
	}

	return r;
}

/* Search a package for a function.
 */
static im_function *
search_package(im_package *pack, const char *name)
{
	int i;

	for (i = 0; i < pack->nfuncs; i++)
		if (strcmp(pack->table[i]->name, name) == 0)
			return pack->table[i];

	return NULL;
}

/* Search all packages for a function.
 */
im_function *
im_find_function(const char *name)
{
	im_function *fn = im_map_packages(
		(VSListMap2Fn) search_package, (void *) name);

	if (!fn) {
		vips_error("im_find_function", _("\"%s\" not found"), name);
		return NULL;
	}

	return fn;
}

/* Test for package is of name.
 */
static im_package *
package_name(im_package *pack, const char *name)
{
	if (strcmp(pack->name, name) == 0)
		return pack;

	return NULL;
}

/* Find a package.
 */
im_package *
im_find_package(const char *name)
{
	im_package *pack = im_map_packages(
		(VSListMap2Fn) package_name, (void *) name);

	if (!pack) {
		vips_error("im_find_package", _("\"%s\" not found"), name);
		return NULL;
	}

	return pack;
}

/* Test for package contains a function.
 */
static im_package *
package_function(im_package *pack, const char *name)
{
	if (search_package(pack, name))
		return pack;
	else
		return NULL;
}

/* Find a function's package by name.
 */
im_package *
im_package_of_function(const char *name)
{
	im_package *pack = im_map_packages(
		(VSListMap2Fn) package_function, (void *) name);

	if (!pack) {
		vips_error("im_package_of_function",
			_("\"%s\" not found"), name);
		return NULL;
	}

	return pack;
}

/* Free any store we allocated for the argument list.
 */
int
im_free_vargv(im_function *fn, im_object *vargv)
{
	int i;
	int vargc = fn->argc;

	/* Free all elements.
	 */
	for (i = 0; i < vargc; i++)
		if (vargv[i]) {
			/* If there is local storage, free it.
			 */
			if (fn->argv[i].desc->size != 0)
				g_free(vargv[i]);

			/* NULL out pointer.
			 */
			vargv[i] = NULL;
		}

	return 0;
}

/* Allocate any local store the args will need; NULL out all others.
 */
int
im_allocate_vargv(im_function *fn, im_object *vargv)
{
	int i;
	int vargc = fn->argc;

	/* NULL out all pointers.
	 */
	for (i = 0; i < vargc; i++)
		vargv[i] = NULL;

	/* Allocate any space we will need.
	 */
	for (i = 0; i < vargc; i++) {
		int sz = fn->argv[i].desc->size;

		if (sz != 0)
			if (!(vargv[i] = vips_malloc(NULL, sz))) {
				/* Free anything we did allocate.
				 */
				(void) im_free_vargv(fn, vargv);
				return -1;
			}

		/* Zero memory.
		 */
		memset(vargv[i], 0, sz);
	}

	return 0;
}

/* Destroy the objects in the arg list.
 */
static int
destroy_args(im_function *fn, im_object *vargv)
{
	int i;
	int vargc = fn->argc;

	/* Destroy all elements with destroy functions.
	 */
	for (i = 0; i < vargc; i++)
		if (vargv[i])
			/* If there's a destroy function for this type,
			 * trigger it.
			 */
			if (fn->argv[i].desc->dest &&
				fn->argv[i].desc->dest(vargv[i]))
				return -1;

	return 0;
}

/* Init an im_object array from a set of command-line arguments.
 */
static int
build_args(im_function *fn, im_object *vargv, int argc, char **argv)
{
	im_arg_desc *arg = fn->argv;
	int vargc = fn->argc;
	char *str;
	int i, j;

	/* Loop, constructing each im_arg.
	 */
	for (i = 0, j = 0; i < vargc; i++) {
		/* Find type for this arg.
		 */
		im_type_desc *type = arg[i].desc;

		/* Do we need to use up a command line argument?
		 */
		if (type->flags & IM_TYPE_ARG) {
			if (!argv[j]) {
				vips_error("im_run_command",
					"%s", _("too few arguments"));
				return -1;
			}
			str = argv[j++];

			/* Init object.
			 */
			if (type->init && type->init(&vargv[i], str))
				return -1;
		}
		else {
			/* Init object with no arg.
			 */
			if (type->init && type->init(&vargv[i], "no arg"))
				return -1;
		}
	}

	/* Have we used up all the command-line args?
	 */
	if (argv[j]) {
		vips_error("im_run_command", "%s", _("too many arguments"));
		return -1;
	}

	return 0;
}

/* Make a region on sub, closed by callback on main.
 */
static int
region_local_image(IMAGE *main, IMAGE *sub)
{
	VipsRegion *reg;

	if (!(reg = vips_region_new(sub)))
		return -1;
	vips_object_local(main, reg);

	return 0;
}

/* vargv[i] is an output image on a PIO function ... make all input images
 * depend on it.
 */
static int
note_dependencies(im_function *fn, im_object *vargv, int i)
{
	int j;

	for (j = 0; j < fn->argc; j++) {
		im_type_desc *type = fn->argv[j].desc;

		if (!(type->flags & IM_TYPE_OUTPUT)) {
			if (strcmp(type->type, IM_TYPE_IMAGE) == 0) {
				if (region_local_image(vargv[i], vargv[j]))
					return -1;
			}
			else if (strcmp(type->type, IM_TYPE_IMAGEVEC) == 0) {
				im_imagevec_object *iv = vargv[j];
				int k;

				for (k = 0; k < iv->n; k++)
					if (region_local_image(vargv[i],
							iv->vec[k]))
						return -1;
			}
		}
	}

	return 0;
}

/* Call all defined print functions.
 */
static int
print_args(im_function *fn, im_object *vargv)
{
	int i;
	int vargc = fn->argc;

	/* Print all elements.
	 */
	for (i = 0; i < vargc; i++)
		if (fn->argv[i].print && vargv[i])
			if (fn->argv[i].print(vargv[i]))
				return -1;

	return 0;
}

/* Add to the hist of all output images.
 */
static int
add_hist(im_function *fn, im_object *vargv, int argc, char **argv)
{
	int i;
	int vargc = fn->argc;

	/* Search for output images.
	 */
	for (i = 0; i < vargc; i++)
		if (strcmp(fn->argv[i].desc->type, IM_TYPE_IMAGE) == 0 &&
			(fn->argv[i].desc->flags & IM_TYPE_OUTPUT))
			if (im_updatehist(vargv[i], fn->name, argc, argv))
				return -1;

	return 0;
}

/* Call a VIPS function.
 */
static int
dispatch_function(im_function *fn, im_object *vargv, int argc, char **argv)
{
	/* Init memory from command line arguments.
	 */
	if (build_args(fn, vargv, argc, argv))
		return -1;

	/* If this is a PIO function, we need to make sure that we close
	 * the input images after the output images, since the output image
	 * may include delayed image conversion filters which will not run
	 * until the output is closed.
	 *
	 * Do this by:
	 *	- for each output image
	 *		- for each input image
	 *			- create a region on the input, closed by a
	 *			  close callback on the output image
	 */
	if (fn->flags & IM_FN_PIO) {
		int i;

		for (i = 0; i < fn->argc; i++) {
			im_type_desc *type = fn->argv[i].desc;

			if (type->flags & IM_TYPE_OUTPUT &&
				strcmp(type->type, IM_TYPE_IMAGE) == 0)
				if (note_dependencies(fn, vargv, i))
					return -1;
		}
	}

	/* Call function.
	 */
	if (fn->disp(vargv))
		return -1;

	/* Print output.
	 */
	if (print_args(fn, vargv))
		return -1;

	/* Add to history of all output images.
	 */
	if (add_hist(fn, vargv, argc, argv))
		return -1;

	/* All ok!
	 */
	return 0;
}

/* Run a command.
 */
int
im_run_command(char *name, int argc, char **argv)
{
	static im_object object_array[IM_MAX_ARGS];
	im_object *vargv = object_array;
	im_function *fn;

	/* Search packages for a matching function.
	 */
	if (!(fn = im_find_function(name)))
		return -1;

	/* Allocate space for arguments.
	 */
	if (im_allocate_vargv(fn, vargv))
		return -1;

	/* Call it.
	 */
	if (dispatch_function(fn, vargv, argc, argv)) {
		destroy_args(fn, vargv);
		im_free_vargv(fn, vargv);
		return -1;
	}

	/* Clean up and exit.
	 */
	if (destroy_args(fn, vargv))
		return -1;
	im_free_vargv(fn, vargv);

	return 0;
}
