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

/*

	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

 */

#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;

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

	vips_strncpy(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));

	if (!g_spawn_command_line_sync(cmd,
			&std_output, &std_error, &result, &error) ||
		result) {
		if (error) {
			vips_error(class->nickname, "%s", error->message);
			g_error_free(error);
		}
		if (std_error) {
			vips__chomp(std_error);
			if (strcmp(std_error, "") != 0)
				vips_error(class->nickname,
					"error output: %s", std_error);
			VIPS_FREE(std_error);
		}
		if (std_output) {
			vips__chomp(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;
	}

	if (std_error) {
		vips__chomp(std_error);
		if (strcmp(std_error, "") != 0)
			g_warning(_("stderr output: %s"), std_error);
	}
	if (std_output) {
		vips__chomp(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 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;

	/* Commands can have side-effects, so don't cache them.
	 */
	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", 2,
		_("Input format"),
		_("Format for input filename"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsSystem, in_format),
		NULL);

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

	VIPS_ARG_STRING(class, "log", 2,
		_("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
 *
 * Optional arguments:
 *
 * * @in: array of input images
 * * @out: output image
 * * @in_format: write input files like this
 * * @out_format: write output filename like this
 * * @log: stdout of command is returned here
 *
 * vips_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
 * vips_image_new_temp_file() to see how temporary files are created.
 * If @in_format is
 * something like &percnt;s.png, the file will be written in PNG format. By
 * default, @in_format is &percnt;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 &percnt;s
 * in @cmd_format for the names of the input files, if @in is set, and then
 * the next &percnt;s for the output filename, if @out_format is set.
 * You can put a number between the &percnt; and the s to change the order
 * in which the substitution occurs.
 *
 * The command is executed with popen() 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.
 *
 * 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.
 *
 * |[
 * 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",
 *         "log", &log,
 *         NULL))
 *     error ...
 * ]|
 *
 * 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;
}
