From iris.base_logic.lib Require Import invariants own.
From iris.heap_lang Require Import proofmode notation par assert.

(** Each thread is the same: an incr in a critical section *)

Definition check : val :=
  λ: "r",
    let: "x" := !"r" in
    let: "y" := !"r" in
    assert:"x" ≤ "y".

Definition loop_check : val :=
  rec: "loop" "r" :=
    check "r";;
    "loop" "r".

Definition loop_incr : val :=
  rec: "loop" "r" :=
    FAA "r" #1;;
    "loop" "r".

Definition main : expr :=
  let: "r" := ref #0 in
  loop_check "r" ||| loop_incr "r".


(** Resource algebra laws *)

(** since [max] idempotent, we can spawn [◯a] from [●a] *)

Lemma authoritative_gives_fragment a :
  ● (MaxNat a) ~~> ● (MaxNat a) ⋅ ◯ (MaxNat a).
Proof.
  apply auth_update_alloc.
  apply local_update_unital_discrete.
  intros b Vk.
  rewrite left_id.
  intros <-. split; auto.
  rewrite max_nat_op.
  f_equal.
  lia.
Qed.

(** [valid (●a⋅◯b)] implies [b ≼ a] i.e. [b <= a] *)

Lemma fragment_leq_authoritative a b :
  valid (● MaxNat a ⋅ ◯ MaxNat b) → a >= b.
Proof.
  (* also: by intros [?%max_nat_included _]%auth_both_valid_discrete. *)
  intros Hvalid.
  apply auth_both_valid_discrete in Hvalid.
  destruct Hvalid as [Hvalid meh].
  apply max_nat_included in Hvalid.
  apply Hvalid.
Qed.


Section Proofs.

Context `{!heapGS Σ, spawnG Σ}.
Context `{inG Σ (authR max_natO)}.

Definition mcountInv r γ : iProp Σ := TODO.

Definition frag γ k := own γ (◯ (MaxNat k)).

(** Proof of the left thread *)

Lemma read_spec N r γ n :
  {{{ inv N (mcountInv r γ) ∗ own γ (◯ (MaxNat n)) }}}
    ! #r
  {{{ k, RET #k; ⌜k >= n⌝ ∗ own γ (◯ (MaxNat k)) }}}.
Proof.
  iIntros (post) "(#Hinv & #Hfrag) Hpost".
  iInv "Hinv" as (k) "(Hr & Hauth)" "Hclose".
  wp_load.

  (* before closing the invariant, extract the knowledge [k >= n] *)
  iCombine "Hauth" "Hfrag" gives %Hvalid%fragment_leq_authoritative.

  (* also before closing the invariant, we  the knowledge [k >= n] *)
  iPoseProof (own_update _ _ _ (authoritative_gives_fragment k) with "Hauth") as "Hboth".
  iMod "Hboth".
  iDestruct "Hboth" as "(Hauth & #Hfrag')".

  (* closing the invariant *)
  iMod ("Hclose" with "[Hr Hauth]"). now iExists k; iFrame.
  iModIntro.
  iApply "Hpost". iFrame "Hfrag'". auto.
Qed.

Lemma read_spec_no_frag_in_pre N r γ :
  {{{ inv N (mcountInv r γ) }}}
    ! #r
  {{{ k, RET #k; own γ (◯ (MaxNat k)) }}}.
Proof.
  iIntros (post) "#Hinv Hpost".
  iInv "Hinv" as (a) "(Hr & Hauth)" "Hclose".
  wp_load.
  iMod (own_update _ _ _ (authoritative_gives_fragment a) with "Hauth")
    as "(Hauth & #Hfrag')".
  iMod ("Hclose" with "[Hr Hauth]"). now iExists a; iFrame.
  iModIntro.
  iApply "Hpost". iFrame "Hfrag'".
Qed.

(* we could remove the [◯ _] in precondition since it is deducible from ● *)
Lemma check_spec N r γ :
  {{{ inv N (mcountInv r γ) ∗ own γ (◯ (MaxNat 0)) }}}
    check #r
  {{{ RET #(); True }}}.
Proof.
  iIntros (post) "(#Hinv & #Hfrag) Hpost".
  unfold check.
  wp_pures.
  wp_apply (read_spec with "[$]") as (x) "(%Hx & Hx)".
  wp_pures.
  wp_apply (read_spec with "[$]") as (y) "(%Hy & Hy)".
  wp_pures.
  wp_apply wp_assert.
  wp_pures.
  case_bool_decide. 2:lia.
  iModIntro. iSplit; auto.
  iApply "Hpost".
  auto.
Qed.

Lemma incr_spec r ι γ n :
  {{{ inv ι (mcountInv r γ) ∗ own γ (◯ (MaxNat n)) }}}
    FAA #r #1
  {{{ k, RET #k; own γ (◯ (MaxNat (k + 1))) ∗ ⌜k >= n⌝ }}}.
Proof.
  iIntros (post) "(Hinv & Hfrag) Hpost".
  iInv "Hinv" as (a) "(Hr & Hauth)" "Hclose".
  wp_faa.

  (* before closing the invariant, extract the knowledge [k >= n] *)
  iCombine "Hauth" "Hfrag" gives %Hvalid%fragment_leq_authoritative.

    
  assert (● (MaxNat a) ⋅ ◯ (MaxNat n) ~~> ● (MaxNat (a + 1)) ⋅ ◯ (MaxNat (a + 1))) as Hfpu.
  { apply auth_update.
    apply local_update_unital_discrete.
    intros [i] Vk.
    intros [=->].
    split; auto.
    rewrite max_nat_op. f_equal.
    lia. }

  iPoseProof (own_update _ _ _ Hfpu with "[Hauth Hfrag]") as "Hboth".
  by rewrite own_op; iFrame.
  iMod "Hboth".
  iDestruct "Hboth" as "(Hauth & #Hfrag)".
  iMod ("Hclose" with "[Hr Hauth]").
  { iExists (a + 1).
    replace ((a + 1)%nat : Z) with (Z.add a 1) by lia.
    iFrame. }
  iModIntro.
  iApply "Hpost". iFrame "Hfrag". auto.
Qed.

Definition mcountN : namespace :=
  nroot .@ "mcount".

Lemma main_spec : {{{ True }}} main {{{ RET #42; False }}}.
Proof.
  iIntros (post) "Htrue Hpost".
  unfold main.
  wp_alloc r as "Hr".
  wp_pures.
  iMod (own_alloc (● (MaxNat 0) ⋅ ◯ (MaxNat 0))) as (γ) "(Hauth & #Hfrag)".
  { apply auth_both_valid_2.
    constructor.
    unfold "≼".
    exists (MaxNat 0).
    compute.
    reflexivity. }

  iMod (inv_alloc mcountN _ (mcountInv r γ) with "[Hr Hauth]") as "#Hinv".
  by iExists 0; iFrame.

  wp_apply (par_spec (λ _, ⌜False⌝%I) (λ _, ⌜False⌝%I)) as (? ?) "(Hfalse & Hfalse')".

  - wp_pure.
    iLöb as "IH".
    wp_rec.
    wp_apply (check_spec with "[$]") as "Htrue".
    wp_pures.
    auto.

  - wp_pure.
    iLöb as "IH".
    wp_rec.
    wp_apply (incr_spec with "[$]") as (k) "A".
    wp_pures.
    auto.

  - iExFalso.
    auto.
Qed.


End Proofs.
