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


		#include "io_net/io_net_client.h"
		#include "ascii/parser.h"
		#include "container/nlist.h"
		#include "nstring/nstring.h"
		#include "platform.h"
		#include "log/log.h"

		using namespace GS::IO;
		using namespace GS::Threading;


//------------------------------------------------------------------------------
bool				NetWorkerThread::ProcessOpenTask(NetWorkerOpenTask &task)
{
	if	(!server_peer)
		__ERR__(__LOG_F__ << "No peer.\n", false)
	if	(!SendString(server_peer, String("Open,") << task.path))
		__ERR__(__LOG_F__ << "SendString failed, server = '" << server_peer << "', task.path = '" << task.path << "'\n", false)

	if	(!WaitServerResponse())
		__ERR__(__LOG_F__ << "WaitServerResponse = false\n", false)

	String answer(response.c_ptr(), response.GetSize());
	ClearServerResponse();

	StringList args;
	if	(answer.Split(",", args) != 2)
		__ERR__(__LOG_F__ << "NetWorkerThread Wrong arg count: " << answer << "\n", false)

	if	(args[0] != "Success")
		__ERR__(__LOG_F__ << "args[0] != Success ('" << args[0] << "')\n", false)

	task.handle = args[1].Integer();
	return true;
}
bool				NetWorkerThread::ProcessCloseTask(NetWorkerCloseTask &task)
{
	if	(!server_peer || !SendString(server_peer, String("Close,") << task.handle))
		return false;

	if	(WaitServerResponse())	// FIXME does not even check for success...
		ClearServerResponse();
	return true;
}
bool				NetWorkerThread::ProcessSeekTask(NetWorkerSeekTask &task)
{
	String seek_ref;
	switch (task.seek_ref)
	{
		case Base::SeekStart:	seek_ref = "Start";		break;
		case Base::SeekCurrent:	seek_ref = "Current";	break;
		case Base::SeekEnd:		seek_ref = "End";		break;
	}
	if	(!server_peer || !SendString(server_peer, String::Format("Seek,%d,%s,%d", task.offset, seek_ref.c_str(), task.handle)))
		return false;

	if	(!WaitServerResponse())
		return false;
	String answer(response.c_ptr(), response.GetSize());
	ClearServerResponse();

	StringList args;
	if	(answer.Split(",", args) != 2)
		return false;
	if	(args[0] != "Success")
		return false;

	task.pos = size_t(args[1].Integer());
	return true;
}
bool				NetWorkerThread::ProcessTellTask(NetWorkerTellTask &task)
{
	if	(!server_peer || !SendString(server_peer, String("Tell,") << task.handle))
		return false;

	if	(!WaitServerResponse())
		return false;
	String answer(response.c_ptr(), response.GetSize());
	ClearServerResponse();

	StringList args;
	if	(answer.Split(",", args) != 2)
		return false;
	if	(args[0] != "Success")
		return false;

	task.pos = size_t(args[1].Integer());
	return true;
}
bool				NetWorkerThread::ProcessReadTask(NetWorkerReadTask &task)
{
	if	(!server_peer || !SendString(server_peer, String::Format("Read,%d,%d", task.size, task.handle)))
		return false;

	if	(!WaitServerResponse())
		return false;

	// Check success.
	if	(Memory::Compare("Success,", response.c_ptr(), 8))
		return false;	// failed

	// Parse size.
	const char *p_size = response.c_ptr() + 8;
	const char *e_size = AsciiParser::Find(response.c_ptr() + 8, response.End(), ',');
	if	(e_size == NULL)
		return false;
	task.read_size = size_t(String(p_size, e_size).Integer());

	// Get data.
	const char *p_data = e_size + 1;
	if	(task.read_size != size_t(response.End() - p_data))
		return false;	// assert buffer size and reported size match
	if	(task.read_size > task.size)
		return false;	// prevent buffer overrun

	Memory::Copy(task.data, p_data, task.read_size);

	ClearServerResponse();
	return true;
}
bool				NetWorkerThread::ProcessHashTask(NetWorkerHashTask &task)
{
	if	(!server_peer || !SendString(server_peer, String("Hash,") << task.path))
		return false;

	if	(!WaitServerResponse())
		return false;

	// Check success.
	if	(Memory::Compare("Success,", response.c_ptr(), 8))
		return false;	// failed

	// Grab hash.
	task.hash.Set(response.c_ptr() + 8);

	ClearServerResponse();
	return true;
}
void				NetWorkerThread::ProcessTask(NetWorkerBaseTask &task)
{
	bool success = false;

	switch (task.type)
	{
		case NetWorkerBaseTask::TypeOpen:	success = ProcessOpenTask((NetWorkerOpenTask &)task);	break;
		case NetWorkerBaseTask::TypeClose:	success = ProcessCloseTask((NetWorkerCloseTask &)task);	break;
		case NetWorkerBaseTask::TypeSeek:	success = ProcessSeekTask((NetWorkerSeekTask &)task);	break;
		case NetWorkerBaseTask::TypeTell:	success = ProcessTellTask((NetWorkerTellTask &)task);	break;
		case NetWorkerBaseTask::TypeRead:	success = ProcessReadTask((NetWorkerReadTask &)task);	break;
		case NetWorkerBaseTask::TypeHash:	success = ProcessHashTask((NetWorkerHashTask &)task);	break;
	}

	// [EJ] Full memory barrier required here.
	task.success.Set(success ? 1 : 0);
	task.processed.Set(1);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				NetWorkerThread::QueueTask(NetWorkerBaseTask &task)
{
	if	(server_peer == NULL)
		return false;	// [EJ] no queue when not connected

	MutexLock lock(&task_mutex);
	return asbool(task_queue.Add(&task));
}
bool				NetWorkerThread::CancelTask(NetWorkerBaseTask &task)
{
	MutexLock lock(&task_mutex);
	return asbool(task_queue.Remove(&task));
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				NetWorkerThread::OnPeerConnection(void *peer)
{
	__LOG_V__ << "IO::Net: OnPeerConnection\n";

	if	(server_peer || (handshaking != 1)) // already connected
	{
		Disconnect(peer);
		return;
	}

	SetPeerTimeout(peer, TimeoutVeryLong);
	SendString(peer, "RequestIOAccess");

	++handshaking;
}
void				NetWorkerThread::OnPacketReceived(void *peer, const void *data, size_t size)
{
//	__LOG_V__ << "IO::Net: Packet received, handshaking = " << handshaking << ", data: " << String((const char *)data, (const char *)data + size) << "\n";

	if	(handshaking == 2)
	{
		if	((size == 8) && !GS::Memory::Compare(data, "Granted", size))
		{
			server_peer = peer;
			++handshaking;
		}
		else
			Disconnect(peer);
	}
	else
		if	(server_peer == peer)
			ProcessIOPacket(data, size);
}
void				NetWorkerThread::OnConnectionClosed(void *peer)
{
	if	(server_peer == peer)
		server_peer = NULL;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				NetWorkerThread::ProcessIOPacket(const void *data, size_t size)
{
	if	(response.Allocate(size))
		Memory::Copy(response.c_ptr(), data, size);
}
bool				NetWorkerThread::WaitServerResponse()
{
	while (response.GetSize() == 0)
	{
		if	(server_peer == NULL)
			return false;
		UpdateHost();
	}
	return true;
}
void		NetWorkerThread::ClearServerResponse()
{	response.Free();	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void		NetWorkerThread::Execute()
{
	Thread::SetName("GS::IO::NetWorkerThread");

	handshaking = 1;

	__LOG_V__ << "IO::Net - Client worker thread connecting to " << ip << " port " << port << ".\n";
	if	(!OpenClient(ip, port))
		__ERRRAW__(__LOG_E__ << "Connection failed.\n");

	__LOG_V__ << "IO::Net - Entering file server loop...\n";
	for (running.Set(1); running.Get() != 2; )
	{
		UpdateHost();

		if	(server_peer != NULL)
		{
			MutexLock lock(&task_mutex);
			while (NetWorkerBaseTask *task = task_queue.GetCount() > 0 ? task_queue.GetRoot()->Object() : NULL)
			{
				task_queue.RemoveAt(0);
				ProcessTask(*task);
			}

			if	(server_peer == NULL) // [EJ] if connection was lost during this run, drop all pending tasks.
				task_queue.Clear();
		}

		Platform::Get().Sleep(1);
	}

	handshaking = 0;

	__LOG_V__ << "IO::Net - Client worker thread exiting.\n";
	running.Set(0);
}
bool				NetWorkerThread::Start(const char *_ip, int _port)
{
	ip = _ip;
	port = _port;
	return Thread::Start();
}
void				NetWorkerThread::Stop()
{
	if	(running.Get() != 0)
	{
		running.Set(2);
		while (running.Get() != 0); // spinlock
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
NetWorkerThread::NetWorkerThread() : server_peer(0), handshaking(0) {}
//------------------------------------------------------------------------------
