module Debug.Hoed.TH (debug, obs) where
import           Control.Monad
import           Data.Generics.Uniplate.Data
import           Data.List                   (group, nub, sort, (\\))
import           Data.Text (pack)
import           Debug.Hoed
import           Debug.Hoed
import           Debug.Hoed.Compat
import           Language.Haskell.TH
import           Language.Haskell.TH.Syntax
obs :: Q [Dec] -> Q [Dec]
obs decs = do
  decs <- decs
  names <- sequence [ (n,) <$> newName(nameBase n ++ "Obs") | FunD n _ <- decs]
  fmap concat $ forM decs $ \dec ->
    case dec of
      FunD n xx -> do
        let Just n' = lookup n names
            nb = nameBase n
        newDecl <- funD n [clause [] (normalB [| observe (pack nb) $(varE n')|]) []]
        return [newDecl, FunD n' xx]
      SigD n ty | Just n' <- lookup n names -> do
        dec' <- adjustSig n ty
        return [dec']
      _ ->
        return [dec]
debug :: Q [Dec] -> Q [Dec]
debug q = do
  decs <- q
  names <- sequence [ (n,) <$> newName(nameBase n ++ "Debug") | FunD n _ <- decs]
  fmap concat $ forM decs $ \dec ->
    case dec of
      FunD n clauses -> do
        let Just n' = lookup n names
            nb = nameBase n
        newDecl <- funD n [clause [] (normalB [| observe (pack nb) $(varE n')|]) []]
        let clauses' = transformBi adjustValD clauses
        return [newDecl, FunD n' clauses']
      SigD n ty | Just n' <- lookup n names -> do
        dec' <- adjustSig n ty
        return [dec']
      _ ->
        return [dec]
nubOrd :: Ord a => [a] -> [a]
nubOrd = map head . group . sort
kindStar :: Type -> Q [Name]
kindStar t = return $
    nubOrd [x | VarT x <- universe t] \\     
    nubOrd [x | AppT (VarT x) _ <- universe t] 
adjustSig name (ForallT vars ctxt typ) = do
  vs <- kindStar typ
  return $
    SigD name $
    ForallT vars (nub $ map (addConstraint ''Observable . (:[]) . VarT) vs ++ ctxt) typ
adjustSig name other = adjustSig name $ ForallT [] [] other
adjustValD decl@ValD{} = transformBi adjustPat decl
adjustValD other       = other
adjustPat (VarP x) = ViewP (VarE 'observe `AppE` (VarE 'pack `AppE` toLit x)) (VarP x)
adjustPat x        = x
toLit (Name (OccName x) _) = LitE $ StringL x