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

#include <odemx/protocol/Device.h>
#include <odemx/protocol/Layer.h>
#include <odemx/protocol/Medium.h>
#include <odemx/protocol/Sap.h>
#include <odemx/protocol/Pdu.h>
#include <odemx/synchronization/Timer.h>

namespace odemx {
namespace protocol {

Device::Device( base::Simulation& sim, const data::Label& label )
:	ServiceProvider( sim, label )
,	receiveSap_( 0 )
,	address_()
,	medium_( 0 )
,	transmissionCount_( 0 )
,	collisionDetection_( sim, label + " collision detection", synchronization::IMemory::COLLISION_DETECTION )
,	collisionOccurred_( false )
{
	receiveSap_ = addSap( "odemx_internal_device_receive_SAP" );
	assert( receiveSap_ );
}

Device::~Device()
{
}

void Device::setAddress( const AddressType& addr )
{
	ODEMX_TRACE << log( "set address" )
			.detail( "value", addr )
			.scope( typeid(Device) );

	address_ = addr;
}

const Device::AddressType& Device::getAddress() const
{
	return address_;
}

void Device::setMedium( Medium& medium )
{
	if( medium_ && medium_ != &medium )
	{
		error << log( "Device::setMedium():"
					"attempt to register device with another medium" )
					.scope( typeid(Device) );
	}
	else
	{
		ODEMX_TRACE << log( "set medium" )
					.detail( "label", medium.getLabel() )
					.scope( typeid(Device) );

		medium_ = &medium;
	}
}

void Device::resetMedium()
{
	medium_ = 0;
}

bool Device::hasMedium() const
{
	return medium_ != 0;
}

bool Device::busy() const
{
	return transmissionCount_ >= 1;
}

void Device::incTransmissionCount()
{
	++transmissionCount_;

	ODEMX_TRACE << log( "increase transmission count" )
				.detail( "value", transmissionCount_ )
				.scope( typeid(Device) );

	if( transmissionCount_ > 1 )
	{
		collisionOccurred_ = true;
		collisionDetection_.alert();

		ODEMX_TRACE << log( "collision occurred" ).scope( typeid(Device) );

		assert( medium_ );
		medium_->collision();
	}
}

void Device::decTransmissionCount()
{
	if( transmissionCount_ > 0 )
	{
		--transmissionCount_;
	}

	ODEMX_TRACE << log( "decrease transmission count" )
				.detail( "value", transmissionCount_ )
				.scope( typeid(Device) );
}

unsigned int Device::getTransmissionCount() const
{
	return transmissionCount_;
}

bool Device::hasCollisionDetection()
{
	return false;
}

void Device::startTransmission()
{
	ODEMX_TRACE << log( "start transmission" ).scope( typeid(Device) );

	medium_->signalTransmissionStart( this );
}

void Device::endTransmission( PduPtr p )
{
	ODEMX_TRACE << log( "end transmission" ).scope( typeid(Device) );

	medium_->signalTransmissionEnd( this, p );
}

bool Device::send( PduPtr p )
{
	if( ! medium_ )
	{
		error << log( "Device::transmit(): device not connected to a medium" )
				.scope( typeid( Device ) );
		return false;
	}

	if( busy() )
	{
		ODEMX_TRACE << log( "medium busy" ).scope( typeid(Device) );

		return false;
	}

	ODEMX_TRACE << log( "send" ).scope( typeid(Device) );

	// start transmission
	incTransmissionCount();
	collisionOccurred_ = false;

	base::SimTime duration = computeTransmissionDuration( p->getSize() );
	synchronization::Timer durationTimer( getSimulation(), getLabel() + " duration timer" );
	durationTimer.setIn( duration );
	
	startTransmission();

	synchronization::IMemory* alerter = 0;

	if( hasCollisionDetection() )
	{
		alerter = Process::wait( &durationTimer, &collisionDetection_ );
	}
	else
	{
		alerter = Process::wait( &durationTimer );
	}

	if( ! alerter )
	{
		warning << log( "Device::send(): wait state ended by non-memory object" )
					.scope( typeid(Device) );
	}

	// detected collision, end transmission
	// in the first case, the waking time is just earlier
	// invalid data is sent in any case
	if( alerter == &collisionDetection_ || collisionOccurred_ )
	{
		if( alerter == &collisionDetection_ )
		{
			ODEMX_TRACE << log( "collision detected" ).scope( typeid(Device) );
		}
		else
		{
			ODEMX_TRACE << log( "collision occurred" ).scope( typeid(Device) );
		}
		endTransmission( PduPtr() );
		decTransmissionCount();
		return false;
	}

	// transmission finished
	endTransmission( p );
	decTransmissionCount();

	ODEMX_TRACE << log( "transmission succeeded" ).scope( typeid(Device) );

	return true;
}

void Device::handleInput( const std::string& sapName, PduPtr p )
{
	if( sapName == receiveSap_->getName() )
	{
		ODEMX_TRACE << log( "handle receive" ).scope( typeid(Device) );

		handleReceive( p );
	}
	else
	{
		ODEMX_TRACE << log( "handle send" ).scope( typeid(Device) );

		handleSend( sapName, p );
	}
}

	/// Pass a message to the service user, i.e. to the upper layer via the device's SAP
void Device::pass( const std::string& upperLayerSap, PduPtr p )
{
	Sap* sap = getLayer()->getUpperLayer()->getSap( upperLayerSap );
	if( sap )
	{
		ODEMX_TRACE << log( "pass" )
				.detail( "SAP", upperLayerSap )
				.scope( typeid(Device) );

		sap->write( p );
	}
	else
	{
		warning << log( "Device::pass(): upper layer does not offer SAP" )
				.detail( "name", upperLayerSap )
				.scope( typeid(Device) );
	}
}

void Device::receive( PduPtr p )
{
	ODEMX_TRACE << log( "receive" ).scope( typeid(Device) );

	receiveSap_->write( p );
}

} } // namespace odemx::protocol
