//------------------------------------------------------------------------------
//	Copyright (C) 2009 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 Histogram.cpp
 * @author Ronald Kluth
 * @date created at 2009/03/01
 * @brief Implementation of odemx::statistics::Histogram
 * @sa Histogram.h
 * @since 3.0
 */

#include <odemx/statistics/Histogram.h>
#include <odemx/data/Report.h>

#include <cmath>

namespace odemx {
namespace statistics {

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

Histogram::Histogram( base::Simulation& sim, const data::Label& label,
		double lowerBound, double upperBound, unsigned int cellCount )
:	Tally( sim, label )
,	lowerBound_( lowerBound )
,	upperBound_( upperBound )
,	cellCount_( cellCount + 2 )
{
	if( upperBound_ == lowerBound_ )
	{
		upperBound_ = lowerBound_ + 100.0;
	}

	if( upperBound_ < lowerBound_ )
	{
		std::swap( lowerBound_, upperBound_ );
	}

	cellWidth_ = ( upperBound_ - lowerBound_ ) / ( cellCount_ - 2 );

	cellData_.resize( cellCount_, Cell() );
	maxCellIndex_ = cellData_.size() - 1;
}

Histogram::~Histogram()
{
}

//---------------------------------------------------------------------modifiers

void Histogram::update( double value )
{
	Tally::update( value );

	// the vector index (cell) to be updated for the given value
	int cellIndex;

	// correct offset
	value -= lowerBound_;

	if( value < 0.0 )
	{
		cellIndex = 0;
	}
	else
	{
		cellIndex = (int) (std::floor( value / cellWidth_ ) + 1);

		if( cellIndex > maxCellIndex_ )
		{
			cellIndex = maxCellIndex_ ;
		} else if (cellIndex < 0) {
			cellIndex = 0;
		}
	}

	++( cellData_[ cellIndex ].count );
}

void Histogram::reset( base::SimTime time )
{
	Tally::reset( time );

	// reset cell vector by swapping with a zero-initialized temporary
	CellVec tmp( cellCount_, Cell() );
	cellData_.swap( tmp );
}

//-----------------------------------------------------------------------getters

const Histogram::CellVec& Histogram::getData()
{
	// update() only sets the counters,
	// must compute other cell values here

	// values < lower bound
	cellData_[ 0 ].lowerBound = std::min( lowerBound_ - cellWidth_, getMin() );
	cellData_[ 0 ].upperBound = lowerBound_;
	cellData_[ 0 ].percentage = (double)cellData_[ 0 ].count * 100 / getUpdateCount();

	// regular values within bounds
	for( size_t i = 1; i < cellCount_ - 1; ++i )
	{
		cellData_[ i ].lowerBound = lowerBound_ + (i - 1) * cellWidth_;
		cellData_[ i ].upperBound = lowerBound_ + i * cellWidth_;
		cellData_[ i ].percentage = (double)cellData_[ i ].count * 100 / getUpdateCount();
	}

	// values > upper bound
	cellData_[ cellCount_ - 1 ].lowerBound = upperBound_;
	cellData_[ cellCount_ - 1 ].upperBound = std::max( upperBound_ + cellWidth_, getMax() );
	cellData_[ cellCount_ - 1 ].percentage =
		(double)cellData_[ cellCount_ - 1 ].count * 100 / getUpdateCount();

	return cellData_;
}

//------------------------------------------------------------------------report

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

	ReportTable::Definition tallyDef;
	tallyDef.addColumn( "Name", ReportTable::Column::STRING );
	tallyDef.addColumn( "Reset at", ReportTable::Column::SIMTIME );
	tallyDef.addColumn( "Uses", ReportTable::Column::INTEGER );
	tallyDef.addColumn( "Low", ReportTable::Column::REAL );
	tallyDef.addColumn( "High", ReportTable::Column::REAL );
	tallyDef.addColumn( "Cells", ReportTable::Column::INTEGER );
	tallyDef.addColumn( "Min", ReportTable::Column::REAL );
	tallyDef.addColumn( "Max", ReportTable::Column::REAL );
	tallyDef.addColumn( "Mean", ReportTable::Column::REAL );
	tallyDef.addColumn( "Std Deviation", ReportTable::Column::REAL );
	ReportTable& statsTable = report.getTable( "Histogram Statistics Summary", tallyDef );
	statsTable
	<< getLabel()
	<< getResetTime()
	<< getUpdateCount()
	<< lowerBound_
	<< upperBound_
	<< cellCount_
	<< getMin()
	<< getMax()
	<< getMean()
	<< getStandardDeviation();

	ReportTable::Definition visualDef;
	visualDef.addColumn( "Low", ReportTable::Column::REAL );
	visualDef.addColumn( "High", ReportTable::Column::REAL );
	visualDef.addColumn( "Count", ReportTable::Column::INTEGER );
	visualDef.addColumn( "Percentage", ReportTable::Column::REAL );
	visualDef.addColumn( "Bar Diagram", ReportTable::Column::BAR );

	std::ostringstream tableName;
	tableName << "Histogram: " << getLabel() << " [" << getUpdateCount() << " samples]";
	ReportTable& visualTable = report.getTable( tableName.str(), visualDef );

	const CellVec& data = getData();

	// compute multiplier for graphical representation
	double maxPercentage = 0;
	for( CellVec::const_iterator cellIter = data.begin();
		cellIter != data.end(); ++cellIter )
	{
		const Cell& cell = *cellIter;
		if( cell.percentage > maxPercentage )
		{
			maxPercentage = cell.percentage;
		}
	}
	// diagram width should be 100%, i.e. the maximum value should fill it
	double multiplier = 0;
	if( 0 < maxPercentage )
	{
		multiplier = 100 / maxPercentage;
	}

	// compute results
	for( CellVec::const_iterator iter = data.begin(); iter != data.end(); ++iter )
	{
		const Cell& current = *iter;

		long bar = (long) std::floor( multiplier * current.percentage );
		if( current.percentage - bar > 0.5 )
		{
			++bar;
		}

		visualTable
		<< current.lowerBound
		<< current.upperBound
		<< current.count
		<< current.percentage
		<< bar;
	}
};

} } // namespace odemx::statistics
