// NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// 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/>.

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H

#ifndef NELNS_CONFIG
#define NELNS_CONFIG ""
#endif // NELNS_CONFIG

#ifndef NELNS_LOGS
#define NELNS_LOGS ""
#endif // NELNS_LOGS


//
// Includes
//

#include "nel/misc/types_nl.h"

#include <list>
#include <string>

#include "nel/misc/debug.h"
#include "nel/misc/command.h"
#include "nel/misc/variable.h"
#include "nel/misc/displayer.h"

#include "nel/net/callback_server.h"
#include "nel/net/service.h"
#include "nel/net/module_manager.h"

//
// Namespaces
//

using namespace std;

using namespace NLMISC;
using namespace NLNET;


NLMISC_COMMAND(test, "none", "none")
{
	log.displayNL("Raw cmd line : '%s'", rawCommandString.c_str());
	log.displayNL("Dumping %u parameters :", args.size());
	for (uint i=0; i<args.size(); ++i)
	{
		log.displayNL("  %u : '%s'", i, args[i].c_str());
	}
	return true;
}


//
// Structures
//

struct CServiceEntry
{
	CServiceEntry (TSockId sock, const vector<CInetAddress> &a, const string &n, TServiceId s) : SockId(sock), Addr(a), Name(n), SId (s), WaitingUnregistration(false) { }

	TSockId						SockId;			// the connection between the service and the naming service
	vector<CInetAddress>		Addr;			// address to send to the service who wants to lookup this service
												// it s possible to have more than one addr, anyway, the naming service
												// will send good address depending of the sub net address of the service
	string						Name;			// name of the service
	TServiceId					SId;			// id of the service

	bool				WaitingUnregistration;			// true if this service is in unregistration process (wait other service ACK)
	TTime				WaitingUnregistrationTime;		// time of the beginning of the inregistration process
	list<TServiceId>	WaitingUnregistrationServices;	// list of service that we wait the answer
};



// Helper that emulates layer5's send()
//void sendToService( uint16 sid, CMessage& msgout );

// Helper that emulate layer5's getServiceName()
string getServiceName( TServiceId  sid );

// Helper that returns the first address of a service
CInetAddress getHostAddress( TServiceId  sid );

// Asks a service to stop and tell every one
void doUnregisterService (TServiceId sid);


/**
 * Manager for services instances
 * (Moved from the TICKS to the NS)
 * Implementable with layer 5, here implemented in NS (layer 3)
 * \author Olivier Cado
 * \author Nevrax France
 * \date 2003
 */
class CServiceInstanceManager
{
public:
	
	/// Constructor
	CServiceInstanceManager();

	/** Add the name of a service which must not be duplicated
	 * If uniqueOnShard is true, only one service is allowed.
	 * If uniqueOnShard is false, one service is allowed by physical machine.
	 */
	void		addUniqueService( const std::string& serviceName, bool uniqueOnShard )
	{
		_UniqueServices.insert( std::make_pair( serviceName, uniqueOnShard ) );
	}

	/// Check if a service is allowed to start (if so, add it)
	bool		queryStartService( const std::string& serviceName, TServiceId  serviceId, const std::vector<NLNET::CInetAddress> &addr, string& reason );

	/// Release a service instance
	void		releaseService( NLNET::TServiceId serviceId );

	/// Display information
	void		displayInfo( NLMISC::CLog *log = NLMISC::InfoLog ) const;

	/// Make all controlled services quit
	void		killAllServices();

private:

	/// List of restricted services
	std::map< std::string, bool >	_UniqueServices;

	/// List of granted (online) services
	std::set< TServiceId >				_OnlineServices;
};


CServiceInstanceManager *SIMInstance = NULL;


/*
 * Constructor
 */
CServiceInstanceManager::CServiceInstanceManager()
{
	nlassert( ! SIMInstance );
	SIMInstance = this;

	// Note: addCallbackArray() done in CRangeMirrorManager::init()
}


/*
 * Check if a service is allowed to start. Answer with a GSTS (Grant Start Service) message
 */
bool		CServiceInstanceManager::queryStartService( const std::string& serviceName, TServiceId  serviceId, const vector<CInetAddress> &addr, string& reason )
{
	bool grantStarting = true;
	std::map< std::string, bool >::iterator ius = _UniqueServices.find( serviceName );
	if ( ius != _UniqueServices.end() )
	{
		// Service is restricted
		set< TServiceId >::iterator ios;
		bool uniqueOnShard = (*ius).second;
		for ( ios=_OnlineServices.begin(); ios!=_OnlineServices.end(); ++ios )
		{
			string name = getServiceName( *ios );
			if ( name == serviceName )
			{
				if ( uniqueOnShard )
				{
					// Only one service by shard is allowed => deny
					grantStarting = false;
					reason = toString( "Service %s already found as %hu, must be unique on shard", serviceName.c_str(), ios->get() );
					nlinfo( reason.c_str() );
					break;
				}
				else
				{
					// Only one service by physical machine is allowed

					// Implementation for layer5
					//TSockId hostid1, hostid2;
					/*CCallbackNetBase *cnb1 = CUnifiedNetwork::getInstance()->getNetBase( serviceId, hostid1 );
					CCallbackNetBase *cnb2 = CUnifiedNetwork::getInstance()->getNetBase( *ios, hostid2 );
					if ( cnb1->hostAddress( hostid1 ).internalIPAddress() == cnb2->hostAddress( hostid2 ).internalIPAddress() )*/

					// Implementation for NS
					if ( addr[0].internalIPAddress() == getHostAddress( *ios ).internalIPAddress() )
					{
						grantStarting = false;
						reason = toString( "Service %s already found as %hu on same machine", serviceName.c_str(), ios->get() );
						nlinfo( reason.c_str() );
						break;
					}
				}
			}
		}
	}

	if ( grantStarting )
	{
		_OnlineServices.insert( serviceId );
	}
	return grantStarting;
}


/*
 * Release a service instance
 */
void		CServiceInstanceManager::releaseService( NLNET::TServiceId serviceId )
{
	_OnlineServices.erase( serviceId ); // not a problem if not found
}


/*
 * Display information
 */
void		CServiceInstanceManager::displayInfo( NLMISC::CLog *log ) const
{
	log->displayNL( "Restricted services:" );
	std::map< std::string, bool >::const_iterator ius;
	for ( ius=_UniqueServices.begin(); ius!=_UniqueServices.end(); ++ius )
	{
		log->displayNL( "%s -> only one per %s", (*ius).first.c_str(), (*ius).second?"shard":"machine" );
	}
	log->displayNL( "Online registered services:" );
	std::set< TServiceId >::const_iterator ios;
	for ( ios=_OnlineServices.begin(); ios!=_OnlineServices.end(); ++ios )
	{
		log->displayNL( "%s", CUnifiedNetwork::getInstance()->getServiceUnifiedName( *ios ).c_str() );
	}
}


/*
 * Make all controlled services quit
 */
void		CServiceInstanceManager::killAllServices()
{
	// Send to all known online services
	std::set< TServiceId >::const_iterator ios;
	for ( ios=_OnlineServices.begin(); ios!=_OnlineServices.end(); ++ios )
	{
		doUnregisterService( (TServiceId)(*ios) );
	}
}



//
// Variables
//

list<CServiceEntry>	RegisteredServices;		/// List of all registred services

uint16				MinBasePort = 51000;	/// Ports begin at 51000
uint16				MaxBasePort = 52000;	/// (note: in this implementation there can be no more than 1000 services)

const TServiceId	BaseSId(128);			/// Allocated SIds begin at 128 (except for Agent Service)

const TTime			UnregisterTimeout = 10000;	/// After 10s we remove an unregister service if every server didn't ACK the message

CCallbackServer		*CallbackServer = NULL;

//
// Functions
//

bool canAccess (const vector<CInetAddress> &addr, const CServiceEntry &entry, vector<CInetAddress> &accessibleAddr)
{
	accessibleAddr.clear ();
	
	if (entry.WaitingUnregistration)
		return false;

	for (uint i = 0; i < addr.size(); i++)
	{
		uint32 net = addr[i].internalNetAddress();
		for (uint j = 0; j < entry.Addr.size(); j++)
		{
			if (net == entry.Addr[j].internalNetAddress())
			{
				accessibleAddr.push_back (entry.Addr[j]);
			}
		}
	}

	if (accessibleAddr.empty())
	{
		nldebug ("service %s-%hu is not accessible by '%s'", entry.Name.c_str(), entry.SId.get(), vectorCInetAddressToString (addr).c_str ());
	}
	else
	{
		nldebug ("service %s-%hu is accessible by '%s'", entry.Name.c_str(), entry.SId.get(), vectorCInetAddressToString (accessibleAddr).c_str ());
	}

	return !accessibleAddr.empty ();
}

void displayRegisteredServices (CLog *log = InfoLog)
{
	log->displayNL ("Display the %d registered services :", RegisteredServices.size());
	for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
	{
		TSockId id = (*it).SockId;
		if (id == NULL)
		{
			log->displayNL ("> %s-%hu %s '%s' %s %d addr", (*it).Name.c_str(), it->SId.get(), "<NULL>", "<NULL>", (*it).WaitingUnregistration?"WaitUnreg":"", (*it).Addr.size());
			for(uint i = 0; i < (*it).Addr.size(); i++)
				log->displayNL ("              '%s'", (*it).Addr[i].asString().c_str());
		}
		else
		{
			log->displayNL ("> %s-%hu %s '%s' %s %d addr", (*it).Name.c_str(), it->SId.get(), (*it).SockId->asString().c_str(), CallbackServer->hostAddress((*it).SockId).asString().c_str(), (*it).WaitingUnregistration?"WaitUnreg":"", (*it).Addr.size());
			for(uint i = 0; i < (*it).Addr.size(); i++)
				log->displayNL ("              '%s'", (*it).Addr[i].asString().c_str());
		}
	}
	log->displayNL ("End of the list");
}


list<CServiceEntry>::iterator effectivelyRemove (list<CServiceEntry>::iterator &it)
{
	// remove the service from the registered service list
	nlinfo ("Effectively remove the service %s-%hu", (*it).Name.c_str(), it->SId.get());
	return RegisteredServices.erase (it);
}

/*
 * Helper procedure for cbLookupAlternate and cbUnregister.
 * Note: name is used for a LOGS.
 */
list<CServiceEntry>::iterator doRemove (list<CServiceEntry>::iterator it)
{
	nldebug ("Unregister the service %s-%hu '%s'", (*it).Name.c_str(), it->SId.get(), (*it).Addr[0].asString().c_str());
	
	// tell to everybody that this service is unregistered

	CMessage msgout ("UNB");
	msgout.serial ((*it).Name);
	msgout.serial ((*it).SId);

	vector<CInetAddress> accessibleAddress;
	nlinfo ("Broadcast the Unregistration of %s-%hu to all registered services", (*it).Name.c_str(), it->SId.get());
	for (list<CServiceEntry>::iterator it3 = RegisteredServices.begin(); it3 != RegisteredServices.end (); it3++)
	{
		if (canAccess((*it).Addr, (*it3), accessibleAddress))
		{
			CallbackServer->send (msgout, (*it3).SockId);
			//CNetManager::send ("NS", msgout, (*it3).SockId);
			nldebug ("Broadcast to %s-%hu", (*it3).Name.c_str(), it3->SId.get());
		}
	}

	// new system, after the unregistation broadcast, we wait ACK from all services before really remove
	// the service, before, we tag the service as 'wait before unregister'
	// if everybody didn't answer before the time out, we remove it

	(*it).SockId = NULL;

	(*it).WaitingUnregistration = true;
	(*it).WaitingUnregistrationTime = CTime::getLocalTime();

	// we remove all services awaiting his ACK because this service is down so it'll never ACK
	for (list<CServiceEntry>::iterator itr = RegisteredServices.begin(); itr != RegisteredServices.end (); itr++)
	{
		for (list<TServiceId>::iterator itw = (*itr).WaitingUnregistrationServices.begin(); itw != (*itr).WaitingUnregistrationServices.end ();)
		{
			if ((*itw) == (*it).SId)
			{
				itw = (*itr).WaitingUnregistrationServices.erase (itw);
			}
			else
			{
				itw++;
			}
		}
	}

	string res;
	for (list<CServiceEntry>::iterator it2 = RegisteredServices.begin(); it2 != RegisteredServices.end (); it2++)
	{
		if (!(*it2).WaitingUnregistration)
		{
			(*it).WaitingUnregistrationServices.push_back ((*it2).SId);
			res += toString((*it2).SId.get()) + " ";
		}
	}

	nlinfo ("Before removing the service %s-%hu, we wait the ACK of '%s'", (*it).Name.c_str(), (*it).SId.get(), res.c_str());
	
	if ((*it).WaitingUnregistrationServices.empty())
	{
		return effectivelyRemove (it);
	}
	else
	{
		return ++it;
	}

	// Release from the service instance manager
	SIMInstance->releaseService( (*it).SId );
}

void doUnregisterService (TServiceId sid)
{
	list<CServiceEntry>::iterator it;
	for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
	{
		if ((*it).SId == sid)
		{
			// found it, remove it
			doRemove (it);
			return;
		}
	}
	nlwarning ("Service %hu not found", sid.get());
}

void doUnregisterService (TSockId from)
{
	list<CServiceEntry>::iterator it;
	for (it = RegisteredServices.begin(); it != RegisteredServices.end ();)
	{
		if ((*it).SockId == from)
		{
			// it's possible that one "from" have more than one registred service, so we have to find in all the list
			// found it, remove it
			it = doRemove (it);
		}
		else
		{
			it++;
		}
	}
}

/*void doUnregisterService (const CInetAddress &addr)
{
	list<CServiceEntry>::iterator it;
	for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
	{
		if ((*it).Addr == addr)
		{
			// found it, remove it
			doRemove (it);
			return;
		}
	}
	nlwarning ("Service %s not found", addr.asString().c_str());
}*/

/*
 * Helper function for cbRegister.
 * If alloc_sid is true, sid is ignored
 * Returns false in case of failure of sid allocation or bad sid provided
 * Note: the reply is included in this function, because it must be done before things such as syncUniTime()
 */
bool doRegister (const string &name, const vector<CInetAddress> &addr, TServiceId sid, TSockId from, CCallbackNetBase &netbase, bool reconnection = false)
{
	// Find if the service is not already registered
	string reason;
	uint8 ok = true;
	bool needRegister = true;
	/*for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
	{
		if ((*it).Addr.asIPString() == addr.asIPString() )
		{
			// we already have a service on this address, remplace it if it's the same name
			if ((*it).Name == name)
			{
				// it's the same service, replace it
				(*it).SockId = from;
				sid = (*it).SId;
				nlinfo ("Replace the service %s", name.c_str());
			}
			else
			{
				nlwarning ("Try to register %s to %s but the service %s already on this address. ignore it!", name.c_str(), addr.asIPString().c_str(), (*it).Name.c_str());
				ok = false;
			}
			needRegister = false;
			break;
		}
	}*/

	if (needRegister)
	{
		if (sid.get() == 0)
		{
			// we have to find a sid
			sid = BaseSId;
			bool found = false;
			while (!found)
			{
				list<CServiceEntry>::iterator it;
				for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
				{
					if ((*it).SId == sid)
					{
						break;
					}
				}
				if (it == RegisteredServices.end ())
				{
					// ok, we have an empty sid
					found = true;
				}
				else
				{
					sid.set(sid.get()+1);
					if (sid.get() == 0) // round the clock
					{
						nlwarning ("Service identifier allocation overflow");
						ok = false;
						break;
					}
				}
			}

		}
		else
		{
			// we have to check that the user provided sid is available
			list<CServiceEntry>::iterator it;
			for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
			{
				if ((*it).SId == sid)
				{
					nlwarning ("Sid %d already used by another service", sid.get());
					ok = false;
					break;
				}
			}
			if (it != RegisteredServices.end ())
			{
				ok = true;
			}
		}

		// if ok, register the service and send a broadcast to other people
		if (ok)
		{
			// Check if the instance is allowed to start, according to the restriction in the config file			
			if ( SIMInstance->queryStartService( name, sid, addr, reason ) )
			{
				// add him in the registered list
				RegisteredServices.push_back (CServiceEntry(from, addr, name, sid));

				// tell to everybody but not him that this service is registered
				if (!reconnection)
				{
					CMessage msgout ("RGB");
					TServiceId::size_type s = 1;
					msgout.serial (s);
					msgout.serial (const_cast<string &>(name));
					msgout.serial (sid);
					// we need to send all addr to all services even if the service can't access because we use the address index
					// to know which connection comes.
					msgout.serialCont (const_cast<vector<CInetAddress> &>(addr));
					nlinfo ("The service is %s-%d, broadcast the Registration to everybody", name.c_str(), sid.get());

					vector<CInetAddress> accessibleAddress;
					for (list<CServiceEntry>::iterator it3 = RegisteredServices.begin(); it3 != RegisteredServices.end (); it3++)
					{
						// send only services that can be accessed and not itself
						if ((*it3).SId != sid && canAccess(addr, (*it3), accessibleAddress))
						{
							CallbackServer->send (msgout, (*it3).SockId);
							//CNetManager::send ("NS", msgout, (*it3).SockId);
							nldebug ("Broadcast to %s-%hu", (*it3).Name.c_str(), it3->SId.get());
						}
					}
				}

				// set the sid only if it s ok
				from->setAppId (sid.get());
			}
			else
			{
				// Reply "startup denied", and do not send registration to other services
				ok = false;
			}
		}

		// send the message to the service to say if it s ok or not
		if (!reconnection)
		{
			// send the answer to the client
			CMessage msgout ("RG");
			msgout.serial (ok);
			if (ok)
			{
				msgout.serial (sid);

				// send him all services available (also itself)
				TServiceId::size_type nb = 0;

				vector<CInetAddress> accessibleAddress;

				for (list<CServiceEntry>::iterator it2 = RegisteredServices.begin(); it2 != RegisteredServices.end (); it2++)
				{
					// send only services that are available
					if (canAccess(addr, (*it2), accessibleAddress))
						nb++;
				}
				msgout.serial (nb);

				for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
				{
					// send only services that are available
					if (canAccess(addr, (*it), accessibleAddress))
					{
						msgout.serial ((*it).Name);
						msgout.serial ((*it).SId);
						msgout.serialCont ((*it).Addr);
					}
				}
			}
			else
			{
				msgout.serial( reason );
			}
			
			netbase.send (msgout, from);
			netbase.flush (from);
		}
	}

	//displayRegisteredServices ();

	return ok!=0;
}

void checkWaitingUnregistrationServices ()
{
	for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end ();)
	{
		if ((*it).WaitingUnregistration && ((*it).WaitingUnregistrationServices.empty() || CTime::getLocalTime() > (*it).WaitingUnregistrationTime + UnregisterTimeout))
		{
			if ((*it).WaitingUnregistrationServices.empty())
			{
				nlinfo ("Removing the service %s-%hu because all services ACKd the removal", (*it).Name.c_str(), (*it).SId.get());
			}
			else
			{
				string res;
				for (list<TServiceId>::iterator it2 = (*it).WaitingUnregistrationServices.begin(); it2 != (*it).WaitingUnregistrationServices.end (); it2++)
				{
					res += toString(it2->get()) + " ";
				}
				nlwarning ("Removing the service %s-%hu because time out occurs (service numbers %s didn't ACK)", (*it).Name.c_str(), (*it).SId.get(), res.c_str());
			}
			it = effectivelyRemove (it);
		}
		else
		{
			it++;
		}
	}
}


/**
 * Callback for service unregistration ACK. Mean that a service was ACK the unregistration broadcast
 */
static void cbACKUnregistration (CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
{
	TServiceId sid;
	msgin.serial (sid);

	for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
	{
		if ((*it).SId == sid && (*it).WaitingUnregistration)
		{
			for (list<TServiceId>::iterator it2 = (*it).WaitingUnregistrationServices.begin(); it2 != (*it).WaitingUnregistrationServices.end (); it2++)
			{
				if (*it2 == TServiceId(uint16(from->appId())))
				{
					// remove the acked service
					(*it).WaitingUnregistrationServices.erase (it2);
					checkWaitingUnregistrationServices ();
					return;
				}
			}
		}
	}
}


/**
 * Callback for service registration when the naming service goes down and up (don't need to broadcast)
 */
static void cbResendRegisteration (CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
{
	string name;
	vector<CInetAddress> addr;
	TServiceId sid;
	msgin.serial (name);
	msgin.serialCont (addr);
	msgin.serial (sid);

	doRegister (name, addr, sid, from, netbase, true);
}



/**
 * Callback for service registration.
 *
 * Message expected : RG
 * - Name of service to register (string)
 * - Address of service (CInetAddress)
 *
 * Message emitted : RG
 * - Allocated service identifier (TServiceId) or 0 if failed
 */
static void cbRegister (CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
{
	string name;
	vector<CInetAddress> addr;
	TServiceId sid;
	msgin.serial (name);
	msgin.serialCont (addr);
	msgin.serial (sid);

	doRegister (name, addr, sid, from, netbase);
}


/**
 * Callback for service unregistration.
 *
 * Message expected : UNI
 * - Service identifier (TServiceId)
 */
static void cbUnregisterSId (CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
{
	TServiceId sid;
	msgin.serial( sid );

	doUnregisterService (sid);
	//displayRegisteredServices ();
}


/*
 * Helper function for cbQueryPort
 *
 * \warning QueryPort + Registration is not atomic so more than one service could ask a port before register
 */
uint16 doAllocatePort (const CInetAddress &addr)
{
	static uint16 nextAvailablePort = MinBasePort;

	// check if nextavailableport is free

	if (nextAvailablePort >= MaxBasePort) nextAvailablePort = MinBasePort;

	bool ok;
	do
	{
		ok = true;
		list<CServiceEntry>::iterator it;
		for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
		{
			if ((*it).Addr[0].port () == nextAvailablePort)
			{
				nextAvailablePort++;
				ok = false;
				break;
			}
		}
	}
	while (!ok);

	return nextAvailablePort++;
}


/**
 * Callback for port allocation
 * Note: if a service queries a port but does not register itself to the naming service, the
 * port will remain allocated and unused.
 *
 * Message expected : QP
 * - Name of service to register (string)
 * - Address of service (CInetAddress) (its port can be 0)
 *
 * Message emitted : QP
 * - Allocated port number (uint16)
 */
static void cbQueryPort (CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
{
	// Allocate port
	uint16 port = doAllocatePort (netbase.hostAddress (from));

	// Send port back
	CMessage msgout ("QP");
	msgout.serial (port);
	netbase.send (msgout, from);

	nlinfo ("The service got port %hu", port);
}


/*
 * Unregisters a service if it has not been done before.
 * Note: this callback is called whenever someone disconnects from the NS.
 * May be there are too many calls if many clients perform many transactional lookups.
 */
static void cbDisconnect /*(const string &serviceName, TSockId from, void *arg)*/ ( TSockId from, void *arg )
{
	doUnregisterService (from);
	//displayRegisteredServices ();
}

/*
 * a service is connected, send him all services infos
 */
static void cbConnect /*(const string &serviceName, TSockId from, void *arg)*/ ( TSockId from, void *arg )
{
	// we have to wait the registred services message to send all services because it this points, we can't know which sub net
	// the service can use

	//displayRegisteredServices ();

	// set the appid with a bad id (-1)
	from->setAppId (~0);
}

/*// returns the list of accessible services with a list of address
static void cbRegisteredServices(CMessage& msgin, TSockId from, CCallbackNetBase &netbase)
{
	vector<CInetAddress> addr;
	msgin.serialCont (addr);

	nlinfo ("New service ask me the available services, sending him all services available");
	// send to the new service the list of all services that this service can access (depending of his sub net)

	CMessage msgout ("RGB");

	uint8 nb = 0;

	vector<CInetAddress> accessibleAddress;

	for (list<CServiceEntry>::iterator it2 = RegisteredServices.begin(); it2 != RegisteredServices.end (); it2++)
	{
		// send only services that are available
		if (canAccess(addr, (*it2), accessibleAddress))
			nb++;
	}

	msgout.serial (nb);

	for (list<CServiceEntry>::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
	{
		// send only services that are available
		if (canAccess(addr, (*it), accessibleAddress))
		{
			msgout.serial ((*it).Name);
			msgout.serial ((*it).SId);
			msgout.serialCont (accessibleAddress);
		}
	}

	CNetManager::send ("NS", msgout, from);
}*/


/*
 * Helper that emulates layer5 send()
 */
/*void sendToService( uint16 sid, CMessage& msgout )
{
	list<CServiceEntry>::iterator it;
	for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
	{
		if ((*it).SId == sid)
		{
			CallbackServer->send (msgout, (*it).SockId);
		}
	}
}*/


/*
 * Helper that emulate layer5's getServiceName()
 */
string getServiceName( TServiceId  sid )
{
	list<CServiceEntry>::iterator it;
	for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
	{
		if ((*it).SId == sid)
		{
			return (*it).Name;
		}
	}
	return ""; // not found
}


/*
 * Helper that returns the first address of a service
 */
CInetAddress getHostAddress( TServiceId  sid )
{
	list<CServiceEntry>::iterator it;
	for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
	{
		if ((*it).SId == sid)
		{
			return (*it).Addr[0];
		}
	}
	return CInetAddress();
}


//
// Callback array
//

TCallbackItem CallbackArray[] =
{
	{ "RG", cbRegister },
	{ "RRG", cbResendRegisteration },
	{ "QP", cbQueryPort },
	{ "UNI", cbUnregisterSId },
	{ "ACK_UNI", cbACKUnregistration },
//	{ "RS", cbRegisteredServices },
};


//
// Service
//

class CNamingService : public NLNET::IService
{
public:

	/**
	 * Init
	 */
	void init()
	{
		// if a baseport is available in the config file, get it
		CConfigFile::CVar *var;
		if ((var = ConfigFile.getVarPtr ("BasePort")) != NULL)
		{
			uint16 newBasePort = var->asInt ();
			nlinfo ("Changing the MinBasePort number from %hu to %hu", MinBasePort, newBasePort);
			sint32 delta = MaxBasePort - MinBasePort;
			nlassert (delta > 0);
			MinBasePort = newBasePort;
			MaxBasePort = MinBasePort + uint16 (delta);
		}

		// Parameters for the service instance manager
		try
		{
			CConfigFile::CVar& uniqueServices = ConfigFile.getVar("UniqueOnShardServices");
			for ( uint i=0; i!=uniqueServices.size(); ++i )
			{
				_ServiceInstances.addUniqueService( uniqueServices.asString(i), true );
			}
		}
		catch(Exception &)
		{}
		try
		{
			CConfigFile::CVar& uniqueServicesM = ConfigFile.getVar("UniqueByMachineServices");
			for ( uint i=0; i!=uniqueServicesM.size(); ++i )
			{
				_ServiceInstances.addUniqueService( uniqueServicesM.asString(i), false );
			}
		}
		catch(Exception &)
		{}

/*
		// we don't try to associate message from client
		CNetManager::getNetBase ("NS")->ignoreAllUnknownId (true);

		// add the callback in case of disconnection
		CNetManager::setConnectionCallback ("NS", cbConnect, NULL);
		
		// add the callback in case of disconnection
		CNetManager::setDisconnectionCallback ("NS", cbDisconnect, NULL);
*/
		// DEBUG
		// DebugLog->addDisplayer( new CStdDisplayer() );

		vector<CInetAddress> v = CInetAddress::localAddresses();
		nlinfo ("%d detected local addresses:", v.size());
		for (uint i = 0; i < v.size(); i++)
		{
			nlinfo (" %d - '%s'",i, v[i].asString().c_str());
		}

		uint16 nsport = 50000;
		if ((var = ConfigFile.getVarPtr ("NSPort")) != NULL)
		{
			nsport = var->asInt ();
		}

		CallbackServer = new CCallbackServer;
		CallbackServer->init(nsport);
		CallbackServer->addCallbackArray(CallbackArray, sizeof(CallbackArray)/sizeof(CallbackArray[0]));
		CallbackServer->setConnectionCallback(cbConnect, NULL);
		CallbackServer->setDisconnectionCallback(cbDisconnect, NULL);
	}

	/**
	 * Update
	 */
	bool update ()
	{
		checkWaitingUnregistrationServices ();

		CallbackServer->update ();

		return true;
	}

	void release()
	{
		if (CallbackServer != NULL)
			delete CallbackServer;
		CallbackServer = NULL;
	}

private:

	/// Service instance manager singleton
	CServiceInstanceManager		_ServiceInstances;
};


static const char* getCompleteServiceName(const IService* theService)
{
	static std::string s;
	s= "naming_service";

	if (theService->haveLongArg("nsname"))
	{
		s+= "_"+theService->getLongArg("nsname");
	}

	if (theService->haveLongArg("fullnsname"))
	{
		s= theService->getLongArg("fullnsname");
	}

	return s.c_str();
}

static const char* getShortServiceName(const IService* theService)
{
	static std::string s;
	s= "NS";

	if (theService->haveLongArg("shortnsname"))
	{
		s= theService->getLongArg("shortnsname");
	}
	
	return s.c_str();
}
//
/// Naming Service
//
NLNET_SERVICE_MAIN( CNamingService, getShortServiceName(scn), getCompleteServiceName(scn), 0, EmptyCallbackArray, NELNS_CONFIG, NELNS_LOGS)


//
// Commands
//


NLMISC_COMMAND (nsServices, "displays the list of all registered services", "")
{
	if(args.size() != 0) return false;

	displayRegisteredServices (&log);

	return true;
}

NLMISC_COMMAND (kill, "kill a service and send an unregister broadcast to other service", "<ServiceShortName>|<ServiceId>")
{
	if(args.size() != 1) return false;

	// try with number

	TServiceId sid(atoi(args[0].c_str()));

	if(sid.get() == 0)
	{
		// not a number, try a name
		list<CServiceEntry>::iterator it;
		for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++)
		{
			if ((*it).Name == args[0])
			{
				sid = (*it).SId;
				break;
			}
		}
		if (it == RegisteredServices.end())
		{
			log.displayNL ("Bad service name or id '%s'", args[0].c_str());
			return false;
		}
	}

	doUnregisterService (sid);
	return true;
}

NLMISC_DYNVARIABLE(uint32, NbRegisteredServices, "display the number of service that are registered in naming service")
{
	if (get) *pointer = RegisteredServices.size();
}

NLMISC_COMMAND( displayServiceInstances, "SIM: Display info on service instances", "" )
{
	SIMInstance->displayInfo( &log );
	return true;
}

NLMISC_COMMAND( killAllServices, "SIM: Make all the controlled services quit", "" )
{
	SIMInstance->killAllServices();
	return true;
}