#include <iostream>
#include <string>
#include <sstream>
#include <Windows.h>
#include <vector>
#include "Brainstem.h" 

#include "weapon.h"

#pragma comment(lib, "Brainstem.lib")

using namespace Brainstem;

IKernel* Kernel;
IDirector* Director;
IDevice* Device;
IScene* Sandbox;

IDisplay* StaticDisplay;
IDisplay* DynamicDisplayA;
IDisplay* DynamicDisplayB;
int DisplayMode = 0;

ITerrain* Terrain;

std::vector<ISceneNode*> Objects;
std::vector<ILight*> Lights;

PointLight* EnvironmentLight;
SpotLight* FlashLight;

Weapon* AK47u;
Weapon* GodHand;

bool cullDebug;
bool showInfo;

class EventManager : public IEventManager
{
public:
	EventManager() { isMouseDown = false; }

	virtual void OnEvent(const Event& e)
	{
		switch (e.Type)
		{
			case EVENT_KEYDOWN:
				if (!IsKeyDown(e.Key.Char))
					keyDown.push_back(e.Key.Char);

				if (e.Key.Code == 27)
					Kernel->SetPause(!Kernel->IsPaused());

				if (e.Key.Char == 'r')
					Sandbox->ToggleRenderMode();

				if (e.Key.Char == 'p')
					Sandbox->NextCamera();

				if (e.Key.Char == 'i')
					showInfo = !showInfo;

				if (e.Key.Char == 'l')
					EnvironmentLight->Enabled = !EnvironmentLight->Enabled;

				if (e.Key.Char == 'f')
					FlashLight->Enabled = !FlashLight->Enabled;

				if (e.Key.Char == 'x')
				{
					switch(DisplayMode)
					{
						case 0:
							DisplayMode = 1;
							break;
						case 1:
							DisplayMode = 0;
							break;
					}
				}

				if (e.Key.Char == 't')
					Terrain->GenerateRandom(30, (int)(rand() % 2000), 1.5f, 0.6f, 7, 2.0f);

				if(e.Key.Char == 'm')
					Sandbox->ToggleMappingMode();

				if (e.Key.Char == 'c')
					if (cullDebug)
						Sandbox->GetActiveCamera()->CullingFOV = Sandbox->GetActiveCamera()->FOV;
					else
						Sandbox->GetActiveCamera()->CullingFOV = BSTM_PI / 8.0f;

					cullDebug = !cullDebug;

				if (e.Key.Code == 32)
				{
					if (!AK47u->IsSwitching && !GodHand->IsSwitching)
					{
						AK47u->IsActive = !AK47u->IsActive;
						GodHand->IsActive = !GodHand->IsActive;
					}
				}

				break;
			case EVENT_KEYUP:
				for(int i = 0; i < keyDown.size(); i++)
					if (keyDown[i] == e.Key.Char)
						keyDown.erase(keyDown.begin() + i);
				break;
			case EVENT_MOUSESCROLL:

				if (!AK47u->IsSwitching && !GodHand->IsSwitching)
				{
					AK47u->IsActive = !AK47u->IsActive;
					GodHand->IsActive = !GodHand->IsActive;
				}

				break;
			case EVENT_MOUSEDOWN:

				if (e.Mouse.Button == BTN_LEFT)
					isMouseDown = true;
				else if (e.Mouse.Button == BTN_RIGHT)
				{
						if (Lights.size() > 3)
							break;

						Color a = Color(1.0f, 1.0f, 0.0f, 0.0f);

						if (Lights.size() % 2 == 0)
							a = Color(1.0f, 0.0f, 0.0f, 0.0f);

						if (Lights.size() % 3 == 0)
							a = Color(0.0f, 1.0f, 0.0f, 0.0f);

						if (Lights.size() % 4 == 0)
							a = Color(0.0f, 1.0f, 1.0f, 0.0f);

						PointLight* point = new PointLight();
						point->Position =  Sandbox->GetActiveCamera()->Position + (Sandbox->GetActiveCamera()->GetDirection() * 10.0f);
						point->Ambient = a;
						point->Diffuse = a;
						point->Specular = a;
						point->Radius = 50.0f;

						Director->AddLight(Sandbox, point);

						Lights.push_back(point);
				}

				break;
			case EVENT_MOUSEUP:
				isMouseDown = false;
				break;
		}
	}

	bool IsKeyDown(char key)
	{
		return IsKeyDown((int)key);
	}

	bool IsMouseDown()
	{
		return isMouseDown;
	}

	bool IsKeyDown(int key)
	{
		int keyCode = (int)key;

		for(int i = 0; i < keyDown.size(); i++)
			if ((int)keyDown[i] == keyCode)
				return true;

		return false;
	}

private:
	std::vector<char> keyDown;
	bool isMouseDown;
};

Kernel_Params CreateParams(int argc, char *argv[])
{
	Kernel_Params params;

	if (argc > 1)
		params.hWnd = (HWND)atoi(argv[1]); 

	if (argc > 2)
		params.Size.Width = atoi(argv[2]);

	if (argc > 3)
		params.Size.Height = atoi(argv[3]); 

	if (argc > 4)
		if (atoi(argv[4]) != 1)
			params.ShowConsole = false; 

	params.Fullscreen = false;

	if (params.Fullscreen)
	{
		RECT rc;
		GetWindowRect(GetDesktopWindow(), &rc);
		params.Size = Dimension2<int>(rc.right, rc.bottom);
	}

	return params;
}

int main(int argc, char *argv[])
{
	// Initialise Kernel..
	Kernel_Params Params = CreateParams(argc, argv);
	Kernel = CreateKernel(Params);

	if(!Kernel->Init())
	{
		int var;
		std::cin >> var;
		return 1;
	}

	Kernel->SetWindowTitle("Brainstem Sandbox Development Platform - [ALPHA_BUILD]");
	Kernel->SetMouseVisible(false);

	// Initialise Event Manager
	EventManager eventManager;
	Kernel->SetEventManager(&eventManager);

	// Get Device and Director Interfaces..
	Device = Kernel->GetDevice();
	Director = Kernel->GetDirector();

	// Create a scene..
	Sandbox = Director->CreateScene();

	// Setup a rendering display with effects..
	StaticDisplay = Director->CreateDisplay();
	StaticDisplay->Background = Color(1.0f,1.0f, 1.0f, 1.0f);

	DynamicDisplayA = Director->CreateDisplay();
	DynamicDisplayA->Position = Vector3<float>(0.0f, 0.0f, 2.0f);
	DynamicDisplayA->AddEffect("Water");
	DynamicDisplayA->AddEffect("Greyscale");

	DynamicDisplayB = Director->CreateDisplay();
	DynamicDisplayB->Filter = Color(1.0f, 0.5f, 0.5f, 0.5f);
	DynamicDisplayB->Position = Vector3<float>(0.0f, 0.0f, 1.0f);
	DynamicDisplayB->AddEffect("Sharpen");

	// Create some cameras..
	ICamera* CameraStatic = Director->AddStaticCamera(Sandbox, Vector3<float>(0.0f,0.0f,0.0f));
	CameraStatic->Position = Vector3<float>(40.0f,10.0f,40.0f);

	ICamera* Camera = Director->AddFirstPersonCamera(Sandbox, 0.3f, 40.0f, true);
	Camera->Position = Vector3<float>(0.0f,20.0f,0.0f);

	ICamera* CameraTerrain = Director->AddFirstPersonCamera(Sandbox, 0.3f, 40.0f, false);
	CameraTerrain->Position = Vector3<float>(0.0f,10.0f,0.0f);

	// Add some lights to the scene..
	EnvironmentLight = new PointLight();
	EnvironmentLight->Position = Vector3<float>(0.0f, 50.0f, 0.0f);
	EnvironmentLight->Ambient = Color(1.0f, 1.0f, 1.0f, 1.0f);
	EnvironmentLight->Diffuse = Color(1.0f, 1.0f, 1.0f, 1.0f);
	EnvironmentLight->Radius = 300.0f;

	Director->AddLight(Sandbox, EnvironmentLight);

	FlashLight = new SpotLight();
	FlashLight->Enabled = false;
	FlashLight->Position = Vector3<float>(0.0f, 0.0f, 0.0f);
	FlashLight->Ambient = Color(1.0f, 1.0f, 1.0f, 1.0f);
	FlashLight->Diffuse = Color(1.0f, 1.0f, 1.0f, 1.0f);
	FlashLight->Specular = Color(1.0f, 1.0f, 1.0f, 1.0f);
	FlashLight->InnerCone = BSTM_TO_RAD(30.0f);
	FlashLight->OuterCone = BSTM_TO_RAD(50.0f);
	FlashLight->Radius = 50.0f;

	Director->AddLight(Sandbox, FlashLight);

	// Generate some random terrain..
	Terrain = Director->AddTerrainRandom(Sandbox, 30,(int)(rand() % 2000), 1.5f, 0.6f, 5, 2.0f);
	int boundaryOffset = 14.0f;
	Dimension2<int> terrainBounds = Terrain->GetDimensions() / 2.0f;

	// Create the encompassing environment box..
	ISceneNode* SkyBox = Director->AddBox(Sandbox, 1.0f, 1.0f, 1.0f);
	SkyBox->Scale = Vector3<float>(500.0f, 500.0f, 500.0f);
	SkyBox->CullMode = CULL_CW;
	SkyBox->TextureScaling = Vector2<float>(-0.1f, 0.1f);

	// Add the player weapons..
	GodHand = new HandOfGod(Kernel, Director, Sandbox);
	AK47u = new AK47U(Kernel, Director, Sandbox);
	AK47u->IsActive = true;

	showInfo = true;

	std::stringstream infoText;
	std::string currentWeapon;

	infoText << "Brainstem Sandbox Test Environment" << std::endl << std::endl;
	infoText << "Look :: Mouse" << std::endl;
	infoText << "Move :: W, A, S, D Keys" << std::endl;
	infoText << "Shoot :: Left Click" << std::endl;
	infoText << "Switch Weapon :: Mouse Scroll / Space" << std::endl;
	infoText << "Change Camera :: P" << std::endl;
	infoText << "Pause :: Esc" << std::endl << std::endl;
	infoText << "Show / Hide This Information :: I" << std::endl << std::endl;

	infoText << "Create Coloured Light (Max. 4) :: Right Click" << std::endl;
	infoText << "Toggle Overhead Ambient Light :: L" << std::endl;
	infoText << "Toggle Weapon Flashlight :: F" << std::endl << std::endl;

	infoText << "Toggle Normal / Diffuse Mapping :: M" << std::endl;
	infoText << "Toggle Render Mode :: R" << std::endl << std::endl;

	infoText << "Show Camera Culling Process :: C" << std::endl;
	infoText << "Regenerate Random Terrain :: T" << std::endl;
	infoText << "Toggle Advanced Display Mode :: X" << std::endl << std::endl;

	// Start the main game loop..
	while(Kernel->Run())
	{
		Vector3<float> CameraPos = Sandbox->GetActiveCamera()->Position;

		// Set camera WASD control..
		if (CameraStatic != Sandbox->GetActiveCamera())
		{
			if (eventManager.IsKeyDown('w'))
				Sandbox->GetActiveCamera()->Velocity += Vector3<float>(0.0f, 0.0f, -1.0f);
			if (eventManager.IsKeyDown('s'))
				Sandbox->GetActiveCamera()->Velocity += Vector3<float>(0.0f, 0.0f, 1.0f);
			if (eventManager.IsKeyDown('d'))
				Sandbox->GetActiveCamera()->Velocity += Vector3<float>(-1.0f, 0.0f, 0.0f);
			if (eventManager.IsKeyDown('a'))
				Sandbox->GetActiveCamera()->Velocity += Vector3<float>(1.0f, 0.0f, 0.0f);
		}

		// Set camera and light positions..
		FlashLight->Direction = CameraTerrain->GetDirection();
		FlashLight->Position = CameraTerrain->Position;

		CameraStatic->Position = Vector3<float>((sinf(Kernel->GetFrameIndex()/5000.0f)*200.0f), 80.0f, (cosf(Kernel->GetFrameIndex()/5000.0f)*200.0f));

		// If mouse is down, Fire the active weapon..
		if (eventManager.IsMouseDown())
		{
			if (AK47u->IsActive)
				AK47u->Fire();

			if (GodHand->IsActive)
				GodHand->Fire();
		}

		if (AK47u->IsActive)
			currentWeapon = "AK47u";
		else
			currentWeapon = "Hand of God";

		std::stringstream displayText;

		displayText << infoText.str();
		displayText << "Current Weapon: " << currentWeapon << std::endl;
		displayText << "Current Position: " << Sandbox->GetActiveCamera()->Position.X << ", " << Sandbox->GetActiveCamera()->Position.Y << ", " << Sandbox->GetActiveCamera()->Position.Z << std::endl;

		// Update the weapons..
		AK47u->Update(CameraTerrain);
		GodHand->Update(CameraTerrain);

		// Bounds checking..
		if (CameraPos.Z < -(terrainBounds.Width - boundaryOffset))
		{
			Sandbox->GetActiveCamera()->Position.Z = -(terrainBounds.Width - boundaryOffset);
			Sandbox->GetActiveCamera()->Velocity.Z = 0.0f;
		} else if (CameraPos.Z > (terrainBounds.Width - boundaryOffset))
		{
			Sandbox->GetActiveCamera()->Position.Z = (terrainBounds.Width - boundaryOffset);
			Sandbox->GetActiveCamera()->Velocity.Z = 0.0f;
		}

		if (CameraPos.X < -(terrainBounds.Height - boundaryOffset))
		{
			Sandbox->GetActiveCamera()->Position.X = -(terrainBounds.Height - boundaryOffset);
			Sandbox->GetActiveCamera()->Velocity.X = 0.0f;
		} else if (CameraPos.X > (terrainBounds.Height - boundaryOffset))
		{
			Sandbox->GetActiveCamera()->Position.X = (terrainBounds.Height - boundaryOffset);
			Sandbox->GetActiveCamera()->Velocity.X = 0.0f;
		}

		CameraTerrain->Position.Y = Terrain->GetHeight(CameraTerrain->Position.X, 
			CameraTerrain->Position.Z) + 8.0f;

		// Render the different display modes..
		switch(DisplayMode)
		{
			case 0:
				Device->BeginRender(StaticDisplay);
					Director->Render(Sandbox);
					if (showInfo)
						Device->DrawString(displayText.str().c_str(), Vector2<int>(5, 30));
				Device->EndRender();

				break;
			case 1:
				
				DynamicDisplayA->Rotation += Vector3<float>(0.008f, 0.01f,0.0f);
				DynamicDisplayA->Position = Vector3<float>(sinf(Kernel->GetFrameIndex()/800.0f), cosf(Kernel->GetFrameIndex()/800.0f), 2.0f);

				Device->BeginRender(DynamicDisplayA);
					Director->Render(Sandbox);
					if (showInfo)
						Device->DrawString(displayText.str().c_str(), Vector2<int>(5, 30));
				Device->EndRender();

				Device->BeginRender(DynamicDisplayB);
					Director->Render(Sandbox);
					if (showInfo)
						Device->DrawString(displayText.str().c_str(), Vector2<int>(5, 30));
				Device->EndRender();

				break;
		}

		// Present to the screen..
		Device->Present();
	}

	// Shutdown and release all objects..
	Kernel->Release();

	return(0);
}