#include <OgreTexture.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreTextureManager.h>
#include <OgreLogManager.h>
#include <OgreSimpleRenderable.h>
#include "OgreD3D9Texture.h"
#include "OgreD3D9HardwareVertexBuffer.h"
#include "OgreSubMesh.h"
#include "OgreMesh.h"
#include "OgreRenderWindow.h"
#include "ogre.h"

#include <sstream>
#include <fstream>
#include <string>
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#define OIS_DYNAMIC_LIB
#include <OIS/OIS.h>

using namespace Ogre;
using namespace std;

class GridLight
{
public:
	Vector3 pos;
	float brightness;
	int type;
};

namespace
{
	HINSTANCE g_hinst;
	SceneManager *g_sceneManager;
	RenderWindow *g_window;
	Viewport *g_viewport;
	Root *g_root;
	OIS::Keyboard *g_keyboard;
	OIS::Mouse *g_mouse;
	const double g_updateRate = 1.0/60.0;
	bool g_running = true;
	Timer g_timer;
	Camera *g_camera;
	SceneNode *node;
	SceneNode *car;
	SceneNode *carcam;
	OIS::InputManager *g_inputManager;
	GridLight *g_lights;
	int gridsize=16;
}


bool init();
bool update(double deltaT);
void shutdown();

INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
{
	srand((unsigned int)time(NULL));
	try 
	{
		Ogre::ConfigFile cf;
		g_root = new Ogre::Root("plugins.cfg","config.cfg","ogre.log");
		cf.load("resources.cfg");

		Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
		Ogre::String secName, typeName, archName;
		while (seci.hasMoreElements())
		{
			secName = seci.peekNextKey();
			Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
			Ogre::ConfigFile::SettingsMultiMap::iterator i;
			for (i = settings->begin(); i != settings->end(); ++i)
			{
				typeName = i->first;
				archName = i->second;
				Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
					archName, typeName, secName);
			}
		}
		if(g_root->showConfigDialog())
		{
			g_window = g_root->initialise(true, "Base");
		}
		else
		{
			return 1;
		}

		Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
		g_sceneManager = g_root->createSceneManager("OctreeSceneManager", "SceneManager");

		g_camera = g_sceneManager->createCamera("PlayerCamera");
		g_camera->setPosition(Ogre::Vector3(-1000,1000,700));
		g_camera->lookAt(Ogre::Vector3(500,0,700));
		g_camera->setNearClipDistance(0.1);
		g_camera->setFarClipDistance(10000);

		g_viewport = g_window->addViewport(g_camera);
		g_viewport->setBackgroundColour(Ogre::ColourValue(0,0,1));
		g_window->resetStatistics();

		OIS::ParamList pl;
		size_t windowHnd = 0;
		std::ostringstream windowHndStr;

		g_window->getCustomAttribute("WINDOW", &windowHnd);
		windowHndStr << windowHnd;
		pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));

		g_inputManager = OIS::InputManager::createInputSystem( pl );

		g_keyboard = static_cast<OIS::Keyboard*>(g_inputManager->createInputObject( OIS::OISKeyboard, false ));
		g_mouse = static_cast<OIS::Mouse*>(g_inputManager->createInputObject( OIS::OISMouse, false ));


		if(!init())
		{
			return 1;
		}
		double elapsed = 0;
		g_timer.reset();
		unsigned long lastTime = g_timer.getMicroseconds();
		while(g_running)
		{
			unsigned long currentTime = g_timer.getMicroseconds();
			double deltaTime = (currentTime - lastTime)/1000000.0;
			lastTime = currentTime;
			if(deltaTime>1.0) deltaTime = 1.0;
			elapsed+=deltaTime;
			while(elapsed>=g_updateRate)
			{
				elapsed-=g_updateRate;
				if(!update(g_updateRate)) 
				{
					g_running = false;
					break;
				}
			}
			Ogre::WindowEventUtilities::messagePump();
			if(g_window->isClosed())
			{
				break;
			}
			g_root->renderOneFrame();
			Sleep(0);
		}
		shutdown();
	} 
	catch( Ogre::Exception& e ) 
	{
		MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
	}

	return 0;
}

bool init()
{
	g_sceneManager->setAmbientLight(ColourValue::White*0.0);

	Entity *ent;
	car = g_sceneManager->getRootSceneNode()->createChildSceneNode();
	ent = g_sceneManager->createEntity("car","car.mesh");
	car->attachObject(ent);
	ent->setMaterialName("gridlight");
	car->setPosition(500,4,600);
	carcam = car->createChildSceneNode();
	carcam->setFixedYawAxis(true);
	carcam->attachObject(g_camera);
	g_camera->setPosition(0,3,7);
	g_camera->setOrientation(Quaternion(Degree(-20),Vector3(1,0,0)));
	Light *light = g_sceneManager->createLight("headlight");
	car->attachObject(light);
	light->setPosition(0,1,-8);
	light->setType(Light::LT_POINT);
	light->setDiffuseColour(1,1,1);

	car->yaw(Degree(-90));

	node = g_sceneManager->getRootSceneNode()->createChildSceneNode();
	ent = g_sceneManager->createEntity("roads","roads.mesh");
	node->attachObject(ent);
	ent->setMaterialName("gridlight");
	node->setPosition(ent->getBoundingBox().getMinimum()*-1.0);

	g_sceneManager->setSkyBox(true,"FullMoonSky",5000);

	float cellx = ent->getBoundingBox().getSize().x/float(gridsize);
	float cellz = ent->getBoundingBox().getSize().z/float(gridsize);
	float worldx = ent->getBoundingBox().getSize().x;
	float worldz = ent->getBoundingBox().getSize().z;
	g_lights = new GridLight[gridsize*gridsize];
	for(int z=0;z<gridsize;z++)
	{
		for(int x=0;x<gridsize;x++)
		{
			float lx = Ogre::Math::RangeRandom(0,cellz)+cellz*x;
			float lz = Ogre::Math::RangeRandom(0,cellz)+cellz*z;
			g_lights[z*gridsize+x].pos = Vector3(lx,20,lz);
			g_lights[z*gridsize+x].brightness = 0.8;
			g_lights[z*gridsize+x].type = rand()%10;
		}
	}

	MaterialPtr mat = MaterialManager::getSingleton().getByName("gridlight");
	TextureUnitState *tu = mat->getBestTechnique()->getPass(0)->getTextureUnitState(0);
	TexturePtr tex = TextureManager::getSingleton().createManual("gridlight","General",TextureType::TEX_TYPE_2D, gridsize,gridsize,1,0,PixelFormat::PF_FLOAT32_RGBA);
	Vector4 *p = (Vector4 *)tex->getBuffer()->lock(0,gridsize*gridsize*16,HardwareBuffer::LockOptions::HBL_NORMAL);

	for(int z=0;z<gridsize;z++)
	{
		for(int x=0;x<gridsize;x++)
		{
			p[z*gridsize+x] = Vector4(g_lights[z*gridsize+x].pos.x,g_lights[z*gridsize+x].pos.y,g_lights[z*gridsize+x].pos.z,g_lights[z*gridsize+x].brightness);
		}
	}
	tex->getBuffer()->unlock();
	tu->setTextureName("gridlight");



	return true;
}


bool update(double deltaT)
{
	g_keyboard->capture();
	g_mouse->capture();
	carcam->yaw(Degree(g_mouse->getMouseState().X.rel*-10)*deltaT,Node::TS_WORLD);
	carcam->pitch(Degree(g_mouse->getMouseState().Y.rel*-10)*deltaT,Node::TS_LOCAL);
	float speed=100;
	if(g_keyboard->isKeyDown(OIS::KC_W))
	{
		g_camera->moveRelative(Vector3(0,0,-speed)*deltaT);
	}
	if(g_keyboard->isKeyDown(OIS::KC_S))
	{
		g_camera->moveRelative(Vector3(0,0,speed)*deltaT);
	}
	if(g_keyboard->isKeyDown(OIS::KC_A))
	{
		g_camera->moveRelative(Vector3(-speed,0,0)*deltaT);
	}
	if(g_keyboard->isKeyDown(OIS::KC_D))
	{
		g_camera->moveRelative(Vector3(speed,0,0)*deltaT);

	}
	if(g_keyboard->isKeyDown(OIS::KC_LEFT))
	{
		car->yaw(Degree(90*deltaT));
	}
	if(g_keyboard->isKeyDown(OIS::KC_RIGHT))
	{
		car->yaw(Degree(-90*deltaT));
	}
	if(g_keyboard->isKeyDown(OIS::KC_UP))
	{
		car->translate(Vector3(0,0,-30*deltaT),Node::TS_LOCAL);
	}
	if(g_keyboard->isKeyDown(OIS::KC_DOWN))
	{
		car->translate(Vector3(0,0,30*deltaT),Node::TS_LOCAL);
	}
static bool pressedprint=false;
	if(g_keyboard->isKeyDown(OIS::KC_SYSRQ))
	{
		if(!pressedprint)
		{
			pressedprint = true;
		char name[1000];
		sprintf(name,"screenshot_%d.jpg",time(0));
		g_window->writeContentsToFile(name);
		}
	}
	else
	{
		pressedprint = false;
	}

	if(g_keyboard->isKeyDown(OIS::KC_ESCAPE))
	{
		return false;
	}
	static float swing=0;
	swing+=deltaT;
	
	TexturePtr tex = TextureManager::getSingleton().getByName("gridlight","General");
	Vector4 *p = (Vector4 *)tex->getBuffer()->lock(0,gridsize*gridsize*16,HardwareBuffer::LockOptions::HBL_NORMAL);
	for(int z=0;z<gridsize;z++)
	{
		for(int x=0;x<gridsize;x++)
		{
			if(g_lights[z*gridsize+x].type == 9)
			{
				p[z*gridsize+x] = Vector4(g_lights[z*gridsize+x].pos.x,g_lights[z*gridsize+x].pos.y,g_lights[z*gridsize+x].pos.z,g_lights[z*gridsize+x].brightness*((rand()+x+z)%2));
			}
			if(g_lights[z*gridsize+x].type == 8)
			{
				p[z*gridsize+x] = Vector4(g_lights[z*gridsize+x].pos.x+cos(swing+x+z)*100,g_lights[z*gridsize+x].pos.y,g_lights[z*gridsize+x].pos.z,g_lights[z*gridsize+x].brightness);
			}
			if(g_lights[z*gridsize+x].type == 7)
			{
				p[z*gridsize+x] = Vector4(g_lights[z*gridsize+x].pos.x,g_lights[z*gridsize+x].pos.y,g_lights[z*gridsize+x].pos.z+cos(swing*0.5+x+z)*20,g_lights[z*gridsize+x].brightness);
			}
		}
	}
	tex->getBuffer()->unlock();

	return true;
}

void shutdown()
{
}
