#include "Demo.h"
#include "Texture.h"
#include <stdio.h>
#include <stdlib.h>
#include <GL/glew.h>
#include <math.h>

using namespace cg;

Demo::Demo(char *configname, int w, int h)
{
	GLenum err = glewInit();
	if (err != GLEW_OK) {
		//I have no idea what shoud I do, because it is a constructor
		printf("OpenGL init failed\n");
		return;
	}
	float aspect = 1920.0f / 1080.0f;
	height = (float)w / aspect;
	starty = (h - height) / 2;
	width = w;

	glViewport(0, starty, width, height);
	//width = w;
	//height = h;

	//GLfloat coords[] = { -7.0, -4.0, 13.0, -4.0, 13.0, 16.0, -7.0, 16.0};
	//GLfloat coords[] = { 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 };
	GLfloat fscoords[]  = {-1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0}; //full screen coordinates
	GLfloat coords[]    = { 0.0f, 0.0f, 0.3f, 0.0f, 0.3f, 0.3f, 0.0f, 0.3f };
	GLfloat textcoord[] = { 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 };
	GLfloat shadowcoo[] = { 0.0, 0.0,
		                    0.3, 0.0,
		                    0.3, -1.0,
		                    0.0, -1.0};

	glGenVertexArrays(1, &pointsprite);
	glBindVertexArray(pointsprite);

	GLfloat *spritecoords;
	spritecoords = (GLfloat*)malloc(POINTNUM * sizeof(GL_FLOAT));
	glGenBuffers(1, &pointsprites_vba);
	glBindBuffer(GL_ARRAY_BUFFER, pointsprites_vba);
	glBufferData(GL_ARRAY_BUFFER, POINTNUM * sizeof(GL_FLOAT), spritecoords, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
	glEnableVertexAttribArray(0);
	free(spritecoords);

	glGenVertexArrays(1, (GLuint*)&vao);
	glBindVertexArray(vao);

	glGenBuffers(4, (GLuint*)&vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), coords, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
	glEnableVertexAttribArray(0);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), textcoord, GL_STATIC_DRAW);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, NULL);
	glEnableVertexAttribArray(1);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
	glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), fscoords, GL_STATIC_DRAW);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, NULL);
	glEnableVertexAttribArray(2);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);
	glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), shadowcoo, GL_STATIC_DRAW);
	glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 0, NULL);
	glEnableVertexAttribArray(3);
	
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_PROGRAM_POINT_SIZE);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glGenTextures(74, walk);
	for (int i = 0; i < 74; i++) {
		char filename[22];
		sprintf_s(filename, "data\\walk\\out%d.tga", i);
		Texture::loadTGA(filename, walk[i]);
	}

	glGenTextures(1, &deserttex);
	Texture::loadTGA("data\\textures\\desert.tga", deserttex);

	glGenTextures(1, &mountaintex);
	Texture::loadTGA("data\\textures\\mountain.tga", mountaintex);

	glGenTextures(1, &desertbrtex);
	Texture::loadTGA("data\\textures\\desertbright.tga", desertbrtex);

	glGenTextures(1, &stargroundtex);
	Texture::loadTGA("data\\textures\\starnight.tga", stargroundtex);

	glGenTextures(1, &starskytex);
	Texture::loadTGA("data\\textures\\stars.tga", starskytex);

	glGenTextures(1, &meteorgroundtex);
	Texture::loadTGA("data\\textures\\meteor_ground.tga", meteorgroundtex);

	glGenTextures(1, &meteorskysmalltex);
	Texture::loadTGA("data\\textures\\meteor_sky_small.tga", meteorskysmalltex);

	glGenTextures(1, &meteorskybigtex);
	Texture::loadTGA("data\\textures\\meteor_sky_big.tga", meteorskybigtex);

	glGenTextures(1, &credittex);
	Texture::loadTGA("data\\textures\\credits.tga", credittex);

	glGenTextures(1, &titletex);
	Texture::loadTGA("data\\textures\\title.tga", titletex);

	defaultprg = new ShaderProgram();
	defaultprg->addShader("data\\shaders\\default.vs", GL_VERTEX_SHADER);
	defaultprg->addShader("data\\shaders\\default.fs", GL_FRAGMENT_SHADER);
	defaultprg->compile();

	movingtxtprg = new ShaderProgram();
	movingtxtprg->addShader("data\\shaders\\moving.vs", GL_VERTEX_SHADER);
	movingtxtprg->addShader("data\\shaders\\default.fs", GL_FRAGMENT_SHADER);
	movingtxtprg->compile();

	spriteprg = new ShaderProgram();
	spriteprg->addShader("data\\shaders\\sprite.vs", GL_VERTEX_SHADER);
	spriteprg->addShader("data\\shaders\\sprite.fs", GL_FRAGMENT_SHADER);
	spriteprg->compile();

	textsprite = new ShaderProgram();
	textsprite->addShader("data\\shaders\\textsprite.vs", GL_VERTEX_SHADER);
	textsprite->addShader("data\\shaders\\textsprite.fs", GL_FRAGMENT_SHADER);
	textsprite->compile();

	desertshadow = new ShaderProgram();
	desertshadow->addShader("data\\shaders\\dshadow.vs", GL_VERTEX_SHADER);
	desertshadow->addShader("data\\shaders\\default.fs", GL_FRAGMENT_SHADER);
	desertshadow->compile();

	backgroundprg = new ShaderProgram();
	backgroundprg->addShader("data\\shaders\\background.vs", GL_VERTEX_SHADER);
	backgroundprg->addShader("data\\shaders\\background.fs", GL_FRAGMENT_SHADER);
	backgroundprg->compile();

	heatprg = new ShaderProgram();
	heatprg->addShader("data\\shaders\\heatshock.fs", GL_FRAGMENT_SHADER);
	heatprg->addShader("data\\shaders\\background.vs", GL_VERTEX_SHADER);
	heatprg->compile();

	postprocessing = new ShaderProgram();
	postprocessing->addShader("data\\shaders\\background.vs", GL_VERTEX_SHADER);
	postprocessing->addShader("data\\shaders\\postprocess.fs", GL_FRAGMENT_SHADER);
	postprocessing->compile();

	rotateprg = new ShaderProgram();
	rotateprg->addShader("data\\shaders\\background.vs", GL_VERTEX_SHADER);
	rotateprg->addShader("data\\shaders\\rotate.fs", GL_FRAGMENT_SHADER);
	rotateprg->compile();

	meteorprg = new ShaderProgram();
	meteorprg->addShader("data\\shaders\\background.vs", GL_VERTEX_SHADER);
	meteorprg->addShader("data\\shaders\\meteor.fs", GL_FRAGMENT_SHADER);
	meteorprg->compile();

	framebuffer = new Renderbuffer(width, height, starty, 3, false);
	if (framebuffer->getError()) {
		printf("Renderbuffer inicialization error\n");
	}
	framebuffer->addShader(postprocessing);
	framebuffer->setVertexArray(vao);
}


Demo::~Demo()
{
	delete defaultprg;
	delete movingtxtprg;
	delete spriteprg;
	delete rotateprg;
	delete backgroundprg;
	delete meteorprg;
	delete textsprite;
	delete desertshadow;
	delete postprocessing;
	delete heatprg;

	delete framebuffer;
}

void Demo::readConfig(char * filename)
{
	FILE *f;

	f = fopen(filename, "r");
	fclose(f);
}

void Demo::update(double time)
{
	float fade = 0.0f;
	int ks = 9;
	float vignette = 0.8;
	int avg = 0;
	float zoom = 1.0f;

	framebuffer->firstPass();
	framebuffer->setKernelSize(9);

	if (time > 0 && time < 64.3) {
		DesertScene(time);
		avg = 1;
	}
	//64.3 65.53 black screen
	else if (time > 65.53   && time < 70.0) drawBackground(titletex, 0, 9);
	else if (time > 70.0    && time < 94.524) Scene4(time - 70.0);
	else if (time > 94.524  && time < 123.679) {
		MeteorScene(time - 94.524);
		ks = sin(time * 10.0)*4.0 + 4;
		vignette = 0.8f + sin(time*40.0) / 10.0f;
	}
	else if (time > 123.679 && time < 152.981) {
		MountainScene(time - 123.679);
		zoom = (sin(time) * 0.2 + 0.8);
	}
	else if (time > 152.981 && time < 182.000) NightScene(time - 152.981);
	else if (time > 182.000) EndCredit();

	if (time > 58.0   && time < 64.3) fade = ((float)time - 58.0f) / (64.3f - 58.0f);

	framebuffer->setZoom( zoom );
	framebuffer->setAvg(avg);
	framebuffer->setKernelSize(ks);
	framebuffer->setFade(fade);
	framebuffer->setVignette(vignette);
	framebuffer->secondPass();
}

void Demo::Scene4(double time)
{
	glEnable(GL_POINT_SPRITE);
	glPointSize(width / 192);
	glUseProgram(spriteprg->getProgramID());
	glActiveTexture(GL_TEXTURE0);
	int index = (int)(time * 30.0) % 74;
	glBindTexture(GL_TEXTURE_2D, index);
	GLint loc = glGetUniformLocation(spriteprg->getProgramID(), "img");
	glUniform1i(loc, 0);
	loc = glGetUniformLocation(spriteprg->getProgramID(), "time");
	glUniform1f(loc, time);
	glBindVertexArray(pointsprite);
	glDrawArrays(GL_POINTS, 0, POINTNUM / 2);
	glDisable(GL_POINT_SPRITE);
}

void Demo::Scene5(double time)
{
	glUseProgram(textsprite->getProgramID());
	glActiveTexture(GL_TEXTURE0);
	int index = (int)(time * 30.0) % 74;
	glBindTexture(GL_TEXTURE_2D, walk[index]);
	GLint loc = glGetUniformLocation(textsprite->getProgramID(), "img");
	glUniform1i(loc, 0);

	loc = glGetUniformLocation(textsprite->getProgramID(), "time");
	glUniform1f(loc, time);

	glBindVertexArray(pointsprite);
	glDrawArrays(GL_POINTS, 0, POINTNUM / 2);
}

void Demo::drawBackground(GLuint texture, GLuint brighttex, int ks) {
	GLint loc;

	glUseProgram(backgroundprg->getProgramID());

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture);
	loc = glGetUniformLocation(backgroundprg->getProgramID(), "img");
	glUniform1i(loc, 0);
	
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, brighttex);
	loc = glGetUniformLocation(backgroundprg->getProgramID(), "bright");
	glUniform1i(loc, 1);
	
	loc = glGetUniformLocation(backgroundprg->getProgramID(), "width");
	glUniform1i(loc, width);

	loc = glGetUniformLocation(backgroundprg->getProgramID(), "kernelsize");
	glUniform1i(loc, ks);
	
	glBindVertexArray(vao);
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

void Demo::heatBackground(GLuint texture, GLuint brighttex, double time) {
	GLint loc;

	glUseProgram(heatprg->getProgramID());

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture);
	loc = glGetUniformLocation(heatprg->getProgramID(), "img");
	glUniform1i(loc, 0);

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, brighttex);
	loc = glGetUniformLocation(heatprg->getProgramID(), "bright");
	glUniform1i(loc, 1);

	loc = glGetUniformLocation(heatprg->getProgramID(), "width");
	glUniform1i(loc, width);

	loc = glGetUniformLocation(heatprg->getProgramID(), "time");
	glUniform1f(loc, time);

	glBindVertexArray(vao);
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

void Demo::drawMan(GLuint prg, int index, float x, float y, float r, float g, float b) {
	GLint loc;

	glUseProgram(prg);

	glActiveTexture(GL_TEXTURE0);
	
	glBindTexture(GL_TEXTURE_2D, walk[index]);
	loc = glGetUniformLocation(prg, "img");
	glUniform1i(loc, 0);
	loc = glGetUniformLocation(prg, "offset");
	glUniform2f(loc, x, y);
	loc = glGetUniformLocation(prg, "color");
	glUniform3f(loc, r, g, b);
	glBindVertexArray(vao);
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

void Demo::MountainScene(double time) {
	static int previndex;
	static float x = 0.8f;
	float y;

	drawBackground(mountaintex, 0, 9);

	int index = (int)(time * 30) % 74;
	if (index == 0 && previndex == 73) {
		x -= STEPDIST;
	}
	y = -0.7 + time / 100.0;
	drawMan(movingtxtprg->getProgramID(), index, x, y, 0.0, 0.0, 0.0);
	previndex = index;
}

void Demo::DesertScene(double time) {
	static int previndex;
	static float x = 0.8;
	GLint loc;

	heatBackground(deserttex, desertbrtex, time);

	// Draw character
	int index = (int)(time * 23) % 74;
	if (index == 0 && previndex == 73 || index < previndex) {
		x -= STEPDIST;
	}
	drawMan(movingtxtprg->getProgramID(), index, x, -0.28, 0.0, 0.0, 0.0);

	// Draw shadow
	drawMan(desertshadow->getProgramID(), index, x, -0.13, 0.0, 0.0, 0.0);
	
	previndex = index;
}

void Demo::rotateLayer(double time) {
	GLint loc;

	glUseProgram(rotateprg->getProgramID());
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, starskytex);
	loc = glGetUniformLocation(rotateprg->getProgramID(), "rotimg");
	glUniform1i(loc, 0);

	loc = glGetUniformLocation(rotateprg->getProgramID(), "time");
	glUniform1f(loc, time);

	loc = glGetUniformLocation(rotateprg->getProgramID(), "startrail");
	if (time < 10.0) {
		glUniform1f(loc, 1.0);
	}
	else {
		glUniform1f(loc, 1.0 - (time - 10.0) / 10.0);
	}

	glBindVertexArray(vao);
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

void Demo::NightScene(double time) {
	static float x = 0.6;
	static int previndex;

	rotateLayer(time);
	drawBackground(stargroundtex, 0, 9);

	int index = (int)(time * 30) % 74;
	if (index == 0 && previndex == 73) {
		x -= STEPDIST;
	}
	drawMan(movingtxtprg->getProgramID(), index, x, -0.42, 1.0, 1.0, 1.0);

	previndex = index;
}

void Demo::drawMeteorLayer(GLuint smalltex, GLuint bigtex, double time) {
	GLuint loc;

	glUseProgram(meteorprg->getProgramID());

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, smalltex);
	loc = glGetUniformLocation(meteorprg->getProgramID(), "smallimg");
	glUniform1i(loc, 0);

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, bigtex);
	loc = glGetUniformLocation(meteorprg->getProgramID(), "bigimg");
	glUniform1i(loc, 1);

	loc = glGetUniformLocation(meteorprg->getProgramID(), "time");
	glUniform1f(loc, time);

	glBindVertexArray(vao);
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

void Demo::MeteorScene(double time) {
	static float x = 0.9;
	static int previndex;

	int index = (int)(time * 30) % 74;
	if (index == 0 && previndex == 73) {
		x -= STEPDIST;
	}

	drawMeteorLayer(meteorskysmalltex, meteorskybigtex, time);
	drawBackground(meteorgroundtex, meteorgroundtex, 9);
	drawMan(movingtxtprg->getProgramID(), index, x, -0.48, 0.0, sin(time), 0.0);
	
	previndex = index;
}

void Demo::EndCredit() {
	drawBackground(credittex, 0, 9);
}