module Lib.Erasure

import Prelude
import Lib.Common
import Lib.Types
import Data.SnocList
import Lib.TopContext

EEnv : U
EEnv = List (String × Quant × Maybe Tm)

-- TODO look into removing Nothing below, can we recover all of the types?
-- Idris does this in `chk` for linearity checking.

-- check App at type

getType : Tm -> M (Maybe Tm)
getType (Ref fc nm) = do
  top <- getTop
  case lookup nm top of
    Nothing => error fc "\{show nm} not in scope"
    (Just (MkEntry _ name type def _)) => pure $ Just type
getType tm = pure Nothing


erase : EEnv -> Tm -> List (FC × Tm) -> M Tm

-- App a spine using type
eraseSpine : EEnv -> Tm -> List (FC × Tm) -> (ty : Maybe Tm) -> M Tm
eraseSpine env tm Nil _ = pure tm
eraseSpine env t ((fc, arg) :: args) (Just (Pi fc1 str icit Zero a b)) = do
  let u = Erased (getFC arg)
  eraseSpine env (App fc t u) args (Just b)
eraseSpine env t ((fc, arg) :: args) (Just (Pi fc1 str icit Many a b)) = do
  u <- erase env arg Nil
  -- TODO this seems wrong, we need to subst u into b to get the type
  eraseSpine env (App fc t u) args (Just b)
-- eraseSpine env t ((fc, arg) :: args) (Just ty) = do
--   -- Prelude Either and IO instance of <*> have Bnd here, possibly pattern matching
--   -- unifying in the wrong direction? we should have something like a -> b
--   error fc "ceci n'est pas une ∏ \{showTm ty} arg \{show arg}" -- e.g. Bnd 1
eraseSpine env t ((fc, arg) :: args) _ = do
  u <- erase env arg Nil
  eraseSpine env (App fc t u) args Nothing

doAlt : EEnv -> CaseAlt -> M CaseAlt
-- REVIEW do we extend env?
doAlt env (CaseDefault t) = CaseDefault <$> erase env t Nil
doAlt env (CaseCons name args t) = do
  top <- getTop
  let (Just (MkEntry _ str type def _)) = lookup name top
    | _ => error emptyFC "\{show name} dcon missing from context"
  let env' = piEnv env type args
  CaseCons name args <$> erase env' t Nil
  where
    piEnv : EEnv -> Tm -> List String -> EEnv
    piEnv env (Pi fc nm icit rig t u) (arg :: args) = piEnv ((arg, rig, Just t) :: env) u args
    piEnv env _ _ = env

doAlt env (CaseLit lit t) = CaseLit lit <$> erase env t Nil

-- Check erasure and insert "Erased" value
-- We have a solution for Erased values, so important thing here is checking.
-- build stack, see what to do when we hit a non-app
-- This is a little fuzzy because we don't have all of the types.
erase env t sp = case t of
  (App fc u v) => erase env u ((fc,v) :: sp)
  (Ref fc nm) => do
    top <- getTop
    case lookup nm top of
      Nothing => error fc "\{show nm} not in scope"
      (Just (MkEntry _ name type def _)) => eraseSpine env t sp (Just type)
  (Lam fc nm icit rig u) => Lam fc nm icit rig <$> erase ((nm, rig, Nothing) :: env) u Nil
  -- If we get here, we're looking at a runtime pi type
  (Pi fc nm icit rig u v) => do
    u' <- erase env u Nil
    v' <- erase ((nm, Many, Just u) :: env) v Nil
    eraseSpine env (Pi fc nm icit rig u' v') sp (Just $ UU emptyFC)
  -- leaving as-is for now, we don't know the quantity of the apps
  (Meta fc k) => pure t
  (Case fc u alts) => do
    -- REVIEW check if this pushes to env, and write that down or get an index on there
    u' <- erase env u Nil
    alts' <- traverse (doAlt env) alts
    eraseSpine env (Case fc u' alts') sp Nothing
  (Let fc nm u v) => do
    u' <- erase env u Nil
    v' <- erase ((nm, Many, Nothing) :: env) v Nil
    eraseSpine env (Let fc nm u' v') sp Nothing
  (LetRec fc nm ty u v) => do
    u' <- erase ((nm, Many, Just ty) :: env) u Nil
    v' <- erase ((nm, Many, Just ty) :: env) v Nil
    eraseSpine env (LetRec fc nm ty u' v') sp Nothing
  (Bnd fc k) => do
    case getAt (cast k) env of
      Nothing => error fc "bad index \{show k}"
      -- This is working, but empty FC
      Just (nm, Zero, ty) => error fc "used erased value \{show nm} (FIXME FC may be wrong here)"
      Just (nm, Many, ty) => eraseSpine env t sp ty
  (UU fc) => eraseSpine env t sp (Just $ UU fc)
  (Lit fc lit) => eraseSpine env t sp Nothing
  Erased fc => error fc "erased value in relevant context" -- eraseSpine env t sp Nothing


