import data.buffer

structure hoare_state (σ : Type) (α : Type) :=
(pre    : σ → Prop)
(post   : σ → α → σ → Prop)
(action : ∀ s : {s // pre s}, {as' : α × σ // post s.val as'.1 as'.2})

namespace hoare_state

protected def return (σ : Type) {α : Type} (a : α) : hoare_state σ α :=
{pre    := λ s, true,
 post   := λ s a' s', s' = s ∧ a' = a,
 action := λ ⟨s, h⟩, ⟨(a, s), by simp⟩}

protected def bind {σ α β : Type} (m₁ : hoare_state σ α) (m₂ : α → hoare_state σ β) : hoare_state σ β :=
{pre    := λ s₁, m₁.pre s₁ ∧ ∀ a s₂, m₁.post s₁ a s₂ → (m₂ a).pre s₂,
 post   := λ s₁ b s₃, ∃ a s₂, m₁.post s₁ a s₂ ∧ (m₂ a).post s₂ b s₃,
 action := λ ⟨s₁, h₁, h₂⟩,
           match m₁.action ⟨s₁, h₁⟩ with
           | ⟨(a, s₂), h₃⟩ :=
              match (m₂ a).action ⟨s₂, h₂ a s₂ h₃⟩ with
              | ⟨(b, s₃), s₄⟩ := ⟨(b, s₃), ⟨a, s₂, h₃, s₄⟩⟩
              end
           end
}

protected def assert {σ : Type} (p : σ → Prop) : hoare_state σ unit :=
{pre    := λ s, p s,
 post   := λ s _ s', s' = s ∧ p s',
 action := λ ⟨s, h⟩, ⟨((), s), ⟨rfl, h⟩⟩}

protected def write {σ : Type} (new_s : σ) : hoare_state σ unit :=
{pre    := λ s, true,
 post   := λ s _ s', s' = new_s,
 action := λ ⟨s, h⟩, ⟨((), new_s), rfl⟩}

protected def read {σ : Type} : hoare_state σ σ :=
{pre    := λ s, true,
 post   := λ s a s', s' = s ∧ a = s,
 action := λ ⟨s, h⟩, ⟨(s, s), by simp⟩}

protected def assign {α : Type} (p : nat) (v : α) : hoare_state (buffer α) unit :=
{pre    := λ s, p < s.size,
 post   := λ s _ s', ∀ h : p < s.size, s' = s.write ⟨p, h⟩ v,
 action := λ ⟨s, h⟩, ⟨((), s.write ⟨p, h⟩ v), by intros; simp⟩}

local infix ` ::= `:60 := hoare_state.assign

instance (σ : Type) : has_bind (hoare_state σ) :=
⟨@hoare_state.bind σ⟩

protected def run {σ α : Type} (a : hoare_state σ α) (s : σ) (h : a.pre s) : {as' : α × σ // a.post s as'.1 as'.2} :=
a.action ⟨s, h⟩

def test : hoare_state (buffer nat) unit :=
do 0  ::= 1,
   10 ::= 2,
   2  ::= 4

lemma size_write (b : buffer nat) (i : nat) (h : i < b.size) (v : nat) : (b.write ⟨i, h⟩ v).size = b.size :=
begin
  cases b,
  unfold buffer.write buffer.size
end

open nat

def init_mem : nat → hoare_state (buffer nat) unit
| 0        := 0 ::= 0
| (succ p) := succ p ::= 0 >> init_mem p

lemma init_mem_inv : ∀ n (b : buffer nat), n < b.size → (init_mem n).pre b
| 0 b h            := h
| (nat.succ n) b h :=
  have n < b.size, from nat.lt_of_succ_lt h,
  begin
    unfold init_mem has_bind.and_then bind has_bind.bind hoare_state.bind, simp,
    split,
    {unfold hoare_state.assign, simp, exact h},
    {intros _ _,
     unfold hoare_state.assign, simp,
     intro h₁, rewrite h₁ h,
     apply init_mem_inv n,
     rewrite size_write,
     assumption}
  end

def run_init_mem (n : nat) (b : buffer nat) (h : n < b.size) : buffer nat :=
(hoare_state.run (init_mem n) b (init_mem_inv n b h)).1.2

#eval run_init_mem 10 [0,1,2,3,4,5,6,7,8,9,10,11,12,13].to_buffer dec_trivial

end hoare_state
