#include "BstmScene.h"

namespace Brainstem
{
	Scene::Scene(IKernel* kernel, IDirect3DDevice9* device,  ID3DXEffect* effect) 
	{ 
		Kernel = kernel;
		Device = device; 
		Effect = effect; 
	}
	Scene::~Scene() { Release(); }

	void Scene::Init()
	{
		Material bstmMaterial =
		{
			D3DXCOLOR(0.2f, 0.2f, 0.2f, 1.0f),
			D3DXCOLOR(0.7f, 0.7f, 0.7f, 1.0f),
			D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f),
			D3DXCOLOR(0.5f, 0.5f, 0.5f, 0.5f),
			150.0f
		};

		BstmMaterial = bstmMaterial;

		NormalMappingEnabled = false;
		ParallaxMappingEnabled = false;

		TextureScaling = Vector2<float>(1.0f, 1.0f);
		RenderMode = RENDER_SOLID;

		AllowRender = true;
		AllowUpdate = true;
	}

	void Scene::Update()
	{
		if (Cameras.size() <= 0)
		{
			WRITE_WARN(this, "No Camera Found in Active Scene!");
			AllowUpdate = false;
			return;
		}

		if (ActiveCamera != NULL);
			ActiveCamera->Update();

		for(int i = 0; i < Nodes.size(); i++)
			if (Nodes[i]->AllowUpdate)
				Nodes[i]->Update();
	}

	void Scene::Render()
	{
		if (Cameras.size() <= 0)
		{
			WRITE_WARN(this, "No Camera Found in Active Scene!");
			AllowRender = false;
			return;
		}

		static UINT totalPasses;
		static D3DXHANDLE hTechnique;

		D3DXMATRIX View = (D3DXMATRIX)(ActiveCamera->GetViewMatrix().Ptr());
		D3DXMATRIX Projection = (D3DXMATRIX)(ActiveCamera->GetProjectionMatrix().Ptr());
		D3DXMATRIX WorldInverseTranspose;
		D3DXMATRIX WorldViewInverseTranspose;

		D3DXMATRIX BoundsWorld;

		for(int i = 0; i < Nodes.size(); i++)
		{
			if (!Nodes[i]->AllowRender)
				continue;

			BoundingVolume Bounds = Nodes[i]->GetBounds();
 
			if (Nodes[i]->AllowFrustumCulling)
				if(!ActiveCamera->FrustumCubeTest(Bounds.Min, Bounds.Max))
					continue;

			D3DXMATRIX World = (D3DXMATRIX)(Nodes[i]->World.Ptr());

			D3DXMATRIX WorldView = World * View;
			D3DXMATRIX WorldViewProjection = WorldView * Projection;

			D3DXMatrixInverse(&WorldInverseTranspose, 0, &World);
			D3DXMatrixTranspose(&WorldInverseTranspose, &WorldInverseTranspose);

			D3DXMatrixInverse(&WorldViewInverseTranspose, 0, &WorldView);
			D3DXMatrixTranspose(&WorldViewInverseTranspose, &WorldViewInverseTranspose);

			Effect->SetTexture("colorMapTexture", BstmDiffuseTexture);

			if (NormalMappingEnabled)
				Effect->SetTexture("normalMapTexture", BstmNormalTexture);
			else
				Effect->SetTexture("normalMapTexture", BstmBlankTexture);

			if (ParallaxMappingEnabled)
				Effect->SetTexture("heightMapTexture", BstmHeightTexture);
			else
				Effect->SetTexture("heightMapTexture", BstmBlankTexture);

			float textureScaling[2] = { TextureScaling.X, TextureScaling.Y };
			Effect->SetValue("textureScale", textureScaling, sizeof(textureScaling));

			Effect->SetMatrix("worldMatrix", &World);
			Effect->SetMatrix("viewMatrix", &View);
			Effect->SetMatrix("worldViewMatrix", &WorldView);
			Effect->SetMatrix("worldViewProjectionMatrix", &WorldViewProjection);
			Effect->SetMatrix("worldInverseTransposeMatrix", &WorldInverseTranspose);
			Effect->SetMatrix("worldViewInverseTransposeMatrix", &WorldViewInverseTranspose);
			Effect->SetMatrix("worldInverseMatrix", D3DXMatrixIdentity(&WorldInverseTranspose));

			Effect->SetBool("normalMapping", NormalMappingEnabled);
			Effect->SetBool("parallaxMapping", ParallaxMappingEnabled);

			Vector3<float> cameraPos = ActiveCamera->Position;

			Effect->SetVector("cameraPos", &D3DXVECTOR4(cameraPos.X, cameraPos.Y, cameraPos.Z, 0.0f));
			Effect->SetVector("globalAmbient", &D3DXVECTOR4(0.0f, 0.0f, 0.0f, 0.0f));

			ILight* oLight = 0;
			D3DXHANDLE hLight;
			D3DXHANDLE hLightType;
			D3DXHANDLE hLightDir;
			D3DXHANDLE hLightPos;
			D3DXHANDLE hLightAmbient;
			D3DXHANDLE hLightDiffuse;
			D3DXHANDLE hLightSpecular;
			D3DXHANDLE hLightSpotInner;
			D3DXHANDLE hLightSpotOuter;
			D3DXHANDLE hLightRadius;

			DirectionalLight* dLight;
			PointLight* pLight;
			SpotLight* sLight;

			float direction[3];
			float position[3];

			int j = 0;
			int lightCounter = 0;

			while(true)
			{
				if (lightCounter >= Lights.size())
					break;

				oLight = Lights[lightCounter];

				if (!oLight->Enabled)
				{
					lightCounter++;
					continue;
				}

				hLight = Effect->GetParameterElement("lights", j);

				hLightType = Effect->GetParameterByName(hLight, "type");

				switch (oLight->Type)
				{
					case DIR_LIGHT:

						dLight = (DirectionalLight*)oLight;
						
						hLightDir = Effect->GetParameterByName(hLight, "direction");

						direction[0] = dLight->Direction.X;
						direction[1] = dLight->Direction.Y;
						direction[2] = dLight->Direction.Z;

						Effect->SetValue(hLightDir, direction, sizeof(direction));

						break;
					case POINT_LIGHT:

						pLight = (PointLight*)oLight;

						if (!ActiveCamera->FrustumSphereTest(pLight->Position, pLight->Radius))
						{
							lightCounter++;
							continue;
						}

						hLightPos = Effect->GetParameterByName(hLight, "position");
						hLightRadius = Effect->GetParameterByName(hLight, "radius");

						position[0] = pLight->Position.X;
						position[1] = pLight->Position.Y;
						position[2] = pLight->Position.Z;
						
						Effect->SetValue(hLightPos, position, sizeof(position));
						Effect->SetFloat(hLightRadius, pLight->Radius);

						break;
					case SPOT_LIGHT:

						sLight = (SpotLight*)oLight;

						hLightPos = Effect->GetParameterByName(hLight, "position");
						hLightDir = Effect->GetParameterByName(hLight, "direction");
						hLightSpotInner = Effect->GetParameterByName(hLight, "inner");
						hLightSpotOuter = Effect->GetParameterByName(hLight, "outer");
						hLightRadius = Effect->GetParameterByName(hLight, "radius");

						direction[0] = sLight->Direction.X;
						direction[1] = sLight->Direction.Y;
						direction[2] = sLight->Direction.Z;

						position[0] = sLight->Position.X;
						position[1] = sLight->Position.Y;
						position[2] = sLight->Position.Z;

						Effect->SetValue(hLightDir, direction, sizeof(direction));
						Effect->SetValue(hLightPos, position, sizeof(position));
						Effect->SetFloat(hLightSpotInner, sLight->InnerCone);
						Effect->SetFloat(hLightSpotOuter, sLight->OuterCone);
						Effect->SetFloat(hLightRadius, sLight->Radius);

						break;
				}

				hLightAmbient = Effect->GetParameterByName(hLight, "ambient");
				hLightDiffuse = Effect->GetParameterByName(hLight, "diffuse");
				hLightSpecular = Effect->GetParameterByName(hLight, "specular");

				Effect->SetInt(hLightType, oLight->Type);

				Effect->SetVector(hLightAmbient, &D3DXVECTOR4(oLight->Ambient.R, oLight->Ambient.G, oLight->Ambient.B, oLight->Ambient.A));
				Effect->SetVector(hLightDiffuse, &D3DXVECTOR4(oLight->Diffuse.R, oLight->Diffuse.G, oLight->Diffuse.B, oLight->Diffuse.A));
				Effect->SetVector(hLightSpecular, &D3DXVECTOR4(oLight->Specular.R, oLight->Specular.G, oLight->Specular.B, oLight->Specular.A));

				lightCounter++;
				j++;
			}

			Effect->SetInt("numLights", j);
			Effect->SetValue("material", &BstmMaterial, sizeof(Material));

			hTechnique = Effect->GetTechniqueByName(Nodes[i]->GetTechnique());

			if (hTechnique == NULL)
			{
				WRITE_ERROR(this, "Technique [" << Nodes[i]->GetTechnique() << "] Not Found");
				return;
			}

			DWORD OldRenderMode;
			DWORD OldCullMode;

			Device->GetRenderState(D3DRS_FILLMODE, &OldRenderMode);
			Device->GetRenderState(D3DRS_CULLMODE, &OldCullMode);

			Device->SetRenderState(D3DRS_FILLMODE, RenderMode);
			Device->SetRenderState(D3DRS_CULLMODE, Nodes[i]->CullMode);

			Effect->SetTechnique(hTechnique);

			Effect->Begin(&totalPasses, 0);

			for (UINT pass = 0; pass < totalPasses; ++pass)
            {
                if (SUCCEEDED(Effect->BeginPass(pass)))
                {
                    Nodes[i]->Render(ActiveCamera);
                    Effect->EndPass();
                }
            }

            Effect->End();

			Device->SetRenderState(D3DRS_FILLMODE, OldRenderMode);
			Device->SetRenderState(D3DRS_CULLMODE, OldCullMode);
		}
	}

	void Scene::Release()
	{
		WRITE_RELEASE(this);

		for(int i = 0; i < Nodes.size(); i++)
			Nodes[i]->Release();

		SAFE_RELEASE(Effect);
		SAFE_RELEASE(BstmDiffuseTexture);
		SAFE_RELEASE(BstmNormalTexture);
		SAFE_RELEASE(BstmHeightTexture);
		SAFE_RELEASE(BstmBlankTexture);

		Lights.clear();

		for(int i = 0; i < Lights.size(); i++)
			SAFE_DELETE(Lights[i]);
	}

	void Scene::AddCamera(ICamera* camera)
	{
		Cameras.insert(Cameras.end(), camera);
		ActiveCamera = camera;
	}

	void Scene::AddLight(ILight* light)
	{
		if (Lights.size() >= SCENE_MAX_LIGHTS)
		{
			WRITE_ERROR(this, "Cannot Apply Light, Exceeded Maximum Count");
			return;
		}

		Lights.insert(Lights.end(), light);
	}

	void Scene::RemoveLight(ILight* light)
	{
		for(int i = 0; i < Lights.size(); i++)
			if (Lights[i] == light)
				Lights.erase(Lights.begin() + i);
	}

	void Scene::ToggleMappingMode()
	{
		NormalMappingEnabled = !NormalMappingEnabled;
	}

	void Scene::ToggleRenderMode()
	{
		switch (RenderMode)
		{
			case RENDER_SOLID:
				RenderMode = RENDER_WIREFRAME;
				break;
			case RENDER_WIREFRAME:
				RenderMode = RENDER_POINT;
				break;
			default:
				RenderMode = RENDER_SOLID;
				break;
		}
	}

	void Scene::NextCamera()
	{
		for(int i = 0; i < Cameras.size(); i++)
			if ((ICamera*)Cameras[i] == ActiveCamera)
				if (i == Cameras.size()-1)
				{
					ActiveCamera = Cameras[0];
					return;
				}
				else
				{
					ActiveCamera = Cameras[i+1];
					return;
				}
	}

	ICamera* Scene::GetActiveCamera() { return ActiveCamera; }
	void Scene::SetActiveCamera(ICamera* camera) { ActiveCamera = camera; }

	void Scene::AddNode(ISceneNode* node)
	{
		node->Scale = Vector3<float>(1.0f, 1.0f, 1.0f);
		Nodes.insert(Nodes.end(), node);
	}
}
