#include "../Framework/Framework.h"
#include "../Framework/Util/FogVolume.h"
#include "../Framework/Util/T3dLoader.h"
#include "../Framework/Util/Particles.h"
#include "FoggedPolygon.h"

#include "../Framework/Util/DemoLooper.h"
#include "../Framework/Math/PerlinNoise.h"


//#define NO_MUSIC


#ifndef NO_MUSIC
#include "../Framework/Audio/Audio.h"
#endif

class MainApp : public OpenGLApp {
protected:
	TextureID dirt, dirt2, lavaDirt, lava, wall, particle;

	TextureID distanceMap;
	TextureID cubeMap;
    MaterialTable matTable;

	Set <class Polygon *> polys;
	int firstPolygons;

	Set <FogVolume *> fogVolumes;
	Set <ParticleSystem *> particleSystems;

	DemoLooper demoLooper;
//	bool demoMode;
#ifndef NO_MUSIC
	Audio *audio;
	SoundID music;
#endif
	void drawWater();
	void drawWorld();

public:
	void selectPixelFormat(PixelFormat &pixelFormat);

	bool init();
	bool exit();
	bool load();
	bool unload();
	bool drawFrame();
	
//	void setKey(UINT key, bool value);
//	void resetCamera();
};

/*void MainApp::setKey(UINT key, bool value){
	RenderApp::setKey(key, value);
	
	if (value){
		if (key == demoModeKey){
            if (demoMode){
                demoMode = false;
            } else {
                if (demoLooper.getNumberOfNodes() > 0){
                    demoMode = true;
					currTime = (float) (2 * demoLooper.getNumberOfNodes()) - 8;
                }
            }
		}

		switch(key){
        case 'O':
            demoLooper.loadFromFile("path.pth");
            break;
        case 'S':
            demoLooper.writeToFile("path.pth");
            break;
		case 'C':
			demoLooper.clear();
			break;
        case VK_INSERT:
            demoLooper.addNode(position, Wx, Wy, Wz);
			break;
		case VK_BACK:
			demoLooper.removeLastNode();
            break;
		}		
	}	
}



void MainApp::resetCamera(){
	position = Vertex(2500, 500, 10);
	Wx = 0;
	Wy = PI / 2;
	Wz = 0;
}
*/

void MainApp::selectPixelFormat(PixelFormat &pixelFormat){
	pixelFormat.redBits   = 8;
	pixelFormat.greenBits = 8;
	pixelFormat.blueBits  = 8;
	pixelFormat.alphaBits = 8;

	pixelFormat.depthBits = 24;
}

bool polygonFilter(const int flags){
	return ((flags & PF_Invisible) == 0) && ((flags & PF_TwoSided) == 0) && (flags >= 0);
}

bool MainApp::init(){

	initPerlin();

	projection.loadProjectionMatrix(90 * PI / 180, 0.75f, 1, 14000);

	fogVolumes.add(new SphereFogVolume(Vertex(-1500,0,1500), 2000, Color(1, 0.2f, 0), 12000));
	fogVolumes.add(new CubeFogVolume(Vertex(-4000, -128, -4000), Vertex(4000, 400, 4000), Color(1, 0.2f, 0), 12000));
	fogVolumes.add(new CubeFogVolume(Vertex(-3700, 1000, -256), Vertex(-3300, 4000, 256), Color(1, 1, 1), 12000));

	fogVolumes.add(new SphereFogVolume(Vertex(-2000,3800,0), 1000, Color(0.5f, 1, 1), 5000));

	ParticleSystem *particleSystem = new ParticleSystem();
	particleSystem->setSpawnRate(20);
	particleSystem->setSpeed(500, 500, 0.3f, 0.3f);
	particleSystem->setLife(1.3f, 0);
	particleSystem->setDirectionalForce(Vertex(0,-10,0));
	particleSystem->setFrictionFactor(0.7f);
	particleSystem->setPosition(Vertex(-1220, 400, 1140));
	particleSystem->setSize(200, 50);
	particleSystems.add(particleSystem);

	particleSystem = new ParticleSystem();
	particleSystem->setSpawnRate(30);
	particleSystem->setSpeed(150, 20, 0.3f, 0.3f);
	particleSystem->setLife(2.0f, 0);
	particleSystem->setDirectionalForce(Vertex(0,-10,0));
	particleSystem->setFrictionFactor(0.7f);
	particleSystem->setPosition(Vertex(1920, 640, 160));
	particleSystem->setSize(30, 10);
	particleSystems.add(particleSystem);

	particleSystem = new ParticleSystem();
	particleSystem->setSpawnRate(30);
	particleSystem->setSpeed(150, 20, 0.3f, 0.3f);
	particleSystem->setLife(2.0f, 0);
	particleSystem->setDirectionalForce(Vertex(0,-10,0));
	particleSystem->setFrictionFactor(0.7f);
	particleSystem->setPosition(Vertex(780, 930, -60));
	particleSystem->setSize(30, 10);
	particleSystems.add(particleSystem);

	particleSystem = new ParticleSystem();
	particleSystem->setSpawnRate(30);
	particleSystem->setSpeed(100, 20, 0.3f, 0.3f);
	particleSystem->setLife(1.0f, 0);
	particleSystem->setDirectionalForce(Vertex(0,-10,0));
	particleSystem->setFrictionFactor(0.7f);
	particleSystem->setPosition(Vertex(3930, 420, -325));
	particleSystem->setSize(15, 8);
	particleSystems.add(particleSystem);

	particleSystem = new ParticleSystem();
	particleSystem->setColorScheme(COLOR_SCHEME_SMOKE);
	particleSystem->setSpawnRate(20);
	particleSystem->setSpeed(150, 20, 0.3f, 0.3f);
	particleSystem->setLife(2.0f, 0);
	particleSystem->setDirectionalForce(Vertex(-100,-10,0));
	particleSystem->setFrictionFactor(0.7f);
	particleSystem->setPosition(Vertex(1912, 450, 897));
	particleSystem->setSize(20, 5);
	particleSystems.add(particleSystem);

	particleSystem = new ParticleSystem();
	particleSystem->setSpawnRate(30);
	particleSystem->setSpeed(150, 20, 0.3f, 0.3f);
	particleSystem->setLife(1.0f, 0);
	particleSystem->setDirectionalForce(Vertex(0,-10,0));
	particleSystem->setFrictionFactor(0.7f);
	particleSystem->setPosition(Vertex(-2880, 770, -190));
	particleSystem->setSize(20, 10);
	particleSystems.add(particleSystem);

	particleSystem = new ParticleSystem();
	particleSystem->setSpawnRate(30);
	particleSystem->setSpeed(150, 20, 0.3f, 0.3f);
	particleSystem->setLife(1.0f, 0);
	particleSystem->setDirectionalForce(Vertex(0,-10,0));
	particleSystem->setFrictionFactor(0.7f);
	particleSystem->setPosition(Vertex(-2880, 770, 190));
	particleSystem->setSize(20, 10);
	particleSystems.add(particleSystem);

	particleSystem = new ParticleSystem();
	particleSystem->setColorScheme(COLOR_SCHEME_SMOKE);
	particleSystem->setSpawnRate(50);
	particleSystem->setSpeed(350, 20, 0.3f, 0.3f);
	particleSystem->setLife(7.0f, 0);
	particleSystem->setDirectionalForce(Vertex(0,-100,-40));
	particleSystem->setFrictionFactor(0.7f);
	particleSystem->setPosition(Vertex(-2000, 3200, 0));
	particleSystem->setSize(40, 20);
	particleSystems.add(particleSystem);

	//demoMode = true;
	demoLooper.loadFromFile("Path.pth");
	demoLooper.setClampMode(true);

#ifndef NO_MUSIC

	audio = new Audio();


//	music = audio->addSound("Music.ogg", SND_STREAM | SND_LOOPING);
	music = audio->addSound("Music.ogg");
#endif

	return true;
}

bool MainApp::exit(){
#ifndef NO_MUSIC
	delete audio;
#endif
	unsigned int i;

	for (i = 0; i < fogVolumes.getSize(); i++){
		delete fogVolumes[i];
	}
	fogVolumes.clear();

	for (i = 0; i < particleSystems.getSize(); i++){
		delete particleSystems[i];
	}
	particleSystems.clear();

	return true;
}

bool MainApp::load(){

	String supported, missing;

    if (GL_ARB_texture_env_combine) supported += "GL_ARB_texture_env_combine\n"; else missing += "GL_ARB_texture_env_combine\n";
    if (GL_EXT_texture3D)           supported += "GL_EXT_texture3D\n";           else missing += "GL_EXT_texture3D\n";
    if (GL_EXT_texture_edge_clamp)  supported += "GL_EXT_texture_edge_clamp\n";  else missing += "GL_EXT_texture_edge_clamp\n";
    if (GL_EXT_blend_subtract)      supported += "GL_EXT_blend_subtract\n";      else missing += "GL_EXT_blend_subtract\n";

    if (missing.getLength() > 0){
        ALERT("The following required OpenGL extensions are supported:\n\n" + supported +
				   "\nToo bad, these required OpenGL extensions aren't supported by the graphic card and driver you're using:\n\n" + missing +
                   "\nThis application will now exit.", "Missing extensions");
        return false;
    }



	distanceMap = renderer->addDistanceMap(64, TEX_NOMIPMAPPING);

	dirt     = renderer->addTexture("../Textures/Dirt2a.dds");
	dirt2    = renderer->addTexture("../Textures/Dirt_su.dds");
	lavaDirt = renderer->addTexture("../Textures/Lava-Dirt.dds");
	lava     = renderer->addTexture("../Textures/lava.dds");
	wall     = renderer->addTexture("../Textures/CasWAL.dds");

	particle = renderer->addTexture("../Textures/Particle.png");

	cubeMap  = renderer->addCubemap(
		"../Textures/CubeMaps/Sky/sky_negx.jpg",
		"../Textures/CubeMaps/Sky/sky_posx.jpg",
		"../Textures/CubeMaps/Sky/sky_negy.png",
		"../Textures/CubeMaps/Sky/sky_posy.jpg",
		"../Textures/CubeMaps/Sky/sky_negz.jpg",
		"../Textures/CubeMaps/Sky/sky_posz.jpg"
		);


	textTexture = renderer->addTexture("../Textures/font2.dds");



	Material *mat;

	mat = new Material();
	mat->setTextures(dirt2);
    matTable.addMaterialName("Dirt_su", 256, 256, mat, 0);

	mat = new Material();
	mat->setTextures(dirt);
    matTable.addMaterialName("Dirt2a", 256, 256, mat, 0);

	mat = new Material();
	mat->setTextures(lavaDirt);
    matTable.addMaterialName("Lava-Dirt", 256, 256, mat, 0);

	mat = new Material();
	mat->setTextures(lava);
    matTable.addMaterialName("WLAVA", 256, 256, mat, 0);

	mat = new Material();
	mat->setTextures(wall);
    matTable.addMaterialName("CasWAL", 256, 256, mat, 0);


	T3dLoader t3dLoader;
	t3dLoader.loadFromFile("NaliLord.t3d", polys, matTable, &foggedPolyCreator, &polygonFilter, Vertex(0,0,0));

	firstPolygons = polys.getSize();

	t3dLoader.loadFromFile("Sky.t3d", polys, matTable, &foggedPolyCreator, &polygonFilter, Vertex(-2000,3500,0));

#ifndef NO_MUSIC
    audio->playSound(music);
#endif

	return true;
}

bool MainApp::unload(){
	int i, len = polys.getSize();

	for (i = 0; i < len; i++) delete polys[i];

	polys.clear();
	matTable.clear();
	return true;
}

#define WATER_SCALE      8.0f
#define WAVE_SCALE       0.0524f
#define WATER_SIZE       128
#define WATER_HALF_SIZE (WATER_SIZE >> 1)

struct WaterVertex {
	Vertex pos;
	Vertex reflectionVector;
};

void MainApp::drawWater(){
	WaterVertex *dest;
	float x,z;
	int i,j;

	static WaterVertex waterVertices[WATER_SIZE][WATER_SIZE];
	int indices[2 * WATER_SIZE];

	static bool first = true;
	if (first){
		dest = waterVertices[0];
		for (z = -WATER_HALF_SIZE; z < WATER_HALF_SIZE; z++){
			for (x = -WATER_HALF_SIZE; x < WATER_HALF_SIZE; x++){
				dest->pos.x = (x + 0.5f) * WATER_SCALE - 2000;
				dest->pos.z = (z + 0.5f) * WATER_SCALE;
				dest++;
			}
		}

		first = false;
	}

	float time = currTime * 0.3f;

	dest = waterVertices[0];
	for (z = -WATER_HALF_SIZE; z < WATER_HALF_SIZE; z++){
		for (x = -WATER_HALF_SIZE; x < WATER_HALF_SIZE; x++){
			dest->pos.y = 3800 + 0.5f * (1.0f + noise3(0.25f * x * WAVE_SCALE, 17 * time * WAVE_SCALE, (z + 24 * time) * WAVE_SCALE));
			dest++;
		}
	}


	#define succ(x) (x + 1) - (x == WATER_SIZE - 1)
	#define pred(x) (x - 1) + (x == 0)

	// Apply Sobel filter to find mesh normals
	Vertex normal, eyeVector;
	dest = waterVertices[0];
	for (j = 0; j < WATER_SIZE; j++){
		for (i = 0; i < WATER_SIZE; i++){
			normal.x =
				     waterVertices[pred(j)][pred(i)].pos.y  -
				     waterVertices[pred(j)][succ(i)].pos.y  +
				2 * (waterVertices[     j ][pred(i)].pos.y  -
				     waterVertices[     j ][succ(i)].pos.y) +
					 waterVertices[succ(j)][pred(i)].pos.y  -
					 waterVertices[succ(j)][succ(i)].pos.y;

			normal.y = 1.0f;

			normal.z =      
				     waterVertices[pred(j)][pred(i)].pos.y  -
				     waterVertices[succ(j)][pred(i)].pos.y  +
				2 * (waterVertices[pred(j)][     i ].pos.y  -
				     waterVertices[succ(j)][     i ].pos.y) +
					 waterVertices[pred(j)][succ(i)].pos.y  -
					 waterVertices[succ(j)][succ(i)].pos.y;

			normal.fastNormalize();

			eyeVector = position - dest->pos;
			eyeVector.fastNormalize();

			dest->reflectionVector = 2 * (eyeVector * normal) * normal - eyeVector;

			dest++;
		}
	}

	for (i = 0; i < WATER_SIZE; i++){
		indices[2 * i    ] = i;
		indices[2 * i + 1] = i + WATER_SIZE;
	}

	renderer->setTextures(cubeMap);
	renderer->setBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	renderer->apply();

	glColor4f(1,1,1, 0.7f);

	glVertexPointer(3, GL_FLOAT, sizeof(WaterVertex), waterVertices);
	glEnableClientState(GL_VERTEX_ARRAY);

	glClientActiveTextureARB(GL_TEXTURE0_ARB);
	glTexCoordPointer(3, GL_FLOAT, sizeof(WaterVertex), ((char *) waterVertices) + sizeof(Vertex));
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	for (i = 0; i < WATER_SIZE - 1; i++){
		glDrawElements(GL_TRIANGLE_STRIP, WATER_SIZE * 2, GL_UNSIGNED_INT, indices);

		for (j = 0; j < WATER_SIZE * 2; j++) indices[j] += WATER_SIZE;
	}

	glClientActiveTextureARB(GL_TEXTURE0_ARB);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	glDisableClientState(GL_VERTEX_ARRAY);
}

void MainApp::drawWorld(){
	int i, j;

	int startPoly, stopPoly;
	int startFog, stopFog;
	int startParticle, stopParticle;

	if (currTime < 68){
		startFog = 0;
		stopFog  = 2;
		startPoly = 0;
		stopPoly  = firstPolygons;
		startParticle = 0;
		stopParticle  = 7;
	} else if (currTime < 80){
		startFog = 2;
		stopFog  = 3;
		startPoly = 0;
		stopPoly  = firstPolygons;
		startParticle = 5;
		stopParticle  = 7;
	} else {
		startFog = 3;
		stopFog  = fogVolumes.getSize();
		startPoly = firstPolygons;
		stopPoly  = polys.getSize();
		startParticle = 7;
		stopParticle  = particleSystems.getSize();
	}

	for (j = startPoly; j < stopPoly; j++){
		((FoggedPolygon *) polys[j])->draw(renderer, position, fogVolumes[startFog]->getWorldSize());
	}

	glDepthMask(GL_FALSE);
	for (i = startFog; i < stopFog; i++){
		glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
		if (i != startFog){
			glCullFace(GL_BACK);
			for (j = startPoly; j < stopPoly; j++){
				((FoggedPolygon *) polys[j])->drawFoggedOnly(renderer, position, fogVolumes[i]->getWorldSize());
			}
		}

		glCullFace(GL_FRONT);
		renderer->setTextures(distanceMap);
		renderer->setTexEnvs(TEXENV_REPLACE);
		renderer->apply();
		fogVolumes[i]->draw(position);

		glCullFace(GL_BACK);
		renderer->setBlending(GL_ONE, GL_ONE, GL_FUNC_REVERSE_SUBTRACT_EXT);
		renderer->setTextures(distanceMap);
		renderer->setTexEnvs(TEXENV_REPLACE);
		renderer->apply();
		fogVolumes[i]->draw(position);

		if (fogVolumes[i]->isInVolume(position)){
			glDisable(GL_DEPTH_TEST);
			glCullFace(GL_FRONT);
		} else {
			glCullFace(GL_BACK);
		}

		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

		renderer->setBlending(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA);
		renderer->apply();

		glColor3fv(fogVolumes[i]->getColor());
		fogVolumes[i]->draw(position);

		glEnable(GL_DEPTH_TEST);
	}

	glCullFace(GL_BACK);

	Vertex dx(modelView[0], modelView[4], modelView[8]);
	Vertex dy(modelView[1], modelView[5], modelView[9]);

	renderer->setTextures(particle);
	renderer->setBlending(GL_ONE, GL_ONE);
	renderer->apply();

	for (i = startParticle; i < stopParticle; i++){
		particleSystems[i]->update(currTime);
		particleSystems[i]->draw(dx, dy);
	}
	glDepthMask(GL_TRUE);


	if (currTime > 80) drawWater();
}


bool MainApp::drawFrame(){
#ifndef NO_MUSIC
	audio->updateSoundStream(music);
#endif
	//if (demoMode){
		LoopNode node =	demoLooper.getNode(currTime / 2);

		position = Vertex(node.x, node.y, node.z);
		Wx = node.Wx;
		Wy = node.Wy;
		Wz = node.Wz;
	//}

	if (currTime > 20) particleSystems[0]->setSpawnRate(20); else particleSystems[0]->setSpawnRate(0);

//	if (keys['1']) Wz -= 0.01f;
//	if (keys['2']) Wz += 0.01f;
//	if (keys['A']) currTime = 80;

	glClear(((currTime < 107)? 0 : GL_COLOR_BUFFER_BIT) | GL_DEPTH_BUFFER_BIT);
	glDepthFunc(GL_LEQUAL);
	glEnable(GL_DEPTH_TEST);

	glMatrixMode(GL_PROJECTION);
	glLoadMatrixf(projection);

	modelView.loadRotateZXY(-Wx, -Wy, -Wz);
    glMatrixMode(GL_MODELVIEW);
	glLoadMatrixf(modelView);

	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	if (currTime < 107){

		if (currTime > 80){
			renderer->setTextures(cubeMap);
			renderer->setTexEnvs(TEXENV_REPLACE);
			renderer->apply();

			glBegin(GL_TRIANGLE_STRIP);
				glTexCoord3f(-1, -1,  -1);
				glVertex3f  (-6144, -6144, -6144);
				glTexCoord3f(-1,  1,  -1);
				glVertex3f  (-6144,  6144, -6144);

				glTexCoord3f(-1, -1,   1);
				glVertex3f  (-6144, -6144,  6144);
				glTexCoord3f(-1,  1,   1);
				glVertex3f  (-6144,  6144,  6144);

				glTexCoord3f( 1, -1,   1);
				glVertex3f  ( 6144, -6144,  6144);
				glTexCoord3f( 1,  1,   1);
				glVertex3f  ( 6144,  6144,  6144);

				glTexCoord3f( 1, -1,  -1);
				glVertex3f  ( 6144, -6144, -6144);
				glTexCoord3f( 1,  1,  -1);
				glVertex3f  ( 6144,  6144, -6144);
			glEnd();
			glBegin(GL_TRIANGLE_STRIP);
				glTexCoord3f(-1, -1,   1);
				glVertex3f  (-6144, -6144,  6144);
				glTexCoord3f( 1, -1,   1);
				glVertex3f  ( 6144, -6144,  6144);

				glTexCoord3f(-1, -1,  -1);
				glVertex3f  (-6144, -6144, -6144);
				glTexCoord3f( 1, -1,  -1);
				glVertex3f  ( 6144, -6144, -6144);

				glTexCoord3f(-1,  1,  -1);
				glVertex3f  (-6144,  6144, -6144);
				glTexCoord3f( 1,  1,  -1);
				glVertex3f  ( 6144,  6144, -6144);

				glTexCoord3f(-1,  1,   1);
				glVertex3f  (-6144,  6144,  6144);
				glTexCoord3f( 1,  1,   1);
				glVertex3f  ( 6144,  6144,  6144);
			glEnd();
		}

		modelView.translate(-position);
		glLoadMatrixf(modelView);
	/*
		CString str;
		str.Format("CurrTime: %.1f\nTime: %d", currTime, 2 * demoLooper.getNumberOfNodes());
		setDebugString(str);
	*/
		drawWorld();
	}

/*	glDisable(GL_CULL_FACE);
	glDisable(GL_DEPTH_TEST);

	glColor3f(1,1,1);

		renderer->setBlending(GL_DST_ALPHA, GL_ZERO);
		renderer->apply();

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(0,1,1,0,1,-1);

		glBegin(GL_QUADS);
			glVertex2f(0.2f,0.2f);
			glVertex2f(0.8f,0.2f);
			glVertex2f(0.8f,0.8f);
			glVertex2f(0.2f,0.8f);
		glEnd();
*/



	if (currTime > 79 && currTime < 81 || currTime > 106){
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(0,1,1,0,1,-1);

		float f;
		if (currTime > 106){
			f = 1.5f * (1.0f - fabsf(currTime - 107));
		} else {
			f = 1.5f * (1 - fabsf(currTime - 80));
		}
		glColor3f(f,f,f);
		if (currTime < 107) renderer->setBlending(GL_ONE, GL_ONE);
		renderer->apply();

		glBegin(GL_QUADS);
			glVertex2f(0,0);
			glVertex2f(1,0);
			glVertex2f(1,1);
			glVertex2f(0,1);
		glEnd();

		if (currTime > 107){
			renderer->setTextures(textTexture);
			renderer->setTexEnvs(TEXENV_REPLACE);
			renderer->setBlending(SRC_ALPHA, ONE_MINUS_SRC_ALPHA);
			renderer->apply();
			drawText("Credits", 0.15f, 0.2f, 0.1f, 0.1f);
			drawText("Progamming: Humus\n\nMusic: The sins of thy beloved -\n       The mournful euphony", 0.03f, 0.5f, 0.03f, 0.05f);

			if (currTime > 112){
				currTime = 0;
#ifndef NO_MUSIC
			    audio->playSound(music);
#endif
			}
		}
	}


	return true;
}

DECLARE_MAIN_APP(MainApp)
