/***********      .---.         .-"-.      *******************\
* -------- *     /   ._.       / ´ ` \     * ---------------- *
* 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::moveCamera(const vec3 &dir){
	vec3 newPos = camPos + dir * (speed * frameTime);

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

	camPos = newPos;
}

void App::resetCamera(){
	camPos = vec3(-850.0f, -185.0f, -570.0f);
	wx = 0.15f;
	wy = -1.33f;
}

bool App::init(){
	map = new Model();
	if (!map->loadT3d("../Models/Castle/Map.t3d")) return false;

	// Create BSP tree for collision detection
	uint nIndices = map->getIndexCount();
	vec3 *src = (vec3 *) map->getStream(0).vertices;
	uint *inds = map->getStream(0).indices;

	for (uint i = 0; i < nIndices; i += 6){
		vec3 v0 = src[inds[i]];
		vec3 v1 = src[inds[i + 1]];
		vec3 v2 = src[inds[i + 2]];
		vec3 v3 = src[inds[i + 5]];

		bsp.addTriangle(v0, v1, v2);
		bsp.addTriangle(v0, v2, v3);
	}
	bsp.build();

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

	return true;
}

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

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

	// Shaders
	const char *attribs[] = { NULL, "textureCoord", "tangent", "binormal", "normal" };
	if ((lighting = renderer->addShader("lighting.shd", attribs, elementsOf(attribs))) == SHADER_NONE) return false;
	if ((plainTex = renderer->addShader("plainTex.shd")) == SHADER_NONE) return false;
	if ((skybox   = renderer->addShader("skybox.shd"  )) == SHADER_NONE) return false;

	if ((nearestClamp = renderer->addSamplerState(NEAREST, CLAMP, CLAMP, CLAMP)) == SS_NONE) return false;
	if ((linear = renderer->addSamplerState(LINEAR, WRAP, WRAP, WRAP)) == SS_NONE) return false;
	if ((trilinearClamp = renderer->addSamplerState(TRILINEAR, CLAMP, CLAMP, CLAMP)) == SS_NONE) return false;
	if ((trilinearAniso = renderer->addSamplerState(TRILINEAR_ANISO, WRAP, WRAP, WRAP)) == SS_NONE) return false;
	if ((bilinearAniso = renderer->addSamplerState(BILINEAR_ANISO, WRAP, WRAP, WRAP)) == SS_NONE) return false;

	// Textures
	if ((light = renderer->addTexture("../Textures/Corona.dds", true, trilinearClamp)) == TEXTURE_NONE) return false;

	const char *files[] = {
		"../Textures/CubeMaps/DragonMoon/posx.jpg", "../Textures/CubeMaps/DragonMoon/negx.jpg",
		"../Textures/CubeMaps/DragonMoon/posy.jpg", "../Textures/CubeMaps/DragonMoon/negy.jpg",
		"../Textures/CubeMaps/DragonMoon/posz.jpg", "../Textures/CubeMaps/DragonMoon/negz.jpg",
	};
	if ((env = renderer->addCubemap(files, true, trilinearClamp)) == TEXTURE_NONE) return false;



	// Upload map to vertex/index buffer
	if (!map->makeDrawable(renderer, true)) return false;

	const char *maps[] = {
		"EmbeddedStones_1-1f",
		"laying_rock7",
		"brick01",
	};

	const float flatten[] = {
		3.5f,
		4.5f,
		3.5f,
	};

	const float offsets[] = {
		0.05f,
		0.1f,
		0.05f,
	};

	for (int k = 0; k < 3; k++){
		char baseMap[128];
		char bumpMap[128];
		char aperMap[128];

		sprintf(baseMap, "../Textures/%s.dds",     maps[k]);
		sprintf(bumpMap, "../Textures/%sBump.dds", maps[k]);
		sprintf(aperMap, "%sAperture.dds",         maps[k]);

		if ((base[k] = renderer->addTexture(baseMap, true, trilinearAniso)) == TEXTURE_NONE) return false;
		if ((bump[k] = renderer->addNormalMap(bumpMap, FORMAT_RGBA8, true, bilinearAniso)) == TEXTURE_NONE) return false;

		Image apertureImg;
		if (!apertureImg.loadDDS(aperMap)){
			static ShaderID apertureMap = SHADER_NONE, result;
			static TextureID rtTemp[2], rtResult, height;

			if (apertureMap == SHADER_NONE){
				// Shaders
				if ((apertureMap = renderer->addShader("apertureMap.shd")) == SHADER_NONE) return false;
				if ((result = renderer->addShader("result.shd")) == SHADER_NONE) return false;

				// Render targets
				if ((rtTemp[0] = renderer->addRenderTarget(512, 512, FORMAT_RGBA32F, nearestClamp)) == TEXTURE_NONE) return false;
				if ((rtTemp[1] = renderer->addRenderTarget(512, 512, FORMAT_RGBA32F, nearestClamp)) == TEXTURE_NONE) return false;
				if ((rtResult  = renderer->addRenderTarget(512, 512, FORMAT_RGBA16,  nearestClamp)) == TEXTURE_NONE) return false;
			}

			Image img;
			if (!img.loadDDS(bumpMap)) return false;
			img.convert(FORMAT_I16);
			if ((height = renderer->addTexture(img, true, linear)) == TEXTURE_NONE) return false;

			
			// Compute the aperture map
			renderer->changeRenderTarget(rtTemp[0]);

				glClearColor(0, 0, 0, 0);
				glClear(GL_COLOR_BUFFER_BIT);

				for (int i = 0; i < 1024; i++){
					int prev = i & 1;
					int curr = 1 - prev;


					vec3 dir;
					if (i == 0){
						dir = vec3(0, 0, 1);
					} else {
						// Generate points on surface of hemisphere
						float d;
						do {
							dir.x = float(rand()) / RAND_MAX * 2.0f - 1.0f;
							dir.y = float(rand()) / RAND_MAX * 2.0f - 1.0f;
							dir.z = float(rand()) / RAND_MAX;

							d = dot(dir, dir);
						} while (d < 0.9f * 0.9f || d > 1.1f * 1.1f);
						//dir *= (1.0f / sqrtf(d));
					}
					// Flatten the bumps a bit
					dir.z *= flatten[k];
					dir = normalize(dir);

					renderer->changeRenderTarget(rtTemp[curr]);

					renderer->reset();
					renderer->setShader(apertureMap);
					renderer->setTexture("Bump", height, bilinearAniso);
					renderer->setTexture("Prev", rtTemp[prev], nearestClamp);
					renderer->setDepthState(noDepthTest);
					renderer->setShaderConstant3f("dir", dir / 512.0f);
					renderer->setShaderConstant1f("offset", offsets[k]);
					renderer->apply();

					glBegin(GL_QUADS);
						glVertex2f(-1,  1);
						glVertex2f( 1,  1);
						glVertex2f( 1, -1);
						glVertex2f(-1, -1);
					glEnd();
				}

			renderer->changeRenderTarget(rtResult);

				renderer->reset();
				renderer->setShader(result);
				renderer->setTexture("Source", rtTemp[0], nearestClamp);
				renderer->setDepthState(noDepthTest);
				renderer->apply();

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

				// Read back results into apertureImg
				ubyte *pixels = apertureImg.create(FORMAT_RGBA16, 512, 512, 1, 1);
				glReadPixels(0, 0, 512, 512, GL_RGBA, GL_UNSIGNED_SHORT, pixels);

			renderer->changeToMainFramebuffer();

			apertureImg.createMipMaps(3);
			apertureImg.removeMipMaps(2, 1);
			apertureImg.convert(FORMAT_RGBA8);
			apertureImg.saveDDS(aperMap);
		}

		apertureImg.createMipMaps();
		if ((aperture[k] = renderer->addTexture(apertureImg, bilinearAniso)) == TEXTURE_NONE) return false;
	}

	if ((blendAdd = renderer->addBlendState(ONE, ONE)) == BS_NONE) return false;

	return true;
}

void App::unload(){

}

void App::drawLight(const vec3 &lightPos, const vec3 &dx, const vec3 &dy){
	renderer->reset();
	renderer->setShader(plainTex);
	renderer->setBlendState(blendAdd);
	renderer->setTexture("Tex", light, trilinearClamp);
	renderer->apply();

	glBegin(GL_QUADS);
		glTexCoord2f(0,0);
		glVertex3fv(lightPos - 30 * dx + 30 * dy);

		glTexCoord2f(1,0);
		glVertex3fv(lightPos + 30 * dx + 30 * dy);

		glTexCoord2f(1,1);
		glVertex3fv(lightPos + 30 * dx - 30 * dy);

		glTexCoord2f(0,1);
		glVertex3fv(lightPos - 30 * dx - 30 * dy);
	glEnd();
}

void App::drawEnvironment(const float4x4 &mvp){
	renderer->reset();
	renderer->setShader(skybox);
	renderer->setTexture("Env", env, trilinearClamp);
	renderer->setDepthState(noDepthWrite);
	renderer->apply();

	float4x4 iMvp = !mvp;

	float4 v0( 0,  2, 1, 1);
	float4 v1( 3, -1, 1, 1);
	float4 v2(-3, -1, 1, 1);

	glBegin(GL_TRIANGLES);
		glTexCoord4fv(iMvp * v0);
		glVertex4fv(v0);
		glTexCoord4fv(iMvp * v1);
		glVertex4fv(v1);
		glTexCoord4fv(iMvp * v2);
		glVertex4fv(v2);
	glEnd();

}

void App::drawFrame(){
	mat4 projection = perspectiveMatrixX(1.5f, width, height, 10, 5000);
	mat4 mvRotate = rotateXY(-wx, -wy);
	mat4 modelview = mvRotate * translate(-camPos);

	glMatrixMode(GL_PROJECTION);
	glLoadTransposeMatrixf(projection);

	glMatrixMode(GL_MODELVIEW);
	glLoadTransposeMatrixf(modelview);


	vec3 lightPos(550.0f * cosf(time), 240.0f * cosf(0.72f * time), 550.0f * sinf(time * 0.7f));


	glClear(GL_DEPTH_BUFFER_BIT);

	for (uint i = 0; i < map->getBatchCount(); i++){
		renderer->reset();
		renderer->setRasterizerState(cullBack);
		renderer->setShader(lighting);
		renderer->setTexture("Base", base[i], trilinearAniso);
		renderer->setTexture("Bump", bump[i], bilinearAniso);
		renderer->setTexture("Aperture", aperture[i], bilinearAniso);
		renderer->setShaderConstant3f("lightPos", lightPos);
		renderer->setShaderConstant3f("camPos", camPos);
		renderer->apply();

		map->drawBatch(renderer, i);
	}

	drawEnvironment(projection * mvRotate);

	vec3 dx = modelview.rows[0].xyz();
	vec3 dy = modelview.rows[1].xyz();
	drawLight(lightPos, dx, dy);
}
