//------------------------------------------------------------------------------
//	Copyright (C) 2010 Humboldt-Universitaet zu Berlin
//
//	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 Example_ProtocolService.cpp
 * @author Ronald Kluth
 * @date created at 2010/04/01
 * @brief Example showing usage of the protocol simulation module
 * @since 3.0
*/

/**
 * @example Example_ProtocolService.cpp
 * This example shows the simple communication protocol XCS with lowest detail.
 *
 * Class Node uses a simple service implementation to communicate with
 * another node. We abstract from the stack as well as the network topology.
 * The communication runs directly between the connected service
 * objects.
 */

#include <odemx/odemx.h>
using namespace odemx;
using namespace odemx::protocol;
using std::tr1::static_pointer_cast;

typedef Service::AddressType Addr;
typedef std::string SapName;
//
// PDU type used for data transfer between nodes
//
struct StringData: Pdu
{
	typedef std::tr1::shared_ptr< StringData > Ptr;
	Addr src, dest;
	SapName destSap;
	std::string data;

	StringData( const Addr& src, const Addr& dest, const SapName& dSap,
			const std::string& data )
	:	src( src ), dest( dest ), destSap( dSap ), data( data )
	{}
	virtual PduPtr clone() const
	{
		return PduPtr( new StringData( src, dest, destSap , data ) );
	}
	virtual std::size_t getSize() const
	{
		return src.size() + dest.size() + destSap.size() + data.size() ;
	}
};
//
// Simple service implementation that waits before sending the data in
// order to model a transmission duration.
//
class XcsService: public Service
{
public:
	typedef StringData PduType;

	XcsService( base::Simulation& sim, const data::Label& label )
	:	Service( sim, label )
	{
		addSap( "XCSin" );
	}
	virtual void handleSend( const std::string& sap, PduPtr p )
	{
		info << log( "send" );
		holdFor( p->getSize() );
		send( static_pointer_cast< PduType >( p )->dest, p );
	}
	virtual void handleReceive( PduPtr p )
	{
		info << log( "pass" );
		pass( static_pointer_cast< PduType >( p )->destSap, p );
	}
};
//
// Node is a very simple process that can either be a sender or a receiver,
// depending on whether a receiver address is set. In the simplest form, a
// service object is used to facilitate the communication between nodes.
//
class Node: public base::Process
{
public:
	Node( const data::Label& label, const Addr& addr, const Addr& recAddr = "" )
	:	Process( getDefaultSimulation(), label )
	,	service_( getSimulation(), label + " service" )
	,	address_( addr )
	,	receiver_( recAddr )
	,	isSender_( ! recAddr.empty() )
	{
		service_.addReceiveSap( "XCSout" );
		XcsService::registerService( addr, service_ );
	}
	virtual int main()
	{
		if( isSender_ )
		{
			Sap* sap = service_.getSap( "XCSin" );
			StringData::Ptr pdu( new StringData( address_, receiver_, "XCSout", "message" ) );
			info << log( "sending PDU via XCSin" )
					.detail( "to", pdu->dest )
					.detail( "data", pdu->data );
			sap->write( pdu );
		}
		else
		{
			Sap* sap = service_.getReceiveSap( "XCSout" );
			while( true )
			{
				StringData::Ptr pdu = static_pointer_cast< StringData >( sap->read() );
				info << log( "received PDU via XCSout" )
						.detail( "from", pdu->src )
						.detail( "data", pdu->data );
			}
		}
		return 0;
	}
private:
	XcsService service_;
	Addr address_;
	Addr receiver_;
	bool isSender_;
};

//--------------------------------------------------------------------------main

int main()
{
	base::Simulation& sim = getDefaultSimulation();
	sim.addConsumer( data::channel_id::info,
			data::output::OStreamWriter::create( std::cout ) );

	Node sender( "Sender", "Addr0", "Addr1" );
	Node receiver( "Receiver", "Addr1" );
	//
	// Since registered service objects are connected automaticallly,
	// the sending of data will work automatically. No links need be set.
	//
	sender.activate();
	receiver.activate();
	sim.run();
}
