/*
 
 Copyright (C) 2001-2002       A Nourai
 Copyright (C) 2006            Jacek Piszczek (Mac OSX port)
 
 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 included (GNU.txt) 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 <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
#include "quakedef.h"

id _p;
int evcnt = 2;

// Jacek: some keys are bogus, my ibook kb lacks keys and apple's docs lack
// keyCode documentation

unsigned char keyconv[] =
{
	'a', /* 0 */
	's',
	'd',
	'f',
	'h',
	'g',
	'z',
	'x',
	'c',
	'v',
	'0', /* 10 */
	'b',
	'q',
	'w',
	'e',
	'r',
	'y',
	't',
	'1',
	'2',
	'3', /* 20 */
	'4',
	'6',
	'5',
	'=',
	'9',
	'7',
	'-',
	'8',
	'0',
	']', /* 30 */
	'o',
	'u',
	'[',
	'i',
	'p',
	K_ENTER,
	'l',
	'j',
	'\'',
	'k', /* 40 */
	';',
	'\\',
	',',
	'/',
	'n',
	'm',
	'.',
	K_TAB,
	K_SPACE,
	'`', /* 50 */
	K_BACKSPACE,
	'v',
	K_ESCAPE,
	'n',
	'm',
	',',
	'.',
	'/',
	0,
	K_KP_DEL, /* 60 */
	K_KP_HOME,
	K_KP_UPARROW,
	K_KP_PGUP,
	' ',
	K_KP_DEL,
	K_TAB,
	K_KP_STAR,
	K_ENTER,
	K_KP_PLUS,
	K_DEL, /* 70 */
	K_INS,
	K_PGUP,
	K_PGDN,
	K_KP_MINUS,
	K_KP_SLASH,
	K_KP_ENTER,
	0,
	K_KP_MINUS,
	0,
	K_F1, /* 80 */
	K_F2,
	K_KP_INS,
	K_KP_END,
	K_KP_DOWNARROW,
	K_KP_PGDN,
	K_KP_LEFTARROW,
	K_KP_5,
	K_KP_RIGHTARROW,
	K_KP_HOME,
	0, /* 90 */
	K_KP_UPARROW,
	K_KP_PGUP,
	0,
	K_KP_PLUS,
	0,
	K_LSHIFT,
	K_RSHIFT,
	0,
	K_RCTRL,
	K_ALT, /* 100 */
	K_ALT,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	K_PAUSE, /* 110 */
	K_F12,
	0,
	0,
	0,
	K_HOME,
	K_PGUP,
	K_DEL,
	0,
	K_END,
	0, /* 120 */
	K_PGDN,
	0,
	K_LEFTARROW,
	K_RIGHTARROW,
	K_DOWNARROW,
	K_UPARROW,
	0,
	0,
	0,
	0, /* 130 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 140 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 150 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 160 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 170 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 180 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 190 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 200 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 210 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 220 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 230 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 240 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0, /* 250 */
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0
};

// validate the depth
int checkDepth(int d)
{
	if (d == 24)
		 d = 32;
	if (d != 15 && d != 16 && d != 32)
		 d = 32;
    
	return d;
}

@interface FTEApplication : NSApplication
{
	NSOpenGLContext    *_openGLContext;
	NSTimer            *_timer;
	double time, oldtime, newtime;
	unsigned int        oldmflags;
	CFDictionaryRef     olddmode;
}
- (void)initDisplayWidth:(int)width height:(int)height depth:(int)depth;
- (void)flushBuffer;
- (void)runLoop:(NSTimer *)timer;
@end

@implementation FTEApplication

- (id)init
{
	if((self = [super init]))
		[self setDelegate:self];
	
	oldmflags = 0;
    
	return self;
}

- (void)initDisplayWidth:(int)width height:(int)height depth:(int)depth;
{
	long                            value = 1;
	NSOpenGLPixelFormat*            format;
	NSOpenGLPixelFormatAttribute    attributes[] = {
		NSOpenGLPFAFullScreen,
		NSOpenGLPFAScreenMask,
		CGDisplayIDToOpenGLDisplayMask(kCGDirectMainDisplay),
		NSOpenGLPFANoRecovery,
		NSOpenGLPFADoubleBuffer,
		NSOpenGLPFAAccelerated,
		NSOpenGLPFADepthSize, checkDepth(depth),
		0};

	olddmode = CGDisplayCurrentMode(kCGDirectMainDisplay);

	// zeros mean we use the default screen! (but with 32bit depth)
	if (!((width == 0) && (height == 0) && (depth == 0)))
	{
		depth = checkDepth(depth);
		if (width == 0)
			 width = CGDisplayPixelsWide(kCGDirectMainDisplay);
		if (height == 0)
			 height = CGDisplayPixelsHigh(kCGDirectMainDisplay);
		CFDictionaryRef dmode = CGDisplayBestModeForParameters(
			kCGDirectMainDisplay,
			checkDepth(depth),
			width,
			height,
			FALSE);
		CGDisplaySwitchToMode(kCGDirectMainDisplay,dmode);
	}

	// get screen size
	vid.pixelwidth = CGDisplayPixelsWide(kCGDirectMainDisplay);
	vid.pixelheight = CGDisplayPixelsHigh(kCGDirectMainDisplay);

	// capture the display!
	CGDisplayCapture(kCGDirectMainDisplay);
	CGDisplayHideCursor(kCGDirectMainDisplay);
	CGAssociateMouseAndMouseCursorPosition(false);

	format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
	_openGLContext = [[NSOpenGLContext alloc] 
			initWithFormat:format 
			shareContext:nil];
	[format release];
	if(_openGLContext == nil)
	{
		NSLog(@"Cannot create OpenGL context");
		[NSApp terminate:nil];
		return;
	}
    
	[_openGLContext setFullScreen];
	[_openGLContext setValues:&value forParameter:kCGLCPSwapInterval];

	[_openGLContext makeCurrentContext];
    
	_timer = [[NSTimer scheduledTimerWithTimeInterval:1.0/250.0
		target:self 
		selector:@selector(runLoop:) 
		userInfo:nil 
		repeats:YES] 
		retain];
        
	[[NSApp mainWindow] setAcceptsMouseMovedEvents:YES];
}

- (void)dealloc
{
	CGAssociateMouseAndMouseCursorPosition(true);
	CGDisplayRelease(kCGDirectMainDisplay);
	CGDisplayRestoreColorSyncSettings();
	CGDisplaySwitchToMode(kCGDirectMainDisplay,olddmode);
	CGDisplayMoveCursorToPoint(kCGDirectMainDisplay,
                               CGPointMake(vid.width/2,vid.height/2));
	[_openGLContext release];
	[_timer invalidate];
	[_timer release];
    
	[super dealloc];
}

- (void) sendEvent:(NSEvent*)event
{
	if ([event type] == NSKeyDown)
	{
		int code = keyconv[[event keyCode]];
		Key_Event(0, code, code>=128?0:code, TRUE);
		//printf("%d\n",[event keyCode]);
		return;
	}

	if ([event type] == NSKeyUp)
	{
		int code = keyconv[[event keyCode]];
		Key_Event(0, code, 0, FALSE);
		return;
	}

	if ([event type] == NSFlagsChanged)
	{
		unsigned int mflags = [event modifierFlags];

		if ((mflags & NSAlternateKeyMask) ^ (oldmflags & NSAlternateKeyMask))
		{
			Key_Event(0, K_ALT, 0, (mflags & NSAlternateKeyMask) ? TRUE : FALSE);
		}

		if ((mflags & NSControlKeyMask) ^ (oldmflags & NSControlKeyMask))
		{
			Key_Event(0, K_LCTRL, 0, (mflags & NSControlKeyMask) ? TRUE : FALSE);
		}
        
		if ((mflags & NSShiftKeyMask) ^ (oldmflags & NSShiftKeyMask))
		{
			Key_Event(0, K_LSHIFT, 0, (mflags & NSShiftKeyMask) ? TRUE : FALSE);
		}

		if ((mflags & NSCommandKeyMask) ^ (oldmflags & NSCommandKeyMask))
		{
			Key_Event(0, K_LWIN, 0, (mflags & NSCommandKeyMask) ? TRUE : FALSE);
		}

		if ((mflags & NSAlphaShiftKeyMask) ^ (oldmflags & NSAlphaShiftKeyMask))
		{
			Key_Event(0, K_CAPSLOCK, 0, (mflags & NSAlphaShiftKeyMask) ? TRUE : FALSE);
		}

		oldmflags = mflags;
		return;
	}

	if ([event type] == NSMouseMoved)
	{
		IN_MouseMove(0, false, [event deltaX], [event deltaY], 0, 0);

		// lame hack to avoid mouse ptr moving to the top of the screen since
		// a click there causes the mouse to appear and lock the event stream
		// Apple sucks :(
		// NOTE: it seems this is still needed for 10.3.x!
		CGDisplayMoveCursorToPoint(kCGDirectMainDisplay,
			CGPointMake(vid.width - 1,vid.height - 1));
		return;
	}

	if ([event type] == NSLeftMouseDragged)
	{
		IN_MouseMove(0, false, [event deltaX], [event deltaY], 0, 0);
		CGDisplayMoveCursorToPoint(kCGDirectMainDisplay,
			CGPointMake(vid.width - 1,vid.height - 1));
		return;
	}

	if ([event type] == NSRightMouseDragged)
	{
		IN_MouseMove(0, false, [event deltaX], [event deltaY], 0, 0);
		CGDisplayMoveCursorToPoint(kCGDirectMainDisplay,
			CGPointMake(vid.width - 1,vid.height - 1));
		return;
	}

	if ([event type] == NSOtherMouseDragged)
	{
		IN_MouseMove(0, false, [event deltaX], [event deltaY], 0, 0);
		CGDisplayMoveCursorToPoint(kCGDirectMainDisplay,
			CGPointMake(vid.width - 1,vid.height - 1));
		return;
	}

	if ([event type] == NSLeftMouseDown)
	{
		Key_Event(0, K_MOUSE1, 0, TRUE);
		return;
	}

	if ([event type] == NSLeftMouseUp)
	{
		Key_Event(0, K_MOUSE1, 0, FALSE);
		return;
	}

	if ([event type] == NSRightMouseDown)
	{
		Key_Event(0, K_MOUSE2, 0, TRUE);
		return;
	}

	if ([event type] == NSRightMouseUp)
	{
		Key_Event(0, K_MOUSE2, 0, FALSE);
		return;
	}

	if ([event type] == NSOtherMouseDown)
	{
		Key_Event(0, K_MOUSE3, 0, TRUE);
		return;
	}

	if ([event type] == NSOtherMouseUp)
	{
		Key_Event(0, K_MOUSE3, 0, FALSE);
		return;
	}
    
	if ([event type] == NSScrollWheel)
	{
		Key_Event(0, ([event deltaY] > 0.0) ? K_MWHEELUP : K_MWHEELDOWN, 0, TRUE);
		return;
	}
}    

- (void)flushBuffer
{
	// synchronise display
	[_openGLContext flushBuffer];
}

// called on a timer event
- (void)runLoop:(NSTimer *)timer
{
	newtime = Sys_DoubleTime ();
	time = newtime - oldtime;
	oldtime = newtime;
    
	Host_Frame(time);
}

- (void)run
{
	oldtime = Sys_DoubleTime ();
	[super run];
}

@end

static FTEApplication *fteglapp;

BOOL initCocoa(rendererstate_t *info)
{
	// init the application the hard way since we don't want to run it
	// immediately
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	fteglapp = [FTEApplication sharedApplication];

	// store the var for later disposal
	_p = pool;

	// init the display
	[fteglapp initDisplayWidth:info->width height:info->height depth:info->bpp];

	return TRUE;
}

qboolean glcocoaRunLoop(void)
{
	if (!fteglapp)
		return false;
	// this will initialise the NSTimer and run the app
	[NSApp run];

	return true;
}

void killCocoa(void)
{
	// terminates FTEApplicaiton
	[NSApp terminate:nil];
	[_p release];

	fteglapp = NULL;
}

void flushCocoa(void)
{
	// synchronises display
	[NSApp flushBuffer];
}

void cocoaGamma(unsigned short *r,unsigned short *g,unsigned short *b)
{
	uint8_t gammatable[3*256];
	int i;

	// convert the gamma values
	for(i=0;i<256;i++)
	{
		gammatable[i] = r[i] >> 8;
		gammatable[i+256] = g[i] >> 8;
		gammatable[i+512] = b[i] >> 8;
	}
    
	//... and set them
	CGSetDisplayTransferByByteTable(kCGDirectMainDisplay,256,
		gammatable,
		gammatable + 256,
		gammatable + 512);
}