/***********      .---.         .-"-.      *******************\
* -------- *     /   ._.       / ´ ` \     * ---------------- *
* 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();

bool App::onKey(const uint key, const bool pressed){
	if (D3D10App::onKey(key, pressed)) return true;

	if (pressed){
		if (key == KEY_P){
			usePostToneMappingResolve->setChecked(!usePostToneMappingResolve->isChecked());
		}
	}

	return false;
}

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

	float3 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(-477, 483, 1624);
	wx = 0.06f;
	wy = -0.69f;
}

void App::onSize(const int w, const int h){
	D3D10App::onSize(w, h);

	if (renderer){
		// Make sure render targets are the size of the window
		renderer->resizeRenderTarget(hdrRT,    w, h, 1, 1);
		renderer->resizeRenderTarget(depth,    w, h, 1, 1);
		renderer->resizeRenderTarget(resolved, w, h, 1, 1);
	}
}

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

	TexturePacker texPacker;
	uint nIndices = map->getIndexCount();
	float3 *src = (float3 *) map->getStream(0).vertices;
	uint *inds = map->getStream(0).indices;

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

		float w = length(v1 - v0) / 24;
		float h = length(v3 - v0) / 24;

		if (w < 8) w = 8;
		if (h < 8) h = 8;

		uint tw = (int) w;
		uint th = (int) h;

		tw = (tw + 4) & ~7;
		th = (th + 4) & ~7;

		texPacker.addRectangle(tw, th);
	}

	uint tw = 512;
	uint th = 256;

	if (!texPacker.assignCoords(&tw, &th)){
		ErrorMsg("Lightmap too small");
		return false;
	}

	tw = getUpperPowerOfTwo(tw);
	th = getUpperPowerOfTwo(th);


	uint nLmCoords = (nIndices / 6) * 4;
	float2 *lmCoords = new float2[nLmCoords];
	uint *lmIndices = new uint[nIndices];

	uint index = 0;
	for (uint i = 0; i < nIndices; i += 6){
		TextureRectangle *rect = texPacker.getRectangle(index);

		float x0 = float(rect->x + 0.5f) / tw;
		float y0 = float(rect->y + 0.5f) / th;
		float x1 = float(rect->x + rect->width  - 0.5f) / tw;
		float y1 = float(rect->y + rect->height - 0.5f) / th;

		lmCoords[4 * index + 0] = float2(x0, y0);
		lmCoords[4 * index + 1] = float2(x1, y0);
		lmCoords[4 * index + 2] = float2(x1, y1);
		lmCoords[4 * index + 3] = float2(x0, y1);

		lmIndices[i + 0] = 4 * index;
		lmIndices[i + 1] = 4 * index + 1;
		lmIndices[i + 2] = 4 * index + 2;
		lmIndices[i + 3] = 4 * index;
		lmIndices[i + 4] = 4 * index + 2;
		lmIndices[i + 5] = 4 * index + 3;

		float3 v0 = src[inds[i]];
		float3 v1 = src[inds[i + 1]];
		float3 v2 = src[inds[i + 2]];
		float3 v3 = src[inds[i + 5]];

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

		index++;
	}
	bsp.build();

	map->addStream(TYPE_TEXCOORD, 2, nLmCoords, (float *) lmCoords, lmIndices, true);

	map->cleanUp();

	// Init GUI components
	int tab = configDialog->addTab("HDR");
	configDialog->addWidget(tab, new Label(0, 0, 192, 36, "Exposure (+/-)"));
	configDialog->addWidget(tab, exposureControl = new Slider(0, 40, 200, 24, -1, 1, 0.3f));
	configDialog->addWidget(tab, usePostToneMappingResolve = new CheckBox(0, 80, 400, 36, "Use post-tonemap resolve (P)", true));

	return true;
}

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

bool App::initAPI(){
	return D3D10App::initAPI(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, DXGI_FORMAT_UNKNOWN, 1, NO_SETTING_CHANGE);
}

void App::exitAPI(){
	D3D10App::exitAPI();
}

bool App::load(){
	// Shaders
	if ((skyboxShader = renderer->addShader("skybox.shd")) == SHADER_NONE) return false;

	int samples = max(antiAliasSamples, 1);
	char def[32];
	sprintf(def, "#define SAMPLE_COUNT %d\n", samples);
	if ((resolveShader = renderer->addShader("resolve.shd", def)) == SHADER_NONE) return false;
	if ((tonemapShader = renderer->addShader("tonemap.shd")) == SHADER_NONE) return false;
	if ((lightingShader = renderer->addShader("lighting.shd")) == SHADER_NONE) return false;

	// Samplerstates
	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;

	// Render targets
	if ((hdrRT = renderer->addRenderTarget(width, height, 1, 1, FORMAT_RGBA16F, samples, SS_NONE)) == TEXTURE_NONE) return false;
	if ((depth = renderer->addRenderDepth (width, height, 1,    FORMAT_DEPTH24, samples, SS_NONE)) == TEXTURE_NONE) return false;

	// Non-MSAA texture needed the traditional approach
	if ((resolved = renderer->addRenderTarget(width, height, 1, 1, FORMAT_RGBA16F, 1, SS_NONE)) == TEXTURE_NONE) return false;

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

	// Precomputed lightmap
	if ((lightMap = renderer->addTexture("LightMap.dds", false, linearClamp)) == SS_NONE) return false;

	// Textures and bumpmaps
	if ((base[0] = renderer->addTexture  ("../Textures/StoneWall_1-2.dds",                   true, trilinearAniso, SRGB)) == TEXTURE_NONE) return false;
	if ((bump[0] = renderer->addNormalMap("../Textures/StoneWall_1-2Bump.dds", FORMAT_RGBA8, true, trilinearAniso      )) == TEXTURE_NONE) return false;

	if ((base[1] = renderer->addTexture  ("../Textures/floor_wood_3.dds",                   true, trilinearAniso, SRGB)) == TEXTURE_NONE) return false;
	if ((bump[1] = renderer->addNormalMap("../Textures/floor_wood_3Bump.dds", FORMAT_RGBA8, true, trilinearAniso      )) == TEXTURE_NONE) return false;

	if ((base[2] = renderer->addTexture  ("../Textures/brick01.dds",                   true, trilinearAniso, SRGB)) == TEXTURE_NONE) return false;
	if ((bump[2] = renderer->addNormalMap("../Textures/brick01Bump.dds", FORMAT_RGBA8, true, trilinearAniso      )) == TEXTURE_NONE) return false;

	// Rasterizerstates
	if ((noMSAA = renderer->addRasterizerState(CULL_NONE, SOLID, false, false)) == RS_NONE) return false;

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

	return true;
}

void App::unload(){

}

void App::drawFrame(){
	if (keys[KEY_ADD     ]) exposureControl->setValue(exposureControl->getValue() + frameTime);
	if (keys[KEY_SUBTRACT]) exposureControl->setValue(exposureControl->getValue() - frameTime);


	float4x4 projection = toD3DProjection(perspectiveMatrixX(1.5f, width, height, 1.0f, 10000));
	float4x4 modelview = rotateXY(-wx, -wy);
	modelview.translate(-camPos);
	float4x4 mvp = projection * modelview;
	float4x4 invMvpEnv = !(projection * rotateXY(-wx, -0.6f - wy));


	renderer->changeRenderTarget(hdrRT, depth);
		renderer->clear(false, true, false);

		/*
			Castle
		*/
		renderer->reset();
		renderer->setRasterizerState(cullBack);
		renderer->setShader(lightingShader);
		renderer->setShaderConstant4x4f("mvp", mvp);
		renderer->setShaderConstant3f("camPos", camPos);
		renderer->setTexture("LightMap", lightMap);
		renderer->setSamplerState("baseFilter", trilinearAniso);
		renderer->setSamplerState("lmFilter", linearClamp);
		renderer->apply();

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

			map->drawBatch(renderer, i);
		}


		/*
			Skybox
		*/
		renderer->reset();
		renderer->setRasterizerState(noMSAA);
		renderer->setShader(skyboxShader);
		renderer->setShaderConstant4x4f("invMvp", invMvpEnv);
		renderer->setTexture("env", env);
		renderer->setSamplerState("filter", trilinearClamp);
		renderer->setDepthState(noDepthWrite);
		renderer->apply();

		device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		device->Draw(3, 0);

	renderer->changeToMainFramebuffer();

	float exposure = powf(64.0f, exposureControl->getValue());

	if (usePostToneMappingResolve->isChecked()){
		/*
			Post-tonemapping resolve
		*/

		// Tonemap and resolve in a single pass
		renderer->reset();
		renderer->setRasterizerState(noMSAA);
		renderer->setShader(resolveShader);
		renderer->setTexture("hdr", hdrRT);
		renderer->setShaderConstant1f("exposure", exposure);
		renderer->setDepthState(noDepthTest);
		renderer->apply();

		device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		device->Draw(3, 0);
	} else {
		/*
			Traditional approach
		*/
		if (antiAliasSamples > 1){
			// Resolve ...
			device->ResolveSubresource(
				((Direct3D10Renderer *) renderer)->getResource(resolved), 0,
				((Direct3D10Renderer *) renderer)->getResource(hdrRT),    0, DXGI_FORMAT_R16G16B16A16_FLOAT);
		}

		// ... then tonemap
		renderer->reset();
		renderer->setRasterizerState(noMSAA);
		renderer->setShader(tonemapShader);
		renderer->setTexture("hdr", (antiAliasSamples > 1)? resolved : hdrRT);
		renderer->setShaderConstant1f("exposure", exposure);
		renderer->setDepthState(noDepthTest);
		renderer->apply();

		device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		device->Draw(3, 0);
	}

	// Display help text
	static float displayTime = 5.0f;
	if (displayTime > 0){
		if (configDialog->isVisible()){
			displayTime = 0;
		} else {
			displayTime -= min(frameTime, 0.1f);
			renderer->drawText("Press F1 for HDR and\nAntiAliasing options", width * 0.5f - 140, height * 0.5f - 38, 30, 38, defaultFont, linearClamp, blendSrcAlpha, noDepthTest);
		}
	}
}
