/-
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 Std.Data.RBMap

namespace Lean
open Std

/- Similar to trie, but for arbitrary keys -/
inductive PrefixTreeNode (α : Type u) (β : Type v) where
  | Node : Option β → RBNode α (fun _ => PrefixTreeNode α β) → PrefixTreeNode α β

instance : Inhabited (PrefixTreeNode α β) where
  default := PrefixTreeNode.Node none RBNode.leaf

namespace PrefixTreeNode

def empty : PrefixTreeNode α β :=
  PrefixTreeNode.Node none RBNode.leaf

@[specialize]
partial def insert (t : PrefixTreeNode α β) (cmp : α → α → Ordering) (k : List α) (val : β) : PrefixTreeNode α β :=
  let rec insertEmpty (k : List α) : PrefixTreeNode α β :=
    match k with
    | [] => PrefixTreeNode.Node (some val) RBNode.leaf
    | k :: ks =>
      let t := insertEmpty ks
      PrefixTreeNode.Node none (RBNode.singleton k t)
  let rec loop
    | PrefixTreeNode.Node v m, [] =>
      PrefixTreeNode.Node (some val) m -- overrides old value
    | PrefixTreeNode.Node v m, k :: ks =>
      let t := match RBNode.find cmp m k with
        | none   => insertEmpty ks
        | some t => loop t ks
      PrefixTreeNode.Node v (RBNode.insert cmp m k t)
  loop t k

@[specialize]
partial def find? (t : PrefixTreeNode α β) (cmp : α → α → Ordering) (k : List α) : Option β :=
  let rec loop
    | PrefixTreeNode.Node val m, [] => val
    | PrefixTreeNode.Node val m, k :: ks =>
      match RBNode.find cmp m k with
      | none   => none
      | some t => loop t ks
  loop t k

@[specialize]
partial def foldMatchingM [Monad m] (t : PrefixTreeNode α β) (cmp : α → α → Ordering) (k : List α) (init : σ) (f : β → σ → m σ) : m σ :=
  let rec fold : PrefixTreeNode α β → σ → m σ
    | PrefixTreeNode.Node b? n, d => do
      let d ← match b? with
        | none   => pure d
        | some b => f b d
      n.foldM (init := d) fun d _ t => fold t d
  let rec find : List α → PrefixTreeNode α β → σ → m σ
    | [],    t, d => fold t d
    | k::ks, PrefixTreeNode.Node _ m, d =>
      match RBNode.find cmp m k with
      | none   => pure init
      | some t => find ks t d
  find k t init

inductive WellFormed {β} (cmp : α → α → Ordering) : PrefixTreeNode α β → Prop where
  | emptyWff    : WellFormed cmp empty
  | insertWff  {t : PrefixTreeNode α β} {k : List α} {val : β} : WellFormed cmp t → WellFormed cmp (insert t cmp k val)

end PrefixTreeNode

def PrefixTree (α : Type u) (β : Type v) (cmp : α → α → Ordering) : Type (max u v) :=
  { t : PrefixTreeNode α β // t.WellFormed cmp }

open PrefixTreeNode

def PrefixTree.empty : PrefixTree α β p :=
  ⟨PrefixTreeNode.empty, WellFormed.emptyWff⟩

instance : Inhabited (PrefixTree α β p) where
  default := PrefixTree.empty

instance : EmptyCollection (PrefixTree α β p) where
  emptyCollection := PrefixTree.empty

@[inline]
def PrefixTree.insert (t : PrefixTree α β p) (k : List α) (v : β) : PrefixTree α β p :=
  ⟨t.val.insert p k v, WellFormed.insertWff t.property⟩

@[inline]
def PrefixTree.find? (t : PrefixTree α β p) (k : List α) : Option β :=
  t.val.find? p k

@[inline]
def PrefixTree.foldMatchingM [Monad m] (t : PrefixTree α β p) (k : List α) (init : σ) (f : β → σ → m σ) : m σ :=
  t.val.foldMatchingM p k init f

@[inline]
def PrefixTree.foldM [Monad m] (t : PrefixTree α β p) (init : σ) (f : β → σ → m σ) : m σ :=
  t.foldMatchingM [] init f

@[inline]
def PrefixTree.forMatchingM [Monad m] (t : PrefixTree α β p) (k : List α) (f : β → m Unit) : m Unit :=
  t.val.foldMatchingM p k () (fun b _ => f b)

@[inline]
def PrefixTree.forM [Monad m] (t : PrefixTree α β p) (f : β → m Unit) : m Unit :=
  t.forMatchingM [] f

end Lean
