/* load nifti from a file
 *
 * 29/6/18
 * 	- from fitsload.c
 * 9/9/19
 * 	- use double for all floating point scalar metadata, like other loaders
 * 	- remove stray use of "n" property
 */

/*

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

/* TODO
 *
 * - for uncompressed images, we could do direct mapping of the input
 * - perhaps we could stream compressed images? but only if ext is defined at
 *   the start of the file
 *   	(yes, file format is magic number, 348-byte header, extension data,
 *   	pixel data, then all gz'd)
 * - we could use the much faster byteswap in glib?
 * - I have not been able to test the ext stuff :(
 *
 * There should be at least a x2 speedup possible.
 */

#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 <vips/debug.h>
#include <vips/internal.h>

#ifdef HAVE_NIFTI

#include <nifti1_io.h>

#include "pforeign.h"

typedef struct _VipsForeignLoadNifti {
	VipsForeignLoad parent_object;

	/* Source to load from (set by subclasses).
	 */
	VipsSource *source;

	/* Filename from source.
	 */
	const char *filename;

	/* The NIFTI image loaded to memory.
	 */
	nifti_image *nim;

	/* Wrap this VipsImage around the NIFTI pointer, then redirect read
	 * requests to that. Saves a copy.
	 */
	VipsImage *memory;

} VipsForeignLoadNifti;

typedef VipsForeignLoadClass VipsForeignLoadNiftiClass;

G_DEFINE_ABSTRACT_TYPE(VipsForeignLoadNifti, vips_foreign_load_nifti,
	VIPS_TYPE_FOREIGN_LOAD);

static void
vips_foreign_load_nifti_dispose(GObject *gobject)
{
	VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) gobject;

	VIPS_UNREF(nifti->source);
	VIPS_UNREF(nifti->memory);
	VIPS_FREEF(nifti_image_free, nifti->nim);

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

static int
vips_foreign_load_nifti_build(VipsObject *object)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);
	VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) object;

	/* We can only open source which have an associated filename, since
	 * the nifti library works in terms of filenames.
	 */
	if (nifti->source) {
		VipsConnection *connection = VIPS_CONNECTION(nifti->source);

		const char *filename;

		if (!vips_source_is_file(nifti->source) ||
			!(filename = vips_connection_filename(connection))) {
			vips_error(class->nickname,
				"%s", _("no filename available"));
			return -1;
		}

		nifti->filename = filename;
	}

	return VIPS_OBJECT_CLASS(vips_foreign_load_nifti_parent_class)
		->build(object);
}

/* Map DT_* datatype values to VipsBandFormat.
 */
typedef struct _VipsForeignDT2Vips {
	int datatype;
	VipsBandFormat fmt;
} VipsForeignDT2Vips;

static VipsForeignDT2Vips vips_foreign_nifti_DT2Vips[] = {
	{ DT_UINT8, VIPS_FORMAT_UCHAR },
	{ DT_INT8, VIPS_FORMAT_CHAR },
	{ DT_UINT16, VIPS_FORMAT_USHORT },
	{ DT_INT16, VIPS_FORMAT_SHORT },
	{ DT_UINT32, VIPS_FORMAT_UINT },
	{ DT_INT32, VIPS_FORMAT_INT },
	{ DT_FLOAT32, VIPS_FORMAT_FLOAT },
	{ DT_FLOAT64, VIPS_FORMAT_DOUBLE },
	{ DT_COMPLEX64, VIPS_FORMAT_COMPLEX },
	{ DT_COMPLEX128, VIPS_FORMAT_DPCOMPLEX },
	{ DT_RGB, VIPS_FORMAT_UCHAR },
	{ DT_RGBA32, VIPS_FORMAT_UCHAR }
};

VipsBandFormat
vips__foreign_nifti_datatype2BandFmt(int datatype)
{
	int i;

	for (i = 0; i < VIPS_NUMBER(vips_foreign_nifti_DT2Vips); i++)
		if (vips_foreign_nifti_DT2Vips[i].datatype == datatype)
			return vips_foreign_nifti_DT2Vips[i].fmt;

	return VIPS_FORMAT_NOTSET;
}

int
vips__foreign_nifti_BandFmt2datatype(VipsBandFormat fmt)
{
	int i;

	for (i = 0; i < VIPS_NUMBER(vips_foreign_nifti_DT2Vips); i++)
		if (vips_foreign_nifti_DT2Vips[i].fmt == fmt)
			return vips_foreign_nifti_DT2Vips[i].datatype;

	return -1;
}

/* All the header fields we attach as metadata.
 */
typedef struct _VipsForeignNiftiFields {
	char *name;
	GType type;
	glong offset;
} VipsForeignNiftiFields;

static VipsForeignNiftiFields vips_foreign_nifti_fields[] = {
	/* The first 8 must be the dims[] fields, see
	 * vips_foreign_save_nifti_make_nim().
	 */
	{ "ndim", G_TYPE_INT, G_STRUCT_OFFSET(nifti_image, ndim) },
	{ "nx", G_TYPE_INT, G_STRUCT_OFFSET(nifti_image, nx) },
	{ "ny", G_TYPE_INT, G_STRUCT_OFFSET(nifti_image, ny) },
	{ "nz", G_TYPE_INT, G_STRUCT_OFFSET(nifti_image, nz) },
	{ "nt", G_TYPE_INT, G_STRUCT_OFFSET(nifti_image, nt) },
	{ "nu", G_TYPE_INT, G_STRUCT_OFFSET(nifti_image, nu) },
	{ "nv", G_TYPE_INT, G_STRUCT_OFFSET(nifti_image, nv) },
	{ "nw", G_TYPE_INT, G_STRUCT_OFFSET(nifti_image, nw) },

	{ "dx", G_TYPE_DOUBLE, G_STRUCT_OFFSET(nifti_image, dx) },
	{ "dy", G_TYPE_DOUBLE, G_STRUCT_OFFSET(nifti_image, dy) },
	{ "dz", G_TYPE_DOUBLE, G_STRUCT_OFFSET(nifti_image, dz) },
	{ "dt", G_TYPE_DOUBLE, G_STRUCT_OFFSET(nifti_image, dt) },
	{ "du", G_TYPE_DOUBLE, G_STRUCT_OFFSET(nifti_image, du) },
	{ "dv", G_TYPE_DOUBLE, G_STRUCT_OFFSET(nifti_image, dv) },
	{ "dw", G_TYPE_DOUBLE, G_STRUCT_OFFSET(nifti_image, dw) },

	{ "scl_slope", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, scl_slope) },
	{ "scl_inter", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, scl_inter) },

	{ "cal_min", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, cal_min) },
	{ "cal_max", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, cal_max) },

	{ "qform_code", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, qform_code) },
	{ "sform_code", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, sform_code) },

	{ "freq_dim", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, freq_dim) },
	{ "phase_dim", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, phase_dim) },
	{ "slice_dim", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, slice_dim) },

	{ "slice_code", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, slice_code) },
	{ "slice_start", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, slice_start) },
	{ "slice_end", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, slice_end) },
	{ "slice_duration", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, slice_duration) },

	{ "quatern_b", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, quatern_b) },
	{ "quatern_c", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, quatern_c) },
	{ "quatern_d", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, quatern_d) },
	{ "qoffset_x", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, qoffset_x) },
	{ "qoffset_y", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, qoffset_y) },
	{ "qoffset_z", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, qoffset_z) },
	{ "qfac", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, qfac) },

	{ "sto_xyz00", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[0][0]) },
	{ "sto_xyz01", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[0][1]) },
	{ "sto_xyz02", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[0][2]) },
	{ "sto_xyz03", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[0][3]) },

	{ "sto_xyz10", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[1][0]) },
	{ "sto_xyz11", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[1][1]) },
	{ "sto_xyz12", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[1][2]) },
	{ "sto_xyz13", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[1][3]) },

	{ "sto_xyz20", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[2][0]) },
	{ "sto_xyz21", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[2][1]) },
	{ "sto_xyz22", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[2][2]) },
	{ "sto_xyz23", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[2][3]) },

	{ "sto_xyz30", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[3][0]) },
	{ "sto_xyz31", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[3][1]) },
	{ "sto_xyz32", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[3][2]) },
	{ "sto_xyz33", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, sto_xyz.m[3][3]) },

	{ "toffset", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, toffset) },

	{ "xyz_units", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, xyz_units) },
	{ "time_units", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, time_units) },

	{ "nifti_type", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, nifti_type) },
	{ "intent_code", G_TYPE_INT,
		G_STRUCT_OFFSET(nifti_image, intent_code) },
	{ "intent_p1", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, intent_p1) },
	{ "intent_p2", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, intent_p2) },
	{ "intent_p3", G_TYPE_DOUBLE,
		G_STRUCT_OFFSET(nifti_image, intent_p3) },
};

void *
vips__foreign_nifti_map(VipsNiftiMapFn fn, void *a, void *b)
{
	int i;
	void *result;

	for (i = 0; i < VIPS_NUMBER(vips_foreign_nifti_fields); i++) {
		GValue value = G_VALUE_INIT;

		g_value_init(&value, vips_foreign_nifti_fields[i].type);
		result = fn(vips_foreign_nifti_fields[i].name, &value,
			vips_foreign_nifti_fields[i].offset, a, b);
		g_value_unset(&value);

		if (result)
			return result;
	}

	return NULL;
}

/* How I wish glib had something like this :( Just implement the ones we need
 * for vips_foreign_nifti_fields above.
 */
static void
vips_gvalue_read(GValue *value, void *p)
{
	switch (G_VALUE_TYPE(value)) {
	case G_TYPE_INT:
		g_value_set_int(value, *((int *) p));
		break;

	case G_TYPE_DOUBLE:
		/* We set as double rather than float, as things like pyvips
		 * expect double for metadata items.
		 */
		g_value_set_double(value, *((float *) p));
		break;

	default:
		g_warning("vips_gvalue_read: unsupported GType %s",
			g_type_name(G_VALUE_TYPE(value)));
	}
}

static void *
vips_foreign_load_nifti_set(const char *name, GValue *value, glong offset,
	void *a, void *b)
{
	nifti_image *nim = (nifti_image *) a;
	VipsImage *out = VIPS_IMAGE(b);

	char vips_name[256];

	vips_gvalue_read(value, (char *) nim + offset);
	g_snprintf(vips_name, 256, "nifti-%s", name);
	vips_image_set(out, vips_name, value);

	return NULL;
}

static int
vips_foreign_load_nifti_set_header(VipsForeignLoadNifti *nifti,
	nifti_image *nim, VipsImage *out)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(nifti);

	guint width;
	guint height;
	guint bands;
	VipsBandFormat fmt;
	double xres;
	double yres;
	int i;
	char txt[256];

	if (nim->ndim < 1 ||
		nim->ndim > 7) {
		vips_error(class->nickname,
			_("%d-dimensional images not supported"),
			nim->ndim);
		return 0;
	}
	for (i = 1; i < 8 && i < nim->ndim + 1; i++) {
		if (nim->dim[i] <= 0) {
			vips_error(class->nickname,
				"%s", _("invalid dimension"));
			return 0;
		}

		/* If we have several images in a dimension, the spacing must
		 * be non-zero or we'll get a /0 error in resolution
		 * calculation.
		 */
		if (nim->dim[i] > 1 &&
			nim->pixdim[i] == 0) {
			vips_error(class->nickname,
				"%s", _("invalid resolution"));
			return 0;
		}
	}

	/* Unfold higher dimensions vertically. bands is updated below for
	 * DT_RGB. Be careful to avoid height going over 2^31.
	 */
	bands = 1;
	width = (guint) nim->nx;
	height = (guint) nim->ny;
	for (i = 3; i < 8 && i < nim->ndim + 1; i++)
		if (!g_uint_checked_mul(&height, height, nim->dim[i])) {
			vips_error(class->nickname,
				"%s", _("dimension overflow"));
			return 0;
		}
	if (height > INT_MAX) {
		vips_error(class->nickname, "%s", _("dimension overflow"));
		return 0;
	}

	fmt = vips__foreign_nifti_datatype2BandFmt(nim->datatype);
	if (fmt == VIPS_FORMAT_NOTSET) {
		vips_error(class->nickname,
			_("datatype %d not supported"), nim->datatype);
		return -1;
	}

	if (nim->datatype == DT_RGB)
		bands = 3;
	if (nim->datatype == DT_RGBA32)
		bands = 4;

	/* We fold y and z together, so they must have the same resolution..
	 */
	xres = 1.0;
	yres = 1.0;
	if (nim->nz == 1 ||
		nim->dz == nim->dy)
		switch (nim->xyz_units) {
		case NIFTI_UNITS_METER:
			xres = 1000.0 / nim->dx;
			yres = 1000.0 / nim->dy;
			break;
		case NIFTI_UNITS_MM:
			xres = 1.0 / nim->dx;
			yres = 1.0 / nim->dy;
			break;

		case NIFTI_UNITS_MICRON:
			xres = 1.0 / (1000.0 * nim->dx);
			yres = 1.0 / (1000.0 * nim->dy);
			break;

		default:
			break;
		}

#ifdef DEBUG
	printf("get_vips_properties: width = %d\n", width);
	printf("get_vips_properties: height = %d\n", height);
	printf("get_vips_properties: bands = %d\n", bands);
	printf("get_vips_properties: fmt = %d\n", fmt);
#endif /*DEBUG*/

	/* We load to memory then write to out, so we'll hint THINSTRIP.
	 */
	vips_image_pipelinev(out, VIPS_DEMAND_STYLE_THINSTRIP, NULL);

	vips_image_init_fields(out,
		width, height, bands, fmt,
		VIPS_CODING_NONE,
		bands == 1
			? VIPS_INTERPRETATION_B_W
			: VIPS_INTERPRETATION_sRGB,
		xres, yres);

	/* Set some vips metadata for every nifti header field.
	 */
	if (vips__foreign_nifti_map(vips_foreign_load_nifti_set, nim, out))
		return -1;

	/* One byte longer than the spec to leave space for any extra
	 * '\0' termination.
	 */
	g_strlcpy(txt, nim->intent_name, 17);
	vips_image_set_string(out, "nifti-intent_name", txt);
	g_strlcpy(txt, nim->descrip, 81);
	vips_image_set_string(out, "nifti-descrip", txt);

	for (i = 0; i < nim->num_ext; i++) {
		nifti1_extension *ext = &nim->ext_list[i];

		g_snprintf(txt, 256, "nifti-ext-%d-%d", i, ext->ecode);
		vips_image_set_blob_copy(out, txt, ext->edata, ext->esize);
	}

	vips_image_set_int(out, VIPS_META_PAGE_HEIGHT, nim->ny);

	return 0;
}

static int
vips_foreign_load_nifti_header(VipsForeignLoad *load)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(load);
	VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) load;

	/* We can't use the (much faster) nifti_read_header() since it just
	 * reads the 348 bytes of the analyze struct and does not read any of
	 * the extension fields.
	 */

	/* FALSE means don't read data, just the header. Use
	 * nifti_image_load() later to pull the data in.
	 */
	if (!(nifti->nim = nifti_image_read(nifti->filename, FALSE))) {
		vips_error(class->nickname,
			"%s", _("unable to read NIFTI header"));
		return 0;
	}

	if (vips_foreign_load_nifti_set_header(nifti,
			nifti->nim, load->out)) {
		return -1;
	}

	VIPS_SETSTR(load->out->filename, nifti->filename);

	return 0;
}

static int
vips_foreign_load_nifti_load(VipsForeignLoad *load)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(load);
	VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) load;

#ifdef DEBUG
	printf("vips_foreign_load_nifti_load: loading image\n");
#endif /*DEBUG*/

	/* We just read the entire image to memory.
	 */
	if (nifti_image_load(nifti->nim)) {
		vips_error(class->nickname,
			"%s", _("unable to load NIFTI file"));
		return -1;
	}

	if (!(nifti->memory = vips_image_new_from_memory(
			  nifti->nim->data, VIPS_IMAGE_SIZEOF_IMAGE(load->out),
			  load->out->Xsize, load->out->Ysize,
			  load->out->Bands, load->out->BandFmt)))
		return -1;

	if (vips_image_write(nifti->memory, load->real))
		return -1;

	return 0;
}

static void
vips_foreign_load_nifti_class_init(VipsForeignLoadNiftiClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class);
	VipsForeignClass *foreign_class = (VipsForeignClass *) class;
	VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;

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

	object_class->nickname = "niftiload_base";
	object_class->description = _("load a NIFTI image");
	object_class->build = vips_foreign_load_nifti_build;

	/* nificlib has not been fuzzed, so should not be used with
	 * untrusted input unless you are very careful.
	 */
	operation_class->flags |= VIPS_OPERATION_UNTRUSTED;

	/* is_a() is not that quick ... lower the priority.
	 */
	foreign_class->priority = -50;

	load_class->header = vips_foreign_load_nifti_header;
	load_class->load = vips_foreign_load_nifti_load;
}

static void
vips_foreign_load_nifti_init(VipsForeignLoadNifti *nifti)
{
}

typedef struct _VipsForeignLoadNiftiFile {
	VipsForeignLoadNifti parent_object;

	/* Filename for load.
	 */
	char *filename;

} VipsForeignLoadNiftiFile;

typedef VipsForeignLoadNiftiClass VipsForeignLoadNiftiFileClass;

G_DEFINE_TYPE(VipsForeignLoadNiftiFile, vips_foreign_load_nifti_file,
	vips_foreign_load_nifti_get_type());

static int
vips_foreign_load_nifti_file_build(VipsObject *object)
{
	VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) object;
	VipsForeignLoadNiftiFile *file = (VipsForeignLoadNiftiFile *) object;

	if (file->filename &&
		!(nifti->source = vips_source_new_from_file(file->filename)))
		return -1;

	return VIPS_OBJECT_CLASS(vips_foreign_load_nifti_file_parent_class)
		->build(object);
}

const char *vips_foreign_nifti_suffs[] = {
	".nii", ".nii.gz",
	".hdr", ".hdr.gz",
	".img", ".img.gz",
	".nia", ".nia.gz",
	NULL
};

static int
vips_foreign_load_nifti_is_a(const char *filename)
{
	char *hfile;
	znzFile fp;
	nifti_1_header nhdr;

	/* Unfortunately is_nifti_file() is very slow and produces lots of
	 * output. We have to make our own.
	 */

	if (!(hfile = nifti_findhdrname(filename)))
		return 0;

	fp = znzopen(hfile, "rb", nifti_is_gzfile(hfile));
	if (znz_isnull(fp)) {
		free(hfile);
		return 0;
	}
	free(hfile);

	(void) znzread(&nhdr, 1, sizeof(nhdr), fp);

	znzclose(fp);

	/* Test for sanity both ways around. There's a thing to test for byte
	 * order in niftilib, but it's static :(
	 */
	if (nifti_hdr_looks_good(&nhdr))
		return 1;
	swap_nifti_header(&nhdr, FALSE);
	if (nifti_hdr_looks_good(&nhdr))
		return 1;

	return 0;
}

static void
vips_foreign_load_nifti_file_class_init(
	VipsForeignLoadNiftiFileClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsForeignClass *foreign_class = (VipsForeignClass *) class;
	VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;

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

	object_class->nickname = "niftiload";
	object_class->description = _("load NIfTI volume");
	object_class->build = vips_foreign_load_nifti_file_build;

	foreign_class->suffs = vips_foreign_nifti_suffs;

	load_class->is_a = vips_foreign_load_nifti_is_a;

	VIPS_ARG_STRING(class, "filename", 1,
		_("Filename"),
		_("Filename to load from"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadNiftiFile, filename),
		NULL);
}

static void
vips_foreign_load_nifti_file_init(VipsForeignLoadNiftiFile *nifti)
{
}

typedef struct _VipsForeignLoadNiftiSource {
	VipsForeignLoadNifti parent_object;

	/* Load from a source.
	 */
	VipsSource *source;

} VipsForeignLoadNiftiSource;

typedef VipsForeignLoadNiftiClass VipsForeignLoadNiftiSourceClass;

G_DEFINE_TYPE(VipsForeignLoadNiftiSource, vips_foreign_load_nifti_source,
	vips_foreign_load_nifti_get_type());

static int
vips_foreign_load_nifti_source_build(VipsObject *object)
{
	VipsForeignLoadNifti *nifti = (VipsForeignLoadNifti *) object;
	VipsForeignLoadNiftiSource *source =
		(VipsForeignLoadNiftiSource *) object;

	if (source->source) {
		nifti->source = source->source;
		g_object_ref(nifti->source);
	}

	return VIPS_OBJECT_CLASS(vips_foreign_load_nifti_source_parent_class)
		->build(object);
}

static gboolean
vips_foreign_load_nifti_source_is_a_source(VipsSource *source)
{
	VipsConnection *connection = VIPS_CONNECTION(source);

	const char *filename;

	return vips_source_is_file(source) &&
		(filename = vips_connection_filename(connection)) &&
		vips_foreign_load_nifti_is_a(filename);
}

static void
vips_foreign_load_nifti_source_class_init(
	VipsForeignLoadNiftiSourceClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class);
	VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;

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

	object_class->nickname = "niftiload_source";
	object_class->description = _("load NIfTI volumes");
	object_class->build = vips_foreign_load_nifti_source_build;

	operation_class->flags |= VIPS_OPERATION_NOCACHE;

	load_class->is_a_source =
		vips_foreign_load_nifti_source_is_a_source;

	VIPS_ARG_OBJECT(class, "source", 1,
		_("Source"),
		_("Source to load from"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadNiftiSource, source),
		VIPS_TYPE_SOURCE);
}

static void
vips_foreign_load_nifti_source_init(
	VipsForeignLoadNiftiSource *nifti)
{
}

#endif /*HAVE_NIFTI*/

/**
 * vips_niftiload:
 * @filename: file to load
 * @out: (out): decompressed image
 * @...: `NULL`-terminated list of optional named arguments
 *
 * Read a NIFTI image file into a VIPS image.
 *
 * NIFTI metadata is attached with the "nifti-" prefix.
 *
 * ::: seealso
 *     [ctor@Image.new_from_file].
 *
 * Returns: 0 on success, -1 on error.
 */
int
vips_niftiload(const char *filename, VipsImage **out, ...)
{
	va_list ap;
	int result;

	va_start(ap, out);
	result = vips_call_split("niftiload", ap, filename, out);
	va_end(ap);

	return result;
}

/**
 * vips_niftiload_source:
 * @source: source to load from
 * @out: (out): decompressed image
 * @...: `NULL`-terminated list of optional named arguments
 *
 * Exactly as [ctor@Image.niftiload], but read from a source.
 *
 * Returns: 0 on success, -1 on error.
 */
int
vips_niftiload_source(VipsSource *source, VipsImage **out, ...)
{
	va_list ap;
	int result;

	va_start(ap, out);
	result = vips_call_split("niftiload_source", ap, source, out);
	va_end(ap);

	return result;
}
