import GolangLean.Core.TypeSoundness

namespace GolangLean.Core

/-! # Preservation theorem for big-step semantics.

`Γ ⊢ e : T  ∧  HasTypeH ht h  ∧  HasTypeEnv ht env Γ  ∧  BigStep h env e v h'
   ⟹  ∃ ht', HeapTy.extends ht ht'  ∧  HasTypeH ht' h'  ∧  HasTypeV ht' v T  ∧  HasTypeEnv ht' env Γ`

By induction on the big-step derivation, with case-splits on the typing
hypothesis. Each rule case threads heap-typings through its sub-derivations
and weakens earlier results across the extension.

Big-step preservation has no progress analogue — the theorem only speaks
about derivations that exist. -/

/-! ## Heap extension by push. -/

theorem HeapTy.extends_push (ht : HeapTy) (T : Ty) : ht.extends (ht.push T) := by
  refine ⟨by simp [Array.size_push], ?_⟩
  intros i T' hL
  rw [Array.getElem?_push]
  by_cases heq : i = ht.size
  · subst heq
    have hnone : ht[ht.size]? = none := Array.getElem?_eq_none (Nat.le_refl _)
    rw [hnone] at hL
    contradiction
  · simp [heq]; exact hL

/-! ## Heap-conformance preservation. -/

theorem HasTypeH.push
    {ht : HeapTy} {h : Heap} {v : Value} {T : Ty}
    (hH : HasTypeH ht h) (hV : HasTypeV ht v T) :
    HasTypeH (ht.push T) (h.push v) := by
  refine ⟨by simp [Array.size_push, hH.1], ?_⟩
  intros i T' hL
  rw [Array.getElem?_push] at hL
  by_cases heq : i = ht.size
  · subst heq
    simp at hL
    subst hL
    refine ⟨v, ?_, hV.weaken (HeapTy.extends_push ht T)⟩
    rw [Array.getElem?_push]
    simp [hH.1]
  · simp [heq] at hL
    have ⟨v', hgv, hVT⟩ := hH.2 i T' hL
    refine ⟨v', ?_, hVT.weaken (HeapTy.extends_push ht T)⟩
    rw [Array.getElem?_push]
    have hisize : i ≠ h.size := hH.1 ▸ heq
    simp [hisize, hgv]

theorem HasTypeH.setIfInBounds
    {ht : HeapTy} {h : Heap} {loc : Nat} {v : Value} {T : Ty}
    (hH : HasTypeH ht h) (hV : HasTypeV ht v T)
    (hLoc : (ht[loc]? : Option Ty) = some T) :
    HasTypeH ht (h.setIfInBounds loc v) := by
  refine ⟨by rw [Array.size_setIfInBounds]; exact hH.1, ?_⟩
  intros i T' hL
  by_cases hi : loc = i
  · subst hi
    rw [hL] at hLoc
    simp at hLoc
    subst hLoc
    have hloc_lt : loc < h.size := by
      rw [← hH.1]
      rcases Nat.lt_or_ge loc ht.size with hlt | hge
      · exact hlt
      · rw [Array.getElem?_eq_none hge] at hL
        contradiction
    have heq : (h.setIfInBounds loc v)[loc]? = some v := by
      rw [Array.getElem?_setIfInBounds]; simp [hloc_lt]
    exact ⟨v, heq, hV⟩
  · have ⟨v', hgv, hVT⟩ := hH.2 i T' hL
    have heq : (h.setIfInBounds loc v)[i]? = some v' := by
      rw [Array.getElem?_setIfInBounds, if_neg hi]; exact hgv
    exact ⟨v', heq, hVT⟩

/-! ## Inversion for binop typing. -/

theorem binop_apply_sound
    {ht : HeapTy} {op : BinOp} {v1 v2 v : Value} {T1 T2 T : Ty}
    (hOp : op.typeOf T1 T2 = some T)
    (hV1 : HasTypeV ht v1 T1) (hV2 : HasTypeV ht v2 T2)
    (hApp : op.apply v1 v2 = some v) :
    HasTypeV ht v T := by
  cases op <;> cases T1 <;> cases T2 <;> simp [BinOp.typeOf] at hOp <;>
    (try (cases hOp; cases hV1; cases hV2; simp [BinOp.apply] at hApp; cases hApp; constructor))

/-! ## Preservation. -/

theorem preservation :
    ∀ {h env e v h'} (D : BigStep h env e v h')
      {ht Γ T} (hT : HasType Γ e T)
      (hH : HasTypeH ht h) (hE : HasTypeEnv ht env Γ),
      ∃ ht', HeapTy.extends ht ht' ∧ HasTypeH ht' h' ∧
              HasTypeV ht' v T ∧ HasTypeEnv ht' env Γ := by
  intros h env e v h' D
  induction D with
  | unitR =>
    intros ht Γ T hT hH hE
    cases hT
    exact ⟨ht, HeapTy.extends_refl _, hH, .vUnit, hE⟩
  | intLitR n =>
    intros ht Γ T hT hH hE
    cases hT
    exact ⟨ht, HeapTy.extends_refl _, hH, .vInt n, hE⟩
  | boolLitR b =>
    intros ht Γ T hT hH hE
    cases hT
    exact ⟨ht, HeapTy.extends_refl _, hH, .vBool b, hE⟩
  | varR hLook =>
    intros ht Γ T hT hH hE
    cases hT with
    | var hLookT =>
      have ⟨v', hLook', hTV⟩ := hE.lookup_correspondence hLookT
      rw [hLook] at hLook'
      cases hLook'
      exact ⟨ht, HeapTy.extends_refl _, hH, hTV, hE⟩
  | lamR x body =>
    intros ht Γ T hT hH hE
    cases hT with
    | lam hBody => exact ⟨ht, HeapTy.extends_refl _, hH, .vClos hE hBody, hE⟩
  | appR _ _ _ ih1 ih2 ihb =>
    intros ht Γ T hT hH hE
    cases hT with
    | app hT1 hT2 =>
      have ⟨ht1, ext01, hH1, hClosT, hE1⟩ := ih1 hT1 hH hE
      cases hClosT with
      | vClos hEnv' hBody =>
        have ⟨ht2, ext12, hH2, hArgT, _⟩ := ih2 hT2 hH1 hE1
        have hEnv2 := hEnv'.weaken ext12
        have ⟨ht3, ext23, hH3, hValT, _⟩ :=
          ihb hBody hH2 (HasTypeEnv.cons hArgT hEnv2)
        let totalExt := HeapTy.extends_trans (HeapTy.extends_trans ext01 ext12) ext23
        exact ⟨ht3, totalExt, hH3, hValT, hE.weaken totalExt⟩
  | letInR _ _ ih1 ih2 =>
    intros ht Γ T hT hH hE
    cases hT with
    | letIn hT1 hT2 =>
      have ⟨ht1, ext01, hH1, hV1, hE1⟩ := ih1 hT1 hH hE
      have ⟨ht2, ext12, hH2, hV2, _⟩ :=
        ih2 hT2 hH1 (HasTypeEnv.cons hV1 hE1)
      let totalExt := HeapTy.extends_trans ext01 ext12
      exact ⟨ht2, totalExt, hH2, hV2, hE.weaken totalExt⟩
  | ifTR _ _ ihc iht =>
    intros ht Γ T hT hH hE
    cases hT with
    | ifte hTc hT1 _ =>
      have ⟨ht1, ext01, hH1, _, hE1⟩ := ihc hTc hH hE
      have ⟨ht2, ext12, hH2, hV2, _⟩ := iht hT1 hH1 hE1
      let totalExt := HeapTy.extends_trans ext01 ext12
      exact ⟨ht2, totalExt, hH2, hV2, hE.weaken totalExt⟩
  | ifFR _ _ ihc ihf =>
    intros ht Γ T hT hH hE
    cases hT with
    | ifte hTc _ hT2 =>
      have ⟨ht1, ext01, hH1, _, hE1⟩ := ihc hTc hH hE
      have ⟨ht2, ext12, hH2, hV2, _⟩ := ihf hT2 hH1 hE1
      let totalExt := HeapTy.extends_trans ext01 ext12
      exact ⟨ht2, totalExt, hH2, hV2, hE.weaken totalExt⟩
  | binopR _ _ hOp ih1 ih2 =>
    intros ht Γ T hT hH hE
    cases hT with
    | binop hT1 hT2 hOpT =>
      have ⟨ht1, ext01, hH1, hV1, hE1⟩ := ih1 hT1 hH hE
      have ⟨ht2, ext12, hH2, hV2, _⟩ := ih2 hT2 hH1 hE1
      let totalExt := HeapTy.extends_trans ext01 ext12
      have hV1ext := hV1.weaken ext12
      have hVT := binop_apply_sound hOpT hV1ext hV2 hOp
      exact ⟨ht2, totalExt, hH2, hVT, hE.weaken totalExt⟩
  | refMkR _ ih =>
    intros ht Γ T hT hH hE
    cases hT with
    | @refMk _ _ T_inner hT1 =>
      have ⟨ht1, ext01, hH1, hV, _⟩ := ih hT1 hH hE
      have extPush := HeapTy.extends_push ht1 T_inner
      have newExt := HeapTy.extends_trans ext01 extPush
      have hHnew := hH1.push hV
      have hVnew : HasTypeV (ht1.push T_inner) (.vLoc ht1.size) (.ref T_inner) := by
        apply HasTypeV.vLoc
        rw [Array.getElem?_push]
        simp
      rw [hH1.1] at hVnew
      exact ⟨ht1.push T_inner, newExt, hHnew, hVnew, hE.weaken newExt⟩
  | derefR _ Hget ih =>
    intros ht Γ T hT hH hE
    cases hT with
    | @deref _ _ T_inner hT1 =>
      have ⟨ht1, ext01, hH1, hVLoc, _⟩ := ih hT1 hH hE
      cases hVLoc with
      | vLoc hLocT =>
        have ⟨v', hgv, hVT⟩ := hH1.2 _ _ hLocT
        rw [Hget] at hgv
        cases hgv
        exact ⟨ht1, ext01, hH1, hVT, hE.weaken ext01⟩
  | assignR _ _ Hin ih1 ih2 =>
    intros ht Γ T hT hH hE
    cases hT with
    | @assign _ _ _ T_target hT1 hT2 =>
      have ⟨ht1, ext01, hH1, hVLoc, hE1⟩ := ih1 hT1 hH hE
      have ⟨ht2, ext12, hH2, hV2, _⟩ := ih2 hT2 hH1 hE1
      cases hVLoc with
      | vLoc hLocT =>
        have hLocT2 := ext12.2 _ _ hLocT
        have hH2new := hH2.setIfInBounds hV2 hLocT2
        have totalExt := HeapTy.extends_trans ext01 ext12
        exact ⟨ht2, totalExt, hH2new, .vUnit, hE.weaken totalExt⟩
  | seqR _ _ ih1 ih2 =>
    intros ht Γ T hT hH hE
    cases hT with
    | seq hT1 hT2 =>
      have ⟨ht1, ext01, hH1, _, hE1⟩ := ih1 hT1 hH hE
      have ⟨ht2, ext12, hH2, hV2, _⟩ := ih2 hT2 hH1 hE1
      let totalExt := HeapTy.extends_trans ext01 ext12
      exact ⟨ht2, totalExt, hH2, hV2, hE.weaken totalExt⟩

end GolangLean.Core
