{-# LANGUAGE CPP, TemplateHaskell #-}
module Data.GADT.Show.TH
    ( DeriveGShow(..)
    ) where

import Control.Applicative
import Control.Monad
import Data.GADT.Show
import Data.List
import Language.Haskell.TH
import Language.Haskell.TH.Extras

class DeriveGShow t where
    deriveGShow :: t -> Q [Dec]

instance DeriveGShow Name where
    deriveGShow typeName = do
        typeInfo <- reify typeName
        case typeInfo of
            TyConI dec -> deriveGShow dec
            _ -> fail "deriveGShow: the name of a type constructor is required"

instance DeriveGShow Dec where
    deriveGShow (InstanceD cxt (AppT instType dataType) decs)
        | headOfType instType == ''GShow = do
            let dataTypeName = headOfType dataType
            dataTypeInfo <- reify dataTypeName
            case dataTypeInfo of
                TyConI (DataD dataCxt name bndrs cons _) -> do
                    gshowDec <- gshowFunction cons
                    return [InstanceD cxt (AppT instType dataType) [gshowDec]]
                _ -> fail "deriveGShow: the name of an algebraic data type constructor is required"
    deriveGShow (DataD dataCxt name bndrs cons _) = return <$> inst
        where
            inst = instanceD (cxt (map return dataCxt)) (appT (conT ''GShow) (conT name)) [gshowDec]
            gshowDec = gshowFunction cons
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 612
    deriveGShow (DataInstD dataCxt name tyArgs cons _) = return <$> inst
        where
            inst = instanceD (cxt (map return dataCxt)) (appT (conT ''GShow) (foldl1 appT (map return $ (ConT name : init tyArgs)))) [gshowDec]
            -- TODO: figure out proper number of family parameters vs instance parameters
            gshowDec = gshowFunction cons
#endif

instance DeriveGShow t => DeriveGShow [t] where
    deriveGShow [it] = deriveGShow it
    deriveGShow _ = fail "deriveGShow: [] instance only applies to single-element lists"

instance DeriveGShow t => DeriveGShow (Q t) where
    deriveGShow = (>>= deriveGShow)

gshowFunction = funD 'gshowsPrec . map gshowClause

gshowClause con = do
    let conName  = nameOfCon con
        argTypes = argTypesOfCon con
        nArgs    = length argTypes
        
        precName = mkName "p"
    
    argNames <- replicateM nArgs (newName "x")
    
    clause [varP precName, conP conName (map varP argNames)]
        (normalB (gshowBody (varE precName) conName argNames)) []

showsName name = [| showString $(litE . stringL $ nameBase name) |]

gshowBody prec conName [] = showsName conName
gshowBody prec conName argNames = 
    [| showParen ($prec > 10) $( composeExprs $ intersperse [| showChar ' ' |]
        ( showsName conName
        : [ [| showsPrec 11 $arg |]
          | argName <- argNames, let arg = varE argName
          ]
        )) 
     |]
