{-# LANGUAGE TemplateHaskell            #-}

-- | A tool to generate maps to and from 'Text' values corresponding
-- to inhabitants of enumerated types
module Data.API.Tools.Enum
    ( enumTool
    , text_enum_nm
    , map_enum_nm
    ) where

import           Data.API.TH
import           Data.API.Tools.Combinators
import           Data.API.Tools.Datatypes
import           Data.API.Types

import qualified Data.Text                      as T
import qualified Data.Map                       as Map
import           Language.Haskell.TH


-- | Tool to generate the maps between enumerations and 'Text' strings
-- named by 'text_enum_nm' and 'map_enum_nm'.
enumTool :: APITool
enumTool :: APITool
enumTool = Tool APINode -> APITool
apiNodeTool (Tool APINode -> APITool) -> Tool APINode -> APITool
forall a b. (a -> b) -> a -> b
$ Tool (APINode, SpecNewtype)
-> Tool (APINode, SpecRecord)
-> Tool (APINode, SpecUnion)
-> Tool (APINode, SpecEnum)
-> Tool (APINode, APIType)
-> Tool APINode
apiSpecTool Tool (APINode, SpecNewtype)
forall a. Monoid a => a
mempty Tool (APINode, SpecRecord)
forall a. Monoid a => a
mempty Tool (APINode, SpecUnion)
forall a. Monoid a => a
mempty Tool (APINode, SpecEnum)
enum Tool (APINode, APIType)
forall a. Monoid a => a
mempty
  where
    enum :: Tool (APINode, SpecEnum)
enum = ((APINode, SpecEnum) -> Q [Dec]) -> Tool (APINode, SpecEnum)
forall a. (a -> Q [Dec]) -> Tool a
simpleTool ((APINode -> SpecEnum -> Q [Dec]) -> (APINode, SpecEnum) -> Q [Dec]
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry APINode -> SpecEnum -> Q [Dec]
gen_se_tx) Tool (APINode, SpecEnum)
-> Tool (APINode, SpecEnum) -> Tool (APINode, SpecEnum)
forall a. Semigroup a => a -> a -> a
<> ((APINode, SpecEnum) -> Q [Dec]) -> Tool (APINode, SpecEnum)
forall a. (a -> Q [Dec]) -> Tool a
simpleTool (APINode -> Q [Dec]
gen_se_mp (APINode -> Q [Dec])
-> ((APINode, SpecEnum) -> APINode)
-> (APINode, SpecEnum)
-> Q [Dec]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (APINode, SpecEnum) -> APINode
forall a b. (a, b) -> a
fst)


-- | For an enum type @E@, name a function @_text_E :: E -> 'Text'@
-- that gives a string corresponding to the inhabitant of the type.
-- For example, we generate something like this:
--
-- >   _text_FrameRate :: FrameRate -> T.Text
-- >   _text_FrameRate fr =
-- >           case fr of
-- >             FRauto    -> "auto"
-- >             FR10      -> "10"
-- >             FR15      -> "15"
-- >             FR23_97   -> "23.97"
-- >             FR24      -> "24"
-- >             FR25      -> "25"
-- >             FR29_97   -> "29.97"
-- >             FR30      -> "30"
-- >             FR60      -> "60"

text_enum_nm :: APINode -> Name
text_enum_nm :: APINode -> Name
text_enum_nm APINode
an = String -> Name
mkName (String -> Name) -> String -> Name
forall a b. (a -> b) -> a -> b
$ String
"_text_" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Text -> String
T.unpack (TypeName -> Text
_TypeName (TypeName -> Text) -> TypeName -> Text
forall a b. (a -> b) -> a -> b
$ APINode -> TypeName
anName APINode
an)

gen_se_tx :: APINode -> SpecEnum -> Q [Dec]
gen_se_tx :: APINode -> SpecEnum -> Q [Dec]
gen_se_tx APINode
as SpecEnum
se = Name -> TypeQ -> ExpQ -> Q [Dec]
simpleSigD (APINode -> Name
text_enum_nm APINode
as)
                             [t| $tc -> T.Text |]
                             ExpQ
bdy
  where
    tc :: TypeQ
tc  = Name -> TypeQ
conT (Name -> TypeQ) -> Name -> TypeQ
forall a b. (a -> b) -> a -> b
$ APINode -> Name
rep_type_nm APINode
as

    bdy :: ExpQ
bdy  = [MatchQ] -> ExpQ
lamCaseE [ PatQ -> BodyQ -> [DecQ] -> MatchQ
match (FieldName -> PatQ
pt FieldName
fnm) (FieldName -> BodyQ
bd FieldName
fnm) []
                    | (FieldName
fnm,String
_) <- SpecEnum -> [(FieldName, String)]
seAlts SpecEnum
se ]

    pt :: FieldName -> PatQ
pt FieldName
fnm = APINode -> FieldName -> [PatQ] -> PatQ
nodeAltConP APINode
as FieldName
fnm []

    bd :: FieldName -> BodyQ
bd FieldName
fnm = ExpQ -> BodyQ
normalB (ExpQ -> BodyQ) -> ExpQ -> BodyQ
forall a b. (a -> b) -> a -> b
$ String -> ExpQ
stringE (String -> ExpQ) -> String -> ExpQ
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ FieldName -> Text
_FieldName FieldName
fnm



-- | For an enum type @E@, name a map from 'Text' values to
-- inhabitants of the type, for example:
--
-- > _map_FrameRate :: Map Text FrameRate
-- > _map_FrameRate = genTextMap _text_FrameRate

map_enum_nm :: APINode -> Name
map_enum_nm :: APINode -> Name
map_enum_nm  APINode
an = String -> Name
mkName (String -> Name) -> String -> Name
forall a b. (a -> b) -> a -> b
$ String
"_map_"  String -> String -> String
forall a. [a] -> [a] -> [a]
++ Text -> String
T.unpack (TypeName -> Text
_TypeName (TypeName -> Text) -> TypeName -> Text
forall a b. (a -> b) -> a -> b
$ APINode -> TypeName
anName APINode
an)

gen_se_mp :: APINode -> Q [Dec]
gen_se_mp :: APINode -> Q [Dec]
gen_se_mp APINode
as = Name -> TypeQ -> ExpQ -> Q [Dec]
simpleSigD (APINode -> Name
map_enum_nm APINode
as)
                          [t| Map.Map T.Text $tc |]
                          [e| genTextMap $(varE $ text_enum_nm as) |]
  where
    tc :: TypeQ
tc  = Name -> TypeQ
conT (Name -> TypeQ) -> Name -> TypeQ
forall a b. (a -> b) -> a -> b
$ APINode -> Name
rep_type_nm APINode
as

genTextMap :: (Ord a,Bounded a,Enum a) => (a->T.Text) -> Map.Map T.Text a
genTextMap :: (a -> Text) -> Map Text a
genTextMap a -> Text
f = [(Text, a)] -> Map Text a
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [ (a -> Text
f a
x,a
x) | a
x<-[a
forall a. Bounded a => a
minBound..a
forall a. Bounded a => a
maxBound] ]