enum TimeUnit {
	// Plancks = (-5.39 * Math.pow( 10, -44 )),	// The amount of time light takes to travel one Planck length.
	// quectoseconds = Math.pow( 10, -30),			// One nonillionth of a second.
	// rontoseconds = Math.pow( 10, -27 ),			// One octillionth of a second.
	// yoctoseconds = Math.pow( 10, -24 ),			// One septillionth of a second.
	// zeptoseconds = Math.pow( 10, -21 ),			// One sextillionth of a second. Time measurement scale of the NIST and JILA strontium atomic clock. Smallest fragment of time currently measurable is 247 zeptoseconds.[3]
	// attoseconds = Math.pow( 10, -18 ),			// One quintillionth of a second.
	// femtoseconds = Math.pow( 10, -15 ),			// One quadrillionth of a second.
	// svedbergs = Math.pow( 10, -13 ),			// 100 femtoseconds, time unit used for sedimentation rates (usually of proteins).
	picoseconds = Math.pow(10, -12), // One trillionth of a second.
	nanoseconds = Math.pow(10, -9), // One billionth of a second. Time for molecules to fluoresce.
	shakes = Math.pow(10, -8), // 10 nanoseconds, also a casual term for a short period of time.
	microseconds = Math.pow(10, -6), // One millionth of a second. Symbol is μs
	milliseconds = Math.pow(10, -3), // One thousandth of a second. Shortest time unit used on stopwatches.
	centiseconds = Math.pow(10, -2), // One hundredth of a second.
	deciseconds = Math.pow(10, -1), // One tenth of a second.
	seconds = 1, // SI base unit for time.
	decasecond = 10, // Ten seconds (one sixth of a minute)
	minutes = 60, // 60 seconds
	hectoseconds = 100, // 100 seconds
	millidays = 86.4, // 1/1000 d (0.001 d) 	1.44 minutes, or 86.4 seconds. Also marketed as a ".beat" by the Swatch corporation.
	centidays = 864, // 0.01 d (1 % of a day) 	14.4 minutes, or 864 seconds. One-hundredth of a day is 1 cd (centiday), also called "kè" in traditional Chinese timekeeping. The unit was also proposed by Lagrange and endorsed by Rey-Pailhade[5] in the 19th century, named "centijours" (from French centi- 'hundred' and jour 'day').
	kiloseconds = 1000, // 	About 17 minutes.
	hours = 60 * 60, // 60 minutes (60 * 60 seconds)
	decidays = 144 * 60, // 0.1 d (10 % of a day) 	2.4 hours, or 144 minutes. One-tenth of a day is 1 dd (deciday), also called "gēng" in traditional Chinese timekeeping.
	days = 24 * 60 * 60, // 24 h 	Longest unit used on stopwatches and countdowns. The SI day is exactly 86 400 seconds.
	weeks = 7 * 24 * 60 * 60, // 7 d 	Historically sometimes also called "sennight".
	decaday = 10 * 24 * 60 * 60, // 10 d (1 Dd) 	10 days. A period of time analogous to the concept of "week", used by different societies around the world: the ancient Egyptian calendar, the ancient Chinese calendar, and also the French Republican calendar (in which it was called a décade).
	megaseconds = Math.pow(10, 6), // About 11.6 days.
	fortnights = 14 * 24 * 60 * 60, // 2 weeks 	14 days
	hectodays = 100 * 24 * 60 * 60, // 100 d (1 hd) 	100 days, roughly equivalent to 1/4 of a year (91.25 days). In Chinese tradition "bǎi rì" (百日) is the hundredth day after one's birth, also called Baby's 100 Days Celebration.
	semesters = 18 * 7 * 24 * 60 * 60, // 18 weeks 	A division of the academic year.[6] Literally "six months", also used in this sense.
	lunaryears = 354.37 * 24 * 60 * 60, // 	354.37 d
	years = 365 * 24 * 60 * 60, // 12 mo
	decades = 10 * 365 * 24 * 60 * 60, // 10 yr
	gigaseconds = Math.pow(10, 9), // About 31.7 years.
	jubilees = 50 * 365 * 24 * 60 * 60, // 50 yr
	centuries = 100 * 365 * 24 * 60 * 60, // 100 yr
	millennium = 1000 * 365 * 24 * 60 * 60, // 1000 yr 	Also called "kiloannum".
	teraseconds = Math.pow(10, 12), // About 31,709 years.
	petaseconds = Math.pow(10, 15) // About 31709791 years.
	// exaseconds = Math.pow( 10, 18 ),			// About 31709791983 years. Approximately 2.3 times the current age of the universe.
	// zettasecond = Math.pow( 10, 21 ),			// About 31709791983764 years.
}

const UNIT_ABBREVIATIONS: Record<string, TimeUnit> = {
	'ps': TimeUnit.picoseconds,
	'ns': TimeUnit.nanoseconds,
	'us': TimeUnit.microseconds,
	'ms': TimeUnit.milliseconds,
	's': TimeUnit.seconds,
	'm': TimeUnit.minutes,
	'h': TimeUnit.hours,
	'd': TimeUnit.days,
	'w': TimeUnit.weeks,
	'y': TimeUnit.years
};

export class Duration {
	constructor(private length: number = 0, private start: number | undefined = undefined) {}

	public static between(start: Date, end: Date, unit: TimeUnit = TimeUnit.milliseconds) {
		const ms_between: number = end.valueOf() - start.valueOf();
		const factor: number = unit / TimeUnit.milliseconds;
		return factor * ms_between;
	}

	public static of(spec: string | number, unit?: TimeUnit): Duration {
		if (unit) {
			return new Duration(Number(spec) * unit);
		}

		let length_in_ms = 0;
		for (const match of `${spec}`.matchAll(/(?<len>([0-9]*[.])?[0-9]+)\W*(?<unit>\w+)\W/g)) {
			const { len, unit_name } = match.groups ?? {};

			if (typeof len !== 'string' || typeof unit_name !== 'string') {
				throw new Error(`Could not parse duraction spec: ${spec}`);
			}

			const time_unit: TimeUnit | undefined = UNIT_ABBREVIATIONS[unit_name] ?? TimeUnit[unit_name as keyof typeof TimeUnit];
			if (!time_unit) {
				throw new Error(`Could not determine a time unit for: ${unit_name}`);
			}

			length_in_ms += Number(len) * time_unit;
		}

		return new Duration(length_in_ms);
	}
}
