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

import Control.Applicative
import Control.Monad
import Control.Monad.Writer
import Data.GADT.TH.Internal
import Data.Functor.Identity
import Data.GADT.Show
import Data.Traversable (for)
import Data.List
import Data.Set (Set)
import qualified Data.Set as Set
import Language.Haskell.TH
import Language.Haskell.TH.Datatype

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

instance DeriveGShow Name where
 deriveGShow typeName = do
  typeInfo <- reifyDatatype typeName
  let instTypes = datatypeInstTypes typeInfo
      paramVars = Set.unions [freeTypeVariables t | t <- instTypes]
      instTypes' = case reverse instTypes of
        [] -> fail "deriveGEq: Not enough type parameters"
        (_:xs) -> reverse xs
      instanceHead = AppT (ConT ''GShow) (foldl AppT (ConT typeName) instTypes')
  (clauses, cxt) <- runWriterT (mapM (gshowClause typeName paramVars) (datatypeCons typeInfo))

  return [InstanceD Nothing (datatypeContext typeInfo ++ cxt) instanceHead [gshowFunction clauses]]

instance DeriveGShow Dec where
    deriveGShow = deriveForDec ''GShow $ \typeInfo -> do
      let
        instTypes = datatypeInstTypes typeInfo
        paramVars = Set.unions [freeTypeVariables t | t <- instTypes]
      clauses <- mapM (gshowClause (datatypeName typeInfo) paramVars) (datatypeCons typeInfo)
      return $ gshowFunction clauses

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 :: [Clause] -> Dec
gshowFunction clauses = FunD 'gshowsPrec clauses

isApplicationOf :: Type -> Type -> Bool
isApplicationOf t t' = t == t' || case t' of
  AppT u _ -> isApplicationOf t u
  _ -> False

gshowClause :: Name -> Set Name -> ConstructorInfo -> WriterT [Type] Q Clause
gshowClause typeName paramVars con = do
  let conName  = constructorName con
      argTypes = constructorFields con
      conTyVars = Set.fromList (map tvName (constructorVars con))

  precName <- lift $ newName "prec"
  argNames <- forM argTypes $ \_ -> lift $ newName "x"

  argShowExprs <- forM (zip argNames argTypes) $ \(n,t) -> do
    let useShow = do
          u <- lift $ reifyInstancesWithRigids paramVars ''Show [t]
          case u of
            (_:_) -> return ()
            _ -> tell [AppT (ConT ''Show) t]
          return [| showsPrec 11 $(varE n) |]
    case t of
      AppT tyFun tyArg -> do
        if isApplicationOf (ConT typeName) tyFun
          then return [| gshowsPrec 11 $(varE n) |]
          else useShow
      _ -> useShow

  let precPat = if null argNames
        then wildP
        else varP precName

  lift $ clause [precPat, conP conName (map varP argNames)]
    (normalB (gshowBody (varE precName) conName argShowExprs)) []

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

gshowBody :: Q Exp -> Name -> [Q Exp] -> Q Exp
gshowBody prec conName [] = showsName conName
gshowBody prec conName argShowExprs =
  let body = foldr (\e es -> [| $e . $es |]) [| id |] .
               intersperse [| showChar ' ' |] $
                 showsName conName : argShowExprs
  in [| showParen ($prec > 10) $body |]
