/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "io_net/io_net_server.h"
		#include "metafile/nml.h"
		#include "async/task_loop.h"
		#include "platform.h"
		#include "log/log.h"

		using namespace GS::IO;

//		#define	VERBOSE_LOG


// @FIXME Rewrite communication with the controller thread using AsyncCallQueue.

//------------------------------------------------------------------------------
NetServer::Client	*NetServer::GetClient(void *peer)
{
	ListForeachPtr(Client *, client, clients)
		if	(client->peer == peer)
			return client;
	return NULL;
}
int					NetServer::GetClientFreeHandleIndex(const Client &client) const
{
	int free_id = 0;
	ListForeachPtr(ClientHandleInfo *, i, client.handles)
		if	(i->id >= free_id)
			free_id = i->id + 1;
	return free_id;
}
Handle				*NetServer::GetClientHandle(const Client &client, int id) const
{
	ListForeachPtr(ClientHandleInfo *, i, client.handles)
		if	(i->id == id)
			return i->handle;
	return NULL;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				NetServer::ProcessCloseCommand(Client &client, GS::StringList &args)
// Close,handle_id
{
	if	(args.GetCount() != 2)
		return false;

	int id = args[1].Integer();

	ClientHandleInfo *hi = NULL;
	ListForeachPtr(ClientHandleInfo *, i, client.handles)
		if	(i->id == id)
		{
			hi = i;
			break;
		}

	if	(hi == NULL)
		return false;

	client.handles.Remove(hi);

#ifdef	VERBOSE_LOG
	__LOG__ << "IO::NetServer: Close handle " << id << " (client total: " << client.handles.GetCount() << ").\n";
#endif
	return SendString(client.peer, "Success");
}
bool				NetServer::ProcessReadCommand(Client &client, GS::StringList &args)
// Read,size,handle_id
{
	if	(args.GetCount() != 3)
		return false;

	Handle *h = GetClientHandle(client, args[2].Integer());
	if	(h == NULL)
		return false;

	// Read data.
	size_t size = size_t(args[1].Integer());
	Array <char> data(size);
	size_t read_size = h->Read((void *)data, size);

	// Format answer data (FIXME two allocations are not required for this).
	String answer = String::Format("Success,%d,", read_size);

	Array <char> answer_data(answer.Len() + read_size);
	Memory::Copy(answer_data.c_ptr(), answer.c_str(), answer.Len());
	Memory::Copy(answer_data.c_ptr() + answer.Len(), data.c_ptr(), read_size);

	return Send(client.peer, (void *)answer_data.c_ptr(), answer_data.GetSize());
}
bool				NetServer::ProcessSeekCommand(Client &client, GS::StringList &args)
// Seek,offset_from_start,ref,handle_id
{
	if	(args.GetCount() != 4)
		return false;

	Handle *h = GetClientHandle(client, args[3].Integer());
	if	(h == NULL)
		return false;

	ptrdiff_t offset = ptrdiff_t(args[1].Integer());

	Base::SeekRef seek_ref;
	if	(args[2] == "Start")
		seek_ref = Base::SeekStart;
	else if	(args[2] == "Current")
		seek_ref = Base::SeekCurrent;
	else if	(args[2] == "End")
		seek_ref = Base::SeekEnd;
	else
		return false;

	size_t r = h->Seek(offset, seek_ref);
	return SendString(client.peer, String::Format("Success,%d", r));
}
bool				NetServer::ProcessTellCommand(Client &client, GS::StringList &args)
// Tell,handle_id
{
	if	(args.GetCount() != 2)
		return false;

	Handle *h = GetClientHandle(client, args[1].Integer());
	if	(h == NULL)
		return false;

	return SendString(client.peer, String::Format("Success,%d", h->Tell()));
}
bool				NetServer::ProcessOpenCommand(Client &client, GS::StringList &args)
// Open,path
{
	if	(args.GetCount() != 2)
		__ERR__(__LOG_E__ << "NetServer::ProcessOpenCommand(): incorrect argument count.\n", false)

	int index = GetClientFreeHandleIndex(client);

	AutoPtr <ClientHandleInfo> i(new ClientHandleInfo);
	i->id = index;
	i->name = args[1];
	i->handle = basefs->Open(i->name);
	if	(i->handle.IsNull())
		return false; // __ERR__(__LOG_E__ << "NetServer::ProcessOpenCommand(): failed to open '" << i->name << "' on base filesystem.\n", false)

	client.handles.Add(i.Detach());

#ifdef	VERBOSE_LOG
	__LOG__ << "IO::NetServer: Open handle '" << args[1] << "' => " << index << " (client total: " << client.handles.GetCount() << ").\n";
#endif
	return SendString(client.peer, String::Format("Success,%d", index));
}
bool				NetServer::ProcessHashCommand(Client &client, GS::StringList &args)
// Hash,path
{
	if	(args.GetCount() != 2)
		return false;

	String hash = basefs->Hash(args[1]);
	if	(hash.IsEmpty())
		return false;

	return SendString(client.peer, String::Format("Success,%s", hash.c_str()));
}
bool				NetServer::ProcessClientRequest(Client &client, const GS::String &data)
{
	StringList args;
	data.Split(",", args);

	bool r = false;

	// Command dispatch.
	if	(args[0] == "Open")
		r = ProcessOpenCommand(client, args);
	else if	(args[0] == "Seek")
		r = ProcessSeekCommand(client, args);
	else if	(args[0] == "Tell")
		r = ProcessTellCommand(client, args);
	else if	(args[0] == "Read")
		r = ProcessReadCommand(client, args);
	else if	(args[0] == "Close")
		r = ProcessCloseCommand(client, args);
	else if	(args[0] == "Hash")
		r = ProcessHashCommand(client, args);

	if	(!r)
		SendString(client.peer, "Failed");
	return r;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				NetServer::GetStatistics(Statistics &stats)
{
	Time t = Platform::Get().GetTime();

	stats.connected = asbool(clients.GetCount());

	Network::Enet::Statistics enet_stats;
	Network::Enet::GetStatistics(enet_stats);

	if	((t - bandwidth_measure.time).toSec() > 2)
	{
		bandwidth = int((enet_stats.sent_data - bandwidth_measure.value) / (t - bandwidth_measure.time).toSec());

		bandwidth_measure.time = t;
		bandwidth_measure.value = enet_stats.sent_data;
	}

	stats.sent_data = enet_stats.sent_data;
	stats.bandwidth = bandwidth;

	stats.packet_loss = 0;
	if	(clients.GetCount() > 0)
	{
		ListForeachPtr(Client *, client, clients)
			stats.packet_loss += GetPeerPacketLossRatio(client->peer);
		stats.packet_loss /= clients.GetCount();
	}

	// Handle statistics.
	int handle_count = 0;
	ListForeachPtr(Client *, client, clients)
		handle_count += client->handles.GetCount();

	if	(stats.handles.Allocate(handle_count))
	{
		handle_count = 0;
		ListForeachPtr(Client *, client, clients)
			ListForeachPtr(ClientHandleInfo *, i, client->handles)
			{
				Statistics::Handle *h = &stats.handles[handle_count++];
				h->name = i->name;
			}
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				NetServer::OnPeerConnection(void *peer)
{
	__LOG_H__ << "IO::NetServer: OnPeerConnection\n";

	__ASSERT__(GetClient(peer) == NULL);
	clients.Add(new Client(peer));

	SetPeerTimeout(peer, TimeoutVeryLong);
}
void				NetServer::OnPacketReceived(void *peer, const void *data, size_t size)
{
	Client *client = GetClient(peer);
	__ASSERT__(client != NULL);

	if	(size > 512)
	{
		SendString(peer, "DataLengthError");
		return;		// invalid
	}

	String command((char *)data, (char *)data + size);

	if	(client->handshake_step == -1)
		ProcessClientRequest(*client, command);

	else
	{
		switch (client->handshake_step)
		{
			case 0:
				if	(command == "RequestIOAccess")
				{
					__LOG_V__ << "Granting client access.\n";
					client->handshake_step = -1;

					SendString(peer, "Granted");
				}
				else
					Disconnect(peer);
				break;
		}
	}
}
void				NetServer::OnConnectionClosed(void *peer)
{
	__LOG_H__ << "IO::NetServer: OnConnectionClosed\n";

	Client *client = GetClient(peer);
	__ASSERT__(client != NULL);
	clients.Remove(client);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				NetServer::Start(const char *ip, int port)
{
	__LOG_H__ << "IO::NetServer: Starting on " << ip << " port " << port << ".\n";
	return OpenServer(ip, port);
}
void				NetServer::Stop()
{
	__LOG_H__ << "IO::NetServer: Shutting down.\n";

	// Disconnect all clients.
	ListForeachPtr(Client *, c, clients)
		Disconnect(c->peer);

	StartTaskLoop(clients.GetCount() > 0, 2000)
	{
		UpdateHost();
		Platform::Get().Sleep(1);
	}
	EndTaskLoop

	Close();
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
NetServer::NetServer(Base *fs) : basefs(fs), bandwidth(0)
{}
NetServer::~NetServer()
{	Stop();	}
//------------------------------------------------------------------------------
