/***********      .---.         .-"-.      *******************\
* -------- *     /   ._.       / ´ ` \     * ---------------- *
* Author's *     \_  (__\      \_°v°_/     * humus@rogers.com *
*   note   *     //   \\       //   \\     * ICQ #47010716    *
* -------- *    ((     ))     ((     ))    * ---------------- *
*          ****--""---""-------""---""--****                  ********\
* This file is a part of the work done by Humus. You are free to use  *
* the code in any way you like, modified, unmodified or copy'n'pasted *
* into your own work. However, I expect you to respect these points:  *
*  @ If you use this file and its contents unmodified, or use a major *
*    part of this file, please credit the author and leave this note. *
*  @ For use in anything commercial, please request my approval.      *
*  @ Share your work and ideas too as much as you can.                *
\*********************************************************************/

#include "App.h"

BaseApp *app = new App();


void App::resetCamera(){
	camPos = vec3(180, -50, -850);
	wx = 0;
	wy = 0.8f;
}

void App::moveCamera(const vec3 &dir){
	vec3 newPos = camPos + dir * (speed * frameTime);

	vec3 point;
	const BTri *tri;
	if (bsp.intersects(camPos, newPos, &point, &tri)){
		newPos = point + tri->plane.xyz();
	}
	bsp.pushSphere(newPos, 30);

	camPos = newPos;
}

bool App::init(){
	map = new Model();
	if (!map->loadObj("../Models/Room6/Map.obj")){
		delete map;
		return false;
	}

	Stream stream = map->getStream(map->findStream(TYPE_VERTEX));
	vec3 *vertices = (vec3 *) stream.vertices;
	uint *indices = stream.indices;
	for (uint i = 0; i < map->getIndexCount(); i += 3){
		bsp.addTriangle(vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]);
	}
	bsp.build();

	map->computeTangentSpace(true);
	map->cleanUp();
	map->changeAllGeneric(true);


	int tab = configDialog->addTab("Rendering");
	configDialog->addWidget(tab, useBranching  = new CheckBox(0,   0, 200, 36, "Use branching",   true));
	configDialog->addWidget(tab, useSinglePass = new CheckBox(0,  40, 200, 36, "Use single pass", true));
	configDialog->addWidget(tab, useShadows    = new CheckBox(0,  80, 200, 36, "Use shadows",     true));
	configDialog->addWidget(tab, animateLights = new CheckBox(0, 140, 200, 36, "Animate lights",  true));

	return true;
}

void App::exit(){
	delete map;
}

bool App::load(){
	if (!GLSL_supported){
		ErrorMsg("No GLSL support");
		return false;
	}

	if (!GL_EXT_framebuffer_object_supported){
		ErrorMsg("No FBO support");
		return false;
	}

	if (!map->makeDrawable(renderer)) return false;

	// Samplerstates
	if ((trilinearAniso = renderer->addSamplerState(TRILINEAR_ANISO, WRAP, WRAP, WRAP)) == SS_NONE) return false;


	// FBO
	int fboDepthBits = (strstr((const char *) glGetString(GL_VENDOR), "NVIDIA") == NULL)? 16 : 24;

	if ((shadowMap = renderer->addRenderTarget(256, 256, FORMAT_RGBA8, linearClamp, CUBEMAP)) == TEXTURE_NONE) return false;
	if ((shadowMapDepth = renderer->addRenderDepth(256, 256, fboDepthBits)) == TEXTURE_NONE) return false;


	// Shaders
	const char *attribs[] = { NULL, "textureCoord", "tangent", "binormal", "normal" };
	if ((preZSP = renderer->addShader("preZ.shd")) == SHADER_NONE) return false;
	if ((preZMP = renderer->addShader("preZ.shd", attribs, elementsOf(attribs), "#define MULTIPASS\n")) == SHADER_NONE) return false;
	if ((plainTex = renderer->addShader("plainTex.shd", attribs, elementsOf(attribs))) == SHADER_NONE) return false;

	if ((lightingSP[0][0] = renderer->addShader("lightingSP.shd", attribs, elementsOf(attribs), DEFINE_STR(LIGHT_COUNT))) == SHADER_NONE) return false;
	if ((lightingSP[0][1] = renderer->addShader("lightingSP.shd", attribs, elementsOf(attribs), "#define BRANCHING\n" DEFINE_STR(LIGHT_COUNT))) == SHADER_NONE) return false;
	if ((lightingSP[1][0] = renderer->addShader("lightingSP.shd", attribs, elementsOf(attribs), "#define SHADOWS\n" DEFINE_STR(LIGHT_COUNT))) == SHADER_NONE) return false;
	if ((lightingSP[1][1] = renderer->addShader("lightingSP.shd", attribs, elementsOf(attribs), "#define SHADOWS\n#define BRANCHING\n" DEFINE_STR(LIGHT_COUNT))) == SHADER_NONE) return false;

	if ((lightingMP[0][0] = renderer->addShader("lightingMP.shd", attribs, elementsOf(attribs))) == SHADER_NONE) return false;
	if ((lightingMP[0][1] = renderer->addShader("lightingMP.shd", attribs, elementsOf(attribs), "#define BRANCHING\n")) == SHADER_NONE) return false;
	if ((lightingMP[1][0] = renderer->addShader("lightingMP.shd", attribs, elementsOf(attribs), "#define SHADOWS\n")) == SHADER_NONE) return false;
	if ((lightingMP[1][1] = renderer->addShader("lightingMP.shd", attribs, elementsOf(attribs), "#define SHADOWS\n#define BRANCHING\n")) == SHADER_NONE) return false;

	if ((depth = renderer->addShader("depth.shd")) == SHADER_NONE) return false;

	// Textures
	if ((base[0] = renderer->addTexture  ("../Textures/floor_wood_3.dds",                   true, trilinearAniso)) == SHADER_NONE) return false;
	if ((bump[0] = renderer->addNormalMap("../Textures/floor_wood_3Bump.dds", FORMAT_RGBA8, true, trilinearAniso)) == SHADER_NONE) return false;
	parallax[0] = 0.0f;

	if ((base[1] = renderer->addTexture  ("../Textures/brick01.dds",                   true, trilinearAniso)) == SHADER_NONE) return false;
	if ((bump[1] = renderer->addNormalMap("../Textures/brick01Bump.dds", FORMAT_RGBA8, true, trilinearAniso)) == SHADER_NONE) return false;
	parallax[1] = 0.04f;

	if ((base[2] = renderer->addTexture  ("../Textures/stone08.dds",                   true, trilinearAniso)) == SHADER_NONE) return false;
	if ((bump[2] = renderer->addNormalMap("../Textures/stone08Bump.dds", FORMAT_RGBA8, true, trilinearAniso)) == SHADER_NONE) return false;
	parallax[2] = 0.0f;

	if ((base[3] = renderer->addTexture  ("../Textures/StoneWall_1-4.dds",                   true, trilinearAniso)) == SHADER_NONE) return false;
	if ((bump[3] = renderer->addNormalMap("../Textures/StoneWall_1-4Bump.dds", FORMAT_RGBA8, true, trilinearAniso)) == SHADER_NONE) return false;
	parallax[3] = 0.03f;

	if ((light = renderer->addTexture("../Textures/spot.dds", false, linearClamp)) == SHADER_NONE) return false;

	// Blendstates
	if ((blendAdd = renderer->addBlendState(ONE, ONE)) == BS_NONE) return false;
	if ((noColorWrite = renderer->addBlendState(ONE, ZERO, BM_ADD, NONE)) == BS_NONE) return false;

	for (int i = 0; i < LIGHT_COUNT; i++){
		if ((colorMasks[i] = renderer->addBlendState(ONE, ZERO, BM_ADD, 1 << i)) == BS_NONE) return false;
	}

	return true;
}

void App::unload(){
	
}

void App::drawLights(const vec3 *lightPos, const vec3 *color, const vec3 &dx, const vec3 &dy){
	renderer->reset();
	renderer->setShader(plainTex);
	renderer->setBlendState(blendAdd);
	renderer->setTexture("Base", light);
	renderer->setDepthState(noDepthWrite);
	renderer->apply();

	glBegin(GL_QUADS);
	for (uint i = 0; i < LIGHT_COUNT; i++){
		glColor3fv(color[i]);

		glTexCoord2f(0, 0);
		glVertex3fv(lightPos[i] - 25 * dx + 25 * dy);

		glTexCoord2f(1, 0);
		glVertex3fv(lightPos[i] + 25 * dx + 25 * dy);

		glTexCoord2f(1, 1);
		glVertex3fv(lightPos[i] + 25 * dx - 25 * dy);

		glTexCoord2f(0, 1);
		glVertex3fv(lightPos[i] - 25 * dx - 25 * dy);
	}
	glEnd();
}

void App::drawZAmbient(){
	// Pre-Z pass
	renderer->reset();
	if (useSinglePass->isChecked()){
		renderer->setShader(preZSP);
		renderer->setBlendState(noColorWrite);
	} else {
		renderer->setShader(preZMP);
	}
	renderer->setRasterizerState(cullBack);
	renderer->apply();

	for (uint i = 0; i < 4; i++){
		if (!useSinglePass->isChecked()){
			renderer->setTexture("Base", base[i]);
			renderer->setTexture("Bump", bump[i]);
			renderer->applyTextures();

			renderer->setShaderConstant1i("hasParallax", int(parallax[i] > 0.0f));
			renderer->setShaderConstant2f("plxCoeffs", vec2(2, -1) * parallax[i]);
			renderer->setShaderConstant3f("camPos", camPos);
			renderer->applyConstants();
		}

		map->drawBatch(renderer, i);
	}
}

void App::drawDepth(const vec3 *lightPos, const float *invRadius){
	mat4 proj = cubeProjectionMatrixGL(1, 5000);

	for (int face = 0; face < 6; face++){
		renderer->changeRenderTarget(shadowMap, shadowMapDepth, face);

		mat4 mvp = proj * cubeViewMatrix(face);

		for (int i = 0; i < LIGHT_COUNT; i++){
			renderer->reset();
			renderer->setShader(depth);
			renderer->setBlendState(colorMasks[i]);
			renderer->setRasterizerState(cullBack);
			renderer->setShaderConstant4x4f("mvp", mvp * translate(-lightPos[i]));
			renderer->setShaderConstant3f("lightPos", lightPos[i]);
			renderer->setShaderConstant1f("invRadius", invRadius[i]);
			renderer->apply();

			glClear(GL_DEPTH_BUFFER_BIT);
			map->draw(renderer);
		}
	}
	renderer->changeToMainFramebuffer();
}

void App::drawLighting(const vec3 *lightPos, const float *invRadius, const vec3 *color){
	int shaderRow = int(useShadows->isChecked());
	int shaderCol = int(useBranching->isChecked());

	// Main lighting pass
	if (useSinglePass->isChecked()){
		renderer->reset();
		renderer->setShader(lightingSP[shaderRow][shaderCol]);
		renderer->setTexture("ShadowMap", shadowMap);
		renderer->setShaderConstant3f("camPos", camPos);
		renderer->setRasterizerState(cullBack);
		renderer->setDepthState(noDepthWrite);
		renderer->apply();

		for (uint k = 0; k < 4; k++){
			renderer->setTexture("Base", base[k]);
			renderer->setTexture("Bump", bump[k]);
			renderer->applyTextures();

			renderer->setShaderConstant1i("hasParallax", int(parallax[k] > 0.0f));
			renderer->setShaderConstant2f("plxCoeffs", vec2(2, -1) * parallax[k]);
			renderer->setShaderConstantArray3f("lightPos", lightPos, LIGHT_COUNT);
			renderer->setShaderConstantArray1f("invRadius", invRadius, LIGHT_COUNT);
			renderer->setShaderConstantArray3f("lightColor", color, LIGHT_COUNT);
			renderer->applyConstants();

			map->drawBatch(renderer, k);
		}
	} else {
		renderer->reset();
		renderer->setShader(lightingMP[shaderRow][shaderCol]);
		renderer->setTexture("ShadowMap", shadowMap);
		renderer->setShaderConstant3f("camPos", camPos);
		renderer->setRasterizerState(cullBack);
		renderer->setBlendState(blendAdd);
		renderer->setDepthState(noDepthWrite);
		renderer->apply();

		for (uint k = 0; k < 4; k++){
			renderer->setTexture("Base", base[k]);
			renderer->setTexture("Bump", bump[k]);
			renderer->applyTextures();

			renderer->setShaderConstant1i("hasParallax", int(parallax[k] > 0.0f));
			renderer->setShaderConstant2f("plxCoeffs", vec2(2, -1) * parallax[k]);

			for (int i = 0; i < LIGHT_COUNT; i++){
				renderer->setShaderConstant3f("lightPos", lightPos[i]);
				renderer->setShaderConstant1f("invRadius", invRadius[i]);
				renderer->setShaderConstant3f("lightColor", color[i]);
				if (useShadows->isChecked()){
					renderer->setShaderConstant4f("select", vec4(float(i == 0), float(i == 1), float(i == 2), float(i == 3)));
				}
				renderer->applyConstants();

				map->drawBatch(renderer, k);
			}
		}
	}
}

void App::drawFrame(){
	mat4 projection = perspectiveMatrixX(1.5f, width, height, 5, 4000);
	mat4 modelview = rotateXY(-wx, -wy);
	modelview.translate(-camPos);

	glMatrixMode(GL_PROJECTION);
	glLoadTransposeMatrixfARB(projection);

	glMatrixMode(GL_MODELVIEW);
	glLoadTransposeMatrixfARB(modelview);


	static float t = 0;
	if (animateLights->isChecked()) t += frameTime;

	float c = 0.5f + 0.5f * sinf(t * 0.723f);

	vec3 lightPos[LIGHT_COUNT] = {
		0, //vec3(-720, 200 * sinf(1.171f * t), 720 * cosf(0.5723f * t)),
		vec3(350 * cosf(1.82345f * t), 300 * cosf(1.252f * t), 180 * sinf(2.451f * t) - 1300),
		vec3(85 - 250 * c * sinf(t * 2 * 0.723f), 400 * sinf(t * 0.723f) - 320, 150 * c * sinf(t * 3 * 0.723f) - 115),
	};
	vec3 lightColor[LIGHT_COUNT] = {
		vec3(1, 0.7f, 0.2f),
		vec3(0.8f, 1, 0.9f),
		vec3(1, 0.2f, 0.1f),
	};
	float invRadius[LIGHT_COUNT] = {
		1.0f / 600,
		1.0f / 700,
		1.0f / 700,
	};

	float f = fmodf(0.7f * t, 4.0f);
	float cf = cosf(PI * f);
	if (f < 2){
		if (f < 1){
			lightPos[0] = float3(720 * cf, 0, 720);
		} else {
			lightPos[0] = float3(-720, 0, -720 * cf);
		}
	} else {
		if (f < 3){
			lightPos[0] = float3(-720 * cf, 0, -720);
		} else {
			lightPos[0] = float3(720, 0, 720 * cf);
		}
	}


	if (useShadows->isChecked()){
		drawDepth(lightPos, invRadius);
	}

	// Make sure depth writes are on
	renderer->changeDepthState(DS_NONE);
	glClear(GL_DEPTH_BUFFER_BIT);

	drawZAmbient();
	drawLighting(lightPos, invRadius, lightColor);

	drawLights(lightPos, lightColor, modelview.rows[0].xyz(), modelview.rows[1].xyz());
}
