/**
 * @file TestPortTail.cpp
 * @date Jul 29, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx class template PortTailT
 */

#include "TestSynchronization.h"
#include "../TestBase/TestBase.h"

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

	/**
	 * @struct PortTailTFixture
	 * @brief Helper struct providing set-up/tear-down of PortTailT tests
	 *
	 * @copydetails EventFixture
	 */
	struct PortTailTFixture
	{
		SuiteBase::SimulationTest sim;
		TestLogConsumer::Ptr log;
		PortTailTTestObserver observer;
		data::TypeInfo type;
		data::TypeInfo portType;
		data::TypeInfo headType;

		PortTailTFixture()
		:	sim( "PortTailTTestSim" ),
			log( TestLogConsumer::create() ),
			observer( false ),
			type( typeid( PortTailT< PortTestElement >) ),
			portType( typeid(PortT< PortTestElement >) ),
			headType( typeid(PortHeadT< PortTestElement >) )
			{
				sim.addConsumer( log );
			}
	};

	/**
	 * @test odemx::PortTailT construction and destruction
	 *
	 * Expected effects:
	 * @li label is set correctly
	 * @li mode is set correctly
	 * @li capacity is set correctly
	 * @li the Memory type is set correctly
	 * @li observer is set correctly
	 * @li construction and destruction can be observed
	 * @li attempt to create a 0-size port causes an error message and
	 * leads to the creation of a port with default capacity
	 */
	TEST_FIXTURE( PortTailTFixture, ConstructionDestruction )
	{
		observer.history.clear();

		data::Label label = "PortTailTTestConstruction4";
		{
			PortTailT< PortTestElement >::Ptr port4 = PortTailT< PortTestElement >::create(
					sim, label, ERROR_MODE, 1000, &observer );
			CHECK_EQUAL( label + " (tail)", port4->getLabel() );
			CHECK_EQUAL( ERROR_MODE, port4->getMode() );
			CHECK_EQUAL( (std::size_t) 1000, port4->getMaxCapacity() );
			CHECK_EQUAL( &observer, port4->data::Observable< PortTailTObserver< PortTestElement > >::getObservers().front() );
			CHECK( log->getTraceRecord( "create", type ) );
			CHECK( log->getStatisticsRecord( "parameter", "element type", portType ) );
			CHECK( log->getStatisticsRecord( "parameter", "maximum capacity", portType ) );
			CHECK( observer.observed( "onCreate", port4->getLabel() ) );
		}
		CHECK( log->getTraceRecord( "destroy", type ) );

		PortTailT< PortTestElement >::Ptr zeroPort1 = PortTailT< PortTestElement >::create(
						sim, label, ZERO_MODE, 0, &observer );
		CHECK( log->getErrorRecord( "PortT(): attempt to create Port with invalid capacity, using default", portType ) );
		CHECK_EQUAL( (std::size_t) 10000, zeroPort1->getMaxCapacity() );
	}

	/**
	 * @test odemx::PortTailT::put() with ZERO_MODE
	 *
	 * Expected function call effects:
	 * @li successful call returns 1
	 * @li the new element is put into the buffer
	 * @li when the buffer is full, the call returns 0
	 */
	TEST_FIXTURE( PortTailTFixture, ZeroModePut )
	{
		PortTailT< int* >::Ptr tail = PortTailT< int* >::create(
				sim, "PortTailTTestZeroModePut", ZERO_MODE, 1 );
		const std::list< int* >& elementList = tail->getBuffer();
		int* element1 = new int( 1 );
		unsigned int oldCount = tail->count();

		int returnValue = tail->put( element1 );
		CHECK_EQUAL( 1, returnValue );
		CHECK( find( elementList.begin(), elementList.end(), element1 ) != elementList.end() );
		CHECK_EQUAL( oldCount + 1, tail->count() );

		CHECK_EQUAL( (std::size_t) 0, tail->getAvailableSpace() );
		int* element2 = new int( 2 );
		returnValue = tail->put( element2 );
		CHECK_EQUAL( 0, returnValue );
	}

	/**
	 * @test odemx::PortTailT::put() with ERROR_MODE
	 *
	 * Expected function call effects:
	 * @li successful call returns 1
	 * @li the new element is put into the buffer
	 * @li when the buffer is full, the call returns -1 and sends an error
	 * message to \c errorStream()
	 */
	TEST_FIXTURE( PortTailTFixture, ErrorModePut )
	{
		PortTailT< int* >::Ptr tail = PortTailT< int* >::create(
				sim, "PortTailTTestErrorModePut", ERROR_MODE, 1 );
		const std::list< int* >& elementList = tail->getBuffer();

		int* element1 = new int( 1 );
		unsigned int oldCount = tail->count();

		int returnValue = tail->put( element1 );
		CHECK_EQUAL( 1, returnValue );
		CHECK( find( elementList.begin(), elementList.end(), element1 ) != elementList.end() );
		CHECK_EQUAL( oldCount + 1, tail->count() );

		CHECK_EQUAL( (unsigned int)0, tail->getAvailableSpace() );
		int* element2 = new int( 2 );
		returnValue = tail->put( element2 );
		CHECK_EQUAL( -1, returnValue );
		CHECK( log->getErrorRecord( "PortTailT::put(): cannot add element to full port", typeid( PortTailT< int* > ) ) );
	}

	/**
	 * @test odemx::PortTailT::put() with WAITING_MODE
	 *
	 * Expected function call effects:
	 * @li the call can be observed
	 * @li calling on full port from environment produces an error
	 * @li calling on full port from event produces an error
	 * @li interrupt of a waiting Process causes return value 0
	 * @li a waiting Process is alerted and puts its element into the port as
	 * soon as space becomes available
	 * @li the element is appended to the buffer
	 */
	TEST_FIXTURE( PortTailTFixture, WaitModePut )
	{
		PortTailT< PortTestElement >::Ptr tail = PortTailT< PortTestElement >::create(
				sim, "PortTailTTestWaitModePut", WAITING_MODE, 1, &observer );

		// get old number of elements and put some into the port
		const std::list< PortTestElement >& elementList = tail->getBuffer();
		// make an element available
		PortTestElement element1 = { 1 };
		tail->put( element1 );
		CHECK( log->getTraceRecord( "put", type ) );
		CHECK( observer.observed( "onPut", tail->getLabel(), toString( element1.id ) ) );

		// call from environment
		CHECK_EQUAL( (std::size_t) 0, tail->getAvailableSpace() );
		PortTestElement element2 = { 2 };
		int returnValue = tail->put( element2 );
		CHECK_EQUAL( -1, returnValue );
		CHECK( log->getErrorRecord( "PortTailT::put(): port in waiting mode called from environment", type ) );

		// call from Event
		unsigned int id = 42;
		PortTailTTestPutEvent putEvent( sim, "PortTailTTestWaitPutFailEvent", tail, id );
		putEvent.schedule();
		sim.step();
		CHECK( putEvent.putReturnValue == -1 );
		CHECK( log->getErrorRecord( "PortTailT::put(): waiting mode not allowed for events", type ) );

		// call from Process, which gets interrupted
		PortTailTTestPutProcess putProcess1( sim, "PortTailTTestWaitPutProcess", tail, id );
		putProcess1.activate();
		sim.step();
		CHECK_EQUAL( base::Process::IDLE, putProcess1.getProcessState() );

		// interrupt the Process
		putProcess1.interrupt();
		CHECK( putProcess1.isScheduled() );
		sim.step();
		// 0 means that the put()-caller was interrupted
		CHECK( 0 == putProcess1.putReturnValue );

		// successful call from Process
		PortTailTTestPutProcess putProcess2( sim, "PortTailTTestWaitPutProcess", tail, id );
		putProcess2.activate();
		sim.step();

		PortHeadT< PortTestElement >::Ptr head( tail->getHead() );
		// make space available
		head->get();
		CHECK_EQUAL( (std::size_t) 1, tail->getAvailableSpace() );
		CHECK( putProcess2.isAlerted() );
		sim.step();
		// 1 means that the put()-call was successful
		CHECK( 1 == putProcess2.putReturnValue );
		CHECK_EQUAL( id, elementList.back().id );
	}

	/**
	 * @test odemx::PortTailT::put() PortHeadT alert call
	 *
	 * Expected function call effects:
	 * @li Processes waiting at the PortHead of an empty Port are alerted as
	 * soon as data becomes available
	 * @li the new element is immediately taken by the waiting Process
	 */
	TEST_FIXTURE( PortTailTFixture, PutAlertHead )
	{
		PortTailT< PortTestElement >::Ptr tail = PortTailT< PortTestElement >::create(
				sim, "PortTailTTestPutAlertHead", WAITING_MODE, 1, &observer );
		PortHeadT< PortTestElement >::Ptr head( tail->getHead() );

		PortHeadTTestGetProcess getterProcess1( sim, "PortTailTTestPutAlertHeadProcess", head );
		getterProcess1.activate();
		sim.step();
		CHECK_EQUAL( base::Process::IDLE, getterProcess1.getProcessState() );
		CHECK( head->waiting() );

		PortTestElement element1 = { 1 };
		tail->put( element1 );
		CHECK( getterProcess1.isAlerted() );
		sim.step();
		CHECK_EQUAL( element1, getterProcess1.received );
		CHECK( log->getTraceRecord( "alert", headType ) );
	}

	/**
	 * @test odemx::PortTailT::getHead()
	 *
	 * Expected function call effects:
	 * @li mode and maximum capacity values of head and tail are the same
	 */
	TEST_FIXTURE( PortTailTFixture, GetHead )
	{
		PortTailT< PortTestElement >::Ptr tail = PortTailT< PortTestElement >::create(
				sim, "PortTailTTestGetHead", WAITING_MODE, 1 );
		PortHeadT< PortTestElement >::Ptr head( tail->getHead() );
		CHECK_EQUAL( tail->getMaxCapacity(), head->getMaxCapacity() );
		CHECK_EQUAL( tail->getMode(), head->getMode() );
	}

	/**
	 * @test odemx::PortTailT::cut()
	 *
	 * Expected function call effects:
	 * @li there are now two Ports, with the downstream one holding the data
	 * @li a new tail to the old buffer is returned
	 * @li the original tail now has a new internal buffer
	 * @li mode and capacity of the new Port are the same
	 * @li the call can be observed
	 */
	TEST_FIXTURE( PortTailTFixture, Cut )
	{
		PortTailT< PortTestElement >::Ptr tail = PortTailT< PortTestElement >::create(
				sim, "PortTailTTestCut", WAITING_MODE, 2, &observer );
		PortHeadT< PortTestElement >::Ptr head( tail->getHead() );

		PortTestElement element1 = { 1 };
		PortTestElement element2 = { 2 };
		tail->put( element1 );
		tail->put( element2 );
		CHECK_EQUAL( (size_t) 2, head->count() );

		// an intermediary Process could filter data by cutting in between ports
		PortTailT< PortTestElement >::Ptr oldBufferTail( tail->cut() );
		PortHeadT< PortTestElement >::Ptr newBufferHead( tail->getHead() );
		CHECK_EQUAL( tail, newBufferHead->getTail() );
		CHECK_EQUAL( head.get(), oldBufferTail->getHead().get() );
		CHECK_EQUAL( (std::size_t) 0, newBufferHead->count() );
		CHECK_EQUAL( (std::size_t) 2, oldBufferTail->count() );
		CHECK_EQUAL( oldBufferTail->getMode(), newBufferHead->getMode() );
		CHECK_EQUAL( oldBufferTail->getMaxCapacity(), newBufferHead->getMaxCapacity() );
		CHECK( log->getTraceRecord( "cut", type ) );
		CHECK( observer.observed( "onCut", tail->getLabel() ) );
	}

	/**
	 * @test odemx::PortTailT::splice(PortHeadT*)
	 *
	 * Expected function call effects:
	 * @li two ports with separate buffers are joined into one
	 * @li the upstream port's contents is appended to the downstream port's buffer
	 * @li the call can be observed
	 */
	TEST_FIXTURE( PortTailTFixture, Splice )
	{
		PortTailT< PortTestElement >::Ptr tail = PortTailT< PortTestElement >::create(
				sim, "PortTailTTestSplice", WAITING_MODE, 2, &observer );
		PortHeadT< PortTestElement >::Ptr head( tail->getHead() );
		PortTestElement element1 = { 1 };
		PortTestElement element2 = { 2 };
		PortTestElement element3 = { 3 };
		PortTailT< PortTestElement >::Ptr oldBufferTail = tail->cut();
		PortHeadT< PortTestElement >::Ptr newBufferHead = tail->getHead();
		tail->put( element1 );
		tail->put( element2 );
		CHECK_EQUAL( (std::size_t) 2, newBufferHead->count() );
		oldBufferTail->put( element3 );
		CHECK_EQUAL( (std::size_t) 1, head->count() );

		// this will join the ports again and delete the two superflous objects
		data::Label tailLabel = oldBufferTail->getLabel();
		oldBufferTail->data::Observable< PortTailTObserver< PortTestElement > >::addObserver( &observer );
		oldBufferTail->splice( newBufferHead );
		CHECK_EQUAL( (std::size_t) 3, head->count() );
		CHECK_EQUAL( element3, head->getBuffer().front() );
		CHECK_EQUAL( element2, head->getBuffer().back() );
		CHECK( observer.observed( "onSplice", tailLabel ) );
	}

	/**
	 * @test odemx::PortTailT::cppSplice(PortHeadT*) append contents
	 *
	 * Expected function call effects:
	 * @li the contents of the head's port is appended to tail's port
	 * @li the head's buffer is empty
	 * @li the call can be observed
	 */
	TEST_FIXTURE( PortTailTFixture, CppSpliceAppend )
	{
		PortTailT< PortTestElement >::Ptr port1Tail = PortTailT< PortTestElement >::create(
				sim, "PortTailTTestCppSpliceAppend", WAITING_MODE, 2, &observer );
		PortHeadT< PortTestElement >::Ptr port2Head = PortHeadT< PortTestElement >::create(
				sim, "PortTailTTestCppSpliceAppendHead", WAITING_MODE, 2 );
		PortTestElement element1 = { 1 };
		port1Tail->put( element1 );
		PortTailT< PortTestElement >::Ptr port2Tail( port2Head->getTail() );

		PortTestElement element2 = { 2 };
		port2Tail->put( element2 );

		// default is appending of elements
		bool append = true;
		port1Tail->cppSplice( port2Head, append );
		CHECK_EQUAL( element1, port1Tail->getBuffer().front() );
		CHECK_EQUAL( element2, port1Tail->getBuffer().back() );
		CHECK( port2Head->getBuffer().empty() );
		CHECK( observer.observed( "onCppSplice", port1Tail->getLabel(), port2Head->getLabel(), toString( append ) ) );
	}

	/**
	 * @test odemx::PortTailT::cppSplice(PortHeadT*) prepend contents
	 *
	 * Expected function call effects:
	 * @li the contents of the head's port is prepended to tail's port
	 * @li the head's buffer is empty
	 * @li the call can be observed
	 */
	TEST_FIXTURE( PortTailTFixture, CppSplicePrepend )
	{
		PortTailT< PortTestElement >::Ptr port1Tail = PortTailT< PortTestElement >::create(
				sim, "PortTailTTestCppSpliceAppend", WAITING_MODE, 2, &observer );
		PortHeadT< PortTestElement >::Ptr port2Head =  PortHeadT< PortTestElement >::create(
				sim, "PortTailTTestCppSpliceAppendHead", WAITING_MODE, 2 );
		PortTestElement element1 = { 1 };
		port1Tail->put( element1 );
		PortTailT< PortTestElement >::Ptr port2Tail( port2Head->getTail() );

		PortTestElement element2 = { 2 };
		port2Tail->put( element2 );

		// false says: prepend instead
		bool append = false;
		port1Tail->cppSplice( port2Head, append );
		CHECK_EQUAL( element2, port1Tail->getBuffer().front() );
		CHECK_EQUAL( element1, port1Tail->getBuffer().back() );
		CHECK( port2Head->getBuffer().empty() );
		CHECK( observer.observed( "onCppSplice", port1Tail->getLabel(), port2Head->getLabel(), toString( append ) ) );
	}

	/**
	 * @test odemx::PortTailT::setMode(PortMode)
	 *
	 * Expected function call effects:
	 * @li the mode is set correctly
	 * @li the call can be observed
	 */
	TEST_FIXTURE( PortTailTFixture, SetMode )
	{
		PortTailT< PortTestElement >::Ptr tail = PortTailT< PortTestElement >::create(
				sim, "PortTailTTestSetMode", WAITING_MODE, 2, &observer );
		tail->setMode( ERROR_MODE );
		CHECK_EQUAL( ERROR_MODE, tail->getMode() );
		CHECK( log->getTraceRecord( "change mode", type ) );
		CHECK( observer.observed( "onChangeMode", tail->getLabel(), "WAITING_MODE", "ERROR_MODE" ) );
	}

	/**
	 * @test odemx::PortTailT::setMaxCapacity(unsigned int)
	 *
	 * Expected function call effects:
	 * @li the new maximum capacity is set correctly
	 * @li the call can be observed
	 */
	TEST_FIXTURE( PortTailTFixture, SetMaxCapacity )
	{
		std::size_t oldMax = 2;
		PortTailT< PortTestElement >::Ptr tail = PortTailT< PortTestElement >::create(
				sim, "PortTailTTestSetMaxCapacity", WAITING_MODE, oldMax, &observer );
		std::size_t newMax = 125;
		tail->setMaxCapacity( newMax );
		CHECK_EQUAL( newMax, tail->getMaxCapacity() );
		CHECK( log->getTraceRecord( "change max capacity", type ) );
		CHECK( observer.observed( "onChangeMaxCapacity", tail->getLabel(), toString( oldMax ), toString( newMax ) ) );
	}

	/**
	 * @test odemx::PortTailT::alert()
	 *
	 * Expected function call effects:
	 * @li the waiting process is alerted
	 * @li the call can be observed
	 */
	TEST_FIXTURE( PortTailTFixture, Alert )
	{
		PortTailT< PortTestElement >::Ptr tail = PortTailT< PortTestElement >::create(
				sim, "PortTailTTestAlert", WAITING_MODE, 1, &observer );
		PortHeadT< PortTestElement >::Ptr head( tail->getHead() );

		// fill the Port
		PortTestElement element1 = { 1 };
		tail->put( element1 );

		// create Process that gets to wait
		std::size_t id = 17;
		PortTailTTestPutProcess putProcess( sim, "PortTailTTestAlertPutProcess", tail, id );
		putProcess.activate();
		sim.step();

		// make space available
		head->get();
		CHECK_EQUAL( (std::size_t) 1, tail->getAvailableSpace() );
		CHECK( putProcess.isAlerted() );
		sim.step();
		// 1 means that the put()-call was successful
		CHECK( 1 == putProcess.putReturnValue );
		CHECK_EQUAL( id, tail->getBuffer().back().id );
		CHECK( observer.observed( "onAlert", tail->getLabel(), putProcess.getLabel() ) );
	}

	/**
	 * @test odemx::PortTailT::available()
	 *
	 * Expected function call effects:
	 * @li the function returns \c true when the buffer is not full
	 */
	TEST_FIXTURE( PortTailTFixture, Available )
	{
		PortTailT< PortTestElement >::Ptr tail = PortTailT< PortTestElement >::create(
				sim, "PortTailTTestAvailable", WAITING_MODE, 1, &observer );
		CHECK( tail->isAvailable() );

		PortTestElement element1 = { 1 };
		tail->put( element1 );
		CHECK( ! tail->isAvailable() );
	}

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