{-# LANGUAGE TupleSections #-}

-- Convert the concrete syntax into the syntax of miniTT.
module Concrete where

import Exp.Abs
import qualified MTT as A

import Control.Arrow (first)
import Control.Applicative
import Control.Monad.Trans
import Control.Monad.Trans.State
import Control.Monad.Trans.Reader
import Control.Monad.Trans.Error hiding (throwError)
import Control.Monad.Error (throwError)
import Control.Monad (when)
import Data.Functor.Identity
import Data.List (union)

type Tele = [VDecl]

-- | Useful auxiliary functions
unions :: Eq a => [[a]] -> [a]
unions = foldr union []

-- Applicative cons
(<:>) :: Applicative f => f a -> f [a] -> f [a]
a <:> b = (:) <$> a <*> b

-- un-something functions
unIdent :: AIdent -> String
unIdent (AIdent (_,n)) = n

unArg :: Arg -> String
unArg (Arg n) = unIdent n
unArg NoArg   = "_"

unArgs :: [Arg] -> [String]
unArgs = map unArg

unBinder :: Binder -> Arg
unBinder (Binder b) = b

unArgBinder :: Binder -> String
unArgBinder = unArg . unBinder

unArgsBinder :: [Binder] -> [String]
unArgsBinder = map unArgBinder

unWhere :: ExpWhere -> Exp
unWhere (Where e ds) = Let ds e
unWhere (NoWhere e)  = e

-- Flatten a telescope, e.g., flatten (a b : A) (c : C) into
-- (a : A) (b : A) (c : C).
flattenTele :: Tele -> [VDecl]
flattenTele = concatMap (\(VDecl bs e) -> [VDecl [b] e | b <- bs])

-- Note: It is important to only apply unApps to e1 as otherwise the
-- structure of the application will be destroyed which leads to trouble
-- for constructor disambiguation!
unApps :: Exp -> [Exp]
unApps (App e1 e2) = unApps e1 ++ [e2]
unApps e           = [e]

unVar :: Exp -> Arg
unVar (Var b) = b
unVar e       = error $ "unVar bad input: " ++ show e

unVarBinder :: Exp -> String
unVarBinder = unArg . unVar

unPiDecl :: PiDecl -> VDecl
unPiDecl (PiDecl e t) = VDecl (map (Binder . unVar) (unApps e)) t

flattenTelePi :: [PiDecl] -> [VDecl]
flattenTelePi = flattenTele . map unPiDecl

namesTele :: Tele -> [String]
namesTele vs = unions [ unArgsBinder args | VDecl args _ <- vs ]

-------------------------------------------------------------------------------
-- | Resolver and environment

-- local environment for constructors
data Env = Env { constrs :: [String] }
         deriving (Eq, Show)

type Resolver a = ReaderT Env (StateT A.Prim (ErrorT String Identity)) a

emptyEnv :: Env
emptyEnv = Env []

runResolver :: Resolver a -> Either String a
runResolver x = runIdentity $ runErrorT $ evalStateT (runReaderT x emptyEnv) (0,"")

insertConstrs :: [String] -> Env -> Env
insertConstrs cs (Env cs') = Env $ cs ++ cs'

getEnv :: Resolver Env
getEnv = ask

getConstrs :: Resolver [String]
getConstrs = constrs <$> getEnv

genPrim :: Resolver A.Prim
genPrim = do
  prim <- lift get
  lift (modify (first succ))
  return prim

updateName :: String -> Resolver ()
updateName str = lift $ modify (\(g,_) -> (g,str))

lam :: Arg -> Resolver A.Exp -> Resolver A.Exp
lam a e = A.Lam (unArg a) <$> e

lams :: [Arg] -> Resolver A.Exp -> Resolver A.Exp
lams as e = foldr lam e as

resolveExp :: Exp -> Resolver A.Exp
resolveExp U            = return A.U
resolveExp Undef        = A.Undef <$> genPrim
resolveExp PN           = A.Undef <$> genPrim
resolveExp e@(App t s)  = do
  let x:xs = unApps e
  cs <- getConstrs
  if unVarBinder x `elem` cs
    then A.Con (unVarBinder x) <$> mapM resolveExp xs
    else A.App <$> resolveExp t <*> resolveExp s
resolveExp (Pi tele b)  = resolveTelePi (flattenTelePi tele) (resolveExp b)
resolveExp (Fun a b)    = A.Pi <$> resolveExp a <*> lam NoArg (resolveExp b)
resolveExp (Lam bs t)   = lams (map unBinder bs) (resolveExp t)
resolveExp (Split brs)  = A.Fun <$> genPrim <*> mapM resolveBranch brs
resolveExp (Let defs e) = A.lets <$> resolveDefs defs <*> resolveExp e
resolveExp (Var n)      = do
  let x = unArg n
  when (x == "_") (throwError "_ not a valid variable name")
  Env cs <- getEnv
  return (if x `elem` cs then A.Con x [] else A.Var x)

resolveWhere :: ExpWhere -> Resolver A.Exp
resolveWhere = resolveExp . unWhere

resolveBranch :: Branch -> Resolver (String,([String],A.Exp))
resolveBranch (Branch name args e) =
  ((unIdent name,) . (unArgs args,)) <$> resolveWhere e

-- Assumes a flattened telescope.
resolveTele :: [VDecl] -> Resolver [(String,A.Exp)]
resolveTele []                      = return []
resolveTele (VDecl [Binder a] t:ds) =
  ((unArg a,) <$> resolveExp t) <:> resolveTele ds
resolveTele ds                      =
  throwError $ "resolveTele: non flattened telescope " ++ show ds

-- Assumes a flattened telescope.
resolveTelePi :: [VDecl] -> Resolver A.Exp -> Resolver A.Exp
resolveTelePi [] b                      = b
resolveTelePi (VDecl [Binder x] a:as) b =
  A.Pi <$> resolveExp a <*> lam x (resolveTelePi as b)
resolveTelePi (t@(VDecl{}):as) _        =
  throwError ("resolveTelePi: non flattened telescope " ++ show t)

resolveLabel :: Sum -> Resolver (String,[(String,A.Exp)])
resolveLabel (Sum n tele) = (unIdent n,) <$> resolveTele (flattenTele tele)

resolveDefs :: [Def] -> Resolver [A.Def]
resolveDefs [] = return []
resolveDefs (DefTDecl n e:d:ds) = do
  e' <- resolveExp e
  xd <- checkDef (unIdent n,d)
  rest <- resolveDefs ds
  return $ ([(unIdent n, e')],[xd]) : rest
-- resolveDefs (DefMutual defs:ds) = resolveMutual defs <:> resolveDefs ds
resolveDefs (d:_) = error $ "Type declaration expected: " ++ show d

checkDef :: (String,Def) -> Resolver (String,A.Exp)
checkDef (n,Def (AIdent (_,m)) args body) | n == m = do
  updateName n
  (n,) <$> lams args (resolveWhere body)
checkDef (n,DefData (AIdent (_,m)) args sums) | n == m = do
  updateName n
  (n,) <$> lams args (A.Sum <$> genPrim <*> mapM resolveLabel sums)
checkDef (n,d) =
  throwError ("Mismatching names in " ++ show n ++ " and " ++ show d)

resolveMutual :: [Def] -> Resolver A.Def
resolveMutual defs = do
  tdecls' <- mapM resolveTDecl tdecls
  let names = map fst tdecls'
  when (length names /= length decls) $
    throwError $ "Definitions missing in " ++ show defs
  tdef' <- mapM checkDef (zip names decls)
  return (tdecls',tdef')
  where
    (tdecls,decls) = span isTDecl defs
    isTDecl d@(DefTDecl {}) = True
    isTDecl _               = False
    resolveTDecl (DefTDecl n e) = do e' <- resolveExp e
                                     return (unIdent n, e')
