/*
 * Copyright (C) Volition, Inc. 1999.  All rights reserved.
 *
 * All source code herein is the property of Volition, Inc. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/ 



#ifndef _TIMER_H
#define _TIMER_H

#include "globalincs/pstypes.h"

#include "utils/id.h"
#include "utils/Random.h"

#include <cstdint>

using Random = util::Random;

// Empty structs used as type-safe markers.
struct ui_timestamp_tag {};
struct timestamp_tag {};

// These classes use the "strong typedef" pattern, based on
// similar usage in gamesnd, based in turn on this article:
// https://www.ilikebigbits.com/2014_05_06_type_safe_handles.html
// 
// Do not add additional fields or member functions to these classes
// (such as the functionality in timestamp_delta).  These classes must
// remain lightweight so that they can be optimized away.

// Timestamp measuring real time.  Runs regardless of time compression or pausing.
// Useful for timing real-world events and UI features like cursor blinking.
class UI_TIMESTAMP : public util::ID<ui_timestamp_tag, int, -1>
{
public:
	static UI_TIMESTAMP invalid() { return {}; }
	static UI_TIMESTAMP never() { return UI_TIMESTAMP(0); }
	static UI_TIMESTAMP immediate() { return UI_TIMESTAMP(1); }

	UI_TIMESTAMP() = default;
	explicit UI_TIMESTAMP(int val) : ID(val) { }

	inline bool isNever() const { return m_val == 0; }
	inline bool isImmediate() const { return m_val == 1; }
	inline bool isFinite() const { return isValid() && !isNever(); }
};

// Timestamp measuring game time.  Sensitive to time compression and pausing.
// Useful for timing in-mission events.
class TIMESTAMP : public util::ID<timestamp_tag, int, -1>
{
public:
	static TIMESTAMP invalid() { return {}; }
	static TIMESTAMP never() { return TIMESTAMP(0); }
	static TIMESTAMP immediate() { return TIMESTAMP(1); }

	TIMESTAMP() = default;
	explicit TIMESTAMP(int val) : ID(val) { }

	inline bool isNever() const { return m_val == 0; }
	inline bool isImmediate() const { return m_val == 1; }
	inline bool isFinite() const { return isValid() && !isNever(); }
};

// For converting from old-style timestamps:
//   A timestamp of 0 is invalid
//   A timestamp of 1 elapses immediately
//   A timestamp of 2 or more corresponds to that value of the timestamp timer
// See also the implementation of timestamp(int).  Also be careful not to confuse these values
// with the deltas; timestamp(-1) returns 0 and timestamp(0) returns 1.  But timestamp(1)
// returns the current value of timestamp_ms() plus 1 millisecond.  To add further confusion,
// legacy code often used -1 rather than 0 to indicate an invalid timestamp.


//==========================================================================
// This installs the timer services and interrupts at the rate specified by
// count_val.  If 'function' isn't 0, the function pointed to by function will
// be called 'freq' times per second.  Should be > 19 and anything around
// 2-3000 is gonna start slowing down the system.  Count_val should be
// 1,193,180 divided by your target frequency. Use 0 for the normal 18.2 Hz
// interrupt rate.

#ifdef _WIN32
#define TIMER_FREQUENCY 1193180
#endif

extern void timer_init();
extern void timer_close();
extern void timer_start_frame();

//==========================================================================
// These functions return the time since the timer was initialized in
// some various units. The total length of reading time varies for each
// one.  They will roll around after they read 2^32.
// There are milliseconds, milliseconds times 10, milliseconds times 100,
// and microseconds.  They time out after 1000 hrs, 100 hrs, 10 hrs, and
// 1 hr, respectively.

extern fix timer_get_fixed_seconds();		// Rolls about every 9 hours...
extern fix timer_get_approx_seconds();		// Returns time since program started... accurate to 1/120th of a second
extern int timer_get_milliseconds();		//
extern std::uint64_t timer_get_microseconds();
extern std::uint64_t timer_get_nanoseconds();
extern int timer_get_seconds();				// seconds since program started... not accurate, but good for long
											//     runtimes with second-based timeouts


//=================================================================
//=================================================================
//               T I M E S T A M P   F U N C T I O N S
//=================================================================
//=================================================================

// You shouldn't use the output of timestamp() directly, 
// but if you have to, use the TIMESTAMP_FREQUENCY to 
// scale it correctly.
// TODO: remove when nobody uses the output directly
#define TIMESTAMP_FREQUENCY 1000

// conversion factors
constexpr int MILLISECONDS_PER_SECOND = 1000;
constexpr int MICROSECONDS_PER_MILLISECOND = 1000;
constexpr int NANOSECONDS_PER_MICROSECOND = 1000;
constexpr uint64_t MICROSECONDS_PER_SECOND = MICROSECONDS_PER_MILLISECOND * MILLISECONDS_PER_SECOND;
constexpr uint64_t NANOSECONDS_PER_SECOND = NANOSECONDS_PER_MICROSECOND * MICROSECONDS_PER_SECOND;

// use this call to get the current counter value (which represents the time at the time
// this function is called).  I.e. it doesn't return a count that would be in the future,
// but the count that is right now.
int timestamp();
TIMESTAMP _timestamp();					// use a leading underscore for now until all timestamps are converted over

// same, but for use in the UI, so not subject to time compression or pauses
UI_TIMESTAMP ui_timestamp();

inline bool timestamp_valid(int stamp) {
	return stamp > 0;	// 0 is the "official" invalid for legacy timestamps, but -1 is used in a lot of places too
}

// To do timing, call this with the interval you
// want to check.  Then, pass this to timestamp_elapsed
// to see if delta_ms time has elapsed.   If delta_ms is
// zero, the next call to timestamp_elapsed will always
// return 1.  If delta_ms is less than zero, then this is
// considered an invalid timestamp and all calls to 
// timestamp_elapsed will return 0.
// In other words:
// pass -1 for an invalid timestamp that will never time out
// pass 0 for a timestamp that is instantly timed out
// pass n > 0 for timestamp n milliseconds in the future.
int timestamp(int delta_ms);
TIMESTAMP _timestamp(int delta_ms);		// use a leading underscore for now until all timestamps are converted over
UI_TIMESTAMP ui_timestamp(int delta_ms);

TIMESTAMP timestamp_delta(TIMESTAMP stamp, int delta_ms);
UI_TIMESTAMP ui_timestamp_delta(UI_TIMESTAMP stamp, int delta_ms);

int timestamp_get_delta(TIMESTAMP before, TIMESTAMP after);
int ui_timestamp_get_delta(UI_TIMESTAMP before, UI_TIMESTAMP after);

// gets a timestamp randomly between a and b milliseconds in
// the future.
inline int timestamp_rand(int a, int b) {
	return timestamp(Random::next(a, b));
}
inline TIMESTAMP _timestamp_rand(int a, int b) {
	return _timestamp(Random::next(a, b));
}

//	Returns milliseconds until timestamp will elapse.  Invalid timestamps are assumed to occur at approximately T=0.
int timestamp_until(int stamp);
//	Returns milliseconds until timestamp will elapse.  This will Assert against Invalid or Never timestamps but fail gracefully by returning INT_MAX.
int timestamp_until(TIMESTAMP stamp);
//	Returns milliseconds until timestamp will elapse.  This will Assert against Invalid or Never timestamps but fail gracefully by returning INT_MAX.
int ui_timestamp_until(UI_TIMESTAMP stamp);

//	Returns milliseconds after timestamp has elapsed.  Invalid timestamps are assumed to occur at approximately T=0.
int timestamp_since(int stamp);
//	Returns milliseconds after timestamp has elapsed.  This will Assert against Invalid or Never timestamps but fail gracefully by returning INT_MIN.
int timestamp_since(TIMESTAMP stamp);
//	Returns milliseconds after timestamp has elapsed.  This will Assert against Invalid or Never timestamps but fail gracefully by returning INT_MIN.
int ui_timestamp_since(UI_TIMESTAMP stamp);

// A standard comparison function that returns <0, 0, or >0 as the left item is less than, equal to, or greater than the right item.
int timestamp_compare(TIMESTAMP t1, TIMESTAMP t2);
// A standard comparison function that returns <0, 0, or >0 as the left item is less than, equal to, or greater than the right item.
int ui_timestamp_compare(UI_TIMESTAMP t1, UI_TIMESTAMP t2);

// Checks that a timestamp occurs between the "before" and "after" timestamps.
bool timestamp_in_between(TIMESTAMP stamp, TIMESTAMP before, TIMESTAMP after);
// Checks that a timestamp occurs between the "before" and "after" timestamps.
bool ui_timestamp_in_between(UI_TIMESTAMP stamp, UI_TIMESTAMP before, UI_TIMESTAMP after);

// Example that makes a ship fire in 1/2 second

// ...
// ship->next_fire = timestamp(500);
// ...
// if (fire && timestamp_elapsed(ship->next_fire))
//   fire_laser();

bool timestamp_elapsed( int stamp );
bool timestamp_elapsed( TIMESTAMP stamp );
bool timestamp_elapsed_last_frame( TIMESTAMP stamp );
bool ui_timestamp_elapsed( UI_TIMESTAMP stamp );
bool ui_timestamp_elapsed_last_frame( UI_TIMESTAMP stamp );

// safer version of timestamp
bool timestamp_elapsed_safe(int a, int b);
bool timestamp_elapsed_safe(TIMESTAMP a, int b);
bool ui_timestamp_elapsed_safe(UI_TIMESTAMP a, int b);

//=================================================================
//               T I M E S T A M P   A D J U S T M E N T
//=================================================================

// This should be called when the timestamp should stop ticking, e.g when the game is paused.
// The "sudo" is for cases where we want the time to remain paused, e.g. during level loading,
//     even if the game loses focus (which would normally unpause when focus is regained)
void timestamp_pause(bool sudo);

// This should be called when the timestamp should resume ticking, e.g. when the player is in-mission.
// See above re: the sudo parameter
void timestamp_unpause(bool sudo);

// for timestamp handling during debugging
bool timestamp_is_paused();
void timestamp_adjust_pause_offset(int delta_milliseconds);

enum class TIMER_DIRECTION { FORWARD, BACKWARD };
// Alters the timestamp time forward or backward.  Use with caution!
void timestamp_adjust_seconds(float delta_seconds, TIMER_DIRECTION dir);
void timestamp_adjust_microseconds(uint64_t delta_microseconds, TIMER_DIRECTION dir);

// This should be called when the game time compression is changed in any way, so that
// the timestamp will be consistent with the faster or slower time.
void timestamp_update_time_compression();

//=================================================================
//               M I S S I O N   T I M E
//=================================================================

// Save the timestamp corresponding to the beginning of the mission
void timestamp_start_mission();

// Calculate the current mission time using the timestamps
fix timestamp_get_mission_time();
uint64_t timestamp_get_mission_time_in_microseconds();
int timestamp_get_mission_time_in_milliseconds();

// Advance mission time by specific offset (for in-game joining)
void timestamp_offset_mission_time(float offset);

#endif
