// NeL - 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/>.


/*
 * Layer 5 and Service example, front-end server.
 *
 * This front-end server expects pings, and forward them to
 * the real ping server. When the ping server sends a pong back,
 * the front-end server forwards it to the client.
 *
 * Even if the connection to the ping server is broken, our
 * front-end server will keep storing the ping messages and
 * will forward them when the connection is restored.
 *
 * To run this program, ensure there is a file "frontend_service.cfg"
 * containing the location of the naming service (NSHost, NSPort)
 * in the working directory. The naming service must be running.
 */


// We're using the NeL Service framework and layer 5.
#include "nel/net/service.h"
#include "nel/misc/time_nl.h"
#include "nel/misc/displayer.h"
#include "nel/misc/command.h"
#include "nel/misc/hierarchical_timer.h"
#include "nel/misc/bit_mem_stream.h"

#include <deque>

using namespace std;
using namespace NLNET;
using namespace NLMISC;


//
TTime	pingDate;

/*
 * Callback function called when receiving a "PONG" message
 *
 * Arguments:
 * - msgin:	the incoming message (coming from the ping server)
 * - from: the "sockid" of the sender (usually useless for a CCallbackClient)
 * - clientofthepingserver: the CCallbackNetBase object (which really is a CCallbackClient object)
 *
 * Input (expected message from the ping server): PONG
 * - uint32: ping counter
 * - TSockId: "sock id" of the client who sent the ping
 *
 * Output (sent message to a client): PONG
 * - uint32: ping counter
 */
void cbPong(CMessage &msgin, const std::string &serviceName, TServiceId sid)
{
	uint32	counter;
	msgin.serial( counter );
	TTime	pingTime = CTime::getLocalTime()-pingDate;

	nlinfo("Received PONG %u (%u ms)", counter, pingTime);
}

void sendPing()
{
	pingDate = CTime::getLocalTime();
	uint32 counter = 0;
	CMessage msgout("PING");
	msgout.serial( counter );
	nlinfo( "Send PING 0");
	CUnifiedNetwork::getInstance()->send("PS", msgout);
}

//
void cbPos(CMessage &msgin, const std::string &serviceName, TServiceId sid)
{
	// decode the message

	TCPUCycle v1 = CTime::getPerformanceTime ();

	uint32 nbid;
	msgin.serial (nbid);

	for (uint i = 0; i < nbid; i++)
	{
		uint64 id;
		msgin.serial (id);
	}
	
	TCPUCycle v2 = CTime::getPerformanceTime ();

	nlinfo("Received POS from %s (serial: %.2fs)", serviceName.c_str(), CTime::ticksToSecond(v2-v1));
}

TTime t = 0;

void sendRequestVision ()
{
//	nlSleep (1000);
	CMessage msgout("ASK_VISION");
	CUnifiedNetwork::getInstance()->send("GPMS", msgout);
	nlinfo ("ask a new vision");
	t = CTime::getLocalTime ();
}

void cbVision(CMessage &msgin, const std::string &serviceName, TServiceId sid)
{
	uint32 NbValue;
	uint32 Value;
	
	t = CTime::getLocalTime() - t;

	//H_BEFORE (Vision);

	TCPUCycle v1 = CTime::getPerformanceTime ();
	//H_BEFORE (serial);
	msgin.serial (NbValue);
	//H_AFTER (serial);
	//H_BEFORE (serials);
	for (uint i = 0; i < NbValue; i++)
		msgin.serial( Value );
	//H_AFTER (serials);
	TCPUCycle v2 = CTime::getPerformanceTime ();

	nlinfo("%dms of lag, Received Vision with %d values in %.2fms", (uint32) t, NbValue, CTime::ticksToSecond (v2-v1)*1000.0f);
//	sendRequestVision();

	//H_AFTER (Vision);
}


void sendPos()
{
	nlinfo("Simulate receive pos from client, send POS to GPMS");
	CMessage msgout("POS");
	CUnifiedNetwork::getInstance()->send("GPMS", msgout);
}

//
void cbUpGPMS(const std::string &serviceName, TServiceId sid, void *arg)
{
	nlinfo( "GPMS connecting.");
	sendRequestVision ();
}


//
void cbUpPS(const std::string &serviceName, TServiceId sid, void *arg)
{
	nlinfo( "Ping Service connecting.");
	sendPing();
}


void cbDownPS(const std::string &serviceName, TServiceId sid, void *arg)
{
	nlinfo( "Ping Service disconnecting." );
}

//
void cbUpService(const std::string &serviceName, TServiceId sid, void *arg)
{
	nlinfo("Service %s %d is up", serviceName.c_str(), sid.get());
}

void cbDownService(const std::string &serviceName, TServiceId sid, void *arg)
{
	nlinfo("Service %s %d is down", serviceName.c_str(), sid.get());
}


/*
 * Callback array for message received from the ping service
 */
NLNET::TUnifiedCallbackItem CallbackArray[] =
{
	{ "POS", cbPos },
	{ "PONG", cbPong },
	{ "VISION", cbVision }
};


/*
 * CFrontEndService, based on IService
 */
class CFrontEndService : public NLNET::IService
{
public:

	bool	update()
	{
		static TTime	lastPing = CTime::getLocalTime();
		static TTime	lastGetPos = CTime::getLocalTime();

		TTime	ctime = CTime::getLocalTime();

		// check vision every 2 seconds
		if (ctime - lastPing> 2000)
		{
			sendRequestVision();
			lastPing = ctime;
		}

/*
		// check ping every 15 seconds
		if (ctime - lastPing> 15000)
		{
			sendPing();
			lastPing = ctime;
		}

		// do as if receive a position every second
		if (ctime - lastGetPos > 1000)
		{
			sendPos();
			lastGetPos = ctime;
		}
*/
		return true;
	}

	/*
	 * Initialization
	 */
	void init()
	{
/*		uint32 u = 0xFFFFFFFF;
		uint32 z = 0;

		uint32 res = 0;
		CBitMemStream bms2;

		bms2.serial (u, 1);
		bms2.serial (z, 18);
		bms2.serial (u, 4);
		bms2.serial (z, 3);

		nlinfo ("len %d", bms2.length());
		bms2.invert ();
		nlinfo ("len %d", bms2.length());
		bms2.invert ();
		nlinfo ("len %d", bms2.length());
*/
/*		CBitMemStream bms;

		nlinfo ("len %d", bms.length());

		bms.serial (u, 1);
		bms.serial (z, 18);
		bms.serial (u, 4);
		bms.serial (z, 3);
		bms.serial (u, 30);
		nlinfo ("len %d", bms.length());

		bms.clear ();
		nlinfo ("len %d", bms.length());

		bms.serial (z, 1);
		bms.serial (u, 18);
		bms.serial (z, 4);
		bms.serial (u, 3);
		bms.serial (z, 30);
		nlinfo ("len %d", bms.length());


		vector<uint32> cont;
		for(uint i=0;i<32;i++) cont.push_back(i);
		bms.serialCont (cont);

		nlinfo ("len %d", bms.length());


		bms.invert ();
		nlinfo ("len %d", bms.length());

		while (bms.getPosInBit() != 30+3+4+18+1)
		{
			nlinfo ("%d", bms.getPosInBit());
			bms.serial (res, 1);
			nlinfo ((res==0)?"0":"1");
		}
		nlinfo ("%d", bms.getPosInBit());

		vector<uint32> cont2;
		bms.serialCont (cont2);
		nlinfo ("%d", bms.getPosInBit());
		for(uint j=0;j<cont2.size();j++) nlinfo ("const %d %d",j, cont2[j]);

		nlinfo ("%d", bms.getPosInBit());
*/
		// Connect to the ping service
		NLNET::CUnifiedNetwork	*instance = NLNET::CUnifiedNetwork::getInstance();

		instance->setServiceUpCallback("PS", cbUpPS, NULL);
		instance->setServiceDownCallback("PS", cbDownPS, NULL);

		instance->setServiceUpCallback("GPMS", cbUpGPMS, NULL);

		instance->setServiceUpCallback("*", cbUpService, NULL);
		instance->setServiceDownCallback("*", cbDownService, NULL);
	}
};
 
/*
 * Declare a service with the class CFrontEndService, the names "FS" (short) and "frontend_service" (long).
 * The port is set to 37000 and the main callback array is CallbackArray.
 */
NLNET_SERVICE_MAIN( CFrontEndService, "FS", "frontend_service", 37000, CallbackArray, "", "" )