module Each.Transform
    ( transform
    , Env (..)
    , Result (..)
    ) where
import Control.Applicative
import Control.Monad.Reader
import Data.Monoid
import Language.Haskell.TH
import qualified Control.Monad
import qualified Data.Functor
import qualified Each.Invoke
data Result
    = Pure ExpQ 
    | Bind ExpQ 
data Env
    = Env
    { envType :: Maybe TypeQ
    
    }
type M = ReaderT Env Q
transform :: Exp -> Env -> Q Result
transform ex env = runReaderT (transform' ex) env
transform' :: Exp -> M Result
transform' (InfixE Nothing (VarE v) (Just x))
    | v == '(Each.Invoke.~!) = impurify x
transform' (AppE (VarE v) x)
    | v == 'Each.Invoke.bind = impurify x
transform' (InfixE (Just (VarE vf)) (VarE vo) (Just x))
    | vf == 'Each.Invoke.bind && vo == '(Prelude.$) = impurify x
transform' (VarE vn) = pure $ Pure (varE vn)
transform' (ConE cn) = pure $ Pure (conE cn)
transform' (LitE lit) = pure $ Pure (litE lit)
transform' (AppE f x) =
        liftA2 go (transform' f) (transform' x)
    where
        go (Pure ef) (Pure ex) = Pure [| $(ef) $(ex) |]
        go (Pure ef) (Bind ex) = Bind [| Data.Functor.fmap $(ef) $(ex) |]
        go (Bind ef) (Pure ex) = Bind [| Data.Functor.fmap ($ $(ex)) $(ef) |]
        go (Bind ef) (Bind ex) = Bind [| (Control.Applicative.<*>) $(ef) $(ex) |]
transform' (InfixE Nothing op Nothing) =
    transform' op
transform' (InfixE (Just lhs) op Nothing) =
    transform' (AppE op lhs)
transform' (InfixE Nothing op (Just rhs)) =
    lift [| Prelude.flip $(pure op) $(pure rhs) |] >>= transform'
transform' (InfixE (Just lhs) op (Just rhs)) =
    transform' (AppE (AppE op lhs) rhs)
transform' (SigE x ty) = transform' x >>= \tx -> case tx of
    Pure ex -> pure $ Pure [| $(ex) :: $(pure ty) |]
    Bind ex -> do
        Env { envType = mety } <- ask
        case mety of
            Nothing -> do
                ok <- lift $ isExtEnabled PartialTypeSignatures
                if ok
                    
                    then pure $ Bind [| $(ex) :: $(pure WildCardT) $(pure ty) |]
                    else fail errSig
            Just ety ->
                
                pure $ Bind [| $(ex) :: $(ety) $(pure ty) |]
transform' x = fail (errUnsupportedSyntax x)
impurify :: Exp -> M Result
impurify x =
        go <$> transform' x
    where
        go (Pure e) = Bind e
        go (Bind e) = Bind [| Control.Monad.join $(e) |]
errUnsupportedSyntax :: Exp -> String
errUnsupportedSyntax x = "each: Unsupported syntax in " <> pprint x
errSig :: String
errSig =
    "each: Using type signatures on expressions containing 'bind' requires \
    \specifying the context type using 'eachWith', or enabling \
    \-XPartialTypeSignatures."