/* tiff.c
 * TIFF image decoding
 * (c) 2002 Petr 'Brain' Kulhavy
 * This file is a part of the Links program, released under GPL.
 *
 * Compiles in graphics mode only and only when HAVE_TIFF.
 */
#include "cfg.h"

#ifdef G
#include "links.h"

#ifdef HAVE_TIFF
#include <tiffio.h>
#include "bits.h"

struct tiff_decoder{
	unsigned char *tiff_data; /* undecoded data */
	int tiff_size;	/* size of undecoded file */
	int tiff_pos;
	int tiff_open;   /* 1 if tiff was open, means: tiff_data, tiff_size and tiff_pos are valid */
};

void tiff_start(struct cached_image *cimg)
{
	struct tiff_decoder * deco;

	deco=mem_alloc(sizeof(struct tiff_decoder));

	cimg->decoder=deco;
	deco->tiff_size=0;
	deco->tiff_data=NULL;
	deco->tiff_open=0;
	deco->tiff_pos=0;
}


void tiff_restart(struct cached_image *cimg, unsigned char *data, int length)
{
	struct tiff_decoder * deco=(struct tiff_decoder*)cimg->decoder;
	unsigned char *p;

	if (!deco->tiff_data) {
		if ((unsigned)length > MAXINT) overalloc();
		p=mem_alloc(length);
	} else {
		if ((unsigned)length + (unsigned)deco->tiff_size > MAXINT) overalloc();
		if ((unsigned)length + (unsigned)deco->tiff_size < (unsigned)length) overalloc();
		p=mem_realloc(deco->tiff_data,deco->tiff_size+length);
	}
	deco->tiff_data=p;
	memcpy(deco->tiff_data+deco->tiff_size,data,length);
	deco->tiff_size+=length;
}

static toff_t tiff_size(thandle_t data)
{
	struct cached_image *cimg=(struct cached_image *)data;
	struct tiff_decoder *deco=(struct tiff_decoder*)cimg->decoder;

	if (!deco->tiff_open)internal_error("BUG IN LIBTIFF: sizeproc called on closed file. Contact the libtiff authors.\n");

	return deco->tiff_size;
}


static tsize_t tiff_read(thandle_t data, tdata_t dest, tsize_t count)
{
	struct cached_image *cimg=(struct cached_image *)data;
	struct tiff_decoder *deco=(struct tiff_decoder*)cimg->decoder;

	if (!deco->tiff_open)internal_error("BUG IN LIBTIFF: readproc called on closed file. Contact the libtiff authors.\n");

	if (count < 0)
		return 0;

	if (count > deco->tiff_size-deco->tiff_pos)
		count = deco->tiff_size-deco->tiff_pos;
	memcpy(dest,deco->tiff_data+deco->tiff_pos,count);
	deco->tiff_pos+=(int)count;
	return count;
}


static tsize_t tiff_write(thandle_t data, tdata_t dest, tsize_t count)
{
	internal_error("BUG IN LIBTIFF: writeproc called on read-only file. Contact the libtiff authors.\n");
	return 0;
}


static toff_t tiff_seek(thandle_t data, toff_t offset, int whence)
{
	struct cached_image *cimg=(struct cached_image *)data;
	struct tiff_decoder *deco=(struct tiff_decoder*)cimg->decoder;
	long pos;

	if (!deco->tiff_open)internal_error("BUG IN LIBTIFF: seekproc called on closed file. Contact the libtiff authors.\n");

	switch(whence)
	{
		case SEEK_SET:
			pos = offset;
			break;
		case SEEK_CUR:
			pos = (unsigned long)deco->tiff_pos + offset;
			break;
		case SEEK_END:
			pos = (unsigned long)deco->tiff_size + offset;
			break;
		default:
			pos = deco->tiff_pos;
			break;
	}
	if (pos > deco->tiff_size) pos = deco->tiff_size;
	if (pos < 0) pos = 0;
	deco->tiff_pos = (int)pos;
	return deco->tiff_pos;
}


static int tiff_close(void *data)
{
	struct cached_image *cimg=(struct cached_image *)data;
	struct tiff_decoder *deco=(struct tiff_decoder*)cimg->decoder;

	if (!deco->tiff_open)internal_error("BUG IN LIBTIFF: closeproc called on closed file. Contact the libtiff authors.\n");
	tiff_destroy_decoder(cimg);
	return 0;
}


static int tiff_mmap(thandle_t data, tdata_t *dest, toff_t *len)
{
	struct cached_image *cimg=(struct cached_image *)data;
	struct tiff_decoder *deco=(struct tiff_decoder*)cimg->decoder;

	if (!deco->tiff_open)internal_error("BUG IN LIBTIFF: mapproc called on closed file. Contact the libtiff authors.\n");
	*dest=deco->tiff_data;
	*len=deco->tiff_size;
	return 0;
}


static void tiff_munmap(thandle_t data, tdata_t dest, toff_t len)
{
	struct cached_image *cimg=(struct cached_image *)data;
	struct tiff_decoder *deco=(struct tiff_decoder*)cimg->decoder;

	if (!deco->tiff_open)internal_error("BUG IN LIBTIFF: unmapproc called on closed file. Contact the libtiff authors.\n");
}


static void tiff_error_handler(const char* module, const char* fmt, va_list ap)
{
}

static void flip_buffer(void *buf, int width, int height)
{
	if (!big_endian) {	/* little endian --- ja to chci na intelu rychly!!! */
#ifdef t4c
		t4c* buffer=(t4c*)buf;
		register t4c a,b;
		t4c *p,*q;
		int i,l;

		for (l=0,p=buffer,q=buffer+width*(height-1);l<(height>>1);l++,q-=(width<<1))
			for (i=0;i<width;a=*p,b=*q,*p++=b,*q++=a,i++)
				;
#else
		unsigned char* buffer=(unsigned char*)buf;
		unsigned char *p,*q;
		int l;
		unsigned char *tmp;
		int w=4*width;

		if ((unsigned)w > MAXINT) overalloc();
		tmp=mem_alloc(w);

		/* tohle je pomalejsi, protoze se kopiruje pamet->pamet, pamet->pamet */
		/* kdyz mame 4B typek, tak se kopiruje pamet->reg, reg->pamet */
		for (l=0,p=buffer,q=buffer+w*(height-1);l<(height>>1);l++,q-=w,p+=w)
			memcpy(tmp,p,w),memcpy(p,q,w),memcpy(q,tmp,w);
		mem_free(tmp);
#endif
	} else {	/* big endian */
		unsigned char zakazany_uvolneni[4];
		unsigned char* buffer=(unsigned char*)buf;
		int w=width<<2; /* 4 bytes per pixel */
		unsigned char *p,*q;
		int i,l;

		for (l=0,p=buffer,q=buffer+w*(height-1);l<(height>>1);l++,q-=(w<<1))
			for (i=0;i<width;i++,p+=4,q+=4)
			{
				memcpy(zakazany_uvolneni,p,4);
				p[0]=q[3];
				p[1]=q[2];
				p[2]=q[1];
				p[3]=q[0];
				q[0]=zakazany_uvolneni[3];
				q[1]=zakazany_uvolneni[2];
				q[2]=zakazany_uvolneni[1];
				q[3]=zakazany_uvolneni[0];
			}
		if (height&1) /* flip endianity of line in the middle (if the height is odd) */
			for (i=0;i<width;i++,p+=4)
			{
				memcpy(zakazany_uvolneni,p,4);
				p[0]=zakazany_uvolneni[3];
				p[1]=zakazany_uvolneni[2];
				p[2]=zakazany_uvolneni[1];
				p[3]=zakazany_uvolneni[0];
			}
	}
}

void tiff_finish(struct cached_image *cimg)
{
	struct tiff_decoder *deco=(struct tiff_decoder*)cimg->decoder;
	int bla, x, y;
	TIFF *t;

	if (!deco->tiff_size){img_end(cimg);return;}
	deco->tiff_open=1;
	TIFFSetErrorHandler(tiff_error_handler);
	TIFFSetWarningHandler(tiff_error_handler);
	t=TIFFClientOpen(
			"Prave si rek' svy posledni slova. A vybral sis k tomu prihodny misto.",
			"r",
			cimg,
			(TIFFReadWriteProc)tiff_read,
			(TIFFReadWriteProc)tiff_write,
			(TIFFSeekProc)tiff_seek,
			(TIFFCloseProc)tiff_close,
			(TIFFSizeProc)tiff_size,
			(TIFFMapFileProc)tiff_mmap,
			(TIFFUnmapFileProc)tiff_munmap
		);
	if (!t) { img_end(cimg); return; }

	bla = TIFFGetField(t, TIFFTAG_IMAGEWIDTH, &x);
	if (!bla) { TIFFClose(t); img_end(cimg); return; }
	cimg->width = x;
	bla = TIFFGetField(t, TIFFTAG_IMAGELENGTH, &y);
	if (!bla){ TIFFClose(t); img_end(cimg); return; }
	cimg->height = y;
	cimg->buffer_bytes_per_pixel = 4;
	cimg->red_gamma=cimg->green_gamma=cimg->blue_gamma=(float)sRGB_gamma;
	cimg->strip_optimized=0;
	if (header_dimensions_known(cimg)){TIFFClose(t);img_end(cimg);return;}
/* int TIFFReadRGBAImage(TIFF* tif, u_long width, u_long height, u_long* raster, int stopOnError) from man page */
	/*TIFFReadRGBAImage(t,cimg->width,cimg->height,(unsigned long*)(cimg->buffer),1);*/ /* 231: warning: passing arg 4 of `TIFFReadRGBAImage' from incompatible pointer type */
	TIFFReadRGBAImage(t, (unsigned)cimg->width, (unsigned)cimg->height, (void *)cimg->buffer, 1);
	TIFFClose(t);

	/* For some reason the TIFFReadRGBAImage() function chooses the lower
	 * left corner as the origin.  Vertically mirror scanlines.
	 */
	flip_buffer((void*)cimg->buffer, (int)cimg->width, (int)cimg->height);

	img_end(cimg);
}

void tiff_destroy_decoder(struct cached_image *cimg)
{
	struct tiff_decoder *deco=(struct tiff_decoder *)cimg->decoder;
	if (deco->tiff_data) mem_free(deco->tiff_data), deco->tiff_data = NULL;
	deco->tiff_open=0;
}

void add_tiff_version(unsigned char **s, int *l)
{
	unsigned char *p, *pp;
	int pl;
	add_to_str(s, l, cast_uchar "TIFF (");
	p = (unsigned char *)TIFFGetVersion();
	pp = cast_uchar strstr(cast_const_char p, "LIBTIFF, ");
	if (pp) p = pp + 9;
	pp = cast_uchar strstr(cast_const_char p, "Version ");
	if (pp) p = pp + 8;
	pl = (int)strcspn(cast_const_char p, " \n");
	add_bytes_to_str(s, l, p, pl);
	add_chr_to_str(s, l, ')');
}

#endif /* #ifdef HAVE_TIFF */

#endif /* #ifdef G */
