#define Point OSXPoint
#define Rect OSXRect
#define Cursor OSXCursor
#import <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
#undef Rect
#undef Point
#undef Cursor
#undef offsetof
#undef nil

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <keyboard.h>
#include <mouse.h>
#include <cursor.h>
#include "osx-screen.h"
#include "osx-keycodes.h"
#include "devdraw.h"
#include "glendapng.h"

AUTOFRAMEWORK(Cocoa)

#define panic sysfatal

extern Rectangle mouserect;

struct {
	char *label;
	char *winsize;
	QLock labellock;

	Rectangle fullscreenr;
	Rectangle screenr;
	Memimage *screenimage;
	int isfullscreen;
	ulong fullscreentime;
	
	Point xy;
	int buttons;
	int kbuttons;

	CGDataProviderRef provider;
	NSWindow *window;
	CGImageRef image;
	CGContextRef windowctx;

	int needflush;
	QLock flushlock;
	int active;
	int infullscreen;
	int kalting;		// last keystroke was Kalt
} osx;

enum
{
	WindowAttrs = NSClosableWindowMask |
		NSTitledWindowMask |
		NSMiniaturizableWindowMask |
		NSResizableWindowMask
};

static void screenproc(void*);
 void eresized(int);
 void fullscreen(int);
 void seticon(void);
static void activated(int);

enum
{
	CmdFullScreen = 1,
};

@interface P9View : NSView
{}
@end

@implementation P9View
- (BOOL)acceptsFirstResponder
{
	return YES;
}
@end

void screeninit(void);
void _flushmemscreen(Rectangle r);

Memimage*
attachscreen(char *label, char *winsize)
{
	if(label == nil)
		label = "gnot a label";
	osx.label = strdup(label);
	osx.winsize = winsize;
	if(osx.screenimage == nil){
		screeninit();
		if(osx.screenimage == nil)
			panic("cannot create OS X screen");
	}
	return osx.screenimage;
}

void
_screeninit(void)
{
	CGRect cgr;
	NSRect or;
	Rectangle r;
	int havemin;

	memimageinit();

	cgr = CGDisplayBounds(CGMainDisplayID());
	osx.fullscreenr = Rect(0, 0, cgr.size.width, cgr.size.height);
	
	// Create the window.
	r = Rect(0, 0, Dx(osx.fullscreenr)*2/3, Dy(osx.fullscreenr)*2/3);
	havemin = 0;
	if(osx.winsize && osx.winsize[0]){
		if(parsewinsize(osx.winsize, &r, &havemin) < 0)
			sysfatal("%r");
	}
	if(!havemin)
		r = rectaddpt(r, Pt((Dx(osx.fullscreenr)-Dx(r))/2, (Dy(osx.fullscreenr)-Dy(r))/2));
	or = NSMakeRect(r.min.x, r.min.y, r.max.x, r.max.y);
	osx.window = [[NSWindow alloc] initWithContentRect:or styleMask:WindowAttrs
		 backing:NSBackingStoreBuffered defer:NO screen:[NSScreen mainScreen]];
	[osx.window setDelegate:[NSApp delegate]];
	[osx.window setAcceptsMouseMovedEvents:YES];

	P9View *view = [[P9View alloc] initWithFrame:or];
	[osx.window setContentView:view];
	[view release];

	setlabel(osx.label);
	seticon();
	
	// Finally, put the window on the screen.
	eresized(0);
	[osx.window makeKeyAndOrderFront:nil];

	[NSCursor unhide];
}

static Rendez scr;
static QLock slock;

void
screeninit(void)
{
	scr.l = &slock;
	qlock(scr.l);
//	proccreate(screenproc, nil, 256*1024);
	screenproc(NULL);
	while(osx.window == nil)
		rsleep(&scr);
	qunlock(scr.l);
}

static void
screenproc(void *v)
{
	qlock(scr.l);
	_screeninit();
	rwakeup(&scr);
	qunlock(scr.l);
}

static ulong
msec(void)
{
	return nsec()/1000000;
}

//static void
void
mouseevent(NSEvent *event)
{
	int wheel;
	NSPoint op;
	
	op = [event locationInWindow];

	osx.xy = subpt(Pt(op.x, op.y), osx.screenr.min);
	wheel = 0;

	switch([event type]){
	case NSScrollWheel:;
		CGFloat delta = [event deltaY];
		if(delta > 0)
			wheel = 8;
		else
			wheel = 16;
		break;
	
	case NSLeftMouseDown:
	case NSRightMouseDown:
	case NSOtherMouseDown:
	case NSLeftMouseUp:
	case NSRightMouseUp:
	case NSOtherMouseUp:;
		NSInteger but;
		NSUInteger  mod;
		but = [event buttonNumber];
		mod = [event modifierFlags];
		
		// OS X swaps button 2 and 3
		but = (but & ~6) | ((but & 4)>>1) | ((but&2)<<1);
		but = mouseswap(but);
		
		// Apply keyboard modifiers and pretend it was a real mouse button.
		// (Modifiers typed while holding the button go into kbuttons,
		// but this one does not.)
		if(but == 1){
			if(mod & NSAlternateKeyMask) {
				// Take the ALT away from the keyboard handler.
				if(osx.kalting) {
					osx.kalting = 0;
					keystroke(Kalt);
				}
				but = 2;
			}
			else if(mod & NSCommandKeyMask)
				but = 4;
		}
		osx.buttons = but;
		break;

	case NSMouseMoved:
	case NSLeftMouseDragged:
	case NSRightMouseDragged:
	case NSOtherMouseDragged:
		break;
	
	default:
		return;
	}

	mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons|wheel, msec());
}

static int keycvt[] =
{
	[QZ_IBOOK_ENTER] '\n',
	[QZ_RETURN] '\n',
	[QZ_ESCAPE] 27,
	[QZ_BACKSPACE] '\b',
	[QZ_LALT] Kalt,
	[QZ_LCTRL] Kctl,
	[QZ_LSHIFT] Kshift,
	[QZ_F1] KF+1,
	[QZ_F2] KF+2,
	[QZ_F3] KF+3,
	[QZ_F4] KF+4,
	[QZ_F5] KF+5,
	[QZ_F6] KF+6,
	[QZ_F7] KF+7,
	[QZ_F8] KF+8,
	[QZ_F9] KF+9,
	[QZ_F10] KF+10,
	[QZ_F11] KF+11,
	[QZ_F12] KF+12,
	[QZ_INSERT] Kins,
	[QZ_DELETE] 0x7F,
	[QZ_HOME] Khome,
	[QZ_END] Kend,
	[QZ_KP_PLUS] '+',
	[QZ_KP_MINUS] '-',
	[QZ_TAB] '\t',
	[QZ_PAGEUP] Kpgup,
	[QZ_PAGEDOWN] Kpgdown,
	[QZ_UP] Kup,
	[QZ_DOWN] Kdown,
	[QZ_LEFT] Kleft,
	[QZ_RIGHT] Kright,
	[QZ_KP_MULTIPLY] '*',
	[QZ_KP_DIVIDE] '/',
	[QZ_KP_ENTER] '\n',
	[QZ_KP_PERIOD] '.',
	[QZ_KP0] '0',
	[QZ_KP1] '1',
	[QZ_KP2] '2',
	[QZ_KP3] '3',
	[QZ_KP4] '4',
	[QZ_KP5] '5',
	[QZ_KP6] '6',
	[QZ_KP7] '7',
	[QZ_KP8] '8',
	[QZ_KP9] '9',
};

//static void
void
kbdevent(NSEvent *event)
{
	char ch;
	UInt32 code;
	UInt32 mod;
	int k;

	ch = [[event characters] characterAtIndex:0];
	code = [event keyCode];
	mod = [event modifierFlags];

	switch([event type]){
	case NSKeyDown:
		osx.kalting = 0;
		if(mod == NSCommandKeyMask){
			if(ch == 'F' || ch == 'f'){
				if(osx.isfullscreen && msec() - osx.fullscreentime > 500)
					fullscreen(0);
				return;
			}
			
			// Pass most Cmd keys through as Kcmd + ch.
			// OS X interprets a few no matter what we do,
			// so it is useless to pass them through as keystrokes too.
			switch(ch) {
			case 'm':	// minimize window
			case 'h':	// hide window
			case 'H':	// hide others
			case 'q':	// quit
				return;
			}
			if(' ' <= ch && ch <= '~') {
				keystroke(Kcmd + ch);
				return;
			}
			return;
		}
		k = ch;
		if(code < nelem(keycvt) && keycvt[code])
			k = keycvt[code];
		if(k >= 0)
			keystroke(k);
		else{
			keystroke(ch);
		}
		break;

	case NSFlagsChanged:
		if(!osx.buttons && !osx.kbuttons){
			if(mod == NSAlternateKeyMask) {
				osx.kalting = 1;
				keystroke(Kalt);
			}
			break;
		}
		
		// If the mouse button is being held down, treat 
		// changes in the keyboard modifiers as changes
		// in the mouse buttons.
		osx.kbuttons = 0;
		if(mod & NSAlternateKeyMask)
			osx.kbuttons |= 2;
		if(mod & NSCommandKeyMask)
			osx.kbuttons |= 4;
		mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec());
		break;
	}
	return;
}

//static void
void
eresized(int new)
{
	Memimage *m;
	NSRect or;
	ulong chan;
	Rectangle r;
	int bpl;
	CGDataProviderRef provider;
	CGImageRef image;
	CGColorSpaceRef cspace;

	or = [[osx.window contentView] bounds];
	r = Rect(or.origin.x, or.origin.y, or.size.width, or.size.height);
	if(Dx(r) == Dx(osx.screenr) && Dy(r) == Dy(osx.screenr)){
		// No need to make new image.
		osx.screenr = r;
		return;
	}

	chan = XBGR32;
	m = allocmemimage(Rect(0, 0, Dx(r), Dy(r)), chan);
	if(m == nil)
		panic("allocmemimage: %r");
	if(m->data == nil)
		panic("m->data == nil");
	bpl = bytesperline(r, 32);
	provider = CGDataProviderCreateWithData(0,
		m->data->bdata, Dy(r)*bpl, 0);
	//cspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
	cspace = CGColorSpaceCreateDeviceRGB();
	image = CGImageCreate(Dx(r), Dy(r), 8, 32, bpl,
		cspace,
		kCGImageAlphaNoneSkipLast,
		provider, 0, 0, kCGRenderingIntentDefault);
	CGColorSpaceRelease(cspace);
	CGDataProviderRelease(provider);	// CGImageCreate did incref
	
	mouserect = m->r;
	if(new){
		mouseresized = 1;
		mousetrack(osx.xy.x, osx.xy.y, osx.buttons|osx.kbuttons, msec());
	}
//	termreplacescreenimage(m);
	_drawreplacescreenimage(m);	// frees old osx.screenimage if any
	if(osx.image)
		CGImageRelease(osx.image);
	osx.image = image;
	osx.screenimage = m;
	osx.screenr = r;
}

void
flushproc(void *v)
{
	for(;;){
		if(osx.needflush && osx.windowctx && canqlock(&osx.flushlock)){
			if(osx.windowctx){
				CGContextFlush(osx.windowctx);
				osx.needflush = 0;
			}
			qunlock(&osx.flushlock);
		}
		usleep(33333);
	}
}

void
_flushmemscreen(Rectangle r)
{
	CGRect cgr;
	CGImageRef subimg;

	qlock(&osx.flushlock);
	if(osx.windowctx == nil){
		osx.windowctx = [[osx.window graphicsContext] graphicsPort];
//		[osx.window flushWindow];
//		proccreate(flushproc, nil, 256*1024);
	}
	
	cgr.origin.x = r.min.x;
	cgr.origin.y = r.min.y;
	cgr.size.width = Dx(r);
	cgr.size.height = Dy(r);
	subimg = CGImageCreateWithImageInRect(osx.image, cgr);
	cgr.origin.y = Dy(osx.screenr) - r.max.y; // XXX how does this make any sense?
	CGContextDrawImage(osx.windowctx, cgr, subimg);
	osx.needflush = 1;
	qunlock(&osx.flushlock);
	CGImageRelease(subimg);
}

void
activated(int active)
{
	osx.active = active;
}

void
fullscreen(int wascmd)
{
	NSView *view = [osx.window contentView];

	if(osx.isfullscreen){
		[view exitFullScreenModeWithOptions:nil];
		osx.isfullscreen = 0;
	}else{
		[view enterFullScreenMode:[osx.window screen] withOptions:nil];
		osx.isfullscreen = 1;
		osx.fullscreentime = msec();
	}
	eresized(1);
}
		
void
setmouse(Point p)
{
	CGPoint cgp;
	
	cgp.x = p.x + osx.screenr.min.x;
	cgp.y = p.y + osx.screenr.min.y;
	CGWarpMouseCursorPosition(cgp);
}

void
setcursor(Cursor *c)
{
	NSImage *image;
	NSBitmapImageRep *bitmap;
	NSCursor *nsc;
	unsigned char *planes[5];
	int i;

	if(c == nil){
		[NSCursor pop];
		return;
	}
	
	image = [[NSImage alloc] initWithSize:NSMakeSize(16.0, 16.0)];
	bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
		pixelsWide:16
		pixelsHigh:16
		bitsPerSample:1
		samplesPerPixel:2
		hasAlpha:YES
		isPlanar:YES
		colorSpaceName:NSCalibratedWhiteColorSpace
		bytesPerRow:2
		bitsPerPixel:1];

	[bitmap getBitmapDataPlanes:planes];

	for(i=0; i<16; i++){
		planes[0][i] = ((ushort*)c->set)[i];
		planes[1][i] = planes[0][i] | ((ushort*)c->clr)[i];
	}

	[image addRepresentation:bitmap];

	nsc = [[NSCursor alloc] initWithImage:image
		hotSpot:NSMakePoint(c->offset.x, c->offset.y)];
	[nsc push];

	[image release];
	[bitmap release];
	[nsc release];
}

void
getcolor(ulong i, ulong *r, ulong *g, ulong *b)
{
	ulong v;
	
	v = 0;
	*r = (v>>16)&0xFF;
	*g = (v>>8)&0xFF;
	*b = v&0xFF;
}

int
setcolor(ulong i, ulong r, ulong g, ulong b)
{
	/* no-op */
	return 0;
}


int
hwdraw(Memdrawparam *p)
{
	return 0;
}

struct {
	QLock lk;
	char buf[SnarfSize];
	Rune rbuf[SnarfSize];
	NSPasteboard *apple;
} clip;

char*
getsnarf(void)
{
	char *s, *t;
	NSArray *types;
	NSString *string;
	NSData * data;
	NSUInteger ndata;

/*	fprint(2, "applegetsnarf\n"); */
	qlock(&clip.lk);

	clip.apple = [NSPasteboard generalPasteboard];
	types = [clip.apple types];

	string = [clip.apple stringForType:NSStringPboardType];
	if(string == nil){
		fprint(2, "apple pasteboard get item type failed\n");
		qunlock(&clip.lk);
		return nil;
	}

	data = [string dataUsingEncoding:NSUnicodeStringEncoding];
	if(data != nil){
		ndata = [data length];
		qunlock(&clip.lk);
		s = smprint("%.*S", ndata/2, (Rune*)[data bytes]);
		for(t=s; *t; t++)
			if(*t == '\r')
				*t = '\n';
		return s;
	}

	qunlock(&clip.lk);
	return nil;		
}

void
putsnarf(char *s)
{
	NSArray *pboardTypes;
	NSString *string;

/*	fprint(2, "appleputsnarf\n"); */

	if(strlen(s) >= SnarfSize)
		return;
	qlock(&clip.lk);
	strcpy(clip.buf, s);
	runesnprint(clip.rbuf, nelem(clip.rbuf), "%s", s);

	pboardTypes = [NSArray arrayWithObject:NSStringPboardType];

	clip.apple = [NSPasteboard generalPasteboard];
	[clip.apple declareTypes:pboardTypes owner:nil];

	assert(sizeof(clip.rbuf[0]) == 2);
	string = [NSString stringWithCharacters:clip.rbuf length:runestrlen(clip.rbuf)*2];
	if(string == nil){
		fprint(2, "apple pasteboard data create failed\n");
		qunlock(&clip.lk);
		return;
	}
	if(![clip.apple setString:string forType:NSStringPboardType]){
		fprint(2, "apple pasteboard putitem failed\n");
		qunlock(&clip.lk);
		return;
	}
	qunlock(&clip.lk);
}

void
setlabel(char *label)
{
	CFStringRef cs;
	cs = CFStringCreateWithBytes(nil, (uchar*)label, strlen(osx.label), kCFStringEncodingUTF8, false);
	[osx.window setTitle:(NSString*)cs];
	CFRelease(cs);
}

void
kicklabel(char *label)
{
	char *p;

	p = strdup(label);
	if(p == nil)
		return;
	qlock(&osx.labellock);
	free(osx.label);
	osx.label = p;
	qunlock(&osx.labellock);

	setlabel(label);	
}

// static void
void
seticon(void)
{
	NSImage *im;
	NSData *d;

	d = [[NSData alloc] initWithBytes:glenda_png length:(sizeof glenda_png)];
	im = [[NSImage alloc] initWithData:d];
	if(im){
		NSLog(@"here");
		[NSApp setApplicationIconImage:im];
		[[NSApp dockTile] setShowsApplicationBadge:YES];
		[[NSApp dockTile] display];
	}
	[d release];
	[im release];
}