/* horizontal and vertical projection
 *
 * 20/4/06
 *	- from im_histgr()
 * 25/3/10
 * 	- gtkdoc
 * 	- small celanups
 * 11/9/13
 * 	- redo as a class, from vips_hist_find()
 */

/*

	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>
#include <string.h>

#include <vips/vips.h>

#include "statistic.h"

struct _Project;

typedef struct {
	/* Horizontal array: sums of all columns.
	 */
	void *column_sums;

	/* Vertical array: sums of all rows.
	 */
	void *row_sums;
} Histogram;

typedef struct _VipsProject {
	VipsStatistic parent_instance;

	/* Main image histogram. Subhists accumulate to this.
	 */
	Histogram *hist;

	/* Write sums here.
	 */
	VipsImage *columns;
	VipsImage *rows;

} VipsProject;

typedef VipsStatisticClass VipsProjectClass;

G_DEFINE_TYPE(VipsProject, vips_project, VIPS_TYPE_STATISTIC);

/* Save a bit of typing.
 */
#define UI VIPS_FORMAT_UINT
#define I VIPS_FORMAT_INT
#define D VIPS_FORMAT_DOUBLE
#define N VIPS_FORMAT_NOTSET

static const VipsBandFormat vips_project_format_table[10] = {
	/* Band format:  UC  C  US  S  UI  I  F  X  D  DX */
	/* Promotion: */ UI, I, UI, I, UI, I, D, N, D, N
};

static Histogram *
histogram_new(VipsProject *project)
{
	VipsStatistic *statistic = VIPS_STATISTIC(project);
	VipsImage *in = statistic->ready;
	VipsBandFormat outfmt = vips_project_format_table[in->BandFmt];
	int psize = vips_format_sizeof(outfmt) * in->Bands;

	Histogram *hist;

	if (!(hist = VIPS_NEW(project, Histogram)))
		return NULL;
	hist->column_sums = VIPS_ARRAY(project, psize * in->Xsize, guchar);
	hist->row_sums = VIPS_ARRAY(project, psize * in->Ysize, guchar);
	if (!hist->column_sums ||
		!hist->row_sums)
		return NULL;

	memset(hist->column_sums, 0, (size_t) psize * in->Xsize);
	memset(hist->row_sums, 0, (size_t) psize * in->Ysize);

	return hist;
}

static int
vips_project_build(VipsObject *object)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);
	VipsStatistic *statistic = VIPS_STATISTIC(object);
	VipsProject *project = (VipsProject *) object;

	int y;

	if (statistic->in &&
		vips_check_noncomplex(class->nickname, statistic->in))
		return -1;

	g_object_set(object,
		"columns", vips_image_new(),
		"rows", vips_image_new(),
		NULL);

	/* main hist made on first thread start.
	 */

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

	/* Make the output image.
	 */
	if (vips_image_pipelinev(project->columns,
			VIPS_DEMAND_STYLE_ANY, statistic->ready, NULL) ||
		vips_image_pipelinev(project->rows,
			VIPS_DEMAND_STYLE_ANY, statistic->ready, NULL))
		return -1;
	project->columns->Ysize = 1;
	project->columns->BandFmt =
		vips_project_format_table[statistic->ready->BandFmt];
	project->columns->Type = VIPS_INTERPRETATION_HISTOGRAM;
	project->rows->Xsize = 1;
	project->rows->BandFmt =
		vips_project_format_table[statistic->ready->BandFmt];
	project->rows->Type = VIPS_INTERPRETATION_HISTOGRAM;

	if (vips_image_write_line(project->columns, 0,
			(VipsPel *) project->hist->column_sums))
		return -1;
	for (y = 0; y < project->rows->Ysize; y++)
		if (vips_image_write_line(project->rows, y,
				(VipsPel *) project->hist->row_sums +
					y * VIPS_IMAGE_SIZEOF_PEL(project->rows)))
			return -1;

	return 0;
}

/* Build a sub-hist, based on the main hist.
 */
static void *
vips_project_start(VipsStatistic *statistic)
{
	VipsProject *project = (VipsProject *) statistic;

	/* Make the main hist, if necessary.
	 */
	if (!project->hist)
		project->hist = histogram_new(project);

	return (void *) histogram_new(project);
}

/* Add a line of pixels.
 */
#define ADD_PIXELS(OUT, IN) \
	{ \
		OUT *row_sums = ((OUT *) hist->row_sums) + y * nb; \
		OUT *column_sums; \
		IN *p; \
\
		column_sums = ((OUT *) hist->column_sums) + x * nb; \
		p = (IN *) in; \
		for (i = 0; i < n; i++) { \
			for (j = 0; j < nb; j++) { \
				column_sums[j] += p[j]; \
				row_sums[j] += p[j]; \
			} \
\
			p += nb; \
			column_sums += nb; \
		} \
	}

/* Add a region to a project.
 */
static int
vips_project_scan(VipsStatistic *statistic, void *seq,
	int x, int y, void *in, int n)
{
	int nb = statistic->ready->Bands;
	Histogram *hist = (Histogram *) seq;
	int i, j;

	switch (statistic->ready->BandFmt) {
	case VIPS_FORMAT_UCHAR:
		ADD_PIXELS(guint, guchar);
		break;

	case VIPS_FORMAT_CHAR:
		ADD_PIXELS(int, char);
		break;

	case VIPS_FORMAT_USHORT:
		ADD_PIXELS(guint, gushort);
		break;

	case VIPS_FORMAT_SHORT:
		ADD_PIXELS(int, short);
		break;

	case VIPS_FORMAT_UINT:
		ADD_PIXELS(guint, guint);
		break;

	case VIPS_FORMAT_INT:
		ADD_PIXELS(int, int);
		break;

	case VIPS_FORMAT_FLOAT:
		ADD_PIXELS(double, float);
		break;

	case VIPS_FORMAT_DOUBLE:
		ADD_PIXELS(double, double);
		break;

	default:
		g_assert_not_reached();
	}

	return 0;
}

#define ADD_BUFFER(TYPE, Q, P, N) \
	{ \
		TYPE *p = (TYPE *) (P); \
		TYPE *q = (TYPE *) (Q); \
		int n = (N); \
		int i; \
\
		for (i = 0; i < n; i++) \
			q[i] += p[i]; \
	}

/* Join a sub-project onto the main project.
 */
static int
vips_project_stop(VipsStatistic *statistic, void *seq)
{
	VipsProject *project = (VipsProject *) statistic;
	Histogram *hist = project->hist;
	Histogram *sub_hist = (Histogram *) seq;
	VipsImage *in = statistic->ready;
	VipsBandFormat outfmt = vips_project_format_table[in->BandFmt];
	int hsz = in->Xsize * in->Bands;
	int vsz = in->Ysize * in->Bands;

	/* Add on sub-data.
	 */
	switch (outfmt) {
	case VIPS_FORMAT_UINT:
		ADD_BUFFER(guint,
			hist->column_sums, sub_hist->column_sums, hsz);
		ADD_BUFFER(guint, hist->row_sums, sub_hist->row_sums, vsz);
		break;

	case VIPS_FORMAT_INT:
		ADD_BUFFER(int,
			hist->column_sums, sub_hist->column_sums, hsz);
		ADD_BUFFER(int, hist->row_sums, sub_hist->row_sums, vsz);
		break;

	case VIPS_FORMAT_DOUBLE:
		ADD_BUFFER(double,
			hist->column_sums, sub_hist->column_sums, hsz);
		ADD_BUFFER(double, hist->row_sums, sub_hist->row_sums, vsz);
		break;

	default:
		g_assert_not_reached();
	}

	/* Blank out sub-project to make sure we can't add it again.
	 */
	sub_hist->column_sums = NULL;
	sub_hist->row_sums = NULL;

	return 0;
}

static void
vips_project_class_init(VipsProjectClass *class)
{
	GObjectClass *gobject_class = (GObjectClass *) class;
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsStatisticClass *sclass = VIPS_STATISTIC_CLASS(class);

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

	object_class->nickname = "project";
	object_class->description = _("find image projections");
	object_class->build = vips_project_build;

	sclass->start = vips_project_start;
	sclass->scan = vips_project_scan;
	sclass->stop = vips_project_stop;

	VIPS_ARG_IMAGE(class, "columns", 100,
		_("Columns"),
		_("Sums of columns"),
		VIPS_ARGUMENT_REQUIRED_OUTPUT,
		G_STRUCT_OFFSET(VipsProject, columns));

	VIPS_ARG_IMAGE(class, "rows", 101,
		_("Rows"),
		_("Sums of rows"),
		VIPS_ARGUMENT_REQUIRED_OUTPUT,
		G_STRUCT_OFFSET(VipsProject, rows));
}

static void
vips_project_init(VipsProject *project)
{
}

/**
 * vips_project: (method)
 * @in: input image
 * @columns: (out): sums of columns
 * @rows: (out): sums of rows
 * @...: `NULL`-terminated list of optional named arguments
 *
 * Find the horizontal and vertical projections of an image, ie. the sum
 * of every row of pixels, and the sum of every column of pixels. The output
 * format is uint, int or double, depending on the input format.
 *
 * Non-complex images only.
 *
 * ::: seealso
 *     [method@Image.hist_find], [method@Image.profile].
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_project(VipsImage *in, VipsImage **columns, VipsImage **rows, ...)
{
	va_list ap;
	int result;

	va_start(ap, rows);
	result = vips_call_split("project", ap, in, columns, rows);
	va_end(ap);

	return result;
}
