/**
 * @file TestAccumulate.cpp
 * @date Aug 6, 2008
 * @author Ronald Kluth
 *
 * @brief Tests for ODEMx class Accumulate
 */

#include "TestStatistics.h"
#include "../TestBase/TestBase.h"
#include "../TestData/TestData.h"

#include <cmath>

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

	/**
	 * @struct AccumulateFixture
	 * @brief Helper struct providing set-up/tear-down of Accumulate tests
	 *
	 * @copydetails EventFixture
	 */
	struct AccumulateFixture
	{
		SuiteBase::SimulationTest sim;
		SuiteData::ReportTest reporter;
		Accumulate accum, accumLin;

		AccumulateFixture()
		:	sim( "AccumTestSim" ),
			reporter(),
			accum( sim, "AccumTestStepwise", Accumulate::stepwise ),
			accumLin( sim, "AccumTestLinear", Accumulate::linear )
			{}
	};

	/**
	 * @test odemx::Accumulate construction
	 *
	 * Expected effects:
	 * @li label is set correctly
	 * @li statistics manager (Simulation) is set correctly
	 * @li accum values are 0-initialized
	 * @li last reset time is current base::SimTime
	 */
	TEST_FIXTURE( AccumulateFixture, ConstructionDestruction )
	{
		data::Label label = "TestAccumConstructionUserSim";
		std::auto_ptr< Accumulate > accum( new Accumulate( sim, label ) );
		CHECK_EQUAL( label, accum->getLabel() );
		CHECK_EQUAL( (std::size_t) 0, accum->getUpdateCount() );
		CHECK_EQUAL( (std::size_t) 0, accum->getZeros() );
		CHECK_EQUAL( 0.0, accum->getMin() );
		CHECK_EQUAL( 0.0, accum->getMax() );
		CHECK_EQUAL( 0.0, accum->getMean() );
		CHECK_EQUAL( 0.0, accum->getWeightedMean() );
		CHECK_EQUAL( 0.0, accum->getStandardDeviation() );
		CHECK_EQUAL( 0.0, accum->getWeightedStandardDeviation() );
		CHECK_EQUAL( sim.getTime(), accum->getResetTime() );
	}

	/**
	 * @test odemx::Accumulate::update()
	 *
	 * Expected function call effects:
	 * @li usage counter is increased by the number of \c update() calls
	 * @li \c min and \c max are set correctly
	 * @li in stepwise mode, constant value between steps is assumed
	 * @li
	 */
	TEST_FIXTURE( AccumulateFixture, Update )
	{
		double min = 0.0;
		double max = 1.0;
		accum.update( 0, max );
		accumLin.update( 0, max );
		accum.update( 4, min );
		accumLin.update( 4, min );
		accum.update( 5, min );
		accumLin.update( 5, min );

		CHECK_EQUAL( (std::size_t) 3, accum.getUpdateCount() );
		CHECK_EQUAL( min, accum.getMin() );
		CHECK_EQUAL( max, accum.getMax() );
		CHECK_EQUAL( 1.0 / accum.getUpdateCount(), accum.getMean() );
		CHECK_EQUAL( 1.0 / accumLin.getUpdateCount(), accumLin.getMean() );
		CHECK_EQUAL( 0.8, accum.getWeightedMean() );
		CHECK_EQUAL( 0.4, accumLin.getWeightedMean() );
	}

	/**
	 * @test odemx::Accumulate::reset()
	 *
	 * Expected function call effects:
	 * @li the accum values are set to 0
	 * @li the last reset time is set to the sim's time
	 */
	TEST_FIXTURE( AccumulateFixture, Reset )
	{
		base::SimTime simTime = 25;
		accum.update( simTime, 16 );
		accum.update( simTime, 11 );

		accum.reset( simTime );
		CHECK_EQUAL( (std::size_t) 0, accum.getUpdateCount() );
		CHECK_EQUAL( 0.0, accum.getMin() );
		CHECK_EQUAL( 0.0, accum.getMax() );
		CHECK_EQUAL( 0.0, accum.getMean() );
		CHECK_EQUAL( 0.0, accum.getStandardDeviation() );
		CHECK_EQUAL( simTime, accum.getResetTime() );
	}

	/**
	 * @test odemx::Accumulate::report(Report*)
	 *
	 * Expected function call effects:
	 * @li the report table contains correct values
	 */
	TEST_FIXTURE( AccumulateFixture, Report )
	{
		base::SimTime simTime = 25;

		accum.reset( simTime ); // non-0 reset time
		double min = 1.2;
		double max = 3.4;
		accum.update( simTime, min );
		++simTime;
		accum.update( simTime, max );

		double mean = accum.getMean();
		double deviation = std::sqrt(
				( ( min - mean ) * ( min - mean )
				+ ( max - mean ) * ( max - mean ) ) / 2 );

		CHECK_EQUAL( (std::size_t) 2, accum.getUpdateCount() );
		CHECK_EQUAL( min, accum.getMin() );
		CHECK_EQUAL( max, accum.getMax() );
		CHECK_EQUAL( ( min + max ) / 2, mean );
		CHECK_CLOSE( deviation, accum.getStandardDeviation(), 0.000000000000001 );

		reporter.addReportProducer( accum );
		reporter.generateReport();
		CHECK( reporter.processedTables );

		// check the table line
		CHECK_EQUAL( accum.getLabel(), reporter.allTableContent[0][0] );
		CHECK_EQUAL( toString( accum.getResetTime() ), reporter.allTableContent[0][1] );
		CHECK_EQUAL( toString( accum.getUpdateCount() ), reporter.allTableContent[0][2] );
		CHECK_EQUAL( toString( accum.getMin() ), reporter.allTableContent[0][3] );
		CHECK_EQUAL( toString( accum.getMax() ), reporter.allTableContent[0][4] );
		CHECK_EQUAL( toString( accum.getMean() ), reporter.allTableContent[0][5] );
		CHECK_EQUAL( toString( accum.getWeightedMean() ), reporter.allTableContent[0][6] );
		CHECK_EQUAL( toString( accum.getStandardDeviation() ), reporter.allTableContent[0][7] );
		CHECK_EQUAL( toString( accum.getWeightedStandardDeviation() ), reporter.allTableContent[0][8] );
	}

	/**
	 * @test odemx::Accumulate::getMean()
	 *
	 * Expected function call effects:
	 * @li the mean is computed correctly
	 */
	TEST_FIXTURE( AccumulateFixture, GetMean )
	{
		double sum = 0;
		int values[] = { 1, 5, 2, 7, 10, 5 };
		size_t count = ( sizeof( values ) / sizeof( int ) );
		base::SimTime simTime = 0;

		for( size_t i = 0; i < count; ++i )
		{
			sum += values[i];
			accum.update( simTime++, values[i] );
		}

		double mean = sum / count;

		CHECK_EQUAL( mean, accum.getMean() );
	}

	/**
	 * @test odemx::Accumulate::getStandardDeviation()
	 *
	 * Expected function call effects:
	 * @li the standard deviation is computed correctly
	 */
	TEST_FIXTURE( AccumulateFixture, GetStandardDeviation )
	{
		double sum = 0;
		int values[] = { 1, 5, 2, 7, 10, 5 };
		size_t count = ( sizeof( values ) / sizeof( int ) );
		base::SimTime simTime = 0;

		for( size_t i = 0; i < count; ++i )
		{
			sum += values[i];
			accum.update( simTime, values[i] );
		}

		double mean = sum / count;
		double variance = 0;
		for( size_t i = 0; i < count; ++i )
		{
			variance += ( values[i] - mean ) * ( values[i] - mean );
		}
		variance /= count;

		double standardDeviation = std::sqrt( variance );

		CHECK_EQUAL( standardDeviation, accum.getStandardDeviation() );
		CHECK_CLOSE( standardDeviation, accum.getStandardDeviation(), 0.001 );
	}

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