#include "WindowManager.h"

void TransWindow(WindowRef w, Boolean vis)
{
	WindowTransitionAction Action;
	if(vis)
		Action = kWindowShowTransitionAction;
	else
		Action = kWindowHideTransitionAction;
	TransitionWindow(w, kWindowZoomTransitionEffect, Action, NULL);
}

#pragma mark -

void SetOrtho(VWindow * w)
{
	Rect bounds;
	bounds = WindowSize(w);
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	w->Ortho = true;
	glDisable(GL_DEPTH_TEST);
	glOrtho(0, 800, 0, 600, 0, 20);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void SetPerspective(VWindow * w)
{
	Rect bounds;
	GLfloat ratio;
	
	bounds = WindowSize(w);
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	if ((bounds.right - bounds.left) > (bounds.bottom - bounds.top)) {
		ratio = ((GLfloat) (bounds.right - bounds.left) / (bounds.bottom - bounds.top));
		glFrustum(-ratio, ratio, -1.0, 1.0, 1.5, 10000.0);
	} else
	{
		ratio = ((GLfloat) (bounds.bottom - bounds.top) / (bounds.right - bounds.left));
		glFrustum(-1.0, 1.0, -ratio, ratio, 1.5, 10000.0);
	}
	
	glScalef(10.0, 10.0, 10.0);
	
	w->Ortho = false;
	glEnable(GL_DEPTH_TEST);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

static void SetOGLSetttings(VWindow * w)
{
	glLineWidth(1.0);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glEnable(GL_ALPHA_TEST);
	glEnable(GL_POLYGON_OFFSET_FILL);
	glAlphaFunc(GL_GREATER, 0.0f);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
	glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST);
	glHint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); /* Wireframe mode */
	if(w->Ortho)
		SetOrtho(w);
	else
		SetPerspective(w);
}

Rect WindowSize(VWindow * w)
{
	Rect result;
	GDHandle Display;
	
	if(w->IsFullScreen)
	{
		Display = GetMainDevice();
		return (**Display).gdRect;
	}
	else
	{
		GetWindowPortBounds(w->TheWindow, &result);
		return result;
	}
}

void CreateWindowedContext(VWindow * w)
{
	GLint Attribs[] = {AGL_RGBA, AGL_DOUBLEBUFFER, AGL_DEPTH_SIZE, 24, 
		AGL_NO_RECOVERY, AGL_ACCELERATED, AGL_PIXEL_SIZE, 32, AGL_NONE};
	AGLPixelFormat Format;
	Rect windowBounds;
	GLint sync = 1;
	
	Format = aglChoosePixelFormat(NULL, 0, Attribs);
	w->Windowed = aglCreateContext(Format, w->FullScreen);
	
	if(w->FullScreen)
	{
	  glClear(GL_COLOR_BUFFER_BIT);
  	aglSwapBuffers(w->FullScreen);
  	
		aglSetCurrentContext(NULL);
		aglSetDrawable(w->FullScreen, NULL);
		aglDestroyContext(w->FullScreen);
		w->FullScreen = NULL;
	}
	
	aglSetDrawable(w->Windowed, GetWindowPort(w->TheWindow));
	aglSetCurrentContext(w->Windowed);
	aglSetInteger(w->Windowed, AGL_SWAP_INTERVAL, &sync);
	aglDestroyPixelFormat(Format);
	
	GetWindowPortBounds(w->TheWindow, &windowBounds);
	w->IsFullScreen = false;
	SetOGLSetttings(w);
	
	glClear(GL_COLOR_BUFFER_BIT);
	aglSwapBuffers(w->Windowed);
	
	if(aglGetError())
		SysBeep(0);
}

void CreateFullScreenContext(VWindow * w)
{
	GLint Attribs[] = {AGL_RGBA, AGL_DOUBLEBUFFER, AGL_DEPTH_SIZE, 24, 
		AGL_FULLSCREEN, AGL_NO_RECOVERY, AGL_ACCELERATED, AGL_PIXEL_SIZE, 32, AGL_NONE};
	AGLPixelFormat Format;
	GDHandle Display;
	VSettings * s;
	int Width, Height, RefreshRate;
	GLint sync = 1;
	
	Display = GetMainDevice();
	Format = aglChoosePixelFormat(&Display, 1, Attribs);
	w->FullScreen = aglCreateContext(Format, w->Windowed);
	
	if(w->Windowed)
	{
	  glClear(GL_COLOR_BUFFER_BIT);
  	aglSwapBuffers(w->Windowed);
  	
		aglSetCurrentContext(NULL);
		aglSetDrawable(w->Windowed, NULL);
		aglDestroyContext(w->Windowed);
		w->Windowed = NULL;
	}
	
	s = GetSettings();
	if (s == NULL) {
	  /* Default to no change */
	  Width = (**Display).gdRect.right;
	  Height = (**Display).gdRect.bottom;
	  RefreshRate = 0;
	} else {
	  GetFullscreenSettings(s, &Width, &Height, &RefreshRate);
	}
	if (aglSetFullScreen(w->FullScreen, Width, Height, RefreshRate, 0)) {
		aglSetCurrentContext(w->FullScreen);
	} else {
		SysBeep(1);
		aglDestroyPixelFormat(Format);
		CreateWindowedContext(w);
		return;
	}
	aglSetInteger(w->FullScreen, AGL_SWAP_INTERVAL, &sync);
	aglDestroyPixelFormat(Format);
	
	w->IsFullScreen = true;
	SetOGLSetttings(w);
	
	glClear(GL_COLOR_BUFFER_BIT);
	aglSwapBuffers(w->FullScreen);
	
	if(aglGetError())
		SysBeep(0);
}

static void InitWindowContext(VWindow * w)
{
	VSettings * s;
	
	w->FullScreen = NULL;
	w->Windowed = NULL;
	w->Ortho = false;
	w->Scaled = false;
	s = GetSettings();
	if (s != NULL && s->StartInFullScreen) CreateFullScreenContext(w);
	else CreateWindowedContext(w);
}

#pragma mark -

static OSStatus windowEventHandler(EventHandlerCallRef nextEvent, EventRef theEvent, void * userData) {
	VWindow * w;
  UInt32 attributes;
	
	switch (GetEventClass(theEvent)) {
		case kEventClassWindow:
			switch (GetEventKind(theEvent)) {
				case kEventWindowClose:
					w = (VWindow *) userData;
					if (w->quitOnClose) QuitApplicationEventLoop();
					break;
					
				case kEventWindowCollapsing:
					w = (VWindow *) userData;
					w->isMinimized = 1;
					if (!w->IsFullScreen && w->Windowed != NULL) {
						PixMapHandle myPixMap;
						Rect windowBounds;
						void * imageData;
						GWorldPtr origGWorld;
						GDHandle origGDev;
						short currentRow;
						Rect srcRect, destRect;
						
						GetWindowPortBounds(w->TheWindow, &windowBounds);
						OffsetRect(&windowBounds, -windowBounds.left, -windowBounds.top);
						myPixMap = NewPixMap();
						if (myPixMap == NULL) return eventNotHandledErr;
						imageData = NewPtr(4 * windowBounds.right * windowBounds.bottom);
						if (imageData == NULL) {
							DisposePixMap(myPixMap);
							return eventNotHandledErr;
						}
						
						glReadPixels(0, 0, windowBounds.right, windowBounds.bottom, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, imageData);
						
						(**myPixMap).baseAddr = imageData;
						(**myPixMap).rowBytes = ((windowBounds.right * 4) | (1 << 15));
						(**myPixMap).bounds = windowBounds;
						(**myPixMap).pmVersion = 0;
						(**myPixMap).packType = 0;
						(**myPixMap).packSize = 0;
						(**myPixMap).hRes = (72 << 16); /* hRes is a Fixed */
						(**myPixMap).vRes = (72 << 16); /* vRes is a Fixed */
						(**myPixMap).pixelType = RGBDirect;
						(**myPixMap).pixelSize = 32;
						(**myPixMap).cmpCount = 3; /* RGB, ignore Alpha */
						(**myPixMap).cmpSize = 8;
						(**myPixMap).pixelFormat = 0;
						(**myPixMap).pmExt = 0;
						(**myPixMap).pmTable = GetCTable(72); /* Unnecessary? */
						
						GetGWorld(&origGWorld, &origGDev);
						SetPortWindowPort(w->TheWindow);
						srcRect = destRect = windowBounds;
						
						/* Since glReadPixels will give us an upside-down image, copy one row at a time.
							 Not the most efficient way to do it, but it works well enough. */
						for (currentRow = 0; currentRow < windowBounds.bottom; currentRow++) {
							srcRect.bottom = (windowBounds.bottom - currentRow);
							srcRect.top = (srcRect.bottom - 1);
							destRect.top = currentRow;
							destRect.bottom = (currentRow + 1);
							CopyBits((BitMap *) *myPixMap, GetPortBitMapForCopyBits(GetWindowPort(w->TheWindow)), &srcRect, &destRect, srcCopy, NULL);
						}
						/* Make sure the image shows up */
						QDFlushPortBuffer(GetWindowPort(w->TheWindow), NULL);
						SetGWorld(origGWorld, origGDev);
						
						DisposePixMap(myPixMap);
						DisposePtr(imageData);
					}
					break;
					
				case kEventWindowExpanding:
					w = (VWindow *) userData;
					w->isMinimized = 0;
					break;
					
				case kEventWindowBoundsChanged:
					w = (VWindow *) userData;
					GetEventParameter(theEvent, kEventParamAttributes, typeUInt32, NULL, sizeof(UInt32), NULL, &attributes);
					if (attributes & kWindowBoundsChangeSizeChanged) {
						Rect windowBounds;
						
						aglUpdateContext(w->Windowed);
						GetWindowBounds(w->TheWindow, kWindowContentRgn, &windowBounds);
						windowBounds.right -= windowBounds.left;
						windowBounds.bottom -= windowBounds.top;
						windowBounds.left = windowBounds.top = 0;
						glViewport(windowBounds.left, windowBounds.top, windowBounds.right, windowBounds.bottom);
						
						if (w->Ortho) {
							SetOrtho(w);
						} else {
							SetPerspective(w);
						}
						return noErr;
					}
					break;
			}
	}
	
	return eventNotHandledErr;
}

void InitWindow(VWindow * w)
{
	GDHandle mainDevice;
	Point screenSize;
	Rect Bounds;
  EventTypeSpec eventTypes[] = {{kEventClassWindow, kEventWindowClose},
                                {kEventClassWindow, kEventWindowCollapsing},
                                {kEventClassWindow, kEventWindowExpanding},
                                {kEventClassWindow, kEventWindowBoundsChanged}};
	
	mainDevice = GetMainDevice();
	screenSize.h = (**mainDevice).gdRect.right;
	screenSize.v = (**mainDevice).gdRect.bottom;
	
	/* This will center the window onscreen. */
	SetRect(&Bounds, ((screenSize.h / 2) - 800/2), ((screenSize.v / 2) - 600/2),
		((screenSize.h / 2) + 800/2), ((screenSize.v / 2) + 600/2));
	CreateNewWindow(kDocumentWindowClass, 
		kWindowCloseBoxAttribute | kWindowCollapseBoxAttribute | kWindowResizableAttribute | kWindowLiveResizeAttribute | kWindowFullZoomAttribute, 
		&Bounds, &w->TheWindow);
	SetWindowTitleWithCFString(w->TheWindow, CFSTR("Infiltration"));
	InstallStandardEventHandler(GetWindowEventTarget(w->TheWindow));
	
  InstallWindowEventHandler(w->TheWindow, NewEventHandlerUPP(windowEventHandler), GetEventTypeCount(eventTypes), eventTypes, w, NULL);
  
	TransWindow(w->TheWindow, true);
	
	InitWindowContext(w);
	
	w->quitOnClose = 0;
	
	w->isMinimized = 0;
}

void CleanUpWindow(VWindow * w)
{
	TransWindow(w->TheWindow, false);
	
	if(w->FullScreen)
	{
		aglSetCurrentContext(NULL);
		aglSetDrawable(w->FullScreen, NULL);
		aglDestroyContext(w->FullScreen);
		w->FullScreen = NULL;
	}
	if(w->Windowed)
	{
		aglSetCurrentContext(NULL);
		aglSetDrawable(w->Windowed, NULL);
		aglDestroyContext(w->Windowed);
		w->Windowed = NULL;
	}
	
	DisposeWindow(w->TheWindow);
	w->TheWindow = NULL;
}
