------------------------------------------------------------------------
-- Extra lemmas about substitutions
------------------------------------------------------------------------

{-# OPTIONS --safe --without-K #-}

module Data.Fin.Substitution.ExtraLemmas where

open import Data.Fin using (Fin; zero; suc)
open import Data.Fin.Substitution
open import Data.Fin.Substitution.Lemmas
open import Data.Nat using (ℕ; zero; suc; _+_)
open import Data.Vec using (Vec; _∷_; lookup; map)
open import Data.Vec.Properties using (map-cong; map-∘; lookup-map)
open import Data.Vec.Relation.Unary.All hiding (lookup; map)
open import Function using (_∘_; _$_; flip)
open import Level using (_⊔_) renaming (zero to lzero; suc to lsuc)
open import Relation.Binary.Construct.Closure.ReflexiveTransitive
  using (Star; ε; _◅_; _▻_)
open import Relation.Binary.PropositionalEquality as PropEq hiding (subst)
open PropEq.≡-Reasoning
open import Relation.Unary using (Pred)

-- Simple extension of substitutions.
--
-- FIXME: this should go into Data.Fin.Substitution.
record Extension {ℓ} (T : Pred ℕ ℓ) : Set ℓ where
  infixr 5 _/∷_

  field weaken : ∀ {n} → T n → T (suc n)  -- Weakens Ts.

  -- Iterated weakening of types.
  weaken⋆ : ∀ m {n} → T n → T (m + n)
  weaken⋆ zero    t = t
  weaken⋆ (suc m) t = weaken (weaken⋆ m t)

  -- Extension.
  _/∷_ : ∀ {m n} → T (suc n) → Sub T m n → Sub T (suc m) (suc n)
  t /∷ ρ = t ∷ map weaken ρ

-- Helper module
module SimpleExt {ℓ} {T : Pred ℕ ℓ} (simple : Simple T) where
  open Simple simple public

  extension : Extension T
  extension = record { weaken = weaken }
  open Extension extension public hiding (weaken)

-- An generalized version of Data.Fin.Lemmas.Lemmas₀
--
-- FIXME: this should go into Data.Fin.Substitution.Lemmas.
module ExtLemmas₀ {ℓ} {T : Pred ℕ ℓ} (lemmas₀ : Lemmas₀ T) where
  open Data.Fin using (lift; raise)

  open Lemmas₀   lemmas₀ public hiding (lookup-map-weaken-↑⋆)
  open SimpleExt simple

  -- A generalized variant of Lemmas₀.lookup-map-weaken-↑⋆.
  lookup-map-weaken-↑⋆ : ∀ {m n} k x {ρ : Sub T m n} {t} →
                         lookup (map weaken ρ ↑⋆ k) x ≡
                         lookup ((t /∷ ρ) ↑⋆ k) (lift k suc x)
  lookup-map-weaken-↑⋆ zero    x               = refl
  lookup-map-weaken-↑⋆ (suc k) zero            = refl
  lookup-map-weaken-↑⋆ (suc k) (suc x) {ρ} {t} = begin
      lookup (map weaken (map weaken ρ ↑⋆ k)) x
    ≡⟨ lookup-map x weaken (map weaken ρ ↑⋆ k) ⟩
      weaken (lookup (map weaken ρ ↑⋆ k) x)
    ≡⟨ cong weaken (lookup-map-weaken-↑⋆ k x) ⟩
      weaken (lookup ((t /∷ ρ) ↑⋆ k) (lift k suc x))
    ≡⟨ sym (lookup-map (lift k suc x) weaken ((t /∷ ρ) ↑⋆ k)) ⟩
      lookup (map weaken ((t /∷ ρ) ↑⋆ k)) (lift k suc x)
    ∎

-- A version of Data.Fin.Lemmas.Lemmas₁ with additional lemmas.
--
-- FIXME: this should go into Data.Fin.Substitution.Lemmas.
module ExtLemmas₁ {ℓ} {T : Pred ℕ ℓ} (lemmas₁ : Lemmas₁ T) where
  open Data.Fin using (raise; fromℕ; lift)

  open Lemmas₁ lemmas₁
  open Simple simple

  lookup-wk⋆ : ∀ {n} (x : Fin n) k → lookup (wk⋆ k) x ≡ var (raise k x)
  lookup-wk⋆ x zero    = lookup-id x
  lookup-wk⋆ x (suc k) = lookup-map-weaken x {_} {wk⋆ k} (lookup-wk⋆ x k)

  lookup-raise-↑⋆ : ∀ k {m n} x {y} {σ : Sub T m n} →
                    lookup  σ                x  ≡ var          y →
                    lookup (σ ↑⋆ k) (raise k x) ≡ var (raise k y)
  lookup-raise-↑⋆ zero    x         hyp = hyp
  lookup-raise-↑⋆ (suc k) x {y} {σ} hyp =
    lookup-map-weaken (raise k x) {_} {σ ↑⋆ k} (lookup-raise-↑⋆ k x hyp)

-- A generalized version of Data.Fin.Lemmas.Lemmas₄
--
-- FIXME: this should go into Data.Fin.Substitution.Lemmas.
module ExtLemmas₄ {ℓ} {T : Pred ℕ ℓ} (lemmas₄ : Lemmas₄ T) where
  open Data.Fin using (lift; raise)

  open Lemmas₄    lemmas₄ public hiding (⊙-wk; wk-commutes)
  open Lemmas₃    lemmas₃        using (lookup-wk-↑⋆-⊙; /✶-↑✶′)
  open SimpleExt  simple         using (_/∷_; weaken⋆)
  open ExtLemmas₀ lemmas₀        using (lookup-map-weaken-↑⋆)

  ⊙-wk-↑⋆ : ∀ {m n} {ρ : Sub T m n} {t} k →
            ρ ↑⋆ k ⊙ wk ↑⋆ k ≡ wk ↑⋆ k ⊙ (t /∷ ρ) ↑⋆ k
  ⊙-wk-↑⋆ {ρ = ρ} {t} k = sym (begin
    wk ↑⋆ k ⊙ (t /∷ ρ) ↑⋆ k   ≡⟨ lemma ⟩
    map weaken ρ ↑⋆ k         ≡⟨ cong (λ ρ′ → ρ′ ↑⋆ k) map-weaken ⟩
    (ρ ⊙ wk) ↑⋆ k             ≡⟨ ↑⋆-distrib k ⟩
    ρ ↑⋆ k ⊙ wk ↑⋆ k          ∎)
    where
    lemma = extensionality λ x → begin
        lookup (wk ↑⋆ k ⊙ (t /∷ ρ) ↑⋆ k) x
      ≡⟨ lookup-wk-↑⋆-⊙ k ⟩
        lookup ((t /∷ ρ) ↑⋆ k) (lift k suc x)
      ≡⟨ sym (lookup-map-weaken-↑⋆ k x) ⟩
        lookup (map weaken ρ ↑⋆ k) x
      ∎

  ⊙-wk : ∀ {m n} {ρ : Sub T m n} {t} → ρ ⊙ wk ≡ wk ⊙ (t /∷ ρ)
  ⊙-wk = ⊙-wk-↑⋆ zero

  wk-⊙-∷ : ∀ {n m} t {ρ : Sub T m n} → wk ⊙ (t ∷ ρ) ≡ ρ
  wk-⊙-∷ t {ρ} = extensionality λ x → begin
    lookup (wk ⊙ (t ∷ ρ)) x  ≡⟨ lookup-wk-↑⋆-⊙ zero {x} ⟩
    lookup (t ∷ ρ) (suc x)   ≡⟨⟩
    lookup ρ       x         ∎

  wk-↑⋆-commutes : ∀ {m n} {ρ : Sub T m n} {t′} k t →
                   t / ρ ↑⋆ k / wk ↑⋆ k ≡ t / wk ↑⋆ k / (t′ /∷ ρ) ↑⋆ k
  wk-↑⋆-commutes {ρ = ρ} {t} k =
    /✶-↑✶′ (ε ▻ ρ ▻ wk) (ε ▻ wk ▻ (t /∷ ρ)) ⊙-wk-↑⋆ k

  wk-commutes : ∀ {m n} {ρ : Sub T m n} {t′} t →
                t / ρ / wk ≡ t / wk / (t′ /∷ ρ)
  wk-commutes = wk-↑⋆-commutes zero

  raise-/-↑⋆ : ∀ {m n} k x {ρ : Sub T m n} →
               var (raise k x) / ρ ↑⋆ k ≡ var x / ρ / wk⋆ k
  raise-/-↑⋆ zero    x {ρ} = sym (id-vanishes (var x / ρ))
  raise-/-↑⋆ (suc k) x {ρ} = begin
    var (suc (raise k x)) / ρ ↑⋆ suc k   ≡⟨ suc-/-↑ (raise k x) ⟩
    var (raise k x) / ρ ↑⋆ k / wk        ≡⟨ cong (_/ wk) (raise-/-↑⋆ k x) ⟩
    var x / ρ / wk⋆ k / wk               ≡⟨ sym (/-⊙ (var x / ρ)) ⟩
    var x / ρ / wk⋆ k ⊙ wk           ≡⟨ cong (var x / ρ /_) (sym map-weaken) ⟩
    var x / ρ / wk⋆ (suc k)          ∎

  /-wk⋆ : ∀ {n} k {t : T n} → t / wk⋆ k ≡ weaken⋆ k t
  /-wk⋆ zero    {t} = id-vanishes t
  /-wk⋆ (suc k) {t} = begin
    t / map weaken (wk⋆ k)   ≡⟨ cong (t /_) map-weaken ⟩
    t / wk⋆ k ⊙ wk           ≡⟨ /-⊙ t ⟩
    t / wk⋆ k / wk           ≡⟨ /-wk ⟩
    weaken (t / wk⋆ k)       ≡⟨ cong weaken (/-wk⋆ k) ⟩
    weaken (weaken⋆ k t)     ∎

  -- Weakening commutes with substitution.

  weaken-/ : ∀ {m n} {ρ : Sub T m n} {t′} t →
             weaken (t / ρ) ≡ weaken t / (t′ /∷ ρ)
  weaken-/ {ρ = ρ} {t′} t = begin
    weaken (t / ρ)         ≡⟨ sym /-wk ⟩
    t / ρ / wk             ≡⟨ wk-commutes t ⟩
    t / wk / (t′ /∷ ρ)     ≡⟨ cong₂ _/_ /-wk refl ⟩
    weaken t / (t′ /∷ ρ)   ∎

  weaken-/-∷ : ∀ {n m} {t′} {ρ : Sub T m n} t → weaken t / (t′ ∷ ρ) ≡ t / ρ
  weaken-/-∷ {_} {_} {t′} {ρ} t = begin
    weaken t / (t′ ∷ ρ)   ≡⟨ cong (_/ (t′ ∷ ρ)) (sym /-wk) ⟩
    t / wk / (t′ ∷ ρ)     ≡⟨ sym (/-⊙ t) ⟩
    t / (wk ⊙ (t′ ∷ ρ))   ≡⟨ cong (t /_) (wk-⊙-∷ t′) ⟩
    t / ρ                 ∎

-- A generalize version of Data.Fin.Lemmas.AppLemmas
--
-- FIXME: this should go into Data.Fin.Substitution.Lemmas.
module ExtAppLemmas {ℓ₁ ℓ₂} {T₁ : Pred ℕ ℓ₁} {T₂ : Pred ℕ ℓ₂}
                    (appLemmas : AppLemmas T₁ T₂) where

  open AppLemmas appLemmas public hiding (wk-commutes)
  open SimpleExt simple           using (_/∷_)
  private module L₄ = ExtLemmas₄ lemmas₄
  open L₄ public using (wk-⊙-∷)

  wk-↑⋆-commutes : ∀ {m n} {ρ : Sub T₂ m n} {t′} k t →
                   t / ρ ↑⋆ k / wk ↑⋆ k ≡ t / wk ↑⋆ k / (t′ /∷ ρ) ↑⋆ k
  wk-↑⋆-commutes {ρ = ρ} {t} k =
    ⨀→/✶ (ε ▻ ρ ↑⋆ k ▻ wk ↑⋆ k) (ε ▻ wk ↑⋆ k ▻ (t /∷ ρ) ↑⋆ k) (L₄.⊙-wk-↑⋆ k)

  wk-commutes : ∀ {m n} {ρ : Sub T₂ m n} {t′} t →
                t / ρ / wk ≡ t / wk / (t′ /∷ ρ)
  wk-commutes = wk-↑⋆-commutes zero

-- Lemmas relating T₃ substitutions in T₁ and T₂.
record LiftAppLemmas {ℓ₁ ℓ₂ ℓ₃}
                     (T₁ : Pred ℕ ℓ₁) (T₂ : Pred ℕ ℓ₂) (T₃ : Pred ℕ ℓ₃)
                     : Set (ℓ₁ ⊔ ℓ₂ ⊔ ℓ₃) where
  field
    lift          : ∀ {n} → T₃ n → T₂ n
    application₁₃ : Application T₁ T₃
    application₂₃ : Application T₂ T₃
    lemmas₂       : Lemmas₄     T₂
    lemmas₃       : Lemmas₄     T₃

  private
    module L₂ = ExtLemmas₄  lemmas₂
    module L₃ = ExtLemmas₄  lemmas₃
    module A₁ = Application application₁₃
    module A₂ = Application application₂₃

  field
    -- Lifting commutes with application of T₃ substitutions.
    lift-/ : ∀ {m n} t {σ : Sub T₃ m n} → lift (t L₃./ σ) ≡ lift t A₂./ σ

    -- Lifting preserves variables.
    lift-var : ∀ {n} (x : Fin n) → lift (L₃.var x) ≡ L₂.var x

    -- Sequences of T₃ substitutions are equivalent when applied to
    -- T₁s if they are equivalent when applied to T₂ variables.
    /✶-↑✶ :
      ∀ {m n} (σs₁ σs₂ : Subs T₃ m n) →
      (∀ k x → L₂.var x A₂./✶ σs₁ L₃.↑✶ k ≡ L₂.var x A₂./✶ σs₂ L₃.↑✶ k) →
       ∀ k t → t        A₁./✶ σs₁ L₃.↑✶ k ≡ t        A₁./✶ σs₂ L₃.↑✶ k

  lift-lookup-⊙ : ∀ {m n k} x {σ₁ : Sub T₃ m n} {σ₂ : Sub T₃ n k} →
                  lift (lookup (σ₁ L₃.⊙ σ₂) x) ≡ lift (lookup σ₁ x) A₂./ σ₂
  lift-lookup-⊙ x {σ₁} {σ₂} = begin
    lift (lookup (σ₁ L₃.⊙ σ₂) x)   ≡⟨ cong lift (L₃.lookup-⊙ x {σ₁}) ⟩
    lift (lookup σ₁ x L₃./ σ₂)     ≡⟨ lift-/ (lookup σ₁ x) ⟩
    lift (lookup σ₁ x) A₂./ σ₂     ∎

  lift-lookup-⨀ : ∀ {m n} x (σs : Subs T₃ m n) →
                  lift (lookup (L₃.⨀ σs) x) ≡ L₂.var x A₂./✶ σs
  lift-lookup-⨀ x ε = begin
    lift (lookup L₃.id x)    ≡⟨ cong lift (L₃.lookup-id x) ⟩
    lift (L₃.var x)          ≡⟨ lift-var x ⟩
    L₂.var x                 ∎
  lift-lookup-⨀ x (σ ◅ ε) = begin
    lift (lookup σ x)        ≡⟨ cong lift (sym L₃.var-/) ⟩
    lift (L₃.var x L₃./ σ)   ≡⟨ lift-/ _ ⟩
    lift (L₃.var x) A₂./ σ   ≡⟨ cong₂ A₂._/_ (lift-var x) refl ⟩
    L₂.var x A₂./ σ          ∎
  lift-lookup-⨀ x (σ ◅ (σ′ ◅ σs′)) = begin
      lift (lookup (L₃.⨀ σs L₃.⊙ σ) x)
    ≡⟨ lift-lookup-⊙ x {L₃.⨀ σs} ⟩
      lift (lookup (L₃.⨀ σs) x) A₂./ σ
    ≡⟨ cong₂ A₂._/_ (lift-lookup-⨀ x (σ′ ◅ σs′)) refl ⟩
      L₂.var x A₂./✶ σs A₂./ σ
    ∎
    where σs = σ′ ◅ σs′

  -- Sequences of T₃ substitutions are equivalent when applied to
  -- T₁s if they are equivalent when applied as composites.
  /✶-↑✶′ : ∀ {m n} (σs₁ σs₂ : Subs T₃ m n) →
           (∀ k → L₃.⨀ (σs₁ L₃.↑✶ k) ≡ L₃.⨀ (σs₂ L₃.↑✶ k)) →
            ∀ k t → t A₁./✶ σs₁ L₃.↑✶ k ≡ t A₁./✶ σs₂ L₃.↑✶ k
  /✶-↑✶′ σs₁ σs₂ hyp = /✶-↑✶ σs₁ σs₂ (λ k x → begin
      L₂.var x A₂./✶ σs₁ L₃.↑✶ k
    ≡⟨ sym (lift-lookup-⨀ x (σs₁ L₃.↑✶ k)) ⟩
      lift (lookup (L₃.⨀ (σs₁ L₃.↑✶ k)) x)
    ≡⟨ cong (λ σ → lift (lookup σ x)) (hyp k) ⟩
      lift (lookup (L₃.⨀ (σs₂ L₃.↑✶ k)) x)
    ≡⟨ lift-lookup-⨀ x (σs₂ L₃.↑✶ k) ⟩
      L₂.var x A₂./✶ σs₂ L₃.↑✶ k
    ∎)

  -- Derived lemmas about applications of T₃ substitutions to T₁s.
  appLemmas : AppLemmas T₁ T₃
  appLemmas = record
    { application = application₁₃
    ; lemmas₄     = lemmas₃
    ; id-vanishes = /✶-↑✶′ (ε ▻ L₃.id) ε L₃.id-↑⋆ 0
    ; /-⊙         = /✶-↑✶′ (ε ▻ _ L₃.⊙ _) (ε ▻ _ ▻ _) L₃.↑⋆-distrib 0
    }
  open ExtAppLemmas appLemmas public
    hiding (application; lemmas₂; lemmas₃; var; weaken; subst; simple)

-- Lemmas relating T₂ and T₃ substitutions in T₁.

record LiftSubLemmas {ℓ₁ ℓ₂ ℓ₃}
                     (T₁ : Pred ℕ ℓ₁) (T₂ : Pred ℕ ℓ₂) (T₃ : Pred ℕ ℓ₃)
                     : Set (ℓ₁ ⊔ ℓ₂ ⊔ ℓ₃) where
  field
    application₁₂ : Application   T₁ T₂
    liftAppLemmas : LiftAppLemmas T₁ T₂ T₃

  open LiftAppLemmas liftAppLemmas hiding (/✶-↑✶; /-wk)
  private
    module L₃  = ExtLemmas₄  lemmas₃
    module L₂  = ExtLemmas₄  lemmas₂
    module A₁₂ = Application application₁₂
    module A₁₃ = Application (AppLemmas.application appLemmas)
    module A₂₃ = Application application₂₃

  field
    -- Weakening commutes with lifting.
    weaken-lift : ∀ {n} (t : T₃ n) → L₂.weaken (lift t) ≡ lift (L₃.weaken t)

    -- Applying a composition of T₂ substitutions to T₁s
    -- corresponds to two consecutive applications.
    /-⊙₂ : ∀ {m n k} {σ₁ : Sub T₂ m n} {σ₂ : Sub T₂ n k} t →
           t A₁₂./ σ₁ L₂.⊙ σ₂ ≡ t A₁₂./ σ₁ A₁₂./ σ₂

    -- Sequences of T₃ substitutions are equivalent to T₂
    -- substitutions when applied to T₁s if they are equivalent when
    -- applied to variables.
    /✶-↑✶₁ :
      ∀ {m n} (σs₁ : Subs T₃ m n) (σs₂ : Subs T₂ m n) →
      (∀ k x → L₂.var x A₂₃./✶ σs₁ ↑✶ k ≡ L₂.var x L₂./✶  σs₂ L₂.↑✶ k) →
       ∀ k t → t        A₁₃./✶ σs₁ ↑✶ k ≡ t        A₁₂./✶ σs₂ L₂.↑✶ k

    -- Sequences of T₃ substitutions are equivalent to T₂
    -- substitutions when applied to T₂s if they are equivalent when
    -- applied to variables.
    /✶-↑✶₂ :
      ∀ {m n} (σs₁ : Subs T₃ m n) (σs₂ : Subs T₂ m n) →
      (∀ k x → L₂.var x A₂₃./✶ σs₁ ↑✶ k ≡ L₂.var x L₂./✶ σs₂ L₂.↑✶ k) →
       ∀ k t → t        A₂₃./✶ σs₁ ↑✶ k ≡ t        L₂./✶ σs₂ L₂.↑✶ k

  -- Lifting of T₃ substitutions to T₂ substitutions.
  liftSub : ∀ {m n} → Sub T₃ m n → Sub T₂ m n
  liftSub σ = map lift σ

  -- The two types of lifting commute.
  liftSub-↑⋆ : ∀ {m n} (σ : Sub T₃ m n) k →
               liftSub σ L₂.↑⋆ k ≡ liftSub (σ ↑⋆ k)
  liftSub-↑⋆ σ zero    = refl
  liftSub-↑⋆ σ (suc k) = cong₂ _∷_ (sym (lift-var _)) (begin
    map L₂.weaken (liftSub σ L₂.↑⋆ k)   ≡⟨ cong (map _) (liftSub-↑⋆ σ k) ⟩
    map L₂.weaken (map lift (σ ↑⋆ k))   ≡⟨ sym (map-∘ _ _ _) ⟩
    map (L₂.weaken ∘ lift) (σ ↑⋆ k)     ≡⟨ map-cong weaken-lift _ ⟩
    map (lift ∘ L₃.weaken)  (σ ↑⋆ k)    ≡⟨ map-∘ _ _ _ ⟩
    map lift (map L₃.weaken (σ ↑⋆ k))   ∎)

  -- The identity substitutions are equivalent up to lifting.
  liftSub-id : ∀ {n} → liftSub (L₃.id {n}) ≡ L₂.id {n}
  liftSub-id {zero}  = refl
  liftSub-id {suc n} = begin
    liftSub (L₃.id L₃.↑)  ≡⟨ sym (liftSub-↑⋆ L₃.id 1) ⟩
    liftSub L₃.id L₂.↑    ≡⟨ cong L₂._↑ liftSub-id ⟩
    L₂.id ∎

  -- Weakening is equivalent up to lifting.
  liftSub-wk⋆ : ∀ k {n} → liftSub (L₃.wk⋆ k {n}) ≡ L₂.wk⋆ k {n}
  liftSub-wk⋆ zero    = liftSub-id
  liftSub-wk⋆ (suc k) = begin
    liftSub (map L₃.weaken (L₃.wk⋆ k))   ≡⟨ sym (map-∘ _ _ _) ⟩
    map (lift ∘ L₃.weaken) (L₃.wk⋆ k)    ≡⟨ sym (map-cong weaken-lift _) ⟩
    map (L₂.weaken ∘ lift) (L₃.wk⋆ k)    ≡⟨ map-∘ _ _ _ ⟩
    map L₂.weaken (liftSub (L₃.wk⋆ k))   ≡⟨ cong (map _) (liftSub-wk⋆ k) ⟩
    map L₂.weaken (L₂.wk⋆ k)             ∎

  -- Weakening is equivalent up to lifting.
  liftSub-wk : ∀ {n} → liftSub (L₃.wk {n}) ≡ L₂.wk {n}
  liftSub-wk = liftSub-wk⋆ 1

  -- Single variable substitution is equivalent up to lifting.
  liftSub-sub : ∀ {n} (t : T₃ n) → liftSub (L₃.sub t) ≡ L₂.sub (lift t)
  liftSub-sub t = cong₂ _∷_ refl liftSub-id

  -- Lifting commutes with application to variables.
  var-/-liftSub-↑⋆ : ∀ {m n} (σ : Sub T₃ m n) k x →
                     L₂.var x A₂₃./ σ ↑⋆ k ≡ L₂.var x L₂./ liftSub σ L₂.↑⋆ k
  var-/-liftSub-↑⋆ σ k x = begin
      L₂.var x A₂₃./ σ ↑⋆ k
    ≡⟨ cong₂ A₂₃._/_ (sym (lift-var x)) refl ⟩
      lift (L₃.var x) A₂₃./ σ ↑⋆ k
    ≡⟨ sym (lift-/ _) ⟩
      lift (L₃.var x L₃./ σ ↑⋆ k)
    ≡⟨ cong lift L₃.var-/ ⟩
      lift (lookup (σ ↑⋆ k) x)
    ≡⟨ sym (lookup-map x lift (σ ↑⋆ k)) ⟩
      lookup (liftSub (σ ↑⋆ k)) x
    ≡⟨ sym L₂.var-/ ⟩
      L₂.var x L₂./ liftSub (σ ↑⋆ k)
    ≡⟨ cong (L₂._/_ (L₂.var x)) (sym (liftSub-↑⋆ σ k)) ⟩
      L₂.var x L₂./ liftSub σ L₂.↑⋆ k
    ∎

  -- Lifting commutes with application.

  /-liftSub-↑⋆ : ∀ {m n} k t {σ : Sub T₃ m n} →
                 t A₁₃./ σ ↑⋆ k ≡ t A₁₂./ liftSub σ L₂.↑⋆ k
  /-liftSub-↑⋆ k t {σ} =
    /✶-↑✶₁ (ε ▻ σ) (ε ▻ liftSub σ) (var-/-liftSub-↑⋆ σ) k t

  /-liftSub : ∀ {m n} t {σ : Sub T₃ m n} → t A₁₃./ σ ≡ t A₁₂./ liftSub σ
  /-liftSub = /-liftSub-↑⋆ zero

  -- Weakening is equivalent up to choice of application.

  /-wk-↑⋆ : ∀ {n} k {t : T₁ (k + n)} →
            t A₁₃./ L₃.wk ↑⋆ k ≡ t A₁₂./ L₂.wk L₂.↑⋆ k
  /-wk-↑⋆ k {t = t} = begin
      t A₁₃./ L₃.wk ↑⋆ k
    ≡⟨ /-liftSub-↑⋆ k t ⟩
      t A₁₂./ (liftSub L₃.wk) L₂.↑⋆ k
    ≡⟨ cong (λ σ → t A₁₂./ σ L₂.↑⋆ k) liftSub-wk ⟩
      t A₁₂./ L₂.wk L₂.↑⋆ k
    ∎

  /-wk : ∀ {n} {t : T₁ n} → t A₁₃./ L₃.wk ≡ t A₁₂./ L₂.wk
  /-wk = /-wk-↑⋆ zero

  -- Single-variable substitution is equivalent up to choice of
  -- application.

  /-sub-↑⋆ : ∀ {n} k t (s : T₃ n) →
             t A₁₃./ L₃.sub s ↑⋆ k ≡ t A₁₂./ L₂.sub (lift s) L₂.↑⋆ k
  /-sub-↑⋆ k t s = begin
      t A₁₃./ L₃.sub s ↑⋆ k
    ≡⟨ /-liftSub-↑⋆ k t ⟩
      t A₁₂./ liftSub (L₃.sub s) L₂.↑⋆ k
    ≡⟨ cong (λ σ → t A₁₂./ σ L₂.↑⋆ k) (liftSub-sub s) ⟩
      t A₁₂./ L₂.sub (lift s) L₂.↑⋆ k
    ∎

  /-sub : ∀ {n} t (s : T₃ n) →
          t A₁₃./ L₃.sub s ≡ t A₁₂./ L₂.sub (lift s)
  /-sub = /-sub-↑⋆ zero

  -- Lifting commutes with application.
  /-sub-↑ : ∀ {m n} t s (σ : Sub T₃ m n) →
            t A₁₂./ L₂.sub s A₁₃./ σ ≡ (t A₁₃./ σ ↑) A₁₂./ L₂.sub (s A₂₃./ σ)
  /-sub-↑ t s σ = begin
      t A₁₂./ L₂.sub s A₁₃./ σ
    ≡⟨ /-liftSub _ ⟩
      t A₁₂./ L₂.sub s A₁₂./ liftSub σ
    ≡⟨ sym (/-⊙₂ t) ⟩
      t A₁₂./ (L₂.sub s L₂.⊙ liftSub σ)
    ≡⟨ cong₂ A₁₂._/_ refl (L₂.sub-⊙ s) ⟩
      t A₁₂./ (liftSub σ L₂.↑ L₂.⊙ L₂.sub (s L₂./ liftSub σ))
    ≡⟨ /-⊙₂ t ⟩
      t A₁₂./ liftSub σ L₂.↑ A₁₂./ L₂.sub (s L₂./ liftSub σ)
    ≡⟨ cong₂ (A₁₂._/_ ∘ A₁₂._/_ t) (liftSub-↑⋆ _ 1)
             (cong L₂.sub (sym (/-liftSub₂ s))) ⟩
      t A₁₂./ liftSub (σ ↑) A₁₂./ L₂.sub (s A₂₃./ σ)
    ≡⟨ cong₂ A₁₂._/_ (sym (/-liftSub t)) refl ⟩
      t A₁₃./ σ ↑ A₁₂./ L₂.sub (s A₂₃./ σ)
    ∎
    where
      /-liftSub₂ : ∀ {m n} s {σ : Sub T₃ m n} →
                   s A₂₃./ σ ≡ s L₂./ liftSub σ
      /-liftSub₂ s {σ} = /✶-↑✶₂ (ε ▻ σ) (ε ▻ liftSub σ)
                                (var-/-liftSub-↑⋆ σ) zero s

-- Lemmas relating weakening of T₁ to T₂ substitutions in T₁.

record WeakenLemmas {ℓ₁ ℓ₂} (T₁ : Pred ℕ ℓ₁) (T₂ : Pred ℕ ℓ₂)
                    : Set (ℓ₁ ⊔ ℓ₂) where
  field
    weaken : ∀ {n} → T₁ n → T₁ (suc n)    -- Weakening of T₁s.

    -- Lemmas about application of T₂ substitutions in T₁

    appLemmas : AppLemmas T₁ T₂

  open ExtAppLemmas appLemmas hiding (/-wk; weaken; _⊙_)
  open Lemmas₄ lemmas₄ using (_⊙_) renaming (weaken to weaken′)

  -- A lemma relating weakening to the wk substitution

  field /-wk : ∀ {n} {t : T₁ n} → t / wk ≡ weaken t

  extension : Extension T₁
  extension = record { weaken = weaken }
  open Extension extension public using (weaken⋆)

  -- A generalized version of wk-sub-vanishes for T₁s.

  weaken-sub : ∀ {n t′} → (t : T₁ n) → weaken t / sub t′ ≡ t
  weaken-sub t = begin
    weaken t / sub _   ≡⟨ cong₂ _/_ (sym /-wk) refl ⟩
    t / wk / sub _     ≡⟨ wk-sub-vanishes t ⟩
    t                  ∎

  -- A variants of /-wk⋆ for T₁s.

  /-wk⋆ : ∀ {n} k {t : T₁ n} → t / wk⋆ k ≡ weaken⋆ k t
  /-wk⋆ zero    {t} = id-vanishes t
  /-wk⋆ (suc k) {t} = begin
    t / map weaken′ (wk⋆ k)   ≡⟨ /-weaken t ⟩
    t / wk⋆ k / wk            ≡⟨ /-wk ⟩
    weaken (t / wk⋆ k)        ≡⟨ cong weaken (/-wk⋆ k) ⟩
    weaken (weaken⋆ k t)      ∎

  open SimpleExt simple public using (_/∷_)

  -- Weakening commutes with substitution.

  weaken-/ : ∀ {m n} {σ : Sub T₂ m n} {t′} t →
             weaken (t / σ) ≡ weaken t / (t′ /∷ σ)
  weaken-/ {σ = σ} {t′} t = begin
    weaken (t / σ)         ≡⟨ sym /-wk ⟩
    t / σ / wk             ≡⟨ wk-commutes t ⟩
    t / wk / (t′ /∷ σ)     ≡⟨ cong₂ _/_ /-wk refl ⟩
    weaken t / (t′ /∷ σ)   ∎

  weaken-/-∷ : ∀ {n m} {t′} {σ : Sub T₂ m n} (t : T₁ m) →
               weaken t / (t′ ∷ σ) ≡ t / σ
  weaken-/-∷ {_} {_} {t′} {σ} t = begin
    weaken t / (t′ ∷ σ)   ≡⟨ cong (_/ (t′ ∷ σ)) (sym /-wk) ⟩
    t / wk / (t′ ∷ σ)     ≡⟨ sym (/-⊙ t) ⟩
    t / wk ⊙ (t′ ∷ σ)     ≡⟨ cong (t /_) (wk-⊙-∷ t′) ⟩
    t / σ                 ∎

-- T₂-substitutions in term-like T₁
--
-- FIXME: this should go into Data.Fin.Substitution.

record TermLikeSubst {ℓ} (T₁ : Pred ℕ ℓ) (T₂ : ℕ → Set)
                     : Set (lsuc (ℓ ⊔ lzero)) where
  field
    app       : ∀ {T₃} → Lift T₃ T₂ → ∀ {m n} → T₁ m → Sub T₃ m n → T₁ n
    termSubst : TermSubst T₂

  open TermSubst termSubst public
    hiding (app; var; weaken; _/Var_; _/_; _/✶_)

  termApplication : Application T₁ T₂
  termApplication = record { _/_ = app termLift }

  varApplication : Application T₁ Fin
  varApplication = record { _/_ = app varLift }

  open Application termApplication public using (_/_; _/✶_)
  open Application varApplication  public using () renaming (_/_ to _/Var_)

  -- Weakening of T₁s.

  weaken : ∀ {n} → T₁ n → T₁ (suc n)
  weaken t = t /Var VarSubst.wk

-- Lemmas for a term-like T₁ derived from term lemmas for T₂

record TermLikeLemmas {ℓ} (T₁ : Pred ℕ ℓ) (T₂ : ℕ → Set)
                      : Set (lsuc (ℓ ⊔ lzero)) where
  field
    app        : ∀ {T₃} → Lift T₃ T₂ → ∀ {m n} → T₁ m → Sub T₃ m n → T₁ n
    termLemmas : TermLemmas T₂

  termLikeSubst : TermLikeSubst T₁ T₂
  termLikeSubst = record
    { app       = app
    ; termSubst = TermLemmas.termSubst termLemmas
    }

  open TermLikeSubst termLikeSubst using (termSubst; termLift; varLift; weaken)
  open TermSubst     termSubst     using (var; _⊙_; module Lifted)

  field /✶-↑✶₁ : ∀ {T₃} {lift : Lift T₃ T₂} →
                 let open Application (record { _/_ = app lift })
                       using ()     renaming (_/✶_ to _/✶₁_)
                     open Lifted lift
                       using (_↑✶_) renaming (_/✶_ to _/✶₂_)
                 in
                 ∀ {m n} (σs₁ : Subs T₃ m n) (σs₂ : Subs T₃ m n) →
                 (∀ k x → var x /✶₂ σs₁ ↑✶ k ≡ var x /✶₂ σs₂ ↑✶ k) →
                  ∀ k t → t     /✶₁ σs₁ ↑✶ k ≡ t     /✶₁ σs₂ ↑✶ k

  termApplication : Application T₁ T₂
  termApplication = record { _/_ = app termLift }

  varApplication : Application T₁ Fin
  varApplication = record { _/_ = app varLift }

  field /✶-↑✶₂ : let open Application varApplication
                       using () renaming (_/✶_ to _/✶₁₃_)
                     open Application termApplication
                       using () renaming (_/✶_ to _/✶₁₂_)
                     open Lifted varLift
                       using () renaming (_↑✶_ to _↑✶₃_; _/✶_ to _/✶₂₃_)
                     open TermSubst termSubst
                       using () renaming (_↑✶_ to _↑✶₂_; _/✶_ to _/✶₂₂_)
                 in
                 ∀ {m n} (σs₁ : Subs Fin m n) (σs₂ : Subs T₂ m n) →
                 (∀ k x → var x /✶₂₃ σs₁ ↑✶₃ k ≡ var x /✶₂₂ σs₂ ↑✶₂ k) →
                  ∀ k t → t     /✶₁₃ σs₁ ↑✶₃ k ≡ t     /✶₁₂ σs₂ ↑✶₂ k

  -- An instantiation of the above lemmas for T₂ substitutions in T₁s.
  termLiftAppLemmas : LiftAppLemmas T₁ T₂ T₂
  termLiftAppLemmas = record
    { lift          = Lift.lift termLift
    ; application₁₃ = termApplication
    ; application₂₃ = TermLemmas.application termLemmas
    ; lemmas₂       = TermLemmas.lemmas₄ termLemmas
    ; lemmas₃       = TermLemmas.lemmas₄ termLemmas
    ; lift-/        = λ _ → refl
    ; lift-var      = λ _ → refl
    ; /✶-↑✶         = /✶-↑✶₁
    }

  open LiftAppLemmas termLiftAppLemmas public hiding (/-wk; _⊙_)

  -- An instantiation of the above lemmas for variable substitutions
  -- (renamings) in T₁s.
  varLiftSubLemmas : LiftSubLemmas T₁ T₂ Fin
  varLiftSubLemmas = record
    { application₁₂ = termApplication
    ; liftAppLemmas = record
      { lift          = Lift.lift varLift
      ; application₁₃ = varApplication
      ; application₂₃ = Lifted.application varLift
      ; lemmas₂       = TermLemmas.lemmas₄ termLemmas
      ; lemmas₃       = VarLemmas.lemmas₄
      ; lift-/        = λ _ → sym (TermLemmas.app-var termLemmas)
      ; lift-var      = λ _ → refl
      ; /✶-↑✶         = /✶-↑✶₁
      }
    ; weaken-lift = λ _ → TermLemmas.weaken-var termLemmas
    ; /-⊙₂        = AppLemmas./-⊙ appLemmas
    ; /✶-↑✶₁      = /✶-↑✶₂
    ; /✶-↑✶₂      = TermLemmas./✶-↑✶ termLemmas
    }

  open Application   varApplication   public using () renaming (_/_ to _/Var_)
  open LiftSubLemmas varLiftSubLemmas public hiding (/✶-↑✶₁; /✶-↑✶₂; _⊙_; /-wk)
    renaming (liftAppLemmas to varLiftAppLemmas)

  -- Lemmas relating weakening of T₁s to T₂-substitutions in T₁s.

  weakenLemmas : WeakenLemmas T₁ T₂
  weakenLemmas = record
    { weaken    = weaken
    ; appLemmas = appLemmas
    ; /-wk      = sym /-wk
    }
    where open LiftSubLemmas varLiftSubLemmas using (/-wk)

  open WeakenLemmas weakenLemmas public hiding (appLemmas)

  -- Another variant of /-wk⋆ relating VarSubst.wk to weakening of T₁s.

  /Var-wk⋆ : ∀ {n} k {t : T₁ n} →
             t /Var VarSubst.wk⋆ k ≡ weaken⋆ k t
  /Var-wk⋆ k {t} = begin
    t /Var VarSubst.wk⋆ k          ≡⟨ /-liftSub t ⟩
    t / liftSub (VarSubst.wk⋆ k)   ≡⟨ cong (t /_) (liftSub-wk⋆ k) ⟩
    t / wk⋆ k                      ≡⟨ /-wk⋆ k ⟩
    weaken⋆ k t                    ∎


------------------------------------------------------------------------
-- Termination predicates
------------------------------------------------------------------------

{-# OPTIONS --erased-cubical --sized-types #-}

open import Prelude hiding (↑; module W)

module Delay-monad.Alternative.Termination {a} {A : Type a} where

open import Equality.Propositional.Cubical
open import Logical-equivalence using (_⇔_)

open import Equality.Decision-procedures equality-with-J
open import Function-universe equality-with-J hiding (id; _∘_)
open import H-level equality-with-J
open import H-level.Closure equality-with-J
open import H-level.Truncation.Propositional equality-with-paths
  as Trunc
import Nat equality-with-J as N

open import Delay-monad hiding (Delay)
open import Delay-monad.Alternative
open import Delay-monad.Alternative.Equivalence
open import Delay-monad.Alternative.Properties
import Delay-monad.Bisimilarity as B
import Delay-monad.Termination as T

infix 4 _⇓_ _∥⇓∥_ _⇓′_

-- The three logically equivalent types x ⇓ y, x ∥⇓∥ y and x ⇓′ y
-- all mean that the computation x terminates with the value y:
--
-- ∙ _⇓_ is the "obvious" definition. The definition does not
--   involve truncation, and can be used directly.
--
-- ∙ _∥⇓∥_ is a truncated variant of _⇓_. It may be more convenient
--   to construct values of type x ∥⇓∥ y than of type x ⇓ y, because
--   the former type is a proposition.
--
-- ∙ _⇓′_ is an auxiliary definition, used to prove that the other
--   two are logically equivalent. This definition does not use
--   truncation, but is still propositional (if A is a set).

_⇓_ : Delay A → A → Type a
(f , _) ⇓ y = ∃ λ n → f n ↓ y

_∥⇓∥_ : Delay A → A → Type a
x ∥⇓∥ y = ∥ x ⇓ y ∥

_⇓′_ : Delay A → A → Type _
(f , _) ⇓′ y = ∃ λ m → f m ↓ y × (∀ n → ¬ f n ↑ → m N.≤ n)

-- _⇓′_ is a family of propositions (if A is a set).

⇓′-propositional :
  Is-set A → ∀ x {y : A} → Is-proposition (x ⇓′ y)
⇓′-propositional A-set x@(f , _) {y} p q =
                     $⟨ number-unique p q ⟩
  proj₁ p ≡ proj₁ q  ↝⟨ ignore-propositional-component
                          (×-closure 1 (Maybe-closure 0 A-set)
                                       (Π-closure ext 1 λ _ →
                                        Π-closure ext 1 λ _ →
                                        ≤-propositional)) ⟩□
  p ≡ q              □
  where
  number-unique : (p q : x ⇓′ y) → proj₁ p ≡ proj₁ q
  number-unique (pm , f-pm↓y , pm≤) (qm , f-qm↓y , qm≤) =
    N.≤-antisymmetric
      (pm≤ qm λ f-qm↑ → ⊎.inj₁≢inj₂ (nothing  ≡⟨ sym f-qm↑ ⟩
                                     f qm     ≡⟨ f-qm↓y ⟩∎
                                     just y   ∎))
      (qm≤ pm λ f-pm↑ → ⊎.inj₁≢inj₂ (nothing  ≡⟨ sym f-pm↑ ⟩
                                     f pm     ≡⟨ f-pm↓y ⟩∎
                                     just y   ∎))

-- All values that a computation can terminate with are equal.

termination-value-unique :
  ∀ (x : Delay A) {y z} → x ⇓ y → x ⇓ z → y ≡ z
termination-value-unique (f , inc) {y} {z} (m , fm↓y) (n , fn↓z)
  with N.total m n
... | inj₁ m≤n = ⊎.cancel-inj₂
                   (just y  ≡⟨ sym (↓-upwards-closed inc m≤n fm↓y) ⟩
                    f n     ≡⟨ fn↓z ⟩∎
                    just z  ∎)
... | inj₂ n≤m = ⊎.cancel-inj₂
                   (just y  ≡⟨ sym fm↓y ⟩
                    f m     ≡⟨ ↓-upwards-closed inc n≤m fn↓z ⟩∎
                    just z  ∎)

-- _⇓_ is contained in _⇓′_.

⇓→⇓′ : ∀ x {y : A} → x ⇓ y → x ⇓′ y
⇓→⇓′ x@(f , inc) = uncurry find-min
  where
  find-min : ∀ {y} m → f m ↓ y → x ⇓′ y
  find-min     zero    f0↓y    = zero , f0↓y , λ _ _ → N.zero≤ _
  find-min {y} (suc m) f-1+m↓y with inspect (f m)
  ... | nothing , fm↑ = suc m , f-1+m↓y , 1+m-is-min
    where
    1+m-is-min : ∀ n → ¬ f n ↑ → m N.< n
    1+m-is-min n ¬fn↑ with inspect (f n)
    ... | nothing , fn↑ = ⊥-elim (¬fn↑ fn↑)
    ... | just _  , fn↓ = ↑<↓ inc fm↑ fn↓
  ... | just y′ , fm↓y′ =
             $⟨ find-min m fm↓y′ ⟩
    x ⇓′ y′  ↝⟨ Σ-map id (Σ-map with-other-value id) ⟩□
    x ⇓′ y   □
    where
    with-other-value : ∀ {n} → f n ↓ y′ → f n ↓ y
    with-other-value =
      subst (_ ↓_)
            (termination-value-unique x (_ , fm↓y′) (_ , f-1+m↓y))

-- _⇓_ and _∥⇓∥_ are pointwise logically equivalent (if A is a set).

⇓⇔∥⇓∥ : Is-set A → ∀ x {y : A} → x ⇓ y ⇔ x ∥⇓∥ y
⇓⇔∥⇓∥ A-set x {y} = record
  { to   = ∣_∣
  ; from = x ∥⇓∥ y  ↝⟨ Trunc.rec (⇓′-propositional A-set x) (⇓→⇓′ x) ⟩
           x ⇓′ y   ↝⟨ Σ-map id proj₁ ⟩□
           x ⇓ y    □
  }

-- The notion of termination defined here is logically equivalent
-- (via Delay⇔Delay) to the one defined for the coinductive delay
-- monad.

⇓⇔⇓ : ∀ x {y} → x ⇓ y ⇔ _⇔_.to Delay⇔Delay x T.⇓ y
⇓⇔⇓ x = record
  { to   = λ { (n , fn↓y) → to n _ (proj₂ x) refl fn↓y }
  ; from = from _ refl
  }
  where
  to : ∀ {f : ℕ → Maybe A} n x {y} →
       Increasing f →
       x ≡ f 0 → f n ↓ y → Delay⇔Delay.To.to′ f x T.⇓ y
  to (suc n) nothing f-inc f0↑ fn↓y =
    B.laterʳ (to n _ (f-inc ∘ suc) refl fn↓y)

  to {f} zero nothing {y} _ f0↑ fn↓y =
    ⊥-elim $ ⊎.inj₁≢inj₂ (
      nothing  ≡⟨ f0↑ ⟩
      f 0      ≡⟨ fn↓y ⟩∎
      just y   ∎)

  to {f} n (just x) {y} f-inc f0↓x fn↓y =
    subst (_ T.⇓_)
          (⊎.cancel-inj₂ {A = ⊤}
             (just x  ≡⟨ sym $ ↓-upwards-closed₀ f-inc (sym f0↓x) n ⟩
              f n     ≡⟨ fn↓y ⟩∎
              just y  ∎))
          B.now

  from : ∀ {f : ℕ → Maybe A} {y} x →
         x ≡ f 0 → Delay⇔Delay.To.to′ f x T.⇓ y → ∃ λ n → f n ↓ y
  from (just x) f0↓x B.now           = 0 , sym f0↓x
  from nothing  _    (B.laterʳ to⇓y) =
    Σ-map suc id (from _ refl to⇓y)

⇓⇔⇓′ : ∀ {x} {y : A} → x T.⇓ y ⇔ _⇔_.from Delay⇔Delay x ⇓ y
⇓⇔⇓′ = record
  { to   = to
  ; from = uncurry (from _)
  }
  where
  to : ∀ {x y} → x T.⇓ y → Delay⇔Delay.from x ⇓ y
  to B.now        = 0 , refl
  to (B.laterʳ p) = Σ-map suc id (to p)

  from : ∀ x {y} n → proj₁ (Delay⇔Delay.from x) n ↓ y → x T.⇓ y
  from (now x)   n       refl = B.now
  from (later x) zero    ()
  from (later x) (suc n) xn↓y = B.laterʳ (from (force x) n xn↓y)

-- If there is a function f : ℕ → Maybe A satisfying a property
-- similar to termination-value-unique, then this function can be
-- turned into a value in Delay A which is, in some sense, weakly
-- bisimilar to the function.

complete-function :
  (f : ℕ → Maybe A) →
  (∀ {y z} → (∃ λ m → f m ↓ y) → (∃ λ n → f n ↓ z) → y ≡ z) →
  ∃ λ x → ∀ {y} → x ⇓ y ⇔ ∃ λ n → f n ↓ y
complete-function f unique =
    (first-value , inc)
  , λ {y} → record
      { to   = (∃ λ n → first-value n ↓ y)  ↝⟨ uncurry first-value↓→f↓ ⟩□
               (∃ λ n → f n ↓ y)            □
      ; from = (∃ λ n → f n ↓ y)            ↝⟨ Σ-map id (f↓→first-value↓ _) ⟩□
               (∃ λ n → first-value n ↓ y)  □
      }
  where
  -- If f m terminates for some m smaller than or equal to n, then
  -- first-value n returns the value of f for the largest such m, and
  -- otherwise nothing.

  first-value : ℕ → Maybe A
  first-value zero    = f zero
  first-value (suc n) =
    Prelude.[ (λ _ → first-value n) , just ] (f (suc n))

  -- Some simple lemmas.

  step : ∀ {n} → f (suc n) ↑ → first-value (suc n) ≡ first-value n
  step {n} = cong Prelude.[ (λ _ → first-value n) , just ]

  done : ∀ {n y} → f (suc n) ↓ y → first-value (suc n) ↓ y
  done {n} = cong Prelude.[ (λ _ → first-value n) , just ]

  -- If f n terminates with a certain value, then first-value n
  -- terminates with the same value.

  f↓→first-value↓ : ∀ {y} n → f n ↓ y → first-value n ↓ y
  f↓→first-value↓ zero    f↓ = f↓
  f↓→first-value↓ (suc n) f↑ = done f↑

  -- If first-value m terminates with a certain value, then there is
  -- some n for which f n terminates with the same value.

  first-value↓→f↓ : ∀ {y} m → first-value m ↓ y → ∃ λ n → f n ↓ y
  first-value↓→f↓     zero    first-value↓ = zero , first-value↓
  first-value↓→f↓ {y} (suc n) first-value↓ with inspect (f (suc n))
  ... | just y′ , f↓ = suc n , (f (suc n)            ≡⟨ f↓ ⟩
                                just y′              ≡⟨ sym (done f↓) ⟩
                                first-value (suc n)  ≡⟨ first-value↓ ⟩∎
                                just y               ∎)
  ... | nothing , f↑ =
    first-value↓→f↓ n (first-value n        ≡⟨ sym (step f↑) ⟩
                       first-value (suc n)  ≡⟨ first-value↓ ⟩∎
                       just y               ∎)

  -- The function first-value is an increasing sequence.

  inc : Increasing first-value
  inc n with inspect (first-value n) | inspect (f (suc n))
  inc n | _ | nothing , f↑ =
    inj₁ (first-value n        ≡⟨ sym (step f↑) ⟩∎
          first-value (suc n)  ∎)
  inc n | nothing , first-value↑ | just y , f↓ =
    inj₂ (first-value↑ , λ first-value↑ → ⊎.inj₁≢inj₂ (
                             nothing              ≡⟨ sym first-value↑ ⟩
                             first-value (suc n)  ≡⟨ done f↓ ⟩∎
                             just y               ∎))
  inc n | just y , first-value↓ | just y′ , f↓ =
    inj₁ (first-value n        ≡⟨ first-value↓ ⟩
          just y               ≡⟨ cong just $ unique (_ , proj₂ (first-value↓→f↓ n first-value↓)) (_ , f↓) ⟩
          just y′              ≡⟨ sym (done f↓) ⟩∎
          first-value (suc n)  ∎)


module Impure.STLCRef.Properties.Soundness where

open import Data.Nat
open import Data.Sum
open import Data.Product as Pr
open import Data.List
open import Data.Vec hiding (_∷ʳ_)
open import Data.Star
open import Function
open import Extensions.List

open import Relation.Binary.PropositionalEquality as P
open import Relation.Binary.Core using (REL; Reflexive)
open import Relation.Binary.List.Pointwise as PRel hiding (refl)

open import Impure.STLCRef.Syntax hiding (id)
open import Impure.STLCRef.Welltyped
open import Impure.STLCRef.Eval

ref-value-lemma : ∀ {n A Γ Σ} {e : Exp n} → Γ , Σ ⊢ e ∶ Ref A → Val e → ∃ λ i → e ≡ (loc i)
ref-value-lemma (var x) ()
ref-value-lemma (loc p) (loc i) = i , refl
ref-value-lemma (p · p₁) ()
ref-value-lemma (ref p) ()
ref-value-lemma (! p) ()

progress : ∀ {Γ Σ A} {e : Exp 0} {μ} →
           Γ , Σ ⊢ μ →
           Γ , Σ ⊢ e ∶ A →
           --------------------------------------
           Val e ⊎ ∃₂ λ e' μ' → (e , μ ≻ e' , μ')

progress p unit = inj₁ unit
progress p (var ())

progress p (loc {i = i} q) = inj₁ (loc i)

progress p (ƛ wt) = inj₁ (ƛ _ _)

progress p (f · e) with progress p f
progress p (() · e) | inj₁ unit
progress p (() · e) | inj₁ (loc i)
progress p (ƛ f · e) | inj₁ (ƛ _ _) = inj₂ (_ , (_ , AppAbs))
progress p (f · e) | inj₂ (_ , _ , f≻f') = inj₂ (_ , (_ , (Appₗ f≻f')))

progress p (ref wt) with progress p wt
progress p (ref wt) | inj₁ v = inj₂ (_ , (_ , (RefVal v)))
progress p (ref wt) | inj₂ (_ , _ , wt≻wt') = inj₂ (_ , _ , Ref wt≻wt')

progress p (! wt) with progress p wt
progress p (! wt) | inj₁ v with ref-value-lemma wt v
progress p (! loc q) | inj₁ (loc .i) | (i , refl) =
  inj₂ (_ , (_ , (DerefLoc (P.subst (_<_ _) (pointwise-length p) ([]=-length q)))))
progress p (! wt) | inj₂ (_ , _ , wt≻wt') = inj₂ (_ , (_ , (Deref wt≻wt')))

progress p (wt ≔ x) with progress p wt | progress p x
progress p (wt ≔ x) | _ | inj₂ (_ , _ , x≻x') = inj₂ (_ , (_ , (Assign₂ x≻x')))
progress p (wt ≔ x) | inj₂ (_ , _ , wt≻wt') | _ = inj₂ (_ , _ , Assign₁ wt≻wt')
progress p (wt ≔ x) | inj₁ v | inj₁ w with ref-value-lemma wt v
progress p (loc q ≔ x) | inj₁ (loc .i) | inj₁ w | (i , refl) =
  inj₂ (_ , (_ , Assign (P.subst (_<_ _) (pointwise-length p) ([]=-length q)) w))

postulate
  sub-preserves : ∀ {n Γ Σ A B x} {e : Exp (suc n)} →
    (B ∷ Γ) , Σ ⊢ e ∶ A →
    Γ , Σ ⊢ x ∶ B →
    Γ , Σ ⊢ (e / sub x) ∶ A

!!-loc : ∀ {n Σ Σ' A μ i} {Γ : Ctx n} →
         Rel (λ A x → Γ , Σ ⊢ proj₁ x ∶ A) Σ' μ →
         Σ' ⊢loc i ∶ A → (l : i < length μ) → Γ , Σ ⊢ proj₁ (μ !! l) ∶ A
!!-loc [] ()
!!-loc (x∼y ∷ p) here (s≤s z≤n) = x∼y
!!-loc (x∼y ∷ p) (there q) (s≤s l) = !!-loc p q l

-- extending the store preserves expression typings
⊒-preserves : ∀ {n Γ Σ Σ' A} {e : Exp n} → Σ' ⊒ Σ → Γ , Σ ⊢ e ∶ A → Γ , Σ' ⊢ e ∶ A
⊒-preserves ext unit = unit
⊒-preserves ext (var x) = var x
⊒-preserves ext (loc x) = loc (xs⊒ys[i] x ext)
⊒-preserves ext (ƛ p) = ƛ (⊒-preserves ext p)
⊒-preserves ext (p · q) = (⊒-preserves ext p) · ⊒-preserves ext q
⊒-preserves ext (ref p) = ref (⊒-preserves ext p)
⊒-preserves ext (! p) = ! (⊒-preserves ext p)
⊒-preserves ext (p ≔ q) = (⊒-preserves ext p) ≔ (⊒-preserves ext q)

≻-preserves : ∀ {n Γ Σ A} {e : Exp n} {e' μ' μ} →
              Γ , Σ ⊢ e ∶ A →
              Γ , Σ ⊢ μ → e , μ ≻ e' , μ' →
              ---------------------------------
              ∃ λ Σ' → Γ , Σ' ⊢ e' ∶ A × Σ' ⊒ Σ × Γ , Σ' ⊢ μ'

≻-preserves unit p ()
≻-preserves (var x) p ()
≻-preserves (loc p) p₁ ()
≻-preserves (ƛ wt) p ()

≻-preserves {Σ = Σ} (ƛ wt · wt₁) p AppAbs = Σ , sub-preserves wt wt₁ , ⊑-refl , p
≻-preserves {Σ = Σ} (ref {x = x} {A} wt) p (RefVal v) = let ext = ∷ʳ-⊒ A Σ in
  Σ ∷ʳ A ,
    loc (P.subst (λ i → _ ⊢loc i ∶ _) (pointwise-length p) (∷ʳ[length] Σ A)) ,
    ext ,
    pointwise-∷ʳ (PRel.map (⊒-preserves ext) p) (⊒-preserves ext wt)
≻-preserves {Σ = Σ₁} (! loc x) p (DerefLoc l) =
  Σ₁ , !!-loc p x l , ⊑-refl , p
≻-preserves {Σ = Σ₁} (loc x ≔ y) p (Assign l v) =
  Σ₁ , unit , ⊑-refl , pointwise-[]≔ p x l y

-- contextual closure
≻-preserves {Σ = Σ} (wt-f · wt-x) p (Appₗ r) =
  Pr.map
    id
    (λ{ (wt-f' , ext , q) → wt-f' · ⊒-preserves ext wt-x , ext , q})
    (≻-preserves wt-f p r)
≻-preserves (f · x) p (Appᵣ r) =
  Pr.map
    id
    (λ{ (x' , ext , q) → ⊒-preserves ext f · x' , ext , q})
    (≻-preserves x p r)
≻-preserves (ref wt) p (Ref r) = Pr.map id (λ{ (wt' , ext) → ref wt' , ext}) (≻-preserves wt p r)
≻-preserves (! wt) p (Deref r) = Pr.map id (λ{ (wt' , ext) → ! wt' , ext}) (≻-preserves wt p r)
≻-preserves (y ≔ x) p (Assign₁ r) =
  Pr.map
    id
    (λ{ (y' , ext , q) → y' ≔ ⊒-preserves ext x , ext , q})
    (≻-preserves y p r)
≻-preserves (y ≔ x) p (Assign₂ r) =
  Pr.map
    id
    (λ{ (x' , ext , q) → ⊒-preserves ext y ≔ x' , ext , q})
    (≻-preserves x p r)

-- preservation for multistep reductions
preservation : ∀ {n} {e : Exp n} {Γ Σ A μ μ' e'} →
                Γ , Σ ⊢ e ∶ A →
                Γ , Σ ⊢ μ →
                e , μ ≻* e' , μ' →
                -----------------------------------------------
                ∃ λ Σ' → Γ , Σ' ⊢ e' ∶ A × Σ' ⊒ Σ × Γ , Σ' ⊢ μ'
preservation wt ok ε = _ , wt , ⊑-refl , ok
preservation wt ok (x ◅ r) with ≻-preserves wt ok x
... | Σ₂ , wt' , Σ₂⊒Σ , μ₂ok with preservation wt' μ₂ok r
... | Σ₃ , wt'' , Σ₃⊒Σ₂ , μ₃ = Σ₃ , wt'' , ⊑-trans Σ₂⊒Σ Σ₃⊒Σ₂ , μ₃

-- fueled evaluation
open import Data.Maybe
eval : ∀ {e : Exp zero} {Σ A μ} ℕ →
         [] , Σ ⊢ e ∶ A →
         [] , Σ ⊢ μ →
         ---------------------------------------
         Maybe (∃ λ Σ' → ∃ λ e' → ∃ λ μ' →
            (e , μ ≻* e' , μ') × Val e' × ([] , Σ' ⊢ e' ∶ A))
eval 0 _ _ = nothing
eval (suc n) p q with progress q p
eval (suc n) p q | inj₁ x = just (_ , _ , _ , ε , x , p)
eval (suc n) p q | inj₂ (e' , μ' , step) with ≻-preserves p q step
... | (Σ₂ , wte' , ext , μ'-ok) with eval n wte' μ'-ok
... | nothing = nothing
... | just (Σ₃ , e'' , μ'' , steps , v , wte'') = just (Σ₃ , e'' , μ'' , step ◅ steps , v , wte'')


{-# OPTIONS --safe --experimental-lossy-unification #-}

module Cubical.Algebra.Group.EilenbergMacLane.WedgeConnectivity where

open import Cubical.Algebra.Group.EilenbergMacLane.Base
open import Cubical.Foundations.Prelude
open import Cubical.Foundations.HLevels

open import Cubical.Algebra.Group.Base
open import Cubical.HITs.Truncation as Trunc renaming (rec to trRec; elim to trElim)
open import Cubical.HITs.EilenbergMacLane1
open import Cubical.Algebra.AbGroup.Base
open import Cubical.Data.Empty
  renaming (rec to ⊥-rec)
open import Cubical.Data.Nat
open import Cubical.HITs.Susp

{-
This file contains a direct construction of the wedge connectivity lemma for EM
spaces. This direct construction gives nicer reductions and computes better than
the more general theorem. The main results are in the module "wedgeConEM" at the
end of this file.
-}

private
  variable ℓ ℓ' ℓ'' : Level

-- One of the base cases, stated separately to avoid termination issues
  wedgeConFun' : (G : AbGroup ℓ) (H : AbGroup ℓ') (n : ℕ)
              → {A : EM-raw G (suc n) → EM-raw H (suc zero) → Type ℓ''}
              → ((x : _) (y : _) → isOfHLevel (suc n + suc zero) (A x y))
              → (f : (x : _) → A ptEM-raw x)
              → (g : (x : _) → A x ptEM-raw)
              → f ptEM-raw ≡ g ptEM-raw
              → (x : _) (y : _) → A x y
  wedgeConFun'ᵣ : (G : AbGroup ℓ) (H : AbGroup ℓ') (n : ℕ)
              → {A : EM-raw G (suc n) → EM-raw H (suc zero) → Type ℓ''}
              → (hLev : ((x : _) (y : _) → isOfHLevel (suc n + suc zero) (A x y)))
              → (f : (x : _) → A ptEM-raw x)
              → (g : (x : _) → A x ptEM-raw)
              → (p : f ptEM-raw ≡ g ptEM-raw)
              → (x : _) → wedgeConFun' G H n hLev f g p x ptEM-raw ≡ g x
  wedgeConFun' G H zero {A = A} hlev f g p =
    elimSet _ (λ _ → isSetΠ λ _ → hlev _ _) f mainpath
    where
    I→A : (x : fst G) → (i : I) → A (emloop x i) embase
    I→A x i =
      hcomp (λ k → λ {(i = i0) → p (~ k) ; (i = i1) → p (~ k)})
            (g (emloop x i))

    SquareP2 : (x : _) (z : _)
      → SquareP (λ i j → A (emloop x i) (emloop z j))
          (cong f (emloop z)) (cong f (emloop z))
          (λ i → I→A x i) λ i → I→A x i
    SquareP2 x z =
      toPathP (isOfHLevelPathP' 1 (λ _ _ → hlev _ _ _ _) _ _ _ _)

    CubeP2 : (x : _) (g h : _)
      → PathP (λ k → PathP (λ j → PathP (λ i → A (emloop x i) (emcomp g h j k))
              (f (emcomp g h j k)) (f (emcomp g h j k)))
              (λ i → SquareP2 x g i k) λ i → SquareP2 x ((snd (AbGroup→Group H) GroupStr.· g) h) i k)
              (λ _ i → I→A x i) λ j i → SquareP2 x h i j
    CubeP2 x g h = toPathP (isOfHLevelPathP' 1 (isOfHLevelPathP 2 (hlev _ _) _ _) _ _ _ _)

    mainpath : (x : _) → PathP (λ i → (y : EM₁ (AbGroup→Group H)) → A (emloop x i) y) f f
    mainpath x i embase = I→A x i
    mainpath x i (emloop z j) = SquareP2 x z i j
    mainpath x i (emcomp g h j k) = CubeP2 x g h k j i
    mainpath x i (emsquash y z p q r s j k' l) = mega i j k' l
      where
      mega :
        PathP (λ i → PathP (λ j → PathP (λ k →
          PathP (λ l → A (emloop x i) (emsquash y z p q r s j k l))
                    (mainpath x i y)
                    (mainpath x i z))
                    (λ l → mainpath x i (p l))
                     λ l → mainpath x i (q l))
                    (λ k l → mainpath x i (r k l))
                     λ k l → mainpath x i (s k l))
                    (λ j mainpath l → f (emsquash y z p q r s j mainpath l))
                     λ j mainpath l → f (emsquash y z p q r s j mainpath l)
      mega =
        toPathP (isOfHLevelPathP' 1
          (isOfHLevelPathP 2 (isOfHLevelPathP 2 (hlev _ _) _ _) _ _) _ _ _ _)
  wedgeConFun' G H (suc n) {A = A} hLev f g p north y = f y
  wedgeConFun' G H (suc n) {A = A} hLev f g p south y = subst (λ x → A x y) (merid ptEM-raw) (f y)
  wedgeConFun' G H (suc n) {A = A} hLev f g p (merid a i) y = mainₗ a y i
    module _ where
    llem₁ : g south ≡ subst (λ x₁ → A x₁ ptEM-raw) (merid ptEM-raw) (f ptEM-raw)
    llem₁ = (λ i → transp (λ j → A (merid ptEM-raw (j ∨ ~ i)) ptEM-raw) (~ i) (g (merid ptEM-raw (~ i))))
          ∙ cong (subst (λ x₁ → A x₁ ptEM-raw) (merid ptEM-raw)) (sym p)

    llem₁' :
      Square
       (λ i → transp (λ j → A (merid ptEM-raw (i ∨ j)) ptEM-raw) i (g (merid ptEM-raw i))) refl
       (cong (subst (λ x → A x ptEM-raw) (merid ptEM-raw)) (sym p)) llem₁
    llem₁' i j =
      hcomp (λ k → λ { (i = i0) → transp (λ z → A (merid ptEM-raw (j ∨ z)) ptEM-raw) j
                                            (g (merid ptEM-raw j))
                      ; (i = i1) → subst (λ x₁ → A x₁ ptEM-raw) (merid ptEM-raw) (p (~ k))
                      ; (j = i0) → (subst (λ x → A x ptEM-raw) (merid ptEM-raw)) (p (~ k ∨ ~ i))})
            (transp (λ k → A (merid ptEM-raw (k ∨ ~ i ∧ j)) ptEM-raw) (~ i ∧ j) (g (merid ptEM-raw (~ i ∧ j))))

    llem₂ : (λ i₁ → transp (λ j → A (merid ptEM-raw (i₁ ∧ j)) ptEM-raw) (~ i₁) (f ptEM-raw))
        ≡ (λ i₁ →  hcomp (λ k → λ { (i₁ = i0) → p (~ k) ; (i₁ = i1) → llem₁ k })
                          (g (merid ptEM-raw i₁)))
    llem₂ i j =
      hcomp (λ k → λ { (i = i0) → transp (λ j₁ → A (merid ptEM-raw (j ∧ j₁)) ptEM-raw) (~ j) (p (~ k))
                      ; (j = i0) → p (~ k)
                      ; (j = i1) → llem₁' k i})
        (transp (λ k → A (merid ptEM-raw ((i ∨ k) ∧ j)) ptEM-raw) (i ∨ ~ j) (g (merid ptEM-raw (i ∧ j))))

    mainₗ : (a : _) (y : _)
      → PathP (λ i → A (merid a i) y) (f y) (subst (λ x → A x y) (merid ptEM-raw) (f y))
    mainₗ =
      wedgeConFun' G H n
        (λ _ _ → isOfHLevelPathP' (suc (n + 1)) (hLev _ _) _ _)
        (λ x i → transp (λ j → A (merid ptEM-raw (i ∧ j)) x) (~ i) (f x))
        (λ x i → hcomp (λ k → λ { (i = i0) → p (~ k)
                                 ; (i = i1) → llem₁ k})
                        (g (merid x i)))
        llem₂

    mainP : (y : _)
      → mainₗ y ptEM-raw
      ≡ λ i → hcomp (λ k → λ { (i = i0) → p (~ k)
                               ; (i = i1) → llem₁ k})
                      (g (merid y i))
    mainP y =
      wedgeConFun'ᵣ G H n
        (λ _ _ → isOfHLevelPathP' (suc (n + 1)) (hLev _ _) _ _)
        (λ x i → transp (λ j → A (merid ptEM-raw (i ∧ j)) x) (~ i) (f x))
        (λ x i → hcomp (λ k → λ { (i = i0) → p (~ k)
                                 ; (i = i1) → llem₁ k})
                        (g (merid x i)))
        llem₂ y
  wedgeConFun'ᵣ G H zero {A = A} hLev f g p =
    elimProp _ (λ _ → hLev _ _ _ _) p
  wedgeConFun'ᵣ G H (suc n) {A = A} hLev f g p north = p
  wedgeConFun'ᵣ G H (suc n) {A = A} hLev f g p south = sym (llem₁ G H n hLev f g p ptEM-raw i0 ptEM-raw)
  wedgeConFun'ᵣ G H (suc n) {A = A} hLev f g p (merid a i) k = P k i
    where
    llem : _
    llem i j =
      hcomp (λ k → λ { (i = i1) → g (merid a j)
                      ; (j = i0) → p (i ∨ ~ k)
                      ; (j = i1) → llem₁ G H n hLev f g p ptEM-raw i0 ptEM-raw (~ i ∧ k)})
        (g (merid a j))

    P : PathP (λ k → PathP (λ i → A (merid a i) ptEM-raw)
              (p k) (llem₁ G H n hLev f g p ptEM-raw i0 ptEM-raw (~ k)))
              (λ i → mainₗ G H n hLev f g p a i ptEM-raw a ptEM-raw i) λ i → g (merid a i)
    P = mainP G H n hLev f g p a i0 ptEM-raw a ◁ llem

-- Here, the actual stuff gets proved. However an additional natural number is stuck into the context
-- to convince the termination checker
private
  wedgeConFun : (G : AbGroup ℓ) (H : AbGroup ℓ')
                 (k n m : ℕ) → (n + m ≡ k) → {A : EM-raw G (suc n) → EM-raw H (suc m) → Type ℓ''}
              → ((x : _) (y : _) → isOfHLevel (suc n + suc m) (A x y))
              → (f : (x : _) → A ptEM-raw x)
              → (g : (x : _) → A x ptEM-raw)
              → f ptEM-raw ≡ g ptEM-raw
              → (x : _) (y : _) → A x y
  wedgeconLeft : (G : AbGroup ℓ) (H : AbGroup ℓ') (k n m : ℕ) (P : n + m ≡ k)
                 {A : EM-raw G (suc n) → EM-raw H (suc m) → Type ℓ''}
              → (hLev : ((x : _) (y : _) → isOfHLevel (suc n + suc m) (A x y)))
              → (f : (x : _) → A ptEM-raw x)
              → (g : (x : _) → A x ptEM-raw)
              → (p : f ptEM-raw ≡ g ptEM-raw)
               → (x : _) → wedgeConFun G H k n m P hLev f g p ptEM-raw x ≡ f x
  wedgeconRight : (G : AbGroup ℓ) (H : AbGroup ℓ')
                 (k n m : ℕ) (P : n + m ≡ k) {A : EM-raw G (suc n) → EM-raw H (suc m) → Type ℓ''}
              → (hLev : ((x : _) (y : _) → isOfHLevel (suc n + suc m) (A x y)))
              → (f : (x : _) → A ptEM-raw x)
              → (g : (x : _) → A x ptEM-raw)
              → (p : f ptEM-raw ≡ g ptEM-raw)
               → (x : _) → wedgeConFun G H k n m P hLev f g p x ptEM-raw ≡ g x
  wedgeConFun G H k n zero P {A = A} hLev f g p = wedgeConFun' G H n hLev f g p
  wedgeConFun G H k zero (suc m) P {A = A} hLev f g p x y =
    wedgeConFun' H G (suc m) {A = λ x y → A y x}
      (λ x y → subst (λ n → isOfHLevel (2 + n) (A y x)) (+-comm 1 m) (hLev y x))
      g f (sym p) y x
  wedgeConFun G H l (suc n) (suc m) P {A = A} hlev f g p north y = f y
  wedgeConFun G H l (suc n) (suc m) P {A = A} hlev f g p south y = subst (λ x → A x y) (merid ptEM-raw) (f y)
  wedgeConFun G H zero (suc n) (suc m) P {A = A} hlev f g p (merid a i) y = ⊥-path i
    where
    ⊥-path : PathP (λ i → A (merid a i) y) (f y) (subst (λ x → A x y) (merid ptEM-raw) (f y))
    ⊥-path = ⊥-rec (snotz P)
  wedgeConFun G H (suc l) (suc n) (suc m) P {A = A} hlev f g p (merid a i) y = mmain a y i
    module _ where
    llem₃ : g south ≡ (subst (λ x → A x ptEM-raw) (merid ptEM-raw) (f ptEM-raw))
    llem₃ = ((λ i → transp (λ j → A (merid ptEM-raw (~ i ∨ j)) ptEM-raw) (~ i) (g (merid ptEM-raw (~ i)))))
          ∙ cong (subst (λ x → A x ptEM-raw) (merid ptEM-raw)) (sym p)

    llem₃' :
      Square
        (λ i → transp (λ j → A (merid ptEM-raw (~ i ∨ j)) ptEM-raw) (~ i) (g (merid ptEM-raw (~ i))))
        (refl {x = subst (λ x → A x ptEM-raw) (merid ptEM-raw) (f ptEM-raw)})
        llem₃
        ((cong (transport (λ z → A (merid ptEM-raw z) ptEM-raw)) (sym p)))
    llem₃' i j =
      hcomp (λ k → λ { (i = i0) → transp (λ j₁ → A (merid ptEM-raw (~ j ∨ j₁)) ptEM-raw) (~ j) (g (merid ptEM-raw (~ j)))
                      ; (i = i1) → subst (λ x → A x ptEM-raw) (merid ptEM-raw) (p (~ k))
                      ; (j = i1) → cong (transport (λ z → A (merid ptEM-raw z) ptEM-raw)) (sym p) (i ∧ k)})
             (transp (λ j₁ → A (merid ptEM-raw ((~ j ∧ ~ i) ∨ j₁)) ptEM-raw) (~ j ∧ ~ i) (g (merid ptEM-raw (~ j ∧ ~ i))))

    llem₄ : (λ i₁ → transp (λ j → A (merid ptEM-raw (j ∧ i₁)) ptEM-raw) (~ i₁) (f ptEM-raw))
        ≡ (λ i₁ → hcomp (λ k → λ { (i₁ = i0) → p (~ k) ; (i₁ = i1) → llem₃ k })
           (g (merid ptEM-raw i₁)))
    llem₄ i j =
      hcomp (λ k → λ { (i = i0) → transp (λ z → A (merid ptEM-raw (z ∧ j)) ptEM-raw) (~ j) (p (~ k))
                      ; (j = i0) → p (~ k)
                      ; (j = i1) → llem₃' k (~ i)})
            (transp (λ z → A (merid ptEM-raw ((i ∨ z) ∧ j)) ptEM-raw) (i ∨ ~ j) (g (merid ptEM-raw (i ∧ j))))

    mmain : (a : _) (y : _)
          → PathP (λ i → A (merid a i) y) (f y)
                   (subst (λ x → A x y) (merid ptEM-raw) (f y))
    mmain =
      wedgeConFun G H l n (suc m) (cong predℕ P)
        (λ _ _ → isOfHLevelPathP' (suc (n + (suc (suc m)))) (hlev _ _) _ _)
        (λ x i → transp (λ j → A (merid ptEM-raw (j ∧ i)) x) (~ i) (f x))
        (λ y i → hcomp (λ k → λ { (i = i0) → p (~ k)
                                  ; (i = i1) → llem₃ k})
                       (g (merid y i)))
        llem₄

    mainR : (x : _) → mmain x ptEM-raw
                    ≡ λ i → hcomp (λ k → λ { (i = i0) → p (~ k)
                                             ; (i = i1) → llem₃ k})
                                               (g (merid x i))
    mainR x =
      wedgeconRight G H l n (suc m) (cong predℕ P)
        (λ _ _ → isOfHLevelPathP' (suc (n + (suc (suc m)))) (hlev _ _) _ _)
        (λ x i → transp (λ j → A (merid ptEM-raw (j ∧ i)) x) (~ i) (f x))
        (λ y i → hcomp (λ k → λ { (i = i0) → p (~ k)
                                  ; (i = i1) → llem₃ k})
                       (g (merid y i)))
        llem₄ x
  wedgeconLeft G H k zero zero P {A = A} hLev f g p _ = refl
  wedgeconLeft G H k (suc n) zero P {A = A} hLev f g p _ = refl
  wedgeconLeft G H k zero (suc m) P {A = A} hLev f g p x =
    wedgeConFun'ᵣ H G (suc m)
        (λ x₁ y →
           subst (λ n → (x₂ y₁ : A y x₁) → isOfHLevel (suc n) (x₂ ≡ y₁))
           (+-comm 1 m) (hLev y x₁))
        g f (λ i → p (~ i)) x
  wedgeconLeft G H k (suc n) (suc m) P {A = A} hLev f g p _ = refl
  wedgeconRight G H k n zero P {A = A} hLev f g p = wedgeConFun'ᵣ G H n hLev f g p
  wedgeconRight G H k zero (suc m) P {A = A} hLev f g p _ = refl
  wedgeconRight G H zero (suc n) (suc m) P {A = A} hLev f g p x = ⊥-rec (snotz P)
  wedgeconRight G H l (suc n) (suc m) P {A = A} hLev f g p north = p
  wedgeconRight G H l (suc n) (suc m) P {A = A} hLev f g p south =
    sym (llem₃ G H _ n m refl hLev f g p ptEM-raw i0 ptEM-raw)
  wedgeconRight G H (suc l) (suc n) (suc m) P {A = A} hLev f g p (merid a i) k =
    help k i
    where
    llem : _
    llem i j =
      hcomp (λ k → λ { (i = i1) → g (merid a j)
                      ; (j = i0) → p (i ∨ ~ k)
                      ; (j = i1) → llem₃ G H l n m P hLev f g p ptEM-raw i0 ptEM-raw (~ i ∧ k)})
            (g (merid a j))

    help : PathP (λ k → PathP (λ i → A (merid a i) ptEM-raw)
                 (p k) (llem₃ G H l n m P hLev f g p ptEM-raw i0 ptEM-raw (~ k)))
                 (λ i → mmain G H l n m P hLev f g p a i north a north i) (cong g (merid a))
    help = mainR G H l n m P hLev f g p a i0 ptEM-raw a ◁ llem

module wedgeConEM (G : AbGroup ℓ) (H : AbGroup ℓ') (n m : ℕ)
                  {A : EM-raw G (suc n) → EM-raw H (suc m) → Type ℓ''}
                  (hLev : ((x : _) (y : _) → isOfHLevel (suc n + suc m) (A x y)))
                  (f : (x : _) → A ptEM-raw x)
                  (g : (x : _) → A x ptEM-raw)
                  (p : f ptEM-raw ≡ g ptEM-raw) where
  fun : (x : EM-raw G (suc n)) (y : EM-raw H (suc m)) → A x y
  fun = wedgeConFun G H (n + m) n m refl hLev f g p

  left : (x : EM-raw H (suc m)) → fun ptEM-raw x ≡ f x
  left = wedgeconLeft G H (n + m) n m refl hLev f g p

  right : (x : EM-raw G (suc n)) → fun x ptEM-raw ≡ g x
  right = wedgeconRight G H (n + m) n m refl hLev f g p


-- Raw terms, weakening (renaming) and substitution.

{-# OPTIONS --without-K --safe #-}

module Definition.Untyped where

open import Tools.Nat
open import Tools.Product
open import Tools.List
import Tools.PropositionalEquality as PE

infixl 30 _∙_
infix 30 Π_▹_
infixr 22 _▹▹_
infixl 30 _ₛ•ₛ_ _•ₛ_ _ₛ•_
infix 25 _[_]
infix 25 _[_]↑

-- Typing contexts (snoc-lists, isomorphic to lists).

data Con (A : Set) : Set where
  ε   : Con A               -- Empty context.
  _∙_ : Con A → A → Con A  -- Context extension.

record GenT (A : Set) : Set where
  inductive
  constructor ⟦_,_⟧
  field
    l : Nat
    t : A

data Kind : Set where
  Ukind : Kind
  Pikind : Kind
  Natkind : Kind
  Lamkind : Kind
  Appkind : Kind
  Zerokind : Kind
  Suckind : Kind
  Natreckind : Kind

data Term : Set where
  var : (x : Nat) → Term
  gen : (k : Kind) (c : List (GenT Term)) → Term

-- The Grammar of our language.

-- We represent the expressions of our language as de Bruijn terms.
-- Variables are natural numbers interpreted as de Bruijn indices.
-- Π, lam, and natrec are binders.

-- Type constructors.
U      : Term                     -- Universe.
U = gen Ukind []

pattern Univ u = gen (Ukind u) []

Π_▹_   : (A B : Term)     → Term  -- Dependent function type (B is a binder).
Π A ▹ B = gen Pikind (⟦ 0 , A ⟧ ∷ ⟦ 1 , B ⟧ ∷ [])

ℕ      : Term                     -- Type of natural numbers.
ℕ = gen Natkind []

-- Lambda-calculus.
-- var    : (x : Nat)        → Term  -- Variable (de Bruijn index).
-- var = var

lam    : (t : Term)       → Term  -- Function abstraction (binder).
lam t = gen Lamkind (⟦ 1 , t ⟧ ∷ [])

_∘_    : (t u : Term)     → Term  -- Application.
t ∘ u = gen Appkind (⟦ 0 , t ⟧ ∷ ⟦ 0 , u ⟧ ∷ [])

-- Introduction and elimination of natural numbers.
zero   : Term                     -- Natural number zero.
zero = gen Zerokind []

suc    : (t : Term)       → Term  -- Successor.
suc t = gen Suckind (⟦ 0 , t ⟧ ∷ [])

natrec : (A t u v : Term) → Term  -- Recursor (A is a binder).
natrec A t u v = gen Natreckind (⟦ 1 , A ⟧ ∷ ⟦ 0 , t ⟧ ∷ ⟦ 0 , u ⟧ ∷ ⟦ 0 , v ⟧ ∷ [])

-- Injectivity of term constructors w.r.t. propositional equality.

-- If  Π F G = Π H E  then  F = H  and  G = E.

Π-PE-injectivity : ∀ {F G H E} → Π F ▹ G PE.≡ Π H ▹ E → F PE.≡ H × G PE.≡ E
Π-PE-injectivity PE.refl = PE.refl , PE.refl

-- If  suc n = suc m  then  n = m.

suc-PE-injectivity : ∀ {n m} → suc n PE.≡ suc m → n PE.≡ m
suc-PE-injectivity PE.refl = PE.refl

-- Neutral terms.

-- A term is neutral if it has a variable in head position.
-- The variable blocks reduction of such terms.

data Neutral : Term → Set where
  var     : ∀ n                     → Neutral (var n)
  ∘ₙ      : ∀ {k u}     → Neutral k → Neutral (k ∘ u)
  natrecₙ : ∀ {C c g k} → Neutral k → Neutral (natrec C c g k)

-- Weak head normal forms (whnfs).

-- These are the (lazy) values of our language.

data Whnf : Term → Set where

  -- Type constructors are whnfs.
  Uₙ    : Whnf U
  Πₙ    : ∀ {A B} → Whnf (Π A ▹ B)
  ℕₙ    : Whnf ℕ

  -- Introductions are whnfs.
  lamₙ  : ∀ {t} → Whnf (lam t)
  zeroₙ : Whnf zero
  sucₙ  : ∀ {t} → Whnf (suc t)

  -- Neutrals are whnfs.
  ne   : ∀ {n} → Neutral n → Whnf n

-- Whnf inequalities.

-- Different whnfs are trivially distinguished by propositional equality.
-- (The following statements are sometimes called "no-confusion theorems".)

U≢ℕ : U PE.≢ ℕ
U≢ℕ ()

U≢Π : ∀ {F G} → U PE.≢ Π F ▹ G
U≢Π ()

U≢ne : ∀ {K} → Neutral K → U PE.≢ K
U≢ne () PE.refl

ℕ≢Π : ∀ {F G} → ℕ PE.≢ Π F ▹ G
ℕ≢Π ()

ℕ≢ne : ∀ {K} → Neutral K → ℕ PE.≢ K
ℕ≢ne () PE.refl

Π≢ne : ∀ {F G K} → Neutral K → Π F ▹ G PE.≢ K
Π≢ne () PE.refl

zero≢suc : ∀ {n} → zero PE.≢ suc n
zero≢suc ()

zero≢ne : ∀ {k} → Neutral k → zero PE.≢ k
zero≢ne () PE.refl

suc≢ne : ∀ {n k} → Neutral k → suc n PE.≢ k
suc≢ne () PE.refl

-- Several views on whnfs (note: not recursive).

-- A whnf of type ℕ is either zero, suc t, or neutral.

data Natural : Term → Set where
  zeroₙ :                     Natural zero
  sucₙ  : ∀ {t}             → Natural (suc t)
  ne    : ∀ {n} → Neutral n → Natural n

-- A (small) type in whnf is either Π A B, ℕ, or neutral.
-- Large types could also be U.

data Type : Term → Set where
  Πₙ : ∀ {A B} → Type (Π A ▹ B)
  ℕₙ : Type ℕ
  ne : ∀{n} → Neutral n → Type n

-- A whnf of type Π A B is either lam t or neutral.

data Function : Term → Set where
  lamₙ : ∀{t} → Function (lam t)
  ne : ∀{n} → Neutral n → Function n

-- These views classify only whnfs.
-- Natural, Type, and Function are a subsets of Whnf.

naturalWhnf : ∀ {n} → Natural n → Whnf n
naturalWhnf sucₙ = sucₙ
naturalWhnf zeroₙ = zeroₙ
naturalWhnf (ne x) = ne x

typeWhnf : ∀ {A} → Type A → Whnf A
typeWhnf Πₙ = Πₙ
typeWhnf ℕₙ = ℕₙ
typeWhnf (ne x) = ne x

functionWhnf : ∀ {f} → Function f → Whnf f
functionWhnf lamₙ = lamₙ
functionWhnf (ne x) = ne x

------------------------------------------------------------------------
-- Weakening

-- In the following we define untyped weakenings η : Wk.
-- The typed form could be written η : Γ ≤ Δ with the intention
-- that η transport a term t living in context Δ to a context Γ
-- that can bind additional variables (which cannot appear in t).
-- Thus, if Δ ⊢ t : A and η : Γ ≤ Δ then Γ ⊢ wk η t : wk η A.
--
-- Even though Γ is "larger" than Δ we write Γ ≤ Δ to be conformant
-- with subtyping A ≤ B.  With subtyping, relation Γ ≤ Δ could be defined as
-- ``for all x ∈ dom(Δ) have Γ(x) ≤ Δ(x)'' (in the sense of subtyping)
-- and this would be the natural extension of weakenings.

data Wk : Set where
  id    : Wk        -- η : Γ ≤ Γ.
  step  : Wk  → Wk  -- If η : Γ ≤ Δ then step η : Γ∙A ≤ Δ.
  lift  : Wk  → Wk  -- If η : Γ ≤ Δ then lift η : Γ∙A ≤ Δ∙A.

-- Composition of weakening.
-- If η : Γ ≤ Δ and η′ : Δ ≤ Φ then η • η′ : Γ ≤ Φ.

infixl 30 _•_

_•_                :  Wk → Wk → Wk
id      • η′       =  η′
step η  • η′       =  step  (η • η′)
lift η  • id       =  lift  η
lift η  • step η′  =  step  (η • η′)
lift η  • lift η′  =  lift  (η • η′)

repeat : {A : Set} → (A → A) → A → Nat → A
repeat f a 0 = a
repeat f a (1+ n) = f (repeat f a n)

-- Weakening of variables.
-- If η : Γ ≤ Δ and x ∈ dom(Δ) then wkVar ρ x ∈ dom(Γ).

wkVar : (ρ : Wk) (n : Nat) → Nat
wkVar id       n        = n
wkVar (step ρ) n        = 1+ (wkVar ρ n)
wkVar (lift ρ) 0    = 0
wkVar (lift ρ) (1+ n) = 1+ (wkVar ρ n)

  -- Weakening of terms.
  -- If η : Γ ≤ Δ and Δ ⊢ t : A then Γ ⊢ wk η t : wk η A.

mutual
  wkGen : (ρ : Wk) (g : List (GenT Term)) → List (GenT Term)
  wkGen ρ [] = []
  wkGen ρ (⟦ l , t ⟧ ∷ g) = ⟦ l , (wk (repeat lift ρ l) t) ⟧ ∷ wkGen ρ g

  wk : (ρ : Wk) (t : Term) → Term
  wk ρ (var x) = var (wkVar ρ x)
  wk ρ (gen x c) = gen x (wkGen ρ c)

-- Adding one variable to the context requires wk1.
-- If Γ ⊢ t : B then Γ∙A ⊢ wk1 t : wk1 B.

wk1 : Term → Term
wk1 = wk (step id)

-- Weakening of a neutral term.

wkNeutral : ∀ {t} ρ → Neutral t → Neutral (wk ρ t)
wkNeutral ρ (var n)    = var (wkVar ρ n)
wkNeutral ρ (∘ₙ n)    = ∘ₙ (wkNeutral ρ n)
wkNeutral ρ (natrecₙ n) = natrecₙ (wkNeutral ρ n)

-- Weakening can be applied to our whnf views.

wkNatural : ∀ {t} ρ → Natural t → Natural (wk ρ t)
wkNatural ρ sucₙ    = sucₙ
wkNatural ρ zeroₙ   = zeroₙ
wkNatural ρ (ne x) = ne (wkNeutral ρ x)

wkType : ∀ {t} ρ → Type t → Type (wk ρ t)
wkType ρ Πₙ      = Πₙ
wkType ρ ℕₙ      = ℕₙ
wkType ρ (ne x) = ne (wkNeutral ρ x)

wkFunction : ∀ {t} ρ → Function t → Function (wk ρ t)
wkFunction ρ lamₙ    = lamₙ
wkFunction ρ (ne x) = ne (wkNeutral ρ x)

wkWhnf : ∀ {t} ρ → Whnf t → Whnf (wk ρ t)
wkWhnf ρ Uₙ      = Uₙ
wkWhnf ρ Πₙ      = Πₙ
wkWhnf ρ ℕₙ      = ℕₙ
wkWhnf ρ lamₙ    = lamₙ
wkWhnf ρ zeroₙ   = zeroₙ
wkWhnf ρ sucₙ    = sucₙ
wkWhnf ρ (ne x) = ne (wkNeutral ρ x)

-- Non-dependent version of Π.

_▹▹_ : Term → Term → Term
A ▹▹ B = Π A ▹ wk1 B

------------------------------------------------------------------------
-- Substitution

-- The substitution operation  subst σ t  replaces the free de Bruijn indices
-- of term t by chosen terms as specified by σ.

-- The substitution σ itself is a map from natural numbers to terms.

Subst : Set
Subst = Nat → Term

-- Given closed contexts ⊢ Γ and ⊢ Δ,
-- substitutions may be typed via Γ ⊢ σ : Δ meaning that
-- Γ ⊢ σ(x) : (subst σ Δ)(x) for all x ∈ dom(Δ).
--
-- The substitution operation is then typed as follows:
-- If Γ ⊢ σ : Δ and Δ ⊢ t : A, then Γ ⊢ subst σ t : subst σ A.
--
-- Although substitutions are untyped, typing helps us
-- to understand the operation on substitutions.

-- We may view σ as the infinite stream σ 0, σ 1, ...

-- Extract the substitution of the first variable.
--
-- If Γ ⊢ σ : Δ∙A  then Γ ⊢ head σ : subst σ A.

head : Subst → Term
head σ = σ 0

-- Remove the first variable instance of a substitution
-- and shift the rest to accommodate.
--
-- If Γ ⊢ σ : Δ∙A then Γ ⊢ tail σ : Δ.

tail : Subst → Subst
tail σ n = σ (1+ n)

-- Substitution of a variable.
--
-- If Γ ⊢ σ : Δ then Γ ⊢ substVar σ x : (subst σ Δ)(x).

substVar : (σ : Subst) (x : Nat) → Term
substVar σ x = σ x

-- Identity substitution.
-- Replaces each variable by itself.
--
-- Γ ⊢ idSubst : Γ.

idSubst : Subst
idSubst = var

-- Weaken a substitution by one.
--
-- If Γ ⊢ σ : Δ then Γ∙A ⊢ wk1Subst σ : Δ.

wk1Subst : Subst → Subst
wk1Subst σ x = wk1 (σ x)

-- Lift a substitution.
--
-- If Γ ⊢ σ : Δ then Γ∙A ⊢ liftSubst σ : Δ∙A.

liftSubst : (σ : Subst) → Subst
liftSubst σ 0    = var 0
liftSubst σ (1+ x) = wk1Subst σ x

-- Transform a weakening into a substitution.
--
-- If ρ : Γ ≤ Δ then Γ ⊢ toSubst ρ : Δ.

toSubst : Wk → Subst
toSubst pr x = var (wkVar pr x)

-- Apply a substitution to a term.
--
-- If Γ ⊢ σ : Δ and Δ ⊢ t : A then Γ ⊢ subst σ t : subst σ A.

mutual
  substGen : (σ : Subst) (g : List (GenT Term)) → List (GenT Term)
  substGen σ [] = []
  substGen σ (⟦ l , t ⟧ ∷ g) = ⟦ l , (subst (repeat liftSubst σ l) t) ⟧ ∷ substGen σ g

  subst : (σ : Subst) (t : Term) → Term
  subst σ (var x) = substVar σ x
  subst σ (gen x c) = gen x (substGen σ c)

-- Extend a substitution by adding a term as
-- the first variable substitution and shift the rest.
--
-- If Γ ⊢ σ : Δ and Γ ⊢ t : subst σ A then Γ ⊢ consSubst σ t : Δ∙A.

consSubst : Subst → Term → Subst
consSubst σ t 0    = t
consSubst σ t (1+ n) = σ n

-- Singleton substitution.
--
-- If Γ ⊢ t : A then Γ ⊢ sgSubst t : Γ∙A.

sgSubst : Term → Subst
sgSubst = consSubst idSubst

-- Compose two substitutions.
--
-- If Γ ⊢ σ : Δ and Δ ⊢ σ′ : Φ then Γ ⊢ σ ₛ•ₛ σ′ : Φ.

_ₛ•ₛ_ : Subst → Subst → Subst
_ₛ•ₛ_ σ σ′ x = subst σ (σ′ x)

-- Composition of weakening and substitution.
--
--  If ρ : Γ ≤ Δ and Δ ⊢ σ : Φ then Γ ⊢ ρ •ₛ σ : Φ.

_•ₛ_ : Wk → Subst → Subst
_•ₛ_ ρ σ x = wk ρ (σ x)

--  If Γ ⊢ σ : Δ and ρ : Δ ≤ Φ then Γ ⊢ σ ₛ• ρ : Φ.

_ₛ•_ : Subst → Wk → Subst
_ₛ•_ σ ρ x = σ (wkVar ρ x)

-- Substitute the first variable of a term with an other term.
--
-- If Γ∙A ⊢ t : B and Γ ⊢ s : A then Γ ⊢ t[s] : B[s].

_[_] : (t : Term) (s : Term) → Term
t [ s ] = subst (sgSubst s) t

-- Substitute the first variable of a term with an other term,
-- but let the two terms share the same context.
--
-- If Γ∙A ⊢ t : B and Γ∙A ⊢ s : A then Γ∙A ⊢ t[s]↑ : B[s]↑.

_[_]↑ : (t : Term) (s : Term) → Term
t [ s ]↑ = subst (consSubst (wk1Subst idSubst) s) t