{- | Translation from Concrete to Abstract and back -}

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, TypeSynonymInstances, UndecidableInstances #-}
{-# LANGUAGE NondecreasingIndentation #-}

module Scoping (Scope(..),Parse(..),ParseError,Print(..)) where

import Prelude hiding (mapM,print)

import Control.Applicative
import Control.Monad.State hiding (mapM)

import Data.Map (Map)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set

import Data.Traversable

import qualified Concrete as C
import qualified Abstract as A
import qualified OperatorPrecedenceParser as O

import Util
import Text.PrettyPrint

-- * abstract scoping monad

type ParseError = O.ParseError

{-
class (Applicative m, Monad m) => ScopeReader m where
  askName    :: A.Name -> m C.Name
  askFixity  :: C.Name -> m (Maybe C.Fixity)
-}

class (Applicative m, Monad m) => Scope m where
  addGlobal  :: (A.Name -> A.Ident) -> C.Name -> m A.Name
  addCon     :: C.Name -> m A.Name
  addCon      = addGlobal A.Con
  addDef     :: C.Name -> m A.Name
  addDef      = addGlobal A.Def
  addFixity  :: C.Name -> C.Fixity -> m ()
  addLocal   :: (A.Name -> A.Ident) -> C.Name -> (A.Name -> m a) -> m a
  addVar     :: C.Name -> (A.Name -> m a) -> m a
  addVar      = addLocal A.Var
  addLet     :: C.Name -> (A.Name -> m a) -> m a
  addLet      = addLocal A.Let
  getName    :: A.Name -> m C.Name
  getFixity  :: C.Name -> m (Maybe C.Fixity)
  getIdent   :: C.Name -> m A.Ident
--  getIdent n  = fst <$> getIdentAndFixity n
--  getIdentAndFixity :: C.Name -> m (A.Ident, C.Fixity)
  parseError :: ParseError -> m a
  genericError :: String -> m a
  genericError = parseError . O.GenericError

-- * parsing

class Parse c a where
  parse :: Scope m => c -> m a

instance Parse c a => Parse (Maybe c) (Maybe a) where
  parse = mapM parse

instance Parse C.Declarations A.Declarations where
  parse (C.Declarations cdecls) = A.Declarations . concat <$> mapM parse cdecls

instance Parse C.Declaration [A.Declaration] where
  parse cdecl =
    case cdecl of
      C.TypeSig n t -> return <$> do
        t <- parse t
        n <- addCon n
        return $ A.TypeSig n t
      C.Defn n mt e -> return <$> do
        mt <- parse mt
        e  <- parse e
        n  <- addDef n
        return $ A.Defn n mt e
{-
      C.GLet n e -> return <$> do
        e  <- parse e
        n  <- addDef n
        return $ A.GLet n e
-}
      C.Fixity n fx -> const [] <$> addFixity n fx

{-
instance Parse C.Atom A.Atom where
  parse a =
    case a of
      C.Typ     -> return $ A.Typ
      C.Ident n -> getAtom n
-}

instance Parse C.Expr A.Expr where
  parse cexpr =
    case cexpr of
      C.Typ         -> return $ A.Typ
      C.Ident n     -> A.Ident <$> getIdent n
--      C.Atom a     -> A.Atom <$> parse a
      C.Fun t1 t2  -> A.Pi Nothing <$> parse t1 <*> parse t2
      C.Pi x t1 t2 -> do
        t1 <- parse t1
        addVar x $ \ x -> A.Pi (Just x) t1 <$> parse t2
      C.LLet x e e' -> do
        e <- parse e
        addLet x $ \ x -> A.LLet x e <$> parse e'
      C.Lam x mt e -> do
        mt <- parse mt
        addVar x $ \ x -> A.Lam x mt <$> parse e
      C.Apps es -> parseApplication =<< mapM parse es

-- applications [C.Expr] are parsed into list of Stack items
-- which is then resolved into an A.Expr

type Item = O.Item Int A.Expr

{-
instance Parse C.Atom Item where
  parse catom =
    case catom of
      C.Typ     -> return $ O.Atom (A.Atom A.Typ)
      C.Ident n -> do
        (a, fx) <- getAtomAndFixity n
        return $ case fx of
          O.Nofix   -> O.Atom $ A.Atom a
          O.Infix{} -> O.Op fx (\ [x,y] -> A.Atom a `A.App` x `A.App` y)
          _         -> O.Op fx (\ [x]   -> A.Atom a `A.App` x)
-}
{-
instance Parse C.Ident Item where
  parse n = do
        (a, fx) <- getIdentAndFixity n
        return $ case fx of
          O.Nofix   -> O.Ident $ A.Ident a
          O.Infix{} -> O.Op fx (\ [x,y] -> A.Ident a `A.App` x `A.App` y)
          _         -> O.Op fx (\ [x]   -> A.Ident a `A.App` x)
-}

instance Parse C.Expr Item where
  parse cexpr =
    case cexpr of
      C.Ident n -> do
        a  <- getIdent n
        mfx <- getFixity n
        return $ case mfx of
          Nothing           -> O.Atom $ A.Ident a
          Just fx@O.Infix{} -> O.Op fx (\ [x,y] -> A.Ident a `A.App` x `A.App` y)
          Just fx           -> O.Op fx (\ [x]   -> A.Ident a `A.App` x)
      _         -> O.Atom <$> parse cexpr

instance O.Juxtaposition A.Expr where
  juxtaposition = A.App

parseApplication :: Scope m => [Item] -> m A.Expr
parseApplication is =
  case O.parseApplication is of
    Left err -> parseError err
    Right e  -> return e

-- * unparsing

{- How to print an expression

We distinguish 3 kinds of abstract names
- global names (Con,Def)
- user-generated local names
- system-generated local names (e.g., from quote)

We can print an expression from left-to-right, bottom-up as follows:

a) never shadow a global name by a local name
- state:
   * used concrete names, initially the set of all global names
   * map from abstract names to concrete names
- when encountering a global id, just look up its name
- when encountering a local id, check whether it is already in the map
   * if yes, print its name
   * if no, assign it a name and add it
- when abstracting a name, check whether it is in the map
   * if yes, print its name, delete it
   * if no, we have a void abstraction, then choose an unused version of it

b) shadowing of unused global names by local names allowed
- first compute the used global names
- proceed as above, but start with the set of computed global names

In case of b, we do not need to maintain a set of global names after
scope checking.  We can store name suggestions locally with it each
abstract name as in Agda.  Note that there will be sharing, so it is
not more memory intensive.

-}

class Print a c where
  print :: a -> c

instance Print A.Declarations C.Declarations where
  print (A.Declarations adecls) = C.Declarations $ map print adecls

instance Print A.Declaration C.Declaration where
  print adecl =
    case adecl of
      A.TypeSig n t -> C.TypeSig (A.suggestion n) $ print t
      A.Defn n mt e -> C.Defn (A.suggestion n) (fmap print mt) $ print e
--      A.GLet n e -> C.GLet (A.suggestion n) $ print e

instance Print A.Expr C.Expr where
  print e = evalState (printExpr e) $ nameSet $ A.globalCNames e
--   print e = fst $ printExpr e $ nameSet $ A.globalCNames e

printIdent :: A.Ident -> NameM C.Name
printIdent id =
  case id of
    A.Var n -> askName n                   -- locals are potentially renamed
    _ -> return $ A.suggestion $ A.name id -- globals have unique concrete name

printExpr :: A.Expr -> NameM C.Expr
printExpr e =
  case e of
    A.Ident a           -> C.Ident <$> printIdent a
    A.Typ               -> return $ C.Typ
    A.Pi Nothing  t1 t2 -> C.Fun <$> printExpr t1 <*> printExpr t2
    A.Pi (Just x) t1 t2 -> do
      t1 <- printExpr t1
      t2 <- printExpr t2
      x  <- bindName x
      return $ C.Pi x t1 t2
-- C.Pi <$> bindName x <*> printExpr t1 <*> printExpr t2
    A.LLet x e e'        -> do
      e  <- printExpr e
      e' <- printExpr e'
      x  <- bindName x
      return $ C.LLet x e e'
    A.Lam x mt e        -> do
      mt <- mapM printExpr mt
      e  <- printExpr e
      x  <- bindName x
      return $ C.Lam x mt e
-- C.Lam <$> bindName x <*> mapM printExpr mt <*> printExpr e
    A.App f e           -> C.Apps <$> printApp f [e]

printApp :: A.Expr -> [A.Expr] -> NameM [C.Expr]
printApp f es =
  case f of
    A.App f e -> printApp f (e : es)
    -- put extra parentheses around lambda if it is in the head:
    A.Lam{}   -> printExpr f >>= \ f -> (C.Apps [f] :) <$> mapM printExpr es
    _         -> mapM printExpr (f : es)

data NameSet = NameSet
  { usedNames :: Set C.Name
  , naming    :: Map A.UID C.Name
  }

-- | Create a name set from an initial set of used global names.
nameSet :: Set C.Name -> NameSet
nameSet ns = NameSet ns Map.empty

type NameM = State NameSet
-- type NameM a = NameSet -> (a, NameSet)

-- | Retrieve and delete name.
bindName :: A.Name -> NameM C.Name
bindName (A.Name x n) = do
  ns <- get
  let nam = naming ns
  case Map.lookup x nam of
    Just n' -> do
      put $ NameSet (Set.delete n' (usedNames ns)) (Map.delete x nam)
      return n'
    Nothing -> nextVariant n

-- | Retrieve or insert name.
askName :: A.Name -> NameM C.Name
askName n = do
  nam <- gets naming
--  maybe (internalError ["Scoping.askName: not in map", show n]) return $
  maybe (nextName n) return $
    Map.lookup (A.uid n) nam

nextName :: A.Name -> NameM C.Name
nextName (A.Name x n) = do
  n' <- nextVariant n
  modify $ \ (NameSet ns nam) -> NameSet (Set.insert n' ns) (Map.insert x n' nam)
  return n'

-- | Returns an unused variant of a concrete name.
nextVariant :: C.Name -> NameM C.Name
nextVariant n = do
  ns <- gets usedNames
  let loop i = do
      let n' = variant n i
      if Set.member n' ns then loop (i+1)
       else return n'
  loop 0

variant :: C.Name -> Int -> C.Name
variant n i =
  case i of
    0 -> n
    1 -> n ++ "'"
    2 -> n ++ "''"
    3 -> n ++ "'''"
    _ -> n ++ "'" ++ show i
