/*
 *	EmulatorView.mm - Custom NSView for Basilisk II windowed graphics output
 *
 *	$Id: EmulatorView.mm,v 1.14 2005/01/30 21:42:13 gbeauche Exp $
 *
 *  Basilisk II (C) 1997-2005 Christian Bauer
 *
 *  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 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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#import "sysdeps.h"			// Types used in Basilisk C++ code,

#define DEBUG 0
#import <debug.h>

#import <Cocoa/Cocoa.h>

#import	"main_macosx.h"		// For WarningAlert() et al prototypes
#import	"misc_macosx.h"		// For InfoSheet() prototype
#import	"video_macosx.h"	// For init_* globals, and bitmap drawing strategy

#import "EmulatorView.h"

@implementation EmulatorView


//
// Standard NSView methods that we override
//

- (id) initWithFrame: (NSRect) frameRect
{
	self = [super initWithFrame: frameRect];

	output = self;		// Set global for access by Basilisk C++ code
//	bitmap = nil;		// Set by readyToDraw:
	drawView = NO;		// Disable drawing until later
	fullScreen = NO;

	return self;
}

- (void) awakeFromNib
{
	// Here we store the height of the screen which the app was opened on.
	// NSApplication's sendEvent: always uses that screen for its mouse co-ords
	screen_height = (int) [[NSScreen mainScreen] frame].size.height;
}


// Mouse click in this window. If window is not active,
// should the click be passed to this view?
- (BOOL) acceptsFirstMouse: (NSEvent *) event
{
	return [self mouseInView];
}


//
// Key event processing.
// OS X doesn't send us separate events for the modifier keys
// (shift/control/command), so we need to monitor them separately
//

#include <adb.h>

static int prevFlags;

- (void) flagsChanged: (NSEvent *) event
{
	int	flags = [event modifierFlags];

	if ( (flags & NSAlphaShiftKeyMask) != (prevFlags & NSAlphaShiftKeyMask) )
		if ( flags & NSAlphaShiftKeyMask )
			ADBKeyDown(0x39);	// CAPS_LOCK
		else
			ADBKeyUp(0x39);

	if ( (flags & NSShiftKeyMask) != (prevFlags & NSShiftKeyMask) )
		if ( flags & NSShiftKeyMask )
			ADBKeyDown(0x38);	// SHIFT_LEFT
		else
			ADBKeyUp(0x38);

	if ( (flags & NSControlKeyMask) != (prevFlags & NSControlKeyMask) )
		if ( flags & NSControlKeyMask )
			ADBKeyDown(0x36);	// CTL_LEFT
		else
			ADBKeyUp(0x36);

	if ( (flags & NSAlternateKeyMask) != (prevFlags & NSAlternateKeyMask) )
		if ( flags & NSAlternateKeyMask )
			ADBKeyDown(0x3a);	// OPTION_LEFT
		else
			ADBKeyUp(0x3a);

	if ( (flags & NSCommandKeyMask) != (prevFlags & NSCommandKeyMask) )
		if ( flags & NSCommandKeyMask )
			ADBKeyDown(0x37);	// APPLE_LEFT
		else
			ADBKeyUp(0x37);

	prevFlags = flags;
}

//
// Windowed mode. We only send mouse/key events
// if the OS X mouse is within the little screen
//
- (BOOL) mouseInView: (NSEvent *) event
{
	NSRect	box;
	NSPoint loc;

	if ( fullScreen )
	{
		box = displayBox;
		loc = [NSEvent mouseLocation];
	}
	else
	{
		box = [self frame];
		loc = [event locationInWindow];
	}

	D(NSLog (@"%s - loc.x=%f, loc.y=%f, box.origin.x=%f, box.origin.y=%f, box.size.width=%f, box.size.height=%f", __PRETTY_FUNCTION__, loc.x, loc.y, box.origin.x, box.origin.y, box.size.width, box.size.height));
	return [self mouse: loc inRect: box];
}

- (BOOL) mouseInView
{
	NSPoint loc = [[self window] mouseLocationOutsideOfEventStream];
	NSRect	box = [self frame];
	D(NSLog (@"%s - loc.x=%f, loc.y=%f, box.origin.x=%f, box.origin.y=%f",
				__PRETTY_FUNCTION__, loc.x, loc.y, box.origin.x, box.origin.y));
	return [self mouse: loc inRect: box];
}

//
// Custom methods
//

- (void) benchmark
{
	int			i;
	float		seconds;
	NSDate		*startDate;
	char		*method;

	if ( ! drawView )
	{
		WarningSheet (@"The emulator has not been setup yet.",
					  @"Try to run, then pause the emulator, first.", nil, [self window]);
		return;
	}

	drawView = NO;
	[self lockFocus];
	startDate = [NSDate date];
	for (i = 1; i < 300; ++i )
#ifdef NSBITMAP
		[bitmap draw];
#endif
#ifdef CGIMAGEREF
		cgDrawInto([self bounds], cgImgRep);
#endif
#ifdef CGDRAWBITMAP
		[self CGDrawBitmap];
#endif
	seconds = -[startDate timeIntervalSinceNow];
	[self unlockFocus];
	drawView = YES;

#ifdef NSBITMAP
	method = "NSBITMAP";
#endif
#ifdef CGIMAGEREF
	method = "CGIMAGEREF";
#endif
#ifdef CGDRAWBITMAP
	method = "CGDRAWBITMAP";
#endif

	InfoSheet(@"Ran benchmark (300 screen redraws)",
			  [NSString stringWithFormat:
				@"%.2f seconds, %.3f frames per second (using %s implementation)",
				seconds, i/seconds, method],
			  @"Thanks", [self window]);
}

// Return a TIFF for a snapshot of the screen image
- (NSData *) TIFFrep
{
#ifdef NSBITMAP
	return [bitmap TIFFRepresentation];
#else
	NSBitmapImageRep	*b = [NSBitmapImageRep alloc];

	b = [b initWithBitmapDataPlanes: (unsigned char **) &bitmap
						 pixelsWide: x
						 pixelsHigh: y
  #ifdef CGIMAGEREF
					  bitsPerSample: CGImageGetBitsPerComponent(cgImgRep)
					samplesPerPixel: 3
						   hasAlpha: NO
						   isPlanar: NO
					 colorSpaceName: NSCalibratedRGBColorSpace
						bytesPerRow: CGImageGetBytesPerRow(cgImgRep)
					   bitsPerPixel: CGImageGetBitsPerPixel(cgImgRep)];
  #endif
  #ifdef CGDRAWBITMAP
					  bitsPerSample: bps
					samplesPerPixel: spp
						   hasAlpha: hasAlpha
						   isPlanar: isPlanar
					 colorSpaceName: NSCalibratedRGBColorSpace
						bytesPerRow: bytesPerRow
					   bitsPerPixel: bpp];
  #endif

    if ( ! b )
	{
		ErrorAlert("Could not allocate an NSBitmapImageRep for the TIFF");
		return nil;
	}

	return [b TIFFRepresentation];
#endif
}

// Enable display of, and drawing into, the view
#ifdef NSBITMAP
- (void) readyToDraw: (NSBitmapImageRep *) theBitmap
		  imageWidth: (short) width
		 imageHeight: (short) height
{
	numBytes = [theBitmap bytesPerRow] * height;
#endif
#ifdef CGIMAGEREF
- (void) readyToDraw: (CGImageRef) image
			  bitmap: (void *) theBitmap
		  imageWidth: (short) width
		 imageHeight: (short) height
{
	cgImgRep = image;
	numBytes = CGImageGetBytesPerRow(image) * height;
#endif
#ifdef CGDRAWBITMAP
- (void) readyToDraw: (void *) theBitmap
			   width: (short) width
			  height: (short) height
				 bps: (short) bitsPerSample
				 spp: (short) samplesPerPixel
				 bpp: (short) bitsPerPixel
				 bpr: (int)   bpr
			isPlanar: (BOOL)  planar
			hasAlpha: (BOOL)  alpha
{
	bps = bitsPerSample;
	spp = samplesPerPixel;
	bpp = bitsPerPixel;
	bytesPerRow = bpr;
	isPlanar = planar;
	hasAlpha = alpha;
	numBytes = bpr * height;
#endif
	D(NSLog(@"readyToDraw: theBitmap=%lx\n", theBitmap));

	bitmap = theBitmap;
	x = width, y = height;
	drawView = YES;
	[[self window] setAcceptsMouseMovedEvents:	YES];
//	[[self window] setInitialFirstResponder:	self];
	[[self window] makeFirstResponder:			self];
}

- (void) disableDrawing
{
	drawView = NO;
}

- (void) startedFullScreen: (CGDirectDisplayID) display
{
	CGRect	displayBounds = CGDisplayBounds(display);

	fullScreen = YES;
	memcpy(&displayBox, &displayBounds, sizeof(displayBox));
}

- (short) width
{
	return (short)[self bounds].size.width;
}

- (short) height
{
	return (short)[self bounds].size.height;
}

- (BOOL) isFullScreen
{
	return fullScreen;
}

- (BOOL) isOpaque
{
	return drawView;
}

- (BOOL) processKeyEvent: (NSEvent *) event
{
	if ( fullScreen || [self acceptsFirstMouse: event] )
		if ( [event isARepeat] )
			return NO;
		else
			return YES;

	[self interpretKeyEvents:[NSArray arrayWithObject:event]];
	return NO;
}

- (void) keyDown: (NSEvent *) event
{
	if ( [self processKeyEvent: event] )
	{
		int	code = [event keyCode];

		if ( code == 126 )	code = 0x3e;	// CURS_UP
		if ( code == 125 )	code = 0x3d;	// CURS_DOWN
		if ( code == 124 )	code = 0x3c;	// CURS_RIGHT
		if ( code == 123 )	code = 0x3b;	// CURS_LEFT

		ADBKeyDown(code);
	}
}

- (void) keyUp: (NSEvent *) event
{
	if ( [self processKeyEvent: event] )
	{
		int	code = [event keyCode];

		if ( code == 126 )	code = 0x3e;	// CURS_UP
		if ( code == 125 )	code = 0x3d;	// CURS_DOWN
		if ( code == 124 )	code = 0x3c;	// CURS_RIGHT
		if ( code == 123 )	code = 0x3b;	// CURS_LEFT

		ADBKeyUp(code);
	}
}


- (void) fullscreenMouseMove
{
	NSPoint location = [NSEvent mouseLocation];

	D(NSLog (@"%s - loc.x=%f, loc.y=%f",
				__PRETTY_FUNCTION__, location.x, location.y));
	D(NSLog (@"%s - Sending ADBMouseMoved(%d,%d). (%d-%d)",
					__PRETTY_FUNCTION__, (int)location.x,
					screen_height - (int)location.y, screen_height, (int)location.y));
	ADBMouseMoved((int)location.x, screen_height - (int)location.y);
}

static NSPoint	mouse;			// Previous/current mouse location

- (BOOL) processMouseMove: (NSEvent *) event
{
	if ( ! drawView )
	{
		D(NSLog(@"Unable to process event - Emulator has not started yet"));
		return NO;
	}

	if ( fullScreen )
	{
		[self fullscreenMouseMove];
		return YES;
	}

	NSPoint location = [self convertPoint: [event locationInWindow] fromView:nil];

	D(NSLog (@"%s - loc.x=%f, loc.y=%f",
				__PRETTY_FUNCTION__, location.x, location.y));

	if ( NSEqualPoints(location, mouse) )
		return NO;

	mouse = location;

	int	mouseY = y - (int) (y * mouse.y / [self height]);
	int	mouseX =	 (int) (x * mouse.x / [self width]);
	// If the view was not resizable, then this would be simpler:
	// int	mouseY = y - (int) mouse.y;
	// int	mouseX =	 (int) mouse.x;

	ADBMouseMoved(mouseX, mouseY);
	return YES;
}

- (void) mouseDown: (NSEvent *) event
{
	[self processMouseMove: event];
	ADBMouseDown(0);
}

- (void) mouseDragged: (NSEvent *) event
{
	[self processMouseMove: event];
}

- (void) mouseMoved: (NSEvent *) event
{
#if DEBUG
	if ( ! [self mouseInView] )
	{
		NSLog (@"%s - Received event while outside of view", __PRETTY_FUNCTION__);
		return;
	}
#endif
	[self processMouseMove: event];
}

- (void) mouseUp: (NSEvent *) event
{
	[self processMouseMove: event];
	ADBMouseUp(0);
}

#if DEBUG
- (void) randomise		// Draw some coloured snow in the bitmap
{
	unsigned char	*data,
					*pixel;

  #ifdef NSBITMAP
	data = [bitmap bitmapData];
  #else
	data = bitmap;
  #endif

	for ( int i = 0; i < 1000; ++i )
	{
		pixel  = data + (int) (numBytes * rand() / RAND_MAX);
		*pixel = (unsigned char) (256.0 * rand() / RAND_MAX);
	}
}
#endif

- (void) drawRect: (NSRect) rect
{
	if ( ! drawView )		// If the emulator is still being setup,
		return;				// we do not want to draw

#if DEBUG
	NSLog(@"In drawRect");
	[self randomise];
#endif

#ifdef NSBITMAP
	NSRectClip(rect);
	[bitmap draw];
#endif
#ifdef CGIMAGEREF
	cgDrawInto(rect, cgImgRep);
#endif
#ifdef CGDRAWBITMAP
	[self CGDrawBitmap];
#endif
}

- (void) setTo: (int) val		// Set all of bitmap to val
{
	unsigned char	*data
  #ifdef NSBITMAP
							= [bitmap bitmapData];
  #else
							= (unsigned char *) bitmap;
  #endif

	memset(data, val, (long unsigned)numBytes);
}

- (void) blacken	// Set bitmap black
{
	[self setTo: 0];
}

- (void) clear		// Set bitmap white
{
	[self setTo: 0xFF];
}

//
// Extra drawing stuff
//

#ifdef CGDRAWBITMAP
extern "C" void CGDrawBitmap(...);

- (void) CGDrawBitmap
{
	CGContextRef	cgContext = (CGContextRef) [[NSGraphicsContext currentContext]
												graphicsPort];
	NSRect			rect = [self bounds];
	CGRect			cgRect = {
								{rect.origin.x, rect.origin.y},
								{rect.size.width, rect.size.height}
							 };

	CGColorSpaceRef	colourSpace = CGColorSpaceCreateDeviceRGB();


//	CGContextSetShouldAntialias(cgContext, NO);		// Seems to have no effect?

	CGDrawBitmap(cgContext, cgRect, x, y, bps, spp, bpp,
					bytesPerRow, isPlanar, hasAlpha, colourSpace, &bitmap);
}
#endif

#ifdef CGIMAGEREF
void
cgDrawInto(NSRect rect, CGImageRef cgImgRep)
{
	CGContextRef	cgContext = (CGContextRef) [[NSGraphicsContext currentContext]
												graphicsPort];
	CGRect			cgRect = {
								{rect.origin.x, rect.origin.y},
								{rect.size.width, rect.size.height}
							 };

//	CGContextSetShouldAntialias(cgContext, NO);		// Seems to have no effect?

	CGContextDrawImage(cgContext, cgRect, cgImgRep);
}
#endif

@end
