//------------------------------------------------------------------------------
//	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_ProtocolDevice.cpp
 * @author Ronald Kluth
 * @date created at 2010/04/02
 * @brief Example showing usage of the protocol simulation module
 * @since 3.0
*/

/**
 * @example Example_ProtocolDevice.cpp
 * This example shows the simple communication protocol XCS with highest detail.
 *
 * Class Node uses a protocol stack to communicate with another node. The stack
 * is composed of an entity class in the upper layer and a device in the
 * lower layer. The communication runs over a medium which connects the
 * devices of network nodes.
 */

#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 in the upper layer
//
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() ;
	}
};
//
// PDU type used in the lower layer
//
struct TransmissionData: Pdu
{
	Addr& src;
	Addr& dest;
	StringData::Ptr payload;

	TransmissionData( StringData::Ptr data )
	:	src( data->src ), dest( data->dest ), payload( data )
	{}
	virtual PduPtr clone() const
	{
		return PduPtr( new TransmissionData( payload ) );
	}
	virtual std::size_t getSize() const
	{
		return payload->getSize();
	}
};
//
// The entity of the upper layer, which transforms StringData into
// TransmissionData.
//
class XcsEntity: public Entity
{
public:
	XcsEntity( base::Simulation& sim, const data::Label& label )
	:	Entity( sim, label )
	{
		addSap( "XCSin" );
		addSap( "CSout" );
	}
	virtual void handleInput( const std::string& sapName, PduPtr p )
	{
		if( sapName == "XCSin" )
		{
			StringData::Ptr pdu = static_pointer_cast< StringData >( p );
			info << log( "send" );
			send( "CSin", PduPtr( new TransmissionData( pdu ) ) );
		}
		else if( sapName == "CSout" )
		{
			StringData::Ptr pdu = static_pointer_cast< TransmissionData >( p )->payload;
			info << log( "pass" );
			pass( pdu->destSap, pdu );
		}
	}
};
//
// Device class that sends data via the medium
//
class XcsDevice: public Device
{
public:
	typedef TransmissionData PduType;

	XcsDevice( base::Simulation& sim, const data::Label& label )
	:	Device( sim, label )
	{
		addSap( "CSin" );
	}
	virtual void handleSend( const std::string& sap, PduPtr p )
	{
		info << log( "send" );
		send( p );
	}
	virtual void handleReceive( PduPtr p )
	{
		info << log( "pass" );
		pass( "CSout", p );
	}
	virtual base::SimTime computeTransmissionDuration( std::size_t pduSize )
	{
		return static_cast< base::SimTime >( pduSize );
	}
};
//
// Global medium
//
Medium medium( getDefaultSimulation(), "TransmissionMedium" );
//
// A stack factory function that creates the stack's layers and registers
// service providers with them. The layers are then added to the stack
// in top-down order.
//
std::auto_ptr< Stack > makeStack( base::Simulation& sim, const data::Label& label,
		const Addr& address )
{
	Layer* entityLayer = new Layer( sim, label + " entity layer" );
	entityLayer->addServiceProvider( new XcsEntity( sim, label + " entity" ) );
	Layer* deviceLayer = new Layer( sim, label + " device layer" );
	Device* device = new XcsDevice( sim, label + " device" );
	deviceLayer->addServiceProvider( device );
	medium.registerDevice( address, *device );

	std::auto_ptr< Stack > stack( new Stack( sim, label ) );
	stack->addLayer( entityLayer );
	stack->addLayer( deviceLayer );
	return stack;
}
//
// Node is a very simple process that can either be a sender or a receiver,
// depending on whether a reciever address is set. A stack 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 )
	,	stack_( makeStack( getSimulation(), getLabel(), addr ) )
	,	address_( addr )
	,	receiver_( recAddr )
	,	isSender_( ! recAddr.empty() )
	{
		stack_->addReceiveSap( "XCSout" );
	}
	virtual int main()
	{
		if( isSender_ )
		{
			Sap* sap = stack_->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 = stack_->getReceiveSap( "XCSout" );
			while( true )
			{
				StringData::Ptr pdu = std::tr1::static_pointer_cast< StringData >( sap->read() );
				info << log( "received PDU via XCSout" )
						.detail( "from", pdu->src )
						.detail( "data", pdu->data );
			}
		}
		return 0;
	}
private:
	std::auto_ptr< Stack > stack_;
	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" );
	sender.activate();
	receiver.activate();
	//
	// Connect the devices of the two nodes by their addresses.
	//
	medium.addLink( "Addr0", "Addr1", 0 );
	//
	// Let the sender and receiver do their work.
	//
	sim.run();
}
