/-
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 Expr

namespace FindImpl

abbrev cacheSize : USize := 8192

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

abbrev FindM := StateM State

@[inline] unsafe def visited (e : Expr) (size : USize) : FindM Bool := do
s ← get;
let h := ptrAddrUnsafe e;
let i := h % size;
let k := s.keys.uget i lcProof;
if ptrAddrUnsafe k == h then pure true
else do
  modify $ fun s => { keys := s.keys.uset i e lcProof };
  pure false

@[specialize] unsafe partial def findM? (p : Expr → Bool) (size : USize) : Expr → OptionT FindM Expr
| e => condM (liftM $ visited e size) failure $
  if p e then pure e
  else match e with
    | Expr.forallE _ d b _   => findM? d <|> findM? b
    | Expr.lam _ d b _       => findM? d <|> findM? b
    | Expr.mdata _ b _       => findM? b
    | Expr.letE _ t v b _    => findM? t <|> findM? v <|> findM? b
    | Expr.app f a _         => findM? f <|> findM? a
    | Expr.proj _ _ b _      => findM? b
    | e                      => failure

unsafe def initCache : State :=
{ keys    := mkArray cacheSize.toNat (cast lcProof ()) }

@[inline] unsafe def findUnsafe? (p : Expr → Bool) (e : Expr) : Option Expr :=
(findM? p cacheSize e).run' initCache

end FindImpl

@[implementedBy FindImpl.findUnsafe?]
partial def find? (p : Expr → Bool) : Expr → Option Expr
| e =>
  /- This is a reference implementation for the unsafe one above -/
  if p e then some e
  else match e with
    | Expr.forallE _ d b _   => find? d <|> find? b
    | Expr.lam _ d b _       => find? d <|> find? b
    | Expr.mdata _ b _       => find? b
    | Expr.letE _ t v b _    => find? t <|> find? v <|> find? b
    | Expr.app f a _         => find? f <|> find? a
    | Expr.proj _ _ b _      => find? b
    | e                      => none

end Expr
end Lean
