/* lazy open/save ... compat funcs for old im_open() behaviour
 *
 * 30/11/11
 * 	- cut from old image.c
 */

/*

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

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

static gboolean
vips_format_is_vips(VipsFormatClass *format)
{
	return strcmp(VIPS_OBJECT_CLASS(format)->nickname, "vips") == 0;
}

/* Lazy open.
 */

/* What we track during a delayed open.
 */
typedef struct {
	VipsImage *image;
	VipsFormatClass *format; /* Read in pixels with this */
	char *filename;			 /* Get pixels from here */
	gboolean sequential;	 /* Sequential read requested */

	VipsImage *real; /* The real decompressed image */
} Lazy;

static void
lazy_free_cb(VipsImage *image, Lazy *lazy)
{
	VIPS_DEBUG_MSG("lazy_free: %p \"%s\"\n", lazy, lazy->filename);

	g_free(lazy->filename);
	VIPS_UNREF(lazy->real);
	g_free(lazy);
}

static Lazy *
lazy_new(VipsImage *image,
	VipsFormatClass *format, const char *filename, gboolean sequential)
{
	Lazy *lazy;

	lazy = g_new(Lazy, 1);
	VIPS_DEBUG_MSG("lazy_new: %p \"%s\"\n", lazy, filename);
	lazy->image = image;
	lazy->format = format;
	lazy->filename = g_strdup(filename);
	lazy->sequential = sequential;
	lazy->real = NULL;
	g_signal_connect(image, "close", G_CALLBACK(lazy_free_cb), lazy);

	return lazy;
}

static size_t
disc_threshold(void)
{
	static gboolean done = FALSE;
	static size_t threshold;

	if (!done) {
		const char *env;

		done = TRUE;

		/* 100mb default.
		 */
		threshold = 100 * 1024 * 1024;

		if ((env = g_getenv("IM_DISC_THRESHOLD")))
			threshold = vips__parse_size(env);

		if (vips__disc_threshold)
			threshold = vips__parse_size(vips__disc_threshold);

		VIPS_DEBUG_MSG("disc_threshold: %zd bytes\n", threshold);
	}

	return threshold;
}

/* Make the real underlying image: either a direct disc file, or a temp file
 * somewhere.
 */
static VipsImage *
lazy_real_image(Lazy *lazy)
{
	VipsImage *real;

	/* We open via disc if:
	 * - 'sequential' is not set
	 * - disc_threshold() has not been set to zero
	 * - the format does not support lazy read
	 * - the uncompressed image will be larger than disc_threshold()
	 */
	real = NULL;
	if (!lazy->sequential &&
		disc_threshold() &&
		!(vips_format_get_flags(lazy->format, lazy->filename) &
			VIPS_FORMAT_PARTIAL) &&
		VIPS_IMAGE_SIZEOF_IMAGE(lazy->image) > disc_threshold())
		if (!(real = vips_image_new_temp_file("%s.v")))
			return NULL;

	/* Otherwise, fall back to a "p".
	 */
	if (!real &&
		!(real = vips_image_new()))
		return NULL;

	return real;
}

/* Our start function ... do the lazy open, if necessary, and return a region
 * on the new image.
 */
static void *
open_lazy_start(VipsImage *out, void *a, void *dummy)
{
	Lazy *lazy = (Lazy *) a;

	if (!lazy->real) {
		if (!(lazy->real = lazy_real_image(lazy)) ||
			lazy->format->load(lazy->filename, lazy->real) ||
			vips_image_pio_input(lazy->real)) {
			VIPS_UNREF(lazy->real);
			return NULL;
		}
	}

	return vips_region_new(lazy->real);
}

/* Just copy.
 */
static int
open_lazy_generate(VipsRegion *out_region,
	void *seq, void *a, void *b, gboolean *stop)
{
	VipsRegion *ir = (VipsRegion *) seq;

	VipsRect *r = &out_region->valid;

	/* Ask for input we need.
	 */
	if (vips_region_prepare(ir, r))
		return -1;

	/* Attach output region to that.
	 */
	if (vips_region_region(out_region, ir, r, r->left, r->top))
		return -1;

	return 0;
}

/* Lazy open ... init the header with the first OpenLazyFn, delay actually
 * decoding pixels with the second OpenLazyFn until the first generate().
 */
static int
vips_image_open_lazy(VipsImage *image,
	VipsFormatClass *format, const char *filename, gboolean sequential)
{
	Lazy *lazy;

	lazy = lazy_new(image, format, filename, sequential);

	/* Is there a ->header() function? We need to do a lazy load.
	 */
	if (format->header) {
		/* Read header fields to init the return image.
		 */
		if (format->header(filename, image))
			return -1;

		/* Then 'start' creates the real image and 'gen' paints 'image'
		 * with pixels from the real image on demand.
		 */
		if (vips_image_pipelinev(image, image->dhint, NULL) ||
			vips_image_generate(image,
				open_lazy_start, open_lazy_generate,
				vips_stop_one, lazy, NULL))
			return -1;
	}
	else if (format->load) {
		if (format->load(filename, image))
			return -1;
	}
	else
		g_assert(0);

	return 0;
}

/* Lazy save.
 */

/* If we write to (eg.) TIFF, actually do the write
 * to a "p" and on "written" do im_vips2tiff() or whatever. Track save
 * parameters here.
 */

typedef int (*VipsSaveFn)(VipsImage *image, const char *filename);

typedef struct {
	VipsSaveFn save_fn; /* Save function */
	char *filename;		/* Save args */
} SaveBlock;

/* From "written" callback: invoke a delayed save.
 */
static void
vips_image_save_cb(VipsImage *image, int *result, SaveBlock *sb)
{
	if (sb->save_fn(image, sb->filename))
		*result = -1;

	g_free(sb->filename);
	g_free(sb);
}

static void
vips_attach_save(VipsImage *image, VipsSaveFn save_fn, const char *filename)
{
	SaveBlock *sb;

	sb = g_new(SaveBlock, 1);
	sb->save_fn = save_fn;
	sb->filename = g_strdup(filename);
	g_signal_connect(image, "written",
		G_CALLBACK(vips_image_save_cb), sb);
}

IMAGE *
vips__deprecated_open_read(const char *filename, gboolean sequential)
{
	VipsFormatClass *format;

	if (!(format = vips_format_for_file(filename)))
		return NULL;

	if (vips_format_is_vips(format)) {
		/* For vips format, we can just the main vips path.
		 */
		return vips_image_new_mode(filename, "rd");
	}
	else {
		/* For non-vips formats we must go via the old VipsFormat
		 * system to make sure we support the "filename:options"
		 * syntax.
		 */
		IMAGE *image;

		image = vips_image_new();
		if (vips_image_open_lazy(image, format,
				filename, sequential)) {
			g_object_unref(image);
			return NULL;
		}

		/* Yuk. Can't g_object_set() filename since it's after
		 * construct. Just zap the new filename in.
		 */
		VIPS_SETSTR(image->filename, filename);

		return image;
	}
}

IMAGE *
vips__deprecated_open_write(const char *filename)
{
	VipsFormatClass *format;

	if (!(format = vips_format_for_name(filename)))
		return NULL;

	if (vips_format_is_vips(format))
		/* For vips format, we can just the main vips path.
		 */
		return vips_image_new_mode(filename, "w");
	else {
		/* For non-vips formats we must go via the old VipsFormat
		 * system to make sure we support the "filename:options"
		 * syntax.
		 */
		IMAGE *image;

		if (!(image = vips_image_new()))
			return NULL;
		vips_attach_save(image,
			format->save, filename);
		return image;
	}
}
