{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE ExplicitForAll             #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Time.Timestamp
       ( Timestamp (..)
       , fromUnixTime
       , timeDiff
       , timeAdd
       , timeMul
       , (*:*)
       , timeDiv
       , (/:/)
         
       , (+:+)
       , (-:-)
       , (-%-)
       ) where
import GHC.Prim (coerce)
import Time.Rational (KnownDivRat, KnownRat, Rat, RatioNat)
import Time.Units (Second, Time (..), sec, toUnit)
newtype Timestamp = Timestamp Rational
    deriving (Show, Read, Eq, Ord)
fromUnixTime :: Real a => a -> Timestamp
fromUnixTime = Timestamp . toRational
timeDiff :: forall (unit :: Rat) . KnownDivRat Second unit
         => Timestamp
         -> Timestamp
         -> (Ordering, Time unit)
timeDiff (Timestamp a) (Timestamp b) =
    let (order, r) = ratDiff a b
    in (order, toUnit $ sec r)
timeAdd :: forall (unit :: Rat) . KnownDivRat unit Second
        => Time unit
        -> Timestamp
        -> Timestamp
timeAdd t (Timestamp ts) = Timestamp (toRational (unTime $ toUnit @Second t) + ts)
timeMul :: forall (unit :: Rat) . KnownRat unit
        => RatioNat
        -> Time unit
        -> Time unit
timeMul n (Time t) = Time (n * t)
infixr 7 *:*
(*:*) :: forall (unit :: Rat) . KnownRat unit
      => RatioNat -> Time unit -> Time unit
(*:*) = timeMul
timeDiv :: forall (unit :: Rat) . KnownRat unit
        => Time unit
        -> Time unit
        -> RatioNat
timeDiv (Time t1) (Time t2) = t1 / t2
infix 7 /:/
(/:/) :: forall (unit :: Rat) . KnownRat unit
      => Time unit -> Time unit -> RatioNat
(/:/) = timeDiv
infixl 6 +:+
(+:+) :: forall (unitResult :: Rat) (unitLeft :: Rat) . KnownDivRat unitLeft unitResult
      => Time unitLeft
      -> Time unitResult
      -> Time unitResult
t1 +:+ t2 = coerce ((+) :: RatioNat -> RatioNat -> RatioNat) (toUnit @unitResult t1) t2
{-# INLINE (+:+) #-}
infixl 6 -:-
(-:-) :: forall (unitResult :: Rat) (unitLeft :: Rat) . KnownDivRat unitLeft unitResult
      => Time unitLeft
      -> Time unitResult
      -> Time unitResult
t1 -:- t2 = coerce ((-) :: RatioNat -> RatioNat -> RatioNat) (toUnit @unitResult t1) t2
{-# INLINE (-:-) #-}
infix 6 -%-
(-%-) :: forall (unitResult :: Rat) (unitLeft :: Rat) . KnownDivRat unitLeft unitResult
      => Time unitLeft
      -> Time unitResult
      -> (Ordering, Time unitResult)
t1 -%- (Time t2Rat) =
    let (Time t1Rat) = toUnit @unitResult t1
        (order, rat) = ratDiff (toRational t1Rat) (toRational t2Rat)
    in (order, Time rat)
ratDiff :: Rational -> Rational -> (Ordering, RatioNat)
ratDiff r1 r2 =
    let order = compare r1 r2
        diff  = fromRational $ case order of
                     LT -> r2 - r1
                     GT -> r1 - r2
                     EQ -> 0
    in (order, diff)