{-# LANGUAGE CPP, MultiParamTypeClasses, FunctionalDependencies,
             FlexibleInstances, UndecidableInstances, OverlappingInstances
  #-}

{-| Translation from "Agda.Syntax.Concrete" to "Agda.Syntax.Abstract". Involves scope analysis,
    figuring out infix operator precedences and tidying up definitions.
-}
module Agda.Syntax.Translation.ConcreteToAbstract
    ( ToAbstract(..), localToAbstract
    , concreteToAbstract_
    , concreteToAbstract
    , NewModuleQName(..)
    , OldName(..)
    , TopLevel(..)
    , TopLevelInfo(..)
    , topLevelModuleName
    ) where

import Prelude hiding (mapM)
import Control.Applicative
import Control.Monad.Reader hiding (mapM)
import Control.Monad.Error hiding (mapM)
import Data.Typeable
import Data.Traversable (mapM)
import Data.List ((\\), nub)

import Agda.Syntax.Concrete as C
import Agda.Syntax.Abstract as A
import Agda.Syntax.Position
import Agda.Syntax.Common
import Agda.Syntax.Info
import Agda.Syntax.Concrete.Definitions as C
import Agda.Syntax.Concrete.Operators
import Agda.Syntax.Fixity
import Agda.Syntax.Scope.Base
import Agda.Syntax.Scope.Monad
import Agda.Syntax.Strict

import Agda.TypeChecking.Monad.Base (TypeError(..), Call(..), typeError, TCErr(..))
import Agda.TypeChecking.Monad.Trace (traceCall, traceCallCPS, setCurrentRange)
import Agda.TypeChecking.Monad.State
import Agda.TypeChecking.Monad.Options

import {-# SOURCE #-} Agda.Interaction.Imports (scopeCheckImport)

import Agda.Utils.Monad
import Agda.Utils.Tuple
import Agda.Utils.List
import Agda.Utils.Fresh

#include "../../undefined.h"
import Agda.Utils.Impossible


{--------------------------------------------------------------------------
    Exceptions
 --------------------------------------------------------------------------}

notAModuleExpr e	    = typeError $ NotAModuleExpr e
notAnExpression e	    = typeError $ NotAnExpression e
notAValidLetBinding d	    = typeError $ NotAValidLetBinding d
nothingAppliedToHiddenArg e = typeError $ NothingAppliedToHiddenArg e

-- Debugging

printLocals :: Int -> String -> ScopeM ()
printLocals v s = verboseS "scope.top" v $ do
  locals <- scopeLocals <$> getScope
  reportSLn "" 0 $ s ++ " " ++ show locals

printScope :: String -> Int -> String -> ScopeM ()
printScope tag v s = verboseS ("scope." ++ tag) v $ do
  scope <- getScope
  reportSLn "" 0 $ s ++ " " ++ show scope

{--------------------------------------------------------------------------
    Helpers
 --------------------------------------------------------------------------}

lhsArgs :: C.Pattern -> (C.Name, [NamedArg C.Pattern])
lhsArgs p = case appView p of
    Arg _ (Named _ (IdentP (C.QName x))) : ps -> (x, ps)
    _				              -> __IMPOSSIBLE__
    where
	mkHead	  = Arg NotHidden . unnamed
	notHidden = Arg NotHidden . unnamed
	appView p = case p of
	    AppP p arg	  -> appView p ++ [arg]
	    OpAppP _ x ps -> mkHead (IdentP $ C.QName x) : map notHidden ps
	    ParenP _ p	  -> appView p
	    RawAppP _ _	  -> __IMPOSSIBLE__
	    _		  -> [ mkHead p ]

annotateDecl :: ScopeM A.Declaration -> ScopeM A.Declaration
annotateDecl m = annotateDecls $ (:[]) <$> m

annotateDecls :: ScopeM [A.Declaration] -> ScopeM A.Declaration
annotateDecls m = do
  ds <- m
  s  <- getScope
  return $ ScopedDecl s ds

annotateDefn :: ScopeM A.Definition -> ScopeM A.Definition
annotateDefn m = do
  d <- m
  s <- getScope
  return $ ScopedDef s d

annotateExpr :: ScopeM A.Expr -> ScopeM A.Expr
annotateExpr m = do
  e <- m
  s <- getScope
  return $ ScopedExpr s e

expandEllipsis :: C.Pattern -> [C.Pattern] -> C.Clause -> C.Clause
expandEllipsis _ _ c@(C.Clause _ (C.LHS _ _ _) _ _ _) = c
expandEllipsis p ps (C.Clause x (C.Ellipsis _ ps' es) rhs wh wcs) =
  C.Clause x (C.LHS p (ps ++ ps') es) rhs wh wcs

-- | Make sure that each variable occurs only once.
checkPatternLinearity :: [A.Pattern' e] -> ScopeM ()
checkPatternLinearity ps = case xs \\ nub xs of
    []	-> return ()
    ys	-> typeError $ RepeatedVariablesInPattern $ nub ys
  where
    xs = concatMap vars ps
    vars :: A.Pattern' e -> [C.Name]
    vars p = case p of
      A.VarP x	      -> [nameConcrete x]
      A.ConP _ _ args -> concatMap (vars . namedThing . unArg) args
      A.WildP _	      -> []
      A.AsP _ x p     -> nameConcrete x : vars p
      A.DotP _ _      -> []
      A.AbsurdP _     -> []
      A.LitP _	      -> []
      A.DefP _ _ args -> __IMPOSSIBLE__
      A.ImplicitP _   -> __IMPOSSIBLE__

-- | Compute the type of the record constructor (with bogus target type)
recordConstructorType :: [NiceDeclaration] -> C.Expr
recordConstructorType fields = build fs
  where
    fs = reverse $ dropWhile notField $ reverse fields

    notField NiceField{} = False
    notField _           = True

    build (NiceField r f _ _ x e : fs) = C.Pi [C.TypedBindings r NotHidden
                                                [C.TBind r [BName x f] e]
                                              ] $ build fs
      where r = getRange x
    build (d : fs)                     = C.Let noRange (notSoNiceDeclarations [d]) $ build fs
    build []                           = C.Prop noRange

checkModuleMacro apply r p a x tel m args open dir =
    withLocalVars $ do
    tel' <- toAbstract tel
    (x',m1,args') <- toAbstract ( NewModuleName x
                                , OldModuleName m
                                , args
                                )
    printScope "mod.inst" 20 "module macro"
    pushScope x'
    m0 <- getCurrentModule
    openModule_ m $ dir { C.publicOpen = True }
    printScope "mod.inst" 20 "opened source module"
    s : _ <- scopeStack <$> getScope
    (renD, renM) <- renamedCanonicalNames m1 m0 s
    modifyTopScope $ renameCanonicalNames renD renM
    printScope "mod.inst" 20 "renamed stuff"
    popScope p
    printScope "mod.inst" 20 "popped"
    bindModule p x m0
    case open of
      DontOpen -> return ()
      DoOpen   -> openModule_ (C.QName x) $ defaultImportDir { C.publicOpen = C.publicOpen dir }
    printScope "mod.inst" 20 $ case open of
      DontOpen  -> "didn't open"
      DoOpen    -> "opened"
    printScope "mod.inst" 10 $ "before stripping"
    stripNoNames
    printScope "mod.inst" 10 $ "after stripping"
    return [ apply info (m0 `withRangesOf` [x]) tel' m1 args' renD renM ]
  where
    info = ModuleInfo p a r

{--------------------------------------------------------------------------
    Translation
 --------------------------------------------------------------------------}

concreteToAbstract_ :: ToAbstract c a => c -> ScopeM a
concreteToAbstract_ x = toAbstract x

concreteToAbstract :: ToAbstract c a => ScopeInfo -> c -> ScopeM a
concreteToAbstract scope x = withScope_ scope (toAbstract x)

-- | Things that can be translated to abstract syntax are instances of this
--   class.
class ToAbstract concrete abstract | concrete -> abstract where
    toAbstract	  :: concrete -> ScopeM abstract

-- | This function should be used instead of 'toAbstract' for things that need
--   to keep track of precedences to make sure that we don't forget about it.
toAbstractCtx :: ToAbstract concrete abstract =>
		 Precedence -> concrete -> ScopeM abstract
toAbstractCtx ctx c = withContextPrecedence ctx $ toAbstract c

setContextCPS :: Precedence -> (a -> ScopeM b) ->
		 ((a -> ScopeM b) -> ScopeM b) -> ScopeM b
setContextCPS p ret f = do
  p' <- getContextPrecedence
  withContextPrecedence p $ f $ withContextPrecedence p' . ret

localToAbstractCtx :: ToAbstract concrete abstract =>
		     Precedence -> concrete -> (abstract -> ScopeM a) -> ScopeM a
localToAbstractCtx ctx c ret = setContextCPS ctx ret (localToAbstract c)

-- | This operation does not affect the scope, i.e. the original scope
--   is restored upon completion.
localToAbstract :: ToAbstract c a => c -> (a -> ScopeM b) -> ScopeM b
localToAbstract x ret = fst <$> localToAbstract' x ret

-- | Like 'localToAbstract' but returns the scope after the completion of the
--   second argument.
localToAbstract' :: ToAbstract c a => c -> (a -> ScopeM b) -> ScopeM (b, ScopeInfo)
localToAbstract' x ret = do
  scope <- getScope
  withScope scope $ ret =<< toAbstract x

instance (ToAbstract c1 a1, ToAbstract c2 a2) => ToAbstract (c1,c2) (a1,a2) where
  toAbstract (x,y) =
    (,) <$> toAbstract x <*> toAbstract y

instance (ToAbstract c1 a1, ToAbstract c2 a2, ToAbstract c3 a3) =>
	 ToAbstract (c1,c2,c3) (a1,a2,a3) where
    toAbstract (x,y,z) = flatten <$> toAbstract (x,(y,z))
	where
	    flatten (x,(y,z)) = (x,y,z)

instance ToAbstract c a => ToAbstract [c] [a] where
    toAbstract = mapM toAbstract

instance ToAbstract c a => ToAbstract (Maybe c) (Maybe a) where
    toAbstract Nothing  = return Nothing
    toAbstract (Just x) = Just <$> toAbstract x

-- Names ------------------------------------------------------------------

newtype NewName a = NewName a
newtype OldQName  = OldQName C.QName
newtype OldName   = OldName C.Name
newtype PatName   = PatName C.QName

instance ToAbstract (NewName C.Name) A.Name where
  toAbstract (NewName x) = do
    y <- freshAbstractName_ x
    bindVariable x y
    return y

instance ToAbstract (NewName C.BoundName) A.Name where
  toAbstract (NewName (BName x fx)) = do
    y <- freshAbstractName fx x
    bindVariable x y
    return y

nameExpr :: AbstractName -> A.Expr
nameExpr d = mk (anameKind d) $ anameName d
  where
    mk DefName = Def
    mk ConName = Con . AmbQ . (:[])

instance ToAbstract OldQName A.Expr where
  toAbstract (OldQName x) = do
    qx <- resolveName x
    reportSLn "scope.name" 10 $ "resolved " ++ show x ++ ": " ++ show qx
    case qx of
      VarName x'         -> return $ A.Var x'
      DefinedName d      -> return $ nameExpr d
      ConstructorName ds -> return $ A.Con $ AmbQ (map anameName ds)
      UnknownName        -> notInScope x

data APatName = VarPatName A.Name
	      | ConPatName [AbstractName]

instance ToAbstract PatName APatName where
  toAbstract (PatName x) = do
    reportSLn "scope.pat" 10 $ "checking pattern name: " ++ show x
    rx <- resolveName x
    z  <- case (rx, x) of
      -- TODO: warn about shadowing
      (VarName y,     C.QName x)			  -> return $ Left x -- typeError $ RepeatedVariableInPattern y x
      (DefinedName d, C.QName x) | DefName == anameKind d -> return $ Left x
      (UnknownName,   C.QName x)			  -> return $ Left x
      (ConstructorName ds, _)	                          -> return $ Right ds
      _							  -> fail $ "not a constructor: " ++ show x -- TODO
    case z of
      Left x  -> do
	reportSLn "scope.pat" 10 $ "it was a var: " ++ show x
	p <- VarPatName <$> toAbstract (NewName x)
	printLocals 10 "bound it:"
	return p
      Right cs -> do
	reportSLn "scope.pat" 10 $ "it was a con: " ++ show (map anameName cs)
	return $ ConPatName cs

-- Should be a defined name.
instance ToAbstract OldName A.QName where
  toAbstract (OldName x) = do
    rx <- resolveName (C.QName x)
    case rx of
      DefinedName d -> return $ anameName d
      _		    -> __IMPOSSIBLE__

newtype NewModuleName  = NewModuleName  C.Name
newtype NewModuleQName = NewModuleQName C.QName
newtype OldModuleName  = OldModuleName  C.QName

instance ToAbstract NewModuleName A.ModuleName where
  toAbstract (NewModuleName x) = mnameFromList . (:[]) <$> freshAbstractName_ x

instance ToAbstract NewModuleQName A.ModuleName where
  toAbstract (NewModuleQName q) = do
    ms <- resolveModule' q
    case ms of
      [] -> foldr1 A.qualifyM <$> mapM (toAbstract . NewModuleName) (toList q)
      ms -> typeError $ ShadowedModule $
                          map ((`withRangesOfQ` q) . amodName) ms
    where
      toList (C.QName  x) = [x]
      toList (C.Qual m x) = m : toList x

instance ToAbstract OldModuleName A.ModuleName where
  toAbstract (OldModuleName q) = amodName <$> resolveModule q

-- Expressions ------------------------------------------------------------

-- | Peel off 'C.HiddenArg' and represent it as an 'NamedArg'.
mkNamedArg :: C.Expr -> NamedArg C.Expr
mkNamedArg (C.HiddenArg _ e) = Arg Hidden e
mkNamedArg e		     = Arg NotHidden $ unnamed e

-- | Peel off 'C.HiddenArg' and represent it as an 'Arg', throwing away any name.
mkArg :: C.Expr -> Arg C.Expr
mkArg (C.HiddenArg _ e) = Arg Hidden $ namedThing e
mkArg e			= Arg NotHidden e

instance ToAbstract C.Expr A.Expr where
  toAbstract e =
    traceCall (ScopeCheckExpr e) $ annotateExpr $ case e of
  -- Names
      Ident x -> toAbstract (OldQName x)

  -- Literals
      C.Lit l -> return $ A.Lit l

  -- Meta variables
      C.QuestionMark r n -> do
	scope <- getScope
	return $ A.QuestionMark $ MetaInfo
		    { metaRange  = r
		    , metaScope  = scope
		    , metaNumber = n
		    }
      C.Underscore r n -> do
	scope <- getScope
	return $ A.Underscore $ MetaInfo
		    { metaRange  = r
		    , metaScope  = scope
		    , metaNumber = n
		    }

  -- Raw application
      C.RawApp r es -> do
	e <- parseApplication es
	toAbstract e

  -- Application
      C.App r e1 e2 -> do
	e1 <- toAbstractCtx FunctionCtx e1
	e2 <- toAbstractCtx ArgumentCtx e2
	return $ A.App (ExprRange r) e1 e2

  -- Operator application
      C.OpApp r op es -> toAbstractOpApp op es

  -- With application
      C.WithApp r e es -> do
	e  <- toAbstractCtx WithFunCtx e
	es <- mapM (toAbstractCtx WithArgCtx) es
	return $ A.WithApp (ExprRange r) e es

  -- Malplaced hidden argument
      C.HiddenArg _ _ -> nothingAppliedToHiddenArg e

  -- Lambda
      C.AbsurdLam r h -> return $ A.AbsurdLam (ExprRange r) h

      e0@(C.Lam r bs e) -> do
	localToAbstract bs $ \(b:bs') -> do
	e	 <- toAbstractCtx TopCtx e
	let info = ExprRange r
	return $ A.Lam info b $ foldr mkLam e bs'
	where
	    mkLam b e = A.Lam (ExprRange $ fuseRange b e) b e

  -- Function types
      C.Fun r e1 e2 -> do
	e1 <- toAbstractCtx FunctionSpaceDomainCtx $ mkArg e1
	e2 <- toAbstractCtx TopCtx e2
	let info = ExprRange r
	return $ A.Fun info e1 e2

      e0@(C.Pi tel e) ->
	localToAbstract tel $ \tel -> do
	e    <- toAbstractCtx TopCtx e
	let info = ExprRange (getRange e0)
	return $ A.Pi info tel e

  -- Sorts
      C.Set _    -> return $ A.Set (ExprRange $ getRange e) 0
      C.SetN _ n -> return $ A.Set (ExprRange $ getRange e) n
      C.Prop _   -> return $ A.Prop $ ExprRange $ getRange e

  -- Let
      e0@(C.Let _ ds e) ->
	localToAbstract (LetDefs ds) $ \ds' -> do
	e	 <- toAbstractCtx TopCtx e
	let info = ExprRange (getRange e0)
	return $ A.Let info ds' e

  -- Record construction
      C.Rec r fs  -> do
	let (xs, es) = unzip fs
	es <- toAbstractCtx TopCtx es
	return $ A.Rec (ExprRange r) $ zip xs es

  -- Parenthesis
      C.Paren _ e -> toAbstractCtx TopCtx e

  -- Pattern things
      C.As _ _ _ -> notAnExpression e
      C.Dot _ _  -> notAnExpression e
      C.Absurd _ -> notAnExpression e

instance ToAbstract C.LamBinding A.LamBinding where
  toAbstract (C.DomainFree h x) = A.DomainFree h <$> toAbstract (NewName x)
  toAbstract (C.DomainFull tb)	= A.DomainFull <$> toAbstract tb

instance ToAbstract C.TypedBindings A.TypedBindings where
  toAbstract (C.TypedBindings r h bs) = A.TypedBindings r h <$> toAbstract bs

instance ToAbstract C.TypedBinding A.TypedBinding where
  toAbstract (C.TBind r xs t) = do
    t' <- toAbstractCtx TopCtx t
    xs' <- toAbstract (map NewName xs)
    return $ A.TBind r xs' t'
  toAbstract (C.TNoBind e) = do
    e <- toAbstractCtx TopCtx e
    return (A.TNoBind e)

newtype TopLevel a = TopLevel a

-- | Returns the scope inside the checked module.
scopeCheckModule :: Range -> Access -> IsAbstract -> C.QName -> A.ModuleName -> C.Telescope -> [C.Declaration] ->
		    ScopeM (ScopeInfo, [A.Declaration])
scopeCheckModule r a c x m tel ds = do
  pushScope m
  qm <- getCurrentModule
  ds <- withLocalVars $ do
	  tel <- toAbstract tel
	  (:[]) . A.Section info (qm `withRangesOfQ` x) tel <$>
            toAbstract ds
  scope <- getScope
  popScope a
  bindQModule a x qm
  return (scope, ds)
  where
    info = ModuleInfo a c r

data TopLevelInfo = TopLevelInfo
	{ topLevelDecls :: [A.Declaration]
	, outsideScope  :: ScopeInfo
	, insideScope	:: ScopeInfo
	}

-- | The top-level module name.

topLevelModuleName :: TopLevelInfo -> A.ModuleName
topLevelModuleName topLevel = case scopeStack (insideScope topLevel) of
  []    -> __IMPOSSIBLE__
  s : _ -> scopeName s

-- Top-level declarations are always (import|open)* module
instance ToAbstract (TopLevel [C.Declaration]) TopLevelInfo where
    toAbstract (TopLevel ds) = case splitAt (length ds - 1) ds of
	(ds', [C.Module r m tel ds]) -> do
	  setTopLevelModule m
	  am	       <- toAbstract (NewModuleQName m)
	  ds'	       <- toAbstract ds'
	  (scope0, ds) <- scopeCheckModule r PublicAccess ConcreteDef m am tel ds
	  scope	       <- getScope
	  return $ TopLevelInfo (ds' ++ ds) scope scope0
	_ -> __IMPOSSIBLE__


niceDecls :: [C.Declaration] -> ScopeM [NiceDeclaration]
niceDecls ds = case runNice $ niceDeclarations ds of
  Left e   -> throwError $ Exception (getRange e) (show e)
  Right ds -> return ds

instance ToAbstract [C.Declaration] [A.Declaration] where
  toAbstract ds = toAbstract =<< niceDecls ds

newtype LetDefs = LetDefs [C.Declaration]
newtype LetDef = LetDef NiceDeclaration

instance ToAbstract LetDefs [A.LetBinding] where
    toAbstract (LetDefs ds) =
	concat <$> (toAbstract =<< map LetDef <$> niceDecls ds)

instance ToAbstract LetDef [A.LetBinding] where
    toAbstract (LetDef d) =
	case d of
	    NiceDef _ c [C.Axiom _ _ _ _ x t] [C.FunDef _ _ _ _ _ _ [cl]] ->
		do  e <- letToAbstract cl
		    t <- toAbstract t
		    x <- toAbstract (NewName x)
		    return [ A.LetBind (LetRange $ getRange c) x t e ]

            -- You can't open public in a let
            NiceOpen r x dirs | not (C.publicOpen dirs) -> do
              m	      <- toAbstract (OldModuleName x)
              n	      <- length . scopeLocals <$> getScope
              openModule_ x dirs
              return []

            NiceModuleMacro r p a x tel e open dir | not (C.publicOpen dir) -> case appView e of
              AppView (Ident m) args -> checkModuleMacro LetApply r p a x tel m args open dir
              _                      -> notAModuleExpr e

	    _	-> notAValidLetBinding d
	where
	    letToAbstract (C.Clause top clhs@(C.LHS p [] []) (C.RHS rhs) NoWhere []) = do
		p    <- parseLHS (Just top) p
		localToAbstract (snd $ lhsArgs p) $ \args ->
		    do	rhs <- toAbstract rhs
			foldM lambda rhs (reverse args)  -- just reverse because these DomainFree
	    letToAbstract _ = notAValidLetBinding d

	    -- Named patterns not allowed in let definitions
	    lambda e (Arg h (Named Nothing (A.VarP x))) = return $ A.Lam i (A.DomainFree h x) e
		where
		    i = ExprRange (fuseRange x e)
	    lambda e (Arg h (Named Nothing (A.WildP i))) =
		do  x <- freshNoName (getRange i)
		    return $ A.Lam i' (A.DomainFree h x) e
		where
		    i' = ExprRange (fuseRange i e)
	    lambda _ _ = notAValidLetBinding d

instance ToAbstract C.Pragma [A.Pragma] where
    toAbstract (C.OptionsPragma _ opts) = return [ A.OptionsPragma opts ]
    toAbstract (C.CompiledTypePragma _ x hs) = do
      e <- toAbstract $ OldQName x
      case e of
        A.Def x -> return [ A.CompiledTypePragma x hs ]
        _       -> fail $ "Bad compiled type: " ++ show x  -- TODO: error message
    toAbstract (C.CompiledDataPragma _ x hs hcs) = do
      e <- toAbstract $ OldQName x
      case e of
        A.Def x -> return [ A.CompiledDataPragma x hs hcs ]
        _       -> fail $ "Not a datatype: " ++ show x  -- TODO: error message
    toAbstract (C.CompiledPragma _ x hs) = do
      e <- toAbstract $ OldQName x
      y <- case e of
            A.Def x -> return x
            A.Con _ -> fail "Use HASKELL_DATA for constructors" -- TODO
            _       -> __IMPOSSIBLE__
      return [ A.CompiledPragma y hs ]
    toAbstract (C.BuiltinPragma _ b e) = do
	e <- toAbstract e
	return [ A.BuiltinPragma b e ]
    toAbstract (C.LinePragma _ _ _) = return []
    toAbstract (C.ImportPragma _ i) = do
      addHaskellImport i
      return []

-- Only constructor names are bound by definitions.
instance ToAbstract NiceDefinition Definition where

    toAbstract d = annotateDefn $ case d of

    -- Function definitions
      C.FunDef r ds f p a x cs ->
        traceCall (ScopeCheckDefinition d) $ do
          (x',cs') <- toAbstract (OldName x,cs)
          return $ A.FunDef (mkDefInfo x f p a r) x' cs'

    -- Data definitions
      C.DataDef r ind f p a x pars cons ->
        traceCall (ScopeCheckDefinition d) $
        withLocalVars $ do

          -- Check for duplicate constructors
          do let cs   = map conName cons
                 dups = nub $ cs \\ nub cs
                 bad  = filter (`elem` dups) cs
             unless (distinct cs) $
               setCurrentRange (getRange bad) $
                  typeError $ DuplicateConstructors dups

          pars <- toAbstract pars
          cons <- toAbstract (map Constr cons)
          x'   <- toAbstract (OldName x)
          printScope "data" 20 $ "Checked data " ++ show x
          return $ A.DataDef (mkDefInfo x f p a r) x' ind pars cons
        where
          conName (C.Axiom _ _ _ _ c _) = c
          conName _ = __IMPOSSIBLE__

    -- Record definitions (mucho interesting)
      C.RecDef r f p a x pars fields ->
        traceCall (ScopeCheckDefinition d) $
        withLocalVars $ do
          pars   <- toAbstract pars
          x'     <- toAbstract (OldName x)
          contel <- toAbstract $ recordConstructorType fields
          let m = mnameFromList $ (:[]) $ last $ qnameToList x'
          printScope "rec" 15 "before record"
          pushScope m
          afields <- toAbstract fields
          printScope "rec" 15 "checked fields"
          qm <- getCurrentModule
          popScope p
          bindModule p x qm
          printScope "rec" 15 "record complete"
          return $ A.RecDef (mkDefInfo x f p a r) x' pars contel afields

-- The only reason why we return a list is that open declarations disappears.
-- For every other declaration we get a singleton list.
instance ToAbstract NiceDeclaration A.Declaration where

  toAbstract d = annotateDecls $
    traceCall (ScopeCheckDeclaration d) $
    case d of

  -- Axiom
    C.Axiom r f p a x t -> do
      t' <- toAbstractCtx TopCtx t
      y  <- freshAbstractQName f x
      bindName p DefName x y
      return [ A.Axiom (mkDefInfo x f p a r) y t' ]

  -- Fields
    C.NiceField r f p a x t -> do
      t' <- toAbstractCtx TopCtx t
      y  <- freshAbstractQName f x
      bindName p DefName x y
      return [ A.Field (mkDefInfo x f p a r) y t' ]

  -- Primitive function
    PrimitiveFunction r f p a x t -> do
      t' <- toAbstractCtx TopCtx t
      y  <- freshAbstractQName f x
      bindName p DefName x y
      return [ A.Primitive (mkDefInfo x f p a r) y t' ]

  -- Definitions (possibly mutual)
    NiceDef r cs ts ds -> do
      (ts', ds') <- toAbstract (ts, ds)
      return [ Definition (DeclInfo C.noName_ r) ts' ds' ]
			  -- TODO: what does the info mean here?

  -- TODO: what does an abstract module mean? The syntax doesn't allow it.
    NiceModule r p a name tel ds -> do
      aname <- toAbstract (NewModuleQName name)
      snd <$> scopeCheckModule r p a name aname tel ds

    NiceModuleMacro r p a x tel e open dir -> case appView e of
      AppView (Ident m) args -> checkModuleMacro Apply r p a x tel m args open dir
      _                      -> notAModuleExpr e

    NiceOpen r x dir -> do
      m	      <- toAbstract (OldModuleName x)
      n	      <- length . scopeLocals <$> getScope

      printScope "open" 20 $ "opening " ++ show x
      -- Opening (privately) a submodule or opening into a non-parameterised module
      -- is fine. Otherwise we have to create a temporary module.
      if not (C.publicOpen dir) -- && (m `isSubModuleOf` current || n == 0)
	then do
	  reportSLn "scope.open" 20 "normal open"
	  openModule_ x dir
	  printScope "open" 20 $ "result:"
	  return []
	else do
	  reportSLn "scope.open" 20 "fancy open"
	  tmp <- nameConcrete <$> freshNoName (getRange x)
	  d   <- toAbstract $ NiceModuleMacro r PrivateAccess ConcreteDef
					    tmp [] (C.Ident x) DoOpen dir
	  printScope "open" 20 "result:"
	  return [d]

    NicePragma r p -> do
      ps <- toAbstract p
      return $ map (A.Pragma r) ps

    NiceImport r x as open dir -> do

      -- First scope check the imported module and return its name and
      -- interface. This is done with that module as the top-level module.
      (m, i) <- withTopLevelModule x $ do
	m <- toAbstract $ NewModuleQName x
	printScope "import" 10 "before import:"
	(m, i) <- scopeCheckImport m
	printScope "import" 10 $ "scope checked import: " ++ show i
	return (m, i)

      -- Abstract name for the imported module.
      m' <- case as of
	      Nothing -> return m
	      Just y  -> toAbstract $ NewModuleName y

      -- Now, we push a new scope with the name we want for the imported
      -- module containing its interface. We then do a public open on the
      -- imported module and pop the scope. This results in the concrete names
      -- getting renamed to use the "as" name (if any).
      pushScope m'
      modifyTopScope (`mergeScope` setScopeAccess PrivateAccess i)
      openModule_ x $ dir { publicOpen = True }
      popScope PrivateAccess

      -- Finally we bind the desired module name to the right abstract name.
      bindQModule PrivateAccess name m

      printScope "import" 10 "merged imported sig:"
      ds <- case open of
	DontOpen -> return []
	DoOpen   -> do
	  toAbstract [ C.Open r name dir { usingOrHiding = Hiding []
					 , renaming	 = []
					 }
		     ]
      return $ A.Import (ModuleInfo PublicAccess ConcreteDef r) m : ds
      where
	  name = maybe x C.QName as

newtype Constr a = Constr a

instance ToAbstract (Constr C.NiceDeclaration) A.Declaration where
    toAbstract (Constr (C.Axiom r f p a x t)) = do
	t' <- toAbstractCtx TopCtx t
	y  <- freshAbstractQName f x
	bindName p' ConName x y
	return $ A.Axiom (mkDefInfo x f p a r) y t'
	where
	    -- An abstract constructor is private (abstract constructor means
	    -- abstract datatype, so the constructor should not be exported).
	    p' = case (a, p) of
		    (AbstractDef, _) -> PrivateAccess
		    (_, p)	     -> p

    toAbstract _ = __IMPOSSIBLE__    -- a constructor is always an axiom

instance ToAbstract C.Clause A.Clause where
    toAbstract (C.Clause top (C.Ellipsis _ _ _) _ _ _) = fail "bad '...'" -- TODO: errors message
    toAbstract (C.Clause top lhs@(C.LHS p wps with) rhs wh wcs) = withLocalVars $ do
      let wcs' = map (expandEllipsis p wps) wcs
      lhs' <- toAbstract (LeftHandSide top p wps)
      printLocals 10 "after lhs:"
      let (whname, whds) = case wh of
	    NoWhere	   -> (Nothing, [])
	    AnyWhere ds	   -> (Nothing, ds)
	    SomeWhere m ds -> (Just m, ds)
      case whds of
	[] -> do
	  rhs <- toAbstract =<< toAbstractCtx TopCtx (RightHandSide with wcs' rhs)
	  return $ A.Clause lhs' rhs []
	_	-> do
	  m <- C.QName <$> maybe (nameConcrete <$> freshNoName noRange) return whname
	  let acc = maybe PrivateAccess (const PublicAccess) whname  -- unnamed where's are private
	  let tel = []
	  am <- toAbstract (NewModuleQName m)
	  (scope, ds) <- scopeCheckModule (getRange wh) acc ConcreteDef m am tel whds
	  setScope scope
	  -- the right hand side is checked inside the module of the local definitions
	  rhs <- toAbstractCtx TopCtx (RightHandSide with wcs' rhs)
	  qm <- getCurrentModule
	  case acc of
	    PublicAccess  -> popScope PublicAccess
	    PrivateAccess -> popScope_	-- unnamed where clauses are not in scope
	  bindQModule acc m qm
          rhs <- toAbstract rhs
	  return $ A.Clause lhs' rhs ds

data RightHandSide = RightHandSide [C.Expr] [C.Clause] C.RHS
data AbstractRHS = AbsurdRHS'
                 | WithRHS' [A.Expr] [C.Clause]  -- ^ The with clauses haven't been translated yet
                 | RHS' A.Expr

instance ToAbstract AbstractRHS A.RHS where
  toAbstract AbsurdRHS'       = return A.AbsurdRHS
  toAbstract (RHS' e)         = return $ A.RHS e
  toAbstract (WithRHS' es cs) = do
    m   <- getCurrentModule
    -- Hack
    NameId i _ <- fresh
    aux <- A.qualify m <$> freshName_ ("aux" ++ show i)
    A.WithRHS aux es <$> toAbstract cs

instance ToAbstract RightHandSide AbstractRHS where
  toAbstract (RightHandSide [] (_ : _) _)        = __IMPOSSIBLE__
  toAbstract (RightHandSide (_ : _) _ (C.RHS _)) = typeError $ BothWithAndRHS
  toAbstract (RightHandSide [] [] rhs)           = toAbstract rhs
  toAbstract (RightHandSide es cs C.AbsurdRHS)   = do
    es <- toAbstractCtx TopCtx es
    return $ WithRHS' es cs

instance ToAbstract C.RHS AbstractRHS where
    toAbstract C.AbsurdRHS = return $ AbsurdRHS'
    toAbstract (C.RHS e)   = RHS' <$> toAbstract e

data LeftHandSide = LeftHandSide C.Name C.Pattern [C.Pattern]

instance ToAbstract LeftHandSide A.LHS where
    toAbstract (LeftHandSide top lhs wps) =
      traceCall (ScopeCheckLHS top lhs) $ do
	p <- parseLHS (Just top) lhs
	printLocals 10 "before lhs:"
        let (x, ps) = lhsArgs p
	x    <- toAbstract (OldName x)
	args <- toAbstract ps
	wps  <- toAbstract =<< mapM (parseLHS Nothing) wps
	checkPatternLinearity (map (namedThing . unArg) args ++ wps)
	printLocals 10 "checked pattern:"
	args <- toAbstract args -- take care of dot patterns
	wps  <- toAbstract wps
	printLocals 10 "checked dots:"
	return $ A.LHS (LHSRange $ getRange (lhs, wps)) x args wps

instance ToAbstract c a => ToAbstract (Arg c) (Arg a) where
    toAbstract (Arg h e) = Arg h <$> toAbstractCtx (hiddenArgumentCtx h) e

instance ToAbstract c a => ToAbstract (Named name c) (Named name a) where
    toAbstract (Named n e) = Named n <$> toAbstract e

-- Patterns are done in two phases. First everything but the dot patterns, and
-- then the dot patterns. This is because dot patterns can refer to variables
-- bound anywhere in the pattern.

instance ToAbstract c a => ToAbstract (A.Pattern' c) (A.Pattern' a) where
    toAbstract = mapM toAbstract

instance ToAbstract C.Pattern (A.Pattern' C.Expr) where

    toAbstract p@(C.IdentP x) = do
	px <- toAbstract (PatName x)
	case px of
	    VarPatName y  -> return $ VarP y
	    ConPatName ds -> return $ ConP (PatRange (getRange p))
                                           (AmbQ $ map anameName ds)
                                           []

    toAbstract p0@(AppP p q) = do
	(p', q') <- toAbstract (p,q)
	case p' of
	    ConP _ x as -> return $ ConP info x (as ++ [q'])
	    DefP _ x as -> return $ DefP info x (as ++ [q'])
	    _		-> typeError $ InvalidPattern p0
	where
	    r = getRange p0
	    info = PatSource r $ \pr -> if appBrackets pr then ParenP r p0 else p0

    toAbstract p0@(OpAppP r op ps) = do
	p <- toAbstract (IdentP $ C.QName op)
	ps <- toAbstract ps
	case p of
	  ConP _ x as -> return $ ConP info x (as ++ map (Arg NotHidden . unnamed) ps)
	  DefP _ x as -> return $ DefP info x (as ++ map (Arg NotHidden . unnamed) ps)
	  _	      -> __IMPOSSIBLE__
	where
	    r = getRange p0
	    info = PatSource r $ \pr -> if appBrackets pr then ParenP r p0 else p0

    -- Removed when parsing
    toAbstract (HiddenP _ _) = __IMPOSSIBLE__
    toAbstract (RawAppP _ _) = __IMPOSSIBLE__

    toAbstract p@(C.WildP r)    = return $ A.WildP (PatSource r $ const p)
    toAbstract (C.ParenP _ p)   = toAbstract p
    toAbstract (C.LitP l)	= return $ A.LitP l
    toAbstract p0@(C.AsP r x p) = typeError $ NotSupported "@-patterns"
      {- do
	x <- toAbstract (NewName x)
	p <- toAbstract p
	return $ A.AsP info x p
	where
	    info = PatSource r $ \_ -> p0
      -}
    -- we have to do dot patterns at the end
    toAbstract p0@(C.DotP r e) = return $ A.DotP info e
	where info = PatSource r $ \_ -> p0
    toAbstract p0@(C.AbsurdP r) = return $ A.AbsurdP info
	where
	    info = PatSource r $ \_ -> p0

-- | Turn an operator application into abstract syntax. Make sure to record the
-- right precedences for the various arguments.
toAbstractOpApp :: C.Name -> [C.Expr] -> ScopeM A.Expr
toAbstractOpApp op@(C.NoName _ _) es = __IMPOSSIBLE__
toAbstractOpApp op@(C.Name _ xs) es = do
    f  <- getFixity (C.QName op)
    op <- toAbstract (OldQName $ C.QName op)
    foldl app op <$> left f xs es
    where
	app e arg = A.App (ExprRange (fuseRange e arg)) e
		  $ Arg NotHidden $ unnamed arg

	left f (Hole : xs) (e : es) = do
	    e  <- toAbstractCtx (LeftOperandCtx f) e
	    es <- inside f xs es
	    return (e : es)
	left f (Id {} : xs) es = inside f xs es
	left f (Hole  : _)  [] = __IMPOSSIBLE__
	left f []           _  = __IMPOSSIBLE__

	inside f [x]	      es       = right f x es
	inside f (Id {} : xs) es       = inside f xs es
	inside f (Hole  : xs) (e : es) = do
	    e  <- toAbstractCtx InsideOperandCtx e
	    es <- inside f xs es
	    return (e : es)
	inside _ (Hole : _) [] = __IMPOSSIBLE__
	inside _ []         _  = __IMPOSSIBLE__

	right f Hole [e] = do
	    e <- toAbstractCtx (RightOperandCtx f) e
	    return [e]
	right _ (Id {})  [] = return []
	right _ Hole     _  = __IMPOSSIBLE__
	right _ (Id {})  _  = __IMPOSSIBLE__
