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

open import Definition.Typed.EqualityRelation

module Definition.LogicalRelation.Fundamental {{eqrel : EqRelSet}} where
open EqRelSet {{...}}

open import Definition.Untyped
open import Definition.Untyped.Properties
open import Definition.Typed
open import Definition.LogicalRelation
open import Definition.LogicalRelation.Irrelevance
open import Definition.LogicalRelation.Properties
open import Definition.LogicalRelation.Substitution
open import Definition.LogicalRelation.Substitution.Properties
open import Definition.LogicalRelation.Substitution.Conversion
open import Definition.LogicalRelation.Substitution.Reduction
open import Definition.LogicalRelation.Substitution.Reflexivity
open import Definition.LogicalRelation.Substitution.Introductions
import Definition.LogicalRelation.Substitution.Irrelevance as S

open import Agda.Primitive
open import Tools.Product
open import Tools.Unit
open import Tools.Nat
import Tools.PropositionalEquality as PE

mutual
  -- Fundamental theorem for contexts.
  valid : ∀ {Γ} → ⊢ Γ → ⊩ᵛ Γ
  valid ε = ε′
  valid (⊢Γ ∙ A) = let [Γ] , [A] = fundamental A in [Γ] ∙′ [A]

  -- Fundamental theorem for types.
  fundamental : ∀ {Γ A} (⊢A : Γ ⊢ A) → Σω₄ (⊩ᵛ Γ) (λ [Γ] → Γ ⊩ᵛ⟨ ¹ ⟩ A / [Γ])
  fundamental (ℕⱼ x) = valid x , ℕᵛ (valid x)
  fundamental (Uⱼ x) = valid x , Uᵛ (valid x)
  fundamental (Πⱼ_▹_ {F} {G} ⊢F ⊢G) with fundamental ⊢F | fundamental ⊢G
  fundamental (Πⱼ_▹_ {F} {G} ⊢F ⊢G) | [Γ] , [F] | [Γ∙F] , [G] =
    [Γ] , Πᵛ {F} {G} [Γ] [F] (S.irrelevance {A = G} [Γ∙F] ([Γ] ∙′ [F]) [G])
  fundamental (univ {A} ⊢A) with fundamentalTerm ⊢A
  fundamental (univ {A} ⊢A) | [Γ] , [U] , [A] =
    [Γ] , univᵛ {A} [Γ] [U] [A]

  -- Fundamental theorem for type equality.
  fundamentalEq : ∀{Γ A B} → Γ ⊢ A ≡ B
    → ∃ω₄  λ ([Γ] : ⊩ᵛ Γ)
    → ∃ω₃² λ ([A] : Γ ⊩ᵛ⟨ ¹ ⟩ A / [Γ]) ([B] : Γ ⊩ᵛ⟨ ¹ ⟩ B / [Γ])
    → Γ ⊩ᵛ⟨ ¹ ⟩ A ≡ B / [Γ] / [A]
  fundamentalEq (univ {A} {B} x) with fundamentalTermEq x
  fundamentalEq (univ {A} {B} x) | [Γ] , modelsTermEq [U] [t] [u] [t≡u] =
    let [A] = univᵛ {A} [Γ] [U] [t]
        [B] = univᵛ {B} [Γ] [U] [u]
    in  [Γ] , [A] , [B]
    ,   (λ ⊢Δ [σ] → univEqEq (proj₁ ([U] ⊢Δ [σ]))
                             (proj₁ ([A] ⊢Δ [σ]))
                             ([t≡u] ⊢Δ [σ]))
  fundamentalEq (refl D) =
    let [Γ] , [B] = fundamental D
    in  [Γ] , [B] , [B] , (λ ⊢Δ [σ] → reflEq (proj₁ ([B] ⊢Δ [σ])))
  fundamentalEq (sym A≡B) with fundamentalEq A≡B
  fundamentalEq (sym A≡B) | [Γ] , [B] , [A] , [B≡A] =
    [Γ] , [A] , [B]
        , (λ ⊢Δ [σ] → symEq (proj₁ ([B] ⊢Δ [σ]))
                            (proj₁ ([A] ⊢Δ [σ]))
                            ([B≡A] ⊢Δ [σ]))
  fundamentalEq (trans {A} {B₁} {B} A≡B₁ B₁≡B)
    with fundamentalEq A≡B₁ | fundamentalEq B₁≡B
  fundamentalEq (trans {A} {B₁} {B} A≡B B≡C) | [Γ] , [A] , [B₁] , [A≡B₁]
    | [Γ]₁ , [B₁]₁ , [B] , [B₁≡B] =
      [Γ] , [A] , S.irrelevance {A = B} [Γ]₁ [Γ] [B]
          , (λ ⊢Δ [σ] →
               let [σ]′ = S.irrelevanceSubst [Γ] [Γ]₁ ⊢Δ ⊢Δ [σ]
               in  transEq (proj₁ ([A] ⊢Δ [σ])) (proj₁ ([B₁] ⊢Δ [σ]))
                           (proj₁ ([B] ⊢Δ [σ]′)) ([A≡B₁] ⊢Δ [σ])
                           (irrelevanceEq (proj₁ ([B₁]₁ ⊢Δ [σ]′))
                                          (proj₁ ([B₁] ⊢Δ [σ]))
                                          ([B₁≡B] ⊢Δ [σ]′)))
  fundamentalEq (Π-cong {F} {H} {G} {E} ⊢F A≡B A≡B₁)
    with fundamentalEq A≡B | fundamentalEq A≡B₁
  fundamentalEq (Π-cong {F} {H} {G} {E} ⊢F A≡B A≡B₁) | [Γ] , [F] , [H] , [F≡H]
    | [Γ]₁ , [G] , [E] , [G≡E] =
      let [G]′ = S.irrelevance {A = G} [Γ]₁ ([Γ] ∙′ [F]) [G]
          [E]′ = S.irrelevanceLift {A = E} {F = F} {H = H} [Γ] [F] [H] [F≡H]
                                   (S.irrelevance {A = E} [Γ]₁ ([Γ] ∙′ [F]) [E])
          [G≡E]′ = S.irrelevanceEq {A = G} {B = E} [Γ]₁
                                   ([Γ] ∙′ [F]) [G] [G]′ [G≡E]
      in  [Γ]
      ,   Πᵛ {F} {G} [Γ] [F] [G]′
      ,   Πᵛ {H} {E} [Γ] [H] [E]′
      ,   Π-congᵛ {F} {G} {H} {E} [Γ] [F] [G]′ [H] [E]′ [F≡H] [G≡E]′

  -- Fundamental theorem for variables.
  fundamentalVar : ∀ {Γ A x}
                 → x ∷ A ∈ Γ
                 → ([Γ] : ⊩ᵛ Γ)
                 → ∃ω₃ λ ([A] : Γ ⊩ᵛ⟨ ¹ ⟩ A / [Γ])
                 → Γ ⊩ᵛ⟨ ¹ ⟩ var x ∷ A / [Γ] / [A]
  fundamentalVar here (VPack _ _ (V∙ {A = A} {l = l} [Γ] [A])) =
    (λ ⊢Δ [σ] →
       let [σA]  = proj₁ ([A] ⊢Δ (proj₁ [σ]))
           [σA′] = maybeEmb (irrelevance′ (PE.sym (subst-wk A)) [σA])
       in  [σA′]
       ,   (λ [σ′] [σ≡σ′] →
              irrelevanceEq″ (PE.sym (subst-wk A)) (PE.sym (subst-wk A))
                              [σA] [σA′] (proj₂ ([A] ⊢Δ (proj₁ [σ]))
                                                (proj₁ [σ′]) (proj₁ [σ≡σ′]))))
    , (λ ⊢Δ [σ] →
         let [σA]  = proj₁ ([A] ⊢Δ (proj₁ [σ]))
             [σA′] = maybeEmb (irrelevance′ (PE.sym (subst-wk A)) [σA])
         in  irrelevanceTerm′ (PE.sym (subst-wk A)) [σA] [σA′] (proj₂ [σ])
    , (λ [σ′] [σ≡σ′] → irrelevanceEqTerm′ (PE.sym (subst-wk A))
                                          [σA] [σA′] (proj₂ [σ≡σ′])))
  fundamentalVar (there {A = A} h) ([Γ] ∙′ [B]) =
    (λ ⊢Δ [σ] →
       let [h]   = proj₁ (fundamentalVar h [Γ]) ⊢Δ (proj₁ [σ])
           [σA]  = proj₁ [h]
           [σA′] = irrelevance′ (PE.sym (subst-wk A)) [σA]
       in  [σA′]
       ,   (λ [σ′] [σ≡σ′] →
              irrelevanceEq″ (PE.sym (subst-wk A)) (PE.sym (subst-wk A))
                              [σA] [σA′]
                              (proj₂ [h] (proj₁ [σ′]) (proj₁ [σ≡σ′]))))
    , (λ ⊢Δ [σ] →
         let [h]   = (proj₁ (fundamentalVar h [Γ])) ⊢Δ (proj₁ [σ])
             [σA]  = proj₁ [h]
             [σA′] = irrelevance′ (PE.sym (subst-wk A)) [σA]
             [h′] = (proj₂ (fundamentalVar h [Γ])) ⊢Δ (proj₁ [σ])
         in  irrelevanceTerm′ (PE.sym (subst-wk A)) [σA] [σA′] (proj₁ [h′])
         ,   (λ [σ′] [σ≡σ′] →
                irrelevanceEqTerm′ (PE.sym (subst-wk A)) [σA] [σA′]
                                   (proj₂ [h′] (proj₁ [σ′]) (proj₁ [σ≡σ′]))))

  -- Fundamental theorem for terms.
  fundamentalTerm : ∀{Γ A t} → Γ ⊢ t ∷ A
    → ∃ω₄ λ ([Γ] : ⊩ᵛ Γ)
    → ∃ω₃ λ ([A] : Γ ⊩ᵛ⟨ ¹ ⟩ A / [Γ])
    → Γ ⊩ᵛ⟨ ¹ ⟩ t ∷ A / [Γ] / [A]
  fundamentalTerm (ℕⱼ x) = valid x , Uᵛ (valid x) , ℕᵗᵛ (valid x)
  fundamentalTerm (Πⱼ_▹_ {F} {G} ⊢F ⊢G)
    with fundamentalTerm ⊢F | fundamentalTerm ⊢G
  ... | [Γ] , [U] , [F]ₜ | [Γ]₁ , [U]₁ , [G]ₜ =
    let [F]   = univᵛ {F} [Γ] [U] [F]ₜ
        [U]′  = S.irrelevance {A = U} [Γ]₁ ([Γ] ∙′ [F]) [U]₁
        [F]ₜ′ = S.irrelevanceTerm {A = U} {t = F} [Γ] [Γ] [U] (Uᵛ [Γ]) [F]ₜ
        [G]ₜ′ = S.irrelevanceTerm {A = U} {t = G} [Γ]₁ ([Γ] ∙′ [F]) [U]₁
                                  (λ {Δ} {σ} → [U]′ {Δ} {σ}) [G]ₜ
    in  [Γ] , [U]
    ,   S.irrelevanceTerm {A = U} {t = Π F ▹ G} [Γ] [Γ] (Uᵛ [Γ]) [U]
                          (Πᵗᵛ {F} {G} [Γ] [F] (λ {Δ} {σ} → [U]′ {Δ} {σ})
                               [F]ₜ′ [G]ₜ′)
  fundamentalTerm (var ⊢Γ x∷A) = valid ⊢Γ , fundamentalVar x∷A (valid ⊢Γ)
  fundamentalTerm (lamⱼ {F} {G} {t} ⊢F ⊢t)
    with fundamental ⊢F | fundamentalTerm ⊢t
  ... | [Γ] , [F] | [Γ]₁ , [G] , [t] =
    let [G]′ = S.irrelevance {A = G} [Γ]₁ ([Γ] ∙′ [F]) [G]
        [t]′ = S.irrelevanceTerm {A = G} {t = t} [Γ]₁ ([Γ] ∙′ [F]) [G] [G]′ [t]
    in  [Γ] , Πᵛ {F} {G} [Γ] [F] [G]′
    ,   lamᵛ {F} {G} {t} [Γ] [F] [G]′ [t]′
  fundamentalTerm (_∘ⱼ_ {g} {a} {F} {G} Dt Du)
    with fundamentalTerm Dt | fundamentalTerm Du
  ... | [Γ] , [ΠFG] , [t] | [Γ]₁ , [F] , [u] =
    let [ΠFG]′ = S.irrelevance {A = Π F ▹ G} [Γ] [Γ]₁ [ΠFG]
        [t]′ = S.irrelevanceTerm {A = Π F ▹ G} {t = g} [Γ] [Γ]₁ [ΠFG] [ΠFG]′ [t]
        [G[t]] = substSΠ {F} {G} {a} [Γ]₁ [F] [ΠFG]′ [u]
        [t∘u] = appᵛ {F} {G} {g} {a} [Γ]₁ [F] [ΠFG]′ [t]′ [u]
    in  [Γ]₁ , [G[t]] , [t∘u]
  fundamentalTerm (zeroⱼ x) = valid x , ℕᵛ (valid x) , zeroᵛ {l = ¹} (valid x)
  fundamentalTerm (sucⱼ {n} t) with fundamentalTerm t
  fundamentalTerm (sucⱼ {n} t) | [Γ] , [ℕ] , [n] =
    [Γ] , [ℕ] , sucᵛ {n = n} [Γ] [ℕ] [n]
  fundamentalTerm (natrecⱼ {G} {s} {z} {n} ⊢G ⊢z ⊢s ⊢n)
    with fundamental ⊢G | fundamentalTerm ⊢z | fundamentalTerm ⊢s
       | fundamentalTerm ⊢n
  ... | [Γ] , [G] | [Γ]₁ , [G₀] , [z] | [Γ]₂ , [G₊] , [s] | [Γ]₃ , [ℕ] , [n] =
    let sType = Π ℕ ▹ (G ▹▹ G [ suc (var 0) ]↑)
        [Γ]′ = [Γ]₃
        [G]′ = S.irrelevance {A = G} [Γ] ([Γ]′ ∙′ [ℕ]) [G]
        [G₀]′ = S.irrelevance {A = G [ zero ]} [Γ]₁ [Γ]′ [G₀]
        [G₊]′ = S.irrelevance {A = sType} [Γ]₂ [Γ]′ [G₊]
        [Gₙ]′ = substS {F = ℕ} {G = G} {t = n} [Γ]′ [ℕ] [G]′ [n]
        [z]′ = S.irrelevanceTerm {A = G [ zero ]} {t = z} [Γ]₁ [Γ]′
                                 [G₀] [G₀]′ [z]
        [s]′ = S.irrelevanceTerm {A = sType} {t = s} [Γ]₂ [Γ]′ [G₊] [G₊]′ [s]
    in  [Γ]′ , [Gₙ]′
    ,   natrecᵛ {G} {z} {s} {n} [Γ]′ [ℕ] [G]′ [G₀]′ [G₊]′ [Gₙ]′ [z]′ [s]′ [n]
  fundamentalTerm (conv {t} {A} {B} ⊢t A′≡A)
    with fundamentalTerm ⊢t | fundamentalEq A′≡A
  fundamentalTerm (conv {t} {A} {B} ⊢t A′≡A) | [Γ] , [A′] , [t]
    | [Γ]₁ , [A′]₁ , [A] , [A′≡A] =
      let [Γ]′ = [Γ]₁
          [t]′ = S.irrelevanceTerm {A = A} {t = t} [Γ] [Γ]′ [A′] [A′]₁ [t]
      in  [Γ]′ , [A]
      ,   convᵛ {t} {A} {B} [Γ]′ [A′]₁ [A] [A′≡A] [t]′

  -- Fundamental theorem for term equality.
  fundamentalTermEq : ∀{Γ A t t′} → Γ ⊢ t ≡ t′ ∷ A
                    → ∃ω₄ λ ([Γ] : ⊩ᵛ Γ)
                    → [ Γ ⊩ᵛ⟨ ¹ ⟩ t ≡ t′ ∷ A / [Γ] ]
  fundamentalTermEq (refl D) with fundamentalTerm D
  ... | [Γ] , [A] , [t] =
    [Γ] , modelsTermEq [A] [t] [t]
                       (λ ⊢Δ [σ] → reflEqTerm (proj₁ ([A] ⊢Δ [σ]))
                                              (proj₁ ([t] ⊢Δ [σ])))
  fundamentalTermEq (sym D) with fundamentalTermEq D
  fundamentalTermEq (sym D) | [Γ] , modelsTermEq [A] [t′] [t] [t′≡t] =
    [Γ] , modelsTermEq [A] [t] [t′]
                       (λ ⊢Δ [σ] → symEqTerm (proj₁ ([A] ⊢Δ [σ]))
                                             ([t′≡t] ⊢Δ [σ]))
  fundamentalTermEq (trans {t} {u} {r} {A} t≡u u≡t′)
    with fundamentalTermEq t≡u | fundamentalTermEq u≡t′
  fundamentalTermEq (trans {t} {u} {r} {A} t≡u u≡t′)
    | [Γ] , modelsTermEq [A] [t] [u] [t≡u]
    | [Γ]₁ , modelsTermEq [A]₁ [t]₁ [u]₁ [t≡u]₁ =
      let [r]′ = S.irrelevanceTerm {A = A} {t = r} [Γ]₁ [Γ] [A]₁ [A] [u]₁
      in  [Γ] , modelsTermEq [A] [t] [r]′
                  (λ ⊢Δ [σ] →
                     let [σ]′ = S.irrelevanceSubst [Γ] [Γ]₁ ⊢Δ ⊢Δ [σ]
                         [t≡u]₁′ = irrelevanceEqTerm (proj₁ ([A]₁ ⊢Δ [σ]′))
                                                     (proj₁ ([A] ⊢Δ [σ]))
                                                     ([t≡u]₁ ⊢Δ [σ]′)
                     in  transEqTerm (proj₁ ([A] ⊢Δ [σ]))
                                     ([t≡u] ⊢Δ [σ]) [t≡u]₁′)
  fundamentalTermEq (conv {A} {B} {t} {u} t≡u A′≡A)
    with fundamentalTermEq t≡u | fundamentalEq A′≡A
  fundamentalTermEq (conv {A} {B} {t} {u} t≡u A′≡A)
    | [Γ] , modelsTermEq [A′] [t] [u] [t≡u] | [Γ]₁ , [A′]₁ , [A] , [A′≡A] =
      let [t]′ = S.irrelevanceTerm {A = A} {t = t} [Γ] [Γ]₁ [A′] [A′]₁ [t]
          [u]′ = S.irrelevanceTerm {A = A} {t = u} [Γ] [Γ]₁ [A′] [A′]₁ [u]
          [t]″ = convᵛ {t} {A} {B} [Γ]₁ [A′]₁ [A] [A′≡A] [t]′
          [u]″ = convᵛ {u} {A} {B} [Γ]₁ [A′]₁ [A] [A′≡A] [u]′
      in  [Γ]₁
      ,   modelsTermEq [A] [t]″ [u]″
            (λ ⊢Δ [σ] →
               let [σ]′ = S.irrelevanceSubst [Γ]₁ [Γ] ⊢Δ ⊢Δ [σ]
                   [t≡u]′ = irrelevanceEqTerm (proj₁ ([A′] ⊢Δ [σ]′))
                                              (proj₁ ([A′]₁ ⊢Δ [σ]))
                                              ([t≡u] ⊢Δ [σ]′)
               in  convEqTerm₁ (proj₁ ([A′]₁ ⊢Δ [σ])) (proj₁ ([A] ⊢Δ [σ]))
                               ([A′≡A] ⊢Δ [σ]) [t≡u]′)
  fundamentalTermEq (Π-cong {E} {F} {G} {H} ⊢F F≡H G≡E)
    with fundamental ⊢F | fundamentalTermEq F≡H | fundamentalTermEq G≡E
  ... | [Γ] , [F] | [Γ]₁ , modelsTermEq [U] [F]ₜ [H]ₜ [F≡H]ₜ
      | [Γ]₂ , modelsTermEq [U]₁ [G]ₜ [E]ₜ [G≡E]ₜ =
    let [U]′  = Uᵛ [Γ]
        [F]ₜ′ = S.irrelevanceTerm {A = U} {t = F} [Γ]₁ [Γ] [U] [U]′ [F]ₜ
        [H]ₜ′ = S.irrelevanceTerm {A = U} {t = H} [Γ]₁ [Γ] [U] [U]′ [H]ₜ
        [F]′  = S.irrelevance {A = F} [Γ] [Γ]₁ [F]
        [H]   = univᵛ {A = H} [Γ] [U]′ [H]ₜ′
        [F≡H] = S.irrelevanceEq {A = F} {B = H} [Γ]₁ [Γ] [F]′ [F]
                  (univEqᵛ {F} {H} [Γ]₁ [U] [F]′ [F≡H]ₜ)
        [U]₁′ = Uᵛ (VPack _ _ (V∙ {A = F} [Γ] [F]))
        [U]₂′ = Uᵛ (VPack _ _ (V∙ {A = H} [Γ] [H]))
        [G]ₜ′ = S.irrelevanceTerm {A = U} {t = G} [Γ]₂ ([Γ] ∙′ [F])
                                  [U]₁ (λ {Δ} {σ} → [U]₁′ {Δ} {σ}) [G]ₜ
        [E]ₜ′ = S.irrelevanceTermLift {A = U} {F = F} {H = H} {t = E}
                                      [Γ] [F] [H] [F≡H]
                                      (λ {Δ} {σ} → [U]₁′ {Δ} {σ})
                  (S.irrelevanceTerm {A = U} {t = E} [Γ]₂ ([Γ] ∙′ [F])
                                     [U]₁ (λ {Δ} {σ} → [U]₁′ {Δ} {σ}) [E]ₜ)
        [F≡H]ₜ′ = S.irrelevanceEqTerm {A = U} {t = F} {u = H}
                                      [Γ]₁ [Γ] [U] (Uᵛ [Γ]) [F≡H]ₜ
        [G≡E]ₜ′ = S.irrelevanceEqTerm {A = U} {t = G} {u = E} [Γ]₂
                                      (VPack _ _ (V∙ {A = F} [Γ] [F])) [U]₁
                                      (λ {Δ} {σ} → [U]₁′ {Δ} {σ}) [G≡E]ₜ
    in  [Γ]
    ,   modelsTermEq
          [U]′
          (Πᵗᵛ {F} {G} [Γ] [F] (λ {Δ} {σ} → [U]₁′ {Δ} {σ}) [F]ₜ′ [G]ₜ′)
          (Πᵗᵛ {H} {E} [Γ] [H] (λ {Δ} {σ} → [U]₂′ {Δ} {σ}) [H]ₜ′ [E]ₜ′)
          (Π-congᵗᵛ {F} {G} {H} {E} [Γ] [F] [H]
                    (λ {Δ} {σ} → [U]₁′ {Δ} {σ}) (λ {Δ} {σ} → [U]₂′ {Δ} {σ})
                    [F]ₜ′ [G]ₜ′ [H]ₜ′ [E]ₜ′ [F≡H]ₜ′ [G≡E]ₜ′)
  fundamentalTermEq (app-cong {a} {b} {f} {g} {F} {G} f≡g a≡b)
    with fundamentalTermEq f≡g | fundamentalTermEq a≡b
  ... | [Γ] , modelsTermEq [ΠFG] [f] [g] [f≡g]
      | [Γ]₁ , modelsTermEq [F] [a] [b] [a≡b] =
    let [ΠFG]′ = S.irrelevance {A = Π F ▹ G} [Γ] [Γ]₁ [ΠFG]
        [f]′ = S.irrelevanceTerm {A = Π F ▹ G} {t = f} [Γ] [Γ]₁ [ΠFG] [ΠFG]′ [f]
        [g]′ = S.irrelevanceTerm {A = Π F ▹ G} {t = g} [Γ] [Γ]₁ [ΠFG] [ΠFG]′ [g]
        [f≡g]′ = S.irrelevanceEqTerm {A = Π F ▹ G} {t = f} {u = g}
                                     [Γ] [Γ]₁ [ΠFG] [ΠFG]′ [f≡g]
        [G[a]] = substSΠ {F} {G} {a} [Γ]₁ [F] [ΠFG]′ [a]
        [G[b]] = substSΠ {F} {G} {b} [Γ]₁ [F] [ΠFG]′ [b]
        [G[a]≡G[b]] = substSΠEq {F} {G} {F} {G} {a} {b} [Γ]₁ [F] [F] [ΠFG]′
                                [ΠFG]′ (reflᵛ {Π F ▹ G} [Γ]₁ [ΠFG]′) [a] [b] [a≡b]
    in  [Γ]₁ , modelsTermEq [G[a]]
                            (appᵛ {F} {G} {f} {a} [Γ]₁ [F] [ΠFG]′ [f]′ [a])
                            (conv₂ᵛ {g ∘ b} {G [ a ]} {G [ b ]} [Γ]₁
                                    [G[a]] [G[b]] [G[a]≡G[b]]
                                    (appᵛ {F} {G} {g} {b}
                                          [Γ]₁ [F] [ΠFG]′ [g]′ [b]))
                            (app-congᵛ {F} {G} {f} {g} {a} {b}
                                       [Γ]₁ [F] [ΠFG]′ [f≡g]′ [a] [b] [a≡b])
  fundamentalTermEq (β-red {a} {b} {F} {G} ⊢F ⊢b ⊢a)
    with fundamental ⊢F | fundamentalTerm ⊢b | fundamentalTerm ⊢a
  ... | [Γ] , [F] | [Γ]₁ , [G] , [b] | [Γ]₂ , [F]₁ , [a] =
    let [G]′ = S.irrelevance {A = G} [Γ]₁ ([Γ]₂ ∙′ [F]₁) [G]
        [b]′ = S.irrelevanceTerm {A = G} {t = b} [Γ]₁ ([Γ]₂ ∙′ [F]₁) [G] [G]′ [b]
        [G[a]] = substS {F} {G} {a} [Γ]₂ [F]₁ [G]′ [a]
        [b[a]] = substSTerm {F} {G} {a} {b} [Γ]₂ [F]₁ [G]′ [b]′ [a]
        [lam] , [eq] =
          redSubstTermᵛ {G [ a ]} {(lam b) ∘ a} {b [ a ]} [Γ]₂
            (λ {Δ} {σ} ⊢Δ [σ] →
               let [liftσ] = liftSubstS {F = F} [Γ]₂ ⊢Δ [F]₁ [σ]
                   ⊢σF = escape (proj₁ ([F]₁ ⊢Δ [σ]))
                   ⊢σb = escapeTerm (proj₁ ([G]′ (⊢Δ ∙ ⊢σF) [liftσ]))
                                       (proj₁ ([b]′ (⊢Δ ∙ ⊢σF) [liftσ]))
                   ⊢σa = escapeTerm (proj₁ ([F]₁ ⊢Δ [σ]))
                                       (proj₁ ([a] ⊢Δ [σ]))
               in  PE.subst₂ (λ x y → _ ⊢ (lam (subst (liftSubst σ) b))
                                          ∘ (subst σ a) ⇒ x ∷ y)
                             (PE.sym (singleSubstLift b a))
                             (PE.sym (singleSubstLift G a))
                             (β-red ⊢σF ⊢σb ⊢σa))
                         [G[a]] [b[a]]
    in  [Γ]₂ , modelsTermEq [G[a]] [lam] [b[a]] [eq]
  fundamentalTermEq (η-eq {f} {g} {F} {G} ⊢F ⊢t ⊢t′ t≡t′) with
    fundamental ⊢F | fundamentalTerm ⊢t |
    fundamentalTerm ⊢t′ | fundamentalTermEq t≡t′
  ... | [Γ] , [F] | [Γ]₁ , [ΠFG] , [t] | [Γ]₂ , [ΠFG]₁ , [t′]
      | [Γ]₃ , modelsTermEq [G] [t0] [t′0] [t0≡t′0] =
    let [F]′ = S.irrelevance {A = F} [Γ] [Γ]₁ [F]
        [G]′ = S.irrelevance {A = G} [Γ]₃ ([Γ]₁ ∙′ [F]′) [G]
        [t′]′ = S.irrelevanceTerm {A = Π F ▹ G} {t = g}
                                  [Γ]₂ [Γ]₁ [ΠFG]₁ [ΠFG] [t′]
        [ΠFG]″ = Πᵛ {F} {G} [Γ]₁ [F]′ [G]′
        [t]″ = S.irrelevanceTerm {A = Π F ▹ G} {t = f}
                                  [Γ]₁ [Γ]₁ [ΠFG] [ΠFG]″ [t]
        [t′]″ = S.irrelevanceTerm {A = Π F ▹ G} {t = g}
                                   [Γ]₂ [Γ]₁ [ΠFG]₁ [ΠFG]″ [t′]
        [t0≡t′0]′ = S.irrelevanceEqTerm {A = G} {t = wk1 f ∘ var 0}
                                        {u = wk1 g ∘ var 0}
                                        [Γ]₃ ([Γ]₁ ∙′ [F]′) [G] [G]′ [t0≡t′0]
        [t≡t′] = η-eqᵛ {f} {g} {F} {G} [Γ]₁ [F]′ [G]′ [t]″ [t′]″ [t0≡t′0]′
        [t≡t′]′ = S.irrelevanceEqTerm {A = Π F ▹ G} {t = f} {u = g}
                                      [Γ]₁ [Γ]₁ [ΠFG]″ [ΠFG] [t≡t′]
    in  [Γ]₁ , modelsTermEq [ΠFG] [t] [t′]′ [t≡t′]′
  fundamentalTermEq (suc-cong x) with fundamentalTermEq x
  fundamentalTermEq (suc-cong {t} {u} x)
    | [Γ] , modelsTermEq [A] [t] [u] [t≡u] =
      let [suct] = sucᵛ {n = t} [Γ] [A] [t]
          [sucu] = sucᵛ {n = u} [Γ] [A] [u]
      in  [Γ] , modelsTermEq [A] [suct] [sucu]
                             (λ ⊢Δ [σ] →
                                sucEqTerm (proj₁ ([A] ⊢Δ [σ])) ([t≡u] ⊢Δ [σ]))
  fundamentalTermEq (natrec-cong {z} {z′} {s} {s′} {n} {n′} {F} {F′}
                                 F≡F′ z≡z′ s≡s′ n≡n′)
    with fundamentalEq F≡F′ |
         fundamentalTermEq z≡z′      |
         fundamentalTermEq s≡s′      |
         fundamentalTermEq n≡n′
  fundamentalTermEq (natrec-cong {z} {z′} {s} {s′} {n} {n′} {F} {F′}
                                 F≡F′ z≡z′ s≡s′ n≡n′) |
    [Γ]  , [F] , [F′] , [F≡F′] |
    [Γ]₁ , modelsTermEq [F₀] [z] [z′] [z≡z′] |
    [Γ]₂ , modelsTermEq [F₊] [s] [s′] [s≡s′] |
    [Γ]₃ , modelsTermEq [ℕ] [n] [n′] [n≡n′] =
      let sType = Π ℕ ▹ (F ▹▹ F [ suc (var 0) ]↑)
          s′Type = Π ℕ ▹ (F′ ▹▹ F′ [ suc (var 0) ]↑)
          [0] = S.irrelevanceTerm {l = ¹} {A = ℕ} {t = zero}
                                  [Γ]₃ [Γ]₃ (ℕᵛ [Γ]₃) [ℕ] (zeroᵛ {l = ¹} [Γ]₃)
          [F]′ = S.irrelevance {A = F} [Γ] ([Γ]₃ ∙′ [ℕ]) [F]
          [F₀]′ = S.irrelevance {A = F [ zero ]} [Γ]₁ [Γ]₃ [F₀]
          [F₊]′ = S.irrelevance {A = sType} [Γ]₂ [Γ]₃ [F₊]
          [Fₙ]′ = substS {ℕ} {F} {n} [Γ]₃ [ℕ] [F]′ [n]
          [F′]′ = S.irrelevance {A = F′} [Γ] ([Γ]₃ ∙′ [ℕ]) [F′]
          [F₀]″ = substS {ℕ} {F} {zero} [Γ]₃ [ℕ] [F]′ [0]
          [F′₀]′ = substS {ℕ} {F′} {zero} [Γ]₃ [ℕ] [F′]′ [0]
          [F′₊]′ = sucCase {F′} [Γ]₃ [ℕ] [F′]′
          [F′ₙ′]′ = substS {ℕ} {F′} {n′} [Γ]₃ [ℕ] [F′]′ [n′]
          [ℕ≡ℕ] = reflᵛ {ℕ} [Γ]₃ [ℕ]
          [0≡0] = reflᵗᵛ {ℕ} {zero} [Γ]₃ [ℕ] [0]
          [F≡F′]′ = S.irrelevanceEq {A = F} {B = F′}
                                    [Γ] ([Γ]₃ ∙′ [ℕ]) [F] [F]′ [F≡F′]
          [F₀≡F′₀] = substSEq {ℕ} {ℕ} {F} {F′} {zero} {zero}
                              [Γ]₃ [ℕ] [ℕ] [ℕ≡ℕ]
                              [F]′ [F′]′ [F≡F′]′ [0] [0] [0≡0]
          [F₀≡F′₀]′ = S.irrelevanceEq {A = F [ zero ]} {B = F′ [ zero ]}
                                      [Γ]₃ [Γ]₃ [F₀]″ [F₀]′ [F₀≡F′₀]
          [F₊≡F′₊] = sucCaseCong {F} {F′} [Γ]₃ [ℕ] [F]′ [F′]′ [F≡F′]′
          [F₊≡F′₊]′ = S.irrelevanceEq {A = sType} {B = s′Type}
                                      [Γ]₃ [Γ]₃ (sucCase {F} [Γ]₃ [ℕ] [F]′)
                                      [F₊]′ [F₊≡F′₊]
          [Fₙ≡F′ₙ′]′ = substSEq {ℕ} {ℕ} {F} {F′} {n} {n′}
                                [Γ]₃ [ℕ] [ℕ] [ℕ≡ℕ] [F]′ [F′]′ [F≡F′]′
                                [n] [n′] [n≡n′]
          [z]′ = S.irrelevanceTerm {A = F [ zero ]} {t = z}
                                   [Γ]₁ [Γ]₃ [F₀] [F₀]′ [z]
          [z′]′ = convᵛ {z′} {F [ zero ]} {F′ [ zero ]}
                        [Γ]₃ [F₀]′ [F′₀]′ [F₀≡F′₀]′
                        (S.irrelevanceTerm {A = F [ zero ]} {t = z′}
                                           [Γ]₁ [Γ]₃ [F₀] [F₀]′ [z′])
          [z≡z′]′ = S.irrelevanceEqTerm {A = F [ zero ]} {t = z} {u = z′}
                                        [Γ]₁ [Γ]₃ [F₀] [F₀]′ [z≡z′]
          [s]′ = S.irrelevanceTerm {A = sType} {t = s} [Γ]₂ [Γ]₃ [F₊] [F₊]′ [s]
          [s′]′ = convᵛ {s′} {sType} {s′Type} [Γ]₃ [F₊]′ [F′₊]′ [F₊≡F′₊]′
                        (S.irrelevanceTerm {A = sType} {t = s′}
                                           [Γ]₂ [Γ]₃ [F₊] [F₊]′ [s′])
          [s≡s′]′ = S.irrelevanceEqTerm {A = sType} {t = s} {u = s′}
                                        [Γ]₂ [Γ]₃ [F₊] [F₊]′ [s≡s′]
      in  [Γ]₃
      ,   modelsTermEq [Fₙ]′
                       (natrecᵛ {F} {z} {s} {n}
                                [Γ]₃ [ℕ] [F]′ [F₀]′ [F₊]′ [Fₙ]′ [z]′ [s]′ [n])
                       (conv₂ᵛ {natrec F′ z′ s′ n′} {F [ n ]} {F′ [ n′ ]}
                               [Γ]₃ [Fₙ]′ [F′ₙ′]′ [Fₙ≡F′ₙ′]′
                               (natrecᵛ {F′} {z′} {s′} {n′}
                                        [Γ]₃ [ℕ] [F′]′ [F′₀]′ [F′₊]′ [F′ₙ′]′
                                        [z′]′ [s′]′ [n′]))
                       (natrec-congᵛ {F} {F′} {z} {z′} {s} {s′} {n} {n′}
                                     [Γ]₃ [ℕ] [F]′ [F′]′ [F≡F′]′
                                     [F₀]′ [F′₀]′ [F₀≡F′₀]′
                                     [F₊]′ [F′₊]′ [F₊≡F′₊]′ [Fₙ]′
                                     [z]′ [z′]′ [z≡z′]′
                                     [s]′ [s′]′ [s≡s′]′ [n] [n′] [n≡n′])
  fundamentalTermEq (natrec-zero {z} {s} {F} ⊢F ⊢z ⊢s)
    with fundamental ⊢F | fundamentalTerm ⊢z | fundamentalTerm ⊢s
  fundamentalTermEq (natrec-zero {z} {s} {F} ⊢F ⊢z ⊢s) | [Γ] , [F]
    | [Γ]₁ , [F₀] , [z] | [Γ]₂ , [F₊] , [s] =
    let sType = Π ℕ ▹ (F ▹▹ F [ suc (var 0) ]↑)
        [Γ]′ = [Γ]₁
        [ℕ]′ = ℕᵛ {l = ¹} [Γ]′
        [F₊]′ = S.irrelevance {A = sType} [Γ]₂ [Γ]′ [F₊]
        [s]′ = S.irrelevanceTerm {A = sType} {t = s} [Γ]₂ [Γ]′ [F₊] [F₊]′ [s]
        [F]′ = S.irrelevance {A = F} [Γ] ([Γ]′ ∙′ [ℕ]′) [F]
        d , r =
          redSubstTermᵛ {F [ zero ]} {natrec F z s zero} {z} [Γ]′
            (λ {Δ} {σ} ⊢Δ [σ] →
               let ⊢ℕ = escape (proj₁ ([ℕ]′ ⊢Δ [σ]))
                   ⊢F = escape (proj₁ ([F]′ (⊢Δ ∙ ⊢ℕ)
                                               (liftSubstS {F = ℕ}
                                                           [Γ]′ ⊢Δ [ℕ]′ [σ])))
                   ⊢z = PE.subst (λ x → Δ ⊢ subst σ z ∷ x)
                                 (singleSubstLift F zero)
                                 (escapeTerm (proj₁ ([F₀] ⊢Δ [σ]))
                                                (proj₁ ([z] ⊢Δ [σ])))
                   ⊢s = PE.subst (λ x → Δ ⊢ subst σ s ∷ x)
                                 (natrecSucCase σ F)
                                 (escapeTerm (proj₁ ([F₊]′ ⊢Δ [σ]))
                                                (proj₁ ([s]′ ⊢Δ [σ])))
               in PE.subst (λ x → Δ ⊢ subst σ (natrec F z s zero)
                                    ⇒ subst σ z ∷ x)
                           (PE.sym (singleSubstLift F zero))
                           (natrec-zero ⊢F ⊢z ⊢s))
                        [F₀] [z]
    in  [Γ]′ , modelsTermEq [F₀] d [z] r
  fundamentalTermEq (natrec-suc {n} {z} {s} {F} ⊢n ⊢F ⊢z ⊢s)
    with fundamentalTerm ⊢n | fundamental ⊢F
       | fundamentalTerm ⊢z | fundamentalTerm ⊢s
  ... | [Γ] , [ℕ] , [n] | [Γ]₁ , [F] | [Γ]₂ , [F₀] , [z] | [Γ]₃ , [F₊] , [s] =
    let [ℕ]′ = S.irrelevance {A = ℕ} [Γ] [Γ]₃ [ℕ]
        [n]′ = S.irrelevanceTerm {A = ℕ} {t = n} [Γ] [Γ]₃ [ℕ] [ℕ]′ [n]
        [sucn] = sucᵛ {n = n} [Γ]₃ [ℕ]′ [n]′
        [F₀]′ = S.irrelevance {A = F [ zero ]} [Γ]₂ [Γ]₃ [F₀]
        [z]′ = S.irrelevanceTerm {A = F [ zero ]} {t = z}
                                 [Γ]₂ [Γ]₃ [F₀] [F₀]′ [z]
        [F]′ = S.irrelevance {A = F} [Γ]₁ ([Γ]₃ ∙′ [ℕ]′) [F]
        [F[sucn]] = substS {ℕ} {F} {suc n} [Γ]₃ [ℕ]′ [F]′ [sucn]
        [Fₙ]′ = substS {ℕ} {F} {n} [Γ]₃ [ℕ]′ [F]′ [n]′
        [natrecₙ] = natrecᵛ {F} {z} {s} {n}
                            [Γ]₃ [ℕ]′ [F]′ [F₀]′ [F₊] [Fₙ]′ [z]′ [s] [n]′
        t = (s ∘ n) ∘ (natrec F z s n)
        q = subst (liftSubst (sgSubst n))
                  (wk1 (F [ suc (var 0) ]↑))
        y = S.irrelevanceTerm′
              {A = q [ natrec F z s n ]} {A′ = F [ suc n ]} {t = t}
              (natrecIrrelevantSubst′ F z s n) [Γ]₃ [Γ]₃
              (substSΠ {F [ n ]} {q} {natrec F z s n} [Γ]₃
                (substS {ℕ} {F} {n} [Γ]₃ [ℕ]′ [F]′ [n]′)
                (substSΠ {ℕ} {F ▹▹ F [ suc (var 0) ]↑} {n}
                         [Γ]₃ [ℕ]′ [F₊] [n]′)
                [natrecₙ])
              [F[sucn]]
              (appᵛ {F [ n ]} {q} {s ∘ n} {natrec F z s n} [Γ]₃ [Fₙ]′
                (substSΠ {ℕ} {F ▹▹ F [ suc (var 0) ]↑} {n}
                         [Γ]₃ [ℕ]′ [F₊] [n]′)
                (appᵛ {ℕ} {F ▹▹ F [ suc (var 0) ]↑} {s} {n}
                      [Γ]₃ [ℕ]′ [F₊] [s] [n]′)
                [natrecₙ])
        d , r =
          redSubstTermᵛ {F [ suc n ]} {natrec F z s (suc n)} {t } {¹} {_} [Γ]₃
            (λ {Δ} {σ} ⊢Δ [σ] →
               let ⊢n = escapeTerm (proj₁ ([ℕ]′ ⊢Δ [σ]))
                                      (proj₁ ([n]′ ⊢Δ [σ]))
                   ⊢ℕ = escape (proj₁ ([ℕ]′ ⊢Δ [σ]))
                   ⊢F = escape (proj₁ ([F]′ (⊢Δ ∙ ⊢ℕ)
                                               (liftSubstS {F = ℕ}
                                                           [Γ]₃ ⊢Δ [ℕ]′ [σ])))
                   ⊢z = PE.subst (λ x → Δ ⊢ subst σ z ∷ x)
                                 (singleSubstLift F zero)
                                 (escapeTerm (proj₁ ([F₀]′ ⊢Δ [σ]))
                                                (proj₁ ([z]′ ⊢Δ [σ])))
                   ⊢s = PE.subst (λ x → Δ ⊢ subst σ s ∷ x)
                                 (natrecSucCase σ F)
                                 (escapeTerm (proj₁ ([F₊] ⊢Δ [σ]))
                                                (proj₁ ([s] ⊢Δ [σ])))
                   r = _⊢_⇒_∷_.natrec-suc {n = subst σ n}
                                          {z = subst σ z} {s = subst σ s}
                                          {F = subst (liftSubst σ) F}
                                          ⊢n ⊢F ⊢z ⊢s
               in PE.subst (λ x → Δ ⊢ subst σ (natrec F z s (suc n))
                                    ⇒ (subst σ t) ∷ x)
                           (PE.trans (PE.trans (substCompEq F)
                             (substVar-to-subst (λ { 0 → PE.refl
                                         ; (1+ x) → PE.trans (subst-wk (σ x))
                                                              (subst-id (σ x))
                                         })
                                      F))
                             (PE.sym (substCompEq F)))
                           r)
                        [F[sucn]] y
    in  [Γ]₃ , modelsTermEq [F[sucn]] d y r

-- Fundamental theorem for substitutions.
fundamentalSubst : ∀ {Γ Δ σ} (⊢Γ : ⊢ Γ) (⊢Δ : ⊢ Δ)
      → Δ ⊢ˢ σ ∷ Γ
      → ∃ω₄ λ [Γ] → Δ ⊩ˢ σ ∷ Γ / [Γ] / ⊢Δ
fundamentalSubst ε ⊢Δ [σ] = ε′ , tt
fundamentalSubst (⊢Γ ∙ ⊢A) ⊢Δ ([tailσ] , [headσ]) =
  let [Γ] , [A] = fundamental ⊢A
      [Δ] , [A]′ , [t] = fundamentalTerm [headσ]
      [Γ]′ , [σ] = fundamentalSubst ⊢Γ ⊢Δ [tailσ]
      [tailσ]′ = S.irrelevanceSubst [Γ]′ [Γ] ⊢Δ ⊢Δ [σ]
      [idA]  = proj₁ ([A]′ (soundContext [Δ]) (idSubstS [Δ]))
      [idA]′ = proj₁ ([A] ⊢Δ [tailσ]′)
      [idt]  = proj₁ ([t] (soundContext [Δ]) (idSubstS [Δ]))
  in  [Γ] ∙′ [A] , ([tailσ]′
  ,   irrelevanceTerm″ (subst-id _) (subst-id _) [idA] [idA]′ [idt])

-- Fundamental theorem for substitution equality.
fundamentalSubstEq : ∀ {Γ Δ σ σ′} (⊢Γ : ⊢ Γ) (⊢Δ : ⊢ Δ)
      → Δ ⊢ˢ σ ≡ σ′ ∷ Γ
      → ∃ω₄ λ [Γ]
      → ∃ω₃²  λ [σ] ([σ′] : Δ ⊩ˢ σ′ ∷ Γ / [Γ] / ⊢Δ)
      → Δ ⊩ˢ σ ≡ σ′ ∷ Γ / [Γ] / ⊢Δ / [σ]
fundamentalSubstEq ε ⊢Δ σ = ε′ , tt , tt , tt
fundamentalSubstEq (⊢Γ ∙ ⊢A) ⊢Δ (tailσ≡σ′ , headσ≡σ′) =
  let [Γ] , [A] = fundamental ⊢A
      [Γ]′ , [tailσ] , [tailσ′] , [tailσ≡σ′] = fundamentalSubstEq ⊢Γ ⊢Δ tailσ≡σ′
      [Δ] , modelsTermEq [A]′ [t] [t′] [t≡t′] = fundamentalTermEq headσ≡σ′
      [tailσ]′ = S.irrelevanceSubst [Γ]′ [Γ] ⊢Δ ⊢Δ [tailσ]
      [tailσ′]′ = S.irrelevanceSubst [Γ]′ [Γ] ⊢Δ ⊢Δ [tailσ′]
      [tailσ≡σ′]′ = S.irrelevanceSubstEq [Γ]′ [Γ] ⊢Δ ⊢Δ [tailσ] [tailσ]′ [tailσ≡σ′]
      [idA]  = proj₁ ([A]′ (soundContext [Δ]) (idSubstS [Δ]))
      [idA]′ = proj₁ ([A] ⊢Δ [tailσ]′)
      [idA]″ = proj₁ ([A] ⊢Δ [tailσ′]′)
      [idt]  = proj₁ ([t] (soundContext [Δ]) (idSubstS [Δ]))
      [idt′] = proj₁ ([t′] (soundContext [Δ]) (idSubstS [Δ]))
      [idt≡t′]  = [t≡t′] (soundContext [Δ]) (idSubstS [Δ])
  in  [Γ] ∙′ [A]
  ,   ([tailσ]′ , irrelevanceTerm″ (subst-id _) (subst-id _) [idA] [idA]′ [idt])
  ,   ([tailσ′]′ , convTerm₁ [idA]′ [idA]″
                             (proj₂ ([A] ⊢Δ [tailσ]′) [tailσ′]′ [tailσ≡σ′]′)
                             (irrelevanceTerm″ (subst-id _) (subst-id _)
                                                [idA] [idA]′ [idt′]))
  ,   ([tailσ≡σ′]′ , irrelevanceEqTerm″ (subst-id _) (subst-id _) (subst-id _)
                                         [idA] [idA]′ [idt≡t′])


{-
This second-order term syntax was created from the following second-order syntax description:

syntax Sum | S

type
  _⊕_ : 2-ary | l30

term
  inl  : α  ->  α ⊕ β
  inr  : β  ->  α ⊕ β
  case : α ⊕ β  α.γ  β.γ  ->  γ

theory
  (lβ) a : α   f : α.γ  g : β.γ |> case (inl(a), x.f[x], y.g[y])      = f[a]
  (rβ) b : β   f : α.γ  g : β.γ |> case (inr(b), x.f[x], y.g[y])      = g[b]
  (cη) s : α ⊕ β  c : (α ⊕ β).γ |> case (s, x.c[inl(x)], y.c[inr(y)]) = c[s]
-}

module Sum.Syntax where

open import SOAS.Common
open import SOAS.Context
open import SOAS.Variable
open import SOAS.Families.Core
open import SOAS.Construction.Structure
open import SOAS.ContextMaps.Inductive

open import SOAS.Metatheory.Syntax

open import Sum.Signature

private
  variable
    Γ Δ Π : Ctx
    α β γ : ST
    𝔛 : Familyₛ

-- Inductive term declaration
module S:Terms (𝔛 : Familyₛ) where

  data S : Familyₛ where
    var  : ℐ ⇾̣ S
    mvar : 𝔛 α Π → Sub S Π Γ → S α Γ

    inl  : S α Γ → S (α ⊕ β) Γ
    inr  : S β Γ → S (α ⊕ β) Γ
    case : S (α ⊕ β) Γ → S γ (α ∙ Γ) → S γ (β ∙ Γ) → S γ Γ

  open import SOAS.Metatheory.MetaAlgebra ⅀F 𝔛

  Sᵃ : MetaAlg S
  Sᵃ = record
    { 𝑎𝑙𝑔 = λ where
      (inlₒ  ⋮ a)         → inl  a
      (inrₒ  ⋮ a)         → inr  a
      (caseₒ ⋮ a , b , c) → case a b c
    ; 𝑣𝑎𝑟 = var ; 𝑚𝑣𝑎𝑟 = λ 𝔪 mε → mvar 𝔪 (tabulate mε) }

  module Sᵃ = MetaAlg Sᵃ

  module _ {𝒜 : Familyₛ}(𝒜ᵃ : MetaAlg 𝒜) where

    open MetaAlg 𝒜ᵃ

    𝕤𝕖𝕞 : S ⇾̣ 𝒜
    𝕊 : Sub S Π Γ → Π ~[ 𝒜 ]↝ Γ
    𝕊 (t ◂ σ) new = 𝕤𝕖𝕞 t
    𝕊 (t ◂ σ) (old v) = 𝕊 σ v
    𝕤𝕖𝕞 (mvar 𝔪 mε) = 𝑚𝑣𝑎𝑟 𝔪 (𝕊 mε)
    𝕤𝕖𝕞 (var v) = 𝑣𝑎𝑟 v

    𝕤𝕖𝕞 (inl  a)     = 𝑎𝑙𝑔 (inlₒ  ⋮ 𝕤𝕖𝕞 a)
    𝕤𝕖𝕞 (inr  a)     = 𝑎𝑙𝑔 (inrₒ  ⋮ 𝕤𝕖𝕞 a)
    𝕤𝕖𝕞 (case a b c) = 𝑎𝑙𝑔 (caseₒ ⋮ 𝕤𝕖𝕞 a , 𝕤𝕖𝕞 b , 𝕤𝕖𝕞 c)

    𝕤𝕖𝕞ᵃ⇒ : MetaAlg⇒ Sᵃ 𝒜ᵃ 𝕤𝕖𝕞
    𝕤𝕖𝕞ᵃ⇒ = record
      { ⟨𝑎𝑙𝑔⟩ = λ{ {t = t} → ⟨𝑎𝑙𝑔⟩ t }
      ; ⟨𝑣𝑎𝑟⟩ = refl
      ; ⟨𝑚𝑣𝑎𝑟⟩ = λ{ {𝔪 = 𝔪}{mε} → cong (𝑚𝑣𝑎𝑟 𝔪) (dext (𝕊-tab mε)) }  }
      where
      open ≡-Reasoning
      ⟨𝑎𝑙𝑔⟩ : (t : ⅀ S α Γ) → 𝕤𝕖𝕞 (Sᵃ.𝑎𝑙𝑔 t) ≡ 𝑎𝑙𝑔 (⅀₁ 𝕤𝕖𝕞 t)
      ⟨𝑎𝑙𝑔⟩ (inlₒ  ⋮ _) = refl
      ⟨𝑎𝑙𝑔⟩ (inrₒ  ⋮ _) = refl
      ⟨𝑎𝑙𝑔⟩ (caseₒ ⋮ _) = refl

      𝕊-tab : (mε : Π ~[ S ]↝ Γ)(v : ℐ α Π) → 𝕊 (tabulate mε) v ≡ 𝕤𝕖𝕞 (mε v)
      𝕊-tab mε new = refl
      𝕊-tab mε (old v) = 𝕊-tab (mε ∘ old) v

    module _ (g : S ⇾̣ 𝒜)(gᵃ⇒ : MetaAlg⇒ Sᵃ 𝒜ᵃ g) where

      open MetaAlg⇒ gᵃ⇒

      𝕤𝕖𝕞! : (t : S α Γ) → 𝕤𝕖𝕞 t ≡ g t
      𝕊-ix : (mε : Sub S Π Γ)(v : ℐ α Π) → 𝕊 mε v ≡ g (index mε v)
      𝕊-ix (x ◂ mε) new = 𝕤𝕖𝕞! x
      𝕊-ix (x ◂ mε) (old v) = 𝕊-ix mε v
      𝕤𝕖𝕞! (mvar 𝔪 mε) rewrite cong (𝑚𝑣𝑎𝑟 𝔪) (dext (𝕊-ix mε))
        = trans (sym ⟨𝑚𝑣𝑎𝑟⟩) (cong (g ∘ mvar 𝔪) (tab∘ix≈id mε))
      𝕤𝕖𝕞! (var v) = sym ⟨𝑣𝑎𝑟⟩

      𝕤𝕖𝕞! (inl a) rewrite 𝕤𝕖𝕞! a = sym ⟨𝑎𝑙𝑔⟩
      𝕤𝕖𝕞! (inr a) rewrite 𝕤𝕖𝕞! a = sym ⟨𝑎𝑙𝑔⟩
      𝕤𝕖𝕞! (case a b c) rewrite 𝕤𝕖𝕞! a | 𝕤𝕖𝕞! b | 𝕤𝕖𝕞! c = sym ⟨𝑎𝑙𝑔⟩

-- Syntax instance for the signature
S:Syn : Syntax
S:Syn = record
  { ⅀F = ⅀F
  ; ⅀:CS = ⅀:CompatStr
  ; mvarᵢ = S:Terms.mvar
  ; 𝕋:Init = λ 𝔛 → let open S:Terms 𝔛 in record
    { ⊥ = S ⋉ Sᵃ
    ; ⊥-is-initial = record { ! = λ{ {𝒜 ⋉ 𝒜ᵃ} → 𝕤𝕖𝕞 𝒜ᵃ ⋉ 𝕤𝕖𝕞ᵃ⇒ 𝒜ᵃ }
    ; !-unique = λ{ {𝒜 ⋉ 𝒜ᵃ} (f ⋉ fᵃ⇒) {x = t} → 𝕤𝕖𝕞! 𝒜ᵃ f fᵃ⇒ t } } } }

-- Instantiation of the syntax and metatheory
open Syntax S:Syn public
open S:Terms public
open import SOAS.Families.Build public
open import SOAS.Syntax.Shorthands Sᵃ public
open import SOAS.Metatheory S:Syn public


module Plylet where

open import Data.String.Base
open import Data.Char.Base
open import Data.List.Base

data Type : Set where
  -- The type of the unit value
  TUnit : Type
  -- The type constructor for functions →
  TFunc : Type -> Type -> Type
  -- A type loaded from the prelude
  TBuiltin : String -> Type

data Term : Set where
  -- The value of unit
  VUnit : Term
  -- An well-typed application is a valid term
  VApp : Term → Term → Term
  -- A well-formed let expression is a valid term
  VLet : Char -> Term -> Term -> Term
  -- A function from the prelude
  VFunc : String -> Term

data Judgment : Set where
  -- A typing judgment
  TypeJ : Term → Type → Judgment
  -- A coercion validity judgment
  CoerceJ : Type → Type → Judgment

Gamma : Set
Gamma = List Judgment

baseEnv : Gamma
baseEnv =
  TypeJ VUnit TUnit
  ∷ []


open import Agda.Builtin.Equality
open import Agda.Primitive

record R a : Set (lsuc a) where
  field
    P : {A : Set a} → A → A → Set a

r : ∀ ℓ → R ℓ
R.P (r _) = _≡_

postulate
  cong  : ∀ {a b} {A : Set a} {B : Set b} {x y : A}
          (f : A → B) → R.P (r a) x y → R.P (r b) (f x) (f y)
  magic : ∀ {a} {A : Set a} {x y : A} (z : A) → y ≡ z → x ≡ z

record Raw-monad {f : Level → Level}
                 (M : ∀ {ℓ} → Set ℓ → Set (f ℓ)) ℓ₁ ℓ₂ :
                 Set (f ℓ₁ ⊔ f ℓ₂ ⊔ lsuc (ℓ₁ ⊔ ℓ₂)) where
  infixl 5 _>>=_
  field
    return : {A : Set ℓ₁} → A → M A
    _>>=_  : {A : Set ℓ₁} {B : Set ℓ₂} → M A → (A → M B) → M B

module Raw-monad⁺
  {f : Level → Level}
  {M : ∀ {ℓ} → Set ℓ → Set (f ℓ)}
  (m : ∀ {ℓ₁ ℓ₂} → Raw-monad M ℓ₁ ℓ₂)
  where

  private
    module M′ {ℓ₁ ℓ₂} = Raw-monad (m {ℓ₁ = ℓ₁} {ℓ₂ = ℓ₂})
  open M′ public using (_>>=_)

  return : ∀ {a} {A : Set a} → A → M A
  return = M′.return {ℓ₂ = lzero}

open Raw-monad⁺ ⦃ … ⦄ public

record Monad⁻ {f : Level → Level}
              (M : ∀ {ℓ} → Set ℓ → Set (f ℓ))
              ⦃ raw-monad : ∀ {ℓ₁ ℓ₂} → Raw-monad M ℓ₁ ℓ₂ ⦄
              ℓ₁ ℓ₂ ℓ₃ :
              Set (f ℓ₁ ⊔ f ℓ₂ ⊔ f ℓ₃ ⊔ lsuc (ℓ₁ ⊔ ℓ₂ ⊔ ℓ₃)) where
  field
    associativity :
      {A : Set ℓ₁} {B : Set ℓ₂} {C : Set ℓ₃} →
      (x : M A) (f : A → M B) (g : B → M C) →
      x >>= (λ x → f x >>= g) ≡ x >>= f >>= g

module Monad⁻⁺
  {f : Level → Level}
  {M : ∀ {ℓ} → Set ℓ → Set (f ℓ)}
  ⦃ raw-monad : ∀ {ℓ₁ ℓ₂} → Raw-monad M ℓ₁ ℓ₂ ⦄
  (m : ∀ {ℓ₁ ℓ₂ ℓ₃} → Monad⁻ M ℓ₁ ℓ₂ ℓ₃)
  where

  private
    module M′ {ℓ₁ ℓ₂ ℓ₃} = Monad⁻ (m {ℓ₁ = ℓ₁} {ℓ₂ = ℓ₂} {ℓ₃ = ℓ₃})
  open M′ public

open Monad⁻⁺ ⦃ … ⦄ public

data Maybe {a} (A : Set a) : Set a where
  nothing : Maybe A
  just    : A → Maybe A

postulate
  maybe : ∀ {a b} {A : Set a} {B : Maybe A → Set b} →
          ((x : A) → B (just x)) → B nothing → (x : Maybe A) → B x

record MaybeT {ℓ}
              (f : Level → Level)
              (M : Set ℓ → Set (f ℓ))
              (A : Set ℓ) : Set (f ℓ) where
  constructor wrap
  field
    run : M (Maybe A)

open MaybeT

instance

  transformʳ :
    ∀ {ℓ₁ ℓ₂}
    {f : Level → Level}
    {M : ∀ {ℓ} → Set ℓ → Set (f ℓ)} →
    ⦃ raw-monad : ∀ {ℓ₁ ℓ₂} → Raw-monad M ℓ₁ ℓ₂ ⦄ →
    Raw-monad (MaybeT f M) ℓ₁ ℓ₂
  run (Raw-monad.return transformʳ x)   = return (just x)
  run (Raw-monad._>>=_  transformʳ x f) =
    run x >>= maybe (λ x → run (f x)) (return nothing)

transformᵐ :
  {f : Level → Level}
  {M : ∀ {ℓ} → Set ℓ → Set (f ℓ)}
  ⦃ raw-monad : ∀ {ℓ₁ ℓ₂} → Raw-monad M ℓ₁ ℓ₂ ⦄
  ⦃ monad : ∀ {ℓ₁ ℓ₂ ℓ₃} → Monad⁻ M ℓ₁ ℓ₂ ℓ₃ ⦄ →
  Monad⁻ (MaybeT f M) lzero lzero lzero
Monad⁻.associativity transformᵐ x f g = cong wrap (
  magic ((run x >>= maybe (λ x → run (f x)) (return nothing)) >>=
         maybe (λ x → run (g x)) (return nothing))
        (associativity _ _ _))

-- WAS: rejected in 2.6.0 with
-- No instance of type Raw-monad _M_345 ℓ₁ ℓ₂ was found in scope.

-- Should succeed.


{-# OPTIONS --without-K --rewriting #-}

open import HoTT
open import cw.CW
open import homotopy.DisjointlyPointedSet
open import groups.Int
open import cohomology.Theory
open import cohomology.ChainComplex

module cw.cohomology.ReconstructedCochainsIsoCellularCochains {i : ULevel}
  (OT : OrdinaryTheory i) where

  open OrdinaryTheory OT
  open import cw.cohomology.WedgeOfCells OT
  open import cw.cohomology.cellular.ChainComplex as CCC
  open import cw.cohomology.reconstructed.cochain.Complex OT as RCC
  open import cw.cohomology.reconstructed.TipAndAugment OT

  private
    rcc-iso-ccc-nth : ∀ {n} (⊙skel : ⊙Skeleton n) {m} (m≤n : m ≤ n)
      → ⊙has-cells-with-choice 0 ⊙skel i
      →  AbGroup.grp (RCC.cochain-template ⊙skel (inl m≤n))
      ≃ᴳ hom-group (AbGroup.grp (CCC.chain-template (⊙Skeleton.skel ⊙skel) (inl m≤n))) (C2-abgroup 0)
    rcc-iso-ccc-nth ⊙skel {m = O} (inl idp) ac
      =   FreeAbGroup-extend-iso (C2-abgroup 0)
      ∘eᴳ C2×CX₀-diag-β ⊙skel ac
    rcc-iso-ccc-nth ⊙skel {m = S m} (inl idp) ac
      =   FreeAbGroup-extend-iso (C2-abgroup 0)
      ∘eᴳ CXₙ/Xₙ₋₁-diag-β ⊙skel ac
    rcc-iso-ccc-nth ⊙skel {m = O} (inr ltS) ac =
      rcc-iso-ccc-nth (⊙cw-init ⊙skel) (inl idp) (⊙init-has-cells-with-choice ⊙skel ac)
    rcc-iso-ccc-nth ⊙skel {m = S m} (inr ltS) ac =
      rcc-iso-ccc-nth (⊙cw-init ⊙skel) (inl idp) (⊙init-has-cells-with-choice ⊙skel ac)
    rcc-iso-ccc-nth ⊙skel {m = O} (inr (ltSR lt)) ac =
      rcc-iso-ccc-nth (⊙cw-init ⊙skel) (inr lt) (⊙init-has-cells-with-choice ⊙skel ac)
    rcc-iso-ccc-nth ⊙skel {m = S m} (inr (ltSR lt)) ac =
      rcc-iso-ccc-nth (⊙cw-init ⊙skel) (inr lt) (⊙init-has-cells-with-choice ⊙skel ac)

    rcc-iso-ccc-above : ∀ {n} (⊙skel : ⊙Skeleton n) {m} (m≰n : ¬ (m ≤ n))
      →  AbGroup.grp (RCC.cochain-template ⊙skel (inr m≰n))
      ≃ᴳ hom-group (AbGroup.grp (CCC.chain-template (⊙Skeleton.skel ⊙skel) (inr m≰n))) (C2-abgroup 0)
    rcc-iso-ccc-above ⊙skel _
      =   pre∘ᴳ-iso (C2-abgroup 0) lower-iso
      ∘eᴳ trivial-iso-Unit (hom₁-Unit-is-trivial (C2-abgroup 0)) ⁻¹ᴳ
      ∘eᴳ lower-iso

  rcc-iso-ccc-template : ∀ {n} (⊙skel : ⊙Skeleton n) {m : ℕ} (m≤n? : Dec (m ≤ n))
    → ⊙has-cells-with-choice 0 ⊙skel i
    →  AbGroup.grp (RCC.cochain-template ⊙skel m≤n?)
    ≃ᴳ hom-group (AbGroup.grp (CCC.chain-template (⊙Skeleton.skel ⊙skel) m≤n?)) (C2-abgroup 0)
  rcc-iso-ccc-template ⊙skel {m} (inl m≤n) ac = rcc-iso-ccc-nth ⊙skel m≤n ac
  rcc-iso-ccc-template ⊙skel {m} (inr m≰n) _ = rcc-iso-ccc-above ⊙skel m≰n

  rcc-iso-ccc : ∀ {n} (⊙skel : ⊙Skeleton n) (m : ℕ)
    → ⊙has-cells-with-choice 0 ⊙skel i
    →  AbGroup.grp (RCC.cochain-template ⊙skel (≤-dec m n))
    ≃ᴳ hom-group (AbGroup.grp (CCC.chain-template (⊙Skeleton.skel ⊙skel) (≤-dec m n))) (C2-abgroup 0)
  rcc-iso-ccc {n} ⊙skel m = rcc-iso-ccc-template ⊙skel (≤-dec m n)

  rhead-iso-chead : C2 0 ≃ᴳ hom-group (Lift-group {j = i} ℤ-group) (C2-abgroup 0)
  rhead-iso-chead
    =   pre∘ᴳ-iso (C2-abgroup 0) (lower-iso {j = i})
    ∘eᴳ ℤ→ᴳ-iso-idf (C2-abgroup 0) ⁻¹ᴳ


open import Prelude
open import Data.Vec
open import Data.List.Properties
open import Data.List.Any hiding (map)
open import Data.List.All as All using ()
open import Extensions.Vec
open import Data.Product hiding (map)

module Implicits.Resolution.Ambiguous.Semantics where

open import Implicits.Syntax
open import Implicits.Resolution.Ambiguous.Resolution
open import Implicits.Substitutions
open import Implicits.Substitutions.Lemmas
open import Implicits.Semantics
open import Implicits.Semantics.Lemmas

open import SystemF.Everything as F using ()

module DerivationSemantics where

  ⟦_,_⟧r : ∀ {ν n} {Δ} {Γ : Ctx ν n} {a} → Δ ⊢ᵣ a → Γ # Δ →
           ∃ λ t → ⟦ Γ ⟧ctx→ F.⊢ t ∈ ⟦ a ⟧tp→
  ⟦_,_⟧r (r-tabs {r = r} p) m with ⟦ p , #tvar m ⟧r
  ⟦_,_⟧r {Γ = Γ} (r-tabs {r = r} p) m | _ , x =
    , F.Λ (subst (λ u → u F.⊢ _ ∈ ⟦ r ⟧tp→) (⟦weaken⟧ctx→ Γ) x)
  ⟦_,_⟧r (r-tapp a p) m with ⟦ p , m ⟧r
  ⟦_,_⟧r {Γ = Γ} (r-tapp {r = a} b p) m | _ , x =
    , subst
      (λ u → ⟦ Γ ⟧ctx→ F.⊢ _ ∈ u)
      (sym $ ⟦a/sub⟧tp→ a b)
      (x F.[ ⟦ b ⟧tp→ ])
  ⟦_,_⟧r {Γ = Γ} {a = a} (r-ivar x) m with ∈⟶index (All.lookup m x)
  ⟦_,_⟧r {Γ = Γ} {a = a} (r-ivar x) m | i , lookup-i≡r =
    , subst (λ u → _ F.⊢ F.var i ∈ u) eq (F.var i)
    where
      eq = begin
        lookup i ⟦ Γ ⟧ctx→
          ≡⟨ sym $ lookup-⟦⟧ctx→ Γ i ⟩
        ⟦ lookup i Γ ⟧tp→
            ≡⟨ cong ⟦_⟧tp→ lookup-i≡r ⟩
        ⟦ a ⟧tp→ ∎
  ⟦_,_⟧r (r-iabs {a = a} p) m = , F.λ' ⟦ a ⟧tp→ (proj₂ ⟦ p , #ivar a m ⟧r)
  ⟦_,_⟧r (r-iapp p p₁) m = , (proj₂ ⟦ p , m ⟧r) F.· (proj₂ ⟦ p₁ , m ⟧r)

open Semantics _⊢ᵣ_ DerivationSemantics.⟦_,_⟧r public


-- {-# OPTIONS --allow-unsolved-metas #-}
module GUIgeneric.GUIFeaturesPart5 where

open import GUIgeneric.Prelude renaming (addButton to addButton')

open import GUIgeneric.GUIDefinitions renaming (add to add'; add' to add)
open import GUIgeneric.GUI
open import GUIgeneric.GUIExampleLib
open import StateSizedIO.GUI.WxGraphicsLibLevel3 renaming (addButton to addButton')
open import GUIgeneric.GUIFeatures
open import GUIgeneric.GUIFeaturesPart2 hiding ( main ; main1 )
open import GUIgeneric.GUIFeaturesPart3 hiding ( main ; Tea ; Cancel)
open import GUIgeneric.GUIFeaturesPart4 hiding ( main ; cancelNewStateSM ; cancelStateAdded ; cancelFeatureAdded  )
open import GUIgeneric.GUIExample hiding (main )

data StatesBasicVM : Set where
  pay change soda serveSoda  open' close : StatesBasicVM

basicVM : FMachine StatesBasicVM
basicVM  .Features = ⊤
basicVM  .AddStateF = ⊥
basicVM  .GUIF f (inj₁ pay)        =
         simpleSMState "Pay" (inj₁ change)
basicVM  .GUIF f (inj₁ change)     =
         simpleSMState "Get Change" (inj₁ soda)
basicVM  .GUIF f (inj₁ soda)       =
         simpleSMState "Soda" (inj₁ serveSoda)
basicVM  .GUIF f (inj₁ serveSoda)  =
         simpleSMState "Serve Soda" (inj₁ open')
basicVM  .GUIF f (inj₁ open')      =
         simpleSMState "Open" (inj₁ close)
basicVM  .GUIF f (inj₁ close)      =
         simpleSMState "Close" (inj₁ pay)
basicVM  .GUIF f (inj₂ ())

newState : {A B : Set} → A ⊎ B ⊎ ⊤
newState = (inj₂ (inj₂ tt))

{- handler for the new state to be added to the tea machine -}
teaNewStateSM :  (fm : FMachine StatesBasicVM)
                 →    SMachineState
                      (StatesBasicVM ⊎ fm .AddStateF ⊎ ⊤)
                      newState
teaNewStateSM fm = simpleSMState "Serve Tea" (inj₁ open')

{- add the new state to the feature machine -}
TeaMAddNewState :  FMachine StatesBasicVM
                   → FMachine StatesBasicVM
TeaMAddNewState fm =
  addOneStateFMachine fm (teaNewStateSM fm)

{- add a dummy feature "FeatureTea" to the feature machine -}
TeaMAddFeature :  FMachine StatesBasicVM
                  → FMachine StatesBasicVM
TeaMAddFeature fm = addDummyFeatures
                         (TeaMAddNewState fm)
                         FeatureTea

{- redefine in the feature machine one button -}
Tea :  FMachine StatesBasicVM
       → FMachine StatesBasicVM
Tea fm .Features   = TeaMAddFeature fm .Features
Tea fm .AddStateF  = TeaMAddFeature fm .AddStateF
Tea fm .GUIF (f , yesTea) (inj₁ soda) =
      addBtn2StateMachine
      (  TeaMAddFeature fm .GUIF
         (f , yesTea) (inj₁ soda))
         "Tea" newState
Tea fm .GUIF f  s = TeaMAddFeature fm .GUIF f s

{- handler for the new state to be added to the cancel machine -}
cancelNewStateSM : (fm : FMachine StatesBasicVM) →
                    SMachineState (StatesBasicVM ⊎ fm .AddStateF ⊎ ⊤) newState
cancelNewStateSM fm = simpleSMState "Cancelling" (inj₁ pay)

{- add the state to the old feature machine -}
cancelStateAdded : FMachine StatesBasicVM → FMachine StatesBasicVM
cancelStateAdded fm = addOneStateFMachine fm (cancelNewStateSM fm)

{- add a dummy feature "FeatureCancel" to the feature machine -}
cancelFeatureAdded : FMachine StatesBasicVM → FMachine StatesBasicVM
cancelFeatureAdded fm = addDummyFeatures
                         (cancelStateAdded fm)
                         FeatureCancel

{- redefine in the feature machine one button -}
Cancel :  FMachine StatesBasicVM
          → FMachine StatesBasicVM
Cancel fm .Features = cancelFeatureAdded fm .Features
Cancel fm .AddStateF  = cancelFeatureAdded fm .AddStateF
Cancel fm .GUIF (f , yesCancel) (inj₁ soda) =
      addBtn2StateMachine (cancelFeatureAdded fm .GUIF (f , yesCancel) (inj₁ soda))
                          "Cancel" newState
Cancel fm .GUIF f  s = cancelFeatureAdded fm .GUIF f s

{- add the Dummy free feature -}
FreeMAddFeature : FMachine StatesBasicVM
                  → FMachine StatesBasicVM
FreeMAddFeature fm = addDummyFeatures fm FeatureFree

{- redefine the pay button to free in case feature free is yesFree -}
Free :  FMachine StatesBasicVM
        → FMachine StatesBasicVM
Free fm .Features   = FreeMAddFeature fm .Features
Free fm .AddStateF  = FreeMAddFeature fm .AddStateF
Free fm .GUIF (f , yesFree) (inj₁ pay) =
      simpleSMState "Free" (inj₁ soda)
Free fm .GUIF (f , yesFree) (inj₁ open') =
      simpleSMState "Skip" (inj₁ pay)
Free fm .GUIF f  s  = FreeMAddFeature fm .GUIF f s

main1 : NativeIO Unit
main1 = compileFeatureVM (Tea (Cancel basicVM)) ((_ , yesCancel) , yesTea) (inj₁ pay)

main2 : NativeIO Unit
main2 = compileFeatureVM (Free basicVM) (_ , yesFree) (inj₁ pay) --

multiFeatureMachine : FMachine StatesBasicVM
multiFeatureMachine = Free (Cancel (Tea basicVM))

main : NativeIO Unit
main = compileFeatureVM
       multiFeatureMachine
       (((_ , yesTea) , yesCancel) , noFree) (inj₁ pay)


-- Basic intuitionistic propositional calculus, without ∨ or ⊥.
-- Kripke-style semantics with contexts as concrete worlds, and glueing for α and ▻.
-- Hilbert-style syntax.

module BasicIPC.Semantics.KripkeConcreteGluedHilbert where

open import BasicIPC.Syntax.Common public
open import Common.Semantics public

open ConcreteWorlds (Ty) public

-- Partial intuitionistic Kripke models with explicit syntax.

record Model : Set₁ where
  infix 3 _⊩ᵅ_ _[⊢]_
  field
    -- Forcing for atomic propositions; monotonic.
    _⊩ᵅ_   : World → Atom → Set
    mono⊩ᵅ : ∀ {P w w′} → w ≤ w′ → w ⊩ᵅ P → w′ ⊩ᵅ P

    -- Hilbert-style syntax representation; monotonic.
    _[⊢]_   : Cx Ty → Ty → Set
    mono[⊢] : ∀ {A Γ Γ′}  → Γ ⊆ Γ′ → Γ [⊢] A → Γ′ [⊢] A
    [var]    : ∀ {A Γ}     → A ∈ Γ → Γ [⊢] A
    [app]    : ∀ {A B Γ}   → Γ [⊢] A ▻ B → Γ [⊢] A → Γ [⊢] B
    [ci]     : ∀ {A Γ}     → Γ [⊢] A ▻ A
    [ck]     : ∀ {A B Γ}   → Γ [⊢] A ▻ B ▻ A
    [cs]     : ∀ {A B C Γ} → Γ [⊢] (A ▻ B ▻ C) ▻ (A ▻ B) ▻ A ▻ C
    [cpair]  : ∀ {A B Γ}   → Γ [⊢] A ▻ B ▻ A ∧ B
    [cfst]   : ∀ {A B Γ}   → Γ [⊢] A ∧ B ▻ A
    [csnd]   : ∀ {A B Γ}   → Γ [⊢] A ∧ B ▻ B
    [unit]   : ∀ {Γ}       → Γ [⊢] ⊤

    -- NOTE: [lam] is necessary to give meaning to Gentzen-style syntax.
    [lam] : ∀ {A B Γ} → Γ , A [⊢] B → Γ [⊢] A ▻ B

  infix 3 _[⊢]⋆_
  _[⊢]⋆_ : Cx Ty → Cx Ty → Set
  Γ [⊢]⋆ ∅     = 𝟙
  Γ [⊢]⋆ Ξ , A = Γ [⊢]⋆ Ξ × Γ [⊢] A

open Model {{…}} public

-- Forcing in a particular world of a particular model.

module _ {{_ : Model}} where
  infix 3 _⊩_
  _⊩_ : World → Ty → Set
  w ⊩ α P   = Glue (unwrap w [⊢] α P) (w ⊩ᵅ P)
  w ⊩ A ▻ B = Glue (unwrap w [⊢] (A ▻ B)) (∀ {w′} → w ≤ w′ → w′ ⊩ A → w′ ⊩ B)
  w ⊩ A ∧ B = w ⊩ A × w ⊩ B
  w ⊩ ⊤    = 𝟙

  infix 3 _⊩⋆_
  _⊩⋆_ : World → Cx Ty → Set
  w ⊩⋆ ∅     = 𝟙
  w ⊩⋆ Ξ , A = w ⊩⋆ Ξ × w ⊩ A

-- Monotonicity with respect to context inclusion.

module _ {{_ : Model}} where
  mono⊩ : ∀ {A w w′} → w ≤ w′ → w ⊩ A → w′ ⊩ A
  mono⊩ {α P}   ξ s = mono[⊢] (unwrap≤ ξ) (syn s) ⅋ mono⊩ᵅ ξ (sem s)
  mono⊩ {A ▻ B} ξ s = mono[⊢] (unwrap≤ ξ) (syn s) ⅋ λ ξ′ → sem s (trans≤ ξ ξ′)
  mono⊩ {A ∧ B} ξ s = mono⊩ {A} ξ (π₁ s) , mono⊩ {B} ξ (π₂ s)
  mono⊩ {⊤}    ξ s = ∙

  mono⊩⋆ : ∀ {Ξ w w′} → w ≤ w′ → w ⊩⋆ Ξ → w′ ⊩⋆ Ξ
  mono⊩⋆ {∅}     ξ ∙        = ∙
  mono⊩⋆ {Ξ , A} ξ (ts , t) = mono⊩⋆ {Ξ} ξ ts , mono⊩ {A} ξ t

-- Extraction of syntax representation in a particular model.

module _ {{_ : Model}} where
  reifyʳ : ∀ {A w} → w ⊩ A → unwrap w [⊢] A
  reifyʳ {α P}   s = syn s
  reifyʳ {A ▻ B} s = syn s
  reifyʳ {A ∧ B} s = [app] ([app] [cpair] (reifyʳ {A} (π₁ s))) (reifyʳ {B} (π₂ s))
  reifyʳ {⊤}    s = [unit]

  reifyʳ⋆ : ∀ {Ξ w} → w ⊩⋆ Ξ → unwrap w [⊢]⋆ Ξ
  reifyʳ⋆ {∅}     ∙        = ∙
  reifyʳ⋆ {Ξ , A} (ts , t) = reifyʳ⋆ ts , reifyʳ t

-- Useful theorems in functional form.

module _ {{_ : Model}} where
  [multicut] : ∀ {Ξ A Γ} → Γ [⊢]⋆ Ξ → Ξ [⊢] A → Γ [⊢] A
  [multicut] {∅}     ∙        u = mono[⊢] bot⊆ u
  [multicut] {Ξ , B} (ts , t) u = [app] ([multicut] ts ([lam] u)) t

  [pair] : ∀ {A B Γ} → Γ [⊢] A → Γ [⊢] B → Γ [⊢] A ∧ B
  [pair] t u = [app] ([app] [cpair] t) u

  [fst] : ∀ {A B Γ} → Γ [⊢] A ∧ B → Γ [⊢] A
  [fst] t = [app] [cfst] t

  [snd] : ∀ {A B Γ} → Γ [⊢] A ∧ B → Γ [⊢] B
  [snd] t = [app] [csnd] t

-- Additional useful equipment.

module _ {{_ : Model}} where
  _⟪$⟫_ : ∀ {A B w} → w ⊩ A ▻ B → w ⊩ A → w ⊩ B
  s ⟪$⟫ a = sem s refl≤ a

  ⟪K⟫ : ∀ {A B w} → w ⊩ A → w ⊩ B ▻ A
  ⟪K⟫ {A} a = [app] [ck] (reifyʳ a) ⅋ λ ξ →
                K (mono⊩ {A} ξ a)

  ⟪S⟫ : ∀ {A B C w} → w ⊩ A ▻ B ▻ C → w ⊩ A ▻ B → w ⊩ A → w ⊩ C
  ⟪S⟫ s₁ s₂ a = (s₁ ⟪$⟫ a) ⟪$⟫ (s₂ ⟪$⟫ a)

  ⟪S⟫′ : ∀ {A B C w} → w ⊩ A ▻ B ▻ C → w ⊩ (A ▻ B) ▻ A ▻ C
  ⟪S⟫′ {A} {B} {C} s₁ = [app] [cs] (syn s₁) ⅋ λ ξ s₂ →
                          [app] ([app] [cs] (mono[⊢] (unwrap≤ ξ) (syn s₁))) (syn s₂) ⅋ λ ξ′ →
                            ⟪S⟫ (mono⊩ {A ▻ B ▻ C} (trans≤ ξ ξ′) s₁) (mono⊩ {A ▻ B} ξ′ s₂)

  _⟪,⟫′_ : ∀ {A B w} → w ⊩ A → w ⊩ B ▻ A ∧ B
  _⟪,⟫′_ {A} a = [app] [cpair] (reifyʳ a) ⅋ λ ξ →
                   _,_ (mono⊩ {A} ξ a)

-- Forcing in a particular world of a particular model, for sequents.

module _ {{_ : Model}} where
  infix 3 _⊩_⇒_
  _⊩_⇒_ : World → Cx Ty → Ty → Set
  w ⊩ Γ ⇒ A = w ⊩⋆ Γ → w ⊩ A

  infix 3 _⊩_⇒⋆_
  _⊩_⇒⋆_ : World → Cx Ty → Cx Ty → Set
  w ⊩ Γ ⇒⋆ Ξ = w ⊩⋆ Γ → w ⊩⋆ Ξ

-- Entailment, or forcing in all worlds of all models, for sequents.

infix 3 _⊨_
_⊨_ : Cx Ty → Ty → Set₁
Γ ⊨ A = ∀ {{_ : Model}} {w : World} → w ⊩ Γ ⇒ A

infix 3 _⊨⋆_
_⊨⋆_ : Cx Ty → Cx Ty → Set₁
Γ ⊨⋆ Ξ = ∀ {{_ : Model}} {w : World} → w ⊩ Γ ⇒⋆ Ξ

-- Additional useful equipment, for sequents.

module _ {{_ : Model}} where
  lookup : ∀ {A Γ w} → A ∈ Γ → w ⊩ Γ ⇒ A
  lookup top     (γ , a) = a
  lookup (pop i) (γ , b) = lookup i γ

  _⟦$⟧_ : ∀ {A B Γ w} → w ⊩ Γ ⇒ A ▻ B → w ⊩ Γ ⇒ A → w ⊩ Γ ⇒ B
  (s₁ ⟦$⟧ s₂) γ = s₁ γ ⟪$⟫ s₂ γ

  ⟦K⟧ : ∀ {A B Γ w} → w ⊩ Γ ⇒ A → w ⊩ Γ ⇒ B ▻ A
  ⟦K⟧ {A} {B} a γ = ⟪K⟫ {A} {B} (a γ)

  ⟦S⟧ : ∀ {A B C Γ w} → w ⊩ Γ ⇒ A ▻ B ▻ C → w ⊩ Γ ⇒ A ▻ B → w ⊩ Γ ⇒ A → w ⊩ Γ ⇒ C
  ⟦S⟧ s₁ s₂ a γ = ⟪S⟫ (s₁ γ) (s₂ γ) (a γ)

  _⟦,⟧_ : ∀ {A B Γ w} → w ⊩ Γ ⇒ A → w ⊩ Γ ⇒ B → w ⊩ Γ ⇒ A ∧ B
  (a ⟦,⟧ b) γ = a γ , b γ

  ⟦π₁⟧ : ∀ {A B Γ w} → w ⊩ Γ ⇒ A ∧ B → w ⊩ Γ ⇒ A
  ⟦π₁⟧ s γ = π₁ (s γ)

  ⟦π₂⟧ : ∀ {A B Γ w} → w ⊩ Γ ⇒ A ∧ B → w ⊩ Γ ⇒ B
  ⟦π₂⟧ s γ = π₂ (s γ)


open import Nat
open import Prelude
open import core

module judgemental-inconsistency where
  data incon : τ̇ → τ̇ → Set where
    ICNumArr1 : {t1 t2 : τ̇} → incon num (t1 ==> t2)
    ICNumArr2 : {t1 t2 : τ̇} → incon (t1 ==> t2) num
    ICArr1 : {t1 t2 t3 t4 : τ̇} →
               incon t1 t3 →
               incon (t1 ==> t2) (t3 ==> t4)
    ICArr2 : {t1 t2 t3 t4 : τ̇} →
               incon t2 t4 →
               incon (t1 ==> t2) (t3 ==> t4)

  -- inconsistency is symmetric
  inconsym : ∀ {t1 t2} → incon t1 t2 → incon t2 t1
  inconsym ICNumArr1 = ICNumArr2
  inconsym ICNumArr2 = ICNumArr1
  inconsym (ICArr1 x) = ICArr1 (inconsym x)
  inconsym (ICArr2 x) = ICArr2 (inconsym x)

  --inconsistency isn't reflexive
  incon-nrefl : ∀{t} → incon t t → ⊥
  incon-nrefl (ICArr1 x) = incon-nrefl x
  incon-nrefl (ICArr2 x) = incon-nrefl x

  -- first half of iso
  to~̸ : (t1 t2 : τ̇) → incon t1 t2 → t1 ~̸ t2
  to~̸ .num ._ ICNumArr1 ()
  to~̸ ._ .num ICNumArr2 ()
  to~̸ ._ ._ (ICArr1 incon) TCRefl = abort (incon-nrefl incon)
  to~̸ ._ ._ (ICArr1 incon) (TCArr x x₁) = to~̸ _ _ incon x
  to~̸ ._ ._ (ICArr2 incon) TCRefl = abort (incon-nrefl incon)
  to~̸ ._ ._ (ICArr2 incon) (TCArr x x₁) = (to~̸ _ _ incon x₁)

  -- second half of iso
  from~̸ : (t1 t2 : τ̇) → t1 ~̸ t2 → incon t1 t2
  from~̸ num (t2 ==> t3) ncon = ICNumArr1
  from~̸ (t1 ==> t2) num ncon = ICNumArr2
  from~̸ (t1 ==> t2) (t3 ==> t4) ncon with ~dec t1 t3
  ... | Inl qq = ICArr2 (from~̸ t2 t4 (λ x → ncon (TCArr qq x)))
  ... | Inr qq = ICArr1 (from~̸ _ _ qq)
  -- the remaining consistent types all lead to absurdities
  from~̸ num num ncon = abort (ncon TCRefl)
  from~̸ num ⦇-⦈ ncon = abort (ncon TCHole1)
  from~̸ ⦇-⦈ num ncon = abort (ncon TCHole2)
  from~̸ ⦇-⦈ ⦇-⦈ ncon = abort (ncon TCRefl)
  from~̸ ⦇-⦈ (t2 ==> t3) ncon = abort (ncon TCHole2)
  from~̸ (t1 ==> t2) ⦇-⦈ ncon = abort (ncon TCHole1)

  -- need to display that at least one of the round-trips above is stable
  -- for this to be structure preserving and really an iso.
  rt1 : (t1 t2 : τ̇) → (x : t1 ~̸ t2) → (to~̸ t1 t2 (from~̸ t1 t2 x)) == x
  rt1 num (t2 ==> t3) x         = funext (λ x₁ → abort (x x₁))
  rt1 (t1 ==> t2) num x         = funext (λ x₁ → abort (x x₁))
  rt1 (t1 ==> t2) (t3 ==> t4) x = funext (λ x₁ → abort (x x₁))
  rt1 num num x                 = abort (x TCRefl)
  rt1 num ⦇-⦈ x                  = abort (x TCHole1)
  rt1 ⦇-⦈ num x                  = abort (x TCHole2)
  rt1 ⦇-⦈ ⦇-⦈ x                   = abort (x TCRefl)
  rt1 ⦇-⦈ (t2 ==> t3) x          = abort (x TCHole2)
  rt1 (t1 ==> t2) ⦇-⦈ x          = abort (x TCHole1)

  -- if inconsistency at arrows is proof-irrelevant, then all of
  -- inconsistency is proof-irrelevant
  incon-irrelev : (arr-incon-irrelev : {t1 t2 t3 t4 : τ̇} (x y : incon (t1 ==> t2) (t3 ==> t4)) → x == y) →
                (t1 t2 : τ̇) (x y : incon t1 t2) → x == y
  incon-irrelev arr-incon-irrelev .num _ ICNumArr1 ICNumArr1 = refl
  incon-irrelev arr-incon-irrelev _ .num ICNumArr2 ICNumArr2 = refl
  incon-irrelev arr-incon-irrelev _ _ (ICArr1 x) (ICArr1 y) = ap1 ICArr1 (incon-irrelev arr-incon-irrelev _ _ x y)
  incon-irrelev arr-incon-irrelev _ _ (ICArr1 x) (ICArr2 y) = arr-incon-irrelev (ICArr1 x) (ICArr2 y)
  incon-irrelev arr-incon-irrelev _ _ (ICArr2 x) (ICArr1 y) = arr-incon-irrelev (ICArr2 x) (ICArr1 y)
  incon-irrelev arr-incon-irrelev _ _ (ICArr2 x) (ICArr2 y) = ap1 ICArr2 (incon-irrelev arr-incon-irrelev _ _ x y )

  -- if inconsistency at arrows is proof-irrelevant, then the round trip is
  -- stable up to equality
  rt2 : (arr-incon-irrelev : {t1 t2 t3 t4 : τ̇} (x y : incon (t1 ==> t2) (t3 ==> t4)) → x == y) →
        (t1 t2 : τ̇) → (x : incon t1 t2) → (from~̸ t1 t2 (to~̸ t1 t2 x)) == x
  rt2 arr-incon-irrelev .num _ ICNumArr1 = refl
  rt2 arr-incon-irrelev _ .num ICNumArr2 = refl
  rt2 arr-incon-irrelev (t1 ==> t2) (t3 ==> t4) (ICArr1 x) with ~dec t1 t3
  rt2 arr-incon-irrelev (t1 ==> t2) (t3 ==> t4) (ICArr1 x₁) | Inl x = abort (to~̸ t1 t3 x₁ x)
  rt2 arr-incon-irrelev (t1 ==> t2) (t3 ==> t4) (ICArr1 x₁) | Inr x = ap1 ICArr1 (incon-irrelev arr-incon-irrelev t1 t3 (from~̸ t1 t3 x) x₁)
  rt2 arr-incon-irrelev (t1 ==> t2) (t3 ==> t4) (ICArr2 x) with ~dec t1 t3
  rt2 arr-incon-irrelev (t1 ==> t2) (t3 ==> t4) (ICArr2 x₁) | Inl x = ap1 ICArr2 (incon-irrelev arr-incon-irrelev t2 t4 (from~̸ t2 t4 (to~̸ t2 t4 x₁)) x₁)
  rt2 arr-incon-irrelev (t1 ==> t2) (t3 ==> t4) (ICArr2 x₁) | Inr x = arr-incon-irrelev (ICArr1 (from~̸ t1 t3 x)) (ICArr2 x₁)

  -- if inconsistency at arrows is proof-irrelevant, then the two
  -- defintions of inconsistency are isomorphic
  incon-iso : (arr-incon-irrelev : {t1 t2 t3 t4 : τ̇} (x y : incon (t1 ==> t2) (t3 ==> t4)) → x == y)
                                   → (t1 t2 : τ̇) → (incon t1 t2) ≃ (t1 ~̸ t2)
  incon-iso arr-incon-irrelev t1 t2 = (to~̸ t1 t2) , (from~̸ t1 t2) , (rt2 arr-incon-irrelev t1 t2) , (rt1 t1 t2)


------------------------------------------------------------------------
-- A coinductive definition of weak similarity
------------------------------------------------------------------------

{-# OPTIONS --sized-types #-}

open import Labelled-transition-system

module Similarity.Weak {ℓ} (lts : LTS ℓ) where

open import Equality.Propositional
open import Prelude
open import Prelude.Size

open import Bisimilarity.Weak lts as WB using ([_]_≈_; [_]_≈′_)
open import Expansion lts as E using ([_]_≳_; [_]_≳′_)
open import Indexed-container hiding (⟨_⟩)
open import Relation
open import Similarity lts as S using ([_]_≤_; [_]_≤′_)
import Similarity.General

open LTS lts

private
  module General = Similarity.General lts _[_]⇒̂_ ⟶→⇒̂

open General public
  using (module StepC; ⟨_⟩; challenge; force;
         [_]_≡_; [_]_≡′_; []≡↔; Extensionality; extensionality)
  renaming ( reflexive-≤ to reflexive-≼
           ; reflexive-≤′ to reflexive-≼′
           ; ≡⇒≤ to ≡⇒≼
           ; ≤:_ to ≼:_
           ; ≤′:_ to ≼′:_
           )

-- StepC is given in the following way, rather than via open public,
-- to make hyperlinks to it more informative.

StepC : Container (Proc × Proc) (Proc × Proc)
StepC = General.StepC

-- The following definitions are given explicitly, in order to make
-- the code easier to follow.

Weak-similarity : Size → Rel₂ ℓ Proc
Weak-similarity = ν StepC

Weak-similarity′ : Size → Rel₂ ℓ Proc
Weak-similarity′ = ν′ StepC

infix 4 [_]_≼_ [_]_≼′_ _≼_ _≼′_

[_]_≼_ : Size → Proc → Proc → Type ℓ
[ i ] p ≼ q = ν StepC i (p , q)

[_]_≼′_ : Size → Proc → Proc → Type ℓ
[ i ] p ≼′ q = ν′ StepC i (p , q)

_≼_ : Proc → Proc → Type ℓ
_≼_ = [ ∞ ]_≼_

_≼′_ : Proc → Proc → Type ℓ
_≼′_ = [ ∞ ]_≼′_

private

  -- However, these definitions are definitionally equivalent to
  -- corresponding definitions in General.

  indirect-Weak-similarity : Weak-similarity ≡ General.Similarity
  indirect-Weak-similarity = refl

  indirect-Weak-similarity′ : Weak-similarity′ ≡ General.Similarity′
  indirect-Weak-similarity′ = refl

  indirect-[]≼ : [_]_≼_ ≡ General.[_]_≤_
  indirect-[]≼ = refl

  indirect-[]≼′ : [_]_≼′_ ≡ General.[_]_≤′_
  indirect-[]≼′ = refl

  indirect-≼ : _≼_ ≡ General._≤_
  indirect-≼ = refl

  indirect-≼′ : _≼′_ ≡ General._≤′_
  indirect-≼′ = refl

mutual

  -- Weak bisimilarity is contained in weak similarity.

  ≈⇒≼ : ∀ {i p q} → [ i ] p ≈ q → [ i ] p ≼ q
  ≈⇒≼ = λ p≈q →
    ⟨ (λ q⟶q′ →
         let p′ , p⇒̂p′ , p′≈′q′ = WB.left-to-right p≈q q⟶q′
         in p′ , p⇒̂p′ , ≈⇒≼′ p′≈′q′)
    ⟩

  ≈⇒≼′ : ∀ {i p q} → [ i ] p ≈′ q → [ i ] p ≼′ q
  force (≈⇒≼′ p≳′q) = ≈⇒≼ (S.force p≳′q)

mutual

  -- Similarity is contained in weak similarity.

  ≤⇒≼ : ∀ {i p q} → [ i ] p ≤ q → [ i ] p ≼ q
  ≤⇒≼ = λ p≤q →
    ⟨ (λ q⟶q′ →
         let p′ , p⟶p′ , p′≤′q′ = S.challenge p≤q q⟶q′
         in p′ , ⟶→⇒̂ p⟶p′ , ≤⇒≼′ p′≤′q′)
    ⟩

  ≤⇒≼′ : ∀ {i p q} → [ i ] p ≤′ q → [ i ] p ≼′ q
  force (≤⇒≼′ p≳′q) = ≤⇒≼ (S.force p≳′q)

-- Weak similarity is a weak simulation (of a certain kind).

weak-is-weak⇒̂ :
  ∀ {p p′ q μ} →
  p ≼ q → p [ μ ]⇒̂ p′ →
  ∃ λ q′ → q [ μ ]⇒̂ q′ × p′ ≼ q′
weak-is-weak⇒̂ = is-weak⇒̂ challenge (λ p≼′q → force p≼′q) ⇒̂→⇒ id

mutual

  -- Weak similarity is transitive.
  --
  -- Note that the size of the second argument is not preserved.
  --
  -- TODO: Can one prove that the size cannot be preserved?

  transitive-≼ : ∀ {i p q r} → [ i ] p ≼ q → q ≼ r → [ i ] p ≼ r
  transitive-≼ p≼q q≼r =
    ⟨ (λ p⟶p′ →
         let q′ , q⇒̂q′ , p′≼q′ = challenge p≼q p⟶p′
             r′ , r⇒̂r′ , q′≼r′ = weak-is-weak⇒̂ q≼r q⇒̂q′
         in r′ , r⇒̂r′ , transitive-≼′ p′≼q′ q′≼r′)
    ⟩

  transitive-≼′ :
    ∀ {i p q r} → [ i ] p ≼′ q → q ≼ r → [ i ] p ≼′ r
  force (transitive-≼′ p≼q q≼r) = transitive-≼ (force p≼q) q≼r

mutual

  -- A fully size-preserving transitivity-like lemma.
  --
  -- Note that expansion could be replaced by a kind of one-sided
  -- expansion.

  transitive-≳≼ : ∀ {i p q r} → [ i ] p ≳ q → [ i ] q ≼ r → [ i ] p ≼ r
  transitive-≳≼ p≳q q≼r =
    ⟨ (λ p⟶p′ → case E.left-to-right p≳q p⟶p′ of λ where
                  (_ , done s , p′≳q) →
                    _ , silent s done
                      , transitive-≳≼′
                          p′≳q (record { force = λ { {_} → q≼r } })

                  (q′ , step q⟶q′ , p′≳q′) →
                    let r′ , r⇒̂r′ , q′≼r′ = challenge q≼r q⟶q′
                    in r′ , r⇒̂r′ , transitive-≳≼′ p′≳q′ q′≼r′)
    ⟩

  transitive-≳≼′ :
    ∀ {i p q r} → [ i ] p ≳′ q → [ i ] q ≼′ r → [ i ] p ≼′ r
  force (transitive-≳≼′ p≼q q≼r) =
    transitive-≳≼ (force p≼q) (force q≼r)


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

module Fragment.Extensions.CSemigroup.Monomial where

open import Fragment.Equational.Theory.Bundles

open import Fragment.Algebra.Signature
open import Fragment.Algebra.Free Σ-magma hiding (_~_)
open import Fragment.Algebra.Homomorphism Σ-magma
open import Fragment.Algebra.Algebra Σ-magma
  using (Algebra; IsAlgebra; Interpretation; Congruence)
open import Fragment.Equational.Model Θ-csemigroup

open import Fragment.Setoid.Morphism using (_↝_)

open import Fragment.Extensions.CSemigroup.Nat

open import Data.Nat using (ℕ; zero; suc; _≟_)
open import Data.Fin using (Fin; #_; zero; suc; toℕ; fromℕ)
open import Data.Vec using (Vec; []; _∷_)
open import Data.Vec.Relation.Binary.Pointwise.Inductive using ([]; _∷_)

open import Relation.Nullary using (yes ; no)
open import Relation.Binary.PropositionalEquality as PE using (_≡_)
open import Relation.Binary using (Setoid; IsEquivalence)
import Relation.Binary.Reasoning.Setoid as Reasoning

data Monomial : ℕ → Set where
  leaf : ∀ {n} → ℕ⁺ → Monomial (suc n)
  skip : ∀ {n} → Monomial n → Monomial (suc n)
  cons : ∀ {n} → ℕ⁺ → Monomial n → Monomial (suc n)

lift : ∀ {n} → Monomial n → Monomial (suc n)
lift (leaf x)    = leaf x
lift (skip x)    = skip (lift x)
lift (cons x xs) = cons x (lift xs)

_⊗_ : ∀ {n} → Monomial n → Monomial n → Monomial n
leaf x    ⊗ leaf y    = leaf (x + y)
leaf x    ⊗ skip y    = cons x y
leaf x    ⊗ cons y ys = cons (x + y) ys
skip x    ⊗ leaf y    = cons y x
skip x    ⊗ skip y    = skip (x ⊗ y)
skip x    ⊗ cons y ys = cons y (x ⊗ ys)
cons x xs ⊗ leaf y    = cons (x + y) xs
cons x xs ⊗ skip y    = cons x (xs ⊗ y)
cons x xs ⊗ cons y ys = cons (x + y) (xs ⊗ ys)

⊗-cong : ∀ {n} {x y z w : Monomial n}
         → x ≡ y → z ≡ w → x ⊗ z ≡ y ⊗ w
⊗-cong = PE.cong₂ _⊗_

⊗-comm : ∀ {n} (x y : Monomial n) → x ⊗ y ≡ y ⊗ x
⊗-comm (leaf x)    (leaf y)    = PE.cong leaf (+-comm x y)
⊗-comm (leaf x)    (skip y)    = PE.refl
⊗-comm (leaf x)    (cons y ys) = PE.cong₂ cons (+-comm x y) PE.refl
⊗-comm (skip x)    (leaf y)    = PE.refl
⊗-comm (skip x)    (skip y)    = PE.cong skip (⊗-comm x y)
⊗-comm (skip x)    (cons y ys) = PE.cong (cons y) (⊗-comm x ys)
⊗-comm (cons x xs) (leaf y)    = PE.cong₂ cons (+-comm x y) PE.refl
⊗-comm (cons x xs) (skip y)    = PE.cong (cons x) (⊗-comm xs y)
⊗-comm (cons x xs) (cons y ys) = PE.cong₂ cons (+-comm x y) (⊗-comm xs ys)

⊗-assoc : ∀ {n} (x y z : Monomial n) → (x ⊗ y) ⊗ z ≡ x ⊗ (y ⊗ z)
⊗-assoc (leaf x)    (leaf y)    (leaf z)    = PE.cong leaf (+-assoc x y z)
⊗-assoc (leaf x)    (leaf y)    (skip z)    = PE.refl
⊗-assoc (leaf x)    (leaf y)    (cons z zs) = PE.cong₂ cons (+-assoc x y z) PE.refl
⊗-assoc (leaf x)    (skip y)    (leaf z)    = PE.refl
⊗-assoc (leaf x)    (skip y)    (skip z)    = PE.refl
⊗-assoc (leaf x)    (skip y)    (cons z zs) = PE.refl
⊗-assoc (leaf x)    (cons y ys) (leaf z)    = PE.cong₂ cons (+-assoc x y z) PE.refl
⊗-assoc (leaf x)    (cons y ys) (skip z)    = PE.refl
⊗-assoc (leaf x)    (cons y ys) (cons z zs) = PE.cong₂ cons (+-assoc x y z) PE.refl
⊗-assoc (skip x)    (leaf y)    (leaf z)    = PE.refl
⊗-assoc (skip x)    (leaf y)    (skip z)    = PE.refl
⊗-assoc (skip x)    (leaf y)    (cons z zs) = PE.refl
⊗-assoc (skip x)    (skip y)    (leaf z)    = PE.refl
⊗-assoc (skip x)    (skip y)    (skip z)    = PE.cong skip (⊗-assoc x y z)
⊗-assoc (skip x)    (skip y)    (cons z zs) = PE.cong (cons z) (⊗-assoc x y zs)
⊗-assoc (skip x)    (cons y ys) (leaf z)    = PE.refl
⊗-assoc (skip x)    (cons y ys) (skip z)    = PE.cong (cons y) (⊗-assoc x ys z)
⊗-assoc (skip x)    (cons y ys) (cons z zs) = PE.cong (cons (y + z)) (⊗-assoc x ys zs)
⊗-assoc (cons x xs) (leaf y)    (leaf z)    = PE.cong₂ cons (+-assoc x y z) PE.refl
⊗-assoc (cons x xs) (leaf y)    (skip z)    = PE.refl
⊗-assoc (cons x xs) (leaf y)    (cons z zs) = PE.cong₂ cons (+-assoc x y z) PE.refl
⊗-assoc (cons x xs) (skip y)    (leaf z)    = PE.refl
⊗-assoc (cons x xs) (skip y)    (skip z)    = PE.cong (cons x) (⊗-assoc xs y z)
⊗-assoc (cons x xs) (skip y)    (cons z zs) = PE.cong (cons (x + z)) (⊗-assoc xs y zs)
⊗-assoc (cons x xs) (cons y ys) (leaf z)    = PE.cong₂ cons (+-assoc x y z) PE.refl
⊗-assoc (cons x xs) (cons y ys) (skip z)    = PE.cong (cons (x + y)) (⊗-assoc xs ys z)
⊗-assoc (cons x xs) (cons y ys) (cons z zs) = PE.cong₂ cons (+-assoc x y z) (⊗-assoc xs ys zs)

module _ (n : ℕ) where

  private

    Monomial/≡ : Setoid _ _
    Monomial/≡ = PE.setoid (Monomial n)

    Monomial⟦_⟧ : Interpretation Monomial/≡
    Monomial⟦ • ⟧ (x ∷ y ∷ []) = x ⊗ y

    Monomial⟦_⟧-cong : Congruence Monomial/≡ Monomial⟦_⟧
    Monomial⟦ • ⟧-cong (p ∷ q ∷ []) = PE.cong₂ _⊗_ p q

    Monomial/≡-isAlgebra : IsAlgebra (Monomial/≡)
    Monomial/≡-isAlgebra = record { ⟦_⟧     = Monomial⟦_⟧
                                  ; ⟦⟧-cong = Monomial⟦_⟧-cong
                                  }

    Monomial/≡-algebra : Algebra
    Monomial/≡-algebra =
      record { ∥_∥/≈           = Monomial/≡
             ; ∥_∥/≈-isAlgebra = Monomial/≡-isAlgebra
             }

    Monomial/≡-models : Models Monomial/≡-algebra
    Monomial/≡-models comm θ  = ⊗-comm (θ (# 0)) (θ (# 1))
    Monomial/≡-models assoc θ = ⊗-assoc (θ (# 0)) (θ (# 1)) (θ (# 2))

    Monomial/≡-isModel : IsModel Monomial/≡
    Monomial/≡-isModel =
      record { isAlgebra = Monomial/≡-isAlgebra
             ; models    = Monomial/≡-models
             }

  J' : Model
  J' = record { ∥_∥/≈   = Monomial/≡
              ; isModel = Monomial/≡-isModel
              }

private

  module _ {n : ℕ} where

    open Setoid ∥ J n ∥/≈ using (_≈_)

    _·_ : ∥ J n ∥ → ∥ J n ∥ → ∥ J n ∥
    x · y = term • (x ∷ y ∷ [])

    ·-cong : ∀ {x y z w} → x ≈ y → z ≈ w → x · z ≈ y · w
    ·-cong p q = cong • (p ∷ q ∷ [])

    ·-comm : ∀ x y → x · y ≈ y · x
    ·-comm x y = axiom comm (env {A = ∥ J n ∥ₐ} (x ∷ y ∷ []))

    ·-assoc : ∀ x y z → (x · y) · z ≈ x · (y · z)
    ·-assoc x y z = axiom assoc (env {A = ∥ J n ∥ₐ} (x ∷ y ∷ z ∷ []))

  module _ (n : ℕ) where

    open Setoid ∥ J (suc n) ∥/≈ using (_≈_)

    exp : ℕ⁺ → ∥ J (suc n) ∥
    exp one     = atomise n
    exp (suc k) = atomise n · exp k

    exp-hom : ∀ {j k} → exp j · exp k ≈ exp (j + k)
    exp-hom {one}   {k} = refl
    exp-hom {suc j} {k} = begin
        (atomise n · exp j) · exp k
      ≈⟨ ·-assoc (atomise n) (exp j) (exp k) ⟩
        atomise n · (exp j · exp k)
      ≈⟨ ·-cong refl exp-hom ⟩
        atomise n · exp (j + k)
      ∎
      where open Reasoning ∥ J (suc n) ∥/≈

  ∣syn∣ : ∀ {n} → ∥ J' n ∥ → ∥ J n ∥
  ∣syn∣ {suc n} (leaf k)    = exp n k
  ∣syn∣ {suc n} (skip x)    = ∣ raise ∣ (∣syn∣ x)
  ∣syn∣ {suc n} (cons k xs) = exp n k · ∣ raise ∣ (∣syn∣ xs)

  ∣syn∣-cong : ∀ {n} → Congruent _≡_ ≈[ J n ] ∣syn∣
  ∣syn∣-cong {n} p = Setoid.reflexive ∥ J n ∥/≈ (PE.cong ∣syn∣ p)

  ∣syn∣⃗ : ∀ {n} → ∥ J' n ∥/≈ ↝ ∥ J n ∥/≈
  ∣syn∣⃗ {n} = record { ∣_∣      = ∣syn∣ {n}
                      ; ∣_∣-cong = ∣syn∣-cong {n}
                      }

  ∣syn∣-hom : ∀ {n} → Homomorphic ∥ J' n ∥ₐ ∥ J n ∥ₐ ∣syn∣
  ∣syn∣-hom {suc n} • (leaf x    ∷ leaf y    ∷ []) = exp-hom n
  ∣syn∣-hom {suc n} • (leaf x    ∷ skip y    ∷ []) = refl
  ∣syn∣-hom {suc n} • (leaf x    ∷ cons y ys ∷ []) = begin
      exp n x · (exp n y · ∣ raise ∣ (∣syn∣ ys))
    ≈⟨ sym (·-assoc (exp n x) (exp n y) _) ⟩
      (exp n x · exp n y) · ∣ raise ∣ (∣syn∣ ys)
    ≈⟨ ·-cong (exp-hom n) refl ⟩
      exp n (x + y) · ∣ raise ∣ (∣syn∣ ys)
    ∎
    where open Reasoning ∥ J (suc n) ∥/≈
  ∣syn∣-hom {suc n} • (skip x    ∷ leaf y    ∷ []) = ·-comm _ (exp n y)
  ∣syn∣-hom {suc n} • (skip x    ∷ skip y    ∷ []) = begin
      ∣ raise ∣ (∣syn∣ x) · ∣ raise ∣ (∣syn∣ y)
    ≈⟨ ∣ raise ∣-hom • (∣syn∣ x ∷ ∣syn∣ y ∷ []) ⟩
      ∣ raise ∣ (∣syn∣ x · ∣syn∣ y)
    ≈⟨ ∣ raise ∣-cong (∣syn∣-hom • (x ∷ y ∷ [])) ⟩
      ∣ raise ∣ (∣syn∣ (x ⊗ y))
    ∎
    where open Reasoning ∥ J (suc n) ∥/≈
  ∣syn∣-hom {suc n} • (skip x    ∷ cons y ys ∷ []) = begin
      ∣ raise ∣ (∣syn∣ x) · (exp n y · ∣ raise ∣ (∣syn∣ ys))
    ≈⟨ sym (·-assoc _ (exp n y) _) ⟩
      (∣ raise ∣ (∣syn∣ x) · exp n y) · ∣ raise ∣ (∣syn∣ ys)
    ≈⟨ ·-cong (·-comm _ (exp n y)) refl ⟩
      (exp n y · ∣ raise ∣ (∣syn∣ x)) · ∣ raise ∣ (∣syn∣ ys)
    ≈⟨ ·-assoc (exp n y) _ _ ⟩
      exp n y · (∣ raise ∣ (∣syn∣ x) · ∣ raise ∣ (∣syn∣ ys))
    ≈⟨ ·-cong refl (∣ raise ∣-hom • (∣syn∣ x ∷ ∣syn∣ ys ∷ [])) ⟩
      exp n y · ∣ raise ∣ (∣syn∣ x · ∣syn∣ ys)
    ≈⟨ ·-cong refl (∣ raise ∣-cong (∣syn∣-hom • (x ∷ ys ∷ []))) ⟩
      exp n y · ∣ raise ∣ (∣syn∣ (x ⊗ ys))
    ∎
    where open Reasoning ∥ J (suc n) ∥/≈
  ∣syn∣-hom {suc n} • (cons x xs ∷ leaf y    ∷ []) = begin
      (exp n x · ∣ raise ∣ (∣syn∣ xs)) · exp n y
    ≈⟨ ·-assoc (exp n x) _ (exp n y) ⟩
      exp n x · (∣ raise ∣ (∣syn∣ xs) · exp n y)
    ≈⟨ ·-cong refl (·-comm _ (exp n y)) ⟩
      exp n x · (exp n y · ∣ raise ∣ (∣syn∣ xs))
    ≈⟨ sym (·-assoc (exp n x) (exp n y) _) ⟩
      (exp n x · exp n y) · ∣ raise ∣ (∣syn∣ xs)
    ≈⟨ ·-cong (exp-hom n) refl ⟩
      exp n (x + y) · ∣ raise ∣ (∣syn∣ xs)
    ∎
    where open Reasoning ∥ J (suc n) ∥/≈
  ∣syn∣-hom {suc n} • (cons x xs ∷ skip y    ∷ []) = begin
      (exp n x · ∣ raise ∣ (∣syn∣ xs)) · ∣ raise ∣ (∣syn∣ y)
    ≈⟨ ·-assoc (exp n x) _ _ ⟩
      exp n x · (∣ raise ∣ (∣syn∣ xs) · ∣ raise ∣ (∣syn∣ y))
    ≈⟨ ·-cong refl (∣ raise ∣-hom • (∣syn∣ xs ∷ ∣syn∣ y ∷ [])) ⟩
      exp n x · ∣ raise ∣ (∣syn∣ xs · ∣syn∣ y)
    ≈⟨ ·-cong refl (∣ raise ∣-cong (∣syn∣-hom • (xs ∷ y ∷ []))) ⟩
      exp n x · ∣ raise ∣ (∣syn∣ (xs ⊗ y))
    ∎
    where open Reasoning ∥ J (suc n) ∥/≈
  ∣syn∣-hom {suc n} • (cons x xs ∷ cons y ys ∷ []) = begin
      (exp n x · ∣ raise ∣ (∣syn∣ xs)) · (exp n y · ∣ raise ∣ (∣syn∣ ys))
    ≈⟨ ·-assoc (exp n x) _ _ ⟩
      exp n x · (∣ raise ∣ (∣syn∣ xs) · (exp n y · ∣ raise ∣ (∣syn∣ ys)))
    ≈⟨ ·-cong refl (sym (·-assoc _ (exp n y) _)) ⟩
      exp n x · ((∣ raise ∣ (∣syn∣ xs) · exp n y) · ∣ raise ∣ (∣syn∣ ys))
    ≈⟨ ·-cong refl (·-cong (·-comm (∣ raise ∣ (∣syn∣ xs)) _) refl) ⟩
      exp n x · ((exp n y · ∣ raise ∣ (∣syn∣ xs)) · ∣ raise ∣ (∣syn∣ ys))
    ≈⟨ ·-cong refl (·-assoc (exp n y) _ _) ⟩
      exp n x · (exp n y · (∣ raise ∣ (∣syn∣ xs) · ∣ raise ∣ (∣syn∣ ys)))
    ≈⟨ sym (·-assoc (exp n x) (exp n y) _) ⟩
      (exp n x · exp n y) · (∣ raise ∣ (∣syn∣ xs) · ∣ raise ∣ (∣syn∣ ys))
    ≈⟨ ·-cong (exp-hom n) (∣ raise ∣-hom • (∣syn∣ xs ∷ ∣syn∣ ys ∷ [])) ⟩
      exp n (x + y) · ∣ raise ∣ (∣syn∣ xs · ∣syn∣ ys)
    ≈⟨ ·-cong refl (∣ raise ∣-cong (∣syn∣-hom • (xs ∷ ys ∷ []))) ⟩
      exp n (x + y) · ∣ raise ∣ (∣syn∣ (xs ⊗ ys))
    ∎
    where open Reasoning ∥ J (suc n) ∥/≈

syn : ∀ {n} → ∥ J' n ∥ₐ ⟿ ∥ J n ∥ₐ
syn = record { ∣_∣⃗    = ∣syn∣⃗
             ; ∣_∣-hom = ∣syn∣-hom
             }

private

  tab : ∀ {n} → Fin n → ∥ J' n ∥
  tab {suc zero}    zero    = leaf one
  tab {suc (suc n)} zero    = skip (tab {suc n} zero)
  tab {suc (suc n)} (suc k) = lift (tab {suc n} k)

norm : ∀ {n} → ∥ J n ∥ₐ ⟿ ∥ J' n ∥ₐ
norm {n} = interp (J' n) tab

private

  step-exp : ∀ {n} → (k : ℕ⁺)
             → ≈[ J (suc (suc n)) ] (exp (suc n) k)
                                    (∣ step ∣ (exp n k))
  step-exp one     = refl
  step-exp (suc k) = ·-cong refl (step-exp k)

  step-lift : ∀ {n} → (x : ∥ J' n ∥)
              → ≈[ J (suc n) ] (∣ syn ∣ (lift x))
                               (∣ step ∣ (∣ syn ∣ x))
  step-lift {suc n} (leaf x)    = step-exp x
  step-lift {suc n} (skip x)    = begin
      ∣ raise ∣ (∣ syn ∣ (lift x))
    ≈⟨ ∣ raise ∣-cong (step-lift x) ⟩
      ∣ raise ∣ (∣ step ∣ (∣ syn ∣ x))
    ≈⟨ sym (step-raise {x = ∣ syn ∣ x}) ⟩
      ∣ step ∣ (∣ raise ∣ (∣ syn ∣ x))
    ∎
    where open Reasoning ∥ J (suc (suc n)) ∥/≈
  step-lift {suc n} (cons x xs) = begin
      exp (suc n) x · ∣ raise ∣ (∣ syn ∣ (lift xs))
    ≈⟨ ·-cong refl (∣ raise ∣-cong (step-lift xs)) ⟩
      exp (suc n) x · ∣ raise ∣ (∣ step ∣ (∣ syn ∣ xs))
    ≈⟨ ·-cong (step-exp x) (sym (step-raise {x = ∣ syn ∣ xs})) ⟩
      ∣ step ∣ (exp n x) · ∣ step ∣ (∣ raise ∣ (∣ syn ∣ xs))
    ≈⟨ ∣ step ∣-hom • (exp n x ∷ (∣ raise ∣ (∣ syn ∣ xs)) ∷ []) ⟩
      ∣ step ∣ (exp n x · ∣ raise ∣ (∣ syn ∣ xs))
    ∎
    where open Reasoning ∥ J (suc (suc n)) ∥/≈

  syn-tab : ∀ {n} → (k : Fin n)
            → ≈[ J n ] (∣ syn ∣ (tab k)) (atom (dyn k))
  syn-tab {suc zero}    zero    = refl
  syn-tab {suc (suc n)} zero    = ∣ raise ∣-cong (syn-tab zero)
  syn-tab {suc (suc n)} (suc k) = begin
      ∣ syn ∣ (lift (tab {suc n} k))
    ≈⟨ step-lift (tab {suc n} k) ⟩
      ∣ step ∣ (∣ syn ∣ (tab {suc n} k))
    ≈⟨ ∣ step ∣-cong (syn-tab k) ⟩
      ∣ step ∣ (atom (dyn k))
    ∎
    where open Reasoning ∥ J (suc (suc n)) ∥/≈

syn⊙norm≗id : ∀ {n} → syn {n} ⊙ norm {n} ≗ id
syn⊙norm≗id {n} {atom (dyn k)}        = syn-tab k
syn⊙norm≗id {n} {term • (x ∷ y ∷ [])} = begin
    ∣ syn ∣ (∣ norm ∣ x ⊗ ∣ norm ∣ y)
  ≈⟨ sym (∣ syn ∣-hom • (∣ norm ∣ x ∷ ∣ norm ∣ y ∷ [])) ⟩
    ∣ syn ∣ (∣ norm ∣ x) · ∣ syn ∣ (∣ norm ∣ y)
  ≈⟨ ·-cong syn⊙norm≗id syn⊙norm≗id ⟩
    x · y
  ∎
  where open Reasoning ∥ J n ∥/≈

private

  tab-diag : ∀ n → tab {suc n} (fromℕ n) ≡ leaf one
  tab-diag zero    = PE.refl
  tab-diag (suc n) = PE.cong lift (tab-diag n)

  norm-exp : ∀ {n} (k : ℕ⁺) → ∣ norm ∣ (exp n k) ≡ leaf k
  norm-exp {n} one     = tab-diag n
  norm-exp {n} (suc k) = begin
      ∣ norm ∣ (atomise n · exp n k)
    ≈⟨ PE.sym (∣ norm ∣-hom • (atomise n ∷ exp n k ∷ [])) ⟩
      ∣ norm ∣ (atomise n) ⊗ ∣ norm ∣ (exp n k)
    ≈⟨ ⊗-cong (tab-diag n) (norm-exp k) ⟩
      leaf one ⊗ leaf k
    ∎
    where open Reasoning ∥ J' (suc n) ∥/≈

  tab-skip : ∀ {n} → (k : Fin n) → tab {suc n} (up k) ≡ skip (tab {n} k)
  tab-skip {suc zero}    zero    = PE.refl
  tab-skip {suc (suc n)} zero    = PE.refl
  tab-skip {suc (suc n)} (suc k) = PE.cong lift (tab-skip k)

  norm-raise : ∀ {n x} → ∣ norm {suc n} ∣ (∣ raise ∣ x) ≡ skip (∣ norm {n} ∣ x)
  norm-raise {n} {atom (dyn k)}        = tab-skip k
  norm-raise {n} {term • (x ∷ y ∷ [])} = begin
      ∣ norm ∣ (∣ raise ∣ (x · y))
    ≡⟨ ∣ norm ∣-cong (sym (∣ raise ∣-hom • (x ∷ y ∷ []))) ⟩
      ∣ norm ∣ (∣ raise ∣ x · ∣ raise ∣ y)
    ≡⟨ PE.sym (∣ norm ∣-hom • (∣ raise ∣ x ∷ ∣ raise ∣ y ∷ [])) ⟩
      ∣ norm ∣ (∣ raise ∣ x) ⊗ ∣ norm ∣ (∣ raise ∣ y)
    ≡⟨ ⊗-cong (norm-raise {x = x}) (norm-raise {x = y}) ⟩
      skip (∣ norm ∣ x ⊗ ∣ norm ∣ y)
    ≡⟨ PE.cong skip (∣ norm ∣-hom • (x ∷ y ∷ [])) ⟩
      skip (∣ norm ∣ (x · y))
    ∎
    where open PE.≡-Reasoning

norm⊙syn≗id : ∀ {n} → norm {n} ⊙ syn {n} ≗ id
norm⊙syn≗id {suc n} {leaf x} = norm-exp x
norm⊙syn≗id {suc n} {skip x} = begin
    ∣ norm ∣ (∣ raise ∣ (∣ syn ∣ x))
  ≈⟨ norm-raise {x = ∣ syn ∣ x} ⟩
    skip (∣ norm ∣ (∣ syn ∣ x))
  ≈⟨ PE.cong skip norm⊙syn≗id ⟩
    skip x
  ∎
  where open Reasoning ∥ J' (suc n) ∥/≈
norm⊙syn≗id {suc n} {cons x xs} = begin
    ∣ norm ∣ (exp n x · ∣ raise ∣ (∣ syn ∣ xs))
  ≈⟨ PE.sym (∣ norm ∣-hom • (exp n x ∷ ∣ raise ∣ (∣ syn ∣ xs) ∷ [])) ⟩
    ∣ norm ∣ (exp n x) ⊗ ∣ norm ∣ (∣ raise ∣ (∣ syn ∣ xs))
  ≈⟨ ⊗-cong (norm-exp x) (norm-raise {x = ∣ syn ∣ xs}) ⟩
    leaf x ⊗ skip (∣ norm ∣ (∣ syn ∣ xs))
  ≈⟨ ⊗-cong (PE.refl {x = leaf x}) (PE.cong skip (norm⊙syn≗id {x = xs})) ⟩
    leaf x ⊗ skip xs
  ∎
  where open Reasoning ∥ J' (suc n) ∥/≈

iso : ∀ {n} → ∥ J n ∥ₐ ≃ ∥ J' n ∥ₐ
iso = record { _⃗   = norm
             ; _⃖   = syn
             ; invˡ = norm⊙syn≗id
             ; invʳ = syn⊙norm≗id
             }


module Syntax where

import Level
open import Data.Empty
open import Data.Unit as Unit
open import Data.Nat
open import Data.List as List renaming ([] to Ø; [_] to [_]L)
open import NonEmptyList as NList
open import Data.Vec as Vec hiding ([_]; _++_)
open import Data.Product as Prod
open import Function

open import Relation.Binary.PropositionalEquality as PE hiding ([_])
open import Relation.Binary using (module IsEquivalence; Setoid; module Setoid)
open ≡-Reasoning

open import Common.Context as Context

open import Algebra
open Monoid {{ ... }} hiding (refl)

open import SyntaxRaw

infixr 5 _§ₒ_ _§ₘ_

-----------------------------------------
--- Separate types and terms
-----------------------------------------

mutual
  data DecentType : {Θ : TyCtx} {Γ₁ Γ₂ : RawCtx} → Raw Θ Γ₁ Γ₂ → Set where
    DT-⊤ : (Θ : TyCtx) (Γ : RawCtx) → DecentType (⊤-Raw Θ Γ)
    DT-tyVar : {Θ : TyCtx} (Γ₁ : RawCtx) {Γ₂ : RawCtx} →
               (X : TyVar Θ Γ₂) → DecentType (tyVarRaw Γ₁ X)
    DT-inst : ∀{Θ Γ₁ Γ₂ A} →
              (B : Raw Θ Γ₁ (Γ₂ ↑ A)) → (t : Raw Ø Γ₁ Ø) →
              DecentType B → DecentTerm t →
              DecentType (instRaw {Γ₂ = Γ₂} B t)
    DT-paramAbstr : ∀{Θ Γ₂} {B : U} (Γ₁ : RawCtx) →
                    {A : Raw Θ (B ∷ Γ₁) Γ₂} →
                    DecentType A →
                    DecentType (paramAbstrRaw Γ₁ A)
    DT-fp : ∀{Θ Γ₂} (Γ₁ : RawCtx) →
            (ρ : FP) → (D : FpData Raw Θ Γ₂) →
            DecentFpData D →
            DecentType (fpRaw Γ₁ ρ D)

  data DecentTerm : {Γ₁ Γ₂ : RawCtx} → Raw Ø Γ₁ Γ₂ → Set where
    DO-unit : (Γ : RawCtx) → DecentTerm (unitRaw Γ)
    DO-objVar : {Γ : RawCtx} {A : U} →
                (x : RawVar Γ A) → DecentTerm (objVarRaw x)
    DO-inst : {Γ₁ Γ₂ : RawCtx} {A : U} →
              (t : Raw Ø Γ₁ (Γ₂ ↑ A)) → (s : Raw Ø Γ₁ Ø) →
              DecentTerm t → DecentTerm s →
              DecentTerm (instRaw {Γ₂ = Γ₂} t s)
    DO-dialg : (Δ : RawCtx) (Γ : RawCtx) (A : U) →
               (ρ : FP) (k : ℕ) →
               DecentTerm (dialgRaw Δ Γ A ρ k)
    DO-mapping : (Γ : RawCtx) (Δ : RawCtx) →
                 (gs : FpMapData Raw Γ) → (ρ : FP) →
                 DecentFpMapData gs →
                 DecentTerm (recRaw Γ Δ gs ρ)

  DecentFpMapData : {Γ : RawCtx} → FpMapData Raw Γ → Set
  DecentFpMapData [ (Γ' , A , f , t) ] =
    DecentType A × DecentCtxMor f × DecentTerm t
  DecentFpMapData ((Γ' , A , f , t) ∷ ts) =
    DecentType A × DecentCtxMor f × DecentTerm t × DecentFpMapData ts

  DecentCtxMor : {Γ₁ Γ₂ : RawCtx} → CtxMor Raw Γ₁ Γ₂ → Set
  DecentCtxMor {Γ₂ = Ø}      []      = ⊤
  DecentCtxMor {Γ₂ = x ∷ Γ₂} (t ∷ f) = DecentTerm t × DecentCtxMor f

  DecentFpData : ∀{Θ Γ₂} → FpData Raw Θ Γ₂ → Set
  DecentFpData [ Γ , f , A ] = DecentCtxMor f × DecentType A
  DecentFpData ((Γ , f , A) ∷ D)
    = (DecentCtxMor f × DecentType A) × DecentFpData D

{-
DT-syntax : (Θ : TyCtx) (Γ₁ Γ₂ : RawCtx) → Raw Θ Γ₁ Γ₂ → Set
DT-syntax _ _ _ A = DecentType A

syntax DT-syntax Θ Γ₁ Γ₁ A = Θ ∥ Γ₁ ⊨ A ε Γ₂ ━

DO-syntax : (Γ₁ Γ₂ : RawCtx) → Raw Ø Γ₁ Γ₂ → Set
DO-syntax _ _ t = DecentTerm t

syntax DO-syntax Γ₁ Γ₂ t = Γ₁ ⊢ t ∈ Γ₂ ⊸?
-}

-------------------------------------
------- Pre-types and terms
-------------------------------------

PreType : (Θ : TyCtx) (Γ₁ Γ₂ : RawCtx) → Set
PreType Θ Γ₁ Γ₂ = Σ (Raw Θ Γ₁ Γ₂) λ A → DecentType A

_∣_/_⊸Ty = PreType

mkPreType : ∀ {Θ Γ₁ Γ₂} {A : Raw Θ Γ₁ Γ₂} → DecentType A → PreType Θ Γ₁ Γ₂
mkPreType {A = A} p = (A , p)

PreTerm : (Γ₁ Γ₂ : RawCtx) → Set
PreTerm Γ₁ Γ₂ = Σ (Raw Ø Γ₁ Γ₂) λ t → DecentTerm t

mkPreTerm : ∀ {Γ₁ Γ₂} {t : Raw Ø Γ₁ Γ₂} → DecentTerm t → PreTerm Γ₁ Γ₂
mkPreTerm {t = t} p = (t , p)

CtxMorP = CtxMor (λ _ → PreTerm)

mkCtxMorP : {Γ₁ Γ₂ : RawCtx} {f : CtxMor Raw Γ₁ Γ₂} →
             DecentCtxMor f → CtxMorP Γ₁ Γ₂
mkCtxMorP {Γ₂ = Ø}              p        = []
mkCtxMorP {Γ₂ = x ∷ Γ₂} {t ∷ f} (p , ps) = (t , p) ∷ (mkCtxMorP ps)

TyCtxMorP : TyCtx → TyCtx → Set
TyCtxMorP Θ₁ Ø = ⊤
TyCtxMorP Θ₁ (Γ ∷ Θ₂) = PreType Θ₁ Ø Γ × TyCtxMorP Θ₁ Θ₂

FpDataP : TyCtx → RawCtx → Set
FpDataP Θ Γ = NList (Σ RawCtx (λ Γ' → CtxMorP Γ' Γ × PreType (Γ ∷ Θ) Γ' Ø))

mkFpDataP : ∀ {Θ Γ} {D : FpData Raw Θ Γ} → DecentFpData D → FpDataP Θ Γ
mkFpDataP {D = [ Γ , f , A ]} (p , q) = [ Γ , (mkCtxMorP p) , mkPreType q ]
mkFpDataP {D = (Γ , f , A) ∷ D} ((p , q) , r) =
  (Γ , mkCtxMorP p , mkPreType q) ∷ mkFpDataP {D = D} r

-- | List of types, context morphisms and terms (Aₖ, fₖ, gₖ) such that
--            Γₖ, x : Aₖ[C/X] ⊢ gₖ : C fₖ or
--            Γₖ, x : C fₖ ⊢ gₖ : Aₖ[C/X],
-- which are the premisses of the rule for recursion and corecursion,
-- respectively.
FpMapDataP : RawCtx → Set
FpMapDataP Γ = NList (Σ RawCtx λ Γ' →
                        PreType [ Γ ]L Γ' Ø × CtxMorP Γ' Γ × PreTerm (∗ ∷ Γ') Ø)

mkFpMapDataP : ∀{Γ} {gs : FpMapData Raw Γ} → DecentFpMapData gs → FpMapDataP Γ
mkFpMapDataP {Γ} {[ Γ' , A , f , t ]} (A-decent , f-decent , t-decent) =
  [ Γ' , mkPreType A-decent , mkCtxMorP f-decent , mkPreTerm t-decent ]
mkFpMapDataP {Γ} {(Γ' , A , f , t) ∷ gs} (A-decent , f-decent , t-decent , r) =
  (Γ' , mkPreType A-decent , mkCtxMorP f-decent , mkPreTerm t-decent)
  ∷ mkFpMapDataP {Γ} {gs} r

getFpData : ∀{Γ} → FpMapDataP Γ → FpDataP Ø Γ
getFpData [ Γ' , A , f , _ ]     = [ Γ' , f , A ]
getFpData ((Γ' , A , f , _) ∷ d) = (Γ' , f , A) ∷ getFpData d

projCtxMor₁ : {Γ₁ Γ₂ : RawCtx} → CtxMorP Γ₂ Γ₁ → CtxMor Raw Γ₂ Γ₁
projCtxMor₁ = Vec.map proj₁

projCtxMor₂ : {Γ₁ Γ₂ : RawCtx} →
              (f : CtxMorP Γ₂ Γ₁) →  DecentCtxMor (projCtxMor₁ f)
projCtxMor₂ {Ø} [] = tt
projCtxMor₂ {x ∷ Γ₁} ((t , p) ∷ f) = (p , projCtxMor₂ f)

projPTList₁ : ∀{Γ} → FpMapDataP Γ → FpMapData Raw Γ
projPTList₁ =
  NList.map (Prod.map id (Prod.map proj₁ (Prod.map projCtxMor₁ proj₁)))

projPTList₂ : ∀{Γ} → (gs : FpMapDataP Γ) → DecentFpMapData (projPTList₁ gs)
projPTList₂ [ (Γ' , A , f , t) ] = (proj₂ A , projCtxMor₂ f , proj₂ t)
projPTList₂ ((Γ' , A , f , t) ∷ gs) =
  (proj₂ A , projCtxMor₂ f , proj₂ t , projPTList₂ gs)

projFpData₁ : ∀ {Θ Γ} → FpDataP Θ Γ → FpData Raw Θ Γ
projFpData₁ = NList.map (Prod.map id (Prod.map projCtxMor₁ proj₁))

projFpData₂ : ∀ {Θ Γ} → (D : FpDataP Θ Γ) → DecentFpData (projFpData₁ D)
projFpData₂ [ (Γ , f , A) ] = (projCtxMor₂ f , proj₂ A)
projFpData₂ ((Γ , f , A) ∷ D) = ((projCtxMor₂ f , proj₂ A) , projFpData₂ D)

-----------------------------------------
----- Constructors for pre terms
-----------------------------------------

⊤-PT : (Θ : TyCtx) (Γ : RawCtx) → PreType Θ Γ Ø
⊤-PT Θ Γ = mkPreType (DT-⊤ Θ Γ)

instPT : ∀ {Θ Γ₁ Γ₂ A} → PreType Θ Γ₁ (Γ₂ ↑ A) → PreTerm Γ₁ Ø → PreType Θ Γ₁ Γ₂
instPT (B , p) (t , q) = mkPreType (DT-inst _ _ p q)

_⊙_ = instPT

tyVarPT : {Θ : TyCtx} (Γ₁ : RawCtx) {Γ₂ : RawCtx} → TyVar Θ Γ₂ → PreType Θ Γ₁ Γ₂
tyVarPT Γ₁ X = mkPreType (DT-tyVar _ X)

paramAbstrPT : {Θ : TyCtx} {Γ₂ : RawCtx} (Γ₁ : RawCtx) {A : U} →
               PreType Θ (A ∷ Γ₁) Γ₂ → PreType Θ Γ₁ (Γ₂ ↑ A)
paramAbstrPT Γ₁ (A , p) = mkPreType (DT-paramAbstr Γ₁ p)

fpPT : {Θ : TyCtx} {Γ₂ : RawCtx} (Γ₁ : RawCtx) →
       FP → FpDataP Θ Γ₂ → PreType Θ Γ₁ Γ₂
fpPT Γ₁ ρ D = mkPreType (DT-fp Γ₁ ρ (projFpData₁ D) (projFpData₂ D))

unitPO : (Γ : RawCtx) → PreTerm Γ Ø
unitPO Γ = mkPreTerm (DO-unit _)

varPO : {Γ : RawCtx} {A : U} → RawVar Γ A → PreTerm Γ Ø
varPO x = mkPreTerm (DO-objVar x)

instPO : ∀ {Γ₁ Γ₂ A} → PreTerm Γ₁ (Γ₂ ↑ A) → PreTerm Γ₁ Ø → PreTerm Γ₁ Γ₂
instPO (t , p) (s , q) = mkPreTerm (DO-inst _ _ p q)

_§ₒ_ = instPO

dialgPO : (Δ : RawCtx) (Γ : RawCtx) (A : U) → FP → ℕ → PreTerm Δ (A ∷ Γ)
dialgPO Δ Γ A ρ k = mkPreTerm (DO-dialg _ Γ A ρ k)

α : (Δ : RawCtx) (Γ : RawCtx) (A : U) → ℕ → PreTerm Δ (A ∷ Γ)
α Δ Γ A k = dialgPO Δ Γ A μ k

ξ : (Δ : RawCtx) (Γ : RawCtx) (A : U) → ℕ → PreTerm Δ (A ∷ Γ)
ξ Δ Γ A k = dialgPO Δ Γ A ν k

-- | Generalised recursion, does recursion or corecursion, depending on ρ
grec : (Γ : RawCtx) (Δ : RawCtx) →
       FpMapDataP Γ → FP → PreTerm Δ (∗ ∷ Γ)
grec Γ Δ gs ρ = mkPreTerm (DO-mapping Γ Δ (projPTList₁ gs) ρ (projPTList₂ gs))

-- | Recursion for inductive types
rec : (Γ : RawCtx) (Δ : RawCtx) →
      FpMapDataP Γ → PreTerm Δ (∗ ∷ Γ)
rec Γ Δ gs = grec Γ Δ gs μ

-- Corecursion
corec : (Γ : RawCtx) (Δ : RawCtx) →
        FpMapDataP Γ → PreTerm Δ (∗ ∷ Γ)
corec Γ Δ gs = grec Γ Δ gs ν

instWCtxMorP : {Γ₁ Γ₂ Γ₃ : RawCtx} →
               PreTerm Γ₁ (Γ₃ ++ Γ₂) → CtxMorP Γ₁ Γ₂ → PreTerm Γ₁ Γ₃
instWCtxMorP {Γ₁} {Ø}      {Γ₃} t [] = subst (PreTerm Γ₁) (proj₂ identity Γ₃) t
instWCtxMorP {Γ₁} {x ∷ Γ₂} {Γ₃} t (s ∷ f) =
  instPO
    (instWCtxMorP {Γ₂ = Γ₂} {Γ₃ = Γ₃ ↑ x}
      (subst (PreTerm Γ₁) (mvVar _ Γ₃ x) t) f)
    s

_§ₘ'_ = instWCtxMorP

_§ₘ_ : {Γ₁ Γ₂ : RawCtx} →
       PreTerm Γ₁ Γ₂ → CtxMorP Γ₁ Γ₂ → PreTerm Γ₁ Ø
t §ₘ f = instWCtxMorP {Γ₃ = Ø} t f

instTyWCtxMorP : ∀ {Θ Γ₁ Γ₂ Γ₃} →
                 PreType Θ Γ₁ (Γ₃ ++ Γ₂) → CtxMorP Γ₁ Γ₂ → PreType Θ Γ₁ Γ₃
instTyWCtxMorP {Θ} {Γ₁} {Ø}      {Γ₃} A [] =
  subst (PreType Θ Γ₁) (proj₂ identity Γ₃) A
instTyWCtxMorP {Θ} {Γ₁} {x ∷ Γ₂} {Γ₃} A (s ∷ f) =
  (instTyWCtxMorP (subst (PreType Θ Γ₁) (mvVar _ Γ₃ x) A) f) ⊙ s

_§ₜ_ : ∀ {Θ Γ₁ Γ₂} →
       PreType Θ Γ₁ Γ₂ → CtxMorP Γ₁ Γ₂ → PreType Θ Γ₁ Ø
A §ₜ f = instTyWCtxMorP {Γ₃ = Ø} A f

---------------------------------------------------------
--------- Recursion for pre-types
---------------------------------------------------------

FpDataP' : (TyCtx → RawCtx → RawCtx → Set) → TyCtx → RawCtx → Set
FpDataP' V Θ Γ = NList (Σ RawCtx (λ Γ' → CtxMorP Γ' Γ × V (Γ ∷ Θ) Γ' Ø))

{-# NON_TERMINATING #-}
mapPT : {V : TyCtx → RawCtx → RawCtx → Set} →
        ((Θ : TyCtx) (Γ₁ : RawCtx) → V Θ Γ₁ Ø) →
        (∀{Θ Γ₁ Γ₂} → TyVar Θ Γ₂ → V Θ Γ₁ Γ₂) →
        (∀{Θ Γ₁ Γ₂ A} → V Θ Γ₁ (Γ₂ ↑ A) → PreTerm Γ₁ Ø → V Θ Γ₁ Γ₂) →
        (∀{Θ Γ₁ Γ₂ A} → V Θ (A ∷ Γ₁) Γ₂ → V Θ Γ₁ (Γ₂ ↑ A)) →
        (∀{Θ Γ₁ Γ₂} → FP → FpDataP' V Θ Γ₂ → V Θ Γ₁ Γ₂) →
        ∀{Θ Γ₁ Γ₂} → PreType Θ Γ₁ Γ₂ → V Θ Γ₁ Γ₂
mapPT ⊤-x _ _ _ _ (._ , DT-⊤ Θ Γ) = ⊤-x Θ Γ
mapPT _ var-x _ _ _ (._ , DT-tyVar Γ₁ X) = var-x X
mapPT ⊤-x var-x inst-x abstr-x fp-x (._ , DT-inst B t B-dec t-dec) =
  let r = mapPT ⊤-x var-x inst-x abstr-x fp-x (B , B-dec)
  in inst-x r (t , t-dec)
mapPT ⊤-x var-x inst-x abstr-x fp-x (._ , DT-paramAbstr Γ₁ {A} A-dec) =
  let r = mapPT ⊤-x var-x inst-x abstr-x fp-x (A , A-dec)
  in abstr-x r
mapPT ⊤-x var-x inst-x abstr-x fp-x (._ , DT-fp Γ₁ ρ D D-dec) =
  let D' = NList.map
           (Prod.map id
             (Prod.map id
               (mapPT ⊤-x var-x inst-x abstr-x fp-x))) (mkFpDataP {D = D} D-dec)
  in fp-x ρ D'

----------------------------------------------------------
--------- Meta theory for decent type predicate
---------------------------------------------------------

weakenDO : (Γ₁ : RawCtx) → {Γ₂ Γ₃ : RawCtx} {t : Raw Ø (Γ₁ ++ Γ₂) Γ₃} →
           (A : U) → DecentTerm t → DecentTerm (weaken Γ₁ A t)
weakenDO Γ₁ B (DO-unit ._)             = DO-unit _
weakenDO Γ₁ B (DO-objVar x)            = DO-objVar (weakenObjVar Γ₁ B x)
weakenDO Γ₁ B (DO-inst t s p q)        =
  DO-inst _ _ (weakenDO Γ₁ B p) (weakenDO Γ₁ B q)
weakenDO Γ₁ B (DO-dialg ._ Γ A ρ k)    = DO-dialg _ _ A ρ k
weakenDO Γ₁ B (DO-mapping Γ ._ gs ρ p) = DO-mapping Γ _ gs ρ p

weakenDT : ∀ {Θ} → (Γ₁ : RawCtx) → {Γ₂ Γ₃ : RawCtx} {A : Raw Θ (Γ₁ ++ Γ₂) Γ₃} →
           (B : U) → DecentType A → DecentType (weaken Γ₁ B A)
weakenDT Γ₁ B (DT-⊤ Θ ._)           = DT-⊤ Θ _
weakenDT Γ₁ B (DT-tyVar _ X)        = DT-tyVar _ X
weakenDT Γ₁ B (DT-inst A t p q)     =
  DT-inst _ _ (weakenDT Γ₁ B p) (weakenDO Γ₁ B q)
weakenDT Γ₁ B (DT-paramAbstr _ p)   = DT-paramAbstr _ (weakenDT _ B p)
weakenDT Γ₁ B (DT-fp _ ρ D p)       = DT-fp _ ρ D p

weakenDO₁ : ∀ {Γ₁ Γ₂} {t : Raw Ø Γ₁ Γ₂} →
            (A : U) → DecentTerm t → DecentTerm (weaken₁ A t)
weakenDO₁ = weakenDO Ø

weakenDT₁ : ∀ {Θ Γ₁ Γ₂} {A : Raw Θ Γ₁ Γ₂} →
            (B : U) → DecentType A → DecentType (weaken₁ B A)
weakenDT₁ = weakenDT Ø

weakenDecentCtxMor : {Γ₁ Γ₂ : RawCtx} {f : CtxMor Raw Γ₁ Γ₂} →
                     (A : U) → DecentCtxMor f →
                     DecentCtxMor (Vec.map (weaken₁ A) f)
weakenDecentCtxMor {Γ₂ = Ø}      {[]}    A p        = tt
weakenDecentCtxMor {Γ₂ = x ∷ Γ₂} {t ∷ f} A (p , ps) =
  (weakenDO₁ A p , weakenDecentCtxMor A ps)

weakenCtxMorP : {Γ₁ Γ₂ : RawCtx} → CtxMorP Γ₁ Γ₂ → CtxMorP (∗ ∷ Γ₁) Γ₂
weakenCtxMorP f = mkCtxMorP (weakenDecentCtxMor ∗ (projCtxMor₂ f))

-----------------------------------------------------
------ Meta operations on pre-terms and pre-types
-----------------------------------------------------

weakenPT : {Θ : TyCtx} (Γ₁ : RawCtx) {Γ₂ Γ₃ : RawCtx} →
           (A : U) → PreType Θ (Γ₁ ++ Γ₂) Γ₃ → PreType Θ (Γ₁ ++ A ∷ Γ₂) Γ₃
weakenPT Γ₁ A (B , p) = (_ , weakenDT Γ₁ A p)

weakenPT₁ : ∀ {Θ Γ₁ Γ₂} → (A : U) → PreType Θ Γ₁ Γ₂ → PreType Θ (A ∷ Γ₁) Γ₂
weakenPT₁ = weakenPT Ø

weakenPO : (Γ₁ : RawCtx) {Γ₂ Γ₃ : RawCtx} →
           (A : U) → PreTerm (Γ₁ ++ Γ₂) Γ₃ → PreTerm (Γ₁ ++ A ∷ Γ₂) Γ₃
weakenPO Γ₁ A (t , p) = (_ , weakenDO Γ₁ A p)

weakenPO₁ : {Γ₁ Γ₂ : RawCtx} → (A : U) → PreTerm Γ₁ Γ₂ → PreTerm (A ∷ Γ₁) Γ₂
weakenPO₁ = weakenPO Ø

get' : {Γ₁ Γ₂ : RawCtx} {A : U} →
       (f : CtxMor (λ _ → PreTerm) Γ₂ Γ₁) →
       (x : RawVar Γ₁ A) →
       DecentTerm (get {Raw} (projCtxMor₁ f) x)
get' (t ∷ f) zero                = proj₂ t
get' (t ∷ f) (succ {b = _} _ x)  = get' f x

-- | Lift substitutions to DecentTerm predicate
substDO : {Γ₁ Γ Γ₂ : RawCtx} {t : Raw Ø Γ₁ Γ} →
          (f : CtxMorP Γ₂ Γ₁) →
          DecentTerm t → DecentTerm (substRaw t (projCtxMor₁ f))
substDO f (DO-unit Γ₁)              = DO-unit _
substDO f (DO-objVar x)             = get' f x
substDO f (DO-inst t s p q)         = DO-inst _ _ (substDO f p) (substDO f q)
substDO f (DO-dialg Γ₁ Γ A ρ k)     = DO-dialg _ _ A ρ k
substDO f (DO-mapping Γ Γ₁ gs ρ p)  = DO-mapping Γ _ _ _ p

-- | Lift substRaw to pre terms
substP : {Γ₁ Γ Γ₂ : RawCtx} →
         PreTerm Γ₁ Γ → CtxMorP Γ₂ Γ₁ → PreTerm Γ₂ Γ
substP (t , p) f = (substRaw t (projCtxMor₁ f) , substDO f p)

_↓[_] = substP

-- | Context identity is a decent context morphism
ctxidDO : (Γ : RawCtx) → DecentCtxMor (ctxid Γ)
ctxidDO Ø       = tt
ctxidDO (x ∷ Γ) = (DO-objVar zero , weakenDecentCtxMor _ (ctxidDO Γ))

mkCtxMorP₁ : {Γ₁ Γ₂ : RawCtx} {f : CtxMor Raw Γ₁ Γ₂} →
             (p : DecentCtxMor f) → projCtxMor₁ (mkCtxMorP p) ≡ f
mkCtxMorP₁ {Γ₂ = Ø}      {[]} p = refl
mkCtxMorP₁ {Γ₂ = A ∷ Γ₂} {t ∷ f} (p , ps) =
  begin
    projCtxMor₁ {A ∷ Γ₂} ((t , p) ∷ mkCtxMorP ps)
  ≡⟨ refl ⟩
    t ∷ projCtxMor₁ (mkCtxMorP ps)
  ≡⟨ cong (λ u → t ∷ u) (mkCtxMorP₁ ps) ⟩
    t ∷ f
  ∎

ctxidP : (Γ : RawCtx) → CtxMorP Γ Γ
ctxidP Γ = mkCtxMorP (ctxidDO Γ)

_↓[_/0] : {Γ₁ Γ Γ₂ : RawCtx} →
          PreTerm (∗ ∷ Γ₁) Γ → PreTerm Γ₁ Ø  → PreTerm Γ₁ Γ
_↓[_/0] t s = t ↓[ s ∷ ctxidP _ ]

_•_ : {Γ₁ Γ₂ Γ₃ : RawCtx} → CtxMorP Γ₂ Γ₃ → CtxMorP Γ₁ Γ₂ → CtxMorP Γ₁ Γ₃
_•_ {Γ₃ = Ø} [] f = []
_•_ {Γ₃ = A ∷ Γ₃} (t ∷ g) f = substP t f ∷ (g • f)

-- | Context projection is a decent context morphism
ctxProjDO : (Γ₁ Γ₂ : RawCtx) → DecentCtxMor (ctxProjRaw Γ₁ Γ₂)
ctxProjDO Γ₁ Ø        = ctxidDO Γ₁
ctxProjDO Γ₁ (x ∷ Γ₂) = weakenDecentCtxMor _ (ctxProjDO Γ₁ Γ₂)

ctxProjP : (Γ₁ Γ₂ : RawCtx) → CtxMorP (Γ₂ ++ Γ₁) Γ₁
ctxProjP Γ₁ Γ₂ = mkCtxMorP (ctxProjDO Γ₁ Γ₂)

ctxProjP' : (Γ₁ Γ₂ Γ₃ : RawCtx) → CtxMorP (Γ₁ ++ Γ₂ ++ Γ₃) Γ₂
ctxProjP' Γ₁ Γ₂ Ø =
  subst (λ Γ → CtxMorP (Γ₁ ++ Γ) Γ₂)
        (PE.sym (proj₂ identity Γ₂))
        (ctxProjP Γ₂ Γ₁)
ctxProjP' Γ₁ Γ₂ (A ∷ Γ₃) =
  let f = ctxProjP' Γ₁ Γ₂ Γ₃
  in subst (λ Γ → Vec (PreTerm Γ Ø) (length' Γ₂))
           (assoc Γ₁ Γ₂ (A ∷ Γ₃))
           (Vec.map (weakenPO (Γ₁ ++ Γ₂) A)
                    (subst (λ Γ → Vec (PreTerm Γ Ø) (length' Γ₂))
                           (PE.sym (assoc Γ₁ Γ₂ Γ₃))
                           f
                    )
           )

weakenDO' : {Γ₁ Γ₃ : RawCtx} {t : Raw Ø Γ₁ Γ₃} →
            (Γ₂ : RawCtx) → DecentTerm t → DecentTerm (weaken' Γ₂ t)
weakenDO' {Γ₁} {t = t} Γ₂ p =
  subst DecentTerm
        (cong (substRaw t) (mkCtxMorP₁ (ctxProjDO Γ₁ Γ₂)))
        (substDO (ctxProjP Γ₁ Γ₂) p)

weakenPO' : {Γ₁ Γ₃ : RawCtx} →
            (Γ₂ : RawCtx) → PreTerm Γ₁ Γ₃ → PreTerm (Γ₂ ++ Γ₁) Γ₃
weakenPO' Γ₂ (t , p) = (weaken' Γ₂ t , weakenDO' Γ₂ p)

-- | Lift extension of context morphism to decent terms
extendP : {Γ₁ Γ₂ : RawCtx} →
           (A : U) → (f : CtxMorP Γ₂ Γ₁) → CtxMorP (A ∷ Γ₂) (A ∷ Γ₁)
extendP {Γ₁} {Γ₂} A f = varPO zero ∷ Vec.map (weakenPO₁ A) f

getPO : {Γ₁ Γ₂ : RawCtx} {A : U} → CtxMorP Γ₂ Γ₁ → RawVar Γ₁ A → PreTerm Γ₂ Ø
getPO f x = (get {Raw} (projCtxMor₁ f) x , get' f x)

substPO : ∀ {Γ₁ Γ Γ₂} → PreTerm Γ₁ Γ → CtxMorP Γ₂ Γ₁ → PreTerm Γ₂ Γ
substPO (._ , DO-unit Γ₁)             f = unitPO _
substPO (._ , DO-objVar x)            f = getPO f x
substPO (._ , DO-inst t s p q)        f =
  instPO (substPO (t , p) f) (substPO (s , q) f)
substPO (._ , DO-dialg Γ₁ Γ A ρ k)    f = dialgPO _ Γ A ρ k
substPO (._ , DO-mapping Γ Γ₁ gs ρ p) f = grec Γ _ (mkFpMapDataP {Γ} {gs} p) ρ

weakenPO'' : {Γ₁ Γ₃ : RawCtx} →
             (Γ₂ Γ₂' : RawCtx) → PreTerm Γ₁ Γ₃ → PreTerm (Γ₂' ++ Γ₁ ++ Γ₂) Γ₃
weakenPO'' Γ₂ Γ₂' t = substPO t (ctxProjP' Γ₂' _ Γ₂)

-- | Lift substitution to pretypes
substPT : ∀ {Θ Γ₁ Γ Γ₂} → PreType Θ Γ₁ Γ → CtxMorP Γ₂ Γ₁ → PreType Θ Γ₂ Γ
substPT (._ , DT-⊤ Θ Γ)               f = ⊤-PT _ _
substPT (._ , DT-tyVar Γ₁ X)          f = tyVarPT _ X
substPT (._ , DT-inst B t p q)        f =
  (substPT (B , p) f) ⊙ (substPO (t , q) f)
substPT (._ , DT-paramAbstr Γ₁ {A} p) f =
  paramAbstrPT _ (substPT (A , p) (extendP _ f))
substPT (._ , DT-fp Γ₁ ρ D q)         f = fpPT _ ρ (mkFpDataP {D = D} q)

weakenPT' : ∀ {Θ Γ₁ Γ₂} (Γ : RawCtx) → PreType Θ Γ₁ Γ₂ → PreType Θ (Γ ++ Γ₁) Γ₂
weakenPT' {Γ₁ = Γ₁} Γ A = substPT A (ctxProjP Γ₁ Γ)

weakenPT'' : ∀ {Θ Γ₁} (Γ : RawCtx) → PreType Θ Ø Γ₁ → PreType Θ Γ Γ₁
weakenPT'' Γ A =
  subst (λ u → PreType _ u _) (proj₂ identity Γ) (weakenPT' Γ A)

-- | Project a specific variable out
projVar : (Γ₁ Γ₂ : RawCtx) (A : U) → PreTerm (Γ₂ ++ A ∷ Γ₁) Ø
projVar Γ₁ Ø        A = varPO zero
projVar Γ₁ (∗ ∷ Γ₂) A = weakenPO₁ _ (projVar Γ₁ Γ₂ A)

extendProj : {Γ₁ Γ₂ : RawCtx} → (Γ₃ Γ₄ : RawCtx) →
             CtxMorP (Γ₄ ++ Γ₃ ++ Γ₂) Γ₁ →
             CtxMorP (Γ₄ ++ Γ₃ ++ Γ₂) (Γ₃ ++ Γ₁)
extendProj Ø Γ₄ f = f
extendProj {Γ₁} {Γ₂ = Γ₂} (A ∷ Γ₃) Γ₄ f =
  let p = (assoc Γ₄ (A ∷ Ø) (Γ₃ ++ Γ₂))
      f' = subst (λ u → CtxMorP u Γ₁) (PE.sym p) f
      g = extendProj {Γ₁} {Γ₂} Γ₃ (Γ₄ ↑ A) f'
      g' = subst (λ u → CtxMorP u (Γ₃ ++ Γ₁)) p g
  in projVar (Γ₃ ++ Γ₂) Γ₄ A ∷ g'

weakenTyVar₁ : ∀{Θ₂ Γ₁} (Θ₁ : TyCtx) (Γ : RawCtx) →
               TyVar (Θ₁ ++ Θ₂) Γ₁ → TyVar (Θ₁ ++ Γ ∷ Θ₂) Γ₁
weakenTyVar₁ Ø         Γ X           = succ _ X
weakenTyVar₁ (Γ₁ ∷ Θ₁) Γ zero        = zero
weakenTyVar₁ (Γ₂ ∷ Θ₁) Γ (succ Γ₁ X) = succ Γ₁ (weakenTyVar₁ Θ₁ Γ X)

weakenTyFpData'₁ : ∀ {Θ₂ Γ₁} (Θ₁ : TyCtx) →
                   {D : FpData Raw (Θ₁ ++ Θ₂) Γ₁} →
                   (Γ : RawCtx) → DecentFpData D →
                   Σ (FpData Raw (Θ₁ ++ Γ ∷ Θ₂) Γ₁) DecentFpData

-- | Auxiliary definition to allow Agda to see that it is provided with
-- a well-defined reursion.
weakenTy'₁ : ∀ {Θ₂ Γ₁ Γ₂} (Θ₁ : TyCtx) (Γ : RawCtx) →
             (A : Raw (Θ₁ ++ Θ₂) Γ₁ Γ₂) → DecentType A →
             PreType (Θ₁ ++ Γ ∷ Θ₂) Γ₁ Γ₂
weakenTy'₁ Θ₁ Γ ._ (DT-⊤ ._ Γ₁) =
  ⊤-PT _ _
weakenTy'₁ Θ₁ Γ .(tyVarRaw Γ₁ X) (DT-tyVar Γ₁ X) =
  tyVarPT Γ₁ (weakenTyVar₁ Θ₁ Γ X)
weakenTy'₁ Θ₁ Γ .(instRaw B t) (DT-inst B t p q) =
  (weakenTy'₁ Θ₁ Γ B p) ⊙ (t , q)
weakenTy'₁ Θ₁ Γ .(paramAbstrRaw Γ₁ A) (DT-paramAbstr Γ₁ {A} p) =
  paramAbstrPT Γ₁ (weakenTy'₁ Θ₁ Γ A p)
weakenTy'₁ Θ₁ Γ .(fpRaw Γ₁ ρ D) (DT-fp Γ₁ ρ D p) =
  let (D' , p') = weakenTyFpData'₁ Θ₁ {D} Γ p
  in fpPT Γ₁ ρ (mkFpDataP {D = D'} p')

weakenTyFpData'₁ {Γ₁ = Γ₁} Θ₁ {[ Γ₂ , f , A ]}   Γ (p , q) =
  let (A' , q') = weakenTy'₁ (Γ₁ ∷ Θ₁) Γ A q
  in ([ Γ₂ , f , A' ] , p , q')
weakenTyFpData'₁ {Γ₁ = Γ₁} Θ₁ {(Γ₂ , f , A) ∷ D} Γ ((p , q) , r) =
  let (A' , q') = weakenTy'₁ (Γ₁ ∷ Θ₁) Γ A q
      (D' , r') = weakenTyFpData'₁ Θ₁ {D} Γ r
  in ((Γ₂ , f , A') ∷ D' , (p , q') , r')

weakenTy₁ : ∀ {Θ₂ Γ₁ Γ₂} (Θ₁ : TyCtx) (Γ : RawCtx) →
            PreType (Θ₁ ++ Θ₂) Γ₁ Γ₂ →
            PreType (Θ₁ ++ Γ ∷ Θ₂) Γ₁ Γ₂
weakenTy₁ Θ₁ Γ (A , p) = weakenTy'₁ Θ₁ Γ A p

weakenTyCtxMor₁ : ∀ {Θ₁ Θ₂} →
                  (Γ : RawCtx) → TyCtxMorP Θ₂ Θ₁ → TyCtxMorP (Γ ∷ Θ₂) Θ₁
weakenTyCtxMor₁ {Ø} Γ tt = tt
weakenTyCtxMor₁ {Γ₁ ∷ Θ₁} Γ (A , f) = (weakenTy₁ Ø Γ A , weakenTyCtxMor₁ Γ f)

getTy : ∀ {Θ₁ Θ₂ Γ₁ Γ₂} → TyCtxMorP Θ₁ Θ₂ → TyVar Θ₂ Γ₂ → PreType Θ₁ Γ₁ Γ₂
getTy {Θ₂ = Ø} tt ()
getTy {Θ₁} {Θ₂ = Γ ∷ Θ₂} {Γ₁} (B , f) zero =
  subst (λ Γ' → PreType Θ₁ Γ' Γ) (proj₂ identity Γ₁) (weakenPT' Γ₁ B)
getTy {Θ₂ = Γ ∷ Θ₂} (B , f) (succ Γ₂ X) = getTy f X

extendTy : ∀ {Θ₁ Θ₂} →
           TyCtxMorP Θ₁ Θ₂ → (Γ : RawCtx) → TyCtxMorP (Γ ∷ Θ₁) (Γ ∷ Θ₂)
extendTy f Γ = (tyVarPT Ø zero , weakenTyCtxMor₁ Γ f)

substTyFpData' : ∀ {Θ₁ Θ₂ Γ} →
                 (D : FpData Raw Θ₂ Γ) → DecentFpData D →
                 TyCtxMorP Θ₁ Θ₂ → FpDataP Θ₁ Γ

-- | Substitution for type variables, auxilary version to have a clearly
-- terminating definition.
substTy' : ∀ {Θ₁ Θ₂ Γ₁ Γ₂} →
            (A : Raw Θ₂ Γ₁ Γ₂) → DecentType A → TyCtxMorP Θ₁ Θ₂ →
            PreType Θ₁ Γ₁ Γ₂

substTy' {Θ₁} ._ (DT-⊤ Θ Γ)               f = ⊤-PT Θ₁ _
substTy' {Θ₁} ._ (DT-tyVar Γ₁ X)          f = getTy f X
substTy' {Θ₁} ._ (DT-inst B t p q)        f =
  (substTy' B p f) ⊙ (t , q)
substTy' {Θ₁} ._ (DT-paramAbstr Γ₁ {A} p) f =
  paramAbstrPT Γ₁ (substTy' A p f)
substTy' {Θ₁} ._ (DT-fp Γ₁ ρ D p)         f =
  fpPT Γ₁ ρ (substTyFpData' D p f)

substTyFpData' {Γ = Γ} [ Γ₁ , g , A ] (p , q) f =
  [ Γ₁ , mkCtxMorP p , substTy' A q (extendTy f Γ) ]
substTyFpData' {Γ = Γ} ((Γ₁ , g , A) ∷ D) ((p , q) , r) f =
  (Γ₁ , mkCtxMorP p , substTy' A q (extendTy f Γ))
  ∷ substTyFpData' D r f

-- | Substitution for type variables
substTy : ∀ {Θ₁ Θ₂ Γ₁ Γ₂} →
          PreType Θ₂ Γ₁ Γ₂ → TyCtxMorP Θ₁ Θ₂ → PreType Θ₁ Γ₁ Γ₂
substTy (A , p) = substTy' A p

{-
weakenTy : {Θ₁ : TyCtx} {Γ₁ Γ₂ : RawCtx}
           (Θ₂ : TyCtx) → Raw Θ₁ Γ₁ Γ₂ → Raw (Θ₂ ++ Θ₁) Γ₁ Γ₂
weakenTy = {!!}
-}

-----------------------------------------------
--- Other operations
----------------------------------------------
Λ : ∀ {Θ Γ₁ Γ₂} → PreType Θ Γ₁ Γ₂ → PreType Θ Ø (Γ₂ ++ Γ₁)
Λ {Γ₁ = Ø}           A =
  subst (λ Γ → PreType _ Ø Γ) (PE.sym (proj₂ identity _)) A
Λ {Γ₁ = B ∷ Γ₁} {Γ₂} A =
  let A' = Λ (paramAbstrPT Γ₁ A)
  in subst (λ Γ → PreType _ Ø Γ) (assoc Γ₂ (B ∷ Ø) Γ₁) A'

--------------------------------------------------
-- Examples
--------------------------------------------------

-- We could prove the following
-- DT-Prod : (Γ : RawCtx) → DecentType (ProdRaw Γ)
-- However, it is easier to construct the product directly as pretype.

Prod : (Γ : RawCtx) → PreType (Γ ︵ Γ) Ø Γ
Prod Γ = fpPT Ø ν D
  where
    Δ = Γ ︵ Γ

    A : TyVar (Γ ∷ Δ) Γ
    A = succ Γ zero

    B : TyVar (Γ ∷ Δ) Γ
    B = succ Γ (succ Γ zero)

    D₁ = (Γ , ctxidP Γ , instTyWCtxMorP (tyVarPT Γ A) (ctxidP Γ))

    D₂ = (Γ , ctxidP Γ , instTyWCtxMorP (tyVarPT Γ B) (ctxidP Γ))

    D : FpDataP Δ Γ
    D = D₁ ∷ [ D₂ ]