//------------------------------------------------------------------------------
//	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 StatisticsBuffer.cpp
 * @author Ronald Kluth
 * @date created at 2009/04/07
 * @brief Implementation of class odemx::data::buffer::StatisticsBuffer
 * @sa StatisticsBuffer.h
 * @since 3.0
 */

#include <odemx/data/buffer/StatisticsBuffer.h>
#include <odemx/data/ManagedChannels.h>
#include <odemx/data/Producer.h>
#include <odemx/data/Report.h>
#include <odemx/util/Exceptions.h>
#include <odemx/util/StringConversion.h>

namespace odemx {
namespace data {
namespace buffer {

//------------------------------------------------------construction/destruction

std::tr1::shared_ptr< StatisticsBuffer > StatisticsBuffer::create(
		base::Simulation& sim, const Label& label, bool bufferUpdates )
{
	return std::tr1::shared_ptr< StatisticsBuffer >(
			new StatisticsBuffer( sim, label, bufferUpdates ) );
}

StatisticsBuffer::StatisticsBuffer( base::Simulation& sim, const Label& label,
		bool bufferUpdates )
:	ReportProducer( sim, label )
,	statistics_()
,	bufferUpdates_( bufferUpdates )
{
}

StatisticsBuffer::~StatisticsBuffer()
{
}

//---------------------------------------------------------------------accessors

const StatisticsBuffer::StatisticsMap& StatisticsBuffer::getStatistics() const
{
	return statistics_;
}

/// Check for existing ProducerStats or create and add a new stats object
StatisticsBuffer::ProducerStatsPtr StatisticsBuffer::getObjectStats(
		const data::Producer& sender )
{
	ProducerStatsPtr objectStats;
	StatisticsMap::iterator found = statistics_.find( sender.getLabel() );
	if( found != statistics_.end() )
	{
		objectStats = found->second;
	}
	else
	{
		objectStats.reset( new ProducerStats() );
		statistics_[ sender.getLabel() ] = objectStats;
		objectStats->resetTime = 0;
	}

	// store the sender type for report output
	if( ! objectStats->senderType.isSet() )
	{
		objectStats->senderType = sender.getType();
	}
	return objectStats;
}

bool StatisticsBuffer::isBufferingUpdates() const
{
	return bufferUpdates_;
}

const StatisticsBuffer::UpdateDeque& StatisticsBuffer::getUpdates(
		const Label& producer, const StringLiteral& property ) const
{
	StatisticsMap::const_iterator found = statistics_.find( producer );

	if( found != statistics_.end() )
	{
		UpdateMap::const_iterator foundProp =
				found->second->updateStats.find( property );

		if( foundProp != found->second->updateStats.end() )
		{
			return foundProp->second.updates;
		}
		else
		{
			throw DataException( "StatisticsBuffer::getUpdates(): property not found" );
		}
	}
	else
	{
		throw DataException( "StatisticsBuffer::getUpdates(): producer not found" );
	}
}

bool StatisticsBuffer::isEmpty() const
{
	return statistics_.empty();
}

void StatisticsBuffer::reset( base::SimTime resetTime )
{
	for( StatisticsMap::iterator iter = statistics_.begin();
		iter != statistics_.end(); ++iter )
	{
		ProducerStatsPtr objectStats = iter->second;
		objectStats->resetTime = resetTime;
		CountMap empty1;
		UpdateMap empty2;
		objectStats->countStats.swap( empty1 );
		objectStats->updateStats.swap( empty2 );
	}
}

//---------------------------------------------------------------record handling

void StatisticsBuffer::consume( const Log::ChannelId channelId, const SimRecord& record )
{
	// ignore log records from other channels
	if( channelId != channel_id::statistics )
	{
		return;
	}

	const data::StringLiteral& recordType = record.getText();
	const data::Producer& sender = record.getSender();
	base::SimTime time = record.getTime();

	// check the statistics record type and choose the appropriate action
	if( recordType == "parameter" )
	{
		ProducerStatsPtr objectStats = getObjectStats( sender );
		const SimRecord::DetailVec& details = record.getDetails();
		for( SimRecord::DetailVec::const_iterator iter = details.begin();
			 iter != details.end(); ++iter )
		{
			// save parameter name and value
			objectStats->paramStats[ iter->first ] = iter->second;
		}
	}
	else if( recordType == "count" )
	{
		ProducerStatsPtr objectStats = getObjectStats( sender );
		const SimRecord::DetailVec& details = record.getDetails();
		for( SimRecord::DetailVec::const_iterator iter = details.begin();
			 iter != details.end(); ++iter )
		{
			// add the given value to the counter
			objectStats->countStats[ iter->first ] +=
					iter->second.convert< std::size_t >();
		}
	}
	else if( recordType == "update" )
	{
		ProducerStatsPtr objectStats = getObjectStats( sender );
		const SimRecord::DetailVec& details = record.getDetails();
		for( SimRecord::DetailVec::const_iterator iter = details.begin();
			 iter != details.end(); ++iter )
		{
			const Log::StringLiteral& property = iter->first;

			// get the updated property
			UpdateMap::iterator updateIter = objectStats->updateStats.find( property );
			if( updateIter == objectStats->updateStats.end() )
			{
				// create new update stats if necessary
				std::pair< UpdateMap::iterator, bool> result =
						objectStats->updateStats.insert(
								UpdateMap::value_type( property,
										UpdateStats( const_cast< base::Simulation& >( sender.getSimulation() ),
												sender.getLabel() + " " + property.c_str() ) ) );
				assert( result.second );
				updateIter = result.first;
			}
			// get reference to the update stats which must exist now
			UpdateStats& updateStats = updateIter->second;
			// store the update time and value and accumulate statistics
			double value = iter->second.convert< double >();
			// compute accumulated value
			updateStats.accumulate.update( time, value );
			// store updates, if enabled
			if( bufferUpdates_ )
			{
				updateStats.updates.push_back( std::make_pair( time, value ) );
			}
		}
	}
	else if( recordType == "reset" )
	{
		ProducerStatsPtr objectStats = getObjectStats( sender );
		objectStats->resetTime = time;
		CountMap empty1;
		UpdateMap empty2;
		objectStats->countStats.swap( empty1 );
		objectStats->updateStats.swap( empty2 );
	}
}

void StatisticsBuffer::report( data::Report& report )
{
	using namespace data;

	// iterate over the statistics data of all senders
	for( StatisticsMap::const_iterator iter = statistics_.begin();
		 iter != statistics_.end(); ++iter )
	{
		const Label& sender = iter->first;
		const buffer::StatisticsBuffer::ProducerStatsPtr objectStats = iter->second;

		if( ! ( objectStats->paramStats.empty() && objectStats->countStats.empty() ) )
		{
			ReportTable::Definition paramCountTableDef;

			// build table definition for param and counter table
			paramCountTableDef.addColumn( "Name", ReportTable::Column::STRING );
			paramCountTableDef.addColumn( "Reset at", ReportTable::Column::SIMTIME );

			for( ParamMap::iterator paramIter = objectStats->paramStats.begin();
				paramIter != objectStats->paramStats.end(); ++paramIter )
			{
				paramCountTableDef.addColumn( paramIter->first.c_str(), ReportTable::Column::STRING );
			}

			for( CountMap::iterator countIter = objectStats->countStats.begin();
				countIter != objectStats->countStats.end(); ++countIter )
			{
				paramCountTableDef.addColumn( countIter->first.c_str(), ReportTable::Column::INTEGER );
			}

			// tables are associated with sender types
			ReportTable& table = report.getTable(
					objectStats->senderType.toString(), paramCountTableDef );

			table << sender << objectStats->resetTime;

			for( ParamMap::iterator paramIter = objectStats->paramStats.begin();
				paramIter != objectStats->paramStats.end(); ++paramIter )
			{
				table << paramIter->second.convert< std::string >();
			}

			for( CountMap::iterator countIter = objectStats->countStats.begin();
				countIter != objectStats->countStats.end(); ++countIter )
			{
				table << countIter->second;
			}
		}

		// define accum table
		ReportTable::Definition def;
		def.addColumn( "Producer", ReportTable::Column::STRING );
		def.addColumn( "Property", ReportTable::Column::STRING );
		def.addColumn( "Updates", ReportTable::Column::INTEGER );
		def.addColumn( "Min", ReportTable::Column::REAL );
		def.addColumn( "Max", ReportTable::Column::REAL );
		def.addColumn( "Mean", ReportTable::Column::REAL );
		def.addColumn( "Weighted Mean", ReportTable::Column::REAL );
		def.addColumn( "Std Deviation", ReportTable::Column::REAL );
		def.addColumn( "Weighted StDev", ReportTable::Column::REAL );
		ReportTable& table = report.getTable( "odemx::data::buffer::StatisticsBuffer Updates", def );

		// print accum stats
		for( UpdateMap::iterator updateIter = objectStats->updateStats.begin();
			updateIter != objectStats->updateStats.end(); ++updateIter )
		{
			const statistics::Accumulate& accum = updateIter->second.accumulate;
			table
			<< sender
			<< updateIter->first.c_str()
			<< accum.getUpdateCount()
			<< accum.getMin()
			<< accum.getMax()
			<< accum.getMean()
			<< accum.getWeightedMean()
			<< accum.getStandardDeviation()
			<< accum.getWeightedStandardDeviation();
		}
	}
}

} } } // namespace odemx::data::buffer
