// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.



#include "stdpch.h"

#include "reynolds_manager.h"

#include "nel/georges/u_form_loader.h"
#include "nel/georges/load_form.h"

using namespace std;
using namespace NLMISC;
using namespace NLPACS;
using namespace NLGEORGES;


/*
 * Manager variables
 */

// Continents
CContinentContainer							CReynoldsManager::_Continents;

// Track Map, all tracks referenced, held by a simple pointer so when no one references a track, it is deleted
CReynoldsManager::TTrackMap					CReynoldsManager::_TrackMap;

// Controlled Track Map, only controlled tracks, held by a smart pointer
CReynoldsManager::TControlledTrackMap		CReynoldsManager::_ControlledTrackMap;

// Command interface
CReynoldsManager::ICommandInterface			*CReynoldsManager::_CommandInterface = NULL;

// User init callback
CReynoldsManager::TUserCallback				CReynoldsManager::_UserInitCallback = NULL;

// User motion callback
CReynoldsManager::TUserMotionCallback		CReynoldsManager::_UserMotionCallback = NULL;

// User release callback
CReynoldsManager::TUserCallback				CReynoldsManager::_UserReleaseCallback = NULL;

// Georges sheets
map<CSheetId, CTrack::CSheet>				CReynoldsManager::_Sheets;

// Sheets initialised ?
bool										CReynoldsManager::_Initialised;

// Current internal cycle
uint32										CReynoldsManager::_Cycle;

/*
 * Manager methods
 */


// ------------------------------------
// Constructor
CReynoldsManager::CReynoldsManager()
{
	nlerror("CReynoldsManager: static library, must not be instanciated !");
}



// ------------------------------------
// Init
void	CReynoldsManager::init(const std::string &packSheetFile)
{
	_Continents.init(100, 100, 8.0, 1);

	initSheets(packSheetFile);

	_Cycle = 0;
}

// ------------------------------------
// Update
void	CReynoldsManager::update(double dt)
{
	TControlledTrackMap::iterator	it;
	for (it=_ControlledTrackMap.begin(); it!=_ControlledTrackMap.end(); ++it)
	{
		CTrack	*track = (CTrack*)((*it).second);
		track->update(dt);
	}

	++_Cycle;
}

// ------------------------------------
// Release
void	CReynoldsManager::release()
{
	_Continents.clear();

	_ControlledTrackMap.clear();

	if (!_TrackMap.empty())
	{
		nlwarning("ReynoldsLib:CReynoldsManager:release(): TrackMap not empty at release !!");

		TTrackMap::iterator	it;
		for (it=_TrackMap.begin(); it!=_TrackMap.end(); ++it)
			delete (*it).second;

		_TrackMap.clear();
	}
}



// ------------------------------------
// Load continent
void	CReynoldsManager::loadContinent(const std::string &name, const std::string &file, sint index)
{
	_Continents.loadContinent(name, file, index);
}





// ------------------------------------
// Create Track
CTrack	*CReynoldsManager::createTrack(const CEntityId &entity)
{
	// look for the target, and create a new track if not found
	TTrackMap::iterator	itt = _TrackMap.find(entity);
	if (itt == _TrackMap.end())
	{
		CTrack	*track = new CTrack();
		pair<TTrackMap::iterator, bool>	res = _TrackMap.insert(TTrackMap::value_type(entity, track));
		itt = res.first;

		requestSheet(entity);
	}
	return (*itt).second;
}

// ------------------------------------
// Remove Track from map
void	CReynoldsManager::removeTrackFromMap(const CEntityId &entity)
{
	_TrackMap.erase(entity);
}








// ------------------------------------
// Follow
void	CReynoldsManager::follow(const NLMISC::CEntityId &entity, const NLMISC::CEntityId &target)
{
	// look for the track, and create a new one if not found -- in both maps
	CTrack	*etrack = createTrack(entity);

	TControlledTrackMap::iterator	ite = _ControlledTrackMap.find(entity);
	if (ite == _ControlledTrackMap.end())
		_ControlledTrackMap.insert(TControlledTrackMap::value_type(entity, etrack));

	// look for the target, and create a new track if not found
	CTrack	*ttrack = createTrack(target);

	nldebug("ReynoldsLib:CReynoldsManager:follow(): %s now follows %s", entity.toString().c_str(), target.toString().c_str());

	// let entity follow the target
	etrack->follow(ttrack);
}

// ------------------------------------
// Go to
void	CReynoldsManager::goTo(const NLMISC::CEntityId &entity, const NLMISC::CVectorD &position)
{
	// look for the track, and create a new one if not found -- in both maps
	CTrack	*etrack = createTrack(entity);

	TControlledTrackMap::iterator	ite = _ControlledTrackMap.find(entity);
	if (ite == _ControlledTrackMap.end())
		_ControlledTrackMap.insert(TControlledTrackMap::value_type(entity, etrack));

	// create a fake track for point position
	static uint	i = 0;
	CEntityId	id(0, i++, 0, 0);
	CTrack	*pointTo = createTrack(id);
	pointTo->setStatic();
	pointTo->setId(id, CSheetId::Unknown);
	pointTo->setPosition(position, 0.0f);

	nldebug("ReynoldsLib:CReynoldsManager:goTo(): %s now goes to (%.1f,%.1f)", entity.toString().c_str(), position.x, position.y);

	// let entity follow the target
	etrack->follow(pointTo);
}

// ------------------------------------
// Stop
void	CReynoldsManager::leaveMove(const NLMISC::CEntityId &entity)
{
	TControlledTrackMap::iterator	ite = _ControlledTrackMap.find(entity);
	if (ite == _ControlledTrackMap.end())
	{
		nlwarning("ReynoldsLib:CReynoldsManager:leaveMove(): undefined entity %s", entity.toString().c_str());
		return;
	}

	nldebug("ReynoldsLib:CReynoldsManager:leaveMove(): %s control left", entity.toString().c_str());

	(*ite).second->leave();
	_ControlledTrackMap.erase(ite);
}

// ------------------------------------
// Destroy
void	CReynoldsManager::destroy(const NLMISC::CEntityId &entity)
{
	CTrack	*track = getTrack(entity);
	if (track != NULL)
		track->forceRelease();

	if (track != NULL && track->hasControlOwned())
		track->leave();

	_ControlledTrackMap.erase(entity);
}






// ------------------------------------
// Request Sheet
void	CReynoldsManager::requestSheet(const NLMISC::CEntityId &entity)
{
	if (!checkInterface())
		return;

	_CommandInterface->requestSheet(entity);
}

// ------------------------------------
// Request Position
void	CReynoldsManager::requestPosition(const NLMISC::CEntityId &entity)
{
	if (!checkInterface())
		return;

	_CommandInterface->requestPosition(entity);
}

// ------------------------------------
// Request Position Updates
void	CReynoldsManager::requestPositionUpdates(const NLMISC::CEntityId &entity)
{
	if (!checkInterface())
		return;

	_CommandInterface->requestPositionUpdates(entity);
}

// ------------------------------------
// Unrequest Position Updates
void	CReynoldsManager::unrequestPositionUpdates(const NLMISC::CEntityId &entity)
{
	if (!checkInterface())
		return;

	_CommandInterface->unrequestPositionUpdates(entity);
}

// ------------------------------------
// Request Vision
void	CReynoldsManager::requestVision(const NLMISC::CEntityId &entity)
{
	if (!checkInterface())
		return;

	_CommandInterface->requestVision(entity);
}

// ------------------------------------
// Unrequest Vision
void	CReynoldsManager::unrequestVision(const NLMISC::CEntityId &entity)
{
	if (!checkInterface())
		return;

	_CommandInterface->unrequestVision(entity);
}


// ------------------------------------
// Updated position
void	CReynoldsManager::updatedPosition(const CEntityId &entity, const CVectorD &position, float heading)
{
	if (_CommandInterface != NULL)
		_CommandInterface->updatePosition(entity, position, heading);
}

// ------------------------------------
// Updated state
void	CReynoldsManager::stateChanged(const CEntityId &entity, CTrackBase::TTrackState state)
{
	if (_CommandInterface != NULL)
		_CommandInterface->stateChanged(entity, state);
}


// ------------------------------------
// TrackStop
void	CReynoldsManager::trackStop(CTrack *track)
{
	if (checkInterface())
		_CommandInterface->stopTrack(track->getId());

	// remove track from controlled tracks
	_ControlledTrackMap.erase(track->getId());
}


// ------------------------------------
// Create Move primitive
void	CReynoldsManager::createMovePrimitive(const NLMISC::CVectorD &pos, NLPACS::UMovePrimitive *&primitive, NLPACS::UMoveContainer *&container)
{
	primitive = NULL;
	container = NULL;

	uint8	continent = _Continents.findContinent(pos);
	if (continent == -1)
	{
		nlwarning("ReynoldsLib:CReynoldsManager:createMovePrimitive(): unable to create move primitive");
		return;
	}

	container = _Continents.getMoveContainer(continent);
	primitive = container->addNonCollisionablePrimitive();
}





// ------------------------------------
// Set Sheet
void	CReynoldsManager::setSheet(const NLMISC::CEntityId &id, const NLMISC::CSheetId &sheet)
{
	CTrack	*track = getTrack(id);
	if (track == NULL)
	{
		nlwarning("ReynoldsLib:CReynoldsManager:setSheet(): Track %s not found", id.toString().c_str());
		return;
	}

	if (track->hasId())
	{
		nlwarning("ReynoldsLib:CReynoldsManager:setSheet(): Track %s already has an Id", id.toString().c_str());
		return;
	}

	track->setId(id, sheet);
}

// ------------------------------------
// Set Position
void	CReynoldsManager::setPosition(const NLMISC::CEntityId &id, const NLMISC::CVectorD &position, float heading)
{
	CTrack	*track = getTrack(id);
	if (track == NULL)
	{
		nlwarning("ReynoldsLib:CReynoldsManager:setPosition(): Track %s not found", id.toString().c_str());
		return;
	}

	track->setPosition(position, heading);
}

// ------------------------------------
// Set Vision
void	CReynoldsManager::setVision(const NLMISC::CEntityId &id, const std::vector<NLMISC::CEntityId> &in, const std::vector<NLMISC::CEntityId> &out)
{
	CTrack	*track = getTrack(id);
	if (track == NULL)
	{
		nlwarning("ReynoldsLib:CReynoldsManager:setVision(): Track %s not found", id.toString().c_str());
		return;
	}

	track->updateVision(in, out);
}

// ------------------------------------
// Set Vision
void	CReynoldsManager::setVision(const NLMISC::CEntityId &id, const std::vector<NLMISC::CEntityId> &vision)
{
	CTrack	*track = getTrack(id);
	if (track == NULL)
	{
		nlwarning("ReynoldsLib:CReynoldsManager:setVision(): Track %s not found", id.toString().c_str());
		return;
	}

	track->updateVision(vision);
}




//-------------------------------------------------------------------------
// Init Sheets
void	CReynoldsManager::initSheets(const std::string &packSheetFile)
{
	if (_Initialised)
		return;

	std::vector<std::string> filters;
	filters.push_back("creature");
	filters.push_back("player");

	loadForm(filters, packSheetFile, _Sheets);

	_Initialised=true;
}


//-------------------------------------------------------------------------
// Lookup Sheet
const CTrack::CSheet *CReynoldsManager::lookup(const CSheetId &id)
{
	nlassert(_Initialised);

	// setup an iterator and lookup the sheet id in the map
	std::map<CSheetId, CTrack::CSheet>::iterator it;
	it=_Sheets.find(id);

	// if we found a valid entry return a pointer to the creature record otherwise 0
	if (it != _Sheets.end())
		return &((*it).second);
	else
		return NULL;
}