Load loadpath.
Require Import ssreflect ssrbool ssrnat ssrfun eqtype seq fintype finfun tuple 
               path.
Require Import prelude heaps rels hprop domains ordtype finmap secmod seclog 
               datatypes array min kvmaps.
Set Implicit Arguments.
Unset Strict Implicit.
Import Prenex Implicits.

Module A := Array. Module M := HashTab. 

(* hashmaps are key-value maps parameterized by a hash function and a base
key-value map m.  they are constructed by attaching a hash table to the kvmap
m, which is used to resolve collisions. 

in order to ensure that hashmaps are uniquely represented, they respect the
following invariant: (1) for each (k, v)-pair stored by the data structure, 
(k, v) is stored in the hash table for fast retrieval only if either:
- k is the only key stored that hashes to a particular bucket, or
- k is among multiple keys that hash to a particular bucket but k is also the
  smallest such key wrt. the ordering on keys; and
(2) (k, v) is stored in the stash only if slot hash(k) in the hash table already
contains a smaller key k' (ie. such that hask(k') == hash(k) and ord k' k). 

each key in the hash table is thus the (smallest) representative of the
equivalence class of stored keys that hash to a particular bucket. *)

(* beginning of specification of hashmaps proper *)

Module HashMap. Section HashMap.
Variables (n : nat) (K : ordType) (V : eqType) (hash : K -> 'I_n).
Variable  (m : @HashTab.Sig K V n hash).

Notation mT := (M.T m).

Definition hashmap := ({array 'I_n -> option (K * V)} * mT)%type.

(* projections *)

Definition T1 := [fun x : hashmap => fst x].
Definition T2 := [fun x : hashmap => snd x].

Section PreShape.
Variables (t : hashmap)
          (f1 : {ffun 'I_n -> option (K * V)}) (f2 : {finMap K -> V}).

Definition preshape := 
  [Pred i | exists j, exists k, i = j :+ k /\ 
      A.shape (T1 t) f1 j /\ (M.shape (T2 t) f2 k)].

End PreShape.

Lemma filter_nil {T} p (s : seq T) : 
  filter p s = [::] -> all (fun x => ~~ p x) s.
Proof.
elim: s=>//= t s' IH A. case: (p t) in A |- *; first by congruence.
by apply/andP; split=>//; apply: (IH A).
Qed.

Section Shape.
Variables (t : hashmap) (f : {finMap K -> V}).

(* filter keys that hash to a given bucket *)

Section SameBucket.
Variable (ix : 'I_n) (s : seq K).

Definition samebkt : seq K -> _ := filter [pred k | hash k == ix].

Lemma samebkt_index : all [pred k | hash k == ix] (samebkt s).
Proof. apply: filter_all. Qed.

Lemma samebkt_subseq : subseq (samebkt s) s. 
Proof. apply: filter_subseq. Qed.

Lemma samebktE k : k \in samebkt s -> k \in s.
Proof. by move=> A; apply: (mem_subseq (samebkt_subseq)). Qed.

Lemma samebktI k : hash k == ix -> k \in s -> k \in samebkt s.
Proof. by rewrite /samebkt mem_filter=> A B; apply/andP. Qed.

Lemma mem_samebkt : samebkt s =i [pred k | (k \in s) && (hash k == ix)].
Proof. by move=> k; rewrite /samebkt mem_filter /in_mem andbC. Qed.

Lemma samebkt_noneE : samebkt s = [::] -> all [pred k | hash k != ix] s.
Proof. by move=> A; apply (filter_nil A). Qed.

End SameBucket.

(*
Lemma samebkt_ins k (v : V) :
        samebkt (hash k) (supp (ins k v f)) =i 
        k :: samebkt (hash k) (supp f).
Proof.
move=>x; rewrite in_cons 2!mem_filter /= supp_ins inE /=.
by case: (x =P k)=>// ->; rewrite eq_refl.
Qed.
*)

Lemma samebkt_ins z k (v : V) : 
        samebkt z (supp (ins k v f)) =i 
        [predU samebkt z (supp f) & 
               if z == hash k then pred1 k else pred0].
Proof.
move=>x; rewrite inE /samebkt /= !mem_filter supp_ins !inE /=.
case E: (x == k)=>/=; last first.
- by case: ifP=>/=; rewrite ?E orbF.
rewrite (eqP E) (eq_sym z).
by case: ifP=>//=; rewrite eq_refl orbT.
Qed.

Lemma samebkt_rem z k : 
        samebkt z (supp (rem k f)) =i 
        [predD samebkt z (supp f) & 
               if z == hash k then pred1 k else pred0].
Proof.
move=>x; rewrite inE /samebkt /= !mem_filter supp_rem !inE /= eq_sym.
case E1: (z == hash k)=>/=; case E2: (x == k)=>//=;
by rewrite (eqP E2) E1.
Qed.

(* find the min key that hashes to a given bucket *)

(* calculate the minimum key that hashes to a particular bucket *)

Section MinKey.
Variable (ix : 'I_n).

Definition minkey := min (samebkt ix (supp f)).

Lemma minkey_lowerb k : 
  minkey = Some k -> lowerb k (samebkt ix (supp f)).
Proof. by apply: min_lowerb. Qed.

Lemma minkey_hash k : minkey = Some k -> ix = hash k.
Proof. 
rewrite /minkey=> A; suff B: k \in (samebkt ix (supp f)).
by move: B; rewrite mem_filter /=; case/andP; move/eqP.
by apply: min_in. 
Qed.

Lemma minkey_fnd k : minkey = Some k -> exists v, fnd k f = Some v.
Proof.
rewrite /minkey=> A; suff B: k \in (samebkt ix (supp f)).
by apply: fnd_supp_in; move: B; rewrite mem_filter; case/andP.
by apply: min_in. 
Qed.

Lemma minkey_fnd_none k : minkey = None -> ix = hash k -> fnd k f = None.
Proof.
rewrite /minkey=> A; suff B: k \notin (samebkt ix (supp f)).
move: (samebkt_noneE (min_none A))=> D E. suff F: k \notin (supp f). 
by apply: (fnd_supp F). move: (allP D)=> F; case G: (k \in supp f)=>//. 
by move: (F k G)=>/=; rewrite E; case/negP.
by rewrite (min_none A).
Qed.

End MinKey.

(* the hashtab is a finite function from buckets or
   indices to option(key-value pair) *)
 
Definition hashtab := 
  [ffun ix => if minkey ix is Some k then 
                if fnd k f is Some v then Some (k, v)
                else None (* this case cannot occur *)
              else None].

(* the stash is a finite map that contains all the remaining keys *)

Definition stash :=
  foldr (fun ix f0 => if hashtab ix is Some (k, v) then rem k f0
                      else f0) f (enum 'I_n).

Definition hasmin ix := if minkey ix is Some k then true else false.

(* alternative, equivalent defns. of stash *)

Definition stashxx :=
  foldr (fun ix f0 => if hashtab ix is Some (k, v) then rem k f0 
                      else f0) f (filter hasmin (enum 'I_n)).

Definition toKey ix := if hashtab ix is Some (k, v) then Some k else None.

Definition remkeys (ks : seq (option K)) := foldr (fun o f0 => 
  oapp (fun k => rem k f0) f0 o) f ks.

Definition stashyy := remkeys (map toKey (filter hasmin (enum 'I_n))).

(* lemmas about the hashtab and stash *)

Lemma hashtab_hash ix k v : hashtab ix = Some (k, v) -> hash k = ix.
Proof with try congruence.
rewrite ffunE. case A: (minkey ix)=> [k'|]... case: (fnd k' f)=>[v'|]...
case=> B _; move: B A=> ->; rewrite /minkey.
by move=> A; case: (minkey_hash A).
Qed.

Lemma hashtab_fnd ix k v : hashtab ix = Some (k, v) -> fnd k f = Some v.
Proof with try congruence.
rewrite ffunE. case A: (minkey ix)=> [k'|]... case B: (fnd k' f)=>[v'|]...
Qed.

Lemma hashtab_some_eqv ix k v : 
  hashtab ix = Some (k, v) <-> (fnd k f = Some v /\ minkey ix = Some k).
Proof.
split=> [A|[A B]]. split; first by apply: (hashtab_fnd A).
move: A; rewrite ffunE; case B: (minkey ix)=>[k'|]; [|congruence].
by case C: (fnd k' f); congruence.
by rewrite ffunE B A.
Qed.

Lemma hashtab_none_eqv ix : 
  hashtab ix = None <-> (forall k, ix = hash k -> fnd k f = None).
Proof with try congruence.
rewrite ffunE; split. case A: (minkey ix)=> [k'|]... case B: (fnd k' f)...
move=> _; move: (minkey_hash A)=> C; move: C A=> -> C.
move: (minkey_fnd C)=> [v D]... by move=> _ k; apply: (minkey_fnd_none A).
move=> A. case B: (minkey ix)=> [k'|]... by rewrite (A _ (minkey_hash B)).
Qed.

Lemma hashtab_none_minkey ix :
        hashtab ix = None -> minkey ix = None.
Proof.
rewrite ffunE; case H': (minkey _)=>[a|//].
by case/minkey_fnd: H'=>v ->.
Qed.

Lemma stash_fnd k v : fnd k stash = Some v -> fnd k f = Some v.
Proof.
rewrite /stash; elim: (enum 'I_n)=>//= n' s' IH.
case: (hashtab n')=> [[k' v']|] //. by rewrite fnd_rem; case: (k == k'). 
Qed.

Lemma stash_stashxx : stash = stashxx.
Proof.
rewrite /stash /stashxx; elim: (enum 'I_n)=>//= ix s IH.
case A:(hashtab ix)=>// [[k v]|]; move:(A);[rewrite hashtab_some_eqv=> [[B C]]|].
rewrite /hasmin C /= A; apply/fmapP=> k'; rewrite 2!fnd_rem; case: (k' == k)=>//.
by rewrite IH.
rewrite IH=>// _; rewrite /hashtab ffunE in A.
move: A; case B: (minkey ix)=> [k'|]. 
by case A: (fnd k' f)=>//; move: A; case: (minkey_fnd B)=> v ->; congruence.
by rewrite /hasmin B.
Qed.

Lemma stashxx_stashyy : stashxx = stashyy.
Proof.
rewrite /stashxx /stashyy /toKey; elim: (enum 'I_n)=>//= ix s IH.
case A:(hashtab ix)=>// [[k v]|]; move:(A);[rewrite hashtab_some_eqv=> [[B C]]|].
rewrite /hasmin C /= A; apply/fmapP=> k'; rewrite 2!fnd_rem; case: (k' == k)=>//.
by rewrite IH.
rewrite /hashtab ffunE in A; move:A; case B: (minkey ix)=> [k'|].
case A: (fnd k' f)=>//; move:A; case: (minkey_fnd B)=> v ->; congruence.
by rewrite /hasmin B.
Qed.

Lemma remkeys_in k ks : Some k \in ks -> fnd k (remkeys ks) = None.
Proof.
elim: ks=>//= o ks' IH; rewrite in_cons; case/orP. case/eqP=> <- /=.
by rewrite fnd_rem eq_refl. 
by move=>A; case:(IH A)=> B; case:o=>// k1; rewrite fnd_rem; case:(k == k1).
Qed.

Lemma remkeys_none k ks : fnd k f = None -> fnd k (remkeys ks) = None.
Proof.
elim: ks=>//= o ks' IH A; case: (IH A)=> B.
by case: o=>//= k'; rewrite fnd_rem; case: (k == k').
Qed.

Lemma stashyy_fnd_none k : minkey (hash k) = Some k -> fnd k stashyy = None.
Proof.
move=> A; case B: (fnd k f)=>[v'|].
case: (@hashtab_some_eqv (hash k) k v')=> _; case/(_ (conj B A))=> C.
have D: hasmin (hash k) by rewrite /hasmin A. 
have E: hash k \in (filter hasmin (enum 'I_n)). 
- by rewrite mem_filter mem_enum; apply/andP.
have F: toKey (hash k) \in (map toKey (filter hasmin (enum 'I_n))).
- move: E; elim: (enum 'I_n)=>//= ix' s' IH.
case: (hasmin ix')=>//; rewrite in_cons; move/orP=>E; rewrite in_cons; apply/orP. 
case: E; first by case/eqP=> ->; left; apply/eqP.
by move=> E; right; apply: (IH E).
by rewrite /toKey C in F; apply: remkeys_in.
case: (hashtab_none_eqv (hash k))=> [C D].
rewrite /stashyy; elim: (enum 'I_n)=>//= ix s' IH.
by case:(hasmin ix)=>//=; case:(toKey ix)=>//k1; rewrite fnd_rem; case:(k == k1).
Qed.

Lemma stash_fnd_none k : minkey (hash k) = Some k -> fnd k stash = None.
Proof.
rewrite stash_stashxx stashxx_stashyy=> A.
by apply: stashyy_fnd_none; rewrite -(minkey_hash A).
Qed.

Lemma hashtab_stash_fnd ix k v : hashtab ix = Some (k, v) -> fnd k stash = None.
Proof.
rewrite hashtab_some_eqv; move=> [_ B]; apply: stash_fnd_none.
by rewrite -(minkey_hash B).
Qed.

Lemma fnd_stash_none k : fnd k f = None -> fnd k stash = None.
Proof.
rewrite /stash; elim: (enum 'I_n)=>// k' s' IH A; move: (IH A) =>//= B.
case: (hashtab k')=> [[k1 v1]|]; first by rewrite fnd_rem=>//; case: (k == k1).
by apply: IH.
Qed.

Lemma stash_some_eqv k v : 
  fnd k stash = Some v <-> (fnd k f = Some v /\ minkey (hash k) <> Some k).
Proof.
split=> [A|[A B]]. have B: fnd k f = Some v by apply: (stash_fnd A). split=>//. 
move=> C; have D: hashtab (hash k) = Some (k, v) by rewrite hashtab_some_eqv.
move: A (hashtab_stash_fnd D)=> ->; congruence.
case C: (hashtab (hash k))=> [[k' v']|]. 
- move: C; rewrite hashtab_some_eqv; move=> [C D]. 
  rewrite /stash; elim: (enum 'I_n)=>//= n' l' IH. 
  case E: (hashtab n')=> [[k1 v1]|]. rewrite fnd_rem=>//; case F: (k == k1).
  move: E; rewrite hashtab_some_eqv; move=> [E1 E2]; move: F; case/eqP=> F.
  rewrite F in B; exfalso; apply: B; by move: (minkey_hash E2)=> <-.
  by apply: IH. by apply: IH. move: C; rewrite hashtab_none_eqv=> C. 
by case: (C k (erefl _))=> D; move: D A=> ->; congruence.
Qed.

Lemma stash_none_eqv k :
        fnd k stash = None <-> (fnd k f = None \/ minkey (hash k) = Some k).
Proof.
case E: (fnd k stash)=>[a|].
- by case/stash_some_eqv: E=>-> E; split=>//; case.
split=>// _; move: E; rewrite /stash.
elim: (enum 'I_n)=>[|k' s' IH] /=; first by left. 
case E: (hashtab k')=>[[k1 v1]|]; last by move/IH.
rewrite fnd_rem; case: ifP=>E2; last by move/IH.
rewrite -(eqP E2) in E; right.
case/hashtab_some_eqv: (E)=>_ <-.
by move/hashtab_hash: E=><-.
Qed.

Lemma fnd_eqv (k : K) (v : V) : 
  fnd k f = (if hashtab (hash k) is Some (k', v) then 
                 if k == k' then Some v else fnd k stash
             else None).
Proof.
rewrite ffunE. case A: (minkey (hash k))=> [k'|]; [case B: (fnd k' f)=> [v'|]|]. 
case C: (k == k'); first by move: C; case/eqP=> ->.
symmetry. case D: (fnd k f)=> [v1|]. rewrite (@stash_some_eqv k v1).
- by split=>// E; move: A E C=> ->; case=> ->; case/negP.
by apply: fnd_stash_none.
by move: (fnd_supp_in (samebktE (min_in A)))=> [v' C]; congruence.
by apply: (minkey_fnd_none A).
Qed.

Definition shape := preshape t hashtab stash.

End Shape.

Lemma minkey_ins k z v f : 
        minkey (ins k v f) z = 
        if z == hash k then 
          if minkey f z is Some k' then Some (minn' k k')
          else Some k
        else minkey f z.
Proof.
rewrite /minkey/min/minn'.
have H2: samebkt z (supp (ins k v f)) =i 
         [predU samebkt z (supp f) & 
                if z == hash k then pred1 k else pred0].
- by apply: samebkt_ins.
case E: (z == hash k)=>/=; last first.
- rewrite E in H2.
  case E1: (samebkt _ _)=>[|k0' s']; rewrite {}E1 in H2.
  - case E2: (samebkt _ _)=>[//|k0 s].
    by rewrite E2 in H2; move/(_ k0): H2; rewrite !inE eq_refl.
  case E2: (samebkt _ _)=>[|k0 s]; rewrite E2 in H2.
  - by move/(_ k0'): H2; rewrite !inE eq_refl.
  suff: k0 :: s =i k0' :: s' by move/minP.
  by move=>t; rewrite H2 !inE /= orbF.
rewrite E in H2.
case E1: (samebkt _ _)=>[//|k0' s']; rewrite {}E1 in H2. 
- by move/(_ k): H2; rewrite !inE /= eq_refl orbT.
case E2: (samebkt _ _)=>[//|k0 s]; rewrite {}E2 in H2.
- have {H2}: k0' :: s' =i [:: k] by move=>t; rewrite H2 !inE.
  by move/minP.
have {H2} H2: k0' :: s' =i [:: k, k0 & s]. 
- by move=>t; rewrite H2 !inE /= orbC.
set k' := min' s k0.
suff /=: min [:: k, k0 & s] = if ord k k' then Some k else Some k'.
- by rewrite -(minP H2); case: ifP. 
case H1: (ord k k'); congr Some. 
- case: totalP=>O; first by rewrite (minn_min' s O) /minn' H1.
  - case/orP: (min_ordb s k); first by move/eqP.
    by rewrite {1}(eqP O); case: totalP H1.
  case/orP: (min_ordb s k0).
  - by rewrite eq_sym; case: totalP (trans O H1).
  by move=>O'; case: totalP H1 (trans O' O).
case: totalP H1=>// H1 _; last first.
- rewrite -{k H1}(eqP H1) in E H2 *.
  case/orP: (min_ordb s k0)=>E2.
  - by rewrite (eqP E2) irr min_min'.
  by case: totalP E2=>// O _; rewrite min_min'.
case: (totalP k0 k)=>//; first by move/eqP=><-. 
move/(trans H1) => T.
move/min_in': (T)=>T'.
rewrite -(min_base' (y:=k) T') //.
by apply: min_base' T' T.
Qed.

Lemma hashtab_ins k v f z : 
        hashtab (ins k v f) z = 
        if z == hash k then 
          if minkey f z is Some k' then 
            if ~~ ord k' k then Some (k, v)
            else hashtab f z 
          else Some (k, v)
        else hashtab f z.
Proof.
rewrite /hashtab !ffunE /= minkey_ins.
case E: (z == hash k); case H1: (minkey _ _)=>[b|] //;
rewrite fnd_ins ?eq_refl //; last first.
- case H2: (b == k)=>//; rewrite (eqP H2) in H1.
  by move/minkey_hash: H1 E=>->; rewrite eq_refl.
rewrite /minn'; case: totalP=>/=.
- by rewrite eq_sym; case: totalP.
- by move/eqP=>->; rewrite eq_refl.
by rewrite eq_refl.
Qed.

Lemma stash_ins k v f :
        stash (ins k v f) = 
        if minkey f (hash k) is Some k' then 
           if ord k' k then ins k v (stash f)
           else if k' == k then stash f
                else 
                  if fnd k' f is Some v' then 
                     ins k' v' (stash f)
                  else stash f (* cannot happen *)
        else stash f.
Proof.
apply: fmapP=>z.
case E: (fnd z _)=>[a|]; apply/sym.
- case/stash_some_eqv: E.
  rewrite fnd_ins minkey_ins.
  case: ifP=>E1.
  - rewrite {z E1}(eqP E1) eq_refl.
    case=><-{a}. 
    case H1: (minkey _ _)=>[a|//]; rewrite /minn'.
    case: totalP=>//; last by move/eqP=>->.
    by rewrite fnd_ins eq_refl.
  move=>F1.
  case: ifP=>E2.
  - rewrite (eqP E2).
    case H1: (minkey _ _)=>[b|]; last first.
    - by move=>_; apply/stash_some_eqv; rewrite (eqP E2) H1.
    rewrite /minn'; case: totalP=>O.
    - rewrite fnd_ins E1 => N1.
      by apply/stash_some_eqv; rewrite (eqP E2) H1.
    - move=>N1.
      by apply/stash_some_eqv; rewrite (eqP E2) H1.
    case/minkey_fnd: (H1)=>v' E3 _; rewrite E3 fnd_ins.
    case: ifP=>E4; first by rewrite -E3 -F1 (eqP E4).
    apply/stash_some_eqv; rewrite (eqP E2) H1; split=>//.
    by case=>E; rewrite E eq_refl in E4.
  move=>N1.
  case H2: (minkey _ _)=>[b|]; last by apply/stash_some_eqv.
  rewrite eq_sym; case: totalP=>O; last first.
  - by rewrite fnd_ins E1; apply/stash_some_eqv.
  - by apply/stash_some_eqv.
  case/minkey_fnd: (H2)=>v' E3; rewrite E3 fnd_ins.
  case: ifP=>E4; first by rewrite -E3 -(eqP E4).
  by apply/stash_some_eqv.
case/stash_none_eqv: E.
- rewrite fnd_ins.
  case: ifP=>[//|E1 E2].
  case H1: (minkey _ _)=>[a|]; last by apply/stash_none_eqv; left.
  rewrite eq_sym; case: totalP=>O; last first.
  - by rewrite fnd_ins E1; apply/stash_none_eqv; left.
  - by apply/stash_none_eqv; left. 
  case/minkey_fnd: (H1)=>v' E3.
  rewrite E3 fnd_ins; case: ifP=>E4; first by rewrite -E3 -(eqP E4).
  by apply/stash_none_eqv; left.
rewrite minkey_ins; case: ifP=>E1; last first.
- move=>H1.
  case H2: (minkey _ _)=>[a|]; last by apply/stash_none_eqv; right.
  rewrite eq_sym; case: totalP=>O; last first.
  - rewrite fnd_ins.
    case: ifP=>E2; first by rewrite (eqP E2) eq_refl in E1.
    by apply/stash_none_eqv; right.
  - by apply/stash_none_eqv; right.
  case/minkey_fnd: (H2)=>v' E2; rewrite E2 fnd_ins.
  case: ifP=>E3; last by apply/stash_none_eqv; right.
  rewrite {z E3}(eqP E3) in E1 H1.
  by move/minkey_hash: H2 E1=>->; rewrite eq_refl.
rewrite (eqP E1) /minn'.
case H1: (minkey _ _)=>[a|]; last first. 
- case=><-; apply/stash_none_eqv; left.
  by rewrite (minkey_fnd_none H1).
case: totalP=>O [E]. 
- rewrite fnd_ins eq_sym -E; case: totalP O=>// O _.
  by apply/stash_none_eqv; right; rewrite {1}E (eqP E1).
- rewrite {a O}(eqP O) in E H1 *; rewrite -{z E1}E.
  by apply/stash_none_eqv; right. 
case/minkey_fnd: (H1)=>v' E2.
rewrite -{z E1}E E2 fnd_ins eq_sym.
case: totalP O=>// O _.
apply/stash_none_eqv; left.
move/minkey_lowerb/(_ k): H1; rewrite mem_filter /= eq_refl /=.
case: suppP=>// w ->; move/(_ (erefl _)).
by case; [move/eqP|]; case: totalP O.
Qed.

(* lemmas for remove *)

Lemma supp_stash f :
        supp (stash f) =i 
        [predD supp f & [pred k | minkey f (hash k) == Some k]].
Proof.
move=>x; rewrite !inE /minkey/min/samebkt andbC.
case E1: (filter _ _)=>[|y s] /=.
- move/filter_nil: E1; case: allP=>//= E1 _.
  case E2: (x \in supp f); first by move/E1: E2; rewrite eq_refl.
  by case: suppP=>// v; case: suppP E2=>//; move/fnd_stash_none=>->.
case E2: (x \in supp f) => /=; last first.
- by case: suppP=>// v; case: suppP E2=>//; move/fnd_stash_none=>->.
case: suppP.
- move=>v; case/stash_some_eqv=>_.
  by rewrite /minkey/samebkt/min E1; case: eqP=>// ->.
case/stash_none_eqv; first by case: suppP E2=>// v ->.
by rewrite /minkey/samebkt/min E1; case=>->; rewrite eq_refl.
Qed.

Lemma samebkt_stash z f :
        samebkt z (supp (stash f)) =i
        [predD samebkt z (supp f) & 
               if minkey f z is Some k then pred1 k else pred0].
Proof.
move=>x; rewrite /samebkt mem_filter supp_stash /= !inE /=. 
rewrite inE /= mem_filter /=; case: eqP=>//=; last by rewrite andbF.
move=><-; case E: (minkey f (hash x))=>[a|] //; rewrite inE /=.
case: (x =P a)=>/=; first by move=>->; rewrite eq_refl.
by case: eqP=>//; case=>->.
Qed.

Lemma minkey_rem k f z :
        minkey (rem k f) z = 
        if z == hash k then 
          if minkey f z is Some k' then 
             if k == k' then minkey (stash f) z
             else minkey f z
          else None
        else minkey f z.
Proof.
rewrite /minkey/min.
have H2: samebkt z (supp (rem k f)) =i
         [predD samebkt z (supp f) & 
                if z == hash k then pred1 k else pred0].
- by apply: samebkt_rem.
case E: (z == hash k)=>/= in H2 *; last first.
- case E1: (samebkt _ _)=>[|k1 s1] in H2 *;
  case E2: (samebkt _ _)=>[//|k2 s2] in H2 *.
  - by move/(_ k2): H2; rewrite !inE /= eq_refl.
  - by move/(_ k1): H2; rewrite !inE /= eq_refl.
  have: k1 :: s1 =i k2 :: s2 by move=>x; rewrite H2 inE.
  by move/minP.
case E1: (samebkt _ _)=>[|k1 s1] in H2 *.
- case E2: (samebkt _ _)=>[//|k2 s2] in H2 *.
  case L: (k \in k2 :: s2); last first.
  - move: (H2 k2); move/negbT: L.
    rewrite !inE /= negb_or eq_refl /= eq_sym.
    by case/andP=>->.
  have {H2}: [:: k] =i k2 :: s2.
  - by move=>x; move: (H2 x); rewrite !inE; case: eqP=>//= ->.
  case/minP=>H2; rewrite H2 eq_refl.
  have M: minkey f z = Some k by rewrite /minkey E2 /= H2.
  case E3: (samebkt _ _)=>[//|k3 s3].
  suff: k3 \in samebkt z (supp (rem k f)) by rewrite E1.
  have {E3}: k3 \in samebkt z (supp (stash f)) by rewrite E3 inE eq_refl.
  by rewrite samebkt_stash samebkt_rem M E.
case E2: (samebkt _ _)=>[//|k2 s2] in H2 *.
- by move: (H2 k1); rewrite /= !inE /= eq_refl andbC.
case: ifP=>T.
- have M: minkey f z = Some k by rewrite /minkey E2 (eqP T).
  have S : samebkt z (supp (stash f)) =i samebkt z (supp (rem k f)).
  - by move=>x; rewrite samebkt_stash samebkt_rem M E. 
  case E3: (samebkt _ _)=>[|k3 s3] in S *.
  - by rewrite E1 in S; move: (S k1); rewrite inE eq_refl.
  by rewrite E1 in S; move/minP: S.
case L: (k \in k2 :: s2); last first.
- suff: k2 :: s2 =i k1 :: s1 by move/minP.
  by move=>x; move: (H2 x); rewrite !inE; case: (x =P k)=>// ->->.
have {H2}: k2 :: s2 =i [:: k, k1 & s1].
- by move=>x; move: (H2 x); rewrite !inE; case: (x =P k)=>// ->.
move/minP; rewrite min_cons /minn' /=. 
by case: totalP=>// _ [H]; rewrite H eq_refl in T.
Qed.

Lemma hashtab_rem k f z : 
        hashtab (rem k f) z = 
        if z == hash k then 
          if minkey f z is Some k' then 
             if k == k' then 
               if minkey (stash f) z is Some k'' then 
                 if fnd k'' (stash f) is Some v'' then Some (k'', v'')
                 else None (* cannot happen *)
               else None
             else hashtab f z
          else None
        else hashtab f z.
Proof.
rewrite /hashtab !ffunE minkey_rem.
case E: (z == hash k); last first.
- case H1: (minkey _ _)=>[b|] //.
  rewrite fnd_rem; case: ifP=>//; move/eqP=>F; rewrite -{k}F in E *.
  by move/minkey_hash: H1 E=>->; rewrite eq_refl.
rewrite {z E}(eqP E). 
case H1: (minkey _ _)=>[b|] //.
case H2: (k == b); last by rewrite fnd_rem eq_sym H2.
rewrite -{b H2}(eqP H2) in H1 *.
case H3: (minkey _ _)=>[c|] //.
case H4: (fnd c (stash f))=>[d|]; last first.
- by case/minkey_fnd: H3=>v; rewrite H4.
case/stash_some_eqv: H4=>H4 H5.
rewrite fnd_rem H4; case: eqP=>// E.
by rewrite E H1 in H5.
Qed.

Lemma stash_rem k f :
        stash (rem k f) = 
        if minkey f (hash k) is Some k' then 
          if ord k' k then rem k (stash f)
          else if k' == k then 
            if minkey (stash f) (hash k) is Some k'' 
            then rem k'' (stash f) else stash f
          else stash f
        else stash f.
Proof.
apply/fmapP=>z; case E1: (fnd z _)=>[a|]; apply/sym.
- case/stash_some_eqv: E1; rewrite fnd_rem minkey_rem.
  case: ifP=>// N1 E1; case: ifP=>H1; last first.
  - move=>M1; case H2: (minkey _ _)=>[b|]; last by apply/stash_some_eqv.
    rewrite eq_sym; case: totalP=>O; last 1 first.
    - by rewrite fnd_rem N1; apply/stash_some_eqv.
    - by apply/stash_some_eqv.
    rewrite -{b O}(eqP O) in H2 *.
    case H3: (minkey _ _)=>[c|]; last by apply/stash_some_eqv.
    rewrite fnd_rem; case: eqP=>T; last by apply/stash_some_eqv.
    rewrite -{}T in H3; move/minkey_hash: H3=>T.
    by rewrite T eq_refl in H1.
  rewrite (eqP H1).
  case H2: (minkey _ _)=>[b|]; last first.
  - by move=>_; apply/stash_some_eqv; rewrite (eqP H1) H2.
  rewrite (eq_sym b); case: totalP=>O; last 1 first.
  - move=>T; rewrite fnd_rem N1.
    by apply/stash_some_eqv; rewrite (eqP H1) H2.
  - by move=>T; apply/stash_some_eqv; rewrite (eqP H1) H2.
  rewrite -{b O}(eqP O) in H2 *.
  case: (minkey _ _)=>[c|]; last first.
  - move=>_; apply/stash_some_eqv; rewrite (eqP H1) H2.
    by split=>//; case=>E; rewrite E eq_refl in N1.
  rewrite fnd_rem; case: eqP; first by move=>->.
  move=>T _; apply/stash_some_eqv; rewrite (eqP H1) H2. 
  by split=>//; case=>E; rewrite E eq_refl in N1.
case/stash_none_eqv: E1; last first.
- rewrite minkey_rem; case: ifP=>E1.
  - rewrite (eqP E1); case H2: (minkey _ _)=>[b|//].
    rewrite (eq_sym b); case: totalP=>O.
    - by case=>E; apply/stash_none_eqv; right; rewrite (eqP E1) -E.
    - by rewrite -{b O}(eqP O) in H2 * => ->; rewrite fnd_rem eq_refl.
    case=>E; rewrite fnd_rem -E eq_sym; case: totalP O=>// O _.
    by apply/stash_none_eqv; right; rewrite {1}E (eqP E1).
  move=>H1; case H2: (minkey _ _)=>[b|]; last by apply/stash_none_eqv; right.
  rewrite eq_sym; case: totalP=>O; last 1 first.
  - by rewrite fnd_rem; case: ifP=>// _; apply/stash_none_eqv; right.
  - by apply/stash_none_eqv; right.
  rewrite -{b O}(eqP O) in H2 *.
  case H3: (minkey _ _)=>[c|]; last by apply/stash_none_eqv; right.
  by rewrite fnd_rem; case: ifP=>// _; apply/stash_none_eqv; right.
rewrite fnd_rem; case: ifP=>E1 E2.
- rewrite {z E1 E2}(eqP E1).
  case H2: (minkey _ _)=>[b|]; last first.
  - by apply/stash_none_eqv; left; apply: minkey_fnd_none H2 _.
  rewrite eq_sym; case: totalP=>O; last first.
  - by rewrite fnd_rem eq_refl.
  - rewrite -{b O}(eqP O) in H2 *.
    case H3: (minkey _ _)=>[c|]; last by apply/stash_none_eqv; right.
    by rewrite fnd_rem; case: eqP=>// _; apply/stash_none_eqv; right.
  apply/stash_none_eqv; left; move/minkey_lowerb/(_ k): H2.
  rewrite /samebkt mem_filter inE eq_refl /=.
  case: suppP=>// v _; case/(_ (erefl _)); last by case: totalP O.
  by move=>T; rewrite T irr in O.
case H2: (minkey _ _)=>[b|]; last by apply/stash_none_eqv; left.
rewrite eq_sym; case: totalP=>O; last 1 first.
- by rewrite fnd_rem E1; apply/stash_none_eqv; left.
- by apply/stash_none_eqv; left.
rewrite -{b O}(eqP O) in H2 *.
case H3: (minkey _ _)=>[c|]; last by apply/stash_none_eqv; left.
by rewrite fnd_rem; case: eqP=>// _; apply/stash_none_eqv; left.
Qed.

Definition sshape ss ff := fun ii => 
  shape ss.1 ff.1 ii.1 /\ shape ss.2 ff.2 ii.2.

Lemma sshape_fun s f i1 i2 : sshape (s, s) (f, f) (i1, i2) -> i1 = i2.
Proof.
move=> [[?]][?][/= ->][/= [-> _] A] [?][?][->][[->]] _ B. 
by f_equal; apply: (M.shape_fun A B).
Qed.

Lemma sshape_refl xx ff ii :
  sshape xx ff ii -> shape xx.1 ff.1 ii.1 /\ shape xx.2 ff.2 ii.2.
Proof. by move=>[A B]. Qed.

Lemma sshape_refl1 x f i : shape x f i -> sshape (x, x) (f, f) (i, i).
Proof. by []. Qed.

Lemma sshape_refl2 x1 x2 f1 f2 i1 i2 : 
  shape x1 f1 i1 -> shape x2 f2 i2 -> sshape (x1, x2) (f1, f2) (i1, i2).
Proof. by []. Qed.

Definition pre_kvmap := fun x i => exists f, exists j, exists h,
                                     i = j :+ h /\ j \In shape x f.

Notation empf := ([ffun => None]).

Notation nil := (nil K V).

Lemma hashtab_nil : hashtab nil = empf.
Proof. by []. Qed.

Lemma stash_nil : stash nil = nil.
Proof. 
apply/fmapP=> k; rewrite /stash; elim: (enum 'I_n)=>//= ix s' IH. 
set nil' := (finmap.nil _ _). case: ((hashtab nil') ix)=> [[k1 v1]|].
rewrite fnd_rem. by case: (k == k1). by apply: IH.
Qed.

Definition keys (s : seq (K * V)) := map key s.

(* inner conditional specs for remove *)

Definition removeS1 (kv0 : K * V) t : spec [::: option (K * V); K] unit :=
  (fun o k => pre_kvmap t,
   fun oo kk yy ii mm =>
     forall ff jj hh, 
       ii = jj :++ hh -> sshape (t, t) ff jj ->
       hashtab ff.1 (hash kk.1) = oo.1 ->
       hashtab ff.2 (hash kk.2) = oo.2 ->
     exists jj', mm = jj' :++ hh /\
       (if (isSome_pred oo.1 && ((getSome0 kv0 oo.1).1 == kk.1)) 
        then shape t (rem kk.1 ff.1) 
        (*if toplevel array at position (hash k) is either None or k' s.t. k<>k':
              1. toplevel array remains unchanged; and 
              2. key k is removed from the stash*)
        else preshape t (hashtab ff.1) (rem kk.1 (stash ff.1))) jj'.1 /\
       (if (isSome_pred oo.2 && ((getSome0 kv0 oo.2).1 == kk.2)) 
        then shape t (rem kk.2 ff.2) 
        else preshape t (hashtab ff.2) (rem kk.2 (stash ff.2))) jj'.2).

Definition removeS2 (kv0 : K * V) t
  : spec [::: option K; option (K * V); K] unit :=
  (fun mk o k => pre_kvmap t,
   fun mkey oo kk yy ii mm =>
     forall ff jj hh,
       ii = jj :++ hh -> sshape (t, t) ff jj ->
       hashtab ff.1 (hash kk.1) = oo.1 ->
       hashtab ff.2 (hash kk.2) = oo.2 ->
       isSome_pred oo.1 && ((getSome0 kv0 oo.1).1 == kk.1) -> 
       isSome_pred oo.2 && ((getSome0 kv0 oo.2).1 == kk.2) -> 
       (*mkey is the minkey hashing to (hash k) in the stash*)
       mkey.1 = minkey (stash ff.1) (hash kk.1) ->
       mkey.2 = minkey (stash ff.2) (hash kk.2) ->
     exists jj', mm = jj' :++ hh /\
       (if mkey.1 is Some k then
        (*if mkey is Some k: 
               1. let v = fnd k (stash f); 
               2. remove k from stash; and 
               3. update toplevel array with (k, v)*)
           if fnd k (stash ff.1) is Some v then
               preshape t (A.updf (hash kk.1) (Some (k, v)) (hashtab ff.1))
                          (rem k (stash ff.1))
           else Pred0 (*case can't happen: stash minkey is Some k ==> minkey in stash*)
        (*if mkey is None, delete key from toplevel array by overwriting with None; and
          leave stash untouched*)
        else preshape t (A.updf (hash kk.1) None (hashtab ff.1)) (stash ff.1)) jj'.1 /\
       (if mkey.2 is Some k then
           if fnd k (stash ff.2) is Some v then
               preshape t (A.updf (hash kk.2) (Some (k, v)) (hashtab ff.2))
                          (rem k (stash ff.2))
           else Pred0
        else preshape t (A.updf (hash kk.2) None (hashtab ff.2)) (stash ff.2)) jj'.2).

Program Definition remove (kv0 : K * V) (t : hashmap) 
  : STsec [::: K] unit 
  (fun k => pre_kvmap t,
   fun kk yy ii mm =>
     forall ff jj hh, 
       ii = jj :++ hh -> sshape (t, t) ff jj ->
     exists jj', mm = jj' :++ hh /\
       sshape (t, t) (rem kk.1 ff.1, rem kk.2 ff.2) jj') :=
  Do (A.read (T1 t) @ (fun k => [hash k]);;
      Ifbool (fun o k => isSome_pred o && ((getSome0 kv0 o).1 == k)) 
      (*key k is present in the toplevel array*)
      then M.minkey (T2 t) @ (fun o k => [hash k]);;
           Ifbool (fun mk o k => isNone mk) 
           then A.write (T1 t) @ (fun mk o k => [hash k, None])
           else M.lookup (T2 t) @ (fun mk o k => [getSome0 kv0.1 mk]);;
                M.remove (T2 t) @ (fun v mk o k => [getSome0 kv0.1 mk]);;
                A.write (T1 t) @ (fun _ v mk o k => 
                   [hash k, Some (getSome0 kv0.1 mk, getSome0 kv0.2 v)])
           with [::: option K; option (K * V); K], unit, (removeS2 kv0 t)
      (*toplevel array is either None at (hash k) or (Some k' v') with k <> k'.
        just calling M.remove in this case is somewhat inefficient; better to check 
        first whether o is None and if so, just return. however, this way we have one 
        fewer conditional.*)
      else M.remove (T2 t) @ (fun o k => [k]) 
      with [::: option  (K * V); K], unit, (removeS1 kv0 t)).
Next Obligation.
rewrite !gh; split=>/=.
- move=>a i [f][j][h][->][j1][k1][->][A M]; case: ifP=>E /=; sauto. 
  - apply: sval_do; first by eexists j1, (k1 :+ h), (hashtab f); rewrite unA. 
  rewrite -!unA unCA.
  apply: sbnd_do=>[|y1 m1] /=; first by eexists (stash f), k1, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 M))=>[[-> _]] /= [-> _].
  sauto; apply: sbnd_do=>[|y2 m2] /=; first by eexists (stash f), _, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 M))=>[[k1' k2']][[-> _]] MM.
  by rewrite unCA; sauto; apply: sval_do; eexists j1, _, (hashtab f).
move=>a1 a2 _ _ [[[[]]]] /= [f1 f2][j1 j2][h1 h2][[[[[[[]]]]]]] /= [->->].
move=>H1 H2 H3 H4 H5 H6 H7 _ _; case: H1 (H1)=>/=.
case=>i1 [k1][->{j1}][[->{i1}]] D1 M1; case=>i2 [k2][->{j2}][[->{i2}]] D2 M2.
set j1 := updi2 _ _; set j2 := updi2 _ _.
have [A1 A2] : A.shape t.1 (hashtab f1) j1 /\ A.shape t.1 (hashtab f2) j2 by [].
move=>H1 _ _; case: ifP=>E1 /=; case: ifP=>E2; vauto.
(* case 1 *)
- rewrite -!unA; apply: A.val_write=>// _ j2' [[/=->]] D1' [[/=->]] D2'.
  rewrite !unA; eexists (_ :+ k1, _ :+ k2); split=>//.
  case: a1.1 E1 H6=>// _ H6; case: a2.1 E2 H7=>//= _ H7.
  by split; eexists _, _.
(* case 2 *) 
- rewrite -!unA.
  apply: val_doL=>[|y1 m1] /=; first by eexists j1, _, (hashtab f1).
  case/(_ (_, _) (_, _) (_, _) (erefl _) (conj A1 A1))=>[[j1' j2']].
  case=>[[-> _]][[/= -> Dj1]][/= -> Dj2]; rewrite unCA. 
  apply: bnd_doV=>[|y2 m2] /=; first by eexists (stash f2), k2, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 M2))=>[[-> _]][-> _]; lauto.
  apply: bnd_doV=>[|y3 m3] /=; first by eexists (stash f2), k2, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 M2))=>[[k1' k2']][[-> E]] MM.
  rewrite unCA; lauto; apply: A.val_writeV=>// _ [->] D _; rewrite !unA.
  eexists (_ :+ k1, _ :+ k1'); split=>//.
  case: a1.1 E1 H6 MM D=>// _ H6.
  case: a2.1 E2 H7=>// a21 _; move/sym=>H7 /= MM D.
  split; first by eexists _, _.
  case/minkey_fnd: (H7) D=>w -> /= D; eexists _, _; do !split=>//.
  by case/M.sshape_refl: MM.
(* case 3 *)
- apply/vrfC; rewrite -!unA.
  apply: val_doL=>[|y1 m1] /=; first by eexists j2, _, (hashtab f2).
  case/(_ (_, _) (_, _) (_, _) (erefl _) (conj A2 A2))=>[[j2' j1']].
  case=>[[-> _]][[/= -> Dj1]][/= -> Dj2]; rewrite unCA. 
  apply: bnd_doV=>[|y2 m2] /=; first by eexists (stash f1), k1, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 M1))=>[[-> _]][-> _]; lauto.
  apply: bnd_doV=>[|y3 m3] /=; first by eexists (stash f1), k1, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 M1))=>[[k2' k1']][[-> E]] MM.
  rewrite unCA; lauto; apply: A.val_writeV=>//= _ [->] D _; rewrite !unA.
  eexists (_ :+ k2', _ :+ k2); split=>//.
  case: a1.1 E1 H6 MM D=>// a11 _ ; move/sym=>H6.
  case: a2.1 E2 H7=>//=  _ H7 MM D. 
  split; last by eexists _, _.
  case/minkey_fnd: H6 D=>w -> /= D; eexists _, _; do !split=>//.
  by case/M.sshape_refl: MM.
(* case 4 *)
rewrite -!unA (unCA j1) (unCA j2). 
apply: bnd_do=>[|y1 y2 m1 m2] /=.
- by split; [exists (stash f1)|exists (stash f2)]; eexists _, _.
case/(_ (_, _) (_, _) (_, _) (erefl _) (M.sshape_refl2 M1 M2)).
case=>->->{m1 m2} [->->{y1 y2}]; vauto.
apply: bnd_do=>[|y1 y2 m1 m2] /=.
- by split; [exists (stash f1)|exists (stash f2)]; eexists _, _.
case/(_ (_, _) (_, _) (_, _) (erefl _) (M.sshape_refl2 M1 M2)).
case=>k1' k2' [[->->{m1 m2}]] MM; rewrite (unCA k1') (unCA k2'); vauto.
apply: A.val_write=>// j1' j2' AA; rewrite !unA.
eexists (j1' :+ k1', j2' :+ k2'); split=>//=.
case: a1.1 E1 H6 MM AA=>// a11 _; move/sym=>H6.
case: a2.1 E2 H7=>//= a21 _; move/sym=>H7 MM AA.
case/minkey_fnd: H6 AA=>w1 ->; case/minkey_fnd: H7=>w2 ->.
case=>[[/= -> Dj1']][/= -> Dj2']. 
by split; eexists _, _; do !split=>//; case/M.sshape_refl: MM.
Qed.
Next Obligation.
rewrite !gh; split.
- move=>a _ [f][_][h][->][j][k][->][A M] /=.
  rewrite -!unA unCA; case: ifP=>E; sauto; last first.
  - by apply: sval_do; eexists (stash f), k, _.
  apply: sbnd_do=>[|y1 m1] /=; first by eexists (stash f), k, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 M))=>[[-> _]][-> _].
  rewrite unCA unA.
  by apply: sval_do; eexists f, _, _; split=>//; eexists _, _.
move=>a1 a2 _ _ [[[[]]]][f1 f2][j1 j2][h1 h2][[[[]]]] /= ->->.
move=>H1 H2 H3 _ _ _ _; case: H1 (H1)=>/=.
case=>i1 [k1][->{j1}][[->{i1}]] D1 M1; case=>i2 [k2][->{j2}][[->{i2}]] D2 M2.
set j1 := updi2 _ _; set j2 := updi2 _ _.
have [A1 A2] : A.shape t.1 (hashtab f1) j1 /\ A.shape t.1 (hashtab f2) j2 by [].
have [S1 S2] : shape t f1 (j1 :+ k1) /\ shape t f2 (j2 :+ k2)
  by split; eexists _, _.
have minkey_stash_some : forall z k (f : {finMap K -> V}) h,
  minkey (stash f) (hash z) = Some k ->
  minkey f (hash z) = Some z ->
  (if fnd k (stash f) is Some v then 
    preshape t (A.updf (hash z) (Some (k, v)) (hashtab f)) 
               (rem k (stash f))
  else Pred0) h ->
  shape t (rem z f) h.
- move=>z k f h O; case/minkey_fnd: (O)=>w O' H'; rewrite O'.
  case=>r1 [t1][->][[->]] Dr1 Mt1. 
  eexists _, _; do !split=>//; last first.
  - by rewrite stash_rem H' irr eq_refl O.
  congr (updi2 _ (fgraph _)); apply/ffunP=>x.
  rewrite /A.updf hashtab_rem ffunE /=.
  by case: eqP=>// ->; rewrite H' eq_refl O O'.
have minkey_stash_none : forall z (f : {finMap K -> V}) h,
  minkey (stash f) (hash z) = None ->
  minkey f (hash z) = Some z ->
  preshape t (A.updf (hash z) None (hashtab f)) (stash f) h ->
  shape t (rem z f) h.
- move=>z f h O; move/minkey_fnd_none: (O); move/(_ _ (erefl _))=>O' H'.
  case=>r1 [t1][->][[->]] Dr1 Mt1 /=.
  eexists _, _; do !split=>//; last first.
  - by rewrite stash_rem H' irr eq_refl O.
  congr (updi2 _ (fgraph _)); apply/ffunP=>x.
  rewrite /A.updf hashtab_rem ffunE /=.
  by case: eqP=>// ->; rewrite H' O eq_refl.
move=>H1; case: ifP=>E1 /=; case: ifP=>E2; vauto.
(* case 1 *) 
- rewrite -!unA -(unCA k1) -(unCA k2).
  apply: bnd_do=>[|y1 y2 m1 m2] /=.
  - by split; [exists (stash f1) | exists (stash f2)]; eexists _, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl2 M1 M2))=>[].
  case=>[->->{m1 m2}][->->{y1 y2}]; rewrite (unCA k1) (unCA k2) !unA; vauto.
  apply: val_do=>[|_ _ m1 m2] /=.
  - by split; [exists f1 | exists f2]; eexists _, _.
  case/(_ _ (_, _) (_, _) (erefl _) H1 H2 H3 E1 E2 (erefl _) (erefl _)).
  case=>k1' k2' [[->->{m1 m2}]] /= [T1 T2] _ _; eexists (_, _); split=>//.
  case: {1 3 4}a1.1 (erefl a1.1) E1=>//= [[a11 a12]] Y1 /= E1.
  case: {1 3 4}a2.1 (erefl a2.1) E2=>//= [[a21 a22]] Y2 /= E2. 
  rewrite -Y1 -Y2 in H2 H3.
  rewrite {a11 E1}(eqP E1) {a21 E2}(eqP E2) in Y1 Y2 H2 H3.
  case/hashtab_some_eqv: H2=>H2 H2'.
  case/hashtab_some_eqv: H3=>H3 H3'; move: T1 T2.
  case O1: (min _)=>[b1|] O1'; case O2: (min _)=>[b2|] O2'.
  - split; first by [apply: minkey_stash_some O1 _ _];
    by apply: minkey_stash_some O2 _ _.
  - split; first by [apply: minkey_stash_some O1 _ _];
    by apply: minkey_stash_none O2 _ _.
  - split; first by [apply: minkey_stash_none O1 _ _];
    by apply: minkey_stash_some O2 _ _.
  split; first by [apply: minkey_stash_none O1 _ _];
  by apply: minkey_stash_none O2 _ _.
(* case 2 *)
- rewrite -!unA -(unCA k1) -(unCA k2).
  apply: bnd_doL=>[|y1 m1] /=; first by eexists (stash f1), _, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 M1))=>[].
  case=>->{m1} _ [->{y1} _]; rewrite (unCA k1) (unCA k2) !unA; vauto.
  apply: val_doL=>[|y1 m1] /=; first by eexists f1, _, _.
  move/(_ (_, _) (_, _) (_, _) (erefl _) (conj S1 S1) H2 H2 E1 E1).
  case/(_ (erefl _) (erefl _))=>[[k1' k2']][[-> _]][/= T1 _].
  rewrite -unA unCA.
  apply: val_doV=>[|y3 m3] /=; first by eexists (stash f2), _, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 M2))=>[].
  case=>[k1'' k2''][[-> _]] /= M1' _ _.
  rewrite unA; eexists (k1', k1'' :+ j2); split=>//=.
  case: {1 3 4}a1.1 (erefl a1.1) E1=>//= [[a11 a12]] Y1 /= E1.
  rewrite -Y1 in H2; rewrite {a11 E1}(eqP E1) in Y1 H2.
  case/hashtab_some_eqv: H2=>H2 H2'.
  case: {1 3 4}a2.1 (erefl a2.1) E2=>/= [[a21 a22]|] Y2 /= E2;
  rewrite -Y2 in H3; move: T1. 
  - case/hashtab_some_eqv: H3=>H3 H3'.
    case O1: (min _)=>[b1|] O1'.
    - split; first by apply: minkey_stash_some O1 _ _.
      rewrite unC; eexists _, _; do !split=>//;
      by case/M.sshape_refl: M1'.
    split; first by apply: minkey_stash_none O1 _ _.
    rewrite unC; eexists _, _; do !split=>//.
    by case/M.sshape_refl: M1'.
  move/hashtab_none_eqv: H3; move/(_ _ (erefl _))=>H3'.
  case O1: (min _)=>[b1|] O1'.
  - split; first by apply: minkey_stash_some O1 _ _.
    rewrite unC; eexists _, _; do !split=>//;
    by case/M.sshape_refl: M1'.
  split; first by apply: minkey_stash_none O1 _ _.
  rewrite unC; eexists _, _; do !split=>//.
  by case/M.sshape_refl: M1'.    
(* case 3 *) 
- rewrite -!unA -(unCA k1) -(unCA k2); apply/vrfC.
  apply: bnd_doL=>[|y1 m1] /=; first by eexists (stash f2), _, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 M2))=>[].
  case=>->{m1} _ [->{y1} _]; rewrite (unCA k1) (unCA k2) !unA; vauto.
  apply: val_doL=>[|y1 m1] /=; first by eexists f2, _, _.
  move/(_ (_, _) (_, _) (_, _) (erefl _) (conj S2 S2) H3 H3 E2 E2).
  case/(_ (erefl _) (erefl _))=>[[k1' k2']][[-> _]][/= T2 _].
  rewrite -unA unCA.
  apply: val_doV=>[|y3 m3] /=; first by eexists (stash f1), _, _.
  case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 M1))=>[].
  case=>[k1'' k2''][[-> _]] /= M2' _ _.
  rewrite unA; eexists (k1'' :+ j1, k1'); split=>//=.
  case: {1 3 4}a2.1 (erefl a2.1) E2=>//= [[a21 a22]] Y2 /= E2.
  rewrite -Y2 in H3; rewrite {a21 E2}(eqP E2) in Y2 H3.
  case/hashtab_some_eqv: H3=>H3 H3'.
  case: {1 3 4}a1.1 (erefl a1.1) E1=>/= [[a11 a12]|] Y1 /= E1;
  rewrite -Y1 in H2; move: T2. 
  - case/hashtab_some_eqv: H2=>H2 H2'.
    case O2: (min _)=>[b2|] O2'.
    - split; last by apply: minkey_stash_some O2 _ _. 
      rewrite unC; eexists _, _; do !split=>//.
      by case/M.sshape_refl: M2'.
    split; last by apply: minkey_stash_none O2 _ _.
    rewrite unC; eexists _, _; do !split=>//.
    by case/M.sshape_refl: M2'. 
  move/hashtab_none_eqv: H2; move/(_ _ (erefl _))=>H2'.
  case O2: (min _)=>[b2|] O2'.
  - split; last by apply: minkey_stash_some O2 _ _.
    rewrite unC; eexists _, _; do !split=>//;
    by case/M.sshape_refl: M2'.
  split; last by apply: minkey_stash_none O2 _ _.
  rewrite unC; eexists _, _; do !split=>//.
  by case/M.sshape_refl: M2'.
(* case 4 *)
rewrite -!unA (unCA j1) (unCA j2).
apply: val_do=>[|y1 y2 m1 m2] /=.
- by split; [exists (stash f1) | exists (stash f2)]; eexists _, _. 
case/( _ _ (_, _) (_, _) (erefl _) (M.sshape_refl2 M1 M2)). 
case=>k1' k2' [[->->]] /= MM _ _; rewrite -(unCA j1) -(unCA j2) !unA.
eexists (j1 :+ k1', j2 :+ k2'); split=>//=.
by split; eexists _, _; do !split=>//; case/M.sshape_refl: MM.
Qed.
Next Obligation.
rewrite !gh; split=>/=.
- move=>a i [f][_][h][->][j][k][->][A M]; sauto; rewrite -!unA.
  apply: A.sbnd_read A=>v A; rewrite unA.
  by apply: sval_do; eexists f, _, _; split=>//; eexists _, _.
move=>a1 a2 _ _ [[[[]]]][f1 f2][i1 i2][h1 h2][[[]]] /= ->->.
move=>H1 _ _ _ _; case: H1 (H1)=>/=.
case=>_ [k1][->][[->]] D1 M1 [_][k2][->][[->]] D2 M2. 
set j1 := updi2 _ _. set j2 := updi2 _ _.
move=>H1; vauto.
have AA : A.sshape (t.1, t.1) (hashtab f1, hashtab f2) (j1, j2) by [].
rewrite -!unA; apply: A.bnd_read AA _; rewrite !unA; vauto.
apply: val_do=>[|y1 y2 m1 m2] /=.
- split=>_; [exists f1 | exists f2]; 
  by eexists _, _; split=>//; eexists _, _.
case/(_ _ (_, _) (_, _) (erefl _) H1 (erefl _) (erefl _))=>[[jk1 jk2]][/=].
case=>->->{m1 m2}. 
suff shape_rem: forall k (f : {finMap K -> V}) t h, 
  preshape t (hashtab f) (rem k (stash f)) h -> 
  isSome_pred ((hashtab f) (hash k)) &&
  ((getSome0 (s, s0) ((hashtab f) (hash k))).1 == k) = false ->
  shape t (rem k f) h.
- case: ifP=>E1; case: ifP=>E2; case=>T1 T2 _ _;
  by exists (jk1, jk2); do !split=>//; apply: shape_rem.
move=>k f q h.
case=>_ [j][->][[->]] D M H.
eexists _, j; do !split=>//.
- congr (updi2 _ (fgraph _)); apply/ffunP=>z; rewrite hashtab_rem.
  case: eqP=>// ->.
  case O: (minkey _ _)=>[a|]; last first.
  - move/minkey_fnd_none: (O); move/(_ _ (erefl _))=>O'.
    by rewrite /hashtab ffunE /= O.
  case/minkey_fnd: (O)=>v O'.
  have E: hashtab f (hash k) = Some (a, v).
  - by rewrite /hashtab ffunE /= O O'.
  by rewrite E /= in H; rewrite eq_sym H.
rewrite stash_rem.
case O: (minkey _ _)=>[a|]; last first.
- suff S: k \notin supp (stash f).
  - by rewrite (rem_supp S) in M.
  move/minkey_fnd_none: O; move/(_ _ (erefl _)).  
  by case: suppP=>// v; move/stash_fnd=>->.
case/minkey_fnd: (O)=>v O'.
have E: hashtab f (hash k) = Some (a, v).
- by rewrite /hashtab ffunE /= O O'.
rewrite E /= in H; rewrite eq_sym in H *.
case: totalP H=>// H _.
suff S: k \notin supp (stash f).
- by rewrite (rem_supp S) in M.
move/minkey_lowerb: O; move/(_ k).
rewrite /samebkt mem_filter /= eq_refl /=.
case: suppP=>//; last by case: suppP=>// w; move/stash_fnd=>->.
move=>w _; case/(_ (erefl _))=>F; last by case: totalP H F.
by rewrite F irr in H.
Qed.






Program Definition new : STsec [:::] hashmap
  (fun i => True,
   fun yy ii mm =>
     exists jj, mm = jj :++ ii /\
       sshape (yy.1, yy.2) (nil, nil) jj /\
       (ii.1 =~ ii.2 -> yy.1 = yy.2 /\ jj.1 = jj.2)) :=
  Do (A.new [finType of 'I_n] None @0 tt;;
      M.new m @ (fun t1 => tt);;
      ret @ (fun t2 t1 => [(t1, t2)])).
Next Obligation.
split=> [a i _ D|a1 a2 i1 i2 _ _ D1 D2]; first by apply: sbnd_do=>//.
vauto; apply: A.bnd_new=> j1 j2 a3 a4; vauto; apply: M.bnd_new=> j3 j4 a5 a6.
vauto=> A1 A2 [B1 B2] [C1 C2] _.
exists (j3 :+ j1, j4 :+ j2); split; first by rewrite 2!unA /plus2 /=.
split=>//=. split=>//=. exists j1; exists j3; split=>//; first by rewrite unC.
case: C1=> [/= [-> ?] [-> ?]]; move: (M.sshape_refl B1)=> [? ?].
by rewrite hashtab_nil stash_nil.
exists j2; exists j4; split=>//; first by rewrite unC.
case: C1=> [/= [-> ?] [-> ?]]; move: (M.sshape_refl B1)=> [? ?].
by rewrite hashtab_nil stash_nil.
move=>H; case: (C2 H)=> [E1 E2]; move: E1 E2 B2 A1 A2=> -> ->; move=> B2 A1 A2.
case: B2; [ | by move=>-> -> ]. apply: lowUn=>//; first by case: (defUnr A1).
by case: (defUnr A2).
Qed.

Definition lookupS1 (kv0 : K * V) t 
  : spec [::: option (K * V); K] (option V) :=
  (fun o k => pre_kvmap t,
   fun oo kk yy ii mm =>
     forall ff jj hh, 
       ii = jj :++ hh -> sshape (t, t) ff jj ->
       hashtab ff.1 (hash kk.1) = oo.1 ->
       hashtab ff.2 (hash kk.2) = oo.2 ->
       mm = ii /\
       (yy.1 = if isNone oo.1 then None else fnd kk.1 ff.1) /\
       (yy.2 = if isNone oo.2 then None else fnd kk.2 ff.2)).

Definition lookupS2 (kv0 : K * V) t 
  : spec [::: option (K * V); K] (option V) :=
  (fun o k => pre_kvmap t,
   fun oo kk yy ii mm =>
     forall ff jj hh, 
       ii = jj :++ hh -> sshape (t, t) ff jj ->
       hashtab ff.1 (hash kk.1) = oo.1 ->
       hashtab ff.2 (hash kk.2) = oo.2 ->
       isSome oo.1 -> isSome oo.2 ->
       mm = ii /\
       (yy.1 = if (getSome0 kv0 oo.1).1 == kk.1 
               then Some (getSome0 kv0 oo.1).2 
               else fnd kk.1 (stash ff.1)) /\
       (yy.2 = if (getSome0 kv0 oo.2).1 == kk.2 
               then Some (getSome0 kv0 oo.2).2 
               else fnd kk.2 (stash ff.2))).

Program Definition lookup (kv0 : K * V) (t : hashmap) :
  STsec [::: K] (option V)
  (fun k => pre_kvmap t,
   fun kk yy ii mm =>
     forall ff jj hh, ii = jj :++ hh -> sshape (t, t) ff jj ->
       mm = ii /\ yy.1 = fnd kk.1 ff.1 /\ yy.2 = fnd kk.2 ff.2) :=
  Do (A.read (T1 t) @ (fun k => [hash k]);;
      (* if bucket not in use, key not present *)
      Ifbool (fun o k => isNone o) then ret @ (fun o k => [None])
      (* else, check whether keys match *)
      else Ifbool (fun o k => (getSome0 kv0 o).1 == k) 
           (* if they do, just return val *)
           then ret @ (fun o k => [Some ((getSome0 kv0 o).2)])
           (* otherwise, go to stash *)
           else M.lookup (T2 t) @ (fun o k => [k])
           with [::: option (K * V); K], option V, (lookupS2 kv0 t)
      with [::: option (K * V); K], option V, (lookupS1 kv0 t)).
Next Obligation.
rewrite !gh; split=> [a i [f][j][h][A1 [j1][h1][A2][?]?] ? |].
case: ifP=>?; apply: sval_do=>//= _; rewrite A2 -unA unC -unA in A1; rewrite A1.
by exists (stash f); exists h1; exists (h :+ j1).
move=> a1 a2 i1 i2[[[[]]]][f f'][j j'][h h'][[[[[[/= -> ->]]]]]].
move=>[[j1]][h1][/= -> [[-> _] A]][j2][h2][/= -> [[-> _] B]] C D E F _ _ G H.
case: ifP=>/= Q; case: ifP=>/= R; first by vauto.
by vauto; apply: val_retL=>//; rewrite -unA unC -unA; apply: M.val_lookupV=>//.
- vauto. apply: val_doL=>//= [S|]; set u := updi2 _ _. 
by exists (stash f); exists h1; exists (u :+ h); split=>//; rewrite -unAC unC.
move=> y1 ? T; apply: val_retV=>// _ _; split=>//. 
case: (T (stash f, stash f) (h1, h1) (u :+ h, u :+ h))=>//=.
by rewrite /plus2 /= -unAC unC. by apply: M.sshape_refl1=>//.
by move=>[[/=-> _]][[/=-> _]].
case: (T (stash f, stash f) (h1, h1) (u :+ h, u :+ h))=>//=.
by rewrite /plus2 /= -unAC unC. by apply: M.sshape_refl1=>//.
by move=>[[/=-> _]][[/=-> _]].
- vauto; apply: val_do=>//=. set u1 := updi2 _ _; set u2 := updi2 _ _; split=>// _. 
by exists (stash f); exists h1; exists (u1 :+ h); split=>//; rewrite -unAC unC.
by exists (stash f'); exists h2; exists (u2 :+ h'); split=>//; rewrite -unAC unC.
set u1 := updi2 _ _; set u2 := updi2 _ _; move=> y1 y2 m1 m2 S _ _.
case: (S (stash f, stash  f') (h1, h2) (u1 :+ h, u2 :+ h'))=>//.
by rewrite /plus2 /= -unAC unC -(unAC u2) (unC (u2 :+ h')).
by apply: M.sshape_refl2.
Qed.
Next Obligation.
split=> [ a i [f][j][h][A1 [j1][h1][A2][?]?] ? |].
case: ifP=>?; apply: sval_do=>//= _; move: A2 A1=> -> ->.
exists f; exists (j1 :+ h1); exists h; split; first by rewrite -unA.
by exists j1; exists h1.
move=> a1 a2 i1 i2 A1 A2 D1 D2; case: ifP=>/= E; case: ifP=>/= F.
- vauto=> D3 D4 _ //= ff jj hh C G. by rewrite E. 
- vauto; lauto=>//; apply: val_doV=>//= y m0 B C _ _ ff jj hh D G H I.
case: (B (ff.2, ff.2) (jj.2, jj.2) (hh.2, hh.2))=>//.
by case: jj D G=> jj1 jj2; case=> _ -> _; rewrite /plus2 /=. by case: G.
by move: F; case: a2.1. by move: F; case: a2.1.
move=>/= [[/= -> _]] [L _]; split=>//. split=>//. by move: E; case: a1.1=>//. 
rewrite fnd_eqv // I L=>//=. 
by move: F; case: a2.1=>// [[k v]] _ /=; rewrite eq_sym.
- vauto; apply: val_doL=>//= y1 m1 B; apply: val_retV=>// _ _ _.
move=> ff jj hh C D /= G H; case: (B (ff.1, ff.1) (jj.1, jj.1) (hh.1, hh.1))=>//. 
by case: C=> -> _; rewrite /plus2 /=. by case: D.
by move: E; case: a1.1. by move: E; case: a1.1.
move=>/= [[/= -> _]] [L _]; split=>//. split=>//. 
rewrite fnd_eqv // G L=>//=; move: E; case: a1.1=>// [[k v]] /= _.
by rewrite eq_sym.
- apply: val_do=>//= y1 y2 m1 m2 C _ _ _ ff jj hh D G H I.
case: (C _ _ _ D G H I); first by move: E; case: a1.1. by move: F; case: a2.1.
move=> [[/= -> ->]] [L M]; split=>//. split=>//; rewrite fnd_eqv //.
by move: L H E=> -> ->; rewrite eq_sym; case: a1.1=>// [[k v]] _ /=.
by move: M I F=> -> ->; rewrite eq_sym; case: a2.1=>// [[k v]] _ /=.
Qed.
Next Obligation.
rewrite !gh; split=> [ a i [f][j][h][A1 [j1][h1][A2][[B1 B2]]C] |].
sauto; apply: sbnd_do=>//=; move: B1 A2 A1=> -> -> -> D.
set u := updi2 _ _; exists u; exists (h1 :+ h); exists (hashtab f); split=>//.
by rewrite -unA. by split=>//; apply: (defUnl (defUnl D)).
move=> m1 [[/= -> _] A]; sauto=> E; apply: sval_do=>//= _.
set u := updi2 _ _; exists f; exists (u :+ h1); exists h.
split=>//. exists u; exists h1; split=>//. split=>//. 
by split; [rewrite /u | apply: (defUnl (defUnl E))].
move=> a1 a2 i1 i2 [[[[]]]][f1 f2][j1 j2][h1 h2][[/= -> ->]] A _ _ B C. vauto.
case: A=>/= [[x1]][y1][-> [[D E] F]] [x2][y2][-> [[G H] I]].
rewrite -2!unA.
apply: (@A.bnd_read _ _ _ _ _ _ _ _ _ _ _ _ (hashtab f1) (hashtab f2))=>//.
vauto; apply val_do=>//=. split=>// J; rewrite unA. 
by exists f1; exists (x1 :+ y1); exists h1; split=>//; exists x1; exists y1.
by exists f2; exists (x2 :+ y2); exists h2; split=>//; exists x2; exists y2.
move=> y3 y4 m1 m2 J _ _; case: (J (f1, f2) (x1 :+ y1, x2 :+ y2) (h1, h2))=>//. 
by rewrite 2!unA /plus2 /=. 
split; first by exists x1; exists y1. by exists x2; exists y2=>//.
move=> [[/= -> ->]] [-> ->]; split=>//. split; rewrite fnd_eqv //.
by set u := hashtab f1 _; case: u=>// [[k v]].
by set u := hashtab f2 _; case: u=>// [[k v]].
Qed.

(* the intermediate specs for the conditionals *)

Definition insertS1 (kv0 : K * V) (t : hashmap) : 
             spec [::: option (K * V); K; V] unit := 
  (fun o k v => pre_kvmap t,
   fun oo kk vv yy ii mm =>
     forall ff jj hh, 
       ii = jj :++ hh -> sshape (t, t) ff jj ->
       hashtab ff.1 (hash kk.1) = oo.1 -> 
       hashtab ff.2 (hash kk.2) = oo.2 ->                   
       isSome oo.1 -> isSome oo.2 ->
     exists jj', mm = jj' :++ hh /\
       (if ord kk.1 (getSome0 kv0 oo.1).1 then 
          preshape t
          (A.updf (hash kk.1) (Some (kk.1, vv.1)) (hashtab ff.1))
             (ins (getSome0 kv0 oo.1).1 (getSome0 kv0 oo.1).2 
             (stash ff.1))
        else 
          preshape t
          (hashtab ff.1)
          (ins kk.1 vv.1 (stash ff.1))) jj'.1 /\
          (if ord kk.2 (getSome0 kv0 oo.2).1 then 
             preshape t
             (A.updf (hash kk.2) (Some (kk.2, vv.2)) (hashtab ff.2))
             (ins (getSome0 kv0 oo.2).1 (getSome0 kv0 oo.2).2 
             (stash ff.2))
           else 
             preshape t
             (hashtab ff.2)
             (ins kk.2 vv.2 (stash ff.2))) jj'.2).

Definition insertS2 (kv0 : K * V) (t : hashmap) : 
             spec [::: option (K * V); K; V] unit :=
  (fun o k v => pre_kvmap t,
   fun oo kk vv yy ii mm =>
     forall ff jj hh, 
       ii = jj :++ hh -> sshape (t, t) ff jj ->
       hashtab ff.1 (hash kk.1) = oo.1 -> 
       hashtab ff.2 (hash kk.2) = oo.2 -> 
     exists jj', mm = jj' :++ hh /\
       (if isNone oo.1 || isSome_pred oo.1 && (kk.1 == (getSome0 kv0 oo.1).1) then 
          preshape t 
          (A.updf (hash kk.1) (Some (kk.1, vv.1)) (hashtab ff.1))
          (stash ff.1)
        else shape t (ins kk.1 vv.1 ff.1)) jj'.1 /\ 
          (if isNone oo.2 || isSome_pred oo.2 && (kk.2 == (getSome0 kv0 oo.2).1) then
             preshape t 
             (A.updf (hash kk.2) (Some (kk.2, vv.2)) (hashtab ff.2))
             (stash ff.2)
           else shape t (ins kk.2 vv.2 ff.2)) jj'.2).

Program Definition insert (kv0 : K * V) (t : hashmap) :
  STsec [::: K; V] unit
  (fun k v => pre_kvmap t,
   fun kk vv yy ii mm =>
     forall ff jj hh, 
       ii = jj :++ hh -> sshape (t, t) ff jj ->
     exists jj', mm = jj' :++ hh /\
       sshape (t, t) (ins kk.1 vv.1 ff.1, ins kk.2 vv.2 ff.2) jj') :=
  Do (A.read (T1 t) @ (fun k v => [hash k]);;
      Ifbool (fun o k v => isNone o || isSome_pred o && (k == (getSome0 kv0 o).1)) then
        A.write (T1 t) @ (fun o k v => [hash k, Some (k, v)])
      else Ifbool (fun o k v => ord k (getSome0 kv0 o).1) then
             M.insert (T2 t) @ (fun o k v => 
               [(getSome0 kv0 o).1, (getSome0 kv0 o).2]);;
             A.write (T1 t) @ (fun _ o k v => [hash k, Some (k, v)])
           else (* ord k' k *) M.insert (T2 t) @ (fun o k v => [k, v])
           with [::: option (K * V); K; V], unit, (insertS1 kv0 t)
      with [::: option (K * V); K; V], unit, (insertS2 kv0 t)).
Next Obligation. 
rewrite !gh; split=> [a i [f][j][h][A1 [j1][h1][A2][?]?] |].
case: ifP=>?; sauto. apply: sbnd_do=>//= B; rewrite A1 A2.
by rewrite -unA unC -unA; exists (stash f); exists h1; exists (h :+ j1).
move=> m0 C; sauto=> D. 
case: (C (stash f, stash f) (h1, h1) (j1 :+ h, j1 :+ h))=>//.
by rewrite /plus2 /= unA (unC h1). by apply: M.sshape_refl2.
move=> [m1 m2][[/= Q R]] /= E; move: D; rewrite R unC -unA; apply: sval_do=>//=.
by move=> _; exists j1; exists (h :+ m2); exists (hashtab f). 
rewrite A1 A2 (unC j1) -unA=> A; apply: sval_do=>//= _.
by exists (stash f); exists h1; exists (j1 :+ h).
move=> a1 a2 i1 i2[[[[]]]][f f'][j j'][h h'][[[[[[-> ->]]]]]].
move=>[[j1]][h1][/=-> [[-> _] A]][j2][h2][/=-> [[-> _] B]] C D E F _ _ G H.
case: ifP=>/= Q; case: ifP=>/= R; vauto. 
(* case 1 *)
- rewrite -unA unC -unA -unA (unC (updi2 _ _)) -unA. 
  have S: M.sshape (t.2, t.2) (stash f, stash f') (h1, h2) by apply: M.sshape_refl2. 
  rewrite 2!(unC (updi2 _ _)) -2!unA in G H. 
  rewrite unA unAC -unA in G; rewrite unA unAC -unA in H.
  apply: (M.bnd_insert G H S)=>// h1' h2'=> I J; case/M.sshape_refl=>/= T U.
  vauto; rewrite unA unC (unA h2') (unC (h2' :+ h')).
  set u1 := updi2 _ _; set u2 := updi2 _ _.
  have W: A.sshape (t.1, t.1) (hashtab f, hashtab f') (u1, u2). 
  - by rewrite !unA in I J; move: (defUnr I) (defUnr J); rewrite /u1 /u2.
  apply: (A.val_write W)=> j1' j2' [/= [-> _] [-> _]].
  set u1' := updi2 _ _; set u2' := updi2 _ _; exists (u1' :+ h1', u2' :+ h2').
  rewrite /plus2 /= 2!unA; split=>//.
  split=>//=.
  - exists u1'; exists h1'; do 3!split=>//. 
    case: W=>/=[[_ H1]]_; rewrite /u1 /u1' 2!updi2D in H1 *.
    apply/orP; case:(orP H1)=>H2; first by left.
    by right; rewrite !fgraph_map /= !size_map in H2 *.
  exists u2'; exists h2'; do 3!split=>//.
  case: W=>/=[[_ H1]]_; rewrite /u1 /u1' 2!updi2D in H1 |- *.
  apply/orP; case:(orP H1)=>H2; first by left.
  by right; rewrite !fgraph_map /= !size_map in H2 *.
(* case 2 *)
- rewrite -unA unC -unA -unA (unC (updi2 _ _)) -unA. 
  have S: M.sshape (t.2, t.2) (stash f, stash f') (h1, h2) by apply: M.sshape_refl2. 
  rewrite 2!(unC (updi2 _ _)) -2!unA in G H. 
  rewrite unA unAC -unA in G; rewrite unA unAC -unA in H.
  apply:(M.bnd_insertL S G H)=>//w1 H1; case/M.sshape_refl=>/=H2 H3.
  vauto=>/=; rewrite unA unC.
  apply: val_doL=>/=.
  - move=>D'; eexists _, _, (hashtab f).
    by do !split=>//=; apply: defUnl D'.
  have W: A.sshape (t.1, t.1) (hashtab f, hashtab f)
                   (updi2 t.1 (fgraph (hashtab f)), updi2 t.1 (fgraph (hashtab f))).
  - by do !split=>//; move/defUnr/defUnr: G.
  move=>[] m1 /=; case/(_ (_, _) (_, _) (hashtab f, hashtab f) (erefl _) W).
  case=>j1' j2' [[-> E1]] /= Ash1.
  apply: val_doV=>/=; first by move=>D'; exists (stash f'), h2; eexists _. 
  move=>_ m2; case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 H3)).
  case=>k1 k2 [[-> E2]] /= Msh1 Dm1 Dm2 /=; rewrite unA (unC h') (unCA k1) unA.
  eexists (j1' :+ w1, _ :+ k1); split=>//=.
  split; first by eexists _, _; split=>//; case: Ash1.
  eexists _, _; do !split=>//; first by move/defUnr/defUnr: Dm1.
  by case/M.sshape_refl: Msh1.
(* case 3 *)
- apply/vrfC; rewrite -unA unC -unA -unA (unC (updi2 _ _)) -unA. 
  have S: M.sshape (t.2, t.2) (stash f', stash f) (h2, h1) by apply: M.sshape_refl2. 
  rewrite 2!(unC (updi2 _ _)) -2!unA in G H. 
  rewrite unA unAC -unA in G; rewrite unA unAC -unA in H.
  apply:(M.bnd_insertL S H G)=>//w1 H1; case/M.sshape_refl=>/=H2 H3.
  vauto=>/=; rewrite unA unC.
  apply: val_doL=>/=.
  - move=>D'; eexists _, _, (hashtab f').
    by do !split=>//=; apply: defUnl D'.
  have W: A.sshape (t.1, t.1) (hashtab f', hashtab f')
                   (updi2 t.1 (fgraph (hashtab f')), updi2 t.1 (fgraph (hashtab f'))).
  - by do !split=>//; move/defUnr/defUnr: H.
  move=>[] m1 /=; case/(_ (_, _) (_, _) (hashtab f', hashtab f') (erefl _) W).
  case=>j1' j2' [[-> E1]] /= Ash1.
  apply: val_doV=>/=; first by move=>D'; exists (stash f), h1; eexists _. 
  move=>_ m2; case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl1 H3)).
  case=>k1 k2 [[-> E2]] /= Msh1 Dm1 Dm2 /=.
  rewrite (unC h) (unCA k1) !unA.
  eexists (_ :+ k1, j1' :+ w1); split=>//=.
  split; last by eexists _, _; split=>//; case: Ash1.
  eexists _, _; do !split=>//; first by move/defUnr/defUnr: Dm1.
  by case/M.sshape_refl: Msh1.
(* case 4 *)
rewrite -(unC h1) -(unC h2) -!unA in G H *.
apply: val_do=>/=.
- by split=>_; [exists (stash f) | exists (stash f')]; eexists _, _.
move=>_ _ m1 m2; case/(_ _ (_, _) (_, _) (erefl _) (M.sshape_refl2 A B)).
case=>j1' j2' [[->->]] /= Msh D1' D2'.
rewrite !unA. eexists (j1' :+ _, j2' :+ _); split=>//=.
case/M.sshape_refl: Msh=>/= M1 M2; split; rewrite unC.
- by eexists _, j1'; do !split=>//; move/defUnr/defUnl: D1'.
by eexists _, j2'; do !split=>//; move/defUnr/defUnl: D2'.
Qed.
Next Obligation.
rewrite !gh; split=> [a _ [f][j][h][->][j1][h1][{j}->][A B] D /=|]. 
- case: ifP=>T.
  - move: D; sauto; apply: sval_do=>D /=.
    by exists j1, (h1 :+ h), (hashtab f); rewrite unA.
  apply: sval_do=>//= D'; exists f, (j1 :+ h1), h.
  by split=>//; exists j1, h1.
move=>a1 a2 _ _ [[[[]]]][f f'][j j'][h h'] /= [[[[->->]]]][/=].
case=>j1 [h1][->{j }][[->{j1}]] _ M1.
case=>j2 [h2][->{j'}][[->{j2}]] _ M2 A1 A2 _ _ D1 D2.
case: ifP=>/= Q; case: ifP=>/= R.
(* case 1 *)
- vauto; rewrite -!unA in D1 D2 *. 
  set u := updi2 _ _ in D1 *; set u' := updi2 _ _ in D2 *.
  have W: A.sshape (t.1, t.1) (hashtab f, hashtab f') (u, u').
  - by do !split=>//=; [move/defUnl: D1 | move/defUnl: D2].
  apply: (A.val_write W)=>j1' j2' [/= AA1 AA2].
  rewrite !unA; eexists (j1' :+ h1, j2' :+ h2); split=>//.
  by split; eexists _, _.
(* case 2 *)
- have [a R']: exists a, a2.1 = Some a by case: a2.1 R=>// a; exists a. 
  set u := updi2 _ _ in D1 *; set u' := updi2 _ _ in D2 *.
  move/defUnl/defUnl: D1=>D1; move/defUnl/defUnl: D2=>D2.
  have W: A.shape t.1 (hashtab f) u by [].
  have WW: A.sshape (t.1, t.1) (hashtab f, hashtab f) (u, u) by [].
  rewrite -unA.
  apply: val_doL=>/=.
  - by exists (updi2 t.1 (fgraph (hashtab f))), (h1 :+ h), (hashtab f).
  move=>[] m1; case/(_ (_, _) (_, _) _ (erefl _) WW).
  case=>j1' j2' [[-> E1]] /= T1.
  have W1: u' :+ h2 \In shape t f' by exists u', h2.
  have WW1: sshape (t, t) (f', f') (u' :+ h2, u' :+ h2) by [].  
  apply: val_doV=>/=; first by exists f', (u' :+ h2), h'.
  move=>_ m2. 
  move/(_ _ (_,_) (_,_) (erefl _) WW1 A2 A2); rewrite R'; case/(_ I I).
  case=>k1 k2 [[-> E2]][/= H1 H2] D1' D2'.
  rewrite !unA; exists (j1' :+ h1, k1); split=>//=.
  split; first by eexists _, _; do 2!split=>//; case: T1.
  rewrite R' in A2. 
  case: a R' H1 H2 A2=>/= b1 b2 R' H1 _ A2.
  case/hashtab_some_eqv: (A2)=>F1 F2.
  case: ifP H1=>O.
  - case=>j [k][->][[->]] /= =>D M; eexists _, _.
    do !split=>//=; last first.
    - by rewrite stash_ins F2 F1; case: totalP O. 
    congr (updi2 _ (fgraph _)); apply/ffunP=>z.
    rewrite /A.updf !ffunE /= hashtab_ins. 
    by case: ifP=>E //; rewrite (eqP E) F2; case: totalP O.
  case=> j [k][->][[->]] /= =>D M; eexists _, _.
  do !split=>//; last first.
  - rewrite stash_ins F2 F1.
    case: totalP O=>//; move/eqP=>E3 _.
    by rewrite {b1}E3 in F1 F2 R' A2 *; rewrite R' /= eq_refl in R.
  congr (updi2 _ (fgraph _)); apply/ffunP=>z.
  rewrite hashtab_ins; case: ifP=>E //.
  rewrite (eqP E) /= !ffunE F2 F1.
  case: totalP O=>//=; move/eqP=>E3 _.
  by rewrite {b1}E3 in F1 F2 R' A2 *; rewrite R' /= eq_refl in R.
(* case 3 *)
- have [a Q']: exists a, a1.1 = Some a by case: a1.1 Q=>// a; exists a.
  set u := updi2 _ _ in D1 *; set u' := updi2 _ _ in D2 *.
  move/defUnl/defUnl: D1=>D1; move/defUnl/defUnl: D2=>D2.
  have W: A.shape t.1 (hashtab f') u' by [].
  have WW: A.sshape (t.1, t.1) (hashtab f', hashtab f') (u', u') by [].
  apply/vrfC; rewrite -unA.
  apply: val_doL=>/=.
  - by exists (updi2 t.1 (fgraph (hashtab f'))), (h2 :+ h'), (hashtab f').
  move=>[] m1; case/(_ (_, _) (_, _) _ (erefl _) WW).
  case=>j1' j2' [[-> E1]] /= T1.
  have W1: u :+ h1 \In shape t f by exists u, h1.
  have WW1: sshape (t, t) (f, f) (u :+ h1, u :+ h1) by [].  
  apply: val_doV=>/=; first by exists f, (u :+ h1), h.
  move=>_ m2. 
  move/(_ _ (_,_) (_,_) (erefl _) WW1 A1 A1); rewrite Q'; case/(_ I I).
  case=>k1 k2 [[-> E2]][/= H1 H2] D1' D2'.
  rewrite !unA; exists (k1, j1' :+ h2); split=>//=.
  split; last by eexists _, _; do 2!split=>//; case: T1.
  rewrite Q' in A1. 
  case: a Q' H1 H2 A1=>/= b1 b2 Q' H1 _ A1.
  case/hashtab_some_eqv: (A1)=>F1 F2.
  case: ifP H1=>O.
  - case=>j [k][->][[->]] /= =>D M; eexists _, _.
    do !split=>//=; last first.
    - by rewrite stash_ins F2 F1; case: totalP O. 
    congr (updi2 _ (fgraph _)); apply/ffunP=>z.
    rewrite /A.updf !ffunE /= hashtab_ins. 
    by case: ifP=>E //; rewrite (eqP E) F2; case: totalP O.
  case=> j [k][->][[->]] /= =>D M; eexists _, _.
  do !split=>//; last first.
  - rewrite stash_ins F2 F1.
    case: totalP O=>//; move/eqP=>E3 _.
    by rewrite {b1}E3 in F1 F2 Q' A1 *; rewrite Q' /= eq_refl in Q.
  congr (updi2 _ (fgraph _)); apply/ffunP=>z.
  rewrite hashtab_ins; case: ifP=>E //.
  rewrite (eqP E) /= !ffunE F2 F1.
  case: totalP O=>//=; move/eqP=>E3 _.
  by rewrite {b1}E3 in F1 F2 Q' A1 *; rewrite Q' /= eq_refl in Q.
(* case 4 *)
set u := updi2 _ _ in D1 *; set u' := updi2 _ _ in D2 *.
move/defUnl/defUnl: D1=>D1; move/defUnl/defUnl: D2=>D2.
have [W W'] : u :+ h1 \In shape t f /\ u' :+ h2 \In shape t f'.
- by split; eexists _, _.
have WW : sshape (t, t) (f, f') (u :+ h1, u' :+ h2) by [].
apply: val_do=>[|[][] m1 m2] /=.
- by split=>_; [exists f | exists f']; eexists _, _.
case: a1.1 A1 Q=>// [[a11 b11]] /= A1 Q1.
case: a2.1 A2 R=>//= [[a21 b21]] /= A2 Q2.
case/(_ _ (_,_) (_, _) (erefl _) WW A1 A2 I I)=>[[k1 k2]][].
case=>->-> [H1 H2] D1' D2'; exists (k1, k2); do !split=>//=.
- case/hashtab_some_eqv: (A1)=>F1 F2.
  case: ifP H1=>O /=. 
  - case=>j [k][->][[->]] /= => D M; eexists _, _.
    do !split=>//; last first.
    - by rewrite stash_ins F2 F1; case: totalP O.
    congr (updi2 _ (fgraph _)); apply/ffunP=>z.
    rewrite /A.updf !ffunE /= hashtab_ins. 
    by case: ifP=>E //; rewrite (eqP E) F2; case: totalP O.
  case=> j [k][->][[->]] /= =>D M; eexists _, _.
  do !split=>//; last first.
  - rewrite stash_ins F2 F1.
    by case: totalP O=>//; rewrite eq_sym Q1.
  congr (updi2 _ (fgraph _)); apply/ffunP=>z.
  rewrite hashtab_ins; case: ifP=>E //.
  rewrite (eqP E) /= !ffunE F2 F1.
  by case: totalP O=>//=; rewrite eq_sym Q1.
case/hashtab_some_eqv: (A2)=>F1 F2.
case: ifP H2=>O /=. 
- case=>j [k][->][[->]] /= => D M; eexists _, _.
  do !split=>//; last first.
  - by rewrite stash_ins F2 F1; case: totalP O.
  congr (updi2 _ (fgraph _)); apply/ffunP=>z.
  rewrite /A.updf !ffunE /= hashtab_ins. 
  by case: ifP=>E //; rewrite (eqP E) F2; case: totalP O.
case=> j [k][->][[->]] /= =>D M; eexists _, _.
do !split=>//; last first.
- rewrite stash_ins F2 F1.
  by case: totalP O=>//; rewrite eq_sym Q2.
congr (updi2 _ (fgraph _)); apply/ffunP=>z.
rewrite hashtab_ins; case: ifP=>E //.
rewrite (eqP E) /= !ffunE F2 F1.
by case: totalP O=>//=; rewrite eq_sym Q2.
Qed.
Next Obligation.
rewrite !gh; split.
- move=>a i [f][j][h][->][j1][k1][->][A M] /=.
  have AA : A.sshape (T1 t, T1 t) (hashtab f, hashtab f) (j1, j1) by [].
  sauto; rewrite -unA; apply: A.sbnd_read A=>y1 m1 /=; rewrite unA.
  by apply: sval_do; eexists f, _, _; split=>//; eexists _, _.
move=>a1 a2 _ _ [[[[]]]][f1 f2][i1 i2][h1 h2][[/= -> ->]] H.
case: H (H)=>/= [[j1]][k1][->{i1}][[->]] D1 M1 [j2][k2][->{i2}][[->]] D2 M2.
move=>H _ _ _ _.
set u1 := updi2 _ _ in D1 H *; set u2 := updi2 _ _ in D2 H *.
have AA : A.sshape (T1 t, T1 t) (hashtab f1, hashtab f2) (u1, u2) by [].
rewrite -!unA; vauto; apply: A.bnd_read AA _.
rewrite !unA. vauto. 
apply: val_do=>[|[][] m1 m2] /=.
- by split; [exists f1 | exists f2]; eexists _, _; split=>//; eexists _, _.
case/(_ _ (_, _) (_, _) (erefl _) H (erefl _) (erefl _)). 
case=>[g1 g2][[->->]][/= H1 H2] D1' D2'; exists (g1, g2); split=>//.
split=>/=.
- case: ifP H1=>/= E1 [j][k][->][[->]] D11 M1'; eexists _, _; split=>//.
  case/orP: E1=>/=; case E: (hashtab _ _)=>[[k' v']|] //=.
  - move=>_; do !split=>//; last by rewrite stash_ins hashtab_none_minkey.
    congr (updi2 _ (fgraph _)); apply/ffunP=>z.
    rewrite /A.updf !ffunE /= hashtab_ins.
    by case: eqP=>// ->{z}; rewrite hashtab_none_minkey.
  case/hashtab_some_eqv: E=>F1 F2 E.
  do !split=>//; last first.
  - by rewrite stash_ins F2 (eqP E) irr eq_refl.
  congr (updi2 _ (fgraph _)); apply/ffunP=>z.
  rewrite /A.updf !ffunE /= hashtab_ins.
  by case: eqP=>// ->{z}; rewrite F2 (eqP E) irr.
case: ifP H2=>/= E1 [j][k][->][[->]] D11 M1'; eexists _, _; split=>//.
case/orP: E1=>/=; case E: (hashtab _ _)=>[[k' v']|] //=.
- move=>_; do !split=>//; last by rewrite stash_ins hashtab_none_minkey.
  congr (updi2 _ (fgraph _)); apply/ffunP=>z.
  rewrite /A.updf !ffunE /= hashtab_ins.
  by case: eqP=>// ->{z}; rewrite hashtab_none_minkey.
case/hashtab_some_eqv: E=>F1 F2 E.
do !split=>//; last first.
- by rewrite stash_ins F2 (eqP E) irr eq_refl.
congr (updi2 _ (fgraph _)); apply/ffunP=>z.
rewrite /A.updf !ffunE /= hashtab_ins.
by case: eqP=>// ->{z}; rewrite F2 (eqP E) irr.
Qed.


Program Definition ret_minkey (t : hashmap) : 
  STsec [::: 'I_n] (option K)
  (fun ix => pre_kvmap t,
   fun iix yy ii mm => 
     forall ff jj hh, ii = jj :++ hh -> sshape (t, t) ff jj ->
       mm = ii /\ 
       yy.1 = min (filter (fun k => hash k == iix.1) (supp ff.1)) /\
       yy.2 = min (filter (fun k => hash k == iix.2) (supp ff.2))) :=
  Do (A.read (T1 t) @ (fun ix => [ix]);;
      ret @ (fun kv ix => [match kv return option K 
                           with Some (k, v) => Some k | _ => None end])).
Next Obligation.
rewrite !gh; split.
- move=>a i [f][j][h][/=->][j1][k1][->][H1 H2]; sauto; apply: sbnd_do=>//= _. 
 by exists j1, (k1 :+ h), (hashtab f); rewrite -unA; split.
move=>a1 a2 _ _ [[[[[]]]]]f1 f2[i1 i2][h1 h2][[->->]][/=].
case=>_ [k1][->][[->]] D1 M1 [_][k2][->][[->]] D2 M2 _ _ _ _; vauto.
set j1 := updi2 _ _; set j2 := updi2 _ _; rewrite -!unA.
have AA: A.sshape (T1 t, T1 t) (hashtab f1, hashtab f2) (j1, j2) by [].
apply: A.bnd_read AA _; rewrite !unA; vauto=>_ _; split=>//.
case H1: (hashtab _ _)=>[[b1 c1]|]; case H2: (hashtab _ _)=>[[b2 c2]|] /=.
- by case/hashtab_some_eqv: H1; case/hashtab_some_eqv: H2.
- by case/hashtab_some_eqv: H1; move/hashtab_none_minkey: H2.
- by move/hashtab_none_minkey: H1; case/hashtab_some_eqv: H2.
by move/hashtab_none_minkey: H1; move/hashtab_none_minkey: H2.
Qed.


Lemma bnd_new : 
  forall (B : Type) (e1 e2 : ST [::: hashmap] B) i1 i2 (r : d B -> d heap -> Prop),
    (forall j1 j2 t1 t2,
      verify2 (j1 :+ i1) (j2 :+ i2) (e1 @0 [t1]) (e2 @0 [t2])
      (fun y m =>
        (sshape (t1, t2) (nil, nil) (j1, j2) /\
          (i1 =~ i2 -> t1 = t2 /\ j1 = j2)) ->
        r y m)) ->
    verify2 i1 i2 (new @0 tt;; e1) (new @0 tt;; e2) r.
Proof.
move=>B e1 e2 i1 i2 r T; apply: bnd_do=>// y1 y2 m1 m2 [].
case=>j1 j2 [[->->]][/= S] H D1 D2 v1 v2 n1 n2 R1 R2.
by apply: (T j1 j2 y1 y2 D1 D2 v1 v2 n1 n2 R1 R2).
Qed. 

(* the inner defs are not needed in bnd_insert *)
(* because verify2 already subsumes them *)
Lemma bnd_insert kv0 : 
  forall (B : Type) (e1 e2 : ST [::: unit] B) (t : hashmap) k1 k2 v1 v2
         j1 j2 h1 h2 f1 f2 (r : d B -> d heap -> Prop),
     def (j1 :+ h1) -> def (j2 :+ h2) -> sshape (t, t) (f1, f2) (j1, j2) ->
     (forall j1' j2',
       def (j1' :+ h1) -> def (j2' :+ h2) -> 
       sshape (t, t) (ins k1 v1 f1, ins k2 v2 f2) (j1', j2') ->    
       verify2 (j1' :+ h1) (j2' :+ h2) (e1 @0 [tt]) (e2 @0 [tt]) r) ->
     verify2 (j1 :+ h1) (j2 :+ h2) 
     (insert kv0 t @0 [k1, v1];; e1) (insert kv0 t @0 [k2, v2];; e2) r.
Proof.
move=>B e1 e2 t k1 k2 v1 v2 j1 j2 h1 h2 f1 f2 r D1 D2 [/= S1 S2] H.
apply: bnd_do=>[|[][] m1 m2] /=.
- by split; [exists f1 | exists f2]; eexists _, _.
case/(_ (_, _) (_, _) (_, _) (erefl _) (conj S1 S2))=>[[t1 t2]][].
by case=>->->{m1 m2} /= S; apply: vrf2D=>T1 T2; apply: H S.
Qed.

(* ditto for bnd_insertL and the inner defs *)
Lemma bnd_insertL kv0 : 
  forall B (e1 : ST [::: unit] B) e2 (t : hashmap) k1 v1
         j1 j2 h1 h2 f1 f2 (r : d B -> d heap -> Prop),
    sshape (t, t) (f1, f2) (j1, j2) -> def (j1 :+ h1) -> def (j2 :+ h2) ->
    (forall j1',
      def (j1' :+ h1) -> 
      sshape (t, t) (ins k1 v1 f1, f2) (j1', j2) ->    
      verify2 (j1' :+ h1) (j2 :+ h2) (e1 @0 [tt]) e2 r) ->
    verify2 (j1 :+ h1) (j2 :+ h2) (insert kv0 t @0 [k1, v1];; e1) e2 r.
Proof.
move=>B e1 e2 t k1 v1 j1 j2 h1 h2 f1 f2 r [/= S1 S2] D1 D2 H.
apply: bnd_doL=>[|[] m1] /=; first by eexists f1, _ , _.
case/(_ (_, _) (_, _) (_, _) (erefl _) (conj S1 S1))=>[[t1 t2]][].
by case=>->{m1} _ [/= S1' _]; apply: vrf2D=>D _; apply: H D _.
Qed.

Lemma val_lookupV kv0 (t : hashmap) j h f k (r : option V -> heap -> Prop) :
  shape t f j -> (def (j :+ h) -> r (fnd k f) (j :+ h)) -> 
  verify (j :+ h) (lookup kv0 t @0 [k]) r.
Proof.
move=>S H; apply: val_doV=>[|y q] /=; first by eexists f, _, _.
by case/(_ (_, _) (_, _) (_, _) (erefl _) (conj S S))=>[[-> _]][-> _].
Qed.

Lemma shape_fun t s h1 h2 : shape t s h1 -> shape t s h2 -> h1 = h2.
Proof. 
move=>[j][k][/=-> [[/=-> A]]] B [j'][k'][/=-> [[/=-> C]]] D.
by f_equal=>//; apply: (M.shape_fun B D).
Qed.

Section HashMap.
Variable kv0 : K * V.

Definition HashMap := 
  HashTab.Make shape_fun sshape_refl sshape_refl1 sshape_refl2 (remove kv0) ret_minkey
               bnd_new (@bnd_insert kv0) (@bnd_insertL kv0) 
               (@val_lookupV kv0).

End HashMap.

End HashMap. End HashMap.

(* hashmaps with a single "filter" *)

Section OneLevel.
Variables (K : ordType) (V : eqType) (kv0 : K * V) 
          (sz : nat) (hash : K -> 'I_sz).

Definition one_level : @HashTab.Sig K V sz hash := 
  HashMap.HashMap (@M.HashableAssocList K V sz hash) kv0.

End OneLevel.

(* hashmaps with two "filters" *)

Section TwoLevel.
Variables (K : ordType) (V : eqType) (kv0 : K * V) 
          (sz : nat) (hash : K -> 'I_sz).

Definition two_level : @HashTab.Sig K V sz hash := 
  HashMap.HashMap (one_level kv0 hash) kv0.

End TwoLevel.

(* d-level hashmaps *)

Section DLevel.
Variables (K : ordType) (V : eqType) (kv0 : K * V) (sz : nat).
Variable (hash : K -> 'I_sz).

Fixpoint build_map (n : nat) : @HashTab.Sig K V sz hash :=
  if n is S n' then HashMap.HashMap (build_map n') kv0
  else (@M.HashableAssocList K V sz hash).

End DLevel. 