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

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

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

	/**
	 * @struct ServiceFixture
	 * @brief Helper struct providing set-up/tear-down of Service tests
	 *
	 * @copydetails EventFixture
	 */
	struct ServiceFixture
	{
		SuiteBase::SimulationTest sim;
		TestLogConsumer::Ptr log;
		ServiceTest svc;
		data::TypeInfo type;

		ServiceFixture()
		:	sim( "ServiceTestSim" )
		,	log( TestLogConsumer::create() )
		,	svc( sim, "ServiceTest" )
		,	type( typeid(Service) )
		{
			sim.addConsumer( log );
		}
	};

	TEST_FIXTURE( ServiceFixture, ConstructionDestruction )
	{
		data::Label label = "ServiceTestConstructionDestruction";
		{
			ServiceTest svc( sim, label );
			CHECK( svc.getSap( "odemx_internal_service_receive_SAP" ) );

			svc.addReceiveSap( "SAP" );
		}
		CHECK( log->getTraceRecord( "destroy", typeid( odemx::synchronization::PortHeadT< PduPtr > ) ) );
	}

	TEST_FIXTURE( ServiceFixture, RegisterService )
	{
		const std::string& address = "Address";
		Service::registerService( address, svc );
		CHECK( log->getTraceRecord( "set address", type ) );
		CHECK_EQUAL( address, svc.getAddress() );
		CHECK( ! Service::getAddressMap().empty() );
		CHECK_EQUAL( address, Service::getAddressMap().begin()->first );
		CHECK_EQUAL( &svc, Service::getAddressMap().begin()->second );

		// clean up
		Service::removeService( address );
	}

	TEST_FIXTURE( ServiceFixture, RemoveService )
	{
		const std::string& address = "Address";
		Service::registerService( address, svc );
		Service::removeService( address );

		CHECK( Service::getAddressMap().empty() );
	}

	TEST_FIXTURE( ServiceFixture, GetPeer )
	{
		const std::string& address = "Address";
		ServiceTest peer( sim, "ServiceTestGetPeer" );
		Service* found = svc.getPeer( "NotFound" );
		CHECK_EQUAL( (Service*) 0, found );

		Service::registerService( address, peer );
		found = svc.getPeer( address );
		CHECK_EQUAL( &peer, found );

		// clean up
		Service::removeService( address );
	}

	TEST_FIXTURE( ServiceFixture, Send )
	{
		bool success = svc.send( "NotFound", PduPtr( new PduTest() ) );
		CHECK( ! success );
		CHECK( log->getWarningRecord( "Service::send(): peer not registered", type ) );

		std::string address = "Address";
		ServiceTest peer( sim, "ServiceTestGetPeer" );
		sim.run();

		success = Service::registerService( address, peer );
		CHECK( success );
		CHECK_EQUAL( &peer, Service::getPeer( address ) );
		success = svc.send( address, PduPtr( new PduTest() ) );
		CHECK( success );
		CHECK( log->getTraceRecord( "send", type ) );

		// clean up
		Service::removeService( address );
	}

	TEST_FIXTURE( ServiceFixture, Receive )
	{
		// no error model
		svc.receive( PduPtr( new PduTest() ) );
		CHECK( log->getTraceRecord( "receive success", type ) );

		log->clear();
		Service::setErrorModel( ErrorModelTestSuccess::create() );
		svc.receive( PduPtr( new PduTest() ) );
		CHECK( log->getTraceRecord( "receive success", type ) );

		log->clear();
		Service::setErrorModel( ErrorModelTestFailure::create() );
		svc.receive( PduPtr( new PduTest() ) );
		CHECK( log->getTraceRecord( "receive failure", type ) );
	}

	TEST_FIXTURE( ServiceFixture, PassToReceiveSap )
	{
		bool success = svc.pass( "SAP", PduPtr( new PduTest() ) );
		CHECK( ! success );
		CHECK( log->getWarningRecord( "Service::pass(): output SAP not found", type ) );

		Sap* sap = svc.addReceiveSap( "SAP" );
		success = svc.pass( "SAP", PduPtr( new PduTest() ) );
		CHECK( success );
		CHECK_EQUAL( (std::size_t) 1, sap->getBuffer()->getBuffer().size() );
		CHECK( log->getTraceRecord( "pass", type ) );
	}

	TEST_FIXTURE( ServiceFixture, PassToLayerSap )
	{
		Layer layer( sim, "ServiceTestPassLayer1" );
		Layer topLayer( sim, "ServiceTestPassLayer2" );
		std::auto_ptr< Sap > sap( new Sap( sim, "ServiceTestPassLayer", "SAP" ) );
		topLayer.addSap( sap.get() );

		layer.setUpperLayer( &topLayer );
		svc.setLayer( &layer );
		bool success = svc.pass( "SAP", PduPtr( new PduTest() ) );
		CHECK( success );
		CHECK_EQUAL( (std::size_t) 1, sap->getBuffer()->getBuffer().size() );
	}

	TEST_FIXTURE( ServiceFixture, AddReceiveSap )
	{
		Sap* sap = svc.addReceiveSap( "SAP" );
		CHECK( sap != 0 );
		CHECK( log->getTraceRecord( "add receive SAP", type ) );

		Sap* found = svc.addReceiveSap( "SAP" );
		CHECK_EQUAL( sap, found );
		CHECK( log->getWarningRecord( "Service::addReceiveSap(): SAP already exists", type ) );
	}

	TEST_FIXTURE( ServiceFixture, GetReceiveSap )
	{
		Sap* found = svc.getReceiveSap( "SAP" );
		CHECK_EQUAL( (Sap*) 0, found );

		Sap* sap = svc.addReceiveSap( "SAP" );

		found = svc.getReceiveSap( "SAP" );
		CHECK_EQUAL( sap, found );
	}

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

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

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

	TEST_FIXTURE( ServiceFixture, HandleReceive )
	{
		// get internal receive SAP
		Sap* sap = svc.getSap( "odemx_internal_service_receive_SAP" );

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

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

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