/*

This file is part of Tins IMage Viewer (timv).
Copyright (c) 2010, Martin Furter
All rights reserved.

ANY-LICENSE:
You can use this software under any license approved by the
Open Source Initiative as long as the license you choose is
compatible to the dependencies of Tins IMage Viewer (timv).

See http://www.opensource.org/licenses/ for a list of
approved licenses.

*/
/*============================================================================*/
// {{{ includes

#define _XOPEN_SOURCE 500

#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <imageinfo.h>
#include <gio/gio.h>
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>

// }}} includes
/*============================================================================*/
// {{{ defines

#define INFO_STORED_SIZE \
		( G_STRUCT_OFFSET(ImageInfo,filenamelen) - \
		  G_STRUCT_OFFSET(ImageInfo,width) )
#define THUMB_SIZE		64

// }}} defines
/*============================================================================*/
// {{{ static variables

/* GObject stuff - nothing to worry about*/
static GObjectClass* parent_class = NULL;

static GdkColor bgColorNormal = { 0, 0xFFFF, 0xFFFF, 0xFFFF };
static GdkColor bgColorModified = { 0, 0xFFFF, 0x8000, 0xFFFF };

// }}} static variables
/*============================================================================*/
// {{{ rotate_pixbuf

static void rotate_pixbuf( GdkPixbuf** ppixbuf, int angle )
{
	// printf( "*** rotate %d -> %d ***\n", angle, 90* angle );
	GdkPixbuf* pixbuf = gdk_pixbuf_rotate_simple( *ppixbuf, 90*angle );
	g_object_unref( *ppixbuf );
	*ppixbuf = pixbuf;
}

// }}} rotate_pixbuf
/*----------------------------------------------------------------------------*/
// {{{ flip_pixbuf

static void flip_pixbuf( GdkPixbuf** ppixbuf, gboolean horizontal )
{
	GdkPixbuf* pixbuf = gdk_pixbuf_flip( *ppixbuf, horizontal );
	g_object_unref( *ppixbuf );
	*ppixbuf = pixbuf;
}

// }}} flip_pixbuf
/*============================================================================*/
// {{{ ImageInfo functions
/*----------------------------------------------------------------------------*/
// {{{ image_info_finalize

/* destructor */
static void image_info_finalize( GObject* object )
{
	ImageInfo* info = IMAGE_INFO( object );

	/* free all records and free all memory used by the list */
	if( info->pixbuf )
	{
		g_object_unref( info->pixbuf );
		info->pixbuf = 0;
	}

	/* must chain up - finalize parent */
	parent_class->finalize( object );
}

// }}} image_info_finalize
/*----------------------------------------------------------------------------*/
// {{{ image_info_init

/* constructor*/
static void image_info_init( ImageInfo* info )
{
	info->width = 0;
	info->height = 0;
	info->mtime = 0;
	info->angle = 0;
	info->flip = 0;
	info->filenamelen = 0;
	info->filename = 0;
	info->pixbuf = 0;
	info->savedAngle = 0;
	info->savedFlip = 0;
	info->modified = 0;
	info->bgColor = bgColorNormal;
}

// }}} image_info_init
/*----------------------------------------------------------------------------*/
// {{{ image_info_class_init

/* initialize class */
static void image_info_class_init( ImageInfoClass* klass )
{
	GObjectClass* object_class;

	parent_class = (GObjectClass*)g_type_class_peek_parent( klass );
	object_class = (GObjectClass*)klass;

	object_class->finalize = image_info_finalize;
}

// }}} image_info_class_init
/*----------------------------------------------------------------------------*/
// {{{ image_info_get_type

/* register type if not done yet */
GType image_info_get_type()
{
	static GType image_info_type = 0;

	/* Some boilerplate type registration stuff */
	if (image_info_type == 0)
	{
		static const GTypeInfo image_info_info = {
			sizeof (ImageInfoClass),
			NULL, /* base_init*/
			NULL, /* base_finalize*/
			(GClassInitFunc) image_info_class_init,
			NULL, /* class finalize*/
			NULL, /* class_data*/
			sizeof (ImageInfo),
			0, /* n_preallocs*/
			(GInstanceInitFunc) image_info_init
		};

		/* First register the new derived type with the GObject type system */
		image_info_type = g_type_register_static( G_TYPE_OBJECT,
				"ImageInfo", &image_info_info, (GTypeFlags)0 );
	}

	return image_info_type;
}

// }}} image_info_get_type
/*----------------------------------------------------------------------------*/
// {{{ image_info_new

ImageInfo* image_info_new( const char* filename, time_t mtime )
{
	union {
		uint32_t i[8];
		uint8_t b[32];
		char c[32];
	} data;
	ImageInfo* info;
	int filebuflen;
	GdkPixbufFormat* pixbuffmt;
	int width;
	int height;
	int rc;
	int fd;
	GInputStream* input;

	info = (ImageInfo*)g_object_new( IMAGE_INFO_TYPE, NULL );
	g_assert( info != NULL );

	info->filenamelen = strlen( filename );
	filebuflen = info->filenamelen + 6;
	info->filename = malloc( filebuflen );
	snprintf( info->filename, filebuflen, "%s.timv", filename );
	pixbuffmt = gdk_pixbuf_get_file_info( filename, &width, &height );
	if( !pixbuffmt )
	{
		g_object_unref( info );
		return 0;
	}
	fd = open( info->filename, O_RDONLY );
	//printf( "fd %d file '%s'\n", fd, info->filename );
	if( fd >= 0 )
	{
		printf( "    reading '%s'\n", info->filename );
		rc = read( fd, data.b, 32 );
		if( rc != 32 )
		{
			// file is too short or maybe read error
			close( fd );
			fd = -1;
		}
		else if( strncmp( data.c, IMAGE_INFO_NAME, 4 ) == 0 )
		{
			if( data.b[4] == IMAGE_INFO_VERSION )
			{
				// latest file version
				lseek( fd, 8+INFO_STORED_SIZE, SEEK_SET );
				memcpy( &info->width, data.i+2, INFO_STORED_SIZE );
			}
			else
			{
				// unknown version
				close( fd );
				fd = -1;
			}
		}
		else if( data.i[3] == 0 )
		{
			// old header with 64-bit time_t, convert it
			info->width = data.i[0];
			info->height = data.i[1];
			info->mtime = data.i[2];
			info->angle = data.b[12];
			info->flip = data.b[13];
			info->modified = 1;
			lseek( fd, 18, SEEK_SET );
			printf( "      -> old header 64bit time_t\n" );
		}
		else
		{
			// old header with 32-bit time_t, convert it
			info->width = data.i[0];
			info->height = data.i[1];
			info->mtime = data.i[2];
			info->angle = data.b[16];
			info->flip = data.b[17];
			info->modified = 1;
			lseek( fd, 14, SEEK_SET );
			printf( "      -> old header 32bit time_t\n" );
		}
		if( info->angle > 3 )
		{
			printf( "      -> fixing angle %d -> %d\n", info->angle, info->angle & 3 );
			info->angle &= 3;
		}
		if( fd >= 0 )
		{
			input = g_unix_input_stream_new( fd, TRUE );
			if( info->width == width && info->height == height &&
				info->mtime == mtime )
			{
				info->pixbuf = gdk_pixbuf_new_from_stream( input, 0, 0 );
			}
			g_input_stream_close( input, 0, 0 );
			g_object_unref( input );
		}
		else
		{
			memset( &info->width, 0, INFO_STORED_SIZE );
		}
#if 0
		input = g_unix_input_stream_new( fd, TRUE );
		g_input_stream_read( input, &info->width, INFO_STORED_SIZE, 0, 0 );
		if( info->width == width && info->height == height &&
			info->mtime == mtime )
		{
			info->pixbuf = gdk_pixbuf_new_from_stream( input, 0, 0 );
		}
		g_input_stream_close( input, 0, 0 );
		g_object_unref( input );
#endif
	}
	if( info->pixbuf )
	{
		info->savedAngle = info->angle;
		info->savedFlip = info->flip;
	}
	else
	{
		info->width = width;
		info->height = height;
		info->mtime = mtime;
		info->pixbuf = gdk_pixbuf_new_from_file_at_scale( filename,
				THUMB_SIZE, THUMB_SIZE, TRUE, 0 );
		image_info_save( info );
	}
	info->filename[info->filenamelen] = 0;

	return info;
}

// }}} image_info_new
/*----------------------------------------------------------------------------*/
// {{{ image_info_save

gboolean image_info_save( ImageInfo* info )
{
	int fd;
	ImageInfoHeader_t hdr;
	strcpy( hdr.name, IMAGE_INFO_NAME );
	hdr.version = IMAGE_INFO_VERSION;
	hdr.unused1 = 0;
	hdr.unused2 = 0;
	hdr.unused3 = 0;

	info->filename[info->filenamelen] = '.';
	printf( "    writing %s\n", info->filename );
	fd = open( info->filename, O_WRONLY|O_CREAT,
				S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
	info->filename[info->filenamelen] = 0;
	if( fd < 0 )
	{
		printf( "    couldn't open file for writing\n" );
		return FALSE;
	}
	GOutputStream* output = g_unix_output_stream_new( fd, TRUE );
	g_output_stream_write( output, &hdr, sizeof(hdr), 0, 0 );
	g_output_stream_write( output, &info->width, INFO_STORED_SIZE, 0, 0 );
	gdk_pixbuf_save_to_stream( info->pixbuf, output, "png", 0, 0, NULL );
	g_output_stream_close( output, 0, 0 );
	g_object_unref( output );
	info->savedAngle = info->angle;
	info->savedFlip = info->flip;
	info->modified = 0;
	info->bgColor = bgColorNormal;
	return TRUE;
}

// }}} image_info_save
/*----------------------------------------------------------------------------*/
// {{{ image_info_get_image

GdkPixbuf* image_info_get_image( ImageInfo* info,
		int width, int height )
{
	if( width < 0 && height < 0 )
	{
		width = info->width;
		height = info->height;
	}
	else
	{
		if( info->angle & 1 )
		{
			int tmp = width;
			width = height;
			height = tmp;
		}
	}
	GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size( info->filename,
			width, height, 0 );
	if( pixbuf && info->angle )
	{
		rotate_pixbuf( &pixbuf, info->angle );
	}
	if( pixbuf && info->flip )
	{
		flip_pixbuf( &pixbuf, info->flip == 2 );
	}
	return pixbuf;
}

// }}} image_info_get_image
/*----------------------------------------------------------------------------*/
// {{{ image_info_rotate_or_flip

gboolean image_info_rotate_or_flip( ImageInfo* info,
		char angle, char flip, GdkPixbuf** image )
{
	char modified = info->modified;
	if( angle )
	{
		info->angle = (info->angle + angle) & 3;
		if( info->flip != 0 )
		{
			// when rotating a flipped image swap flip H/V because
			// flip is applied after rotate when loading
			info->flip ^= 3;
		}
		angle = (2-angle) & 3;
		rotate_pixbuf( image, angle );
		rotate_pixbuf( &info->pixbuf, angle );
	}
	if( flip )
	{
		info->flip ^= flip;
		if( info->flip == 3 )
		{
			// flip H+V => rotate 180
			info->flip = 0;
			info->angle = (info->angle + 2) & 3;
		}
		else if( info->flip != 0 && info->angle == 2 )
		{
			// flip H + rotate 180 => flip V + rotate 0
			// flip V + rotate 180 => flip H + rotate 0
			info->angle = 0;
			info->flip ^= 3;
		}
		flip_pixbuf( image, flip == 2 );
		flip_pixbuf( &info->pixbuf, flip == 2 );
	}
	info->modified = (	info->savedAngle == info->angle &&
						info->savedFlip == info->flip	) ? 0 : 1;
	info->bgColor = info->modified ? bgColorModified : bgColorNormal;
	return info->modified != modified;
}

// }}} image_info_rotate_or_flip
/*----------------------------------------------------------------------------*/
// {{{ image_info_copy

void image_info_copy( ImageInfo* dst, ImageInfo* src )
{
	char* filename = NULL;
	GdkPixbuf* pixbuf = NULL;

	if( dst )
	{
		filename = dst->filename;
		pixbuf = dst->pixbuf;
		if( src )
		{
			*dst = *src;
			if( filename && strcmp( filename, src->filename ) == 0 )
			{
				dst->filename = filename;
				filename = NULL;
			}
			else
			{
				dst->filename = strdup( src->filename );
			}
			if( pixbuf == src->pixbuf )
			{
				dst->pixbuf = pixbuf;
				pixbuf = NULL;
			}
			else
			{
				g_object_ref( dst->pixbuf );
			}
		}
		else
		{
			memset( dst, 0, sizeof(*dst) );
		}
		if( filename )
		{
			free( filename );
		}
		if( pixbuf )
		{
			g_object_unref( pixbuf );
		}
	}
}

// }}} image_info_copy
/*----------------------------------------------------------------------------*/
// }}} ImageInfo functions
/*============================================================================*/

