

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of CMSDK version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  and is released under the following
//_/_/  
//_/_/  EXTENDED BSD License
//_/_/
//_/_/  Redistribution and use in source and binary forms, with or without
//_/_/  modification, is permitted provided that the following conditions 
//_/_/  are met:
//_/_/
//_/_/  - Redistributions of source code must retain the above copyright 
//_/_/    and collaboration notice, this list of conditions and the following 
//_/_/    disclaimer.
//_/_/
//_/_/  - Redistributions in binary form must reproduce the above copyright 
//_/_/    notice, this list of conditions and the following disclaimer in the 
//_/_/    documentation and/or other materials provided with the distribution.
//_/_/
//_/_/  - Neither the name of its copyright holders nor the names of its 
//_/_/    contributors may be used to endorse or promote products derived from 
//_/_/    this software without specific prior written permission.
//_/_/
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*! @file PsyTime.h
	@brief CMSDK time: µs-resolution 64-bit timestamps and the Time Mapping Constant (TMC).

	All Psyclone timestamps are unsigned 64-bit integers counting *microseconds
	since year 0000* (proleptic Gregorian); the constant USEC_YEAR_0_TO_1970 is the
	offset of the Unix epoch in this scheme, so unix_us = psytime - USEC_YEAR_0_TO_1970.
	A uint64 of µs-since-year-0 does not overflow for ~584,000 years, so wrap-around
	of timestamps themselves is not a practical concern; EstNextTMCWrap() instead
	estimates the wrap of the underlying hardware counter.

	The TMC (Time Mapping Constant) maps the machine's monotonic hardware clock to
	this absolute timescale: GetTimeNow() = monotonic_reading + CurrentTMC
	(+ NetTimeAdjust when slaved to a remote master). SyncToHardwareClock()
	recomputes the TMC from the wall clock; slave nodes adjust via
	SetCurrentNetSyncDif() so that all nodes in a distributed system share one
	timeline. Platform note: on Windows the monotonic source is
	QueryPerformanceCounter/Frequency; on POSIX (Linux/macOS) it is
	clock_gettime(CLOCK_MONOTONIC) paired with gettimeofday() for the wall-clock
	anchor.
*/
#if !defined(_PSYTIME_H_)
#define _PSYTIME_H_

//#include "MemoryManager.h"
#include "Types.h"
#include "sys/timeb.h"

namespace cmlabs {

/** @defgroup PsyTime Functions dealing with time
*  The CMSDK time layer: µs-resolution 64-bit timestamps, one shared timeline
*  for every thread, process and machine in a Psyclone system.
*
*  @par Why its own time system
*  Timestamps are stamped into every @ref DataMessage, stored in shared-memory
*  segments and sent over the network, so they must be a fixed-width wire type
*  that means the same thing everywhere: an unsigned 64-bit count of
*  microseconds since year 0000 (proleptic Gregorian). A uint64 of µs does not
*  wrap for ~584,000 years, comparisons and arithmetic are plain integer ops,
*  and unix_us = psytime - USEC_YEAR_0_TO_1970 converts to/from the Unix epoch.
*
*  @par Why not std::chrono or raw OS calls
*  std::chrono clocks have implementation-defined epochs and representations,
*  so their values cannot be written into a shared-memory struct or a network
*  packet and read by another binary; raw OS clocks differ per platform
*  (QueryPerformanceCounter/GetSystemTime on Windows,
*  clock_gettime/gettimeofday on POSIX) and wall clocks can step backwards
*  under NTP. PsyTime instead reads the machine's @e monotonic clock and maps
*  it onto the absolute timescale with the Time Mapping Constant (TMC):
*  GetTimeNow() = monotonic + CurrentTMC (+ NetTimeAdjust when this node is a
*  TMC_SLAVE synced to a remote master) — giving timestamps that are both
*  absolute (calendar-meaningful) and monotonic between re-syncs, and that
*  agree across all nodes of a distributed system.
*
*  @par Measuring an interval and formatting timestamps
*  @code
*  uint64 start = GetTimeNow();               // µs since year 0
*  DoSomeWork();
*  int64 elapsed = GetTimeAge(start);         // now - start, in µs
*  printf("took %s\n", PrintTimeDifString(elapsed).c_str()); // "1.500ms", "2m 3s", ...
*
*  uint64 deadline = start + 5 * PSYSECOND;   // duration constants in µs
*  if (GetTimeNow() > deadline) { }           // plain integer comparison
*
*  std::string stamp    = PrintTimeString(start);              // "21/07/2026 15:30:02.123.456"
*  std::string sortable = PrintTimeSortableMillisecString(start); // "20260721-153002.123"
*  std::string logdate  = PrintDateStringSortableDelimiter(start, "-"); // "2026-07-21"
*  uint64 parsed = GetTimeFromString("Sun, 06 Nov 1994 08:49:37 GMT");  // RFC dates too
*  @endcode
*  For calendar fields use GetDateAndTime() / GetTimeFromPsyDateAndTime();
*  see PsyDateAndTime for the 1-based day/month conventions.
*
*  @note Prefer the std::string Print*String() formatters; the char* variants
*  return a `new char[]` buffer the caller must `delete []`.
*
*  @warning Durations are unsigned in most APIs: subtracting a later timestamp
*  from an earlier one underflows. Use GetTimeAge()/GetTimeDifference() for
*  signed differences. See the file documentation in PsyTime.h for TMC
*  synchronisation details and per-translation-unit state caveats.
*  @{
*/

/*! Microseconds between year 0000 and the Unix epoch (1970-01-01). Subtract to convert PsyTime → Unix µs. */
#define USEC_YEAR_0_TO_1970 62167305600000000L
/*! Seconds in a (non-leap) 365-day year. */
#define SECS_PER_YEAR 31536000
/*! Average seconds per Gregorian month (365.2425/12 days). */
#define SECS_PER_MONTH 2629744
#define SECS_PER_WEEK 604800 //!< Seconds per week.
#define SECS_PER_DAY 86400   //!< Seconds per day.
#define SECS_PER_HOUR 3600   //!< Seconds per hour.
#define SECS_PER_MIN 60      //!< Seconds per minute.
/*! Same value as USEC_YEAR_0_TO_1970: the PsyTime timestamp of 1970-01-01 00:00:00 UTC. */
#define TIME_YEAR_1970 62167305600000000L

/*! @name Millisecond duration constants
	Common durations expressed in milliseconds. @warning MINUTE_MS..WEEK_MS expand as
	`SECS_PER_X * 1000` without parentheses — beware when used inside larger expressions.
	@{ */
#define SECOND_MS	1000
#define MINUTE_MS	SECS_PER_MIN * 1000
#define HOUR_MS		SECS_PER_HOUR * 1000
#define DAY_MS		SECS_PER_DAY * 1000
#define WEEK_MS		SECS_PER_WEEK * 1000
#define MONTH_MS	(uint64)SECS_PER_MONTH * 1000
#define YEAR_MS		(uint64)SECS_PER_YEAR * 1000
/*! @} */

/*! @name Microsecond duration constants
	Common durations expressed in microseconds — the native PsyTime unit.
	@{ */
#define SECOND_US	(uint64)1000000
#define MINUTE_US	(uint64)SECS_PER_MIN * 1000000
#define HOUR_US		(uint64)SECS_PER_HOUR * 1000000
#define DAY_US		(uint64)SECS_PER_DAY * 1000000
#define WEEK_US		(uint64)SECS_PER_WEEK * 1000000
#define MONTH_US	(uint64)SECS_PER_MONTH * 1000000
#define YEAR_US		(uint64)SECS_PER_YEAR * 1000000
/*! @} */

/*! @name PSY* duration aliases
	Preferred spelling of the µs duration constants in application code, e.g. `5*PSYSECOND`.
	@{ */
#define PSYSECOND	SECOND_US
#define PSYMINUTE	MINUTE_US
#define PSYHOUR		HOUR_US
#define PSYDAY		DAY_US
#define PSYWEEK		WEEK_US
#define PSYMONTH	MONTH_US
#define PSYYEAR		YEAR_US
/*! @} */

//! Abbreviated weekday names indexed 1..7 (Mon..Sun); index 0 is "None".
static char PsyDays[][5] = {"None", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
//! Full weekday names indexed 1..7 (Monday..Sunday); index 0 is "None".
static char PsyDaysFull[][10] = {"None", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
//! Abbreviated month names indexed 1..12 (Jan..Dec); index 0 is "None".
static char PsyMonths[][5] = {"None", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
//! Full month names indexed 1..12; index 0 is "None".
static char PsyMonthsFull[][10] = {"None", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};

/*! @brief A PsyTime timestamp or time difference broken down into calendar fields.

	Equivalent of `struct tm` for the PsyTime timescale, extended with sub-second
	fields and timezone/DST information. Produced by GetDateAndTime(),
	GetDateAndTimeUTC() and GetTimeDifference(); converted back with
	GetTimeFromPsyDateAndTime(). Note the 1-based conventions for day-of-week,
	month and day-of-year (index 0 means "none" in the name tables above).
*/
struct PsyDateAndTime {
	uint32 usec;      /* microseconds after the minute - [0,999999] */
	uint32 msec;      /* milliseconds after the minute - [0,999] */
	uint16 sec;       /* seconds after the minute - [0,59] */
	uint16 min;       /* minutes after the hour - [0,59] */
	uint16 hour;      /* hours since midnight - [0,23] */
	uint16 day;      /* day of the month - [1,31] */
	uint16 mon;       /* months since January - [1,12] */
	uint16 year;      /* years since 0000 */
	uint16 wday;      /* day of the week - [1,7] */
	uint16 yday;      /* day of the year - [1,366] */
	bool negative;    /* is the time value negative */
	bool dst;         /* daylight savings in effect */
	int32 dstsec;     /* daylight savings in minutes when in effect */
	int32 tzsec;	  /* timezone in sec */
	char zonename[64];  /* the name of the current time zone */
};

/*! Return the current absolute time (µs since year 0) according to the TMC.
	Reads the platform monotonic clock (QueryPerformanceCounter on Windows,
	CLOCK_MONOTONIC on POSIX) and adds CurrentTMC plus any network adjustment.

	Because timestamps are plain uint64 microseconds, time arithmetic is ordinary
	integer arithmetic using the PSY* duration constants:
	@code
	uint64 now      = GetTimeNow();
	uint64 deadline = now + 5 * PSYSECOND;          // 5 s from now
	uint64 anHourAgo = now - PSYHOUR;
	if (GetTimeAge(msg->getCreatedTime()) > 2 * PSYMINUTE)
		; // message is older than two minutes
	printf("%s\n", PrintTimeString(now).c_str());   // "21/07/2026 15:30:02.123.456"
	printf("%s\n", PrintTimeSortableString(now).c_str()); // "20260721-153002"
	@endcode

	@return Current PsyTime timestamp in microseconds.
	@note Fast and non-blocking; safe to call from any thread.
*/
uint64 GetTimeNow();

/*! Recompute the Time Mapping Constant by anchoring the monotonic clock to the wall clock.
	Waits for a wall-clock tick edge for accuracy (gettimeofday loop on POSIX).
	@return The new TMC value (also stored in CurrentTMC).
	@note May block briefly (up to one clock tick). Call sparingly — typically once at startup and at TMC_SYNC_INTERVAL.
*/
uint64 SyncToHardwareClock();
/*! Set the network time difference for a slave node so its GetTimeNow() matches the master.
	@param netTimeDif Signed offset in µs between this node and the time master.
	@return true on success.
*/
bool SetCurrentNetSyncDif(int64 netTimeDif);
/*! Install externally computed sync data (e.g. received from the time master).
	@param tmc Time Mapping Constant to use.
	@param netTimeAdjust Signed network adjustment in µs.
	@return true on success.
*/
bool SetCurrentTimeSyncData(uint64 tmc, int64 netTimeAdjust);
/*! Read the current sync data.
	@param tmc Receives the current Time Mapping Constant.
	@param netTimeAdjust Receives the current network adjustment in µs.
	@return true on success.
*/
bool GetCurrentTimeSyncData(uint64& tmc, int64& netTimeAdjust);
//bool SetLocalSyncAdjustment(int64 adjust);
//bool ChangeLocalSyncAdjustment(int64 adjust);

/*! @name TMC roles and sync interval
	@{ */
#define TMC_MASTER 0 //!< This node is the time master; others sync to it.
#define TMC_SLAVE 1  //!< This node slaves its clock to a remote master.
#define TMC_SYNC_INTERVAL 60000000	//!< Re-sync interval in µs (1 minute).
/*! @} */

//! Current Time Mapping Constant (µs offset from monotonic clock to absolute PsyTime). @note file-static: each translation unit gets its own copy.
static uint64 CurrentTMC = 0;
//! Current TMC role: TMC_MASTER or TMC_SLAVE.
static uint8 CurrentTMCMode = TMC_MASTER;
//! PsyTime timestamp of the last hardware-clock sync.
static uint64 LastTMCSync = 0;
//! Signed network time adjustment (µs) applied when slaved to a remote master.
static int64 NetTimeAdjust = 0;

/*! Estimate when the underlying hardware counter will next wrap around.
	@return Estimated PsyTime timestamp of the next hardware-counter wrap.
*/
uint64 EstNextTMCWrap();

/*! Age of a timestamp relative to now.
	@param t Timestamp to compare (0 returns 0).
	@return now - t in µs; negative when @a t is in the future.
*/
int64 GetTimeAge(uint64 t);
/*! Age of a timestamp relative to now, in milliseconds.
	@param t Timestamp to compare (0 returns 0).
	@return now - t in ms; negative when @a t is in the future.
*/
int32 GetTimeAgeMS(uint64 t);
/*! Parse a textual date/time into a PsyTime timestamp.
	Accepted formats (tried in order): RFC 822/1123 ("Sun, 06 Nov 1994 08:49:37 GMT"),
	RFC 850 ("Sunday, 06-Nov-94 08:49:37 GMT"), ANSI C asctime()
	("Sun Nov  6 08:49:37 1994"), and the sortable forms produced by the
	PrintTimeSortable* functions ("YYYYMMDD-HHMMSS[.mmm[.uuu]]") — sortable
	input is interpreted as GMT.
	@param str Date/time string.
	@return Timestamp in µs, or 0 on parse failure.
*/
uint64 GetTimeFromString(const char* str);
/*! Format a timestamp as an HTTP-date (RFC 7231) string, e.g. for HTTP headers.
	@param time Timestamp to format.
	@param buffer Destination buffer.
	@param size Size of @a buffer in bytes.
	@return Number of characters written.
*/
uint32 GetHTTPTime(uint64 time, char* buffer, uint32 size);
/*! Convert a broken-down PsyDateAndTime back into a µs timestamp.
	@param tad Broken-down date/time (fields as documented in PsyDateAndTime).
	@return Timestamp in µs since year 0.
*/
uint64 GetTimeFromPsyDateAndTime(struct PsyDateAndTime &tad);

/*! @return The local timezone's offset from GMT/UTC, in seconds. */
uint32 GetTimeOffsetGMT();

/*! Break a timestamp into calendar fields.
	@param t Timestamp in µs since year 0.
	@param local true for local time (applies timezone/DST), false for UTC.
	@return Filled PsyDateAndTime.
*/
struct PsyDateAndTime GetDateAndTime(uint64 t, bool local = true);
/*! Break a timestamp into calendar fields in UTC. @param t Timestamp in µs. */
struct PsyDateAndTime GetDateAndTimeUTC(uint64 t);

/*! Express the difference t1 - t2 as calendar fields (negative flag set when t2 > t1).
	@param t1 First timestamp. @param t2 Second timestamp.
	@return Difference as a PsyDateAndTime (duration semantics).
*/
struct PsyDateAndTime GetTimeDifference(uint64 t1, uint64 t2);
/*! Express a signed µs difference as calendar fields. @param dif Difference in µs. */
struct PsyDateAndTime GetTimeDifference(int64 dif);

/*! @name char* time formatters
	Format timestamps into newly allocated char buffers.
	@warning Ownership: each of these allocates a fresh 1024-byte buffer with
	`new char[]` and returns it — the CALLER must `delete []` the result or it
	leaks. Prefer the std::string variants below, which have no such footgun.
	Common parameters: @a t timestamp in µs; @a local = local time vs UTC;
	@a us / @a ms = include microseconds / milliseconds in the output.
	@{ */
char* PrintTime(uint64 t, bool local = true, bool us = true, bool ms = true);          //!< Full date + time-of-day.
char* PrintTimeOnly(uint64 t, bool local = true, bool us = true, bool ms = true);      //!< Time-of-day only.
char* PrintTimeDif(uint64 t, bool us = true, bool ms = true);                          //!< Format a µs duration with scale-adaptive units: "250us", "1.500ms", "2.750s", "2m 3s", "01:02:03", "3 days 01:02:03".
char* PrintDate(uint64 t, bool local = true);                                          //!< Date only, human-readable.
char* PrintDateSortable(uint64 t, bool local = true);                                  //!< Date only, sortable (e.g. YYYY-MM-DD).
char* PrintDateSortableDelimiter(uint64 t, const char* del, bool local = true);        //!< Sortable date with custom field delimiter.

char* PrintTimeSortable(uint64 t, bool local);          //!< Sortable date+time, second resolution.
char* PrintTimeSortableMillisec(uint64 t, bool local);  //!< Sortable date+time with milliseconds.
char* PrintTimeSortableMicrosec(uint64 t, bool local);  //!< Sortable date+time with microseconds.
/*! @} */

/*! @name std::string time formatters
	Thread-safe, leak-free equivalents of the char* formatters above (they wrap
	the char* variants and free the buffer); same parameter conventions
	(@a t µs timestamp, @a local, @a us, @a ms). Preferred in application code.

	@code
	uint64 t = GetTimeNow();
	std::string full     = PrintTimeString(t);              // "21/07/2026 15:30:02.123.456"
	std::string clock    = PrintTimeOnlyString(t, true, false, true); // "15:30:02.123"
	std::string sortable = PrintTimeSortableMillisecString(t);        // "20260721-153002.123"
	std::string logname  = PrintDateStringSortableDelimiter(t, "-");  // "2026-07-21"
	std::string age      = PrintTimeDifString(GetTimeAge(msgTime));   // e.g. "2m 3s"
	@endcode
	@{ */
std::string PrintTimeNowString(bool local = true, bool us = true, bool ms = true);     //!< Format GetTimeNow().
std::string PrintTimeString(uint64 t, bool local = true, bool us = true, bool ms = true);
std::string PrintTimeOnlyString(uint64 t, bool local = true, bool us = true, bool ms = true);
std::string PrintTimeDifString(uint64 t, bool us = true, bool ms = true);
std::string PrintDateString(uint64 t, bool local = true);
std::string PrintDateStringSortable(uint64 t, bool local = true);
std::string PrintDateStringSortableDelimiter(uint64 t, const char* del, bool local = true);

std::string PrintTimeSortableString(uint64 t, bool local = true);         //!< Sortable date+time, second resolution.
std::string PrintTimeSortableMillisecString(uint64 t, bool local = true); //!< Sortable date+time with milliseconds.
std::string PrintTimeSortableMicrosecString(uint64 t, bool local = true); //!< Sortable date+time with microseconds.
/*! @} */

/*! Convert an ftime-style value (ms since Unix epoch) to a PsyTime µs timestamp.
	@param t Milliseconds since 1970-01-01.
	@return µs since year 0 (adds USEC_YEAR_0_TO_1970).
*/
uint64 FTime2PsyTime(uint64 t);

/*! Run the PsyTime unit tests. @return true if all tests pass. */
bool PsyTime_UnitTest();

/** @} */ // end of time

} // namespace cmlabs

#endif //_PSYTIME_H_

