module Data.Time.Distance
    ( TimeUnit(..)
    , TimeDirection(..)
    , distanceOfTime
    , distanceOfTimeInWords
    ) where

import qualified Data.Maybe as M
import qualified Data.Time as T

data TimeDirection
    = Past
    | Present
    | Future

data TimeUnit
    = Millisecond
    | Second
    | Minute
    | Hour
    | Day
    | Week
    | Month
    | Year

data TimeDifference = TimeDifference TimeDistance TimeDirection
data TimeDistance = TimeDistance Integer TimeUnit

instance Show TimeDirection where
    show Past = "ago"
    show Present = ""
    show Future = "from now"

instance Show TimeUnit where
    show Millisecond = "millisecond"
    show Second = "second"
    show Minute = "minute"
    show Hour = "hour"
    show Day = "day"
    show Week = "week"
    show Month = "month"
    show Year = "year"

instance Show TimeDistance where
    show (TimeDistance amount unit) = show amount ++ " " ++ pluralizeUnit
      where
        pluralizeUnit
            | amount > 1 = show unit ++ "s"
            | otherwise = show unit

instance Show TimeDifference where
    show (TimeDifference _ Present) = "now"
    show (TimeDifference distance direction) =
        show distance ++ " " ++ show direction

distanceOfTimeInWords :: T.UTCTime -> T.UTCTime -> String
distanceOfTimeInWords a = show . distanceOfTime a

distanceOfTime :: T.UTCTime -> T.UTCTime -> TimeDifference
distanceOfTime a b = TimeDifference (TimeDistance amount unit) (directionFromDiff diff)
  where
    diff = T.diffUTCTime a b
    (TimeDistance i unit) = M.fromMaybe years $ bestTimeDistance diff
    amount
        | c == 0 = abs $ floor $ diff * 1000
        | otherwise = abs $ floor diff `div` c
    c = i `div` 1000

bestTimeDistance :: T.NominalDiffTime -> Maybe TimeDistance
bestTimeDistance v = safeLast $ filter timeValuesUnderBounds timeValues
  where
    timeValues = [milliseconds, seconds, minutes, hours, days, weeks, months, years]
    timeValuesUnderBounds (TimeDistance i _) = toNominalDifftime (i `div` 1000) < abs v
    toNominalDifftime = fromInteger . toInteger

milliseconds, seconds, minutes, hours, days, weeks, months, years :: TimeDistance
milliseconds = TimeDistance 1 Millisecond
seconds      = buildTime 1000 milliseconds Second
minutes      = buildTime 60   seconds      Minute
hours        = buildTime 60   minutes      Hour
days         = buildTime 24   hours        Day
weeks        = buildTime 7    days         Week
months       = buildTime 730  hours        Month
years        = buildTime 365  days         Year

buildTime :: Integer -> TimeDistance -> TimeUnit -> TimeDistance
buildTime multiplier (TimeDistance i _) = TimeDistance (multiplier * i)

directionFromDiff :: T.NominalDiffTime -> TimeDirection
directionFromDiff t
    | t < 0 = Past
    | t == 0 = Present
    | t > 0 = Future
    | otherwise = Present

safeLast :: [a] -> Maybe a
safeLast [] = Nothing
safeLast xs = Just $ last xs