{-# LANGUAGE BangPatterns #-}

module Main
  ( main
  ) where

import           Data.Hourglass
                   ( Date (..), DateTime (..), Elapsed (..), TimeOfDay (..)
                   , timeGetDate, timeGetDateTimeOfDay, timeGetElapsed
                   )
import           Data.List ( intercalate )
import qualified Data.Time.Calendar as T
import qualified Data.Time.Clock as T
import qualified Data.Time.Clock.POSIX as T
import           Test.Tasty.Bench ( bench, bgroup, defaultMain, nf, nfIO )
import           Time.System ( timeCurrent, timeCurrentP )

timeToTuple :: T.UTCTime -> (Int, Int, Int, Int, Int, Int)
timeToTuple utcTime = (fromIntegral y, m, d, h, mi, sec)
 where
  (!y, !m, !d) = T.toGregorian (T.utctDay utcTime)
  !daytime     = floor $ T.utctDayTime utcTime
  (!dt, !sec)  = daytime `divMod` 60
  (!h , !mi)   = dt `divMod` 60

timeToTupleDate :: T.UTCTime -> (Int, Int, Int)
timeToTupleDate utcTime = (fromIntegral y, m, d)
 where
  (!y, !m, !d) = T.toGregorian (T.utctDay utcTime)

timePosixDict :: [ (Elapsed, T.POSIXTime) ]
timePosixDict =
  [-- (Elapsed 0, 0)
  --, (Elapsed 1000000, 1000000)
  --, (Elapsed 9000099, 9000099)
  {-,-} (Elapsed 1398232846, 1398232846) -- currentish time (at the time of writing)
  --, (Elapsed 5134000099, 5134000099)
  --, (Elapsed 10000000000000, 10000000000000) -- year 318857 ..
  ]

dateDict :: [ (Int, Int, Int, Int, Int, Int) ]
dateDict =
  [{- (1970, 1, 1, 1, 1, 1)
  , -}(2014, 5, 5, 5, 5, 5)
  --, (2114, 11, 5, 5, 5, 5)
  ]

main :: IO ()
main = defaultMain
  [ bgroup "highlevel"   $ concatMap toHighLevel timePosixDict
  , bgroup "to-dateTime" $ concatMap toCalendar timePosixDict
  , bgroup "to-date"     $ concatMap toCalendarDate timePosixDict
  , bgroup "utc-to-date" $ concatMap toCalendarUTC timePosixDict
  , bgroup "to-posix"    $ concatMap toPosix dateDict
  , bgroup "system"      fromSystem
  ]
 where
  toHighLevel (posixHourglass, posixTime) =
    [ bench (showH posixHourglass) $ nf timeGetDateTimeOfDay posixHourglass
    , bench (showT posixTime) $ nf T.posixSecondsToUTCTime posixTime
    ]
  toCalendar (posixHourglass, posixTime) =
    [ bench (showH posixHourglass) $ nf timeGetDateTimeOfDay posixHourglass
    , bench (showT posixTime) $ nf (timeToTuple . T.posixSecondsToUTCTime) posixTime
    ]
  toCalendarDate (posixHourglass, posixTime) =
    [ bench (showH posixHourglass) $ nf timeGetDate posixHourglass
    , bench (showT posixTime) $ nf (timeToTupleDate . T.posixSecondsToUTCTime) posixTime
    ]
  toCalendarUTC (posixHourglass, posixTime) =
    [ bench (showH posixHourglass) $ nf timeGetDateTimeOfDay posixHourglass
    , bench (showT utcTime) $ nf timeToTuple utcTime
    ]
   where
    !utcTime = T.posixSecondsToUTCTime posixTime

  toPosix v =
    [ bench ("hourglass/" ++ n v) $ nf hourglass v
    , bench ("time/" ++ n v) $ nf time v
    ]
   where
    n (y, m, d, h, mi, s) =
         intercalate "-" (map show [y, m, d])
      ++ " "
      ++ intercalate ":" (map show [h, mi, s])
    hourglass (y, m, d, h, mi, s) =
      timeGetElapsed $
        DateTime
          (Date y (toEnum (m - 1)) d)
          (TimeOfDay (fromIntegral h) (fromIntegral mi) (fromIntegral s) 0)
    time (y, m, d, h, mi, s) =
      let day = T.fromGregorian (fromIntegral y) m d
          diffTime = T.secondsToDiffTime $ fromIntegral (h * 3600 + mi * 60 + s)
      in  T.utcTimeToPOSIXSeconds (T.UTCTime day diffTime)

  fromSystem =
    [ bench "hourglass/p"    $ nfIO timeCurrent
    , bench "hourglass/ns"   $ nfIO timeCurrentP
    , bench "time/posixTime" $ nfIO T.getPOSIXTime
    , bench "time/utcTime"   $ nfIO T.getCurrentTime
    ]

  showH :: Show a => a -> String
  showH a = "hourglass/" ++ show a

  showT :: Show a => a -> String
  showT a = "time/" ++ show a
