module std::time;
import std::io, std::time::os;

typedef Time @structlike = long;
typedef Duration @structlike = long;
typedef Clock @structlike = ulong;
typedef NanoDuration (Printable) @structlike = long;

const Time FAR_FUTURE = (Time)long.max;
const Time FAR_PAST = (Time)long.min;

const NanoDuration NANO_DURATION_ZERO = (NanoDuration)0;
const Duration US            = (Duration)1;
const Duration MS            = (Duration)1_000;
const Duration SEC           = (Duration)1_000_000;
const Duration MIN           = (Duration)60 * SEC;
const Duration HOUR          = (Duration)60 * MIN;
const Duration DAY           = (Duration)24 * HOUR;
const Duration WEEK          = (Duration)7 * DAY;
const Duration MONTH         = (Duration)30 * DAY;
const Duration YEAR          = (Duration)36525 * DAY / 100;
const Duration FOREVER       = (Duration)long.max;
const Duration DURATION_ZERO = (Duration)0;

fn Duration us(long l) @inline => l * US;
fn Duration ms(long l) @inline => l * MS;
fn Duration sec(long l) @inline =>l * SEC;
fn Duration min(long l) @inline => l * MIN;
fn Duration hour(long l) @inline => l * HOUR;
fn Duration from_float(double s) @inline => (Duration)(s * (double)SEC);

struct DateTime
{
	int usec;
	char sec;
	char min;
	char hour;
	char day;
	Month month;
	Weekday weekday;
	int year;
	ushort year_day;
	Time time;
}

struct TzDateTime
{
	inline DateTime date_time;
	int gmt_offset;
}

enum Weekday : char (String name, String abbrev)
{
	MONDAY    { "Monday",    "Mon" },
	TUESDAY   { "Tuesday",   "Tue" },
	WEDNESDAY { "Wednesday", "Wed" },
	THURSDAY  { "Thursday",  "Thu" },
	FRIDAY    { "Friday",    "Fri" },
	SATURDAY  { "Saturday",  "Sat" },
	SUNDAY    { "Sunday",    "Sun" },
}

enum Month : char (String name, String abbrev, int days, bool leap)
{
	JANUARY    { "January",   "Jan", 31, false },
	FEBRUARY   { "February",  "Feb", 28, true  },
	MARCH      { "March",     "Mar", 31, false },
	APRIL      { "April",     "Apr", 30, false },
	MAY        { "May",       "May", 31, false },
	JUNE       { "June",      "Jun", 30, false },
	JULY       { "July",      "Jul", 31, false },
	AUGUST     { "August",    "Aug", 31, false },
	SEPTEMBER  { "September", "Sep", 30, false },
	OCTOBER    { "October",   "Oct", 31, false },
	NOVEMBER   { "November",  "Nov", 30, false },
	DECEMBER   { "December",  "Dec", 31, false }
}

fn Time now()
{
	$if $defined(os::native_timestamp):
		return os::native_timestamp();
	$else
		return (Time)0;
	$endif
}

fn Time Time.add_seconds(time, long seconds) => time + seconds * SEC;
fn Time Time.add_minutes(time, long minutes) => time + minutes * MIN;
fn Time Time.add_hours(time, long hours) => time + hours * HOUR;
fn Time Time.add_days(time, long days) => time + days * DAY;
fn Time Time.add_weeks(time, long weeks) => time + weeks * WEEK;
fn Time Time.add_duration(time, Duration duration) @operator_s(+) @inline => (Time)((long)time + (long)duration);
fn Time Time.sub_duration(time, Duration duration) @operator(-) @inline => (Time)((long)time - (long)duration);

fn int Time.compare_to(time, Time other)
{
	if ((long)time == (long)other) return 0;
	return time > other ? 1 : -1;
}

fn double Time.to_seconds(time) => (long)time / (double)SEC;
fn Duration Time.diff_us(time, Time other) @operator(-) => (Duration)((long)time - (long)other);
fn double Time.diff_sec(time, Time other) => (long)time.diff_us(other) / (double)SEC;
fn double Time.diff_min(time, Time other) => (long)time.diff_us(other) / (double)MIN;
fn double Time.diff_hour(time, Time other) => (long)time.diff_us(other) / (double)HOUR;
fn double Time.diff_days(time, Time other) => (long)time.diff_us(other) / (double)DAY;
fn double Time.diff_weeks(time, Time other) => (long)time.diff_us(other) / (double)WEEK;

fn double NanoDuration.to_sec(nd) => (double)nd / 1_000_000_000.0;
fn long NanoDuration.to_ms(nd) => (long)nd / 1_000_000;
fn Duration NanoDuration.to_duration(nd) => (Duration)nd / 1_000;
fn NanoDuration Duration.to_nano(td) => (NanoDuration)td * 1_000;
fn long Duration.to_ms(td) => (long)td / 1_000;
macro Duration Duration.mult(#td, long #val) @operator_s(*) @safemacro => (Duration)((long)#td * #val);

fn usz? NanoDuration.to_format(&self, Formatter* formatter) @dynamic
{
	NanoDuration nd = *self;
	if (nd == NANO_DURATION_ZERO)
	{
		return formatter.printf("0s")!;
	}

	bool neg = nd < NANO_DURATION_ZERO;
	if (neg) nd = -nd;

	DString str = dstring::temp_with_capacity(64);
	if (nd < (NanoDuration)1_000_000_000)
	{
		// Less than 1s: print milliseconds, microseconds and nanoseconds.
		NanoDuration ms = nd / 1_000_000;
		if (ms > NANO_DURATION_ZERO)
		{
			str.appendf("%dms", ms);
			nd -= ms * 1_000_000;
		}
		NanoDuration us = nd / 1000;
		if (us > NANO_DURATION_ZERO)
		{
			str.appendf("%dµs", us);
			nd -= us * 1000;
		}
		if (nd > NANO_DURATION_ZERO)
		{
			str.appendf("%dns", nd);
		}
	}
	else
	{
		// More than 1s: print hours, minutes and seconds.
		NanoDuration ms = (nd - nd / 1_000_000_000 * 1_000_000_000) / 1_000_000;

		nd /= 1_000_000_000;
		NanoDuration hour = nd / 3600;
		if (hour > NANO_DURATION_ZERO)
		{
			str.appendf("%dh", hour);
			nd -= hour * 3600;
		}
		NanoDuration min = nd / 60;
		if (min > NANO_DURATION_ZERO)
		{
			str.appendf("%dm", min);
			nd -= min * 60;
		}
		NanoDuration sec = nd;
		if (ms > NANO_DURATION_ZERO)
		{
			// Ignore trailing zeroes.
			while (ms / 10 * 10 == ms) ms /= 10;
			str.appendf("%d.%ds", sec, ms);
		}
		else
		{
			str.appendf("%ds", sec);
		}
	}
	return formatter.printf(str.str_view())!;
}