#include "quakedef.h"

#ifdef _WIN32
#include "winquake.h"

extern vrect_t         scr_vrect;

/* 
============================================================================== 
						LIGHT BLOOMS
============================================================================== 
*/ 

static float Diamond8x[8][8] = { 
	{0.0f, 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f, 0.0f}, 
	{0.0f, 0.0f, 0.2f, 0.3f, 0.3f, 0.2f, 0.0f, 0.0f}, 
	{0.0f, 0.2f, 0.4f, 0.6f, 0.6f, 0.4f, 0.2f, 0.0f}, 
	{0.1f, 0.3f, 0.6f, 0.9f, 0.9f, 0.6f, 0.3f, 0.1f}, 
	{0.1f, 0.3f, 0.6f, 0.9f, 0.9f, 0.6f, 0.3f, 0.1f}, 
	{0.0f, 0.2f, 0.4f, 0.6f, 0.6f, 0.4f, 0.2f, 0.0f}, 
	{0.0f, 0.0f, 0.2f, 0.3f, 0.3f, 0.2f, 0.0f, 0.0f}, 
	{0.0f, 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f, 0.0f} };

static float Diamond6x[6][6] = { 
	{0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f}, 
	{0.0f, 0.3f, 0.5f, 0.5f, 0.3f, 0.0f},  
	{0.1f, 0.5f, 0.9f, 0.9f, 0.5f, 0.1f}, 
	{0.1f, 0.5f, 0.9f, 0.9f, 0.5f, 0.1f}, 
	{0.0f, 0.3f, 0.5f, 0.5f, 0.3f, 0.0f}, 
	{0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f} };

static float Diamond4x[4][4] = {  
	{0.3f, 0.4f, 0.4f, 0.3f},  
	{0.4f, 0.9f, 0.9f, 0.4f}, 
	{0.4f, 0.9f, 0.9f, 0.4f}, 
	{0.3f, 0.4f, 0.4f, 0.3f} };

static int		BLOOM_SIZE;

cvar_t		gl_bloom = {"gl_bloom", "1", true};
cvar_t		gl_bloom_alpha = {"gl_bloom_alpha", "0.24", true};
cvar_t		gl_bloom_diamond_size = {"gl_bloom_diamond_size", "8", true};
cvar_t		gl_bloom_intensity = {"gl_bloom_intensity", "1.1", true};
cvar_t		gl_bloom_darken = {"gl_bloom_darken", "3", true};
cvar_t		gl_bloom_sample_size = {"gl_bloom_sample_size", "256", true};
cvar_t		gl_bloom_fast_sample = {"gl_bloom_fast_sample", "0", true};

int	gl_bloomscreentexture;
int gl_bloomeffecttexture;
int gl_bloombackuptexture;
int gl_bloomdownsamplingtexture;

static int		r_screendownsamplingtexture_size;
static int		screen_texture_width, screen_texture_height;
static int		r_screenbackuptexture_size;

//current refdef size:
static int	curView_x;
static int	curView_y;
static int	curView_width;
static int	curView_height;

//texture coordinates of screen data inside screentexture
static float screenText_tcw;
static float screenText_tch;

static int	sample_width;
static int	sample_height;

//texture coordinates of adjusted textures
static float sampleText_tcw;
static float sampleText_tch;

//this macro is in sample size workspace coordinates
#define R_Bloom_SamplePass( xpos, ypos )							\
	glBegin(GL_QUADS);												\
	glTexCoord2f(	0,						sampleText_tch);		\
	glVertex2f(	xpos,					ypos);						\
	glTexCoord2f(	0,						0);						\
	glVertex2f(	xpos,					ypos+sample_height);		\
	glTexCoord2f(	sampleText_tcw,			0);						\
	glVertex2f(	xpos+sample_width,		ypos+sample_height);		\
	glTexCoord2f(	sampleText_tcw,			sampleText_tch);		\
	glVertex2f(	xpos+sample_width,		ypos);						\
	glEnd();

#define R_Bloom_Quad( x, y, width, height, textwidth, textheight )	\
	glBegin(GL_QUADS);												\
	glTexCoord2f(	0,			textheight);						\
	glVertex2f(	x,			y);										\
	glTexCoord2f(	0,			0);									\
	glVertex2f(	x,			y+height);								\
	glTexCoord2f(	textwidth,	0);									\
	glVertex2f(	x+width,	y+height);								\
	glTexCoord2f(	textwidth,	textheight);						\
	glVertex2f(	x+width,	y);										\
	glEnd();

/*
=================
R_Bloom_InitBackUpTexture
=================
*/
void R_Bloom_InitBackUpTexture( int width, int height )
{
	byte	*data;
	data = malloc( width * height * 4 );
	r_screenbackuptexture_size = width;
	gl_bloombackuptexture = GL_LoadTexture("***gl_bloombackuptexture***", width, height, data, false, false, 4, 4, false );
	free ( data );
}

/*
=================
R_Bloom_InitEffectTexture
=================
*/
void R_Bloom_InitEffectTexture( void )
{
	byte	*data;
	float	bloomsizecheck;
	
	if( gl_bloom_sample_size.value < 32 ) Cvar_SetValue ("gl_bloom_sample_size", 32);

	if( gl_bloom_sample_size.value > gl_max_size.value*0.5 ) Cvar_SetValue ("gl_bloom_sample_size", (int)gl_max_size.value*0.5);

	//make sure bloom size is a power of 2
	BLOOM_SIZE = gl_bloom_sample_size.value;
	bloomsizecheck = (float)BLOOM_SIZE;
	while(bloomsizecheck > 1.0f) bloomsizecheck /= 2.0f;
	if( bloomsizecheck != 1.0f )
	{
		BLOOM_SIZE = 32;
		while( BLOOM_SIZE < gl_bloom_sample_size.value )
			BLOOM_SIZE *= 2;
	}

	//make sure bloom size doesn't have stupid values
	if( BLOOM_SIZE > screen_texture_width || BLOOM_SIZE > screen_texture_height )
		BLOOM_SIZE = min( screen_texture_width, screen_texture_height );

	if( BLOOM_SIZE != gl_bloom_sample_size.value ) Cvar_SetValue ("gl_bloom_sample_size", BLOOM_SIZE);

	data = malloc( BLOOM_SIZE * BLOOM_SIZE * 4 );
	gl_bloomeffecttexture = GL_LoadTexture("***gl_bloomeffecttexture***", BLOOM_SIZE, BLOOM_SIZE, data, false, false, 4, 4, false );
	free ( data );
}

/*
=================
R_Bloom_InitTextures
=================
*/
void R_Bloom_InitTextures( void )
{
	byte	*data;
	int		size;
	int maxtexsize;

	//find closer power of 2 to screen size 
	for (screen_texture_width = 1;screen_texture_width < glwidth;screen_texture_width *= 2);
	for (screen_texture_height = 1;screen_texture_height < glheight;screen_texture_height *= 2);

	//disable blooms if we can't handle a texture of that size
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxtexsize);
	if( screen_texture_width > maxtexsize ||
		screen_texture_height > maxtexsize ) {
		screen_texture_width = screen_texture_height = 0;
		Cvar_SetValue ("gl_bloom", 0);
		Con_Printf( "WARNING: 'R_InitBloomScreenTexture' too high resolution for Light Bloom. Effect disabled\n" );
		return;
	}

	//init the screen texture
	size = screen_texture_width * screen_texture_height * 4;
	data = malloc( size );
	memset( data, 255, size );
	if (!gl_bloomscreentexture)
		gl_bloomscreentexture = texture_extension_number++;
	GL_Bind(gl_bloomscreentexture);
	glTexImage2D (GL_TEXTURE_2D, 0, gl_solid_format, screen_texture_width, screen_texture_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	free ( data );

	//validate bloom size and init the bloom effect texture
	R_Bloom_InitEffectTexture ();

	//if screensize is more than 2x the bloom effect texture, set up for stepped downsampling
	gl_bloomdownsamplingtexture = 0;
	r_screendownsamplingtexture_size = 0;
	if( glwidth > (BLOOM_SIZE * 2) && !gl_bloom_fast_sample.value )
	{
		r_screendownsamplingtexture_size = (int)(BLOOM_SIZE * 2);
		data = malloc( r_screendownsamplingtexture_size * r_screendownsamplingtexture_size * 4 );
		gl_bloomdownsamplingtexture = GL_LoadTexture("***gl_bloomdownsamplingtexture***", r_screendownsamplingtexture_size, r_screendownsamplingtexture_size, data, false, false, 4, 4, false );
		free ( data );
	}

	//Init the screen backup texture
	if( r_screendownsamplingtexture_size )
		R_Bloom_InitBackUpTexture( r_screendownsamplingtexture_size, r_screendownsamplingtexture_size );
	else
		R_Bloom_InitBackUpTexture( BLOOM_SIZE, BLOOM_SIZE );
}

void R_BloomRegister(void)
{
	Cvar_RegisterVariable (&gl_bloom);
	Cvar_RegisterVariable (&gl_bloom_alpha);
	Cvar_RegisterVariable (&gl_bloom_diamond_size);
	Cvar_RegisterVariable (&gl_bloom_intensity);
	Cvar_RegisterVariable (&gl_bloom_darken);
	Cvar_RegisterVariable (&gl_bloom_sample_size);
	Cvar_RegisterVariable (&gl_bloom_fast_sample);
}

/*
=================
R_InitBloomTextures
=================
*/
void R_InitBloomTextures( void )
{
	BLOOM_SIZE = 0;
	if( !gl_bloom.value )
		return;

	gl_bloomscreentexture = 0;	//this came from a vid_restart, where none of the textures are valid any more.
	R_Bloom_InitTextures ();
}


/*
=================
R_Bloom_DrawEffect
=================
*/
void R_Bloom_DrawEffect( void )
{
	GL_Bind(gl_bloomeffecttexture);
	glEnable(GL_BLEND);
	glBlendFunc(GL_ONE, GL_ONE);
	glColor4f(gl_bloom_alpha.value, gl_bloom_alpha.value, gl_bloom_alpha.value, 1.0f);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glBegin(GL_QUADS);							
	glTexCoord2f(	0,							sampleText_tch	);	
	glVertex2f(	curView_x,					curView_y	);				
	glTexCoord2f(	0,							0	);				
	glVertex2f(	curView_x,					curView_y + curView_height	);	
	glTexCoord2f(	sampleText_tcw,				0	);				
	glVertex2f(	curView_x + curView_width,	curView_y + curView_height	);	
	glTexCoord2f(	sampleText_tcw,				sampleText_tch	);	
	glVertex2f(	curView_x + curView_width,	curView_y	);				
	glEnd();
	glDisable(GL_BLEND);
}

/*
=================
R_Bloom_GeneratexDiamonds
=================
*/
void R_Bloom_GeneratexDiamonds( void )
{
	int			i, j;
	static float intensity;

	//set up sample size workspace
	glViewport( 0, 0, sample_width, sample_height );
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity ();
	glOrtho(0, sample_width, sample_height, 0, -10, 100);
	glMatrixMode( GL_MODELVIEW );
    glLoadIdentity ();

	//copy small scene into gl_bloomeffecttexture
	GL_Bind(gl_bloomeffecttexture);
	glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height);
	//start modifying the small scene corner
	glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
	glEnable(GL_BLEND);
	//darkening passes
	if( gl_bloom_darken.value )
	{
		glBlendFunc(GL_DST_COLOR, GL_ZERO);
		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		for(i=0; i<gl_bloom_darken.value ;i++) {
			R_Bloom_SamplePass( 0, 0 );
		}
		glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height);
	}

	//bluring passes
	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); //(GL_ONE, GL_ONE);
	
	if( gl_bloom_diamond_size.value > 7 || gl_bloom_diamond_size.value <= 3)
	{
		if( gl_bloom_diamond_size.value != 8 ) Cvar_SetValue( "gl_bloom_diamond_size", 8 );

		for(i=0; i<gl_bloom_diamond_size.value; i++) {
			for(j=0; j<gl_bloom_diamond_size.value; j++) {
				intensity = gl_bloom_intensity.value * 0.3 * Diamond8x[i][j];
				if( intensity < 0.01f ) continue;
				glColor4f( intensity, intensity, intensity, 1.0);
				R_Bloom_SamplePass( i-4, j-4 );
			}
		}
	} else if( gl_bloom_diamond_size.value > 5 ) {
		
		if( gl_bloom_diamond_size.value != 6 ) Cvar_SetValue("gl_bloom_diamond_size", 6 );

		for(i=0; i<gl_bloom_diamond_size.value; i++) {
			for(j=0; j<gl_bloom_diamond_size.value; j++) {
				intensity = gl_bloom_intensity.value * 0.5 * Diamond6x[i][j];
				if( intensity < 0.01f ) continue;
				glColor4f( intensity, intensity, intensity, 1.0);
				R_Bloom_SamplePass( i-3, j-3 );
			}
		}
	} else if( gl_bloom_diamond_size.value > 3 ) {

		if( gl_bloom_diamond_size.value != 4 ) Cvar_SetValue("gl_bloom_diamond_size", 4 );

		for(i=0; i<gl_bloom_diamond_size.value; i++) {
			for(j=0; j<gl_bloom_diamond_size.value; j++) {
				intensity = gl_bloom_intensity.value * 0.8f * Diamond4x[i][j];
				if( intensity < 0.01f ) continue;
				glColor4f( intensity, intensity, intensity, 1.0);
				R_Bloom_SamplePass( i-2, j-2 );
			}
		}
	}
	
	glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height);

	//restore full screen workspace
	glViewport( 0, 0, glwidth, glheight );
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity ();
	glOrtho(0, glwidth, glheight, 0, -10, 100);
	glMatrixMode( GL_MODELVIEW );
    glLoadIdentity ();
}											

/*
=================
R_Bloom_DownsampleView
=================
*/
void R_Bloom_DownsampleView( void )
{
	glDisable( GL_BLEND );
	glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );

	//stepped downsample
	if( r_screendownsamplingtexture_size )
	{
		int		midsample_width = r_screendownsamplingtexture_size * sampleText_tcw;
		int		midsample_height = r_screendownsamplingtexture_size * sampleText_tch;
		
		//copy the screen and draw resized
		GL_Bind(gl_bloomscreentexture);
		glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, curView_x, glheight - (curView_y + curView_height), curView_width, curView_height);
		R_Bloom_Quad( 0,  glheight-midsample_height, midsample_width, midsample_height, screenText_tcw, screenText_tch  );
	
		//now copy into Downsampling (mid-sized) texture
		GL_Bind(gl_bloomdownsamplingtexture);
		glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, midsample_width, midsample_height);


		//now draw again in bloom size
		glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
		R_Bloom_Quad( 0,  glheight-sample_height, sample_width, sample_height, sampleText_tcw, sampleText_tch );

		//now blend the big screen texture into the bloom generation space (hoping it adds some blur)
		glEnable( GL_BLEND );
		glBlendFunc(GL_ONE, GL_ONE);
		glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
		GL_Bind(gl_bloomscreentexture);
		R_Bloom_Quad( 0,  glheight-sample_height, sample_width, sample_height, screenText_tcw, screenText_tch );
		glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
		glDisable( GL_BLEND );

	} else {	//downsample simple

		GL_Bind(gl_bloomscreentexture);
		glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, curView_x, glheight - (curView_y + curView_height), curView_width, curView_height);
		R_Bloom_Quad( 0, glheight-sample_height, sample_width, sample_height, screenText_tcw, screenText_tch );
	}
}

/*
=================
R_BloomBlend
=================
*/
void R_BloomBlend (void)//refdef_t *fd, meshlist_t *meshlist )
{
	if(!gl_bloom.value)
		return;

	if( !BLOOM_SIZE || screen_texture_width < glwidth || screen_texture_height < glheight)
		R_Bloom_InitTextures();

	if( screen_texture_width < BLOOM_SIZE || screen_texture_height < BLOOM_SIZE )
		return;

	//set up full screen workspace
	glViewport( 0, 0, glwidth, glheight );
	glDisable( GL_DEPTH_TEST );
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity ();
	glOrtho(0, glwidth, glheight, 0, -10, 100);
	glMatrixMode( GL_MODELVIEW );
    glLoadIdentity ();
	glDisable(GL_CULL_FACE);

	glDisable( GL_BLEND );
	glEnable( GL_TEXTURE_2D );

	glColor4f( 1, 1, 1, 1 );

	//set up current sizes
	curView_x = scr_vrect.x*((float)glwidth/vid.width);
	curView_y = scr_vrect.y*((float)glheight/vid.height);
	curView_width = scr_vrect.width*((float)glwidth/vid.width);
	curView_height = scr_vrect.height*((float)glheight/vid.height);
	screenText_tcw = ((float)curView_width / (float)screen_texture_width);
	screenText_tch = ((float)curView_height / (float)screen_texture_height);
	if( scr_vrect.height > scr_vrect.width ) {
		sampleText_tcw = ((float)scr_vrect.width / (float)scr_vrect.height);
		sampleText_tch = 1.0f;
	} else {
		sampleText_tcw = 1.0f;
		sampleText_tch = ((float)scr_vrect.height / (float)scr_vrect.width);
	}
	sample_width = BLOOM_SIZE * sampleText_tcw;
	sample_height = BLOOM_SIZE * sampleText_tch;

	//copy the screen space we'll use to work into the backup texture
	GL_Bind(gl_bloombackuptexture);
	glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, r_screenbackuptexture_size * sampleText_tcw, r_screenbackuptexture_size * sampleText_tch);
	//create the bloom image
	R_Bloom_DownsampleView();
	R_Bloom_GeneratexDiamonds();

	//restore the screen-backup to the screen
	glDisable(GL_BLEND);
	GL_Bind(gl_bloombackuptexture);
	glColor4f( 1, 1, 1, 1 );
	R_Bloom_Quad( 0, glheight - (r_screenbackuptexture_size * sampleText_tch), r_screenbackuptexture_size * sampleText_tcw, r_screenbackuptexture_size * sampleText_tch, sampleText_tcw, sampleText_tch );

	R_Bloom_DrawEffect();
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

#endif
