/* SimplyGL window system module for Cocoa */
/* mac-framework: -framework Cocoa */

#include "config.h"

#ifdef USE_WSYS_MODULE_COCOA

#import <Cocoa/Cocoa.h>
#include "sgl.h"
#include "wsys.h"
#include "log.h"

@interface OpenGLView : NSOpenGLView
{
	int foo;
}

-(id) initWithFrame: (NSRect) frame pixelFormat: (NSOpenGLPixelFormat*) pf;

-(void) drawRect: (NSRect) rect;
-(void) reshape;
-(void) keyDown: (NSEvent*) ev;
-(void) keyUp: (NSEvent*) ev;
-(void) mouseDown: (NSEvent*) ev;
-(void) mouseUp: (NSEvent*) ev;
-(void) rightMouseDown: (NSEvent*) ev;
-(void) rightMouseUp: (NSEvent*) ev;
-(void) otherMouseDown: (NSEvent*) ev;
-(void) otherMouseUp: (NSEvent*) ev;
-(void) mouseDragged: (NSEvent*) ev;
-(void) rightMouseDragged: (NSEvent*) ev;
-(void) otherMouseDragged: (NSEvent*) ev;

-(BOOL) acceptsFirstResponder;
@end


@interface AppDelegate : NSObject
{
	int foo;
}

-(void) applicationWillFinishLaunching: (NSNotification*) notification;
-(void) applicationDidFinishLaunching: (NSNotification*) notification;

-(BOOL) applicationShouldTerminate: (NSApplication*) app;
-(BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) app;
-(void) applicationWillTerminate: (NSNotification*) notification;
@end

struct window;

@interface WinDelegate : NSObject <NSWindowDelegate>
{
	@public
	struct window *win;
}
-(id) init;
-(void) dealloc;

-(void) windowDidExpose: (NSNotification*) notification;
-(void) windowDidResize: (NSNotification*) notification;
-(BOOL) windowShouldClose: (id) win;
-(void) windowWillClose: (NSNotification*) notification;
@end


struct window {
	int wid;
	int width, height;
	NSWindow *win;
	OpenGLView *view;
	NSOpenGLContext *ctx;
	int needs_redisplay;
	struct window *next;
};


static int init(void);
static void shutdown(void);

/* video mode switching */
static int set_vidmode(int xsz, int ysz);
static int get_vidmode(int *xsz, int *ysz);

/* create/destroy windows */
static int create_window(int xsz, int ysz, unsigned int flags);
static void close_window(int wid);

/* window management */
static int set_active(int wid);
static struct window *find_window(int wid);
static int activate_window(struct window *win);
static int set_title(const char *str);
static void redisplay(void);
static void swap_buffers(void);

static int get_modifiers(void);

/* event handling and friends */
static void set_event(int idx, int enable);
static int process_events(void);

static void select_event_window(NSEvent *ev);
static void handle_key(NSEvent *ev, int state);
static void handle_mouse(NSEvent *ev, int state);
static void handle_motion(NSEvent *ev);

static void fill_attr(NSOpenGLPixelFormatAttribute *attr, unsigned int flags);


static struct wsys_module ws = {
	"cocoa", 0,
	init,
	shutdown,
	set_vidmode,
	get_vidmode,
	create_window,
	close_window,
	set_active,
	set_title,
	redisplay,
	swap_buffers,
	get_modifiers,
	set_event,
	process_events,
	0
};

static struct window *winlist, *active_win;
static int quit_main_loop;

static NSAutoreleasePool *global_pool;

void sgl_register_cocoa(void)
{
	sgl_register_module(&ws);
}


@implementation OpenGLView

-(id) initWithFrame: (NSRect) frame pixelFormat: (NSOpenGLPixelFormat*) pf
{
	self = [super initWithFrame: frame pixelFormat: pf];
	return self;
}

-(void) drawRect: (NSRect) rect
{
	sgl_display_callback_t func = sgl_get_callback(SGL_DISPLAY);
	if(func) {
		func();
	}
}

-(void) reshape
{
	NSSize sz;
	sgl_reshape_callback_t func;

	sz = [self bounds].size;

	if((func = sgl_get_callback(SGL_RESHAPE)) && (sz.width != active_win->width ||
				sz.height != active_win->height)) {
		active_win->width = sz.width;
		active_win->height = sz.height;
		func(sz.width, sz.height);
	}
}

-(void) keyDown: (NSEvent*) ev
{
	handle_key(ev, 1);
}

-(void) keyUp: (NSEvent*) ev
{
	handle_key(ev, 0);
}

-(void) mouseDown: (NSEvent*) ev
{
	handle_mouse(ev, 1);
}

-(void) mouseUp: (NSEvent*) ev
{
	handle_mouse(ev, 0);
}

-(void) rightMouseDown: (NSEvent*) ev
{
	handle_mouse(ev, 1);
}

-(void) rightMouseUp: (NSEvent*) ev
{
	handle_mouse(ev, 0);
}

-(void) otherMouseDown: (NSEvent*) ev
{
	handle_mouse(ev, 1);
}

-(void) otherMouseUp: (NSEvent*) ev
{
	handle_mouse(ev, 0);
}

-(void) mouseDragged: (NSEvent*) ev
{
	handle_motion(ev);
}

-(void) rightMouseDragged: (NSEvent*) ev
{
	handle_motion(ev);
}

-(void) otherMouseDragged: (NSEvent*) ev
{
	handle_motion(ev);
}


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

@implementation AppDelegate
-(void) applicationWillFinishLaunching: (NSNotification*) notification
{
}

-(void) applicationDidFinishLaunching: (NSNotification*) notification
{
}

-(BOOL) applicationShouldTerminate: (NSApplication*) app
{
	return NSTerminateNow;
}

-(BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) app
{
	return YES;
}

-(void) applicationWillTerminate: (NSNotification*) notification
{
	/*[NSApp setDelegate: nil];
	[global_pool drain];*/
}
@end

@implementation WinDelegate
-(id) init
{
	self = [super init];
	return self;
}

-(void) dealloc
{
	[super dealloc];
}

-(void) windowDidExpose: (NSNotification*) notification
{
}

-(void) windowDidResize: (NSNotification*) notification
{
}

-(BOOL) windowShouldClose: (id) win
{
	assert(self->win);
	close_window(self->win->wid);
	return YES;
}

-(void) windowWillClose: (NSNotification*) notification
{
	/*[NSApp terminate: nil];*/
}
@end

static int init(void)
{
	AppDelegate *delegate;

	global_pool = [[NSAutoreleasePool alloc] init];

	[NSApplication sharedApplication];

	delegate = [[AppDelegate alloc] init];
	[NSApp setDelegate: delegate];
	return 0;
}

static void shutdown(void)
{
	while(winlist) {
		close_window(winlist->wid);
	}

	quit_main_loop = 1;
	[NSApp terminate: nil];
}


/* video mode switching */
static int set_vidmode(int xsz, int ysz)
{
	return 0;	/* TODO */
}

static int get_vidmode(int *xsz, int *ysz)
{
	return 0;	/* TODO */
}


/* create/destroy windows */
static int create_window(int xsz, int ysz, unsigned int flags)
{
	NSAutoreleasePool *pool;
	WinDelegate *delegate;
	NSWindow *nswin;
	NSRect rect;
	OpenGLView *view;
	NSOpenGLPixelFormat *pf;
	NSOpenGLPixelFormatAttribute attr[32];
	unsigned int style;
	struct window *win;
	static int next_id = 1;

	if(!(win = malloc(sizeof *win))) {
		return -1;
	}

	pool = [[NSAutoreleasePool alloc] init];

	/* create the view */
	fill_attr(attr, flags);
	pf = [[[NSOpenGLPixelFormat alloc] initWithAttributes: attr] autorelease];
	view = [[OpenGLView alloc] initWithFrame: rect pixelFormat: pf];

	/* create the window and attach the OpenGL view */
	rect.origin.x = rect.origin.y = 0;
	rect.size.width = xsz;
	rect.size.height = ysz;

	style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask |
		NSResizableWindowMask;

	nswin = [[NSWindow alloc] initWithContentRect: rect styleMask: style
		backing: NSBackingStoreBuffered defer: YES];

	delegate = [[WinDelegate alloc] init];

	[nswin setDelegate: delegate];
	[nswin setTitle: @"OpenGL/Cocoa"];
	[nswin setReleasedWhenClosed: YES];
	[nswin setContentView: view];
	[nswin makeFirstResponder: view];
	[nswin makeKeyAndOrderFront: nil];
	[view release];

	win->win = nswin;
	win->view = view;
	win->ctx = [view openGLContext];
	win->wid = next_id++;
	win->needs_redisplay = 1;
	win->next = winlist;
	winlist = win;

	delegate->win = win;

	if(!active_win) {
		activate_window(win);
	}

	[pool drain];
	return win->wid;
}

static void close_window(int wid)
{
	struct window *win, *prev, dummy;
	sgl_close_callback_t close_func;

	dummy.next = win = winlist;
	prev = &dummy;

	while(win) {
		if(win->wid == wid) {
			if((close_func = sgl_get_callback(SGL_CLOSE))) {
				close_func(wid);
			}
			[win->win close];

			prev->next = win->next;

			if(!dummy.next) {
				winlist = 0;
			}

			if(active_win == win) {
				activate_window(winlist);
			}
			free(win);
			break;
		}
		prev = win;
		win = win->next;
	}

	if(!dummy.next) {
		winlist = 0;
		shutdown();
	}
}


/* window management */
static int set_active(int wid)
{
	struct window *win = find_window(wid);
	return activate_window(win);
}

static struct window *find_window(int wid)
{
	struct window *win = winlist;

	while(win) {
		if(win->wid == wid) {
			return win;
		}
		win = win->next;
	}
	return 0;
}

static int activate_window(struct window *win)
{
	if(!win) {
		return -1;
	}
	[win->ctx makeCurrentContext];
	active_win = win;
	return 0;
}

static int set_title(const char *str)
{
	NSString *nsstr;

	nsstr = [[NSString alloc] initWithCString: str encoding: NSASCIIStringEncoding];
	[active_win->win setTitle: nsstr];
	[nsstr release];
	return 0;
}

static void redisplay(void)
{
	active_win->needs_redisplay = 1;
}

static void swap_buffers(void)
{
	[active_win->ctx flushBuffer];
}


static int get_modifiers(void)
{
	unsigned int nsmod = [NSEvent modifierFlags];
	unsigned int mod = 0;

	if(nsmod & NSShiftKeyMask) {
		mod |= SGL_MOD_SHIFT;
	}
	if(nsmod & NSControlKeyMask) {
		mod |= SGL_MOD_CONTROL;
	}
	if(nsmod & NSAlternateKeyMask) {
		mod |= SGL_MOD_ALT;
	}
	return mod;
}


/* event handling and friends */
static void set_event(int idx, int enable)
{
}

static int process_events(void)
{
	NSAutoreleasePool *pool;
	NSRunLoop *runloop;
	NSDate *block, *nonblock, *limdate;

	sgl_idle_callback_t idle;
	sgl_display_callback_t disp;
	struct window *win;

	pool = [[NSAutoreleasePool alloc] init];

	idle = sgl_get_callback(SGL_IDLE);
	disp = sgl_get_callback(SGL_DISPLAY);

	win = winlist;
	while(win) {
		if(win->needs_redisplay && disp) {
			activate_window(win);
			disp();
			win->needs_redisplay = 0;
		}
		win = win->next;
	}

	runloop = [[NSRunLoop currentRunLoop] retain];
	block = [runloop limitDateForMode: NSDefaultRunLoopMode];
	nonblock = [[NSDate distantPast] retain];
	limdate = idle ? nonblock : block;

	while(!quit_main_loop) {
		NSEvent *ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: limdate
			inMode: NSDefaultRunLoopMode dequeue: YES];
		if(!ev) break;

		[NSApp sendEvent: ev];
		if(limdate == block) {
			limdate = nonblock;
		}
	}

	if(idle) {
		idle();
	}

	[runloop release];
	[pool drain];

	/*sgl_log("%s returning: %d\n", __func__, quit_main_loop ? -1 : 0);*/
	return quit_main_loop ? -1 : 0;
}

static void select_event_window(NSEvent *ev)
{
	NSWindow *nswin;
	WinDelegate *del;
	struct window *win;

	if(!ev || !(nswin = [ev window])) {
		sgl_log("%s failed\n", __func__);
		return;
	}
	del = [nswin delegate];
	win = del->win;

	activate_window(win);
}

static void handle_key(NSEvent *ev, int state)
{
	NSString *str;
	sgl_keyboard_callback_t func = sgl_get_callback(SGL_KEYBOARD);

	if(func) {
		str = [ev characters];
		if([str length]) {
			unichar c = [str characterAtIndex: 0];

			select_event_window(ev);
			func(c, state);
		}
	}
}

static void handle_mouse(NSEvent *ev, int state)
{
	int bn;
	NSPoint pt;
	sgl_mouse_callback_t func = sgl_get_callback(SGL_MOUSE);

	if(func) {
		bn = [ev buttonNumber];
		if(bn == 2) {
			bn = 1;
		} else if(bn == 1) {
			bn = 2;
		}
		pt = [ev locationInWindow];

		select_event_window(ev);
		func(0, bn, state, pt.x, pt.y - 1);
	}
}

static void handle_motion(NSEvent *ev)
{
	NSPoint pt;
	sgl_motion_callback_t func = sgl_get_callback(SGL_MOTION);

	if(func) {
		pt = [ev locationInWindow];

		select_event_window(ev);
		func(0, pt.x, pt.y - 1);
	}
}

static void fill_attr(NSOpenGLPixelFormatAttribute *attr, unsigned int flags)
{
	int i = 0;

	/* this is very important. makes pixelformat selection behave like GLX
	 * where any non-zero value will denote "choose highest possible". This
	 * is pretty much what we intend, as the user doesn't actually pass any
	 * of these numbers.
	 */
	attr[i++] = NSOpenGLPFAMaximumPolicy;

	attr[i++] = NSOpenGLPFAColorSize;
	attr[i++] = 1;

	if(flags & SGL_DOUBLE) {
		attr[i++] = NSOpenGLPFADoubleBuffer;
	}
	if(flags & SGL_DEPTH) {
		attr[i++] = NSOpenGLPFADepthSize;
		attr[i++] = 1;
	}
	if(flags & SGL_STENCIL) {
		attr[i++] = NSOpenGLPFAStencilSize;
		attr[i++] = 8;	/* max-policy has no effect on stencil selection */
	}
	if(flags & SGL_STEREO) {
		attr[i++] = NSOpenGLPFAStereo;
	}
	if(flags & SGL_MULTISAMPLE) {
		attr[i++] = NSOpenGLPFASampleBuffers;
		attr[i++] = 1;
		attr[i++] = NSOpenGLPFASamples;
		attr[i++] = 4;	/* TODO don't hardcode, query */
	}
	attr[i++] = 0;
}

#else
int sgl_wsys_cocoa_silence_the_fucking_empty_file_warnings;
#endif	/* USE_WSYS_MODULE_COCOA */
