module Lib.LiftWhere

import Prelude
import Lib.Common
import Lib.Types
import Lib.TopContext
import Lib.Ref2
import Data.SortedMap
import Data.IORef

-- track depth and whether we need to replace Bnd with a top level call
LiftEnv : U
LiftEnv = List (Maybe (QName × Nat))

liftWhereTm : {{Ref2 Defs St}} → QName → LiftEnv → Tm → M Tm
liftWhereTm name env (Lam fc nm icit quant t) =
  Lam fc nm icit quant <$> liftWhereTm name (Nothing :: env) t
liftWhereTm name env (App fc t u) =
  App fc <$> liftWhereTm name env t <*> liftWhereTm name env u
liftWhereTm name env (Pi fc nm icit quant t u) = do
  t <- liftWhereTm name env t
  u <- liftWhereTm name (Nothing :: env) u
  pure $ Pi fc nm icit quant t u
liftWhereTm name env (Let fc nm v sc) = do
  v <- liftWhereTm name env v
  sc <- liftWhereTm name (Nothing :: env) sc
  pure $ Let fc nm v sc
liftWhereTm name env tm@(Case fc t alts) = do
  t <- liftWhereTm name env t
  alts' <- traverse liftWhereAlt alts
  pure $ Case fc t alts'
  where
    -- This is where I wish I had put indexes on things
    liftWhereAlt : CaseAlt → M CaseAlt
    liftWhereAlt (CaseDefault tm) = CaseDefault <$> liftWhereTm name env tm
    liftWhereAlt (CaseLit l tm) = CaseLit l <$> liftWhereTm name env tm
    liftWhereAlt (CaseCons qn args tm) =
      CaseCons qn args <$> liftWhereTm name (map (const Nothing) args ++ env) tm
-- This is where the magic happens
liftWhereTm name env (LetRec fc nm ty t u) = do
  let l = length env
  qn <- getName name nm
  let env' = (Just (qn, S l) :: env)
  -- environment should subst this function (see next case)
  t' <- liftWhereTm qn env' t
  -- TODO we could have subst in this var and dropped the extra argument
  modifyRef Defs (updateMap qn (Fn $ wrapLam (S l) t'))

  -- The rest
  u' <- liftWhereTm qn env' u
  pure $ LetRec fc nm (Erased fc) (Erased fc) u'
  where
    -- TODO might be nice if we could nest the names (Foo.go.go) for nested where
    getName : QName → String → M QName
    getName qn@(QN ns nm) ext = do
      let qn' = QN ns (nm ++ "." ++ ext)
      top <- getRef Defs
      let (Just _) = lookupMap qn' top | _ => pure qn'
      getName qn (ext ++ "'")

    -- Hacky - CompileExp expects a pi type that matches arity
    wrapPi : Nat → Tm → Tm
    wrapPi Z tm = tm
    wrapPi (S k) tm = Pi fc "_" Explicit Many (Erased fc) $ wrapPi k tm

    wrapLam : Nat → Tm → Tm
    wrapLam Z tm = tm
    -- REVIEW We've already erased, hopefully we don't need quantity
    wrapLam (S k) tm = Lam fc "_" Explicit Many $ wrapLam k tm

-- And where it lands
liftWhereTm name env tm@(Bnd fc k) = case getAt (cast k) env of
  Just (Just (qn, v)) => pure $ apply (length' env) (cast v) (Ref fc qn)
  _ => pure tm
  where
    apply : Int → Int → Tm → Tm
    apply l 0 tm = tm
    -- (l - k) is like lvl2ix, but witih k one bigger
    apply l k tm = App fc (apply l (k - 1) tm) (Bnd fc (l - k))
liftWhereTm name env tm = pure tm

liftWhereFn : {{Ref2 Defs St}} → QName × Def → M Unit
liftWhereFn (name, Fn tm) = do
  tm' <- liftWhereTm name Nil tm
  modifyRef Defs $ updateMap name (Fn tm')
  -- updateDef name fc type (Fn tm')
liftWhereFn _ = pure MkUnit

liftWhere : {{Ref2 Defs St}} → M Unit
liftWhere = do
  defs <- getRef Defs
  ignore $ traverse liftWhereFn $ toList defs
