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

open import Definition.Typed.EqualityRelation

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

open import Definition.Untyped as U
open import Definition.Typed
open import Definition.Typed.Weakening
open import Agda.Primitive

open import Tools.Product
open import Tools.Embedding
import Tools.PropositionalEquality as PE

-- The different cases of the logical relation are spread out through out
-- this file. This is due to them having different dependencies.

-- We will refer to expressions that satisfies the logical relation as reducible.

-- Reducibility of Neutrals:

-- Neutral type
record _⊩ne_ (Γ : Con Term) (A : Term) : Set where
  constructor ne
  field
    K   : Term
    D   : Γ ⊢ A :⇒*: K
    neK : Neutral K
    K≡K : Γ ⊢ K ~ K ∷ U

-- Neutral type equality
record _⊩ne_≡_/_ (Γ : Con Term) (A B : Term) ([A] : Γ ⊩ne A) : Set where
  constructor ne₌
  open _⊩ne_ [A]
  field
    M   : Term
    D′  : Γ ⊢ B :⇒*: M
    neM : Neutral M
    K≡M : Γ ⊢ K ~ M ∷ U

-- Neutral term in WHNF
record _⊩neNf_∷_ (Γ : Con Term) (k A : Term) : Set where
  inductive
  constructor neNfₜ
  field
    neK  : Neutral k
    ⊢k   : Γ ⊢ k ∷ A
    k≡k  : Γ ⊢ k ~ k ∷ A

-- Neutral term
record _⊩ne_∷_/_ (Γ : Con Term) (t A : Term) ([A] : Γ ⊩ne A) : Set where
  inductive
  constructor neₜ
  open _⊩ne_ [A]
  field
    k   : Term
    d   : Γ ⊢ t :⇒*: k ∷ K
    nf  : Γ ⊩neNf k ∷ K

-- Neutral term equality in WHNF
record _⊩neNf_≡_∷_ (Γ : Con Term) (k m A : Term) : Set where
  inductive
  constructor neNfₜ₌
  field
    neK  : Neutral k
    neM  : Neutral m
    k≡m  : Γ ⊢ k ~ m ∷ A

-- Neutral term equality
record _⊩ne_≡_∷_/_ (Γ : Con Term) (t u A : Term) ([A] : Γ ⊩ne A) : Set where
  constructor neₜ₌
  open _⊩ne_ [A]
  field
    k m : Term
    d   : Γ ⊢ t :⇒*: k ∷ K
    d′  : Γ ⊢ u :⇒*: m ∷ K
    nf  : Γ ⊩neNf k ≡ m ∷ K

-- Reducibility of natural numbers:

-- Natural number type
_⊩ℕ_ : (Γ : Con Term) (A : Term) → Set
Γ ⊩ℕ A = Γ ⊢ A :⇒*: ℕ

-- Natural number type equality
data _⊩ℕ_≡_ (Γ : Con Term) (A B : Term) : Set where
  ℕ₌ : Γ ⊢ B ⇒* ℕ → Γ ⊩ℕ A ≡ B

mutual
  -- Natural number term
  data _⊩ℕ_∷ℕ (Γ : Con Term) (t : Term) : Set where
    ℕₜ : (n : Term) (d : Γ ⊢ t :⇒*: n ∷ ℕ) (n≡n : Γ ⊢ n ≅ n ∷ ℕ)
         (prop : Natural-prop Γ n)
       → Γ ⊩ℕ t ∷ℕ

  -- WHNF property of natural number terms
  data Natural-prop (Γ : Con Term) : (n : Term) → Set where
    sucᵣ  : ∀ {n} → Γ ⊩ℕ n ∷ℕ → Natural-prop Γ (suc n)
    zeroᵣ : Natural-prop Γ zero
    ne    : ∀ {n} → Γ ⊩neNf n ∷ ℕ → Natural-prop Γ n

mutual
  -- Natural number term equality
  data _⊩ℕ_≡_∷ℕ (Γ : Con Term) (t u : Term) : Set where
    ℕₜ₌ : (k k′ : Term) (d : Γ ⊢ t :⇒*: k  ∷ ℕ) (d′ : Γ ⊢ u :⇒*: k′ ∷ ℕ)
          (k≡k′ : Γ ⊢ k ≅ k′ ∷ ℕ)
          (prop : [Natural]-prop Γ k k′) → Γ ⊩ℕ t ≡ u ∷ℕ

  -- WHNF property of Natural number term equality
  data [Natural]-prop (Γ : Con Term) : (n n′ : Term) → Set where
    sucᵣ  : ∀ {n n′} → Γ ⊩ℕ n ≡ n′ ∷ℕ → [Natural]-prop Γ (suc n) (suc n′)
    zeroᵣ : [Natural]-prop Γ zero zero
    ne    : ∀ {n n′} → Γ ⊩neNf n ≡ n′ ∷ ℕ → [Natural]-prop Γ n n′

-- Natural extraction from term WHNF property
natural : ∀ {Γ n} → Natural-prop Γ n → Natural n
natural (sucᵣ x) = sucₙ
natural zeroᵣ = zeroₙ
natural (ne (neNfₜ neK ⊢k k≡k)) = ne neK

-- Natural extraction from term equality WHNF property
split : ∀ {Γ a b} → [Natural]-prop Γ a b → Natural a × Natural b
split (sucᵣ x) = sucₙ , sucₙ
split zeroᵣ = zeroₙ , zeroₙ
split (ne (neNfₜ₌ neK neM k≡m)) = ne neK , ne neM

-- Type levels

data TypeLevel : Set where
  ⁰ : TypeLevel
  ¹ : TypeLevel

data _<_ : (i j : TypeLevel) → Set where
  0<1 : ⁰ < ¹

toLevel : TypeLevel → Level
toLevel ⁰ = lzero
toLevel ¹ = lsuc lzero

-- Logical relation

record LogRelKit (ℓ : Level) : Set (lsuc (lsuc ℓ)) where
  constructor Kit
  field
    _⊩U : (Γ : Con Term) → Set (lsuc ℓ)
    _⊩Π_ : (Γ : Con Term) → Term → Set (lsuc ℓ)

    _⊩_ : (Γ : Con Term) → Term → Set (lsuc ℓ)
    _⊩_≡_/_ : (Γ : Con Term) (A B : Term) → Γ ⊩ A → Set ℓ
    _⊩_∷_/_ : (Γ : Con Term) (t A : Term) → Γ ⊩ A → Set ℓ
    _⊩_≡_∷_/_ : (Γ : Con Term) (t u A : Term) → Γ ⊩ A → Set ℓ

module LogRel (l : TypeLevel) (rec : ∀ {l′} → l′ < l → LogRelKit (toLevel l)) where

  record _⊩¹U (Γ : Con Term) : Set (lsuc (lsuc (toLevel l))) where
    constructor Uᵣ
    field
      l′ : TypeLevel
      l< : l′ < l
      ⊢Γ : ⊢ Γ

  -- Universe type equality
  record _⊩¹U≡_ (Γ : Con Term) (B : Term) : Set (lsuc (toLevel l)) where
    constructor U₌
    field
      B≡U : B PE.≡ U

  -- Universe term
  record _⊩¹U_∷U/_ {l′} (Γ : Con Term) (t : Term) (l< : l′ < l) : Set (lsuc (toLevel l)) where
    constructor Uₜ
    open LogRelKit (rec l<)
    field
      A     : Term
      d     : Γ ⊢ t :⇒*: A ∷ U
      typeA : Type A
      A≡A   : Γ ⊢ A ≅ A ∷ U
      [t]   : Γ ⊩ t

  -- Universe term equality
  record _⊩¹U_≡_∷U/_ {l′} (Γ : Con Term) (t u : Term) (l< : l′ < l) : Set (lsuc (toLevel l)) where
    constructor Uₜ₌
    open LogRelKit (rec l<)
    field
      A B   : Term
      d     : Γ ⊢ t :⇒*: A ∷ U
      d′    : Γ ⊢ u :⇒*: B ∷ U
      typeA : Type A
      typeB : Type B
      A≡B   : Γ ⊢ A ≅ B ∷ U
      [t]   : Γ ⊩ t
      [u]   : Γ ⊩ u
      [t≡u] : Γ ⊩ t ≡ u / [t]

  RedRel : Set (lsuc (lsuc (lsuc (toLevel l))))
  RedRel = Con Term → Term → (Term → Set (lsuc (toLevel l))) → (Term → Set (lsuc (toLevel l))) → (Term → Term → Set (lsuc (toLevel l))) → Set (lsuc (lsuc (toLevel l)))

  record _⊩⁰_/_ (Γ : Con Term) (A : Term) (_⊩_▸_▸_▸_ : RedRel) : Set (lsuc (lsuc (toLevel l))) where
    inductive
    eta-equality
    constructor LRPack
    field
      ⊩Eq : Term → Set (lsuc (toLevel l))
      ⊩Term : Term → Set (lsuc (toLevel l))
      ⊩EqTerm : Term → Term → Set (lsuc (toLevel l))
      ⊩LR : Γ ⊩ A ▸ ⊩Eq ▸ ⊩Term ▸ ⊩EqTerm

  _⊩⁰_≡_/_ : {R : RedRel} (Γ : Con Term) (A B : Term) → Γ ⊩⁰ A / R → Set (lsuc (toLevel l))
  Γ ⊩⁰ A ≡ B / LRPack ⊩Eq ⊩Term ⊩EqTerm ⊩Red = ⊩Eq B

  _⊩⁰_∷_/_ : {R : RedRel} (Γ : Con Term) (t A : Term) → Γ ⊩⁰ A / R → Set (lsuc (toLevel l))
  Γ ⊩⁰ t ∷ A / LRPack ⊩Eq ⊩Term ⊩EqTerm ⊩Red = ⊩Term t

  _⊩⁰_≡_∷_/_ : {R : RedRel} (Γ : Con Term) (t u A : Term) → Γ ⊩⁰ A / R → Set (lsuc (toLevel l))
  Γ ⊩⁰ t ≡ u ∷ A / LRPack ⊩Eq ⊩Term ⊩EqTerm ⊩Red = ⊩EqTerm t u

  record _⊩¹Π_/_ (Γ : Con Term) (A : Term) (R : RedRel) : Set (lsuc (lsuc (toLevel l))) where
    inductive
    eta-equality
    constructor Πᵣ
    field
      F : Term
      G : Term
      D : Γ ⊢ A :⇒*: Π F ▹ G
      ⊢F : Γ ⊢ F
      ⊢G : Γ ∙ F ⊢ G
      A≡A : Γ ⊢ Π F ▹ G ≅ Π F ▹ G
      [F] : ∀ {ρ Δ} → ρ ∷ Δ ⊆ Γ → (⊢Δ : ⊢ Δ) → Δ ⊩⁰ U.wk ρ F / R
      [G] : ∀ {ρ Δ a}
          → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
          → Δ ⊩⁰ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ
          → Δ ⊩⁰ U.wk (lift ρ) G [ a ] / R
      G-ext : ∀ {ρ Δ a b}
            → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
            → ([a] : Δ ⊩⁰ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
            → ([b] : Δ ⊩⁰ b ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
            → Δ ⊩⁰ a ≡ b ∷ U.wk ρ F / [F] [ρ] ⊢Δ
            → Δ ⊩⁰ U.wk (lift ρ) G [ a ] ≡ U.wk (lift ρ) G [ b ] / [G] [ρ] ⊢Δ [a]

  record _⊩¹Π_≡_/_ {R : RedRel} (Γ : Con Term) (A B : Term) ([A] : Γ ⊩¹Π A / R) : Set (lsuc (toLevel l)) where
    inductive
    eta-equality
    constructor Π₌
    open _⊩¹Π_/_ [A]
    field
      F′     : Term
      G′     : Term
      D′     : Γ ⊢ B ⇒* Π F′ ▹ G′
      A≡B    : Γ ⊢ Π F ▹ G ≅ Π F′ ▹ G′
      [F≡F′] : ∀ {ρ Δ}
             → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
             → Δ ⊩⁰ U.wk ρ F ≡ U.wk ρ F′ / [F] [ρ] ⊢Δ
      [G≡G′] : ∀ {ρ Δ a}
             → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
             → ([a] : Δ ⊩⁰ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
             → Δ ⊩⁰ U.wk (lift ρ) G [ a ] ≡ U.wk (lift ρ) G′ [ a ] / [G] [ρ] ⊢Δ [a]

  -- Term of Π-type
  _⊩¹Π_∷_/_ : {R : RedRel} (Γ : Con Term) (t A : Term) ([A] : Γ ⊩¹Π A / R) → Set (lsuc (toLevel l))
  Γ ⊩¹Π t ∷ A / Πᵣ F G D ⊢F ⊢G A≡A [F] [G] G-ext =
    ∃ λ f → Γ ⊢ t :⇒*: f ∷ Π F ▹ G
          × Function f
          × Γ ⊢ f ≅ f ∷ Π F ▹ G
          × (∀ {ρ Δ a b}
            → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
              ([a] : Δ ⊩⁰ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
              ([b] : Δ ⊩⁰ b ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
              ([a≡b] : Δ ⊩⁰ a ≡ b ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
            → Δ ⊩⁰ U.wk ρ f ∘ a ≡ U.wk ρ f ∘ b ∷ U.wk (lift ρ) G [ a ] / [G] [ρ] ⊢Δ [a])
          × (∀ {ρ Δ a} → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
            → ([a] : Δ ⊩⁰ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
            → Δ ⊩⁰ U.wk ρ f ∘ a ∷ U.wk (lift ρ) G [ a ] / [G] [ρ] ⊢Δ [a])
  -- Issue: Agda complains about record use not being strictly positive.
  --        Therefore we have to use ×

  -- Term equality of Π-type
  _⊩¹Π_≡_∷_/_ : {R : RedRel} (Γ : Con Term) (t u A : Term) ([A] : Γ ⊩¹Π A / R) → Set (lsuc (toLevel l))
  Γ ⊩¹Π t ≡ u ∷ A / Πᵣ F G D ⊢F ⊢G A≡A [F] [G] G-ext =
    let [A] = Πᵣ F G D ⊢F ⊢G A≡A [F] [G] G-ext
    in  ∃₂ λ f g →
        Γ ⊢ t :⇒*: f ∷ Π F ▹ G
    ×   Γ ⊢ u :⇒*: g ∷ Π F ▹ G
    ×   Function f
    ×   Function g
    ×   Γ ⊢ f ≅ g ∷ Π F ▹ G
    ×   Γ ⊩¹Π t ∷ A / [A]
    ×   Γ ⊩¹Π u ∷ A / [A]
    ×   (∀ {ρ Δ a} → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
        → ([a] : Δ ⊩⁰ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
        → Δ ⊩⁰ U.wk ρ f ∘ a ≡ U.wk ρ g ∘ a ∷ U.wk (lift ρ) G [ a ] / [G] [ρ] ⊢Δ [a])
  -- Issue: Same as above.

  -- Logical relation definition
  data _⊩LR_▸_▸_▸_ : RedRel where
    LRU : ∀ {Γ} (⊢Γ : ⊢ Γ) → (l' : TypeLevel) → (l< : l' < l) → Γ ⊩LR U
      ▸ (λ B → Γ ⊩¹U≡ B)
      ▸ (λ t → Γ ⊩¹U t ∷U/ l<)
      ▸ (λ t u → Γ ⊩¹U t ≡ u ∷U/ l<)
    LRℕ : ∀ {Γ A} → Γ ⊩ℕ A → Γ ⊩LR A
      ▸ (λ B → ι′ (Γ ⊩ℕ A ≡ B))
      ▸ (λ t → ι′ (Γ ⊩ℕ t ∷ℕ))
      ▸ (λ t u → ι′ (Γ ⊩ℕ t ≡ u ∷ℕ))
    LRne : ∀ {Γ A} → (neA : Γ ⊩ne A) → Γ ⊩LR A
      ▸ (λ B → ι′ (Γ ⊩ne A ≡ B / neA))
      ▸ (λ t → ι′ (Γ ⊩ne t ∷ A / neA))
      ▸ (λ t u → ι′ (Γ ⊩ne t ≡ u ∷ A / neA))
    LRΠ : ∀ {Γ A} → (ΠA : Γ ⊩¹Π A / _⊩LR_▸_▸_▸_) → Γ ⊩LR A
      ▸ (λ B → Γ ⊩¹Π A ≡ B / ΠA)
      ▸ (λ t → Γ ⊩¹Π t ∷ A / ΠA)
      ▸ (λ t u → Γ ⊩¹Π t ≡ u ∷ A / ΠA)
    LRemb : ∀ {Γ A l′} (l< : l′ < l) (let open LogRelKit (rec l<)) ([A] : Γ ⊩ A) → Γ ⊩LR A
      ▸ (λ B → ι (Γ ⊩ A ≡ B / [A]))
      ▸ (λ t → ι (Γ ⊩ t ∷ A / [A]))
      ▸ (λ t u → ι (Γ ⊩ t ≡ u ∷ A / [A]))

  _⊩¹_ : (Γ : Con Term) → (A : Term) → Set (lsuc (lsuc (toLevel l)))
  Γ ⊩¹ A = Γ ⊩⁰ A / _⊩LR_▸_▸_▸_

  _⊩¹_≡_/_ : (Γ : Con Term) (A B : Term) → Γ ⊩¹ A → Set (lsuc (toLevel l))
  Γ ⊩¹ A ≡ B / [A] = Γ ⊩⁰ A ≡ B / [A]

  _⊩¹_∷_/_ : (Γ : Con Term) (t A : Term) → Γ ⊩¹ A → Set (lsuc (toLevel l))
  Γ ⊩¹ t ∷ A / [A] = Γ ⊩⁰ t ∷ A / [A]

  _⊩¹_≡_∷_/_ : (Γ : Con Term) (t u A : Term) → Γ ⊩¹ A → Set (lsuc (toLevel l))
  Γ ⊩¹ t ≡ u ∷ A / [A] = Γ ⊩⁰ t ≡ u ∷ A / [A]

open LogRel public using (Uᵣ; Πᵣ; Π₌; U₌ ; Uₜ; Uₜ₌ ; LRU ; LRℕ ; LRne ; LRΠ ; LRemb ; LRPack)

pattern Πₜ f d funcF f≡f [f] [f]₁ = f , d , funcF , f≡f , [f] , [f]₁
pattern Πₜ₌ f g d d′ funcF funcG f≡g [f] [g] [f≡g] = f , g , d , d′ , funcF , funcG , f≡g , [f] , [g] , [f≡g]
pattern ℕᵣ a = LRPack _ _ _ (LRℕ a)
pattern emb′ a b = LRPack _ _ _ (LRemb a b)
pattern Uᵣ′ a b c = LRPack _ _ _ (LRU c a b)
pattern ne′ a b c d = LRPack _ _ _ (LRne (ne a b c d))
pattern Πᵣ′ a b c d e f g h i = LRPack _ _ _ (LRΠ (Πᵣ a b c d e f g h i))

kit₀ : LogRelKit (lsuc (lzero))
kit₀ = Kit _⊩¹U (λ Γ A → Γ ⊩¹Π A / _⊩LR_▸_▸_▸_) _⊩¹_ _⊩¹_≡_/_ _⊩¹_∷_/_ _⊩¹_≡_∷_/_ where open LogRel ⁰ (λ ())

logRelRec : ∀ l {l′} → l′ < l → LogRelKit (toLevel l)
logRelRec ⁰ = λ ()
logRelRec ¹ 0<1 = kit₀

kit : ∀ (l : TypeLevel) → LogRelKit (lsuc (toLevel l))
kit l = Kit _⊩¹U (λ Γ A → Γ ⊩¹Π A / _⊩LR_▸_▸_▸_) _⊩¹_ _⊩¹_≡_/_ _⊩¹_∷_/_ _⊩¹_≡_∷_/_ where open LogRel l (logRelRec l)

-- a bit of repetition in "kit ¹" definition, would work better with Fin 2 for
-- TypeLevel because you could recurse.

record _⊩′⟨_⟩U (Γ : Con Term) (l : TypeLevel) : Set where
  constructor Uᵣ
  field
    l′ : TypeLevel
    l< : l′ < l
    ⊢Γ : ⊢ Γ

_⊩′⟨_⟩Π_ : (Γ : Con Term) (l : TypeLevel) → Term → Set (lsuc (lsuc (toLevel l)))
Γ ⊩′⟨ l ⟩Π A = Γ ⊩Π A where open LogRelKit (kit l)

_⊩⟨_⟩_ : (Γ : Con Term) (l : TypeLevel) → Term → Set (lsuc (lsuc (toLevel l)))
Γ ⊩⟨ l ⟩ A = Γ ⊩ A where open LogRelKit (kit l)

_⊩⟨_⟩_≡_/_ : (Γ : Con Term) (l : TypeLevel) (A B : Term) → Γ ⊩⟨ l ⟩ A → Set (lsuc (toLevel l))
Γ ⊩⟨ l ⟩ A ≡ B / [A] = Γ ⊩ A ≡ B / [A] where open LogRelKit (kit l)

_⊩⟨_⟩_∷_/_ : (Γ : Con Term) (l : TypeLevel) (t A : Term) → Γ ⊩⟨ l ⟩ A → Set (lsuc (toLevel l))
Γ ⊩⟨ l ⟩ t ∷ A / [A] = Γ ⊩ t ∷ A / [A] where open LogRelKit (kit l)

_⊩⟨_⟩_≡_∷_/_ : (Γ : Con Term) (l : TypeLevel) (t u A : Term) → Γ ⊩⟨ l ⟩ A → Set (lsuc (toLevel l))
Γ ⊩⟨ l ⟩ t ≡ u ∷ A / [A] = Γ ⊩ t ≡ u ∷ A / [A] where open LogRelKit (kit l)


------------------------------------------------------------------------------
-- Miscellaneous properties
------------------------------------------------------------------------------

{-# OPTIONS --exact-split              #-}
{-# OPTIONS --no-sized-types           #-}
{-# OPTIONS --no-universe-polymorphism #-}
{-# OPTIONS --without-K                #-}

module FOTC.Program.SortList.Properties.MiscellaneousATP where

open import FOTC.Base
open import FOTC.Base.List
open import FOTC.Data.Bool
open import FOTC.Data.Bool.PropertiesATP
open import FOTC.Data.Nat.Inequalities
open import FOTC.Data.Nat.List.PropertiesATP
open import FOTC.Data.Nat.List.Type
open import FOTC.Data.Nat.Type
open import FOTC.Data.List
open import FOTC.Data.List.PropertiesATP
open import FOTC.Program.SortList.Properties.Totality.BoolATP
open import FOTC.Program.SortList.SortList

------------------------------------------------------------------------------
-- This is a weird result but recall that "the relation ≤ between
-- lists is only an ordering if nil is excluded" (Burstall 1969,
-- p. 46).
-- xs≤[] : ∀ {is} → ListN is → OrdList is → ≤-Lists is []
-- xs≤[] nilLN                     _       = ≤-Lists-[] []
-- xs≤[] (lncons {i} {is} Ni LNis) LOconsL =
--   prf $ xs≤[] LNis (subList-OrdList Ni LNis LOconsL)
--   where
--     postulate prf : ≤-Lists is [] →  --IH.
--                     ≤-Lists (i ∷ is) []
--     {-# ATP prove prf ≤-ItemList-Bool ordList-Bool x&&y≡true→x≡true #-}

x≤ys→x≤zs→x≤ys++zs : ∀ {i js ks} → N i → ListN js → ListN ks →
                     ≤-ItemList i js →
                     ≤-ItemList i ks →
                     ≤-ItemList i (js ++ ks)
x≤ys→x≤zs→x≤ys++zs {i} {ks = ks} Ni lnnil LNks _ i≤k =
  subst (≤-ItemList i) (sym (++-leftIdentity ks)) i≤k
x≤ys→x≤zs→x≤ys++zs {i} {ks = ks} Ni (lncons {j} {js} Nj LNjs) LNks i≤j∷js i≤k =
  prf (x≤ys→x≤zs→x≤ys++zs Ni LNjs LNks (&&-list₂-t₂ helper₁ helper₂ helper₃) i≤k)
  where
  helper₁ : Bool (le i j)
  helper₁ = le-Bool Ni Nj

  helper₂ : Bool (le-ItemList i js)
  helper₂ = le-ItemList-Bool Ni LNjs

  helper₃ : le i j && le-ItemList i js ≡ true
  helper₃ = trans (sym (le-ItemList-∷ i j js)) i≤j∷js

  postulate prf : ≤-ItemList i (js ++ ks) → ≤-ItemList i ((j ∷ js) ++ ks)
  {-# ATP prove prf &&-list₂-t helper₁ helper₂ helper₃ #-}

xs≤ys→xs≤zs→xs≤ys++zs : ∀ {is js ks} → ListN is → ListN js → ListN ks →
                        ≤-Lists is js →
                        ≤-Lists is ks →
                        ≤-Lists is (js ++ ks)
xs≤ys→xs≤zs→xs≤ys++zs lnnil LNjs LNks _ _ = le-Lists-[] _
xs≤ys→xs≤zs→xs≤ys++zs {js = js} {ks} (lncons {i} {is} Ni LNis)
                      LNjs LNks i∷is≤js i∷is≤ks =
  prf ((xs≤ys→xs≤zs→xs≤ys++zs LNis LNjs LNks
                              (&&-list₂-t₂ helper₁ helper₂ helper₃)
                              (&&-list₂-t₂ helper₄ helper₅ helper₆)))
  where
  helper₁ = le-ItemList-Bool Ni LNjs
  helper₂ = le-Lists-Bool LNis LNjs
  helper₃ = trans (sym (le-Lists-∷ i is js)) i∷is≤js

  helper₄ = le-ItemList-Bool Ni LNks
  helper₅ = le-Lists-Bool LNis LNks
  helper₆ = trans (sym (le-Lists-∷ i is ks)) i∷is≤ks

  postulate prf : ≤-Lists is (js ++ ks) → ≤-Lists (i ∷ is) (js ++ ks)
  {-# ATP prove prf x≤ys→x≤zs→x≤ys++zs &&-list₂-t helper₁ helper₂ helper₃ helper₄ helper₅ helper₆ #-}

xs≤zs→ys≤zs→xs++ys≤zs : ∀ {is js ks} → ListN is → ListN js → ListN ks →
                        ≤-Lists is ks →
                        ≤-Lists js ks →
                        ≤-Lists (is ++ js) ks
xs≤zs→ys≤zs→xs++ys≤zs {js = js} {ks} lnnil LNjs LNks is≤ks js≤ks =
  subst (λ t → ≤-Lists t ks)
        (sym (++-leftIdentity js))
        js≤ks
xs≤zs→ys≤zs→xs++ys≤zs {js = js} {ks}
                      (lncons {i} {is} Ni LNis) LNjs LNks i∷is≤ks js≤ks =
  prf (xs≤zs→ys≤zs→xs++ys≤zs LNis LNjs LNks
                             (&&-list₂-t₂ helper₁ helper₂ helper₃)
                             js≤ks)
  where
  helper₁ = le-ItemList-Bool Ni LNks
  helper₂ = le-Lists-Bool LNis LNks
  helper₃ = trans (sym (le-Lists-∷ i is ks)) i∷is≤ks

  postulate prf : ≤-Lists (is ++ js) ks → ≤-Lists ((i ∷ is) ++ js) ks
  {-# ATP prove prf &&-list₂-t helper₁ helper₂ helper₃ #-}

------------------------------------------------------------------------------
-- References
--
-- Burstall, R. M. (1969). Proving properties of programs by
-- structural induction. The Computer Journal 12.1, pp. 41–48.


{-# OPTIONS --cubical --safe #-}

module DTLC.Base where

open import Cubical.Foundations.Prelude hiding (Σ; _,_)
open import Cubical.Foundations.Function

--------------------------------------------------------------------------
-- Adapted from: Type Theory in Type Theory using Quotient Inductive Types
--               by Thorsten Altenkirch and Ambrus Kaposi
-- See: http://www.cs.nott.ac.uk/~psztxa/publ/tt-in-tt.pdf
--
-- Note: We have changed a number of names relative to this paper, in the
--  interest of clarity. In particular, Tms is now _~>_, with the
--  arguments reversed. Thus, _∘_ (which we call _∘*_) also is reversed
--  with respect to [AK16]. Furthermore, we renamed π₁ to tail*, π₂
--  to head*, and app to unlam to make their functions more clear.
--------------------------------------------------------------------------

infixl 6 _,_
infixl 6 _,*_
infixr 5 _∘*_

PathPSyntax = PathP
syntax PathPSyntax A x y = x ≡[ A ]≡ y
infix 2 PathPSyntax

-----------------
-- Declarations
-----------------

-- We will mutually inductively define:

-- The type of contexts
data Ctx : Type₀

-- The type of types
data Typ : Ctx -> Type₀

-- The type of judgements x : τ ⊣ Γ ("x has type τ in context Γ")
Term : (Γ : Ctx) -> Typ Γ -> Type₀
syntax Term Γ τ = τ ⊣ Γ

-- The type of transformations / generalized subsitutions between contexts
_~>_ : Ctx -> Ctx -> Type₀

-- ...in fact, we define terms and transformations together:

data TmQV : Type₀ where
  TmV : TmQV
  TrV : TmQV

TmQArg : TmQV -> Ctx -> Type₀
TmQArg TmV Γ = Typ Γ
TmQArg TrV _ = Ctx

data TmQ : (a : TmQV) -> (Γ : Ctx) -> TmQArg a Γ -> Type₀

Term = TmQ TmV
_~>_ = TmQ TrV

-- Some forward declarations:
id* : ∀ {Γ} -> Γ ~> Γ
_∘*_ : ∀ {Γ Δ Σ} -> (Δ ~> Σ) -> (Γ ~> Δ) -> Γ ~> Σ
_[_]T : ∀ {Γ Δ} (τ : Typ Γ) (f : Γ ~> Δ) -> Typ Δ
_[_] : ∀ {Γ Δ τ} (x : τ ⊣ Γ) (f : Γ ~> Δ) -> (τ [ f ]T) ⊣ Δ

----------------
-- Definitions
----------------

data Ctx where
  ∙ : Ctx
  _,_ : (Γ : Ctx) -> Typ Γ -> Ctx

-- Unlike Altenkirch-Kaposi, we have a universe of types U:
data Typ where
  U : ∀ {Γ} -> Typ Γ
  type : ∀ {Γ} (τ : U ⊣ Γ) -> Typ Γ

-- Subsitution on types is defined in terms of substition on terms!
U [ f ]T = U
(type τ) [ f ]T = type (τ [ f ])

-- Substitution on types is a functor -- definitions are below
[id]T : ∀ {Γ} (τ : Typ Γ) -> τ [ id* ]T ≡ τ
[][]T : ∀ {Γ Δ Σ} (g : Δ ~> Σ) (f : Γ ~> Δ) (τ : Typ Γ)
        -> τ [ f ]T [ g ]T ≡ τ [ g ∘* f ]T

-- Having these is quite useful for readibility:

coeTm : ∀ {Γ} {x y : Typ Γ} -> x ≡ y -> Term Γ x -> Term Γ y
coeTm p = transport (cong (Term _) p)

coeTmTy : ∀ {Γ} {τ σ : Term Γ U} -> τ ≡ σ -> Term Γ (type τ) -> Term Γ (type σ)
coeTmTy p = transport (cong (Term _ ∘ type) p)

-- We define terms and transformations together
data TmQ where

  -- Ctx and ~> form a category
  -- Note: these are the definitions of id* and ∘* -- see below
  id*C : ∀ {Γ} -> Γ ~> Γ
  _∘*C_ : ∀ {Γ Δ Σ} -> (Δ ~> Σ) -> (Γ ~> Δ) -> Γ ~> Σ

  id*-l : ∀ {Γ Δ} (f : Γ ~> Δ) -> id* ∘* f ≡ f
  id*-r : ∀ {Γ Δ} (f : Γ ~> Δ) -> f ∘* id* ≡ f
  assoc : ∀ {Γ Δ Σ P} (h : Σ ~> P) (g : Δ ~> Σ) (f : Γ ~> Δ)
          -> (h ∘* g) ∘* f ≡ h ∘* (g ∘* f)

  -- ~> has a list structure on its first argument
  ∙* : ∀ {Δ} -> ∙ ~> Δ
  _,*_ : ∀ {Γ Δ τ} (f : Γ ~> Δ) (x : Term Δ (τ [ f ]T)) -> (Γ , τ) ~> Δ

  tail* : ∀ {Γ Δ τ} (f : (Γ , τ) ~> Δ) -> Γ ~> Δ
  head* : ∀ {Γ Δ τ} (f : (Γ , τ) ~> Δ) -> (τ [ tail* f ]T) ⊣ Δ

  ,*-∘* : ∀ {Γ Δ Σ τ} (g : Δ ~> Σ) (f : Γ ~> Δ) (x : Term Δ (τ [ f ]T))
          -> g ∘* (f ,* x) ≡ (g ∘* f) ,* coeTm ([][]T g f τ) (x [ g ])

  -- Computation rules for tail* and head*
  tail*-β : ∀ {Γ Δ τ} (f : Γ ~> Δ) (x : Term Δ (τ [ f ]T)) -> tail* (f ,* x) ≡ f
  head*-β : ∀ {Γ Δ τ} (f : Γ ~> Δ) (x : Term Δ (τ [ f ]T))
            -> head* (f ,* x) ≡[ (λ i → (τ [ tail*-β f x i ]T) ⊣ Δ) ]≡ x

  -- Uniqueness rules for ~>
  ∙*-η : ∀ {Δ} (f : ∙ ~> Δ) -> f ≡ ∙*
  ,*-η : ∀ {Γ Δ τ} (f : (Γ , τ) ~> Δ) -> (tail* f ,* head* f) ≡ f

  -- A useful combinator, defined in terms of the above
  _↑_ : ∀ {Γ Δ} (f : Γ ~> Δ) (τ : Typ Γ) -> (Γ , τ) ~> (Δ , τ [ f ]T)
  ↑-β : ∀ {Γ Δ} (f : Γ ~> Δ) (τ : Typ Γ) -> (Γ , τ) ~> (Δ , τ [ f ]T)
        -> f ↑ τ ≡ (tail* id* ∘* f) ,* coeTm ([][]T (tail* id*) f τ) (head* id*)

  -- The definition of substitution on terms _[_]
  _[_]C : ∀ {Γ Δ τ} (x : τ ⊣ Γ) (f : Γ ~> Δ) -> (τ [ f ]T) ⊣ Δ

  -- Substitution on terms is a functor
  [id] : ∀ {Γ τ} (x : τ ⊣ Γ) -> x [ id* ] ≡[ (λ i → ([id]T τ i) ⊣ Γ) ]≡ x
  [][] : ∀ {Γ Δ Σ τ} (g : Δ ~> Σ) (f : Γ ~> Δ) (x : τ ⊣ Γ)
         -> x [ f ] [ g ] ≡[ (λ i → ([][]T g f τ i) ⊣ Σ) ]≡ x [ g ∘* f ]

  -- Pi types and their computation rule for substition
  Π : ∀ {Γ} (τ : Typ Γ) (σ : Typ (Γ , τ)) -> U ⊣ Γ
  Π[] : ∀ {Γ Δ} (τ : Typ Γ) (σ : Typ (Γ , τ)) (f : Γ ~> Δ)
        -> (Π τ σ) [ f ] ≡ Π (τ [ f ]T) (σ [ f ↑ τ ]T)

  -- lambdas and their computation rule for substitition
  lam : ∀ {Γ τ σ} (y : σ ⊣ (Γ , τ)) -> type (Π τ σ) ⊣ Γ
  lam[] : ∀ {Γ Δ τ σ} (y : σ ⊣ (Γ , τ)) (f : Γ ~> Δ)
          -> lam y [ f ] ≡[ (λ i → type (Π[] τ σ f i) ⊣ Δ) ]≡ lam (y [ f ↑ τ ])

  -- Pi type elim, whose computation rule for substition is derivable using Π-η
  -- (Note: application is defined in terms of this below)
  unlam : ∀ {Γ τ σ} (ℓ : type (Π τ σ) ⊣ Γ) -> σ ⊣ (Γ , τ)

  -- Computation and uniqueness for Pi types
  Π-β : ∀ {Γ τ σ} (y : σ ⊣ (Γ , τ))      -> unlam (lam y) ≡ y
  Π-η : ∀ {Γ τ σ} (ℓ : type (Π τ σ) ⊣ Γ) -> lam (unlam ℓ) ≡ ℓ

-- The missing substitution computation rule for unlam, derivable because we have definitional η!
unlam[] : ∀ {Γ Δ τ σ} (ℓ : type (Π τ σ) ⊣ Γ) (f : Γ ~> Δ)
          -> unlam ℓ [ f ↑ τ ] ≡ unlam (coeTmTy (Π[] τ σ f) (ℓ [ f ]))
unlam[] {Γ} {Δ} {τ} {σ} ℓ f =
              (unlam ℓ) [ f ↑ τ ]                     ≡[ i ]⟨ Π-β ((unlam ℓ) [ f ↑ τ ]) (~ i) ⟩
  unlam (lam ((unlam ℓ) [ f ↑ τ ]))                   ≡[ i ]⟨ unlam (fromPathP (lam[] (unlam ℓ) f) (~ i)) ⟩
  unlam (coeTmTy (Π[] τ σ f) ((lam (unlam ℓ)) [ f ])) ≡[ i ]⟨ unlam (coeTmTy (Π[] τ σ f) ((Π-η ℓ i) [ f ])) ⟩
  unlam (coeTmTy (Π[] τ σ f) (ℓ [ f ]))               ∎

-- Missing definitions from above
[id]T U = refl
[id]T (type τ) = cong type ([id] τ)
[][]T g f U = refl
[][]T g f (type τ) = cong type ([][] g f τ)
id* = id*C
_∘*_ = _∘*C_
_[_] = _[_]C

--------------------
-- Derived Notions
--------------------

-- Weakening on contexts
wkn : ∀ {Γ τ} -> Γ ~> (Γ , τ)
wkn = tail* id*

-- A single substition at the top of a context
<_> : ∀ {Γ τ} (x : τ ⊣ Γ) -> (Γ , τ) ~> Γ
< x > = id* ,* coeTm (sym ([id]T _)) x

-- Using substition, we can now write the usual definition of application
_$_ : ∀ {Γ τ σ} (ℓ : (type (Π τ σ)) ⊣ Γ) (x : τ ⊣ Γ) -> (σ [ < x > ]T) ⊣ Γ
ℓ $ x = unlam ℓ [ < x > ]
infixl 4 _$_

-- Variables (as de Bruijn indices) are also derived!

var₀ : ∀ {Γ τ} -> (τ [ wkn ]T) ⊣ (Γ , τ)
var₀ = head* id*

var₊ : ∀ {Γ τ σ} -> τ ⊣ Γ -> (τ [ wkn ]T) ⊣ (Γ , σ)
var₊ x = x [ wkn ]

var₁ : ∀ {Γ τ σ} -> (τ [ wkn ]T [ wkn ]T) ⊣ ((Γ , τ) , σ)
var₁ = var₊ var₀

var₂ : ∀ {Γ τ σ ρ} -> (τ [ wkn ]T [ wkn ]T [ wkn ]T) ⊣ (((Γ , τ) , σ) , ρ)
var₂ = var₊ var₁

-- The other classical operations on contexts:

ctr : ∀ {Γ τ} -> (Γ , τ , τ [ wkn ]T) ~> (Γ , τ)
ctr {Γ} {τ} = id* ,* coeTm (sym p) var₀
  where p : τ [ wkn ]T [ id* ]T ≡ τ [ tail* id* ]T
        p =
          τ [ wkn ]T [ id* ]T ≡⟨ [][]T id* wkn _ ⟩
          τ [ id* ∘* wkn ]T   ≡⟨ cong (τ [_]T) (id*-l wkn) ⟩
          τ [ tail* id* ]T ∎

-- exg : ∀ {Γ τ σ} -> (Γ , τ , σ [ wkn ]T) ~> (Γ , σ , τ [ wkn ]T)
-- exg {Γ} {τ} {σ} = wkn ∘* wkn ,*
--                   coeTm ([][]T wkn wkn τ) var₀ ,*
--                   coeTm (sym p) (coeTm ([][]T wkn wkn σ) var₁)
--   where p : σ [ wkn ]T [ (wkn ∘* wkn) ,* coeTm ([][]T wkn wkn τ) var₀ ]T ≡ σ [ wkn ∘* wkn ]T
--         p = begin
--           σ [ wkn ]T [ (wkn ∘* wkn) ,* coeTm ([][]T wkn wkn τ) var₀ ]T ≡⟨ [][]T _ _ σ ⟩
--           σ [ ((wkn ∘* wkn) ,* coeTm ([][]T wkn wkn τ) var₀) ∘* wkn ]T ≡⟨ cong (σ [_]T) {!!} ⟩
--           σ [ wkn ∘* wkn ]T ∎
--         -- p' : (wkn ∘* wkn) ,* coeTm ([][]T wkn wkn τ) var₁ ≡ wkn
--         -- p' = begin
--         --   (wkn ∘* wkn) ,* coeTm ([][]T wkn wkn τ) var₁ ≡⟨ sym (,*-∘* wkn wkn var₀) ⟩
--         --   wkn ∘* (wkn ,* var₀) ≡⟨ cong (wkn ∘*_) (,*-η id*) ⟩
--         --   wkn ∘* id*           ≡⟨ id*-r wkn ⟩
--         --   wkn ∎

--------------------------------------
-- Experimental: Encoding more types
--------------------------------------

_⇒_ : ∀ {Γ} (τ : U ⊣ Γ) (σ : U ⊣ (Γ , type τ)) -> U ⊣ Γ
x ⇒ y = Π (type x) (type y)
infixr 4 _⇒_

-- The natural number type

N : ∀ {Γ} -> U ⊣ Γ
N = Π U (type (var₀ ⇒ (var₁ ⇒ var₂) ⇒ var₂))

-- N[] : ∀ {Γ Δ} (f : Γ ~> Δ) -> type N [ f ]T ≡ type N
-- N[] f = cong type p
--   where p : Π U (type (var₀ ⇒ (var₁ ⇒ var₂) ⇒ var₂)) [ f ] ≡  Π U (type (var₀ ⇒ (var₁ ⇒ var₂) ⇒ var₂))
--         p = begin
--           Π U (type (var₀ ⇒ (var₁ ⇒ var₂) ⇒ var₂)) [ f ] ≡⟨ Π[] _ _ f ⟩
--           Π U (type ((var₀ ⇒ (var₁ ⇒ var₂) ⇒ var₂) [ f ↑ U ])) ≡⟨ {!!} ⟩
--           Π U (type (var₀ ⇒ (var₁ ⇒ var₂) ⇒ var₂)) ∎

z : ∀ {Γ} -> type N ⊣ Γ
z = lam (lam (lam var₁))

-- s : ∀ {Γ} (n : type N ⊣ Γ) -> type N ⊣ Γ
-- s {Γ} n = lam (lam (lam {!!})) -- var₀ $ (n' $ var₀ $ var₁ $ var₂)
--   where n' : type N ⊣ (((Γ , U) , type var₀) , type (var₁ ⇒ var₂))
--         n' = coe (cong (Term _) {!!}) (var₊ (var₊ (var₊ n)))

-- The dependent pair type

-- Σ : ∀ {Γ} (τ : Typ Γ) (σ : Typ (Γ , τ)) -> U ⊣ Γ
-- Σ τ σ = Π U (type ((Π (τ [ tail* id* ]T) (type (Π (σ [ tail* id* ↑ τ ]T) (type var₂)))) ⇒ var₀))

-- pair : ∀ {Γ τ σ} (x : τ ⊣ Γ) (y : σ ⊣ (Γ , τ)) -> (type (Σ τ σ)) ⊣ Γ
-- pair x y = lam (lam {!!})

  -- Σ : ∀ {Γ} (τ : Typ Γ) (σ : Typ (Γ , τ)) -> U ⊣ Γ
  -- Σ[] : ∀ {Γ Δ} (τ : Typ Γ) (σ : Typ (Γ , τ)) (f : Γ ~> Δ)
  --       -> (Σ τ σ) [ f ] ≡ Σ (τ [ f ]T) (σ [ f ↑ τ ]T)

  -- pair : ∀ {Γ τ σ} (x : τ ⊣ Γ) (y : σ ⊣ (Γ , τ)) -> (type (Σ τ σ)) ⊣ Γ
  -- pair[] : ∀ {Γ Δ τ σ} (x : τ ⊣ Γ) (y : σ ⊣ (Γ , τ)) (f : Γ ~> Δ)
  --          -> (pair x y) [ f ] ≡[ (λ i → type (Σ[] τ σ f i) ⊣ Δ) ]≡ pair (x [ f ]) (y [ f ↑ τ ])

  -- Σ-ind : ∀ {Γ τ σ} (ρ : Typ (Γ , type (Σ τ σ)))
  --                   (xy : (ρ [ {!!} ∘* ({!!} ↑ type (Σ τ σ)) ]T) ⊣ ((Γ , τ) , σ))
  --                   -> ρ ⊣ (Γ , type (Σ τ σ))


------------------------------------------------------------------------
-- M-types for indexed containers, defined coinductively (in Cubical
-- Agda)
------------------------------------------------------------------------

{-# OPTIONS --erased-cubical --safe --guardedness #-}

import Equality.Path as P

module Container.Indexed.M.Codata
  {e⁺} (eq : ∀ {a p} → P.Equality-with-paths a p e⁺) where

open P.Derived-definitions-and-properties eq

open import Logical-equivalence using (_⇔_)
open import Prelude

open import Bijection equality-with-J using (_↔_)
open import Container.Indexed equality-with-J
import Container.Indexed.M.Function equality-with-J as F
import Container.Indexed.Variant equality-with-J as V
open import Container.Indexed.Variant.M.Codata eq as VM using (out-M)
open import Equality.Path.Isomorphisms eq
open import Equivalence equality-with-J as Eq using (_≃_)
open import Function-universe equality-with-J hiding (id; _∘_)
open import H-level equality-with-J
open import H-level.Closure equality-with-J
open import Univalence-axiom equality-with-J

private
  variable
    a iℓ s p : Level
    A I      : Type a
    i x      : A
    C        : Container I s p
    n        : ℕ

------------------------------------------------------------------------
-- The M-type and some basic results

-- An M-type for a given container.
--
-- This definition is similar to one in "Indexed containers" by
-- Altenkirch, Ghani, Hancock, McBride and Morris.

record M {I : Type iℓ} (C : Container I s p) (i : I) :
         Type (iℓ ⊔ s ⊔ p) where
  coinductive
  constructor in-M
  field
    out-M : ⟦ C ⟧ (M C) i

open M public

-- An η-law.

η : in-M (out-M x) ≡ x
η = _↔_.from ≡↔≡ η′
  where
  η′ : in-M (out-M x) P.≡ x
  η′ {x = x} _ .out-M = x .out-M

-- M C is, in a certain sense, a fixpoint of ⟦ C ⟧.

M-fixpoint : ⟦ C ⟧ (M C) i ≃ M C i
M-fixpoint = Eq.↔→≃ in-M out-M (λ _ → η) refl

-- A coalgebra defined using M and out-M.

M-coalgebra : (C : Container I s p) → Coalgebra C
M-coalgebra C = M C , λ _ → out-M

------------------------------------------------------------------------
-- Some conversion lemmas

-- One can convert between M and VM.M.

M≃M :
  ∀ p {I : Type i} {C : V.Container I s (i ⊔ p)} {i} →
  M (_⇔_.to (V.Container⇔Container p) C) i ≃ VM.M C i
M≃M p {C = C} =
  Eq.↔→≃ to from
    (λ c → _↔_.from ≡↔≡ (to-from c))
    (λ c → _↔_.from ≡↔≡ (from-to c))
  where
  to : M (_⇔_.to (V.Container⇔Container p) C) i → VM.M C i
  to x .out-M .proj₁     = x .out-M .proj₁
  to x .out-M .proj₂ i p = to (x .out-M .proj₂ (i , p))

  from : VM.M C i → M (_⇔_.to (V.Container⇔Container p) C) i
  from x .out-M .proj₁         = x .out-M .proj₁
  from x .out-M .proj₂ (i , p) = from (x .out-M .proj₂ i p)

  to-from : (x : VM.M C i) → to (from x) P.≡ x
  to-from x i .out-M .proj₁     = x .out-M .proj₁
  to-from x i .out-M .proj₂ j p = to-from (x .out-M .proj₂ j p) i

  from-to :
    (x : M (_⇔_.to (V.Container⇔Container p) C) i) →
    from (to x) P.≡ x
  from-to x i .out-M .proj₁   = x .out-M .proj₁
  from-to x i .out-M .proj₂ p = from-to (x .out-M .proj₂ p) i

-- M-coalgebra is related to VM.M-coalgebra (assuming univalence).

M-coalgebra≡M-coalgebra :
  ∀ p {I : Type i} (C : V.Container I s (i ⊔ p)) →
  Univalence (i ⊔ s ⊔ p) →
  M-coalgebra (_⇔_.to (V.Container⇔Container p) C) ≡
  _≃_.to (V.Coalgebra≃Coalgebra p C ext) (VM.M-coalgebra C)
M-coalgebra≡M-coalgebra p C univ =
  Σ-≡,≡→≡
    (⟨ext⟩ lemma₁)
    (⟨ext⟩ λ i → ⟨ext⟩ λ x →

     subst (λ P → P ⇾ ⟦ _⇔_.to (V.Container⇔Container p) C ⟧ P)
       (⟨ext⟩ lemma₁) (λ _ → out-M) i x                                ≡⟨ cong (_$ x) $ sym $
                                                                          push-subst-application _ _ ⟩
     subst (λ P → P i → ⟦ _⇔_.to (V.Container⇔Container p) C ⟧ P i)
       (⟨ext⟩ lemma₁) out-M x                                          ≡⟨ subst-→ ⟩

     subst (λ P → ⟦ _⇔_.to (V.Container⇔Container p) C ⟧ P i)
       (⟨ext⟩ lemma₁)
       (subst (_$ i) (sym (⟨ext⟩ lemma₁)) x .out-M)                    ≡⟨ cong (subst (λ P → ⟦ _⇔_.to (V.Container⇔Container p) C ⟧ P i) _) $
                                                                          cong out-M $
                                                                          cong (flip (subst (_$ i)) _) $ sym $
                                                                          ext-sym lemma₁ ⟩
     subst (λ P → ⟦ _⇔_.to (V.Container⇔Container p) C ⟧ P i)
       (⟨ext⟩ lemma₁)
       (subst (_$ i) (⟨ext⟩ (sym ∘ lemma₁)) x .out-M)                  ≡⟨ cong (subst (λ P → ⟦ _⇔_.to (V.Container⇔Container p) C ⟧ P i) _) $
                                                                          cong out-M $
                                                                          subst-ext id (sym ∘ lemma₁) ⟩
     subst (λ P → ⟦ _⇔_.to (V.Container⇔Container p) C ⟧ P i)
       (⟨ext⟩ lemma₁)
       (subst id (sym (lemma₁ i)) x .out-M)                            ≡⟨ cong (subst (λ P → ⟦ _⇔_.to (V.Container⇔Container p) C ⟧ P i) _) $
                                                                          cong out-M $
                                                                          subst-id-in-terms-of-inverse∘≡⇒↝ equivalence {A≡B = lemma₁ i} ⟩
     subst (λ P → ⟦ _⇔_.to (V.Container⇔Container p) C ⟧ P i)
       (⟨ext⟩ lemma₁)
       (_≃_.from (≡⇒≃ (lemma₁ i)) x .out-M)                            ≡⟨ cong (subst (λ P → ⟦ _⇔_.to (V.Container⇔Container p) C ⟧ P i) _) $
                                                                          cong (λ (eq : M (_⇔_.to (V.Container⇔Container p) C) i ≃ VM.M C i) →
                                                                                _≃_.from eq x .out-M) $
                                                                          _≃_.right-inverse-of (≡≃≃ univ) _ ⟩
     subst (λ P → ⟦ _⇔_.to (V.Container⇔Container p) C ⟧ P i)
       (⟨ext⟩ lemma₁)
       (_≃_.from (M≃M p {i = i}) x .out-M)                             ≡⟨⟩

     subst (λ P → ∃ λ (s : V.Shape C i) →
                    ((i , _) : ∃ (V.Position C s)) → P i)
       (⟨ext⟩ lemma₁)
       (_≃_.from (M≃M p {i = i}) x .out-M)                             ≡⟨ push-subst-pair-× _ _ ⟩

     ( x .out-M .proj₁
     , subst (λ P → ((i , _) : ∃ (V.Position C (x .out-M .proj₁))) →
                    P i)
       (⟨ext⟩ lemma₁)
       (_≃_.from (M≃M p {i = i}) x .out-M .proj₂)
     )                                                                 ≡⟨ cong (x .out-M .proj₁ ,_) $
                                                                          ⟨ext⟩ (lemma₃ i x) ⟩∎
     Σ-map id uncurry (x .out-M)                                       ∎)
  where
  lemma₁ = λ _ → ≃⇒≡ univ (M≃M p)

  lemma₂ : ∀ _ _ _ → _
  lemma₂ _ _ p =
    sym (Σ-≡,≡→≡ (sym (⟨ext⟩ lemma₁)) (refl p′))                ≡⟨ cong sym $ cong (Σ-≡,≡→≡ (sym (⟨ext⟩ lemma₁))) $ sym $
                                                                   trans-symʳ _ ⟩
    sym (Σ-≡,≡→≡ (sym (⟨ext⟩ lemma₁))
           (trans (subst-const _) (sym (subst-const _))))       ≡⟨ cong sym $ Σ-≡,≡→≡-subst-const _ _ ⟩

    sym (cong₂ _,_ (sym (⟨ext⟩ lemma₁)) (sym (subst-const _)))  ≡⟨ cong sym cong₂-sym ⟩

    sym (sym (cong₂ _,_ (⟨ext⟩ lemma₁) (subst-const _)))        ≡⟨ sym-sym _ ⟩∎

    cong₂ _,_ (⟨ext⟩ lemma₁) (subst-const _)                    ∎
    where
    p′ = subst (const _) (sym (⟨ext⟩ lemma₁)) p

  lemma₃ : ∀ _ _ _ → _
  lemma₃ i x (i′ , p′) =
    subst (λ P → ((i , _) : ∃ _) → P i)
    (⟨ext⟩ lemma₁)
    (_≃_.from (M≃M p {i = i}) x .out-M .proj₂)
    (i′ , p′)                                               ≡⟨ subst-∀ ⟩

    subst (λ ((P , i , _) : _ × ∃ _) → P i)
      (sym $ Σ-≡,≡→≡ (sym (⟨ext⟩ lemma₁)) (refl _))
      (_≃_.from (M≃M p {i = i}) x .out-M .proj₂ $
       subst (const _) (sym (⟨ext⟩ lemma₁)) (i′ , p′))      ≡⟨ cong (flip (subst (λ ((P , i , _) : _ × ∃ _) → P i)) _) $
                                                               lemma₂ i x _ ⟩
    subst (λ ((P , i , _) : _ × ∃ _) → P i)
      (cong₂ _,_ (⟨ext⟩ lemma₁) (subst-const _))
      (_≃_.from (M≃M p {i = i}) x .out-M .proj₂ $
       subst (const _) (sym (⟨ext⟩ lemma₁)) (i′ , p′))      ≡⟨ elim₁
                                                                 (λ {q} eq →
                                                                    subst (λ ((P , i , _) : _ × ∃ _) → P i)
                                                                      (cong₂ _,_ (⟨ext⟩ lemma₁) eq)
                                                                      (_≃_.from (M≃M p {i = i}) x .out-M .proj₂ q) ≡
                                                                    subst (λ ((P , i , _) : _ × ∃ (V.Position C (x .out-M .proj₁))) → P i)
                                                                      (cong₂ {u = i′ , p′} _,_ (⟨ext⟩ lemma₁) (refl _))
                                                                      (_≃_.from (M≃M p {i = i}) x .out-M .proj₂ (i′ , p′)))
                                                                 (refl _)
                                                                 _ ⟩
    subst (λ ((P , i , _) : _ × ∃ _) → P i)
      (cong₂ _,_ (⟨ext⟩ lemma₁) (refl _))
      (_≃_.from (M≃M p {i = i}) x .out-M .proj₂ (i′ , p′))  ≡⟨ cong (flip (subst (λ ((P , i , _) : _ × ∃ _) → P i)) _) $
                                                               cong₂-reflʳ _ ⟩
    subst (λ ((P , i , _) : _ × ∃ _) → P i)
      (cong (_, i′ , p′) (⟨ext⟩ lemma₁))
      (_≃_.from (M≃M p {i = i}) x .out-M .proj₂ (i′ , p′))  ≡⟨ sym $ subst-∘ _ _ _ ⟩

    subst (_$ i′) (⟨ext⟩ lemma₁)
      (_≃_.from (M≃M p {i = i}) x .out-M .proj₂ (i′ , p′))  ≡⟨ subst-ext _ _ ⟩

    subst id (lemma₁ i′)
      (_≃_.from (M≃M p {i = i}) x .out-M .proj₂ (i′ , p′))  ≡⟨⟩

    subst id (≃⇒≡ univ (M≃M p {i = i′}))
      (_≃_.from (M≃M p {i = i′}) (x .out-M .proj₂ i′ p′))   ≡⟨ subst-id-in-terms-of-≡⇒↝ equivalence ⟩

    _≃_.to (≡⇒≃ (≃⇒≡ univ (M≃M p {i = i′})))
      (_≃_.from (M≃M p {i = i′}) (x .out-M .proj₂ i′ p′))   ≡⟨ cong (λ eq → _≃_.to eq (_≃_.from (M≃M p {i = i′}) (x .out-M .proj₂ i′ p′))) $
                                                               _≃_.right-inverse-of (≡≃≃ univ) _ ⟩
    _≃_.to (M≃M p {i = i′})
      (_≃_.from (M≃M p {i = i′}) (x .out-M .proj₂ i′ p′))   ≡⟨ _≃_.right-inverse-of (M≃M p {i = i′}) _ ⟩

    x .out-M .proj₂ i′ p′                                   ≡⟨⟩

    Σ-map id uncurry (x .out-M) .proj₂ (i′ , p′)            ∎

------------------------------------------------------------------------
-- Lemmas related to lift-positions

-- Lifting the position type family using lift-positions does not
-- affect the resulting M type (up to pointwise equivalence).

M≃M-lift-positions : M C i ≃ M (lift-positions C) i
M≃M-lift-positions =
  Eq.↔→≃ to from
    (λ c → _↔_.from ≡↔≡ (to-from c))
    (λ c → _↔_.from ≡↔≡ (from-to c))
  where
  to : M C i → M (lift-positions C) i
  to x .out-M .proj₁          = x .out-M .proj₁
  to x .out-M .proj₂ (lift p) = to (x .out-M .proj₂ p)

  from : M (lift-positions C) i → M C i
  from x .out-M .proj₁   = x .out-M .proj₁
  from x .out-M .proj₂ p = from (x .out-M .proj₂ (lift p))

  to-from : (x : M (lift-positions C) i) → to (from x) P.≡ x
  to-from x i .out-M .proj₁   = x .out-M .proj₁
  to-from x i .out-M .proj₂ p = to-from (x .out-M .proj₂ p) i

  from-to : (x : M C i) → from (to x) P.≡ x
  from-to x i .out-M .proj₁   = x .out-M .proj₁
  from-to x i .out-M .proj₂ p = from-to (x .out-M .proj₂ p) i

-- M-coalgebra C is related to M-coalgebra (lift-positions C)
-- (assuming univalence).

≡M-coalgebra-lift-positions :
  {I : Type i} {C : Container I s p} →
  Univalence (i ⊔ s ⊔ p) →
  Coalgebra≃Coalgebra-lift-positions _ (M-coalgebra C) ≡
  M-coalgebra (lift-positions C)
≡M-coalgebra-lift-positions {C = C} univ =
  Σ-≡,≡→≡
    (⟨ext⟩ lemma)
    (⟨ext⟩ λ i → ⟨ext⟩ λ x →

     subst (λ P → P ⇾ ⟦ lift-positions C ⟧ P)
       (⟨ext⟩ lemma)
       ((Σ-map id (_∘ lower) ∘_) ∘ (λ _ → out-M))
       i x                                                             ≡⟨ cong (_$ x) $ sym $
                                                                          push-subst-application _ _ ⟩
     subst (λ P → P i → ⟦ lift-positions C ⟧ P i)
       (⟨ext⟩ lemma) (Σ-map id (_∘ lower) ∘ out-M) x                   ≡⟨ subst-→ ⟩

     subst (λ P → ⟦ lift-positions C ⟧ P i)
       (⟨ext⟩ lemma)
       (Σ-map id (_∘ lower) $
        subst (_$ i) (sym (⟨ext⟩ lemma)) x .out-M)                     ≡⟨ cong (subst (λ P → ⟦ lift-positions C ⟧ P i) _) $
                                                                          cong (Σ-map id (_∘ lower)) $ cong out-M $
                                                                          cong (flip (subst (_$ i)) _) $ sym $
                                                                          ext-sym lemma ⟩
     subst (λ P → ⟦ lift-positions C ⟧ P i)
       (⟨ext⟩ lemma)
       (Σ-map id (_∘ lower) $
        subst (_$ i) (⟨ext⟩ (sym ∘ lemma)) x .out-M)                   ≡⟨ cong (subst (λ P → ⟦ lift-positions C ⟧ P i) _) $
                                                                          cong (Σ-map id (_∘ lower)) $ cong out-M $
                                                                          subst-ext id (sym ∘ lemma) ⟩
     subst (λ P → ⟦ lift-positions C ⟧ P i)
       (⟨ext⟩ lemma)
       (Σ-map id (_∘ lower) $
        subst id (sym (lemma i)) x .out-M)                             ≡⟨ cong (subst (λ P → ⟦ lift-positions C ⟧ P i) _) $
                                                                          cong (Σ-map id (_∘ lower)) $ cong out-M $
                                                                          subst-id-in-terms-of-inverse∘≡⇒↝ equivalence {A≡B = lemma i} ⟩
     subst (λ P → ⟦ lift-positions C ⟧ P i)
       (⟨ext⟩ lemma)
       (Σ-map id (_∘ lower) $
        _≃_.from (≡⇒≃ (lemma i)) x .out-M)                             ≡⟨ cong (subst (λ P → ⟦ lift-positions C ⟧ P i) _) $
                                                                          cong (λ (eq : M C i ≃ M (lift-positions C) i) →
                                                                                Σ-map id (_∘ lower) (_≃_.from eq x .out-M)) $
                                                                          _≃_.right-inverse-of (≡≃≃ univ) _ ⟩
     subst (λ P → ⟦ lift-positions C ⟧ P i)
       (⟨ext⟩ lemma)
       (Σ-map id (_∘ lower) $
        _≃_.from M≃M-lift-positions x .out-M)                          ≡⟨⟩

     subst (λ P → ⟦ lift-positions C ⟧ P i)
       (⟨ext⟩ lemma)
       ( x .out-M .proj₁
       , _≃_.from M≃M-lift-positions ∘ x .out-M .proj₂
       )                                                               ≡⟨ (cong (subst (λ P → ⟦ lift-positions C ⟧ P i) _) $ cong (_ ,_) $
                                                                           ⟨ext⟩ λ p →
                                                                           cong (λ eq → _≃_.from eq (x .out-M .proj₂ p)) $ sym $
                                                                           _≃_.right-inverse-of (≡≃≃ univ) _) ⟩
     subst (λ P → ⟦ lift-positions C ⟧ P i)
       (⟨ext⟩ lemma)
       ( x .out-M .proj₁
       , _≃_.from (≡⇒≃ (lemma _)) ∘ x .out-M .proj₂
       )                                                               ≡⟨ (cong (subst (λ P → ⟦ lift-positions C ⟧ P i) _) $ cong (_ ,_) $
                                                                           ⟨ext⟩ λ p →
                                                                           cong (λ (f : ∀ i → M C i ≡ M (lift-positions C) i) →
                                                                                _≃_.from (≡⇒≃ (f _)) (x .out-M .proj₂ p)) $ sym $
                                                                           _≃_.left-inverse-of (Eq.extensionality-isomorphism bad-ext) _) ⟩
     subst (λ P → ⟦ lift-positions C ⟧ P i)
       (⟨ext⟩ lemma)
       ( x .out-M .proj₁
       , _≃_.from (≡⇒≃ (ext⁻¹ (⟨ext⟩ lemma) _)) ∘ x .out-M .proj₂
       )                                                               ≡⟨ elim₁
                                                                            (λ eq →
                                                                               subst (λ P → ⟦ lift-positions C ⟧ P i)
                                                                                eq
                                                                                ( x .out-M .proj₁
                                                                                , _≃_.from (≡⇒≃ (ext⁻¹ eq _)) ∘ x .out-M .proj₂
                                                                                ) ≡
                                                                               x .out-M)
                                                                            (
         subst (λ P → ⟦ lift-positions C ⟧ P i)
           (refl _)
           ( x .out-M .proj₁
           , _≃_.from (≡⇒≃ (ext⁻¹ (refl (M (lift-positions C))) _)) ∘
             x .out-M .proj₂
           )                                                                 ≡⟨ subst-refl _ _ ⟩

           x .out-M .proj₁
         , _≃_.from (≡⇒≃ (ext⁻¹ (refl (M (lift-positions C))) _)) ∘
           x .out-M .proj₂                                                   ≡⟨ (cong (_ ,_) $ ⟨ext⟩ λ p →
                                                                                cong (λ eq → _≃_.from eq (x .out-M .proj₂ p)) $
                                                                                trans (cong ≡⇒≃ $ ext⁻¹-refl (M (lift-positions C))) $
                                                                                ≡⇒↝-refl) ⟩∎
         x .out-M                                                            ∎)
                                                                            (⟨ext⟩ lemma) ⟩∎
     x .out-M                                                          ∎)
  where
  lemma = λ _ → ≃⇒≡ univ M≃M-lift-positions

------------------------------------------------------------------------
-- Finality

private

  -- M-coalgebra C is sometimes a final coalgebra (assuming
  -- univalence). Note that the last index argument of C is not
  -- unrestricted.

  M-final′ :
    ∀ p {I : Type i} {C : Container I s (i ⊔ p)} →
    Univalence (i ⊔ s ⊔ p) →
    Univalence (i ⊔ p) →
    Final′ (M-coalgebra C)
  M-final′ p {C = C} univ₁ univ₂ =                                    $⟨ VM.M-final′ C′ ⟩
    V.Final′ (VM.M-coalgebra C′)                                      ↝⟨ subst V.Final′ $ sym $ _≃_.to-from C≃C $ sym $
                                                                         M-coalgebra≡M-coalgebra p C′ univ₁ ⟩
    V.Final′ (_≃_.from C≃C (M-coalgebra (_⇔_.to C⇔C C′)))             ↔⟨ V.Final′≃Final′ p C′ ext ext
                                                                           (_≃_.from C≃C (M-coalgebra (_⇔_.to C⇔C C′))) ⟩
    Final′ (_≃_.to C≃C $ _≃_.from C≃C $ M-coalgebra (_⇔_.to C⇔C C′))  ↝⟨ subst Final′ (_≃_.right-inverse-of C≃C (M-coalgebra (_⇔_.to C⇔C C′))) ⟩
    Final′ (M-coalgebra (_⇔_.to C⇔C C′))                              ↝⟨ subst (Final′ ∘ M-coalgebra) $
                                                                         _≃_.right-inverse-of (V.Container≃Container p ext univ₂) _ ⟩□
    Final′ (M-coalgebra C)                                            □
    where
    C⇔C = V.Container⇔Container p
    C′  = _⇔_.from C⇔C C
    C≃C = V.Coalgebra≃Coalgebra p C′ ext

  -- M-coalgebra C is sometimes a final coalgebra (assuming univalence).
  -- Note that the last index argument of C is not unrestricted.

  M-final″ :
    ∀ p {I : Type i} {C : Container I s (i ⊔ p)} →
    Univalence (i ⊔ s ⊔ p) →
    Univalence (i ⊔ p) →
    Final (M-coalgebra C)
  M-final″ p {C = C} univ₁ univ₂ =
    block λ b →
    Final′→Final
      ext
      ( F.M-coalgebra b ext C
      , F.M-final b ext ext
      )
      ( M-coalgebra C
      , M-final′ p univ₁ univ₂
      )

-- M-coalgebra C is a final coalgebra (assuming univalence).
--
-- TODO: Can this be proved directly, without using VM.M-final′ or
-- univalence?

M-final :
  {I : Type i} {C : Container I s p} →
  Univalence (i ⊔ s ⊔ p) →
  Univalence (i ⊔ p) →
  Final (M-coalgebra C)
M-final {p = p} {C = C} univ₁ univ₂ =                        $⟨ M-final″ p univ₁ univ₂ ⟩
  Final (M-coalgebra (lift-positions C))                     ↔⟨⟩

  (∀ Y → Contractible (Y ⇨ M-coalgebra (lift-positions C)))  ↝⟨ (Π-cong-contra-→ (Coalgebra≃Coalgebra-lift-positions _) λ Y →
                                                                 H-level-cong _ 0 (
      Coalgebra≃Coalgebra-lift-positions _ Y ⇨
      M-coalgebra (lift-positions C)                               ↝⟨ ≡⇒↝ _ $ cong (Coalgebra≃Coalgebra-lift-positions _ Y ⇨_) $ sym $
                                                                      ≡M-coalgebra-lift-positions univ₁ ⟩
      Coalgebra≃Coalgebra-lift-positions _ Y ⇨
      Coalgebra≃Coalgebra-lift-positions _ (M-coalgebra C)         ↝⟨ inverse $ ⇨≃⇨-lift-positions ext _ (M-coalgebra C) ⟩□

      Y ⇨ M-coalgebra C                                            □)) ⟩

  (∀ Y → Contractible (Y ⇨ M-coalgebra C))                   ↔⟨⟩

  Final (M-coalgebra C)                                      □

------------------------------------------------------------------------
-- H-levels

-- If the shape types of C have h-level n, then M C i has h-level n
-- (assuming univalence).

H-level-M :
  {I : Type i} {C : Container I s p} →
  Univalence (i ⊔ s ⊔ p) →
  Univalence (i ⊔ p) →
  (∀ i → H-level n (Shape C i)) →
  ∀ {i} → H-level n (M C i)
H-level-M {C = C} univ₁ univ₂ h =
  F.H-level-final-coalgebra
    ext
    (M-coalgebra C , M-final univ₁ univ₂)
    h


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

module Flat where
  open import lib.Basics
  open import Basics
  open import Bool

  open import lib.types.Bool
  open import lib.NType2
  open import lib.Equivalence2
  open import lib.types.Suspension
  open import lib.types.IteratedSuspension
  open import lib.types.Pushout as Pushout
  open import lib.types.Truncation

  -- The type of points of A, ♭ A, is inductively generated by the crisp elements of A
  data ♭ {@♭ l : ULevel} (@♭ A : Type l) : Type l where
    _^♭ : A ::→ ♭ A

  -- The deduced mapping principle of ♭ A: it represents crisp functions.
  ♭-elim : {c : ULevel} {@♭ l : ULevel}{@♭ A : Type l}
         → (C : ♭ A → Type c)
         → ((@♭ u : A) → C (u ^♭))
         → (x : ♭ A) → C x
  ♭-elim C f (x ^♭) = f x
  syntax ♭-elim C (λ u → t) a = let♭ u ^♭:= a in♭ t in-family C

   {- Mike's "let" notation (following Felix) -}
   -- An implicit version of ♭-elim
  let♭ : {@♭ i j : ULevel} {@♭ A : Type i} {B : ♭ A → Type j}
         (a : ♭ A) (f : (@♭ u : A) → B (u ^♭))
         → B a
  let♭ (u ^♭) f = f u
  syntax let♭ a (λ u → t) = let♭ u ^♭:= a in♭ t

  ♭-rec : {@♭ i : ULevel} {j : ULevel} {@♭ A : Type i} {B : Type j}
                → (A ::→ B) → (♭ A) → B
  ♭-rec f (a ^♭) = f a

  ♭-rec-eq : {@♭ i : ULevel} {j : ULevel} {@♭ A : Type i} {B : Type j}
                → (A ::→ B) ≃ (♭ A → B)
  ♭-rec-eq {A = A} {B = B} =
    equiv ♭-rec
      (λ f → λ (@♭ a : A) → f (a ^♭))  -- fro
      (λ f → λ= (λ { (a ^♭) → refl })) -- to-fro
      (λ f → refl)                     -- fro-to

  -- Crisp function types, but from A : ♭ Type
  _♭:→_ : {@♭ i : ULevel} {j : ULevel} (A : ♭ (Type i)) (B : Type j) → Type (lmax i j)
  (A ^♭) ♭:→ B = A ::→ B

  -- The inclusion of the points of A into A
  _↓♭ : {@♭ i : ULevel} {@♭ A : Type i} → (♭ A → A)
  _↓♭ (x ^♭) = x

  -- The (judgemental) computation rule for mapping out of points
  ♭-β : {@♭ i : ULevel} {@♭ A : Type i} (@♭ a : ♭  A)
        → ((a ↓♭) ^♭) == a
  ♭-β (a ^♭) = refl

  -- Application of crisp functions to crisp equalities
  -- Used mostly to apply _^♭.
  ♭-ap : {@♭ i j : ULevel} {@♭ A : Type i} {B : Type j}
         (f : (@♭ a : A) → B) {@♭ x y : A} (@♭ p : x == y)
         → (f x) == (f y)
  ♭-ap f refl = refl

  -- ♭ is a functor
  ♭→ : {@♭ i j : ULevel} {@♭ A : Type i} {@♭ B : Type j}
       → (@♭ f : A → B) → (♭ A → ♭ B)
  ♭→ f (x ^♭) = (f x) ^♭

  -- The naturality square of the inclusion of points (judgemental!)
  ♭→-nat : {@♭ i j : ULevel} {@♭ A : Type i} {@♭ B : Type j} (@♭ f : A → B)
           → (a : ♭ A) → (f ∘ _↓♭) a  == (_↓♭ ∘ (♭→ f)) a
  ♭→-nat f (a ^♭) = refl

  -- Proof of functoriality
  ♭→∘ : {@♭ i j k : ULevel} {@♭ A : Type i} {@♭ B : Type j} {@♭ C : Type k}
        {@♭ f : A → B} {@♭ g : B → C}
        → ♭→ (g ∘ f) == (♭→ g) ∘ (♭→ f)
  ♭→∘ {f = f} {g = g} =
    λ= (λ a → -- We test on elements, but
          let♭ u ^♭:= a in♭ -- might as well assume those elements are crisp
            refl -- where it follows by definition.
          in-family (λ a → (♭→ (g ∘ f)) a == ((♭→ g) ∘ (♭→ f)) a)  )

  -- ♭→ preserves crisp equivalences
  ♭→e : {@♭ i j : ULevel} {@♭ A : Type i} {@♭ B : Type j}
        → (@♭ e : A ≃ B) → (♭ A) ≃ (♭ B)
  ♭→e e =
    equiv (♭→ (–> e))
          (♭→ (<– e))
          (λ { (a ^♭) → ♭-ap _^♭ (<–-inv-r e a) })
          (λ { (a ^♭) → ♭-ap _^♭ (<–-inv-l e a) })

  -- A type is discrete if the inclusion of its point is an equivalence.
  _is-discrete : {@♭ l : ULevel} → (@♭ A : Type l) → Type l
  _is-discrete {l} A = is-equiv {l} {l} {♭ A} {A} _↓♭

  -- The equivalence between the points of a discrete type and itself
  discrete-eq : {@♭ l : ULevel} {@♭ A : Type l} → A is-discrete → (♭ A) ≃ A
  discrete-eq = _↓♭ ,_

  _is-discrete-is-a-prop : {@♭ l : ULevel} (@♭ A : Type l) → (A is-discrete) is-a-prop
  A is-discrete-is-a-prop = is-equiv-is-prop

  is-discrete-prop : {@♭ l : ULevel} → SubtypeProp (♭ (Type l)) l
  is-discrete-prop = (λ { (A ^♭) → A is-discrete }) , (λ { (A ^♭) → A is-discrete-is-a-prop })

  -- The subtype of discrete types
  Discrete : (@♭ l : ULevel) → Type (lsucc l)
  Discrete l = Subtype is-discrete-prop

  Discrete-_-Type_ : (@♭ n : ℕ₋₂) (@♭ i : ULevel) → Type (lsucc i)
  Discrete- n -Type i = Subtype prop
    where prop : SubtypeProp (♭ (n -Type i)) i
          prop = (λ { (A ^♭) → (fst A) is-discrete }) , (λ { (A ^♭) → (fst A) is-discrete-is-a-prop })

  DiscSet : (@♭ i : ULevel) → Type (lsucc i)
  DiscSet i = Discrete- ⟨ S (S O) ⟩₋₂ -Type i

  DiscSet₀ = DiscSet lzero

  ∈Disc : {@♭ i : ULevel} (A : DiscSet i) → Type i
  ∈Disc A = ((♭→ ∈) (fst A)) ↓♭

  DiscSet-to-Set : {@♭ i : ULevel} → DiscSet i → hSet i
  DiscSet-to-Set A = (fst A) ↓♭

  -- DiscSet_is-discrete : {@♭ i : ULevel} → (@♭ A : DiscSet i) → (∈Disc A) is-discrete
  -- DiscSet A is-discrete = {!(snd A)!}

  -- Just in case, we apply univalence to the discrete-eq
  discrete-id : {@♭ l : ULevel} {@♭ A : Type l} → A is-discrete → (♭ A) == A
  discrete-id p = ua (discrete-eq p)

  -- Obviously, the points of a type are discrete
  ♭-is-discrete : {@♭ l : ULevel} {@♭ A : Type l} → (♭ A) is-discrete
  ♭-is-discrete {_} {A} =
                        _↓♭ is-an-equivalence-because
                           fro is-inverse-by
                             to-fro
                             and
                             fro-to
                        where
                          fro : ♭ A → ♭ (♭ A)
                          fro (a ^♭) = (a ^♭) ^♭

                          to-fro : (a : ♭ A) → ((fro a) ↓♭) == a
                          to-fro (a ^♭) = refl

                          fro-to  : (a : ♭ (♭ A)) → fro (a ↓♭) == a
                          fro-to ((a ^♭) ^♭) = refl

  -- To prove the ♭i is an equivalence, it suffices to give a section of it.
  _is-discrete-because_is-section-by_ : {@♭ l : ULevel} (@♭ A : Type l)
                        (@♭ s : A → ♭ A) (@♭ p : (a : A) → ((s a) ↓♭)== a)
                        → A is-discrete
  A is-discrete-because s is-section-by p =
      _↓♭ is-an-equivalence-because s is-inverse-by p and
        (λ { (a ^♭) → ! (♭-β (s a)) ∙ (♭-ap _^♭ (p a))})

  -- ♭ commutes with identity types in the following sense
  module _ {@♭ i : ULevel} {@♭ A : Type i} where
    ♭-identity-eq : (@♭ x y : A) → ♭ (x == y) ≃ ((x ^♭) == (y ^♭))
    ♭-identity-eq _ _ = equiv to fro to-fro fro-to
      where
        to : {@♭ x y : A} → ♭ (x == y) → ((x ^♭) == (y ^♭))
        to (refl ^♭) = refl

        fro : {@♭ x y : A} → ((x ^♭) == (y ^♭)) → ♭ (x == y)
        fro refl = refl ^♭

        to-fro : {@♭ x y : A} → (p : (x ^♭) == (y ^♭)) → (to (fro p) == p)
        to-fro refl = refl

        fro-to : {@♭ x y : A} → (p : ♭ (x == y)) → fro (to p) == p
        fro-to (refl ^♭) = refl

        {- From Tslil:
                There once was a Bear in a Zoo
                Who didn't know quite what to do
                    It bored him so
                    To walk to and fro
                So he flipped it and walked fro and to
        -}

  -- ♭ preserves crisp level.
  ♭-preserves-level : {@♭ i : ULevel} {@♭ A : Type i}
                      {@♭ n : ℕ₋₂} (@♭ p : has-level n A)
                      → has-level n (♭ A)
  ♭-preserves-level {_}{A}{⟨-2⟩} p =
    has-level-in ((a ^♭) ,
      (λ {(y ^♭) → (–> (♭-identity-eq a y)) ((contr-path p y )^♭)}))
    where -- If A contracts onto a, then ♭ A contracts onto a ^♭
      a = contr-center p
  ♭-preserves-level {_}{A}{S n}  p =
    has-level-in (λ {(x ^♭) (y ^♭) →
      equiv-preserves-level
        (♭-identity-eq x y)
        {{♭-preserves-level {_}{(x == y)}{n} (has-level-apply p x y)}}})
       {-
         Because the equality types of ♭ A are themselves ♭ (x == y) for x y : A,
         and because we know that x == y has level n if A has level (S n), we can recurse
         to show that ♭ (x == y) has level n and therefore ♭ A has level (S n).
       -}

  -- A version of ♭ that acts on n-types.
  ♭ₙ : {@♭ i : ULevel} {@♭ n : ℕ₋₂}
       → (@♭ A : n -Type i) → (n -Type i)
  ♭ₙ A = (♭ (fst A)) , ♭-preserves-level (snd A)

  ♭-Trunc-map : {@♭ i : ULevel} {@♭ n : ℕ₋₂} (@♭ X : Type i)
             → (Trunc n (♭ X)) → ♭ (Trunc n X)
  ♭-Trunc-map X =
    Trunc-elim {{p = ♭-preserves-level Trunc-level}}
      (λ { (a ^♭) → [ a ] ^♭ })

  -- Until ♯ works we have to postulate this.
  ♭-Trunc-eq : {@♭ i : ULevel} {@♭ n : ℕ₋₂} (@♭ X : Type i)
             → (Trunc n (♭ X)) ≃ ♭ (Trunc n X)
  ♭-Trunc-eq X = (♭-Trunc-map X) , unproven
    where
      postulate unproven : (♭-Trunc-map X) is-an-equiv

  ⊤-is-discrete : ⊤ is-discrete
  ⊤-is-discrete = _↓♭ is-an-equivalence-because
                         (λ {unit → unit ^♭}) is-inverse-by
                           (λ {unit → refl})
                           and
                           (λ {(unit ^♭) → refl})

  -- ♭ commutes with coproducts.
  ♭-commutes-with-⊔ : {@♭ i j : ULevel} {@♭ A : Type i} {@♭ B : Type j}
                      → ♭ (A ⊔ B) ≃ (♭ A) ⊔ (♭ B)
  ♭-commutes-with-⊔ = to ,
    (to is-an-equivalence-because
      fro is-inverse-by
        (λ { (inl (a ^♭)) → refl ;
             (inr (b ^♭)) → refl })
        and
        (λ { ((inl a) ^♭) → refl ;
             ((inr b) ^♭) → refl }))
    where
      to : {@♭ i j : ULevel} {@♭ A : Type i} {@♭ B : Type j}
           → ♭ (A ⊔ B) → (♭ A) ⊔ (♭ B)
      to ((inl a) ^♭) = inl (a ^♭)
      to ((inr b) ^♭) = inr (b ^♭)

      fro : {@♭ i j : ULevel} {@♭ A : Type i} {@♭ B : Type j}
           → (♭ A) ⊔ (♭ B) → ♭ (A ⊔ B)
      fro (inl (a ^♭)) = (inl a) ^♭
      fro (inr (b ^♭)) = (inr b) ^♭

  -- ♭ commutes with Σ types for crisp families.
  module _ {@♭ i j : ULevel} {@♭ A : Type i} {@♭ B : A → Type j} where
    ♭-commutes-with-Σ : ♭ (Σ A B) ≃ Σ (♭ A) (λ a → let♭ u ^♭:= a in♭ ♭ (B u))
    ♭-commutes-with-Σ = equiv to fro ((λ { ((a ^♭) , (b ^♭)) → refl })) ((λ { ((a , b) ^♭) → refl }))
      where
        to : ♭ (Σ A B) → Σ (♭ A) (λ a → let♭ u ^♭:= a in♭ ♭ (B u))
        to ((a , b) ^♭) = ((a ^♭) , (b ^♭))

        fro : Σ (♭ A) (λ a → let♭ u ^♭:= a in♭ ♭ (B u)) → ♭ (Σ A B)
        fro ((a ^♭) , (b ^♭)) = (a , b) ^♭

  ℕ-is-discrete : ℕ is-discrete
  ℕ-is-discrete =
    ℕ is-discrete-because fro is-section-by to-fro
    where
      fro : ℕ → ♭ ℕ
      fro O = O ^♭
      fro (S n) = (♭→ S) (fro n)

      to-fro : (n : ℕ) → ((fro n) ↓♭) == n
      to-fro O = refl
      to-fro (S n) = (fro (S n)) ↓♭
                   =⟨ ! ((♭→-nat S) (fro n)) ⟩
                     (S ∘ _↓♭) (fro n)
                   =⟨ ap S (to-fro n) ⟩
                     S n
                   =∎
        {-
          When proving this equality by induction on n, the case of 0 presents no problem,
          but when we work with (S n), we have to deal with the fact that n is not crisp.
          However, as a constructor (and therefore defined in the empty context), S is crisp.
          Therefore, we can use the naturality sqaure of ♭ to commute it past fro, and then recurse.
        -}

  -- We copy the proof for ULevel (or would but we can't pattern match on lsucc?)
  {-
  ULevel-is-discrete : ULevel is-discrete
  ULevel-is-discrete =
    ULevel is-discrete-because fro is-section-by to-fro
    where
      fro : ULevel → ♭ ULevel
      fro lzero = lzero ^♭
      fro (lsucc n) = (♭→ lsucc) (fro n)

      to-fro : (n : ULevel) → ♭i (fro n) == n
      to-fro lzero = refl
      to-fro (lsucc n) = ♭i (fro (lsucc n))
                       =⟨ ! ((♭→-nat lsucc) (fro n)) ⟩
                         (lsucc ∘ ♭i) (fro n)
                       =⟨ ap lsucc (to-fro n) ⟩
                         lsucc n
                       =∎ -}

  Bool-is-discrete : Bool is-discrete
  Bool-is-discrete = Bool is-discrete-because fro is-section-by to-fro
    where
      fro : Bool → ♭ Bool
      fro true = true ^♭
      fro false = false ^♭

      to-fro : (b : Bool) → ((fro b) ↓♭)== b
      to-fro true = refl
      to-fro false = refl

  Bool-to-♭PropT₀ : Bool → ♭ PropT₀
  Bool-to-♭PropT₀ true = True ^♭
  Bool-to-♭PropT₀ false = False ^♭

  Bool-to-♭PropT₀∘↓♭=♭→Bool-to-PropT₀ : Bool-to-♭PropT₀ ∘ _↓♭ == (♭→ Bool-to-PropT₀)
  Bool-to-♭PropT₀∘↓♭=♭→Bool-to-PropT₀ = λ= (λ { (true ^♭) → refl ; (false ^♭) → refl })

{-
  _-Sphere-is-discrete : (@♭ n : ℕ) → (Sphere n) is-discrete
  n -Sphere-is-discrete = (Sphere n) is-discrete-because
    (fro n) is-section-by {!!}
    where
      ♭-north : {@♭ n : ℕ} → ♭ (Sphere n)
      ♭-north {O} = true ^♭
      ♭-north {S n} = {!north!}

      ♭-south : {@♭ n : ℕ} → ♭ (Sphere n)
      ♭-south = {!!}

      fro : (@♭ n : ℕ) → (Sphere n) → ♭ (Sphere n)
      fro O = λ { true → true ^♭ ; false →  false ^♭ }
      fro (S n) = {!Susp-rec {A = Sphere n} {C = ♭ (Sphere (S n))} ♭-north ♭-south!}

-}


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

{-
Truncated higher inductive types look like higher inductive types except that
they are truncated down to some fixed truncation level.
This allow to define truncations (obviously) but also free algebras for
algebraic theories, Eilenberg-MacLane spaces, etc.

The idea is that to get an n-truncated higher inductive type, you just have to
add the following two constructors, where [I] is the HIT you are defining.

    top  : (f : Sⁿ n → I) → I
    rays : (f : Sⁿ n → I) (x : Sⁿ n) → top f ≡ f x

In this file, I prove whatever is needed to go from the usual elimination rules
generated by the previous constructors to the elimination rules that we expect
from truncated higher inductive types.

We have to note that the previous definition does not work for [n = 0], because
this only adds a point to I instead of turning it into a contractible space.
0-truncated higher inductive types are not really interesting but handling
truncations is easier when 0 is not special

Hence I’m adding the following path constructor

    hack-prop : (p : n ≡ 0) (x y : τ) → (x ≡ y)

This will force [I] to be a prop (hence contractible because [top] of the empty
function is of type [I]) and does not change anything when [n] is not 0.

I may use the following syntax for [n]-truncated higher inductive types:

    (n)data I : Set i where
      …

-}

module Homotopy.TruncatedHIT where

open import Base
open import Spaces.Spheres public
open import Spaces.Suspension public

_+1 : ℕ₋₂ → ℕ
⟨-2⟩ +1 = 0
(S ⟨-2⟩) +1 = 0
(S n) +1 = S (n +1)

-- [hSⁿ n] is what is supposed to be filled to get something n-truncated
hSⁿ : ℕ₋₂ → Set
hSⁿ ⟨-2⟩ = ⊥
hSⁿ n = Sⁿ (n +1)

-- Type of fillings of a sphere
filling : ∀ {i} (n : ℕ₋₂) {A : Set i} (f : hSⁿ n → A) → Set i
filling {i} n {A} f = Σ A (λ t → ((x : hSⁿ n) → t ≡ f x))

-- Type of dependent fillings of a sphere above a ball
filling-dep : ∀ {i j} (n : ℕ₋₂) {A : Set i} (P : A → Set j) (f : hSⁿ n → A)
  (fill : filling n f) (p : (x : hSⁿ n) → P (f x)) → Set j
filling-dep {i} {j} n {A} P f fill p =
  Σ (P (π₁ fill)) (λ t → ((x : hSⁿ n) → transport P (π₂ fill x) t ≡ p x))

-- [has-n-spheres-filled n A] is inhabited iff every n-sphere in [A] can be
-- filled with an (n+1)-ball.
-- We will show that this is equivalent to being n-truncated, *for n > 0*
-- (for n = 0, having 0-spheres filled only means that A is inhabited)
has-spheres-filled : ∀ {i} (n : ℕ₋₂) (A : Set i) → Set i
has-spheres-filled n A = (f : hSⁿ n → A) → filling n f

-- [has-n-spheres-filled] satisfy the same inductive property
-- than [is-truncated]
fill-paths : ∀ {i} (n : ℕ₋₂) (A : Set i) (t : has-spheres-filled (S n) A)
  → (n ≡ ⟨-2⟩ → is-contr A) → ((x y : A) → has-spheres-filled n (x ≡ y))
fill-paths ⟨-2⟩ A t contr x y f =
  (contr-has-all-paths (contr refl) x y , abort)
fill-paths (S n) A t _ x y f =
  ((! (π₂ u (north _)) ∘ π₂ u (south _))
  , (λ z → ! (lemma (paths _ z)) ∘ suspension-β-paths-nondep _ _ _ _ f _)) where

  -- [f] is a map from [hSⁿ (S n)] to [x ≡ y], we can build from it a map
  -- from [hSⁿ (S n)] to [A]
  newf : hSⁿ (S (S n)) → A
  newf = suspension-rec-nondep _ _ x y f

  u : filling (S (S n)) newf
  u = t newf
  -- I’ve got a filling

  -- Every path in the sphere is equal (in A) to the canonical path going
  -- through the center of the filled sphere
  lemma : {p q : hSⁿ (S (S n))} (l : p ≡ q)
    → ap newf l ≡ ! (π₂ u p) ∘ π₂ u q
  lemma {p = a} refl = ! (opposite-left-inverse (π₂ u a))

-- We first prove that if n-spheres are filled, then the type is n-truncated,
-- we have to prove it for n = -1, and then use the previous lemma
abstract
  spheres-filled-is-truncated : ∀ {i} (n : ℕ₋₂) (A : Set i)
    → ((n ≡ ⟨-2⟩ → is-contr A) → has-spheres-filled n A → is-truncated n A)
  spheres-filled-is-truncated ⟨-2⟩ A contr t = contr refl
  spheres-filled-is-truncated (S ⟨-2⟩) A _ t =
    all-paths-is-prop (λ x y → ! (π₂ (t (f x y)) true) ∘ π₂ (t (f x y)) false)
    where
      f : (x y : A) → bool {zero} → A
      f x y true = x
      f x y false = y
  spheres-filled-is-truncated (S (S n)) A _ t = λ x y →
    spheres-filled-is-truncated (S n) (x ≡ y) (λ ())
      (fill-paths (S n) A t (λ ()) x y)

-- We now prove the converse
abstract
  truncated-has-spheres-filled : ∀ {i} (n : ℕ₋₂) (A : Set i)
    (t : is-truncated n A) → has-spheres-filled n A
  truncated-has-spheres-filled ⟨-2⟩ A t f = (π₁ t , abort)
  truncated-has-spheres-filled (S ⟨-2⟩) A t f =
    (f true , (λ {true → refl ; false → π₁ (t (f true) (f false))}))
  truncated-has-spheres-filled (S (S n)) A t f =
    (f (north _)
    , (suspension-rec _ _ refl (ap f (paths _ (⋆Sⁿ _)))
        (λ x → trans-cst≡app (f (north _)) f (paths _ _) _
               ∘ (! (π₂ filled-newf x) ∘ π₂ filled-newf (⋆Sⁿ _))))) where

    newf : hSⁿ (S n) → (f (north _) ≡ f (south _))
    newf x = ap f (paths _ x)

    filled-newf : filling (S n) newf
    filled-newf = truncated-has-spheres-filled (S n) _ (t _ _) newf

-- I prove that if [A] has [S n]-spheres filled, then the type of fillings
-- of [n]-spheres is a proposition. The idea is that two fillings of
-- an [n]-sphere define an [S n]-sphere, which is then filled.
filling-has-all-paths : ∀ {i} (n : ℕ₋₂) (A : Set i)
  ⦃ fill : has-spheres-filled (S n) A ⦄ (f : hSⁿ n → A)
  → has-all-paths (filling n f)
filling-has-all-paths ⟨-2⟩ A ⦃ fill ⦄ f fill₁ fill₂ =
  Σ-eq (! (π₂ big-map-filled true) ∘ π₂ big-map-filled false) (funext abort)
  where

  big-map : hSⁿ ⟨-1⟩ → A
  big-map true = π₁ fill₁
  big-map false = π₁ fill₂

  big-map-filled : filling ⟨-1⟩ big-map
  big-map-filled = fill big-map

filling-has-all-paths (S n) A ⦃ fill ⦄ f fill₁ fill₂ =
  Σ-eq (! (π₂ big-map-filled (north _)) ∘ π₂ big-map-filled (south _))
  (funext (λ x →
                   trans-Π2 _ (λ t x₁ → t ≡ f x₁)
                   (! (π₂ big-map-filled (north _)) ∘
                    π₂ big-map-filled (south _))
                   (π₂ fill₁) x
                   ∘ (trans-id≡cst
                        (! (π₂ big-map-filled (north _)) ∘
                         π₂ big-map-filled (south _))
                        (π₂ fill₁ x)
                   ∘ move!-right-on-left
                       (! (π₂ big-map-filled (north _)) ∘
                        π₂ big-map-filled (south _))
                       _ _
                    (move-left-on-right _
                       (! (π₂ big-map-filled (north _)) ∘
                        π₂ big-map-filled (south _))
                       _
                    (! (suspension-β-paths-nondep _ _ _ _ g x)
                     ∘ lemma (paths _ x)))))) where

  g : hSⁿ (S n) → (π₁ fill₁ ≡ π₁ fill₂)
  g x = π₂ fill₁ x ∘ ! (π₂ fill₂ x)

  big-map : hSⁿ (S (S n)) → A
  big-map = suspension-rec-nondep _ _ (π₁ fill₁) (π₁ fill₂) g

  big-map-filled : filling (S (S n)) big-map
  big-map-filled = fill big-map

  lemma : {u v : hSⁿ (S (S n))} (p : u ≡ v)
    → ap big-map p ≡ (! (π₂ big-map-filled u) ∘  π₂ big-map-filled v)
  lemma {u = a} refl = ! (opposite-left-inverse (π₂ big-map-filled a))

abstract
  truncated-has-filling-dep : ∀ {i j} (A : Set i) (P : A → Set j) (n : ℕ₋₂)
    ⦃ trunc : (x : A) → is-truncated n (P x) ⦄
    (contr : n ≡ ⟨-2⟩ → is-contr A)
    (fill : has-spheres-filled n A) (f : hSⁿ n → A) (p : (x : hSⁿ n) → P (f x))
    → filling-dep n P f (fill f) p
  truncated-has-filling-dep A P ⟨-2⟩ ⦃ trunc ⦄ contr fill f p =
    (π₁ (trunc (π₁ (fill f))) , abort)
  truncated-has-filling-dep A P (S n) ⦃ trunc ⦄ contr fill f p =
    transport (λ t → filling-dep (S n) P f t p) eq fill-dep  where

    -- Combining [f] and [p] we have a sphere in the total space of [P]
    newf : hSⁿ (S n) → Σ A P
    newf x = (f x , p x)

    -- But this total space is (S n)-truncated
    ΣAP-truncated : is-truncated (S n) (Σ A P)
    ΣAP-truncated =
      Σ-is-truncated (S n) (spheres-filled-is-truncated (S n) A contr fill)
                     trunc

    -- Hence the sphere is filled
    tot-fill : filling (S n) newf
    tot-fill = truncated-has-spheres-filled (S n) (Σ A P) ΣAP-truncated newf

    -- We can split this filling as a filling of [f] in [A] …
    new-fill : filling (S n) f
    new-fill = (π₁ (π₁ tot-fill) , (λ x → base-path (π₂ tot-fill x)))

    -- and a dependent filling above the previous filling of [f], along [p]
    fill-dep : filling-dep (S n) P f new-fill p
    fill-dep = (π₂ (π₁ tot-fill) , (λ x → fiber-path (π₂ tot-fill x)))

    A-has-spheres-filled-S : has-spheres-filled (S (S n)) A
    A-has-spheres-filled-S =
      truncated-has-spheres-filled (S (S n)) A
        (truncated-is-truncated-S (S n)
          (spheres-filled-is-truncated (S n) A contr fill))

    -- But both the new and the old fillings of [f] are equal, hence we will
    -- have a dependent filling above the old one
    eq : new-fill ≡ fill f
    eq = filling-has-all-paths (S n) A f new-fill (fill f)


----------------------------------------------------------------------
-- Copyright: 2013, Jan Stolarek, Lodz University of Technology     --
--                                                                  --
-- License: See LICENSE file in root of the repo                    --
-- Repo address: https://github.com/jstolarek/dep-typed-wbl-heaps   --
--                                                                  --
-- Weight biased leftist heap that proves rank invariant and uses   --
-- a single-pass merging algorithm.                                 --
----------------------------------------------------------------------

module SinglePassMerge.RankProof where

open import Basics
-- We import rank proofs conducted earlier - they will be re-used.
open import TwoPassMerge.RankProof
            using    ( makeT-lemma )
            renaming ( proof-1 to proof-1a; proof-2 to proof-2a )

data Heap : Nat → Set where
  empty : Heap zero
  node  : Priority → {l r : Nat} → l ≥ r → Heap l → Heap r → Heap (suc (l + r))

-- Note [Privoving rank invariant in single-pass merge]
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--
-- Recall that in TwoPassMerge.RankProof we need to prove that:
--
--  1) makeT constructs node of correct size. There were two cases:
--
--     a) constructing a node without swapping l and r subtrees passed
--        to makeT. No extra proof was required as everything followed
--        from definitions.
--
--     b) constructing a node by swapping l and r subtrees (when rank
--        of r was greater than rank of l). We had to prove that:
--
--                    suc (a + b) ≡ suc (b + a)
--
--        This proof is supplied by a function makeT-lemma.
--
--  2) resulting heap produced by merge has correct size. Merge has 4
--     cases (see [Two-pass merging algorithm]):
--
--     a) h1 is empty. No extra proof required (everything follows
--        from definition of +). See [merge, proof 0a].
--
--     b) h2 is empty. We had to prove that:
--
--                 h1 ≡ h1 + 0
--
--        This proof is supplied by +0 (in Basics.Reasoning) and is
--        inlined in the definition of merge. See [merge, proof 0b].
--
--     c) priority p1 is higher than p2. We had to prove:
--
--            suc (l1 + (r1 + suc (l2 + r2)))
--                           ≡ suc ((l1 + r1) + suc (l2 + r2))
--
--        This proof is supplied by proof-1 (renamed to proof-1a in
--        this module). See [merge, proof 1] for details.
--
--     d) priority p2 is higher than p1. We had to prove:
--
--            suc (l2 + (r2  + suc (l1 + r1)))
--                           ≡ suc ((l1 + r1) + suc (l2 + r2))
--
--        This proof is supplied by proof-2 (renamed to proof-2a in
--        this module). See [merge, proof 2] for details.
--
-- Now that we inline makeT into merge and we will have to construct
-- new proofs of merge that take into account the fact that makeT has
-- been inlinined. Let's take a detailed look into the problem and
-- analyze possible solutions.
--
-- First of all let us note that we only made calls to makeT in
-- inductive cases of merge (points 2c and 2d above). This means that
-- implementation of base cases of merge (2a and 2b above) remains
-- unchanged. Let's take a look at proofs we need to supply for each
-- of four inductive cases of merge (see [Single-pass merging
-- algorithm] for description of each case):
--
--   1) case c described in [Single-pass merging algorithm]. Call to
--      makeT would not swap left and right when creating a node from
--      parameters passed to it. Required proof is:
--
--            suc (l1 + (r1 + suc (l2 + r2)))
--                           ≡ suc ((l1 + r1) + suc (l2 + r2))
--
--   2) case d described in [Single-pass merging algorithm]. Call to
--      makeT would swap left and right when creating a node from
--      parameters passed to it. Required proof is:
--
--            suc ((r1 + suc (l2 + r2)) + l1)
--                           ≡ suc ((l1 + r1) + suc (l2 + r2))
--
--   3) case e described in [Single-pass merging algorithm]. Call to
--      makeT would not swap left and right when creating a node from
--      parameters passed to it. Required proof is:
--
--            suc (l2 + (r2  + suc (l1 + r1)))
--                           ≡ suc ((l1 + r1) + suc (l2 + r2))
--
--   4) case f described in [Single-pass merging algorithm]. Call to
--      makeT would swap left and right when creating a node from
--      parameters passed to it. Required proof is:
--
--            suc ((r2 + suc (l1 + r1)) + l2)
--                           ≡ suc ((l1 + r1) + suc (l2 + r2))
--
-- First of all we must note that proofs required in cases 1 and 3 are
-- exactly the same as in the two-pass merging algorithm. This allows
-- us to re-use the old proofs (renamed to proof-1a and proof-2a
-- here). What about proofs of cases 2 and 4? One thing we could do is
-- construct proofs of these properties using technique described in
-- Note [Constructing equality proofs using transitivity]. This is
-- left as an exercise to the reader. Here we will proceed in a
-- different way. Notice that properties we have to prove in cases for
-- 2 and 4 are very similar to properties 1 and 3 (we omit the RHS
-- since it is always the same):
--
--  1: suc (l1 + (r1 + suc (l2 + r2)))
--  2: suc ((r1 + suc (l2 + r2)) + l1)
--
--  3: suc (l2 + (r2  + suc (l1 + r1)))
--  4: suc ((r2 + suc (l1 + r1)) + l2)
--
-- The only difference between 1 and 2 and between 3 and 4 is the
-- order of parameters inside outer suc. This is expected: in cases 2
-- and 4 we swap left and right subtree passed to node and this is
-- reflected in the types. Now, if we could prove that:
--
--            suc ((r1 + suc (l2 + r2)) + l1)
--                           ≡ suc (l1 + (r1 + suc (l2 + r2)))
--
-- and
--
--            suc ((r2 + suc (l1 + r1)) + l2)
--                           ≡ suc (l2 + (r2  + suc (l1 + r1)))
--
-- then we could use transitivity to combine these new proofs with old
-- proof-1a and proof-2a. If we abstract the parameters in the above
-- equalities we see that the property we need to prove is:
--
--                    suc (a + b) ≡ suc (b + a)
--
-- And that happens to be makeT-lemma! New version of merge was
-- created by inlining calls to make and now it turns out we can
-- construct proofs of that implementation by using proofs of
-- makeT. This leads us to very elegant solutions presented below.

proof-1b : (l1 r1 l2 r2 : Nat) → suc ((r1 + suc (l2 + r2)) + l1)
                               ≡ suc ((l1 + r1) + suc (l2 + r2))
proof-1b l1 r1 l2 r2 = trans (makeT-lemma (r1 + suc (l2 + r2)) l1)
                             (proof-1a l1 r1 l2 r2)

proof-2b : (l1 r1 l2 r2 : Nat) → suc ((r2 + suc (l1 + r1)) + l2)
                               ≡ suc ((l1 + r1) + suc (l2 + r2))
proof-2b l1 r1 l2 r2 = trans (makeT-lemma (r2 + suc (l1 + r1)) l2)
                             (proof-2a l1 r1 l2 r2)

-- We can now use proof-1a, proof-1b, proof-2a and proof-2b in
-- definition of merge.
merge : {l r : Nat} → Heap l → Heap r → Heap (l + r)
merge {zero} {_}     empty h2 = h2
merge {suc l} {zero} h1 empty = subst Heap (sym (+0 (suc l))) h1
merge {suc .(l1-rank + r1-rank)} {suc .(l2-rank + r2-rank)}
  (node p1 {l1-rank} {r1-rank} l1≥r1 l1 r1)
  (node p2 {l2-rank} {r2-rank} l2≥r2 l2 r2)
  with p1 < p2
  | order l1-rank (r1-rank + suc (l2-rank + r2-rank))
  | order l2-rank (r2-rank + suc (l1-rank + r1-rank))
merge {suc .(l1-rank + r1-rank)} {suc .(l2-rank + r2-rank)}
  (node p1 {l1-rank} {r1-rank} l1≥r1 l1 r1)
  (node p2 {l2-rank} {r2-rank} l2≥r2 l2 r2)
  | true
  | ge l1≥r1+h2
  | _
  = subst Heap
          (proof-1a l1-rank r1-rank l2-rank r2-rank)
          (node p1 l1≥r1+h2 l1 (merge r1 (node p2 l2≥r2 l2 r2)))
merge {suc .(l1-rank + r1-rank)} {suc .(l2-rank + r2-rank)}
  (node p1 {l1-rank} {r1-rank} l1≥r1 l1 r1)
  (node p2 {l2-rank} {r2-rank} l2≥r2 l2 r2)
  | true
  | le l1≤r1+h2
  | _
  = subst Heap
          (proof-1b l1-rank r1-rank l2-rank r2-rank)
          (node p1 l1≤r1+h2 (merge r1 (node p2 l2≥r2 l2 r2)) l1)
merge {suc .(l1-rank + r1-rank)} {suc .(l2-rank + r2-rank)}
  (node p1 {l1-rank} {r1-rank} l1≥r1 l1 r1)
  (node p2 {l2-rank} {r2-rank} l2≥r2 l2 r2)
  | false
  | _
  | ge l2≥r2+h1
  = subst Heap
          (proof-2a l1-rank r1-rank l2-rank r2-rank)
          (node p2 l2≥r2+h1 l2 (merge r2 (node p1 l1≥r1 l1 r1)))
merge {suc .(l1-rank + r1-rank)} {suc .(l2-rank + r2-rank)}
  (node p1 {l1-rank} {r1-rank} l1≥r1 l1 r1)
  (node p2 {l2-rank} {r2-rank} l2≥r2 l2 r2)
  | false
  | _
  | le l2≤r2+h1
  = subst Heap
          (proof-2b l1-rank r1-rank l2-rank r2-rank)
          (node p2 l2≤r2+h1 (merge r2 (node p1 l1≥r1 l1 r1)) l2)


{-# OPTIONS --safe #-}
module Cubical.Algebra.Group.Exact where

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Function

open import Cubical.Data.Unit

open import Cubical.Algebra.Group.Base
open import Cubical.Algebra.Group.Morphisms
open import Cubical.Algebra.Group.MorphismProperties
open import Cubical.Algebra.Group.GroupPath
open import Cubical.Algebra.Group.Instances.Unit

open import Cubical.HITs.PropositionalTruncation as PT

-- TODO : Define exact sequences
-- (perhaps short, finite, ℕ-indexed and ℤ-indexed)

SES→isEquiv : ∀ {ℓ ℓ'} {L R : Group ℓ-zero}
  → {G : Group ℓ} {H : Group ℓ'}
  → UnitGroup₀ ≡ L
  → UnitGroup₀ ≡ R
  → (lhom : GroupHom L G) (midhom : GroupHom G H) (rhom : GroupHom H R)
  → ((x : _) → isInKer midhom x → isInIm lhom x)
  → ((x : _) → isInKer rhom x → isInIm midhom x)
  → isEquiv (fst midhom)
SES→isEquiv {R = R} {G = G} {H = H} =
  J (λ L _ → UnitGroup₀ ≡ R →
      (lhom : GroupHom L G) (midhom : GroupHom G H)
      (rhom : GroupHom H R) →
      ((x : fst G) → isInKer midhom x → isInIm lhom x) →
      ((x : fst H) → isInKer rhom x → isInIm midhom x) →
      isEquiv (fst midhom))
      ((J (λ R _ → (lhom : GroupHom UnitGroup₀ G) (midhom : GroupHom G H)
                   (rhom : GroupHom H R) →
                   ((x : fst G) → isInKer midhom x → isInIm lhom x) →
                   ((x : _) → isInKer rhom x → isInIm midhom x) →
                   isEquiv (fst midhom))
         main))
  where
  main : (lhom : GroupHom UnitGroup₀ G) (midhom : GroupHom G H)
         (rhom : GroupHom H UnitGroup₀) →
         ((x : fst G) → isInKer midhom x → isInIm lhom x) →
         ((x : fst H) → isInKer rhom x → isInIm midhom x) →
         isEquiv (fst midhom)
  main lhom midhom rhom lexact rexact =
    BijectionIsoToGroupEquiv {G = G} {H = H}
      bijIso' .fst .snd
    where
    bijIso' : BijectionIso G H
    BijectionIso.fun bijIso' = midhom
    BijectionIso.inj bijIso' x inker =
      PT.rec (GroupStr.is-set (snd G) _ _)
           (λ s → sym (snd s) ∙ IsGroupHom.pres1 (snd lhom))
           (lexact _ inker)
    BijectionIso.surj bijIso' x = rexact x refl

-- exact sequence of 4 groups. Useful for the proof of π₄S³
record Exact4 {ℓ ℓ' ℓ'' ℓ''' : Level} (G : Group ℓ)
  (H : Group ℓ') (L : Group ℓ'') (R : Group ℓ''')
  (G→H : GroupHom G H) (H→L : GroupHom H L) (L→R : GroupHom L R)
  : Type (ℓ-max ℓ (ℓ-max ℓ' (ℓ-max ℓ'' ℓ'''))) where
  field
    ImG→H⊂KerH→L : (x : fst H) → isInIm G→H x → isInKer H→L x
    KerH→L⊂ImG→H : (x : fst H) → isInKer H→L x → isInIm G→H x

    ImH→L⊂KerL→R : (x : fst L) → isInIm H→L x → isInKer L→R x
    KerL→R⊂ImH→L : (x : fst L) → isInKer L→R x → isInIm H→L x

open Exact4

extendExact4Surjective : {ℓ ℓ' ℓ'' ℓ''' ℓ'''' : Level}
  (G : Group ℓ) (H : Group ℓ') (L : Group ℓ'') (R : Group ℓ''') (S : Group ℓ'''')
  (G→H : GroupHom G H) (H→L : GroupHom H L) (L→R : GroupHom L R) (R→S : GroupHom R S)
  → isSurjective G→H
  → Exact4 H L R S H→L L→R R→S
  → Exact4 G L R S (compGroupHom G→H H→L) L→R R→S
ImG→H⊂KerH→L (extendExact4Surjective G H L R S G→H H→L L→R R→S surj ex) x =
  PT.rec (GroupStr.is-set (snd R) _ _)
    (uncurry λ g → J (λ x _ → isInKer L→R x)
      (ImG→H⊂KerH→L ex (fst H→L (fst G→H g))
        ∣ (fst G→H g) , refl ∣₁))
KerH→L⊂ImG→H (extendExact4Surjective G H L R S G→H H→L L→R R→S surj ex) x ker =
  PT.rec squash₁
    (uncurry λ y → J (λ x _ → isInIm (compGroupHom G→H H→L) x)
      (PT.map (uncurry
        (λ y → J (λ y _ → Σ[ g ∈ fst G ] fst H→L (fst G→H g) ≡ H→L .fst y)
        (y , refl))) (surj y)))
    (KerH→L⊂ImG→H ex x ker)
ImH→L⊂KerL→R (extendExact4Surjective G H L R S G→H H→L L→R R→S surj ex) =
  ImH→L⊂KerL→R ex
KerL→R⊂ImH→L (extendExact4Surjective G H L R S G→H H→L L→R R→S surj ex) =
  KerL→R⊂ImH→L ex

-- Useful lemma in the proof of π₄S³≅ℤ
transportExact4 : {ℓ ℓ' ℓ'' : Level}
                   {G G₂ : Group ℓ} {H H₂ : Group ℓ'} {L L₂ : Group ℓ''} {R : Group₀}
                   (G≡G₂ : G ≡ G₂) (H≡H₂ : H ≡ H₂) (L≡L₂ : L ≡ L₂)
                → UnitGroup₀ ≡ R
                → (G→H : GroupHom G H) (G₂→H₂ : GroupHom G₂ H₂)
                   (H→L : GroupHom H L) (H₂→L₂ : GroupHom H₂ L₂)
                   (L→R : GroupHom L R)
                → Exact4 G H L R G→H H→L L→R
                → PathP (λ i → GroupHom (G≡G₂ i) (H≡H₂ i)) G→H G₂→H₂
                → PathP (λ i → GroupHom (H≡H₂ i) (L≡L₂ i)) H→L H₂→L₂
                → Exact4 G₂ H₂ L₂ UnitGroup₀ G₂→H₂ H₂→L₂ (→UnitHom L₂)
transportExact4 {G = G} {G₂ = G₂} {H = H} {H₂ = H₂} {L = L} {L₂ = L₂} {R = R} =
  J4 (λ G₂ H₂ L₂ R G≡G₂ H≡H₂ L≡L₂ Unit≡R
                → (G→H : GroupHom G H) (G₂→H₂ : GroupHom G₂ H₂)
                   (H→L : GroupHom H L) (H₂→L₂ : GroupHom H₂ L₂)
                   (L→R : GroupHom L R)
                → Exact4 G H L R G→H H→L L→R
                → PathP (λ i → GroupHom (G≡G₂ i) (H≡H₂ i)) G→H G₂→H₂
                → PathP (λ i → GroupHom (H≡H₂ i) (L≡L₂ i)) H→L H₂→L₂
                → Exact4 G₂ H₂ L₂ UnitGroup₀ G₂→H₂ H₂→L₂ (→UnitHom L₂))
      (λ G→H G₂→H₂ H→L H₂→L₂ L→R ex pp1 pp2
        → J4 (λ G₂→H₂ H₂→L₂ (x : Unit) (y : Unit)
                 pp1 pp2 (_ : tt ≡ x) (_ : tt ≡ x)
             → Exact4 G H L UnitGroup₀ G₂→H₂ H₂→L₂ (→UnitHom L))
               ex G₂→H₂ H₂→L₂ tt tt pp1 pp2 refl refl )
      G₂ H₂ L₂ R
  where
  J4 : ∀ {ℓ ℓ₂ ℓ₃ ℓ₄ ℓ'} {A : Type ℓ}
       {A₂ : Type ℓ₂} {A₃ : Type ℓ₃} {A₄ : Type ℓ₄}
       {x : A} {x₂ : A₂} {x₃ : A₃} {x₄ : A₄}
       (B : (y : A) (z : A₂) (w : A₃) (u : A₄)
       → x ≡ y → x₂ ≡ z → x₃ ≡ w → x₄ ≡ u → Type ℓ')
    → B x x₂ x₃ x₄ refl refl refl refl
    → (y : A) (z : A₂) (w : A₃) (u : A₄)
       (p : x ≡ y) (q : x₂ ≡ z) (r : x₃ ≡ w) (s : x₄ ≡ u)
    → B y z w u p q r s
  J4 {x = x} {x₂ = x₂} {x₃ = x₃} {x₄ = x₄} B b y z w u =
    J (λ y p → (q : x₂ ≡ z) (r : x₃ ≡ w) (s : x₄ ≡ u) →
      B y z w u p q r s)
      (J (λ z q → (r : x₃ ≡ w) (s : x₄ ≡ u) → B x z w u refl q r s)
        (J (λ w r → (s : x₄ ≡ u) → B x x₂ w u refl refl r s)
          (J (λ u s → B x x₂ x₃ u refl refl refl s) b)))


{-# OPTIONS --cubical #-}

module Data.List.Permute where

open import Prelude
open import Data.Nat
open import Data.Nat.Properties using (_≤ᴮ_)

infixr 5 _∹_∷_
data Premuted {a} (A : Type a) : Type a where
 [] : Premuted A
 _∹_∷_ : ℕ → A → Premuted A → Premuted A

mutual
  merge : Premuted A → Premuted A → Premuted A
  merge [] = id
  merge (n ∹ x ∷ xs) = mergeˡ n x (merge xs)

  mergeˡ : ℕ → A → (Premuted A → Premuted A) → Premuted A → Premuted A
  mergeˡ i x xs [] = i ∹ x ∷ xs []
  mergeˡ i x xs (j ∹ y ∷ ys) = merge⁺ i x xs j y ys (i ≤ᴮ j)

  merge⁺ : ℕ → A → (Premuted A → Premuted A) → ℕ → A → Premuted A → Bool → Premuted A
  merge⁺ i x xs j y ys true  = i ∹ x ∷ xs ((j ∸ i) ∹ y ∷ ys)
  merge⁺ i x xs j y ys false = j ∹ y ∷ mergeˡ ((i ∸ j) ∸ 1) x xs ys

merge-idʳ : (xs : Premuted A) → merge xs [] ≡ xs
merge-idʳ [] = refl
merge-idʳ (i ∹ x ∷ xs) = cong (i ∹ x ∷_) (merge-idʳ xs)

open import Algebra

≤≡-trans : ∀ x y z → (x ≤ᴮ y) ≡ true → (y ≤ᴮ z) ≡ true → (x ≤ᴮ z) ≡ true
≤≡-trans zero y z p₁ p₂ = refl
≤≡-trans (suc x) zero z p₁ p₂ = ⊥-elim (subst (bool ⊤ ⊥) p₁ tt)
≤≡-trans (suc x) (suc y) zero p₁ p₂ = ⊥-elim (subst (bool ⊤ ⊥) p₂ tt)
≤≡-trans (suc x) (suc y) (suc z) p₁ p₂ = ≤≡-trans x y z p₁ p₂

<≡-trans : ∀ x y z → (x ≤ᴮ y) ≡ false → (y ≤ᴮ z) ≡ false → (x ≤ᴮ z) ≡ false
<≡-trans zero y z p₁ p₂ = ⊥-elim (subst (bool ⊥ ⊤) p₁ tt)
<≡-trans (suc x) zero z p₁ p₂ = ⊥-elim (subst (bool ⊥ ⊤) p₂ tt)
<≡-trans (suc x) (suc y) zero p₁ p₂ = p₂
<≡-trans (suc x) (suc y) (suc z) p₁ p₂ = <≡-trans x y z p₁ p₂

≤≡-sub : ∀ i j k → (j ≤ᴮ k) ≡ true → (j ∸ i ≤ᴮ k ∸ i) ≡ true
≤≡-sub zero j k p = p
≤≡-sub (suc i) zero k p = refl
≤≡-sub (suc i) (suc j) zero p = ⊥-elim (subst (bool ⊤ ⊥) p tt)
≤≡-sub (suc i) (suc j) (suc k) p = ≤≡-sub i j k p

≥≡-sub : ∀ i j k → (j ≤ᴮ k) ≡ false → (i ≤ᴮ k) ≡ true → (j ∸ i ≤ᴮ k ∸ i) ≡ false
≥≡-sub i zero k p _ = ⊥-elim (subst (bool ⊥ ⊤) p tt)
≥≡-sub zero (suc j) k p _ = p
≥≡-sub (suc i) (suc j) zero p p₂ = ⊥-elim (subst (bool ⊤ ⊥) p₂ tt)
≥≡-sub (suc i) (suc j) (suc k) p p₂ = ≥≡-sub i j k p p₂

<≡-sub : ∀ i j k → (i ≤ᴮ j) ≡ false → (j ≤ᴮ k) ≡ false → (i ∸ k ∸ 1 ≤ᴮ j ∸ k ∸ 1) ≡ false
<≡-sub zero j k p _ = ⊥-elim (subst (bool ⊥ ⊤) p tt)
<≡-sub (suc i) zero k p p₂ = ⊥-elim (subst (bool ⊥ ⊤) p₂ tt)
<≡-sub (suc i) (suc j) zero p p₂ = p
<≡-sub (suc i) (suc j) (suc k) p p₂ = <≡-sub i j k p p₂

>≡-sub : ∀ i j k → (i ≤ᴮ j) ≡ true → (j ≤ᴮ k) ≡ false → (i ≤ᴮ k) ≡ false → (i ∸ k ∸ 1 ≤ᴮ j ∸ k ∸ 1) ≡ true
>≡-sub i zero    k p₁ p₂ p₃ = ⊥-elim (subst (bool ⊥ ⊤) p₂ tt)
>≡-sub zero (suc j) k p₁ p₂ p₃ = ⊥-elim (subst (bool ⊥ ⊤) p₃ tt)
>≡-sub (suc i) (suc j) zero p₁ p₂ p₃ = p₁
>≡-sub (suc i) (suc j) (suc k) p₁ p₂ p₃ = >≡-sub i j k p₁ p₂ p₃

zero-sub : ∀ n → zero ∸ n ≡ zero
zero-sub zero = refl
zero-sub (suc n) = refl

≤-sub-id : ∀ i j k → (i ≤ᴮ j) ≡ true → k ∸ i ∸ (j ∸ i) ≡ k ∸ j
≤-sub-id zero j k p = refl
≤-sub-id (suc i) zero k p = ⊥-elim (subst (bool ⊤ ⊥) p tt)
≤-sub-id (suc i) (suc j) zero p = zero-sub (j ∸ i)
≤-sub-id (suc i) (suc j) (suc k) p = ≤-sub-id i j k p

lemma₁ : ∀ i j k → (j ≤ᴮ k) ≡ false → i ∸ k ∸ 1 ∸ (j ∸ k ∸ 1) ∸ 1 ≡ i ∸ j ∸ 1
lemma₁ zero j k _ = cong (λ zk → zk ∸ 1 ∸ (j ∸ k ∸ 1) ∸ 1) (zero-sub k) ; cong (_∸ 1) (zero-sub (j ∸ k ∸ 1)) ; sym (cong (_∸ 1) (zero-sub j))
lemma₁ (suc i) zero k p₂ = ⊥-elim (subst (bool ⊥ ⊤) p₂ tt)
lemma₁ (suc i) (suc j) zero p₂ = refl
lemma₁ (suc i) (suc j) (suc k) p₂ = lemma₁ i j k p₂

lemma₂ : ∀ i j k → (i ≤ᴮ j) ≡ true → (j ≤ᴮ k) ≡ false → (i ≤ᴮ k) ≡ false → j ∸ i ≡ j ∸ k ∸ 1 ∸ (i ∸ k ∸ 1)
lemma₂ i zero    k p₁ p₂ p₃ = ⊥-elim (subst (bool ⊥ ⊤) p₂ tt)
lemma₂ zero (suc j) k p₁ p₂ p₃ = ⊥-elim (subst (bool ⊥ ⊤) p₃ tt)
lemma₂ (suc i) (suc j) zero p₁ p₂ p₃ = refl
lemma₂ (suc i) (suc j) (suc k) p₁ p₂ p₃ = lemma₂ i j k p₁ p₂ p₃

{-# TERMINATING #-}
merge-assoc : Associative (merge {A = A})
merge-assoc [] ys zs = refl
merge-assoc (i ∹ x ∷ xs) [] zs = cong (flip merge zs) (merge-idʳ (i ∹ x ∷ xs))
merge-assoc (i ∹ x ∷ xs) (j ∹ y ∷ ys) [] = merge-idʳ (merge (i ∹ x ∷ xs) (j ∹ y ∷ ys)) ; sym (cong (merge (i ∹ x ∷ xs)) (merge-idʳ (j ∹ y ∷ ys)))
merge-assoc (i ∹ x ∷ xs) (j ∹ y ∷ ys) (k ∹ z ∷ zs) with merge-assoc xs (j ∸ i ∹ y ∷ ys) (k ∸ i ∹ z ∷ zs) | merge-assoc (i ∸ k ∸ 1 ∹ x ∷ xs) (j ∸ k ∸ 1 ∹ y ∷ ys) zs | (merge-assoc (i ∸ j ∸ 1 ∹ x ∷ xs) ys (k ∸ j ∹ z ∷ zs)) | i ≤ᴮ j | inspect (i ≤ᴮ_) j | j ≤ᴮ k | inspect (j ≤ᴮ_) k
merge-assoc (i ∹ x ∷ xs) (j ∹ y ∷ ys) (k ∹ z ∷ zs) | r | _ | _ | true  | 〖 ij 〗 | true  | 〖 jk 〗 = cong (merge⁺ i x (merge (merge xs (j ∸ i ∹ y ∷ ys))) k z zs) (≤≡-trans i j k ij jk) ;
                                                                                cong (i ∹ x ∷_) (r ;
                                                                                cong (merge xs) (cong (merge⁺ (j ∸ i) y (merge ys) (k ∸ i) z zs) (≤≡-sub i j k jk) ; cong (λ kij → j ∸ i ∹ y ∷ merge ys (kij ∹ z ∷ zs)) (≤-sub-id i j k ij))) ;
                                                                                cong (merge⁺ i x (merge xs) j y (merge ys (k ∸ j ∹ z ∷ zs))) (sym ij)
merge-assoc (i ∹ x ∷ xs) (j ∹ y ∷ ys) (k ∹ z ∷ zs) | _ | r | _ | false | 〖 ij 〗 | false | 〖 jk 〗 = cong (merge⁺ j y (merge (mergeˡ (i ∸ j ∸ 1) x (merge xs) ys)) k z zs ) jk ;
                                                                                cong (k ∹ z ∷_) (cong (λ s → mergeˡ (j ∸ k ∸ 1) y (merge (mergeˡ s x (merge xs) ys)) zs) (sym (lemma₁ i j k jk)) ;
                                                                                cong (λ s → merge (merge⁺ (i ∸ k ∸ 1) x (merge xs) (j ∸ k ∸ 1) y ys s) zs) (sym (<≡-sub i j k ij jk)) ;
                                                                                r) ;
                                                                                cong (merge⁺ i x (merge xs) k z (mergeˡ (j ∸ k ∸ 1) y (merge ys) zs)) (sym (<≡-trans i j k ij jk))
merge-assoc (i ∹ x ∷ xs) (j ∹ y ∷ ys) (k ∹ z ∷ zs) | _ | _ | r | false | 〖 ij 〗 | true  | 〖 jk 〗 = cong (merge⁺ j y (merge (mergeˡ (i ∸ j ∸ 1) x (merge xs) ys)) k z zs) jk ;
                                                                                cong (j ∹ y ∷_) r ;
                                                                                cong (merge⁺ i x (merge xs) j y (merge ys (k ∸ j ∹ z ∷ zs))) (sym ij)
merge-assoc (i ∹ x ∷ xs) (j ∹ y ∷ ys) (k ∹ z ∷ zs) | _ | _ | _ | true  | ij | false | jk with i ≤ᴮ k | inspect (i ≤ᴮ_) k
merge-assoc (i ∹ x ∷ xs) (j ∹ y ∷ ys) (k ∹ z ∷ zs) | _ | r | _ | true | 〖 ij 〗 | false | 〖 jk 〗 | false | 〖 ik 〗 = cong (k ∹ z ∷_) ((cong (λ s → mergeˡ (i ∸ k ∸ 1) x (merge (merge xs (s ∹ y ∷ ys))) zs) (lemma₂ i j k ij jk ik) ;
                                                                                cong (λ c → merge (merge⁺ (i ∸ k ∸ 1) x (merge xs) (j ∸ k ∸ 1) y ys c) zs) (sym (>≡-sub i j k ij jk ik ))) ;
                                                                                r )
merge-assoc (i ∹ x ∷ xs) (j ∹ y ∷ ys) (k ∹ z ∷ zs) | r | _ | _ | true | 〖 ij 〗 | false | 〖 jk 〗 | true  | 〖 ik 〗 = cong (i ∹ x ∷_) (r ;
                                                                                cong (merge xs) (cong (merge⁺ (j ∸ i) y (merge ys) (k ∸ i) z zs) (≥≡-sub i j k jk ik) ;
                                                                                cong (λ s → k ∸ i ∹ z ∷ mergeˡ s y (merge ys) zs) (cong (_∸ 1) (≤-sub-id i k j ik)) ))

open import Data.List

index : List ℕ → List A → List (Premuted A)
index _  []       = []
index [] (x ∷ xs) = (0 ∹ x ∷ []) ∷ index [] xs
index (i ∷ is) (x ∷ xs) = (i ∹ x ∷ []) ∷ index is xs

unindex : Premuted A → List A
unindex [] = []
unindex (_ ∹ x ∷ xs) = x ∷ unindex xs

open import TreeFold

shuffle : List ℕ → List A → List A
shuffle is = unindex ∘ treeFold merge [] ∘ index is

open import Data.List.Syntax

e : List ℕ
e = shuffle [ 0 , 1 , 0 ] [ 1 , 2 , 3 ]


open import Data.Graph

module Data.Graph.Path.Cut {ℓᵥ ℓₑ} (g : FiniteGraph ℓᵥ ℓₑ) where

open import Data.Fin as Fin using (Fin; zero; suc)
open import Data.Fin.Properties as Fin-Props using (pigeonhole)
open import Data.List as List using (List; []; _∷_)
open import Data.List.Any as Any using (Any; here; there)
open import Data.List.Membership.Propositional as ∈L renaming (_∈_ to _∈L_)
open import Data.Nat as ℕ
open import Data.Nat.Properties as ℕ-Props
open import Data.Product as Σ
open import Data.Sum as ⊎
open import Finite
import Finite.Pigeonhole
open import Function
open import Induction.Nat
open import Induction.WellFounded
import Level as ℓ
open import Relation.Binary.PropositionalEquality
open import Relation.Binary.PreorderReasoning ≤-preorder
open import Relation.Nullary hiding (module Dec)
open import Relation.Nullary.Decidable as Dec
open import Relation.Nullary.Negation

open FiniteGraph g
open IsFinite

infix 3 _∈_
data _∈_ x : ∀ {a b n} → Path a b n → Set where
  here : ∀ {b c n} {e : Edge x b} {p : Path b c n} → x ∈ e ∷ p
  there : ∀ {a b c n} {e : Edge a b} {p : Path b c n} → x ∈ p → x ∈ e ∷ p

infix 3 _∈?_
_∈?_ : ∀ {a b n} x (p : Path a b n) → Dec (x ∈ p)
x ∈? [] = no λ ()
_∈?_ {a} x (e ∷ p) =
  case decEqVertex a x of λ where
    (yes refl) → yes here
    (no a≢x) →
      case x ∈? p of λ where
        (yes i) → yes (there i)
        (no ¬i) →
          no λ where
            here → contradiction refl a≢x
            (there i) → contradiction i ¬i

index : ∀ {a b x n} {p : Path a b n} → x ∈ p → Fin n
index here = zero
index (there i) = suc (index i)

lookup : ∀ {a b n} → Path a b n → Fin n → Vertex
lookup {a} (e ∷ p) zero = a
lookup (e ∷ p) (suc i) = lookup p i

∈-lookup : ∀ {a b n} {p : Path a b n} (i : Fin n) → lookup p i ∈ p
∈-lookup {p = []} ()
∈-lookup {p = e ∷ p} zero = here
∈-lookup {p = e ∷ p} (suc i) = there (∈-lookup i)

finiteIndex : ∀ {a b n} (p : Path a b n) → Fin n → Fin (size vertexFinite)
finiteIndex p = Any.index ∘ membership vertexFinite ∘ lookup p

prefixLength : ∀ {a b x n} {p : Path a b n} → x ∈ p → ℕ
prefixLength here = zero
prefixLength (there i) = suc (prefixLength i)

suffixLength : ∀ {a b x n} {p : Path a b n} → x ∈ p → ℕ
suffixLength {n = n} here = n
suffixLength (there i) = suffixLength i

split : ∀ {a b x n} {p : Path a b n}
  (i : x ∈ p) →
  Path a x (prefixLength i) × Path x b (suffixLength i)
split {p = p} here = [] , p
split {p = e ∷ p} (there i) = Σ.map₁ (e ∷_) (split i)

prefix : ∀ {a b x n} {p : Path a b n} (i : x ∈ p) → Path a x (prefixLength i)
prefix = proj₁ ∘ split

suffix : ∀ {a b x n} {p : Path a b n} (i : x ∈ p) → Path x b (suffixLength i)
suffix = proj₂ ∘ split

splitLengthsAddUp : ∀ {a b x n} {p : Path a b n}
  (i : x ∈ p) →
  n ≡ prefixLength i + suffixLength i
splitLengthsAddUp here = refl
splitLengthsAddUp (there i) = cong suc (splitLengthsAddUp i)

data Repeats : ∀ {a b n} → Path a b n → Set where
  here : ∀ {a b c n} {e : Edge a b} {p : Path b c n} → a ∈ p → Repeats (e ∷ p)
  there : ∀ {a b c n} {e : Edge a b} {p : Path b c n} → Repeats p → Repeats (e ∷ p)

repeats? : ∀ {a b n} (p : Path a b n) → Dec (Repeats p)
repeats? [] = no λ ()
repeats? {a} (e ∷ p) =
  case a ∈? p of λ where
    (yes i) → yes (here i)
    (no ¬i) →
      case repeats? p of λ where
        (yes r) → yes (there r)
        (no ¬r) →
          no λ where
            (here i) → contradiction i ¬i
            (there r) → contradiction r ¬r

Acyclic : ∀ {a b n} → Path a b n → Set
Acyclic p = ¬ Repeats p

acyclic? : ∀ {a b n} (p : Path a b n) → Dec (Acyclic p)
acyclic? = ¬? ∘ repeats?

data Segmented a b : ℕ → Set (ℓᵥ ℓ.⊔ ℓₑ) where
  _◄_◄_ : ∀ {x m n l} →
    Path a x m →
    Path x x (suc n) →
    Path x b l →
    Segmented a b (m + suc n + l)

segment : ∀ {a b n} {p : Path a b n} → Repeats p → Segmented a b n
segment {p = []} ()
segment {p = e ∷ p} (here i) rewrite splitLengthsAddUp i = [] ◄ e ∷ prefix i ◄ suffix i
segment {p = e ∷ p} (there r) =
  case segment r of λ where
    (p₁ ◄ p₂ ◄ p₃) → (e ∷ p₁) ◄ p₂ ◄ p₃

cutLoop< : ∀ {a b n} {p : Path a b n} → Repeats p → Path< a b n
cutLoop< r = case segment r of λ where (_◄_◄_ {m = m} p₁ p₂ p₃) → -, lengthLem m , p₁ ++ p₃
  where
    lengthLem : ∀ x {y z} → suc (x + z) ≤ x + suc y + z
    lengthLem zero = s≤s (n≤m+n _ _)
    lengthLem (suc x) = s≤s (lengthLem x)

indicesLoop : ∀ {a b n i j} {p : Path a b n} → i ≢ j → lookup p i ≡ lookup p j → Repeats p
indicesLoop {i = zero} {zero} {e ∷ p} z≢z eq = contradiction refl z≢z
indicesLoop {i = zero} {suc j} {e ∷ p} _ refl = here (∈-lookup j)
indicesLoop {i = suc i} {zero} {e ∷ p} _ refl = here (∈-lookup i)
indicesLoop {i = suc i} {suc j} {e ∷ p} si≢sj eq = there (indicesLoop (si≢sj ∘ cong suc) eq)

findLoop : ∀ {a b n} (p : Path a b n) → n > size vertexFinite → Repeats p
findLoop p gt =
  let i , j , i≢j , eq = pigeonhole gt (finiteIndex p) in
    indicesLoop i≢j (indexOf-injective vertexFinite eq)

acyclic-length-≤ : ∀ {a b n} (p : Path a b n) → Acyclic p → n ≤ size vertexFinite
acyclic-length-≤ {n = n} p ¬r =
  case n ≤? size vertexFinite of λ where
    (yes le) → le
    (no ¬le) → contradiction (findLoop p (≰⇒> ¬le)) ¬r

shortenPath : ∀ {a b n} → Path a b n → n > size vertexFinite → Path< a b n
shortenPath p = cutLoop< ∘ findLoop p

shortenPathEnough : ∀ {a b n}
  (p : Path a b n) →
  n > size vertexFinite →
  Path≤ a b (size vertexFinite)
shortenPathEnough = <-rec _ wfRec _
  where
    wfRec =
      λ n rec p gt →
        let n′ , le , p′ = shortenPath p gt in
          case size vertexFinite <? n′ of λ where
            (yes n′>v) → rec _ le p′ n′>v
            (no n′≯v) → -, ≮⇒≥ n′≯v , p′

shortEnoughPath : ∀ {a b n} (p : Path a b n) → Path≤ a b (size vertexFinite)
shortEnoughPath {n = n} p =
  case size vertexFinite <? n of λ where
    (yes n>v) → shortenPathEnough p n>v
    (no n≯v) → -, ≮⇒≥ n≯v , p

cutAllLoops : ∀ {a b n} →
  (p : Path a b n) →
  Repeats p →
  ∃ λ (p : Path≤ a b n) → ¬ Repeats (proj₂ (proj₂ p))
cutAllLoops = <-rec _ wfRec _
  where
    wfRec = λ x rec p r →
      case cutLoop< r of λ where
        (n′ , lt , p′) →
          case repeats? p′ of λ where
            (yes r) →
              case rec _ lt p′ r of λ where
                ((n′′ , le′′ , p′′) , ¬r′′) →
                  (n′′ , ≤-trans le′′ (<⇒≤ lt) , p′′) , ¬r′′
            (no ¬r) → (n′ , <⇒≤ lt , p′) , ¬r

acyclicPath : ∀ {a b n} →
  (p : Path a b n) →
  ∃ λ (p : Path≤ a b n) → ¬ Repeats (proj₂ (proj₂ p))
acyclicPath p =
  case repeats? p of λ where
    (yes r) → cutAllLoops p r
    (no ¬r) → (-, ≤-refl , p) , ¬r

minimalPath : ∀ {a b n} →
  Path a b n →
  ∃ λ (p : Path≤ a b (size vertexFinite)) → ¬ Repeats (proj₂ (proj₂ p))
minimalPath p =
  let
    x , x≤max , p′ = shortEnoughPath p
    (y , y≤x , p′′) , ¬r = acyclicPath p′
  in
    (y , ≤-trans y≤x x≤max , p′′) , ¬r