// This file is part of ThriceBall.
//
// Author: Kai Hillenbrand (AnTeevY)
// © Copyright, 2009 by Kai Hillenbrand, http://www.anteevy.de

// --------------------------------------------------

#include "level.h"

CLevel::CLevel(CRenderer *renderer, NxOgre::Scene *scene, OGRE3DRenderSystem *rendersys)
: m_pRenderer(renderer), m_pScene(scene), m_pSceneMgr(renderer->getSceneManager()), m_pRenderSystem(rendersys),
m_pPlayer(renderer->getPlayer()), m_sLevelName(""), m_sBackgroundName(""), m_iBallStartNum(0)
{
	std::cout << "ThriceBall Log: Created Level" << std::endl;	// for the log
}

void CLevel::restart()
{
	std::cout << "ThriceBall Log: Restart Level" << std::endl;	// for the log

	m_pPlayer->setBallPos(m_vStartPos);		// set the ball's start position
	// set the ball's respawn position (and 0 means its a start checkpoint)
	m_pPlayer->setRespawnPos(m_vStartPos, 0);
	m_pPlayer->setCurBall(m_iBallStartNum);	// set the ball number
	loadContent(true);						// load only actors
}

void CLevel::nextCheckpoint()
{
	// parse through the checkpoints
	for (int i = 0; i < int(m_vpCheckpointList.size()); i++)
	{
		// find out which is the current respawn checkpoint
		if (m_vpCheckpointList[i] == m_pCurCheckpoint)
		{
			// if currently on the last checkpoint, go to first one
			if ((i + 1) == int(m_vpCheckpointList.size()))
				m_pCurCheckpoint = m_vpCheckpointList[0];
			else	// else, just take the next one
				m_pCurCheckpoint = m_vpCheckpointList[i + 1];

			// set the ball's start (and because its a checkpoint, too its respawn) position
			m_pPlayer->setBallPos(m_pCurCheckpoint->getRespawnPos());
			break;
		}
	}
}

void CLevel::loadLevel(String name)
{
	std::cout << "ThriceBall Log: Load Level" << std::endl;	// for the log

	// create nxs and mesh name by parameter
	m_sLevelName = name;	

	loadContent(false);	// load complete content, not only actors
	loadLevelMesh();
	createSky();
}

void CLevel::loadLevelMesh()
{
	std::cout << "ThriceBall Log: Load Level Mesh" << std::endl;	// for the log

	// prepare resources
	String sNxs = "levels:" + m_sLevelName + ".nxs";
	NxOgre::ArchiveResourceIdentifier nxs(sNxs.c_str());
	String sMesh = m_sLevelName + ".mesh";
	NxOgre::ResourceSystem::getSingleton()->openArchive("levels", "file:levels");

	// load the .nxs file
	NxOgre::Mesh* triangleMesh = NxOgre::MeshManager::getSingleton()->load(nxs);
	NxOgre::TriangleGeometry* triangleGeometry = new NxOgre::TriangleGeometry(triangleMesh);
	m_pScene->createSceneGeometry(triangleGeometry, NxOgre::Matrix44(NxOgre::Real3(0, 0, 0)));
	
	// create the visible mesh
	Ogre::Entity* triangleEntity = m_pSceneMgr->createEntity("Level", sMesh);
	triangleEntity->setCastShadows(m_pRenderer->shadowsEnabled());
	Ogre::SceneNode* triangleNode = m_pSceneMgr->getRootSceneNode()->createChildSceneNode();
	triangleNode->attachObject(triangleEntity);
	triangleNode->setPosition(Vector3(0, 0, 0));
}

void CLevel::createSky()
{		
	std::cout << "ThriceBall Log: Create Sky" << std::endl;	// for the log

	// foreground plane
	Plane skyPlane(Vector3::UNIT_Y, -100);
	MeshManager::getSingleton().createPlane("SkyPlane",
	ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, skyPlane,
	5000, 5000, 1, 1, true, 1, 50, 50, Vector3::UNIT_Z);

	Entity *skyPlaneEnt = m_pSceneMgr->createEntity("SkyPlaneEnt", "SkyPlane");
	m_pSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(skyPlaneEnt);

	skyPlaneEnt->setMaterialName(m_sBackgroundName);
	skyPlaneEnt->setCastShadows(false);
}

void CLevel::loadContent(bool actorsOnly)
{
	std::cout << "ThriceBall Log: Load Content" << std::endl;	// for the log

	// little xml file overview:
	// Level
	// -- Data
	// ----- Start (pX, pY, pZ, num)
	// ----- Finish (pX, pY, pZ)
	// ----- DeathPlane (pY)
	// ----- Background <name>
	// ----- Energy (shot, brake, death, time)
	// -- Actors
	// ----- Box (pX, pY, pZ, sX, sY, sZ, mass)
	// ----- Sphere (pX, pY, pZ, rad, mass)
	// -- Content
	// ----- Checkpoint (pX, pY, pZ)
	// ----- Platform (pX, pY, pZ, pX2, pY2, pZ2, speed, wait, sX, sY, sZ)
	// ----- Gate (pX, pY, pZ, color)
	// ----- Jumppad (pX, pY, pZ, push, size)
	// ----- Drainer (pX, pY, pZ, amount, sX, sZ)
	// ----- Info <Text> (pX, pY, pZ, size)


	String filename = "levels/" + m_sLevelName + ".xml";

	// load the xml doc and setup variables
	TiXmlDocument doc(filename.c_str());
	doc.LoadFile();

	TiXmlHandle hDoc(&doc);
	TiXmlElement *elemLevel = hDoc.FirstChildElement().Element();
	TiXmlElement *elemData = hDoc.FirstChildElement().FirstChild("Data").Element();
	TiXmlElement *elemActors = hDoc.FirstChildElement().FirstChild("Actors").Element();
	TiXmlElement *elemContent = hDoc.FirstChildElement().FirstChild("Content").Element();

	// declare variables for all three sections
	float pX = 0, pY = 0, pZ = 0;
	CCheckpoint *chkpnt = 0;

	if (!actorsOnly)	// do not read in data if actors only
	{
		// read in the data section
		int ballnum = 0;
		String dataName = "";
		TiXmlElement *elemDataSub = elemData->FirstChildElement();		
		m_pDeathVolume = 0;
		float shot = 5.f, brake = 0.05f, death = 10.f, time = 0.01f;
		
		// parse through the data members
		for (elemDataSub; elemDataSub; elemDataSub = elemDataSub->NextSiblingElement())
		{
			// reset the variables
			pX = pY = pZ = 0;
			dataName = "";
			chkpnt = 0;

			// read in common values
			dataName = elemDataSub->Value();
			elemDataSub->QueryFloatAttribute("pX", &pX);
			elemDataSub->QueryFloatAttribute("pY", &pY);
			elemDataSub->QueryFloatAttribute("pZ", &pZ);

			if (dataName == "Start")
			{			
				elemDataSub->QueryIntAttribute("num", &ballnum);	// read in the starting ball number

				// save values for possible later restart
				m_vStartPos = NxOgre::Vec3(pX, pY + 1, pZ);
				m_iBallStartNum = ballnum;

				chkpnt = new CCheckpoint(m_pRenderer, this, m_pScene, m_pRenderSystem);
				chkpnt->create(pX, pY, pZ, 0);			// type 0 means start checkpoint
				m_vpCheckpointList.push_back(chkpnt);	// add the checkpoint to the list
				m_pPlayer->setBallPos(m_vStartPos);		// set the ball's start position				
				m_pPlayer->setRespawnPos(m_vStartPos, chkpnt->getType());	// set the ball's respawn position
				m_pPlayer->setCurBall(m_iBallStartNum);	// set the ball number			
			}
			else if (dataName == "Finish")
			{
				chkpnt = new CCheckpoint(m_pRenderer, this, m_pScene, m_pRenderSystem);
				chkpnt->create(pX, pY, pZ, 1);			// type 1 means finish checkpoint
			}
			else if (dataName == "DeathPlane")
			{
				m_pDeathVolume = m_pScene->createVolume(new NxOgre::Box(1000,1,1000), 
					NxOgre::Matrix44(NxOgre::Real3(0, pY, 0)),	this, NxOgre::Enums::VolumeCollisionType_OnEnter);
			}
			else if (dataName == "Background")
			{
				m_sBackgroundName = elemDataSub->GetText();
			}
			else if (dataName == "Energy")
			{
				elemDataSub->QueryFloatAttribute("shot", &shot);
				elemDataSub->QueryFloatAttribute("brake", &brake);
				elemDataSub->QueryFloatAttribute("death", &death);
				elemDataSub->QueryFloatAttribute("time", &time);

				m_pPlayer->setEnergies(shot, brake, death, time);
			}
		} // data section
	} // if not actors only
	
	// read in the actors section
	TiXmlElement *elemActorsSub = elemActors->FirstChildElement();
	float sX = 1, sY = 1, sZ = 1, mass = 1, rad = 1;
	OGRE3DBody *actor = 0;
	String actorName;

	if (actorsOnly)	// if only actors, delete all previous actors before reloading them
	{
		for (int i = 0; i < int(m_vpActorList.size()); i++)
		{
			m_pRenderSystem->destroyBody(m_vpActorList[i]);
			m_vpActorList[i] = 0;
		}
		m_vpActorList.clear();
	}

	// parse through the actors members
	for (elemActorsSub; elemActorsSub; elemActorsSub = elemActorsSub->NextSiblingElement())
	{
		// reset the variables
		pX = pY = pZ = 0;
		sX = sY = sZ = mass = rad = 1;
		actorName = "";
		
		// read in common values
		actorName = elemActorsSub->Value();
		elemActorsSub->QueryFloatAttribute("pX", &pX);
		elemActorsSub->QueryFloatAttribute("pY", &pY);
		elemActorsSub->QueryFloatAttribute("pZ", &pZ);		
		elemActorsSub->QueryFloatAttribute("mass", &mass);
				
		// check which model should be used
		if (actorName == "Box")
		{
			// read in box scale
			elemActorsSub->QueryFloatAttribute("sX", &sX);
			elemActorsSub->QueryFloatAttribute("sY", &sY);
			elemActorsSub->QueryFloatAttribute("sZ", &sZ);

			// create the box and add it to the actor list
			actor = m_pRenderSystem->createBody(new NxOgre::Box(NxOgre::Vec3(sX, sY, sZ)), 
				NxOgre::Real3(pX, pY, pZ), "cube.1m.mesh");
			actor->getEntity()->getParentSceneNode()->scale(sX, sY, sZ);	// scale the mesh
			m_vpActorList.push_back(actor);
		}
		else if (actorName == "Sphere")
		{
			// read in sphere radius
			elemActorsSub->QueryFloatAttribute("rad", &rad);

			// create the sphere and add it to the actor list
			actor = m_pRenderSystem->createBody(new NxOgre::Sphere(rad), 
				NxOgre::Real3(pX, pY, pZ), "ball.mesh");
			actor->getEntity()->getParentSceneNode()->scale(2 * rad, 2 * rad, 2 * rad);	// scale the mesh
			actor->setAngularDamping(10);
			m_vpActorList.push_back(actor);
		}			
		
		actor->setMass(mass);	// set the actor's mass
	}

	if (!actorsOnly)	// do not read in content if actors only
	{
		// read in the content section
		TiXmlElement *elemContentSub = elemContent->FirstChildElement();
		String contentName = "";
		float pX2 = 0, pY2 = 0, pZ2 = 0;
		float speed = 0, wait = 0, gateColor = 0, push = 0, amount = 0, size = 1;
		CPlatform *pltfrm = 0;
		CGate *gate = 0;
		CJumppad *jmppd = 0;
		CDrainer *drnr = 0;
		CInfo *info = 0;
		String infoText = "";
		String lightName = "";
		bool lightExists = true;
		Light *worldLight = 0;
		float red = 0, green = 0, blue = 0;

		// parse through the content members
		for (elemContentSub; elemContentSub; elemContentSub = elemContentSub->NextSiblingElement())
		{
			// reset the variables
			pX = pY = pZ = pX2 = pY2 = pZ2 = size = 0;
			sX = sY = sZ = speed = 1;
			contentName = "";
			chkpnt = 0;
			pltfrm = 0;
			gate = 0;
			jmppd = 0;
			drnr = 0;
			info = 0;
			wait = gateColor = push = amount = 0;

			// read in common values
			contentName = elemContentSub->Value();
			elemContentSub->QueryFloatAttribute("pX", &pX);
			elemContentSub->QueryFloatAttribute("pY", &pY);
			elemContentSub->QueryFloatAttribute("pZ", &pZ);

			if (contentName == "Checkpoint")	// if it's a checkpoint
			{			
				chkpnt = new CCheckpoint(m_pRenderer, this, m_pScene, m_pRenderSystem);
				chkpnt->create(pX, pY, pZ, 2);			// type 2 means normal checkpoint
				m_vpCheckpointList.push_back(chkpnt);	// add the checkpoint to the list
			}
			else if (contentName == "Platform")	// if it's a platform
			{
				// read in platform values
				elemContentSub->QueryFloatAttribute("pX2",	&pX2);
				elemContentSub->QueryFloatAttribute("pY2",	&pY2);
				elemContentSub->QueryFloatAttribute("pZ2",	&pZ2);
				elemContentSub->QueryFloatAttribute("sX",	&sX);
				elemContentSub->QueryFloatAttribute("sY",	&sY);
				elemContentSub->QueryFloatAttribute("sZ",	&sZ);
				elemContentSub->QueryFloatAttribute("speed",&speed);
				elemContentSub->QueryFloatAttribute("wait", &wait);

				pltfrm = new CPlatform(m_pRenderer, this, m_pScene, m_pRenderSystem);
				pltfrm->create(pX, pY, pZ, pX2, pY2, pZ2, speed, wait, sX, sY, sZ);
				m_vpPlatformList.push_back(pltfrm);
			}
			else if (contentName == "Gate")
			{
				// read in the gate's color value
				elemContentSub->QueryFloatAttribute("color",&gateColor);

				gate = new CGate(m_pRenderer, this, m_pScene, m_pRenderSystem);
				gate->create(pX, pY, pZ, gateColor);
			}
			else if (contentName == "Jumppad")
			{
				// read in the jumppad's push strength and size values
				elemContentSub->QueryFloatAttribute("push", &push);
				elemContentSub->QueryFloatAttribute("size", &size);
			
				jmppd = new CJumppad(m_pRenderer, this, m_pScene, m_pRenderSystem);
				jmppd->create(pX, pY, pZ, push, size);
			}
			else if (contentName == "Drainer")
			{
				// read in the drainers's drain and size values
				elemContentSub->QueryFloatAttribute("amount", &amount);
				elemContentSub->QueryFloatAttribute("sX", &sX);
				elemContentSub->QueryFloatAttribute("sZ", &sZ);
			
				drnr = new CDrainer(m_pRenderer, this, m_pScene, m_pRenderSystem);
				drnr->create(pX, pY, pZ, amount, sX, sZ);
			}
			else if (contentName == "Info")
			{
				// read in the info text and size
				infoText = elemContentSub->GetText();
				elemContentSub->QueryFloatAttribute("size", &size);
			
				info = new CInfo(m_pRenderer, this, m_pScene, m_pRenderSystem);
				info->create(pX, pY, pZ, size, infoText);
			}
			else if (contentName == "Lamp")
			{
				// read in color values
				elemContentSub->QueryFloatAttribute("red",	&red);
				elemContentSub->QueryFloatAttribute("green",&green);
				elemContentSub->QueryFloatAttribute("blue",	&blue);

				// avoid twice the same light name
				lightName = "";
				lightExists = true;

				while (lightExists)
				{
					lightName = "Light_" + StringConverter::toString(Ogre::Math::RangeRandom(0,10000));
					lightExists = m_pSceneMgr->hasLight(lightName);
				}

				// create the light
				worldLight = m_pSceneMgr->createLight(lightName);
				worldLight->setDiffuseColour(red, green, blue);	
				worldLight->setPosition(pX, pY, pZ);
			}
		} // content section
	} // if not actors only
}

void CLevel::onVolumeEvent(NxOgre::Volume* volume, NxOgre::Shape* volumeShape, NxOgre::RigidBody* rigidBody, 
		NxOgre::Shape* rigidBodyShape, unsigned int collisionEvent)
{
	NxOgre::Actor* actor = static_cast<NxOgre::Actor*>(rigidBody);

	// if ball enters the death volume, it dies and gets respawned
	if((collisionEvent == NxOgre::Enums::VolumeCollisionType_OnEnter) && (volume == m_pDeathVolume))
	{
		if (actor == m_pPlayer->getCurBall())
			m_pPlayer->respawn();
	}
}

void CLevel::movePlatforms()
{
	// parse through the platforms and move them
	for (int i = 0; i < int(m_vpPlatformList.size()); i++)
	{
		m_vpPlatformList[i]->move();
	}
}