#include <gl\glut.h>
#include <gl\glext.h>
#include <stdio.h>
#include <iostream>

#include "PostProcess.h"

#define GLH_EXT_SINGLE_FILE

#include "include\nvparse.h"
#include "include\glh_extensions.h"
#include "include\glh_genext.h"

// GL_ARB_vertex_program functions
extern PFNGLGENPROGRAMSARBPROC					glGenProgramsARB;
extern PFNGLDELETEPROGRAMSARBPROC				glDeleteProgramsARB;
extern PFNGLBINDPROGRAMARBPROC					glBindProgramARB;
extern PFNGLPROGRAMSTRINGARBPROC				glProgramStringARB;
extern PFNGLPROGRAMENVPARAMETER4FVARBPROC		glProgramEnvParameter4fvARB;
extern PFNGLPROGRAMLOCALPARAMETER4FVARBPROC		glProgramLocalParameter4fvARB;

// GL_ARB_multitexture functions
/*extern PFNGLACTIVETEXTUREARBPROC			glActiveTextureARB;
extern PFNGLMULTITEXCOORD2FARBPROC			glMultiTexCoord2fARB;
extern PFNGLCLIENTACTIVETEXTUREARBPROC		glClientActiveTextureARB;*/

// GL_NV_register_combiners functions
extern PFNGLCOMBINERPARAMETERFVNVPROC		glCombinerParameterfvNV;

extern GLuint LoadProgramARB(GLenum target, std::string filename);

GLuint GL_tex_rect = GL_TEXTURE_RECTANGLE_NV;
GLuint sum_combiners, lerp_combiners;
GLuint sum_fp, lerp_fp;

GLuint scene_tex = 0, scene_tex2 = 0;
int win_w, win_h;

#define USE_RC 1

//--------------------------------------------------------------------------------------
// Name: copy_scene_to_texture
// Desc: Copies the screen contents to a texture rectangle. The texture dimensions should
//		 match the screen resolution.
//--------------------------------------------------------------------------------------
void copy_scene_to_texture( GLuint tex )
{
	int viewport[4];
	glGetIntegerv( GL_VIEWPORT, viewport );

	glBindTexture( GL_tex_rect, tex );
	glCopyTexSubImage2D( GL_tex_rect, 0, 0, 0, 0, 0, viewport[2], viewport[3] );
}


//--------------------------------------------------------------------------------------
// Name: init_combiners
// Desc: Initializes register combiners for post processing effects. Taken from the NVIDIA
//		 SDK 9.5 (AntiAliasingWithPostProcessing.cpp).
//--------------------------------------------------------------------------------------
void init_combiners()
{
	int Error;

#if USE_RC
    // sum 4 textures
    sum_combiners = glGenLists(1);
    glNewList(sum_combiners, GL_COMPILE);
    nvparse(   
    "!!RC1.0\n"
    "const0 = ( 0.25, 0.25, 0.25, 1 );\n"
    "{\n"
    "	rgb {\n"
    "		spare0 = tex0 * const0;\n"
    "	}\n"
    "}\n"
    "{\n"
    "	rgb {\n"
    "		discard = tex1 * const0;\n"
    "       discard = spare0;\n"
    "       spare0 = sum();\n"
    "	}\n"
    "}\n"
    "{\n"
    "	rgb {\n"
    "		discard = tex2 * const0;\n"
    "       discard = spare0;\n"
    "       spare0 = sum();\n"
    "	}\n"
    "}\n"
    "{\n"
    "	rgb {\n"
    "		discard = tex3 * const0;\n"
    "       discard = spare0;\n"
    "       spare0 = sum();\n"
    "	}\n"
    "}\n"

    "out.rgb = spare0;\n"
    "out.a = spare0;\n"
    );
    glEndList();
    nvparse_print_errors(stderr);

    // lerp between two textures
    lerp_combiners = glGenLists(1);
    glNewList(lerp_combiners, GL_COMPILE);
    nvparse(   
    "!!RC1.0\n"
    "const0 = ( 0.6, 0.6, 0.6, 0.0 );\n"
    "{\n"
    "	rgb {\n"
//    "		discard = tex0 * unsigned_invert(const0);\n"
//    "		discard = tex1 * const0;\n"
    "		discard = tex0;\n"
    "		discard = tex1;\n"
    "       spare0 = sum();\n"
    "	}\n"
    "}\n"
    "out.rgb = spare0;\n"
    "out.a = spare0;\n"
    );
    glEndList();
    nvparse_print_errors(stderr);
#else

	// Load fragment program from text file
	sum_fp = LoadProgramARB( GL_FRAGMENT_PROGRAM_ARB, "..\\programs\\postprocess_sum_arb.fp" );
	if( !sum_fp ) 
	{
		printf( "ERROR: Error loading postprocess_sum_arb.fp!\n" );
	}

	// Check for errors in the fragment program.  If the program contains errors
	// then we can return the position of the string where the error occured.
	// If there is no error, then -1 will be returned.
	glGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, &Error );

	// If we do find an error, we can print the string containing detailed information
	// about the program's error.
	if( Error != -1 )
	{
		GLubyte* string = (GLubyte*) glGetString( GL_PROGRAM_ERROR_STRING_ARB );
		printf( "Error in postprocess_sum_arb.fp!\n\nPosition: %d\nDetails: %s\n\n", Error, string );
		exit(0);
	}

	lerp_fp = LoadProgramARB( GL_FRAGMENT_PROGRAM_ARB, "..\\programs\\postprocess_lerp_arb.fp" );
	if( !lerp_fp ) 
	{
		printf( "ERROR: Error loading postprocess_lerp_arb.fp!\n" );
	}

	// Check for errors in the fragment program.  If the program contains errors
	// then we can return the position of the string where the error occured.
	// If there is no error, then -1 will be returned.
	glGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, &Error );

	// If we do find an error, we can print the string containing detailed information
	// about the program's error.
	if( Error != -1 )
	{
		GLubyte* string = (GLubyte*) glGetString( GL_PROGRAM_ERROR_STRING_ARB );
		printf( "Error in postprocess_lerp_arb.fp!\n\nPosition: %d\nDetails: %s\n\n", Error, string );
		exit(0);
	}
#endif
}

//--------------------------------------------------------------------------------------
// Name: resize_post_process_textures
// Desc: 
//--------------------------------------------------------------------------------------
int resize_post_process_textures( GLsizei w, GLsizei h )
{
	if( scene_tex )
		glDeleteTextures( 1, &scene_tex );
	if( scene_tex2 )
		glDeleteTextures( 1, &scene_tex2 );

	glGenTextures( 1, &scene_tex );
	glBindTexture( GL_tex_rect, scene_tex );
	glTexParameteri( GL_tex_rect, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_tex_rect, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_tex_rect, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_tex_rect, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexImage2D( GL_tex_rect, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );

	glGenTextures( 1, &scene_tex2 );
	glBindTexture( GL_tex_rect, scene_tex2 );
	glTexParameteri( GL_tex_rect, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_tex_rect, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_tex_rect, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_tex_rect, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexImage2D( GL_tex_rect, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );

	win_w = w;
	win_h = h;

	return 1;
}

//--------------------------------------------------------------------------------------
// Name: init_post_process_effects
// Desc: Initializes resources needed for the post process effects we are going to be 
//		 using for the game.
//--------------------------------------------------------------------------------------
int init_post_process_effects()
{
	int viewport[4];

	// Initialize multitexturing extensions
	if( !glh_init_extensions( "GL_ARB_multitexture" ) )
	{
		printf( "ERROR: glh_init_extensions(): Error initializing GL_ARB_multitexture." );
		exit(0);
	}

	// Initialize register combiners
	init_combiners();

	// Initialize scene textures
	glGetIntegerv( GL_VIEWPORT, viewport );
	resize_post_process_textures( viewport[2], viewport[3] );

	return 1;
}

//--------------------------------------------------------------------------------------
// Name: uninit_post_process_effects
// Desc: 
//--------------------------------------------------------------------------------------
void uninit_post_process_effects()
{
	if( sum_fp ) glDeleteProgramsARB( 1, &sum_fp );
	if( lerp_fp ) glDeleteProgramsARB( 1, &lerp_fp );

	if( sum_combiners ) glDeleteLists( sum_combiners, 1 );
	if( lerp_combiners ) glDeleteLists( lerp_combiners, 1 );

	if( scene_tex ) glDeleteTextures( 1, &scene_tex );
	if( scene_tex2 ) glDeleteTextures( 1, &scene_tex2 );
}

//--------------------------------------------------------------------------------------
// Name: texCoord2f
// Desc: Sets texture coordinates for the first 4 texture units.
//--------------------------------------------------------------------------------------
void texCoord2f(float s, float t)
{
	int i;
    // send texture coordinate to 4 texture units
    for(i=0; i<4; i++) {
        glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, s, t);
    }
}

//--------------------------------------------------------------------------------------
// Name: draw_quad
// Desc: Draws a quad, assuming there's a texture rectangle attached.
//--------------------------------------------------------------------------------------
void draw_quad(int tw, int th)
{
    glBegin(GL_QUADS);
    texCoord2f(0.0, 0.0); glVertex2f(0.0, 0.0);
    texCoord2f(tw, 0.0); glVertex2f(1.0, 0.0);
    texCoord2f(tw, th); glVertex2f(1.0, 1.0);
    texCoord2f(0.0, th); glVertex2f(0.0, 1.0);
    glEnd();
}

//--------------------------------------------------------------------------------------
// Name: deaw_fullscreen_quad
// Desc: Draws a 2D quad.
//--------------------------------------------------------------------------------------
void draw_fullscreen_quad()
{
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    draw_quad(win_w, win_h);

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
}

//--------------------------------------------------------------------------------------
// Name: display_scene_texture_blur
// Desc: Blurs the scene texture.
//--------------------------------------------------------------------------------------
void display_scene_texture_blur(float scale)
{
	int i;
	float constant[4] = { 0.25, 0.25, 0.25, 1.0 };

    // texture coordinate offsets to perform 3x3 Gaussian blur using 4 bilinear samples
    float offset[4][2] = {
        -0.5, -0.5,
        0.5, -0.5,
        0.5, 0.5,
        -0.5, 0.5,
    };

    for(i=0; i<4; i++) 
	{
        glActiveTextureARB(GL_TEXTURE0_ARB + i);
        glBindTexture(GL_TEXTURE_RECTANGLE_NV, scene_tex);
        glEnable(GL_tex_rect);

        glMatrixMode(GL_TEXTURE);
        glLoadIdentity();
        // scale distance between samples for each pass
        glTranslatef(offset[i][0]*scale, offset[i][1]*scale, 0.0);
    }

    glDisable(GL_DEPTH_TEST);
#if USE_RC
    glCallList(sum_combiners);
    glEnable(GL_REGISTER_COMBINERS_NV);
#else
	glEnable( GL_FRAGMENT_PROGRAM_ARB );
	glBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, sum_fp );
	glProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, constant );
#endif

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    draw_fullscreen_quad();

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);

	/*glGetFloatv( GL_VIEWPORT, viewport );

	glEnable2D();

	glBegin(GL_QUADS);
    texCoord2f(0.0, 0.0);			glVertex2f(0.0, 0.0);
    texCoord2f(viewport[2], 0.0);   glVertex2f(viewport[2], 0.0);
    texCoord2f(viewport[2], viewport[3]); glVertex2f(viewport[2], viewport[3]);
    texCoord2f(0.0, viewport[3]);	glVertex2f(0.0, viewport[3]);
    glEnd();

	glDisable2D();*/

    for(i=0; i<4; i++) 
	{
        glActiveTextureARB(GL_TEXTURE0_ARB + i);
        glDisable(GL_tex_rect);
        glMatrixMode(GL_TEXTURE);
        glLoadIdentity();
    }
    glEnable(GL_DEPTH_TEST);
#if USE_RC
    glDisable(GL_REGISTER_COMBINERS_NV);
#else
	glDisable( GL_FRAGMENT_PROGRAM_ARB );
#endif
}

//--------------------------------------------------------------------------------------
// Name: display_scene_texture_combined
// Desc: Combines the previously blured texture with the other unmodified copy.
//--------------------------------------------------------------------------------------
void display_scene_texture_combined()
{
	float lerp = 0.6f;
	float lerp_col[] = { lerp, lerp, lerp, 0.0 };

    glActiveTextureARB(GL_TEXTURE0_ARB);
    glBindTexture(GL_TEXTURE_RECTANGLE_NV, scene_tex2);
    glEnable(GL_TEXTURE_RECTANGLE_NV);

    glActiveTextureARB(GL_TEXTURE1_ARB);
    glBindTexture(GL_TEXTURE_RECTANGLE_NV, scene_tex);
    glEnable(GL_TEXTURE_RECTANGLE_NV);

#if USE_RC
    glCallList(lerp_combiners);
    glEnable(GL_REGISTER_COMBINERS_NV);
    glCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV, (GLfloat *) lerp_col);
#else
	glEnable( GL_FRAGMENT_PROGRAM_ARB );
	glBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, lerp_fp );
	glProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, lerp_col );
#endif
    glDisable(GL_DEPTH_TEST);

	draw_fullscreen_quad();

    /*glGetFloatv( GL_VIEWPORT, viewport );

	glEnable2D();

	glBegin(GL_QUADS);
    texCoord2f(0.0, 0.0);			glVertex2f(0.0, 0.0);
    texCoord2f(viewport[2], 0.0);   glVertex2f(viewport[2], 0.0);
    texCoord2f(viewport[2], viewport[3]); glVertex2f(viewport[2], viewport[3]);
    texCoord2f(0.0, viewport[3]);	glVertex2f(0.0, viewport[3]);
    glEnd();

	glDisable2D();*/

    glEnable(GL_DEPTH_TEST);
#if USE_RC
    glDisable(GL_REGISTER_COMBINERS_NV);
#else
	glDisable( GL_FRAGMENT_PROGRAM_ARB );
#endif

    glActiveTextureARB(GL_TEXTURE0_ARB);
    glDisable(GL_TEXTURE_RECTANGLE_NV);

    glActiveTextureARB(GL_TEXTURE1_ARB);
    glDisable(GL_TEXTURE_RECTANGLE_NV);
}

//--------------------------------------------------------------------------------------
// Name: post_process_blur
// Desc: Creates a blur effect on the entire scene based on the number of desired passes.
//--------------------------------------------------------------------------------------
void post_process_blur( int passes )
{
	float i;

	// Copy the scene to a texture
	copy_scene_to_texture( scene_tex2 );

	 // blur scene image
    for( i = 0; i < passes; i++ ) 
	{
        copy_scene_to_texture( scene_tex );
        display_scene_texture_blur( i + 1 );
    }

    // display original and blurred image combined
    display_scene_texture_combined();
	
	// Ensure that texture unit 0 is active
	glActiveTextureARB( GL_TEXTURE0_ARB );
}