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

module rational-numbers where

import integers
open integers public

--------------------------------------------------------------------------------

{- We introduce the type of non-zero integers. -}

ℤ\0 : UU lzero
ℤ\0 = Σ ℤ (λ k → ¬ (Id zero-ℤ k))

int-ℤ\0 : ℤ\0 → ℤ
int-ℤ\0 = pr1

{- We first show that multiplication by a non-zero integer is an injective
   function. -}

mul-ℤ-ℤ\0 : ℤ → ℤ\0 → ℤ
mul-ℤ-ℤ\0 x y = mul-ℤ x (int-ℤ\0 y)

mul-ℤ\0 : ℤ\0 → ℤ\0 → ℤ\0
mul-ℤ\0 (pair x Hx) (pair y Hy) = pair (mul-ℤ x y) (neq-zero-mul-ℤ x y Hx Hy)

postulate is-emb-mul-ℤ-ℤ\0 : (y : ℤ\0) → is-emb (λ x → mul-ℤ-ℤ\0 x y)

is-injective-mul-ℤ-ℤ\0 :
  (y : ℤ\0) (x1 x2 : ℤ) → Id (mul-ℤ-ℤ\0 x1 y) (mul-ℤ-ℤ\0 x2 y) → Id x1 x2
is-injective-mul-ℤ-ℤ\0 y x1 x2 = inv-is-equiv (is-emb-mul-ℤ-ℤ\0 y x1 x2)

--------------------------------------------------------------------------------

{- We introduce the prerational numbers. -}

ℚ' : UU lzero
ℚ' = ℤ × ℤ\0

--------------------------------------------------------------------------------

{- We introduce the equivalence relation on the prerational numbers that will
   define the rational numbers. -}

equiv-ℚ' : ℚ' → ℚ' → UU lzero
equiv-ℚ' (pair x1 x2) (pair y1 y2) =
  Id (mul-ℤ-ℤ\0 x1 y2) (mul-ℤ-ℤ\0 y1 x2)

refl-equiv-ℚ' :
  (x : ℚ') → equiv-ℚ' x x
refl-equiv-ℚ' (pair x1 x2) = refl

symmetric-equiv-ℚ' :
  (x y : ℚ') → equiv-ℚ' x y → equiv-ℚ' y x
symmetric-equiv-ℚ' (pair x1 x2) (pair y1 y2) = inv

transitive-equiv-ℚ' :
  (x y z : ℚ') →
  equiv-ℚ' x y → equiv-ℚ' y z → equiv-ℚ' x z
transitive-equiv-ℚ' (pair x1 x2) (pair y1 y2) (pair z1 z2) p q =
  is-injective-mul-ℤ-ℤ\0 y2
    ( mul-ℤ-ℤ\0 x1 z2)
    ( mul-ℤ-ℤ\0 z1 x2)
    ( ( associative-mul-ℤ x1 (int-ℤ\0 z2) (int-ℤ\0 y2)) ∙
      ( ( ap ( mul-ℤ x1)
             ( commutative-mul-ℤ (int-ℤ\0 z2) (int-ℤ\0 y2))) ∙
        ( ( inv (associative-mul-ℤ x1 (int-ℤ\0 y2) (int-ℤ\0 z2))) ∙
          ( ( ap ( λ t → mul-ℤ t (int-ℤ\0 z2))
                 ( p ∙ (commutative-mul-ℤ y1 (int-ℤ\0 x2)))) ∙
            ( ( associative-mul-ℤ (int-ℤ\0 x2) y1 (int-ℤ\0 z2)) ∙
              ( ( ap (mul-ℤ (int-ℤ\0 x2)) q) ∙
                ( ( inv (associative-mul-ℤ (int-ℤ\0 x2) z1 (int-ℤ\0 y2))) ∙
                  ( ap ( λ t → mul-ℤ-ℤ\0 t y2)
                       ( commutative-mul-ℤ (int-ℤ\0 x2) z1)))))))))

--------------------------------------------------------------------------------

{- We define addition on the prerational numbers. -}

add-ℚ' : ℚ' → ℚ' → ℚ'
add-ℚ' (pair x1 x2) (pair y1 y2) =
  pair (add-ℤ (mul-ℤ-ℤ\0 x1 y2) (mul-ℤ-ℤ\0 y1 x2)) (mul-ℤ\0 x2 y2)

equiv-add-ℚ' :
  (x y x' y' : ℚ') → equiv-ℚ' x x' → equiv-ℚ' y y' →
  equiv-ℚ' (add-ℚ' x y) (add-ℚ' x' y')
equiv-add-ℚ'
  ( pair x1 (pair x2 Hx))
  ( pair y1 (pair y2 Hy))
  ( pair x1' (pair x2' Hx'))
  ( pair y1' (pair y2' Hy')) e f =
  ( right-distributive-mul-add-ℤ
    ( mul-ℤ x1 y2)
    ( mul-ℤ y1 x2)
    ( mul-ℤ x2' y2')) ∙
  ( ( ap-add-ℤ
      ( ( interchange-2-3-mul-ℤ {x1}) ∙
        ( ( ap (λ t → mul-ℤ t (mul-ℤ y2 y2')) e) ∙
          ( move-four-mul-ℤ {x1'})))
      ( ( move-five-mul-ℤ {y1}) ∙
        ( ( ap (mul-ℤ (mul-ℤ x2 x2')) f) ∙
          ( interchange-1-3-mul-ℤ {x2} {x2'} {y1'})))) ∙
    ( inv
      ( right-distributive-mul-add-ℤ
        ( mul-ℤ x1' y2')
        ( mul-ℤ y1' x2')
        ( mul-ℤ x2 y2))))

--------------------------------------------------------------------------------

{- We define multiplication on the prerational numbers. -}

mul-ℚ' : ℚ' → ℚ' → ℚ'
mul-ℚ' (pair x1 x2) (pair y1 y2) = pair (mul-ℤ x1 y1) (mul-ℤ\0 x2 y2)

ap-mul-ℤ :
  {a b a' b' : ℤ} →
  Id a a' → Id b b' → Id (mul-ℤ a b) (mul-ℤ a' b')
ap-mul-ℤ refl refl = refl

equiv-mul-ℚ' :
  (x y x' y' : ℚ') → equiv-ℚ' x x' → equiv-ℚ' y y' →
  equiv-ℚ' (mul-ℚ' x y) (mul-ℚ' x' y')
equiv-mul-ℚ'
  ( pair x1 (pair x2 Hx))
  ( pair y1 (pair y2 Hy))
  ( pair x1' (pair x2' Hx'))
  ( pair y1' (pair y2' Hy')) e f =
  ( interchange-2-3-mul-ℤ {x1} {y1} {x2'}) ∙
  ( ( ap-mul-ℤ e f) ∙
    ( interchange-2-3-mul-ℤ {x1'}))


open import Agda.Builtin.Bool
open import Agda.Builtin.Equality
open import Agda.Builtin.List
open import Agda.Builtin.Reflection renaming (bindTC to _>>=_)
open import Agda.Builtin.Unit

postulate
  @0 A : Set

@0 _ : @0 Set → (Set → Set) → Set
_ = λ @0 where
  A G → G A

@0 _ : @0 Set → (Set → Set) → Set
_ = λ @0 { A G → G A }

data ⊥ : Set where

@0 _ : @0 ⊥ → Set
_ = λ @0 { () }

@0 _ : @0 ⊥ → Set
_ = λ @0 where
  ()

@0 _ : @0 ⊥ → Set
_ =
  λ @0
    @ω
    @erased
    @plenty
    @(tactic (λ _ → Set))
    @irr
    @irrelevant
    @shirr
    @shape-irrelevant
    @relevant
    @♭
    @flat
    @notlock
    @lock
    @tick where
    ()

F : ⊤ → ⊤
F = λ { tt → tt }

macro

  `F : Term → TC ⊤
  `F goal = do
    F ← withNormalisation true (quoteTC F)
    F ← quoteTC F
    unify goal F

_ :
  `F ≡
  pat-lam
    (clause []
      (arg (arg-info visible relevant) (con (quote tt) []) ∷ [])
      (con (quote tt) []) ∷
     [])
    []
_ = refl


module _ where

open import Agda.Builtin.Reflection
open import Agda.Builtin.Bool
open import Agda.Builtin.Unit
open import Agda.Builtin.String
open import Common.Prelude

_<_ = primQNameLess

True : Bool → Set
True true  = ⊤
True false = ⊥

zzz aaa : ⊤
zzz = _
aaa = _

⊥-elim : {A : Set} → ⊥ → A
⊥-elim ()

check : (x y : Name) → True (x < y) → String
check x y prf with x < y
check x y prf | true = "A-ok"
check x y prf | false = ⊥-elim prf

main : IO Unit
main = putStrLn (check (quote zzz) (quote aaa) _)


import Level as L
open import Relation.Binary
open import Relation.Binary.PropositionalEquality
open import Data.Product
open import Data.Empty
open import Data.Nat
open import Data.Nat.Properties
open import RevMachine

module RevNoRepeat {ℓ} (M : RevMachine {ℓ}) where
  infix  99 _ᵣₑᵥ
  infixr 10 _∷_
  infixr 10 _++↦_
  open RevMachine.RevMachine M

  is-stuck : State → Set _
  is-stuck st = ∄[ st' ] (st ↦ st')

  is-initial : State → Set _
  is-initial st = ∄[ st' ] (st' ↦ st)

  data _↦ᵣₑᵥ_ : State → State → Set (L.suc ℓ) where
    _ᵣₑᵥ : ∀ {st₁ st₂} → st₁ ↦ st₂ → st₂ ↦ᵣₑᵥ st₁

  data _↦*_ : State → State → Set (L.suc ℓ) where
    ◾ : {st : State} → st ↦* st
    _∷_ : {st₁ st₂ st₃ : State} → st₁ ↦ st₂ → st₂ ↦* st₃ → st₁ ↦* st₃

  _++↦_ : {st₁ st₂ st₃ : State} → st₁ ↦* st₂ → st₂ ↦* st₃ → st₁ ↦* st₃
  ◾ ++↦ st₁↦*st₂ = st₁↦*st₂
  (st₁↦st₁' ∷ st₁'↦*st₂) ++↦ st₂'↦*st₃ = st₁↦st₁' ∷ (st₁'↦*st₂ ++↦ st₂'↦*st₃)

  len↦ : ∀ {st st'} → st ↦* st' → ℕ
  len↦ ◾ = 0
  len↦ (_ ∷ r) = 1 + len↦ r

  data _↦ᵣₑᵥ*_ : State → State → Set (L.suc ℓ) where
    ◾ : ∀ {st} → st ↦ᵣₑᵥ* st
    _∷_ : ∀ {st₁ st₂ st₃} → st₁ ↦ᵣₑᵥ st₂ → st₂ ↦ᵣₑᵥ* st₃ → st₁ ↦ᵣₑᵥ* st₃

  _++↦ᵣₑᵥ_ : ∀ {st₁ st₂ st₃} → st₁ ↦ᵣₑᵥ* st₂ → st₂ ↦ᵣₑᵥ* st₃ → st₁ ↦ᵣₑᵥ* st₃
  ◾ ++↦ᵣₑᵥ st₁↦ᵣₑᵥ*st₂ = st₁↦ᵣₑᵥ*st₂
  (st₁↦ᵣₑᵥst₁' ∷ st₁'↦ᵣₑᵥ*st₂) ++↦ᵣₑᵥ st₂'↦ᵣₑᵥ*st₃ = st₁↦ᵣₑᵥst₁' ∷ (st₁'↦ᵣₑᵥ*st₂ ++↦ᵣₑᵥ st₂'↦ᵣₑᵥ*st₃)

  Rev↦ : ∀ {st₁ st₂} → st₁ ↦* st₂ → st₂ ↦ᵣₑᵥ* st₁
  Rev↦ ◾ = ◾
  Rev↦ (r ∷ rs) = Rev↦ rs ++↦ᵣₑᵥ ((r ᵣₑᵥ) ∷ ◾)

  Rev↦ᵣₑᵥ : ∀ {st₁ st₂} → st₁ ↦ᵣₑᵥ* st₂ → st₂ ↦* st₁
  Rev↦ᵣₑᵥ ◾ = ◾
  Rev↦ᵣₑᵥ ((r ᵣₑᵥ) ∷ rs) = Rev↦ᵣₑᵥ rs ++↦ (r ∷ ◾)

  deterministic* : ∀ {st st₁ st₂} → st ↦* st₁ → st ↦* st₂
                 → is-stuck st₁ → is-stuck st₂
                 → st₁ ≡ st₂
  deterministic* ◾ ◾ stuck₁ stuck₂ = refl
  deterministic* ◾ (r ∷ ↦*₂) stuck₁ stuck₂ = ⊥-elim (stuck₁ (_ , r))
  deterministic* (r ∷ ↦*₁) ◾ stuck₁ stuck₂ = ⊥-elim (stuck₂ (_ , r))
  deterministic* (r₁ ∷ ↦*₁) (r₂ ∷ ↦*₂) stuck₁ stuck₂ with deterministic r₁ r₂
  ... | refl = deterministic* ↦*₁ ↦*₂ stuck₁ stuck₂

  deterministic*' : ∀ {st st₁ st'} → (rs₁ : st ↦* st₁) → (rs : st ↦* st')
                  → is-stuck st'
                  → Σ[ rs' ∈ st₁ ↦* st' ] (len↦ rs ≡ len↦ rs₁ + len↦ rs')
  deterministic*' ◾ rs stuck = rs , refl
  deterministic*' (r ∷ rs₁) ◾ stuck = ⊥-elim (stuck (_ , r))
  deterministic*' (r ∷ rs₁) (r' ∷ rs) stuck with deterministic r r'
  ... | refl with deterministic*' rs₁ rs stuck
  ... | (rs' , eq) = rs' , cong suc eq

  deterministicᵣₑᵥ* : ∀ {st st₁ st₂} → st ↦ᵣₑᵥ* st₁ → st ↦ᵣₑᵥ* st₂
                    → is-initial st₁ → is-initial st₂
                    → st₁ ≡ st₂
  deterministicᵣₑᵥ* ◾ ◾ initial₁ initial₂ = refl
  deterministicᵣₑᵥ* ◾ (r ᵣₑᵥ ∷ ↦ᵣₑᵥ*₂) initial₁ initial₂ = ⊥-elim (initial₁ (_ , r))
  deterministicᵣₑᵥ* (r ᵣₑᵥ ∷ ↦ᵣₑᵥ*₁) ◾ initial₁ initial₂ = ⊥-elim (initial₂ (_ , r))
  deterministicᵣₑᵥ* (r₁ ᵣₑᵥ ∷ ↦ᵣₑᵥ*₁) (r₂ ᵣₑᵥ ∷ ↦ᵣₑᵥ*₂) initial₁ initial₂ with deterministicᵣₑᵥ r₁ r₂
  ... | refl = deterministicᵣₑᵥ* ↦ᵣₑᵥ*₁ ↦ᵣₑᵥ*₂ initial₁ initial₂

  data _↦[_]_ : State → ℕ → State → Set (L.suc ℓ) where
    ◾ : ∀ {st} → st ↦[ 0 ] st
    _∷_ : ∀ {st₁ st₂ st₃ n} → st₁ ↦ st₂ → st₂ ↦[ n ] st₃ → st₁ ↦[ suc n ] st₃

  _++↦ⁿ_ : ∀ {st₁ st₂ st₃ n m} → st₁ ↦[ n ] st₂ → st₂ ↦[ m ] st₃ → st₁ ↦[ n + m ] st₃
  ◾ ++↦ⁿ st₁↦*st₂ = st₁↦*st₂
  (st₁↦st₁' ∷ st₁'↦*st₂) ++↦ⁿ st₂'↦*st₃ = st₁↦st₁' ∷ (st₁'↦*st₂ ++↦ⁿ st₂'↦*st₃)

  data _↦ᵣₑᵥ[_]_ : State → ℕ → State → Set (L.suc ℓ) where
    ◾ : ∀ {st} → st ↦ᵣₑᵥ[ 0 ] st
    _∷_ : ∀ {st₁ st₂ st₃ n} → st₁ ↦ᵣₑᵥ st₂ → st₂ ↦ᵣₑᵥ[ n ] st₃ → st₁ ↦ᵣₑᵥ[ suc n ] st₃

  _++↦ᵣₑᵥⁿ_ : ∀ {st₁ st₂ st₃ n m} → st₁ ↦ᵣₑᵥ[ n ] st₂ → st₂ ↦ᵣₑᵥ[ m ] st₃ → st₁ ↦ᵣₑᵥ[ n + m ] st₃
  ◾ ++↦ᵣₑᵥⁿ st₁↦*st₂ = st₁↦*st₂
  (st₁↦st₁' ∷ st₁'↦*st₂) ++↦ᵣₑᵥⁿ st₂'↦*st₃ = st₁↦st₁' ∷ (st₁'↦*st₂ ++↦ᵣₑᵥⁿ st₂'↦*st₃)

  deterministicⁿ : ∀ {st st₁ st₂ n} → st ↦[ n ] st₁ → st ↦[ n ] st₂
                 → st₁ ≡ st₂
  deterministicⁿ ◾ ◾ = refl
  deterministicⁿ (r₁ ∷ rs₁) (r₂ ∷ rs₂) with deterministic r₁ r₂
  ... | refl = deterministicⁿ rs₁ rs₂

  deterministicᵣₑᵥⁿ : ∀ {st st₁ st₂ n} → st ↦ᵣₑᵥ[ n ] st₁ → st ↦ᵣₑᵥ[ n ] st₂
                 → st₁ ≡ st₂
  deterministicᵣₑᵥⁿ ◾ ◾ = refl
  deterministicᵣₑᵥⁿ (r₁ ᵣₑᵥ ∷ rs₁) (r₂ ᵣₑᵥ ∷ rs₂) with deterministicᵣₑᵥ r₁ r₂
  ... | refl = deterministicᵣₑᵥⁿ rs₁ rs₂

  private
    split↦ⁿ : ∀ {st st'' n m} → st ↦ᵣₑᵥ[ n + m ] st''
            → ∃[ st' ] (st ↦ᵣₑᵥ[ n ] st' × st' ↦ᵣₑᵥ[ m ] st'')
    split↦ⁿ {n = 0} {m} rs = _ , ◾ , rs
    split↦ⁿ {n = suc n} {m} (r ∷ rs) with split↦ⁿ {n = n} {m} rs
    ... | st' , st↦ⁿst' , st'↦ᵐst'' = st' , (r ∷ st↦ⁿst') , st'↦ᵐst''

    diff : ∀ {n m} → n < m → ∃[ k ] (0 < k × n + k ≡ m)
    diff {0} {(suc m)} (s≤s z≤n) = suc m , s≤s z≤n , refl
    diff {(suc n)} {(suc (suc m))} (s≤s (s≤s n≤m)) with diff {n} {suc m} (s≤s n≤m)
    ... | k , 0<k , eq = k , 0<k , cong suc eq

  Revⁿ : ∀ {st st' n} → st ↦[ n ] st' → st' ↦ᵣₑᵥ[ n ] st
  Revⁿ {n = 0} ◾ = ◾
  Revⁿ {n = suc n} (r ∷ rs) rewrite +-comm 1 n = Revⁿ rs ++↦ᵣₑᵥⁿ (r ᵣₑᵥ ∷ ◾)

  NoRepeat : ∀ {st₀ stₙ stₘ n m}
           → is-initial st₀
           → n < m
           → st₀ ↦[ n ] stₙ
           → st₀ ↦[ m ] stₘ
           → stₙ ≢ stₘ
  NoRepeat {stₙ = st} {_} {m} st₀-is-init n<m st₀↦ᵐst st₀↦ᵐ⁺ᵏ⁺¹st refl with diff n<m
  ... | suc k , 0<k , refl with (Revⁿ st₀↦ᵐst , Revⁿ st₀↦ᵐ⁺ᵏ⁺¹st)
  ... | st'↦ᵐst₀' , st'↦ᵐ⁺ᵏ⁺¹st₀' with split↦ⁿ {n = m} {suc k} st'↦ᵐ⁺ᵏ⁺¹st₀'
  ... | st′ , st'↦ᵐst′ , st′↦ᵏ⁺¹st₀' with deterministicᵣₑᵥⁿ st'↦ᵐst₀' st'↦ᵐst′
  ... | refl with st′↦ᵏ⁺¹st₀'
  ... | r ᵣₑᵥ ∷ rs = ⊥-elim (st₀-is-init (_ , r))


------------------------------------------------------------------------
-- One can construct combinators which do not preserve equality
------------------------------------------------------------------------

module TotalParserCombinators.NotACongruence where

open import Codata.Musical.Notation
open import Data.Bool
open import Data.List
open import Function
open import Function.Equality
open import Function.Inverse as Inv
open import Relation.Binary.PropositionalEquality using (_≡_; refl)
open import Relation.Nullary

open import TotalParserCombinators.Congruence using (_≅P_; _∷_)
open import TotalParserCombinators.Derivative
open import TotalParserCombinators.Laws
open import TotalParserCombinators.Lib
open import TotalParserCombinators.Parser
open import TotalParserCombinators.Semantics as S using (_∈_·_; _≅_)

-- It is easy to construct a combinator which does not preserve
-- equality. All it takes is to inspect the input combinator's
-- structure (or that of its index), and make some suitable decision
-- based on this information. Examples:

module Example₁ where

  -- A combinator which removes ambiguity.

  unambiguous : ∀ {Tok R xs} →
                Parser Tok R xs → Parser Tok R (take 1 xs)
  unambiguous {xs = xs} p =
      token >>= (λ t → ♯ unambiguous (D t p))
    ∣ return⋆ (take 1 xs)

  -- The following two parsers are (parser) equal.

  p₁ : Parser Bool Bool (true ∷ (false ∷ []))
  p₁ = return true ∣ return false

  p₂ : Parser Bool Bool (false ∷ (true ∷ []))
  p₂ = return false ∣ return true

  equal : p₁ ≅P p₂
  equal = AdditiveMonoid.commutative (return true) (return false)

  -- However, unambiguous does not respect this equality.

  unambiguous-is-not-a-congruence :
    ¬ (unambiguous p₁ ≅ unambiguous p₂)
  unambiguous-is-not-a-congruence eq =
    case Inverse.to eq ⟨$⟩ S.∣-right [] (S.∣-left S.return) of λ
      { (S.∣-right .[] (S.∣-left               ()))
      ; (S.∣-right .[] (S.∣-right .([ false ]) ()))
      ; (S.∣-left []∈) → helper refl []∈
      }
    where
    helper : ∀ {s} {f : Bool → List Bool}
               {p : ∀ b → ∞ (Parser Bool Bool (f b))} →
               s ≡ [] →
             ¬ (true ∈ token >>= p · s)
    helper () (S._>>=_ S.token _)

module Example₂ where

  -- A combinator which returns true (without consuming input) if its
  -- argument is "fail", and false otherwise.

  is-fail-bag : ∀ {Tok R xs} → Parser Tok R xs → List Bool
  is-fail-bag fail = [ true  ]
  is-fail-bag _    = [ false ]

  is-fail : ∀ {Tok R xs}
            (p : Parser Tok R xs) → Parser Tok Bool (is-fail-bag p)
  is-fail fail             = return true
  is-fail (return x)       = return false
  is-fail token            = return false
  is-fail (p₁ ∣ p₂)        = return false
  is-fail (f <$> p)        = return false
  is-fail (p₁ ⊛ p₂)        = return false
  is-fail (p₁ >>= p₂)      = return false
  is-fail (nonempty p)     = return false
  is-fail (cast xs₁≈xs₂ p) = return false

  -- The following parsers are equal, but is-fail treats them
  -- differently.

  p₁ : Parser Bool Bool []
  p₁ = fail

  p₂ : Parser Bool Bool []
  p₂ = fail ∣ fail

  equal : p₁ ≅P p₂
  equal = Inv.id ∷ λ _ → ♯ equal

  is-fail-is-not-a-congruence : ¬ (is-fail p₁ ≅ is-fail p₂)
  is-fail-is-not-a-congruence eq with Inverse.to eq ⟨$⟩ S.return
  ... | ()


open import Data.Nat as N using (ℕ; zero; suc; _<_; _≤_; _>_; z≤n; s≤s; _∸_)
open import Data.Nat.Properties as N
open import Data.Vec as V using (Vec; []; _∷_)
open import Data.Vec.Properties as V
open import Data.Fin as F using (Fin; zero; suc)
import      Data.Fin.Properties as F
open import Data.List as L using (List; []; _∷_)
open import Data.Unit using (⊤; tt)
open import Data.Nat.Properties
import      Data.Nat.DivMod as N
open import Data.Bool using (Bool; true; false)
open import Data.Product renaming (_×_ to _⊗_)

open import Relation.Binary.PropositionalEquality -- using (≡-Reasoning; _≡_; refl; cong; sym; subst; subst₂) -- (Extensionality)
open import Relation.Nullary
open import Relation.Nullary.Decidable
open import Relation.Nullary.Negation

open import Function
open import Reflection
open import Level using () renaming (zero to ℓ0; suc to lsuc)

open import Array.Base
open import Array.Properties

open import Agda.Builtin.Float

data dy-args : ∀ m n → Vec ℕ m → Vec ℕ n → Set where
  n-n : ∀ {n s} → dy-args n n s  s
  n-0 : ∀ {n s} → dy-args n 0 s  []
  0-n : ∀ {n s} → dy-args 0 n [] s

dy-args-dim : ∀ {m n sx sy} → dy-args m n sx sy → ℕ
dy-args-dim {m}    n-n = m
dy-args-dim {m}    n-0 = m
dy-args-dim {m}{n} 0-n = n

dy-args-shp : ∀ {m n sx sy} → (dy : dy-args m n sx sy) → Vec ℕ (dy-args-dim dy)
dy-args-shp {m}{n}{sx}     n-n = sx
dy-args-shp {m}{n}{sx}     n-0 = sx
dy-args-shp {m}{n}{sx}{sy} 0-n = sy

dy-args-ok? : Term → TC ⊤
dy-args-ok? hole = do
  goal ← inferType hole
  def (quote dy-args) (vArg m ∷ vArg n ∷ vArg sx ∷ vArg sy ∷ []) ← reduce goal
    where _ → typeError (strErr "expected dy-args expression in goal, found:" ∷ termErr goal ∷ [])
  reduce m >>= λ where
    (lit (nat 0)) → unify hole (con (quote 0-n) [])
    (meta id _) → blockOnMeta id
    m → reduce n >>= λ where
      (lit (nat 0)) → unify hole (con (quote n-0) [])
      (meta id _) → blockOnMeta id
      n → do
        catchTC
          (unify m n)
          (typeError (strErr "no valid dy-args found for goal: " ∷ termErr goal ∷ []))
        -- XXX we could further check that sx and sy unify as well, however, this would
        --     fail later if they don't.
        unify hole (con (quote n-n) [])

dy-type : ∀ a → Set a → Set a
dy-type a X = ∀ {m n sx sy} {@(tactic dy-args-ok?) args : dy-args m n sx sy}
              → Ar X m sx → Ar X n sy → Ar X _ (dy-args-shp args)

lift′ : ∀ {a}{X : Set a} → (_⊕_ : X → X → X) → dy-type a X
lift′ _⊕_ {args = n-n} (imap a) (imap b) = imap λ iv → a iv ⊕ b iv
lift′ _⊕_ {args = n-0} (imap a) (imap b) = imap λ iv → a iv ⊕ b []
lift′ _⊕_ {args = 0-n} (imap a) (imap b) = imap λ iv → a [] ⊕ b iv

Scal : ∀ {a} → Set a → Set a
Scal X = Ar X 0 []

--scal : ∀ {a}{X : Set a} → X → Scal X
--scal = cst

--unscal : ∀ {a}{X : Set a} → Scal X → X
--unscal (imap f) = f []

--module reduce-split where
--  split-thm : ∀ m n → (m N.⊓ n) N.+ (n N.∸ m) ≡ n
--  split-thm zero    n       = refl
--  split-thm (suc m) zero    = refl
--  split-thm (suc m) (suc n) = cong suc (split-thm m n)
--
--  {-# REWRITE split-thm #-}
--
--  split : ∀ {X : Set}{n} → (m : ℕ) → (xy : Vec X n)
--        → ∃₂ λ (x : Vec X (m N.⊓ n)) (y : Vec X (n N.∸ m)) → x V.++ y ≡ xy
--  split zero xy = [] , xy , refl
--  split {n = zero}  (suc m) xy = [] , xy , refl
--  split {X = X}{n = suc n} (suc m) (x ∷ xy) with split m xy
--  ... | xs , ys , refl = x ∷ xs , ys , refl
--
--  split-axis : ∀ m → (k : Fin m) → ∃₂ λ l r → l N.+ 1 N.+ r ≡ m
--  split-axis zero ()
--  split-axis (suc m) zero    = 0 , m , refl
--  split-axis (suc m) (suc k) with split-axis m k
--  ... | l' , r' , refl = suc l' , r' , refl
--
--
--  split-vec : ∀ {l r}{X : Set} → (xyz : Vec X (l N.+ 1 N.+ r))
--            → Σ[ x ∈ Vec X l ] Σ[ y ∈ Vec X 1 ] Σ[ z ∈ Vec X r ] ((x V.++ y) V.++ z ≡ xyz)
--  split-vec {zero}  {r} (h ∷ t) = [] , (h ∷ []) , t , refl
--  split-vec {suc l} {r} (h ∷ t) with split-vec {l = l} t
--  ... | l' , m' , r' , refl = h ∷ l' , m' , r' , refl

data SVup (X : Set) : Set → (d : ℕ) → (sh : Vec ℕ d) → Set where
  instance
    scal : SVup X X 0 []
    vect : ∀ {n} → SVup X (Vec X n) 1 (n ∷ [])
    arry : ∀ {d s} → SVup X (Ar X d s) d s
    -- XXX do we need the id case for arrays themselves?

infixr 30 ▴_
▴_ : ∀ {X A d s}{{t : SVup X A d s}} → A → Ar X d s
▴_ ⦃ t = scal ⦄ a = cst a --imap λ _ → a
▴_ ⦃ t = vect ⦄ a = imap λ iv → V.lookup a $ ix-lookup iv zero --imap λ where (i ∷ []) → V.lookup a i
▴_ ⦃ t = arry ⦄ a = a

{-
data SVdown (X : Set) : (d : ℕ) → (sh : Vec ℕ d) → Set → Set where
  instance
    scal : SVdown X 0 [] X
    vect : ∀ {n} → SVdown X 1 (n ∷ []) (Vec X n)
    -- XXX do we need the id case for arrays themselves?
-}

infixr 30 ▾_
▾_ : ∀ {X A d s}{{t : SVup X A d s}} → Ar X d s → A
▾_ ⦃ t = scal ⦄ (imap a) = a []
▾_ ⦃ t = vect ⦄ (imap a) = V.tabulate λ i → a $ i ∷ []
▾_ ⦃ t = arry ⦄ a = a

-- FIXME These cases are missing
-- Ar X 1 [n] + Vec X 0
-- Ar X 1 [n] + Vec X 1
-- Ar X 1 [n] + Vec X n

-- FIXME we should allow for different types of lhs and rhs and result,
--       e.g. replicate : ℕ → Char → String (not critical, but would be nice).
-- FIXME we can add the cases when indices are added with scalars/arrays/vectors.
--
-- Simplify handling concatenation of `Prog`s and `String`s
data DyScalVec (X : Set) : Set → Set → (d : ℕ) → (sh : Vec ℕ d) → Set where
  instance
    s-s :           DyScalVec X X X 0 []
    s-v : ∀ {n} →   DyScalVec X X (Vec X n) 1 (n ∷ [])
    s-a : ∀ {d s} → DyScalVec X X (Ar X d s) d s
    v-s : ∀ {n} →   DyScalVec X (Vec X n) X 1 (n ∷ [])
    v-v : ∀ {n} →   DyScalVec X (Vec X n) (Vec X n) 1 (n ∷ [])
    -- v-a : X (Ar X 1 (n ∷ [])) (Vec X n) 1 (n ∷ [])
    a-s : ∀ {d s} → DyScalVec X (Ar X d s) X d s
    -- a-v : X (Vec X n) (Ar X 1 (n ∷ [])) 1 (n ∷ [])
    a-a : ∀ {m n sx sy}{@(tactic dy-args-ok?) args : dy-args m n sx sy} → DyScalVec X (Ar X m sx) (Ar X n sy) (dy-args-dim args) (dy-args-shp args)

▴ₗ : ∀ {X A B d s} {{t : DyScalVec X A B d s}} → A → Ar X d s
▴ₗ {{s-s}} a = cst a
▴ₗ {{s-v}} a = cst a
▴ₗ {{s-a}} a = cst a
▴ₗ {{v-s}} a = ▴ a
▴ₗ {{v-v}} a = ▴ a
▴ₗ {{a-s}} a = a
▴ₗ ⦃ t = a-a {args = n-n} ⦄ a = a
▴ₗ ⦃ t = a-a {args = n-0} ⦄ a = a
▴ₗ ⦃ t = a-a {args = 0-n} ⦄ a = cst (sel a [])

▴ᵣ : ∀ {X A B d s} {{t : DyScalVec X A B d s}} → B → Ar X d s
▴ᵣ {{s-s}} b = cst b
▴ᵣ {{s-v}} b = ▴ b
▴ᵣ {{s-a}} b = b
▴ᵣ {{v-s}} b = cst b
▴ᵣ {{v-v}} b = ▴ b
▴ᵣ {{a-s}} b = cst b
▴ᵣ ⦃ t = a-a {args = n-n} ⦄ b = b
▴ᵣ ⦃ t = a-a {args = n-0} ⦄ b = cst (sel b [])
▴ᵣ ⦃ t = a-a {args = 0-n} ⦄ b = b

--infixr 20 _-′_
--_-′_ = lift′ N._∸_

_-safe-_ : (a : ℕ) → (b : ℕ) .{≥ : a N.≥ b} → ℕ
a -safe- b = a N.∸ b

{-
infixr 20 _-ₙ_
_-ₙ_ : ∀ {n}{s}
     → (a : Ar ℕ n s) → (b : Ar ℕ n s)
     → .{≥ : a ≥a b}
     → Ar ℕ n s
(imap f -ₙ imap g) {≥} = imap λ iv → (f iv -safe- g iv) {≥ = ≥ iv}
-}

---- FIXME: I guess we can eliminate _-′_ and lift′ entirely by inlining definitions.
--infixr 20 _-_
--_-_ : ∀ {A B d s}{{t : DyScalVec ℕ A B d s}} → (a : A) → (b : B) → .{≥ : ▴ₗ a ≥a ▴ᵣ b} → Ar ℕ d s
--_-_ ⦃ t = s-s ⦄ a b {a≥b} = imap λ iv → (a -safe- b) {a≥b iv}
--_-_ ⦃ t = s-v ⦄ a b {a≥b} = imap λ iv → (a -safe- sel (▴ b) iv) {a≥b iv}
--_-_ ⦃ t = s-a ⦄ a b {a≥b} = imap λ iv → (a -safe- sel b iv) {a≥b iv} --= ▴ a -′ b
--_-_ ⦃ t = v-s ⦄ a b {a≥b} = imap λ iv → (sel (▴ a) iv -safe- b) {a≥b iv} -- = ▴ a -′ ▴ b
--_-_ ⦃ t = v-v ⦄ a b {a≥b} = imap λ iv → (sel (▴ a) iv -safe- sel (▴ b) iv) {a≥b iv} --= ▴ a -′ ▴ b
--_-_ ⦃ t = a-s ⦄ a b {a≥b} = imap λ iv → (sel a iv -safe- b) {a≥b iv} --= a -′ ▴ b
--_-_ ⦃ t = a-a {args = n-n} ⦄ a b {a≥b} = imap λ iv → (sel a iv -safe- sel b iv) {a≥b iv}
--_-_ ⦃ t = a-a {args = n-0} ⦄ a b {a≥b} = imap λ iv → (sel a iv -safe- sel b []) {a≥b iv}
--_-_ ⦃ t = a-a {args = 0-n} ⦄ a b {a≥b} = imap λ iv → (sel a [] -safe- sel b iv) {a≥b iv}

infixr 20 _-_
_-_ : ∀ {A B d s}{{t : DyScalVec ℕ A B d s}} → (a : A) → (b : B) → .{≥ : ▴ₗ a ≥a ▴ᵣ b} → Ar ℕ d s
_-_ ⦃ t = s-s ⦄ a b  = imap λ iv → (a N.∸ b)
_-_ ⦃ t = s-v ⦄ a b  = imap λ iv → (a N.∸ sel (▴ b) iv)
_-_ ⦃ t = s-a ⦄ a b  = imap λ iv → (a N.∸ sel b iv)
_-_ ⦃ t = v-s ⦄ a b  = imap λ iv → (sel (▴ a) iv N.∸ b)
_-_ ⦃ t = v-v ⦄ a b  = imap λ iv → (sel (▴ a) iv N.∸ sel (▴ b) iv)
_-_ ⦃ t = a-s ⦄ a b  = imap λ iv → (sel a iv N.∸ b)
_-_ ⦃ t = a-a {args = n-n} ⦄ a b  = imap λ iv → (sel a iv N.∸ sel b iv)
_-_ ⦃ t = a-a {args = n-0} ⦄ a b  = imap λ iv → (sel a iv N.∸ sel b [])
_-_ ⦃ t = a-a {args = 0-n} ⦄ a b  = imap λ iv → (sel a [] N.∸ sel b iv)
-- _-′_ {args = args} a b

lift : ∀ {X A B d s}{{t : DyScalVec X A B d s}} → A → B → (_⊕_ : X → X → X) → Ar X d s
lift ⦃ t ⦄ a b _⊕_ = imap λ iv → sel (▴ₗ a) iv ⊕ sel (▴ᵣ b) iv

-- ℕ operations
infixr 20 _+_ _×_
_+_ _×_  : ∀ {A B d s}{{t : DyScalVec ℕ A B d s}} → A → B → Ar ℕ d s
a + b = lift a b N._+_
a × b = lift a b N._*_

-- Float operations
infixr 20 _+ᵣ_ _-ᵣ_ _×ᵣ_ _÷ᵣ_
_+ᵣ_ _-ᵣ_ _×ᵣ_ _÷ᵣ_ : ∀ {A B d s}{{t : DyScalVec Float A B d s}} → A → B → Ar Float d s
a +ᵣ b = lift a b primFloatPlus
a -ᵣ b = lift a b primFloatMinus
a ×ᵣ b = lift a b primFloatTimes
-- XXX we can request the proof that the right argument is not zero.
-- However, the current primFloatDiv has the type Float → Float → Float, so...
a ÷ᵣ b = lift a b primFloatDiv

lift-unary : ∀ {X A d s}{{t : SVup X A d s}} → (X → X) → A → Ar X d s
lift-unary ⦃ t = scal ⦄ f a = cst $ f a
lift-unary ⦃ t = vect ⦄ f a = imap λ iv → f $ V.lookup a $ ix-lookup iv zero
lift-unary ⦃ t = arry ⦄ f (imap a) = imap λ iv → f $ a iv

infixr 20 -ᵣ_ ÷ᵣ_ *ᵣ_
-ᵣ_ ÷ᵣ_ *ᵣ_ : ∀ {A d s}{{t : SVup Float A d s}} → A → Ar Float d s

*ᵣ_ = lift-unary primFloatExp
-ᵣ_ = lift-unary primFloatNegate
÷ᵣ_ = lift-unary (primFloatDiv 1.0)

module reduce-custom where
  drop-last : ∀ {m}{X : Set} → Vec X m → Vec X (m N.∸ 1)
  drop-last {0}  x = x
  drop-last {1}  x = []
  drop-last {suc (suc m)} (x ∷ xs) = x ∷ drop-last xs

  gz : ℕ → ℕ
  gz 0 = 0
  gz _ = 1

  take-last : ∀ {m}{X : Set} → Vec X m → Vec X (gz m)
  take-last {0}     x = x
  take-last {1}     x = x
  take-last {suc (suc m)} (x ∷ xs) = take-last xs

  _tldl++_ : ∀ {d s} → Ix (d N.∸ 1) (drop-last s) → Ix (gz d) (take-last s) → Ix d s
  _tldl++_ {0}  {s} iv jv = iv
  _tldl++_ {1}  {s} iv jv = jv
  _tldl++_ {suc (suc d)} {s ∷ ss} (i ∷ iv) jv = i ∷ (iv tldl++ jv)

  reduce-1d : ∀ {X Y : Set}{s} → (X → Y → Y) → Y → Ar X 1 s → Y
  reduce-1d {s = 0 ∷ []}     _⊕_ ε a = ε
  reduce-1d {s = suc x ∷ []} _⊕_ ε (imap a) = a (zero ∷ []) ⊕ reduce-1d {s = x ∷ []} _⊕_ ε (imap λ where (i ∷ []) → a (suc i ∷ []))

  infixr 20 _/′_
  _/′_ : ∀ {X Y : Set}{d s} → (X → Y → Y) → Ar X d s → {ε : Y} → Ar Y _ (drop-last s)
  _/′_ {d = 0}     f (imap a) {ε} =  imap λ iv → ε
  _/′_ {d = suc d} f (imap a) {ε} = imap λ iv → reduce-1d f ε (imap λ jv → a (iv tldl++ jv))

  data reduce-neut : {X Y : Set} → (X → Y → Y) → Y → Set where
    instance
      plus-nat : reduce-neut N._+_ 0
      mult-nat : reduce-neut N._*_ 1
      plus-flo : reduce-neut primFloatPlus 0.0
      gplus-float : reduce-neut (_+ᵣ_ {{t = a-a {sx = []}{sy = []}{args = n-n} }}) (cst 0.0)

  infixr 20 _/_
  _/_ : ∀ {X Y : Set}{n s ε}
        → (_⊕_ : X → Y → Y) → {{c : reduce-neut _⊕_ ε}}
        → Ar X n s → Ar Y _ (drop-last s)
  _/_ {ε = ε} f a = (f /′ a){ε}

  infixr 20 _//_
  _//_ : ∀ {X Y : Set}{n s ε}
        → (_⊕_ : Scal X → Scal Y → Scal Y) → {{c : reduce-neut _⊕_ ε}}
        → Ar X n s → Ar Y _ (drop-last s)
  _//_ {ε = ε} f a = imap λ jv → ▾ (sel ((f /′ (imap λ iv → ▴ (sel a iv))){ε}) jv)

  infixr 20 _/ᵣ_
  _/ᵣ_ : ∀ {X : Set}{d}{s}{m} → (n : ℕ) → Ar X (d N.+ 1) (s V.++ (m ∷ [])) → Ar X (d N.+ 1) (s V.++ (n N.* m ∷ []))
  _/ᵣ_ {d = d} {s = s} 0       a = imap λ iv → magic-fin (ix-lookup (take-ix-r s _ iv) zero)
  _/ᵣ_ {d = d} {s = s} (suc n) a = imap λ iv → let i   = ix-lookup (take-ix-r s _ iv) zero
                                                   l   = take-ix-l s _ iv
                                                   i/n = F.fromℕ< $ /-mono-f {b = suc n} (F.toℕ<n i) _
                                               in sel a $ l ix++ (i/n ∷ [])

  infixr 20 _⌿ᵣ_
  _⌿ᵣ_ : ∀ {X : Set}{d s m} → (n : ℕ) → Ar X (1 N.+ d) ((m ∷ []) V.++ s) → Ar X (1 N.+ d) ((n N.* m ∷ []) V.++ s)
  _⌿ᵣ_ {d = d} {s = s} 0       a = imap λ iv → magic-fin (ix-lookup iv zero)
  _⌿ᵣ_ {d = d} {s = s} (suc n) a = imap λ iv → let i   = ix-lookup iv zero
                                                   r   = take-ix-r _ s iv
                                                   i/n = F.fromℕ< $ /-mono-f {b = suc n} (F.toℕ<n i) _
                                               in sel a $ (i/n ∷ []) ix++ r

open reduce-custom public

-- shape and flatten
infixr 20 ρ_
ρ_ : ∀ {ℓ}{X : Set ℓ}{d s} → Ar X d s → Ar ℕ 1 (d ∷ [])
ρ_ {s = s} _ = s→a s

infixr 20 ,_
,_ : ∀ {a}{X : Set a}{n s} → Ar X n s → Ar X 1 (prod s ∷ [])
,_ {s = s} p = imap λ iv → sel p (off→idx s iv)

-- Note that two dots in an upper register combined with
-- the underscore form the _̈  symbol.  When the symbol is
-- used on its own, it looks like ̈ which is the correct
-- "spelling".
infixr 20 _̈_
_̈_ : ∀ {a}{X Y : Set a}{n s}
    → (X → Y)
    → Ar X n s
    → Ar Y n s
f ̈ imap p = imap $ f ∘ p

module _ where
  data iota-type : (d : ℕ) → (n : ℕ) → (Vec ℕ d) → Set where
    instance
      iota-scal : iota-type 0 1 []
      iota-vec  : ∀ {n} → iota-type 1 n (n ∷ [])

  iota-res-t : ∀ {d n s} → iota-type d n s → (sh : Ar ℕ d s) → Set

  iota-res-t {n = n} iota-scal sh = Ar (Σ ℕ λ x → x N.< ▾ sh)
                                      1 (▾ sh ∷ [])

  iota-res-t {n = n} iota-vec  sh = Ar (Σ (Ar ℕ 1 (n ∷ []))
                                        λ v → v <a sh)
                                      n (a→s sh)

  data iota-t : Set → (n : ℕ) → Set where
    instance
      iota-scal : iota-t ℕ 0
      iota-vect : ∀ {n} → iota-t (Vec ℕ n) n
      iota-arrs : iota-t (Ar ℕ 0 []) 1
      iota-arrv : ∀ {n} → iota-t (Ar ℕ 1 (n ∷ [])) n

  iota-ty : ∀ {X n} → iota-t X n → X → Set
  iota-ty {n = n} iota-scal x = Ar (Ix 1 (x ∷ [])) 1 (x ∷ [])
  iota-ty {n = n} iota-vect x = Ar (Ix n x) n x
  iota-ty {n = n} iota-arrs x = Ar (Ix n (▾ x ∷ [])) n (▾ x ∷ [])
  iota-ty {n = n} iota-arrv x = Ar (Ix n (▾ x)) n (▾ x)

  iota_ : ∀ {X n}{{t : iota-t X n}} → (x : X) → iota-ty t x
  iota_ ⦃ t = iota-scal ⦄ x = imap id
  iota_ ⦃ t = iota-vect ⦄ x = imap id
  iota_ ⦃ t = iota-arrs ⦄ x = imap id
  iota_ ⦃ t = iota-arrv ⦄ x = imap id

{-
  iota-t : ∀ {A d s} → SVup ℕ A d s → Ar ℕ d s → Set
  iota-t {d = d}{s}      scal a = Ar (Ix 1 (▾ a ∷ [])) 1 (▾ a ∷ [])
  iota-t {d = d}{n ∷ []} vect a = Ar (Ix n (▾ a))      n (▾ a)
  iota-t {d = d}{n ∷ []} arry a = Ar (Ix n (▾ a))      n (▾ a)

  iota_ : ∀ {A d s}{{t : SVup ℕ A d s}} → (a : Ar ℕ d s) → iota-t t a
  iota_ {d = d}{s}⦃ t = scal ⦄ a = imap id
  iota_ {d = d}{s}⦃ t = vect ⦄ a = imap id
  iota_ {d = d}{s}⦃ t = arry ⦄ a = {!!} --imap id
-}

  a<b⇒b≡c⇒a<c : ∀ {a b c} → a N.< b → b ≡ c → a N.< c
  a<b⇒b≡c⇒a<c a<b refl = a<b

  infixr 20 ι_
  ι_ : ∀ {d n s}{{c : iota-type d n s}}
    → (sh : Ar ℕ d s)
    → iota-res-t c sh
  ι_ ⦃ c = iota-scal ⦄ s = (imap λ iv → (F.toℕ $ ix-lookup iv zero) , F.toℕ<n (ix-lookup iv zero))
  ι_ {n = n} {s = s ∷ []} ⦃ c = iota-vec ⦄ (imap sh) = imap cast-ix→a
    where
      cast-ix→a : _
      cast-ix→a iv = let
                      ix , pf = ix→a iv
                    in ix , λ jv → a<b⇒b≡c⇒a<c (pf jv) (s→a∘a→s (imap sh) jv)

module cnn where
  open reduce-custom

  -- blog←{⍺×⍵×1-⍵}
  -- NOTE: We use + instead of - in the last example, as we are not in ℝ, and N.∸ needs a proof.
  blog : ∀ {n s} → Ar ℕ n s → Ar ℕ n s → Ar ℕ n s
  blog α ω = α × ω × 1 + ω

  conv : ∀ {n wₛ aₛ}
        → Ar ℕ n wₛ
        → Ar ℕ n aₛ
        → {≥ : ▴ aₛ ≥a ▴ wₛ}
        → Ar ℕ n $ ▾ (1 + (aₛ - wₛ){≥})
  conv _ _ = cst 1
  -- backbias←{+/,⍵}
  backbias : ∀ {n s} → Ar ℕ n s → Ar ℕ _ []
  backbias ω = N._+_ / , ω

  -- meansqerr←{÷∘2+/,(⍺-⍵)*2}
  meansqerr : ∀ {n s} → Ar ℕ n s → Ar ℕ n s → Ar ℕ _ []
  meansqerr α ω = _+ 2 $ N._+_ / , (α + ω) × (α + ω)

  -- backavgpool←{2⌿2/⍵÷4}⍤2
  backavgpool : ∀ {s} → Ar ℕ 2 s → Ar ℕ 2 $ ▾ (2 × s)
  backavgpool {s = _ ∷ _ ∷ []} ω = 2 /ᵣ′ 2 ⌿ᵣ ω × 4
    where
      infixr 20 _/ᵣ′_
      _/ᵣ′_ = _/ᵣ_ {s = _ ∷ []}

  -- Something that could go in Stdlib.
  ≡⇒≤ : ∀ {a b} → a ≡ b → a N.≤ b
  ≡⇒≤ refl = ≤-refl

  -- This should be perfectly generaliseable --- instead of 2
  -- we can use any m>0
  a<b⇒k<2⇒a*2+k<b*2 : ∀ {a b k} → a N.< b → k N.< 2 → a N.* 2 N.+ k N.< b N.* 2
  a<b⇒k<2⇒a*2+k<b*2 {a} {b} {zero} a<b k<2
                    rewrite (+-identityʳ (a N.* 2))
                          | (*-comm a 2)
                          | (*-comm b 2) = *-monoʳ-< 1 a<b
  a<b⇒k<2⇒a*2+k<b*2 {a} {b} {suc zero} a<b k<2 = ≤-trans (N.s≤s (≡⇒≤ (+-comm _ 1)))
                                                          (*-monoˡ-≤ 2 a<b)
  a<b⇒k<2⇒a*2+k<b*2 {a} {b} {suc (suc k)} a<b (N.s≤s (N.s≤s ()))

  A<B⇒K<2⇒A*2+K<B*2 : ∀ {n s}{a b k : Ar ℕ n s} → a <a b → k <a (cst 2) → ((a × 2) + k) <a (b × 2)
  A<B⇒K<2⇒A*2+K<B*2 {a = imap a} {imap b} {imap k} a<b k<2 = λ iv → a<b⇒k<2⇒a*2+k<b*2 (a<b iv) (k<2 iv)

  ---
  a<n⇒b<n⇒a+b<m+n : ∀ {m n} a b → a N.< m → b N.< n → a N.+ b N.< m N.+ n
  a<n⇒b<n⇒a+b<m+n {m}     zero b a<m b<n            = ≤-stepsˡ m b<n
  a<n⇒b<n⇒a+b<m+n {suc m} (suc a) b (N.s≤s a<m) b<n = N.s≤s (a<n⇒b<n⇒a+b<m+n a b a<m b<n)

  a<n⇒b<n⇒a+b<m+n-1 : ∀ {m n} a b → a N.< m → b N.< n → a N.+ b N.< m N.+ n N.∸ 1
  a<n⇒b<n⇒a+b<m+n-1 {suc m} {suc n} zero b a<m b<n            = ≤-stepsˡ m b<n
  a<n⇒b<n⇒a+b<m+n-1 {suc m} {suc n} (suc a) b (N.s≤s a<m) b<n rewrite +-suc m n =
      N.s≤s $ begin
       suc (a N.+ b)       ≤⟨ a<n⇒b<n⇒a+b<m+n-1 a b a<m b<n ⟩
       m N.+ suc n N.∸ 1   ≡⟨ cong₂ N._∸_ (+-suc m n) refl ⟩
       suc (m N.+ n) N.∸ 1 ≡⟨⟩
       m N.+ n
      ∎
      where open N.≤-Reasoning

  _+ff_ : ∀ {m n} → Fin m → Fin n → Fin (m N.+ n N.∸ 1)
  a +ff b = F.fromℕ< (a<n⇒b<n⇒a+b<m+n-1 _ _ (F.toℕ<n a) (F.toℕ<n b))

  _+f_ : ∀ {m n} → Fin m → Fin n → Fin (m N.+ n)
  a +f b = F.fromℕ< (a<n⇒b<n⇒a+b<m+n _ _ (F.toℕ<n a) (F.toℕ<n b))

  _*lf_ : ∀ {n} → (m : ℕ) → Fin n → .{m N.> 0} → Fin (suc (m N.* n N.∸ m))
  _*lf_ {n} m (b) {m>0} = let
                            m*sb≤m*n    = *-monoʳ-≤ m $ F.toℕ<n b
                            m+m*b≤m*n   = subst₂ N._≤_ (*-suc m _)   refl m*sb≤m*n
                            m+m*b-m≤m*n = ∸-monoˡ-≤  m m+m*b≤m*n
                            m*b≤m*n-m   = subst₂ N._≤_ (m+n∸m≡n m _) refl m+m*b-m≤m*n
                          in F.fromℕ< (N.s≤s m*b≤m*n-m)

  --_i+_ : ∀ {n s s'} → Ix n s → Ix n s' → Ix n (▾ ((s + s') -′ ▴ 1))
  --iv i+ jv = ix-tabulate λ i → subst Fin (sym (V.lookup∘tabulate _ i)) $ ix-lookup iv i +ff ix-lookup jv i

  -- Note that we explicitly use _-′_ instead of _-_ to avoid the proof.
  -- It is ok if m ≥ s×m as this means that s=0, and therefore there is no
  -- such index (element of Fin 0 at some position in s).
  --_il×_ : ∀ {n s} → (m : ℕ) → Ix n s → .{≥ : m N.> 0} → let s = 1 + (m × s) -′ ▴ m in Ix n (▾ s)
  --(a il× ix) {m>0} = ix-tabulate λ i → subst Fin (sym (V.lookup∘tabulate _ i)) $ (a *lf ix-lookup ix i){m>0}

{-
  avgpool' : ∀ {s}
          → Ar Float 2 $ ▾ (2 × s)
          → Ar Float 2 s
  avgpool' {s} (imap p) = imap body
    where
          m>0⇒k≤k*m : ∀ {m} → m N.> 0 → ∀ k → k N.≤ k N.* m
          m>0⇒k≤k*m m>0 zero = N.z≤n
          m>0⇒k≤k*m m>0 (suc k) = +-mono-≤ m>0 (m>0⇒k≤k*m m>0 k)

          --a≤a*b

          thm : ∀ {d s} → Ix d s → ∀ i → d N.+ (d N.* V.lookup s i N.∸ d) ≡ d N.* V.lookup s i
          thm {d}{s} ix i with V.lookup s i | ix-lookup ix i
          ... | zero  | ()
          ... | suc m | _ = m+[n∸m]≡n (m>0⇒k≤k*m {m = suc m} (N.s≤s N.z≤n) d)

          thmx : ∀ {d s} → Ix d s → ∀ i → V.lookup (▾ (d + (d × s) -′ ▴ d)) i ≡ V.lookup (▾ (d × s)) i
          thmx {d}{s} ix i with V.lookup s i | inspect (V.lookup s) i  | ix-lookup ix i
          ... | zero  | _      | ()
          ... | suc m | [ pf ] | _ = begin
             V.lookup (V.tabulate (λ z → d N.+ (d N.* V.lookup s z N.∸ d))) i ≡⟨ lookup∘tabulate _ i ⟩
             d N.+ (d N.* V.lookup s i N.∸ d) ≡⟨ cong (d N.+_) (cong (N._∸ d) (cong (d N.*_) pf)) ⟩
             d N.+ (d N.* suc m N.∸ d)        ≡⟨ m+[n∸m]≡n (m>0⇒k≤k*m {m = suc m} (N.s≤s N.z≤n) d) ⟩
             d N.* suc m                      ≡⟨ cong (d N.*_) (sym pf) ⟩
             d N.* V.lookup s i               ≡⟨ sym (lookup∘tabulate _ i) ⟩
             V.lookup (V.tabulate (λ z → d N.* V.lookup s z)) i
             ∎
             where open ≡-Reasoning

          body : _ → _
          body iv = let
                    ixs = iota (2 ∷ 2 ∷ [])
                    ix = (2 il× iv) {N.s≤s N.z≤n}
                    f iv = p $ subst-ix {!!} $ iv i+ ix
                    xx = primFloatPlus / , f ̈ ixs
                    in {!!}

-}

  avgpool : ∀ {s}
          → Ar ℕ 2 $ ▾ (s × 2)
          → Ar ℕ 2 s
  avgpool {s} (imap p) = imap body
    where body : _ → _
          body iv = let
               sh = s × 2
               ix , ix<s = ix→a iv
               bx = ix × 2
               ixs = ι (cst {s = 2 ∷ []} 2)
               use-ixs = λ where
                 (i , pf) → p $ a→ix (bx + i) sh $ A<B⇒K<2⇒A*2+K<B*2 ix<s pf
               in ▾ (_× 4 $ N._+_ / , use-ixs ̈ ixs)

--test : ∀ {s : Vec ℕ 2} → ℕ
--test {s} = {! s × 2!}

-- Take and Drop
ax+sh<s : ∀ {n}
        → (ax sh s : Ar ℕ 1 (n ∷ []))
        → (s≥sh : s ≥a sh)
        → (ax <a (s - sh) {≥ = s≥sh})
        → (ax + sh) <a s
ax+sh<s (imap ax) (imap sh) (imap s) s≥sh ax<s-sh iv =
    let
      ax+sh<s-sh+sh = +-monoˡ-< (sh iv) (ax<s-sh iv)
      s-sh+sh≡s     = m∸n+n≡m (s≥sh iv)
    in a<b⇒b≡c⇒a<c ax+sh<s-sh+sh s-sh+sh≡s
{-
_↑_ : ∀ {a}{X : Set a}{n s}
    → (sh : Ar ℕ 1 (n ∷ []))
    → (a : Ar X n s)
    → {pf : s→a s ≥a sh}
    → Ar X n $ a→s sh
_↑_ {s = s} sh       (imap f) {pf}   with (prod $ a→s sh) N.≟ 0
_↑_ {s = s} sh       (imap f) {pf} | yes Πsh≡0 = imap λ iv → magic-fin $ Πs≡0⇒Fin0 _ iv Πsh≡0 --mkempty _ Πsh≡0
_↑_ {s = s} (imap q) (imap f) {pf} | no  Πsh≢0 = imap λ iv → {- mtake
  where
    mtake : _
    mtake iv = -} let
                  ai , ai< = ix→a iv
                  ix<q jv = a<b⇒b≡c⇒a<c (ai< jv) (s→a∘a→s (imap q) jv)
                  ix = a→ix ai (s→a s) λ jv → ≤-trans (ix<q jv) (pf jv)
               in f (subst-ix (a→s∘s→a s) ix)
-}
{- -}
_↑_ : ∀ {a}{X : Set a}{n s}
    → (sh : Ar ℕ 1 (n ∷ []))
    → (a : Ar X n s)
    → {pf : s→a s ≥a sh}
    → Ar X n $ a→s sh
_↑_ {s = s} sh       (imap f) {pf} = case (prod $ a→s sh) N.≟ 0 of λ where
        (yes Πsh≡0) → imap λ iv → magic-fin $ Πs≡0⇒Fin0 _ iv Πsh≡0
        (no  Πsh≢0) → imap λ iv → let
                  ai , ai< = ix→a iv
                  ix<q jv = a<b⇒b≡c⇒a<c (ai< jv) (s→a∘a→s sh jv)
                  ix = a→ix ai (s→a s) λ jv → ≤-trans (ix<q jv) (pf jv)
               in f (subst-ix (a→s∘s→a s) ix)
{- -}

_↓_ : ∀ {a}{X : Set a}{n s}
    → (sh : Ar ℕ 1 (n ∷ []))
    → (a : Ar X n s)
    → {pf : (s→a s) ≥a sh}
    → Ar X n $ a→s $ (s→a s - sh) {≥ = pf}
_↓_ {s = s} sh (imap x) {pf} with
                             let p = prod $ a→s $ (s→a s - sh) {≥ = pf}
                             in  p N.≟ 0
_↓_ {s = s} sh       (imap f) {pf} | yes Π≡0 = mkempty _ Π≡0
_↓_ {s = s} (imap q) (imap f) {pf} | no  Π≢0 = imap mkdrop
  where
    mkdrop : _
    mkdrop iv = let
                  ai , ai< = ix→a iv
                  ax = ai + (imap q)
                  thmx = ax+sh<s
                           ai (imap q) (s→a s) pf
                           λ jv → a<b⇒b≡c⇒a<c (ai< jv)
                                  (s→a∘a→s ((s→a s - (imap q)) {≥ = pf}) jv)
                  ix = a→ix ax (s→a s) thmx
                in f (subst-ix (a→s∘s→a s) ix)

∸-monoˡ-< : ∀ {m n o} → m < n → o ≤ m → m ∸ o < n ∸ o
∸-monoˡ-< {o = zero}  m<n o≤m = m<n
∸-monoˡ-< {suc m} {o = suc o} (s≤s m<n) (s≤s o≤m) = ∸-monoˡ-< m<n o≤m

a+b-a≡a : ∀ {n} {s₁ : Vec ℕ n} {s : Ix 1 (n ∷ []) → ℕ}
             {jv : Ix 1 (n ∷ [])} →
           V.lookup (V.tabulate (λ i → s (i ∷ []) N.+ V.lookup s₁ i))
           (ix-lookup jv zero)
           ∸ s jv
           ≡ V.lookup s₁ (ix-lookup jv zero)
a+b-a≡a {zero} {[]} {s} {x ∷ []} = magic-fin x
a+b-a≡a {suc n} {x ∷ s₁} {s} {zero ∷ []} = m+n∸m≡n (s (zero ∷ [])) x
a+b-a≡a {suc n} {x ∷ s₁} {s} {suc j ∷ []} = a+b-a≡a {s₁ = s₁} {s = λ { (j ∷ []) → s (suc j ∷ [])}} {jv = j ∷ []}

pre-pad : ∀ {a}{X : Set a}{n}{s₁ : Vec ℕ n}
        → (sh : Ar ℕ 1 (n ∷ []))
        → X
        → (a : Ar X n s₁)
        → Ar X n (a→s $ sh + ρ a)
pre-pad {s₁ = s₁} (imap s) e (imap f) = imap body
  where
    body : _
    body iv = let ix , ix<s = ix→a iv
              in case ix ≥a? (imap s) of λ where
                   (yes p) → let
                      fx = (ix - (imap s)) {≥ = p}
                      fv = a→ix fx (s→a s₁)
                                λ jv → a<b⇒b≡c⇒a<c
                                          (∸-monoˡ-< (ix<s jv) (p jv))
                                          (a+b-a≡a {s₁ = s₁} {s = s} {jv = jv})
                      in f (subst-ix (λ i → lookup∘tabulate _ i) fv)
                   (no ¬p) → e

b≤a⇒c<b⇒a-b+c<a : ∀ {a b c} → b ≤ a → c < b → a ∸ b N.+ c < a
b≤a⇒c<b⇒a-b+c<a {suc a} {suc b} {zero}  b≤a c<b rewrite +-identityʳ (a ∸ b) = s≤s (m∸n≤m a b)
b≤a⇒c<b⇒a-b+c<a {suc a} {suc b} {suc c} (s≤s b≤a) (s≤s c<b) = let q = b≤a⇒c<b⇒a-b+c<a b≤a c<b
                                                              in subst₂ _<_ (sym $ +-suc (a ∸ b) c) refl $ +-monoʳ-< 1 q

[a+b≥c]⇒b<c⇒a+b-c<a : ∀ {a b c} → a N.+ b N.≥ c → b < c → a N.+ b ∸ c < a
[a+b≥c]⇒b<c⇒a+b-c<a {a}{b}{c} a+b≥c b<c = let a+b<a+c = +-monoʳ-< a b<c
                                          in subst₂ _<_ refl (m+n∸n≡m _ c) $ ∸-monoˡ-< a+b<a+c a+b≥c

{-
_-↑⟨_⟩_ : ∀ {a}{X : Set a}{n}{s₁ : Vec ℕ n}
        → (sh : Ar ℕ 1 (n ∷ []))
        → X
        → (a : Ar X n s₁)
        → Ar X n (▾ sh)
_-↑⟨_⟩_ {s₁ = s₁} s e a = imap λ iv → let
                  ix , ix<s = ix→a iv
                  in case ((ρ a) + ix) ≥a? s of λ where
                             (yes p) → let
                                 ov = (((ρ a) + ix) - s){p}
                                 oi = a→ix ov (ρ a) λ { jv@(i ∷ []) → [a+b≥c]⇒b<c⇒a+b-c<a (p jv) (subst₂ _<_ refl (V.lookup∘tabulate _ i) $ ix<s jv) }
                                in sel a (subst-ix (λ i → lookup∘tabulate _ i) oi)
                             (no _) → e
-}

_-↑⟨_⟩_ : ∀ {a}{X : Set a}{n}{s₁ : Vec ℕ n}
        → (sh : Ar ℕ 1 (n ∷ []))
        → X
        → (a : Ar X n s₁)
        → Ar X n (▾ sh)
_-↑⟨_⟩_ {s₁ = s₁} s e a = imap body
  where
    body : _
    body iv with ix→a iv
    ... | ix , ix<s with ((ρ a) + ix) ≥a? s
    ... | (yes p) = let
                      ov = (((ρ a) + ix) - s){p}
                      oi = a→ix ov (ρ a) λ { jv@(i ∷ []) → [a+b≥c]⇒b<c⇒a+b-c<a (p jv) (subst₂ _<_ refl (V.lookup∘tabulate _ i) $ ix<s jv) }
                    in sel a (subst-ix (λ i → lookup∘tabulate _ i) oi)
    ... | _  = e

{-
_↑⟨_⟩_ : ∀ {a}{X : Set a}{n}{s : Vec ℕ n}
      → (sh : Ar ℕ 1 (n ∷ []))
      → X
      → (a : Ar X n s)
      → Ar X n (▾ sh)
_↑⟨_⟩_ {s = s} (imap sh) e (imap a) = imap body
  where
    body : _
    body iv = let ix , ix<s = ix→a iv
              in case ix <a? (ρ imap a) of λ where
                (yes p) → let
                            av = a→ix ix (ρ imap a) p
                          in a (subst-ix (λ i → lookup∘tabulate _ i) av)
                (no ¬p) → e
-}

_↑⟨_⟩_ : ∀ {a}{X : Set a}{n}{s : Vec ℕ n}
      → (sh : Ar ℕ 1 (n ∷ []))
      → X
      → (a : Ar X n s)
      → Ar X n (▾ sh)
_↑⟨_⟩_ {s = s} (imap sh) e (imap a) = imap body
  where
    body : _
    body iv with ix→a iv
    ... | ix , ix<s with ix <a? (ρ imap a)
    ... | (yes p) = let
                      av = a→ix ix (ρ imap a) p
                    in a (subst-ix (λ i → lookup∘tabulate _ i) av)
    ... | (no ¬p) = e

_̈⟨_⟩_ : ∀ {a}{X Y Z : Set a}{n s}
     → Ar X n s
     → (X → Y → Z)
     → Ar Y n s → Ar Z n s
--(imap p) ̈⟨ f ⟩ (imap p₁) = imap λ iv → f (p iv) (p₁ iv)
p ̈⟨ f ⟩ p₁ = imap λ iv → f (sel p iv) (sel p₁ iv)

module test where
  s : Vec ℕ 3
  s = 1 ∷ 2 ∷ 3 ∷ []
  a : Ar ℕ 3 s
  a = cst 10

  b : Ar ℕ 0 []
  b = cst 20

  test/ : _
  test/ = reduce-custom._/_ N._+_ a

  -- These tests work, which is nice.
  test₁ : Ar ℕ 3 s
  test₁ = a + b

  test₂ : Ar ℕ 3 s
  test₂ = b + a

  test₃ : Ar ℕ 3 s
  test₃ = a + a

  test₄ : Ar ℕ 0 []
  test₄ = b + b

  -- This looks much better.
  test-nn : ∀ {n s} → (a b : Ar ℕ n s) → Ar ℕ n s
  test-nn {n}{s} x y = x + y

  test-n0 : ∀ {n s} →  Ar ℕ n s → Ar ℕ n s
  test-n0 x = x + b

  test-0n : ∀ {n s} →  Ar ℕ n s → Ar ℕ n s
  test-0n x = b + x

  -- This definition should fail, as sx ≠ sy (not necessarily)
  --test-fail : ∀ {n sx sy} → Ar ℕ n sx → Ar ℕ n sy → Ar ℕ n sy
  --test-fail x y = x + y


{-# OPTIONS --without-K #-}
module Model.Quantification where

open import Model.RGraph as RG using (RGraph)
open import Model.Size as MS using (_<_ ; ⟦_⟧Δ ; ⟦_⟧n ; ⟦_⟧σ)
open import Model.Type.Core
open import Source.Size.Substitution.Theory
open import Source.Size.Substitution.Universe as SS using (Sub⊢ᵤ)
open import Util.HoTT.Equiv
open import Util.HoTT.FunctionalExtensionality
open import Util.HoTT.HLevel
open import Util.Prelude hiding (_∘_)
open import Util.Relation.Binary.PropositionalEquality using
  ( Σ-≡⁺ ; subst-sym-subst ; subst-subst-sym )

import Source.Size as SS
import Source.Type as ST

open RGraph
open RG._⇒_
open SS.Ctx
open SS.Sub⊢ᵤ

record ⟦∀⟧′ {Δ} n (T : ⟦Type⟧ ⟦ Δ ∙ n ⟧Δ) (δ : Obj ⟦ Δ ⟧Δ) : Set where
  no-eta-equality
  field
    arr : ∀ m (m<n : m < ⟦ n ⟧n .fobj δ) → Obj T (δ , m , m<n)
    param : ∀ m m<n m′ m′<n → eq T _ (arr m m<n) (arr m′ m′<n)

open ⟦∀⟧′ public

⟦∀⟧′Canon : ∀ {Δ} n (T : ⟦Type⟧ ⟦ Δ ∙ n ⟧Δ) (δ : Obj ⟦ Δ ⟧Δ) → Set
⟦∀⟧′Canon n T δ
  = Σ[ f ∈ (∀ m (m<n : m < ⟦ n ⟧n .fobj δ) → Obj T (δ , m , m<n)) ]
      (∀ m m<n m′ m′<n → eq T _ (f m m<n) (f m′ m′<n))

⟦∀⟧′≅⟦∀⟧′Canon : ∀ {Δ n T δ} → ⟦∀⟧′ {Δ} n T δ ≅ ⟦∀⟧′Canon n T δ
⟦∀⟧′≅⟦∀⟧′Canon = record
  { forth = λ { record { arr = x ; param = y } → x , y }
  ; isIso = record
    { back = λ x → record { arr = proj₁ x ; param = proj₂ x }
    ; back∘forth = λ { record {} → refl }
    ; forth∘back = λ _ → refl
    }
  }

abstract
  ⟦∀⟧′Canon-IsSet : ∀ {Δ n T δ} → IsSet (⟦∀⟧′Canon {Δ} n T δ)
  ⟦∀⟧′Canon-IsSet {T = T} = Σ-IsSet
    (∀-IsSet λ m → ∀-IsSet λ m<n → λ _ _ → T .Obj-IsSet _ _)
    (λ f → IsOfHLevel-suc 1 (∀-IsProp λ m → ∀-IsProp λ m<n → ∀-IsProp λ m′
      → ∀-IsProp λ m′<n → T .eq-IsProp))

  ⟦∀⟧′-IsSet : ∀ {Δ n T δ} → IsSet (⟦∀⟧′ {Δ} n T δ)
  ⟦∀⟧′-IsSet {Δ} {n} {T} {δ}
    = ≅-pres-IsOfHLevel 2 (≅-sym ⟦∀⟧′≅⟦∀⟧′Canon) (⟦∀⟧′Canon-IsSet {Δ} {n} {T} {δ})

  ⟦∀⟧′-≡⁺ : ∀ {Δ n T δ} (f g : ⟦∀⟧′ {Δ} n T δ)
    → (∀ m m<n → f .arr m m<n ≡ g .arr m m<n)
    → f ≡ g
  ⟦∀⟧′-≡⁺ {T = T} record{} record{} f≈g = ≅-Injective ⟦∀⟧′≅⟦∀⟧′Canon (Σ-≡⁺
    ( (funext λ m → funext λ m<n → f≈g m m<n)
    , funext λ m → funext λ m<n → funext λ m′ → funext λ m<n′ → T .eq-IsProp _ _))

⟦∀⟧′-resp-≈⟦Type⟧ : ∀ {Δ n T U}
  → T ≈⟦Type⟧ U
  → ∀ {δ} → ⟦∀⟧′ {Δ} n T δ ≅ ⟦∀⟧′ n U δ
⟦∀⟧′-resp-≈⟦Type⟧ T≈U = record
  { forth = λ f → record
    { arr = λ m m<n → T≈U .forth .fobj (f .arr m m<n)
    ; param = λ m m<n m′ m′<n → T≈U .forth .feq _ (f .param m m<n m′ m′<n)
    }
  ; isIso = record
    { back = λ f → record
      { arr = λ m m<n → T≈U .back .fobj (f .arr m m<n)
      ; param = λ m m<n m′ m′<n → T≈U .back .feq _ (f .param m m<n m′ m′<n)
      }
    ; back∘forth = λ x → ⟦∀⟧′-≡⁺ _ _ λ m m<n → T≈U .back-forth .≈⁻ _ _
    ; forth∘back = λ x → ⟦∀⟧′-≡⁺ _ _ λ m m<n → T≈U .forth-back .≈⁻ _ _
    }
  }

⟦∀⟧ : ∀ {Δ} n (T : ⟦Type⟧ ⟦ Δ ∙ n ⟧Δ) → ⟦Type⟧ ⟦ Δ ⟧Δ
⟦∀⟧ n T = record
  { ObjHSet = λ δ → HLevel⁺ (⟦∀⟧′ n T δ) ⟦∀⟧′-IsSet
  ; eqHProp = λ _ f g → ∀-HProp _ λ m → ∀-HProp _ λ m<n → ∀-HProp _ λ m′
      → ∀-HProp _ λ m′<n → T .eqHProp _ (f .arr m m<n) (g .arr m′ m′<n)
  ; eq-refl = λ f → f .param
  }

⟦∀⟧-resp-≈⟦Type⟧ : ∀ {Δ} n {T U : ⟦Type⟧ ⟦ Δ ∙ n ⟧Δ}
  → T ≈⟦Type⟧ U
  → ⟦∀⟧ n T ≈⟦Type⟧ ⟦∀⟧ n U
⟦∀⟧-resp-≈⟦Type⟧ n T≈U = record
  { forth = record
    { fobj = ⟦∀⟧′-resp-≈⟦Type⟧ T≈U .forth
    ; feq = λ δ≈δ′ x≈y a a₁ a₂ a₃ → T≈U .forth .feq _ (x≈y a a₁ a₂ a₃)
    }
  ; back = record
    { fobj = ⟦∀⟧′-resp-≈⟦Type⟧ T≈U .back
    ; feq = λ δ≈δ′ x≈y a a₁ a₂ a₃ → T≈U .back .feq _ (x≈y a a₁ a₂ a₃)
    }
  ; back-forth = ≈⁺ λ δ x → ⟦∀⟧′-resp-≈⟦Type⟧ T≈U .back∘forth _
  ; forth-back = ≈⁺ λ δ x → ⟦∀⟧′-resp-≈⟦Type⟧ T≈U .forth∘back _
  }

absₛ : ∀ {Δ n} {Γ : ⟦Type⟧ ⟦ Δ ⟧Δ} {T : ⟦Type⟧ ⟦ Δ ∙ n ⟧Δ}
  → subT ⟦ SS.Wk ⟧σ Γ ⇒ T
  → Γ ⇒ ⟦∀⟧ n T
absₛ {Δ} {n} {Γ} {T} f = record
  { fobj = λ {δ} x → record
    { arr = λ m m<n → f .fobj x
    ; param = λ m m<n m′ m′<n → f .feq _ (Γ .eq-refl x)
    }
  ; feq = λ _ x≈y m m′ m<nγ m′<nγ′ → f .feq _ x≈y
  }

appₛ : ∀ {Δ n m} {Γ : ⟦Type⟧ ⟦ Δ ⟧Δ} {T : ⟦Type⟧ ⟦ Δ ∙ n ⟧Δ}
  → (m<n : m SS.< n)
  → Γ ⇒ ⟦∀⟧ n T
  → Γ ⇒ subT ⟦ SS.Sing m<n ⟧σ T
appₛ {m = m} {T = T} m<n f = record
  { fobj = λ {δ} x → f .fobj x .arr (⟦ m ⟧n .fobj δ) (MS.⟦<⟧ m<n)
  ; feq = λ δ≈δ′ {x y} x≈y → f .feq _ x≈y _ _ _ _
  }

subT-⟦∀⟧ : ∀ {Δ Ω n σ} (⊢σ : σ ∶ Δ ⇒ᵤ Ω) (T : ⟦Type⟧ ⟦ Ω ∙ n ⟧Δ)
  → ⟦∀⟧ (n [ σ ]ᵤ)
      (subT (⟦_⟧σ {Ω = Ω ∙ n} (Lift ⊢σ refl)) T)
  ≈⟦Type⟧ subT ⟦ ⊢σ ⟧σ (⟦∀⟧ n T)
subT-⟦∀⟧ {Δ} {Ω} {n} {σ} ⊢σ T = record
  { forth = record
    { fobj = λ {γ} f → record
      { arr = λ m m<n
          → transportObj T
              (MS.⟦Δ∙n⟧-≡⁺ Ω n _ _ refl refl)
              (f .arr m (subst (m <_) (sym (MS.⟦sub⟧ ⊢σ n)) m<n))
      ; param = λ m m<n m′ m′<n
          → transportObj-resp-eq T _ _ (f .param _ _ _ _)
      }
    ; feq = λ γ≈γ′ f≈g a a₁ a₂ a₃ → transportObj-resp-eq T _ _ (f≈g _ _ _ _)
    }
  ; back = record
    { fobj = λ {γ} f → record
      { arr = λ m m<n
          → transportObj T
              (MS.⟦Δ∙n⟧-≡⁺ Ω n _ _ refl refl)
              (f .arr m (subst (m <_) (MS.⟦sub⟧ ⊢σ n) m<n))
      ; param = λ m m<n m′ m′<n
          → transportObj-resp-eq T _ _ (f .param _ _ _ _)
      }
    ; feq = λ γ≈γ′ f≈g a a₁ a₂ a₃ → transportObj-resp-eq T _ _ (f≈g _ _ _ _)
    }
  ; back-forth = ≈⁺ λ γ f → ⟦∀⟧′-≡⁺ _ _ λ m m<n
      → trans
          (transportObj∘transportObj T
            (MS.⟦Δ∙n⟧-≡⁺ Ω n _ _ refl refl)
            (MS.⟦Δ∙n⟧-≡⁺ Ω n _ _ refl refl))
          (trans
            (cong
              (λ p → transportObj T
                (trans
                  (MS.⟦Δ∙n⟧-≡⁺ Ω n
                    (subst (m <_) (MS.⟦sub⟧ ⊢σ n) p)
                    (subst (m <_) (MS.⟦sub⟧ ⊢σ n) m<n)
                    refl refl)
                  (MS.⟦Δ∙n⟧-≡⁺ Ω n
                    (subst (m <_) (MS.⟦sub⟧ ⊢σ n) m<n)
                    (subst (m <_) (MS.⟦sub⟧ ⊢σ n) m<n)
                    refl refl))
                (f .arr m p))
              (subst-sym-subst (MS.⟦sub⟧ ⊢σ n)))
            (transportObj-refl T _))
  ; forth-back = ≈⁺ λ γ f → ⟦∀⟧′-≡⁺ _ _ λ m m<n
      → trans
          (transportObj∘transportObj T
            (MS.⟦Δ∙n⟧-≡⁺ Ω n _ _ refl refl)
            (MS.⟦Δ∙n⟧-≡⁺ Ω n _ _ refl refl))
          (trans
            (cong
              (λ p → transportObj T
                (trans
                  (MS.⟦Δ∙n⟧-≡⁺ Ω n p p refl refl)
                  (MS.⟦Δ∙n⟧-≡⁺ Ω n p m<n refl refl))
                (f .arr m p))
              (subst-subst-sym (MS.⟦sub⟧ ⊢σ n)))
            (transportObj-refl T _))
  }


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

open import HoTT hiding (left; right)

module homotopy.BlakersMassey {i j k}
  {A : Type i} {B : Type j} (Q : A → B → Type k)
  m (f-conn : ∀ a → is-connected (S m) (Σ B (λ b → Q a b)))
  n (g-conn : ∀ b → is-connected (S n) (Σ A (λ a → Q a b)))
  where

open import homotopy.blakersmassey.Pushout Q
import homotopy.blakersmassey.CoherenceData Q m f-conn n g-conn as Coh

-- These extra parameters will be discharged in the end
module _ {a₀} {b₀} (q₀₀ : Q a₀ b₀) where

  -- Step 1:
  -- Generalizing the theorem to [bmleft x == p] for arbitrary p.
  -- This requires a pushout-rec

  -- "extended" version
  code-bmleft-template : ∀ a₁ {p} (r' : bmleft a₁ == p) → bmleft a₀ == p → Type (lmax i (lmax j k))
  code-bmleft-template a₁ r' r = Trunc (m +2+ n) (hfiber (λ q₁₀ → bmglue q₀₀ ∙' ! (bmglue q₁₀) ∙' r') r)

  code-bmleft : ∀ a₁ → bmleft a₀ == bmleft a₁ → Type (lmax i (lmax j k))
  code-bmleft a₁ = code-bmleft-template a₁ idp

  code-bmright : ∀ b₁ → bmleft a₀ == bmright b₁ → Type (lmax i (lmax j k))
  code-bmright b₁ r = Trunc (m +2+ n) (hfiber bmglue r)

  -- The template from [Coh.eqv] to the input for [apd code glue]
  -- for using the identification elimination.
  code-bmglue-template : ∀ {a₁ p}
    → (code : (r : bmleft a₀ == p) → Type (lmax i (lmax j k)))
    → (r : bmleft a₁ == p)
    → (∀ r' → code-bmleft-template a₁ r r' ≃ code r')
    → code-bmleft-template a₁ idp == code [ (λ p → bmleft a₀ == p → Type (lmax i (lmax j k))) ↓ r ]
  code-bmglue-template _ idp lemma = λ= (ua ∘ lemma)

  -- The real glue, that is, the template with actual equivalence.
  code-bmglue : ∀ {a₁ b₁} (q₁₁ : Q a₁ b₁)
    → code-bmleft a₁ == code-bmright b₁
      [ (λ p → bmleft a₀ == p → Type (lmax i (lmax j k))) ↓ bmglue q₁₁ ]
  code-bmglue {a₁} {b₁} q₁₁ =
    code-bmglue-template (code-bmright b₁) (bmglue q₁₁) (Coh.eqv q₀₀ q₁₁)

  -- Here's the data structure for the generalized theorem.
  module Code = BMPushoutElim code-bmleft code-bmright code-bmglue

  code : ∀ p → bmleft a₀ == p → Type (lmax i (lmax j k))
  code = Code.f

  -- Step 2:
  -- [code] is contractible!

  -- The center for [idp].  We will use transport to find the center
  -- in other fibers.
  code-center-idp : code (bmleft a₀) idp
  code-center-idp = [ q₀₀ , !-inv'-r (bmglue q₀₀) ]

  -- The following is the breakdown of the path for coercing:
  --   [ap2 code r (↓-cst=idf-in' r)]
  -- We will need the broken-down version anyway,
  -- so why not breaking them down from the very beginning?

  -- The template here, again, is to keep the possibility
  -- of plugging in [idp] for [r].
  coerce-path-template : ∀ {p} r
    → code-bmleft a₀ == code p [ (λ p → bmleft a₀ == p → Type (lmax i (lmax j k))) ↓ r ]
    → code-bmleft a₀ idp == code p r
  coerce-path-template idp lemma = app= lemma idp

  -- The real path.
  coerce-path : ∀ {p} r → code (bmleft a₀) idp == code p r
  coerce-path r = coerce-path-template r (apd code r)

  -- Find the center in other fibers.
  code-center : ∀ {p} r → code p r
  code-center r = coe (coerce-path r) code-center-idp

  -- Part of the decomposed [coe (coerce-path r)]
  code-bmleft-template-diag : ∀ {p} (r : bmleft a₀ == p)
    → code-bmleft a₀ idp → code-bmleft-template a₀ r r
  code-bmleft-template-diag r = Trunc-rec Trunc-level
    λ {(q₀₀' , shift) →
      [ q₀₀' , ! (∙'-assoc (bmglue q₀₀) (! (bmglue q₀₀')) r) ∙ ap (_∙' r) shift ∙' ∙'-unit-l r ]}

  abstract
    code-bmleft-template-diag-idp : ∀ x → code-bmleft-template-diag idp x == x
    code-bmleft-template-diag-idp =
      Trunc-elim (λ _ → =-preserves-level Trunc-level)
        λ{(q₁₀ , shift) → ap (λ p → [ q₁₀ , p ]) (ap-idf shift)}

  -- Here shows the use of two templates.  It will be super painful
  -- if we cannot throw in [idp].  Now we only have to deal with
  -- simple computations.
  abstract
    coe-coerce-path-code-bmglue-template : ∀ {p} (r : bmleft a₀ == p)
      (lemma : ∀ r' → code-bmleft-template a₀ r r' ≃ code p r')
      (x : code-bmleft a₀ idp)
      → coe (coerce-path-template r (code-bmglue-template (code p) r lemma)) x
      == –> (lemma r) (code-bmleft-template-diag r x)
    coe-coerce-path-code-bmglue-template idp lemma x =
      coe (app= (λ= (ua ∘ lemma)) idp) x
        =⟨ ap (λ p → coe p x) (app=-β (ua ∘ lemma) idp) ⟩
      coe (ua (lemma idp)) x
        =⟨ coe-β (lemma idp) x ⟩
      –> (lemma idp) x
        =⟨ ! (ap (–> (lemma idp)) (code-bmleft-template-diag-idp x)) ⟩
      –> (lemma idp) (code-bmleft-template-diag idp x)
        =∎

  -- Here is the actually lemma we want!
  -- A succinct breakdown of [coerce-path code (glue q)].
  abstract
    coe-coerce-path-code-bmglue : ∀ {b₁} (q₀₁ : Q a₀ b₁) x
      → coe (coerce-path (bmglue q₀₁)) x
      == Coh.to q₀₀ q₀₁ (bmglue q₀₁) (code-bmleft-template-diag (bmglue q₀₁) x)
    coe-coerce-path-code-bmglue q₀₁ x =
      coe (coerce-path-template (bmglue q₀₁) (apd code (bmglue q₀₁))) x
        =⟨ ap (λ p → coe (coerce-path-template (bmglue q₀₁) p) x) (Code.glue-β q₀₁) ⟩
      coe (coerce-path-template (bmglue q₀₁) (code-bmglue q₀₁)) x
        =⟨ coe-coerce-path-code-bmglue-template (bmglue q₀₁) (Coh.eqv q₀₀ q₀₁) x ⟩
      Coh.to q₀₀ q₀₁ (bmglue q₀₁) (code-bmleft-template-diag (bmglue q₀₁) x)
        =∎

  -- This is the only case you care for contractibiltiy.
  abstract
    code-coh-lemma : ∀ {b₁} (q₀₁ : Q a₀ b₁) → code-center (bmglue q₀₁) == [ q₀₁ , idp ]
    code-coh-lemma q₀₁ =
      coe (coerce-path (bmglue q₀₁)) code-center-idp
        =⟨ coe-coerce-path-code-bmglue q₀₁ code-center-idp ⟩
      Coh.to' q₀₀ q₀₁ (bmglue q₀₁) (q₀₀ , α₁α₁⁻¹α₂=α₂ (bmglue q₀₀) (bmglue q₀₁))
        =⟨ ap (Coh.To.ext q₀₀ (_ , q₀₀) (_ , q₀₁) (bmglue q₀₁)) (path-lemma (bmglue q₀₀) (bmglue q₀₁)) ⟩
      Coh.To.ext q₀₀ (_ , q₀₀) (_ , q₀₁) (bmglue q₀₁) (! path)
        =⟨ Coh.To.β-r q₀₀ (_ , q₀₁) (bmglue q₀₁) (! path) ⟩
      [ q₀₁ , path ∙' ! path ]
        =⟨ ap (λ p → [ q₀₁ , p ]) (!-inv'-r path) ⟩
      [ q₀₁ , idp ]
        =∎
      where
        path = Coh.βPair.path (Coh.βpair-bmright q₀₀ q₀₁ (bmglue q₀₁))

        -- this is defined to be the path generated by [code-bmleft-template-diag]
        α₁α₁⁻¹α₂=α₂ : ∀ {p₁ p₂ p₃ : BMPushout} (α₁ : p₁ == p₂) (α₂ : p₁ == p₃)
          → α₁ ∙' ! α₁ ∙' α₂ == α₂
        α₁α₁⁻¹α₂=α₂ α₁ α₂ = ! (∙'-assoc α₁ (! α₁) α₂) ∙ ap (_∙' α₂) (!-inv'-r α₁) ∙' ∙'-unit-l α₂

        -- the relation of this path and the one from CoherenceData
        path-lemma : ∀ {p₁ p₂ p₃ : BMPushout} (α₁ : p₁ == p₂) (α₂ : p₁ == p₃)
          → α₁α₁⁻¹α₂=α₂ α₁ α₂ == ! (Coh.α₁=α₂α₂⁻¹α₁ α₂ α₁)
        path-lemma idp idp = idp

  -- Make [r] free to apply identification elimination.
  code-coh : ∀ {b₁} (r : bmleft a₀ == bmright b₁) (s : hfiber bmglue r) → code-center r == [ s ]
  code-coh ._ (q₀₁ , idp) = code-coh-lemma q₀₁

  -- Finish the lemma.
  code-contr : ∀ {b₁} (r : bmleft a₀ == bmright b₁) → is-contr (Trunc (m +2+ n) (hfiber bmglue r))
  code-contr r = code-center r , Trunc-elim
    (λ _ → =-preserves-level Trunc-level) (code-coh r)

-- The final theorem.
-- It is sufficient to find some [q₀₀].
blakers-massey : ∀ {a₀ b₀} → has-conn-fibers (m +2+ n) (bmglue {a₀} {b₀})
blakers-massey {a₀} r = Trunc-rec
  (prop-has-level-S is-connected-is-prop)
  (λ{(_ , q₀₀) → code-contr q₀₀ r})
  (fst (f-conn a₀))


-- Andreas, 2013-09-17 catches a bug in constraint solving
-- with meta-variables applied to underapplied record constructors
-- {-# OPTIONS --show-implicit -v tc.meta.assign:50 -v tc.conv.elim:30 #-}
module Issue889 where

record Monad (M : Set → Set) : Set₁ where
  field
    return : ∀ {A} → A → M A
    _>>=_  : ∀ {A} {B : M A → Set} (m : M A) →
             ((x : A) → M (B (return x))) → M (B m)

record Σ (A : Set) (B : A → Set) : Set where
  constructor _,_
  field
    proj₁ : A
    proj₂ : B proj₁

_×_ : (A B : Set) → Set
A × B = Σ A λ _ → B

State : Set → Set → Set
State S X = S → X × S

-- Here is the underapplied record constructor
-- (made underapplied by eta-contraction, resp.).
state-return : ∀ {S X} → X → State S X
state-return x = _,_ x -- λ s → x , s

-- When giving the @_>>=_@ function to the record goal we get:
--
-- An internal error has occurred. Please report this as a bug.
-- Location of the error: src/full/Agda/TypeChecking/Conversion.hs:524
state-monad : ∀ {S} → Monad (State S)
state-monad {S} = record { return = state-return; _>>=_ = {!_>>=_!} }
  where
  postulate
    _>>=_ : ∀ {A} {B : State S A → Set} (m : State S A) →
          ((x : A) → State S (B (state-return x))) → State S (B m)
-- Bug arises when checking
--   _B (state-return x) = B (state-return x)
--   _B (_,_ x)          = B (_,_ x)
-- then Agda does something special for record constructors
-- [Miller unification in the presence of records]
-- (now only done for FULLY APPLIED record constructors!)
-- and produces some garbage solution for _B.

------------------------------------------------------------------------

-- Note that if we define the product type using data instead of
-- record then it works:

data _×′_ (A B : Set) : Set where
  _,_ : A → B → A ×′ B

State′ : Set → Set → Set
State′ S X = S → X ×′ S

state-return′ : ∀ {S X} → X → State′ S X
state-return′ x = λ s → x , s

state-monad′ : ∀ {S} → Monad (State′ S)
state-monad′ {S} = record { return = state-return′; _>>=_ = _>>=_ }
  where
  postulate
    _>>=_ : ∀ {A} {B : State′ S A → Set} (m : State′ S A) →
          ((x : A) → State′ S (B (state-return′ x))) → State′ S (B m)


------------------------------------------------------------------------
-- Eliminators and initiality
------------------------------------------------------------------------

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

module Partiality-algebra.Eliminators where

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

open import Bijection equality-with-J using (_↔_)
import Equivalence equality-with-J as Eq
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 Surjection equality-with-J using (_↠_)

open import Partiality-algebra as PA hiding (id; _∘_)

------------------------------------------------------------------------
-- Eliminators

-- I (NAD) have tried to follow the spirit of the rules for HITs
-- specified in the HoTT-Agda library
-- (https://github.com/HoTT/HoTT-Agda/blob/master/core/lib/types/HIT_README.txt).
-- However, at the time of writing those rules don't apply to indexed
-- types.

-- A specification of a given partiality algebra's eliminator
-- arguments (for given motive sizes).

record Arguments {a p′ q′} {A : Type a} p q
                 (PA : Partiality-algebra p′ q′ A) :
                 Type (a ⊔ lsuc (p ⊔ q) ⊔ p′ ⊔ q′) where

  open Partiality-algebra PA

  -- Predicate transformer related to increasing sequences.

  Inc : (P : T → Type p)
        (Q : ∀ {x y} → P x → P y → x ⊑ y → Type q) →
        Increasing-sequence → Type (p ⊔ q)
  Inc P Q s =
    ∃ λ (p : ∀ n → P (s [ n ])) →
      ∀ n → Q (p n) (p (suc n)) (increasing s n)

  field
    P  : T → Type p
    Q  : ∀ {x y} → P x → P y → x ⊑ y → Type q
    pe : P never
    po : ∀ x → P (now x)
    pl : ∀ s (pq : Inc P Q s) → P (⨆ s)
    pa : ∀ {x y} (x⊑y : x ⊑ y) (x⊒y : x ⊒ y)
         (p-x : P x) (p-y : P y)
         (q-x⊑y : Q p-x p-y x⊑y) (q-x⊒y : Q p-y p-x x⊒y) →
         subst P (antisymmetry x⊑y x⊒y) p-x ≡ p-y
    pp : ∀ {x} → Is-set (P x)
    qr : ∀ x (p : P x) → Q p p (⊑-refl x)
    qt : ∀ {x y z} (x⊑y : x ⊑ y) (y⊑z : y ⊑ z) →
         (px : P x) (py : P y) (pz : P z) →
         Q px py x⊑y → Q py pz y⊑z → Q px pz (⊑-trans x⊑y y⊑z)
    qe : ∀ x (p : P x) → Q pe p (never⊑ x)
    qu : ∀ s (pq : Inc P Q s) n →
         Q (proj₁ pq n) (pl s pq) (upper-bound s n)
    ql : ∀ s ub is-ub (pq : Inc P Q s) (pu : P ub)
         (qu : ∀ n → Q (proj₁ pq n) pu (is-ub n)) →
         Q (pl s pq) pu (least-upper-bound s ub is-ub)
    qp : ∀ {x y} (p-x : P x) (p-y : P y) (x⊑y : x ⊑ y) →
         Is-proposition (Q p-x p-y x⊑y)

-- A specification of a given partiality algebra's eliminators.

record Eliminators {a p q p′ q′} {A : Type a}
                   {PA : Partiality-algebra p′ q′ A}
                   (args : Arguments p q PA) :
                   Type (a ⊔ p ⊔ q ⊔ p′ ⊔ q′) where
  open Partiality-algebra PA
  open Arguments args

  field
    ⊥-rec : (x : T) → P x
    ⊑-rec : ∀ {x y} (x⊑y : x ⊑ y) → Q (⊥-rec x) (⊥-rec y) x⊑y

  inc-rec : (s : Increasing-sequence) → Inc P Q s
  inc-rec = λ { (s , inc) → ( (λ n → ⊥-rec (s   n))
                            , (λ n → ⊑-rec (inc n))
                            ) }

  field

    -- Some "computation" rules.

    ⊥-rec-never : ⊥-rec never ≡ pe
    ⊥-rec-now   : ∀ x → ⊥-rec (now x) ≡ po x
    ⊥-rec-⨆     : ∀ s → ⊥-rec (⨆ s) ≡ pl s (inc-rec s)

-- A specification of the elimination principle for a given partiality
-- algebra (for motives of certain sizes).

Elimination-principle :
  ∀ {a p′ q′} {A : Type a} →
  ∀ p q → Partiality-algebra p′ q′ A → Type (a ⊔ lsuc (p ⊔ q) ⊔ p′ ⊔ q′)
Elimination-principle p q P =
  (args : Arguments p q P) → Eliminators args

------------------------------------------------------------------------
-- Initiality

-- The statement that a given partiality algebra is homotopy-initial.

Initial : ∀ {a p q} p′ q′ {A : Type a} → Partiality-algebra p q A →
          Type (a ⊔ p ⊔ q ⊔ lsuc (p′ ⊔ q′))
Initial p′ q′ {A} P =
  (P′ : Partiality-algebra p′ q′ A) → Contractible (Morphism P P′)

-- If a partiality algebra is initial at certain sizes, then it is
-- also initial at smaller sizes.

lower-initiality :
  ∀ {a p q p₁ q₁} p₂ q₂ {A : Type a}
  (P : Partiality-algebra p q A)
  (initial : Initial (p₁ ⊔ p₂) (q₁ ⊔ q₂) P) →
  Initial p₁ q₁ P
lower-initiality {p₁ = p₁} {q₁} p₂ q₂ {A} P initial P′ =
  Σ-map lower-morphism lemma (initial P″)
  where
  open Partiality-algebra P′

  P″ : Partiality-algebra (p₁ ⊔ p₂) (q₁ ⊔ q₂) A
  P″ = record
    { T                       = ↑ p₂ T
    ; partiality-algebra-with = record
      { _⊑_               = λ x y → ↑ q₂ (lower x ⊑ lower y)
      ; never             = lift never
      ; now               = lift ∘ now
      ; ⨆                 = lift ∘ ⨆ ∘ Σ-map (lower ∘_) (lower ∘_)
      ; antisymmetry      = λ x⊑y y⊑x →
                              cong lift (antisymmetry (lower x⊑y)
                                                      (lower y⊑x))
      ; T-is-set-unused   = λ _ _ → ↑-closure 2 T-is-set _ _
      ; ⊑-refl            = lift ∘ ⊑-refl ∘ lower
      ; ⊑-trans           = λ x⊑y y⊑z →
                              lift (⊑-trans (lower x⊑y)
                                            (lower y⊑z))
      ; never⊑            = lift ∘ never⊑ ∘ lower
      ; upper-bound       = λ _ → lift ∘ upper-bound _
      ; least-upper-bound = λ _ _ →
                              lift ∘
                              least-upper-bound _ _ ∘
                              (lower ∘_)
      ; ⊑-propositional   = ↑-closure 1 ⊑-propositional
      }
    }

  lower-morphism : Morphism P P″ → Morphism P P′
  lower-morphism m = record
    { function     = lower ∘ function
    ; monotone     = lower ∘ monotone
    ; strict       = cong lower strict
    ; now-to-now   = cong lower ∘ now-to-now
    ; ω-continuous = cong lower ∘ ω-continuous
    }
    where
    open Morphism m

  lift-morphism : Morphism P P′ → Morphism P P″
  lift-morphism m = record
    { function     = lift ∘ function
    ; monotone     = lift ∘ monotone
    ; strict       = cong lift strict
    ; now-to-now   = cong lift ∘ now-to-now
    ; ω-continuous = cong lift ∘ ω-continuous
    }
    where
    open Morphism m

  lemma : {m : Morphism P P″} →
          (∀ m′ → m ≡ m′) →
          (∀ m′ → lower-morphism m ≡ m′)
  lemma {m} hyp m′ = _↔_.to equality-characterisation-Morphism
    (lower ∘ Morphism.function m  ≡⟨ cong ((lower ∘_) ∘ Morphism.function) (hyp (lift-morphism m′)) ⟩∎
     Morphism.function m′         ∎)

------------------------------------------------------------------------
-- Specifying the partiality monad using eliminators is logically
-- equivalent (at the meta-level) to specifying it using initiality

-- Any partiality algebra with eliminators (at certain sizes) is
-- initial (at the same sizes).

eliminators→initiality :
  ∀ {a p q p′ q′} {A : Type a} →
  (P : Partiality-algebra p′ q′ A) →
  Elimination-principle p q P →
  Initial p q P
eliminators→initiality {p = p} {q} P elims P′ = morphism , unique
  where
  module P  = Partiality-algebra P
  module P′ = Partiality-algebra P′

  args : Arguments p q P
  args = record
    { P  = λ _ → P′.T
    ; Q  = λ x y _ → x P′.⊑ y
    ; pe = P′.never
    ; po = P′.now
    ; pl = λ _ → P′.⨆
    ; pa = λ x⊑y y⊑x u v u⊑v v⊑u →
             let eq = Partiality-algebra.antisymmetry P x⊑y y⊑x in

             subst (const P′.T) eq u  ≡⟨ subst-const eq ⟩
             u                        ≡⟨ P′.antisymmetry u⊑v v⊑u ⟩∎
             v                        ∎
    ; pp = P′.T-is-set
    ; qr = λ _ → P′.⊑-refl
    ; qt = λ _ _ _ _ _ → P′.⊑-trans
    ; qe = λ _ → P′.never⊑
    ; qu = λ _ → P′.upper-bound
    ; ql = λ _ _ _ → P′.least-upper-bound
    ; qp = λ _ _ _ → P′.⊑-propositional
    }

  module E = Eliminators (elims args)

  morphism : Morphism P P′
  morphism = record
    { function     = E.⊥-rec
    ; monotone     = E.⊑-rec
    ; strict       = E.⊥-rec-never
    ; now-to-now   = E.⊥-rec-now
    ; ω-continuous = E.⊥-rec-⨆
    }

  open Morphism

  function-unique : (f : Morphism P P′) →
                    E.⊥-rec ≡ function f
  function-unique f = sym $ ⟨ext⟩ $ Eliminators.⊥-rec (elims (record
    { Q  = λ _ _ _ → ↑ _ ⊤
    ; pe = function f P.never  ≡⟨ strict f ⟩
           P′.never            ≡⟨ sym $ E.⊥-rec-never ⟩∎
           E.⊥-rec P.never     ∎
    ; po = λ x →
             function f (P.now x)  ≡⟨ now-to-now f x ⟩
             P′.now x              ≡⟨ sym $ E.⊥-rec-now x ⟩∎
             E.⊥-rec (P.now x)     ∎
    ; pl = λ s hyp →
             function f (P.⨆ s)            ≡⟨ ω-continuous f s ⟩
             P′.⨆ (sequence-function f s)  ≡⟨ cong P′.⨆ $ _↔_.to P′.equality-characterisation-increasing (proj₁ hyp) ⟩
             P′.⨆ (E.inc-rec s)            ≡⟨ sym $ E.⊥-rec-⨆ s ⟩
             E.⊥-rec (P.⨆ s)               ∎
    ; pa = λ _ _ _ _ _ _ → P′.T-is-set _ _
    ; pp = λ _ _ → mono₁ 1 P′.T-is-set _ _
    ; qp = λ _ _ _ _ _ → refl
    }))

  unique : ∀ f → morphism ≡ f
  unique f =
    _↔_.to equality-characterisation-Morphism (function-unique f)

-- Any partiality algebra with initiality (at certain sufficiently
-- large sizes) has eliminators (at certain sizes).

initiality→eliminators :
  ∀ {a p q p′ q′} {A : Type a} {P : Partiality-algebra p′ q′ A}
  (initial : Initial (p ⊔ p′) (q ⊔ q′) P) →
  Elimination-principle p q P
initiality→eliminators {p = p} {q} {p′} {q′} {A} {PA} initial args =
  record
    { ⊥-rec       = ⊥-rec
    ; ⊑-rec       = ⊑-rec
    ; ⊥-rec-never = ⊥-rec-never
    ; ⊥-rec-now   = ⊥-rec-now
    ; ⊥-rec-⨆     = ⊥-rec-⨆
    }
  where
  open Partiality-algebra PA

  open Arguments args

  private

    -- A partiality algebra with ∃ P as the carrier type.

    ∃PA : Partiality-algebra (p ⊔ p′) (q ⊔ q′) A
    ∃PA = record
      { T                       = ∃ P
      ; partiality-algebra-with = record
        { _⊑_               = λ { (_ , p) (_ , q) → ∃ (Q p q) }
        ; never             = never , pe
        ; now               = λ x → now x , po x
        ; ⨆                 = λ s → ⨆ (Σ-map (proj₁ ∘_) (proj₁ ∘_) s)
                                  , pl _ ( proj₂ ∘ proj₁ s
                                         , proj₂ ∘ proj₂ s
                                         )
        ; antisymmetry      = λ { {x = (x , p)} {y = (y , q)}
                                  (x⊑y , Qx⊑y) (y⊑x , Qy⊑x) →
                                  Σ-≡,≡→≡
                                    (antisymmetry x⊑y y⊑x)
                                    (pa x⊑y y⊑x p q Qx⊑y Qy⊑x)
                                }
        ; T-is-set-unused   = Σ-closure 2 T-is-set (λ _ → pp)
        ; ⊑-refl            = λ _ → _ , qr _ _
        ; ⊑-trans           = Σ-zip ⊑-trans (qt _ _ _ _ _)
        ; never⊑            = λ _ → _ , qe _ _
        ; upper-bound       = λ _ _ → upper-bound _ _ , qu _ _ _
        ; least-upper-bound = λ _ _ ⊑qs →
                                  least-upper-bound _ _ (proj₁ ∘ ⊑qs)
                                , ql _ _ _ _ _ (proj₂ ∘ ⊑qs)
        ; ⊑-propositional   = Σ-closure 1 ⊑-propositional λ _ → qp _ _ _
        }
      }

    -- Initiality gives us a morphism from PA to ∃PA.

    eliminator-morphism : Morphism PA ∃PA
    eliminator-morphism = proj₁ (initial ∃PA)

    open Morphism eliminator-morphism

    -- We can construct a morphism from ∃PA to PA directly.

    proj₁-morphism : Morphism ∃PA PA
    proj₁-morphism = record
      { function     = proj₁
      ; monotone     = proj₁
      ; strict       = refl
      ; now-to-now   = λ _ → refl
      ; ω-continuous = λ _ → refl
      }

    -- By composing the two morphisms we get an endomorphism on PA.

    id′ : Morphism PA PA
    id′ = proj₁-morphism PA.∘ eliminator-morphism

    -- Due to initiality this morphism must be equal to id.

    id′≡id : id′ ≡ PA.id
    id′≡id =
      let m , unique = lower-initiality p q PA initial PA in

      id′    ≡⟨ sym $ unique id′ ⟩
      m      ≡⟨ unique PA.id ⟩∎
      PA.id  ∎

    abstract

      -- This provides us with a key lemma used to define the
      -- eliminators.

      lemma : ∀ x → proj₁ (function x) ≡ x
      lemma x = cong (λ m → Morphism.function m x) id′≡id

    -- As an aside this means that there is a split surjection from
    -- ∃ P to T.

    ↠T : ∃ P ↠ T
    ↠T = record
      { logical-equivalence = record
        { to   = Morphism.function proj₁-morphism
        ; from = function
        }
      ; right-inverse-of = lemma
      }

  -- The eliminators.

  ⊥-rec : ∀ x → P x
  ⊥-rec x =                 $⟨ proj₂ (function x) ⟩
    P (proj₁ (function x))  ↝⟨ subst P (lemma x) ⟩□
    P x                     □

  ⊑-rec : ∀ {x y} (x⊑y : x ⊑ y) → Q (⊥-rec x) (⊥-rec y) x⊑y
  ⊑-rec {x} {y} x⊑y =                                                   $⟨ proj₂ (monotone x⊑y) ⟩
    Q (proj₂ (function x)) (proj₂ (function y)) (proj₁ (monotone x⊑y))  ↝⟨ subst Q′ $
                                                                             Σ-≡,≡→≡ (cong₂ _,_ (lemma x) (lemma y)) (
        subst (λ xy → P (proj₁ xy) × P (proj₂ xy) ×
                      proj₁ xy ⊑ proj₂ xy)
              (cong₂ _,_ (lemma x) (lemma y))
              ( proj₂ (function x)
              , proj₂ (function y)
              , proj₁ (monotone x⊑y)
              )                                                                 ≡⟨ push-subst-, {y≡z = cong₂ _,_ (lemma x) (lemma y)}
                                                                                (λ xy → P (proj₁ xy))
                                                                                (λ xy → P (proj₂ xy) × proj₁ xy ⊑ proj₂ xy)
                                                                                {p = ( proj₂ (function x)
                                                                                , proj₂ (function y)
                                                                                , proj₁ (monotone x⊑y)
                                                                                )} ⟩
        ( subst (λ xy → P (proj₁ xy))
                (cong₂ _,_ (lemma x) (lemma y))
                (proj₂ (function x))
        , subst (λ xy → P (proj₂ xy) × proj₁ xy ⊑ proj₂ xy)
                (cong₂ _,_ (lemma x) (lemma y))
                ( proj₂ (function y)
                , proj₁ (monotone x⊑y)
                )
        )                                                                       ≡⟨ cong₂ _,_
                                                                                (subst-∘ P proj₁ (cong₂ _,_ (lemma x) (lemma y))
                                                                                {p = proj₂ (function x)})
                                                                                (push-subst-, {y≡z = cong₂ _,_ (lemma x) (lemma y)}
                                                                                (λ xy → P (proj₂ xy))
                                                                                (λ xy → proj₁ xy ⊑ proj₂ xy)
                                                                                {p = ( proj₂ (function y)
                                                                                , proj₁ (monotone x⊑y)
                                                                                )}) ⟩
        ( subst P (cong proj₁ $ cong₂ _,_ (lemma x) (lemma y))
                (proj₂ (function x))
        , subst (λ xy → P (proj₂ xy))
                (cong₂ _,_ (lemma x) (lemma y))
                (proj₂ (function y))
        , subst (λ xy → proj₁ xy ⊑ proj₂ xy)
                (cong₂ _,_ (lemma x) (lemma y))
                (proj₁ (monotone x⊑y))
        )                                                                       ≡⟨ cong₂ _,_
                                                                                (cong (λ p → subst P p (proj₂ (function x))) $
                                                                                cong-proj₁-cong₂-, (lemma x) (lemma y))
                                                                                (cong₂ _,_
                                                                                (subst-∘ P proj₂ (cong₂ _,_ (lemma x) (lemma y))
                                                                                {p = proj₂ (function y)})
                                                                                (⊑-propositional _ _)) ⟩
        ( subst P (lemma x) (proj₂ (function x))
        , subst P (cong proj₂ $ cong₂ _,_ (lemma x) (lemma y))
                (proj₂ (function y))
        , x⊑y
        )                                                                       ≡⟨ cong (λ p → ⊥-rec x , subst P p (proj₂ (function y)) , x⊑y) $
                                                                                cong-proj₂-cong₂-, (lemma x) (lemma y) ⟩∎
        (⊥-rec x , ⊥-rec y , x⊑y)                                               ∎) ⟩□

    Q (⊥-rec x) (⊥-rec y) x⊑y                                           □
    where
    Q′ :
      (∃ λ xy → P (proj₁ xy) × P (proj₂ xy) × proj₁ xy ⊑ proj₂ xy) →
      Type q
    Q′ (_ , px , py , x⊑y) = Q px py x⊑y

  inc-rec : ∀ s → Inc P Q s
  inc-rec (s , inc) = ⊥-rec ∘ s , ⊑-rec ∘ inc

  -- "Computation" rules.

  private

    -- A lemma.

    ⊥-rec-lemma′ :
      ∀ {a b} {A : Type a} {B : A → Type b} {x y : Σ A B} →
      Is-set A →
      x ≡ y → (p : proj₁ x ≡ proj₁ y) →
      subst B p (proj₂ x) ≡ proj₂ y
    ⊥-rec-lemma′ {B = B} A-set =
      elim (λ {x y} _ → (p : proj₁ x ≡ proj₁ y) →
                        subst B p (proj₂ x) ≡ proj₂ y)
           (λ { (_ , x₂) p →
                subst B p x₂     ≡⟨ cong (λ p → subst B p _) $ A-set p refl ⟩
                subst B refl x₂  ≡⟨⟩
                x₂               ∎
              })

    -- A specific instance of the lemma above.

    ⊥-rec-lemma : ∀ {x y} → function x ≡ (x , y) → ⊥-rec x ≡ y
    ⊥-rec-lemma {x} {y} eq =
      ⊥-rec x                                 ≡⟨⟩
      subst P (lemma x) (proj₂ (function x))  ≡⟨ ⊥-rec-lemma′ T-is-set eq (lemma x) ⟩∎
      y                                       ∎

  ⊥-rec-never : ⊥-rec never ≡ pe
  ⊥-rec-never = ⊥-rec-lemma strict

  ⊥-rec-now : ∀ x → ⊥-rec (now x) ≡ po x
  ⊥-rec-now x = ⊥-rec-lemma (now-to-now x)

  ⊥-rec-⨆ : ∀ s → ⊥-rec (⨆ s) ≡ pl s (inc-rec s)
  ⊥-rec-⨆ s = ⊥-rec-lemma
    (function (⨆ s)                                           ≡⟨ ω-continuous s ⟩

     ⨆ (Σ-map (proj₁ ∘_) (proj₁ ∘_) (sequence-function s)) ,
     pl _ ( proj₂ ∘ proj₁ (sequence-function s)
          , proj₂ ∘ proj₂ (sequence-function s)
          )                                                   ≡⟨⟩

     ⨆ ( proj₁ ∘ function ∘ proj₁ s
       , proj₁ ∘ monotone ∘ proj₂ s
       ) ,
     pl _ ( proj₂ ∘ function ∘ proj₁ s
          , proj₂ ∘ monotone ∘ proj₂ s
          )                                                   ≡⟨ Σ-≡,≡→≡ (cong ⨆ lemma₁) lemma₂ ⟩

     ⨆ s , pl s ( ⊥-rec ∘ proj₁ s
                , ⊑-rec ∘ proj₂ s
                )                                             ≡⟨⟩

     ⨆ s , pl s (inc-rec s)                                   ∎)
     where
     lemma₁ =
       ( proj₁ ∘ function ∘ proj₁ s
       , proj₁ ∘ monotone ∘ proj₂ s
       )                             ≡⟨ _↔_.to equality-characterisation-increasing (λ n →
                                          lemma (s [ n ])) ⟩∎
       s                             ∎

     lemma₂ =
       subst P (cong ⨆ lemma₁)
         (pl _ ( proj₂ ∘ function ∘ proj₁ s
               , proj₂ ∘ monotone ∘ proj₂ s
               ))                                         ≡⟨ sym $ subst-∘ P ⨆ lemma₁ ⟩

       subst (P ∘ ⨆) lemma₁
         (pl _ ( proj₂ ∘ function ∘ proj₁ s
               , proj₂ ∘ monotone ∘ proj₂ s
               ))                                         ≡⟨ elim
                                                               (λ {s′ s} eq → ∀ {i′ i} →
                                                                              subst (λ s → ∀ n → P (s [ n ])) eq (proj₁ i′) ≡ proj₁ i →
                                                                              subst (P ∘ ⨆) eq (pl s′ i′) ≡ pl s i)
                                                               (λ s {i′ i} eq →
                                                                  pl s i′  ≡⟨ cong (pl s) (Σ-≡,≡→≡ eq (⟨ext⟩ λ _ → qp _ _ _ _ _)) ⟩∎
                                                                  pl s i   ∎)
                                                               lemma₁
                                                               (⟨ext⟩ λ n →
           let lemma₃ =
                 cong _[ n ] lemma₁                               ≡⟨ sym $ cong-∘ (_$ n) proj₁ lemma₁ ⟩
                 cong (_$ n) (cong proj₁ lemma₁)                  ≡⟨ cong (cong (_$ n)) $ proj₁-Σ-≡,≡→≡ (⟨ext⟩ (lemma ∘ proj₁ s)) _ ⟩
                 cong (_$ n) (⟨ext⟩ (lemma ∘ proj₁ s))            ≡⟨ cong-ext (lemma ∘ proj₁ s) ⟩∎
                 lemma (s [ n ])                                  ∎
           in
           subst (λ s → ∀ n → P (s [ n ])) lemma₁
                 (proj₂ ∘ function ∘ proj₁ s) n                   ≡⟨ sym $ push-subst-application lemma₁ (λ s n → P (s [ n ])) ⟩

           subst (λ s → P (s [ n ])) lemma₁
                 (proj₂ (function (s [ n ])))                     ≡⟨ subst-∘ P _[ n ] lemma₁ ⟩

           subst P (cong _[ n ] lemma₁)
                 (proj₂ (function (s [ n ])))                     ≡⟨ cong (λ eq → subst P eq (proj₂ (function (s [ n ])))) lemma₃ ⟩∎

           subst P (lemma (s [ n ]))
                 (proj₂ (function (s [ n ])))                     ∎) ⟩

       pl s ( (λ n → subst P (lemma (s [ n ]))
                           (proj₂ (function (s [ n ]))))
            , ⊑-rec ∘ proj₂ s
            )                                             ≡⟨⟩

       pl s ( ⊥-rec ∘ proj₁ s
            , ⊑-rec ∘ proj₂ s
            )                                             ∎

-- For any partiality algebra (with certain levels) initiality (at
-- possibly larger levels) is logically equivalent to having
-- eliminators (at the same possibly larger levels).

initiality⇔eliminators :
  ∀ {a p q p′ q′} {A : Type a} →
  (P : Partiality-algebra p′ q′ A) →
  Initial (p ⊔ p′) (q ⊔ q′) P
    ⇔
  Elimination-principle (p ⊔ p′) (q ⊔ q′) P
initiality⇔eliminators P = record
  { to   = initiality→eliminators
  ; from = eliminators→initiality P
  }

-- For any partiality algebra initiality at all levels is logically
-- equivalent (at the meta-level) to having eliminators at all levels.

∀initiality→∀eliminators :
  ∀ {a p q} {A : Type a} →
  (P : Partiality-algebra p q A) →
  (∀ p q → Initial p q P) →
  (∀ p q → Elimination-principle p q P)
∀initiality→∀eliminators {p = p′} {q = q′} P initial p q =
  initiality→eliminators (initial (p ⊔ p′) (q ⊔ q′))

∀eliminators→∀initiality :
  ∀ {a p q} {A : Type a} →
  (P : Partiality-algebra p q A) →
  (∀ p q → Elimination-principle p q P) →
  (∀ p q → Initial p q P)
∀eliminators→∀initiality P elim p q =
  eliminators→initiality P (elim p q)


{-# OPTIONS -v tc.unquote:30 #-}
open import Common.Prelude
open import Common.Reflection
open import Agda.Builtin.Sigma

data Box : Bool → Set where
  box : (b : Bool) → Box b

works : (b : Bool) → Box b → Bool
works b (box .b) = unquote (give (var 0 []))

works₂ : (b : Bool) → Box b → Bool
unquoteDef works₂ = defineFun works₂ (clause
  ( ("b" , arg (argInfo visible relevant) unknown) ∷ [])
  ( arg (argInfo visible relevant) (var 0)
  ∷ arg (argInfo visible relevant) (con (quote box)
        (arg (argInfo visible relevant) (dot unknown) ∷ []))
  ∷ [])
  (var 0 []) ∷ [])

works₃ : (b : Bool) → Box b → (x y : Bool) → Bool
unquoteDef works₃ = defineFun works₃ (clause
  ( ("y" , arg (argInfo visible relevant) unknown)
  ∷ ("x" , arg (argInfo visible relevant) unknown)
  ∷ ("b" , arg (argInfo visible relevant) unknown)
  ∷ [])
  ( arg (argInfo visible relevant) (var 2)
  ∷ arg (argInfo visible relevant) (con (quote box)
        (arg (argInfo visible relevant) (dot unknown) ∷ []))
  ∷ arg (argInfo visible relevant) (var 1)
  ∷ arg (argInfo visible relevant) (var 0)
  ∷ [])
  (var 2 []) ∷ [])


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

open import Base
open import Homotopy.Pushout
open import Homotopy.VanKampen.Guide

{-
  This module provides the function code⇒path
  for the homotopy equivalence for
  van Kampen theorem.
-}

module Homotopy.VanKampen.CodeToPath {i} (d : pushout-diag i)
  (l : legend i (pushout-diag.C d)) where

  open pushout-diag d
  open legend l

  open import Homotopy.Truncation
  open import Homotopy.PathTruncation
  open import Homotopy.VanKampen.Code d l

  private
    pgl : ∀ n → _≡₀_ {A = P} (left (f $ loc n)) (right (g $ loc n))
    pgl n = proj (glue $ loc n)

    p!gl : ∀ n → _≡₀_ {A = P} (right (g $ loc n)) (left (f $ loc n))
    p!gl n = proj (! (glue $ loc n))

    ap₀l : ∀ {a₁ a₂} → a₁ ≡₀ a₂ → _≡₀_ {A = P} (left a₁) (left a₂)
    ap₀l p = ap₀ left p

    ap₀r : ∀ {b₁ b₂} → b₁ ≡₀ b₂ → _≡₀_ {A = P} (right b₁) (right b₂)
    ap₀r p = ap₀ right p

  module _ {a₁ : A} where
    private
      ap⇒path-split : (∀ {a₂} → a-code-a a₁ a₂ → _≡₀_ {A = P} (left a₁) (left  a₂))
                    × (∀ {b₂} → a-code-b a₁ b₂ → _≡₀_ {A = P} (left a₁) (right b₂))
      ap⇒path-split = a-code-rec-nondep a₁
        (λ a₂ → left a₁ ≡₀ left  a₂)
        ⦃ λ a₂ → π₀-is-set _ ⦄
        (λ b₂ → left a₁ ≡₀ right b₂)
        ⦃ λ b₂ → π₀-is-set _ ⦄
        (λ p → ap₀l p)
        (λ n pco p → (pco ∘₀ p!gl n) ∘₀ ap₀l p)
        (λ n pco p → (pco ∘₀ pgl  n) ∘₀ ap₀r p)
        (λ n {co} pco →
          (((pco ∘₀ pgl n) ∘₀ refl₀) ∘₀ p!gl n) ∘₀ refl₀
            ≡⟨ refl₀-right-unit _ ⟩
          ((pco ∘₀ pgl n) ∘₀ refl₀) ∘₀ p!gl n
            ≡⟨ ap (λ x → x ∘₀ p!gl n)
                  $ refl₀-right-unit $ pco ∘₀ pgl n ⟩
          (pco ∘₀ pgl n) ∘₀ p!gl n
            ≡⟨ concat₀-assoc pco (pgl n) (p!gl n) ⟩
          pco ∘₀ (pgl n ∘₀ p!gl n)
            ≡⟨ ap (λ x → pco ∘₀ proj x)
                  $ opposite-right-inverse $ glue $ loc n ⟩
          pco ∘₀ refl₀
            ≡⟨ refl₀-right-unit pco ⟩∎
          pco
            ∎)
        (λ n {co} pco →
          (((pco ∘₀ p!gl n) ∘₀ refl₀) ∘₀ pgl n) ∘₀ refl₀
            ≡⟨ refl₀-right-unit _ ⟩
          ((pco ∘₀ p!gl n) ∘₀ refl₀) ∘₀ pgl n
            ≡⟨ ap (λ x → x ∘₀ pgl n)
                  $ refl₀-right-unit $ pco ∘₀ p!gl n ⟩
          (pco ∘₀ p!gl n) ∘₀ pgl n
            ≡⟨ concat₀-assoc pco (p!gl n) (pgl n) ⟩
          pco ∘₀ (p!gl n ∘₀ pgl n)
            ≡⟨ ap (λ x → pco ∘₀ proj x)
                  $ opposite-left-inverse $ glue $ loc n ⟩
          pco ∘₀ refl₀
            ≡⟨ refl₀-right-unit pco ⟩∎
          pco
            ∎)
        (λ n₁ {co} pco n₂ r →
          (((pco ∘₀ pgl n₁) ∘₀ ap₀r (ap₀ g r)) ∘₀ p!gl n₂) ∘₀ refl₀
            ≡⟨ refl₀-right-unit _ ⟩
          ((pco ∘₀ pgl n₁) ∘₀ ap₀r (ap₀ g r)) ∘₀ p!gl n₂
            ≡⟨ concat₀-assoc (pco ∘₀ pgl n₁) (ap₀r (ap₀ g r)) (p!gl n₂) ⟩
          (pco ∘₀ pgl n₁) ∘₀ (ap₀r (ap₀ g r) ∘₀ p!gl n₂)
            ≡⟨ ap (λ x → (pco ∘₀ pgl n₁) ∘₀ (x ∘₀ p!gl n₂)) $ ! $ ap₀-compose right g r ⟩
          (pco ∘₀ pgl n₁) ∘₀ (ap₀ (right ◯ g) r ∘₀ p!gl n₂)
            ≡⟨ ap (λ x → (pco ∘₀ pgl n₁) ∘₀ x)
                  $ homotopy₀-naturality (right ◯ g) (left ◯ f) (proj ◯ (! ◯ glue)) r ⟩
          (pco ∘₀ pgl n₁) ∘₀ (p!gl n₁ ∘₀ ap₀ (left ◯ f) r)
            ≡⟨ ! $ concat₀-assoc (pco ∘₀ pgl n₁) (p!gl n₁) (ap₀ (left ◯ f) r) ⟩
          ((pco ∘₀ pgl n₁) ∘₀ p!gl n₁) ∘₀ ap₀ (left ◯ f) r
            ≡⟨ ap (λ x → ((pco ∘₀ pgl n₁) ∘₀ p!gl n₁) ∘₀ x) $ ap₀-compose left f r ⟩
          ((pco ∘₀ pgl n₁) ∘₀ p!gl n₁) ∘₀ ap₀l (ap₀ f r)
            ≡⟨ ap (λ x → (x ∘₀ p!gl n₁) ∘₀ ap₀l (ap₀ f r))
                  $ ! $ refl₀-right-unit $ pco ∘₀ pgl n₁ ⟩∎
          (((pco ∘₀ pgl n₁) ∘₀ refl₀) ∘₀ p!gl n₁) ∘₀ ap₀l (ap₀ f r)
            ∎)

    aa⇒path : ∀ {a₂} → a-code-a a₁ a₂ → _≡₀_ {A = P} (left a₁) (left  a₂)
    aa⇒path = π₁ ap⇒path-split
    ab⇒path : ∀ {b₂} → a-code-b a₁ b₂ → _≡₀_ {A = P} (left a₁) (right b₂)
    ab⇒path = π₂ ap⇒path-split

    ap⇒path : ∀ {p₂ : P} → a-code a₁ p₂ → left a₁ ≡₀ p₂
    ap⇒path {p₂} = pushout-rec
      (λ p → a-code a₁ p → left a₁ ≡₀ p)
      (λ a → aa⇒path {a})
      (λ b → ab⇒path {b})
      (loc-fiber-rec l
        (λ c → transport (λ p → a-code a₁ p → left a₁ ≡₀ p) (glue c) aa⇒path
             ≡ ab⇒path)
        ⦃ λ c → →-is-set (π₀-is-set (left a₁ ≡ right (g c))) _ _ ⦄
        (λ n → funext λ co →
            transport (λ p → a-code a₁ p → left a₁ ≡₀ p) (glue $ loc n) aa⇒path co
                ≡⟨ trans-→ (a-code a₁) (λ x → left a₁ ≡₀ x) (glue $ loc n) aa⇒path co ⟩
            transport (λ p → left a₁ ≡₀ p) (glue $ loc n) (aa⇒path $ transport (a-code a₁) (! (glue $ loc n)) co)
                ≡⟨ ap (transport (λ p → left a₁ ≡₀ p) (glue $ loc n) ◯ aa⇒path) $ trans-a-code-!glue-loc n co ⟩
            transport (λ p → left a₁ ≡₀ p) (glue $ loc n) (aa⇒path $ ab⇒aa n co)
                ≡⟨ trans-cst≡₀id (glue $ loc n) (aa⇒path $ ab⇒aa n co) ⟩
            (aa⇒path $ ab⇒aa n co) ∘₀ pgl n
                ≡⟨ refl ⟩
            ((ab⇒path co ∘₀ p!gl n) ∘₀ refl₀) ∘₀ pgl n
                ≡⟨ ap (λ x → x ∘₀ pgl n) $ refl₀-right-unit $ ab⇒path co ∘₀ p!gl n ⟩
            (ab⇒path co ∘₀ p!gl n) ∘₀ pgl n
                ≡⟨ concat₀-assoc (ab⇒path co) (p!gl n) (pgl n) ⟩
            ab⇒path co ∘₀ (p!gl n ∘₀ pgl n)
                ≡⟨ ap (λ x → ab⇒path co ∘₀ proj x) $ opposite-left-inverse $ glue $ loc n ⟩
            ab⇒path co ∘₀ refl₀
                ≡⟨ refl₀-right-unit $ ab⇒path co ⟩∎
            ab⇒path co
                ∎))
      p₂

  -- FIXME
  -- There is tension between reducing duplicate code and
  -- maintaining definitional equality. I could not make
  -- the neccessary type conversion definitional, so
  -- I just copied and pasted the previous module.
  module _ {b₁ : B} where
    private
      bp⇒path-split : (∀ {b₂} → b-code-b b₁ b₂ → _≡₀_ {A = P} (right b₁) (right  b₂))
                    × (∀ {a₂} → b-code-a b₁ a₂ → _≡₀_ {A = P} (right b₁) (left a₂))
      bp⇒path-split = b-code-rec-nondep b₁
        (λ b₂ → right b₁ ≡₀ right  b₂)
        ⦃ λ b₂ → π₀-is-set _ ⦄
        (λ a₂ → right b₁ ≡₀ left a₂)
        ⦃ λ a₂ → π₀-is-set _ ⦄
        (λ p → ap₀r p)
        (λ n pco p → (pco ∘₀ pgl  n) ∘₀ ap₀r p)
        (λ n pco p → (pco ∘₀ p!gl n) ∘₀ ap₀l p)
        (λ n {co} pco →
          (((pco ∘₀ p!gl n) ∘₀ refl₀) ∘₀ pgl n) ∘₀ refl₀
            ≡⟨ refl₀-right-unit _ ⟩
          ((pco ∘₀ p!gl n) ∘₀ refl₀) ∘₀ pgl n
            ≡⟨ ap (λ x → x ∘₀ pgl n)
                  $ refl₀-right-unit $ pco ∘₀ p!gl n ⟩
          (pco ∘₀ p!gl n) ∘₀ pgl n
            ≡⟨ concat₀-assoc pco (p!gl n) (pgl n) ⟩
          pco ∘₀ (p!gl n ∘₀ pgl n)
            ≡⟨ ap (λ x → pco ∘₀ proj x)
                  $ opposite-left-inverse $ glue $ loc n ⟩
          pco ∘₀ refl₀
            ≡⟨ refl₀-right-unit pco ⟩∎
          pco
            ∎)
        (λ n {co} pco →
          (((pco ∘₀ pgl n) ∘₀ refl₀) ∘₀ p!gl n) ∘₀ refl₀
            ≡⟨ refl₀-right-unit _ ⟩
          ((pco ∘₀ pgl n) ∘₀ refl₀) ∘₀ p!gl n
            ≡⟨ ap (λ x → x ∘₀ p!gl n)
                  $ refl₀-right-unit $ pco ∘₀ pgl n ⟩
          (pco ∘₀ pgl n) ∘₀ p!gl n
            ≡⟨ concat₀-assoc pco (pgl n) (p!gl n) ⟩
          pco ∘₀ (pgl n ∘₀ p!gl n)
            ≡⟨ ap (λ x → pco ∘₀ proj x)
                  $ opposite-right-inverse $ glue $ loc n ⟩
          pco ∘₀ refl₀
            ≡⟨ refl₀-right-unit pco ⟩∎
          pco
            ∎)
        (λ n₁ {co} pco n₂ r →
          (((pco ∘₀ p!gl n₁) ∘₀ ap₀l (ap₀ f r)) ∘₀ pgl n₂) ∘₀ refl₀
            ≡⟨ refl₀-right-unit _ ⟩
          ((pco ∘₀ p!gl n₁) ∘₀ ap₀l (ap₀ f r)) ∘₀ pgl n₂
            ≡⟨ concat₀-assoc (pco ∘₀ p!gl n₁) (ap₀l (ap₀ f r)) (pgl n₂) ⟩
          (pco ∘₀ p!gl n₁) ∘₀ (ap₀l (ap₀ f r) ∘₀ pgl n₂)
            ≡⟨ ap (λ x → (pco ∘₀ p!gl n₁) ∘₀ (x ∘₀ pgl n₂)) $ ! $ ap₀-compose left f r ⟩
          (pco ∘₀ p!gl n₁) ∘₀ (ap₀ (left ◯ f) r ∘₀ pgl n₂)
            ≡⟨ ap (λ x → (pco ∘₀ p!gl n₁) ∘₀ x)
                  $ homotopy₀-naturality (left ◯ f) (right ◯ g) (proj ◯  glue) r ⟩
          (pco ∘₀ p!gl n₁) ∘₀ (pgl n₁ ∘₀ ap₀ (right ◯ g) r)
            ≡⟨ ! $ concat₀-assoc (pco ∘₀ p!gl n₁) (pgl n₁) (ap₀ (right ◯ g) r) ⟩
          ((pco ∘₀ p!gl n₁) ∘₀ pgl n₁) ∘₀ ap₀ (right ◯ g) r
            ≡⟨ ap (λ x → ((pco ∘₀ p!gl n₁) ∘₀ pgl n₁) ∘₀ x) $ ap₀-compose right g r ⟩
          ((pco ∘₀ p!gl n₁) ∘₀ pgl n₁) ∘₀ ap₀r (ap₀ g r)
            ≡⟨ ap (λ x → (x ∘₀ pgl n₁) ∘₀ ap₀r (ap₀ g r))
                  $ ! $ refl₀-right-unit $ pco ∘₀ p!gl n₁ ⟩∎
          (((pco ∘₀ p!gl n₁) ∘₀ refl₀) ∘₀ pgl n₁) ∘₀ ap₀r (ap₀ g r)
            ∎)

    bb⇒path : ∀ {b₂} → b-code-b b₁ b₂ → _≡₀_ {A = P} (right b₁) (right  b₂)
    bb⇒path = π₁ bp⇒path-split
    ba⇒path : ∀ {a₂} → b-code-a b₁ a₂ → _≡₀_ {A = P} (right b₁) (left a₂)
    ba⇒path = π₂ bp⇒path-split

    bp⇒path : ∀ {p₂ : P} → b-code b₁ p₂ → right b₁ ≡₀ p₂
    bp⇒path {p₂} = pushout-rec
      (λ p → b-code b₁ p → right b₁ ≡₀ p)
      (λ a → ba⇒path {a})
      (λ b → bb⇒path {b})
      (λ c → loc-fiber-rec l
        (λ c → transport (λ p → b-code b₁ p → right b₁ ≡₀ p) (glue c) ba⇒path
             ≡ bb⇒path)
        ⦃ λ c → →-is-set (π₀-is-set (right b₁ ≡ right (g c))) _ _ ⦄
        (λ n → funext λ co →
            transport (λ p → b-code b₁ p → right b₁ ≡₀ p) (glue $ loc n) ba⇒path co
                ≡⟨ trans-→ (b-code b₁) (λ x → right b₁ ≡₀ x) (glue $ loc n) ba⇒path co ⟩
            transport (λ p → right b₁ ≡₀ p) (glue $ loc n) (ba⇒path $ transport (b-code b₁) (! (glue $ loc n)) co)
                ≡⟨ ap (transport (λ p → right b₁ ≡₀ p) (glue $ loc n) ◯ ba⇒path) $ trans-b-code-!glue-loc n co ⟩
            transport (λ p → right b₁ ≡₀ p) (glue $ loc n) (ba⇒path $ bb⇒ba n co)
                ≡⟨ trans-cst≡₀id (glue $ loc n) (ba⇒path $ bb⇒ba n co) ⟩
            (ba⇒path $ bb⇒ba n co) ∘₀ pgl n
                ≡⟨ refl ⟩
            ((bb⇒path co ∘₀ p!gl n) ∘₀ refl₀) ∘₀ pgl n
                ≡⟨ ap (λ x → x ∘₀ pgl n) $ refl₀-right-unit $ bb⇒path co ∘₀ p!gl n ⟩
            (bb⇒path co ∘₀ p!gl n) ∘₀ pgl n
                ≡⟨ concat₀-assoc (bb⇒path co) (p!gl n) (pgl n) ⟩
            bb⇒path co ∘₀ (p!gl n ∘₀ pgl n)
                ≡⟨ ap (λ x → bb⇒path co ∘₀ proj x) $ opposite-left-inverse $ glue $ loc n ⟩
            bb⇒path co ∘₀ refl₀
                ≡⟨ refl₀-right-unit $ bb⇒path co ⟩∎
            bb⇒path co
                ∎)
        c)
      p₂

  module _ where

    private
      Lbaaa : ∀ n {a₂} → b-code-a (g $ loc n) a₂ → Set i
      Lbaaa n co = aa⇒path {f $ loc n} (ba⇒aa n co) ≡ pgl n ∘₀ ba⇒path co
      Lbbab : ∀ n {b₂} → b-code-b (g $ loc n) b₂ → Set i
      Lbbab n co = ab⇒path {f $ loc n} (bb⇒ab n co) ≡ pgl n ∘₀ bb⇒path co

    private
      bp⇒ap⇒path-split : ∀ n
        → (∀ {a₂} co → Lbbab n {a₂} co)
        × (∀ {b₂} co → Lbaaa n {b₂} co)
    abstract
      bp⇒ap⇒path-split n = b-code-rec (g $ loc n)
        (λ co → ab⇒path {f $ loc n} (bb⇒ab n co) ≡ pgl n ∘₀ bb⇒path co)
        ⦃ λ _ → ≡-is-set $ π₀-is-set _ ⦄
        (λ co → aa⇒path {f $ loc n} (ba⇒aa n co) ≡ pgl n ∘₀ ba⇒path co)
        ⦃ λ _ → ≡-is-set $ π₀-is-set _ ⦄
        (λ p →
          ab⇒path (bb⇒ab n (⟧b p))
            ≡⟨ refl ⟩
          (refl₀ ∘₀ pgl n) ∘₀ ap₀r p
            ≡⟨ ap (λ x → x ∘₀ ap₀r p) $ refl₀-left-unit $ pgl n ⟩
          pgl n ∘₀ ap₀r p
            ≡⟨ refl ⟩∎
          pgl n ∘₀ bb⇒path (⟧b p)
            ∎)
        (λ n₁ {co} eq p₁ →
          (aa⇒path (ba⇒aa n co) ∘₀ pgl n₁) ∘₀ ap₀r p₁
            ≡⟨ ap (λ x → (x ∘₀ pgl n₁) ∘₀ ap₀r p₁) eq ⟩
          ((pgl n ∘₀ ba⇒path co) ∘₀ pgl n₁) ∘₀ ap₀r p₁
            ≡⟨ ap (λ x → x ∘₀ ap₀r p₁)
                  $ concat₀-assoc (pgl n) (ba⇒path co) (pgl n₁) ⟩
          (pgl n ∘₀ (ba⇒path co ∘₀ pgl n₁)) ∘₀ ap₀r p₁
            ≡⟨ concat₀-assoc (pgl n) (ba⇒path co ∘₀ pgl n₁) (ap₀r p₁) ⟩∎
          pgl n ∘₀ ((ba⇒path co ∘₀ pgl n₁) ∘₀ ap₀r p₁)
            ∎)
        (λ n₁ {co} eq p₁ →
          (ab⇒path (bb⇒ab n co) ∘₀ p!gl n₁) ∘₀ ap₀l p₁
            ≡⟨ ap (λ x → (x ∘₀ p!gl n₁) ∘₀ ap₀l p₁) eq ⟩
          ((pgl n ∘₀ bb⇒path co) ∘₀ p!gl n₁) ∘₀ ap₀l p₁
            ≡⟨ ap (λ x → x ∘₀ ap₀l p₁)
                  $ concat₀-assoc (pgl n) (bb⇒path co) (p!gl n₁) ⟩
          (pgl n ∘₀ (bb⇒path co ∘₀ p!gl n₁)) ∘₀ ap₀l p₁
            ≡⟨ concat₀-assoc (pgl n) (bb⇒path co ∘₀ p!gl n₁) (ap₀l p₁) ⟩∎
          pgl n ∘₀ ((bb⇒path co ∘₀ p!gl n₁) ∘₀ ap₀l p₁)
            ∎)
        (λ _ co → prop-has-all-paths (π₀-is-set _ _ _) _ co)
        (λ _ co → prop-has-all-paths (π₀-is-set _ _ _) _ co)
        (λ _ _ _ _ → prop-has-all-paths (π₀-is-set _ _ _) _ _)

    ba⇒aa⇒path : ∀ n {a₂} co → Lbaaa n {a₂} co
    ba⇒aa⇒path n = π₂ $ bp⇒ap⇒path-split n
    bb⇒ab⇒path : ∀ n {b₂} co → Lbbab n {b₂} co
    bb⇒ab⇒path n = π₁ $ bp⇒ap⇒path-split n

    private
      bp⇒ap⇒path : ∀ n {p₂} (co : b-code (g $ loc n) p₂)
        → ap⇒path {f $ loc n} {p₂} (bp⇒ap n {p₂} co)
        ≡ pgl n ∘₀ bp⇒path {g $ loc n} {p₂} co
    abstract
      bp⇒ap⇒path n {p₂} = pushout-rec
        (λ x → ∀ (co : b-code (g $ loc n) x)
               → ap⇒path {f $ loc n} {x} (bp⇒ap n {x} co)
               ≡ pgl n ∘₀ bp⇒path {g $ loc n} {x} co)
        (λ a₂ → ba⇒aa⇒path n {a₂})
        (λ b₂ → bb⇒ab⇒path n {b₂})
        (λ _ → funext λ _ → prop-has-all-paths (π₀-is-set _ _ _) _ _)
        p₂

    code⇒path : ∀ {p₁ p₂} → code p₁ p₂ → p₁ ≡₀ p₂
    code⇒path {p₁} {p₂} = pushout-rec
      (λ p₁ → code p₁ p₂ → p₁ ≡₀ p₂)
      (λ a → ap⇒path {a} {p₂})
      (λ b → bp⇒path {b} {p₂})
      (loc-fiber-rec l
        (λ c → transport (λ x → code x p₂ → x ≡₀ p₂) (glue c) (ap⇒path {f c} {p₂})
             ≡ bp⇒path {g c} {p₂})
        ⦃ λ c → →-is-set (π₀-is-set (right (g c) ≡ p₂)) _ _ ⦄
        (λ n → funext λ (co : code (right $ g $ loc n) p₂) →
          transport (λ x → code x p₂ → x ≡₀ p₂) (glue $ loc n) (ap⇒path {f $ loc n} {p₂}) co
              ≡⟨ trans-→ (λ x → code x p₂) (λ x → x ≡₀ p₂) (glue $ loc n) (ap⇒path {f $ loc n} {p₂}) co ⟩
          transport (λ x → x ≡₀ p₂) (glue $ loc n) (ap⇒path {f $ loc n} {p₂}
            $ transport (λ x → code x p₂) (! (glue $ loc n)) co)
              ≡⟨ ap (transport (λ x → x ≡₀ p₂) (glue $ loc n) ◯ ap⇒path {f $ loc n} {p₂})
                    $ trans-!glue-code-loc n {p₂} co ⟩
          transport (λ x → x ≡₀ p₂) (glue $ loc n) (ap⇒path {f $ loc n} {p₂} $ bp⇒ap n {p₂} co)
              ≡⟨ ap (transport (λ x → x ≡₀ p₂) (glue $ loc n)) $ bp⇒ap⇒path n {p₂} co ⟩
          transport (λ x → x ≡₀ p₂) (glue $ loc n) (pgl n ∘₀ bp⇒path {g $ loc n} {p₂} co)
              ≡⟨ trans-id≡₀cst (glue $ loc n) (pgl n ∘₀ bp⇒path {g $ loc n} {p₂} co) ⟩
          p!gl n ∘₀ (pgl n ∘₀ bp⇒path {g $ loc n} {p₂} co)
              ≡⟨ ! $ concat₀-assoc (p!gl n) (pgl n) (bp⇒path {g $ loc n} {p₂} co) ⟩
          (p!gl n ∘₀ pgl n) ∘₀ bp⇒path {g $ loc n} {p₂} co
              ≡⟨ ap (λ x → proj x ∘₀ bp⇒path {g $ loc n} {p₂} co) $ opposite-left-inverse (glue $ loc n) ⟩
          refl₀ ∘₀ bp⇒path {g $ loc n} {p₂} co
              ≡⟨ refl₀-left-unit (bp⇒path {g $ loc n} {p₂} co) ⟩∎
          bp⇒path {g $ loc n} {p₂} co
              ∎))
      p₁