/*
								+----------------------------------+
								|                                  |
								|  ***  Long time-span timer  ***  |
								|                                  |
								|   Copyright © -tHE SWINe- 2008   |
								|                                  |
								|          LongTimer.cpp           |
								|                                  |
								+----------------------------------+
*/

/**
 *	@file LongTimer.cpp
 *	@author -tHE SWINe-
 *	@date 2008
 *	@brief long period timer
 */

#include "NewFix.h"

#include "CallStack.h"
#include <math.h>
#include <limits.h>
#include "Timer.h"
#include "LongTimer.h"
#include "Integer.h"
#include "MinMax.h"

/*
 *								=== CLongTimer ===
 */

const int CLongTimer::n_counter_num = 4; // minutes, hours, days, years
const int CLongTimer::p_counter_mod_list[] = {60, 24, 365, INT_MAX};

/*
 *	CLongTimer::CLongTimer()
 *		- default constructor; resets all counters
 */
CLongTimer::CLongTimer()
{
	ResetTimer();
}

/*
 *	void CLongTimer::ResetTimer()
 *		- resets all counters
 */
void CLongTimer::ResetTimer()
{
	for(int i = 0; i < n_counter_num; ++ i)
		p_counter_list[i] = 0;
	f_add_seconds = 0;
	timer.ResetTimer();
}

float CLongTimer::f_Speed() const
{
	return m_f_speed;
}

void CLongTimer::Set_Speed(float f_speed)
{
	_ASSERTE(f_speed >= 0);
	m_f_speed = max(0, f_speed);
}

/*
 *	bool CLongTimer::TimeSample() const
 *		- updates timer counters
 *		- note this should be called from time to time (minute basis to hour basis)
 *		  so internal timer (counting seconds in double) wouldn't lose precission
 *		- returns true on success, false on failure (year counter overflow,
 *		  it would take INT_MAX + 1 years)
 */
bool CLongTimer::TimeSample() const
{
	double f_time = timer.f_Time() * m_f_speed;
	if(f_time + f_add_seconds > 60) {
		// do not keep timer counting more than a minute
		// to avoid decreased double precision on high numbers

		timer.ResetTimer();
		f_add_seconds += f_time;
		// add seconds and reset timer

		double f_add_quantity = 0;
		int n_add_quantity = 0;
		if(f_add_seconds / 60 <= INT_MAX) {
			n_add_quantity = int(f_add_seconds / 60);
			f_add_seconds -= n_add_quantity * 60.0; // double won't overflow
		} else {
			f_add_quantity = floor(f_add_seconds / 60);
			f_add_seconds -= f_add_quantity * 60;
			// too long update interval; int would overflow
			// use less precise double (rounding error is possible)
		}

		for(int i = 0; i < n_counter_num; ++ i) {
			int n_counter_modulo = p_counter_mod_list[i];
			int &n_counter_value = p_counter_list[i];
			if(n_add_quantity) {
				_ASSERTE(f_add_quantity == 0);
				if(n_counter_value <= INT_MAX - n_add_quantity) {
					n_counter_value = n_counter_value + n_add_quantity;
					n_add_quantity = n_counter_value / n_counter_modulo;
					n_counter_value %= n_counter_modulo;
					// carry out in integer precission (preferred)
				} else {
					double f_counter_value = double(n_counter_value) + n_add_quantity;
					if(f_counter_value / n_counter_modulo <= INT_MAX) {
						n_add_quantity = int(f_counter_value / n_counter_modulo);
					} else {
						n_add_quantity = 0;
						f_add_quantity = floor(f_counter_value / n_counter_modulo);
						// will carry out the next step in double precission
					}
					n_counter_value = int(fmod(f_counter_value, n_counter_modulo));
				}
			} else {
				if(f_add_quantity == 0)
					break;
				// nothing to add

				double f_counter_value = double(n_counter_value) + f_add_quantity;
				if(f_counter_value / n_counter_modulo <= INT_MAX) {
					n_add_quantity = int(f_counter_value / n_counter_modulo);
					f_add_quantity = 0;
					// will carry out the next step in integer precission (preferred)
				} else
					f_add_quantity = floor(f_counter_value / n_counter_modulo);
				n_counter_value = int(fmod(f_counter_value, n_counter_modulo));
			}
		}
		// update next counters

		if(f_add_quantity > 0 || n_add_quantity > 0)
			return false;
		// last counter overflowed
	}

	return true;
}

/*
 *	double CLongTimer::f_Seconds() const
 *		- returns seconds part
 *		- note this doesn't invoke TimeSample(),
 *		  it returns time after it's last invokation
 */
double CLongTimer::f_Seconds() const
{
	return f_add_seconds + timer.f_Time() * m_f_speed;
}

/*
 *	int CLongTimer::n_Minutes() const
 *		- returns minutes part
 *		- note this doesn't invoke TimeSample(),
 *		  it returns time after it's last invokation
 */
int CLongTimer::n_Minutes() const
{
	return p_counter_list[0];
}

/*
 *	int CLongTimer::n_Hours() const
 *		- returns hours part
 *		- note this doesn't invoke TimeSample(),
 *		  it returns time after it's last invokation
 */
int CLongTimer::n_Hours() const
{
	return p_counter_list[1];
}

/*
 *	int CLongTimer::n_Days() const
 *		- returns days in week (0 - 6)
 *		- note this doesn't invoke TimeSample(),
 *		  it returns time after it's last invokation
 */
int CLongTimer::n_Days() const
{
	return (p_counter_list[2] % 30) % 7;
}

/*
 *	int CLongTimer::n_DaysMonth() const
 *		- returns days in month (0 - 29)
 *		- note this doesn't invoke TimeSample(),
 *		  it returns time after it's last invokation
 *		- note when using month days, time is seconds + minutes +
 *		  hours + month-days + months + years (weeks are not counted)
 */
int CLongTimer::n_DaysMonth() const
{
	return p_counter_list[2] % 30;
}

/*
 *	int CLongTimer::n_DaysYear() const
 *		- returns days in year (0 - 364)
 *		- note this doesn't invoke TimeSample(),
 *		  it returns time after it's last invokation
 *		- note when using year days, time is seconds + minutes +
 *		  hours + year-days + years (weeks and months are not counted)
 */
int CLongTimer::n_DaysYear() const
{
	return p_counter_list[2];
}

/*
 *	int CLongTimer::n_Weeks() const
 *		- returns weeks part (0 - 4; month is 4 weeks + 2 days)
 *		- note this doesn't invoke TimeSample(),
 *		  it returns time after it's last invokation
 */
int CLongTimer::n_Weeks() const
{
	return (p_counter_list[2] % 30) / 7;
}

/*
 *	int CLongTimer::n_Months() const
 *		- returns months part (0 - 12; year is 12 months + 5 days)
 *		- note month is normalized to be 30 days here
 *		- note this doesn't invoke TimeSample(),
 *		  it returns time after it's last invokation
 */
int CLongTimer::n_Months() const
{
	return p_counter_list[2] / 30;
}

/*
 *	int CLongTimer::n_Years() const
 *		- returns years part
 *		- note this doesn't invoke TimeSample(),
 *		  it returns time after it's last invokation
 */
int CLongTimer::n_Years() const
{
	return p_counter_list[3];
}

/*
 *								=== ~CLongTimer ===
 */
