{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE PatternSynonyms            #-}

-- | Definition of `Duration` and various other types, constructors and functions for building
-- `Duration`s.
module Data.Duration
  (
    -- * Duration type
    Duration (..)
  , DurationUnit (..)
  , (#)
  , pattern Microseconds
  , pattern Milliseconds
  , pattern Seconds
  , pattern Minutes
  , pattern Hours
  , pattern Days
  , pattern Weeks

    -- * Non-operators for building durations
  , microseconds
  , milliseconds
  , seconds
  , minutes
  , hours
  , days
  , weeks

    -- * `Num` types for building durations from integer literals
  , Microseconds (..)
  , Microsecond
  , Milliseconds (..)
  , Millisecond
  , Seconds (..)
  , Second
  , Minutes (..)
  , Minute
  , Hours (..)
  , Hour
  , Days (..)
  , Day
  , Weeks (..)
  , Week
  , ToDuration (..)
  ) where

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

-- | A microsecond precision duration, represented as an integer.
newtype Duration = Duration
  { -- | Duration in microseconds
    durationUs :: Int
  } deriving (Eq, Ord, Num)

instance Show Duration where
  show (Duration us) = show (Microseconds_ us)

-- | `Duration` units to be used with `#`. See constructor docs for max duration
-- values for each unit.
data DurationUnit
  -- | Max bound: 9223372036854775807
  = Microsecond
  -- | Max bound: 9223372036854775
  | Millisecond
  -- | Max bound: 9223372036854
  | Second
  -- | Max bound: 153722867280
  | Minute
  -- | Max bound: 2562047788
  | Hour
  -- | Max bound: 106751991
  | Day
  -- | Max bound: 15250284
  | Week
  deriving (Show, Eq)

pattern Microseconds :: DurationUnit
pattern Microseconds = Microsecond

pattern Milliseconds :: DurationUnit
pattern Milliseconds = Millisecond

pattern Seconds :: DurationUnit
pattern Seconds = Second

pattern Minutes :: DurationUnit
pattern Minutes = Minute

pattern Hours :: DurationUnit
pattern Hours = Hour

pattern Days :: DurationUnit
pattern Days = Day

pattern Weeks :: DurationUnit
pattern Weeks = Week

-- | Make a duration from a value + unit. Note that this function does not check
-- overflows. See `DurationUnit` for max bounds for units.
(#) :: Int -> DurationUnit -> Duration
t # u =
    Duration $
    case u of
      Microsecond -> t
      Millisecond -> t * 1000
      Second      -> t * 1000000
      Minute      -> t * 60 * 1000000
      Hour        -> t * 60 * 60 * 1000000
      Day         -> t * 24 * 60 * 60 * 1000000
      Week        -> t * 7 * 24 * 60 * 60 * 1000000

--------------------------------------------------------------------------------
-- * `Num` types and `ToDuration` typeclass for building durations with just
-- integer literals and type annotations.

newtype Microseconds = Microseconds_ { microsecondsInt :: Int }
  deriving (Eq, Ord, Num)

type Microsecond = Microseconds

instance Show Microseconds where
  show (Microseconds_ t) = show t ++ " μs"

newtype Milliseconds = Milliseconds_ { millisecondsInt :: Int }
  deriving (Eq, Ord, Num)

type Millisecond = Milliseconds

instance Show Milliseconds where
  show (Milliseconds_ t) = show t ++ " ms"

newtype Seconds = Seconds_ { secondsInt :: Int }
  deriving (Eq, Ord, Num)

type Second = Seconds

instance Show Seconds where
  show (Seconds_ t) = show t ++ " sec"

newtype Minutes = Minutes_ { minutesInt :: Int }
  deriving (Eq, Ord, Num)

type Minute = Minutes

instance Show Minutes where
  show (Minutes_ t) = show t ++ " min"

newtype Hours = Hours_ { hoursInt :: Int }
  deriving (Eq, Ord, Num)

type Hour = Hours

instance Show Hours where
  show (Hours_ t) = show t ++ " hours"

newtype Days = Days_ { daysInt :: Int }
  deriving (Eq, Ord, Num)

type Day = Days

instance Show Days where
  show (Days_ t) = show t ++ " days"

newtype Weeks = Weeks_ { weeksInt :: Int }
  deriving (Eq, Ord, Num)

type Week = Weeks

microseconds, milliseconds, seconds, minutes, hours, days, weeks :: Int -> Duration
microseconds t = t # Microseconds
milliseconds t = t # Milliseconds
seconds t = t # Seconds
minutes t = t # Minutes
hours t = t # Hours
days t = t # Days
weeks t = t # Weeks

class ToDuration a where
  toDuration :: a -> Duration

instance ToDuration Duration where
  toDuration = id

instance ToDuration Microseconds where
  toDuration (Microseconds_ t) = t # Microseconds

instance ToDuration Milliseconds where
  toDuration (Milliseconds_ t) = t # Milliseconds

instance ToDuration Seconds where
  toDuration (Seconds_ t) = t # Seconds

instance ToDuration Minutes where
  toDuration (Minutes_ t) = t # Minutes

instance ToDuration Hours where
  toDuration (Hours_ t) = t # Hours

instance ToDuration Days where
  toDuration (Days_ t) = t # Days

instance ToDuration Weeks where
  toDuration (Weeks_ t) = t # Weeks