/* 
   viewer.m

   Image viewer

   Copyright (C) 1999 Free Software Foundation, Inc.

   Author:  Felipe A. Rodriguez <far@pcmagic.net>
   Date: 	March 1999
   
   This file is part of the mySTEP Library and is provided
   under the terms of the GNU Library General Public License.
*/ 

#include <png.h>
#include <setjmp.h>
#include <jpeglib.h>
#include <gif_lib.h>

#import <Foundation/NSArray.h>
#import <AppKit/AppKit.h>

// Class variables
static NSArray *__bitmapImageRepsJPG;
static NSArray *__bitmapImageRepsGIF;
static NSArray *__bitmapImageRepsPNG;


//*****************************************************************************
//
// 		GSBitmapImageRepPNG 
//
//*****************************************************************************

@interface GSBitmapImageRepPNG : NSBitmapImageRep
@end

@implementation GSBitmapImageRepPNG 

+ (void) initialize
{
	if (self == [GSBitmapImageRepPNG class])
		__bitmapImageRepsPNG = [NSArray arrayWithObjects: @"png", nil];
}

+ (BOOL) canInitWithData:(NSData *)data
{
	return YES;
}

+ (NSArray *) imageFileTypes				{ return __bitmapImageRepsPNG; }
+ (NSArray *) imageUnfilteredFileTypes		{ return __bitmapImageRepsPNG; }

void
png_warning(png_structp png_ptr, png_const_charp message)
{
PNG_CONST char *name = "UNKNOWN (ERROR!)";

	if (png_ptr != NULL && png_ptr->error_ptr != NULL)
		name = png_ptr->error_ptr;
	NSLog(@"%s: libpng warning: %s\n", name, message);
}

void
png_error(png_structp png_ptr, png_const_charp message)
{
	png_warning(png_ptr, message);
}

void
png_read(png_structp png_ptr, png_bytep data, png_size_t length)
{
	if (!memcpy((void *)data, (void *)png_ptr->io_ptr, (size_t)length))
		png_error(png_ptr, "Read Error");

	png_ptr->io_ptr += length;
}

+ (NSArray *) imageRepsWithData:(NSData *)data
{
NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
GSBitmapImageRepPNG *imageRep = [[self class] alloc];
png_structp read_ptr;
png_infop read_info_ptr, end_info_ptr;
png_uint_32 y, width, height;
int num_pass, pass;
int bit_depth, color_type, row_bytes, intent;
int interlace_type, compression_type, filter_type;
const char *pin = [data bytes];
char *buffer;
BOOL alpha;
png_color_16p background;
double screen_gamma = 2.2;				// A good guess for a PC monitors

	read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
	png_set_error_fn(read_ptr, (png_voidp)NULL, png_error, png_warning);
	read_info_ptr = png_create_info_struct(read_ptr);
	end_info_ptr = png_create_info_struct(read_ptr);
										// Establish the setjmp return context 
	if (setjmp(read_ptr->jmpbuf))		// for my_error_exit to use.  PNG code
		{								// has signaled an error.  Clean up.
		NSLog(@"error while decompressing PNG");
		png_destroy_read_struct(&read_ptr, &read_info_ptr, &end_info_ptr);
		NSRunAlertPanel(0, @"error while decompressing PNG",@"Continue",0,0);
		return nil;
		}

	png_set_read_fn(read_ptr, (png_voidp)pin, png_read);
	png_set_read_status_fn(read_ptr, NULL);
	png_read_info(read_ptr, read_info_ptr);

	if (png_get_IHDR(read_ptr, read_info_ptr, &width, &height, &bit_depth,
			&color_type, &interlace_type, &compression_type, &filter_type));

	if (png_get_valid(read_ptr, read_info_ptr, PNG_INFO_tRNS))
		{									// Expand paletted or RGB images
		png_set_tRNS_to_alpha(read_ptr);	// with transparency to full alpha
		alpha = YES;						// channels so the data will be
		}									// available as RGBA quartets.
    else
		alpha = (color_type & PNG_COLOR_MASK_ALPHA);
				// expand palette images to RGB, low-bit-depth grayscale 
				// images to 8 bits, transparency chunks to full alpha channel.
    if (color_type == PNG_COLOR_TYPE_PALETTE
			|| (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8))
        png_set_expand(read_ptr);
    if (png_get_valid(read_ptr, read_info_ptr, PNG_INFO_tRNS))
        png_set_expand(read_ptr);
    if (bit_depth == 16)					// strip 16-bit-per-sample
        png_set_strip_16(read_ptr);			// images to 8 bits per sample
    if (color_type == PNG_COLOR_TYPE_GRAY
			|| color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
        png_set_gray_to_rgb(read_ptr);		// convert grayscale to RGB[A]

	if (png_get_sRGB(read_ptr, read_info_ptr, &intent))
		png_set_gamma(read_ptr, screen_gamma, 0.45455);
	else
		{									// Tell libpng to handle gamma
		double image_gamma;					// conversion

		if (png_get_gAMA(read_ptr, read_info_ptr, &image_gamma))
			png_set_gamma(read_ptr, screen_gamma, image_gamma);
		else
			png_set_gamma(read_ptr, screen_gamma, 0.45455);
		}

	num_pass = png_set_interlace_handling(read_ptr);
	png_read_update_info(read_ptr, read_info_ptr);
	row_bytes = png_get_rowbytes(read_ptr, read_info_ptr);

	imageRep = [imageRep initWithBitmapDataPlanes: NULL
						 pixelsWide: width
						 pixelsHigh: height
						 bitsPerSample: 8
						 samplesPerPixel: (alpha) ? 4 : 3
						 hasAlpha: alpha
						 isPlanar: NO
						 colorSpaceName: NSDeviceRGBColorSpace
						 bytesPerRow: row_bytes
						 bitsPerPixel: read_info_ptr->pixel_depth];

    buffer = [imageRep bitmapData];

	for (pass = 0; pass < num_pass; pass++)
		{
		for (y = 0; y < height; y++)
			{
			png_bytep r_buf[1];

			r_buf[0] = buffer + y * row_bytes;

			png_read_rows(read_ptr, (png_bytepp)r_buf, (png_bytepp)NULL, 1);
		}	}

	png_read_end(read_ptr, end_info_ptr);
	png_destroy_read_struct(&read_ptr, &read_info_ptr, &end_info_ptr);

	[array addObject: [imageRep autorelease]];

	return array;
}
												// Loads only the default image
- (id) initWithData:(NSData *)data				// (first) from TIFF contained
{										 		// in data.
	return self;
}

@end /* GSBitmapImageRepPNG */

//*****************************************************************************
//
// 		GSBitmapImageRepGIF 
//
//*****************************************************************************

@interface GSBitmapImageRepGIF : NSBitmapImageRep
@end

@implementation GSBitmapImageRepGIF 

+ (void) initialize
{
	if (self == [GSBitmapImageRepGIF class])
		__bitmapImageRepsGIF = [NSArray arrayWithObjects: @"gif", nil];
}

+ (BOOL) canInitWithData:(NSData *)data
{
	return YES;
}

+ (NSArray *) imageFileTypes				{ return __bitmapImageRepsGIF; }
+ (NSArray *) imageUnfilteredFileTypes		{ return __bitmapImageRepsGIF; }


typedef struct {
	char *data;
	long size;
	long position;
} GIF;

GIF * 
GIFOpenData(char *data, long size)
{												// Open a tiff from a stream. 
GIF *handle;									// Returns NULL if can't read 
												// the tiff info.
	NSDebugLog (@"GIFOpenData\n");
	handle = objc_malloc (sizeof(GIF));
	handle->data = data;
	handle->position = 0;
	handle->size = size;

	return handle;
}

static int
ReadGIF(GifFileType *handle, GifByteType *buf, int count)
{
GIF *chand = (GIF *)handle->UserData;

	NSDebugLog (@"ReadGIF\n");
	if (chand->position >= chand->size)
		return 0;
	if (chand->position + count > chand->size)
		count = chand->size - chand->position;
	memcpy(buf, chand->data + chand->position, count);
	chand->position += count;

	return count;
}

int
ReadGIFToBuf(GifFileType *GifFile, GIF *handle, char *data)
{
int	i, j, k, Size, Width, Height, ExtCode, Count;
GifRecordType RecordType = UNDEFINED_RECORD_TYPE;
GifByteType *Extension;
GifRowType *ScreenBuffer;    // unsinged char
unsigned char *buffer;
static ColorMapObject *ColorMap;
static GifColorType *ColorMapEntry;

	NSDebugLog (@"ReadGIFToBuf\n");

    while (RecordType != TERMINATE_RECORD_TYPE)
		{
		if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR)
			return 6;

		switch (RecordType)					// Scan the content of the GIF file
			{
			case IMAGE_DESC_RECORD_TYPE:
				if (DGifGetImageDesc(GifFile) == GIF_ERROR)
					return 1;

				Width = GifFile->Image.Width;
				Height = GifFile->Image.Height;
				ColorMap = (GifFile->Image.ColorMap
						 ? GifFile->Image.ColorMap : GifFile->SColorMap);

				buffer = malloc(Width * sizeof(GifColorType));

				if (GifFile->Image.Interlace)		// Need to perform 4 passes
					{								// on interlaced images
					unsigned char *GifRow = buffer;
					int offset[] = { 0, 4, 2, 1 };
					int jump[] = { 8, 8, 4, 2 };

					NSLog (@"(GifFile->Image.Interlace)\n");

				for (i = 0; i < 4; i++)
					for (k = offset[i]; k < Height; k += jump[i])
						{
						unsigned char *p;

						if (DGifGetLine(GifFile, buffer, Width) == GIF_ERROR)
							return 2;

						p = data + (k * Width * 3);		// assume RGB for now
						for (j = 0; j < Width; j++)
							{
							ColorMapEntry = &ColorMap->Colors[GifRow[j]];
	
							*p++ = ColorMapEntry->Red;
							*p++ = ColorMapEntry->Green;
							*p++ = ColorMapEntry->Blue;
					}	}	}
				else
					{
					for (i = 0; i < Height; i++)
						{
						unsigned char *GifRow = buffer;
						unsigned char *p;

						if (DGifGetLine(GifFile, buffer, Width) == GIF_ERROR)
							return 3;

						p = data + (i * Width * 3);		// assume RGB for now
						for (j = 0; j < Width; j++)
							{
							ColorMapEntry = &ColorMap->Colors[GifRow[j]];
	
							*p++ = ColorMapEntry->Red;
							*p++ = ColorMapEntry->Green;
							*p++ = ColorMapEntry->Blue;
					}	}	}
				break;

			case EXTENSION_RECORD_TYPE:	   // Skip any extension blocks in file

				if (DGifGetExtension(GifFile,&ExtCode,&Extension) == GIF_ERROR)
					return 4;

				while (Extension != NULL)
					if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR)
						return 5;
				break;

			case TERMINATE_RECORD_TYPE:
			default:				// Should be trapped by DGifGetRecordType
				break;
		}	}

	free(buffer);

	return 0;
}

+ (NSArray *) imageRepsWithData:(NSData *)data
{
NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
GSBitmapImageRepGIF *imageRep = [[self class] alloc];
GIF *handle = GIFOpenData((char *)[data bytes], [data length]);
GifFileType *GifFile = DGifOpen(handle, ReadGIF);

	imageRep = [imageRep initWithBitmapDataPlanes: NULL
						 pixelsWide: GifFile->SWidth
						 pixelsHigh: GifFile->SHeight
						 bitsPerSample: 8
//						 samplesPerPixel: GifFile->SColorResolution
						 samplesPerPixel: 3
						 hasAlpha: (GifFile->SColorResolution > 3)
						 isPlanar: NO
						 colorSpaceName: NSDeviceRGBColorSpace
						 bytesPerRow: GifFile->SWidth * sizeof(GifPixelType)
						 bitsPerPixel: GifFile->SColorMap->BitsPerPixel];
												// read gif into data array
	if (ReadGIFToBuf(GifFile, handle, [imageRep bitmapData]))
		[NSException raise:NSTIFFException format: @"invalid GIF image"];

	[array addObject: [imageRep autorelease]];

	return array;
}
												// Loads only the default image
- (id) initWithData:(NSData *)data				// (first) from TIFF contained
{										 		// in data.
	return self;
}

@end /* GSBitmapImageRepGIF */

//*****************************************************************************
//
// 		GSBitmapImageRepJPEG 
//
//*****************************************************************************

@interface GSBitmapImageRepJPEG : NSBitmapImageRep
@end

@implementation GSBitmapImageRepJPEG 

+ (void) initialize				// in practice, only two of them can load in	 
{								// data from an external source.
	if (self == [GSBitmapImageRepJPEG class])
		{
		__bitmapImageRepsJPG = [NSArray arrayWithObjects: @"jpeg", @"jpg",nil];
//		_pbBitmapImageReps = [NSArray arrayWithObjects: NSTIFFPboardType, nil];
		}
}

+ (BOOL) canInitWithData:(NSData *)data
{
	return YES;
}

+ (NSArray *) imageFileTypes				{ return __bitmapImageRepsJPG; }
+ (NSArray *) imageUnfilteredFileTypes		{ return __bitmapImageRepsJPG; }

struct my_error_mgr {
	struct jpeg_error_mgr pub;		// "public" fields
	jmp_buf setjmp_buffer;			// setjmp for error recovery
};

typedef struct my_error_mgr *my_error_ptr;

 									// Here's the routine that will replace the 
METHODDEF(void)						// standard error_exit method:
jpg_error_exit (j_common_ptr cinfo)
{									// cinfo->err really points to a 
									// my_error_mgr struct, so coerce pointer
	my_error_ptr myerr = (my_error_ptr) cinfo->err;
									// Display the message.  could postpone
									// this til after return, if we choose.
	(*cinfo->err->output_message) (cinfo);

	longjmp(myerr->setjmp_buffer, 1);	// Return control to the setjmp point
}

								// Expanded data source object for stdio input
typedef struct {
	struct jpeg_source_mgr pub;		// public fields
	FILE *infile;					// source stream
	JOCTET *buffer;					// start of buffer
	boolean start_of_file;			// have we gotten any data yet?
} my_source_mgr;

typedef my_source_mgr * my_src_ptr;


METHODDEF(boolean)								// Fill input buffer, called 
gs_fill_input_buffer (j_decompress_ptr cinfo)	// whenever buffer is emptied.
{
my_src_ptr src = (my_src_ptr) cinfo->src;

	src->start_of_file = FALSE;
	
	return TRUE;
}
						// Skip data --- used to skip over a potentially large 
METHODDEF(void)			// amount of uninteresting data such as an APPn marker
gs_skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
my_src_ptr src = (my_src_ptr) cinfo->src;
				// Just a dumb implementation for now.  Could use fseek() 
				// except it doesn't work on pipes.  Not clear that being smart 
				// is worth any trouble anyway --- large skips are infrequent.
	if (num_bytes > 0) 
		{
		while (num_bytes > (long) src->pub.bytes_in_buffer) 
			{
			num_bytes -= (long) src->pub.bytes_in_buffer;
			(void) gs_fill_input_buffer(cinfo);
				// note we assume that gs_fill_input_buffer will never return 
				// FALSE, so suspension need not be handled.
			}
		src->pub.next_input_byte += (size_t) num_bytes;
		src->pub.bytes_in_buffer -= (size_t) num_bytes;
		}
}

+ (NSArray *) imageRepsWithData:(NSData *)data
{				// struct containing the JPEG decompression parameters and 
				// pointers to working space (which is allocated as needed by 
				// the JPEG library).
struct jpeg_decompress_struct cinfo;
				// We use our private extension JPEG error handler.  Note that 
				// this struct must live as long as the main JPEG parameter
				// struct, to avoid dangling-pointer problems.
struct my_error_mgr jerr;
NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
GSBitmapImageRepJPEG *imageRep;
JSAMPROW buffer[1];						// Set up normal JPEG error routines, 
										// then override error_exit.
	cinfo.err = jpeg_std_error(&jerr.pub);
	jerr.pub.error_exit = jpg_error_exit;
										// Establish the setjmp return context 
	if (setjmp(jerr.setjmp_buffer)) 	// for jpg_error_exit to use.
		{		// If we get here, the JPEG code has signaled an error. Need to
				// clean up the JPEG object, close the input file, and return.
		jpeg_destroy_decompress(&cinfo);
		NSLog(@"error while decompressing JPEG");
		NSRunAlertPanel(0, @"error while decompressing JPEG",@"Continue",0,0);
		return nil;
		}

	jpeg_create_decompress(&cinfo);				// init JPEG decompression obj
	jpeg_stdio_src(&cinfo, (FILE *)NULL);		// specify the JPEG data source

	cinfo.src->fill_input_buffer = &gs_fill_input_buffer;
	cinfo.src->skip_input_data = &gs_skip_input_data;
	cinfo.src->next_input_byte = [data bytes];
	cinfo.src->bytes_in_buffer = [data length];

    jpeg_read_header(&cinfo, TRUE);

	if(cinfo.jpeg_color_space == JCS_GRAYSCALE)
		cinfo.out_color_space = JCS_GRAYSCALE;
	else
        cinfo.out_color_space = JCS_RGB;
    cinfo.quantize_colors = FALSE;
    cinfo.do_fancy_upsampling = FALSE;
    cinfo.do_block_smoothing = FALSE;

	jpeg_calc_output_dimensions(&cinfo);
    jpeg_start_decompress(&cinfo);

	imageRep = [[[[self class] alloc] initWithBitmapDataPlanes: NULL
									  pixelsWide: cinfo.image_width
									  pixelsHigh: cinfo.image_height
									  bitsPerSample: 8
									  samplesPerPixel: cinfo.num_components
									  hasAlpha: (cinfo.num_components > 3)
									  isPlanar: NO
									  colorSpaceName: NSDeviceRGBColorSpace
									  bytesPerRow: 0
									  bitsPerPixel: 0] autorelease];
//	imageRep->compression = info->compression;

    buffer[0] = [imageRep bitmapData];

    while (cinfo.output_scanline < cinfo.output_height)
		{
        jpeg_read_scanlines(&cinfo, buffer, (JDIMENSION)1);
		buffer[0] += (cinfo.output_width * 3);		// data is in RGB planes
		}											// so mult by row stride

	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);				// release jpg and it's mem

	[array addObject: imageRep];

	return array;
}
												// Loads only the default image
- (id) initWithData:(NSData *)data				// (first) from TIFF contained
{										 		// in data.
	return self;
}

@end /* GSBitmapImageRepJPEG */

//*****************************************************************************
//
// 		Document 
//
//*****************************************************************************

@interface Document : GSDocument
{
	NSWindow *_documentWindow;
}

@end

@implementation Document

- (id) initWithPath:(NSString *)filename 
{
static NSRect wr = {{100,0},{0,0}};
NSImageView *v;
NSImage *image;

    if (!filename)
		return nil; 

	[self init];

	_documentWindow = [[NSWindow alloc] init];

	image = [[NSImage alloc] initWithContentsOfFile: filename];
	wr.size = [image size];
	v = [[NSImageView alloc] initWithFrame: (NSRect){{0,0}, wr.size}];
	[v setImage:image];
	[v setImageScaling:NSScaleToFit];
	[v setAutoresizingMask: (NSViewWidthSizable|NSViewHeightSizable)];
	[[_documentWindow contentView] addSubview: v];

	[_documentWindow setDelegate:self];
	[_documentWindow setTitle:[filename lastPathComponent]];

	if (NSMinY(wr) == 0)
		wr.origin.y = NSHeight([[NSScreen mainScreen] frame]) - 50;
	wr.origin = [_documentWindow cascadeTopLeftFromPoint:wr.origin];
	wr.origin.y -= NSHeight(wr);
	[_documentWindow setFrame:wr display:YES];
	wr.origin.y += NSHeight(wr);
	[_documentWindow orderFront:nil];

	[self setDocumentName:filename];

    return self;
}

- (NSWindow *) window					{ return _documentWindow; }

- (void) windowWillClose:(NSNotification *)notification 
{											
	NSLog(@"windowWillClose1");
    [_documentWindow setDelegate:nil];
    [self release];
}

@end /* Document */

//*****************************************************************************
//
// 		Controller 
//
//*****************************************************************************

@interface Controller : NSObject
{
	NSMutableArray *imageArray;
	int index;
	BOOL cycled;
}

@end

@interface NSApplication (NeXTSTEPAppIconWindow)

- (NSWindow*) appIcon;

@end

@implementation Controller

- (void) method:menuCell
{
	NSLog (@"method invoked from cell with title '%@'", [menuCell title]);
}

- (void) applicationDidFinishLaunching:(NSNotification *)aNotification
{
Document *document;
NSString *m = [[NSBundle mainBundle] bundlePath];
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
NSArray *arguments = [processInfo arguments];
NSString *initialFile;

	[NSApp setApplicationIconImage:[NSImage imageNamed:@"g0.tiff"]];

	[[NSApp appIcon] setDelegate:self];
	[[[NSApp appIcon] contentView] registerForDraggedTypes:nil];

	[NSImageRep registerImageRepClass: [GSBitmapImageRepJPEG class]];
	[NSImageRep registerImageRepClass: [GSBitmapImageRepGIF class]];
	[NSImageRep registerImageRepClass: [GSBitmapImageRepPNG class]];

	if ([arguments count] < 2) 
		initialFile = [m stringByAppendingString:@"/Resources/w1s.jpg"];
	else
		initialFile = [arguments objectAtIndex:1];

	document = [[Document alloc] initWithPath:initialFile];
}

- (BOOL) application:(NSApplication *)sender openFile:(NSString *)filename 
{
    return [Document openDocumentWithPath:filename];
}

- (void) fireAnimator:(id)sender
{
	if (cycled)
		index--;
	else
		index++;

	NSDebugLog (@"fireAnimator counter at %d", index);
	[NSApp setApplicationIconImage:[imageArray objectAtIndex:index]];

	if(index >= 8)
		cycled = YES;
	
	if(cycled && index == 0)
		{
		[sender invalidate];
		cycled = NO;
		}
}

- (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSDragPboard];
NSDictionary *d = [pb propertyListForType:NSFilenamesPboardType];
NSString *Path = [d objectForKey:@"SourcePath"];

	NSDebugLog (@"Path: '%@'\n", Path);
	NSDebugLog (@"performDragOperation");

	if (!imageArray)
		{									// produce an image objects array
		imageArray = [NSMutableArray new];
		[imageArray addObject: [NSImage imageNamed:@"g0"]];		// open
		[imageArray addObject: [NSImage imageNamed:@"g20"]];	// open
		[imageArray addObject: [NSImage imageNamed:@"g21"]];
		[imageArray addObject: [NSImage imageNamed:@"g22"]];
		[imageArray addObject: [NSImage imageNamed:@"g23"]];
		[imageArray addObject: [NSImage imageNamed:@"g24"]];	// close
		[imageArray addObject: [NSImage imageNamed:@"g4"]];
		[imageArray addObject: [NSImage imageNamed:@"g4"]];
		[imageArray addObject: [NSImage imageNamed:@"g4"]];
		}

	[NSTimer scheduledTimerWithTimeInterval: 0.2
			 target: self
			 selector: @selector(fireAnimator:)
			 userInfo: nil
			 repeats: YES];

	return YES;
}

- (unsigned int) draggingUpdated:(id <NSDraggingInfo>)sender
{
	return NSDragOperationGeneric;
}

@end
