//------------------------------------------------------------------------------
//	Copyright (C) 2008, 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 TimeFormat.cpp
 * @author Ronald Kluth
 * @date created at 2008/03/14
 * @brief Implementation of odemx::data::output::TimeFormat
 * @sa TimeFormat.h
 * @since 2.1
 */

#if defined(_MSC_VER)
#pragma warning(disable : 4244) // conversion of SimTime to double possible data loss
#endif

#include <odemx/data/output/TimeFormat.h>
#include <odemx/util/Exceptions.h>

#include <cmath>
#include <cassert>
#include <iomanip>
#include <stdexcept>

namespace odemx {
namespace data {
namespace output {

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

TimeFormat::TimeFormat()
:	base( 0, 0, 0, static_cast< base::SimTime >(0), TimeUnit::none )
{
	resetTimeValues();
}

TimeFormat::TimeFormat( const TimeBase& timeBase )
:	base( timeBase )
{
	resetTimeValues();
}

TimeFormat::~TimeFormat()
{
}

//-----------------------------------------------------------------------setters

void TimeFormat::setTimeBase( unsigned short year, unsigned short month,
		unsigned short day, base::SimTime dayTimeOffset, TimeUnit::Type unit )
{
	base.year = year;
	base.month = month;
	base.day = day;
	base.offset = dayTimeOffset;
	base.unit = unit;
}

void TimeFormat::setTimeBase( const TimeBase& timeBase )
{
	base = timeBase;
}

void TimeFormat::resetTimeValues()
{
	simTime = static_cast< base::SimTime >(0);
	year = month = day = hour = min = 0;
	sec = 0.0;
}

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

const TimeBase& TimeFormat::getTimeBase() const
{
	return base;
}

//-------------------------------------------------------------string conversion

const std::string TimeFormat::timeToString( base::SimTime time )
{
	computeTimeValues( time );
	return makeString();
}

unsigned short TimeFormat::daysOfMonth( unsigned short month, unsigned short year )
{
	bool leapYear = false;

	if( year % 4 == 0 ) leapYear = true;
	if( year % 100 == 0 ) leapYear = false;
	if( year % 400 == 0 ) leapYear = true;

    switch( month )
    {
	case 4:
	case 6:
	case 9:
	case 11:
		return 30;

	case 2:
		return leapYear ? 29 : 28;

	default:
		return 31;
    }
}

void TimeFormat::computeTimeValues( base::SimTime time )
{
	resetTimeValues();

	if( getTimeBase().unit == TimeUnit::none )
	{
		throw DataException( "TimeFormat::computeTimeValues():"
				" cannot compute time, no base unit given" );
	}

	// store the SimTime in minutes for easy computation
	double simMinutes = 0.0;
	// store the daytime offset
	double dayTimeOffset = 0.0;

	// convert SimTime to minutes for convenience
	// by evaluating the given Time::unit of the Format definition
	switch( getTimeBase().unit )
	{
	case TimeUnit::milliseconds:
		simMinutes = time / double(60000);
		dayTimeOffset = getTimeBase().offset / double(60000);
		break;

	case TimeUnit::seconds:
		simMinutes = time / double(60);
		dayTimeOffset = getTimeBase().offset / double(60);
		break;

	case TimeUnit::minutes:
		simMinutes = time;
		dayTimeOffset = getTimeBase().offset;
		break;

	case TimeUnit::hours:
		simMinutes = time * 60;
		dayTimeOffset = getTimeBase().offset * 60;
		break;

	case TimeUnit::none:
		assert(false);
	}

	// compute number of days from minutes
	day = (unsigned short) floor( simMinutes / 1440.0 ); // 24h * 60' = 1440'
	// subtract days from minutes to get the day time
	simMinutes = simMinutes - ( day * 1440.0 );

	// add time base data representing the starting point of the simulation
	year += getTimeBase().year;
	month += getTimeBase().month;
	day += getTimeBase().day;
	simMinutes += dayTimeOffset;

	// increase number of days if offset addition put minutes over 1440
	while( simMinutes > 1440.0 )
	{
		++day;
		simMinutes -= 1440.0;
	}

	// determine month and year
	while( day > daysOfMonth( month, year ) )
	{
		day -= daysOfMonth( month, year );
		++month;

		if( month > 12 )
		{
			month = 1;
			++year;
		}
	}

	// compute the day time in hours, minutes, seconds and milliseconds
	hour = (unsigned short) floor( simMinutes / 60.0 );
	simMinutes -= hour * 60.0;

	// get minutes integer value
	min = (unsigned short) floor( simMinutes );

	// get seconds with milliseconds as double value
	sec = ( simMinutes - min ) * 60.0;
}

} } } // namespace odemx::data::output
