/***************************************************************************
 Copyright (c) 2009 Stephen Roderick <xxxstephen AT theptrgroupxxx DOT comxxx>
                                             (remove the x's above)

 ***************************************************************************
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Lesser General Public            *
 *   License as published by the Free Software Foundation; either          *
 *   version 2.1 of the License, or (at your option) any later version.    *
 *                                                                         *
 *   This library 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     *
 *   Lesser General Public License for more details.                       *
 *                                                                         *
 *   You should have received a copy of the GNU Lesser General Public      *
 *   License along with this library; if not, write to the Free Software   *
 *   Foundation, Inc., 59 Temple Place,                                    *
 *   Suite 330, Boston, MA  02111-1307  USA                                *
 *                                                                         *
 ***************************************************************************/

/** \file SimpleNonPeriodicClient.cpp
	A simple TCP client using a non-periodic component.

	This very simple client connects to a remote TCP port, and reads data
	from that port and prints it out. This is done using a non-periodic
	component, as the amount of time spent blocked waiting for data is
	variable and indeterminate (subject to the timeout values).

	\section tobuild To build

	Open a shell
	<code>
	cd /path/to/SimpleNonPeriodicClient
	mkdir build
	cd build
	cmake ..
	make
	</code>

	\section torun To run

	Start one shell and run netcat to act as the server (NB 50001 is the
	"HostPort" value from your SimpleNonPeriodicClient.cpf file)

	<code>
	nc -l 50001
	</code>

	Start a second shell and deploy the SimpleNonPeriodicClient component

	<code>
	cd /path/to/SimpleNonPeriodicClient/build
	deployer-macosx -s ../SimpleNonPeriodicClient.xml
	</code>

	Now type in the first shell, and after N characters the data will be
	printed by the SimpleNonPeriodicClient component (where N is the size
	of the buffer in updateHook()).

	\section assumptions Assumptions

	The build directory is within the source directory. This helps with
	dynamic library loading.

	Does not attempt reconnection if unable connect on first attempt.

	Non-robust error handling.

	Does not validate property values (a robust component would validate that
	the timeouts were valid, eg. not negative, within a configureHook()).
*/

#include "SimpleNonPeriodicClient.hpp"
#include <rtt/Logger.hpp>
#include <ocl/ComponentLoader.hpp>

// all Qt headers MUST come after Orocos headers (due to Qt's preprocessor
// redefining madness ...)
#include <QTcpSocket>

#ifndef	min
#define	min(a,b)		((a) < (b) ? (a) : (b))
#endif

using namespace RTT;

SimpleNonPeriodicClient::SimpleNonPeriodicClient(std::string name) :
		RTT::TaskContext(name),
		lastRead_port("lastRead", ""),
		countRead_attr("countRead", 0),
		hostName_prop("HostName", "Name to listen for incoming connections on (FQDN or IPv4)", ""),
		hostPort_prop("HostPort", "Port to listen on (1024-65535 inclusive)", 0),
		connectionTimeout_prop("ConnectionTimeout", "Timeout in seconds, when waiting for connection", 0),
		readTimeout_prop("ReadTimeout", "Timeout in seconds, when waiting for read", 0),
		socket(new QTcpSocket),
		quit(false)
{
	ports()->addPort(&lastRead_port);

	attributes()->addAttribute(&countRead_attr);

	properties()->addProperty(&hostName_prop);
	properties()->addProperty(&hostPort_prop);
	properties()->addProperty(&connectionTimeout_prop);
	properties()->addProperty(&readTimeout_prop);
}

SimpleNonPeriodicClient::~SimpleNonPeriodicClient()
{
	delete socket;
}

bool SimpleNonPeriodicClient::startHook()
{
	Logger::In in(this->getName().data());

	bool		rc					= false;		// prove otherwise
	std::string	hostName			= hostName_prop.rvalue();
	int			hostPort			= hostPort_prop.rvalue();
	int 		connectionTimeout	= connectionTimeout_prop.rvalue();

	quit = false;

	// attempt to connect to remote host/port
	log(Info) << "Connecting to " << hostName << ":" << hostPort << endlog();
	socket->connectToHost(hostName.c_str(), hostPort);
	if (socket->waitForConnected(1000 * connectionTimeout))	// to millseconds
	{
		log(Info) << "Connected" << endlog();
		rc = true;
	}
	else
	{
		log(Error) << "Error connecting: " << socket->error() << ", "
				   << socket->errorString().toStdString() << endlog();
		// as we now return false, this component will fail to start.
	}

	return rc;
}

void SimpleNonPeriodicClient::updateHook()
{
	Logger::In in(this->getName().data());

	// wait for some data to arrive, timing out if necessary
	int 	readTimeout		= readTimeout_prop.rvalue();
	log(Debug) << "Waiting for data with timeout=" << readTimeout << " seconds" << endlog();
	if (!socket->waitForReadyRead(1000 * readTimeout))
	{
		log(Error) << "Error waiting for data: " << socket->error() << ", "
				   << socket->errorString().toStdString()
				   << ". Num bytes = "
				   << socket->bytesAvailable() << endlog();
		log(Error) << "Disconnecting" << endlog();
		// disconnect socket, and do NOT call this function again
		// ie no engine()->getActivity()->trigger()
		socket->disconnectFromHost();
		return;
	}

	// read and print whatever data is available, but stop if instructed
	// to quit
	while (!quit && (0 < socket->bytesAvailable()))
	{
#define	BUFSIZE		10
		char			str[BUFSIZE + 1];	// +1 for terminator
		qint64			numRead;

		numRead = socket->read((char*)&str[0],
							   min(BUFSIZE, socket->bytesAvailable()));
		str[BUFSIZE] = '\0';		// forcibly terminate
		if (0 < numRead)
		{
			log(Info) << "Got " << numRead << " bytes : '" << &str[0] << "'" << endlog();
			countRead_attr.set(countRead_attr.get() + 1);
			lastRead_port.Set(&str[0]);
		}
	}

	// if not quitting then trigger another immediate call to this function, to
	// get the next batch of data
	if (!quit)
	{
		engine()->getActivity()->trigger();
	}
}

void SimpleNonPeriodicClient::stopHook()
{
	Logger::In in(this->getName().data());

	log(Debug) << "Stopping()" << endlog();

	// disconnect if the socket is valid and connected
	if (socket->isValid() &&
		(QAbstractSocket::ConnectedState == socket->state()))
	{
		log(Info) << "Disconnecting" << endlog();
		socket->disconnectFromHost();
	}
}

bool SimpleNonPeriodicClient::breakUpdateHook()
{
	Logger::In in(this->getName().data());

	/* indicate to updateHook() that we want it to return so that the component
	   can be stopped.

	   If you wanted to force the waitForReadyRead() in updateHook() to return
	   immediately (this is the most likely place updateHook() is blocked), then
	   you could force this using something like "socket->abort()" at this
	   point.

	   NB sending signals to force a system call to immediately return, may not
	   have the intended effect when this component is deployed ...  in case you
	   were thinking of using that to get out of a read(), write(), etc ... :-)
	*/
	log(Debug) << "Breaking the updateHook()" << endlog();
	quit = true;

	return true;
}

ORO_CREATE_COMPONENT(SimpleNonPeriodicClient)
