/**
 * @file TestDevice.cpp
 * @date May 5, 2010
 * @author ron
 *
 * @brief
 */


#include "TestProtocol.h"
#include "../TestBase/TestBase.h"

/// @cond DOXYGEN_SKIP
SUITE( Protocol )
{
/// @endcond

	/**
	 * @struct DeviceFixture
	 * @brief Helper struct providing set-up/tear-down of Device tests
	 *
	 * @copydetails EventFixture
	 */
	struct DeviceFixture
	{
		SuiteBase::SimulationTest sim;
		TestLogConsumer::Ptr log;
		MediumTest medium;
		DeviceTest dev;
		data::TypeInfo type;

		DeviceFixture()
		:	sim( "DeviceTestSim" )
		,	log( TestLogConsumer::create() )
		,	medium( sim, "DeviceTestMedium" )
		,	dev( sim, "DeviceTest" )
		,	type( typeid(Device) )
		{
			sim.addConsumer( log );
		}
	};

	TEST_FIXTURE( DeviceFixture, ConstructionDestruction )
	{
		data::Label label = "DeviceTestConstructionDestruction";
		{
			DeviceTest dev( sim, label );
			CHECK( dev.getSap( "odemx_internal_device_receive_SAP" ) );
		}
		CHECK( log->getTraceRecord( "destroy", typeid( odemx::synchronization::PortHeadT< PduPtr > ) ) );
	}

	TEST_FIXTURE( DeviceFixture, SetAddress )
	{
		dev.setAddress( "addr0" );
		CHECK( log->getTraceRecord( "set address", type ) );
		CHECK_EQUAL( "addr0", dev.getAddress() );
	}

	TEST_FIXTURE( DeviceFixture, SetResetMedium )
	{
		dev.setMedium( medium );
		CHECK( log->getTraceRecord( "set medium", type ) );
		CHECK( dev.hasMedium() );

		MediumTest medium2( sim, "DeviceTestSetResetMedium" );
		dev.setMedium( medium2 );
		CHECK( log->getErrorRecord( "Device::setMedium():"
					"attempt to register device with another medium", type ) );

		dev.resetMedium();
		CHECK( ! dev.hasMedium() );
	}

	TEST_FIXTURE( DeviceFixture, IncDecTransmissionCount )
	{
		CHECK_EQUAL( 0, dev.getTransmissionCount() );
		dev.incTransmissionCount();
		CHECK_EQUAL( 1, dev.getTransmissionCount() );
		CHECK( log->getTraceRecord( "increase transmission count", type ) );

		dev.setMedium( medium );
		dev.incTransmissionCount();
		unsigned int count = dev.getTransmissionCount();
		CHECK( log->getTraceRecord( "collision occurred", type ) );

		dev.decTransmissionCount();
		CHECK_EQUAL( count - 1, dev.getTransmissionCount() );
		CHECK( log->getTraceRecord( "decrease transmission count", type ) );
	}

	TEST_FIXTURE( DeviceFixture, StartTransmission )
	{
		dev.setMedium( medium );
		dev.startTransmission();

		CHECK( log->getTraceRecord( "start transmission", type ) );
		CHECK( log->getTraceRecord( "signal transmission start", typeid(Medium) ) );
	}

	TEST_FIXTURE( DeviceFixture, EndTransmission )
	{
		dev.setMedium( medium );
		dev.endTransmission( PduPtr( new PduTest() ) );

		CHECK( log->getTraceRecord( "end transmission", type ) );
		CHECK( log->getTraceRecord( "signal transmission end", typeid(Medium) ) );
	}

	TEST_FIXTURE( DeviceFixture, Send )
	{
		bool success = dev.send( PduPtr( new PduTest() ) );
		CHECK( ! success );
		CHECK( log->getErrorRecord( "Device::transmit(): device not connected to a medium", type ) );

		dev.setMedium( medium );
		// medium busy
		dev.incTransmissionCount();
		success = dev.send( PduPtr( new PduTest() ) );
		CHECK( ! success );
		CHECK( log->getTraceRecord( "medium busy", type ) );
		// medium free again
		dev.decTransmissionCount();

		// connect 2 devices
		DeviceTest dev2( sim, "DeviceTestSendCollision" );
		medium.registerDevice( "addr1", dev );
		medium.registerDevice( "addr2", dev2 );
		medium.addLink( "addr1", "addr2", 10, true );

		Sap* sap = dev.addSap( "SAP" );

		// start devices, i.e. have them wait
		sim.run();
		CHECK( ! dev.isScheduled() );
		CHECK( ! dev2.isScheduled() );

		dev.doSend = true;
		sap->getBuffer()->getTail()->setMode( odemx::synchronization::ERROR_MODE );
		sap->write( PduPtr( new PduTest( 152 ) ) );
		// wake up the device and have it send
		// schedules transmission start event after delay 10
		// simulate transmission
		sim.run();

		CHECK( dev2.handledReceive );
		CHECK_EQUAL( 152, dev2.receivedPdu->value );
		CHECK( log->getTraceRecord( "transmission succeeded", type ) );
		CHECK( log->getTraceRecord( "handle receive", type ) );
	}

	TEST_FIXTURE( DeviceFixture, SendCollision )
	{
		// connect 2 devices
		DeviceTest dev2( sim, "DeviceTestSendCollision" );
		medium.registerDevice( "addr1", dev );
		medium.registerDevice( "addr2", dev2 );
		CHECK( log->getTraceRecord( "set medium", type ) );

		medium.addLink( "addr1", "addr2", 10, true );

		Sap* sap = dev.addSap( "SAP" );
		Sap* sap2 = dev2.addSap( "SAP" );

		// start devices, i.e. have them wait
		sim.run();
		CHECK( ! dev.isScheduled() );
		CHECK( ! dev2.isScheduled() );

		dev.doSend = true;
		sap->getBuffer()->getTail()->setMode( odemx::synchronization::ERROR_MODE );
		sap->write( PduPtr( new PduTest() ) );
		// wake up the device and have it send
		// schedules transmission start event after delay 10
		sim.step();

		// advance sim time and schedule another transmission start at 14
		sim.setCurrentTime( 4 );
		dev2.doSend = true;
		sap2->getBuffer()->getTail()->setMode( odemx::synchronization::ERROR_MODE );
		sap2->write( PduPtr( new PduTest() ) );
		sim.step();

		// simulate collision
		sim.run();

		// 4: dev2 inc
		// 10: event tm dev -> dev2 inc, schedule tm end
		// creates collision at dev2
		// 14: event tm dev2 -> dev inc, schedule tm end
		// creates collision at dev
		// 20: event tm end from dev2 -> dev dec
		// 24: event tm end from dev -> dev2 dec
		// done

		CHECK( log->getTraceRecord( "collision occurred", type ) );
		CHECK( log->getTraceRecord( "collision detected", type ) );
	}

	TEST_FIXTURE( DeviceFixture, Pass )
	{
		Layer layer( sim, "DeviceTestPassLayer" );
		Layer layer2( sim, "DeviceTestPassLayer" );

		layer.setUpperLayer( &layer2 );
		dev.setLayer( &layer );
		dev.pass( "SAP", PduPtr( new PduTest() ) );
		CHECK( log->getWarningRecord( "Device::pass(): "
				"upper layer does not offer SAP", type ) );

		std::auto_ptr< Sap > sap( new Sap( sim, "DeviceTestPassSap", "SAP" ) );
		layer2.addSap( sap.get() );
		dev.pass( "SAP", PduPtr( new PduTest() ) );
		CHECK( log->getTraceRecord( "pass", type ) );
		CHECK_EQUAL( (std::size_t) 1, sap->getBuffer()->count() );
	}

	TEST_FIXTURE( DeviceFixture, Receive )
	{
		Sap* sap = dev.getSap( "odemx_internal_device_receive_SAP" );
		dev.receive( PduPtr( new PduTest() ) );
		CHECK( log->getTraceRecord( "receive", type ) );
		CHECK_EQUAL( (std::size_t) 1, sap->getBuffer()->count() );
	}

	TEST_FIXTURE( DeviceFixture, HandleSend )
	{
		// need to add a SAP for waiting
		Sap* sap = dev.addSap( "SAP" );

		// start the service, which will go into wait state
		sim.step();
		CHECK( ! dev.isScheduled() );

		sap->getBuffer()->getTail()->setMode( odemx::synchronization::ERROR_MODE );
		sap->write( PduPtr( new PduTest() ) );
		sim.step();
		CHECK_EQUAL( "SAP", dev.inputSap );
		CHECK( dev.handledSend );
		CHECK( log->getTraceRecord( "handle send", type ) );
	}

	TEST_FIXTURE( DeviceFixture, HandleReceive )
	{
		// get internal receive SAP
		Sap* sap = dev.getSap( "odemx_internal_device_receive_SAP" );

		// start the service, which will go into wait state
		sim.step();
		CHECK( ! dev.isScheduled() );

		sap->getBuffer()->getTail()->setMode( odemx::synchronization::ERROR_MODE );
		sap->write( PduPtr( new PduTest() ) );
		sim.step();
		CHECK( dev.handledReceive );
		CHECK( log->getTraceRecord( "handle receive", type ) );
	}

/// @cond DOXYGEN_SKIP
}
/// @endcond
