/* vips_system(): run a command on an image
 *
 * 7/3/00 JC
 *	- hacked it in
 * 21/10/02 JC
 *	- use mktemp() if mkstemp() is not available
 * 10/3/03 JC
 *	- out can be NULL
 * 23/12/04
 *	- use g_mkstemp()
 * 8/9/09
 * 	- add .v suffix (thanks Roland)
 * 	- use vipsbuf
 * 	- rewrite to make it simpler
 * 2/2/10
 * 	- gtkdoc
 * 4/6/13
 * 	- redo as a class
 * 	- input and output images are now optional
 * 3/5/14
 * 	- switch to g_spawn_command_line_sync() from popen() ... helps stop
 * 	  stray command-windows on Windows
 * 27/3/16
 * 	- allow [options] in out_format
 * 20/8/25
 *	- add "cache" argument
 */

/*

	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_UNISTD_H
#include <unistd.h>
#endif /*HAVE_UNISTD_H*/
#include <stdarg.h>
#include <string.h>
#include <errno.h>

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

typedef struct _VipsSystem {
	VipsOperation parent_instance;

	VipsArrayImage *in;
	VipsImage *out;
	char *cmd_format;
	char *in_format;
	char *out_format;
	char *log;
	gboolean cache;

	/* Array of names we wrote the input images to.
	 */
	char **in_name;

	/* Output name without any options, so /tmp/vips-weifh.svg, for
	 * example.
	 */
	char *out_name;

	/* Output name with any options, so /tmp/vips-weifh.svg[scale=2], for
	 * example.
	 */
	char *out_name_options;

} VipsSystem;

typedef VipsOperationClass VipsSystemClass;

G_DEFINE_TYPE(VipsSystem, vips_system, VIPS_TYPE_OPERATION);

static void
vips_system_dispose(GObject *gobject)
{
	VipsSystem *system = (VipsSystem *) gobject;

	if (system->in_name) {
		int i;

		for (i = 0; i < VIPS_AREA(system->in)->n; i++) {
			g_unlink(system->in_name[i]);
			VIPS_FREE(system->in_name[i]);
		}
	}

	VIPS_FREE(system->out_name);
	VIPS_FREE(system->out_name_options);

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

static int
vips_system_build(VipsObject *object)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);
	VipsSystem *system = (VipsSystem *) object;

	int i;

	char cmd[VIPS_PATH_MAX];

	char *p;
	char *std_output;
	char *std_error;
	int result;
	GError *error = NULL;

	if (VIPS_OBJECT_CLASS(vips_system_parent_class)->build(object))
		return -1;

	/* Write the input images to files. We must always make copies of the
	 * files, even if this image is a disc file already, in case the
	 * command needs a different format.
	 */
	if (system->in) {
		char *in_format = system->in_format ? system->in_format : "%s.tif";
		int n;
		VipsImage **in = vips_array_image_get(system->in, &n);

		if (!(system->in_name = VIPS_ARRAY(object, n, char *)))
			return -1;
		memset(system->in_name, 0, n * sizeof(char *));
		for (i = 0; i < n; i++) {
			if (!(system->in_name[i] = vips__temp_name(in_format)))
				return -1;
			if (vips_image_write_to_file(in[i], system->in_name[i], NULL))
				return -1;
		}
	}

	/* Make the output filename.
	 */
	if (system->out_format) {
		char filename[VIPS_PATH_MAX];
		char option_string[VIPS_PATH_MAX];

		vips__filename_split8(system->out_format, filename, option_string);
		if (!(system->out_name = vips__temp_name(filename)))
			return -1;
		system->out_name_options =
			g_strconcat(system->out_name, option_string, NULL);
	}

	g_strlcpy(cmd, system->cmd_format, VIPS_PATH_MAX);
	if (system->in)
		for (i = 0; i < VIPS_AREA(system->in)->n; i++)
			if (vips__substitute(cmd, VIPS_PATH_MAX, system->in_name[i])) {
				vips_error(class->nickname, "%s",
					_("unable to substitute input filename"));
				return -1;
			}
	if (system->out_name &&
		vips__substitute(cmd, VIPS_PATH_MAX, system->out_name)) {
		vips_error(class->nickname, "%s",
			_("unable to substitute output filename"));
		return -1;
	}

	/* Swap all "%%" in the string for a single "%". We need this for
	 * compatibility with older printf-based vips_system()s which
	 * needed a double %%.
	 */
	for (p = cmd; *p; p++)
		if (p[0] == '%' &&
			p[1] == '%')
			memmove(p, p + 1, strlen(p));

#ifdef DEBUG
	printf("vips_system_build: spawn <<%s>> ...\n", cmd);
#endif /*DEBUG*/
	if (!g_spawn_command_line_sync(cmd,
			&std_output, &std_error, &result, &error) ||
		result) {
#ifdef DEBUG
		printf("\t... failed\n");
#endif /*DEBUG*/

		if (error) {
			vips_error(class->nickname, "%s", error->message);
			g_error_free(error);
		}
		if (std_error) {
			g_strchomp(std_error);
			if (strcmp(std_error, "") != 0)
				vips_error(class->nickname, "error output: %s", std_error);
			VIPS_FREE(std_error);
		}
		if (std_output) {
			g_strchomp(std_output);
			if (strcmp(std_output, "") != 0)
				vips_error(class->nickname, "output: %s", std_output);
			VIPS_FREE(std_output);
		}
		vips_error_system(result, class->nickname,
			_("command \"%s\" failed"), cmd);

		return -1;
	}

#ifdef DEBUG
	printf("\t... success\n");
#endif /*DEBUG*/

	if (std_error) {
		g_strchomp(std_error);
		if (strcmp(std_error, "") != 0)
			g_warning("stderr output: %s", std_error);
	}
	if (std_output) {
		g_strchomp(std_output);
		g_object_set(system, "log", std_output, NULL);
	}

	VIPS_FREE(std_output);
	VIPS_FREE(std_error);

	if (system->out_name_options) {
		VipsImage *out;

		if (!(out = vips_image_new_from_file(system->out_name_options, NULL)))
			return -1;
		vips_image_set_delete_on_close(out, TRUE);
		g_object_set(system, "out", out, NULL);
	}

	return 0;
}

static VipsOperationFlags
vips_system_get_flags(VipsOperation *operation)
{
	VipsOperationFlags flags =
		VIPS_OPERATION_CLASS(vips_system_parent_class)->get_flags(operation);
	VipsSystem *system = (VipsSystem *) operation;

	if (system->cache)
		flags &= ~VIPS_OPERATION_NOCACHE;

	return flags;
}

static void
vips_system_class_init(VipsSystemClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(class);
	VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class);

	gobject_class->dispose = vips_system_dispose;
	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	vobject_class->nickname = "system";
	vobject_class->description = _("run an external command");
	vobject_class->build = vips_system_build;

	operation_class->get_flags = vips_system_get_flags;
	operation_class->flags = VIPS_OPERATION_NOCACHE;

	VIPS_ARG_BOXED(class, "in", 0,
		_("Input"),
		_("Array of input images"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsSystem, in),
		VIPS_TYPE_ARRAY_IMAGE);

	VIPS_ARG_IMAGE(class, "out", 1,
		_("Output"),
		_("Output image"),
		VIPS_ARGUMENT_OPTIONAL_OUTPUT,
		G_STRUCT_OFFSET(VipsSystem, out));

	VIPS_ARG_STRING(class, "cmd_format", 2,
		_("Command"),
		_("Command to run"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsSystem, cmd_format),
		NULL);

	VIPS_ARG_STRING(class, "in_format", 3,
		_("Input format"),
		_("Format for input filename"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsSystem, in_format),
		NULL);

	VIPS_ARG_STRING(class, "out_format", 4,
		_("Output format"),
		_("Format for output filename"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsSystem, out_format),
		NULL);

	VIPS_ARG_BOOL(class, "cache", 5,
		_("Cache"),
		_("Cache this call"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsSystem, cache),
		FALSE);

	VIPS_ARG_STRING(class, "log", 6,
		_("Log"),
		_("Command log"),
		VIPS_ARGUMENT_OPTIONAL_OUTPUT,
		G_STRUCT_OFFSET(VipsSystem, log),
		NULL);

}

static void
vips_system_init(VipsSystem *system)
{
}

/**
 * vips_system:
 * @cmd_format: command to run
 * @...: `NULL`-terminated list of optional named arguments
 *
 * [ctor@Image.system] runs a command, optionally passing a set of images in and
 * optionally getting an image back. The command's stdout is returned in @log.
 *
 * First, if @in is set, the array of images are written to files. See
 * [ctor@Image.new_temp_file] to see how temporary files are created.
 * If @in_format is something like `%s.png`, the file will be written in PNG
 * format. By default, @in_format is `%s.tif`.
 *
 * If @out_format is set, an output filename is formed in the same way. Any
 * trailing `[options]` are stripped from @out_format.
 *
 * The command string to run is made by substituting the first set of `%s`
 * in @cmd_format for the names of the input files, if @in is set, and then
 * the next `%s` for the output filename, if @out_format is set.
 * You can put a number between the `%` and the `s` to change the order
 * in which the substitution occurs.
 *
 * The command is executed with [`popen()`](man:popen(3)) and the output
 * captured in @log.
 *
 * After the command finishes, if @out_format is set, the output image is
 * opened and returned in @out. You can append `[options]` to @out_format to
 * control how this open happens.
 *
 * Closing @out image will automatically delete the output file.
 *
 * Finally the input images are deleted.
 *
 * If @cache is set, this call will be added to the libvips operation cache
 * and reused if possible.
 *
 * For example, this call will run the ImageMagick convert program on an
 * image, using JPEG files to pass images into and out of the convert command.
 *
 * ```c
 * VipsArrayImage *in;
 * VipsImage *out;
 * char *log;
 *
 * if (vips_system("convert %s -swirl 45 %s",
 *         "in", in,
 *         "out", &out,
 *         "in_format", "%s.jpg",
 *         "out_format", "%s.jpg",
 *         "cache", TRUE,
 *         "log", &log,
 *         NULL))
 *     error ...
 * ```
 *
 * ::: tip "Optional arguments"
 *     * @in: [struct@ArrayImage], array of input images
 *     * @out: [class@Image], output, image
 *     * @in_format: `gchararray`, write input files like this
 *     * @out_format: `gchararray`, write output filename like this
 *     * @cache: `gboolean`, cache this call
 *     * @log: `gchararray`, output, stdout of command is returned here
 *
 * Returns: 0 on success, -1 on failure.
 */
int
vips_system(const char *cmd_format, ...)
{
	va_list ap;
	int result;

	va_start(ap, cmd_format);
	result = vips_call_split("system", ap, cmd_format);
	va_end(ap);

	return result;
}
