"A clock providing access to the current instant, date and time using a time-zone.
 
 Instances of this class are used to find the current instant, which can be
 interpreted using the stored time-zone to find the current date and time.
 As such, a clock can be used instead of `system.milliseconds`.
 
 The primary purpose of this abstraction is to allow alternate clocks to be
 plugged in as and when required. Applications use an object to obtain the
 current time rather than a static method. This can simplify testing.
 
 Applications should _avoid_ using the top level objects directly.
 Instead, they should pass a [[Clock]] into any method that requires it.
 A dependency injection framework is one way to achieve this.
 
 This approach allows an alternate clock, such as [[fixedTime]] to be used during testing.
 
 The [[systemTime]] top level factory method offers clocks based on the best available 
 system clock, such as `system.milliseconds`."
shared interface Clock {

    "Gets the current millisecond instant of the clock."
    shared formal Integer milliseconds();

    "Gets the current instant of the clock."
    shared formal Instant instant();

}

"Gets a clock that obtains the current instant using the best available system clock."
shared object systemTime satisfies Clock {

    "Return number of milliseconds from system time."
    shared actual Integer milliseconds() => system.milliseconds;

    "Return current instant from system time."
    shared actual Instant instant() => Instant( milliseconds() );

    "Returns only the kind of this implementation as time can change to every call."
    shared actual String string = "System time";

}

"Gets a clock that always returns the same instant in the UTC time-zone."
shared Clock fixedTime(Instant|Integer instant) {
    switch(instant)
    case (is Instant){
        return FixedInstant(instant);
    }
    case (is Integer){
        return FixedMilliseconds(instant);
    }
}

"Implementation of a clock that always returns the same instant.
 
 This is typically used for testing."
class FixedInstant(Instant fixedInstant) satisfies Clock {

    "Returns milliseconds from the fixed instant."
    shared actual Integer milliseconds() => fixedInstant.millisecondsOfEpoch;

    "Returns the fixed instant."
    shared actual Instant instant() => fixedInstant;

    "Returns the fixed [[Instant]] that this [[Clock]] represents."
    shared actual String string = "Fixed to ``instant()``";

}

"Implementation of a clock that always returns the same instant.

 This is typically used for testing."
class FixedMilliseconds(Integer fixedMilliseconds) satisfies Clock {

    "Returns the fixed milliseconds."
    shared actual Integer milliseconds() => fixedMilliseconds;

    "Returns the instant from the fixed milliseconds."
    shared actual Instant instant() => Instant(fixedMilliseconds);

    "Returns the fixed [[Instant]] that this [[Clock]] represents."
    shared actual String string = "Fixed to ``instant()``";

}

"Returns an implementation of a clock that always returns a 
 constant offset from the value of the provided clock."
shared Clock offsetTime(Clock baseClock, Duration offset) 
       => OffsetClock(baseClock, offset);

"An implementation of a [[Clock]] that returns time with a constant 
 offset from the provided clock."
class OffsetClock(Clock baseClock, Duration offset) satisfies Clock {
    shared actual Integer milliseconds() => baseClock.milliseconds() + offset.milliseconds;
    shared actual Instant instant() => Instant( milliseconds() );

    "Returns the [[offsetTime]] period from given [[Clock]]."
    shared actual String string = "Offset of ``offset.period.normalized()`` from ``baseClock``";

}