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

#ifndef ODEMX_PROTOCOL_MEDIUM_INCLUDED
#define ODEMX_PROTOCOL_MEDIUM_INCLUDED

#include <odemx/data/Producer.h>
#include <odemx/base/Event.h>
#include <odemx/protocol/Device.h>
#include <odemx/protocol/Pdu.h>

//----------------------------------------------------------forward declarations

namespace odemx {
namespace protocol {

class ErrorModel;

//-----------------------------------------------------------header declarations

/**
 * @brief Models the transmission medium used by devices to transmit data
 * @ingroup protocol
 * @author Ronald Kluth
 * @since 3.0
 * @see odemx::protocol::Device odemx::protocol::ErrorModel
 *
 * The Medium manages the topology of a network in the form of links between
 * Device objects. All devices must be registered to a medium in order to
 * be able to send PDUs to their neighbor nodes. Whenever a device transmits
 * data, the medium is responsible for scheduling the corresponding TransmissionStart
 * and TransmissionEnd events for all neighbors of a node.
 *
 * Furthermore, an error model can be assigned to the medium so that arbitrary
 * transmission failures may be modeled in a uniform way. If more fine-grained
 * control is required, each link can also be assigned an error model.
 *
 */
class Medium
:	public data::Producer
{
public:
	/// Map type to associate addresses with device objects
	typedef std::map< Device::AddressType, Device* > DeviceMap;

	/// Pointer type to manage ErrorModel object lifetime
	typedef std::tr1::shared_ptr< ErrorModel > ErrorModelPtr;

	/// Structure containing the link properties delay and error model
	struct LinkInfo
	{
		/// Defaut construction
		LinkInfo();
		/// Construction with link delay and error model
		LinkInfo( base::SimTime delay, ErrorModelPtr errorModel );
		/// The specified link delay
		base::SimTime delay_;
		/// The provided error model for this link
		ErrorModelPtr errorModel_;
	};

	/// Map type associating device pointers with link info objects
	typedef std::map< Device*, LinkInfo > LinkInfoMap;

	/// Map type associating device pointers with a link info map (i.e. links to neighbors)
	typedef std::map< Device*, LinkInfoMap > TopologyMap;

	/// Construction
	Medium( base::Simulation& sim, const data::Label& label );

	/// Destruction
	virtual ~Medium();

	/**
	 * @brief Notify all neighbors of the device that it will start sending
	 * @param sender The device that starts a transmission
	 *
	 * This method is used to signal the start of a transmission to all
	 * neighbor nodes in the network. This is implemented by scheduling
	 * TransmissionStart events after the specified link delay for each
	 * registered link of the sender device.
	 */
	void signalTransmissionStart( Device* sender );

	/**
	 * @brief Notify all neighbors of the device about the transmission end and deliver PDUs
	 * @param sender The device that finished its transmission
	 * @param p The PDU to be delivered to receiving nodes
	 *
	 * This method is used to signal the end of a transmission to all
	 * neighbor nodes in the network. This can either mean that the transmission
	 * was aborted, for example due o collision detection, or that it was
	 * transmitted to the medium successfully (which not necessarily means
	 * the PDU arrives at the receivers).
	 *
	 * Like @c signalTransmissionStart, this method also schedules events,
	 * this time of type TransmissionEnd, after the specified link delay
	 * for each registered link of the sender device. This kind of events
	 * always transports a PDU, which may or may not contain valid data.
	 *
	 * The latter fact is due to the usage of the links' error models.
	 * If the link has no error model, or the message passes it, then a valid
	 * PDU is delivered. However, the error model may still have corrupted the
	 * contained data.
	 */
	void signalTransmissionEnd( Device* sender, PduPtr p );
	
	/**
	 * @brief Set the default error model of the medium
	 * @param errorModel The default error model of the medium
	 *
	 * During construction the medium is not assigned an error model.
	 * So this method must be used in order to activate that functionality.
	 * The provided error model will then be used as the default error
	 * model that all Links are initialized with, unless a different error
	 * model is provided when adding the link.
	 */
	void setErrorModel( ErrorModelPtr errorModel );

	/**
	 * @brief Add a link between two devices
	 * @param srcAddr Address of the sender device
	 * @param destAddr Address of the receiver device
	 * @param delay Time it takes to send data over this link
	 * @param duplex Determines whether to add the link in both directions
	 * @param errorModel Provides a link-specific error model
	 * @return @c true if the link was successfully added
	 *
	 * This method wraps the other @c addLink overload by first retrieving
	 * the pointers to registered devices and then calling the overload.
	 * By default, the link is unidirectional. Supply @c true for parameter
	 * @c duplex to have the link added in both directions.
	 */
	bool addLink( const Device::AddressType& srcAddr,
			const Device::AddressType& destAddr,
			base::SimTime delay, bool duplex = false,
			ErrorModelPtr errorModel = ErrorModelPtr() );

	/**
	 * @brief Add a unidirectional link between two devices
	 * @return @c false if the given devices are the same
	 *
	 * This method writes a link info entry to the topology map. If there is a
	 * pre-existing link, its properties will be overwritten. This method
	 * also automatically sets the medium for @c src, or both devices in
	 * case of duplex mode.
	 */
	bool addLink( Device* src, Device* dest, base::SimTime delay,
			ErrorModelPtr errorModel = ErrorModelPtr() );

	/**
	 * @brief Remove a link between two devices
	 * @param srcAddr Address of the sender device
	 * @param destAddr Address of the receiver device
	 * @param duplex Determines whether to remove the link in both directions
	 *
	 * This method simply retrieves the device pointers and
	 * calls the overload. If @c duplex is true, the link will be
	 * removed in both directions.
	 */
	void removeLink( const Device::AddressType& srcAddr,
			const Device::AddressType& destAddr, bool duplex = false );

	/**
	 * @brief Remove a unidirectional link between two devices
	 *
	 * The method tries to find the given nodes in the topology map
	 * end erases the link information between them.
	 */
	void removeLink( Device* src, Device* dest );

	/**
	 * @brief Register a new network device by its address
	 * @param address The address for the device
	 * @param device The device object
	 * @return @c true if the device was inserted successfully
	 *
	 * This method sets the device's address, and also registers this medium
	 * object with the device. Finally, it inserts the address and the device
	 * pointer in the device lookup map.
	 */
	bool registerDevice( const Device::AddressType& address, Device& device );

	/// Get access to a registered device
	Device* getDevice( const Device::AddressType& address ) const;

	/// Get read access to the topology map
	const TopologyMap& getTopology() const;

	/// Get read access to the device map
	const DeviceMap& getDevices() const;

	/// Keep collision statistics
	void collision() const;

protected:
	/// Stores the links between all nodes in the network
	TopologyMap topology_;
	/// Associates all registered device addresses with Device objects
	DeviceMap devices_;
	/// The default error model to be set for links if none is provided
	ErrorModelPtr defaultErrorModel_;

protected:
	/// Event type for delayed PDU delivery, models the start of a transmission
	class TransmissionStart
	:	public base::Event
	{
	public:
		/// Construction
		TransmissionStart( base::Simulation& sim, const data::Label& label,
				Device& device );

		/// Increases the receiving device's transmission count
		virtual void eventAction();
	private:
		Device* device_;
	};

	/// Event type for delayed PDU delivery, models the end and delivery of a transmission
	class TransmissionEnd
	:	public base::Event
	{
	public:
		/// Construction
		TransmissionEnd( base::Simulation& sim, const data::Label& label,
				Device& device, PduPtr p );

		/// Delivers a PDU at the receiving device and decreases its transmission count
		virtual void eventAction();
	private:
		Device* device_;
		PduPtr pdu_;
	};
};

} } // namespace odemx::protocol

#endif /* ODEMX_PROTOCOL_MEDIUM_INCLUDED */
