#include "BstmDirector.h"

namespace Brainstem
{
	BstmDirector::BstmDirector(IKernel* kernel) { Kernel = kernel; }

	BstmDirector::~BstmDirector() { }

	bool BstmDirector::Init()
	{
		Renderer = dynamic_cast<BstmDevice*>(Kernel->GetDevice());
		Device = (IDirect3DDevice9*)Kernel->GetDevice()->GetDevicePtr();

		Renderer->CompileEffectFromResources(ID_SCENE_SHADER, Effect);

		HMODULE instance = reinterpret_cast<HMODULE>(&__ImageBase);
		D3DXCreateTextureFromResource(Device, instance, MAKEINTRESOURCE(ID_TEX_LOGO), &BstmDiffuseTexture);
		D3DXCreateTextureFromResource(Device, instance, MAKEINTRESOURCE(ID_TEX_LOGO_NM), &BstmNormalTexture);
		D3DXCreateTextureFromResource(Device, instance, MAKEINTRESOURCE(ID_TEX_LOGO_HM), &BstmHeightTexture);
		D3DXCreateTextureFromResource(Device, instance, MAKEINTRESOURCE(ID_TEX_BLANK), &BstmBlankTexture);

		AllowRender = true;
		AllowUpdate = true;

		return true;
	}

	void BstmDirector::Update()
	{
		if (!AllowUpdate)
			return;

		if (Scenes.size() <= 0)
		{
			WRITE_WARN(this, "No Scenes Found in BstmDirector!");
			AllowUpdate = false;
			return;
		}

		if (ActiveScene != NULL)
			if (ActiveScene->AllowUpdate)
				ActiveScene->Update();
	}

	IScene* BstmDirector::GetActiveScene() { return ActiveScene; }
	void BstmDirector::SetActiveScene(IScene* scene) { ActiveScene = scene; }

	IScene* BstmDirector::CreateScene()
	{
		Scene* newScene = new Scene(Kernel, Device, Effect);

		newScene->BstmDiffuseTexture = BstmDiffuseTexture;
		newScene->BstmNormalTexture = BstmNormalTexture;
		newScene->BstmHeightTexture = BstmHeightTexture;
		newScene->BstmBlankTexture = BstmBlankTexture;

		Scenes.insert(Scenes.end(), newScene);
		newScene->Init();
		ActiveScene = newScene;

		return newScene;
	}

	IDisplay* BstmDirector::CreateDisplay()
	{
		return CreateDisplay(Color(0.39f, 0.58f, 0.92f, 1.0f));
	}

	IDisplay* BstmDirector::CreateDisplay(Color color)
	{
		IDisplay* newDisplay = new Display(Device);
		newDisplay->Background = color;

		Displays.insert(Displays.end(), newDisplay);
		return newDisplay;
	}

	ITexture* BstmDirector::CreateTexture(const char* filename)
	{
		Texture* newTexture = new Texture(Device, filename);
		newTexture->ID = filename;

		ITexture* textureInstance = FindTextureInstance(filename);

		if (textureInstance != NULL)
			newTexture = dynamic_cast<Texture*>(textureInstance);

		Textures.insert(Textures.end(), newTexture);

		return newTexture;
	}

	void BstmDirector::Render(IScene* scene)
	{
		if (!AllowRender)
			return;

		if (scene == NULL)
		{
			WRITE_ERROR(this, "Cannot Render NULL Scene");
			AllowRender = false;
		}

		if (scene->AllowRender)
			scene->Render();
	}

	IMesh* BstmDirector::FindMeshInstance(const char* id)
	{
		IMesh* meshInstance = NULL;

		for(int i = 0; i < Meshes.size(); i++)
			if (Meshes[i]->ID.compare(id) == 0)
			{
				meshInstance = Meshes[i];
				break;
			}

		return meshInstance;
	}

	ITexture* BstmDirector::FindTextureInstance(const char* id)
	{
		ITexture* textureInstance = NULL;

		for(int i = 0; i < Textures.size(); i++)
		{
			Texture* thisTexture = dynamic_cast<Texture*>((ITexture*)Textures[i]);

			if (thisTexture->ID.compare(id) == 0)
			{
				textureInstance = Textures[i];
				break;
			}
		}

		return textureInstance;
	}

	ISprite* BstmDirector::AddSprite(IScene* scene)
	{
		ERR_SCENE_NULL(scene, "Sprite");

		Sprite* newSprite = new Sprite(Device);
		scene->AddNode(newSprite);
		newSprite->Init();

		return newSprite;
	}

	ISceneNode* BstmDirector::AddTeapot(IScene* scene)
	{
		ERR_SCENE_NULL(scene, "Teapot Primitive");

		return AddPrimitive(scene, "BSTM_TEAPOT", new Teapot(Device));
	}

	ISceneNode* BstmDirector::AddPrimitive(IScene* scene, const char* id, Primitive* primitive)
	{
		IMesh* meshInstance = FindMeshInstance(id);
		scene->AddNode(primitive);

		if (meshInstance == NULL)
		{
			primitive->Init();
			primitive->Mesh->ID = id;

			meshInstance = primitive->Mesh;
			Meshes.insert(Meshes.end(), meshInstance);
		}
		else
		{
			primitive->Mesh = meshInstance;
		}

		primitive->SetEffect(Effect);

		return primitive;
	}

	ISceneNode* BstmDirector::AddSphere(IScene* scene, float radius, unsigned int slices, unsigned int stacks)
	{
		ERR_SCENE_NULL(scene, "Sphere Primitive");

		std::stringstream primID;
		primID << "BSTM_SPHERE_" << radius << slices << stacks;

		return AddPrimitive(scene, primID.str().c_str(), new Sphere(Device, radius, slices, stacks));
	}

	ISceneNode* BstmDirector::AddBox(IScene* scene, float width, float height, float depth)
	{
		ERR_SCENE_NULL(scene, "Box Primitive");

		std::stringstream primID;
		primID << "BSTM_BOX_" << width << height << depth;

		return AddPrimitive(scene, primID.str().c_str(), new Box(Device, width, height, depth));
	}

	ISceneNode* BstmDirector::AddPlane(IScene* scene, float width, float height)
	{
		ERR_SCENE_NULL(scene, "Plane Primitive");

		std::stringstream primID;
		primID << "BSTM_PLANE_" << width << height;

		return AddPrimitive(scene, primID.str().c_str(), new Plane(Device, width, height));
	}

	ISceneNode* BstmDirector::AddXModel(IScene* scene, const char* filename)
	{
		ERR_SCENE_NULL(scene, "Model");

		Model* newModel = new Model(Device, Effect, filename);

		IMesh* meshInstance = FindMeshInstance(filename);

		if (meshInstance == NULL)
		{
			ERR_NODE_INIT_NULL(newModel->Init(), "Model");

			newModel->Mesh->ID = filename;

			meshInstance = newModel->Mesh;
			Meshes.insert(Meshes.end(), meshInstance);
		}
		else
		{
			newModel->Mesh = meshInstance;
		}

		scene->AddNode(newModel);

		return newModel;
	}

	ISceneNode* BstmDirector::AddPrimitive(IScene* scene, Primitive* primitive)
	{
		scene->AddNode(primitive);
		primitive->Init();
		primitive->SetEffect(Effect);
		return primitive;
	}

	ICamera* BstmDirector::AddFirstPersonCamera(IScene* scene, float rotationSpeed, float movementSpeed, bool isNoClip)
	{
		ERR_SCENE_NULL(scene, "Camera");

		ICamera* Camera = new CameraFirstPerson(Kernel, Device, BSTM_PI / 3.0f, 1.0f, 1500.0f, rotationSpeed, movementSpeed, isNoClip);
		scene->AddCamera(Camera);
		Camera->Init();

		return Camera;
	}

	ICamera* BstmDirector::AddStaticCamera(IScene* scene, Vector3<float> target)
	{
		ERR_SCENE_NULL(scene, "Camera");

		ICamera* Camera = new CameraStatic(Kernel, Device, BSTM_PI / 3.0f, 1.0f, 1500.0f, target);
		scene->AddCamera(Camera);
		Camera->Init();

		return Camera;
	}

	ITerrain* BstmDirector::AddTerrainFromFile(IScene* scene, unsigned int patches, const char* filename, float scaleFactor)
	{
		ERR_SCENE_NULL(scene, "Terrain");

		Terrain* newTerrain = new Terrain(Device, Effect);
		ERR_NODE_INIT_NULL(newTerrain->Init(), "Terrain");
		newTerrain->GenerateFromFile(patches, filename, scaleFactor);
		scene->AddNode(newTerrain);

		return newTerrain;
	}

	ITerrain* BstmDirector::AddTerrainRandom(IScene* scene, unsigned int patches, int seed, float noiseSize, float persistence, int octaves, float scaleFactor)
	{
		ERR_SCENE_NULL(scene, "Terrain");

		Terrain* newTerrain = new Terrain(Device, Effect);
		ERR_NODE_INIT_NULL(newTerrain->Init(), "Terrain");
		newTerrain->GenerateRandom(patches, seed, noiseSize, persistence, octaves, scaleFactor);
		scene->AddNode(newTerrain);

		return newTerrain;
	}

	ISky* BstmDirector::AddSkyDome(IScene* scene)
	{
		ERR_SCENE_NULL(scene, "Sky");

		Sky* sky = new Sky(Device, Effect);
		ERR_NODE_INIT_NULL(sky->Init(), "Sky");
		scene->AddNode(sky);

		return sky;
	}

	void BstmDirector::AddLight(IScene* scene, ILight* light)
	{
		scene->AddLight(light);
	}

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

		for(int i = 0; i < Meshes.size(); i++)
			SAFE_RELEASE(Meshes[i]);

		for(int i = 0; i < Scenes.size(); i++)
			SAFE_RELEASE(Scenes[i]);

		for(int i = 0; i < Textures.size(); i++)
			SAFE_RELEASE(Textures[i]);

		for(int i = 0; i < Displays.size(); i++)
			SAFE_RELEASE(Displays[i]);
	}
}