/*
    PIP - Platform Independent Primitives
    Timer
    Copyright (C) 2016  Ivan Pelipenko peri4ko@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "pitime.h"
#include "pisystemtests.h"
#ifdef WINDOWS
   extern FILETIME __pi_ftjan1970;
   long long __PIQueryPerformanceCounter() {LARGE_INTEGER li; QueryPerformanceCounter(&li); return li.QuadPart;}
#endif
#ifdef MAC_OS
//#  include <mach/mach_traps.h>
//#  include <mach/mach.h>
#  include <mach/clock.h>
//#  include <crt_externs.h>
   extern clock_serv_t __pi_mac_clock;
#endif


/*! \class PISystemTime
 * \brief System time
 * 
 * \section PISystemTime_sec0 Synopsis
 * This class provide arithmetic functions for POSIX system time.
 * This time represents as seconds and nanosecons in integer formats.
 * You can take current system time with function \a PISystemTime::current(),
 * compare times, sum or subtract two times, convert time to/from
 * seconds, milliseconds, microseconds or nanoseconds.
 * \section PISystemTime_sec1 Example
 * \snippet pitimer.cpp system_time
*/


/*! \class PITimeMeasurer
 * \brief Time measurements
 * 
 * \section PITimeMeasurer_sec0 Synopsis
 * Function \a reset() set time mark to current
 * system time, then functions double elapsed_*() returns time elapsed from this mark.
 * These functions can returns nano-, micro-, milli- and seconds with suffixes "n", "u", "m"
 * and "s"
*/


void piUSleep(int usecs) {
	if (usecs <= 0) return;
#ifdef WINDOWS
	if (usecs > 0) Sleep(usecs / 1000);
#else
	usecs -= PISystemTests::usleep_offset_us;
	if (usecs > 0) usleep(usecs);
#endif
}


bool operator ==(const PITime & t0, const PITime & t1) {
	return (t0.hours == t1.hours && t0.minutes == t1.minutes && t0.seconds == t1.seconds);
}


bool operator <(const PITime & t0, const PITime & t1) {
	if (t0.hours == t1.hours) {
		if (t0.minutes == t1.minutes) {
			return t0.seconds < t1.seconds;
		} else return t0.minutes < t1.minutes;
	} else return t0.hours < t1.hours;
}


bool operator >(const PITime & t0, const PITime & t1) {
	if (t0.hours == t1.hours) {
		if (t0.minutes == t1.minutes) {
			return t0.seconds > t1.seconds;
		} else return t0.minutes > t1.minutes;
	} else return t0.hours > t1.hours;
}

bool operator ==(const PIDate & t0, const PIDate & t1) {
	return (t0.year == t1.year && t0.month == t1.month && t0.day == t1.day);
}


bool operator <(const PIDate & t0, const PIDate & t1) {
	if (t0.year == t1.year) {
		if (t0.month == t1.month) {
			return t0.day < t1.day;
		} else return t0.month < t1.month;
	} else return t0.year < t1.year;
}


bool operator >(const PIDate & t0, const PIDate & t1) {
	if (t0.year == t1.year) {
		if (t0.month == t1.month) {
			return t0.day > t1.day;
		} else return t0.month > t1.month;
	} else return t0.year > t1.year;
}

bool operator ==(const PIDateTime & t0, const PIDateTime & t1) {
	return (t0.year == t1.year && t0.month == t1.month && t0.day == t1.day &&
			t0.hours == t1.hours && t0.minutes == t1.minutes && t0.seconds == t1.seconds);
}


bool operator <(const PIDateTime & t0, const PIDateTime & t1) {
	if (t0.year == t1.year) {
		if (t0.month == t1.month) {
			if (t0.day == t1.day) {
				if (t0.hours == t1.hours) {
					if (t0.minutes == t1.minutes) {
						return t0.seconds < t1.seconds;
					} else return t0.minutes < t1.minutes;
				} else return t0.hours < t1.hours;
			} else return t0.day < t1.day;
		} else return t0.month < t1.month;
	} else return t0.year < t1.year;
}


bool operator >(const PIDateTime & t0, const PIDateTime & t1) {
	if (t0.year == t1.year) {
		if (t0.month == t1.month) {
			if (t0.day == t1.day) {
				if (t0.hours == t1.hours) {
					if (t0.minutes == t1.minutes) {
						return t0.seconds > t1.seconds;
					} else return t0.minutes > t1.minutes;
				} else return t0.hours > t1.hours;
			} else return t0.day > t1.day;
		} else return t0.month > t1.month;
	} else return t0.year > t1.year;
}


PITime PITime::current() {
	time_t rt = ::time(0);
	tm * pt = localtime(&rt);
	PITime t;
	t.seconds = pt->tm_sec;
	t.minutes = pt->tm_min;
	t.hours = pt->tm_hour;
	return t;
}


PITime PITime::fromSystemTime(const PISystemTime & st) {
	double s = st.toSeconds();
	int v = s;
	PITime ret;
	ret.milliseconds = (s - v) * 1000;
	ret.seconds = v % 60; v = (v - ret.seconds) / 60;
	ret.minutes = v % 60; v = (v - ret.minutes) / 60;
	ret.hours = v;
	return ret;
}


PIDate PIDate::current() {
	time_t rt = ::time(0);
	tm * pt = localtime(&rt);
	PIDate d;
	d.day = pt->tm_mday;
	d.month = pt->tm_mon + 1;
	d.year = pt->tm_year + 1900;
	return d;
}


PIDateTime PIDateTime::current() {
	time_t rt = ::time(0);
	tm * pt = localtime(&rt);
	PIDateTime dt;
	dt.milliseconds = 0;
	dt.seconds = pt->tm_sec;
	dt.minutes = pt->tm_min;
	dt.hours = pt->tm_hour;
	dt.day = pt->tm_mday;
	dt.month = pt->tm_mon + 1;
	dt.year = pt->tm_year + 1900;
	return dt;
}


#ifdef WINDOWS
PISystemTime::PISystemTime(const FILETIME & t) {
	ullong lt = ullong(t.dwHighDateTime) * 0x100000000U + ullong(t.dwLowDateTime);
	seconds = lt / 10000000U;
	nanoseconds = (lt % 10000000U) * 100U;
}
#endif


PISystemTime PISystemTime::abs() const {
	if (seconds < 0)
		return PISystemTime(piAbsl(seconds) - 1, 1000000000l - piAbsl(nanoseconds));
	else
		return PISystemTime(piAbsl(seconds), piAbsl(nanoseconds));
}


PISystemTime PISystemTime::current(bool precise_but_not_system) {
#ifdef WINDOWS
	if (precise_but_not_system) {
		llong qpc(0);
		if (__pi_perf_freq > 0) {
			qpc = __PIQueryPerformanceCounter();
			return PISystemTime::fromSeconds(qpc / double(__pi_perf_freq));
		}
		return PISystemTime();
	} else {
		FILETIME ft, sft;
#  if (_WIN32_WINNT >= 0x0602)
		GetSystemTimePreciseAsFileTime(&ft);
#  else
		GetSystemTimeAsFileTime(&ft);
#  endif
		sft.dwHighDateTime = ft.dwHighDateTime - __pi_ftjan1970.dwHighDateTime;
		if (ft.dwLowDateTime < __pi_ftjan1970.dwLowDateTime) {
			sft.dwLowDateTime = ft.dwLowDateTime + (0xFFFFFFFF - __pi_ftjan1970.dwLowDateTime);
			sft.dwHighDateTime++;
		} else
			sft.dwLowDateTime = ft.dwLowDateTime - __pi_ftjan1970.dwLowDateTime;
		ullong lt = ullong(sft.dwHighDateTime) * 0x100000000U + ullong(sft.dwLowDateTime);
		return PISystemTime(lt / 10000000U, (lt % 10000000U) * 100U);
	}
	//long t_cur = GetCurrentTime();
	//return PISystemTime(t_cur / 1000, (t_cur % 1000) * 1000000);
#else
#  ifdef MAC_OS
	mach_timespec_t t_cur;
	clock_get_time(__pi_mac_clock, &t_cur);
#  else
	timespec t_cur;
	clock_gettime(0, &t_cur);
#  endif
	return PISystemTime(t_cur.tv_sec, t_cur.tv_nsec);
#endif
}


PIString PITime::toString(const PIString & format) const {
	PIString ts = format;
	ts.replace("hh", PIString::fromNumber(hours).expandLeftTo(2, '0'));
	ts.replace("h", PIString::fromNumber(hours));
	ts.replace("mm", PIString::fromNumber(minutes).expandLeftTo(2, '0'));
	ts.replace("m", PIString::fromNumber(minutes));
	ts.replace("ss", PIString::fromNumber(seconds).expandLeftTo(2, '0'));
	ts.replace("s", PIString::fromNumber(seconds));
	ts.replace("zzz", PIString::fromNumber(milliseconds).expandLeftTo(3, '0'));
	ts.replace("zz", PIString::fromNumber(milliseconds).expandLeftTo(2, '0'));
	ts.replace("z", PIString::fromNumber(milliseconds));
	return ts;
}


PIString PIDate::toString(const PIString & format) const {
	PIString ts = format;
	ts.replace("yyyy", PIString::fromNumber(year).expandLeftTo(4, '0'));
	ts.replace("yy", PIString::fromNumber(year).right(2));
	ts.replace("y", PIString::fromNumber(year).right(1));
	ts.replace("MM", PIString::fromNumber(month).expandLeftTo(2, '0'));
	ts.replace("M", PIString::fromNumber(month));
	ts.replace("dd", PIString::fromNumber(day).expandLeftTo(2, '0'));
	ts.replace("d", PIString::fromNumber(day));
	return ts;
}


PIString PIDateTime::toString(const PIString & format) const {
	PIString ts = format;
	ts.replace("yyyy", PIString::fromNumber(year).expandLeftTo(4, '0'));
	ts.replace("yy", PIString::fromNumber(year).right(2));
	ts.replace("y", PIString::fromNumber(year).right(1));
	ts.replace("MM", PIString::fromNumber(month).expandLeftTo(2, '0'));
	ts.replace("M", PIString::fromNumber(month));
	ts.replace("dd", PIString::fromNumber(day).expandLeftTo(2, '0'));
	ts.replace("d", PIString::fromNumber(day));
	ts.replace("hh", PIString::fromNumber(hours).expandLeftTo(2, '0'));
	ts.replace("h", PIString::fromNumber(hours));
	ts.replace("mm", PIString::fromNumber(minutes).expandLeftTo(2, '0'));
	ts.replace("m", PIString::fromNumber(minutes));
	ts.replace("ss", PIString::fromNumber(seconds).expandLeftTo(2, '0'));
	ts.replace("s", PIString::fromNumber(seconds));
	ts.replace("zzz", PIString::fromNumber(milliseconds).expandLeftTo(3, '0'));
	ts.replace("zz", PIString::fromNumber(milliseconds).expandLeftTo(2, '0'));
	ts.replace("z", PIString::fromNumber(milliseconds));
	return ts;
}


#ifdef WINDOWS
PIDateTime::PIDateTime(FILETIME t) {
	FILETIME lt;
	SYSTEMTIME st;
	FileTimeToLocalFileTime(&t, &lt);
	FileTimeToSystemTime(&lt, &st);
	year = st.wYear;
	month = st.wMonth;
	day = st.wDay;
	hours = st.wHour;
	minutes = st.wMinute;
	seconds = st.wSecond;
	milliseconds = st.wMilliseconds;
}


FILETIME PIDateTime::toFILETIME() const {
	FILETIME lt, ret;
	SYSTEMTIME st;
	st.wYear = year;
	st.wMonth = month;
	st.wDay = day;
	st.wHour = hours;
	st.wMinute = minutes;
	st.wSecond = seconds;
	st.wMilliseconds = milliseconds;
	SystemTimeToFileTime(&st, &lt);
	LocalFileTimeToFileTime(&lt, &ret);
	return ret;
}
#endif


time_t PIDateTime::toSecondSinceEpoch() const {
	tm pt;
	memset(&pt, 0, sizeof(pt));
	pt.tm_sec = seconds;
	pt.tm_min = minutes;
	pt.tm_hour = hours;
	pt.tm_mday = day;
	pt.tm_mon = month - 1;
#ifdef WINDOWS
	pt.tm_year = piMaxi(year - 1900, 70);
#else
	pt.tm_year = piMaxi(year - 1900, 0);
#endif
	return mktime(&pt);
}


PIDateTime PIDateTime::fromSecondSinceEpoch(const time_t sec) {
	tm * pt = localtime(&sec);
	PIDateTime dt;
	dt.seconds = pt->tm_sec;
	dt.minutes = pt->tm_min;
	dt.hours = pt->tm_hour;
	dt.day = pt->tm_mday;
	dt.month = pt->tm_mon + 1;
	dt.year = pt->tm_year + 1900;
	return dt;

}


PIString time2string(const PITime & time, const PIString & format) {
	PIString ts = format;
	ts.replace("hh", PIString::fromNumber(time.hours).expandLeftTo(2, '0'));
	ts.replace("h", PIString::fromNumber(time.hours));
	ts.replace("mm", PIString::fromNumber(time.minutes).expandLeftTo(2, '0'));
	ts.replace("m", PIString::fromNumber(time.minutes));
	ts.replace("ss", PIString::fromNumber(time.seconds).expandLeftTo(2, '0'));
	ts.replace("s", PIString::fromNumber(time.seconds));
	return ts;
}


PIString date2string(const PIDate & date, const PIString & format) {
	PIString ts = format;
	ts.replace("yyyy", PIString::fromNumber(date.year).expandLeftTo(4, '0'));
	ts.replace("yy", PIString::fromNumber(date.year).right(2));
	ts.replace("y", PIString::fromNumber(date.year).right(1));
	ts.replace("MM", PIString::fromNumber(date.month).expandLeftTo(2, '0'));
	ts.replace("M", PIString::fromNumber(date.month));
	ts.replace("dd", PIString::fromNumber(date.day).expandLeftTo(2, '0'));
	ts.replace("d", PIString::fromNumber(date.day));
	return ts;
}


PIString datetime2string(const PIDateTime & date, const PIString & format) {
	PIString ts = format;
	ts.replace("hh", PIString::fromNumber(date.hours).expandLeftTo(2, '0'));
	ts.replace("h", PIString::fromNumber(date.hours));
	ts.replace("mm", PIString::fromNumber(date.minutes).expandLeftTo(2, '0'));
	ts.replace("m", PIString::fromNumber(date.minutes));
	ts.replace("ss", PIString::fromNumber(date.seconds).expandLeftTo(2, '0'));
	ts.replace("s", PIString::fromNumber(date.seconds));
	ts.replace("yyyy", PIString::fromNumber(date.year).expandLeftTo(4, '0'));
	ts.replace("yy", PIString::fromNumber(date.year).right(2));
	ts.replace("y", PIString::fromNumber(date.year).right(1));
	ts.replace("MM", PIString::fromNumber(date.month).expandLeftTo(2, '0'));
	ts.replace("M", PIString::fromNumber(date.month));
	ts.replace("dd", PIString::fromNumber(date.day).expandLeftTo(2, '0'));
	ts.replace("d", PIString::fromNumber(date.day));
	return ts;
}



PITimeMeasurer::PITimeMeasurer() {
	reset();
}


double PITimeMeasurer::elapsed_n() const {
	return (PISystemTime::current(true) - t_st).toNanoseconds() - PISystemTests::time_elapsed_ns;
}


double PITimeMeasurer::elapsed_u() const {
	return (PISystemTime::current(true) - t_st).toMicroseconds() - PISystemTests::time_elapsed_ns / 1.E+3;
}


double PITimeMeasurer::elapsed_m() const {
	return (PISystemTime::current(true) - t_st).toMilliseconds() - PISystemTests::time_elapsed_ns / 1.E+6;
}


double PITimeMeasurer::elapsed_s() const {
	return (PISystemTime::current(true) - t_st).toSeconds() - PISystemTests::time_elapsed_ns / 1.E+9;
}


PISystemTime PITimeMeasurer::elapsed() const {
	return (PISystemTime::current(true) - t_st);
}
