{-# LANGUAGE CPP #-}

-- | ADT getters generation with Template Haskell
--
-- Example:
--
-- > {-# LANGUAGE TemplateHaskell #-}
-- > data Blah a = NoBlah | YesBlah a | ManyBlah a Int
-- > $(mkADTGetters ''Blah)
--
-- Generates
--
-- > gNoBlah :: Blah a -> Maybe ()
-- > gYesBlah :: Blah a -> Maybe a
-- > gManyBlah :: Blah a -> Maybe (a, Int)
--
-- Where
--
-- > gYesBlah (YesBlah a) = Just a
-- > gYesBlah _ = Nothing
--
-- etc.

module Data.ADT.Getters
    ( mkADTGetters
    ) where

import Language.Haskell.TH.Syntax

mkADTGetters :: Name -> Q [Dec]
mkADTGetters typeName = do
    TyConI (DataD _ _ typeVars constructors _) <- reify typeName
    return $ constructors >>= mkADTGetterFunc typeName typeVars

#if !(MIN_VERSION_template_haskell(2,4,0))
type TyVarBndr = Name
#endif

tyVarBndrName :: TyVarBndr -> Name
#if MIN_VERSION_template_haskell(2,4,0)
tyVarBndrName (PlainTV name) = name
tyVarBndrName (KindedTV name _) = name
#else
tyVarBndrName = id
#endif

mkADTGetterFunc :: Name -> [TyVarBndr] -> Con -> [Dec]
mkADTGetterFunc typeName typeVars constructor =
    [ SigD resName
        . ForallT typeVars []
        . AppT (AppT ArrowT (foldl AppT (ConT typeName) (map (VarT . tyVarBndrName) typeVars)))
        . AppT (ConT (mkName "Maybe"))
        $ case containedTypes of
        [] -> TupleT 0
        [x] -> x
        xs -> foldl AppT (TupleT (length xs)) xs
    , FunD resName
        [ Clause [ConP name (map VarP varNames)] clauseJust []
        , Clause [WildP] clauseNothing []
        ]
    ]
    where
        NormalC name params = constructor
        containedTypes = map snd params
        resName = mkName $ 'g' : nameBase name
        varNames = map (mkName . ('x' :) . show) [0 .. length params - 1]
        clauseJust =
            NormalB . AppE (ConE (mkName "Just"))
            $ case varNames of
            [] -> TupE []
            [x] -> VarE x
            xs -> TupE (map VarE xs)
        clauseNothing = NormalB . ConE . mkName $ "Nothing"