{-# LANGUAGE RankNTypes, OverloadedStrings, TemplateHaskell, FlexibleInstances #-}

module AWS.Lib.FromText
    ( FromText(..)
    , deriveFromText
    -- re-exports
    , monadThrow
    , IPv4
    , AddrRange
    , Text
    , UTCTime
    , AWSException(..)
    ) where

import Control.Applicative ((<$>))
import Control.Monad
import Data.Conduit (MonadThrow)
import Data.IP (IPv4, AddrRange)
import Data.Monoid ((<>))
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time (UTCTime)
import qualified Data.Time as Time
import qualified Data.Time.Parse as TP
import Language.Haskell.TH
import Safe

import AWS.Class

class Read a => FromText a
  where
    fromText :: MonadThrow m => Text -> m a
    fromText t
        = maybe (monadThrow $ TextConversionException t) return
        . fromTextMay
        $ t

    fromTextMay :: Text -> Maybe a
    fromTextMay = readMay . T.unpack

    fromMaybeText :: MonadThrow m => Text -> Maybe Text -> m a
    fromMaybeText name
        = maybe
            (monadThrow $ TextConversionException $ "no text: " <> name)
            fromText

instance FromText a => FromText (Maybe a)
  where
    fromText = return . join . fromTextMay
    fromMaybeText _name Nothing  = return Nothing
    fromMaybeText _name (Just t) = return $ fromTextMay t
    fromTextMay = Just . fromTextMay

instance FromText Int
instance FromText Integer
instance FromText Double
instance FromText IPv4
instance FromText (AddrRange IPv4)

instance FromText Text
  where
    fromTextMay t
        | t == ""   = Nothing
        | otherwise = Just t

instance FromText Bool
  where
    fromTextMay "true"  = Just True
    fromTextMay "false" = Just False
    fromTextMay _       = Nothing

instance FromText UTCTime
  where
    fromTextMay t
        = Time.localTimeToUTC Time.utc . fst
        <$> (TP.strptime fmt $ T.unpack t)
      where
        fmt = "%FT%T"

deriveFromText :: String -> [String] -> DecsQ
deriveFromText dstr strs = do
    ctrs <- map (\(NormalC name _) -> name) <$> cons
    x <- newName "x"
    let cases = caseE (varE x) (map f (zip strs ctrs) ++ [wild])
    let fun = funD 'fromTextMay [clause [varP x] (normalB cases) []]
    (:[]) <$> instanceD ctx typ [fun]
  where
    d = mkName dstr
    cons = do
        (TyConI (DataD _ _ _ cs _)) <- reify d
        return cs
    f (s, t) = match (litP $ stringL s) (normalB $ [|Just $(conE t)|]) []
    wild = match wildP (normalB [|Nothing|]) []
    typ = appT (conT ''FromText) (conT d)
    ctx = return []