{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE TypeSynonymInstances      #-}
{-# LANGUAGE FlexibleInstances         #-}
{-# LANGUAGE FlexibleContexts          #-}
{-# LANGUAGE TupleSections             #-}
{-# LANGUAGE DeriveDataTypeable        #-}
{-# LANGUAGE ScopedTypeVariables       #-}


module Language.Haskell.Liquid.Types.Visitors (
  
  CBVisitable (..)
  
  -- * visitors
  , coreVisitor 
  , CoreVisitor (..)

  ) where

import           CoreSyn
import           Data.Hashable
import           DataCon

import           Data.List                        (foldl', (\\), delete)
import qualified Data.HashSet                     as S
import           Prelude                          hiding (error)
import           Language.Fixpoint.Misc
import           Language.Haskell.Liquid.GHC.API
import           Language.Haskell.Liquid.GHC.Misc ()


------------------------------------------------------------------------------
-------------------------------- A CoreBind Visitor --------------------------
------------------------------------------------------------------------------

-- TODO: syb-shrinkage

class CBVisitable a where
  freeVars :: S.HashSet Var -> a -> [Var]
  readVars :: a -> [Var]
  letVars  :: a -> [Var]
  literals :: a -> [Literal]

instance CBVisitable [CoreBind] where
  freeVars env cbs = (sortNub xs) \\ ys
    where xs = concatMap (freeVars env) cbs
          ys = concatMap bindings cbs

  readVars = concatMap readVars
  letVars  = concatMap letVars
  literals = concatMap literals

instance CBVisitable CoreBind where
  freeVars env (NonRec x e) = freeVars (extendEnv env [x]) e
  freeVars env (Rec xes)    = concatMap (freeVars env') es
                              where (xs,es) = unzip xes
                                    env'    = extendEnv env xs

  readVars (NonRec _ e)     = readVars e
  readVars (Rec xes)        = concat [x `delete` nubReadVars e |(x, e) <- xes]
    where nubReadVars = sortNub . readVars

  letVars (NonRec x e)      = x : letVars e
  letVars (Rec xes)         = xs ++ concatMap letVars es
    where
      (xs, es)              = unzip xes

  literals (NonRec _ e)      = literals e
  literals (Rec xes)         = concatMap (literals . snd) xes

instance CBVisitable (Expr Var) where
  freeVars = exprFreeVars
  readVars = exprReadVars
  letVars  = exprLetVars
  literals = exprLiterals

exprFreeVars :: S.HashSet Id -> Expr Id -> [Id]
exprFreeVars = go
  where
    go env (Var x)         = if x `S.member` env then [] else [x]
    go env (App e a)       = go env e ++ go env a
    go env (Lam x e)       = go (extendEnv env [x]) e
    go env (Let b e)       = freeVars env b ++ go (extendEnv env (bindings b)) e
    go env (Tick _ e)      = go env e
    go env (Cast e _)      = go env e
    go env (Case e x _ cs) = go env e ++ concatMap (freeVars (extendEnv env [x])) cs
    go _   _               = []

exprReadVars :: (CBVisitable (Alt t), CBVisitable (Bind t)) => Expr t -> [Id]
exprReadVars = go
  where
    go (Var x)             = [x]
    go (App e a)           = concatMap go [e, a]
    go (Lam _ e)           = go e
    go (Let b e)           = readVars b ++ go e
    go (Tick _ e)          = go e
    go (Cast e _)          = go e
    go (Case e _ _ cs)     = go e ++ concatMap readVars cs
    go _                   = []

exprLetVars :: Expr Var -> [Var]
exprLetVars = go
  where
    go (Var _)             = []
    go (App e a)           = concatMap go [e, a]
    go (Lam x e)           = x : go e
    go (Let b e)           = letVars b ++ go e
    go (Tick _ e)          = go e
    go (Cast e _)          = go e
    go (Case e x _ cs)     = x : go e ++ concatMap letVars cs
    go _                   = []

exprLiterals :: (CBVisitable (Alt t), CBVisitable (Bind t))
             => Expr t -> [Literal]
exprLiterals = go
  where
    go (Lit l)             = [l]
    go (App e a)           = concatMap go [e, a]
    go (Let b e)           = literals b ++ go e
    go (Lam _ e)           = go e
    go (Tick _ e)          = go e
    go (Cast e _)          = go e
    go (Case e _ _ cs)     = go e ++ concatMap literals cs
    go (Type t)            = go' t
    go _                   = []

    go' (LitTy tl)         = [tyLitToLit tl]
    go' _                  = []


    tyLitToLit (StrTyLit fs) = LitString (bytesFS fs)
    tyLitToLit (NumTyLit i)  = LitNumber LitNumInt (fromIntegral i) intPrimTy



instance CBVisitable (Alt Var) where
  freeVars env (a, xs, e) = freeVars env a ++ freeVars (extendEnv env xs) e
  readVars (_,_, e)       = readVars e
  letVars  (_,xs,e)       = xs ++ letVars e
  literals (c,_, e)       = literals c ++ literals e

instance CBVisitable AltCon where
  freeVars _ (DataAlt dc) = [ x | AnId x <- dataConImplicitTyThings dc]
  freeVars _ _            = []
  readVars _              = []
  letVars  _              = []
  literals (LitAlt l)     = [l]
  literals _              = []

extendEnv :: (Eq a, Hashable a) => S.HashSet a -> [a] -> S.HashSet a
extendEnv = foldl' (flip S.insert)

bindings :: Bind t -> [t]
bindings (NonRec x _) = [x]
bindings (Rec  xes  ) = map fst xes

----------------------------------------------------------------------------------------
-- | @BindVisitor@ allows for generic, context sensitive traversals over the @CoreBinds@ 
----------------------------------------------------------------------------------------
data CoreVisitor env acc = CoreVisitor 
  { envF  :: env -> Var             -> env 
  , bindF :: env -> acc -> Var      -> acc  
  , exprF :: env -> acc -> CoreExpr -> acc 
  }

coreVisitor :: (CoreVisitor env acc) -> env -> acc -> [CoreBind] -> acc
coreVisitor vis env acc cbs   = snd (foldl' step (env, acc) cbs) 
  where
    stepXE (env, acc) (x,e)   = (env', stepE env' acc'   e)  
      where 
        env'                  = envF  vis env     x 
        acc'                  = bindF vis env acc x  

    step ea (NonRec x e)      = stepXE ea (x, e) 
    step ea (Rec    xes)      = foldl' stepXE ea xes 

    -- step (env, acc) (NonRec x e) = stepXE env acc x e 
    -- step (env, acc) (Rec    xes) = (env', foldl' (stepE env') acc' es) 
      -- where 
        -- acc'                     = foldl' (bindF vis env') acc xs
        -- env'                     = foldl' (envF  vis)      env xs 
        -- xs                       = fst <$> xes 
        -- es                       = snd <$> xes
        -- foldl' (\(env, acc) (x, e) ->  )

    stepE env acc e              = goE env (exprF vis env acc e) e 

    goE _   acc (Var _)          = acc 
    goE env acc (App e1 e2)      = stepE  env (stepE env acc e1) e2 
    goE env acc (Tick _ e)       = stepE  env acc e 
    goE env acc (Cast e _)       = stepE  env acc e  
    goE env acc (Lam x e)        = snd (stepXE (env, acc) (x, e))
    goE env acc (Let b e)        = stepE env' acc' e where (env', acc') = step (env, acc) b 
    goE env acc (Case e _ _ cs)  = foldl' (goC env) (stepE env acc e) cs
    goE _   acc _                = acc 

    goC env acc (_, xs, e)       = stepE  env' acc' e 
      where
        env'                     = foldl' (envF  vis)     env xs 
        acc'                     = foldl' (bindF vis env) acc xs
