#ifdef USE_TEMPORAL
#include <random>
#include <chrono>
//#include <dirent.h>
#include <string>

#include "OglRenderer.h"

uint seed = chrono::system_clock::now( ).time_since_epoch( ).count( );
default_random_engine gen( seed );
uniform_real_distribution<double> dist( 0.0, 1.0 );

void OglRenderer::updateBuffers(uint wi, uint he) {
	vector<OglTexTarget*> textars;
	for (uint i = 0; i<2; i++) {
		textars.clear();
		texColor[i].intFormat = GL_RGBA;
		texColor[i].format = GL_RGBA;
		texColor[i].minFilter = GL_LINEAR;
		texColor[i].magFilter = GL_LINEAR;
		texColor[i].clampBorder = true;

		texNormal[i].intFormat = GL_RGBA16F;
		texNormal[i].format = GL_RGBA;
		texNormal[i].minFilter = GL_LINEAR;
		texNormal[i].magFilter = GL_LINEAR;

		texVelocity[i].intFormat = GL_RGBA16F;
		texVelocity[i].format = GL_RGBA;
		texVelocity[i].type = GL_FLOAT;
		texVelocity[i].minFilter = GL_LINEAR;
		texVelocity[i].magFilter = GL_LINEAR;

		textars.push_back(&texColor[i]);
		textars.push_back(&texNormal[i]);
		textars.push_back(&texVelocity[i]);
		GBuffer0[i].init(textars, DEPTHBUF_TEXTURE, wi, he);
	}

	textars.clear();
	textars.push_back(&texColor2);
	textars.push_back(&texNormal2);
	textars.push_back(&texVelocity2);
	GBuffer[0].init(textars, DEPTHBUF_TEXTURE, wi, he, 2);
	GBuffer[1].init(textars, DEPTHBUF_TEXTURE, wi, he, 2, false); //extra depth buffer
	gbId = 0;

	textars.clear();
	DirLBuffer.init(textars, DEPTHBUF_TEXTURE | DEPTHBUF_LINEAR | DEPTHBUF_SHADOW, 1024, 1024);

	texNum = 2;
	cnt = 0;

	for (int i = 0; i < texNum; i++)
	{
		textars.clear();
		texAA[i].intFormat = GL_RGB16;
		texAA[i].format = GL_RGB;
		texAA[i].magFilter = GL_LINEAR;
		texAA[i].minFilter = GL_LINEAR;
		texAA[i].clampBorder = true;
		textars.push_back(&texAA[i]);
		AABuffer[i].init(textars, 0, wi, he);
	}
	AAp = 0;

	textars.clear();
	texAATemp.intFormat = GL_RGBA;
	texAATemp.format = GL_RGBA;
	texAATemp.magFilter = GL_LINEAR;
	texAATemp.minFilter = GL_LINEAR;
	texAATemp.clampBorder = true;
	textars.push_back(&texAATemp);
	AATemp.init(textars, 0, wi, he);

	textars.clear();
	texAO.intFormat = GL_RG32F;
	texAO.format = GL_RG;
	//texAO.type = GL_FLOAT;
	texAO.magFilter = GL_LINEAR;
	texAO.minFilter = GL_LINEAR;
	texAO.clampBorder = true;
	textars.push_back(&texAO);
	AOBuffer.init(textars, 0, wi, he);

	imgNow = 0;
	imgTex.format = GL_RGBA;
	imgTex.intFormat =  GL_RGBA16F;
	imgTex.minFilter = GL_LINEAR;
	imgTex.magFilter = GL_LINEAR;
	imgVar[0].format = imgVar[1].format = GL_RGBA;
	imgVar[0].intFormat = imgVar[1].intFormat = GL_RGBA16F;
	imgVar[0].minFilter = imgVar[1].minFilter = GL_LINEAR;
	imgVar[0].magFilter = imgVar[0].minFilter = GL_LINEAR;

	imgTex.init(wi, he, 0);
	imgVar[0].init(wi, he, 1);
	imgVar[1].init(wi, he, 2);

	for (uint i = 0; i < WAVELEVELS; i++) {
		textars.clear();
		waveletTex[i].intFormat = GL_RGBA16F;
		waveletTex[i].format = GL_RGBA;
		waveletTex[i].clampBorder = true;
		waveletTex[i].minFilter = GL_LINEAR;
		waveletTex[i].magFilter = GL_LINEAR;
		textars.push_back(&waveletTex[i]);
		waveletBuffer[i].init(textars, 0, wi, he);
	}

	textars.clear();
	texVar.intFormat = GL_RGBA16F;
	texVar.format = GL_RGBA;
	texVar.clampBorder = true;
	texVar.minFilter = GL_LINEAR;
	texVar.magFilter = GL_LINEAR;
	textars.push_back(&texVar);
	varBuffer.init(textars, 0, wi, he);
}

void OglRenderer::init(uint wi,uint he, string shaderDir)
{
	first = true;
	updateBuffers(wi,he);

	createRandomTexture();

	DIR *d;
	struct dirent *dir;
	d = opendir(shaderDir.c_str());
	int i = 0;
	if ( d )
	{
		while ( (dir = readdir( d )) != NULL )
		{
			if ( i > 1 )
			{
				string name = dir->d_name;
				string subname = name.substr( name.length() - 4, 4 );
				if ( subname == ".vsh" )
				{
					cout << "vertex: " << name << endl;
					string compileName = "shaders\\" + name;
					OglVertexShader vsh; vsh.compile( compileName.c_str() );
					vsh.notDelete();
					shVertex[name] = vsh;
				}
				else if ( subname == ".fsh" )
				{
					cout << "fragment: " << name << endl;
					string compileName = "shaders\\" + name;
					OglFragmentShader fsh; fsh.compile( compileName.c_str( ) );
					fsh.notDelete();
					shFragment[name] = fsh;
				}
				else if (subname == ".gsh") {
					cout << "geometry: " << name << endl;
					string compileName = "shaders\\" + name;
					OglGeometryShader gsh; gsh.compile(compileName.c_str());
					gsh.notDelete();
					shGeometry[name] = gsh;
				}
				else if (subname == ".csh") {
					cout << "compute: " << name << endl;
					string compileName = "shaders\\" + name;
					OglComputeShader csh; csh.compile(compileName.c_str());
					csh.notDelete();
					shCompute[name] = csh;
				}
				else
					cout << "error reading shader " << name << endl;
			}
			i++;
		}

		closedir( d );
	}

	//buildShader( "GBuff", "GBuff.vsh", "GBuff.fsh" );
	//buildShader( "GBuffAA", "GBuffAA.vsh", "GBuffAA.fsh" );
	//buildShaderG("GBuffAAG", "GBuffAA.vsh", "GBuff.gsh", "GBuffAAG.fsh");
	//
	//buildShader( "AmbQ", "Quad.vsh", "AmbQ.fsh" );
	//buildShader( "AmbDirQ", "Quad.vsh", "AmbDirQ.fsh" );
	//buildShader( "AmbDirShadowQ", "QuadInv.vsh", "AmbDirShadowQ.fsh" );
	//buildShader( "AmbDirShadowQG", "QuadInv.vsh", "AmbDirShadowQG.fsh");

	buildShader( "Quad", "Quad.vsh", "Quad.fsh" );
	buildShader( "QuadA", "Quad.vsh", "QuadAlpha.fsh");
	buildShader( "QuadAA", "Quad.vsh", "QuadAA.fsh" );
	buildShader( "TempAA", "Quad.vsh", "TempAA.fsh");
	buildShader( "varBlur", "Quad.vsh", "varBlur.fsh");
	//buildShader( "QuadAAG", "Quad.vsh", "QuadAAG.fsh");

	//buildShader( "Blur", "Quad.vsh", "Blur.fsh" );
	//buildShader( "ShadowMap", "ShadowMap.vsh", "ShadowMap.fsh" );
	//buildShader( "EVSMGauss", "Quad.vsh", "EVSMGauss.fsh" );
	//buildShader( "EVSMGaussV", "Quad.vsh", "EVSMGaussV.fsh" );

	//buildShader( "AO", "QuadInv.vsh", "AO.fsh" );
	//buildShader( "AOG", "QuadInv.vsh", "AOG.fsh");

	//buildShaderC("Ray", "Ray.csh");
	buildShader( "WaveletBlur", "Quad.vsh", "WaveletBlur.fsh");

	activeShader = 0;

	//quad.toBuffs( bufQuad );
	//bufQuad.postInit( );

	lastWiggle = glm::vec3( 0, 0, 0 );
	wp = 0;
	rx[0] = 0.16; ry[0] = 0.35;
	rx[1] = 0.82; ry[1] = 0.20;
	rx[2] = 0.40; ry[2] = 0.88;
	rx[3] = 0.84; ry[3] = 0.69;

	enableBuffer( &AABuffer[0], true );
	enableBuffer( &AABuffer[1], true );
}

void OglRenderer::createRandomTexture( )
{
	vector<uchar> data;
	for ( uint i = 0; i < 64 * 64; i++ )
	{
		/*uchar r = dist( gen )*256;
		GLfloat angle = dist( gen ) * 2 * PI;
		uchar g = cos( angle )*128+128;
		uchar b = sin( angle )*128+128;*/
		uchar r = dist( gen ) * 256;
		uchar g = dist( gen ) * 256;
		uchar b = dist( gen ) * 256;

		data.push_back( r );
		data.push_back( g );
		data.push_back( b );
	}

	randomTex.intFormat = GL_RGB;
	randomTex.format = GL_RGB;
	randomTex.clampBorder = false;
	randomTex.magFilter = GL_LINEAR;
	randomTex.minFilter = GL_LINEAR;
	randomTex.init( &data[0], 64, 64 );
}

void OglRenderer::buildShader( string strProg, string strVertex, string strFragment )
{
	OglShaderProg prog;
	prog.link( shVertex[strVertex], shFragment[strFragment] );
	shProg[strProg] = prog;
}
void OglRenderer::buildShaderG(string strProg, string strVertex, string strGeom, string strFragment)
{
	OglShaderProg prog;
	prog.linkG(shVertex[strVertex], shGeometry[strGeom], shFragment[strFragment]);
	shProg[strProg] = prog;
}
void OglRenderer::buildShaderC(string strProg, string strCompute) {
	OglShaderProg prog;
	prog.linkC(shCompute[strCompute]);
	shProg[strProg] = prog;
}

void OglRenderer::enableBuffer( OglFrameBuffer *buffer, bool clear )
{
	if ( buffer )
		buffer->enable();
	else
		glBindFramebuffer( GL_FRAMEBUFFER, 0 );
	if (clear)
		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}






void OglRenderer::GBufferP( OglFrameBuffer *buffer, uint wi, uint he )
{
	enableBuffer(buffer);

	activeShader = &shProg["GBuff"];
	activeShader->enable( );
}
void OglRenderer::GBufferAA( OglFrameBuffer *buffer, uint wi, uint he )
{
	enableBuffer( buffer );

	glm::vec3 wiggle = glm::vec3( (rx[wp]*0 + dist( gen )*1 - 0.5) * 1.5f / wi, (ry[wp]*0 + dist( gen )*1 - 0.5) * 1.5f / he, 0 );
	//wiggle = glm::vec3(0);
	activeShader = &shProg["GBuffAA"];
	activeShader->enable( );
	activeShader->importValue( "wiggle", wiggle );
	activeShader->importValue( "lastWiggle", lastWiggle );
	lastWiggle = wiggle;

	wp++;
	wp %= 4;
}
void OglRenderer::GBufferAAG(OglFrameBuffer *buffer, CamData camData, uint wi, uint he)
{
	enableBuffer(buffer);

	glm::vec3 wiggle = glm::vec3((rx[wp] * 0 + dist(gen) * 1 - 0.5) * 1.5f / wi, (ry[wp] * 0 + dist(gen) * 1 - 0.5) * 1.5f / he, 0);
	activeShader = &shProg["GBuffAAG"];
	activeShader->enable();
	activeShader->importValue("wiggle", wiggle);
	activeShader->importValue("lastWiggle", lastWiggle);
	activeShader->importValue("zClip", camData.zClip);
	activeShader->importValue("camPos", camData.pos);
	activeShader->importValueTex2DArray("depthTex", &GBuffer[1-gbId].depthTexture);
	lastWiggle = wiggle;

	wp++;
	wp %= 4;
}







void OglRenderer::Ambient( OglFrameBuffer *buffer, glm::vec3 ambient, uint wi, uint he )
{
	enableBuffer( buffer );
	activeShader = &shProg["AmbQ"];

	activeShader->enable( );
	activeShader->importValue( "colorTexture", &texColor[imgNow] );
	activeShader->importValue( "ambient", ambient );
	//bufQuad.render();
}
void OglRenderer::AmbDir( OglFrameBuffer *buffer, glm::vec3 ambient, LightData lDirData, CamData camData, uint wi, uint he, bool geom )
{
	enableBuffer( &AOBuffer );
	glViewport( 0, 0, AOBuffer.wi, AOBuffer.he );
	static float iRand = 1;

	if (geom)
		activeShader = &shProg["AOG"];
	else
		activeShader = &shProg["AO"];
	activeShader->enable( );

	if (geom) {
		activeShader->importValueTex2DArray("normalTexture", &texNormal2);
		activeShader->importValueTex2DArray("depthTexture", &GBuffer[gbId].depthTexture);
	}
	else {
		activeShader->importValue("normalTexture", &texNormal[imgNow]);
	}
	activeShader->importValue("randomTexture", &randomTex);

	activeShader->importValue( "pixel", glm::vec2( 1.0f / wi, 1.0f / he ) );
	activeShader->importValue( "MVP", camData.matrix );
	activeShader->importValue( "iRand", iRand );

	activeShader->importValue( "camPos", camData.pos );
	activeShader->importValue( "zClip", camData.zClip );
	activeShader->importValue( "zClipRatio", camData.zClipRatio );
	activeShader->importValue( "invMat", camData.invMatrix );

	//bufQuad.render( );


	enableBuffer( buffer );
	glViewport( 0, 0, wi, he );

	if (lDirData.shadow) {
		if (geom)
			activeShader = &shProg["AmbDirShadowQG"];
		else
			activeShader = &shProg["AmbDirShadowQ"];
	}
	else
		activeShader = &shProg["AmbDirQ"];

	activeShader->enable();
	if (geom) {
		activeShader->importValueTex2DArray("colorTexture", &texColor2);
		activeShader->importValueTex2DArray("normalTexture", &texNormal2);
		activeShader->importValueTex2DArray("depthTexture", &GBuffer[gbId].depthTexture);
	}
	else {
		activeShader->importValue("colorTexture", &texColor[imgNow]);
		activeShader->importValue("normalTexture", &texNormal[imgNow]);
		activeShader->importValue("depthTexture", &GBuffer0[imgNow].depthTexture);
	}
	activeShader->importValue( "aoTexture", &texAO );

	activeShader->importValue( "ambient", ambient );
	activeShader->importValue( "dirL", lDirData.pos );
	activeShader->importValue( "dirColor", lDirData.color );
	activeShader->importValue( "pixel", glm::vec2( 1.0f / wi, 1.0f / he) );
	activeShader->importValue( "iRand", iRand );
	activeShader->importValue( "MVP", camData.matrix );

	if ( lDirData.shadow )
	{
		activeShader->importValue( "camPos", camData.pos );
		activeShader->importValue( "zClip", camData.zClip );
		activeShader->importValue( "zClipRatio", camData.zClipRatio );
		activeShader->importValue( "invMat", camData.invMatrix );

		activeShader->importValue( "lDirMVP", lDirData.MVP );
		activeShader->importValue( "lDirDepthTexture", /*&texDirLRange[1]*/ &DirLBuffer.depthTexture );
		activeShader->importValue( "lDirPixel", glm::vec2( 1.0f / 1024, 1.0f / 1024 ) );
		activeShader->importValue( "randomTexture", &randomTex );
	}

	iRand+=1;
	if ( iRand > INT_MAX-1 ) iRand = 1;
	//bufQuad.render( );

	if (geom)
		gbId = !gbId;
}











void OglRenderer::Quad( OglFrameBuffer *buffer, uint wi, uint he )
{
	enableBuffer( buffer );
	activeShader = &shProg["Quad"];

	activeShader->enable( );
	activeShader->importValue( "tex", &texAA[AAp] /*&DirLBuffer.depthTexture*/ );
	//bufQuad.render( );

	AAp++;
	AAp %= texNum;
}
void OglRenderer::QuadAA( OglFrameBuffer *buffer, uint wi, uint he, bool geom )
{
	static float iRand = 0;
	enableBuffer( buffer );
	activeShader = &shProg[geom? "QuadAAG" :"QuadAA"];

	activeShader->enable( );
	activeShader->importValue( "tex", &texAATemp );
	activeShader->importValue( "prev0", &texAA[(AAp - 1) % texNum] );
	if (geom)
		activeShader->importValueTex2DArray("velTexture", &texVelocity2);
	else
		activeShader->importValue("velTexture", &texVelocity[imgNow]);
	activeShader->importValue( "iRand", iRand );
	activeShader->importValue( "wiggle", lastWiggle );

	activeShader->importValue( "pixel", glm::vec3( 1.0f / wi, 1.0f / he, 0 ) );
	//bufQuad.render( );

	iRand++;
	if ( iRand == 2 ) iRand = 0;
	//AAp++;
	//AAp %= texNum;
}
void OglRenderer::QuadSrc(OglFrameBuffer *buffer, uint wi, uint he, OglTex *src) {
	enableBuffer(buffer);
	activeShader = &shProg["Quad"];

	activeShader->enable();
	activeShader->importValue("tex", src);
	activeShader->importValue("leaveProb", leaveProb);
	//bufQuad.render();
}
void OglRenderer::QuadA(OglFrameBuffer *buffer, uint wi, uint he, OglTex *src) {
	enableBuffer(buffer);
	activeShader = &shProg["QuadA"];

	activeShader->enable();
	activeShader->importValue("tex", src);
	//bufQuad.render();
}
void OglRenderer::varBlur(OglFrameBuffer *buffer, uint wi, uint he, OglTex *src) {
	enableBuffer(buffer);
	activeShader = &shProg["varBlur"];

	activeShader->enable();
	activeShader->importValue("tex", &imgTex);
	activeShader->importValue("varTex", src);
	activeShader->importValue("res", glm::vec2(wi,he));
	activeShader->importValue("time", (float)dist(gen));
	activeShader->importValue("leaveProb", leaveProb);
	//bufQuad.render();
}
void OglRenderer::TempAA(OglFrameBuffer *buffer, uint wi, uint he, OglTex *src, OglTex *prevSrc, glm::vec2 res) {
	enableBuffer(buffer);
	activeShader = &shProg["TempAA"];

	activeShader->enable();
	activeShader->importValue("tex", src);
	activeShader->importValue("prevTex", prevSrc);
	activeShader->importValue("velTex", &texVelocity[imgNow]);
	activeShader->importValue("varTex", &texVar);
	activeShader->importValue("res", res);
	activeShader->importValue("time", (float) dist(gen));
	activeShader->importValue("leaveProb", leaveProb);
	//bufQuad.render();
}
void OglRenderer::WaveletBlur(OglFrameBuffer *buffer, uint wi, uint he, OglTex *src, OglTex *srcNormalDepth, glm::vec2 res, float iter) {
	enableBuffer(buffer);
	activeShader = &shProg["WaveletBlur"];

	activeShader->enable();
	activeShader->importValue("tex", src);
	activeShader->importValue("firstVarTex", &texVar);
	activeShader->importValue("texNormalDepth", &texNormal[imgNow]);
	activeShader->importValue("varTex", &imgVar[imgNow]);
	activeShader->importValue("res", res);
	activeShader->importValue("iter", (float)pow(2,iter));
	activeShader->importValue("time", (float)dist(gen));
	activeShader->importValue("randomTexture", &randomTex);
	//bufQuad.render();
}











void OglRenderer::EVSMGauss( OglFrameBuffer *buffer, uint wi, uint he )
{
	enableBuffer( buffer );
	activeShader = &shProg["EVSMGauss"];
	glViewport( 0, 0, 128, 128);

	activeShader->enable( );
	activeShader->importValue( "tex", &DirLBuffer.depthTexture );

	activeShader->importValue( "pixel", glm::vec2( 1.0f / 128, 1.0f / 128 ) );
	//bufQuad.render( );

	glViewport( 0, 0, wi, he );
}
void OglRenderer::EVSMGaussV( OglFrameBuffer *buffer, uint wi, uint he )
{
	enableBuffer( buffer );
	activeShader = &shProg["EVSMGaussV"];
	glViewport( 0, 0, 128, 128 );

	activeShader->enable( );
	activeShader->importValue( "tex", &texDirLRange[0] );

	activeShader->importValue( "pixel", glm::vec2( 1.0f / 128, 1.0f / 128 ) );
	//bufQuad.render( );

	glViewport( 0, 0, wi, he );
}
#endif