#include "../Framework2/Direct3D/Direct3DApp.h"
#include "../Framework2/Util/Model.h"

D3DVERTEXELEMENT9 roomVF[] = {
	{ 0, 0,                               D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
	{ 0, sizeof(vec3) * 1,                D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
	{ 0, sizeof(vec3) * 1 + sizeof(vec2), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT,  0 },
	{ 0, sizeof(vec3) * 2 + sizeof(vec2), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 0 },
	{ 0, sizeof(vec3) * 3 + sizeof(vec2), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0 },
	D3DDECL_END()
};
/*
struct RoomVertex {
	vec3 vertex;
	vec2 texCoord;
	vec3 tangent;
	vec3 binormal;
	vec3 normal;
};*/

class MainApp : public Direct3DApp {
public:
	void resetCamera();

	bool init();
	bool exit();
	bool load();
	bool unload();

	void drawSky();
	void drawRoom();
	void drawRoomDepth(const mat4 &mvp);
	void drawLight();
	bool drawFrame();
protected:
	TextureID depthTarget, cubeTarget;

	ShaderID room, depth, depthColor;

	TextureID light;

	TextureID base[3];
	TextureID bump[2];

	vec3 lightPos;

	Model *model;
	LPDIRECT3DVERTEXDECLARATION9 roomVDecl;
};

void MainApp::resetCamera(){
	position = vec3(-450, 90, -220);
	wx = 0.2f;
	wy = -0.8f;
	wz = 0;
}

bool MainApp::init(){
	model = new Model();
	return model->loadFromFile("../Models/SimpleRoom/Map.hmdl");
}

bool MainApp::exit(){
	delete model;
	return true;
}

bool MainApp::load(){
	if (caps.PixelShaderVersion < D3DPS_VERSION(2,0) || caps.VertexShaderVersion < D3DVS_VERSION(2,0)){
		String error("This demo requires pixel and vertex shader version 2.0.\n"
					 "Your card/driver only supports pixel shader ");
		error.sprintf("%d.%d", (caps.PixelShaderVersion  >> 8) & 255, caps.PixelShaderVersion  & 255);
		error += " and vertex shader ";
		error.sprintf("%d.%d", (caps.VertexShaderVersion >> 8) & 255, caps.VertexShaderVersion & 255);

		addToLog(error);		
		return false;
	}



	cubeTarget = renderer->addRenderCubemap(512, FORMAT_RGBA8, false);
	depthTarget = renderer->addRenderDepth(512, 512, FORMAT_D16);


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

	setDefaultFont("../Textures/Fonts/Future.font", "../Textures/Fonts/Future.dds");

	if ((base[0] = renderer->addTexture("../Textures/FieldStone.dds")) == TEXTURE_NONE) return false;
	if ((bump[0] = renderer->addTexture("../Textures/FieldStoneBump.png", TEX_NORMALMAP)) == TEXTURE_NONE) return false;
	if ((base[1] = renderer->addTexture("../Textures/Wood.dds")) == TEXTURE_NONE) return false;
	if ((bump[1] = renderer->addTexture("../Textures/WoodBump.png", TEX_NORMALMAP)) == TEXTURE_NONE) return false;
	if ((base[2] = renderer->addTexture("../Textures/AngleMosaic.dds")) == TEXTURE_NONE) return false;

	if ((light = renderer->addTexture("../Textures/Particle.png")) == TEXTURE_NONE) return false;

	dev->CreateVertexDeclaration(roomVF, &roomVDecl);
	return true;
}

bool MainApp::unload(){
	roomVDecl->Release();
	return true;
}

void MainApp::drawRoom(){
	dev->SetVertexDeclaration(roomVDecl);
	dev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
	for (unsigned int i = 0; i < 3; i++){
		if (i == 2){
			drawLight();

			dev->SetVertexDeclaration(roomVDecl);
			dev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
			renderer->setBlending(DST_COLOR, ZERO);
			renderer->setTextures(base[i]);
		} else {
			renderer->setTextures(base[i], bump[i], cubeTarget);
			renderer->setShader(room);
		}
		renderer->apply();

		renderer->changeShaderConstant4x4f("view_proj_matrix", projection * modelView);
		renderer->changeShaderConstant3f("lightPos", lightPos);

		Batch *batch = model->getBatch(i);
		dev->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, batch->getVertexCount(), batch->getIndexCount() / 3, batch->getIndices(), D3DFMT_INDEX16, batch->getVertices(), batch->getVertexSize());
	}
}

void MainApp::drawRoomDepth(const mat4 &mvp){
	dev->SetVertexDeclaration(roomVDecl);

	dev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
	for (unsigned int i = 0; i < 3; i++){
		if (i == 2){
			dev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

			renderer->setTextures(base[i]);
			renderer->setShader(depthColor);
			renderer->setMask(RED | GREEN | BLUE);
		} else {
			renderer->setShader(depth);
		}
		renderer->apply();

		renderer->changeShaderConstant4x4f("view_proj_matrix", mvp);
		renderer->changeShaderConstant3f("lightPos", lightPos);

		Batch *batch = model->getBatch(i);

		dev->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, batch->getVertexCount(), batch->getIndexCount() / 3, batch->getIndices(), D3DFMT_INDEX16, batch->getVertices(), batch->getVertexSize());
	}
}

void MainApp::drawLight(){
	renderer->setTextures(light);
	renderer->setBlending(ONE, ONE);
	renderer->apply();

	vec3 dx(modelView.elem[0][0], modelView.elem[0][1], modelView.elem[0][2]);
	vec3 dy(modelView.elem[1][0], modelView.elem[1][1], modelView.elem[1][2]);

	struct Vertex {
		vec3 position;
		vec2 texCoord;
	};

	Vertex vertices[4];

	vertices[0].position = lightPos + 30 * (-dx + dy);
	vertices[1].position = lightPos + 30 * ( dx + dy);
	vertices[2].position = lightPos + 30 * ( dx - dy);
	vertices[3].position = lightPos + 30 * (-dx - dy);

	vertices[0].texCoord = vec2(0, 0);
	vertices[1].texCoord = vec2(1, 0);
	vertices[2].texCoord = vec2(1, 1);
	vertices[3].texCoord = vec2(0, 1);


	dev->SetFVF(D3DFVF_XYZ | D3DFVF_TEXCOORDSIZE2(0) | (1 << D3DFVF_TEXCOUNT_SHIFT));
	dev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, vertices, sizeof(Vertex));
}

bool MainApp::drawFrame(){
    dev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0, 0, 0, 0), 1.0f, 0);

	projection = projectionMatrixX(1.5f, float(height) / float(width), 1, 3000);
	dev->SetTransform(D3DTS_PROJECTION, (D3DMATRIX *) (const float *) transpose(projection));

	modelView = rotateZXY(-wx, -wy, -wz);
	modelView.translate(-position);
	dev->SetTransform(D3DTS_VIEW, (D3DMATRIX *) (const float *) transpose(modelView));

	lightPos = vec3(450.0f * cosf(0.7f * time), 25, 280.0f * sinf(0.7f * time) + 60);


	mat4 proj = projectionMatrixX(PI / 2, 1, 1, 3000);
	for (int i = 0; i < 6; i++){
		renderer->changeRenderTarget(cubeTarget, depthTarget, i);
		dev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(255, 255, 255, 255), 1.0f, 0);

		mat4 view = cubemapMatrix(i);
		view.translate(-lightPos);

		drawRoomDepth(proj * view);
	}

	renderer->changeToDefaultRenderTarget();

	drawRoom();

	return true;
}

App *app = new MainApp();
