module CC.TypeSafety where

open import Data.Nat
open import Data.Unit using (; tt)
open import Data.Bool using (true; false) renaming (Bool to 𝔹)
open import Data.List
open import Data.Product using (_×_; ∃-syntax; proj₁; proj₂) renaming (_,_ to ⟨_,_⟩)
open import Data.Sum using (_⊎_; inj₁; inj₂)
open import Data.Maybe
open import Relation.Nullary using (¬_; Dec; yes; no)
open import Relation.Nullary.Negation using (contradiction)
open import Relation.Binary.PropositionalEquality using (_≡_; refl; trans; subst; sym)
open import Function using (case_of_)

open import Common.Utils
open import Common.Types
open import CC.CCStatics
open import CC.Reduction

open import CC.HeapTyping    public
open import CC.WellTyped     public
open import CC.SubstPreserve public

data Progress (M : Term) (μ : Heap) (pc : StaticLabel) : Set where
  step :  {N μ′}
     M  μ  pc —→ N  μ′
      ----------------------------- Step
     Progress M μ pc

  done : Value M
      ----------------------- Done
     Progress M μ pc

  err : Err M
      ----------------------- Error
     Progress M μ pc

progress :  {Σ gc A} pc M  [] ; Σ ; gc ; pc  M  A   μ  Σ  μ  Progress M μ pc
progress pc ($ k of ) ⊢const μ ⊢μ = done V-const
progress pc (addr a of ) (⊢addr _) μ ⊢μ = done V-addr
progress pc (` x) (⊢var ())
progress pc (ƛ⟦ _  A ˙ N of ) (⊢lam ⊢M) μ ⊢μ = done V-ƛ
progress pc (L · M) (⊢app ⊢L ⊢M) μ ⊢μ =
  case progress pc L ⊢L μ ⊢μ of λ where
  (step L→L′)  step (ξ {F = □· M} L→L′)
  (done v) 
    case progress pc M ⊢M μ ⊢μ of λ where
    (step M→M′)  step (ξ {F = (L ·□) v} M→M′)
    (done w) 
      case canonical-fun ⊢L v of λ where
      (Fun-ƛ _ _)  step (β w)
      (Fun-proxy f i _)  step (fun-cast (fun-is-value f) w i)
    (err (E-error {e}))  step (ξ-err {F = (L ·□) v} {e = e})
  (err (E-error {e}))  step (ξ-err {F = □· M} {e = e})
progress pc (if L A M N) (⊢if {g = g} ⊢L ⊢M ⊢N) μ ⊢μ =
  case progress pc L ⊢L μ ⊢μ of λ where
  (step L→L′)  step (ξ {F = if□ A M N} L→L′)
  (done v) 
    case canonical-const ⊢L v of λ where
    (Const-base {𝔹} {true} _)   step β-if-true
    (Const-base {𝔹} {false} _)  step β-if-false
    (Const-inj  {𝔹} {true} _)   step (if-cast-true (I-base-inj _))
    (Const-inj  {𝔹} {false} _)  step (if-cast-false (I-base-inj _))
  (err (E-error {e}))  step (ξ-err {F = if□ A M N} {e = e})
progress pc (`let M N) (⊢let ⊢M ⊢N) μ ⊢μ =
  case progress pc M ⊢M μ ⊢μ of λ where
  (step M→M′)  step (ξ {F = let□ N} M→M′)
  (done v)  step (β-let v)
  (err (E-error {e}))  step (ξ-err {F = let□ N} {e = e})
progress pc (ref⟦   M) (⊢ref ⊢M pc′≼ℓ) μ ⊢μ =
  step ref-static
progress pc (ref?⟦   M) (⊢ref? ⊢M) μ ⊢μ =
  case pc ≼?  of λ where
  (yes pc≼ℓ)  step (ref?-ok pc≼ℓ)
  (no  pc⋠ℓ)  step (ref?-fail pc⋠ℓ)
progress {Σ} pc (ref✓⟦   M) (⊢ref✓ ⊢M pc≼ℓ) μ ⊢μ =
  case progress pc M ⊢M μ ⊢μ of λ where
    (step M→M′)  step (ξ {F = ref✓⟦  ⟧□} M→M′)
    (done v) 
      let  n , fresh  = gen-fresh μ in step (ref v fresh)
    (err (E-error {e})) 
      step (ξ-err {F = ref✓⟦  ⟧□} {e = e})
progress pc (! M) (⊢deref ⊢M) μ ⊢μ =
  case progress pc M ⊢M μ ⊢μ of λ where
  (step M→M′)  step (ξ {F = !□} M→M′)
  (done v) 
    case canonical-ref ⊢M v of λ where
    (Ref-addr {n = n} {ℓ₁ = ℓ₁} eq _) 
      let  wf , V₁ , v₁ , eq , ⊢V₁  = ⊢μ n ℓ₁ eq in
      step (deref {v = v₁} eq)
    (Ref-proxy r i _)  step (deref-cast (ref-is-value r) i)
  (err (E-error {e}))  step (ξ-err {F = !□} {e = e})
progress pc (L := M) (⊢assign ⊢L ⊢M pc′≼ℓ) μ ⊢μ =
  step assign-static
progress pc (L :=? M) (⊢assign? ⊢L ⊢M) μ ⊢μ =
  case progress pc L ⊢L μ ⊢μ of λ where
  (step L→L′)  step (ξ {F = □:=? M} L→L′)
  (done v) 
    case canonical-ref ⊢L v of λ where
    (Ref-addr {n = n} {ℓ₁ = ℓ₁} eq sub) 
      let  V₁ , v₁ , eq₁ , ⊢V₁  = ⊢μ n ℓ₁ eq in
      case pc ≼? ℓ₁ of λ where
      (yes pc≼ℓ₁)  step (assign?-ok pc≼ℓ₁)
      (no  pc⋠ℓ₁)  step (assign?-fail pc⋠ℓ₁)
    (Ref-proxy r i (<:-ty _ (<:-ref (<:-ty _ _) _))) 
      step (assign?-cast (ref-is-value r) i)
  (err (E-error {e}))  step (ξ-err {F = □:=? M} {e = e})
progress pc (L :=✓ M) (⊢assign✓ ⊢L ⊢M pc≼ℓ) μ ⊢μ =
  case progress pc L ⊢L μ ⊢μ of λ where
  (step L→L′)  step (ξ {F = □:=✓ M} L→L′)
  (done v) 
    case progress pc M ⊢M μ ⊢μ of λ where
    (step M→M′)  step (ξ {F = (L :=✓□) v} M→M′)
    (done w) 
      case canonical-ref ⊢L v of λ where
      (Ref-addr eq _)  step (assign w)
      (Ref-proxy r i _) 
        case i of λ where
        (I-ref _ I-label I-label)  step (assign-cast (ref-is-value r) w i)
    (err (E-error {e}))  step (ξ-err {F = (L :=✓□) v} {e = e})
  (err (E-error {e}))  step (ξ-err {F = □:=✓ M} {e = e})
progress pc (prot  M) (⊢prot ⊢M) μ ⊢μ =
  case progress (pc  ) M ⊢M μ ⊢μ of λ where
  (step M→N)  step (prot-ctx M→N)
  (done v)  step (prot-val v)
  (err E-error)  step prot-err
progress pc (M  c ) (⊢cast ⊢M) μ ⊢μ =
  case progress pc M ⊢M μ ⊢μ of λ where
  (step M→M′)  step (ξ {F = □⟨ c } M→M′)
  (done v) 
    case active-or-inert c of λ where
    (inj₁ a) 
      case applycast-progress (⊢value-pc ⊢M v) v a of λ where
       N , M⟨c⟩↝N   step (cast v a M⟨c⟩↝N)
    (inj₂ i)  done (V-cast v i)
  (err (E-error {e}))  step (ξ-err {F = □⟨ c } {e = e})
progress pc (cast-pc g M) (⊢cast-pc ⊢M pc~g) μ ⊢μ =
  case progress pc M ⊢M μ ⊢μ of λ where
  (step M→N)  step (ξ {F = cast-pc g } M→N)
  (done v)  step (β-cast-pc v)
  (err E-error)  step (ξ-err {F = cast-pc g })
progress pc (error e) ⊢err μ ⊢μ = err E-error
progress pc M (⊢sub ⊢M _) μ ⊢μ = progress pc M ⊢M μ ⊢μ
progress pc M (⊢sub-pc ⊢M _) μ ⊢μ = progress pc M ⊢M μ ⊢μ


preserve :  {Σ gc pc M M′ A μ μ′}
   [] ; Σ ; gc ; pc  M  A
   Σ  μ
   l pc  gc
   M  μ  pc —→ M′  μ′
    ---------------------------------------------------------------
   ∃[ Σ′ ] (Σ′  Σ) × ([] ; Σ′ ; gc ; pc  M′  A) × (Σ′  μ′)
preserve ⊢plug ⊢μ pc≾gc (ξ {F = F} M→M′) =
  let  gc′ , B , pc≾gc′ , ⊢M , wt-plug  = plug-inversion ⊢plug pc≾gc
       Σ′ , Σ′⊇Σ , ⊢M′ , ⊢μ′   = preserve ⊢M ⊢μ pc≾gc′ M→M′ in
   Σ′ , Σ′⊇Σ , wt-plug ⊢M′ Σ′⊇Σ , ⊢μ′ 
preserve {Σ} ⊢M ⊢μ pc≾gc ξ-err =  Σ , ⊇-refl Σ , ⊢err , ⊢μ 
preserve {Σ} (⊢prot ⊢V) ⊢μ pc≾gc (prot-val v) =
   Σ , ⊇-refl Σ , ⊢value-pc (stamp-val-wt ⊢V v) (stamp-val-value v) , ⊢μ 
preserve (⊢prot ⊢M) ⊢μ pc≾gc (prot-ctx M→M′) =
  let  Σ′ , Σ′⊇Σ , ⊢M′ , ⊢μ′  = preserve ⊢M ⊢μ (consis-join-≾ pc≾gc ≾-refl) M→M′ in
   Σ′ , Σ′⊇Σ , ⊢prot ⊢M′ , ⊢μ′ 
preserve {Σ} ⊢M ⊢μ pc≾gc prot-err =  Σ , ⊇-refl Σ , ⊢err , ⊢μ 
preserve {Σ} (⊢app ⊢V ⊢M) ⊢μ pc≾gc (β v) =
  case canonical-fun ⊢V V-ƛ of λ where
  (Fun-ƛ ⊢N (<:-ty ℓ<:g (<:-fun gc⋎g<:gc′ A<:A′ B′<:B))) 
    let gc⋎ℓ<:gc⋎g = consis-join-<:ₗ <:ₗ-refl ℓ<:g
        gc⋎ℓ<:gc′  = <:ₗ-trans gc⋎ℓ<:gc⋎g gc⋎g<:gc′ in
     Σ , ⊇-refl Σ ,
      ⊢sub (⊢prot (substitution-pres (⊢sub-pc ⊢N gc⋎ℓ<:gc′) (⊢value-pc (⊢sub ⊢M A<:A′) v)))
           (stamp-<: B′<:B ℓ<:g) , ⊢μ 
preserve {Σ} (⊢if ⊢L ⊢M ⊢N) ⊢μ pc≾gc (β-if-true { = }) =
  case const-label-≼ ⊢L of λ where
   ℓ′ , refl , ℓ≼ℓ′  
    let gc⋎ℓ<:gc⋎ℓ′ = consis-join-<:ₗ <:ₗ-refl (<:-l ℓ≼ℓ′)
        A⋎ℓ<:A⋎ℓ′   = stamp-<: <:-refl (<:-l ℓ≼ℓ′) in
     Σ , ⊇-refl Σ , ⊢sub (⊢prot (⊢sub-pc ⊢M gc⋎ℓ<:gc⋎ℓ′)) A⋎ℓ<:A⋎ℓ′ , ⊢μ 
preserve {Σ} (⊢if ⊢L ⊢M ⊢N) ⊢μ pc≾gc (β-if-false { = }) =
  case const-label-≼ ⊢L of λ where
   ℓ′ , refl , ℓ≼ℓ′  
    let gc⋎ℓ<:gc⋎ℓ′ = consis-join-<:ₗ <:ₗ-refl (<:-l ℓ≼ℓ′)
        A⋎ℓ<:A⋎ℓ′   = stamp-<: <:-refl (<:-l ℓ≼ℓ′) in
     Σ , ⊇-refl Σ , ⊢sub (⊢prot (⊢sub-pc ⊢N gc⋎ℓ<:gc⋎ℓ′)) A⋎ℓ<:A⋎ℓ′ , ⊢μ 
preserve {Σ} (⊢let ⊢V ⊢N) ⊢μ pc≾gc (β-let v) =
   Σ , ⊇-refl Σ , substitution-pres ⊢N (⊢value-pc ⊢V v) , ⊢μ 
preserve {Σ} (⊢ref ⊢M pc′≼ℓ) ⊢μ (≾-l pc≼pc′) ref-static =
   Σ , ⊇-refl Σ , ⊢ref✓ ⊢M (≼-trans pc≼pc′ pc′≼ℓ) , ⊢μ 
preserve {Σ} (⊢ref✓ {T = T} {} ⊢V pc≼ℓ) ⊢μ pc≾gc (ref {n = n} {.} v fresh) =
   cons-Σ (a⟦   n) T Σ , ⊇-fresh (a⟦   n) T ⊢μ fresh ,
    ⊢addr (lookup-Σ-cons (a⟦   n) Σ) , ⊢μ-new (⊢value-pc ⊢V v) v ⊢μ fresh 
preserve {Σ} (⊢ref? ⊢M) ⊢μ pc≾gc (ref?-ok pc≼ℓ) =
   Σ , ⊇-refl Σ , ⊢ref✓ ⊢M pc≼ℓ , ⊢μ 
preserve {Σ} (⊢ref? ⊢M) ⊢μ pc≾gc (ref?-fail pc⋠ℓ) =
   Σ , ⊇-refl Σ , ⊢err , ⊢μ 
preserve {Σ} (⊢deref ⊢a) ⊢μ pc≾gc (deref { = } {ℓ₁} eq) =
  case canonical-ref ⊢a V-addr of λ where
  (Ref-addr {n = n} {g = l ℓ′} {ℓ₁ = ℓ₁} eq₁ (<:-ty (<:-l ℓ≼ℓ′) (<:-ref A′<:A A<:A′))) 
    case <:-antisym A′<:A A<:A′ of λ where
    refl 
      let  wf , V₁ , v₁ , eq′ , ⊢V₁  = ⊢μ n ℓ₁ eq₁ in
      case trans (sym eq) eq′ of λ where
      refl 
        let leq : ℓ₁  (ℓ₁  )  ℓ₁  ℓ′
            leq = subst      _) (sym ℓ₁⋎[ℓ₁⋎ℓ]≡ℓ₁⋎ℓ) (join-≼′ ≼-refl ℓ≼ℓ′) in
         Σ , ⊇-refl Σ ,  ⊢sub (⊢prot (⊢value-pc ⊢V₁ v₁)) (<:-ty (<:-l leq) <:ᵣ-refl) , ⊢μ 
preserve {Σ} (⊢assign ⊢L ⊢M pc′≼ℓ) ⊢μ (≾-l pc≼pc′) assign-static =
   Σ , ⊇-refl Σ , ⊢assign✓ ⊢L ⊢M (≼-trans pc≼pc′ pc′≼ℓ) , ⊢μ 
preserve {Σ} (⊢assign✓ { = ℓ′} ⊢a ⊢V pc≼ℓ′) ⊢μ pc≾gc (assign { = } {ℓ₁} v) =
 case canonical-ref ⊢a V-addr of λ where
 (Ref-addr eq (<:-ty (<:-l ℓ≼ℓ′) (<:-ref A′<:A A<:A′))) 
   case <:-antisym A′<:A A<:A′ of λ where
   refl   Σ , ⊇-refl Σ , ⊢const , ⊢μ-update (⊢value-pc ⊢V v) v ⊢μ eq 
preserve {Σ} (⊢assign? ⊢a ⊢M) ⊢μ pc≾gc (assign?-ok pc≼ℓ₁) =
 case canonical-ref ⊢a V-addr of λ where
 (Ref-addr eq₁ (<:-ty _ (<:-ref A′<:A A<:A′))) 
   case <:-antisym A′<:A A<:A′ of λ where
   refl   Σ , ⊇-refl Σ , ⊢assign✓ ⊢a ⊢M pc≼ℓ₁ , ⊢μ 
preserve {Σ} ⊢M ⊢μ pc≾gc (assign?-fail pc⋠ℓ₁) =
   Σ , ⊇-refl Σ , ⊢err , ⊢μ 
preserve {Σ} (⊢cast ⊢V) ⊢μ pc≾gc (cast v a V⟨c⟩↝M) =
   Σ , ⊇-refl Σ , applycast-pres (⊢value-pc ⊢V v) v a V⟨c⟩↝M , ⊢μ 
preserve {Σ} {gc} {pc} (⊢if {A = A} {L} {M} {N} ⊢L ⊢M ⊢N) ⊢μ pc≾gc (if-cast-true i) with i
... | (I-base-inj (cast (` Bool of l ℓ′) (` Bool of ) p _)) =
  case canonical-const ⊢L (V-cast V-const i) of λ where
  (Const-inj { = } ℓ≼ℓ′) 
    let ⊢M† : [] ; Σ ;  ; pc    M  A
        ⊢M† = subst    [] ; Σ ;  ; pc    M  A) g⋎̃⋆≡⋆ ⊢M in
    let A⋎ℓ<:A⋎ℓ′ = stamp-<: <:-refl (<:-l ℓ≼ℓ′) in
     Σ , ⊇-refl Σ , ⊢cast (⊢sub (⊢prot (⊢cast-pc ⊢M† ~⋆)) A⋎ℓ<:A⋎ℓ′), ⊢μ 
preserve {Σ} {gc} {pc} (⊢if {A = A} {L} {M} {N} ⊢L ⊢M ⊢N) ⊢μ pc≾gc (if-cast-false i) with i
... | (I-base-inj (cast (` Bool of l ℓ′) (` Bool of ) p _)) =
  case canonical-const ⊢L (V-cast V-const i) of λ where
  (Const-inj { = } ℓ≼ℓ′) 
    let ⊢N† : [] ; Σ ;  ; pc    N  A
        ⊢N† = subst    [] ; Σ ;  ; pc    N  A) g⋎̃⋆≡⋆ (⊢N {pc  }) in
    let A⋎ℓ<:A⋎ℓ′ = stamp-<: <:-refl (<:-l ℓ≼ℓ′) in
     Σ , ⊇-refl Σ , ⊢cast (⊢sub (⊢prot (⊢cast-pc ⊢N† ~⋆)) A⋎ℓ<:A⋎ℓ′) , ⊢μ 
preserve {Σ} {gc} {pc} ⊢M ⊢μ pc≾gc (fun-cast {V} {W} {pc = pc} v w i) =
   Σ , ⊇-refl Σ , elim-fun-proxy-wt ⊢M v w i , ⊢μ 
preserve {Σ} (⊢deref {A = A′} ⊢M) ⊢μ pc≾gc (deref-cast v i) =
  case canonical-ref ⊢M (V-cast v i) of λ where
  (Ref-proxy r _ (<:-ty g₂<:g (<:-ref B<:A′ A′<:B))) 
    case <:-antisym B<:A′ A′<:B of λ where
    refl 
       Σ , ⊇-refl Σ ,
        ⊢sub (⊢cast (⊢deref (ref-wt r))) (stamp-<: <:-refl g₂<:g) , ⊢μ 
preserve {Σ} ⊢M ⊢μ pc≾gc (assign?-cast v i) =
   Σ , ⊇-refl Σ , elim-ref-proxy-wt ⊢M v i unchecked , ⊢μ 
preserve {Σ} {gc} ⊢M ⊢μ pc≾gc (assign-cast v w i) =
   Σ , ⊇-refl Σ , elim-ref-proxy-wt ⊢M v i   checked , ⊢μ 
preserve {Σ} (⊢cast-pc ⊢V _) ⊢μ pc≾gc (β-cast-pc v) =
   Σ , ⊇-refl Σ , ⊢value-pc ⊢V v , ⊢μ 
preserve (⊢sub ⊢M A<:B) ⊢μ pc≾gc M→M′ =
  let  Σ′ , Σ′⊇Σ , ⊢M′ , ⊢μ′  = preserve ⊢M ⊢μ pc≾gc M→M′ in
   Σ′ , Σ′⊇Σ , ⊢sub ⊢M′ A<:B , ⊢μ′ 
preserve (⊢sub-pc ⊢M gc<:gc′) ⊢μ pc≾gc M→M′ =
  let  Σ′ , Σ′⊇Σ , ⊢M′ , ⊢μ′  = preserve ⊢M ⊢μ (≾-<: pc≾gc gc<:gc′) M→M′ in
   Σ′ , Σ′⊇Σ , ⊢sub-pc ⊢M′ gc<:gc′ , ⊢μ′