From iris.algebra Require Import excl gmap.
Definition map_Excl `{Countable K} {V} (m : gmap K V) : gmap K (excl V) :=
  Excl <$> m.
Global Instance : Params (@map_Excl) 4 := {}.
Section map_Excl.
  Context `{Countable K} {V : ofe}.
  Implicit Type m : gmap K V.
  Global Instance map_excl_proper : Proper ((≡) ==> (≡)) (map_Excl (K:=K) (V:=V)).
  Proof. solve_proper. Qed.
  Lemma map_Excl_valid m : ✓ (map_Excl m).
  Proof. intros i. rewrite /map_Excl lookup_fmap. destruct (m !! i); done. Qed.
  Lemma map_Excl_empty : map_Excl ∅ = (ε : gmap K (excl V)).
  Proof. rewrite /map_Excl fmap_empty. done. Qed.
  Lemma map_Excl_empty_inv m : map_Excl m ≡ ∅ -> m = ∅.
  Proof.
    rewrite /map_Excl. intros G.
    apply map_empty_equiv_eq in G.
    eapply fmap_empty_iff. done.
  Qed.
  Lemma map_Excl_injective m1 m2 :
    map_Excl m1 ≡ map_Excl m2 -> m1 ≡ m2.
  Proof.
    rewrite /map_Excl.
    intros Hm i.
    specialize (Hm i).
    rewrite !lookup_fmap in Hm.
    destruct (m1 !! i),(m2 !! i); simpl in *; inversion Hm; subst; try done.
    inversion H2. subst. rewrite H3. done.
  Qed.
  Lemma map_Excl_insert m k v :
    map_Excl (<[ k := v ]> m) = <[ k := Excl v ]> $ map_Excl m.
  Proof.
    rewrite /map_Excl fmap_insert //.
  Qed.
  Lemma map_Excl_insert_op m k v :
    m !! k = None ->
    map_Excl (<[ k := v ]> m) = {[ k := Excl v ]} ⋅ map_Excl m.
  Proof.
    intros HH.
    rewrite /map_Excl fmap_insert insert_singleton_op //
            lookup_fmap HH //.
  Qed.
  Lemma map_Excl_union m1 m2 :
    m1 ##ₘ m2 -> map_Excl (m1 ∪ m2) ≡ map_Excl m1 ⋅ map_Excl m2.
  Proof.
    induction m1 using map_ind; intros; decompose_map_disjoint.
    - by rewrite map_Excl_empty !left_id_L.
    - assert ((m ∪ m2) !! i = None).
      { rewrite lookup_union H0 H1 //. }
      rewrite -insert_union_l !map_Excl_insert_op // IHm1 // assoc //.
  Qed.
  Lemma map_Excl_valid_op m1 m2 : ✓ (map_Excl m1 ⋅ map_Excl m2) ↔ m1 ##ₘ m2.
  Proof.
    split; last first.
    { intros. rewrite -map_Excl_union //. apply map_Excl_valid. }
    induction m1 as [|k x m1 Hm1 IH] using map_ind.
    { intros _. apply map_disjoint_empty_l. }
    rewrite map_Excl_insert_op // -assoc. intros Hvalid.
    apply map_disjoint_insert_l_2; last by eapply IH, cmra_valid_op_r.
    apply eq_None_not_Some; intros [x' Hm2].
    move: (Hvalid k).
    by rewrite !lookup_op /map_Excl lookup_singleton !lookup_fmap Hm1 Hm2.
  Qed.
  Lemma map_Excl_singleton_op_inv m me k x :
    map_Excl m ≡ {[ k := Excl x ]} ⋅ me ->
    ∃ m', m ≡ <[ k := x ]> m' ∧ me ≡ map_Excl m' ∧ m' !! k = None.
  Proof.
    rewrite /map_Excl. intros HH.
    exists (delete k m).
    assert (m !! k ≡ Some x).
    { specialize (HH k).
      revert HH. rewrite lookup_fmap lookup_op lookup_singleton.
      case: (m!!k); case:(me!!k); simpl; intros; inversion HH; subst;
      inversion H2; subst. rewrite H3. done. }
    split_and!.
    - intros i.
      destruct (decide (k = i)); subst.
      + rewrite lookup_insert //.
      + rewrite lookup_insert_ne // lookup_delete_ne //.
    - intros i.
      specialize (HH i).
      destruct (decide (k = i)); subst.
      + rewrite lookup_fmap lookup_delete.
        revert HH. rewrite !lookup_fmap lookup_op lookup_singleton H0 /=.
        case: (me !! i); eauto.
        intros a HH. inversion HH. subst. inversion H3.
      + rewrite lookup_fmap lookup_delete_ne //.
        revert HH. rewrite lookup_fmap lookup_op lookup_singleton_ne //.
        case: (me !! i); simpl; intros; inversion HH; subst; eauto.
        rewrite H3. done.
    - rewrite lookup_delete //.
  Qed.
  Lemma map_Excl_union_inv m me1 me2 :
    map_Excl m ≡ me1 ⋅ me2 ->
    ∃ m1 m2, m ≡ m1 ∪ m2 ∧ me1 ≡ map_Excl m1 ∧ me2 ≡ map_Excl m2 ∧ m1 ##ₘ m2.
  Proof.
    revert m. induction me1 as [|k xe me1 Hx IH] using map_ind; intros m.
    - rewrite left_id_L. intros Hr1.
      exists ∅,m.
      rewrite left_id_L map_Excl_empty. split_and!; try solve_map_disjoint.
    - rewrite insert_singleton_op // -assoc.
      destruct xe as [x|].
      + intros (m' & Hr1 & ? & ?)%map_Excl_singleton_op_inv.
        setoid_rewrite Hr1.
        destruct (IH m') as (m1 & m2 & Hr2 & Hr3 & Hr4 & ?); first done.
        exists (<[ k := x]> m1),m2.
        assert (m' !! k ≡ None) as H1'. { rewrite H1 //. }
        rewrite ->Hr2 in H1'.
        apply None_equiv_eq in H1'.
        apply lookup_union_None in H1' as [].
        repeat split.
        * rewrite Hr2. rewrite insert_union_l //.
        * rewrite map_Excl_insert_op //. rewrite Hr3 //.
        * done.
        * by apply map_disjoint_insert_l.
      + intros Hm. assert (✓ (ExclBot : excl V)) as [].
        eapply (singleton_valid k), (cmra_valid_op_l _ (me1 ⋅ me2)).
        rewrite -Hm. apply map_Excl_valid.
  Qed.
  Lemma map_Excl_disjoint m1 m2 :
    m1 ##ₘ m2 <-> map_Excl m1 ##ₘ map_Excl m2.
  Proof.
    split; first apply map_disjoint_fmap.
    unfold map_Excl. intros G ?. specialize (G i).
    rewrite !lookup_fmap in G.
    destruct (m1 !! i),(m2 !! i); done.
  Qed.
  Lemma map_Excl_singleton (a : K) (b : V) :
    map_Excl {[ a := b ]} = {[ a := Excl b ]}.
  Proof.
    rewrite /map_Excl map_fmap_singleton //.
  Qed.
  Lemma map_Excl_singleton_inv a b m :
    map_Excl m ≡ {[ a := Excl b ]} -> m ≡ {[ a := b ]}.
  Proof.
    intros HH.
    intros i.
    specialize (HH i).
    destruct (decide (i = a)); subst.
    - rewrite lookup_singleton. rewrite lookup_fmap in HH.
      rewrite lookup_singleton in HH.
      inversion HH. subst. inversion H2. subst.
      destruct (m !! a) eqn:E; simpl in *.
      + inversion HH; subst.
        inversion H5; subst. rewrite H6. done.
      + inversion HH.
    - rewrite lookup_singleton_ne //.
      rewrite lookup_singleton_ne // in HH.
      unfold map_Excl in HH.
      rewrite lookup_fmap in HH.
      inversion HH. symmetry in H1.
      apply fmap_None in H1. rewrite H1 //.
  Qed.
End map_Excl.