-- NOTE: Cannot be guessed as it inducts on the data type (not its constructors)

-- | Derivation for the 'Typeable' class, as described in the Scrap
-- Your Boilerplate papers.  This derivation generates instances for
-- all kinds of TypeableK classes; as such we do NOT require the
-- GHC-specific generic downkinding instances to provide lower kind
-- instances.
--
-- The generated 'TypeRep' uses only the base name of the type, so
-- identically named types in different modules can be treated as the
-- same, with disasterous consequences.
--
-- Also creates a @typename_\<the type name\>@ value to hold the
-- 'TypeRep'.

module Data.Derive.Typeable(makeTypeable) where

import Language.Haskell.TH.All
import Data.Char

-- based on the macros in: http://darcs.haskell.org/packages/base/include/Typeable.h

{-
#define INSTANCE_TYPEABLE1(tycon,tcname,str) \
tcname = mkTyCon str; \
instance Typeable1 tycon where { typeOf1 _ = mkTyConApp tcname [] }; \
instance Typeable a => Typeable (tycon a) where { typeOf = typeOfDefault }

#define INSTANCE_TYPEABLE2(tycon,tcname,str) \
tcname = mkTyCon str; \
instance Typeable2 tycon where { typeOf2 _ = mkTyConApp tcname [] }; \
instance Typeable a => Typeable1 (tycon a) where { \
  typeOf1 = typeOf1Default }; \
instance (Typeable a, Typeable b) => Typeable (tycon a b) where { \
  typeOf = typeOfDefault }

#define INSTANCE_TYPEABLE3(tycon,tcname,str) \
tcname = mkTyCon str; \
instance Typeable3 tycon where { typeOf3 _ = mkTyConApp tcname [] }; \
instance Typeable a => Typeable2 (tycon a) where { \
  typeOf2 = typeOf2Default }; \
instance (Typeable a, Typeable b) => Typeable1 (tycon a b) where { \
  typeOf1 = typeOf1Default }; \
instance (Typeable a, Typeable b, Typeable c) => Typeable (tycon a b c) where { \
  typeOf = typeOfDefault }
-}


makeTypeable :: Derivation
makeTypeable = derivation typeable' "Typeable"
typeable' dat = (funN nam [sclause [] (l1 "mkTyCon" $ lit . show $ qualifiedDataName dat)])
                : map f [0..dataArity dat]
    where
        nam = [if x == '.' then '_' else x | x <- "typename_" ++ dataName dat]

        f n = InstanceD (map (l1 "Typeable") vars)
                        (l1 ("Typeable"++sn) $ lK (dataName dat) vars)
                        [funN ("typeOf"++sn) [sclause [WildP | n == 0] def]]
            where
                def = if n == 0 then l2 "mkTyConApp" (l0 nam) (lst [])
                                else l0 ("typeOf" ++ sn ++ "Default")
                vars = take n $ map (vr . return) ['a'..]
                sn = let i = dataArity dat - n in if i == 0 then "" else show i