/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import init.data.hashmap init.data.rbmap
universes u v w w'

namespace Lean

/- Staged map for implementing the Environment. The idea is to store
   imported entries into a hashtable and local entries into a red-black tree.

   Hypotheses:
   - The number of entries (i.e., declarations) coming from imported files is much bigger than
     the number of entries in the current file.
   - HashMap is faster than RBMap.
   - When we are reading imported files, we have exclusive access to the map, and efficient
     destructive updates are performed.

   Remarks:
   - We never remove declarations from the Environment. In principle, we could support
     deletion by using `(RBMap α (Option β) lt)` where the value `none` would indicate
     that an entry was "removed" from the hashtable.
   - We do not need additional bookkeeping for extracting the local entries.
-/
structure SMap (α : Type u) (β : Type v) (lt : α → α → Bool) [HasBeq α] [Hashable α] :=
(stage₁ : Bool         := true)
(map₁   : HashMap α β  := {})
(map₂   : RBMap α β lt := {})

namespace SMap
variables {α : Type u} {β : Type v} {lt : α → α → Bool} [HasBeq α] [Hashable α]

instance : Inhabited (SMap α β lt) := ⟨{}⟩

def empty : SMap α β lt := {}
instance : HasEmptyc (SMap α β lt) := ⟨SMap.empty⟩

@[specialize] def insert : SMap α β lt → α → β → SMap α β lt
| ⟨true, m₁, m₂⟩, k, v  => ⟨true, m₁.insert k v, m₂⟩
| ⟨false, m₁, m₂⟩, k, v => ⟨false, m₁, m₂.insert k v⟩

@[specialize] def find : SMap α β lt → α → Option β
| ⟨true, m₁, _⟩, k   => m₁.find k
| ⟨false, m₁, m₂⟩, k => (m₂.find k).orelse (m₁.find k)

@[specialize] def contains : SMap α β lt → α → Bool
| ⟨true, m₁, _⟩, k   => m₁.contains k
| ⟨false, m₁, m₂⟩, k => m₁.contains k || m₂.contains k

/- Similar to `find`, but searches for result in the hashmap first.
   So, the result is correct only if we never "overwrite" `map₁` entries using `map₂`. -/
@[specialize] def find' : SMap α β lt → α → Option β
| ⟨true, m₁, _⟩, k   => m₁.find k
| ⟨false, m₁, m₂⟩, k => (m₁.find k).orelse (m₂.find k)

/- Move from stage 1 into stage 2. -/
def switch (m : SMap α β lt) : SMap α β lt :=
if m.stage₁ then { stage₁ := false, .. m } else m

@[inline] def foldStage2 {σ : Type w} (f : σ → α → β → σ) (s : σ) (m : SMap α β lt) : σ :=
m.map₂.fold f s

def size (m : SMap α β lt) : Nat :=
m.map₁.size + m.map₂.size

def stageSizes (m : SMap α β lt) : Nat × Nat :=
(m.map₁.size, m.map₂.size)

def maxDepth (m : SMap α β lt) : Nat :=
m.map₂.maxDepth

def numBuckets (m : SMap α β lt) : Nat :=
m.map₁.numBuckets

end SMap
end Lean
