{-# LANGUAGE UndecidableInstances #-}

{-# OPTIONS_GHC -fwarn-missing-signatures #-}

module Agda.Syntax.Translation.ReflectedToAbstract where

import Control.Monad.Reader

import Data.Traversable as Trav hiding (mapM)

import Agda.Syntax.Fixity
import Agda.Syntax.Literal
import Agda.Syntax.Position
import Agda.Syntax.Info
import Agda.Syntax.Common
import Agda.Syntax.Abstract as A hiding (Apply)
import Agda.Syntax.Abstract.Pattern
import Agda.Syntax.Reflected as R

import Agda.TypeChecking.Monad as M hiding (MetaInfo)
import Agda.Syntax.Scope.Monad (getCurrentModule)

import Agda.Utils.Maybe
import Agda.Utils.Monad
import Agda.Utils.List
import Agda.Utils.Functor
import Agda.Utils.Size

type Names = [Name]

type WithNames a = ReaderT Names TCM a
-- Note: we only need the TCM for fresh names

-- | Adds a new unique name to the current context.
withName :: String -> (Name -> WithNames a) -> WithNames a
withName s f = do
  name <- freshName_ s
  ctx  <- asks $ map nameConcrete
  let name' = head $ filter (notTaken ctx) $ iterate nextName name
  local (name:) $ f name'
  where
    notTaken xs x = isNoName x || nameConcrete x `notElem` xs

-- | Returns the name of the variable with the given de Bruijn index.
askName :: Int -> WithNames (Maybe Name)
askName i = reader (!!! i)

class ToAbstract r a | r -> a where
  toAbstract :: r -> WithNames a

-- | Translate reflected syntax to abstract, using the names from the current typechecking context.
toAbstract_ :: ToAbstract r a => r -> TCM a
toAbstract_ = withShowAllArguments . toAbstractWithoutImplicit

-- | Drop implicit arguments unless --show-implicit is on.
toAbstractWithoutImplicit :: ToAbstract r a => r -> TCM a
toAbstractWithoutImplicit x = runReaderT (toAbstract x) =<< getContextNames

instance ToAbstract r a => ToAbstract (Named name r) (Named name a) where
  toAbstract = traverse toAbstract

instance ToAbstract r a => ToAbstract (Arg r) (NamedArg a) where
  toAbstract (Arg i x) = Arg i <$> toAbstract (unnamed x)

instance ToAbstract [Arg Term] [NamedArg Expr] where
  toAbstract = traverse toAbstract

instance ToAbstract r Expr => ToAbstract (Dom r, Name) (A.TypedBinding) where
  toAbstract (Dom{domInfo = i,unDom = x}, name) = do
    dom <- toAbstract x
    return $ TBind noRange [unnamedArg i $ BindName name] dom

instance ToAbstract (Expr, Elim) Expr where
  toAbstract (f, Apply arg) = do
    arg     <- toAbstract arg
    showImp <- lift showImplicitArguments
    return $ if showImp || visible arg
             then App (setOrigin Reflected defaultAppInfo_) f arg
             else f

instance ToAbstract (Expr, Elims) Expr where
  toAbstract (f, elims) = foldM (curry toAbstract) f elims

instance ToAbstract r a => ToAbstract (R.Abs r) (a, Name) where
  toAbstract (Abs s x) = withName s' $ \name -> (,) <$> toAbstract x <*> return name
    where s' = if (isNoName s) then "z" else s -- TODO: only do this when var is free

instance ToAbstract Literal Expr where
  toAbstract l = return (A.Lit l)

instance ToAbstract Term Expr where
  toAbstract t = case t of
    R.Var i es -> do
      mname <- askName i
      case mname of
        Nothing -> do
          cxt   <- lift $ getContextTelescope
          names <- asks $ drop (size cxt) . reverse
          lift $ withShowAllArguments' False $ typeError $ DeBruijnIndexOutOfScope i cxt names
        Just name -> toAbstract (A.Var name, es)
    R.Con c es -> toAbstract (A.Con (unambiguous $ killRange c), es)
    R.Def f es -> do
      af <- lift $ mkDef (killRange f)
      toAbstract (af, es)
    R.Lam h t  -> do
      (e, name) <- toAbstract t
      let info  = setHiding h $ setOrigin Reflected defaultArgInfo
      return $ A.Lam exprNoRange (DomainFree $ unnamedArg info $ BindName name) e
    R.ExtLam cs es -> do
      name <- freshName_ extendedLambdaName
      m    <- lift $ getCurrentModule
      let qname   = qualify m name
          cname   = nameConcrete name
          defInfo = mkDefInfo cname noFixity' PublicAccess ConcreteDef noRange
      cs <- toAbstract $ map (QNamed qname) cs
      toAbstract (A.ExtendedLam exprNoRange defInfo qname cs, es)
    R.Pi a b   -> do
      (b, name) <- toAbstract b
      a         <- toAbstract (a, name)
      return $ A.Pi exprNoRange [a] b
    R.Sort s   -> toAbstract s
    R.Lit l    -> toAbstract l
    R.Meta x es    -> toAbstract (A.Underscore info, es)
      where info = emptyMetaInfo{ metaNumber = Just x }
    R.Unknown      -> return $ Underscore emptyMetaInfo

mkDef :: QName -> TCM A.Expr
mkDef f =
  ifM (isMacro . theDef <$> getConstInfo f)
      (return $ A.Macro f)
      (return $ A.Def f)

mkSet :: Expr -> Expr
mkSet e = App (setOrigin Reflected defaultAppInfo_) (A.Set exprNoRange 0) $ defaultNamedArg e

instance ToAbstract Sort Expr where
  toAbstract (SetS x) = mkSet <$> toAbstract x
  toAbstract (LitS x) = return $ A.Set exprNoRange x
  toAbstract UnknownS = return $ mkSet $ Underscore emptyMetaInfo

instance ToAbstract R.Pattern (Names, A.Pattern) where
  toAbstract pat = case pat of
    R.ConP c args -> do
      (names, args) <- toAbstractPats args
      return (names, A.ConP (ConPatInfo ConOCon patNoRange ConPatEager) (unambiguous $ killRange c) args)
    R.DotP    -> return ([], A.WildP patNoRange)
    R.VarP s | isNoName s -> withName "z" $ \ name -> return ([name], A.VarP $ BindName name)
        -- Ulf, 2016-08-09: Also bind noNames (#2129). This to make the
        -- behaviour consistent with lambda and pi.
        -- return ([], A.WildP patNoRange)
    R.VarP s  -> withName s $ \ name -> return ([name], A.VarP $ BindName name)
    R.LitP l  -> return ([], A.LitP l)
    R.AbsurdP -> return ([], A.AbsurdP patNoRange)
    R.ProjP d -> return ([], A.ProjP patNoRange ProjSystem $ unambiguous $ killRange d)

toAbstractPats :: [Arg R.Pattern] -> WithNames (Names, [NamedArg A.Pattern])
toAbstractPats pats = case pats of
    []   -> return ([], [])
    p:ps -> do
      (names,  p)  <- (distributeF . fmap distributeF) <$> toAbstract p
      (namess, ps) <- local (names++) $ toAbstractPats ps
      return (namess++names, p:ps)

instance ToAbstract (QNamed R.Clause) A.Clause where
  toAbstract (QNamed name (R.Clause pats rhs)) = do
    (names, pats) <- toAbstractPats pats
    rhs           <- local (names++) $ toAbstract rhs
    let lhs = spineToLhs $ SpineLHS (LHSRange noRange) name pats
    return $ A.Clause lhs [] (RHS rhs Nothing) noWhereDecls False
  toAbstract (QNamed name (R.AbsurdClause pats)) = do
    (_, pats) <- toAbstractPats pats
    let lhs = spineToLhs $ SpineLHS (LHSRange noRange) name pats
    return $ A.Clause lhs [] AbsurdRHS noWhereDecls False

instance ToAbstract [QNamed R.Clause] [A.Clause] where
  toAbstract = traverse toAbstract
