//! \~\file pisystemtime.h
//! \~\ingroup Types
//! \~\brief
//! \~english System time structs and methods
//! \~russian Типы и методы системного времени
/*
    PIP - Platform Independent Primitives
    Time structs
    Ivan Pelipenko peri4ko@yandex.ru

    This program 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 3 of the License, or
    (at your option) any later version.

    This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PISYSTEMTIME_H
#define PISYSTEMTIME_H


#include "pistring.h"


//! \~\ingroup Types
//! \~\brief
//! \~english System time with nanosecond precision.
//! \~russian Системное время с точностью до наносекунд.
class PIP_EXPORT PISystemTime {
public:
	//! \~english Contructs time with seconds and nanoseconds = 0
	//! \~russian Создает время с секундами и наносекундами = 0
	PISystemTime() { seconds = nanoseconds = 0; }

	//! \~english Contructs time with "s" seconds and "ns" nanoseconds
	//! \~russian Создает время с секундами "s" и наносекундами "ns"
	PISystemTime(int s, int ns) {
		seconds     = s;
		nanoseconds = ns;
		checkOverflows();
	}

	//! \~english Contructs time with "s" seconds and "ns" nanoseconds
	//! \~russian Создает время с секундами "s" и наносекундами "ns"
	PISystemTime(std::pair<int, int> s_ns) {
		seconds     = s_ns.first;
		nanoseconds = s_ns.second;
		checkOverflows();
	}


	//! \~\ingroup Types
	//! \~\brief
	//! \~english Frequency type.
	//! \~russian Тип частоты.
	class PIP_EXPORT Frequency {
	public:
		//! \~english Contructs null frequency
		//! \~russian Создает нулевую частоту
		Frequency() {}

		//! \~english Returns if frequency is null
		//! \~russian Возвращает нулевая ли частота
		bool isNull() const { return value_hz == 0.; }

		//! \~english Returns frequency as hertz
		//! \~russian Возвращает частоту в герцах
		double toHertz() const { return value_hz; }

		//! \~english Returns frequency as kilohertz
		//! \~russian Возвращает частоту в килогерцах
		double toKHertz() const { return value_hz / 1E+3; }

		//! \~english Returns frequency as megahertz
		//! \~russian Возвращает частоту в мегагерцах
		double toMHertz() const { return value_hz / 1E+6; }

		//! \~english Returns frequency as gigahertz
		//! \~russian Возвращает частоту в гигагерцах
		double toGHertz() const { return value_hz / 1E+9; }

		//! \~english Returns frequency as \a PISystemTime time interval
		//! \~russian Возвращает частоту как \a PISystemTime временной интервал
		PISystemTime toSystemTime() const;

		//! \~english Returns frequency as \a PISystemTime time interval
		//! \~russian Возвращает частоту как \a PISystemTime временной интервал
		operator PISystemTime() const { return toSystemTime(); }

		//! \~english Returns \a Frequency contains "hz" hertz
		//! \~russian Возвращает \a Frequency содержащую "hz" герц
		static Frequency fromHertz(double hz) { return Frequency(hz); }

		//! \~english Returns \a Frequency contains "khz" kilohertz
		//! \~russian Возвращает \a Frequency содержащую "khz" килогерц
		static Frequency fromKHertz(double khz) { return Frequency(khz * 1E+3); }

		//! \~english Returns \a Frequency contains "mhz" megahertz
		//! \~russian Возвращает \a Frequency содержащую "mhz" мегагерц
		static Frequency fromMHertz(double mhz) { return Frequency(mhz * 1E+6); }

		//! \~english Returns \a Frequency contains "ghz" gigahertz
		//! \~russian Возвращает \a Frequency содержащую "ghz" гигагерц
		static Frequency fromGHertz(double ghz) { return Frequency(ghz * 1E+9); }

		//! \~english Returns \a Frequency that corresponds "st" time interval
		//! \~russian Возвращает \a Frequency соответствующую временному интервалу "st"
		static Frequency fromSystemTime(const PISystemTime & st);

		//! \~english Returns sum between this frequency and "f"
		//! \~russian Возвращает сумму этой частоты с "f"
		Frequency operator+(const Frequency & f) const { return Frequency(value_hz + f.value_hz); }

		//! \~english Returns difference between this frequency and "f"
		//! \~russian Возвращает разницу этой частоты с "f"
		Frequency operator-(const Frequency & f) const { return Frequency(value_hz - f.value_hz); }

		//! \~english Returns multiplication between this frequency and "v"
		//! \~russian Возвращает эту частоту умноженную на "v"
		Frequency operator*(const double & v) const { return Frequency(value_hz * v); }

		//! \~english Returns division between this frequency and "v"
		//! \~russian Возвращает эту частоту поделённую на "v"
		Frequency operator/(const double & v) const { return Frequency(value_hz / v); }

		//! \~english Multiply frequency by "v"
		//! \~russian Умножает частоту на "v"
		Frequency & operator*=(const double & v) {
			value_hz *= v;
			return *this;
		}

		//! \~english Divide frequency by "v"
		//! \~russian Делит частоту на "v"
		Frequency & operator/=(const double & v) {
			value_hz /= v;
			return *this;
		}

		//! \~english Add to frequency "f"
		//! \~russian Добавляет к частоте "f"
		Frequency & operator+=(const Frequency & f) {
			value_hz += f.value_hz;
			return *this;
		}

		//! \~english Subtract from frequency "f"
		//! \~russian Вычитает из частоты "f"
		Frequency & operator-=(const Frequency & f) {
			value_hz -= f.value_hz;
			return *this;
		}

		//! \~english Compare operator
		//! \~russian Оператор сравнения
		bool operator==(const Frequency & f) const { return value_hz == f.value_hz; }

		//! \~english Compare operator
		//! \~russian Оператор сравнения
		bool operator!=(const Frequency & f) const { return value_hz != f.value_hz; }

		//! \~english Compare operator
		//! \~russian Оператор сравнения
		bool operator>(const Frequency & f) const { return value_hz > f.value_hz; }

		//! \~english Compare operator
		//! \~russian Оператор сравнения
		bool operator<(const Frequency & f) const { return value_hz < f.value_hz; }

		//! \~english Compare operator
		//! \~russian Оператор сравнения
		bool operator>=(const Frequency & f) const { return value_hz >= f.value_hz; }

		//! \~english Compare operator
		//! \~russian Оператор сравнения
		bool operator<=(const Frequency & f) const { return value_hz <= f.value_hz; }

	private:
		Frequency(double hz): value_hz(hz) {}

		double value_hz = 0.;
	};


	//! \~english Returns if time is null
	//! \~russian Возвращает нулевое ли время
	bool isNull() const { return (seconds == 0) && (nanoseconds == 0); }

	//! \~english Returns if time is not null
	//! \~russian Возвращает не нулевое ли время
	bool isNotNull() const { return !isNull(); }

	//! \~english Returns if time is greater than 0
	//! \~russian Возвращает больше ли 0 время
	bool isPositive() const { return (seconds >= 0) && (nanoseconds >= 0); }

	//! \~english Returns if time is lesser than 0
	//! \~russian Возвращает меньше ли 0 время
	bool isNegative() const { return (seconds < 0) || (nanoseconds < 0); }

	//! \~english Returns time value in seconds
	//! \~russian Возвращает значение времени в секундах
	double toSeconds() const { return double(seconds) + nanoseconds / 1.e+9; }

	//! \~english Returns time value in milliseconds
	//! \~russian Возвращает значение времени в миллисекундах
	double toMilliseconds() const { return seconds * 1.e+3 + nanoseconds / 1.e+6; }

	//! \~english  Returns time value in microseconds
	//! \~russian Возвращает значение времени в микросекундах
	double toMicroseconds() const { return seconds * 1.e+6 + nanoseconds / 1.e+3; }

	//! \~english Returns time value in nanoseconds
	//! \~russian Возвращает значение времени в наносекундах
	double toNanoseconds() const { return seconds * 1.e+9 + double(nanoseconds); }


	//! \~english  Add to time "v" seconds
	//! \~russian Добавляет ко времени "v" секунд
	PISystemTime & addSeconds(double v) {
		*this += fromSeconds(v);
		return *this;
	}

	//! \~english Add to time "v" milliseconds
	//! \~russian Добавляет ко времени "v" миллисекунд
	PISystemTime & addMilliseconds(double v) {
		*this += fromMilliseconds(v);
		return *this;
	}

	//! \~english Add to time "v" microseconds
	//! \~russian Добавляет ко времени "v" микросекунд
	PISystemTime & addMicroseconds(double v) {
		*this += fromMicroseconds(v);
		return *this;
	}

	//! \~english Add to time "v" nanoseconds
	//! \~russian Добавляет ко времени "v" наносекунд
	PISystemTime & addNanoseconds(double v) {
		*this += fromNanoseconds(v);
		return *this;
	}


	//! \~english Sleep for this time
	//! \~russian Ожидать это время
	void sleep() const;

	//! \~english On *nix system assign current value to timespec struct
	//! \~russian На *nix системах присваивает время к timespec структуре
	void toTimespec(void * ts);

	//! \~english Returns \a Frequency that corresponds this time interval
	//! \~russian Возвращает \a Frequency соответствующую этому временному интервалу
	PISystemTime::Frequency toFrequency() const;

	//! \~english Returns "yyyy-MM-dd hh:mm:ss.zzz" for absolute time and "<V> <d|h|m|s|ms|us|ns> ..." for relative
	//! \~russian Возвращает "yyyy-MM-dd hh:mm:ss.zzz" для абсолютного времени и "<V> <d|h|m|s|ms|us|ns> ..." для относительного
	PIString toString() const;

	//! \~english Returns copy of this time with absolutely values of s and ns
	//! \~russian Возвращает копию времени с модулем значения
	PISystemTime abs() const;

	//! \~english Returns sum of this time with "t"
	//! \~russian Возвращает сумму этого времени с "t"
	PISystemTime operator+(const PISystemTime & t) const {
		PISystemTime tt(*this);
		tt.seconds += t.seconds;
		tt.nanoseconds += t.nanoseconds;
		tt.checkOverflows();
		return tt;
	}

	//! \~english Returns difference between this time and "t"
	//! \~russian Возвращает разницу между этим временем и "t"
	PISystemTime operator-(const PISystemTime & t) const {
		PISystemTime tt(*this);
		tt.seconds -= t.seconds;
		tt.nanoseconds -= t.nanoseconds;
		tt.checkOverflows();
		return tt;
	}

	//! \~english Returns multiplication between this time and "t"
	//! \~russian Возвращает это время умноженное на "t"
	PISystemTime operator*(const double & v) const { return fromMilliseconds(toMilliseconds() * v); }

	//! \~english Returns division between this time and "t"
	//! \~russian Возвращает это время поделённое на "t"
	PISystemTime operator/(const double & v) const { return fromMilliseconds(toMilliseconds() / v); }

	//! \~english Add to time "t"
	//! \~russian Добавляет ко времени "t"
	PISystemTime & operator+=(const PISystemTime & t) {
		seconds += t.seconds;
		nanoseconds += t.nanoseconds;
		checkOverflows();
		return *this;
	}

	//! \~english Subtract from time "t"
	//! \~russian Вычитает из времени "t"
	PISystemTime & operator-=(const PISystemTime & t) {
		seconds -= t.seconds;
		nanoseconds -= t.nanoseconds;
		checkOverflows();
		return *this;
	}

	//! \~english Multiply time by "v"
	//! \~russian Умножает время на "v"
	PISystemTime & operator*=(const double & v) {
		*this = fromMilliseconds(toMilliseconds() * v);
		return *this;
	}

	//! \~english Divide time by "v"
	//! \~russian Делит время на "v"
	PISystemTime & operator/=(const double & v) {
		*this = fromMilliseconds(toMilliseconds() / v);
		return *this;
	}


	//! \~english Compare operator
	//! \~russian Оператор сравнения
	bool operator==(const PISystemTime & t) const { return ((seconds == t.seconds) && (nanoseconds == t.nanoseconds)); }

	//! \~english Compare operator
	//! \~russian Оператор сравнения
	bool operator!=(const PISystemTime & t) const { return ((seconds != t.seconds) || (nanoseconds != t.nanoseconds)); }

	//! \~english Compare operator
	//! \~russian Оператор сравнения
	bool operator>(const PISystemTime & t) const {
		if (seconds == t.seconds) return nanoseconds > t.nanoseconds;
		return seconds > t.seconds;
	}

	//! \~english Compare operator
	//! \~russian Оператор сравнения
	bool operator<(const PISystemTime & t) const {
		if (seconds == t.seconds) return nanoseconds < t.nanoseconds;
		return seconds < t.seconds;
	}

	//! \~english Compare operator
	//! \~russian Оператор сравнения
	bool operator>=(const PISystemTime & t) const {
		if (seconds == t.seconds) return nanoseconds >= t.nanoseconds;
		return seconds >= t.seconds;
	}

	//! \~english Compare operator
	//! \~russian Оператор сравнения
	bool operator<=(const PISystemTime & t) const {
		if (seconds == t.seconds) return nanoseconds <= t.nanoseconds;
		return seconds <= t.seconds;
	}


	//! \~english Contructs time from seconds "v"
	//! \~russian Создает время из "v" секунд
	static PISystemTime fromSeconds(double v) {
		int s = piFloord(v);
		return PISystemTime(s, int((v - s) * 1000000000.));
	}

	//! \~english Contructs time from milliseconds "v"
	//! \~russian Создает время из "v" миллисекунд
	static PISystemTime fromMilliseconds(double v) {
		int s = piFloord(v / 1000.);
		return PISystemTime(s, int((v / 1000. - s) * 1000000000.));
	}

	//! \~english Contructs time from microseconds "v"
	//! \~russian Создает время из "v" микросекунд
	static PISystemTime fromMicroseconds(double v) {
		int s = piFloord(v / 1000000.);
		return PISystemTime(s, int((v / 1000000. - s) * 1000000000.));
	}

	//! \~english Contructs time from nanoseconds "v"
	//! \~russian Создает время из "v" наносекунд
	static PISystemTime fromNanoseconds(double v) {
		int s = piFloord(v / 1000000000.);
		return PISystemTime(s, int((v / 1000000000. - s) * 1000000000.));
	}

	//! \~english Contructs time from string "s" ("yyyy-MM-dd hh:mm:ss.zzz" or free form)
	//! \~russian Создает время из строки "s" ("yyyy-MM-dd hh:mm:ss.zzz", либо произвольная форма)
	static PISystemTime fromString(PIString s);

	//! \~english Returns current system time
	//! \~russian Возвращает текущее системное время
	static PISystemTime current(bool precise_but_not_system = false);

	//! \~english Seconds time part
	//! \~russian Секунды времени
	int seconds;

	//! \~english Nanoseconds time part
	//! \~russian Наносекунды времени
	int nanoseconds;

private:
	void checkOverflows() {
		while (nanoseconds >= 1000000000) {
			nanoseconds -= 1000000000;
			seconds++;
		}
		while (nanoseconds < 0) {
			nanoseconds += 1000000000;
			seconds--;
		}
	}
};

//! \relatesalso PICout
//! \~english \brief Output operator to PICout
//! \~russian \brief Оператор вывода в PICout
inline PICout operator<<(PICout s, const PISystemTime & v) {
	s.space();
	s.saveAndSetControls(0);
	s << "(" << v.seconds << " s, " << v.nanoseconds << " ns)";
	s.restoreControls();
	return s;
}

//! \relatesalso PICout
//! \~english \brief Output operator to PICout
//! \~russian \brief Оператор вывода в PICout
inline PICout operator<<(PICout s, const PISystemTime::Frequency & f) {
	s.space();
	s.saveAndSetControls(0);
	s << f.toHertz() << " Hz";
	s.restoreControls();
	return s;
}


//! \~\ingroup Types
//! \~\brief
//! \~english Time measurements.
//! \~russian Измерение времени.
class PIP_EXPORT PITimeMeasurer {
public:
	PITimeMeasurer();

	//! \~\brief
	//! \~english Set time mark to current system time.
	//! \~russian Устанавливает отметку времени на текущее время.
	//! \~\details
	//! \~english
	//! This function used for set start time mark. Later
	//! you can find out elapsed time from this time mark to any
	//! moment of time with \a elapsed(), \a elapsed_s(),
	//! \a elapsed_m(), \a elapsed_u(), \a elapsed_n().
	//! \~russian
	//! Этот метод используется для установки начальной отметки времени.
	//! Далее можно узнать прошедшее от этой метки время с помощью методов
	//! \a elapsed(), \a elapsed_s(), \a elapsed_m(),
	//! \a elapsed_u(), \a elapsed_n().
	//! \~\a elapsed_u() or \a elapsed_n() functions.
	//! \~\sa \a elapsed(), \a elapsed_s(), \a elapsed_m(), \a elapsed_u(), \a elapsed_n() */
	void reset() { t_st = PISystemTime::current(true); }

	//! \~\brief
	//! \~english Returns nanoseconds elapsed from last \a reset() execution or from timer measurer creation.
	//! \~russian Возвращает в наносекундах время, прошедшее с последнего вызова \a reset(), либо создания измерителя.
	double elapsed_n() const;

	//! \~\brief
	//! \~english Returns microseconds elapsed from last \a reset() execution or from timer measurer creation.
	//! \~russian Возвращает в микросекундах время, прошедшее с последнего вызова \a reset(), либо создания измерителя.
	double elapsed_u() const;

	//! \~\brief
	//! \~english Returns milliseconds elapsed from last \a reset() execution or from timer measurer creation.
	//! \~russian Возвращает в милисекундах время, прошедшее с последнего вызова \a reset(), либо создания измерителя.
	double elapsed_m() const;

	//! \~\brief
	//! \~english Returns seconds elapsed from last \a reset() execution or from timer measurer creation.
	//! \~russian Возвращает в секундах время, прошедшее с последнего вызова \a reset(), либо создания измерителя.
	double elapsed_s() const;

	//! \~\brief
	//! \~english Returns PISystemTime elapsed from last \a reset() execution or from timer measurer creation.
	//! \~russian Возвращает в PISystemTime время, прошедшее с последнего вызова \a reset(), либо создания измерителя.
	PISystemTime elapsed() const;

	//! \~\brief
	//! \~english Returns PISystemTime elapsed from last \a reset() execution or from timer measurer creation and call \a reset().
	//! \~russian Возвращает в PISystemTime время, прошедшее с последнего вызова \a reset(), либо создания измерителя, и вызывает \a
	//! reset().
	PISystemTime elapsedAndReset();

	//! \~\brief
	//! \~english Returns time mark of last \a reset() execution or timer measurer creation.
	//! \~russian Возвращает отметку времени последнего вызова \a reset(), либо создания измерителя.
	PISystemTime reset_time() DEPRECATEDM("use PISystemTime::resetTime()") { return t_st; }

	//! \~\brief
	//! \~english Returns time mark of last \a reset() execution or timer measurer creation.
	//! \~russian Возвращает отметку времени последнего вызова \a reset(), либо создания измерителя.
	PISystemTime resetTime() const { return t_st; }

private:
	PISystemTime t_st;
};

#endif // PITIME_H
