/-
Copyright (c) 2018 Robert Y. Lewis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Robert Y. Lewis
-/
import data.option.defs
import data.list.defs

/-!
# rb_map

This file defines additional operations on native rb_maps and rb_sets.
These structures are defined in core in `init.meta.rb_map`.
They are meta objects, and are generally the most efficient dictionary structures
to use for pure metaprogramming right now.
-/

namespace native

/-! ### Declarations about `rb_set` -/
namespace rb_set

meta instance {key} [has_lt key] [decidable_rel ((<) : key → key → Prop)] :
  inhabited (rb_set key) :=
⟨mk_rb_set⟩

/-- `filter s P` returns the subset of elements of `s` satisfying `P`. -/
meta def filter {key} (s : rb_set key) (P : key → bool) : rb_set key :=
s.fold s (λ a m, if P a then m else m.erase a)

/-- `mfilter s P` returns the subset of elements of `s` satisfying `P`,
where the check `P` is monadic. -/
meta def mfilter {m} [monad m] {key} (s : rb_set key) (P : key → m bool) : m (rb_set key) :=
s.fold (pure s) (λ a m,
  do x ← m,
     mcond (P a) (pure x) (pure $ x.erase a))

/-- `union s t` returns an rb_set containing every element that appears in either `s` or `t`. -/
meta def union {key} (s t : rb_set key) : rb_set key :=
s.fold t (λ a t, t.insert a)

/--
`of_list_core empty l` turns a list of keys into an `rb_set`.
It takes a user_provided `rb_set` to use for the base case.
This can be used to pre-seed the set with additional elements,
and/or to use a custom comparison operator.
-/
meta def of_list_core {key} (base : rb_set key) : list key → rb_map key unit
| []      := base
| (x::xs) := rb_set.insert (of_list_core xs) x

/--
`of_list l` transforms a list `l : list key` into an `rb_set`,
inferring an order on the type `key`.
-/
meta def of_list {key} [has_lt key] [decidable_rel ((<) : key → key → Prop)] :
  list key → rb_set key :=
of_list_core mk_rb_set

/--
`sdiff s1 s2` returns the set of elements that are in `s1` but not in `s2`.
It does so by folding over `s2`. If `s1` is significantly smaller than `s2`,
it may be worth it to reverse the fold.
-/
meta def sdiff {α} (s1 s2 : rb_set α) : rb_set α :=
s2.fold s1 $ λ v s, s.erase v

/--
`insert_list s l` inserts each element of `l` into `s`.
-/
meta def insert_list {key} (s : rb_set key) (l : list key) : rb_set key :=
l.foldl rb_set.insert s

end rb_set

/-! ### Declarations about `rb_map` -/

namespace rb_map

meta instance {key data : Type} [has_lt key] [decidable_rel ((<) : key → key → Prop)] :
  inhabited (rb_map key data) :=
⟨mk_rb_map⟩

/-- `find_def default m k` returns the value corresponding to `k` in `m`, if it exists.
Otherwise it returns `default`. -/
meta def find_def {key value} (default : value) (m : rb_map key value) (k : key) :=
(m.find k).get_or_else default

/-- `ifind m key` returns the value corresponding to `key` in `m`, if it exists.
Otherwise it returns the default value of `value`. -/
meta def ifind {key value} [inhabited value] (m : rb_map key value) (k : key) : value :=
(m.find k).iget

/-- `zfind m key` returns the value corresponding to `key` in `m`, if it exists.
Otherwise it returns 0. -/
meta def zfind {key value} [has_zero value] (m : rb_map key value) (k : key) : value :=
(m.find k).get_or_else 0

/-- Returns the pointwise sum of `m1` and `m2`, treating nonexistent values as 0. -/
meta def add {key value} [has_add value] [has_zero value] [decidable_eq value]
  (m1 m2 : rb_map key value) : rb_map key value :=
m1.fold m2
  (λ n v m,
   let nv := v + m2.zfind n in
   if nv = 0 then m.erase n else m.insert n nv)

variables {m : Type → Type*} [monad m]
open function

/-- `mfilter P s` filters `s` by the monadic predicate `P` on keys and values. -/
meta def mfilter {key val} [has_lt key] [decidable_rel ((<) : key → key → Prop)]
  (P : key → val → m bool) (s : rb_map key val) : m (rb_map.{0 0} key val) :=
rb_map.of_list <$> s.to_list.mfilter (uncurry P)

/-- `mmap f s` maps the monadic function `f` over values in `s`. -/
meta def mmap {key val val'} [has_lt key] [decidable_rel ((<) : key → key → Prop)]
  (f : val → m val') (s : rb_map key val) : m (rb_map.{0 0} key val') :=
rb_map.of_list <$> s.to_list.mmap (λ ⟨a,b⟩, prod.mk a <$> f b)

/-- `scale b m` multiplies every value in `m` by `b`. -/
meta def scale {key value} [has_lt key] [decidable_rel ((<) : key → key → Prop)] [has_mul value]
  (b : value) (m : rb_map key value) : rb_map key value :=
m.map ((*) b)

section
open format prod
variables {key : Type} {data : Type} [has_to_tactic_format key] [has_to_tactic_format data]
private meta def pp_key_data (k : key) (d : data) (first : bool) : tactic format :=
do fk ← tactic.pp k, fd ← tactic.pp d, return $
(if first then to_fmt "" else to_fmt "," ++ line) ++ fk ++ space ++ to_fmt "←" ++ space ++ fd

meta instance : has_to_tactic_format (rb_map key data) :=
⟨λ m, do
  (fmt, _) ← fold m (return (to_fmt "", tt))
     (λ k d p, do p ← p, pkd ← pp_key_data k d (snd p), return (fst p ++ pkd, ff)),
  return $ group $ to_fmt "⟨" ++ nest 1 fmt ++ to_fmt "⟩"⟩
end

end rb_map

/-! ### Declarations about `rb_lmap` -/

namespace rb_lmap

meta instance (key : Type) [has_lt key] [decidable_rel ((<) : key → key → Prop)] (data : Type) :
  inhabited (rb_lmap key data) :=
⟨rb_lmap.mk _ _⟩

/-- Construct a rb_lmap from a list of key-data pairs -/
protected meta def of_list {key : Type} {data : Type} [has_lt key]
  [decidable_rel ((<) : key → key → Prop)] : list (key × data) → rb_lmap key data
| []           := rb_lmap.mk key data
| ((k, v)::ls) := (of_list ls).insert k v

/-- Returns the list of values of an `rb_lmap`. -/
protected meta def values {key data} (m : rb_lmap key data) : list data :=
m.fold [] (λ _, (++))

end rb_lmap
end native

/-! ### Declarations about `name_set` -/

namespace name_set

meta instance : inhabited name_set := ⟨mk_name_set⟩

/-- `filter P s` returns the subset of elements of `s` satisfying `P`. -/
meta def filter (P : name → bool) (s : name_set) : name_set :=
s.fold s (λ a m, if P a then m else m.erase a)

/-- `mfilter P s` returns the subset of elements of `s` satisfying `P`,
where the check `P` is monadic. -/
meta def mfilter {m} [monad m] (P : name → m bool) (s : name_set) : m name_set :=
s.fold (pure s) (λ a m,
  do x ← m,
     mcond (P a) (pure x) (pure $ x.erase a))

/-- `mmap f s` maps the monadic function `f` over values in `s`. -/
meta def mmap {m} [monad m] (f : name → m name) (s : name_set) : m name_set :=
s.fold (pure mk_name_set) (λ a m,
  do x ← m,
     b ← f a,
     (pure $ x.insert b))

/-- `insert_list s l` inserts every element of `l` into `s`. -/
meta def insert_list (s : name_set) (l : list name) : name_set :=
l.foldr (λ n s', s'.insert n) s

/--
`local_list_to_name_set lcs` is the set of unique names of the local
constants `lcs`. If any of the `lcs` are not local constants, the returned set
will contain bogus names.
-/
meta def local_list_to_name_set (lcs : list expr) : name_set :=
lcs.foldl (λ ns h, ns.insert h.local_uniq_name) mk_name_set

end name_set

/-! ### Declarations about `name_map` -/

namespace name_map

meta instance {data : Type} : inhabited (name_map data) :=
⟨mk_name_map⟩

end name_map

/-! ### Declarations about `expr_set` -/

namespace expr_set

/--
`local_set_to_name_set lcs` is the set of unique names of the local constants
`lcs`. If any of the `lcs` are not local constants, the returned set will
contain bogus names.
-/
meta def local_set_to_name_set (lcs : expr_set) : name_set :=
lcs.fold mk_name_set $ λ h ns, ns.insert h.local_uniq_name

end expr_set

namespace list

/--
`to_rb_map as` is the map that associates each index `i` of `as` with the
corresponding element of `as`.

```
to_rb_map ['a', 'b', 'c'] = rb_map.of_list [(0, 'a'), (1, 'b'), (2, 'c')]
```
-/
meta def to_rb_map {α : Type} : list α → native.rb_map ℕ α :=
foldl_with_index (λ i mapp a, mapp.insert i a) native.mk_rb_map

end list
