//------------------------------------------------------------------------------
//	Copyright (C) 2009, 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 Medium.cpp
 * @author Ronald Kluth
 * @date created at 2009/11/01
 * @brief Implementation of class odemx::protocol::Medium
 * @sa Medium.h
 * @since 3.0
 */

#include <odemx/protocol/Medium.h>
#include <odemx/protocol/Device.h>
#include <odemx/protocol/ErrorModel.h>
#include <odemx/protocol/Pdu.h>
#include <odemx/util/TypeToString.h>

namespace odemx {
namespace protocol {

//---------------------------------------------------------------------link info

Medium::LinkInfo::LinkInfo()
{
}

Medium::LinkInfo::LinkInfo( base::SimTime delay, ErrorModelPtr errorModel )
:	delay_( delay )
,	errorModel_( errorModel )
{
}

//-----------------------------------------------------------transmission events

Medium::TransmissionStart::TransmissionStart( base::Simulation& sim,
		const data::Label& label, Device& device )
:	Event( sim, label )
,	device_( &device )
{
}

void Medium::TransmissionStart::eventAction()
{
	device_->incTransmissionCount();
}

Medium::TransmissionEnd::TransmissionEnd( base::Simulation& sim,
		const data::Label& label, Device& device, PduPtr p )
:	Event( sim, label )
,	device_( &device )
,	pdu_( p )
{
}

void Medium::TransmissionEnd::eventAction()
{
	device_->decTransmissionCount();
	if( pdu_ )
	{
		device_->receive( pdu_ );
	}
}

//------------------------------------------------------construction/destruction

Medium::Medium( base::Simulation& sim, const data::Label& label )
:	Producer( sim, label )
,	topology_()
,	defaultErrorModel_()
{
	ODEMX_TRACE << log( "create" ).scope( typeid(Medium) );
}

Medium::~Medium()
{
	ODEMX_TRACE << log( "destroy" ).scope( typeid(Medium) );
}

void Medium::signalTransmissionStart( Device* device )
{
	assert( device );

	ODEMX_TRACE << log( "signal transmission start" )
			.detail( "device", device->getLabel() )
			.scope( typeid(Medium) );

	LinkInfoMap& receivers = topology_[ device ];
	for( LinkInfoMap::const_iterator iter = receivers.begin();
		iter != receivers.end(); ++iter )
	{
		// get the neighbor and the link properties
		Device* peerDevice = iter->first;
		const LinkInfo& link = iter->second;

		// ignore self-messaging
		if( device == peerDevice )
		{
			continue;
		}
		
		// data is propagated if there is no error model for the link,
		// or if the data passes the error model
		base::Event* event = new TransmissionStart( getSimulation(),
				"Transmission Start", *peerDevice );
		event->scheduleIn( link.delay_ );
	}
}

void Medium::signalTransmissionEnd( Device* device, PduPtr p )
{
	ODEMX_TRACE << log( "signal transmission end" )
			.detail( "device", device->getLabel() )
			.scope( typeid(Medium) );

	LinkInfoMap& receivers = topology_[ device ];
	for( LinkInfoMap::const_iterator iter = receivers.begin();
		iter != receivers.end(); ++iter )
	{
		statistics << count( "transmissions" ).scope( typeid(Medium) );

		// get the neighbor and the link properties
		Device* peerDevice = iter->first;
		const LinkInfo& link = iter->second;

		// ignore self-messaging
		if( device == peerDevice )
		{
			continue;
		}

		// the pdu gets clone
		PduPtr clonePdu;
		if( p )
		{
			clonePdu = p->clone();
		}

		// data is propagated if there is no error model for the link,
		// or if the data passes the error model
		if( ! link.errorModel_ || link.errorModel_->apply( clonePdu ) )
		{
			base::Event* event = new TransmissionEnd( getSimulation(),
					"Transmission End", *peerDevice, clonePdu );
			event->scheduleIn( link.delay_ );

			statistics << count( "deliveries" ).scope( typeid(Medium) );
		}
		else // failed to pass error model, cannot not deliver no PDU
		{
			base::Event* event = new TransmissionEnd( getSimulation(),
					"Transmission End", *peerDevice, PduPtr() );
			event->scheduleIn( link.delay_ );

			statistics << count( "errors" ).scope( typeid(Medium) );
		}
	}
}

void Medium::collision() const
{
	statistics << count( "collisions" ).scope( typeid(Medium) );
}

void Medium::setErrorModel( ErrorModelPtr errorModel )
{
	ODEMX_TRACE << log( "set error model" )
			.detail( "type", typeToString( typeid( *errorModel ) ) )
			.scope( typeid(Medium) );

	defaultErrorModel_ = errorModel;
}

bool Medium::addLink( const Device::AddressType& srcAddr,
		const Device::AddressType& destAddr, base::SimTime delay,
		bool duplex, ErrorModelPtr errorModel )
{
	ODEMX_TRACE << log( "add link" )
			.detail( "from", srcAddr )
			.detail( "to", destAddr )
			.detail( "delay", delay )
			.detail( "error model", errorModel ? typeToString( typeid( *errorModel ) ) : "default" )
			.scope( typeid(Medium) );

	Device* src = getDevice( srcAddr );
	Device* dest = getDevice( destAddr );

	assert( src );
	assert( dest );

	bool success = addLink( src, dest, delay, errorModel );
	if( duplex && success )
	{
		return addLink( dest, src, delay, errorModel );
	}
	return success;
}

bool Medium::addLink( Device* src, Device* dest, base::SimTime delay,
		ErrorModelPtr errorModel )
{
	if( src == dest )
	{
		warning << log( "Medium::addLink(): the given devices are the same" )
				.scope( typeid( Medium ) );
		return false;
	}

	// overwrite existing entries
	if( errorModel )
	{
		topology_[ src ][ dest ] = LinkInfo( delay, errorModel );
	}
	else
	{
		topology_[ src ][ dest ] = LinkInfo( delay, defaultErrorModel_ );
	}
	return true;
}

void Medium::removeLink( const Device::AddressType& srcAddr,
		const Device::AddressType& destAddr, bool duplex )
{
	ODEMX_TRACE << log( "remove link" )
			.detail( "from", srcAddr )
			.detail( "to", destAddr )
			.scope( typeid(Medium) );

	Device* src = getDevice( srcAddr );
	Device* dest = getDevice( destAddr );

	removeLink( src, dest );
	if( duplex )
	{
		return removeLink( dest, src );
	}
}

void Medium::removeLink( Device* src, Device* dest )
{
	if( src == dest )
	{
		warning << log( "Medium::removeLink(): the given devices are the same" )
				.scope( typeid( Medium ) );
		return;
	}

	TopologyMap::iterator foundSrc = topology_.find( src );
	if( foundSrc != topology_.end() )
	{
		LinkInfoMap& links = foundSrc->second;
		LinkInfoMap::iterator foundDest = links.find( dest );
		if( foundDest != links.end() )
		{
			// found link between both nodes, erase LinkInfo
			links.erase( foundDest );
		}
	}
}

bool Medium::registerDevice( const Device::AddressType& address, Device& device )
{
	device.setAddress( address );
	device.setMedium( *this );
	return devices_.insert( DeviceMap::value_type( address, &device ) ).second;
}

Device* Medium::getDevice( const Device::AddressType& address ) const
{
	DeviceMap::const_iterator found = devices_.find( address );
	if( found != devices_.end() )
	{
		return found->second;
	}

	error << log( "Medium::getDevice(): no device registered" )
			.detail( "address", address )
			.scope( typeid(Medium) );
	return 0;
}

const Medium::TopologyMap& Medium::getTopology() const
{
	return topology_;
}

const Medium::DeviceMap& Medium::getDevices() const
{
	return devices_;
}

} } // namespace odemx::protocol
