module posts.agda.typed-protocols where

open import Relation.Binary.PropositionalEquality as Eq
open Eq using (_≡_; refl)
open import Relation.Nullary using (¬_)
open import Data.Empty using (⊥; ⊥-elim)
open import Data.List
open import Data.Nat
open import Data.Unit using (⊤; tt)
open import Function.Base using (_$_; _|>_)

-- Client / server roles
--
data PeerRole : Set where
  ClientRole : PeerRole
  ServerRole : PeerRole

dual-role : PeerRole → PeerRole
dual-role ClientRole = ServerRole
dual-role ServerRole = ClientRole

-- Objective agency: who is responsible for sending next message.
--
data Agency : Set where
  ClientAgency : Agency
  ServerAgency : Agency
  NobodyAgency : Agency

-- Relative agency to the current 'PeerRole'.  It answer the question: are we
-- responsible for sending next message?
--
data RelativeAgency : Set where
  WeHaveAgency    : RelativeAgency
  TheyHaveAgency  : RelativeAgency
  NobodyHasAgency : RelativeAgency

relative : PeerRole   → Agency       → RelativeAgency

relative   ClientRole   ClientAgency = WeHaveAgency
relative   ClientRole   ServerAgency = TheyHaveAgency
relative   ClientRole   NobodyAgency = NobodyHasAgency

relative   ServerRole   ClientAgency = TheyHaveAgency
relative   ServerRole   ServerAgency = WeHaveAgency
relative   ServerRole   NobodyAgency = NobodyHasAgency

-- 'relative' function obeys the following three exclusion lemmas:
--
-- * it is an absurd if client and server have agency
-- * it is an absurd if 'WeHaveAgency'   and the dual role terminated
--   ('NobodyHasAgency')
-- * it is an absurd if 'TheyHaveAgency' and the dual role terminated
--   ('NobodyHasAgency')
--
-- Note that these lemmas are provided regardless of the protocol, unlike the
-- Haskell implementation which requires to prove them for each protocol.

exclusion-lemma-client-and-server-have-agency₁
  : ∀ {agency : Agency}
      {pr     : PeerRole}
  → WeHaveAgency ≡ relative            pr  agency
  → WeHaveAgency ≡ relative (dual-role pr) agency
  → ⊥
exclusion-lemma-client-and-server-have-agency₁
  {ClientAgency}
  {ClientRole}
  refl ()
exclusion-lemma-client-and-server-have-agency₁
  {ServerAgency}
  {ServerRole}
  refl ()

exclusion-lemma-client-and-server-have-agency₂
  : ∀ {agency : Agency}
      {pr     : PeerRole}
  → TheyHaveAgency ≡ relative            pr  agency
  → TheyHaveAgency ≡ relative (dual-role pr) agency
  → ⊥
exclusion-lemma-client-and-server-have-agency₂
  {ServerAgency}
  {ClientRole}
  refl ()
exclusion-lemma-client-and-server-have-agency₂
  {ClientAgency}
  {ServerRole}
  refl ()

exclusion-lemma-we-have-agency-and-nobody-has-agency
  : ∀ {agency  : Agency}
      {pr      : PeerRole}
      {pr'     : PeerRole}
  → WeHaveAgency    ≡ relative pr  agency
  → NobodyHasAgency ≡ relative pr' agency
  → ⊥
exclusion-lemma-we-have-agency-and-nobody-has-agency
  {ClientAgency}
  {ClientRole}
  {ServerRole}
  refl ()
exclusion-lemma-we-have-agency-and-nobody-has-agency
  {ServerAgency}
  {ServerRole}
  {ClientRole}
  refl ()

exclusion-lemma-they-have-agency-and-nobody-has-agency
  : ∀ {agency : Agency}
      {pr     : PeerRole}
      {pr'    : PeerRole}
  → TheyHaveAgency  ≡ relative pr  agency
  → NobodyHasAgency ≡ relative pr' agency
  → ⊥
exclusion-lemma-they-have-agency-and-nobody-has-agency
  {ServerAgency}
  {ClientRole}
  {ServerRole}
  refl ()
exclusion-lemma-they-have-agency-and-nobody-has-agency
  {ClientAgency}
  {ServerRole}
  {ClientRole}
  refl ()

-----------
-- Peer API
--

-- We index each primitive with 'IsPipelined'
--
data IsPipelined : Set where
  Pipelined    : IsPipelined
  NonPipelined : IsPipelined

-- Promised protocol transition
--
data Trans (ps : Set) : Set where
  Tr : ps → ps → Trans ps

-- 'Peer' explicitly indexed by:
-- * message type
-- * objective protocol agency
-- * peer role
-- * return type
-- * 'IsPipelined'
-- * queue of unrealised transitions, due to pipelining
-- * current state
--
data Peer {ps     : Set}
          (msg    : ps → ps → Set)
          (agency : ps → Agency)
          (pr     : PeerRole)
          (a      : Set)
        : IsPipelined
        → List (Trans ps)
        → ps
        → Set where

  --
  -- non-pipelined primitives
  --

  -- non-pipelined send a message
  Yield : ∀ {st st' : ps}
            {pl     : IsPipelined}
        → WeHaveAgency ≡ relative pr (agency st)
        → msg st st'
        → Peer msg agency pr a pl [] st'
        → Peer msg agency pr a pl [] st

  -- non-pipelined receive a message
  Await : ∀ {st : ps}
            {pl : IsPipelined}
        → TheyHaveAgency ≡ relative pr (agency st)
        → (∀ {st' : ps}
           → msg st st'
           → Peer msg agency pr a pl [] st'
          )
        → Peer    msg agency pr a pl [] st

  -- protocol termination
  Done  : ∀ {st : ps}
            {pl : IsPipelined}
        → NobodyHasAgency ≡ relative pr (agency st)
        → a
        → Peer msg agency pr a pl [] st

  --
  -- pipelining primitives
  --

  -- pipeline a single message
  YieldPipelined
        : ∀ {st st' st'' : ps}
            {q           : List (Trans ps)}
        → WeHaveAgency ≡ relative pr (agency st)
        → msg st st'
        → Peer msg agency pr a Pipelined (q ∷ʳ Tr st' st'') st''
        → Peer msg agency pr a Pipelined  q                 st

  -- partially collect a promissed transition
  Collect
        : ∀ {st st' st'' : ps}
            {q           : List (Trans ps)}
        → TheyHaveAgency ≡ relative pr (agency st')
        → (∀ {stNext : ps}
           → msg st' stNext
           → Peer msg agency pr a Pipelined (Tr stNext st'' ∷ q) st
          )
        → Peer    msg agency pr a Pipelined (Tr st'    st'' ∷ q) st

  -- collect the identity transition
  CollectDone
        : ∀ {st : ps}
            {q  : List (Trans ps)}
        → Peer msg agency pr a Pipelined             q  st
        → Peer msg agency pr a Pipelined (Tr st st ∷ q) st

--------------
-- PingPong v1
--

-- Protocol states
--
data PingPong : Set where
  StIdle : PingPong
  StBusy : PingPong
  StDone : PingPong

-- Agency of PingPong states
--
pingPongAgency : PingPong → Agency
pingPongAgency StIdle = ClientAgency
pingPongAgency StBusy = ServerAgency
pingPongAgency StDone = NobodyAgency

-- Protocol messages
--
data PingPongMsg : ∀ (st st' : PingPong) → Set where
  MsgPing : PingPongMsg StIdle StBusy
  MsgPong : PingPongMsg StBusy StIdle
  MsgDone : PingPongMsg StIdle StDone

--
-- PingPong v1, examples
--

-- ping client which computes unit (tt : ⊤)
--
ping : Peer PingPongMsg pingPongAgency ClientRole ⊤ NonPipelined [] StIdle
ping =
      Yield refl MsgPing
    $ await
    $ Yield refl MsgPing
    $ await
    $ Yield refl MsgDone
    $ Done refl tt
  where
    await : Peer PingPongMsg pingPongAgency ClientRole ⊤ NonPipelined [] StIdle
          → Peer PingPongMsg pingPongAgency ClientRole ⊤ NonPipelined [] StBusy
    await k = Await refl λ {MsgPong → k}

-- pipelined client which computes unit (tt : ⊤)
--
pipelinedPing
    : Peer PingPongMsg pingPongAgency ClientRole ⊤ Pipelined [] StIdle
pipelinedPing =
      YieldPipelined refl MsgPing
    $ YieldPipelined refl MsgPing
    $ YieldPipelined refl MsgPing
    $ collect
    $ collect
    $ collect
    $ Yield refl MsgDone
    $ Done  refl tt
  where
    collect : ∀ {q : List (Trans PingPong)}
            → Peer PingPongMsg pingPongAgency ClientRole ⊤ Pipelined q  StIdle
            → Peer PingPongMsg pingPongAgency ClientRole ⊤ Pipelined
                                                 (Tr StBusy StIdle ∷ q) StIdle
    collect k =
      Collect refl λ {MsgPong → CollectDone k}

--------------
-- PingPong v2
--
-- The same states and agency as PingPong v1, but with additional 'MsgBusy'
-- transition.

data PingPongMsg2 : ∀ (st st' : PingPong) → Set where
  MsgPingPong : ∀ {st st' : PingPong}
              → PingPongMsg st st' → PingPongMsg2 st st'
  MsgBusy     : PingPongMsg2 StBusy StBusy

-- we use unbounded recursion in 'pipelinedPing2'
{-# NON_TERMINATING #-}

-- pipelined ping client which computes the number of busy messages
--
pipelinedPing2
    : Peer PingPongMsg2 pingPongAgency ClientRole ℕ Pipelined [] StIdle
pipelinedPing2 =
      YieldPipelined refl (MsgPingPong MsgPing)
    $ YieldPipelined refl (MsgPingPong MsgPing)
    $ YieldPipelined refl (MsgPingPong MsgPing)
    $          collect 0
    $ λ { n1 → collect n1
      λ { n2 → collect n2
      λ { n3 → Yield refl (MsgPingPong MsgDone)
             $ Done  refl n3 }}}
  where
    collect : ∀ {q : List (Trans PingPong)}
            → ℕ
            → (ℕ → Peer PingPongMsg2 pingPongAgency ClientRole ℕ Pipelined q  StIdle)
            → Peer PingPongMsg2 pingPongAgency ClientRole ℕ Pipelined
                                                       (Tr StBusy StIdle ∷ q) StIdle
    collect n k =
      Collect refl
        λ {  MsgBusy              → collect (n + 1) k
          ; (MsgPingPong MsgPong) → CollectDone (k n)
          }

------------------------
-- Non-pipelined Duality
--

-- Termination witness
data Termination (ps     : Set)
                 (agency : ps → Agency)
                 (a      : Set)
                 (b      : Set)
               : Set where
  Terminated : ∀ {st : ps} {pr : PeerRole}
             → a
             → b
             → NobodyHasAgency ≡ relative            pr  (agency st)
             → NobodyHasAgency ≡ relative (dual-role pr) (agency st)
             → Termination ps agency a b

theorem-non-pipelined-duality
  : ∀ {ps     : Set}
      {msg    : ps → ps → Set}
      {agency : ps → Agency}
      {pr     : PeerRole}
      {a      : Set}
      {b      : Set}
      {st     : ps}
  → Peer msg agency            pr  a NonPipelined [] st
  → Peer msg agency (dual-role pr) b NonPipelined [] st
  → Termination ps agency a b

theorem-non-pipelined-duality (Yield _ msg k)  (Await _ k')     =
  theorem-non-pipelined-duality k (k' msg)

theorem-non-pipelined-duality (Await _ k)      (Yield _ msg k') =
  theorem-non-pipelined-duality (k msg) k'

theorem-non-pipelined-duality (Done termA a)   (Done termB b)   =
  Terminated a b termA termB

-- excluded cases

theorem-non-pipelined-duality (Yield weHaveAgency _ _)
                              (Yield theyHaveAgency _ _)  =
  ⊥-elim (exclusion-lemma-client-and-server-have-agency₁ weHaveAgency theyHaveAgency)

theorem-non-pipelined-duality (Await theyHaveAgency _)
                              (Await weHaveAgency _) =
  ⊥-elim (exclusion-lemma-client-and-server-have-agency₂ theyHaveAgency weHaveAgency)

theorem-non-pipelined-duality (Yield weHaveAgency _ _)
                              (Done nobodyHasAgency _) =
  ⊥-elim (exclusion-lemma-we-have-agency-and-nobody-has-agency
            weHaveAgency
            nobodyHasAgency)

theorem-non-pipelined-duality (Done nobodyHasAgency _)
                              (Yield weHaveAgency _ _) =
  ⊥-elim (exclusion-lemma-we-have-agency-and-nobody-has-agency
            weHaveAgency
            nobodyHasAgency)

theorem-non-pipelined-duality (Await theyHaveAgency _)
                              (Done nobodyHasAgency _) =
  ⊥-elim (exclusion-lemma-they-have-agency-and-nobody-has-agency
            theyHaveAgency
            nobodyHasAgency)

theorem-non-pipelined-duality (Done nobodyHasAgency _)
                              (Await theyHaveAgency _) =
  ⊥-elim (exclusion-lemma-they-have-agency-and-nobody-has-agency
            theyHaveAgency
            nobodyHasAgency)

--------------
-- Un-pipeline
--

-- Transition queue which allows to transform pipelined 'Peer' into
-- non-pipelined one.   Pipelined messages are pushed to the end together with
-- promised transitions to be collected.
--
data PrQueue {ps     : Set}
             (msg    : ps → ps → Set)
             (agency : ps -> Agency)
             (pr     : PeerRole)
           : ps
           → List (Trans ps)
           → ps
           → Set where

  ConsMsgQ : ∀ {st st' st'' : ps}
               {q           : List (Trans ps)}
           → WeHaveAgency ≡ relative pr (agency st)
           → msg st st'
           → PrQueue msg agency pr st' q st''
           → PrQueue msg agency pr st  q st''

  ConsTrQ  : ∀ {st st' st'' : ps}
               {q           : List (Trans ps)}
           → PrQueue msg agency pr st'             q  st''
           → PrQueue msg agency pr st (Tr st st' ∷ q) st''

  EmptyQ   : ∀ {st : ps}
           → PrQueue msg agency pr st [] st

snockMsgQ : ∀ {ps          : Set}
              {msg         : ps → ps → Set}
              {agency      : ps → Agency}
              {pr          : PeerRole}
              {st st' st'' : ps}
              {q           : List (Trans ps)}
          → WeHaveAgency ≡ relative pr (agency st')
          → msg st' st''
          → PrQueue msg agency pr st q st'
          → PrQueue msg agency pr st q st''
snockMsgQ tok msg (ConsMsgQ tok' msg' q) = ConsMsgQ tok' msg' (snockMsgQ tok msg q)
snockMsgQ tok msg (ConsTrQ q)            = ConsTrQ (snockMsgQ tok msg q)
snockMsgQ tok msg  EmptyQ                = ConsMsgQ tok msg EmptyQ

snockTrQ : ∀ {ps          : Set}
             {msg         : ps → ps → Set}
             {agency      : ps → Agency}
             {pr          : PeerRole}
             {st st' st'' : ps}
             {q           : List (Trans ps)}
         → PrQueue msg agency pr st  q st'
         → PrQueue msg agency pr st (q ∷ʳ Tr st' st'') st''
snockTrQ (ConsMsgQ tok msg q) = ConsMsgQ tok msg (snockTrQ q)
snockTrQ (ConsTrQ q)          = ConsTrQ (snockTrQ q)
snockTrQ  EmptyQ              = ConsTrQ EmptyQ

-- Every pipelined peer can be transformed into non-pipelined one, by
-- preserving the order of all transition.
--
theorem-unpipeline
  : ∀ {ps     : Set}
      {msg    : ps → ps → Set}
      {agency : ps → Agency}
      {pr     : PeerRole}
      {pl     : IsPipelined}
      {a      : Set}
      {stInit : ps}
  → Peer msg agency pr a pl           [] stInit
  → Peer msg agency pr a NonPipelined [] stInit
theorem-unpipeline = go EmptyQ
  where
    go : ∀ {ps     : Set}
           {msg    : ps → ps → Set}
           {agency : ps → Agency}
           {pr     : PeerRole}
           {pl     : IsPipelined}
           {a      : Set}
           {q      : List (Trans ps)}
           {st st' : ps}
      → PrQueue msg agency pr             st q  st'
      → Peer    msg agency pr a pl           q  st'
      → Peer    msg agency pr a NonPipelined [] st

    -- non-piplined primitives
    go EmptyQ (Done  tok a)     = Done tok a
    go EmptyQ (Yield tok msg k) = Yield tok msg (go EmptyQ k)
    go EmptyQ (Await tok k)     = Await tok λ {msg → go EmptyQ (k msg)}

    -- push msg and promised transition to back of the 'PrQueue'
    go q      (YieldPipelined tok msg k) =
      go (  q
         |> snockMsgQ tok msg
         |> snockTrQ
         )
         k

    go (ConsMsgQ tok msg q) k =
      Yield tok msg (go q k)

    go (ConsTrQ q) (Collect tok k) =
      Await tok λ {msg → go (ConsTrQ q) (k msg)}

    go (ConsTrQ q) (CollectDone k) =
      go q k

----------
-- Duality
--

theorem-pipelined-duality
  : ∀ {ps     : Set}
      {msg    : ps → ps → Set}
      {agency : ps → Agency}
      {pr     : PeerRole}
      {pl     : IsPipelined}
      {a      : Set}
      {b      : Set}
      {st     : ps}
  → Peer msg agency            pr  a pl [] st
  → Peer msg agency (dual-role pr) b pl [] st
  → Termination ps agency a b
theorem-pipelined-duality a b =
  theorem-non-pipelined-duality (theorem-unpipeline a)
                                (theorem-unpipeline b)


------------------------------------------------------------------------
-- Is-equivalence, defined in terms of contractible fibres
------------------------------------------------------------------------

-- Partly based on Voevodsky's work on univalent foundations.

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

open import Equality

module Equivalence.Contractible-preimages
  {e⁺} (eq : ∀ {a p} → Equality-with-J a p e⁺) where

open Derived-definitions-and-properties eq

open import Prelude as P hiding (id)

open import Bijection eq using (_↔_)
open import Equality.Decision-procedures eq
open import H-level eq as H-level
open import H-level.Closure eq
open import Preimage eq as Preimage using (_⁻¹_)
open import Surjection eq using (_↠_)

private
  variable
    a b ℓ p q : Level
    A B       : Type a
    f g       : A → B

------------------------------------------------------------------------
-- Is-equivalence

-- A function f is an equivalence if all preimages under f are
-- contractible.

Is-equivalence :
  {A : Type a} {B : Type b} →
  (A → B) → Type (a ⊔ b)
Is-equivalence f = ∀ y → Contractible (f ⁻¹ y)

abstract

  -- Is-equivalence f is a proposition, assuming extensional equality.

  propositional :
    Extensionality (a ⊔ b) (a ⊔ b) →
    {A : Type a} {B : Type b} (f : A → B) →
    Is-proposition (Is-equivalence f)
  propositional {a = a} ext f =
    Π-closure (lower-extensionality a lzero ext) 1 λ _ →
      Contractible-propositional ext

  -- If the domain is contractible and the codomain is propositional,
  -- then Is-equivalence f is contractible.

  sometimes-contractible :
    Extensionality (a ⊔ b) (a ⊔ b) →
    {A : Type a} {B : Type b} {f : A → B} →
    Contractible A → Is-proposition B →
    Contractible (Is-equivalence f)
  sometimes-contractible {a = a} ext A-contr B-prop =
    Π-closure (lower-extensionality a lzero ext) 0 λ _ →
      cojoin ext (Σ-closure 0 A-contr (λ _ → +⇒≡ B-prop))

  -- Is-equivalence f is not always contractible.

  not-always-contractible₁ :
    ∃ λ (A : Type a) → ∃ λ (B : Type b) → ∃ λ (f : A → B) →
      Is-proposition A × Contractible B ×
      ¬ Contractible (Is-equivalence f)
  not-always-contractible₁ =
    ⊥ ,
    ↑ _ ⊤ ,
    const (lift tt) ,
    ⊥-propositional ,
    ↑-closure 0 ⊤-contractible ,
    λ c → ⊥-elim (proj₁ (proj₁ (proj₁ c (lift tt))))

  not-always-contractible₂ :
    ∃ λ (A : Type a) → ∃ λ (B : Type b) → ∃ λ (f : A → B) →
      Contractible A × Is-set B ×
      ¬ Contractible (Is-equivalence f)
  not-always-contractible₂ =
    ↑ _ ⊤ ,
    ↑ _ Bool ,
    const (lift true) ,
    ↑-closure 0 ⊤-contractible ,
    ↑-closure 2 Bool-set ,
    λ c → Bool.true≢false (cong lower
            (proj₂ (proj₁ (proj₁ c (lift false)))))

-- Is-equivalence respects extensional equality.

respects-extensional-equality :
  (∀ x → f x ≡ g x) →
  Is-equivalence f → Is-equivalence g
respects-extensional-equality f≡g f-eq = λ b →
  H-level.respects-surjection
    (_↔_.surjection (Preimage.respects-extensional-equality f≡g))
    0
    (f-eq b)

-- If f is an equivalence, then it has an inverse.

inverse :
  {@0 A : Type a} {@0 B : Type b} {@0 f : A → B} →
  Is-equivalence f → B → A
inverse eq y = proj₁ (proj₁ (eq y))

right-inverse-of :
  {@0 A : Type a} {@0 B : Type b} {@0 f : A → B} →
  (eq : Is-equivalence f) → ∀ x → f (inverse eq x) ≡ x
right-inverse-of eq x = proj₂ (proj₁ (eq x))

abstract

  left-inverse-of :
    (eq : Is-equivalence f) → ∀ x → inverse eq (f x) ≡ x
  left-inverse-of {f = f} eq x =
    cong (proj₁ {B = λ x′ → f x′ ≡ f x}) (
      proj₁ (eq (f x))  ≡⟨ proj₂ (eq (f x)) (x , refl (f x)) ⟩∎
      (x , refl (f x))  ∎)

-- All fibres of an equivalence over a given point are equal to a
-- given fibre.

irrelevance :
  {@0 A : Type a} {@0 B : Type b} {@0 f : A → B} →
  (eq : Is-equivalence f) →
  ∀ y (p : f ⁻¹ y) → (inverse eq y , right-inverse-of eq y) ≡ p
irrelevance eq y = proj₂ (eq y)

abstract

  -- The two proofs left-inverse-of and right-inverse-of are
  -- related.

  left-right-lemma :
    (eq : Is-equivalence f) →
    ∀ x → cong f (left-inverse-of eq x) ≡ right-inverse-of eq (f x)
  left-right-lemma {f = f} eq x =
    lemma₁ f _ _ (lemma₂ (irrelevance eq (f x) (x , refl (f x))))
    where
    lemma₁ : {x y : A} (f : A → B) (p : x ≡ y) (q : f x ≡ f y) →
             refl (f y) ≡ trans (cong f (sym p)) q →
             cong f p ≡ q
    lemma₁ f = elim
      (λ {x y} p → ∀ q → refl (f y) ≡ trans (cong f (sym p)) q →
                         cong f p ≡ q)
      (λ x q hyp →
         cong f (refl x)                  ≡⟨ cong-refl f ⟩
         refl (f x)                       ≡⟨ hyp ⟩
         trans (cong f (sym (refl x))) q  ≡⟨ cong (λ p → trans (cong f p) q) sym-refl ⟩
         trans (cong f (refl x)) q        ≡⟨ cong (λ p → trans p q) (cong-refl f) ⟩
         trans (refl (f x)) q             ≡⟨ trans-reflˡ _ ⟩∎
         q                                ∎)

    lemma₂ : ∀ {f : A → B} {y} {f⁻¹y₁ f⁻¹y₂ : f ⁻¹ y}
             (p : f⁻¹y₁ ≡ f⁻¹y₂) →
             proj₂ f⁻¹y₂ ≡
             trans (cong f (sym (cong (proj₁ {B = λ x → f x ≡ y}) p)))
                   (proj₂ f⁻¹y₁)
    lemma₂ {f = f} {y = y} =
      let pr = proj₁ {B = λ x → f x ≡ y} in
      elim {A = f ⁻¹ y}
        (λ {f⁻¹y₁ f⁻¹y₂} p →
           proj₂ f⁻¹y₂ ≡
             trans (cong f (sym (cong pr p))) (proj₂ f⁻¹y₁))
        (λ f⁻¹y →
           proj₂ f⁻¹y                                               ≡⟨ sym $ trans-reflˡ _ ⟩
           trans (refl (f (proj₁ f⁻¹y))) (proj₂ f⁻¹y)               ≡⟨ cong (λ p → trans p (proj₂ f⁻¹y)) (sym (cong-refl f)) ⟩
           trans (cong f (refl (proj₁ f⁻¹y))) (proj₂ f⁻¹y)          ≡⟨ cong (λ p → trans (cong f p) (proj₂ f⁻¹y)) (sym sym-refl) ⟩
           trans (cong f (sym (refl (proj₁ f⁻¹y)))) (proj₂ f⁻¹y)    ≡⟨ cong (λ p → trans (cong f (sym p)) (proj₂ f⁻¹y))
                                                                            (sym (cong-refl pr)) ⟩∎
           trans (cong f (sym (cong pr (refl f⁻¹y)))) (proj₂ f⁻¹y)  ∎)

  right-left-lemma :
    (eq : Is-equivalence f) →
    ∀ x →
    cong (inverse eq) (right-inverse-of eq x) ≡
    left-inverse-of eq (inverse eq x)
  right-left-lemma {f = f} eq x =
    let f⁻¹ = inverse eq in
    subst
      (λ x → cong f⁻¹ (right-inverse-of eq x) ≡
             left-inverse-of eq (f⁻¹ x))
      (right-inverse-of eq x)
      (cong f⁻¹ (right-inverse-of eq (f (f⁻¹ x)))             ≡⟨ cong (cong f⁻¹) $ sym $ left-right-lemma eq _ ⟩

       cong f⁻¹ (cong f (left-inverse-of eq (f⁻¹ x)))         ≡⟨ cong-∘ f⁻¹ f _ ⟩

       cong (f⁻¹ ∘ f) (left-inverse-of eq (f⁻¹ x))            ≡⟨ cong-roughly-id (f⁻¹ ∘ f) (λ _ → true) (left-inverse-of eq _) _ _
                                                                   (λ z _ → left-inverse-of eq z) ⟩
       trans (left-inverse-of eq (f⁻¹ (f (f⁻¹ x))))
         (trans (left-inverse-of eq (f⁻¹ x))
            (sym (left-inverse-of eq (f⁻¹ x))))               ≡⟨ cong (trans _) $ trans-symʳ _ ⟩

       trans (left-inverse-of eq (f⁻¹ (f (f⁻¹ x)))) (refl _)  ≡⟨ trans-reflʳ _ ⟩∎

       left-inverse-of eq (f⁻¹ (f (f⁻¹ x)))                   ∎)

abstract

  -- If Σ-map P.id f is an equivalence, then f is also an equivalence.

  drop-Σ-map-id :
    {A : Type a} {P : A → Type p} {Q : A → Type q}
    (f : ∀ {x} → P x → Q x) →
    Is-equivalence {A = Σ A P} {B = Σ A Q} (Σ-map P.id f) →
    ∀ x → Is-equivalence (f {x = x})
  drop-Σ-map-id {p = ℓp} {q = ℓq} {A = A} {P = P} {Q = Q} f eq x z =
    H-level.respects-surjection surj 0 (eq (x , z))
    where
    map-f : Σ A P → Σ A Q
    map-f = Σ-map P.id f

    to-P : ∀ {x y} {p : ∃ Q} → (x , f y) ≡ p → Type (ℓp ⊔ ℓq)
    to-P {y = y} {p} _ = ∃ λ y′ → f y′ ≡ proj₂ p

    to : map-f ⁻¹ (x , z) → f ⁻¹ z
    to ((x′ , y) , eq) = elim¹ to-P (y , refl (f y)) eq

    from : f ⁻¹ z → map-f ⁻¹ (x , z)
    from (y , eq) = (x , y) , cong (_,_ x) eq

    to∘from : ∀ p → to (from p) ≡ p
    to∘from (y , eq) = elim¹
      (λ {z′} (eq : f y ≡ z′) →
         _≡_ {A = ∃ λ (y : P x) → f y ≡ z′}
             (elim¹ to-P (y , refl (f y)) (cong (_,_ x) eq))
             (y , eq))
      (elim¹ to-P (y , refl (f y)) (cong (_,_ x) (refl (f y)))  ≡⟨ cong (elim¹ to-P (y , refl (f y))) $
                                                                        cong-refl (_,_ x) ⟩
       elim¹ to-P (y , refl (f y)) (refl (x , f y))             ≡⟨ elim¹-refl to-P _ ⟩∎
       (y , refl (f y))                                         ∎)
      eq

    surj : map-f ⁻¹ (x , z) ↠ f ⁻¹ z
    surj = record
      { logical-equivalence = record { to = to; from = from }
      ; right-inverse-of    = to∘from
      }

  -- A "computation" rule for drop-Σ-map-id.

  inverse-drop-Σ-map-id :
    {A : Type a} {P : A → Type p} {Q : A → Type q}
    {f : ∀ {x} → P x → Q x} {x : A} {y : Q x}
    {eq : Is-equivalence {A = Σ A P} {B = Σ A Q} (Σ-map P.id f)} →
    inverse (drop-Σ-map-id f eq x) y ≡
    subst P (cong proj₁ (right-inverse-of eq (x , y)))
      (proj₂ (inverse eq (x , y)))
  inverse-drop-Σ-map-id
    {P = P} {Q = Q} {f = f} {x = x} {y = y} {eq = eq} =

    let lemma = elim¹
          (λ {q′} eq →
             cong proj₁
               (proj₂ (other-singleton-contractible (Σ-map P.id f p′))
                  (q′ , eq)) ≡
             eq)
          (cong proj₁
             (proj₂ (other-singleton-contractible (Σ-map P.id f p′))
                (Σ-map P.id f p′ , refl _))                           ≡⟨ cong (cong proj₁) $
                                                                         other-singleton-contractible-refl _ ⟩

           cong proj₁ (refl _)                                        ≡⟨ cong-refl _ ⟩∎

           refl _                                                     ∎)
          _
    in
    proj₁
      (subst
         (λ ((_ , y) , _) → f ⁻¹ y)
         (proj₂
            (other-singleton-contractible (Σ-map P.id f p′))
            (q′ , right-inverse-of eq q′))
         (proj₂ p′ , refl _))                                 ≡⟨ cong proj₁ $ push-subst-pair _ _ ⟩

    subst
      (λ ((x , _) , _) → P x)
      (proj₂
         (other-singleton-contractible (Σ-map P.id f p′))
         (q′ , right-inverse-of eq q′))
      (proj₂ p′)                                              ≡⟨ trans (subst-∘ _ _ _) (subst-∘ _ _ _) ⟩

    subst P
      (cong proj₁ $ cong proj₁ $
       proj₂
         (other-singleton-contractible (Σ-map P.id f p′))
         (q′ , right-inverse-of eq q′))
      (proj₂ p′)                                              ≡⟨ cong (λ eq → subst P (cong proj₁ eq) (proj₂ p′))
                                                                 lemma ⟩∎
    subst P
      (cong proj₁ (right-inverse-of eq q′))
      (proj₂ p′)                                              ∎
    where
    q′ : ∃ Q
    q′ = x , y

    p′ : ∃ P
    p′ = inverse eq q′

------------------------------------------------------------------------
-- _≃_

-- A notion of equivalence.

infix 4 _≃_

_≃_ : Type a → Type b → Type (a ⊔ b)
A ≃ B = ∃ λ (f : A → B) → Is-equivalence f

-- An identity equivalence.

id : A ≃ A
id = P.id , singleton-contractible

-- Equalities can be converted to equivalences.

≡⇒≃ : A ≡ B → A ≃ B
≡⇒≃ = elim (λ {A B} _ → A ≃ B) (λ _ → id)

-- If ≡⇒≃ is applied to reflexivity, then the result is equal to id.

≡⇒≃-refl : ≡⇒≃ (refl A) ≡ id
≡⇒≃-refl = elim-refl (λ {A B} _ → A ≃ B) (λ _ → id)

-- Univalence for fixed types.

Univalence′ : (A B : Type ℓ) → Type (lsuc ℓ)
Univalence′ A B = Is-equivalence (≡⇒≃ {A = A} {B = B})

-- Univalence.

Univalence : ∀ ℓ → Type (lsuc ℓ)
Univalence ℓ = {A B : Type ℓ} → Univalence′ A B


-- This file needs to be rewritten so that Rng's are defined as a
-- record (as is the case for other algebraic structures like
-- rings). As this file isn't used for anything at the moment this
-- rewrite has been postponed.

{-# OPTIONS --cubical --no-import-sorts --safe #-}
module Cubical.Experiments.Rng where

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.SIP
open import Cubical.Data.Sigma

open import Cubical.Structures.Axioms
open import Cubical.Structures.Auto
open import Cubical.Structures.Macro
open import Cubical.Algebra.Semigroup hiding (⟨_⟩)
open import Cubical.Algebra.AbGroup

private
  variable
    ℓ ℓ' : Level

module _ {ℓ} where
  rawRngDesc : Desc ℓ
  rawRngDesc = autoDesc (λ (X : Type ℓ) → (X → X → X) × (X → X → X))

  open Macro ℓ rawRngDesc public renaming
    ( structure to RawRngStructure
    ; equiv to RawRngEquivStr
    ; univalent to rawRngUnivalentStr
    )

RngAxioms : (X : Type ℓ) (s : RawRngStructure X) → Type ℓ
RngAxioms X (_·_ , _+_) =
  AbGroupΣTheory.AbGroupAxioms X _·_ ×
  SemigroupΣTheory.SemigroupAxioms X _+_ ×
  ((x y z : X) → x · (y + z) ≡ (x · y) + (x · z)) × ((x y z : X) → (x + y) · z ≡ (x · z) + (y · z))

RngStructure : Type ℓ → Type ℓ
RngStructure = AxiomsStructure RawRngStructure RngAxioms

Rng : Type (ℓ-suc ℓ)
Rng {ℓ} = TypeWithStr ℓ RngStructure

RngEquivStr : StrEquiv RngStructure ℓ
RngEquivStr = AxiomsEquivStr RawRngEquivStr RngAxioms

isPropRngAxioms : (X : Type ℓ) (s : RawRngStructure X) → isProp (RngAxioms X s)
isPropRngAxioms X (_·_ , _+_) = isPropΣ (AbGroupΣTheory.isPropAbGroupAxioms X _·_)
                                  λ _ → isPropΣ (SemigroupΣTheory.isPropSemigroupAxioms X _+_)
                                  λ { (isSetX , _) → isPropΣ (isPropΠ3 (λ _ _ _ → isSetX _ _))
                                  λ _ → isPropΠ3 (λ _ _ _ → isSetX _ _)}

rngUnivalentStr : UnivalentStr {ℓ} RngStructure RngEquivStr
rngUnivalentStr = axiomsUnivalentStr _ isPropRngAxioms rawRngUnivalentStr

RngPath : (M N : Rng {ℓ}) → (M ≃[ RngEquivStr ] N) ≃ (M ≡ N)
RngPath = SIP rngUnivalentStr


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

open import lib.Basics
open import lib.NType2
open import lib.PathFunctor
open import lib.PathGroupoid

open import lib.types.Bool
open import lib.types.IteratedSuspension
open import lib.types.LoopSpace
open import lib.types.Nat
open import lib.types.Paths
open import lib.types.Pi
open import lib.types.Pointed
open import lib.types.Sigma
open import lib.types.Suspension
open import lib.types.TLevel
open import lib.types.Unit

open import nicolai.pseudotruncations.Preliminary-definitions
open import nicolai.pseudotruncations.Liblemmas
open import nicolai.pseudotruncations.SeqColim
open import nicolai.pseudotruncations.wconstSequence

module nicolai.pseudotruncations.PseudoTruncs where

{- Definition of the Pseudo-truncation -}
module _ {i} where

  private
    data #Pseudotrunc-aux (n : ℕ) (A : Type i) : Type i where
      #point : A → #Pseudotrunc-aux n A
      #hub : (r : Sphere' n → A) → #Pseudotrunc-aux n A

    data #Pseudotrunc (n : ℕ) (A : Type i) : Type i where
      #trick-aux : #Pseudotrunc-aux n A → (Unit → Unit) → #Pseudotrunc n A

  Pseudo_-1-trunc : (n : ℕ) (A : Type i) → Type i
  Pseudo_-1-trunc = #Pseudotrunc

  point_-1 : {A : Type i} → (n : ℕ) → A → Pseudo n -1-trunc A
  point_-1 {A} n a = #trick-aux (#point a) _

  hub_-1 : {A : Type i} (n : ℕ) (r : Sphere' n → A) → Pseudo n -1-trunc A
  hub_-1 {A} n r = #trick-aux (#hub r) _

  postulate
    spoke_-1 : {A : Type i} (n : ℕ) (r : Sphere' n → A) (x : Sphere' n)
               → point n -1 (r x) == hub n -1 r

  {- The induction principle -}
  module PseudotruncInduction
    {A : Type i} (n : ℕ) {j}
    {P : Pseudo n -1-trunc A → Type j}
    (Point-1 : (a : A) → P (point n -1 a))
    (Hub-1 : (r : Sphere' n → A) → P (hub n -1 r))
    (Spoke-1 : (r : Sphere' n → A) → (x : Sphere' n)
               → Point-1 (r x) == Hub-1 r [ P ↓ spoke n -1 r x ])
    where

    f : Π (Pseudo n -1-trunc A) P
    f = f-aux phantom where
      f-aux : Phantom Spoke-1 → Π (Pseudo n -1-trunc A) P
      f-aux phantom (#trick-aux (#point x) _) = Point-1 x
      f-aux phantom (#trick-aux (#hub r) _) = Hub-1 r

    postulate
      pathβ : (r : Sphere' {i} n → A)
              → (x : Sphere' {i} n)
              → apd f (spoke n -1 r x) == Spoke-1 r x

open PseudotruncInduction public renaming
  (f to Pseudotrunc-ind ; pathβ to Pseudotrunc-ind-pathβ)

{- We derive the recursion principle from the induction principle -}
module PseudotruncRecursion
  {i j} {A : Type i}
  {P : Type j} (n : ℕ)
  (Point-1 : A → P)
  (Hub-1 : (r : Sphere' {i} n → A) → P)
  (Spoke-1 : (r : Sphere' n → A) (x : Sphere' n) → Point-1 (r x) == Hub-1 r)
  where

  rec : Pseudo n -1-trunc A → P
  rec = Pseudotrunc-ind n  {P = λ _ → P} Point-1 Hub-1
          (λ r x → from-transp
                    (λ _ → P)
                    (spoke n -1 r x)
                    ((transport (λ _ → P) (spoke n -1 r x) (Point-1 (r x)))
                        =⟨ transport-const-fam (spoke n -1 r x) (Point-1 (r x)) ⟩
                      Point-1 (r x)
                        =⟨ Spoke-1 r x ⟩
                      Hub-1 r
                        ∎))

open PseudotruncRecursion public renaming (rec to Pseudotrunc-rec)

{- A lemma that will be important later: any map from
   the sphere, composed with the points constructor,
   is null. -}

open import nicolai.pseudotruncations.pointed-O-Sphere
open import nicolai.pseudotruncations.LoopsAndSpheres
open null

module _ {i} {A : Type i} (n : ℕ) where

  from-sphere-null : (g : Sphere' {i} n → A)
                   → isNull (point n -1 (g (nor' n))) ((point n -1) ∘ g)
  from-sphere-null g x = point n -1 (g x)
                           =⟨ spoke n -1 g x ⟩
                         hub n -1 g
                           =⟨ ! (spoke n -1 g (nor' n)) ⟩
                         point n -1 (g (nor' n))
                           ∎

module cmp-nll {i} {Â : Ptd i} (n : ℕ) (g : ⊙Sphere' {i} n →̇ Â) where

  {- Pointed version of first constructor -}
  -- points∙ : Â →̇ (Pseudo n -1-trunc (fst Â) , point n -1 (snd Â))
  -- points∙ = point n -1 , idp

  from-sphere-null∙ : isNulld ((point n -1 , idp) ⊙∘ g)
  from-sphere-null∙ = λ x →
                         point n -1 ((fst g) x)
                           =⟨ spoke n -1 (fst g) x ⟩
                         hub n -1 (fst g)
                           =⟨ ! (spoke n -1 (fst g) (nor' n)) ⟩
                         point n -1 ((fst g) (nor' n))
                           =⟨ ap (point n -1) (snd g) ⟩
                         point n -1 (snd Â)
                           ∎

  {- Unfortunately, we will need this lemma not for maps
        g : Sphere' (S n) → A,
     but we will need it for maps
        g : Susp (Sphere' n) → A,
     and Sphere' (S n) is NOT judgmentally equal to Susp (Sphere' n).
     We have to give a second proof. -}

module cmp-nll' {i} {Â : Ptd i} (n : ℕ) (g : ⊙Susp (⊙Sphere' {i} n) →̇ Â) where

  {- "translate" equivalence -}
  tr≃⊙ : ⊙Sphere' {i} (S n) ⊙≃ ⊙Susp (⊙Sphere' {i} n)
  tr≃⊙ = coe-equiv∙ (susp'-switch n)

  tr≃ : Sphere' {i} (S n) ≃ fst (⊙Susp (⊙Sphere' {i} n))
  tr≃ = fst (fst tr≃⊙) , snd tr≃⊙

  tr–̇> : (⊙Sphere' {i} (S n)) →̇ ⊙Susp (⊙Sphere' {i} n)
  tr–̇> = fst tr≃⊙ -- –> (fst (fst tr-eq) , snd tr-eq) , {!!}

  from-sphere-null'∙ : isNulld ((point S n -1 , idp) ⊙∘ g)
  from-sphere-null'∙ = λ x →

    fst ((point S n -1 , idp) ⊙∘ g) x
      =⟨ ap (fst ((point S n -1 , idp) ⊙∘ g)) (! (<–-inv-r tr≃ x)) ⟩
    fst ((point S n -1 , idp) ⊙∘ g) (–> tr≃ (<– tr≃ x))
      =⟨ cmp-nll.from-sphere-null∙ {i} {Â} (S n) (g ⊙∘ tr–̇>) (<– tr≃ x) ⟩
    point S n -1 (snd Â)
      ∎


{-# OPTIONS --without-K --exact-split --allow-unsolved-metas #-}

module 21-image where

import 20-sequences
open 20-sequences public

{- We give the formal specification of propositional truncation. -}

precomp-Prop :
  { l1 l2 l3 : Level} {A : UU l1} (P : hProp l2) →
  (A → type-Prop P) → (Q : hProp l3) →
  (type-Prop P → type-Prop Q) → A → type-Prop Q
precomp-Prop P f Q g = g ∘ f

universal-property-propositional-truncation :
  ( l : Level) {l1 l2 : Level} {A : UU l1} (P : hProp l2) →
  ( A → type-Prop P) → UU (lsuc l ⊔ l1 ⊔ l2)
universal-property-propositional-truncation l P f =
  (Q : hProp l) → is-equiv (precomp-Prop P f Q)

universal-property-propositional-truncation' :
  ( l : Level) {l1 l2 : Level} {A : UU l1} (P : hProp l2) →
  ( A → type-Prop P) → UU (lsuc l ⊔ l1 ⊔ l2)
universal-property-propositional-truncation' l {A = A} P f =
  (Q : hProp l) → (A → type-Prop Q) → (type-Prop P → type-Prop Q)

universal-property-propositional-truncation-simplify :
  { l1 l2 : Level} {A : UU l1} (P : hProp l2)
  ( f : A → type-Prop P) →
  ( (l : Level) → universal-property-propositional-truncation' l P f) →
  ( (l : Level) → universal-property-propositional-truncation l P f)
universal-property-propositional-truncation-simplify P f up-P l Q =
  is-equiv-is-prop
    ( is-prop-Π (λ x → is-prop-type-Prop Q))
    ( is-prop-Π (λ x → is-prop-type-Prop Q))
    ( up-P l Q)

precomp-Π-Prop :
  { l1 l2 l3 : Level} {A : UU l1} (P : hProp l2) →
  ( f : A → type-Prop P) (Q : type-Prop P → hProp l3) →
  ( g : (p : type-Prop P) → type-Prop (Q p)) → (x : A) → type-Prop (Q (f x))
precomp-Π-Prop P f Q g x = g (f x)

dependent-universal-property-propositional-truncation :
  ( l : Level) {l1 l2 : Level} {A : UU l1} (P : hProp l2) →
  ( A → type-Prop P) → UU (lsuc l ⊔ l1 ⊔ l2)
dependent-universal-property-propositional-truncation l P f =
  (Q : type-Prop P → hProp l) → is-equiv (precomp-Π-Prop P f Q)

{- We introduce the image inclusion of a map. -}

precomp-emb :
  { l1 l2 l3 l4 : Level} {X : UU l1} {A : UU l2} (f : A → X)
  {B : UU l3} ( i : B ↪ X) (q : hom-slice f (map-emb i)) →
  {C : UU l4} ( j : C ↪ X) (r : hom-slice (map-emb i) (map-emb j)) →
  hom-slice f (map-emb j)
precomp-emb f i q j r =
  pair
    ( ( map-hom-slice (map-emb i) (map-emb j) r) ∘
      ( map-hom-slice f (map-emb i) q))
    ( ( triangle-hom-slice f (map-emb i) q) ∙h
      ( ( triangle-hom-slice (map-emb i) (map-emb j) r) ·r
        ( map-hom-slice f (map-emb i) q)))

is-prop-hom-slice :
  { l1 l2 l3 : Level} {X : UU l1} {A : UU l2} (f : A → X) →
  { B : UU l3} (i : B ↪ X) → is-prop (hom-slice f (map-emb i))
is-prop-hom-slice {X = X} f i =
  is-prop-is-equiv
    ( (x : X) → fib f x → fib (map-emb i) x)
    ( fiberwise-hom-hom-slice f (map-emb i))
    ( is-equiv-fiberwise-hom-hom-slice f (map-emb i))
    ( is-prop-Π
      ( λ x → is-prop-Π
        ( λ p → is-prop-map-is-emb (map-emb i) (is-emb-map-emb i) x)))

universal-property-image :
  ( l : Level) {l1 l2 l3 : Level} {X : UU l1} {A : UU l2} (f : A → X) →
  { B : UU l3} (i : B ↪ X) (q : hom-slice f (map-emb i)) →
  UU (lsuc l ⊔ l1 ⊔ l2 ⊔ l3)
universal-property-image l {X = X} f i q =
  ( C : UU l) (j : C ↪ X) → is-equiv (precomp-emb f i q j)

universal-property-image' :
  ( l : Level) {l1 l2 l3 : Level} {X : UU l1} {A : UU l2} (f : A → X) →
  { B : UU l3} (i : B ↪ X) (q : hom-slice f (map-emb i)) →
  UU (lsuc l ⊔ l1 ⊔ l2 ⊔ l3)
universal-property-image' l {X = X} f i q =
  ( C : UU l) (j : C ↪ X) →
    hom-slice f (map-emb j) → hom-slice (map-emb i) (map-emb j)

universal-property-image-universal-property-image' :
  ( l : Level) {l1 l2 l3 : Level} {X : UU l1} {A : UU l2} (f : A → X) →
  { B : UU l3} (i : B ↪ X) (q : hom-slice f (map-emb i)) →
  universal-property-image' l f i q → universal-property-image l f i q
universal-property-image-universal-property-image' l f i q up' C j =
  is-equiv-is-prop
    ( is-prop-hom-slice (map-emb i) j)
    ( is-prop-hom-slice f j)
    ( up' C j)


-- Laws for weakenings and substitutions.

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

module Definition.Untyped.Properties where

open import Definition.Untyped

open import Tools.Nat
open import Tools.List
open import Tools.PropositionalEquality hiding (subst)

-- Weakening properties

-- Two weakenings ρ and ρ′ are extensionally equal if they agree on
-- all arguments when interpreted as functions mapping variables to
-- variables.  Formally, they are considered equal iff
--
--   (∀ x → wkVar ρ x ≡ wkVar ρ′ x)
--
-- Intensional (propositional) equality would be too fine.  For
-- instance,
--
--   lift id : Γ∙A ≤ Γ∙A
--
-- is extensionally equal to
--
--   id : Γ∙A ≤ Γ∙A
--
-- but syntactically different.

-- "lift" preserves equality of weakenings.  Or:
-- If two weakenings are equal under wkVar, then they are equal when lifted.

wkVar-lift : ∀ {ρ ρ′}
  → (∀ x → wkVar ρ x ≡ wkVar ρ′ x)
  → (∀ x → wkVar (lift ρ) x ≡ wkVar (lift ρ′) x)

wkVar-lift eq 0      = refl
wkVar-lift eq (1+ x) = cong 1+ (eq x)

wkVar-lifts : ∀ {ρ ρ′}
  → (∀ x → wkVar ρ x ≡ wkVar ρ′ x)
  → (∀ n x → wkVar (repeat lift ρ n) x ≡ wkVar (repeat lift ρ′ n) x)
wkVar-lifts eq 0 x = eq x
wkVar-lifts eq (1+ n) x = wkVar-lift (wkVar-lifts eq n) x

-- Extensionally equal weakenings, if applied to a term,
-- yield the same weakened term.  Or:
-- If two weakenings are equal under wkVar, then they are equal under wk.

mutual
  wkVar-to-wk : ∀ {ρ ρ′}
    → (∀ x → wkVar ρ x ≡ wkVar ρ′ x)
    → ∀ t → wk ρ t ≡ wk ρ′ t
  wkVar-to-wk eq (var x) = cong var (eq x)
  wkVar-to-wk eq (gen x c) = cong (gen x) (wkVar-to-wkGen eq c)

  wkVar-to-wkGen : ∀ {ρ ρ′}
    → (∀ x → wkVar ρ x ≡ wkVar ρ′ x)
    → ∀ t → wkGen ρ t ≡ wkGen ρ′ t
  wkVar-to-wkGen eq [] = refl
  wkVar-to-wkGen eq (⟦ l , t ⟧ ∷ g) =
    cong₂ _∷_ (cong (⟦_,_⟧ l) (wkVar-to-wk (wkVar-lifts eq l) t))
              (wkVar-to-wkGen eq g)

-- lift id  is extensionally equal to  id.

wkVar-lift-id : (x : Nat) → wkVar (lift id) x ≡ wkVar id x
wkVar-lift-id 0    = refl
wkVar-lift-id (1+ x) = refl

wkVar-lifts-id : (n x : Nat) → wkVar (repeat lift id n) x ≡ wkVar id x
wkVar-lifts-id 0 x = refl
wkVar-lifts-id (1+ n) 0 = refl
wkVar-lifts-id (1+ n) (1+ x) = cong 1+ (wkVar-lifts-id n x)

-- id is the identity renaming.

wkVar-id : (x : Nat) → wkVar id x ≡ x
wkVar-id x = refl

mutual
  wk-id : (t : Term) → wk id t ≡ t
  wk-id (var x) = refl
  wk-id (gen x c) = cong (gen x) (wkGen-id c)

  wkGen-id : ∀ x → wkGen id x ≡ x
  wkGen-id [] = refl
  wkGen-id (⟦ l , t ⟧ ∷ g) =
    cong₂ _∷_ (cong (⟦_,_⟧ l)
              (trans (wkVar-to-wk (wkVar-lifts-id l) t) (wk-id t))) (wkGen-id g)

-- lift id  is also the identity renaming.

wk-lift-id : (t : Term) → wk (lift id) t ≡ t
wk-lift-id t = trans (wkVar-to-wk wkVar-lift-id t) (wk-id t)

-- The composition of weakenings is correct...
--
-- ...as action on variables.

wkVar-comp : ∀ ρ ρ′ x → wkVar ρ (wkVar ρ′ x) ≡ wkVar (ρ • ρ′) x
wkVar-comp id       ρ′        x       = refl
wkVar-comp (step ρ) ρ′        x       = cong 1+ (wkVar-comp ρ ρ′ x)
wkVar-comp (lift ρ) id        x       = refl
wkVar-comp (lift ρ) (step ρ′) x       = cong 1+ (wkVar-comp ρ ρ′ x)
wkVar-comp (lift ρ) (lift ρ′) 0    = refl
wkVar-comp (lift ρ) (lift ρ′) (1+ x) = cong 1+ (wkVar-comp ρ ρ′ x)

wkVar-comps : ∀ n ρ ρ′ x
            → wkVar (repeat lift ρ n • repeat lift ρ′ n) x
            ≡ wkVar (repeat lift (ρ • ρ′) n) x
wkVar-comps 0 ρ ρ′ x = refl
wkVar-comps (1+ n) ρ ρ′ 0 = refl
wkVar-comps (1+ n) ρ ρ′ (1+ x) = cong 1+ (wkVar-comps n ρ ρ′ x)

-- ... as action on terms.

mutual
  wk-comp : ∀ ρ ρ′ t → wk ρ (wk ρ′ t) ≡ wk (ρ • ρ′) t
  wk-comp ρ ρ′ (var x) = cong var (wkVar-comp ρ ρ′ x)
  wk-comp ρ ρ′ (gen x c) = cong (gen x) (wkGen-comp ρ ρ′ c)

  wkGen-comp : ∀ ρ ρ′ g → wkGen ρ (wkGen ρ′ g) ≡ wkGen (ρ • ρ′) g
  wkGen-comp ρ ρ′ [] = refl
  wkGen-comp ρ ρ′ (⟦ l , t ⟧ ∷ g) =
    cong₂ _∷_ (cong (⟦_,_⟧ l) (trans (wk-comp (repeat lift ρ l) (repeat lift ρ′ l) t)
                                     (wkVar-to-wk (wkVar-comps l ρ ρ′) t)))
              (wkGen-comp ρ ρ′ g)

-- The following lemmata are variations on the equality
--
--   wk1 ∘ ρ = lift ρ ∘ wk1.
--
-- Typing:  Γ∙A ≤ Γ ≤ Δ  <==>  Γ∙A ≤ Δ∙A ≤ Δ.

lift-step-comp : (ρ : Wk) → step id • ρ ≡ lift ρ • step id
lift-step-comp id       = refl
lift-step-comp (step ρ) = cong step (lift-step-comp ρ)
lift-step-comp (lift ρ) = refl

wk1-wk : ∀ ρ t → wk1 (wk ρ t) ≡ wk (step ρ) t
wk1-wk ρ t = wk-comp (step id) ρ t

lift-wk1 : ∀ ρ t → wk (lift ρ) (wk1 t) ≡ wk (step ρ) t
lift-wk1 pr A = trans (wk-comp (lift pr) (step id) A)
                      (sym (cong (λ x → wk x A) (lift-step-comp pr)))

wk1-wk≡lift-wk1 : ∀ ρ t → wk1 (wk ρ t) ≡ wk (lift ρ) (wk1 t)
wk1-wk≡lift-wk1 ρ t = trans (wk1-wk ρ t) (sym (lift-wk1 ρ t))

-- Substitution properties.

-- Two substitutions σ and σ′ are equal if they are pointwise equal,
-- i.e., agree on all variables.
--
--   ∀ x →  σ x ≡ σ′ x

-- If  σ = σ′  then  lift σ = lift σ′.

substVar-lift : ∀ {σ σ′}
  → (∀ x → σ x ≡ σ′ x)
  → ∀ x → liftSubst σ x ≡ liftSubst σ′ x

substVar-lift eq 0    = refl
substVar-lift eq (1+ x) = cong wk1 (eq x)

substVar-lifts : ∀ {σ σ′}
  → (∀ x → σ x ≡ σ′ x)
  → ∀ n x → repeat liftSubst σ n x ≡ repeat liftSubst σ′ n x

substVar-lifts eq 0 x = eq x
substVar-lifts eq (1+ n) 0 = refl
substVar-lifts eq (1+ n) (1+ x) = cong wk1 (substVar-lifts eq n x)

-- If  σ = σ′  then  subst σ t = subst σ′ t.

mutual
  substVar-to-subst : ∀ {σ σ′}
    → ((x : Nat) → σ x ≡ σ′ x)
    → (t : Term) → subst σ t ≡ subst σ′ t
  substVar-to-subst eq (var x) = eq x
  substVar-to-subst eq (gen x c) = cong (gen x) (substVar-to-substGen eq c)

  substVar-to-substGen : ∀ {σ σ′}
    → ((x : Nat) → σ x ≡ σ′ x)
    → ∀ g → substGen σ g ≡ substGen σ′ g
  substVar-to-substGen eq [] = refl
  substVar-to-substGen eq (⟦ l , t ⟧ ∷ g) =
    cong₂ _∷_ (cong (⟦_,_⟧ l) (substVar-to-subst (substVar-lifts eq l) t))
              (substVar-to-substGen eq g)

-- lift id = id  (as substitutions)

subst-lift-id : (x : Nat) → (liftSubst idSubst) x ≡ idSubst x
subst-lift-id 0    = refl
subst-lift-id (1+ x) = refl

subst-lifts-id : (n x : Nat) → (repeat liftSubst idSubst n) x ≡ idSubst x
subst-lifts-id 0 x = refl
subst-lifts-id (1+ n) 0 = refl
subst-lifts-id (1+ n) (1+ x) = cong wk1 (subst-lifts-id n x)

-- Identity substitution.

mutual
  subst-id : (t : Term) → subst idSubst t ≡ t
  subst-id (var x) = refl
  subst-id (gen x c) = cong (gen x) (substGen-id c)

  substGen-id : ∀ g → substGen idSubst g ≡ g
  substGen-id [] = refl
  substGen-id (⟦ l , t ⟧ ∷ g) =
    cong₂ _∷_ (cong (⟦_,_⟧ l) (trans (substVar-to-subst (subst-lifts-id l) t)
                                     (subst-id t)))
              (substGen-id g)

-- Correctness of composition of weakening and substitution.

-- Composition of liftings is lifting of the composition.
-- lift ρ •ₛ lift σ = lift (ρ •ₛ σ)

subst-lift-•ₛ : ∀ {ρ σ} t
              → subst (lift ρ •ₛ liftSubst σ) t
              ≡ subst (liftSubst (ρ •ₛ σ)) t
subst-lift-•ₛ =
  substVar-to-subst (λ { 0 → refl ; (1+ x) → sym (wk1-wk≡lift-wk1 _ _)})

helper1 : ∀ {ρ σ} (n x : Nat) →
      (lift (repeat lift ρ n) •ₛ liftSubst (repeat liftSubst σ n)) x ≡
      liftSubst (repeat liftSubst (ρ •ₛ σ) n) x
helper1 0 0 = refl
helper1 0 (1+ x) = sym (wk1-wk≡lift-wk1 _ _)
helper1 (1+ n) 0 = refl
helper1 (1+ n) (1+ x) = trans (sym (wk1-wk≡lift-wk1 _ _)) (cong wk1 (helper1 n x))

subst-lifts-•ₛ : ∀ {ρ σ} n t
              → subst (repeat lift ρ n •ₛ repeat liftSubst σ n) t
              ≡ subst (repeat liftSubst (ρ •ₛ σ) n) t
subst-lifts-•ₛ 0 t = refl
subst-lifts-•ₛ (1+ n) t = substVar-to-subst (helper1 n) t

-- lift σ ₛ• lift ρ = lift (σ ₛ• ρ)

subst-lift-ₛ• : ∀ {ρ σ} t
              → subst (liftSubst σ ₛ• lift ρ) t
              ≡ subst (liftSubst (σ ₛ• ρ)) t
subst-lift-ₛ• = substVar-to-subst (λ { 0 → refl ; (1+ x) → refl})

helper2 : ∀ {ρ σ} (n x : Nat) →
      liftSubst (repeat liftSubst σ n) (wkVar (lift (repeat lift ρ n)) x) ≡
      liftSubst (repeat liftSubst (λ x₁ → σ (wkVar ρ x₁)) n) x
helper2 0 0 = refl
helper2 0 (1+ x) = refl
helper2 (1+ n) 0 = refl
helper2 (1+ n) (1+ x) = cong wk1 (helper2 n x)

subst-lifts-ₛ• : ∀ {ρ σ} n t
              → subst (repeat liftSubst σ n ₛ• repeat lift ρ n) t
              ≡ subst (repeat liftSubst (σ ₛ• ρ) n) t
subst-lifts-ₛ• 0 t = refl
subst-lifts-ₛ• (1+ n) t = substVar-to-subst (helper2 n) t

-- wk ρ ∘ subst σ = subst (ρ •ₛ σ)

mutual
  wk-subst : ∀ {ρ σ} t → wk ρ (subst σ t) ≡ subst (ρ •ₛ σ) t
  wk-subst (var x) = refl
  wk-subst (gen x c) = cong (gen x) (wkGen-substGen c)

  wkGen-substGen : ∀ {ρ σ} t → wkGen ρ (substGen σ t) ≡ substGen (ρ •ₛ σ) t
  wkGen-substGen [] = refl
  wkGen-substGen (⟦ l , t ⟧ ∷ c) =
    cong₂ _∷_ (cong (⟦_,_⟧ l) (trans (wk-subst t) (subst-lifts-•ₛ l t)))
              (wkGen-substGen c)

-- subst σ ∘ wk ρ = subst (σ •ₛ ρ)

mutual
  subst-wk : ∀ {ρ σ} t → subst σ (wk ρ t) ≡ subst (σ ₛ• ρ) t
  subst-wk (var x) = refl
  subst-wk (gen x c) = cong (gen x) (substGen-wkGen c)

  substGen-wkGen : ∀ {ρ σ} t → substGen σ (wkGen ρ t) ≡ substGen (σ ₛ• ρ) t
  substGen-wkGen [] = refl
  substGen-wkGen (⟦ l , t ⟧ ∷ c) =
    cong₂ _∷_ (cong (⟦_,_⟧ l) (trans (subst-wk t) (subst-lifts-ₛ• l t)))
              (substGen-wkGen c)

-- Composition of liftings is lifting of the composition.

wk-subst-lift : ∀ {ρ σ} G
              → wk (lift ρ) (subst (liftSubst σ) G)
              ≡ subst (liftSubst (ρ •ₛ σ)) G
wk-subst-lift G = trans (wk-subst G) (subst-lift-•ₛ G)

-- Renaming with ρ is the same as substituting with ρ turned into a substitution.

wk≡subst : ∀ ρ t → wk ρ t ≡ subst (toSubst ρ) t
wk≡subst ρ t = trans (cong (wk ρ) (sym (subst-id t))) (wk-subst t)

-- Composition of substitutions.

-- Composition of liftings is lifting of the composition.

substCompLift : ∀ {σ σ′} x
              → (liftSubst σ ₛ•ₛ liftSubst σ′) x
              ≡ (liftSubst (σ ₛ•ₛ σ′)) x
substCompLift          0    = refl
substCompLift {σ} {σ′} (1+ x) = trans (subst-wk (σ′ x)) (sym (wk-subst (σ′ x)))

substCompLifts : ∀ {σ σ′} n x
              → (repeat liftSubst σ n ₛ•ₛ repeat liftSubst σ′ n) x
              ≡ (repeat liftSubst (σ ₛ•ₛ σ′) n) x
substCompLifts 0 x = refl
substCompLifts (1+ n) 0 = refl
substCompLifts {σ} {σ′} (1+ n) (1+ x) =
  trans (substCompLift {repeat liftSubst σ n} {repeat liftSubst σ′ n} (1+ x))
        (cong wk1 (substCompLifts n x))

-- Soundness of the composition of substitutions.

mutual
  substCompEq : ∀ {σ σ′} (t : Term)
              → subst σ (subst σ′ t) ≡ subst (σ ₛ•ₛ σ′) t
  substCompEq (var x) = refl
  substCompEq (gen x c) = cong (gen x) (substGenCompEq c)

  substGenCompEq : ∀ {σ σ′} t
              → substGen σ (substGen σ′ t) ≡ substGen (σ ₛ•ₛ σ′) t
  substGenCompEq [] = refl
  substGenCompEq (⟦ l , t ⟧ ∷ c) =
    cong₂ _∷_ (cong (⟦_,_⟧ l) (trans (substCompEq t)
                                     (substVar-to-subst (substCompLifts l) t)))
              (substGenCompEq c)

-- Weakening single substitutions.

-- Pulling apart a weakening composition in specific context _[a].

wk-comp-subst : ∀ {a} ρ ρ′ G
  → wk (lift (ρ • ρ′)) G [ a ] ≡ wk (lift ρ) (wk (lift ρ′) G) [ a ]

wk-comp-subst {a} ρ ρ′ G =
  cong (λ x → x [ a ]) (sym (wk-comp (lift ρ) (lift ρ′) G))

-- Pushing a weakening into a single substitution.
-- ρ (t[a]) = ((lift ρ) t)[ρ a]

wk-β : ∀ {ρ a} t → wk ρ (t [ a ]) ≡ wk (lift ρ) t [ wk ρ a ]
wk-β t = trans (wk-subst t) (sym (trans (subst-wk t)
               (substVar-to-subst (λ { 0 → refl ; (1+ x) → refl}) t)))

-- Pushing a weakening into a single shifting substitution.
-- If  ρ′ = lift ρ  then  ρ′(t[a]↑) = ρ′(t) [ρ′(a)]↑

wk-β↑ : ∀ {ρ a} t → wk (lift ρ) (t [ a ]↑) ≡ wk (lift ρ) t [ wk (lift ρ) a ]↑
wk-β↑ t = trans (wk-subst t) (sym (trans (subst-wk t)
                (substVar-to-subst (λ { 0 → refl ; (1+ x) → refl}) t)))

-- A specific equation on weakenings used for the reduction of natrec.

wk-β-natrec : ∀ ρ G
  → Π ℕ ▹ (Π wk (lift ρ) G ▹ wk (lift (lift ρ)) (wk1 (G [ suc (var 0) ]↑)))
  ≡ Π ℕ ▹ (wk (lift ρ) G ▹▹ wk (lift ρ) G [ suc (var 0) ]↑)
wk-β-natrec ρ G =
  cong₂ Π_▹_ refl (cong₂ Π_▹_ refl
    (trans (wk-comp (lift (lift ρ)) (step id)
                    (subst (consSubst (wk1Subst var) (suc (var 0))) G))
       (trans (wk-subst G) (sym (trans (wk-subst (wk (lift ρ) G))
         (trans (subst-wk G)
                (substVar-to-subst (λ { 0 → refl ; (1+ x) → refl}) G)))))))

-- Composing a singleton substitution and a lifted substitution.
-- sg u ∘ lift σ = cons id u ∘ lift σ = cons σ u

substVarSingletonComp : ∀ {u σ} (x : Nat)
  → (sgSubst u ₛ•ₛ liftSubst σ) x ≡ (consSubst σ u) x
substVarSingletonComp 0 = refl
substVarSingletonComp {σ = σ} (1+ x) = trans (subst-wk (σ x)) (subst-id (σ x))

-- The same again, as action on a term t.

substSingletonComp : ∀ {a σ} t
  → subst (sgSubst a ₛ•ₛ liftSubst σ) t ≡ subst (consSubst σ a) t
substSingletonComp = substVar-to-subst substVarSingletonComp

-- A single substitution after a lifted substitution.
-- ((lift σ) G)[t] = (cons σ t)(G)

singleSubstComp : ∀ t σ G
                 → (subst (liftSubst σ) G) [ t ]
                 ≡ subst (consSubst σ t) G
singleSubstComp t σ G = trans (substCompEq G) (substSingletonComp G)

-- A single substitution after a lifted substitution (with weakening).
-- ((lift (ρ ∘ σ)) G)[t] = (cons (ρ ∘ σ) t)(G)

singleSubstWkComp : ∀ {ρ} t σ G
               → wk (lift ρ) (subst (liftSubst σ) G) [ t ]
               ≡ subst (consSubst (ρ •ₛ σ) t) G
singleSubstWkComp t σ G =
  trans (cong (subst (consSubst var t))
              (trans (wk-subst G) (subst-lift-•ₛ G)))
        (trans (substCompEq G) (substSingletonComp G))

-- Pushing a substitution into a single substitution.

singleSubstLift : ∀ {σ} G t
                → subst σ (G [ t ])
                ≡ subst (liftSubst σ) G [ subst σ t ]
singleSubstLift G t =
  trans (substCompEq G)
        (trans (trans (substVar-to-subst (λ { 0 → refl ; (1+ x) → refl}) G)
                      (sym (substSingletonComp G)))
               (sym (substCompEq G)))

-- More specific laws.

idWkLiftSubstLemma : ∀ σ G
  → wk (lift (step id)) (subst (liftSubst σ) G) [ var 0 ]
  ≡ subst (liftSubst σ) G
idWkLiftSubstLemma σ G =
  trans (singleSubstWkComp (var 0) σ G)
        (substVar-to-subst (λ { 0 → refl ; (1+ x) → refl}) G)

substVarComp↑ : ∀ {t} σ x
  → (consSubst (wk1Subst idSubst) (subst (liftSubst σ) t) ₛ•ₛ liftSubst σ) x
  ≡ (liftSubst σ ₛ•ₛ consSubst (wk1Subst idSubst) t) x
substVarComp↑ σ 0 = refl
substVarComp↑ σ (1+ x) = trans (subst-wk (σ x)) (sym (wk≡subst (step id) (σ x)))

singleSubstLift↑ : ∀ σ G t
                 → subst (liftSubst σ) (G [ t ]↑)
                 ≡ subst (liftSubst σ) G [ subst (liftSubst σ) t ]↑
singleSubstLift↑ σ G t =
  trans (substCompEq G)
        (sym (trans (substCompEq G) (substVar-to-subst (substVarComp↑ σ) G)))

substConsComp : ∀ {σ t G}
       → subst (consSubst (λ x → σ (1+ x)) (subst (tail σ) t)) G
       ≡ subst σ (subst (consSubst (λ x → var (1+ x)) (wk1 t)) G)
substConsComp {t = t} {G = G} =
  trans (substVar-to-subst (λ { 0 → sym (subst-wk t) ; (1+ x) → refl }) G)
        (sym (substCompEq G))

wkSingleSubstId : ∀ F → (wk (lift (step id)) F) [ var 0 ] ≡ F
wkSingleSubstId F =
  trans (subst-wk F)
        (trans (substVar-to-subst (λ { 0 → refl ; (1+ x) → refl}) F)
               (subst-id F))

cons-wk-subst : ∀ ρ σ a t
       → subst (sgSubst a ₛ• lift ρ ₛ•ₛ liftSubst σ) t
       ≡ subst (consSubst (ρ •ₛ σ) a) t
cons-wk-subst ρ σ a = substVar-to-subst
  (λ { 0 → refl
     ; (1+ x) → trans (subst-wk (σ x)) (sym (wk≡subst ρ (σ x))) })

natrecSucCaseLemma : ∀ {σ} (x : Nat)
  → (step id •ₛ consSubst (wk1Subst idSubst) (suc (var 0)) ₛ•ₛ liftSubst σ) x
  ≡ (liftSubst (liftSubst σ) ₛ• step id ₛ•ₛ consSubst (wk1Subst idSubst) (suc (var 0))) x
natrecSucCaseLemma 0 = refl
natrecSucCaseLemma {σ} (1+ x) =
  trans (subst-wk (σ x))
           (sym (trans (wk1-wk (step id) _)
                             (wk≡subst (step (step id)) (σ x))))

natrecSucCase : ∀ σ F
  → Π ℕ ▹ (Π subst (liftSubst σ) F
                ▹ subst (liftSubst (liftSubst σ)) (wk1 (F [ suc (var 0) ]↑)))
  ≡ Π ℕ ▹ (subst (liftSubst σ) F ▹▹ subst (liftSubst σ) F [ suc (var 0) ]↑)
natrecSucCase σ F =
  cong₂ Π_▹_ refl
    (cong₂ Π_▹_ refl
       (trans (trans (subst-wk (F [ suc (var 0) ]↑))
                           (substCompEq F))
                 (sym (trans (wk-subst (subst (liftSubst σ) F))
                                   (trans (substCompEq F)
                                             (substVar-to-subst natrecSucCaseLemma F))))))

natrecIrrelevantSubstLemma : ∀ F z s m σ (x : Nat)
  → (sgSubst (natrec (subst (liftSubst σ) F) (subst σ z) (subst σ s) m)
     ₛ•ₛ liftSubst (sgSubst m)
     ₛ•ₛ liftSubst (liftSubst σ)
     ₛ•  step id
     ₛ•ₛ consSubst (tail idSubst) (suc (var 0))) x
  ≡ (consSubst σ (suc m)) x
natrecIrrelevantSubstLemma F z s m σ 0 =
  cong suc (trans (subst-wk m) (subst-id m))
natrecIrrelevantSubstLemma F z s m σ (1+ x) =
  trans (subst-wk (wk (step id) (σ x)))
           (trans (subst-wk (σ x))
                     (subst-id (σ x)))

natrecIrrelevantSubst : ∀ F z s m σ
  → subst (consSubst σ (suc m)) F
  ≡ subst (liftSubst (sgSubst m))
          (subst (liftSubst (liftSubst σ))
                 (wk1 (F [ suc (var 0) ]↑)))
                   [ natrec (subst (liftSubst σ) F) (subst σ z) (subst σ s) m ]
natrecIrrelevantSubst F z s m σ =
  sym (trans (substCompEq (subst (liftSubst (liftSubst σ))
        (wk (step id)
         (subst (consSubst (tail idSubst) (suc (var 0))) F))))
         (trans (substCompEq (wk (step id)
        (subst (consSubst (tail idSubst) (suc (var 0))) F)))
        (trans
           (subst-wk (subst (consSubst (tail idSubst) (suc (var 0))) F))
           (trans (substCompEq F)
                     (substVar-to-subst (natrecIrrelevantSubstLemma F z s m σ) F)))))

natrecIrrelevantSubstLemma′ : ∀ F z s n (x : Nat)
  → (sgSubst (natrec F z s n)
     ₛ•ₛ liftSubst (sgSubst n)
     ₛ•  step id
     ₛ•ₛ consSubst (tail idSubst) (suc (var 0))) x
  ≡ (consSubst var (suc n)) x
natrecIrrelevantSubstLemma′ F z s n 0 =
  cong suc (trans (subst-wk n) (subst-id n))
natrecIrrelevantSubstLemma′ F z s n (1+ x) = refl

natrecIrrelevantSubst′ : ∀ F z s n
  → subst (liftSubst (sgSubst n))
      (wk1 (F [ suc (var 0) ]↑))
      [ natrec F z s n ]
  ≡ F [ suc n ]
natrecIrrelevantSubst′ F z s n =
  trans (substCompEq (wk (step id)
                         (subst (consSubst (tail idSubst) (suc (var 0))) F)))
        (trans (subst-wk (subst (consSubst (tail idSubst) (suc (var 0))) F))
               (trans (substCompEq F)
                      (substVar-to-subst (natrecIrrelevantSubstLemma′ F z s n) F)))

cons0wkLift1-id : ∀ σ G
    → subst (sgSubst (var 0))
            (wk (lift (step id)) (subst (liftSubst σ) G))
    ≡ subst (liftSubst σ) G
cons0wkLift1-id σ G =
  trans (subst-wk (subst (liftSubst σ) G))
        (trans (substVar-to-subst (λ { 0 → refl ; (1+ x) → refl })
                                  (subst (liftSubst σ) G))
               (subst-id (subst (liftSubst σ) G)))

substConsId : ∀ {σ t} G
        → subst (consSubst σ (subst σ t)) G
        ≡ subst σ (subst (sgSubst t) G)
substConsId G =
  sym (trans (substCompEq G)
             (substVar-to-subst (λ { 0 → refl ; (1+ x) → refl}) G))

substConsTailId : ∀ {G t σ}
                → subst (consSubst (tail σ) (subst σ t)) G
                ≡ subst σ (subst (consSubst (tail idSubst) t) G)
substConsTailId {G} {t} {σ} =
  trans (substVar-to-subst (λ { 0 → refl
                            ; (1+ x) → refl }) G)
        (sym (substCompEq G))

substConcatSingleton′ : ∀ {a σ} t
                      → subst (σ ₛ•ₛ sgSubst a) t
                      ≡ subst (consSubst σ (subst σ a)) t
substConcatSingleton′ t = substVar-to-subst (λ { 0 → refl ; (1+ x) → refl}) t

wk1-tailId : ∀ t → wk1 t ≡ subst (tail idSubst) t
wk1-tailId t = trans (sym (subst-id (wk1 t))) (subst-wk t)