/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
/*
 * Copyright (C) 2005, Jonathan Blandford <jrb@gnome.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, 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 General Public License for more details.
 *
 * You should have received a copy of the GNU 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.
 */

/* FIXME: Should probably buffer calls to libtiff with TIFFSetWarningHandler
 */

#include "config.h"

#include <config.h>
#include <stdio.h>
#include <glib.h>
#include <glib/gi18n-lib.h>

#include "tiffio.h"
#include "tiff2ps.h"
#include "tiff-document.h"
#include "ev-document-misc.h"
#include "ev-document-thumbnails.h"
#include "ev-file-exporter.h"
#include "ev-file-helpers.h"

struct _TiffDocumentClass
{
  EvDocumentClass parent_class;
};

struct _TiffDocument
{
  EvDocument parent_instance;

  TIFF *tiff;
  gint n_pages;
  TIFF2PSContext *ps_export_ctx;

  gchar *uri;
};

typedef struct _TiffDocumentClass TiffDocumentClass;

static void tiff_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface);
static void tiff_document_document_file_exporter_iface_init (EvFileExporterInterface *iface);

EV_BACKEND_REGISTER_WITH_CODE (TiffDocument, tiff_document,
			 {
			   EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS,
							   tiff_document_document_thumbnails_iface_init);
			   EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_FILE_EXPORTER,
							   tiff_document_document_file_exporter_iface_init);
			 });

static TIFFErrorHandler orig_error_handler = NULL;
static TIFFErrorHandler orig_warning_handler = NULL;

static void
push_handlers (void)
{
	orig_error_handler = TIFFSetErrorHandler (NULL);
	orig_warning_handler = TIFFSetWarningHandler (NULL);
}

static void
pop_handlers (void)
{
	TIFFSetErrorHandler (orig_error_handler);
	TIFFSetWarningHandler (orig_warning_handler);
}

static gboolean
tiff_document_load (EvDocument  *document,
		    const char  *uri,
		    GError     **error)
{
	TiffDocument *tiff_document = TIFF_DOCUMENT (document);
	gchar *filename;
	TIFF *tiff;

	filename = g_filename_from_uri (uri, NULL, error);
	if (!filename)
		return FALSE;

	push_handlers ();

	tiff = TIFFOpen (filename, "r");
	if (tiff) {
		guint32 w, h;

		/* FIXME: unused data? why bother here */
		TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &w);
		TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &h);
	}

	if (!tiff) {
		pop_handlers ();

    		g_set_error_literal (error,
				     EV_DOCUMENT_ERROR,
				     EV_DOCUMENT_ERROR_INVALID,
				     _("Invalid document"));

		g_free (filename);
		return FALSE;
	}

	tiff_document->tiff = tiff;
	g_free (tiff_document->uri);
	g_free (filename);
	tiff_document->uri = g_strdup (uri);

	pop_handlers ();
	return TRUE;
}

static gboolean
tiff_document_save (EvDocument  *document,
		    const char  *uri,
		    GError     **error)
{
	TiffDocument *tiff_document = TIFF_DOCUMENT (document);

	return ev_xfer_uri_simple (tiff_document->uri, uri, error);
}

static int
tiff_document_get_n_pages (EvDocument  *document)
{
	TiffDocument *tiff_document = TIFF_DOCUMENT (document);

	g_return_val_if_fail (TIFF_IS_DOCUMENT (document), 0);
	g_return_val_if_fail (tiff_document->tiff != NULL, 0);

	if (tiff_document->n_pages == -1) {
		push_handlers ();
		tiff_document->n_pages = 0;

		do {
			tiff_document->n_pages ++;
		}
		while (TIFFReadDirectory (tiff_document->tiff));
		pop_handlers ();
	}

	return tiff_document->n_pages;
}

static void
tiff_document_get_resolution (TiffDocument *tiff_document,
			      gfloat       *x_res,
			      gfloat       *y_res)
{
	gfloat x = 72.0, y = 72.0;
	gushort unit;

	if (TIFFGetField (tiff_document->tiff, TIFFTAG_XRESOLUTION, &x) &&
	    TIFFGetField (tiff_document->tiff, TIFFTAG_YRESOLUTION, &y)) {
		if (TIFFGetFieldDefaulted (tiff_document->tiff, TIFFTAG_RESOLUTIONUNIT, &unit)) {
			if (unit == RESUNIT_CENTIMETER) {
				x *= 2.54;
				y *= 2.54;
			}
		}
	}

	*x_res = x;
	*y_res = y;
}

static void
tiff_document_get_page_size (EvDocument *document,
			     EvPage     *page,
			     double     *width,
			     double     *height)
{
	guint32 w, h;
	gfloat x_res, y_res;
	TiffDocument *tiff_document = TIFF_DOCUMENT (document);

	g_return_if_fail (TIFF_IS_DOCUMENT (document));
	g_return_if_fail (tiff_document->tiff != NULL);

	push_handlers ();
	if (TIFFSetDirectory (tiff_document->tiff, page->index) != 1) {
		pop_handlers ();
		return;
	}

	TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGEWIDTH, &w);
	TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGELENGTH, &h);
	tiff_document_get_resolution (tiff_document, &x_res, &y_res);
	h = h * (x_res / y_res);

	*width = w;
	*height = h;

	pop_handlers ();
}

static cairo_surface_t *
tiff_document_render (EvDocument      *document,
		      EvRenderContext *rc)
{
	TiffDocument *tiff_document = TIFF_DOCUMENT (document);
	int width, height;
	float x_res, y_res;
	gint rowstride, bytes;
	guchar *pixels = NULL;
	guchar *p;
	int orientation;
	cairo_surface_t *surface;
	cairo_surface_t *rotated_surface;
	static const cairo_user_data_key_t key;

	g_return_val_if_fail (TIFF_IS_DOCUMENT (document), NULL);
	g_return_val_if_fail (tiff_document->tiff != NULL, NULL);

	push_handlers ();
	if (TIFFSetDirectory (tiff_document->tiff, rc->page->index) != 1) {
		pop_handlers ();
		g_warning("Failed to select page %d", rc->page->index);
		return NULL;
	}

	if (!TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGEWIDTH, &width)) {
		pop_handlers ();
		g_warning("Failed to read image width");
		return NULL;
	}

	if (! TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGELENGTH, &height)) {
		pop_handlers ();
		g_warning("Failed to read image height");
		return NULL;
	}

	if (! TIFFGetField (tiff_document->tiff, TIFFTAG_ORIENTATION, &orientation)) {
		orientation = ORIENTATION_TOPLEFT;
	}

	tiff_document_get_resolution (tiff_document, &x_res, &y_res);

	pop_handlers ();

	/* Sanity check the doc */
	if (width <= 0 || height <= 0) {
		g_warning("Invalid width or height.");
		return NULL;
	}

	rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, width);
	if (rowstride / 4 != width) {
		g_warning("Overflow while rendering document.");
		/* overflow, or cairo was changed in an unsupported way */
		return NULL;
	}

	if (height >= INT_MAX / rowstride) {
		g_warning("Overflow while rendering document.");
		/* overflow */
		return NULL;
	}

	bytes = height * rowstride;

	pixels = g_try_malloc (bytes);
	if (!pixels) {
		g_warning("Failed to allocate memory for rendering.");
		return NULL;
	}

	if (!TIFFReadRGBAImageOriented (tiff_document->tiff,
					width, height,
					(uint32 *)pixels,
					orientation, 0)) {
		g_warning ("Failed to read TIFF image.");
		g_free (pixels);
		return NULL;
	}

	surface = cairo_image_surface_create_for_data (pixels,
						       CAIRO_FORMAT_RGB24,
						       width, height,
						       rowstride);
	cairo_surface_set_user_data (surface, &key,
				     pixels, (cairo_destroy_func_t)g_free);
	pop_handlers ();

	/* Convert the format returned by libtiff to
	* what cairo expects
	*/
	p = pixels;
	while (p < pixels + bytes) {
		guint32 *pixel = (guint32*)p;
		guint8 r = TIFFGetR(*pixel);
		guint8 g = TIFFGetG(*pixel);
		guint8 b = TIFFGetB(*pixel);
		guint8 a = TIFFGetA(*pixel);

		*pixel = (a << 24) | (r << 16) | (g << 8) | b;

		p += 4;
	}

	rotated_surface = ev_document_misc_surface_rotate_and_scale (surface,
								     (width * rc->scale) + 0.5,
								     (height * rc->scale * (x_res / y_res)) + 0.5,
								     rc->rotation);
	cairo_surface_destroy (surface);

	return rotated_surface;
}

static GdkPixbuf *
tiff_document_render_pixbuf (EvDocument      *document,
			     EvRenderContext *rc)
{
	TiffDocument *tiff_document = TIFF_DOCUMENT (document);
	int width, height;
	float x_res, y_res;
	gint rowstride, bytes;
	guchar *pixels = NULL;
	GdkPixbuf *pixbuf;
	GdkPixbuf *scaled_pixbuf;
	GdkPixbuf *rotated_pixbuf;

	push_handlers ();
	if (TIFFSetDirectory (tiff_document->tiff, rc->page->index) != 1) {
		pop_handlers ();
		return NULL;
	}

	if (!TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGEWIDTH, &width)) {
		pop_handlers ();
		return NULL;
	}

	if (! TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGELENGTH, &height)) {
		pop_handlers ();
		return NULL;
	}

	tiff_document_get_resolution (tiff_document, &x_res, &y_res);

	pop_handlers ();

	/* Sanity check the doc */
	if (width <= 0 || height <= 0)
		return NULL;


	if (width >= INT_MAX / 4)
		/* overflow */
		return NULL;

	rowstride = width * 4;

	if (height >= INT_MAX / rowstride)
		/* overflow */
		return NULL;
	bytes = height * rowstride;

	pixels = g_try_malloc (bytes);
	if (!pixels)
		return NULL;

	if (!TIFFReadRGBAImageOriented (tiff_document->tiff,
					width, height,
					(uint32 *)pixels,
					ORIENTATION_TOPLEFT, 0)) {
		g_free (pixels);
		return NULL;
	}

	pixbuf = gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8,
					   width, height, rowstride,
					   (GdkPixbufDestroyNotify) g_free, NULL);
	pop_handlers ();

	scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
						 width * rc->scale,
						 height * rc->scale * (x_res / y_res),
						 GDK_INTERP_BILINEAR);
	g_object_unref (pixbuf);

	rotated_pixbuf = gdk_pixbuf_rotate_simple (scaled_pixbuf, 360 - rc->rotation);
	g_object_unref (scaled_pixbuf);

	return rotated_pixbuf;
}

static gchar *
tiff_document_get_page_label (EvDocument *document,
			      EvPage     *page)
{
	TiffDocument *tiff_document = TIFF_DOCUMENT (document);
	static gchar *label;

	if (TIFFGetField (tiff_document->tiff, TIFFTAG_PAGENAME, &label) &&
	    g_utf8_validate (label, -1, NULL)) {
		return g_strdup (label);
	}

	return NULL;
}

static void
tiff_document_finalize (GObject *object)
{
	TiffDocument *tiff_document = TIFF_DOCUMENT (object);

	if (tiff_document->tiff)
		TIFFClose (tiff_document->tiff);
	if (tiff_document->uri)
		g_free (tiff_document->uri);

	G_OBJECT_CLASS (tiff_document_parent_class)->finalize (object);
}

static void
tiff_document_class_init (TiffDocumentClass *klass)
{
	GObjectClass    *gobject_class = G_OBJECT_CLASS (klass);
	EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass);

	gobject_class->finalize = tiff_document_finalize;

	ev_document_class->load = tiff_document_load;
	ev_document_class->save = tiff_document_save;
	ev_document_class->get_n_pages = tiff_document_get_n_pages;
	ev_document_class->get_page_size = tiff_document_get_page_size;
	ev_document_class->render = tiff_document_render;
	ev_document_class->get_page_label = tiff_document_get_page_label;
}

static GdkPixbuf *
tiff_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document,
					EvRenderContext      *rc,
					gboolean              border)
{
	GdkPixbuf *pixbuf;

	pixbuf = tiff_document_render_pixbuf (EV_DOCUMENT (document), rc);

	if (border) {
		GdkPixbuf *tmp_pixbuf = pixbuf;

		pixbuf = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf);
		g_object_unref (tmp_pixbuf);
	}

	return pixbuf;
}

static void
tiff_document_thumbnails_get_dimensions (EvDocumentThumbnails *document,
					 EvRenderContext      *rc,
					 gint                 *width,
					 gint                 *height)
{
	gdouble page_width, page_height;

	tiff_document_get_page_size (EV_DOCUMENT (document),
				     rc->page,
				     &page_width, &page_height);

	if (rc->rotation == 90 || rc->rotation == 270) {
		*width = (gint) (page_height * rc->scale);
		*height = (gint) (page_width * rc->scale);
	} else {
		*width = (gint) (page_width * rc->scale);
		*height = (gint) (page_height * rc->scale);
	}
}

static void
tiff_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface)
{
	iface->get_thumbnail = tiff_document_thumbnails_get_thumbnail;
	iface->get_dimensions = tiff_document_thumbnails_get_dimensions;
}

/* postscript exporter implementation */
static void
tiff_document_file_exporter_begin (EvFileExporter        *exporter,
				   EvFileExporterContext *fc)
{
	TiffDocument *document = TIFF_DOCUMENT (exporter);

	document->ps_export_ctx = tiff2ps_context_new(fc->filename);
}

static void
tiff_document_file_exporter_do_page (EvFileExporter *exporter, EvRenderContext *rc)
{
	TiffDocument *document = TIFF_DOCUMENT (exporter);

	if (document->ps_export_ctx == NULL)
		return;
	if (TIFFSetDirectory (document->tiff, rc->page->index) != 1)
		return;
	tiff2ps_process_page (document->ps_export_ctx, document->tiff,
			      0, 0, 0, 0, 0);
}

static void
tiff_document_file_exporter_end (EvFileExporter *exporter)
{
	TiffDocument *document = TIFF_DOCUMENT (exporter);

	if (document->ps_export_ctx == NULL)
		return;
	tiff2ps_context_finalize(document->ps_export_ctx);
}

static EvFileExporterCapabilities
tiff_document_file_exporter_get_capabilities (EvFileExporter *exporter)
{
	return  EV_FILE_EXPORTER_CAN_PAGE_SET |
		EV_FILE_EXPORTER_CAN_COPIES |
		EV_FILE_EXPORTER_CAN_COLLATE |
		EV_FILE_EXPORTER_CAN_REVERSE |
		EV_FILE_EXPORTER_CAN_GENERATE_PS;
}

static void
tiff_document_document_file_exporter_iface_init (EvFileExporterInterface *iface)
{
	iface->begin = tiff_document_file_exporter_begin;
	iface->do_page = tiff_document_file_exporter_do_page;
	iface->end = tiff_document_file_exporter_end;
	iface->get_capabilities = tiff_document_file_exporter_get_capabilities;
}

static void
tiff_document_init (TiffDocument *tiff_document)
{
	tiff_document->n_pages = -1;
}
