/* VipsInsert
 *
 * Copyright: 1990, J. Cupitt
 *
 * Author: J. Cupitt
 * Written on: 02/08/1990
 * Modified on :
 * 31/8/93 JC
 *	- ANSIfied
 *	- Nicos' reformatting undone. Grr!
 * 22/12/94
 *	- modernised
 *	- now does IM_CODING_LABQ too
 * 22/6/95 JC
 *	- partialized
 * 10/2/02 JC
 *	- adapted for im_prepare_to() stuff
 * 14/4/04
 *	- sets Xoffset / Yoffset
 * 3/7/06
 * 	- add sanity range checks
 * 24/3/09
 * 	- added IM_CODING_RAD support
 * 30/1/10
 * 	- cleanups
 * 	- formatalike/bandalike
 * 	- gtkdoc
 * 29/9/11
 * 	- rewrite as a class
 * 	- add expand, bg options
 * 5/11/21
 * 	- add minimise for seq pipelines
 */

/*

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

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

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

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

#include "pconversion.h"

typedef struct _VipsInsert {
	VipsConversion parent_instance;

	/* Params.
	 */
	VipsImage *main;
	VipsImage *sub;
	int x;
	int y;
	gboolean expand;
	VipsArrayDouble *background;

	/* Pixel we paint calculated from background.
	 */
	VipsPel *ink;

	/* Inputs cast and banded up, plus a NULL at the end. main is 0, sub
	 * is 1.
	 */
	VipsImage *processed[3];

	/* Geometry.
	 */
	VipsRect rout;		/* Output space */
	VipsRect rimage[2]; /* Position of input in output */

	/* TRUE if we've minimised an input.
	 */
	gboolean minimised[2];

} VipsInsert;

typedef VipsConversionClass VipsInsertClass;

G_DEFINE_TYPE(VipsInsert, vips_insert, VIPS_TYPE_CONVERSION);

/* Trivial case: we just need pels from one of the inputs.
 */
static int
vips__insert_just_one(VipsRegion *out_region, VipsRegion *ir, int x, int y)
{
	VipsRect need;

	/* Find the part of pos we need.
	 */
	need = out_region->valid;
	need.left -= x;
	need.top -= y;
	if (vips_region_prepare(ir, &need))
		return -1;

	/* Attach our output to it.
	 */
	if (vips_region_region(out_region, ir,
			&out_region->valid, need.left, need.top))
		return -1;

	return 0;
}

/* Paste in parts of ir that fall within out_region -- ir is an input REGION
 * for an image positioned at pos within out_region.
 *
 * Also used by vips_arrayjoin.
 */
int
vips__insert_paste_region(VipsRegion *out_region,
	VipsRegion *ir, VipsRect *pos)
{
	VipsRect ovl;

	/* Does any of the sub-image appear in the area we have been asked
	 * to make?
	 */
	vips_rect_intersectrect(&out_region->valid, pos, &ovl);
	if (!vips_rect_isempty(&ovl)) {
		/* Find the part of in we need.
		 */
		ovl.left -= pos->left;
		ovl.top -= pos->top;

		/* Paint this area of pixels into out_region.
		 */
		if (vips_region_prepare_to(ir, out_region, &ovl,
				ovl.left + pos->left, ovl.top + pos->top))
			return -1;
	}

	return 0;
}

static int
vips_insert_gen(VipsRegion *out_region,
	void *seq, void *a, void *b, gboolean *stop)
{
	VipsRegion **ir = (VipsRegion **) seq;
	VipsRect *r = &out_region->valid;
	VipsInsert *insert = (VipsInsert *) b;
	VipsConversion *conversion = VIPS_CONVERSION(insert);

	int i;

	/* Three cases:
	 *
	 * 1. If r is entirely within sub, we can just paint from sub.
	 * 2. If r is entirely within main and does not touch sub, we can
	 *    paint from main.
	 * 3. We must paint from both, and the background.
	 */
	if (vips_rect_includesrect(&insert->rimage[1], r)) {
		/* Just the subimage.
		 */
		if (vips__insert_just_one(out_region, ir[1],
				insert->rimage[1].left, insert->rimage[1].top))
			return -1;
	}
	else if (vips_rect_includesrect(&insert->rimage[0], r) &&
		!vips_rect_overlapsrect(&insert->rimage[1], r)) {
		/* Just the main image.
		 */
		if (vips__insert_just_one(out_region, ir[0],
				insert->rimage[0].left, insert->rimage[0].top))
			return -1;
	}
	else {
		/* Output requires both (or neither) input. If it is not
		 * entirely inside both the main and the sub, then there is
		 * going to be some background.
		 */
		vips_region_paint_pel(out_region, r, insert->ink);

		/* Paste the background first.
		 */
		for (i = 0; i < 2; i++)
			if (vips__insert_paste_region(out_region, ir[i],
					&insert->rimage[i]))
				return -1;
	}

	/* See arrayjoin for almost this code again. Move into conversion.c?
	 */
	if (vips_image_is_sequential(conversion->out))
		for (i = 0; i < 2; i++) {
			int bottom_edge =
				VIPS_RECT_BOTTOM(&insert->rimage[i]);

			/* 1024 is a generous margin. 256 is too small.
			 */
			if (!insert->minimised[i] &&
				r->top > bottom_edge + 1024) {
				insert->minimised[i] = TRUE;
				vips_image_minimise_all(insert->processed[i]);
			}
		}

	return 0;
}

/* Make a pair of vector constants into a set of formatted pixels. bands can
 * be 3 while n is 1, meaning expand the constant to the number of bands.
 * imag can be NULL, meaning all zero for the imaginary component.
 */
VipsPel *
vips__vector_to_pels(const char *domain,
	int bands, VipsBandFormat format, VipsCoding coding,
	double *real, double *imag, int n)
{
	/* Run our pipeline relative to this.
	 */
	VipsImage *context = vips_image_new();

	VipsImage **t = (VipsImage **)
		vips_object_local_array(VIPS_OBJECT(context), 8);

	VipsImage *in;
	double *ones;
	VipsPel *result;
	int i;

#ifdef VIPS_DEBUG
	printf("vips__vector_to_pels: starting\n");
#endif /*VIPS_DEBUG*/

	ones = VIPS_ARRAY(context, n, double);
	for (i = 0; i < n; i++)
		ones[i] = 1.0;

	/* Make the real and imaginary parts.
	 */
	if (vips_black(&t[0], 1, 1, "bands", bands, NULL) ||
		vips_linear(t[0], &t[1], ones, real, n, NULL)) {
		g_object_unref(context);
		return NULL;
	}
	in = t[1];

	if (imag) {
		if (vips_black(&t[2], 1, 1, "bands", bands, NULL) ||
			vips_linear(t[2], &t[3], ones, imag, n, NULL) ||
			vips_complexform(in, t[3], &t[4], NULL)) {
			g_object_unref(context);
			return NULL;
		}
		in = t[4];
	}

	/* Cast to the output type and coding.
	 */
	if (vips_cast(in, &t[5], format, NULL) ||
		vips_image_encode(t[5], &t[6], coding)) {
		g_object_unref(context);
		return NULL;
	}
	in = t[6];

	/* Write to memory, copy to output buffer.
	 */
	vips_image_set_int(in, "hide-progress", 1);
	if (!(t[7] = vips_image_new_memory()) ||
		vips_image_write(in, t[7])) {
		g_object_unref(context);
		return NULL;
	}
	in = t[7];

	if (!(result =
				VIPS_ARRAY(NULL, VIPS_IMAGE_SIZEOF_PEL(in), VipsPel))) {
		g_object_unref(context);
		return NULL;
	}

	memcpy(result, in->data, VIPS_IMAGE_SIZEOF_PEL(in));

#ifdef VIPS_DEBUG
	{
		int i;

		printf("vips__vector_to_ink:\n");
		printf("\t(real, imag) = ");
		for (i = 0; i < n; i++)
			printf("(%g, %g) ", real[i], imag ? imag[i] : 0);
		printf("\n");
		printf("\tink = ");
		for (i = 0; i < VIPS_IMAGE_SIZEOF_PEL(in); i++)
			printf("%d ", result[i]);
		printf("\n");
	}
#endif /*VIPS_DEBUG*/

	g_object_unref(context);

	return result;
}

static void
vips__vector_to_ink_cb(VipsObject *object, char *buf)
{
	g_free(buf);
}

/* Calculate a pixel for an image from a vec of double. Valid while im is
 * valid.
 */
VipsPel *
vips__vector_to_ink(const char *domain,
	VipsImage *im, double *real, double *imag, int n)
{
	int bands;
	VipsBandFormat format;
	VipsPel *result;

	vips_image_decode_predict(im, &bands, &format);

	if (!(result = vips__vector_to_pels(domain,
			  bands, format, im->Coding, real, imag, n)))
		return NULL;

	g_signal_connect(im, "postclose",
		G_CALLBACK(vips__vector_to_ink_cb), result);

	return result;
}

static int
vips_insert_build(VipsObject *object)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);
	VipsConversion *conversion = VIPS_CONVERSION(object);
	VipsInsert *insert = (VipsInsert *) object;
	VipsImage **t = (VipsImage **) vips_object_local_array(object, 6);

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

	if (vips_image_pio_input(insert->main) ||
		vips_image_pio_input(insert->sub) ||
		vips_check_bands_1orn(class->nickname, insert->main, insert->sub) ||
		vips_check_coding_known(class->nickname, insert->main) ||
		vips_check_coding_same(class->nickname, insert->main, insert->sub))
		return -1;

	/* Cast our input images up to a common format and bands.
	 */
	if (vips__formatalike(insert->main, insert->sub, &t[0], &t[1]) ||
		vips__bandalike(class->nickname, t[0], t[1], &t[2], &t[3]))
		return -1;
	insert->processed[0] = t[2];
	insert->processed[1] = t[3];

	/* Joins can get very wide (eg. consider joining a set of tiles
	 * horizontally to make a large image), we don't want mem use to shoot
	 * up. SMALLTILE will guarantee we keep small and local.
	 */
	if (vips_image_pipeline_array(conversion->out,
			VIPS_DEMAND_STYLE_SMALLTILE, insert->processed))
		return -1;

	/* Calculate geometry.
	 */
	insert->rimage[0].left = 0;
	insert->rimage[0].top = 0;
	insert->rimage[0].width = insert->processed[0]->Xsize;
	insert->rimage[0].height = insert->processed[0]->Ysize;

	insert->rimage[1].left = insert->x;
	insert->rimage[1].top = insert->y;
	insert->rimage[1].width = insert->processed[1]->Xsize;
	insert->rimage[1].height = insert->processed[1]->Ysize;

	if (insert->expand) {
		/* Expand output to bounding box of these two.
		 */
		vips_rect_unionrect(&insert->rimage[0], &insert->rimage[1],
			&insert->rout);

		/* Translate origin to top LH corner of rout.
		 */
		insert->rimage[0].left -= insert->rout.left;
		insert->rimage[0].top -= insert->rout.top;
		insert->rimage[1].left -= insert->rout.left;
		insert->rimage[1].top -= insert->rout.top;
		insert->rout.left = 0;
		insert->rout.top = 0;
	}
	else
		insert->rout = insert->rimage[0];

	conversion->out->Xsize = insert->rout.width;
	conversion->out->Ysize = insert->rout.height;

	if (!(insert->ink = vips__vector_to_ink(
			  class->nickname, conversion->out,
			  (double *) VIPS_ARRAY_ADDR(insert->background, 0), NULL,
			  VIPS_AREA(insert->background)->n)))
		return -1;

	if (vips_image_generate(conversion->out,
			vips_start_many, vips_insert_gen, vips_stop_many,
			insert->processed, insert))
		return -1;

	return 0;
}

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

	VIPS_DEBUG_MSG("vips_insert_class_init\n");

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

	vobject_class->nickname = "insert";
	vobject_class->description =
		_("insert image @sub into @main at @x, @y");
	vobject_class->build = vips_insert_build;

	operation_class->flags = VIPS_OPERATION_SEQUENTIAL;

	VIPS_ARG_IMAGE(class, "main", 0,
		_("Main"),
		_("Main input image"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsInsert, main));

	VIPS_ARG_IMAGE(class, "sub", 1,
		_("Sub-image"),
		_("Sub-image to insert into main image"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsInsert, sub));

	VIPS_ARG_INT(class, "x", 3,
		_("X"),
		_("Left edge of sub in main"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsInsert, x),
		-VIPS_MAX_COORD, VIPS_MAX_COORD, 0);

	VIPS_ARG_INT(class, "y", 4,
		_("Y"),
		_("Top edge of sub in main"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsInsert, y),
		-VIPS_MAX_COORD, VIPS_MAX_COORD, 0);

	VIPS_ARG_BOOL(class, "expand", 5,
		_("Expand"),
		_("Expand output to hold all of both inputs"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsInsert, expand),
		FALSE);

	VIPS_ARG_BOXED(class, "background", 6,
		_("Background"),
		_("Color for new pixels"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsInsert, background),
		VIPS_TYPE_ARRAY_DOUBLE);
}

static void
vips_insert_init(VipsInsert *insert)
{
	/* Init our instance fields.
	 */
	insert->background = vips_array_double_newv(1, 0.0);
}

/**
 * vips_insert: (method)
 * @main: big image
 * @sub: small image
 * @out: (out): output image
 * @x: left position of @sub
 * @y: top position of @sub
 * @...: `NULL`-terminated list of optional named arguments
 *
 * Insert @sub into @main at position @x, @y.
 *
 * Normally @out shows the whole of @main. If @expand is `TRUE` then @out is
 * made large enough to hold all of @main and @sub.
 * Any areas of @out not coming from
 * either @main or @sub are set to @background (default 0).
 *
 * If @sub overlaps @main,
 * @sub will appear on top of @main.
 *
 * If the number of bands differs, one of the images
 * must have one band. In this case, an n-band image is formed from the
 * one-band image by joining n copies of the one-band image together, and then
 * the two n-band images are operated upon.
 *
 * The two input images are cast up to the smallest common type (see table
 * Smallest common format in
 * [arithmetic](libvips-arithmetic.html)).
 *
 * ::: tip "Optional arguments"
 *     * @expand: `gdouble`, expand output to hold whole of both images
 *     * @background: [struct@ArrayDouble], colour for new pixels
 *
 * ::: seealso
 *     [method@Image.join], [method@Image.embed], [method@Image.extract_area].
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_insert(VipsImage *main, VipsImage *sub, VipsImage **out,
	int x, int y, ...)
{
	va_list ap;
	int result;

	va_start(ap, y);
	result = vips_call_split("insert", ap, main, sub, out, x, y);
	va_end(ap);

	return result;
}
