{-# LANGUAGE PatternGuards, DeriveFunctor #-}

module IRTS.LangOpts where

import Control.Monad.State hiding (lift)
import Control.Applicative hiding (Const)

import IRTS.Lang
import Idris.Core.TT
import Idris.Core.CaseTree
import Data.List
import Debug.Trace

inlineAll :: [(Name, LDecl)] -> [(Name, LDecl)]
inlineAll lds = let defs = addAlist lds emptyContext in
                    map (\ (n, def) -> (n, doInline defs def)) lds

nextN :: State Int Name
nextN = do i <- get
           put (i + 1)
           return $ sMN i "in"

-- Inline inside a declaration. Variables are still Name at this stage.
-- Need to preserve uniqueness of variable names in the resulting definition,
-- so invent a new name for every variable we encounter
doInline :: LDefs -> LDecl -> LDecl
doInline defs d@(LConstructor _ _ _) = d
doInline defs (LFun opts topn args exp) 
      = let res = evalState (inlineWith [topn] (map (\n -> (n, LV (Glob n))) args) exp) 0 in
            LFun opts topn args res
  where
    inlineWith :: [Name] -> [(Name, LExp)] -> LExp -> State Int LExp
    inlineWith done env var@(LV (Glob n)) 
                                     = case lookup n env of
                                            Just t -> return t
                                            Nothing -> return var
    inlineWith done env (LLazyApp n es) = LLazyApp n <$> (mapM (inlineWith done env) es)
    inlineWith done env (LForce e) = LForce <$> inlineWith done env e
    inlineWith done env (LLazyExp e) = LLazyExp <$> inlineWith done env e
    -- Extend the environment for Let and Lam so that bound names aren't
    -- expanded with any top level argument definitions they shadow
    inlineWith done env (LLet n val sc) 
       = do n' <- nextN
            LLet n' <$> inlineWith done env val <*>
                        inlineWith done ((n, LV (Glob n')) : env) sc
    inlineWith done env (LLam args sc)
       = do ns' <- mapM (\n -> do n' <- nextN
                                  return (n, n')) args
            LLam (map snd ns') <$>
                 inlineWith done (map (\ (n,n') -> (n, LV (Glob n'))) ns' ++ env) sc
    inlineWith done env (LProj exp i) = LProj <$> inlineWith done env exp <*> return i
    inlineWith done env (LCon loc i n es)
       = LCon loc i n <$> mapM (inlineWith done env) es
    inlineWith done env (LCase ty e alts) 
       = LCase ty <$> inlineWith done env e <*> mapM (inlineWithAlt done env) alts
    inlineWith done env (LOp f es) = LOp f <$> mapM (inlineWith done env) es
    -- the interesting case!
    inlineWith done env (LApp t (LV (Glob n)) es)
       | n `notElem` done,
         [LFun opts _ args body] <- lookupCtxt n defs,
         Inline `elem` opts,
         length es == length args
           = do es' <- mapM (inlineWith done env) es
                inlineWith (n : done) (zip args es' ++ env) body
    inlineWith done env (LApp t f es)
       = LApp t <$> inlineWith done env f <*> mapM (inlineWith done env) es
    inlineWith done env (LForeign l t s args)
       = LForeign l t s <$> mapM (\(t, e) -> do e' <- inlineWith done env e
                                                return (t, e')) args
    inlineWith done env t = return t

    inlineWithAlt done env (LConCase i n es rhs)
       = do ns' <- mapM (\n -> do n' <- nextN
                                  return (n, n')) es
            LConCase i n (map snd ns') <$> 
              inlineWith done (map (\ (n,n') -> (n, LV (Glob n'))) ns' ++ env) rhs
    inlineWithAlt done env (LConstCase c e) = LConstCase c <$> inlineWith done env e
    inlineWithAlt done env (LDefaultCase e) = LDefaultCase <$> inlineWith done env e