/****************************/
/*        SCREEN.C           */
/* (c)2004 Pangea Software  */
/* By Brian Greenstone      */
/****************************/


/****************************/
/*    EXTERNS				*/
/****************************/

extern	IBNibRef 			gNibs;
extern	AGLContext			gAGLContext;
extern	float				gOneOverFramesPerSecond;


/****************************/
/*    PROTOTYPES            */
/****************************/

static void BuildResolutionMenu(void);
static void CreateDisplayModeList(void);
static pascal OSStatus DoScreenModeDialog_EventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData);


/****************************/
/*    CONSTANTS             */
/****************************/


typedef struct
{
	int		rezH,rezV;
}VideoModeType;


#define	MAX_VIDEO_MODES		50

#define	INVALID_DISPLAY		(CGDirectDisplayID)-1


/**********************/
/*     VARIABLES      */
/**********************/

static short				gNumVideoModes = 0;
static VideoModeType		gVideoModeList[MAX_VIDEO_MODES];

CGDirectDisplayID			gCGDisplayID = INVALID_DISPLAY;
short						gGameWindowWidth, gGameWindowHeight;
WindowPtr					gGameWindow = nil;

static	WindowRef			gDialogWindow;
CGrafPtr					gGameWindowGrafPtr;

Boolean						gPlayInWindow = false;
short						gDesiredColorDepth;
short						gDesiredRezH, gDesiredRezV;

u_long						gDisplayVRAM;


			/* GAMMA VALUES */
			
CGGammaValue	gGammaRedMin, gGammaRedMax, gGammaRedGamma;
CGGammaValue	gGammaGreenMin,gGammaGreenMax,gGammaGreenGamma;
CGGammaValue	gGammaBlueMin, gGammaBlueMax, gGammaBlueGamma;

float	gGammaBrightness = 1.0f;


/************************** SWITCH DISPLAY MODE *****************************/
//
// Captures the Main display and changes the video mode to the input resolution and depth.
//
// INPUT:	width	=	desired horizontal resolution
//			height	=	desired vertical resolution
//			depth	=	value of 16 or 32 for the desired bit depth
//

void SwitchDisplayMode(int width, int height, int depth)
{
CFDictionaryRef 	refDisplayMode = 0;
	

		/* GET THE MAIN DISPLAY & CAPTURE IT */

	gCGDisplayID = CGMainDisplayID();			// we already have this from CreateDisplayModeList(), so this isn't really needed
	CGDisplayCapture(gCGDisplayID);
	

			/* FIND BEST MATCH FOR WHAT WE WANT */
				
	refDisplayMode = CGDisplayBestModeForParameters(
								gCGDisplayID,
								depth,				// 16 or 32-bit depth
								width,				// horiz rez
								height,				// vert rez
								nil);

	if (refDisplayMode == nil)
		DoFatalAlert("\pCGDisplayBestModeForParameters failed!");		
			
			
				/* SWITCH TO IT */
				
	CGDisplaySwitchToMode (gCGDisplayID, refDisplayMode);


		/* CALCULATE THE ACTUAL WIDTH/HEIGHT OF OUR SCREEN */

	gGameWindowWidth = CGDisplayPixelsWide(gCGDisplayID);	
	gGameWindowHeight = CGDisplayPixelsHigh(gCGDisplayID);
}



/********************* CREATE GAME WINDOW ****************************/
//
// This gets called if the user wants to play the game in a window instead of full-screen
//

void CreateGameWindow(void)
{
int			w, h;
GDHandle 	gdevice;
Rect		r;
		
		
			/* GET MAIN DISPLAY INFO */
			
	gdevice = GetMainDevice();
	r = (**gdevice).gdRect;						// get Rect of display

			/* SIZE WINDOW TO HALF SCREEN SIZE */
			
	w = r.right - r.left;						// get width/height of display
	h = r.bottom - r.top;
	
	r.left		= 0;							// set Rect to size of window that we want
	r.top		= 0;
	r.right		= gGameWindowWidth = w / 2;
	r.bottom	= gGameWindowHeight = h / 2;	
	
			
					/* CREATE THE WINDOW*/
					
	gGameWindow = NewCWindow(nil, &r, "\p", false, plainDBox, (WindowPtr)-1L, false, 0);

			/* MOVE WINDOW TO CENTER OF SCREEN */
			
	MoveWindow(gGameWindow, r.right/2, r.bottom/2, true);
	ShowWindow(gGameWindow);		

	gGameWindowGrafPtr = GetWindowPort(gGameWindow);
	
}



#pragma mark -

/********************* DO SCREEN MODE DIALOG *************************/

void DoScreenModeDialog(void)
{
OSErr			err;
EventHandlerRef	ref;
EventTypeSpec	list[] = { { kEventClassCommand,  kEventProcessCommand } };
ControlID 		idControl;
ControlRef 		control;
int				i;
EventHandlerUPP winEvtHandler;		

			/*************************/
			/* INITIALIZE THE DIALOG */
			/*************************/
			
			/* BUILD LIST OF VIDEO MODES FOR USER TO CHOOSE FROM */
				
	CreateDisplayModeList();


			/* CREATE DIALOG WINDOW FROM THE NIB */
				
    err = CreateWindowFromNib(gNibs, CFSTR("VideoMode"), &gDialogWindow);
	if (err)
		DoFatalAlert("\pCreateWindowFromNib failed!");

	BuildResolutionMenu();								// put video mode options in pop-up menu


			/* CREATE NEW WINDOW EVENT HANDLER */
				
    winEvtHandler = NewEventHandlerUPP(DoScreenModeDialog_EventHandler);
    InstallWindowEventHandler(gDialogWindow, winEvtHandler, GetEventTypeCount(list), list, 0, &ref);



			/**********************/
			/* PROCESS THE DIALOG */
			/**********************/
			
    ShowWindow(gDialogWindow);
	RunAppModalLoopForWindow(gDialogWindow);


			/*********************/
			/* GET RESULT VALUES */
			/*********************/
			
			/* GET "PLAY IN WINDOW" CHECKBOX */

    idControl.signature = 'wind';
    idControl.id 		= 0; 
    GetControlByID(gDialogWindow, &idControl, &control);
	gPlayInWindow = GetControlValue(control);


			/* GET "16/32" RADIO BUTTONS */

    idControl.signature = 'bitd';
    idControl.id 		= 0; 
    GetControlByID(gDialogWindow, &idControl, &control);
	
	if (GetControlValue(control) == 1)
		gDesiredColorDepth = 16;
	else
		gDesiredColorDepth = 32;


			/* GET RESOLUTION MENU VALUE */
			
    idControl.signature = 'rezm';
    idControl.id 		= 0; 
    GetControlByID(gDialogWindow, &idControl, &control);
	i = GetControlValue(control);
	
	gDesiredRezH = gVideoModeList[i-1].rezH;
	gDesiredRezV = gVideoModeList[i-1].rezV;


	

				/***********/
				/* CLEANUP */
				/***********/
				
	DisposeEventHandlerUPP (winEvtHandler);
	DisposeWindow (gDialogWindow);	
}


/****************** DO SCREEN MODE DIALOG EVENT HANDLER *************************/
//
// Event handler called by the OS to process events for this dialog.
//

static pascal OSStatus DoScreenModeDialog_EventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData)
{
#pragma unused (myHandler, userData)
HICommand 		command;

	switch(GetEventKind(event))
	{
				
				/*******************/
				/* PROCESS COMMAND */
				/*******************/
				//
				// We only care if the user pressed the OK or Quit buttons.
				// Let the OS handle all other events.
				//
				
		case	kEventProcessCommand:
				GetEventParameter (event, kEventParamDirectObject, kEventParamHICommand,
								NULL, sizeof(command), NULL, &command);
				switch(command.commandID)
				{
							/* "OK" BUTTON */
							
					case	'ok  ':
		                    QuitAppModalLoopForWindow(gDialogWindow);
							break;

							/* "QUIT" BUTTON */
							
					case	'quit':
		                    ExitToShell();
							break;
				}
				break;
    }

    return (eventNotHandledErr);
}



/****************** BUILD RESOLUTION MENU *************************/

static void BuildResolutionMenu(void)
{
Str255 		menuStrings[MAX_VIDEO_MODES];
short		i;
Str32		s,t;
MenuHandle 	hMenu;
Size 		tempSize;
ControlID 	idControl;
ControlRef 	control;
	
			/* BUILD MENU ITEM STRINGS */
				
	for (i=0; i < gNumVideoModes; i++)
	{
		NumToString(gVideoModeList[i].rezH, s);		// "width"
		s[s[0]+1] = 'x';							// "x"
		s[0]++;
		NumToString(gVideoModeList[i].rezV, t);		// "height"
		BlockMove(&t[1], &s[s[0]+1], t[0]);
		s[0] += t[0];	
		BlockMove(&s[0], &menuStrings[i][0], s[0]+1);		
	}
				
			
    		/* GET MENU HANDLE FROM DIALOG */
    		
    idControl.signature = 'rezm';
    idControl.id 		= 0; 
    GetControlByID(gDialogWindow, &idControl, &control);
    GetControlData(control, kControlMenuPart, kControlPopupButtonMenuHandleTag, sizeof(MenuHandle), &hMenu, &tempSize);
    
    	/* MAKE SURE MENU IS EMPTY */
    		
    DeleteMenuItems(hMenu, 1, CountMenuItems (hMenu));


			/* ADD NEW ITEMS TO THE MENU */
			
    for (i = 0; i < gNumVideoModes; i++)
        AppendMenu(hMenu, menuStrings[i]);


			/* FORCE UPDATE OF MENU EXTENTS */
			
    SetControlMaximum(control, gNumVideoModes);
	SetControlValue(control, 1);               			
}

#pragma mark -

/********************* CREATE DISPLAY MODE LIST *************************/

static void CreateDisplayModeList(void)
{
int				i, numDeviceModes;
CFArrayRef 		modeList; 

	gNumVideoModes = 0;							// clear our modes list

				/* GET MAIN MONITOR ID */
				
	gCGDisplayID = CGMainDisplayID();


				/* GET LIST OF MODES FOR THIS MONITOR */
				
	modeList = CGDisplayAvailableModes(gCGDisplayID); 
	if (modeList == nil)
		DoFatalAlert("\pCGDisplayAvailableModes failed!");

	numDeviceModes = CFArrayGetCount(modeList); 


				/*********************/
				/* EXTRACT MODE INFO */
				/*********************/
				
	for (i = 0; i < numDeviceModes; i++)
    {      
    	CFNumberRef			numRef;
		CFDictionaryRef 	mode; 
		Boolean				skip;
		int					j;
		long				width, height, dep;
    				
						

        mode = CFArrayGetValueAtIndex( modeList, i);					// Get the mode out of the list
		if (mode == nil)
			DoFatalAlert("\pCFArrayGetValueAtIndex failed!");
					
		numRef = CFDictionaryGetValue(mode, kCGDisplayWidth);			// get modeÕs width
		CFNumberGetValue(numRef, kCFNumberLongType, &width) ; 
						
		numRef = CFDictionaryGetValue(mode, kCGDisplayHeight);			// get modeÕs height
		CFNumberGetValue(numRef, kCFNumberLongType, &height); 	

		numRef = CFDictionaryGetValue(mode, kCGDisplayBitsPerPixel);	// get modeÕs depth
		CFNumberGetValue(numRef, kCFNumberLongType, &dep); 	
		if (dep != 32)													// only look for 32-bit modes
			continue;


					/***************************/
					/* SEE IF ADD TO MODE LIST */
					/***************************/

					/* SEE IF IT'S A VALID MODE FOR US */

		if (CFDictionaryGetValue(mode, kCGDisplayModeUsableForDesktopGUI) != kCFBooleanTrue)
			continue;	
							
								
						/* CHECK IF ALREADY IN LIST */
						
		skip = false;	
		for (j = 0; j < gNumVideoModes; j++)
		{
			if ((gVideoModeList[j].rezH == width) && (gVideoModeList[j].rezV == height))
			{
				skip = true;
				break;
			}
		}
		
		if (!skip)
		{	
				/* THIS REZ NOT IN LIST YET, SO ADD */
			
			if (gNumVideoModes < MAX_VIDEO_MODES)
			{
				gVideoModeList[gNumVideoModes].rezH = width;
				gVideoModeList[gNumVideoModes].rezV = height;
				gNumVideoModes++;
			}
		}
	}
} 


#pragma mark -

/******************** CALC DISPLAY VRAM ***********************/

void CalcDisplayVRAM(void)
{
io_service_t	port;
CFTypeRef		classCode;

			/* LOOK UP DISPLAYÕS VRAM */
			
	port = CGDisplayIOServicePort(CGMainDisplayID());
	classCode = IORegistryEntryCreateCFProperty(port, CFSTR(kIOFBMemorySizeKey),
											kCFAllocatorDefault, kNilOptions);
	

				/* EXTRACT VALUE */

	if (CFGetTypeID(classCode) == CFNumberGetTypeID())
	{
		CFNumberGetValue(classCode, kCFNumberSInt32Type, &gDisplayVRAM);
	}
	else
	{
		gDisplayVRAM = 0x4000000;		// if failed, then just assume 64MB VRAM to be safe			
	}	
}

#pragma mark -

/********************* INIT GAMMA VALUES ********************/
//
// Called once when application starts up to grab the display's current
// gamma information.
//

void InitGammaValues(void)
{
	CGGetDisplayTransferByFormula(gCGDisplayID,
						&gGammaRedMin, &gGammaRedMax, &gGammaRedGamma,
						&gGammaGreenMin, &gGammaGreenMax, &gGammaGreenGamma,
						&gGammaBlueMin, &gGammaBlueMax, &gGammaBlueGamma);
						
	gGammaBrightness = 1.0;
}


/****************** GAMMA FADE OUT *********************/
//
// Fades out the gamma over a duration of fadeDuration seconds.
//

void GammaFadeOut(float fadeDuration)
{	
    while (gGammaBrightness > 0.0f)
	{
				/* SET NEW GAMMA */
					
		SetGammaFade(gGammaBrightness);
		
				/* DECAY BRIGHTNESS */
				
		CalcFramesPerSecond();
		gGammaBrightness -= gOneOverFramesPerSecond / fadeDuration;

    }

			/* MAKE SURE WE'RE TOTALLY FADED */
			
	SetGammaFade(0);
	gGammaBrightness = 0;
}

/****************** GAMMA FADE OUT ONE FRAME *********************/
//
// Does one frame of animation's worth of gamma fade-out.
//
// INPUT: fadeDuration = determines the rate at which we fade.
//

void GammaFadeOutOneFrame(float fadeDuration)
{	
    if (gGammaBrightness > 0.0f)
	{
				/* DECAY BRIGHTNESS */
				
		gGammaBrightness -= gOneOverFramesPerSecond / fadeDuration;
		if (gGammaBrightness < 0.0f)			// pin to 0.0
			gGammaBrightness = 0.0f;
			
				/* SET NEW GAMMA */
					
		SetGammaFade(gGammaBrightness);
    }
}


/****************** GAMMA FADE IN *********************/
//
// Fades in the gamma over a duration of fadeDuration seconds.
//

void GammaFadeIn(float fadeDuration)
{	
    while (gGammaBrightness < 1.0f)
	{
				/* SET NEW GAMMA */
					
		SetGammaFade(gGammaBrightness);
		
				/* DECAY BRIGHTNESS */
				
		CalcFramesPerSecond();
		gGammaBrightness += gOneOverFramesPerSecond / fadeDuration;

    }

			/* MAKE SURE WE'RE TOTALLY FADED IN */
			
	SetGammaFade(1);
	gGammaBrightness = 1.0;
}


/****************** GAMMA FADE IN ONE FRAME *********************/
//
// Does one frame of animation's worth of gamma fade-in.
//
// INPUT: fadeDuration = determines the rate at which we fade.
//

void GammaFadeInOneFrame(float fadeDuration)
{	
    if (gGammaBrightness < 1.0f)
	{
				/* DECAY BRIGHTNESS */
				
		gGammaBrightness += gOneOverFramesPerSecond / fadeDuration;
		if (gGammaBrightness > 1.0f)			// pin to 1.0
			gGammaBrightness = 1.0f;
			
				/* SET NEW GAMMA */
					
		SetGammaFade(gGammaBrightness);
    }
}


/****************** SET GAMMA FADE *********************/
//
// Sets the display's gamma fade brightness based on the input brightness value.
//
// INPUT:  brightness = value from 0.0 to 1.0.
//

void SetGammaFade(float brightness)
{	
	float	redMax		= gGammaRedMax * brightness;
	float	greenMax	= gGammaGreenMax * brightness;
	float	blueMax		= gGammaBlueMax * brightness;
	
 
	CGSetDisplayTransferByFormula(gCGDisplayID,
					gGammaRedMin,	redMax,		gGammaRedGamma,
					gGammaGreenMin, greenMax,	gGammaGreenGamma,
					gGammaBlueMin,	blueMax,	gGammaBlueGamma);
}

#pragma mark -


/******************* ENTER 2D *************************/
//
// Called whenever we need to bring up a 2D window or dialog
// from full-screen mode.
//

void Enter2D(void)
{
		/* DONÕT DO ANYTHING IF IN WINDOWED MODE OR NO DISPLAY YET */

	if (gPlayInWindow || (gCGDisplayID == INVALID_DISPLAY))		
		return;

	InitCursor();					// make sure thereÕs a cursor


		/* NEED TO UN-CAPTURE THE DISPLAY */
			
	CGDisplayRelease(gCGDisplayID);

			/* DISABLE GL */

	if (gAGLContext)
	{
		glFlush();
		glFinish();
		
		aglSetDrawable(gAGLContext, nil);

		glFlush();
		glFinish();
	}
}

/********************* EXIT 2D ***********************/
//
// Called when we want to re-activate our full-screen draw context.
//

void Exit2D(void)
{
		/* DONÕT DO ANYTHING IF IN WINDOWED MODE OR NO DISPLAY YET */

	if (gPlayInWindow || (gCGDisplayID == INVALID_DISPLAY))		
		return;


	HideCursor();				// make cursor go away
	
			/* RE-CAPTURE THE DISPLAY */

	CGDisplayCapture(gCGDisplayID);


			/* RE-ENABLE GL */

	if (gAGLContext)
		aglSetFullScreen(gAGLContext, 0, 0, 0, 0);
}


