/*
    PIP - Platform Independent Primitives
    Timer
    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/>.
*/

#include "pidatetime.h"

#include "piincludes_p.h"

#include <ctime>
#ifdef ARDUINO
#  include <Arduino.h>
#endif
#ifdef MAC_OS
// #  include <mach/mach_traps.h>
// #  include <mach/mach.h>
#  include <mach/clock.h>
// #  include <crt_externs.h>
#endif
#ifdef MICRO_PIP
#  include <sys/time.h>
#endif

//! \addtogroup Types
//! \{
//!
//! \~\class PITime pidatetime.h
//!
//! \~\class PIDate pidatetime.h
//!
//! \~\class PIDateTime pidatetime.h
//!
//! \}


PIPair<PIString, PIChar> extractComponent(PIString & str) {
	static PIString valid = "zsmhdMy";
	PIPair<PIString, PIChar> ret;
	if (str.isEmpty()) return ret;
	int ind = str.findAny(valid);
	if (ind < 0) return ret;
	ret.first  = str.takeLeft(ind);
	ret.second = str.takeLeft(1)[0];
	while (str.isNotEmpty()) {
		if (str[0] == ret.second)
			str.cutLeft(1);
		else
			break;
	}
	return ret;
}
PIString extractInteger(PIString & str) {
	PIString ret;
	while (str.isNotEmpty()) {
		if (str[0].isDigit())
			ret += str.takeLeft(1);
		else
			break;
	}
	return ret;
}


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;
}


PISystemTime PITime::toSystemTime() const {
	return PISystemTime((hours * 60. + minutes) * 60. + seconds, milliseconds * 1000.);
}


PITime PITime::fromString(PIString string, PIString format) {
	PITime ret;
	for (;;) {
		auto comp = extractComponent(format);
		if (comp.second == PIChar()) break;
		if (comp.first.isNotEmpty()) {
			if (!string.startsWith(comp.first)) break;
			string.cutLeft(comp.first.size_s());
		}
		int cv = extractInteger(string).toInt();
		switch (comp.second.toAscii()) {
		case 'z': ret.milliseconds = cv; break;
		case 's': ret.seconds = cv; break;
		case 'm': ret.minutes = cv; break;
		case 'h': ret.hours = cv; break;
		default: break;
		}
	}
	return ret;
}


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;
}


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;
}


PIDate PIDate::fromString(PIString string, PIString format) {
	PIDate ret;
	for (;;) {
		auto comp = extractComponent(format);
		if (comp.second == PIChar()) break;
		if (comp.first.isNotEmpty()) {
			if (!string.startsWith(comp.first)) break;
			string.cutLeft(comp.first.size_s());
		}
		int cv = extractInteger(string).toInt();
		switch (comp.second.toAscii()) {
		case 'd': ret.day = cv; break;
		case 'M': ret.month = cv; break;
		case 'y': ret.year = cv; break;
		default: break;
		}
	}
	return ret;
}


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;
}


time_t PIDateTime::toSecondSinceEpoch() const {
	tm pt;
	piZeroMemory(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::fromString(PIString string, PIString format) {
	PIDateTime ret;
	for (;;) {
		auto comp = extractComponent(format);
		if (comp.second == PIChar()) break;
		if (comp.first.isNotEmpty()) {
			if (!string.startsWith(comp.first)) break;
			string.cutLeft(comp.first.size_s());
		}
		int cv = extractInteger(string).toInt();
		switch (comp.second.toAscii()) {
		case 'z': ret.milliseconds = cv; break;
		case 's': ret.seconds = cv; break;
		case 'm': ret.minutes = cv; break;
		case 'h': ret.hours = cv; break;
		case 'd': ret.day = cv; break;
		case 'M': ret.month = cv; break;
		case 'y': ret.year = cv; break;
		default: break;
		}
	}
	return ret;
}


PISystemTime PIDateTime::toSystemTime() const {
	return PISystemTime(piMaxi(0, int(toSecondSinceEpoch())), milliseconds * 1000000);
}


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;
}


PICout operator<<(PICout s, const PITime & v) {
	s.space();
	s.saveAndSetControls(0);
	s << "PITime(" << v.hours << ":";
	s << PIString::fromNumber(v.minutes).expandLeftTo(2, '0') << ":";
	s << PIString::fromNumber(v.seconds).expandLeftTo(2, '0') << ":";
	s << PIString::fromNumber(v.milliseconds).expandLeftTo(3, '0') << ")";
	s.restoreControls();
	return s;
}


PICout operator<<(PICout s, const PIDate & v) {
	s.space();
	s.saveAndSetControls(0);
	s << "PIDate(" << v.day << "-";
	s << PIString::fromNumber(v.month).expandLeftTo(2, '0') << "-";
	s << v.year << ")";
	s.restoreControls();
	return s;
}


PICout operator<<(PICout s, const PIDateTime & v) {
	s.space();
	s.saveAndSetControls(0);
	s << "PIDateTime(";
	s << v.day << "-";
	s << PIString::fromNumber(v.month).expandLeftTo(2, '0') << "-";
	s << v.year << " ";
	s << v.hours << ":";
	s << PIString::fromNumber(v.minutes).expandLeftTo(2, '0') << ":";
	s << PIString::fromNumber(v.seconds).expandLeftTo(2, '0') << ":";
	s << PIString::fromNumber(v.milliseconds).expandLeftTo(3, '0') << ")";
	s.restoreControls();
	return s;
}
