/-
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import Lean.Expr

namespace Lean
namespace Level

partial def replace (f? : Level → Option Level) (u : Level) : Level :=
  match f? u with
  | some v => v
  | none   => match u with
    | max v₁ v₂  _ => mkLevelMax' (replace f? v₁) (replace f? v₂)
    | imax v₁ v₂ _ => mkLevelIMax' (replace f? v₁) (replace f? v₂)
    | succ v _     => mkLevelSucc (replace f? v)
    | _            => u

end Level

namespace Expr

namespace ReplaceLevelImpl

abbrev cacheSize : USize := 8192

structure State where
  keys    : Array Expr -- Remark: our "unsafe" implementation relies on the fact that `()` is not a valid Expr
  results : Array Expr

abbrev ReplaceM := StateM State

@[inline] unsafe def cache (i : USize) (key : Expr) (result : Expr) : ReplaceM Expr := do
  modify fun s => { keys := s.keys.uset i key lcProof, results := s.results.uset i result lcProof };
  pure result

@[specialize] unsafe def replaceUnsafeM (f? : Level → Option Level) (size : USize) (e : Expr) : ReplaceM Expr := do
  let rec visit (e : Expr) := do
    let c ← get
    let h := ptrAddrUnsafe e
    let i := h % size
    if ptrAddrUnsafe (c.keys.uget i lcProof) == h then
      pure <| c.results.uget i lcProof
    else match e with
        | Expr.forallE _ d b _   => cache i e <| e.updateForallE! (← visit d) (← visit b)
        | Expr.lam _ d b _       => cache i e <| e.updateLambdaE! (← visit d) (← visit b)
        | Expr.mdata _ b _       => cache i e <| e.updateMData! (← visit b)
        | Expr.letE _ t v b _    => cache i e <| e.updateLet! (← visit t) (← visit v) (← visit b)
        | Expr.app f a _         => cache i e <| e.updateApp! (← visit f) (← visit a)
        | Expr.proj _ _ b _      => cache i e <| e.updateProj! (← visit b)
        | Expr.sort u _          => cache i e <| e.updateSort! (u.replace f?)
        | Expr.const n us _      => cache i e <| e.updateConst! (us.map (Level.replace f?))
        | e                      => pure e
  visit e

unsafe def initCache : State :=
  { keys    := mkArray cacheSize.toNat (cast lcProof ()), -- `()` is not a valid `Expr`
    results := mkArray cacheSize.toNat arbitrary }

@[inline] unsafe def replaceUnsafe (f? : Level → Option Level) (e : Expr) : Expr :=
  (replaceUnsafeM f? cacheSize e).run' initCache

end ReplaceLevelImpl

@[implementedBy ReplaceLevelImpl.replaceUnsafe]
partial def replaceLevel (f? : Level → Option Level) : Expr → Expr
  | e@(Expr.forallE _ d b _)   => let d := replaceLevel f? d; let b := replaceLevel f? b; e.updateForallE! d b
  | e@(Expr.lam _ d b _)       => let d := replaceLevel f? d; let b := replaceLevel f? b; e.updateLambdaE! d b
  | e@(Expr.mdata _ b _)       => let b := replaceLevel f? b; e.updateMData! b
  | e@(Expr.letE _ t v b _)    => let t := replaceLevel f? t; let v := replaceLevel f? v; let b := replaceLevel f? b; e.updateLet! t v b
  | e@(Expr.app f a _)         => let f := replaceLevel f? f; let a := replaceLevel f? a; e.updateApp! f a
  | e@(Expr.proj _ _ b _)      => let b := replaceLevel f? b; e.updateProj! b
  | e@(Expr.sort u _)          => e.updateSort! (u.replace f?)
  | e@(Expr.const n us _)      => e.updateConst! (us.map (Level.replace f?))
  | e                          => e

end Expr
end Lean
