/**
 * Times.cpp
 *
 * Copyright 2016-2021 Heartland Software Solutions Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the license at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the LIcense is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "times_internal.h"

#ifdef INTEL_COMPILER
#include <mathimf.h>
#else
#include <cmath>
#endif
#include "worldlocation.h"
#include "Times.h"
#include "types.h"
#include <stdarg.h>
#include <string.h>
#include <memory>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <stdexcept>
#include <locale>
#include <ctime>
#include <codecvt>
#include <assert.h>
#include <optional>
#include <inttypes.h>
#include <sys/stat.h>

#include <boost/algorithm/string.hpp>

#ifdef __GNUC__
#include <unistd.h>
#include <algorithm>
#include <fstream>
#endif

#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif

#ifndef PATH_MAX
#ifdef MAX_PATH
#define PATH_MAX MAX_PATH
#else
#define PATH_MAX 250
#endif
#endif

#ifdef _MSC_VER
#define realpath(N, R) _fullpath((R), (N), sizeof(R))
#endif

#define WTIME_1900	(9467107200000000LL)
#define WTIME_1970	(11676096000000000LL)
#define WTIME_2000	(12622780800000000LL)
#define WTIME_2010	(12938400000000000LL)

using namespace HSS_Time;
using namespace HSS_Time_Private;

#if defined(_MSC_VER) && _MSC_VER < 1900
INTNM::int32_t c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
{
	int count = -1;

	if (size != 0)
		count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
	if (count == -1)
		count = _vscprintf(format, ap);

	return count;
}

INTNM::int32_t c99_snprintf(char *outBuf, size_t size, const char *format, ...)
{
	int count;
	va_list ap;

	va_start(ap, format);
	count = c99_vsnprintf(outBuf, size, format, ap);
	va_end(ap);

	return count;
}
#endif

WTimeSpan::WTimeSpan() {
}

WTimeSpan::WTimeSpan(INTNM::int64_t time, bool units_are_seconds) {
	if (units_are_seconds)
		m_timeSpan = time * 1000000LL;
	else
		m_timeSpan = time;
}

WTimeSpan::WTimeSpan(INTNM::int32_t lDays, INTNM::int32_t nHours, INTNM::int32_t nMins, INTNM::int32_t nSecs) {
	m_timeSpan = ((INTNM::int64_t)(nSecs + 60 * (nMins + 60 * (nHours + 24 * lDays)))) * 1000000LL;
}

WTimeSpan::WTimeSpan(INTNM::int32_t lDays, INTNM::int32_t nHours, INTNM::int32_t nMins, const double &nSecs) {
	m_timeSpan = ((INTNM::int64_t)(60 * (nMins + 60 * (nHours + 24 * lDays)))) * 1000000LL + (INTNM::int64_t)(nSecs * 1000000.0);
}

WTimeSpan::WTimeSpan(INTNM::int32_t lDays, INTNM::int32_t nHours, INTNM::int32_t nMins, INTNM::int32_t nSecs, INTNM::int32_t uSecs) {
	m_timeSpan = (INTNM::int64_t)uSecs + ((INTNM::int64_t)(nSecs + 60 * (nMins + 60 * (nHours + 24 * lDays)))) * 1000000LL;
}

WTimeSpan::WTimeSpan(const WTimeSpan &timeSrc) {
	m_timeSpan = timeSrc.m_timeSpan;
}

#if defined(TIMES_WINDOWS) && !defined(_NO_MFC)
WTimeSpan::WTimeSpan(const CString &timeSrc, INTNM::int16_t *cnt)
{
	CT2CA pszConvertedAnsiString(timeSrc);
	WTimeSpan w(std::string(pszConvertedAnsiString), cnt);
	*this = w;
}
#endif

WTimeSpan::WTimeSpan(const std::string &timeSrc, INTNM::int16_t *cnt) {
	INTNM::int32_t day = 0, hour = 0, minute = 0, read = 0;
	std::string::size_type idx = 0;
	double second = 0.0;
	bool found = false;

	std::string trimmedSrc = boost::trim_copy(timeSrc);

	if (boost::starts_with(trimmedSrc, "P") || boost::starts_with(trimmedSrc, "-P"))
	{
		bool negative = boost::starts_with(trimmedSrc, "-");

		idx = negative ? 2 : 1;
		std::string value = "";
		bool afterT = false;
		bool isFraction = false;
		if (cnt)
			*cnt = 0;
		while (idx < trimmedSrc.length())
		{
			char c = trimmedSrc[idx];
			if (c == 'T' || c == 't')
				afterT = true;
			else if (c == '.' || c == ',')
			{
				isFraction = true;
				value += ".";
			}
			else if (c >= '0' && c <= '9')
				value += c;
			else if (value.length() > 0)
			{
				double val = -1;
				int ival = -1;
				if (isFraction)
					val = std::stod(value);
				else
					ival = std::stoi(value);

				if (val > 0 || ival > 0)
				{
					switch (c)
					{
					case 'Y':
					case 'y':
						//approximate years to days
						if (isFraction)
							second = val * 31'536'000;
						else
							day = ival * 365;
						if (cnt)
							(*cnt)++;
						break;
					case 'M':
					case 'm':
						if (afterT)
						{
							if (isFraction)
								second = val * 60;
							else
								minute = ival;
							if (cnt)
								(*cnt)++;
						}
						else
						{
							//approximate months to days
							if (isFraction)
								second = val * 2'592'000;
							else
								day = ival * 30;
							if (cnt)
								(*cnt)++;
						}
						break;
					case 'D':
					case 'd':
						if (isFraction)
							second = val * 86'400;
						else
							day = ival;
						if (cnt)
							(*cnt)++;
						break;
					case 'W':
					case 'w':
						if (isFraction)
							second = val * 7 * 86'400;
						else
							day = ival * 7;
						if (cnt)
							(*cnt)++;
						break;
					case 'H':
					case 'h':
						if (isFraction)
							second = val * 3600;
						else
							hour = ival;
						if (cnt)
							(*cnt)++;
						break;
					case 'S':
					case 's':
						if (isFraction)
							second = val;
						else
							second = ival;
						if (cnt)
							(*cnt)++;
						break;
					}
				}
				value.clear();
				//only the smallest value is allowed to be a fraction
				if (isFraction)
					break;
			}
			idx++;
		}
		if (negative)
		{
			if (day > 0)
				day = -day;
			else if (hour > 0)
				hour = -hour;
			else if (minute > 0)
				minute = -minute;
			else
				second = -second;
		}
	}
	else
	{
		if (idx = trimmedSrc.find("day")) {
			if (idx == std::string::npos)
				idx = 0;
			else
				found = true;
			if (found) {
				idx += 3;
				if (trimmedSrc[idx] == 's')
					idx++;
			}
		}
		for (std::string::size_type i = idx; i < trimmedSrc.size(); i++) {
			char c = trimmedSrc[i];
			if ((!isdigit(c)) && (c != ':') && (!isspace(c)) && (c != '-') && (c != '.')) {
				if (cnt)
					*cnt = 0;
				return;
			}
		}

#if defined(TIMES_WINDOWS) && !defined(_NO_MFC)
		if (found) {
			read = sscanf_s(trimmedSrc.c_str(), "%d days %d:%d:%lf", &day, &hour, &minute, &second);
			if (read <= 1)
				read = sscanf_s(trimmedSrc.c_str(), "%d day %d:%d:%lf", &day, &hour, &minute, &second);
		}
		if (read <= 1)
			read = sscanf_s(trimmedSrc.c_str(), "%d:%d:%lf", &hour, &minute, &second);
#else
		if (found) {
			read = sscanf(trimmedSrc.c_str(), "%d days %d:%d:%lf", &day, &hour, &minute, &second);
			if (read <= 1)
				read = sscanf(trimmedSrc.c_str(), "%d day %d:%d:%lf", &day, &hour, &minute, &second);
		}
		if (read <= 1)
			read = sscanf(trimmedSrc.c_str(), "%d:%d:%lf", &hour, &minute, &second);
#endif
		if (cnt)
			*cnt = (short)read;
		if (found) {
			switch (read) {
			case -1: if (cnt) *cnt = 0;	// added case to deal with glitch on scenario edit dialog
			case 0:
			case 1: hour = 0;
			case 2:	minute = 0;
			case 3:	second = 0.0;
			}
		}
		else {
			switch (read) {
			case -1: if (cnt) *cnt = 0;	// added case to deal with glitch on scenario edit dialog
			case 0:	return;			// bad timeSrc string
			case 1:	minute = 0;
			case 2:	second = 0.0;
			}
		}
	}

	if (day < 0)
		hour = 0 - hour;
	if (hour < 0)
		minute = 0 - minute;
	if (minute < 0)
		second = 0 - second;

	WTimeSpan cts(day, hour, minute, second);
	*this = cts;
}

#if defined(TIMES_WINDOWS) && !defined(_NO_MFC)
WTimeSpan::WTimeSpan(const COleDateTimeSpan &timeSrc) {
	WTimeSpan cts((INTNM::int32_t)timeSrc.GetTotalDays(), timeSrc.GetHours(), timeSrc.GetMinutes(), (std::int32_t)timeSrc.GetSeconds());
	*this = cts;
}
#endif

void WTimeSpan::SetTotalSeconds(INTNM::int64_t secs)				{ m_timeSpan = secs * 1000000LL; }

INTNM::int64_t WTimeSpan::GetYears() const							{ return (INTNM::int64_t)((long double)m_timeSpan / 1000000.0 / 24.0 / 60.0 / 60.0 / 365.25 /*+ 0.75*/); }
INTNM::int64_t WTimeSpan::GetWeeks() const							{ return m_timeSpan / (7LL * 24LL * 60LL * 60LL * 1000000LL); }
INTNM::int64_t WTimeSpan::GetDays() const							{ return m_timeSpan / (24LL * 60LL * 60LL * 1000000LL); }
INTNM::int64_t WTimeSpan::GetTotalHours() const						{ return m_timeSpan / (60LL * 60LL * 1000000LL); }
INTNM::int32_t WTimeSpan::GetHours() const							{ return (INTNM::int32_t)(GetTotalHours() - GetDays() * 24); }
INTNM::int64_t WTimeSpan::GetTotalMinutes() const					{ return m_timeSpan / (60LL * 1000000LL); }
INTNM::int32_t WTimeSpan::GetMinutes() const						{ return (INTNM::int32_t)(GetTotalMinutes() - GetTotalHours() * 60); }
INTNM::int64_t WTimeSpan::GetTotalSeconds() const					{ return m_timeSpan / 1000000LL; }
INTNM::int32_t WTimeSpan::GetSeconds() const						{ return (INTNM::int32_t)(GetTotalSeconds() - GetTotalMinutes() * 60); }
INTNM::int32_t WTimeSpan::GetMilliSeconds() const					{ return (INTNM::int32_t)(GetTotalMilliSeconds() - GetTotalSeconds() * 1000); }
INTNM::int32_t WTimeSpan::GetTotalMilliSeconds() const				{ return m_timeSpan / 1000LL; }
INTNM::int32_t WTimeSpan::GetMicroSeconds() const					{ return m_timeSpan % 1000000LL; }
INTNM::int64_t WTimeSpan::GetTotalMicroSeconds() const				{ return m_timeSpan; }

double WTimeSpan::GetDaysFraction() const							{ return ((double)m_timeSpan) / (24.0 * 60.0 * 60.0 * 1000000.0); }
double WTimeSpan::GetSecondsFraction() const						{ return ((double)m_timeSpan) / 1000000.0; }
double WTimeSpan::GetFractionOfDay() const							{ return ((double)(m_timeSpan % (24LL * 60LL * 60LL * 1000000LL))) / (24.0 * 60.0 * 60.0 * 1000000.0); }
INTNM::int32_t WTimeSpan::GetSecondsOfDay() const					{ return m_timeSpan % (24LL * 60LL * 60LL * 1000000LL); }

void WTimeSpan::PurgeToSecond()										{ m_timeSpan = m_timeSpan - (m_timeSpan % (1000000LL)); }
void WTimeSpan::PurgeToMinute()										{ m_timeSpan = m_timeSpan - (m_timeSpan % (60LL * 1000000LL)); }
void WTimeSpan::PurgeToHour()										{ m_timeSpan = m_timeSpan - (m_timeSpan % (60LL * 60LL * 1000000LL)); }
void WTimeSpan::PurgeToDay()										{ m_timeSpan = m_timeSpan - (m_timeSpan % (60LL * 60LL * 24LL * 1000000LL)); }

const WTimeSpan& WTimeSpan::operator=(const WTimeSpan &timeSrc)		{ if (&timeSrc != this) { m_timeSpan = timeSrc.m_timeSpan; } return *this; }
WTimeSpan WTimeSpan::operator-(const WTimeSpan &timeSpan) const		{ return WTimeSpan(m_timeSpan - timeSpan.m_timeSpan, false); }
WTimeSpan WTimeSpan::operator+(const WTimeSpan &timeSpan) const		{ return WTimeSpan(m_timeSpan + timeSpan.m_timeSpan, false); }
const WTimeSpan& WTimeSpan::operator-=(const WTimeSpan &timeSpan)	{ m_timeSpan -= timeSpan.m_timeSpan; return *this; }
const WTimeSpan& WTimeSpan::operator+=(const WTimeSpan &timeSpan)	{ m_timeSpan += timeSpan.m_timeSpan; return *this; }
WTimeSpan WTimeSpan::operator*(INTNM::int32_t factor) const			{ return WTimeSpan(m_timeSpan * factor, false); }
WTimeSpan WTimeSpan::operator/(INTNM::int32_t factor) const			{ return WTimeSpan(m_timeSpan / factor, false); }
double WTimeSpan::operator/(const WTimeSpan &timeSpan) const		{ return (double)(((long double)m_timeSpan) / ((long double)timeSpan.m_timeSpan)); }
WTimeSpan WTimeSpan::operator*(double factor) const					{ return WTimeSpan((INTNM::int64_t)((long double)m_timeSpan * factor), false); }
WTimeSpan WTimeSpan::operator/(double factor) const					{ return WTimeSpan((INTNM::int64_t)((long double)m_timeSpan / factor), false); }
const WTimeSpan WTimeSpan::operator*=(INTNM::int32_t factor)		{ m_timeSpan *= factor; return *this; }
const WTimeSpan WTimeSpan::operator/=(INTNM::int32_t factor)		{ m_timeSpan /= factor; return *this; }
const WTimeSpan WTimeSpan::operator*=(double f)						{ long double ts = (long double)m_timeSpan; ts *= f; m_timeSpan = (INTNM::int64_t)ts; return *this; }
const WTimeSpan WTimeSpan::operator/=(double f)						{ long double ts = (long double)m_timeSpan; ts /= f; m_timeSpan = (INTNM::int64_t)ts; return *this; }

#ifdef __cpp_impl_three_way_comparison
auto WTimeSpan::operator<=>(const WTimeSpan& timeSpan) const		{ return (m_timeSpan <=> timeSpan.m_timeSpan); }
#endif
bool WTimeSpan::operator==(const WTimeSpan &timeSpan) const			{ return (m_timeSpan == timeSpan.m_timeSpan); }
bool WTimeSpan::operator!=(const WTimeSpan &timeSpan) const			{ return (m_timeSpan != timeSpan.m_timeSpan); }
bool WTimeSpan::operator<(const WTimeSpan &timeSpan) const			{ return (m_timeSpan < timeSpan.m_timeSpan); }
bool WTimeSpan::operator>(const WTimeSpan &timeSpan) const			{ return (m_timeSpan > timeSpan.m_timeSpan); }
bool WTimeSpan::operator<=(const WTimeSpan &timeSpan) const			{ return (m_timeSpan <= timeSpan.m_timeSpan); }
bool WTimeSpan::operator>=(const WTimeSpan &timeSpan) const			{ return (m_timeSpan >= timeSpan.m_timeSpan); }

bool WTimeSpan::ParseTime(const TCHAR *lpszTime) {
#ifdef _UNICODE
    std::vector<char> buffer;
    size_t size = wcstombs(NULL, lpszTime, 0);
    if (size > 0)
    {
        buffer.resize(size);
        wcstombs(static_cast<char*>(&buffer[0]), lpszTime, buffer.size());
    }
    std::string time(&buffer[0]);
#else
	std::string time(lpszTime);
#endif
	return ParseTime(time);
}

#if defined(TIMES_WINDOWS) && !defined(_NO_MFC)
bool WTimeSpan::ParseTime(const CString &time)
{
	CT2CA pszConvertedAnsiString(time);
	return ParseTime(std::string(pszConvertedAnsiString));
}
#endif

bool WTimeSpan::ParseTime(const std::string &time) {
	short cnt;
	WTimeSpan ts(time, &cnt);
	if (cnt > 0)
		*this = ts;
	return (cnt > 0);
}

std::string WTimeSpan::ToString(INTNM::uint32_t flags) const {
	std::string str;
	if ((flags & WTIME_FORMAT_STRING_TIMEZONE))
	{
		if (m_timeSpan == 0)
			str = "PT0M";
		else
		{
			INTNM::int32_t	year = (INTNM::int32_t)GetYears(),
				day = (INTNM::int32_t)(GetDays() - ((long double)year * 365.25 /*- 0.75*/)),
				hour = GetHours(),
				minute = GetMinutes(),
				second = GetSeconds(),
				usecs = GetMicroSeconds();

			if (m_timeSpan < 0)
				str = "-";
			else
				str.clear();
			str += "P";
			if (year < 0)
				year = -year;
			if (day < 0)
				day = -day;
			if (hour < 0)
				hour = -hour;
			if (minute < 0)
				minute = -minute;
			if (second < 0)
				second = -second;
			if (usecs < 0)
				usecs = -usecs;

			if (year > 0)
			{
				str += std::to_string(year);
				str += "Y";
			}
			if (day > 0)
			{
				str += std::to_string(day);
				str += "D";
			}
			if (hour > 0 || minute > 0 || second > 0 || usecs > 0)
			{
				str += "T";
				if (hour > 0)
				{
					str += std::to_string(hour);
					str += "H";
				}
				if (minute > 0)
				{
					str += std::to_string(minute);
					str += "M";
				}
				if (second > 0 || usecs > 0)
				{
					str += std::to_string(second);
					if (usecs > 0)
					{
						str += ".";
						std::string temp = "";
						std::string::size_type zeroIdx = 5;
						for (int i = zeroIdx; i >= 0; i--)
						{
							INTNM::int32_t digit = usecs % 10;
							temp = ((char)(digit + '0')) + temp;
							if (zeroIdx == i && digit == 0)
								zeroIdx--;
							usecs /= 10;
						}
						if (zeroIdx != (temp.length() - 1))
						{
							temp = temp.substr(0, zeroIdx + 1);
						}
						str += temp;
					}
					str += "S";
				}
			}
		}
	}
	else
	{
		char buff[128];
		INTNM::int32_t	year = (INTNM::int32_t)GetYears(),
			day = (flags & WTIME_FORMAT_YEAR) ? ((INTNM::int32_t)(GetDays() - ((long double)year * 365.25 /*- 0.75*/))) : ((INTNM::int32_t)GetDays()),
			hour = (flags & WTIME_FORMAT_DAY) ? GetHours() : (INTNM::int32_t)GetTotalHours(),
			minute = GetMinutes(),
			second = GetSeconds(),
			usecs = GetMicroSeconds();
		bool	special_case = false;

		if (m_timeSpan < 0) {
			if (flags & WTIME_FORMAT_EXCLUDE_SECONDS) {
				if (second <= -30)			// perform any rounding
					minute--;
				if (minute == -60) {
					hour--;
					minute = 0;
				}
				if (hour == -24) {
					day--;
					hour = 0;
				}
			}
			if (day != 0)
				hour = 0 - hour;		// if there there are days, then we don't need the sign on the hour
			if (hour == 0)
				special_case = true;		// if no days and no hours, but it's negative, then we have a special print case
			minute = 0 - minute;			// take care of the sign which isn't needed for the minute
			second = 0 - second;			// ...or the seconds
			usecs = 0 - usecs;
		} else if (flags & WTIME_FORMAT_EXCLUDE_SECONDS) {
			if (second >= 30)			// perform any rounding
				minute++;
			if (minute == 60) {
				hour++;
				minute = 0;
			}
			if (hour == 24) {
				day++;
				hour = 0;
			}
		}
		if ((!year) || (!(flags & WTIME_FORMAT_YEAR))) {
			if ((!day) || (!(flags & WTIME_FORMAT_DAY))) {
				if (flags & WTIME_FORMAT_EXCLUDE_SECONDS) {
					if (special_case) { tm_snprintf(buff, 128, "-0:%2d", minute); str = buff; }
					else { tm_snprintf(buff, 128, "%02d:%02d", hour, minute); str = buff; }
				} else if (special_case) {
					if (flags & WTIME_FORMAT_INCLUDE_USECS) { tm_snprintf(buff, 128, "-0:%02d:%02d.%06d", minute, second, usecs); str = buff; }
					else { tm_snprintf(buff, 128, "-0:%02d:%02d", minute, second); str = buff; }
				} else {
					if (flags & WTIME_FORMAT_INCLUDE_USECS) { tm_snprintf(buff, 128, "%02d:%02d:%02d.%06d", hour, minute, second, usecs); str = buff; }
					else { tm_snprintf(buff, 128, "%02d:%02d:%02d", hour, minute, second); str = buff; }
				}
			} else {
				if ((!hour) && (!minute) && (!second) && (flags & WTIME_FORMAT_CONDITIONAL_TIME)) {
					// only day data and we're told to do that, so don't print hours, min's, sec's
					if (day == 1)						str = "1 day";
					else { tm_snprintf(buff, 128, "%d days", day); str = buff; }
				} else if (day == 1) {
					if (flags & WTIME_FORMAT_EXCLUDE_SECONDS) { tm_snprintf(buff, 128, "1 day %02d:%02d", hour, minute); str = buff; }
					else {
						if (flags & WTIME_FORMAT_INCLUDE_USECS) { tm_snprintf(buff, 128, "1 day %02d:%02d:%02d.%06d", hour, minute, second, usecs); str = buff; }
						else { tm_snprintf(buff, 128, "1 day %02d:%02d:%02d", hour, minute, second); str = buff; }
					}
				} else {
					if (flags & WTIME_FORMAT_EXCLUDE_SECONDS) { tm_snprintf(buff, 128, "%d days %02d:%02d", day, hour, minute); str = buff; }
					else {
						if (flags & WTIME_FORMAT_INCLUDE_USECS) { tm_snprintf(buff, 128, "%d days %02d:%02d:%02d.%06d", day, hour, minute, second, usecs); str = buff; }
						else { tm_snprintf(buff, 128, "%d days %02d:%02d:%02d", day, hour, minute, second); str = buff; }
					}
				}
			}
		} else {
			if ((!day) && (!hour) && (!minute) && (!second) && (flags & WTIME_FORMAT_CONDITIONAL_TIME)) {
				// only day data and we're told to do that, so don't print hours, min's, sec's
				if (year == 1)							str = "1 year";
				else { tm_snprintf(buff, 128, "%d years", year); str = buff; }
			} else if (year == 1) {
				if (flags & WTIME_FORMAT_EXCLUDE_SECONDS) { tm_snprintf(buff, 128, "1 year %d days %02d:%02d", day, hour, minute); str = buff; }
				else {
					if (flags & WTIME_FORMAT_INCLUDE_USECS) { tm_snprintf(buff, 128, "1 year %d days %02d:%02d:%02d.%06d", day, hour, minute, second, usecs); str = buff; }
					else { tm_snprintf(buff, 128, "1 year %d days %02d:%02d:%02d", day, hour, minute, second); str = buff; }
				}
			} else {
				if (flags & WTIME_FORMAT_EXCLUDE_SECONDS) { tm_snprintf(buff, 128, "%d years %d days %02d:%02d", year, day, hour, minute); str = buff; }
				else {
					if (flags & WTIME_FORMAT_INCLUDE_USECS) { tm_snprintf(buff, 128, "%d years %d days %02d:%02d:%02d.%06d", year, day, hour, minute, second, usecs); str = buff; }
					else { tm_snprintf(buff, 128, "%d years %d days %02d:%02d:%02d", year, day, hour, minute, second); str = buff; }
				}
			}
		}
	}

	return str;
}

#if defined(TIMES_WINDOWS) && !defined(_NO_MFC)
COleDateTimeSpan WTimeSpan::AsCOleDateTimeSpan() const {
	COleDateTimeSpan ts((INTNM::int32_t)GetDays(), GetHours(), GetMinutes(), GetSeconds());
	return ts;
}
#endif

WTimeManager::WTimeManager(const WorldLocation &worldLocation) : m_worldLocation(worldLocation) {
}

WTimeSpan WTimeManager::TimeForIndex(INTNM::int32_t index) {
	switch (index) {
		case ITERATION_1SEC:	return WTimeSpan(0, 0, 0, 1);
		case ITERATION_1MIN:	return WTimeSpan(0, 0, 1, 0);
		case ITERATION_5MIN:	return WTimeSpan(0, 0, 5, 0);
		case ITERATION_15MIN:	return WTimeSpan(0, 0, 15, 0);
		case ITERATION_30MIN:	return WTimeSpan(0, 0, 30, 0);
		case ITERATION_1HOUR:	return WTimeSpan(0, 1, 0, 0);
		case ITERATION_2HOUR:	return WTimeSpan(0, 2, 0, 0);
		case ITERATION_1DAY:	return WTimeSpan(1, 0, 0, 0);
	}
	return WTimeSpan(7, 0, 0, 0);
}

INTNM::int32_t WTimeManager::IterationIndex(const WTimeSpan &time) {
	const INTNM::int64_t iteration_amt[ITERATION_1WEEK + 1] = {
		1LL * 1000000LL,
		60LL * 1000000LL,
		60LL * 5LL * 1000000LL,
		60LL * 15LL * 1000000LL,
		60LL * 30LL * 1000000LL,
		60LL * 60LL * 1000000LL,
		60LL * 60LL * 2LL * 1000000LL,
		60LL * 60LL * 24LL * 1000000LL,
		60LL * 60LL * 24LL * 7LL * 1000000LL };

	INTNM::int32_t i;
	for (i = 0; i < ITERATION_1WEEK; i++)
		if (iteration_amt[i] >= time.GetTotalMicroSeconds())
			break;
	return i;
}

bool WTimeManager::isLeapYear(INTNM::int16_t year) {
	return CSunriseSunsetCalc::isLeapYear(year);
}

INTNM::int16_t WTimeManager::daysInMonth(INTNM::int16_t month, INTNM::int16_t year) {
	static const INTNM::int16_t days_in_months[12] = {
		31 /* January */,
		28 /* February */,
		31 /* March */,
		30 /* April */,
		31 /* May */,
		30 /* June */,
		31 /* July */,
		31 /* August */,
		30 /* September */,
		31 /* October */,
		30 /* November */,
		31 /* December */
	};
	if ((month == 2) && (isLeapYear(year)))
		return 29;
	return days_in_months[--month];
}

INTNM::int16_t WTimeManager::GetJulianCount(INTNM::int16_t year) {
	if (isLeapYear(year))
		return 366;
	return 365;
}

static void normalize(INTNM::int16_t *year, INTNM::int16_t *julian) {		// get julian into the range of (0...365or366),
	INTNM::int16_t i;						// adjust the year value accordingly
	if (*julian > 0) {
		i = WTimeManager::GetJulianCount(*year);
		while (*julian > i) {
			(*year)++;
			*julian -= i;
			i = WTimeManager::GetJulianCount(*year);
		}
	} else {
		while (*julian < 0) {
			(*year)--;
			*julian += WTimeManager::GetJulianCount(*year);
		}
	}
}

INTNM::int16_t WTimeManager::ToJulian(INTNM::int16_t year, INTNM::int16_t month, INTNM::int16_t day) {
	short i, calculated = 0;
	if (year < 70)
		year += 2000;
	else if (year < 100)
		year += 1900;
	while (year < 1600)
		year += 100;					// make sure our start time is appropriate for leap year calculations
	if (month > 0) {					// get months into range, adjust year accordingly to deal with leap years
		while (month > 12) {
			year++;
			month -= 12;
		}
	} else {
		while (month < 0) {
			year--;
			month += 12;
		}
	}
	normalize(&year, &day);					// take care of obvious range checking for the days too
	for (i = 1; i < month; i++)
		day += daysInMonth(month, year);
	normalize(&year, &day);
	calculated = day;
								// NOTE if day or month is way out of range, we could have a slight error in calculation
	return calculated;					// due to getting the years correct for leap year calculation...oh well...
}

void WTimeManager::FromJulian(INTNM::int16_t julian, INTNM::int16_t *year, INTNM::int16_t *month, INTNM::int16_t *day) {
	if (*year < 70)
		*year += 2000;
	else if (*year < 100)
		*year += 1900;
	while (*year < 1600)
		*year += 100;
	normalize(year, &julian);
	*month = 1;
	while (julian >= daysInMonth(*month, *year))
		julian -= daysInMonth((short)((*month)++), *year);
//	(*month)--;
	*day = (short)(julian + 1);
}

static std::string_view extractTimezoneFromPath(const char* rp)
{
	std::string_view result = rp;
	std::string_view zoneinfo = "zoneinfo";
	std::size_t pos = result.rfind(zoneinfo);
	if (pos == result.npos)
		return "";
	pos = result.find('/', pos);
	result.remove_prefix(pos + 1);
	return result;
}

static std::string findTimezonePath()
{
	struct stat sb;
	constexpr auto defaultDir = "/usr/share/zoneinfo";
	constexpr auto buildRootDir = "/usr/share/zoneinfo/uclibc";

	if (stat(buildRootDir, &sb) == 0 && S_ISDIR(sb.st_mode))
		return buildRootDir;
	else if (stat(defaultDir, &sb) == 0 && S_ISDIR(sb.st_mode))
		return defaultDir;
	return "";
}

static std::string cachedFindTimezonePath()
{
	static const std::string dir = findTimezonePath();
	return dir;
}

static bool checkValidFile(const char* timezone)
{
	char rp[PATH_MAX + 1] = {};
	if (realpath(timezone, rp) == nullptr)
		return false;
	return extractTimezoneFromPath(rp) != "posixrules";
}

const WTimeManager WTimeManager::GetSystemTimeManager(WorldLocation& location)
{
#ifdef _MSC_VER
	DYNAMIC_TIME_ZONE_INFORMATION dtzi{};
	if (GetDynamicTimeZoneInformation(&dtzi) != TIME_ZONE_ID_INVALID)
	{
		auto len = wcslen(dtzi.TimeZoneKeyName);
		char buffer[128];
		memset(buffer, 0, 128);
		wcstombs(buffer, dtzi.TimeZoneKeyName, len);
		auto zone = WorldLocation::TimeZoneFromWindowsName(buffer);

		bool isDST = false;
		if (dtzi.DaylightBias != 0)
		{
			time_t now;
			time(&now);
			tm tnow;
			gmtime_s(&tnow, &now);
			//it is the month that DST starts
			if (tnow.tm_mon == dtzi.DaylightDate.wMonth)
				isDST = (tnow.tm_mday > dtzi.DaylightDate.wDay) || (tnow.tm_mday == dtzi.DaylightDate.wDay && tnow.tm_hour > dtzi.DaylightDate.wHour);
			//it is the month that DST ends
			else if (tnow.tm_mon == dtzi.StandardDate.wMonth)
				isDST = (tnow.tm_mday < dtzi.StandardDate.wDay) || (tnow.tm_mday == dtzi.StandardDate.wDay && tnow.tm_hour < dtzi.StandardDate.wHour);
			//daylight saving starts earlier in the year than standard time
			else if (dtzi.DaylightDate.wMonth < dtzi.StandardDate.wMonth)
				isDST = (tnow.tm_mon > dtzi.DaylightDate.wMonth) && (tnow.tm_mon < dtzi.StandardDate.wMonth);
			//standard time start earlier in the year than daylight savings time
			else
				isDST = (tnow.tm_mon > dtzi.DaylightDate.wMonth) || (tnow.tm_mon < dtzi.StandardDate.wMonth);
		}

		if (zone)
		{
			if (isDST)
				zone = WorldLocation::GetDaylightSavingsTimeZone(zone);
			location.SetTimeZoneOffset(zone);
		}
		else
		{
			location.m_timezone(HSS_Time::WTimeSpan((dtzi.Bias + dtzi.StandardBias) * 60));
			//it is currently DST
			if (isDST)
			{
				location.m_amtDST(HSS_Time::WTimeSpan(dtzi.DaylightBias * 60));
				location.m_startDST(HSS_Time::WTimeSpan(0));
				location.m_endDST(HSS_Time::WTimeSpan(366, 0, 0, 0));
			}
		}
	}
#else
	std::string timezoneRegion;
	bool complete = false;
	//for OSs that store time info in a file like "/usr/share/zoneinfo/America/Winnipeg"
	//using the symlink "/etc/localtime"
	{
		bool found = true;
		struct stat sb;
		constexpr auto timezone = "/etc/localtime";
		if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0)
		{
			static const bool useRealpath = checkValidFile(timezone);
			char rp[PATH_MAX + 1] = {};
			if (useRealpath)
			{
				if (realpath(timezone, rp) == nullptr)
					found = false;//error
			}
			else
			{
				if (readlink(timezone, rp, sizeof(rp) - 1) <= 0)
					found = false;//error
			}

			if (found)
				timezoneRegion = extractTimezoneFromPath(rp);
			complete = true;
		}
	}

	//for OSs that store time info in a file like "/usr/share/zoneinfo/America/Winnipeg"
	//using the symlink "/etc/TZ"
	if (!complete)
	{
		struct stat sb;
		constexpr auto timezone = "/etc/TZ";
		if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0)
		{
			char rp[PATH_MAX + 1] = {};
			if (readlink(timezone, rp, sizeof(rp) - 1) > 0)
			{
				timezoneRegion = std::string(rp);

				const std::size_t pos = timezoneRegion.find(cachedFindTimezonePath());
				if (pos != timezoneRegion.npos)
					timezoneRegion.erase(0, cachedFindTimezonePath().size() + 1 + pos);
			}
			complete = true;
		}
	}

	//for OSs where the timezone is the first line of "/etc/timezone"
	if (!complete)
	{
		std::ifstream file("/etc/timezone");
		if (file.is_open())
		{
			std::getline(file, timezoneRegion);
			if (!timezoneRegion.empty())
				complete = true;
		}
	}

	//for OSs where the timezone is the first line of "/var/db/zoneinfo"
	if (!complete)
	{
		std::ifstream file("/var/db/zoneinfo");
		if (file.is_open())
		{
			std::getline(file, timezoneRegion);
			if (!timezoneRegion.empty())
				complete = true;
		}
	}

	//for OSs where the timezone is in "/etc/sysconfig/clock"
	//and stored as 'ZONE="America/Winnipeg"'
	if (!complete)
	{
		std::ifstream file("/etc/sysconfig/clock");
		std::string result;
		while (file)
		{
			std::getline(file, result);
			auto p = result.find("ZONE=\"");
			if (p != std::string::npos)
			{
				result.erase(p, p + 6);
				result.erase(result.rfind('"'));
				complete = true;
				break;
			}
		}
	}

	if (timezoneRegion.size())
	{
		auto zone = WorldLocation::TimeZoneFromRegionName(timezoneRegion);
		if (zone)
			location.SetTimeZoneOffset(zone);
	}
#endif

	return WTimeManager(std::move(location));
}

#if defined(TIMES_WINDOWS) && !defined(_NO_MFC)
WTime::WTime(const COleDateTime& timeSrc, const WTimeManager *tm, INTNM::uint32_t flags) {
	construct_time_t(timeSrc.GetYear(), timeSrc.GetMonth(), timeSrc.GetDay(), timeSrc.GetHour(), timeSrc.GetMinute(), timeSrc.GetSecond() );
	m_tm = tm;
	INTNM::uint64_t atm = adjusted_tm(flags);
	m_time = m_time - (atm - m_time);
}
#endif

WTime WTime::Now(const WTimeManager *tm, INTNM::uint32_t flags)
{
    std::time_t t = std::time(0);
    std::tm* now = std::gmtime(&t);
    auto seconds = now->tm_sec;
    if (flags & WTIME_FORMAT_EXCLUDE_SECONDS)
        seconds = 0;
    WTime temp(now->tm_year + 1900, now->tm_mon + 1, now->tm_mday, now->tm_hour, now->tm_min, seconds, tm);
    return WTime(temp, flags, -1);
}

#if TIMES_STATIC==1
WTime   WTime::_gmin(1900, 1, 1, 0, 0, 0, nullptr);
WTime	WTime::_gmax(2100, 1, 1, 0, 0, 0, nullptr);
#else
WTime   _gmin(1900, 1, 1, 0, 0, 0, nullptr),
		_gmax(2100, 1, 1, 0, 0, 0, nullptr);
#endif

WTime& WTime::GlobalMin() { return _gmin; }
WTime& WTime::GlobalMax() {	return _gmax; }

WTime WTime::GlobalMin(const WTimeManager* tm) { return WTime(_gmin, tm); }
WTime WTime::GlobalMax(const WTimeManager* tm) { return WTime(_gmax, tm); }

WTime::WTime(const WTimeManager *tm) {
	m_time = (INTNM::uint64_t)(-1);
	m_tm = tm;
}

WTime::WTime(const WTime& timeSrc) {
	m_time = timeSrc.m_time;
	m_tm = timeSrc.m_tm;
}

WTime::WTime(const WTime& timeSrc, const WTimeManager *tm) {
	m_time = timeSrc.m_time;
	m_tm = tm;
}

WTime::WTime(const WTime& timeSrc, INTNM::uint32_t flags, INTNM::int16_t direction) {
	m_time = timeSrc.m_time;
	m_tm = timeSrc.m_tm;
	if (m_tm) {
		INTNM::uint64_t atm = adjusted_tm(flags);
		if (direction < 0)		m_time = m_time - (atm - m_time);
		else if (direction > 0)		m_time = atm;
	}
}

WTime::WTime(INTNM::uint64_t time, const WTimeManager *tm, bool units_are_seconds) {
	if ((units_are_seconds) && (time != (INTNM::uint64_t)(-1)))
		m_time = time * 1000000LL;
	else
		m_time = time;
	m_tm = tm;
}

WTime::WTime(INTNM::int32_t nYear, INTNM::int32_t nMonth, INTNM::int32_t nDay, INTNM::int32_t nHour, INTNM::int32_t nMin, INTNM::int32_t nSec, const WTimeManager *tm) {
	construct_time_t(nYear, nMonth, nDay, nHour, nMin, nSec);

	m_tm = tm;
}

WTime::WTime(INTNM::int32_t nYear, INTNM::int32_t nMonth, INTNM::int32_t nDay, INTNM::int32_t nHour, INTNM::int32_t nMin, double nSec, const WTimeManager *tm) {
	construct_time_t(nYear, nMonth, nDay, nHour, nMin, 0L);
	m_time += (INTNM::uint64_t)(nSec * 1000000.0);
	m_tm = tm;
}

WTime::WTime(INTNM::int32_t nYear, INTNM::int32_t nMonth, INTNM::int32_t nDay, INTNM::int32_t nHour, INTNM::int32_t nMin, INTNM::int32_t nSec, INTNM::int32_t uSec, const WTimeManager *tm) {
	construct_time_t(nYear, nMonth, nDay, nHour, nMin, nSec);
	m_time += uSec;
	m_tm = tm;
}

void WTime::construct_time_t(INTNM::int32_t nYear, INTNM::int32_t nMonth, INTNM::int32_t nDay, INTNM::int32_t nHour, INTNM::int32_t nMin, INTNM::int32_t nSec)
{
	if (nMonth <= 2)
	{
		nYear -= 1;
		nMonth += 12;
	}
	INTNM::int32_t A = nYear/100LL;
	INTNM::int32_t B = 2LL - A + A/4;
	m_time = floor(365.25*(nYear + 4716)) + floor(30.6001*(nMonth+1)) + (nDay+1) + B - 1524.5;
	m_time -= 2305448; //normalize to epoch
	m_time *= 24;
	m_time += nHour;
	m_time *= 60;
	m_time += nMin;
	m_time *= 60;
	m_time += nSec;							// m_time is # secs since Jan 1, 1600
	m_time *= 1000000;
}

INTNM::uint64_t WTime::adjusted_tm(INTNM::uint32_t flags) const {
	weak_assert(m_time != (INTNM::uint64_t)(-1000000));
	if ((!m_time) || (m_time == (INTNM::uint64_t)-1))
		return m_time;
	if (!flags)
		return m_time;
	return adjusted_tm_math(flags);
}

#if defined(TIMES_WINDOWS) && !defined(_NO_MFC)
COleDateTime WTime::AsCOleDateTime(INTNM::uint32_t flags) const {
	INTNM::uint64_t atm = adjusted_tm(flags);
	WTime nt(atm, m_tm, false);
	INTNM::int32_t	year = nt.GetYear(0),
		month = nt.GetMonth(0),
		day = nt.GetDay(0),
		hour = nt.GetHour(0),
		min = nt.GetMinute(0),
		sec = nt.GetSecond(0);
	COleDateTime t(year, month, day, hour, min, sec);
	if (t.GetYear() == year)
		return t;
	return COleDateTime((INTNM::int64_t)0);
}
#endif

INTNM::uint64_t WTime::adjusted_tm_math(INTNM::uint32_t mode) const {
	weak_assert(m_time != (INTNM::uint64_t)(-1000000));

	if (mode & WTIME_FORMAT_AS_SOLAR)
		if (mode & (WTIME_FORMAT_WITHDST | WTIME_FORMAT_AS_LOCAL))
			throw std::runtime_error("Operation not supported");
	INTNM::uint64_t time;
	if (m_tm) {
		if (mode & WTIME_FORMAT_AS_LOCAL)
			time = m_time + m_tm->m_worldLocation.m_timezone().GetTotalMicroSeconds();
		else if (mode & WTIME_FORMAT_AS_SOLAR)
			time = m_time + m_tm->m_worldLocation.m_solar_timezone(*this).GetTotalMicroSeconds();
		else	time = m_time;

		if ((mode & WTIME_FORMAT_WITHDST) && (m_tm->m_worldLocation.m_startDST() != m_tm->m_worldLocation.m_endDST())) {
			WTime t(time, NULL, false);
			INTNM::uint64_t secs = t.GetSecondsIntoYear(0);
			if (m_tm->m_worldLocation.m_startDST() < m_tm->m_worldLocation.m_endDST()) {
				if (((INTNM::uint64_t)m_tm->m_worldLocation.m_startDST().GetTotalSeconds() <= secs) &&
				    (secs < (INTNM::uint64_t)m_tm->m_worldLocation.m_endDST().GetTotalSeconds()))
					time += m_tm->m_worldLocation.m_amtDST().GetTotalMicroSeconds();
			} else {
				if (((INTNM::uint64_t)m_tm->m_worldLocation.m_startDST().GetTotalSeconds() < secs) ||
				    (secs <= (INTNM::uint64_t)m_tm->m_worldLocation.m_endDST().GetTotalSeconds()))
					time += m_tm->m_worldLocation.m_amtDST().GetTotalMicroSeconds();
			}
		}
	} else	time = m_time;
	return time;
}

INTNM::uint64_t WTime::GetTime(INTNM::uint32_t mode) const			{ if (m_time != (INTNM::uint64_t)(-1)) return adjusted_tm(mode) / 1000000LL; return m_time;  };
INTNM::uint64_t WTime::GetTotalSeconds() const						{ if (m_time != (INTNM::uint64_t)(-1)) return m_time / 1000000LL; return m_time; };
INTNM::uint64_t WTime::GetTotalMilliSeconds() const					{ if (m_time != (INTNM::uint64_t)(-1)) return m_time / 1000LL; return m_time; }
INTNM::uint64_t WTime::GetTotalMicroSeconds() const					{ return m_time; };
const WTimeManager *WTime::GetTimeManager() const					{ return m_tm; };
const WTimeManager *WTime::SetTimeManager(const WTimeManager *tm)	{ m_tm = tm; return m_tm; };
bool WTime::IsValid() const											{ return (m_time != (INTNM::uint64_t)-1); }

INTNM::int32_t WTime::GetYear(INTNM::uint32_t mode) const {
	if (m_time == (INTNM::uint64_t)(-1))
		return -1;

	INTNM::uint64_t z = adjusted_tm(mode) / 24 / 60 / 60 / 1000000;
	z += 2305448;

	INTNM::int32_t a = z + 32044;
	INTNM::int32_t b = (4*a+3)/146097;
	INTNM::int32_t c = a - (b*146097)/4;
	INTNM::int32_t d = (4*c+3)/1461;
	INTNM::int32_t e = c - (1461*d)/4;
	INTNM::int32_t m = (5*e+2)/153;
	INTNM::int32_t nyear = b*100 + d - 4800 + m/10;
	return nyear;
}

INTNM::int32_t WTime::GetMonth(INTNM::uint32_t mode) const {
	if (m_time == (INTNM::uint64_t)(-1))
		return -1;

	INTNM::uint64_t z = adjusted_tm(mode) / 24 / 60 / 60 / 1000000;
	z += 2305448;
	INTNM::int32_t a = z + 32044;
	INTNM::int32_t b = (4*a+3)/146097;
	INTNM::int32_t c = a - (b*146097)/4;
	INTNM::int32_t d = (4*c+3)/1461;
	INTNM::int32_t e = c - (1461*d)/4;
	INTNM::int32_t m = (5*e+2)/153;
	INTNM::int32_t nmonth = m + 3 - 12*(m/10);
	return nmonth;
}

INTNM::int32_t WTime::GetDay(INTNM::uint32_t mode) const {
	if (m_time == (INTNM::uint64_t)(-1))
		return -1;

	INTNM::uint64_t z = adjusted_tm(mode) / 24 / 60 / 60 / 1000000;
	z += 2305448;

	INTNM::int32_t a = z + 32044;
	INTNM::int32_t b = (4*a+3)/146097;
	INTNM::int32_t c = a - (b*146097)/4;
	INTNM::int32_t d = (4*c+3)/1461;
	INTNM::int32_t e = c - (1461*d)/4;
	INTNM::int32_t m = (5*e+2)/153;
	INTNM::int32_t nday = e - (153*m+2)/5 + 1;
	return nday;
}

INTNM::int32_t WTime::GetHour(INTNM::uint32_t flags) const				{ if (m_time == (INTNM::uint64_t)(-1)) return -1; return (INTNM::int32_t)((adjusted_tm(flags) / (60LL * 60LL * 1000000LL)) % 24); };
INTNM::int32_t WTime::GetMinute(INTNM::uint32_t flags) const			{ if (m_time == (INTNM::uint64_t)(-1)) return -1; return (INTNM::int32_t)((adjusted_tm(flags) / (60LL * 1000000LL)) % 60); };
INTNM::int32_t WTime::GetSecond(INTNM::uint32_t flags) const			{ if (m_time == (INTNM::uint64_t)(-1)) return -1; return (INTNM::int32_t)((adjusted_tm(flags) / 1000000LL) % 60); };
INTNM::int32_t WTime::GetMilliSeconds(INTNM::uint32_t flags) const		{ if (m_time == (INTNM::uint64_t)(-1)) return -1; return (INTNM::int32_t)((adjusted_tm(flags) / 1000LL) % 1000); };
INTNM::int32_t WTime::GetMicroSeconds(INTNM::uint32_t flags) const		{ if (m_time == (INTNM::uint64_t)(-1)) return -1; return (INTNM::int32_t)((adjusted_tm(flags) % 1000000LL)); };
WTimeSpan WTime::GetTimeOfDay(INTNM::uint32_t flags) const				{ if (m_time == (INTNM::uint64_t)(-1)) return WTimeSpan(-1, false); INTNM::int64_t time = adjusted_tm(flags) % (60LL * 60LL * 24LL * 1000000LL); return WTimeSpan(time, false); };
INTNM::int32_t WTime::GetDayOfWeek(INTNM::uint32_t flags) const			{ if (m_time == (INTNM::uint64_t)(-1)) return -1; INTNM::int64_t days = adjusted_tm(flags) / (24LL * 60LL * 60LL * 1000000LL) + 0; INTNM::int32_t ret = (INTNM::int32_t)(days % 7); return ret == 0 ? 7L : ret; };

INTNM::uint64_t WTime::GetSecondsIntoYear(INTNM::uint32_t mode) const {
	if (m_time == (INTNM::uint64_t)(-1))
		return -1;

	INTNM::uint64_t atm = adjusted_tm(mode) / 1000000;
	WTime year(GetYear(mode), 1, 1, 0, 0, 0, m_tm);
	return (atm - year.GetTotalSeconds());
}

WTimeSpan WTime::GetWTimeSpanIntoYear(INTNM::uint32_t mode) const {
	if (m_time == (INTNM::uint64_t)(-1))
		return WTimeSpan(-1, false);

	INTNM::uint64_t atm = adjusted_tm(mode);
	WTime year(GetYear(mode), 1, 1, 0, 0, 0, m_tm);
	return WTimeSpan((INTNM::int64_t)(atm - year.m_time), false);
}

bool WTime::IsLeapYear(INTNM::uint32_t flags) const {
	if (m_time == (INTNM::uint64_t)(-1))
		return false;

	return WTimeManager::isLeapYear((INTNM::int16_t)GetYear(flags));
}

INTNM::int32_t WTime::GetDayOfYear(INTNM::uint32_t mode) const {
	if (m_time == (INTNM::uint64_t)(-1))
		return -1;

	INTNM::uint64_t atm = adjusted_tm(mode) / 24 / 60 / 60 / 1000000;
	WTime year(GetYear(mode), 1, 1, 0, 0, 0.0, m_tm);
	INTNM::uint64_t year_atm = year.adjusted_tm(0) / 24 / 60 / 60 / 1000000;
	return (atm - year_atm) + 1;
}

double WTime::GetDayFractionOfYear(INTNM::uint32_t mode) const {
	if (m_time == (INTNM::uint64_t)(-1))
		return -1.0;

	INTNM::uint64_t atm = adjusted_tm(mode) / 1000000;
	WTime year(GetYear(mode), 1, 1, 0, 0, 0.0, m_tm);
	INTNM::uint64_t total_secs = atm - year.GetTotalSeconds();
	return ((double)total_secs) / (24.0 * 60.0 * 60.0) + 1.0;
}

void WTime::PurgeToSecond(INTNM::uint32_t flags)			{ if (m_time != (INTNM::uint64_t)(-1)) m_time = m_time - (adjusted_tm(flags) % (1000000LL)); };
void WTime::PurgeToMinute(INTNM::uint32_t flags)			{ if (m_time != (INTNM::uint64_t)(-1)) m_time = m_time - (adjusted_tm(flags) % (60LL * 1000000LL)); };
void WTime::PurgeToHour(INTNM::uint32_t flags)				{ if (m_time != (INTNM::uint64_t)(-1)) m_time = m_time - (adjusted_tm(flags) % (60LL * 60LL * 1000000LL)); };
void WTime::PurgeToDay(INTNM::uint32_t flags)				{ if (m_time != (INTNM::uint64_t)(-1)) m_time = m_time - (adjusted_tm(flags) % (60LL * 60LL * 24LL * 1000000LL)); };

void WTime::PurgeToYear(INTNM::uint32_t flags) {
	if (m_time != (INTNM::uint64_t)(-1)) {
		WTimeSpan secs = GetWTimeSpanIntoYear(flags);
		m_time -= secs.GetTotalMicroSeconds();
	}
}

const WTime& WTime::operator++() {
	if (m_time != (INTNM::uint64_t)(-1)) {
		WTimeSpan incr;
		if (IsLeapYear(0))	incr = WTimeSpan(366, 0, 0, 0);
		else			incr = WTimeSpan(365, 0, 0, 0);
		return *this += incr;
	}
	return *this;
}

const WTime& WTime::operator--() {
	if (m_time != (INTNM::uint64_t)(-1)) {
		INTNM::int32_t year = (INTNM::int32_t)(GetYear(0) - 1);
		WTimeSpan decr;
		if (WTimeManager::isLeapYear((INTNM::int16_t)year))	decr = WTimeSpan(366, 0, 0, 0);
		else						decr = WTimeSpan(365, 0, 0, 0);
		return *this -= decr;
	}
	return *this;
}

const WTime& WTime::operator+=(INTNM::int32_t years) {
	if (m_time != (INTNM::uint64_t)(-1)) {
		WTimeSpan yr(365, 0, 0, 0);
		WTimeSpan lyr(366, 0, 0, 0);
		INTNM::int32_t year = (INTNM::int32_t)GetYear(0);
		INTNM::int32_t i;
		for (i = 0; i < years; i++, year++) {
			if (WTimeManager::isLeapYear((INTNM::int16_t)year))
				*this += lyr;
			else	*this += yr;
		}
	}
	return *this;
}

const WTime& WTime::operator-=(INTNM::int32_t years) {
	if (m_time != (INTNM::uint64_t)(-1)) {
		WTimeSpan yr(365, 0, 0, (INTNM::int32_t)0);
		WTimeSpan lyr(366, 0, 0, (INTNM::int32_t)0);
		INTNM::int32_t year = (INTNM::int32_t)GetYear(0);
		INTNM::int32_t i;
		for (i = 0, year--; i < years; i++, year--) {
			if (WTimeManager::isLeapYear((INTNM::int16_t)year))
				*this -= lyr;
			else	*this -= yr;
		}
	}
	return *this;
}

const WTime& WTime::operator=(const WTime& timeSrc) {
	if (&timeSrc != this) {

#ifdef _DEBUG
		if (m_tm)
			weak_assert(m_tm == timeSrc.m_tm);
#endif

		m_time = timeSrc.m_time;
		m_tm = timeSrc.m_tm;
	}
	return *this;
}

const WTime& WTime::SetTime(const WTime& timeSrc) {
	if (&timeSrc != this)
		m_time = timeSrc.m_time;
	return *this;
}

bool WTime::operator==(const WTime &time) const {

#ifdef _DEBUG
	weak_assert(m_time != (INTNM::uint64_t)(-1000000));
	if ((m_time != (INTNM::uint64_t)-1) && (m_time != (INTNM::uint64_t)0) &&
		(time.m_time != (INTNM::uint64_t)-1) && (time.m_time != (INTNM::uint64_t)0))
		weak_assert(time.m_tm == m_tm);
#endif

	return (m_time == time.m_time);
}

bool WTime::operator!=(const WTime &time) const {

#ifdef _DEBUG
	weak_assert(m_time != (INTNM::uint64_t)(-1000000));
	if ((m_time != (INTNM::uint64_t)-1) && (m_time != (INTNM::uint64_t)0) &&
		(time.m_time != (INTNM::uint64_t)-1) && (time.m_time != (INTNM::uint64_t)0))
		weak_assert(time.m_tm == m_tm);
#endif

	return (m_time != time.m_time);
}

WTime WTime::operator-(const WTimeSpan &timeSpan) const		{ if (m_time != (INTNM::uint64_t)(-1)) return WTime(m_time - timeSpan.GetTotalMicroSeconds(), m_tm, false); return *this; }
WTime WTime::operator+(const WTimeSpan &timeSpan) const		{ if (m_time != (INTNM::uint64_t)(-1)) return WTime(m_time + timeSpan.GetTotalMicroSeconds(), m_tm, false); return *this; }
const WTime& WTime::operator-=(const WTimeSpan &timeSpan)	{ if (m_time != (INTNM::uint64_t)(-1)) m_time -= timeSpan.GetTotalMicroSeconds(); return *this; }
const WTime& WTime::operator+=(const WTimeSpan &timeSpan)	{ if (m_time != (INTNM::uint64_t)(-1)) m_time += timeSpan.GetTotalMicroSeconds(); return *this; }
WTimeSpan WTime::operator-(const WTime& time) const			{ weak_assert(time.m_tm == m_tm); if (m_time != (INTNM::uint64_t)(-1)) return WTimeSpan(m_time - time.m_time, false); return WTimeSpan(-1, false); }
bool WTime::operator<(const WTime &time) const				{ weak_assert(time.m_tm == m_tm); if (m_time != (INTNM::uint64_t)(-1)) return (m_time < time.m_time); return false; }
bool WTime::operator>(const WTime &time) const				{ weak_assert(time.m_tm == m_tm); if (m_time != (INTNM::uint64_t)(-1)) return (m_time > time.m_time); return false; }
bool WTime::operator<=(const WTime &time) const				{ weak_assert(time.m_tm == m_tm); if (m_time != (INTNM::uint64_t)(-1)) return (m_time <= time.m_time); return false; }
bool WTime::operator>=(const WTime &time) const				{ weak_assert(time.m_tm == m_tm); if (m_time != (INTNM::uint64_t)(-1)) return (m_time >= time.m_time); return false; }

const char *WTimeManager::months_abbrev[12]		= { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
const char *WTimeManager::months[12]			= { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
const char *WTimeManager::days_abbrev[7]		= { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
const char *WTimeManager::days[7]				= { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };

std::string WTime::ToString(INTNM::uint32_t flags) const {
	std::string str;
	char buff[128];
	std::string month_str, day_str;
	INTNM::int32_t year, month, day, hour, minute, second, usecs, day_of_week;
	bool need_leading_space;

	weak_assert(m_time != (INTNM::uint64_t)(-1000000));
	if (/*(!m_time) ||*/ (m_time == (INTNM::uint64_t)-1)) {
		str = "[Time Not Set]";
		return str;
	}

	year = GetYear(flags);
	month = GetMonth(flags);
	day = GetDay(flags);
	hour = GetHour(flags);
	minute = GetMinute(flags);
	second = GetSecond(flags);
	usecs = GetMicroSeconds(flags);

	day_of_week = GetDayOfWeek(flags);

	if (flags & WTIME_FORMAT_ABBREV) {		// decide if we should be abbreviating things or not
		month_str = WTimeManager::months_abbrev[month - 1];
		day_str = WTimeManager::days_abbrev[--day_of_week];
	} else {
		month_str = WTimeManager::months[month - 1];
		day_str = WTimeManager::days[--day_of_week];
	}

	if (flags & WTIME_FORMAT_DAY_OF_WEEK) {	// first, try to prepend the day-of-week to the string
		str = day_str;
		need_leading_space = true;
	} else	need_leading_space = false;

	if (flags & WTIME_FORMAT_DATE) {
		if (need_leading_space)	str += " ";
		else			need_leading_space = true;

		if ((flags & 0x000000ff) == WTIME_FORMAT_STRING_DD_MM_YYYY) {
			tm_snprintf(buff, 128, "%02d/%02d/%04d", day, month, year);
			str += buff;
		} else if ((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYY_MM_DD) {
			tm_snprintf(buff, 128, "%04d/%02d/%02d", year, month, day);
			str += buff;
		} else if ((flags & 0x000000ff) == WTIME_FORMAT_STRING_MM_DD_YYYY) {
			tm_snprintf(buff, 128, "%02d/%02d/%04d", month, day, year);
			str += buff;
		} else if ((flags & 0x000000ff) == WTIME_FORMAT_STRING_DDhMMhYYYY) {
			tm_snprintf(buff, 128, "%02d-%02d-%04d", day, month, year);
			str += buff;
		} else if (((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYhMMhDD) || ((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYhMMhDDT)) {
			tm_snprintf(buff, 128, "%04d-%02d-%02d", year, month, day);
			str += buff;
		} else if ((flags & 0x000000ff) == WTIME_FORMAT_STRING_MMhDDhYYYY) {
			tm_snprintf(buff, 128, "%02d-%02d-%04d", month, day, year);
			str += buff;
		} else if (((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYMMDD) || ((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYMMDDT)) {
			tm_snprintf(buff, 128, "%04d%02d%02d", year, month, day);
			str += buff;
		} else if ((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYMMDDHH) {
			tm_snprintf(buff, 128, "%04d%02d%02d%02d", year, month, day, hour);
			str += buff;
		} else if (flags & WTIME_FORMAT_MONTH) {	// next, try to append date as month, day
			if (flags & WTIME_FORMAT_DAY)
			{
					tm_snprintf(buff, 128, "%s %2d", month_str.c_str(), day);
			}
			else 	tm_snprintf(buff, 128, "%s", month_str.c_str());
			str += buff;

			if (flags & WTIME_FORMAT_YEAR) {	// ...and conditionally add in the year
				tm_snprintf(buff, 128, ", %d", year);
				str += buff;
			}
		} else if (flags & WTIME_FORMAT_DAY) {
			tm_snprintf(buff, 128, "%2d", day);
			str += buff;
		}
	}

	if ((flags & WTIME_FORMAT_TIME) || ((flags & WTIME_FORMAT_CONDITIONAL_TIME) && ((usecs) || (second) || (minute) || (hour)))) {			// finally, try to append the time
		if ((((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYhMMhDDT) || ((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYMMDDT)) && (flags & WTIME_FORMAT_DATE))
			str += "T";
		else if (need_leading_space)
			str += " ";
		if (flags & WTIME_FORMAT_EXCLUDE_SECONDS) {
			if (second >= 30)		// perform any rounding
				minute++;
			tm_snprintf(buff, 128, "%02d:%02d", hour, minute);
		} else {
			if (flags & WTIME_FORMAT_INCLUDE_USECS)
				tm_snprintf(buff, 128, "%02d:%02d:%02d.%06d", hour, minute, second, usecs);
			else
				tm_snprintf(buff, 128, "%02d:%02d:%02d", hour, minute, second);
		}
		str += buff;
	}

	if (((flags & WTIME_FORMAT_STRING_TIMEZONE)) && (m_tm))
	{
		int64_t offset = m_tm->m_worldLocation.m_timezone().GetTotalMinutes();
		if (m_tm->m_worldLocation.m_startDST() != m_tm->m_worldLocation.m_endDST())
		{
			uint64_t intoYear = GetSecondsIntoYear(0);
			if (m_tm->m_worldLocation.m_startDST() < m_tm->m_worldLocation.m_endDST())
			{
				if (((uint64_t)m_tm->m_worldLocation.m_startDST().GetTotalSeconds() <= intoYear) &&
					(intoYear < (uint64_t)m_tm->m_worldLocation.m_endDST().GetTotalSeconds()))
					offset += m_tm->m_worldLocation.m_amtDST().GetTotalMinutes();
			}
			else
			{
				if (((uint64_t)m_tm->m_worldLocation.m_startDST().GetTotalSeconds() < intoYear) ||
					(intoYear <= (uint64_t)m_tm->m_worldLocation.m_endDST().GetTotalSeconds()))
					offset += m_tm->m_worldLocation.m_amtDST().GetTotalMinutes();
			}
		}
		if (offset == 0)
			str += "Z";
		else
		{
			if (offset < 0)
			{
				str += "-";
				offset = -offset;
			}
			else
				str += "+";
			int hourOffset = 0;
			while (offset >= 60)
			{
				hourOffset++;
				offset -= 60;
			}
			tm_snprintf(buff, 128, "%02" PRId32 ":%02" PRId64, hourOffset, offset);
			str += buff;
		}
	}
	return str;
}

#if defined(TIMES_WINDOWS) && !defined(_NO_MFC)
bool WTime::systemParseDateTime(const TCHAR *lpszDate, INTNM::uint32_t flags) {
	DWORD ole_flags = LOCALE_NOUSEROVERRIDE;
	if (!(flags & WTIME_FORMAT_TIME))
		ole_flags |= VAR_DATEVALUEONLY;
	COleDateTime ole_date(2000, 1, 1, 1, 1, 1);
	bool success = ole_date.ParseDateTime(lpszDate, ole_flags) ? true : false;
	if (success) {
		WTime new_time(ole_date, GetTimeManager(), flags);
		*this = new_time;
	}
	return success;
}

bool WTime::ParseDateTime(const CString &lpszDate, INTNM::uint32_t flags)
{
	CT2CA inter(lpszDate);
	return ParseDateTime(std::string(inter), flags);
}
#endif

bool str2int(INTNM::int32_t &i, char const *s, INTNM::int32_t base = 0)
{
	char *end;
	INTNM::int32_t l;
	l = strtol(s, &end, base);
	if (*s == '\0' || *end != '\0')
		return false;
	i = l;
	return true;
}

bool WTime::ParseDateTime(const std::wstring &lpszDate, INTNM::uint32_t flags)
{
	auto converted = std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().to_bytes(lpszDate);
	return ParseDateTime(converted, flags);
}

bool WTime::ParseDateTime(const std::string &lpszDate, INTNM::uint32_t flags, WorldLocation* location) {

#if defined(TIMES_WINDOWS) && !defined(_NO_MFC)
	if (flags & WTIME_FORMAT_PARSE_USING_SYSTEM)
	{
#if _UNICODE
		std::wstring wideDate(lpszDate.begin(), lpszDate.end());
		return systemParseDateTime(wideDate.c_str(), flags);
#else //_UNICODE
		return systemParseDateTime(lpszDate.c_str(), flags);
#endif //_UNICODE
	}
#endif

	static const char delimit[] = "./\\:;-, \t";
	static const char delimit2[] = "./\\:;-, \tT";
	INTNM::int32_t year, month, day, hour, min, sec;
	INTNM::int32_t v1, v2, v3;
	INTNM::int16_t time_scan = 0;
	std::optional<INTNM::int64_t> secondOffset = std::nullopt;
	bool timezoneExists = false;

	bool delimit_found = false;
	while (delimit[time_scan]) {
		if (lpszDate.find(delimit[time_scan]) != std::string::npos) {
			delimit_found = true;
			break;
		}
		time_scan++;
	}
			// TIME_FORMAT_DAY is implied

	if (!delimit_found) {
			// some groups are providing a screwy "YYYYMMDDHH" format
		if (((flags & 0x000000ff) != WTIME_FORMAT_STRING_YYYYMMDD) &&
			((flags & 0x000000ff) != WTIME_FORMAT_STRING_YYYYMMDDT) &&
			((flags & 0x000000ff) != WTIME_FORMAT_STRING_YYYYMMDDHH))
															return false;
		if ((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYMMDD)
			if (lpszDate.size() != 8)						return false;
		if ((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYMMDDHH)
			if (lpszDate.size() != 10)						return false;
		std::string yr, mn, dy, hr;
		yr = lpszDate.substr(0, 4);
		mn = lpszDate.substr(4, 2);
		dy = lpszDate.substr(6, 2);
		if ((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYMMDDHH)
			hr = lpszDate.substr(8, 2);
		bool rd = str2int(year, yr.c_str()); if (!rd)		return false;
		rd = str2int(month, mn.c_str()); if (!rd)			return false;
		rd = str2int(day, dy.c_str()); if (!rd)				return false;

		if ((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYMMDDHH) {
			rd = str2int(hour, hr.c_str());
			if (!rd)										return false;
		} else	hour = 0;
		min = sec = 0;
	} else {
		const char *tok;
		char* next = nullptr;
		char* dateBuf = __strdup(lpszDate.c_str());

		tok = __strtok(dateBuf, delimit, &next);
		if (!tok) {
			free(dateBuf);
			return false;
		}
		short guess_month = 0;
		if (!isdigit(tok[0])) {
			if (((flags & 0x000000ff) != WTIME_FORMAT_STRING_MM_DD_YYYY) &&
				((flags & 0x000000ff) != WTIME_FORMAT_STRING_MMhDDhYYYY) &&
				((flags & 0x000000ff) != 0)) {
				free(dateBuf);
				return false;
			}
			for (v1 = 0; v1 < 12; v1++)
				if ((!__stricmp(WTimeManager::months[v1], tok)) || (!__stricmp(WTimeManager::months_abbrev[v1], tok)))
					break;
			if (v1 == 12) {
				free(dateBuf);
				return false;
			}
			v1++;
			guess_month = 1;
		} else {
			INTNM::int32_t rd = sscanf(tok, "%d", &v1);
			if (rd != 1) {
				free(dateBuf);
				return false;
			}
		}

		tok = __strtok(NULL, delimit, &next);
		if (!tok) {
			free(dateBuf);
			return false;
		}
		if (!isdigit(tok[0])) {
			if (((flags & 0x000000ff) != WTIME_FORMAT_STRING_DD_MM_YYYY) &&
				((flags & 0x000000ff) != WTIME_FORMAT_STRING_YYYY_MM_DD) &&
				((flags & 0x000000ff) != WTIME_FORMAT_STRING_DDhMMhYYYY) &&
				((flags & 0x000000ff) != WTIME_FORMAT_STRING_YYYYhMMhDD) &&
				((flags & 0x000000ff) != WTIME_FORMAT_STRING_YYYYhMMhDDT) &&
				((flags & 0x000000ff) != 0)) {
				free(dateBuf);
				return false;
			}
			for (v2 = 0; v2 < 12; v2++)
				if ((!__stricmp(WTimeManager::months[v2], tok)) || (!__stricmp(WTimeManager::months_abbrev[v2], tok)))
					break;
			if (v2 == 12) {
				free(dateBuf);
				return false;
			}
			v2++;
			guess_month = 2;
		} else {
			INTNM::int32_t rd = sscanf(tok, "%d", &v2);
			if (rd != 1) {
				free(dateBuf);
				return false;
			}
		}

		if ((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYhMMhDDT)
			tok = __strtok(NULL, delimit2, &next);
		else
			tok = __strtok(NULL, delimit, &next);
		if (!tok) {
			free(dateBuf);
			return false;
		}
		if (!isdigit(tok[0])) {
			free(dateBuf);
			return false;
		} else {
			INTNM::int32_t rd = sscanf(tok, "%d", &v3);
			if (rd != 1) {
				free(dateBuf);
				return false;
			}
		}

		if (!(flags & 0x000000ff)) {
			if (guess_month == 1) {
				if (v3 >= 32)
					flags |= WTIME_FORMAT_STRING_MM_DD_YYYY;
			} else if (guess_month == 2) {
				if (v3 >= 32)
					flags |= WTIME_FORMAT_STRING_DD_MM_YYYY;
				else if (v1 >= 32)
					flags |= WTIME_FORMAT_STRING_YYYY_MM_DD;
			} else {
				if (v1 >= 32)
					flags |= WTIME_FORMAT_STRING_YYYY_MM_DD;
				else if (v3 >= 32 && v2 > 12)
					flags |= WTIME_FORMAT_STRING_MM_DD_YYYY;
			}
		}

		if (((flags & 0x000000ff) == WTIME_FORMAT_STRING_DD_MM_YYYY) ||
			((flags & 0x000000ff) == WTIME_FORMAT_STRING_DDhMMhYYYY) ||
			(!(flags & 0x000000ff))) {
			day = v1;
			month = v2;
			year = v3;
		} else if (((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYY_MM_DD) ||
				((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYhMMhDD) ||
				((flags & 0x000000ff) == WTIME_FORMAT_STRING_YYYYhMMhDDT)) {
			day = v3;
			month = v2;
			year = v1;
		} else if (((flags & 0x000000ff) == WTIME_FORMAT_STRING_MM_DD_YYYY) ||
			        ((flags & 0x000000ff) == WTIME_FORMAT_STRING_MMhDDhYYYY)) {
			day = v2;
			month = v1;
			year = v3;
		}

		if (day >= 32) {
			free(dateBuf);
			return false;
		}
		if ((month < 1) || (month > 12)) {
			free(dateBuf);
			return false;
		}
		if (year < 39)							year += 2000;
		else if ((year < 100) && (year > 60))	year += 1900;

		if (flags & WTIME_FORMAT_TIME) {
			bool negative = false;
			char* save = next;
			tok = __strtok(NULL, "", &next);
			if (tok != nullptr)
			{
				if (strchr(tok, 'Z'))
				{
					secondOffset = std::optional<INTNM::int64_t>{ 0 };
					tok = __strtok(NULL, "-+Z", &save);
				}
				else
				{
					timezoneExists = strchr(tok, '+') != nullptr || strchr(tok, '-') != nullptr;
					if (timezoneExists)
					{
						negative = strchr(tok, '-') != nullptr;
						tok = __strtok(NULL, "-+Z", &save);
						next = save;
					}
				}
				WTimeSpan ts(std::string(tok), &time_scan);
				if (!time_scan)
					hour = min = sec = 0;
				else {
					hour = ts.GetHours();
					min = ts.GetMinutes();
					sec = ts.GetSeconds();
				}

				if (timezoneExists)
				{
					tok = __strtok(NULL, "", &next);
					if (tok)
					{
						WTimeSpan ts(std::string(tok), &time_scan);
						if (time_scan)
						{
							if (negative)
								secondOffset = std::optional<INTNM::int64_t>{ -ts.GetTotalSeconds() };
							else
								secondOffset = std::optional<INTNM::int64_t>{ ts.GetTotalSeconds() };
						}
					}
				}
			}
			else
				hour = min = sec = 0;
		}
		else
			hour = min = sec = 0;

		free(dateBuf);
	}

	if ((year >= 1600) && (year < 2900)) {
		WTime t(year, month, day, hour, min, sec, m_tm);
		if (secondOffset.has_value())
		{
			int64_t offset = m_tm->m_worldLocation.m_timezone().GetTotalSeconds();
			uint64_t intoYear = t.GetSecondsIntoYear(0);
			if (location)
			{
				location->m_timezone(WTimeSpan(secondOffset.value()));
				location->m_amtDST(WTimeSpan(0));
			}
			if (m_tm->m_worldLocation.m_startDST() != m_tm->m_worldLocation.m_endDST())
			{
				if (m_tm->m_worldLocation.m_startDST() < m_tm->m_worldLocation.m_endDST())
				{
					if (((uint64_t)m_tm->m_worldLocation.m_startDST().GetTotalSeconds() <= intoYear) &&
						(intoYear < (uint64_t)m_tm->m_worldLocation.m_endDST().GetTotalSeconds()))
						offset += m_tm->m_worldLocation.m_amtDST().GetTotalSeconds();
				}
				else
				{
					if (((uint64_t)m_tm->m_worldLocation.m_startDST().GetTotalSeconds() < intoYear) ||
						(intoYear <= (uint64_t)m_tm->m_worldLocation.m_endDST().GetTotalSeconds()))
						offset += m_tm->m_worldLocation.m_amtDST().GetTotalSeconds();
				}
			}
			offset -= secondOffset.value();
			if (offset)
				t += WTimeSpan(offset);
		}
		else if (timezoneExists)
		{
			if (location)
			{
				location->m_timezone(WTimeSpan(0));
				location->m_amtDST(WTimeSpan(0));
			}
		}

		INTNM::uint64_t atm = adjusted_tm_math(flags);

		m_time = t.m_time - (atm - m_time);
	}
	return true;
}

#if defined(TIMES_WINDOWS) && !defined(_NO_MFC)
CArchive& AFXAPI HSS_Time::operator<<(CArchive& ar, const WTimeSpan timeSpan)	{
	INTNM::uint64_t milli_id = 0x7ffeeffccffaaffd;
	return ar << milli_id << timeSpan.m_timeSpan;
}

CArchive& AFXAPI HSS_Time::operator>>(CArchive& ar, WTimeSpan& rtimeSpan) {
	CArchive &r(ar >> rtimeSpan.m_timeSpan);
	if (rtimeSpan.m_timeSpan == 0x7ffeeffccffaaffd)
		return ar >> rtimeSpan.m_timeSpan;
	rtimeSpan.m_timeSpan *= 1000000;
	return r;
}

CArchive& AFXAPI HSS_Time::operator<<(CArchive& ar, const WTime &time)	{
	INTNM::uint64_t milli_id = 0x7ffeeddccbbaa009;
	return ar << milli_id << time.m_time;
}

CArchive& AFXAPI HSS_Time::operator>>(CArchive& ar, WTime &rtime) {
	CArchive &r(ar >> rtime.m_time);
	if (rtime.m_time == 0x7ffeeddccbbaa009)
		return ar >> rtime.m_time;
	if (rtime.m_time != (INTNM::uint64_t)-1LL) {
		rtime.m_time *= 1000000;
		rtime.m_time += WTIME_1900;		// new offset to year 1600 (no point in going back much further)
	}
	return r;
}
#endif

INTNM::uint64_t WTime::updateSerializedULONGLONG(INTNM::uint64_t toUpdate) {
	if (toUpdate != (INTNM::uint64_t)-1LL) {
		toUpdate *= 1000000LL;
		toUpdate += WTIME_1900;		// new offset to year 1600 (no point in going back much further)
	}
	return toUpdate;
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_SWAR_FUNCTIONS_COMPLEX_SIMD_COMMON_GROUPS_HPP_INCLUDED
#define NT2_SWAR_FUNCTIONS_COMPLEX_SIMD_COMMON_GROUPS_HPP_INCLUDED

#include <nt2/swar/functions/groups.hpp>
#include <nt2/include/functions/real.hpp>
#include <nt2/include/functions/imag.hpp>
#include <nt2/include/functions/bitwise_cast.hpp>
#include <nt2/sdk/complex/hierarchy.hpp>
#include <boost/dispatch/meta/downgrade.hpp>
#include <boost/mpl/not.hpp>
#include <boost/type_traits/is_same.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT_IF  ( groups_, tag::cpu_,
                          (A0)(X),
                          (boost::mpl::not_< boost::is_same<A0, typename boost::dispatch::meta::downgrade<A0>::type> >),
                          ((simd_<complex_<arithmetic_<A0> >,X>))
                          ((simd_<complex_<arithmetic_<A0> >,X>))
                        )
  {
    typedef typename boost::dispatch::meta::downgrade<A0>::type result_type;
    NT2_FUNCTOR_CALL_REPEAT(2)
    {
      return result_type(groups(nt2::real(a0), nt2::real(a1)),
                         groups(nt2::imag(a0), nt2::imag(a1)));
    }
  };

  BOOST_DISPATCH_IMPLEMENT_IF  ( groups_, tag::cpu_,
                          (A0)(X),
                          (boost::mpl::not_< boost::is_same<A0, typename boost::dispatch::meta::downgrade<A0>::type> >),
                          ((simd_<dry_<arithmetic_<A0> >,X>))
                          ((simd_<dry_<arithmetic_<A0> >,X>))
                        )
  {
    typedef typename boost::dispatch::meta::downgrade<A0>::type result_type;
    NT2_FUNCTOR_CALL_REPEAT(2)
    {
      return bitwise_cast<result_type>(groups(nt2::real(a0), nt2::real(a1)));
    }
  };

  BOOST_DISPATCH_IMPLEMENT  ( groups_, tag::cpu_,
                             (A0)(X),
                             ((simd_<complex_<arithmetic_<A0> >,X>))
                           )
  {
    typedef typename boost::dispatch::meta::downgrade<typename A0::value_type>::type base_t;
    typedef typename boost::simd::meta::vector_of< base_t
                                                 , A0::static_size
                                                 >::type result_type;
    NT2_FUNCTOR_CALL_REPEAT(1)
    {
      return result_type(groups(nt2::real(a0)), groups(nt2::imag(a0)));
    }
  };

  BOOST_DISPATCH_IMPLEMENT  ( groups_, tag::cpu_,
                             (A0)(X),
                             ((simd_<dry_<arithmetic_<A0> >,X>))
                           )
  {
    typedef typename boost::dispatch::meta::downgrade<typename A0::value_type>::type base_t;
    typedef typename boost::simd::meta::vector_of< base_t
                                                 , A0::static_size
                                                 >::type result_type;
    NT2_FUNCTOR_CALL_REPEAT(1)
    {
      return bitwise_cast<result_type>(groups(nt2::real(a0)));
    }
  };
} } }
#endif


#include "focal_grid.h"

#include <string>
#include <stdint.h>
#include <iostream>
#include <iomanip>
#include <math.h>
#include <chrono>
#include <vector>
#include <future>
#include <thread>
#include <algorithm>
#include <dirent.h>
#include <armadillo>

using namespace std;

//-------------------------------------------------

inline bool sortByPix0(const voxel_prop &lhs, const voxel_prop &rhs) {
	return lhs.pix_0 < rhs.pix_0;
}

FocalGrid::FocalGrid() {
	// empty
}

bool FocalGrid::LoadCalibration(session_param &ses_par, vox_grid &vox, frames &frame) {

	bool grid_loaded = true;

	try {

		vox.image_size.clear();
		vox.calib_mat.clear();
		vox.X_xyz.clear();
		vox.X_uv.clear();
		vox.uv_offset.clear();

		image_size.clear();
		calib_mat.clear();
		X_xyz.clear();
		X_uv.clear();
		uv_offset.clear();

		string calib_file = ses_par.session_loc + "/" + ses_par.cal_loc + "/" + ses_par.cal_name;

		arma::Mat<double> CalibMatrix;

		CalibMatrix.load(calib_file);

		for (int i=0; i<ses_par.N_cam; i++) {
			vox.image_size.push_back(frame.image_size[i]);
			vox.calib_mat.push_back(CalibMatrix.col(i));
			vox.X_xyz.push_back(FocalGrid::Camera2WorldMatrix(CalibMatrix.col(i)));
			vox.X_uv.push_back(FocalGrid::World2CameraMatrix(CalibMatrix.col(i)));
			arma::Col<double> uv_off_i = {CalibMatrix(10,i)/2.0-CalibMatrix(12,i)/2.0, CalibMatrix(9,i)/2.0-CalibMatrix(11,i)/2.0, 0.0};
			vox.uv_offset.push_back(uv_off_i);

			image_size.push_back(frame.image_size[i]);
			calib_mat.push_back(CalibMatrix.col(i));
			X_xyz.push_back(FocalGrid::Camera2WorldMatrix(CalibMatrix.col(i)));
			X_uv.push_back(FocalGrid::World2CameraMatrix(CalibMatrix.col(i)));
			uv_offset.push_back(uv_off_i);
		}

		// Populate internal parameters

		N_cam = vox.N_cam;
		N_threads = vox.N_threads;
		nx = vox.nx;
		ny = vox.ny;
		nz = vox.nz;
		ds = vox.ds;
		x0 = vox.x0;
		y0 = vox.y0;
		z0 = vox.z0;

	}
	catch (...) {
		grid_loaded = false;
	}

	return grid_loaded;
}

bool FocalGrid::ConstructFocalGrid(vox_grid &vox) {

	bool grid_build = true;

	try {

		vox.voxel_list.clear();

		int vec_size = 0;
		int i = 0;
		vector<future<vector<voxel_prop>>> vox_results;

		while (i<nx) {
			cout << "row " + to_string(i) + " / " + to_string(nx) << endl;
			vox_results.clear();
			for (int j=0; j<N_threads; j++) {
				if (i+j<nx) {
					vox_results.push_back(async(launch::async, &FocalGrid::CheckVoxel,this,i+j));
				}
			}
			for (int j=0; j<N_threads; j++) {
				vector<voxel_prop> vox_results_thread=vox_results.at(j).get();
				vec_size = vox_results_thread.size();
				if (vec_size>1) {
					copy(vox_results_thread.begin(),vox_results_thread.end(),back_inserter(vox.voxel_list));
				}
			}
			i+=N_threads;
		}

		cout << "Sorting" << endl;
		sort(vox.voxel_list.begin(),vox.voxel_list.end(),sortByPix0);

		cout << "Voxel list size:" << endl;
		cout << vox.voxel_list.size() << endl;

	}
	catch (...) {
		grid_build = false;
	}

	return grid_build;
}

vector<tuple<double,double,double,int>> FocalGrid::ProjectImage2Cloud(vector<arma::Col<int>> &frame_in, vox_grid &vox) {

	vector<tuple<double,double,double,int>> pcl_now;

	tuple<bool,vector<int>> view_check;
	tuple<int,bool,vector<int>> code_check;
	vector<vector<int>> code_list;

	int N_row = get<0>(vox.image_size[0]);
	int N_col = get<1>(vox.image_size[0]);

	int vox_ind = 0;
	int max_ind = vox.voxel_list.size();

	for (int i=0; i<(N_row*N_col); i++) {

		if (frame_in[0](i)>0) {
			while (vox.voxel_list[vox_ind].pix_0<=i) {
				if (vox.voxel_list[vox_ind].pix_0==i) {
					view_check = FocalGrid::CheckInView(frame_in, vox, vox_ind);
					if (get<0>(view_check)==true) {
						if (FocalGrid::CheckNeighbors(frame_in, vox, vox_ind)==true) {
							code_check = FocalGrid::CheckCode(code_list, get<1>(view_check));
							if (get<1>(code_check)==true) {
								code_list.push_back(get<2>(code_check));
							}
							pcl_now.push_back(make_tuple(
								vox.voxel_list[vox_ind].x,
								vox.voxel_list[vox_ind].y,
								vox.voxel_list[vox_ind].z,
								get<0>(code_check)));
								//get<1>(view_check)));
						}
					}
				}
				vox_ind++;
				if (vox_ind >= max_ind) {
					break;
				}
			}
			if (vox_ind >= max_ind) {
				break;
			}
		}
	}

	if (pcl_now.size() <= 0) {
		pcl_now.push_back(make_tuple(0.0,0.0,0.0,0));
	}

	return pcl_now;
}

tuple<int,bool,vector<int>> FocalGrid::CheckCode(vector<vector<int>> code_list, vector<int> code_in) {

	int code_out = 0;
	vector<int> new_code;

	int i=0;
	bool code_found = false;
	bool add_code = false;

	int N_codes = code_list.size();

	if (N_codes > 0) {
		while ((i<N_codes) && (code_found==false)) {
			int match_count = 0;
			int body_count = 0;
			for (int j=0; j<N_cam; j++) {
				if (code_list[i][j]==code_in[j]) {
					if (code_in[j]==1) {
						match_count++;
						body_count++;
					}
					else {
						match_count++;
					}
				}
			}
			if (match_count == N_cam) {
				// Code exists, get code number
				code_out = code_list[i][N_cam+1];
				code_found == true;
			}
			else if ((match_count == (N_cam-1)) && (body_count < (N_cam-1))) {
				// Code cannot be found in code_list but it matches with N_cam-1 views
				// and has less than N_cam-1 body views:
				code_out = code_list[i][N_cam+1];
				code_found == true;
			}
			i++;
		}
		if (code_found == false) {
			// Code could not be found in the code list, add code to the list.
			add_code = true;

			// Check first if code_in corresponds to the body:
			int body_count = 0;
			for (int j=0; j<N_cam; j++) {
				if (code_in[j]==1) {
					body_count++;
				}
				new_code.push_back(code_in[j]);
			}

			// If code_in corresponds to the body: pick 1 as pcl code
			if (body_count == N_cam) {
				new_code.push_back(1);
				code_out = 1;
			}
			// If code_in does not correspond to the body: add N_codes+1 as pcl code
			else {
				new_code.push_back(N_codes+1);
				code_out = N_codes+1;
			}
		}
	}
	else {
		// No code exists yet, add curent code to the code list:
		add_code = true;

		// Check first if code_in corresponds to the body:
		int body_count = 0;
		for (int j=0; j<N_cam; j++) {
			if (code_in[j]==1) {
				body_count++;
			}
			new_code.push_back(code_in[j]);
		}

		// If code_in corresponds to the body: pick 1 as pcl code
		if (body_count == N_cam) {
			new_code.push_back(1);
			code_out = 1;
		}
		// If code_in does not correspond to the body: add 2 as pcl code
		else {
			new_code.push_back(2);
			code_out = 2;
		}
	}

	return make_tuple(code_out,add_code,new_code);
}

/*
vector<tuple<double,double,double,int>> FocalGrid::ProjectImage2Cloud(vector<arma::Col<int>> &frame_in, vox_grid &vox) {

	vector<tuple<double,double,double,int>> pcl_now;

	vector<future<vector<tuple<double,double,double,int>>>> future_pcl;

	int N_row = get<0>(vox.image_size[0]);
	int N_col = get<1>(vox.image_size[0]);

	int vec_size = 0;

	// Launch threads:
	for (int j=0; j<N_threads; j++) {
		future_pcl.push_back(async(launch::async, &FocalGrid::ProjectImage2CloudThread,this,j,(N_row*N_col),ref(vox),ref(frame_in)));
	}
	cout << "threads have been launched" << endl;
	// Retrieve information from threads:
	for (int j=0; j<N_threads; j++) {
		vector<tuple<double,double,double,int>> pcl_results_thread=future_pcl.at(j).get();
		vec_size = pcl_results_thread.size();
		if (pcl_results_thread.size()>1) {
			copy(pcl_results_thread.begin(),pcl_results_thread.end(),back_inserter(pcl_now));
		}
	}
	cout << "pcl now size" << endl;
	cout << pcl_now.size() << endl;
	if (pcl_now.size() <= 0) {
		pcl_now.push_back(make_tuple(0.0,0.0,0.0,0));
	}

	return pcl_now;
}

vector<tuple<double,double,double,int>> FocalGrid::ProjectImage2CloudThread(int i_start, int i_end, vox_grid &vox, vector<arma::Col<int>> &frame_in) {

	vector<tuple<double,double,double,int>> pcl_i;

	for (int i = i_start; i<i_end; i+=N_threads) {

		int vox_ind = 0;
		int max_ind = vox.voxel_list.size();

		tuple<bool,int> view_check;

		if (frame_in[0](i) > 0) {
			while (vox.voxel_list[vox_ind].pix_0<=i) {
				if (vox.voxel_list[vox_ind].pix_0==i) {
					view_check = FocalGrid::CheckInView(frame_in, vox, vox_ind);
					if (get<0>(view_check)==true) {
						if (FocalGrid::CheckNeighbors(frame_in, vox, vox_ind)==true) {
							pcl_i.push_back(make_tuple(
								vox.voxel_list[vox_ind].x,
								vox.voxel_list[vox_ind].y,
								vox.voxel_list[vox_ind].z,
								get<1>(view_check)));
						}
					}
				}
				vox_ind++;
				if (vox_ind >= max_ind) {
					break;
				}
			}
		}
	}

	return pcl_i;
}
*/

vector<arma::Col<int>> FocalGrid::ProjectCloud2Image(vector<tuple<double,double,double,int>> &cloud_in, vox_grid &vox) {

	vector<arma::Col<int>> frame_now;

	int N_vox = cloud_in.size();

	for (int n=0; n<N_cam; n++) {
		arma::Col<int> frame_n;
		frame_n.zeros(get<0>(image_size[n])*get<1>(image_size[n]));
		frame_now.push_back(frame_n);
	}

	arma::Col<double> uv;
	arma::Col<double> xyz;
	int u = 0;
	int v = 0;

	for (int i=0; i<N_vox; i++) {
		for (int j=0; j<N_cam; j++) {
			xyz = {get<0>(cloud_in[i]),get<1>(cloud_in[i]),get<2>(cloud_in[i]),1.0};
			uv = X_uv[j]*xyz-uv_offset[j];
			if (uv(0)>=0 && uv(0)<(get<1>(image_size[j]))) {
				if (uv(1)>=0 && uv(1)<(get<0>(image_size[j]))) {
					u = (int) uv(0);
					v = (int) uv(1);
					if (frame_now[j](get<0>(image_size[j])*u+v)==0) {
						frame_now[j](get<0>(image_size[j])*u+v) = get<3>(cloud_in[i]);
					}
					else {
						if (frame_now[j](get<0>(image_size[j])*u+v)>get<3>(cloud_in[i])) {
							frame_now[j](get<0>(image_size[j])*u+v) = get<3>(cloud_in[i]);
						}
					}
				}
			}
		}
	}

	return frame_now;
}

tuple<bool,vector<int>> FocalGrid::CheckInView(vector<arma::Col<int>> &frame_in, vox_grid &vox, int vox_ind) {

	bool in_view = true;
	vector<int> seg_codes;

	int pix_ind = 0;
	int intensity_now = 0;

	for (int i=0; i<N_cam; i++) {
		if (i==0) {
			pix_ind = vox.voxel_list[vox_ind].pix_0;
			intensity_now = frame_in[i](pix_ind);
			seg_codes.push_back(intensity_now);
			if (intensity_now<=0) {
				in_view = false;
			}
		}
		else {
			pix_ind = vox.voxel_list[vox_ind].pix_n[i-1];
			intensity_now = frame_in[i](pix_ind);
			seg_codes.push_back(intensity_now);
			if (intensity_now<=0) {
				in_view = false;
			}
		}
	}

	return make_tuple(in_view,seg_codes);
}

/*
tuple<bool,int> FocalGrid::CheckInView(vector<arma::Col<int>> &frame_in, vox_grid &vox, int vox_ind) {

	int intensity_sum = 0;
	int intensity_now = 0;
	bool in_view = true;
	int n = 0;
	int pix_ind = 0;

	while (in_view==true && n<N_cam) {
		if (n==0) {
			pix_ind = vox.voxel_list[vox_ind].pix_0;
			intensity_now = frame_in[n](pix_ind);
			if (intensity_now>0) {
				if (intensity_now<max_n_seg) {
					intensity_sum += frame_in[n](pix_ind);
				}
				else {
					intensity_sum = pow((max_n_seg*1.0),N_cam);
				}
			}
			else {
				in_view = false;
			}
		}
		else {
			pix_ind = vox.voxel_list[vox_ind].pix_n[n-1];
			intensity_now = frame_in[n](pix_ind);
			if (intensity_now>0) {
				if (intensity_now<max_n_seg) {
					intensity_sum += frame_in[n](pix_ind)*pow((max_n_seg*1.0),n);
				}
				else {
					intensity_sum = pow((max_n_seg*1.0),N_cam);
				}
			}
			else {
				in_view = false;
			}
		}
		n++;
	}

	return make_tuple(in_view,intensity_sum);
}
*/

inline bool FocalGrid::CheckNeighbors(vector<arma::Col<int>> &frame_in, vox_grid &vox, int vox_ind) {

	bool pixel_on = true;

	int uv;
	int on_count = 0;
	int in_view = 0;

	for (int m=0; m<6; m++) {
		in_view =0;
		for (int n=0; n<N_cam; n++) {
			uv = vox.voxel_list[vox_ind].neighbors[m][n];
			if (frame_in[n](uv)>0) {
				in_view++;
			}
		}
		if (in_view == N_cam) {
			on_count++;
		}
	}

	if (on_count==6) {
		pixel_on = false;
	}

	if (on_count<2) {
		pixel_on = false;
	}

	return pixel_on;
}

vector<voxel_prop> FocalGrid::CheckVoxel(int i) {

	vector<voxel_prop> voxel_array;

	voxel_array.resize(1);

	int vox_index = 0;

	int is_neighbor = 0;

	arma::Col<double> xyz(4);
	arma::Col<double> xyz_0(4);
	arma::Col<double> xyz_1(4);
	arma::Col<double> xyz_2(4);
	arma::Col<double> xyz_3(4);
	arma::Col<double> xyz_4(4);
	arma::Col<double> xyz_5(4);

	arma::Mat<double> uv(3,N_cam);
	arma::Mat<double> uv_0(3,N_cam);
	arma::Mat<double> uv_1(3,N_cam);
	arma::Mat<double> uv_2(3,N_cam);
	arma::Mat<double> uv_3(3,N_cam);
	arma::Mat<double> uv_4(3,N_cam);
	arma::Mat<double> uv_5(3,N_cam);

	for (int j=0; j<ny; j++) {
		for (int k=0; k<nz; k++) {

			vox_index = i*(ny*nz)+j*nz+k;

			struct voxel_prop vox_now;

			xyz = {x0-((nx-1)/2.0)*ds+i*ds,
				y0-((ny-1)/2.0)*ds+j*ds,
				z0-((nz-1)/2.0)*ds+k*ds,
				1.0};

			// Calculate projection coordinates:
			for (int n=0; n<N_cam; n++) {
				uv.col(n) = X_uv[n]*xyz-uv_offset[n];
			}

			// Check if the voxel projects to all frames:
			if (FocalGrid::IsVoxel(uv)==1) {

				xyz_0 = {x0-((nx-1)/2.0)*ds+(i+1)*ds,
					y0-((ny-1)/2.0)*ds+j*ds,
					z0-((nz-1)/2.0)*ds+k*ds,
					1.0};

				xyz_1 = {x0-((nx-1)/2.0)*ds+(i-1)*ds,
					y0-((ny-1)/2.0)*ds+j*ds,
					z0-((nz-1)/2.0)*ds+k*ds,
					1.0};

				xyz_2 = {x0-((nx-1)/2.0)*ds+i*ds,
					y0-((ny-1)/2.0)*ds+(j+1)*ds,
					z0-((nz-1)/2.0)*ds+k*ds,
					1.0};

				xyz_3 = {x0-((nx-1)/2.0)*ds+i*ds,
					y0-((ny-1)/2.0)*ds+(j-1)*ds,
					z0-((nz-1)/2.0)*ds+k*ds,
					1.0};

				xyz_4 = {x0-((nx-1)/2.0)*ds+i*ds,
					y0-((ny-1)/2.0)*ds+j*ds,
					z0-((nz-1)/2.0)*ds+(k+1)*ds,
					1.0};

				xyz_5 = {x0-((nx-1)/2.0)*ds+i*ds,
					y0-((ny-1)/2.0)*ds+j*ds,
					z0-((nz-1)/2.0)*ds+(k-1)*ds,
					1.0};

				for (int m=0; m<N_cam; m++) {
					uv_0.col(m) = X_uv[m]*xyz_0-uv_offset[m];
					uv_1.col(m) = X_uv[m]*xyz_1-uv_offset[m];
					uv_2.col(m) = X_uv[m]*xyz_2-uv_offset[m];
					uv_3.col(m) = X_uv[m]*xyz_3-uv_offset[m];
					uv_4.col(m) = X_uv[m]*xyz_4-uv_offset[m];
					uv_5.col(m) = X_uv[m]*xyz_5-uv_offset[m];
				}

				is_neighbor = FocalGrid::IsVoxel(uv_0)+FocalGrid::IsVoxel(uv_1)+
							FocalGrid::IsVoxel(uv_2)+FocalGrid::IsVoxel(uv_3)+
							FocalGrid::IsVoxel(uv_4)+FocalGrid::IsVoxel(uv_5);

				if (is_neighbor == 6) {
					for (int m=0; m<N_cam; m++) {
						if (m==0) {
							vox_now.pix_0 = ((int) uv(1,m))*get<1>(image_size[m])+((int) uv(0,m));
							vox_now.vox_ind = vox_index;
							vox_now.x = xyz(0);
							vox_now.y = xyz(1);
							vox_now.z = xyz(2);
							vox_now.neighbors.resize(6);
							vox_now.neighbors[0].push_back(((int) uv_0(1,m))*get<1>(image_size[m])+((int) uv_0(0,m)));
							vox_now.neighbors[1].push_back(((int) uv_1(1,m))*get<1>(image_size[m])+((int) uv_1(0,m)));
							vox_now.neighbors[2].push_back(((int) uv_2(1,m))*get<1>(image_size[m])+((int) uv_2(0,m)));
							vox_now.neighbors[3].push_back(((int) uv_3(1,m))*get<1>(image_size[m])+((int) uv_3(0,m)));
							vox_now.neighbors[4].push_back(((int) uv_4(1,m))*get<1>(image_size[m])+((int) uv_4(0,m)));
							vox_now.neighbors[5].push_back(((int) uv_5(1,m))*get<1>(image_size[m])+((int) uv_5(0,m)));
						}
						else {
							vox_now.pix_n.push_back(((int) uv(1,m))*get<1>(image_size[m])+((int) uv(0,m)));
							vox_now.neighbors[0].push_back(((int) uv_0(1,m))*get<1>(image_size[m])+((int) uv_0(0,m)));
							vox_now.neighbors[1].push_back(((int) uv_1(1,m))*get<1>(image_size[m])+((int) uv_1(0,m)));
							vox_now.neighbors[2].push_back(((int) uv_2(1,m))*get<1>(image_size[m])+((int) uv_2(0,m)));
							vox_now.neighbors[3].push_back(((int) uv_3(1,m))*get<1>(image_size[m])+((int) uv_3(0,m)));
							vox_now.neighbors[4].push_back(((int) uv_4(1,m))*get<1>(image_size[m])+((int) uv_4(0,m)));
							vox_now.neighbors[5].push_back(((int) uv_5(1,m))*get<1>(image_size[m])+((int) uv_5(0,m)));
						}
					}
					voxel_array.push_back(vox_now);
				}
			}
		}
	}

	return voxel_array;
}

inline int FocalGrid::IsVoxel(arma::Mat<double> uv) {

	int is_voxel = 0;

	int count = 0;

	for (int n=0; n<N_cam; n++) {
		if (uv(0,n)>=0 && uv(0,n)<(get<1>(image_size[n]))) {
			if (uv(1,n)>=0 && uv(1,n)<(get<0>(image_size[n]))) {
				count++;
			}
		}
	}

	if (count == N_cam) {
		is_voxel = 1;
	}

	return is_voxel;

}

arma::Mat<int> FocalGrid::TransformXYZ2UV(arma::Col<double> xyz_pos) {

	arma::Mat<int> uv_mat(2,N_cam);

	for (int n=0; n<N_cam; n++) {
		arma::Col<double> uv = X_uv[n]*xyz_pos-uv_offset[n];
		uv_mat(0,n) = int(uv(0));
		uv_mat(1,n) = int(uv(1));
	}

	return uv_mat;
}

tuple<arma::Mat<int>, arma::Col<double>> FocalGrid::RayCasting(int cam_nr, arma::Col<double> xyz_pos_prev, arma::Col<double> uv_pos_prev, arma::Col<double> uv_pos_now) {

	// Calculate the 3D translation vector:
	arma::Col<double> xyz_uv_prev = X_xyz[cam_nr]*(uv_pos_prev+uv_offset[cam_nr]);
	arma::Col<double> xyz_uv_now = X_xyz[cam_nr]*(uv_pos_now+uv_offset[cam_nr]);
	arma::Col<double> trans_vec = xyz_uv_now-xyz_uv_prev;
	trans_vec(3) = 0.0;

	// Add the translation to the xyz position:
	arma::Col<double> xyz_pos_now = xyz_pos_prev + trans_vec;

	// Project the new position back to the camera views:

	arma::Mat<int> uv_mat(3,N_cam);

	for (int n=0; n<N_cam; n++) {
		arma::Col<double> uv = X_uv[n]*xyz_pos_now-uv_offset[n];
		if (uv(0)>=0 && uv(0)<(get<1>(image_size[n])) && uv(1)>=0 && uv(1)<(get<0>(image_size[n]))) {
			uv_mat(0,n) = int(uv(0));
			uv_mat(1,n) = int(uv(1));
			uv_mat(2,n) = int(uv(2));
		}
		else {
			arma::Col<double> uv_old = X_uv[n]*xyz_pos_prev-uv_offset[n];
			uv_mat(0,n) = int(uv_old(0));
			uv_mat(1,n) = int(uv_old(1));
			uv_mat(2,n) = int(uv_old(2));
		}
	}

	return make_tuple(uv_mat,xyz_pos_now);
}

arma::Mat<double> FocalGrid::Camera2WorldMatrix(arma::Col<double> calib_param) {

	// return the world to camera projection matrix

	arma::Mat<double> C = {{calib_param(0), calib_param(2), 0, 0},
		 			{0, calib_param(1), 0, 0},
		 			{0, 0, 0, 1}};

	double theta = sqrt(pow(calib_param(3),2)+pow(calib_param(4),2)+pow(calib_param(5),2));

	arma::Mat<double> omega = {{0, -calib_param(5), calib_param(4)},
			 			{calib_param(5), 0, -calib_param(3)},
			 			{-calib_param(4), calib_param(3), 0}};

	arma::Mat<double> R(3,3); R.eye();

	R = R+(sin(theta)/theta)*omega+((1-cos(theta))/pow(theta,2))*(omega*omega);

	arma::Col<double> T = {calib_param(6), calib_param(7), calib_param(8)};

	arma::Mat<double> K = {{R(0,0), R(0,1), R(0,2), T(0)},
		 				{R(1,0), R(1,1), R(1,2), T(1)},
		 				{R(2,0), R(2,1), R(2,2), T(2)},
		 				{0, 0, 0, 1}};

	return arma::inv(K)*arma::pinv(C);

}

arma::Mat<double> FocalGrid::World2CameraMatrix(arma::Col<double> calib_param) {

	// return the world to camera projection matrix

	arma::Mat<double> C = {{calib_param(0), calib_param(2), 0, 0},
		 			{0, calib_param(1), 0, 0},
		 			{0, 0, 0, 1}};

	double theta = sqrt(pow(calib_param(3),2)+pow(calib_param(4),2)+pow(calib_param(5),2));

	arma::Mat<double> omega = {{0, -calib_param(5), calib_param(4)},
			 			{calib_param(5), 0, -calib_param(3)},
			 			{-calib_param(4), calib_param(3), 0}};

	arma::Mat<double> R(3,3); R.eye();

	R = R+(sin(theta)/theta)*omega+((1-cos(theta))/pow(theta,2))*(omega*omega);

	arma::Col<double> T = {calib_param(6), calib_param(7), calib_param(8)};

	arma::Mat<double> K = {{R(0,0), R(0,1), R(0,2), T(0)},
		 				{R(1,0), R(1,1), R(1,2), T(1)},
		 				{R(2,0), R(2,1), R(2,2), T(2)},
		 				{0, 0, 0, 1}};

	return C*K;

}


#ifndef OSRM_GUIDANCE_TOOLKIT_HPP_
#define OSRM_GUIDANCE_TOOLKIT_HPP_

#include "util/attributes.hpp"
#include "util/bearing.hpp"
#include "util/coordinate.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/guidance/toolkit.hpp"
#include "util/guidance/turn_lanes.hpp"
#include "util/typedefs.hpp"

#include "extractor/compressed_edge_container.hpp"
#include "extractor/query_node.hpp"
#include "extractor/suffix_table.hpp"

#include "extractor/guidance/discrete_angle.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/guidance/turn_instruction.hpp"

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <map>
#include <string>
#include <unordered_map>
#include <utility>

#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/functional/hash.hpp>
#include <boost/tokenizer.hpp>

namespace osrm
{
namespace extractor
{
namespace guidance
{

using util::guidance::LaneTupelIdPair;
using LaneDataIdMap = std::unordered_map<LaneTupelIdPair, LaneDataID, boost::hash<LaneTupelIdPair>>;

using util::guidance::angularDeviation;
using util::guidance::entersRoundabout;
using util::guidance::leavesRoundabout;

namespace detail
{
const constexpr double DESIRED_SEGMENT_LENGTH = 10.0;

template <typename IteratorType>
util::Coordinate
getCoordinateFromCompressedRange(util::Coordinate current_coordinate,
                                 const IteratorType compressed_geometry_begin,
                                 const IteratorType compressed_geometry_end,
                                 const util::Coordinate final_coordinate,
                                 const std::vector<extractor::QueryNode> &query_nodes)
{
    const auto extractCoordinateFromNode =
        [](const extractor::QueryNode &node) -> util::Coordinate {
        return {node.lon, node.lat};
    };
    double distance_to_current_coordinate = 0;
    double distance_to_next_coordinate = 0;

    // get the length that is missing from the current segment to reach DESIRED_SEGMENT_LENGTH
    const auto getFactor = [](const double first_distance, const double second_distance) {
        BOOST_ASSERT(first_distance < detail::DESIRED_SEGMENT_LENGTH);
        double segment_length = second_distance - first_distance;
        BOOST_ASSERT(segment_length > 0);
        BOOST_ASSERT(second_distance >= detail::DESIRED_SEGMENT_LENGTH);
        double missing_distance = detail::DESIRED_SEGMENT_LENGTH - first_distance;
        return std::max(0., std::min(missing_distance / segment_length, 1.0));
    };

    for (auto compressed_geometry_itr = compressed_geometry_begin;
         compressed_geometry_itr != compressed_geometry_end;
         ++compressed_geometry_itr)
    {
        const auto next_coordinate =
            extractCoordinateFromNode(query_nodes[compressed_geometry_itr->node_id]);
        distance_to_next_coordinate =
            distance_to_current_coordinate +
            util::coordinate_calculation::haversineDistance(current_coordinate, next_coordinate);

        // reached point where coordinates switch between
        if (distance_to_next_coordinate >= detail::DESIRED_SEGMENT_LENGTH)
            return util::coordinate_calculation::interpolateLinear(
                getFactor(distance_to_current_coordinate, distance_to_next_coordinate),
                current_coordinate,
                next_coordinate);

        // prepare for next iteration
        current_coordinate = next_coordinate;
        distance_to_current_coordinate = distance_to_next_coordinate;
    }

    distance_to_next_coordinate =
        distance_to_current_coordinate +
        util::coordinate_calculation::haversineDistance(current_coordinate, final_coordinate);

    // reached point where coordinates switch between
    if (distance_to_current_coordinate < detail::DESIRED_SEGMENT_LENGTH &&
        distance_to_next_coordinate >= detail::DESIRED_SEGMENT_LENGTH)
        return util::coordinate_calculation::interpolateLinear(
            getFactor(distance_to_current_coordinate, distance_to_next_coordinate),
            current_coordinate,
            final_coordinate);
    else
        return final_coordinate;
}
} // namespace detail

// Finds a (potentially interpolated) coordinate that is DESIRED_SEGMENT_LENGTH away
// from the start of an edge
inline util::Coordinate
getRepresentativeCoordinate(const NodeID from_node,
                            const NodeID to_node,
                            const EdgeID via_edge_id,
                            const bool traverse_in_reverse,
                            const extractor::CompressedEdgeContainer &compressed_geometries,
                            const std::vector<extractor::QueryNode> &query_nodes)
{
    const auto extractCoordinateFromNode =
        [](const extractor::QueryNode &node) -> util::Coordinate {
        return {node.lon, node.lat};
    };

    // Uncompressed roads are simple, return the coordinate at the end
    if (!compressed_geometries.HasEntryForID(via_edge_id))
    {
        return extractCoordinateFromNode(traverse_in_reverse ? query_nodes[from_node]
                                                             : query_nodes[to_node]);
    }
    else
    {
        const auto &geometry = compressed_geometries.GetBucketReference(via_edge_id);

        const auto base_node_id = (traverse_in_reverse) ? to_node : from_node;
        const auto base_coordinate = extractCoordinateFromNode(query_nodes[base_node_id]);

        const auto final_node = (traverse_in_reverse) ? from_node : to_node;
        const auto final_coordinate = extractCoordinateFromNode(query_nodes[final_node]);

        if (traverse_in_reverse)
            return detail::getCoordinateFromCompressedRange(
                base_coordinate, geometry.rbegin(), geometry.rend(), final_coordinate, query_nodes);
        else
            return detail::getCoordinateFromCompressedRange(
                base_coordinate, geometry.begin(), geometry.end(), final_coordinate, query_nodes);
    }
}

inline std::pair<std::string, std::string> getPrefixAndSuffix(const std::string &data)
{
    const auto suffix_pos = data.find_last_of(' ');
    if (suffix_pos == std::string::npos)
        return {};

    const auto prefix_pos = data.find_first_of(' ');
    auto result = std::make_pair(data.substr(0, prefix_pos), data.substr(suffix_pos + 1));
    boost::to_lower(result.first);
    boost::to_lower(result.second);
    return result;
}

inline bool requiresNameAnnounced(const std::string &from,
                                  const std::string &to,
                                  const SuffixTable &suffix_table)
{
    // first is empty and the second is not
    if (from.empty() && !to.empty())
        return true;

    // FIXME, handle in profile to begin with?
    // this uses the encoding of references in the profile, which is very BAD
    // Input for this function should be a struct separating streetname, suffix (e.g. road,
    // boulevard, North, West ...), and a list of references
    std::string from_name;
    std::string from_ref;
    std::string to_name;
    std::string to_ref;

    // Split from the format "{name} ({ref})" -> name, ref
    auto split = [](const std::string &name, std::string &out_name, std::string &out_ref) {
        const auto ref_begin = name.find_first_of('(');
        if (ref_begin != std::string::npos)
        {
            if (ref_begin != 0)
                out_name = name.substr(0, ref_begin - 1);
            const auto ref_end = name.find_first_of(')');
            out_ref = name.substr(ref_begin + 1, ref_end - ref_begin - 1);
        }
        else
        {
            out_name = name;
        }
    };

    split(from, from_name, from_ref);
    split(to, to_name, to_ref);

    // check similarity of names
    const auto names_are_empty = from_name.empty() && to_name.empty();
    const auto name_is_contained =
        boost::starts_with(from_name, to_name) || boost::starts_with(to_name, from_name);

    const auto checkForPrefixOrSuffixChange =
        [](const std::string &first, const std::string &second, const SuffixTable &suffix_table) {

            const auto first_prefix_and_suffixes = getPrefixAndSuffix(first);
            const auto second_prefix_and_suffixes = getPrefixAndSuffix(second);
            // reverse strings, get suffices and reverse them to get prefixes
            const auto checkTable = [&](const std::string str) {
                return str.empty() || suffix_table.isSuffix(str);
            };

            const bool is_prefix_change = [&]() -> bool {
                if (!checkTable(first_prefix_and_suffixes.first))
                    return false;
                if (!checkTable(first_prefix_and_suffixes.first))
                    return false;
                return !first.compare(first_prefix_and_suffixes.first.length(),
                                      std::string::npos,
                                      second,
                                      second_prefix_and_suffixes.first.length(),
                                      std::string::npos);
            }();

            const bool is_suffix_change = [&]() -> bool {
                if (!checkTable(first_prefix_and_suffixes.second))
                    return false;
                if (!checkTable(first_prefix_and_suffixes.second))
                    return false;
                return !first.compare(0,
                                      first.length() - first_prefix_and_suffixes.second.length(),
                                      second,
                                      0,
                                      second.length() - second_prefix_and_suffixes.second.length());
            }();

            return is_prefix_change || is_suffix_change;
        };

    const auto is_suffix_change = checkForPrefixOrSuffixChange(from_name, to_name, suffix_table);
    const auto names_are_equal = from_name == to_name || name_is_contained || is_suffix_change;
    const auto name_is_removed = !from_name.empty() && to_name.empty();
    // references are contained in one another
    const auto refs_are_empty = from_ref.empty() && to_ref.empty();
    const auto ref_is_contained =
        from_ref.empty() || to_ref.empty() ||
        (from_ref.find(to_ref) != std::string::npos || to_ref.find(from_ref) != std::string::npos);
    const auto ref_is_removed = !from_ref.empty() && to_ref.empty();

    const auto obvious_change =
        (names_are_empty && refs_are_empty) || (names_are_equal && ref_is_contained) ||
        (names_are_equal && refs_are_empty) || (ref_is_contained && name_is_removed) ||
        (names_are_equal && ref_is_removed) || is_suffix_change;

    return !obvious_change;
}

// To simplify handling of Left/Right hand turns, we can mirror turns and write an intersection
// handler only for one side. The mirror function turns a left-hand turn in a equivalent right-hand
// turn and vice versa.
OSRM_ATTR_WARN_UNUSED
inline ConnectedRoad mirror(ConnectedRoad road)
{
    const constexpr DirectionModifier::Enum mirrored_modifiers[] = {DirectionModifier::UTurn,
                                                                    DirectionModifier::SharpLeft,
                                                                    DirectionModifier::Left,
                                                                    DirectionModifier::SlightLeft,
                                                                    DirectionModifier::Straight,
                                                                    DirectionModifier::SlightRight,
                                                                    DirectionModifier::Right,
                                                                    DirectionModifier::SharpRight};

    if (angularDeviation(road.turn.angle, 0) > std::numeric_limits<double>::epsilon())
    {
        road.turn.angle = 360 - road.turn.angle;
        road.turn.instruction.direction_modifier =
            mirrored_modifiers[road.turn.instruction.direction_modifier];
    }
    return road;
}

inline bool hasRoundaboutType(const TurnInstruction instruction)
{
    using namespace extractor::guidance::TurnType;
    const constexpr TurnType::Enum valid_types[] = {TurnType::EnterRoundabout,
                                                    TurnType::EnterAndExitRoundabout,
                                                    TurnType::EnterRotary,
                                                    TurnType::EnterAndExitRotary,
                                                    TurnType::EnterRoundaboutIntersection,
                                                    TurnType::EnterAndExitRoundaboutIntersection,
                                                    TurnType::EnterRoundaboutAtExit,
                                                    TurnType::ExitRoundabout,
                                                    TurnType::EnterRotaryAtExit,
                                                    TurnType::ExitRotary,
                                                    TurnType::EnterRoundaboutIntersectionAtExit,
                                                    TurnType::ExitRoundaboutIntersection,
                                                    TurnType::StayOnRoundabout};
    const auto valid_end = valid_types + 13;
    return std::find(valid_types, valid_end, instruction.type) != valid_end;
}

// Public service vehicle lanes and similar can introduce additional lanes into the lane string that
// are not specifically marked for left/right turns. This function can be used from the profile to
// trim the lane string appropriately
//
// left|throught|
// in combination with lanes:psv:forward=1
// will be corrected to left|throught, since the final lane is not drivable.
// This is in contrast to a situation with lanes:psv:forward=0 (or not set) where left|through|
// represents left|through|through
OSRM_ATTR_WARN_UNUSED
inline std::string
trimLaneString(std::string lane_string, std::int32_t count_left, std::int32_t count_right)
{
    if (count_left)
    {
        bool sane = count_left < static_cast<std::int32_t>(lane_string.size());
        for (std::int32_t i = 0; i < count_left; ++i)
            // this is adjusted for our fake pipe. The moment cucumber can handle multiple escaped
            // pipes, the '&' part can be removed
            if (lane_string[i] != '|' && lane_string[i] != '&')
            {
                sane = false;
                break;
            }

        if (sane)
        {
            lane_string.erase(lane_string.begin(), lane_string.begin() + count_left);
        }
    }
    if (count_right)
    {
        bool sane = count_right < static_cast<std::int32_t>(lane_string.size());
        for (auto itr = lane_string.rbegin();
             itr != lane_string.rend() && itr != lane_string.rbegin() + count_right;
             ++itr)
        {
            if (*itr != '|' && *itr != '&')
            {
                sane = false;
                break;
            }
        }
        if (sane)
            lane_string.resize(lane_string.size() - count_right);
    }
    return lane_string;
}

// https://github.com/Project-OSRM/osrm-backend/issues/2638
// It can happen that some lanes are not drivable by car. Here we handle this tagging scheme
// (vehicle:lanes) to filter out not-allowed roads
// lanes=3
// turn:lanes=left|through|through|right
// vehicle:lanes=yes|yes|no|yes
// bicycle:lanes=yes|no|designated|yes
OSRM_ATTR_WARN_UNUSED
inline std::string applyAccessTokens(std::string lane_string, const std::string &access_tokens)
{
    typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
    boost::char_separator<char> sep("|", "", boost::keep_empty_tokens);
    tokenizer tokens(lane_string, sep);
    tokenizer access(access_tokens, sep);

    // strings don't match, don't do anything
    if (std::distance(std::begin(tokens), std::end(tokens)) !=
        std::distance(std::begin(access), std::end(access)))
        return lane_string;

    std::string result_string = "";
    const static std::string yes = "yes";

    for (auto token_itr = std::begin(tokens), access_itr = std::begin(access);
         token_itr != std::end(tokens);
         ++token_itr, ++access_itr)
    {
        if (*access_itr == yes)
        {
            // we have to add this in front, because the next token could be invalid. Doing this on
            // non-empty strings makes sure that the token string will be valid in the end
            if (!result_string.empty())
                result_string += '|';

            result_string += *token_itr;
        }
    }
    return result_string;
}

} // namespace guidance
} // namespace extractor
} // namespace osrm

#endif // OSRM_GUIDANCE_TOOLKIT_HPP_