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

#ifndef ODEMX_PROTOCOL_DEVICE_INCLUDED
#define ODEMX_PROTOCOL_DEVICE_INCLUDED

#include <odemx/protocol/ServiceProvider.h>
#include <odemx/synchronization/Memory.h>

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

namespace odemx {
namespace protocol {

class Medium;

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

/**
 * @brief Base class for modeling network interfaces
 * @ingroup protocol
 * @author Ronald Kluth
 * @since 3.0
 * @see odemx::protocol::Medium odemx::protocol::Stack
 *
 * A Device object models a network interface in ODEMx protocol stacks. As such
 * it is a means by which nodes in a network can communicated with each other.
 * Using a device always requires the use of a transmission medium because
 * that is needed to transport data from one node to another. Hence, this class
 * and the class Medium are closely tied.
 *
 * In order to build a network topology, all devices that are connected
 * to a specific medium must explicitly be registered with it by their address.
 * Links between nodes are formed as links between devices. For this purpose,
 * their addresses are used. Transmissions from device always pass through the
 * medium. Whenever a device sends data, the medium schedules transmission
 * events to notify the neighbors of a node about the start, and the end of each
 * transmission.
 *
 * Using two events for transmissions allows to integrate a collision detection
 * mechanism. Collisions occur when two connected devices start sending at
 * roughly the same so that they have not yet received the start events of their
 * neighbor.
 */
class Device
:	public ServiceProvider
{
private:
	/// Memory type that allows the stopping of a transmission when a collision occurs
	typedef synchronization::Memory CollisionDetection;

public:
	/// Address type to be used by device ojects
	typedef std::string AddressType;
	
	/**
	 * @brief Construction
	 * @param sim The simulation context
	 * @param label The label of the object
	 *
	 * The constructor initializes an internal SAP that is needed for
	 * receiving data from the medium. Since devices are service providers,
	 * they also just listen for input and wait. They must therefore be
	 * awakened by an internal SAP when data is transmitted via
	 * the medium
	 */
	Device( base::Simulation& sim, const data::Label& label );

	/// Destruction
	virtual ~Device();

	/**
	 * @brief Set the address of a device
	 *
	 * Normally, there is no need to use this method manually. Upon registration
	 * with a Medium, the device will automatically receive the address provided
	 * in the call to @c registerDevice.
	 */
	void setAddress( const AddressType& addr );

	/// Read the device address. It may not have been set yet.
	const AddressType& getAddress() const;

	/// Set the medium that connects the device to its peers
	void setMedium( Medium& medium );

	/// Reset the internal medium pointer
	void resetMedium();

	/// Check if the medium is set
	bool hasMedium() const;

	/**
	 * @brief Increase the number of active transmissions at the device
	 * @see decTransmissionCount, busy
	 *
	 * This function is called when the device itself begins to transmit, or
	 * when it is notified by one of its neighbors about a transmission start.
	 * Keeping track of the active transmissions is important for implementing
	 * the collision detection mechanism. A collision occurrs when a device's
	 * active transmission count is greater than one. Depending on the
	 * implementation of @c hasCollisionDetection, the device may abort
	 * its own transmission, or it must wait until all of its data is sent.
	 * The latter functionality is implemented with a duration timer.
	 */
	void incTransmissionCount();

	/**
	 * @brief Decrease the number of active transmissions
	 * @see incTransmissionCount, busy
	 *
	 * Whenever the device or its neighbors finish a transmission, this
	 * function is called to decrease the transmission counter.
	 */
	void decTransmissionCount();

	/// Check the number of active transmissions at the device
	unsigned int getTransmissionCount() const;

protected:

	/**
	 * @name Input handling, to be defined by subclasses
	 * @{
	 */
	/**
	 * @brief Implements the device functionality for sending data
	 * @param sapName The SAP used to input the data
	 * @param p The data unit to be transmitted via medium
	 *
	 * Whenever data is provided by an SAP other than the internal receive
	 * SAP, this pure virtual method is called with the name of the SAP
	 * and the PDU to send. Every device must implement this method in
	 * order to specify how an what data is to be transmitted via the medium.
	 * Usually, this method calls @c send.
	 */
	virtual void handleSend( const std::string& sapName, PduPtr p ) = 0;
	/**
	 * @brief Implement the device functionality for message reception
	 * @param p The data unit received from the medium
	 *
	 * This method gets called whenever data arrives from the medium through
	 * the internal receive SAP. The device must implement this method to
	 * define if and how data is to be passed to the upper layer or not.
	 * Usually, this method calls @c pass.
	 */
	virtual void handleReceive( PduPtr p ) = 0;
	//@}

	/**
	 * @brief Transmit data via the associated medium
	 * @param p The protocol data unit to be transmitted
	 * @see computeTransmissionDuration
	 *
	 * Sending data is a complex operation since it involves the interaction
	 * with the transmission medium. First, the medium is checked if it's busy.
	 * In that case, the method returns @c false immediately.
	 *
	 * Otherwise, a transmission is started by increasing the counter and
	 * calling @c signalTransmissionStart on the Medium object. This schedules
	 * TransmissionStart events for all neighbors. Afterwards, the transmission
	 * duration is calculated dependant on the PDU size by calling
	 * @c computeTransmissionDuration.
	 *
	 * Depending on whether @c hasCollisionDetection returns @c true or
	 * @c false, an active transmission may be stopped upon collision
	 * detection or not. In the case of a collision, an empty PDU will
	 * be delivered to the receivers.
	 *
	 * As soon as the wait period ends, the end of the transmission is
	 * made know to all neighbors by calling @c signalTransmissionEnd on the
	 * Medium object. This schedules TransmissionEnd events, which decrease
	 * the counter at the receiver and deliver the PDU.
	 */
	bool send( PduPtr p );

	/**
	 * @brief Pass a message from the medium to the upper layer
	 * @param upperLayerSap The SAP to which to pass the data unit
	 * @param p The data unit to pass on
	 *
	 * This method simply looks up the SAP in the upper layer and writes
	 * the given data unit to it. A warning will be issued if the SAP
	 * is not found.
	 */
	void pass( const std::string& upperLayerSap, PduPtr p );

	/**
	 * @brief Override of the service provider interface
	 * @param sap The SAP by which the data unit was provided
	 * @param p The protocol data unit to handle
	 *
	 * This function simply calls @c handleReceive if the PDU came from the
	 * internal SAP. Otherwise, @c handleReceive is called.
	 */
	virtual void handleInput( const std::string& sap, PduPtr p );

	/**
	 * @brief Determine the time it takes to transmit a data unit via medium
	 * @param pduSize The size of the given data unit
	 * @return The time duration it will take to put the PDU on the medium
	 *
	 * This method is important for the waiting period between transmission
	 * start and end. It computes the time it takes the device to put a
	 * given data unit on the medium. Device subclasses must define this
	 * method in order to provide a sensible implementation depending on
	 * bandwidth and Ouality of service.
	 */
	virtual base::SimTime computeTransmissionDuration( std::size_t pduSize ) = 0;

	/**
	 * @brief Determines whether a device can detect collision
	 * @return @c true if the device has collision detection capability, @c false otherwise
	 *
	 * Normally, a device has to send all of its data to the medium, not knowing
	 * whether it actually arrives intact. However, some devices can abort their
	 * transmission when  a collision is detected on the medium. This method's
	 * implementation decides whether the device has this ability or not.
	 */
	virtual bool hasCollisionDetection();
	
public:
	/**
	 * @brief Put a data unit into the device's receive SAP
	 *
	 * This method should not be called manually. It is part of the interaction
	 * between Medium and Device, and it is always called by TransmissionEnd
	 * events to deliver a PDU from the medium.
	 */
	void receive( PduPtr p );

private:
	/// SAP that receives PDUs from the medium
	Sap* receiveSap_;
	/// Address of the device
	AddressType address_;
	/// Medium used by the device to transmit messages
	Medium* medium_;
	/// Counts the number of active transmissions on the device to detect collisions
	unsigned int transmissionCount_;
	/// Memory object that can awaken the device early from its transmission wait period
	CollisionDetection collisionDetection_;
	/// Stores whether a collision occurred during the last transmission
	bool collisionOccurred_;
	
protected:
	/// Check if the device finds the medium busy
	bool busy() const;
	/// Schedule events for all neighbors to notify them of a transmission start
	void startTransmission();
	/// Schedule events for all neighbors to notify them of a transmission end and deliver the PDU
	void endTransmission( PduPtr p );
};

} } // namespace odemx::protocol

#endif /* ODEMX_PROTOCOL_DEVICE_INCLUDED */
