/***********      .---.         .-"-.      ******************\
* -------- *     /   ._.       / ´ ` \     * --------------- *
* Author's *     \_  (__\      \_°v°_/     * humus@comhem.se *
*   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"

#include <algorithm>

BaseApp *app = new App();

void App::resetCamera(){
	camPos = vec3(2.24f, 4.0f, -24.6f);
	wx = -0.069f;
	wy = -0.372f;

	speed = 4.0f;
}

bool App::init(){
	// Place the buildings
	buildings[0] = Building(-9, 8,  4, 10, 5);
	buildings[1] = Building(-4, 8,  5, 14, 7);
	buildings[2] = Building( 6, 8,  5, 24, 8);
	buildings[3] = Building(12, 8,  4, 12, 5);
	buildings[4] = Building(17, 8,  4, 10, 5);

	buildings[5] = Building(-9, -3,  4, 14, 5);
	buildings[6] = Building(-4, -5,  5, 18, 7);
	buildings[7] = Building( 6, -5,  6, 18, 7);
	buildings[8] = Building(13, -3,  5, 13, 5);
	buildings[9] = Building(19, -3,  5, 12, 5);

	buildings[10] = Building(6,  -16,  18, 10, 6);
	buildings[11] = Building(6,  -36,  18, 10, 6);
	buildings[12] = Building(6,  -28,  4,  8,  10);
	buildings[13] = Building(20, -28,  4,  8,  10);

	buildings[14] = Building(-8, -22,  8, 8, 12);
	buildings[15] = Building(-8, -32,  8, 8, 8);


	// Init GUI components
	int tab = configDialog->addTab("Interior Mapping");

	configDialog->addWidget(tab, new Label(0, 0, 196, 36, "Light percentage"));
	configDialog->addWidget(tab, lightsSlider = new Slider(0, 40, 200, 24, 0, 1, 0.7f));

	return true;
}

void App::exit(){
}

bool App::load(){
	// Shaders
	if ((walls   = renderer->addShader("InteriorMapping.shd")) == SHADER_NONE) return false;
	if ((ceiling = renderer->addShader("Ceiling.shd")) == SHADER_NONE) return false;
	if ((ground  = renderer->addShader("Ground.shd")) == SHADER_NONE) return false;
	if ((stars   = renderer->addShader("Stars.shd")) == SHADER_NONE) return false;

	// Sampler states
	if ((cubeFilter  = renderer->addSamplerState(TRILINEAR_ANISO, CLAMP, CLAMP, CLAMP)) == SS_NONE) return false;
	if ((anisoFilter = renderer->addSamplerState(TRILINEAR_ANISO, WRAP,  WRAP,  WRAP )) == SS_NONE) return false;

	if ((depthLessEqual = renderer->addDepthState(true, false, LEQUAL)) == DS_NONE) return false;

	// Textures
	const char *files[] = {
		"../Textures/CubeMaps/Interior/posx.dds", "../Textures/CubeMaps/Interior/negx.dds",
		"../Textures/CubeMaps/Interior/posy.dds", "../Textures/CubeMaps/Interior/negy.dds",
		"../Textures/CubeMaps/Interior/posz.dds", "../Textures/CubeMaps/Interior/negz.dds",

		"../Textures/CubeMaps/Interior/posz.dds", "../Textures/CubeMaps/Interior/negz.dds",
		"../Textures/CubeMaps/Interior/posy.dds", "../Textures/CubeMaps/Interior/negy.dds",
		"../Textures/CubeMaps/Interior/negx.dds", "../Textures/CubeMaps/Interior/posx.dds",

		"../Textures/CubeMaps/Interior/negx.dds", "../Textures/CubeMaps/Interior/posx.dds",
		"../Textures/CubeMaps/Interior/posy.dds", "../Textures/CubeMaps/Interior/negy.dds",
		"../Textures/CubeMaps/Interior/negz.dds", "../Textures/CubeMaps/Interior/posz.dds",

		"../Textures/CubeMaps/Interior/negz.dds", "../Textures/CubeMaps/Interior/posz.dds",
		"../Textures/CubeMaps/Interior/posy.dds", "../Textures/CubeMaps/Interior/negy.dds",
		"../Textures/CubeMaps/Interior/posx.dds", "../Textures/CubeMaps/Interior/negx.dds",

		"../Textures/CubeMaps/Interior/negz.dds", "../Textures/CubeMaps/Interior/negx.dds",
		"../Textures/CubeMaps/Interior/posy.dds", "../Textures/CubeMaps/Interior/negy.dds",
		"../Textures/CubeMaps/Interior/posz.dds", "../Textures/CubeMaps/Interior/posx.dds",

		"../Textures/CubeMaps/Interior/posx.dds", "../Textures/CubeMaps/Interior/posz.dds",
		"../Textures/CubeMaps/Interior/posy.dds", "../Textures/CubeMaps/Interior/negy.dds",
		"../Textures/CubeMaps/Interior/negx.dds", "../Textures/CubeMaps/Interior/negz.dds",

		"../Textures/CubeMaps/Interior/negx.dds", "../Textures/CubeMaps/Interior/negz.dds",
		"../Textures/CubeMaps/Interior/posy.dds", "../Textures/CubeMaps/Interior/negy.dds",
		"../Textures/CubeMaps/Interior/posx.dds", "../Textures/CubeMaps/Interior/posz.dds",

		"../Textures/CubeMaps/Interior/posz.dds", "../Textures/CubeMaps/Interior/posx.dds",
		"../Textures/CubeMaps/Interior/posy.dds", "../Textures/CubeMaps/Interior/negy.dds",
		"../Textures/CubeMaps/Interior/negz.dds", "../Textures/CubeMaps/Interior/negx.dds",
	};
	if ((cube = renderer->addCubemap(files, true, cubeFilter, 8)) == TEXTURE_NONE) return false;

	if ((wallTex    = renderer->addTexture("../Textures/Window.dds",  true, anisoFilter)) == TEXTURE_NONE) return false;
	if ((ceilingTex = renderer->addTexture("../Textures/stone50.dds", true, anisoFilter, SRGB)) == TEXTURE_NONE) return false;
	if ((groundTex  = renderer->addTexture("../Textures/Cement.dds",  true, anisoFilter, SRGB)) == TEXTURE_NONE) return false;

	// Vertex buffer
	struct Vertex {
		float3 position;
		float2 tangent;
	};

	Vertex vertices[] = {
		{ float3(1, 1, 1), float2(-1, 0) },
		{ float3(0, 1, 1), float2(-1, 0) },
		{ float3(1, 0, 1), float2(-1, 0) },
		{ float3(0, 0, 1), float2(-1, 0) },

		{ float3(0, 1, 0), float2(1, 0) },
		{ float3(1, 1, 0), float2(1, 0) },
		{ float3(0, 0, 0), float2(1, 0) },
		{ float3(1, 0, 0), float2(1, 0) },

		{ float3(1, 1, 0), float2(0, 1) },
		{ float3(1, 1, 1), float2(0, 1) },
		{ float3(1, 0, 0), float2(0, 1) },
		{ float3(1, 0, 1), float2(0, 1) },

		{ float3(0, 1, 1), float2(0, -1) },
		{ float3(0, 1, 0), float2(0, -1) },
		{ float3(0, 0, 1), float2(0, -1) },
		{ float3(0, 0, 0), float2(0, -1) },
	};
	if ((boxVB = renderer->addVertexBuffer(sizeof(vertices), STATIC, vertices)) == VB_NONE) return false;

	// Index buffer
	ushort indices[] = {
		0,1,2,     2,1,3,
		4,5,6,     6,5,7,
		8,9,10,    10,9,11,
		12,13,14,  14,13,15,
	};
	if ((boxIB = renderer->addIndexBuffer(elementsOf(indices), 2, STATIC, indices)) == IB_NONE) return false;

	// Vertex format
	FormatDesc formatDesc[] = {
		{ 0, TYPE_VERTEX,  FORMAT_FLOAT, 3 },
		{ 0, TYPE_TANGENT, FORMAT_FLOAT, 2 },
	};
	if ((boxVF = renderer->addVertexFormat(formatDesc, elementsOf(formatDesc), walls)) == VF_NONE) return false;

	return true;
}

void App::unload(){
}

void App::drawFrame(){
	float4x4 projection = toD3DProjection(perspectiveMatrixY(1.3f, width, height, 0.01f, 200.0f));
	float4x4 view = rotateXY(-wx, -wy);
	float4x4 invViewProjEnv = !(projection * view);
	view.translate(-camPos);
	float4x4 viewProj = projection * view;

	renderer->clear(true, true, false, float4(0.0f, 0.0f, 0.0f, 0));


	// Sort buildings by distance to improve performance
	for (int i = 0; i < BUILDING_COUNT; i++){
		float2 dir = buildings[i].pos + 0.5f * buildings[i].size.xz() - camPos.xz();
		buildings[i].distance = dot(dir, dir);
	}
	std::sort(buildings, buildings + BUILDING_COUNT);


	// Render walls with interior mapping
	renderer->reset();
	renderer->setRasterizerState(cullBack);
	renderer->setShader(walls);
	renderer->setShaderConstant4x4f("viewProj", viewProj);
	renderer->setShaderConstant3f("camPos", camPos);
	renderer->setShaderConstant1f("lightThreshold", lightsSlider->getValue());
	renderer->setTexture("Cube", cube);
	renderer->setTexture("Wall", wallTex);
	renderer->setSamplerState("cubeFilter", cubeFilter);
	renderer->setSamplerState("wallFilter", anisoFilter);
	renderer->setVertexFormat(boxVF);
	renderer->setVertexBuffer(0, boxVB);
	renderer->setIndexBuffer(boxIB);
	renderer->apply();

	device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	for (int i = 0; i < BUILDING_COUNT; i++){
		renderer->setShaderConstant2f("pos", buildings[i].pos);
		renderer->setShaderConstant3f("size", buildings[i].size);
		renderer->applyConstants();

		device->DrawIndexed(4 * 6, 0, 0);
	}


	// Render ceilings
	renderer->reset();
	renderer->setRasterizerState(cullBack);
	renderer->setShader(ceiling);
	renderer->setShaderConstant4x4f("viewProj", viewProj);
	renderer->setTexture("Ceiling", ceilingTex);
	renderer->setSamplerState("filter", anisoFilter);
	renderer->apply();

	device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
	for (int i = 0; i < BUILDING_COUNT; i++){
		renderer->setShaderConstant2f("pos", buildings[i].pos);
		renderer->setShaderConstant3f("size", buildings[i].size);
		renderer->applyConstants();

		device->Draw(4, 0);
	}


	// Render ground
	renderer->reset();
	renderer->setRasterizerState(cullBack);
	renderer->setShader(ground);
	renderer->setShaderConstant4x4f("viewProj", viewProj);
	renderer->setTexture("Ground", groundTex);
	renderer->setSamplerState("filter", anisoFilter);
	renderer->apply();

	device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
	device->Draw(4, 0);


	// Render sky
	renderer->reset();
	renderer->setShader(stars);
	renderer->setShaderConstant4x4f("invViewProj", invViewProjEnv);
	renderer->setDepthState(depthLessEqual);
	renderer->apply();

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