/*
===========================================================================

Return to Castle Wolfenstein single player GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. 

This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).  

RTCW SP Source Code 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 3 of the License, or
(at your option) any later version.

RTCW SP Source Code 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
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with RTCW SP Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/


#import "macosx_display.h"

#include "tr_local.h"
#import "macosx_local.h"

#import <Foundation/Foundation.h>
#import <IOKit/graphics/IOGraphicsTypes.h>  // for interpreting the kCGDisplayIOFlags element of the display mode


NSDictionary *Sys_GetMatchingDisplayMode( qboolean allowStretchedModes ) {
	NSArray *displayModes;
	NSDictionary *mode;
	unsigned int modeIndex, modeCount, bestModeIndex;
	int verbose;
	cvar_t *cMinFreq, *cMaxFreq;
	int minFreq, maxFreq;
	unsigned int colorDepth;

	verbose = r_verbose->integer;

	colorDepth = r_colorbits->integer;
	if ( colorDepth < 16 || !r_fullscreen->integer ) {
		colorDepth = [[glw_state.desktopMode objectForKey: (id)kCGDisplayBitsPerPixel] intValue];
	}

	cMinFreq = ri.Cvar_Get( "r_minDisplayRefresh", "0", CVAR_ARCHIVE );
	cMaxFreq = ri.Cvar_Get( "r_maxDisplayRefresh", "0", CVAR_ARCHIVE );

	if ( cMinFreq && cMaxFreq && cMinFreq->integer && cMaxFreq->integer &&
		 cMinFreq->integer > cMaxFreq->integer ) {
		ri.Error( ERR_FATAL, "r_minDisplayRefresh must be less than or equal to r_maxDisplayRefresh" );
	}

	minFreq = cMinFreq ? cMinFreq->integer : 0;
	maxFreq = cMaxFreq ? cMaxFreq->integer : 0;

	displayModes = (NSArray *)CGDisplayAvailableModes( glw_state.display );
	if ( !displayModes ) {
		ri.Error( ERR_FATAL, "CGDisplayAvailableModes returned NULL -- 0x%0x is an invalid display", glw_state.display );
	}

	modeCount = [displayModes count];
	if ( verbose ) {
		ri.Printf( PRINT_ALL, "%d modes avaliable\n", modeCount );
		ri.Printf( PRINT_ALL, "Current mode is %s\n", [[(id)CGDisplayCurrentMode( glw_state.display ) description] cString] );
	}

	// Default to the current desktop mode
	bestModeIndex = 0xFFFFFFFF;

	for ( modeIndex = 0; modeIndex < modeCount; ++modeIndex ) {
		id object;
		int refresh;

		mode = [displayModes objectAtIndex: modeIndex];
		if ( verbose ) {
			ri.Printf( PRINT_ALL, " mode %d -- %s\n", modeIndex, [[mode description] cString] );
		}

		// Make sure we get the right size
		object = [mode objectForKey: (id)kCGDisplayWidth];

		if ([[mode objectForKey : (id)kCGDisplayWidth] intValue] != glConfig.vidWidth ||
			[[mode objectForKey : (id)kCGDisplayHeight] intValue] != glConfig.vidHeight ) {
			if ( verbose ) {
				ri.Printf( PRINT_ALL, " -- bad size\n" );
			}
			continue;
		}

		if ( !allowStretchedModes ) {
			if ([[mode objectForKey : (id)kCGDisplayIOFlags] intValue] & kDisplayModeStretchedFlag ) {
				if ( verbose ) {
					ri.Printf( PRINT_ALL, " -- stretched modes disallowed\n" );
				}
				continue;
			}
		}

		// Make sure that our frequency restrictions are observed
		refresh = [[mode objectForKey: (id)kCGDisplayRefreshRate] intValue];
		if ( minFreq &&  refresh < minFreq ) {
			if ( verbose ) {
				ri.Printf( PRINT_ALL, " -- refresh too low\n" );
			}
			continue;
		}

		if ( maxFreq && refresh > maxFreq ) {
			if ( verbose ) {
				ri.Printf( PRINT_ALL, " -- refresh too high\n" );
			}
			continue;
		}

		if ([[mode objectForKey : (id)kCGDisplayBitsPerPixel] intValue] != colorDepth ) {
			if ( verbose ) {
				ri.Printf( PRINT_ALL, " -- bad depth\n" );
			}
			continue;
		}

		bestModeIndex = modeIndex;
		if ( verbose ) {
			ri.Printf( PRINT_ALL, " -- OK\n", bestModeIndex );
		}
	}

	if ( verbose ) {
		ri.Printf( PRINT_ALL, " bestModeIndex = %d\n", bestModeIndex );
	}

	if ( bestModeIndex == 0xFFFFFFFF ) {
		ri.Printf( PRINT_ALL, "No suitable display mode available.\n" );
		return nil;
	}

	return [displayModes objectAtIndex : bestModeIndex];
}


#define MAX_DISPLAYS 128

void Sys_GetGammaTable( glwgamma_t *table ) {
	CGTableCount tableSize = 512;
	CGDisplayErr err;

	table->tableSize = tableSize;
	if ( table->red ) {
		free( table->red );
	}
	table->red = malloc( tableSize * sizeof( *table->red ) );
	if ( table->green ) {
		free( table->green );
	}
	table->green = malloc( tableSize * sizeof( *table->green ) );
	if ( table->blue ) {
		free( table->blue );
	}
	table->blue = malloc( tableSize * sizeof( *table->blue ) );

	// TJW: We _could_ loop here if we get back the same size as our table, increasing the table size.
	err = CGGetDisplayTransferByTable( table->display, tableSize, table->red, table->green, table->blue,
									   &table->tableSize );
	if ( err != CGDisplayNoErr ) {
		Com_Printf( "GLimp_Init: CGGetDisplayTransferByTable returned %d.\n", err );
		table->tableSize = 0;
	}
}

void Sys_SetGammaTable( glwgamma_t *table ) {
}


void Sys_StoreGammaTables() {
	// Store the original gamma for all monitors so that we can fade and unfade them all
	CGDirectDisplayID displays[MAX_DISPLAYS];
	CGDisplayCount displayIndex;
	CGDisplayErr err;

	err = CGGetActiveDisplayList( MAX_DISPLAYS, displays, &glw_state.displayCount );
	if ( err != CGDisplayNoErr ) {
		Sys_Error( "Cannot get display list -- CGGetActiveDisplayList returned %d.\n", err );
	}

	glw_state.originalDisplayGammaTables = calloc( glw_state.displayCount, sizeof( *glw_state.originalDisplayGammaTables ) );
	for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) {
		glwgamma_t *table;

		table = &glw_state.originalDisplayGammaTables[displayIndex];
		table->display = displays[displayIndex];
		Sys_GetGammaTable( table );
	}
}


//  This isn't a mathematically correct fade, but we don't care that much.
void Sys_SetScreenFade( glwgamma_t *table, float fraction ) {
	CGTableCount tableSize;
	CGGammaValue *red, *blue, *green;
	CGTableCount gammaIndex;

	if ( !glConfig.deviceSupportsGamma ) {
		return;
	}

	if ( !( tableSize = table->tableSize ) ) {
		// we couldn't get the table for this display for some reason
		return;
	}

//    Com_Printf("0x%08x %f\n", table->display, fraction);

	red = glw_state.tempTable.red;
	green = glw_state.tempTable.green;
	blue = glw_state.tempTable.blue;
	if ( glw_state.tempTable.tableSize < tableSize ) {
		glw_state.tempTable.tableSize = tableSize;
		red = realloc( red, sizeof( *red ) * tableSize );
		green = realloc( green, sizeof( *green ) * tableSize );
		blue = realloc( blue, sizeof( *blue ) * tableSize );
		glw_state.tempTable.red = red;
		glw_state.tempTable.green = green;
		glw_state.tempTable.blue = blue;
	}

	for ( gammaIndex = 0; gammaIndex < table->tableSize; gammaIndex++ ) {
		red[gammaIndex] = table->red[gammaIndex] * fraction;
		blue[gammaIndex] = table->blue[gammaIndex] * fraction;
		green[gammaIndex] = table->green[gammaIndex] * fraction;
	}

	CGSetDisplayTransferByTable( table->display, table->tableSize, red, green, blue );
}

// Fades all the active displays at the same time.

#define FADE_DURATION 0.5
void Sys_FadeScreens() {
	CGDisplayCount displayIndex;
	int stepIndex;
	glwgamma_t *table;
	NSTimeInterval start, current;
	float time;

	if ( !glConfig.deviceSupportsGamma ) {
		return;
	}

	Com_Printf( "Fading all displays\n" );

	start = [NSDate timeIntervalSinceReferenceDate];
	time = 0.0;
	while ( time != FADE_DURATION ) {
		current = [NSDate timeIntervalSinceReferenceDate];
		time = current - start;
		if ( time > FADE_DURATION ) {
			time = FADE_DURATION;
		}

		for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) {
			table = &glw_state.originalDisplayGammaTables[displayIndex];
			Sys_SetScreenFade( table, 1.0 - time / FADE_DURATION );
		}
	}
}

void Sys_FadeScreen( CGDirectDisplayID display ) {
	CGDisplayCount displayIndex;
	glwgamma_t *table;
	int stepIndex;

	if ( !glConfig.deviceSupportsGamma ) {
		return;
	}

	Com_Printf( "Fading display 0x%08x\n", display );

	for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) {
		if ( display == glw_state.originalDisplayGammaTables[displayIndex].display ) {
			NSTimeInterval start, current;
			float time;

			start = [NSDate timeIntervalSinceReferenceDate];
			time = 0.0;

			table = &glw_state.originalDisplayGammaTables[displayIndex];
			while ( time != FADE_DURATION ) {
				current = [NSDate timeIntervalSinceReferenceDate];
				time = current - start;
				if ( time > FADE_DURATION ) {
					time = FADE_DURATION;
				}

				Sys_SetScreenFade( table, 1.0 - time / FADE_DURATION );
			}
			return;
		}
	}

	Com_Printf( "Unable to find display to fade it\n" );
}

void Sys_UnfadeScreens() {
	CGDisplayCount displayIndex;
	int stepIndex;
	glwgamma_t *table;
	NSTimeInterval start, current;
	float time;

	if ( !glConfig.deviceSupportsGamma ) {
		return;
	}

	Com_Printf( "Unfading all displays\n" );

	start = [NSDate timeIntervalSinceReferenceDate];
	time = 0.0;
	while ( time != FADE_DURATION ) {
		current = [NSDate timeIntervalSinceReferenceDate];
		time = current - start;
		if ( time > FADE_DURATION ) {
			time = FADE_DURATION;
		}

		for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) {
			table = &glw_state.originalDisplayGammaTables[displayIndex];
			Sys_SetScreenFade( table, time / FADE_DURATION );
		}
	}
}

void Sys_UnfadeScreen( CGDirectDisplayID display, glwgamma_t *table ) {
	CGDisplayCount displayIndex;
	int stepIndex;

	if ( !glConfig.deviceSupportsGamma ) {
		return;
	}

	Com_Printf( "Unfading display 0x%08x\n", display );

	if ( table ) {
		CGTableCount i;

		Com_Printf( "Given table:\n" );
		for ( i = 0; i < table->tableSize; i++ ) {
			Com_Printf( "  %f %f %f\n", table->red[i], table->blue[i], table->green[i] );
		}
	}

	// Search for the original gamma table for the display
	if ( !table ) {
		for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) {
			if ( display == glw_state.originalDisplayGammaTables[displayIndex].display ) {
				table = &glw_state.originalDisplayGammaTables[displayIndex];
				break;
			}
		}
	}

	if ( table ) {
		NSTimeInterval start, current;
		float time;

		start = [NSDate timeIntervalSinceReferenceDate];
		time = 0.0;

		while ( time != FADE_DURATION ) {
			current = [NSDate timeIntervalSinceReferenceDate];
			time = current - start;
			if ( time > FADE_DURATION ) {
				time = FADE_DURATION;
			}
			Sys_SetScreenFade( table, time / FADE_DURATION );
		}
		return;
	}

	Com_Printf( "Unable to find display to unfade it\n" );
}



