/* Manage pipelines of partial images.
 *
 * J.Cupitt, 17/4/93.
 * 1/7/93 JC
 *	- adapted for partial v2
 *	- ANSIfied
 * 6/7/93 JC
 *	- im_setupout() conventions clarified - see autorewind in
 *	  im_iocheck().
 * 20/7/93 JC
 *	- eval callbacks added
 * 7/9/93 JC
 *	- demand hint mechanism added
 * 25/10/93
 *	- asynchronous output mechanisms removed, as no observable speed-up
 * 9/5/94
 *      - new thread stuff added, with a define to turn it off
 * 15/8/94
 *	- start & stop functions can now be NULL for no-op
 * 7/10/94 JC
 *	- evalend callback system added
 * 23/12/94 JC
 *	- IM_ARRAY uses added
 * 22/2/95 JC
 *	- im_fill_copy() added
 *	- im_region_region() uses modified
 * 24/4/95 JC & KM
 *	- im_fill_lines() bug removed
 * 30/8/96 JC
 *	- revised and simplified ... some code shared with im_iterate()
 *	- new im_generate_region() added
 * 2/3/98 JC
 *	- IM_ANY added
 * 20/7/99 JC
 *	- tile geometry made into ints for easy tuning
 * 30/7/99 RP JC
 *	- threads reorganised for POSIX
 * 29/9/99 JC
 *	- threadgroup stuff added
 * 15/4/04
 *	- better how-many-pixels-calculated
 * 27/11/06
 * 	- merge background write stuff
 * 7/11/07
 * 	- new start/end eval callbacks
 * 7/10/09
 * 	- gtkdoc comments
 * 16/4/10
 * 	- remove threadgroup stuff
 * 24/3/11
 * 	- move demand_hint stuff in here
 * 	- move to vips_ namespace
 * 7/7/12
 * 	- lock around link make/break so we can process an image from many
 * 	  threads
 */

/*

	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 VIPS_DEBUG
#define DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /*HAVE_UNISTD_H*/
#ifdef HAVE_IO_H
#include <io.h>
#endif /*HAVE_IO_H*/

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

/* Max number of images we can handle.
 */
#define MAX_IMAGES (1000)

/* Make an upstream/downstream link. upstream is one of downstream's inputs.
 */
static void
vips__link_make(VipsImage *image_up, VipsImage *image_down)
{
	g_assert(image_up);
	g_assert(image_down);

	image_up->downstream =
		g_slist_prepend(image_up->downstream, image_down);
	image_down->upstream =
		g_slist_prepend(image_down->upstream, image_up);

	/* Propagate the progress indicator.
	 */
	if (image_up->progress_signal &&
		!image_down->progress_signal)
		image_down->progress_signal = image_up->progress_signal;
}

static void *
vips__link_break(VipsImage *image_up, VipsImage *image_down, void *b)
{
	g_assert(image_up);
	g_assert(image_down);

	g_assert(g_slist_find(image_up->downstream, image_down));
	g_assert(g_slist_find(image_down->upstream, image_up));

	image_up->downstream =
		g_slist_remove(image_up->downstream, image_down);
	image_down->upstream =
		g_slist_remove(image_down->upstream, image_up);

	/* Unlink the progress chain.
	 */
	if (image_down->progress_signal &&
		image_down->progress_signal == image_up->progress_signal)
		image_down->progress_signal = NULL;

	return NULL;
}

static void *
vips__link_break_rev(VipsImage *image_down, VipsImage *image_up, void *b)
{
	return vips__link_break(image_up, image_down, b);
}

/* A VipsImage is going ... break all links.
 */
void
vips__link_break_all(VipsImage *image)
{
	g_mutex_lock(&vips__global_lock);

	vips_slist_map2(image->upstream,
		(VipsSListMap2Fn) vips__link_break, image, NULL);
	vips_slist_map2(image->downstream,
		(VipsSListMap2Fn) vips__link_break_rev, image, NULL);

	g_assert(!image->upstream);
	g_assert(!image->downstream);

	g_mutex_unlock(&vips__global_lock);
}

typedef struct _LinkMap {
	gboolean upstream;
	int serial;
	VipsSListMap2Fn fn;
	void *a;
	void *b;
} LinkMap;

static void *
vips__link_mapp(VipsImage *image, LinkMap *map, void *b)
{
	void *res;

	/* Loop?
	 */
	if (image->serial == map->serial)
		return NULL;
	image->serial = map->serial;

	if ((res = map->fn(image, map->a, map->b)))
		return res;

	return vips_slist_map2(map->upstream
			? image->upstream
			: image->downstream,
		(VipsSListMap2Fn) vips__link_mapp, map, NULL);
}

static void *
vips__link_map_cb(VipsImage *image, GSList **images, void *b)
{
	*images = g_slist_prepend(*images, image);

	return NULL;
}

/* Apply a function to an image and all upstream or downstream images,
 * direct and indirect.
 */
void *
vips__link_map(VipsImage *image, gboolean upstream,
	VipsSListMap2Fn fn, void *a, void *b)
{
	static int serial = 0;

	LinkMap map;
	GSList *images;
	GSList *p;
	void *result;

	images = NULL;

	/* The function might do anything, including removing images
	 * or invalidating other images, so we can't trigger them from within
	 * the image loop. Instead we collect a list of images, ref them,
	 * run the functions, and unref.
	 */

	map.upstream = upstream;
	map.fn = (VipsSListMap2Fn) vips__link_map_cb;
	map.a = (void *) &images;
	map.b = NULL;

	/* We will be walking the tree of images and updating the ->serial
	 * member. There will be intense confusion if two threads try to do
	 * this at the same time.
	 */
	g_mutex_lock(&vips__global_lock);

	serial += 1;
	map.serial = serial;

	vips__link_mapp(image, &map, NULL);

	for (p = images; p; p = p->next)
		g_object_ref(p->data);

	g_mutex_unlock(&vips__global_lock);

	result = vips_slist_map2(images, fn, a, b);

	for (p = images; p; p = p->next)
		g_object_unref(p->data);
	g_slist_free(images);

	return result;
}

/* We have to have this as a separate entry point so we can support the old
 * vips7 API.
 */
void
vips__demand_hint_array(VipsImage *image,
	int int_hint, VipsImage **in)
{
	VipsDemandStyle hint = (VipsDemandStyle) int_hint;

	int i, len, nany;
	VipsDemandStyle set_hint;

	/* How many input images are there? And how many are ANY?
	 */
	for (i = 0, len = 0, nany = 0; in[i]; i++, len++)
		if (in[i]->dhint == VIPS_DEMAND_STYLE_ANY)
			nany++;

	/* Find the most restrictive of all the hints available to us.
	 *
	 * We have tried to be smarter about this in the past -- for example,
	 * detecting all ANY inputs and ignoring the hint in this case, but
	 * there are inevitably odd cases which cause problems. For example,
	 * new_from_memory, resize, affine, write_to_memory would run with
	 * FATSTRIP.
	 */
	set_hint = hint;
	for (i = 0; i < len; i++)
		set_hint = (VipsDemandStyle)
			VIPS_MIN((int) set_hint, (int) in[i]->dhint);

	image->dhint = set_hint;

#ifdef DEBUG
	printf("vips_image_pipeline_array: set dhint for \"%s\" to %s\n",
		image->filename,
		vips_enum_nick(VIPS_TYPE_DEMAND_STYLE, image->dhint));
	printf("\toperation requested %s\n",
		vips_enum_nick(VIPS_TYPE_DEMAND_STYLE, hint));
	printf("\tinputs were:\n");
	printf("\t");
	for (i = 0; in[i]; i++)
		printf("%s ", vips_enum_nick(VIPS_TYPE_DEMAND_STYLE, in[i]->dhint));
	printf("\n");
#endif /*DEBUG*/

	/* im depends on all these ims.
	 */
	g_mutex_lock(&vips__global_lock);
	for (i = 0; i < len; i++)
		vips__link_make(in[i], image);
	g_mutex_unlock(&vips__global_lock);

	/* Set a flag on the image to say we remembered to call this thing.
	 * vips_image_generate() and friends check this.
	 */
	image->hint_set = TRUE;
}

/**
 * vips_image_pipeline_array:
 * @image: (out): output image
 * @hint: demand hint for @image
 * @in: (array zero-terminated=1): `NULL`-terminated array of input images
 *
 * Add an image to a pipeline. @image depends on all of the images in @in,
 * @image prefers to supply pixels according to @hint.
 *
 * Operations can set demand hints, that is, hints to the VIPS IO system about
 * the type of region geometry they work best with. For example,
 * operations which transform coordinates will usually work best with
 * [enum@Vips.DemandStyle.SMALLTILE], operations which work on local windows of
 * pixels will like [enum@Vips.DemandStyle.FATSTRIP].
 *
 * Header fields in @image are set from the fields in @in, with lower-numbered
 * images in @in taking priority.
 * For example, if @in[0] and @in[1] both have an item
 * called "icc-profile", it's the profile attached to @in[0] that will end up
 * on @image.
 * Image history is completely copied from all @in. @image will have the history
 * of all the input images.
 * The array of input images can be empty, meaning @image is at the start of a
 * pipeline.
 *
 * VIPS uses the list of input images to build the tree of operations it needs
 * for the cache invalidation system.
 *
 * ::: seealso
 *     [method@Image.pipelinev], [method@Image.generate].
 *
 * Returns: 0 on success, -1 on error.
 */
int
vips_image_pipeline_array(VipsImage *image,
	VipsDemandStyle hint, VipsImage **in)
{
	if (!vips_object_sanity(VIPS_OBJECT(image)))
		return -1;

	/* This function can be called more than once per output image. For
	 * example, jpeg header load will call this once on ->out to set the
	 * default hint, then later call it again to connect the output image
	 * up to the real image.
	 *
	 * It's only ever called first time with in[0] == NULL and second time
	 * with a real value for @in.
	 */
	vips__demand_hint_array(image, hint, in);

	if (in[0] &&
		vips__image_copy_fields_array(image, in))
		return -1;

	if (vips__reorder_set_input(image, in))
		return -1;

	return 0;
}

/**
 * vips_image_pipelinev:
 * @image: output image of pipeline
 * @hint: hint for this image
 * @...: `NULL`-terminated list of input images
 *
 * Build an array and call [func@Image.pipeline_array].
 *
 * ::: seealso
 *     [method@Image.generate].
 */
int
vips_image_pipelinev(VipsImage *image, VipsDemandStyle hint, ...)
{
	va_list ap;
	int i;
	VipsImage *ar[MAX_IMAGES];

	va_start(ap, hint);
	for (i = 0; i < MAX_IMAGES &&
		 (ar[i] = va_arg(ap, VipsImage *));
		 i++)
		;
	va_end(ap);
	if (i == MAX_IMAGES) {
		g_warning("too many images");

		/* Make sure we have a sentinel there.
		 */
		ar[i - 1] = NULL;
	}

	return vips_image_pipeline_array(image, hint, ar);
}

/**
 * vips_start_one:
 * @out: image to generate
 * @a: user data
 * @b: user data
 *
 * Start function for one image in. Input image is @a.
 *
 * ::: seealso
 *     [method@Image.generate].
 */
void *
vips_start_one(VipsImage *out, void *a, void *b)
{
	VipsImage *in = (VipsImage *) a;

	return vips_region_new(in);
}

/**
 * vips_stop_one:
 * @seq: sequence value
 * @a: user data
 * @b: user data
 *
 * Stop function for one image in. Input image is @a.
 *
 * ::: seealso
 *     [method@Image.generate].
 */
int
vips_stop_one(void *seq, void *a, void *b)
{
	VipsRegion *reg = (VipsRegion *) seq;

	g_object_unref(reg);

	return 0;
}

/**
 * vips_stop_many:
 * @seq: sequence value
 * @a: user data
 * @b: user data
 *
 * Stop function for many images in. @a is a pointer to
 * a `NULL`-terminated array of input images.
 *
 * ::: seealso
 *     [method@Image.generate].
 */
int
vips_stop_many(void *seq, void *a, void *b)
{
	VipsRegion **ar = (VipsRegion **) seq;

	if (ar) {
		int i;

		for (i = 0; ar[i]; i++)
			g_object_unref(ar[i]);
		g_free((char *) ar);
	}

	return 0;
}

/**
 * vips_start_many:
 * @out: image to generate
 * @a: user data
 * @b: user data
 *
 * Start function for many images in. @a is a pointer to
 * a `NULL`-terminated array of input images.
 *
 * ::: seealso
 *     [method@Image.generate], [func@allocate_input_array]
 */
void *
vips_start_many(VipsImage *out, void *a, void *b)
{
	VipsImage **in = (VipsImage **) a;

	int i, n;
	VipsRegion **ar;

	/* How many images?
	 */
	for (n = 0; in[n]; n++)
		;

	/* Allocate space for region array.
	 */
	if (!(ar = VIPS_ARRAY(NULL, n + 1, VipsRegion *)))
		return NULL;

	/* Create a set of regions.
	 */
	for (i = 0; i < n; i++)
		if (!(ar[i] = vips_region_new(in[i]))) {
			vips_stop_many(ar, NULL, NULL);
			return NULL;
		}
	ar[n] = NULL;

	return ar;
}

/**
 * vips_allocate_input_array:
 * @out: free array when this image closes
 * @...: `NULL`-terminated list of input images
 *
 * Convenience function -- make a `NULL`-terminated array of input images.
 * Use with [func@start_many].
 *
 * ::: seealso
 *     [method@Image.generate], [func@start_many].
 *
 * Returns: (transfer none) (nullable): `NULL`-terminated array of images.
 *   Do not free the result.
 */
VipsImage **
vips_allocate_input_array(VipsImage *out, ...)
{
	va_list ap;
	VipsImage **ar;
	int i, n;

	/* Count input images.
	 */
	va_start(ap, out);
	for (n = 0; va_arg(ap, VipsImage *); n++)
		;
	va_end(ap);

	/* Allocate array.
	 */
	if (!(ar = VIPS_ARRAY(out, n + 1, VipsImage *)))
		return NULL;

	/* Fill array.
	 */
	va_start(ap, out);
	for (i = 0; i < n; i++)
		ar[i] = va_arg(ap, VipsImage *);
	va_end(ap);
	ar[n] = NULL;

	return ar;
}

/**
 * VipsStartFn:
 * @out: image being calculated
 * @a: user data
 * @b: user data
 *
 * Start a new processing sequence for this generate function. This allocates
 * per-thread state, such as an input region.
 *
 * ::: seealso
 *     [func@start_one], [func@start_many].
 *
 * Returns: a new sequence value
 */

/**
 * VipsGenerateFn:
 * @out: [class@Region] to fill
 * @seq: sequence value
 * @a: user data
 * @b: user data
 * @stop: set this to stop processing
 *
 * Fill @out->valid with pixels. @seq contains per-thread state, such as the
 * input regions. Set @stop to `TRUE` to stop processing.
 *
 * ::: seealso
 *     [method@Image.generate], [func@stop_many].
 *
 * Returns: 0 on success, -1 on error.
 */

/**
 * VipsStopFn:
 * @seq: sequence value
 * @a: user data
 * @b: user data
 *
 * Stop a processing sequence. This frees
 * per-thread state, such as an input region.
 *
 * ::: seealso
 *     [func@stop_one], [func@stop_many].
 *
 * Returns: 0 on success, -1 on error.
 */

/* A write function for VIPS images. Just write() the pixel data.
 */
static int
write_vips(VipsRegion *region, VipsRect *area, void *a)
{
	size_t count;
	void *buf;

	count = (size_t) region->bpl * area->height;
	buf = VIPS_REGION_ADDR(region, 0, area->top);

	do {
		// write() uses int not size_t on windows, so we need to chunk
		// ... max 1gb, why not
		int chunk_size = VIPS_MIN(1024 * 1024 * 1024, count);
		gint64 nwritten = write(region->im->fd, buf, chunk_size);

		/* n == 0 isn't strictly an error, but we treat it as
		 * one to make sure we don't get stuck in this loop.
		 */
		if (nwritten <= 0)
			return errno;

		buf = (void *) ((char *) buf + nwritten);
		count -= nwritten;
	} while (count > 0);

	return 0;
}

/**
 * vips_image_generate:
 * @image: generate this image
 * @start_fn: (scope async): start sequences with this function
 * @generate_fn: (scope async): generate pixels with this function
 * @stop_fn: (scope async): stop sequences with this function
 * @a: user data
 * @b: user data
 *
 * Generates an image. The action depends on the image type.
 *
 * For images created with [ctor@Image.new], [method@Image.generate] just
 * attaches the start/generate/stop callbacks and returns.
 *
 * For images created with [ctor@Image.new_memory], memory is allocated for
 * the whole image and it is entirely generated using [func@sink_memory].
 *
 * For images created with [ctor@Image.new_temp_file] and friends, memory for
 * a few scanlines is allocated and
 * [method@Image.sink_disc] used to generate the image in small chunks. As each
 * chunk is generated, it is written to disc.
 *
 * ::: seealso
 *     [func@sink_memory], [ctor@Image.new], [method@Region.prepare].
 *
 * Returns: 0 on success, or -1 on error.
 */
int
vips_image_generate(VipsImage *image,
	VipsStartFn start_fn, VipsGenerateFn generate_fn, VipsStopFn stop_fn,
	void *a, void *b)
{
	int res;

	VIPS_DEBUG_MSG("vips_image_generate: %p\n", image);

	g_assert(generate_fn);

	if (!vips_object_sanity(VIPS_OBJECT(image)))
		return -1;

	if (!image->hint_set) {
		vips_error("vips_image_generate", "%s", _("demand hint not set"));
		return -1;
	}

	/* We don't use this, but make sure it's set in case any old binaries
	 * are expecting it.
	 */
	image->Bbits = vips_format_sizeof(image->BandFmt) << 3;

	/* Look at output type to decide our action.
	 */
	switch (image->dtype) {
	case VIPS_IMAGE_PARTIAL:
		/* Output to partial image. Just attach functions and return.
		 */
		if (image->generate_fn ||
			image->start_fn ||
			image->stop_fn) {
			vips_error("VipsImage",
				"%s", _("generate() called twice"));
			return -1;
		}

		image->start_fn = start_fn;
		image->generate_fn = generate_fn;
		image->stop_fn = stop_fn;
		image->client1 = a;
		image->client2 = b;

		VIPS_DEBUG_MSG("vips_image_generate: "
					   "attaching partial callbacks\n");

		if (vips_image_written(image))
			return -1;

		break;

	case VIPS_IMAGE_SETBUF:
	case VIPS_IMAGE_SETBUF_FOREIGN:
	case VIPS_IMAGE_MMAPINRW:
	case VIPS_IMAGE_OPENOUT:
		/* Eval now .. sanity check.
		 */
		if (image->generate_fn ||
			image->start_fn ||
			image->stop_fn) {
			vips_error("VipsImage",
				"%s", _("generate() called twice"));
			return -1;
		}

		/* Attach callbacks.
		 */
		image->start_fn = start_fn;
		image->generate_fn = generate_fn;
		image->stop_fn = stop_fn;
		image->client1 = a;
		image->client2 = b;

		if (vips_image_write_prepare(image))
			return -1;

		if (image->dtype == VIPS_IMAGE_OPENOUT)
			res = vips_sink_disc(image, write_vips, NULL);
		else
			res = vips_sink_memory(image);

		/* Error?
		 */
		if (res)
			return -1;

		/* Must come before we rewind.
		 */
		if (vips_image_written(image))
			return -1;

		/* We've written to image ... rewind it ready for reading.
		 */
		if (vips_image_pio_input(image))
			return -1;

		break;

	default:
		/* Not a known output style.
		 */
		vips_error("VipsImage",
			_("unable to output to a %s image"),
			vips_enum_nick(VIPS_TYPE_IMAGE_TYPE,
				image->dtype));
		return -1;
	}

	return 0;
}
