{-# LANGUAGE TemplateHaskell #-}
-- |
-- Load TimeZoneSeries from an Olson file at compile time.
--  
-- For example:
--
-- > myTimeZoneSeries :: TimeZoneSeries
-- > myTimeZoneSeries = $(loadTZFile "/usr/share/zoneinfo/Europe/Stockholm")

module Data.Time.LocalTime.TimeZone.Olson.TH
  (
    loadTZFile
  ) where

import Data.Ratio                          (numerator,
                                            denominator)
import Data.Time.LocalTime.TimeZone.Olson  (getTimeZoneSeriesFromOlsonFile)
import Data.Time.LocalTime.TimeZone.Series (TimeZoneSeries(..))
import Data.Time.LocalTime                 (TimeZone(..))
import Data.Time                           (UTCTime(..),
                                            Day(..),
                                            DiffTime,
                                            secondsToDiffTime)
import Language.Haskell.TH                 (Q,
                                            runIO,
                                            Exp(..),
                                            mkName,
                                            Lit(..),
                                            litE,
                                            integerL)

-- | Make a splice of a TimeZoneSeries from an Olson file.
loadTZFile :: FilePath -- ^ Path to the Olson file.
           -> Q Exp
loadTZFile zf =
  mkTZS =<< (runIO $ getTimeZoneSeriesFromOlsonFile zf)

-- | Make a splice of a TimeZoneSeries.    
mkTZS :: TimeZoneSeries -- ^ The TimeZoneSeries to be spliced
      -> Q Exp
mkTZS (TimeZoneSeries def tlist) = [| TimeZoneSeries $(litTimeZone def) $(mkList tlist) |]

mkList :: [(UTCTime,TimeZone)]
       -> Q Exp
mkList l = [| $(fmap ListE $ mapM mkPair l) |]

mkPair :: (UTCTime,TimeZone)
       -> Q Exp
mkPair (t,tz) = [| ($(litUTCTime t),$(litTimeZone tz)) |]

litUTCTime :: UTCTime
           -> Q Exp
litUTCTime (UTCTime (ModifiedJulianDay d) s) =
  [| UTCTime (ModifiedJulianDay $(litInteger d))
             (secondsToDiffTime $(litInteger $ diffTimeToInteger s)) |]

litInteger :: Integer
           -> Q Exp
litInteger = litE . integerL

diffTimeToInteger :: DiffTime
                  -> Integer
diffTimeToInteger s =
  let r = toRational s
      n = numerator r
      d = denominator r in
  (n `div` d)

litTimeZone :: TimeZone
            -> Q Exp
litTimeZone (TimeZone m s n) =
  [| TimeZone $(litInteger $ toInteger m)
              $(return $ ConE $ mkName $ show s)
              $(litE $ StringL n) |]