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

#include <odemx/data/ReportTable.h>
#include <odemx/base/SimTime.h>
#include <odemx/util/Exceptions.h>
#include <odemx/setup.h>

#include <stdexcept>
#include <sstream>

namespace odemx {
namespace data {

/********************** Column *********************************************/

const std::string ReportTable::Column::typeToString( ReportTable::Column::Type t )
{
	switch( t )
	{
	case Column::BAR: 		return std::string( "BAR" );
	case Column::SIMTIME: 	return std::string( "SIMTIME" );
	case Column::INTEGER: 	return std::string( "INTEGER" );
	case Column::REAL: 		return std::string( "REAL" );
	case Column::STRING: 	return std::string( "STRING" );
	default: return std::string( "UNKNOWN COLUMN TYPE" );
	}
}

/********************* Table Definition ***********************************/

// compare table definitions, used to determine if two tables are the same
bool operator==( const ReportTable::Definition& lhs, const ReportTable::Definition& rhs )
{
	ReportTable::SizeType columns = lhs.getNumberOfColumns();

	// check for same column number
	if( columns != rhs.getNumberOfColumns() )
	{
		return false;
	}

	// compare each column by type and label
	for( ReportTable::SizeType i = 0; i < columns; ++i )
	{
		if( lhs.getTypeOfColumn( i ) != rhs.getTypeOfColumn( i )
			|| lhs.getLabelOfColumn( i ) != rhs.getLabelOfColumn( i ) )
		{
			return false;
		}
	}
	// all checks passed
	return true;
}

ReportTable::SizeType ReportTable::Definition::getNumberOfColumns() const
{
	return columnLabels_.size();
}

const std::string& ReportTable::Definition::getLabelOfColumn( ReportTable::SizeType index ) const
{
	static const std::string error( "ERROR" );
	return ( index < columnLabels_.size() ) ?
			columnLabels_[ index ] : error;
}

ReportTable::Column::Type ReportTable::Definition::getTypeOfColumn( ReportTable::SizeType index ) const
{
	return ( index < columnTypes_.size() ) ?
			columnTypes_[ index ] : ReportTable::Column::INVALID;
}

void ReportTable::Definition::addColumn( const std::string& label, ReportTable::Column::Type type )
{
	if( label.empty() || type == Column::INVALID )
	{
		return;
	}
	columnLabels_.push_back( label );
	columnTypes_.push_back( type );
}

/********************* Table Functions ***********************************/

/// Construction is managed by Report
ReportTable::ReportTable( const Label& label, const ReportTable::Definition& def )
:	label_( label )
,	def_( def )
,	lineCount_( 0 )
{
	// create vectors for columns according to TableDefinition
	for( ReportTable::SizeType i = 0; i < def.getNumberOfColumns(); ++i )
	{
		Column* newColumn = 0;

		// create each column according to the given type
		switch( def.getTypeOfColumn(i) )
		{
		case Column::BAR:
			newColumn = new ColumnT< long >( def.getLabelOfColumn( i ), Column::BAR );
			break;
		case Column::INTEGER:
			newColumn = new ColumnT< long >( def.getLabelOfColumn( i ), Column::INTEGER );
			break;
		case Column::REAL:
			newColumn = new ColumnT< double >( def.getLabelOfColumn( i ), Column::REAL );
			break;
		case Column::STRING:
			newColumn = new ColumnT< std::string >( def.getLabelOfColumn( i ), Column::STRING );
			break;
#ifdef ODEMX_USE_CONTINUOUS
		case Column::SIMTIME:
			newColumn = new ColumnT< double >( def.getLabelOfColumn( i ), Column::SIMTIME );
			break;
#else
		case Column::SIMTIME:
			newColumn = new ColumnT< long long >( def.getLabelOfColumn( i ), Column::SIMTIME );
			break;
#endif
		default: // INVALID
			throw std::logic_error( "ReportTable::ReportTable(): invalid column type" );
			break;
		}

		if( ! newColumn )
		{
			throw DataException( "ReportTable::ReportTable(): invalid column type" );
		}
		columns_.push_back( newColumn );
	}
}

ReportTable::~ReportTable()
{
	// clean up columns
	for( ColumnVec::const_iterator iter = columns_.begin();
		iter != columns_.end(); ++iter )
	{
		delete *iter;
	}
}

/********************** Input Streaming **********************************/

// only four functions actually add data, the others are merely
// wrappers using the three operator << implementations

// this function deals with INTEGER and with BAR column types
ReportTable& operator<<( ReportTable& t, long d )
{
	// try to get the current column according to inputPosition
	// this also checks for the correct type of column
	ReportTable::ColumnT< long >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< long >* >( t.getCurrentInputColumn() );

	// test column type
	if ( currentColumn )
	{
		currentColumn->addData( d );
	}
	else
	{
		throw DataException( "ReportTable operator<<(long): wrong column "
				"type at input position; cannot enter data into Table" );
	}

	// set input pointer to next position for streaming
	t.advanceInputPosition();
	return t;
}


ReportTable& operator<<( ReportTable& t, long long d )
{
	// try to get the current column according to inputPosition
	// this also checks for the correct type of column
	ReportTable::ColumnT< long long >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< long long >* >( t.getCurrentInputColumn() );

	// test column type
	if( currentColumn )
	{
		currentColumn->addData( d );
	}
	else
	{
		throw DataException( "ReportTable operator<<(long long): "
				"wrong column type at input position; cannot enter data into Table" );
	}

	// set input pointer to next position for streaming
	t.advanceInputPosition();
	return t;
}


// the above function forces use of this one:
ReportTable& operator<<( ReportTable& t, double d )
{
	// try to get the current column according to inputPosition
	// this also checks for the correct type of column, returns 0 if wrong
	ReportTable::ColumnT< double >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< double >* >( t.getCurrentInputColumn() );

	// test column type
	if( currentColumn )
	{
		currentColumn->addData( d );
	}
	else
	{
		throw DataException( "ReportTable operator<<(double): wrong "
				"column type at input position; cannot enter data into Table" );
	}

	// set input pointer to next position for streaming
	t.advanceInputPosition();
	return t;
}

ReportTable& operator<<( ReportTable& t, const std::string s )
{
	// try to get the current column according to inputPosition
	// this also checks for the correct type of column, returns 0 if wrong
	ReportTable::ColumnT< std::string >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< std::string >* >( t.getCurrentInputColumn() );

	// test column type
	if( currentColumn )
	{
		currentColumn->addData( s );
	}
	else
	{
		throw DataException( "ReportTable operator<<(string): wrong "
				"column type at input position; cannot enter data into Table" );
		return t;
	}

	// set input pointer to next position for streaming
	t.advanceInputPosition();
	return t;
}

ReportTable& operator<<( ReportTable& t, int i )
{
	return t << (long)i;
}

ReportTable& operator<<( ReportTable& t, unsigned int i )
{
	return t << (long)i;
}

ReportTable& operator<<( ReportTable& t, unsigned long i )
{
	return t << (long)i;
}

ReportTable& operator<<( ReportTable& t, unsigned long long i )
{
	return t << (long long)i;
}

ReportTable& operator<<( ReportTable& t, float f )
{
	return t << (double)f;
}

ReportTable& operator<<( ReportTable& t, const char* s )
{
	return t << std::string( s );
}


// this function is used to skip table cells or the rest of the line
ReportTable& operator<<( ReportTable& t, ReportTable::Definition::ControlCode cc )
{
	ReportTable::SizeType line;

	// control codes may end a line early
	// or cause a jump to the next table cell
	switch( cc )
	{
	case ReportTable::Definition::ENDL:
		// fill line with default values
		line = t.lineCount_;

		while( line == t.lineCount_ )
		{
			// enter default value
			t.addDefaultValue();
		}
		break;
	case ReportTable::Definition::TAB:
		// enter default value
		t.addDefaultValue();
		break;
	default:
		throw DataException( "ReportTable operator<<(ControlCode): unknown "
				"control code; cannot enter data into table" );
	}
	return t;
}

void ReportTable::addDefaultValue()
{
	Column* currentCol = getCurrentInputColumn();

	// enter default value
	switch( currentCol->getType() )
	{
#ifdef ODEMX_USE_CONTINUOUS
	case Column::SIMTIME:
		dynamic_cast< ColumnT< double >* >( currentCol )->addData( 0 );
#else
	case Column::SIMTIME:
		dynamic_cast< ColumnT< long long >* >( currentCol )->addData( 0 );
#endif
		break;
	case Column::BAR: // fall through
	case Column::INTEGER:
		dynamic_cast< ColumnT< long >* >( currentCol )->addData( 0 );
		break;
	case Column::REAL:
		dynamic_cast< ColumnT< double >* >( currentCol )->addData( 0.0 );
		break;
	case Column::STRING:
		dynamic_cast< ColumnT< std::string >* >( currentCol )->addData( "" );
		break;
	default:
		throw DataException( "ReportTable::addDefaultValue(): invalid "
				"column type, cannot add data" );
	}

	// update inputColumnPointer
	advanceInputPosition();
}

/******************** Output streaming ************************************/

ReportTable& operator>>( ReportTable& t, long long& to )
{
	// try to get the current column according to outputPosition
	// this also checks for the correct type of column
	ReportTable::ColumnT< long long >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< long long >* >( t.getCurrentOutputColumn() );

	// test column type
	if( currentColumn )
	{
		to = currentColumn->getData( t.getOutputLine() );
	}
	else
	{
		throw DataException( "ReportTable::operator>>(long long): "
				"wrong column type, cannot extract data" );
	}

	// set output pointer to next position for streaming
	t.advanceOutputPosition();
	return t;
}

ReportTable& operator>>( ReportTable& t, unsigned long long& to )
{
	// try to get the current column according to outputPosition
	// this also checks for the correct type of column, returns 0 if wrong
	if( ReportTable::ColumnT< unsigned long long >* currentColumn =
			dynamic_cast< ReportTable::ColumnT< unsigned long long >* >(
					t.getCurrentOutputColumn() ) )
	{
		to = currentColumn->getData( t.getOutputLine() );
	}
	else if( ReportTable::ColumnT< long long >* currentColumn =
			dynamic_cast< ReportTable::ColumnT< long long >* >(
					t.getCurrentOutputColumn() ) )
	{
		to = currentColumn->getData( t.getOutputLine() );
	}
	else
	{
		throw DataException( "ReportTable::operator>>(unsigned long long):"
				"wrong column type, cannot extract data" );
	}

	// set output pointer to next position for streaming
	t.advanceOutputPosition();
	return t;
}

ReportTable& operator>>( ReportTable& t, long& to )
{
	// try to get the current column according to outputPosition
	// this also checks for the correct type of column, returns 0 if wrong
	ReportTable::ColumnT< long >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< long >* >( t.getCurrentOutputColumn() );

	// test column type
	if ( currentColumn )
	{
		to = currentColumn->getData( t.getOutputLine() );
	}
	else
	{
		throw DataException( "ReportTable::operator>>(long):"
				"wrong column type, cannot extract data" );
	}

	// set output pointer to next position for streaming
	t.advanceOutputPosition();
	return t;
}

ReportTable& operator>>( ReportTable& t, double& to )
{
	// try to get the current column according to outputPosition
	// this also checks for the correct type of column, is 0 if wrong
	ReportTable::ColumnT< double >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< double >* >( t.getCurrentOutputColumn() );

	// test column type
	if( currentColumn )
	{
		to = currentColumn->getData( t.getOutputLine() );
	}
	else
	{
		ReportTable::ColumnT< long >* currentColumn =
			dynamic_cast< ReportTable::ColumnT<long>* >( t.getCurrentOutputColumn() );

		if( currentColumn )
		{
			to = (double)currentColumn->getData( t.getOutputLine() );
		}
		else
		{
			throw DataException( "ReportTable::operator>>(double): "
					"wrong column type, cannot extract data" );
		}
	}

	// set output pointer to next position for streaming
	t.advanceOutputPosition();
	return t;
}

ReportTable& operator>>( ReportTable& t, std::string& to )
{
	// any column type accepted, conversion with ostringstream
	std::ostringstream convert;

	ReportTable::Column* currentColumn = t.getCurrentOutputColumn();

	// try to get the current column according to outputPosition
	// this also checks for the correct type of column
	switch( currentColumn->getType() )
	{
	case ReportTable::Column::BAR: // fall through
	case ReportTable::Column::INTEGER:
		convert << dynamic_cast< ReportTable::ColumnT< long >* >
				( currentColumn )->getData( t.getOutputLine() );
		break;
	case ReportTable::Column::REAL:
		convert << dynamic_cast< ReportTable::ColumnT< double >* >
				( currentColumn )->getData( t.getOutputLine() );
		break;
	case ReportTable::Column::STRING:
		convert << dynamic_cast< ReportTable::ColumnT< std::string >* >
				( currentColumn )->getData( t.getOutputLine() );
		break;
	case ReportTable::Column::SIMTIME:
#ifdef ODEMX_USE_CONTINUOUS
		convert << dynamic_cast< ReportTable::ColumnT< double >* >
				( currentColumn )->getData( t.getOutputLine() );
#else
		convert << dynamic_cast< ReportTable::ColumnT< long long >* >
				( currentColumn )->getData( t.getOutputLine() );
#endif
		break;
	default:
		DataException( "ReportTable::operator>>(string): "
				"invalid column type, cannot extract data" );
	}

	// write string to stream
	to = convert.str();

	// update outputColumnPointer
	t.advanceOutputPosition();
	return t;
}


/****************** Random Access Output **********************************/

base::SimTime ReportTable::getSIMTIME( ReportTable::SizeType col, ReportTable::SizeType line )
{
	if( col >= columns_.size() )
	{
		throw std::out_of_range( "ReportTable::getSIMTIME(): column out of range" );
	}
	if( line >= lineCount_ )
	{
		throw std::out_of_range( "ReportTable::getSIMTIME(): line out of range" );
	}

	// try to get the column col, check for the correct type, is 0 if wrong
#ifdef ODEMX_USE_CONTINUOUS
	ReportTable::ColumnT< double >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< double >* >( columns_[ col ] );
#else
	ReportTable::ColumnT< long long >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< long long >* >( columns_[ col ] );
#endif

	// test column type
	if( currentColumn
		&& currentColumn->getType() == ReportTable::Column::SIMTIME )
	{
		return (base::SimTime) currentColumn->getData( line );
	}
	else
	{
		throw DataException( "ReportTable::getSIMTIME(): wrong column type; "
				"cannot extract data" );
	}
}


long ReportTable::getBAR( ReportTable::SizeType col, ReportTable::SizeType line )
{
	if( col >= columns_.size() )
	{
		throw std::out_of_range( "ReportTable::getBAR(): column out of range" );
	}
	if( line >= lineCount_ )
	{
		throw std::out_of_range( "ReportTable::getBAR(): line out of range" );
	}

	// try to get the column col, check for the correct type, is 0 if wrong
	ReportTable::ColumnT< long >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< long >* >( columns_[ col ] );

	// test column type
	if( currentColumn
		&& currentColumn->getType() == Column::BAR )
	{
		return currentColumn->getData( line );
	}
	else
	{
		throw DataException( "ReportTable::getBAR(): wrong column type; "
				"cannot extract data" );
	}
}

long ReportTable::getINTEGER( ReportTable::SizeType col, ReportTable::SizeType line )
{
	if( col >= columns_.size() )
	{
		throw std::out_of_range( "ReportTable::getINTEGER(): column out of range" );
	}
	if( line >= lineCount_ )
	{
		throw std::out_of_range( "ReportTable::getINTEGER(): line out of range" );
	}

	// try to get the column col, check for the correct type, is 0 if wrong
	ReportTable::ColumnT< long >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< long >* >( columns_[ col ] );

	// test column type
	if ( currentColumn
		&& currentColumn->getType() == Column::INTEGER )
	{
		return currentColumn->getData( line );
	}
	else
	{
		throw DataException( "ReportTable::getINTEGER(): wrong column type; "
				"cannot extract data" );
	}
}

double ReportTable::getREAL( ReportTable::SizeType col, ReportTable::SizeType line )
{
	if( col >= columns_.size() )
	{
		throw std::out_of_range( "ReportTable::getREAL(): column out of range" );
	}
	if( line >= lineCount_ )
	{
		throw std::out_of_range( "ReportTable::getREAL(): line out of range" );
	}

	// try to get the column col, check for the correct type, is 0 if wrong
	ReportTable::ColumnT< double >* currentColumn =
		dynamic_cast< ReportTable::ColumnT< double >* >( columns_[ col ]  );

	// test column type
	if( currentColumn
		&& currentColumn->getType() == Column::REAL )
	{
		return currentColumn->getData( line );
	}
	else
	{
		// cast from long type is also allowed
		ReportTable::ColumnT< long >* currentColumn =
			dynamic_cast< ReportTable::ColumnT< long >* >( columns_[ col ] );

		if( currentColumn
			&& currentColumn->getType() == Column::INTEGER )
		{
			return (double)currentColumn->getData( line );
		}
		else
		{
			throw DataException( "ReportTable::getREAL(): wrong column type; "
							"cannot extract data" );
		}
	}
}

std::string ReportTable::getSTRING( ReportTable::SizeType col, ReportTable::SizeType line )
{
	if( col >= columns_.size() )
	{
		throw std::out_of_range( "ReportTable::getSTRING(): column out of range" );
	}
	if( line >= lineCount_ )
	{
		throw std::out_of_range( "ReportTable::getSTRING(): line out of range" );
	}

	// any column type accepted, conversion with std::ostringstream
	std::ostringstream convert;
	ReportTable::Column* currentColumn = columns_[ col ];

	// checks for the correct type of column, returns 0 if wrong
	switch( currentColumn->getType() )
	{
	case ReportTable::Column::BAR: // fall through
	case ReportTable::Column::INTEGER:
		convert << dynamic_cast< ReportTable::ColumnT< long >* >
				( currentColumn )->getData( line );
		break;
#ifdef ODEMX_USE_CONTINUOUS
	case ReportTable::Column::SIMTIME:
		convert << dynamic_cast< ReportTable::ColumnT< double >* >
				( currentColumn )->getData( line );
#else
	case ReportTable::Column::SIMTIME:
		convert << dynamic_cast< ReportTable::ColumnT< long long >* >
				( currentColumn )->getData( line );
#endif
		break;
	case ReportTable::Column::REAL:
		convert << dynamic_cast< ReportTable::ColumnT< double >* >
				( currentColumn )->getData( line );
		break;
	case ReportTable::Column::STRING:
		convert << dynamic_cast< ReportTable::ColumnT< std::string >* >
				( currentColumn )->getData( line );
		break;
	default:
		throw DataException( "ReportTable::getSTRING(): unknown column type; "
						"cannot extract data" );
	}
	return convert.str();
}

// help procedures
void ReportTable::advanceInputPosition()
{
	// advance the inputPosition by one
	++inputPosition_.col;

	// check for line end
	if( inputPosition_.col >= getNumberOfColumns() )
	{
		inputPosition_.col = inputPosition_.col % getNumberOfColumns();
		++inputPosition_.line;
		++lineCount_;
	}
}

void ReportTable::advanceOutputPosition()
{
	// advance the outputPosition by one
	++outputPosition_.col;

	// check for line end
	if( outputPosition_.col >= getNumberOfColumns() )
	{
		outputPosition_.col = outputPosition_.col % getNumberOfColumns();
		++outputPosition_.line;
	}

	// wrap output streaming at last line to start anew
	if( outputPosition_.line >= getNumberOfLines() )
	{
		outputPosition_.line = 0;
	}
}

} } // namespace data
