-------------------------------------------------------------------------------
-- time-units-types
-- Copyright 2022 Michael B. Gale (github@michael-gale.co.uk)
-------------------------------------------------------------------------------

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTSyntax #-}

-- | Exports types which can be used to describe time periods at
-- the type-level. Use the `durationVal` function to reify them as the
-- corresponding value-level descriptions from "Data.Time.Units" or the
-- `durationMicroseconds` function to reify them as microseconds straight away.
module Data.Time.TypeLevel (
    KnownDuration(..),
    durationMicroseconds,

    TimePeriod(..)
) where

-------------------------------------------------------------------------------

import GHC.TypeLits

import qualified Data.Time.Units as Units
import Data.Proxy
import Data.Kind

-------------------------------------------------------------------------------

data TimePeriod where
    -- | Represents @n@-many attoseconds.
    Attosecond :: Nat -> TimePeriod

    -- | Represents @n@-many femtoseconds.
    Femtosecond :: Nat -> TimePeriod

    -- | Represents @n@-many picoseconds.
    Picosecond :: Nat -> TimePeriod

    -- | Represents @n@-many nanoseconds.
    Nanosecond :: Nat -> TimePeriod

    -- | Represents @n@-many microseconds.
    Microsecond :: Nat -> TimePeriod

    -- | Represents @n@-many milliseconds.
    Millisecond :: Nat -> TimePeriod

    -- | Represents @n@-many seconds.
    Second :: Nat -> TimePeriod

    -- | Represents @n@-many minutes.
    Minute :: Nat -> TimePeriod

    -- | Represents @n@-many hours.
    Hour :: Nat -> TimePeriod

    -- | Represents @n@-many days.
    Day :: Nat -> TimePeriod

    -- | Represents @n@-many weeks.
    Week :: Nat -> TimePeriod

    -- | Represents @n@-many fortnights.
    Fortnight :: Nat -> TimePeriod

-- | A class of types which can be reified as value-level descriptions of
-- time periods.
class KnownDuration k where
    -- | The type representing value-level descriptions of the time period
    -- corresponding to @k@.
    type DurationUnit k :: Type

    -- | `durationVal` reifies the duration as the corresponding value-level
    -- type. Intended to be used with @TypeApplications@.
    --
    -- >>> durationVal @('Second 10)
    -- 10s :: Data.Time.Units.Second
    durationVal :: DurationUnit k

instance KnownNat n => KnownDuration ('Attosecond n) where
    type DurationUnit ('Attosecond n) = Units.Attosecond

    durationVal :: DurationUnit ('Attosecond n)
durationVal =
        Num Attosecond => Integer -> Attosecond
forall a. Num a => Integer -> a
fromInteger @Units.Attosecond (Integer -> Attosecond) -> Integer -> Attosecond
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

-- | `durationMicroseconds` is a convenience function which reifies a
-- type-level duration as microseconds on the value level. Intended to be
-- used with @TypeApplications@.
--
-- >>> durationMicroseconds @('Second 10)
-- 10000000 :: Integer
durationMicroseconds
    :: forall d . (KnownDuration d, Units.TimeUnit (DurationUnit d))
    => Integer
durationMicroseconds :: Integer
durationMicroseconds = DurationUnit d -> Integer
forall a. TimeUnit a => a -> Integer
Units.toMicroseconds (DurationUnit d -> Integer) -> DurationUnit d -> Integer
forall a b. (a -> b) -> a -> b
$ KnownDuration d => DurationUnit d
forall k (k :: k). KnownDuration k => DurationUnit k
durationVal @d

instance KnownNat n => KnownDuration ('Femtosecond n) where
    type DurationUnit ('Femtosecond n) = Units.Femtosecond

    durationVal :: DurationUnit ('Femtosecond n)
durationVal =
        Num Femtosecond => Integer -> Femtosecond
forall a. Num a => Integer -> a
fromInteger @Units.Femtosecond (Integer -> Femtosecond) -> Integer -> Femtosecond
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

instance KnownNat n => KnownDuration ('Picosecond n) where
    type DurationUnit ('Picosecond n) = Units.Picosecond

    durationVal :: DurationUnit ('Picosecond n)
durationVal =
        Num Picosecond => Integer -> Picosecond
forall a. Num a => Integer -> a
fromInteger @Units.Picosecond (Integer -> Picosecond) -> Integer -> Picosecond
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

instance KnownNat n => KnownDuration ('Nanosecond n) where
    type DurationUnit ('Nanosecond n) = Units.Nanosecond

    durationVal :: DurationUnit ('Nanosecond n)
durationVal =
        Num Nanosecond => Integer -> Nanosecond
forall a. Num a => Integer -> a
fromInteger @Units.Nanosecond (Integer -> Nanosecond) -> Integer -> Nanosecond
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

instance KnownNat n => KnownDuration ('Microsecond n) where
    type DurationUnit ('Microsecond n) = Units.Microsecond

    durationVal :: DurationUnit ('Microsecond n)
durationVal =
        Num Microsecond => Integer -> Microsecond
forall a. Num a => Integer -> a
fromInteger @Units.Microsecond (Integer -> Microsecond) -> Integer -> Microsecond
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

instance KnownNat n => KnownDuration ('Millisecond n) where
    type DurationUnit ('Millisecond n) = Units.Millisecond

    durationVal :: DurationUnit ('Millisecond n)
durationVal =
        Num Millisecond => Integer -> Millisecond
forall a. Num a => Integer -> a
fromInteger @Units.Millisecond (Integer -> Millisecond) -> Integer -> Millisecond
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

instance KnownNat n => KnownDuration ('Second n) where
    type DurationUnit ('Second n) = Units.Second

    durationVal :: DurationUnit ('Second n)
durationVal =
        Num Second => Integer -> Second
forall a. Num a => Integer -> a
fromInteger @Units.Second (Integer -> Second) -> Integer -> Second
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

instance KnownNat n => KnownDuration ('Minute n) where
    type DurationUnit ('Minute n) = Units.Minute

    durationVal :: DurationUnit ('Minute n)
durationVal =
        Num Minute => Integer -> Minute
forall a. Num a => Integer -> a
fromInteger @Units.Minute (Integer -> Minute) -> Integer -> Minute
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

instance KnownNat n => KnownDuration ('Hour n) where
    type DurationUnit ('Hour n) = Units.Hour

    durationVal :: DurationUnit ('Hour n)
durationVal =
        Num Hour => Integer -> Hour
forall a. Num a => Integer -> a
fromInteger @Units.Hour (Integer -> Hour) -> Integer -> Hour
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

instance KnownNat n => KnownDuration ('Day n) where
    type DurationUnit ('Day n) = Units.Day

    durationVal :: DurationUnit ('Day n)
durationVal =
        Num Day => Integer -> Day
forall a. Num a => Integer -> a
fromInteger @Units.Day (Integer -> Day) -> Integer -> Day
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

instance KnownNat n => KnownDuration ('Week n) where
    type DurationUnit ('Week n) = Units.Week

    durationVal :: DurationUnit ('Week n)
durationVal =
        Num Week => Integer -> Week
forall a. Num a => Integer -> a
fromInteger @Units.Week (Integer -> Week) -> Integer -> Week
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

instance KnownNat n => KnownDuration ('Fortnight n) where
    type DurationUnit ('Fortnight n) = Units.Fortnight

    durationVal :: DurationUnit ('Fortnight n)
durationVal =
        Num Fortnight => Integer -> Fortnight
forall a. Num a => Integer -> a
fromInteger @Units.Fortnight (Integer -> Fortnight) -> Integer -> Fortnight
forall a b. (a -> b) -> a -> b
$
        Proxy n -> Integer
forall (n :: Nat) (proxy :: Nat -> *).
KnownNat n =>
proxy n -> Integer
natVal (Proxy n
forall k (t :: k). Proxy t
Proxy :: Proxy n)

-------------------------------------------------------------------------------