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

module CauchyProofsT where

-- Proofs about permutations defined in module Cauchy (multiplicative)

open import Level using (Level; _⊔_) renaming (zero to lzero; suc to lsuc)

open import Relation.Binary.PropositionalEquality
  using (_≡_; refl; sym; trans; subst; subst₂; cong; cong₂; setoid;
        proof-irrelevance; module ≡-Reasoning)
open import Relation.Binary.PropositionalEquality.TrustMe
  using (trustMe)
open import Relation.Nullary using (Dec; yes; no; ¬_)
open import Data.Nat.Properties
  using (m≤m+n; n≤m+n; n≤1+n; cancel-*-right-≤; ≰⇒>; ¬i+1+j≤i)
open import Data.Nat.Properties.Simple
  using (+-right-identity; +-suc; +-assoc; +-comm;
        *-assoc; *-comm; *-right-zero; distribʳ-*-+; +-*-suc)
open import Data.Nat.DivMod using (DivMod; result; _divMod_; _div_; _mod_)
open import Relation.Binary using (Rel; Decidable; Setoid)
open import Relation.Binary.Core using (Transitive)

open import Data.String using (String)
  renaming (_++_ to _++S_)
open import Data.Nat.Show using (show)
open import Data.Bool using (Bool; false; true)
open import Data.Nat using (ℕ; suc; _+_; _∸_; _*_; _<_; _≮_; _≤_; _≰_;
  _≥_; z≤n; s≤s; _≟_; _≤?_; ≤-pred; module ≤-Reasoning)
open import Data.Fin
  using (Fin; zero; suc; toℕ; fromℕ; fromℕ≤; _ℕ-_; _≺_; reduce≥;
         raise; inject+; inject₁; inject≤; _≻toℕ_)
  renaming (_+_ to _F+_)
open import Data.Fin.Properties
  using (bounded; inject+-lemma; to-from; toℕ-injective; inject≤-lemma; toℕ-fromℕ≤)
open import Data.Vec.Properties
  using (lookup∘tabulate; tabulate∘lookup; lookup-allFin; tabulate-∘;
         tabulate-allFin; allFin-map; lookup-++-inject+; lookup-++-≥)
open import Data.Product using (Σ)

open import Data.List
  using (List; []; _∷_; _∷ʳ_; foldl; replicate; reverse; downFrom;
         concatMap; gfilter; initLast; InitLast; _∷ʳ'_)
  renaming (_++_ to _++L_; map to mapL; concat to concatL; zip to zipL)
open import Data.List.NonEmpty
  using (List⁺; [_]; _∷⁺_; head; last; _⁺++_)
  renaming (toList to nonEmptyListtoList; _∷ʳ_ to _n∷ʳ_; tail to ntail)
open import Data.List.Any using (Any; here; there; any; module Membership)
open import Data.Maybe using (Maybe; nothing; just; maybe′)
open import Data.Vec
  using (Vec; tabulate; []; _∷_; tail; lookup; zip; zipWith; splitAt;
         _[_]≔_; allFin; toList)
  renaming (_++_ to _++V_; map to mapV; concat to concatV)
open import Function using (id; _∘_; _$_; _∋_)

open import Data.Empty   using (⊥; ⊥-elim)
open import Data.Unit    using (⊤; tt)
open import Data.Sum     using (_⊎_; inj₁; inj₂)
open import Data.Product using (_×_; _,_; proj₁; proj₂)

open import Proofs
open import Cauchy
open import Perm
open import CauchyProofs

------------------------------------------------------------------------------
-- Proofs about multiplicative permutations

concat-nil : ∀ {m} →
  concatV (tabulate {m} (λ b → [])) ≡ subst Cauchy (sym (*-right-zero m)) []
concat-nil {0} = refl
concat-nil {suc m} = concat-nil {m}

empty-vec : ∀ {m} → (eq : m ≡ 0) → allFin m ≡ subst Cauchy (sym eq) []
empty-vec {0} refl = refl
empty-vec {suc m} ()

inj-lemma' : (m n : ℕ) (d : Fin n) (leq : suc (toℕ d) ≤ n + m) →
  inject+ m d ≡ inject≤ (fromℕ (toℕ d)) leq
inj-lemma' m 0 () _
inj-lemma' m (suc n) zero (s≤s _) = refl
inj-lemma' m (suc n) (suc d) (s≤s leq) = cong suc (inj-lemma' m n d leq)

inj-lemma : (m n : ℕ) (d : Fin n) (leq : suc (toℕ d) ≤ suc m * n) →
  inject+ (m * n) d ≡ inject≤ (fromℕ (toℕ d)) leq
inj-lemma m 0 () _
inj-lemma m (suc n) zero (s≤s _) = refl
inj-lemma m (suc n) (suc d) (s≤s leq) = cong suc (inj-lemma' (m * suc n) n d leq)

map-f≡g : {B : Set} (n : ℕ) (f g : Fin n → B) → (∀ i → f i ≡ g i) →
  mapV f (allFin n) ≡ mapV g (allFin n)
map-f≡g n f g eq =
  begin (mapV f (tabulate id)
           ≡⟨ sym (tabulate-∘ f id) ⟩
         tabulate f
           ≡⟨ finext eq ⟩
         tabulate g
           ≡⟨ tabulate-∘ g id ⟩
         mapV g (tabulate id) ∎)
  where open ≡-Reasoning

map-inj-lemma : (m n : ℕ) →
  (mapV (inject+ (m * n)) (allFin n)) ≡
  (mapV
    (λ d → inject≤
             (fromℕ (toℕ d))
             (i*n+k≤m*n {suc m} {n} zero d))
    (allFin n))
map-inj-lemma m n =
  map-f≡g n (inject+ (m * n))
            (λ d → inject≤ (fromℕ (toℕ d)) (i*n+k≤m*n {suc m} zero d))
            (λ d → inj-lemma m n d (i*n+k≤m*n {suc m} zero d))

map-raise-suc : (m n : ℕ) (j : Fin (suc m)) →
  mapV (λ d → raise (suc n)
                (inject≤
                  (fromℕ (toℕ j * suc n + toℕ d))
                   (i*n+k≤m*n j d)))
       (idcauchy (suc n)) ≡
  mapV (λ d → inject≤
                (fromℕ (toℕ (suc j) * suc n + toℕ d))
                (i*n+k≤m*n (suc j) d))
       (idcauchy (suc n))
map-raise-suc m n j = map-f≡g (suc n)
        (λ d → raise (suc n)
            (inject≤
              (fromℕ (toℕ j * suc n + toℕ d))
              (i*n+k≤m*n j d)))
        (λ d → inject≤
            (fromℕ (toℕ (suc j) * suc n + toℕ d))
            (i*n+k≤m*n (suc j) d))
        ( (λ d → raise-suc m n j d (i*n+k≤m*n j d) (i*n+k≤m*n (suc j) d)))

-- what should this be named?

lemma3 : (n m : ℕ) → (j : Fin m) →
  mapV (raise (suc n))
    (mapV (λ d → inject≤
                 (fromℕ (toℕ j * suc n + toℕ d))
                 (i*n+k≤m*n j d))
          (idcauchy (suc n)))
  ≡
  mapV (λ d → inject≤
              (fromℕ (toℕ (suc j) * suc n + toℕ d))
              (i*n+k≤m*n (suc j) d))
       (idcauchy (suc n))
lemma3 n 0 ()
lemma3 n (suc m) j =
  begin (
  _
    ≡⟨ sym (map-∘ (raise (suc n)) (λ d → inject≤
                 (fromℕ (toℕ j * suc n + toℕ d))
                 (i*n+k≤m*n j d)) (idcauchy (suc n))) ⟩
  mapV (λ d → raise (suc n) (inject≤
                 (fromℕ (toℕ j * suc n + toℕ d))
                 (i*n+k≤m*n j d))) (idcauchy (suc n))
    ≡⟨ map-raise-suc m n j ⟩
  _ ∎)
  where open ≡-Reasoning

map-raise-lemma : (m n : ℕ) →
  mapV
    (λ b → mapV
             (raise (suc n))
             (mapV
               (λ d → inject≤
                        (fromℕ (toℕ b * suc n + toℕ d))
                        (i*n+k≤m*n b d))
               (idcauchy (suc n))))
    (idcauchy m) ≡
  mapV
    (λ b → mapV
             (λ d → inject≤
                      (fromℕ (toℕ b * suc n + toℕ d))
                      (i*n+k≤m*n b d))
             (idcauchy (suc n)))
    (tabulate {m} suc)
map-raise-lemma 0 n = refl
map-raise-lemma (suc m) n =
  begin (
  mapV (λ b → mapV (raise (suc n))
             (mapV
               (λ d → inject≤
                        (fromℕ (toℕ b * suc n + toℕ d))
                        (i*n+k≤m*n b d))
               (idcauchy (suc n))))
    (idcauchy (suc m))
    ≡⟨ sym (tabulate-∘ _ id) ⟩
  tabulate  {suc m} (λ b → mapV (raise (suc n))
             (mapV
               (λ d → inject≤
                        (fromℕ (toℕ b * suc n + toℕ d))
                        (i*n+k≤m*n b d))
               (idcauchy (suc n))))
    ≡⟨ finext (lemma3 n (suc m)) ⟩
  tabulate {suc m} ((λ b → mapV
               (λ d → inject≤
                      (fromℕ (toℕ b * suc n + toℕ d))
                      (i*n+k≤m*n b d))
             (idcauchy (suc n))) ∘ suc)
    ≡⟨ tabulate-∘ _ suc ⟩
  mapV (λ b → mapV
             (λ d → inject≤
                      (fromℕ (toℕ b * suc n + toℕ d))
                      (i*n+k≤m*n b d))
             (idcauchy (suc n)))
    (tabulate {suc m} suc) ∎)
  where open ≡-Reasoning

allFin* : (m n : ℕ) → allFin (m * n) ≡
          concatV
            (mapV
              (λ b →
                mapV
                  (λ d → inject≤ (fromℕ (toℕ b * n + toℕ d)) (i*n+k≤m*n b d))
                  (idcauchy n))
            (idcauchy m))
allFin* 0 _ = refl
allFin* (suc m) 0 =
  begin (allFin (suc m * 0)
           ≡⟨ empty-vec {suc m * 0} (*-right-zero (suc m)) ⟩
         subst Cauchy (sym (*-right-zero (suc m))) []
           ≡⟨ sym (concat-nil {suc m}) ⟩
         concatV (tabulate {suc m} (λ b → []))
           ≡⟨ cong concatV (tabulate-allFin {suc m} (λ b → [])) ⟩
         concatV (mapV (λ b → []) (idcauchy (suc m))) ∎)
  where open ≡-Reasoning
allFin* (suc m) (suc n) =
  begin (allFin (suc n + m * suc n)
           ≡⟨ allFin+ (suc n) (m * suc n) ⟩
         mapV (inject+ (m * suc n)) (allFin (suc n)) ++V
         mapV (raise (suc n)) (allFin (m * suc n))
           ≡⟨ cong
                (λ x →
                  mapV (inject+ (m * suc n)) (allFin (suc n)) ++V
                  mapV (raise (suc n)) x)
                (allFin* m (suc n)) ⟩
         mapV (inject+ (m * suc n)) (allFin (suc n)) ++V
         mapV
           (raise (suc n))
           (concatV
             (mapV
               (λ b →
                 mapV
                   (λ d → inject≤
                            (fromℕ (toℕ b * suc n + toℕ d))
                            (i*n+k≤m*n b d))
                   (idcauchy (suc n)))
               (idcauchy m)))
           ≡⟨ cong
                (_++V_ (mapV (inject+ (m * suc n)) (allFin (suc n))))
                (sym (concat-map
                       (mapV
                         (λ b →
                           mapV
                             (λ d → inject≤
                                      (fromℕ (toℕ b * suc n + toℕ d))
                                      (i*n+k≤m*n b d))
                             (idcauchy (suc n)))
                         (idcauchy m))
                       (raise (suc n)))) ⟩
         mapV (inject+ (m * suc n)) (allFin (suc n)) ++V
         concatV
           (mapV
             (mapV (raise (suc n)))
             (mapV
               (λ b →
                 mapV
                   (λ d → inject≤
                            (fromℕ (toℕ b * suc n + toℕ d))
                            (i*n+k≤m*n b d))
                   (idcauchy (suc n)))
               (idcauchy m)))
           ≡⟨ refl ⟩
         concatV
           (mapV (inject+ (m * suc n)) (allFin (suc n)) ∷
            mapV
              (mapV (raise (suc n)))
              (mapV
                (λ b →
                  mapV
                    (λ d → inject≤
                             (fromℕ (toℕ b * suc n + toℕ d))
                             (i*n+k≤m*n b d))
                    (idcauchy (suc n)))
                (idcauchy m)))
           ≡⟨ cong
                 (λ x →
                   concatV
                     ((mapV (inject+ (m * suc n)) (allFin (suc n))) ∷ x))
                 (sym (map-∘
                        (mapV (raise (suc n)))
                        (λ b →
                          mapV
                            (λ d → inject≤
                                     (fromℕ (toℕ b * suc n + toℕ d))
                                     (i*n+k≤m*n b d))
                            (idcauchy (suc n)))
                        (idcauchy m))) ⟩
         concatV
           (mapV (inject+ (m * suc n)) (allFin (suc n)) ∷
            mapV
              (λ b → mapV
                       (raise (suc n))
                       (mapV
                         (λ d → inject≤
                                  (fromℕ (toℕ b * suc n + toℕ d))
                                  (i*n+k≤m*n b d))
                         (idcauchy (suc n))))
              (idcauchy m))
           ≡⟨ cong
                (λ x →
                  concatV
                    (x ∷
                    mapV
                      (λ b →
                        mapV
                          (raise (suc n))
                          (mapV
                            (λ d → inject≤
                                     (fromℕ (toℕ b * suc n + toℕ d))
                                     (i*n+k≤m*n b d))
                            (idcauchy (suc n))))
                      (idcauchy m)))
                (map-inj-lemma m (suc n)) ⟩
         concatV
           (mapV
              (λ d → inject≤
                       (fromℕ (toℕ d))
                       (i*n+k≤m*n {suc m} {suc n} zero d))
              (idcauchy (suc n)) ∷
            (mapV
              (λ b → mapV
                       (raise (suc n))
                       (mapV
                         (λ d → inject≤
                                  (fromℕ (toℕ b * suc n + toℕ d))
                                  (i*n+k≤m*n b d))
                         (idcauchy (suc n))))
              (idcauchy m)))
           ≡⟨ cong
                 (λ x →
                   concatV
                     (mapV
                       (λ d → inject≤
                                (fromℕ (toℕ d))
                                (i*n+k≤m*n {suc m} {suc n} zero d))
                       (idcauchy (suc n)) ∷
                     x))
                 (map-raise-lemma m n) ⟩
         concatV
           (mapV
              (λ d → inject≤
                       (fromℕ (toℕ d))
                       (i*n+k≤m*n {suc m} {suc n} zero d))
              (idcauchy (suc n)) ∷
            (mapV
               (λ b → mapV
                        (λ d → inject≤
                                 (fromℕ (toℕ b * suc n + toℕ d))
                                 (i*n+k≤m*n b d))
                        (idcauchy (suc n)))
               (tabulate {m} suc)))
           ≡⟨ refl ⟩
         concatV
           (mapV
             (λ b →
               mapV
                 (λ d → inject≤
                          (fromℕ (toℕ b * suc n + toℕ d))
                          (i*n+k≤m*n b d))
                 (idcauchy (suc n)))
             (idcauchy (suc m))) ∎)
  where open ≡-Reasoning

tcomp-id : ∀ {m n} → tcompcauchy (idcauchy m) (idcauchy n) ≡ idcauchy (m * n)
tcomp-id {m} {n} = sym (allFin* m n)

-- Behaviour of parallel multiplicative composition wrt sequential

absurd : (m n q : ℕ) (r : Fin (suc n)) (k : Fin (suc (n + m * suc n)))
         (k≡r+q*sn : toℕ k ≡ toℕ r + q * suc n) (p : suc m ≤ q) → ⊥
absurd m n q r k k≡r+q*sn p = ¬i+1+j≤i (toℕ k) {toℕ r} k≥k+sr
  where k≥k+sr : toℕ k ≥ toℕ k + suc (toℕ r)
        k≥k+sr =
          begin (toℕ k + suc (toℕ r)
                   ≡⟨ +-suc (toℕ k) (toℕ r) ⟩
                 suc (toℕ k) + toℕ r
                   ≤⟨ cong+r≤ (bounded k) (toℕ r) ⟩
                 (suc n + m * suc n) + toℕ r
                   ≡⟨ +-comm (suc n + m * suc n) (toℕ r) ⟩
                 toℕ r + (suc n + m * suc n)
                   ≡⟨ refl ⟩
                 toℕ r + suc m * suc n
                   ≤⟨ cong+l≤ (cong*r≤ p (suc n)) (toℕ r) ⟩
                 toℕ r + q * suc n
                   ≡⟨ sym k≡r+q*sn ⟩
                 toℕ k ∎)
          where open ≤-Reasoning

concat-tabulate-[] : ∀ {m n} {f : Fin m →  Fin n} →
    concatV (tabulate {m} ((λ x → []) ∘ f)) ≡
    subst (λ n → Vec (Fin (m * 0)) n) (sym (*-right-zero m)) []
concat-tabulate-[] {0} = refl
concat-tabulate-[] {suc m} {_} {f} = concat-tabulate-[] {m} {_} {f ∘ suc}

tabulate-⊥ : ∀ {m n} {g : Fin (m * 0) → Fin n} →
  tabulate g ≡ subst (Vec (Fin n)) (sym (*-right-zero m)) []
tabulate-⊥ {0} = refl
tabulate-⊥ {suc m}{_} {g} = tabulate-⊥ {m}

toℕ-inject+ : ∀ {n k} → (i : Fin n) → toℕ (inject+ k i) ≡ toℕ i
toℕ-inject+ {0} ()
toℕ-inject+ {suc n} zero = refl
toℕ-inject+ {suc n} (suc i) = cong suc (toℕ-inject+ i)

q≡ : (m : ℕ) (q : ℕ) (¬p : ¬ suc m ≤ q) →
  q ≡ toℕ (fromℕ≤ (s≤s (≤-pred (≰⇒> ¬p))))
q≡ m q ¬p = sym (toℕ-fromℕ≤ (s≤s (≤-pred (≰⇒> ¬p))))

absurd2 : (n q : ℕ) (i : Fin (suc n)) (r  : Fin (suc (suc n)))
  (eq : suc (toℕ i) ≡ toℕ r + suc (suc (n + q * suc (suc n)))) → ⊥
absurd2 n q i r eq = ¬i+1+j≤i (suc (toℕ i)) {toℕ r} leq
  where leq' = begin (suc (toℕ i)
                   ≤⟨ bounded i ⟩
                      suc n
                   ≤⟨ m≤m+n (suc n) (q * suc (suc n)) ⟩
                      suc (n + q * suc (suc n)) ∎)
               where open ≤-Reasoning
        leq = begin (suc (toℕ i) + suc (toℕ r)
                   ≡⟨ +-suc (suc (toℕ i)) (toℕ r) ⟩
                     suc (suc (toℕ i)) + toℕ r
                   ≡⟨ +-comm (suc (suc (toℕ i))) (toℕ r) ⟩
                     toℕ r + suc (suc (toℕ i))
                   ≤⟨ cong+l≤ (s≤s leq') (toℕ r) ⟩
                     toℕ r + suc (suc (n + q * suc (suc n)))
                   ≡⟨ sym eq ⟩
                     suc (toℕ i) ∎)
              where open ≤-Reasoning

small-quotient : (n q : ℕ) → (i : Fin n) → (r : Fin (suc n)) →
  (eq₁ : suc (toℕ i) ≡ toℕ r + q * suc n) → (q ≡ 0) × (toℕ r ≡ suc (toℕ i))
small-quotient 0 _ () _ _
small-quotient (suc n) 0 i r eq = (refl , sym  (trans eq (+-right-identity (toℕ r))))
small-quotient (suc n) (suc q) i r eq = ⊥-elim (absurd2 n q i r eq)

first-row :
  (m n : ℕ) → (f : Fin (suc m) × Fin (suc n) → Fin ((suc m) * (suc n))) →
  tabulate {suc n} (λ x → f (zero , x)) ≡
  tabulate {suc n} ((λ k →  f (fin-project (suc m) (suc n) k)) ∘ inject+ (m * suc n))
first-row m n f =
  finext pf
    where
      pf : (i : Fin (suc n)) →  f (zero , i) ≡
           f (fin-project (suc m) (suc n) (inject+ (m * suc n) i))
      pf zero = refl
      pf (suc i) with suc (toℕ (inject+ (m * suc n) i)) divMod suc n
      pf (suc i) | result q r property with suc m ≤? q
      pf (suc i) | result q r property | yes p =
                ⊥-elim (absurd m n q r (suc (inject+ (m * suc n) i)) property p)
      pf (suc i) | result q r property | no ¬p =
                cong f (cong₂ _,_ (toℕ-injective (sym a₁)) a₂)
        where
           q≡f₁ : q ≡ toℕ (fromℕ≤ (s≤s (≤-pred (≰⇒> ¬p))))
           q≡f₁ = q≡ m q ¬p
           si≡f₂ : _≡_ {A = ℕ} (suc (toℕ (inject+ (m * suc n) i)))  (suc (toℕ i))
           si≡f₂ = cong suc (toℕ-inject+ {k = m * suc n} i)
           sq : (q ≡ 0) × (toℕ r ≡ suc (toℕ i))
           sq = small-quotient n q i r (trans (sym si≡f₂) property)
           a₁ : toℕ (fromℕ≤ (s≤s (≤-pred (≰⇒> ¬p)))) ≡ toℕ {suc q} zero
           a₁ = trans (sym q≡f₁) (proj₁ sq)
           a₂ :  (suc i) ≡ r
           a₂ = toℕ-injective (sym (proj₂ sq))

lookup-2d' : (m n : ℕ) (k : Fin (m * n))
  (f : Fin m × Fin n → Fin (m * n)) →
  lookup k (concatV (tabulate (λ i → tabulate (λ j → f (i , j)))))
  ≡ f (fin-project m n k)
lookup-2d' m n k f =
  let fin-result b d dec dec' = fin-divMod m n k in
  begin (lookup k (concatV (tabulate (λ i → tabulate (λ j → f (i , j)))))
        ≡⟨ cong (λ x → lookup k (concatV x))
            (finext (λ i → tabulate-allFin (λ j → f (i , j)))) ⟩
         lookup k (concatV (tabulate (λ i → mapV (λ j → f (i , j)) (allFin n))))
        ≡⟨ cong
             (λ x → lookup k (concatV x))
             (tabulate-allFin (λ i → mapV (λ j → f (i , j)) (allFin n))) ⟩
         lookup k (concatV (mapV (λ i → mapV (λ j → f (i , j)) (allFin n)) (allFin m)))
        ≡⟨ lookup-2d m n k (allFin m) (allFin n) f ⟩
         f (lookup b (allFin m) , lookup d (allFin n))
        ≡⟨ cong₂ (λ x y → f (x , y)) (lookup-allFin b) (lookup-allFin d) ⟩
         f (b , d)
        ≡⟨ cong f (sym (inv' (fin-product-iso m n) (b , d))) ⟩
         f (split (fin-product-iso m n) (combine (fin-product-iso m n) (b , d)))
        ≡⟨ refl ⟩
         f (fin-project m n (combine (fin-product-iso m n) (b , d)))
        ≡⟨ cong
             (λ x → f (fin-project m n x))
             (sym dec') ⟩
         f (fin-project m n k) ∎)
  where open ≡-Reasoning
        open Fin-Product-Iso

tabulate-concat : ∀ {m n} → (f : Fin m × Fin n → Fin (m * n)) →
  concatV (tabulate {m} (λ i → tabulate {n} (λ j → f (i , j)))) ≡
  tabulate {m * n} (λ (k : Fin (m * n)) → f (fin-project m n k))
tabulate-concat {m} {n} f =
  begin (concatV (tabulate {m} (λ i → tabulate {n} (λ j → f (i , j))))
       ≡⟨ sym (tabulate∘lookup
                (concatV (tabulate {m} (λ i → tabulate {n} (λ j → f (i , j)))))) ⟩
         tabulate (λ i →
           lookup i
             (concatV (tabulate {m} (λ i → tabulate {n} (λ j → f (i , j))))))
       ≡⟨ finext (λ i → lookup-2d' m n i f) ⟩
         tabulate {m * n} (λ (k : Fin (m * n)) → f (fin-project m n k)) ∎)
  where open ≡-Reasoning

lookup-concat :
  ∀ {m n} → (k : Fin (m * n)) → (pm qm : Cauchy m) → (pn qn : Cauchy n) →
  let vs = concatV
             (mapV
               (λ b →
                 mapV
                   (λ d → inject≤ (fromℕ (toℕ b * n + toℕ d)) (i*n+k≤m*n b d))
                   pn)
             pm)
      ws = concatV
             (mapV
               (λ b →
                 mapV
                   (λ d → inject≤ (fromℕ (toℕ b * n + toℕ d)) (i*n+k≤m*n b d))
                   qn)
             qm)
  in lookup (lookup k vs) ws
  ≡
  let (b , d) = fin-project m n k
      x = lookup (lookup b pm) qm
      y = lookup (lookup d pn) qn
  in inject≤ (fromℕ (toℕ x * n + toℕ y)) (i*n+k≤m*n x y)
lookup-concat {0} () pm qm pn qn
lookup-concat {suc m} {0} k pm qm [] [] =
  ⊥-elim (Fin0-⊥ (subst Fin (*-right-zero m) k))
lookup-concat {suc m} {suc n} zero (x ∷ pm) (x₁ ∷ qm) (x₂ ∷ pn) (x₃ ∷ qn) =
  begin (lookup
           (inject≤ (fromℕ (toℕ x * suc n + toℕ x₂)) (i*n+k≤m*n x x₂))
           (concatV
             (mapV
               (λ b →
                 mapV
                   (λ d → inject≤ (fromℕ (toℕ b * suc n + toℕ d)) (i*n+k≤m*n b d))
                   (x₃ ∷ qn))
               (x₁ ∷ qm)))
       ≡⟨ lookup-concat'
            (suc m) (suc n) x x₂ (i*n+k≤m*n x x₂)
            (λ {(b , d) → inject≤ (fromℕ (toℕ b * suc n + toℕ d)) (i*n+k≤m*n b d)})
            (x₁ ∷ qm) (x₃ ∷ qn) ⟩
         inject≤
           (fromℕ (toℕ (lookup x (x₁ ∷ qm)) * suc n + toℕ (lookup x₂ (x₃ ∷ qn))))
           (i*n+k≤m*n (lookup x (x₁ ∷ qm)) (lookup x₂ (x₃ ∷ qn)))
       ≡⟨ refl ⟩
         let (b , d) = fin-project (suc m) (suc n) zero
             x = lookup (lookup b (x ∷ pm)) (x₁ ∷ qm)
             y = lookup (lookup d (x₂ ∷ pn)) (x₃ ∷ qn)
         in inject≤ (fromℕ (toℕ x * suc n + toℕ y)) (i*n+k≤m*n x y) ∎)
  where open ≡-Reasoning
lookup-concat {suc m} {suc n} (suc k) (x ∷ pm) (x₁ ∷ qm) (x₂ ∷ pn) (x₃ ∷ qn) =
  begin (let vs = concatV
                    (mapV
                      (λ b →
                        mapV
                          (λ d →
                            inject≤ (fromℕ (toℕ b * suc n + toℕ d)) (i*n+k≤m*n b d))
                          (x₂ ∷ pn))
                      (x ∷ pm))
             ws = concatV
                    (mapV
                      (λ b →
                        mapV
                          (λ d →
                            inject≤ (fromℕ (toℕ b * suc n + toℕ d)) (i*n+k≤m*n b d))
                          (x₃ ∷ qn))
                    (x₁ ∷ qm))
         in lookup (lookup (suc k) vs) ws
       ≡⟨ cong (λ z →
                lookup
                  (lookup z
                    (concatV
                      (mapV
                        (λ b →
                          mapV
                            (λ d →
                              inject≤ (fromℕ (toℕ b * suc n + toℕ d)) (i*n+k≤m*n b d))
                            (x₂ ∷ pn))
                        (x ∷ pm))))
                  (concatV
                    (mapV
                      (λ b →
                        mapV
                          (λ d →
                            inject≤ (fromℕ (toℕ b * suc n + toℕ d)) (i*n+k≤m*n b d))
                          (x₃ ∷ qn))
                    (x₁ ∷ qm))))
                  (fin-proj-lem (suc m) (suc n) (suc k)) ⟩
         let (b' , d') = fin-project (suc m) (suc n) (suc k)
             vs = concatV
                    (mapV
                      (λ b →
                        mapV
                          (λ d →
                            inject≤ (fromℕ (toℕ b * suc n + toℕ d)) (i*n+k≤m*n b d))
                          (x₂ ∷ pn))
                      (x ∷ pm))
             ws = concatV
                    (mapV
                      (λ b →
                        mapV
                          (λ d →
                            inject≤ (fromℕ (toℕ b * suc n + toℕ d)) (i*n+k≤m*n b d))
                          (x₃ ∷ qn))
                    (x₁ ∷ qm))
         in lookup
              (lookup
                (inject≤ (fromℕ (toℕ b' * (suc n) + toℕ d')) (i*n+k≤m*n b' d'))
                vs)
              ws
       ≡⟨ cong
            (λ x → lookup x ws)
            (lookup-concat' (suc m) (suc n) b' d' (i*n+k≤m*n b' d')
              (λ {(b , d) → inject≤ (fromℕ (toℕ b * suc n + toℕ d)) (i*n+k≤m*n b d)})
              (x ∷ pm) (x₂ ∷ pn)) ⟩
         let (b' , d') = fin-project (suc m) (suc n) (suc k)
             ws = concatV
                    (mapV
                      (λ b →
                        mapV
                          (λ d →
                            inject≤ (fromℕ (toℕ b * suc n + toℕ d)) (i*n+k≤m*n b d))
                          (x₃ ∷ qn))
                    (x₁ ∷ qm))
         in lookup
              (inject≤
                (fromℕ (toℕ (lookup b' (x ∷ pm)) * suc n + toℕ (lookup d' (x₂ ∷ pn))))
                (i*n+k≤m*n (lookup b' (x ∷ pm)) (lookup d' (x₂ ∷ pn))))
              ws
       ≡⟨ lookup-concat' (suc m) (suc n) (lookup b' (x ∷ pm)) (lookup d' (x₂ ∷ pn))
             (i*n+k≤m*n (lookup b' (x ∷ pm)) (lookup d' (x₂ ∷ pn)))
             (λ {(b , d) → inject≤ (fromℕ (toℕ b * suc n + toℕ d)) (i*n+k≤m*n b d)})
             (x₁ ∷ qm) (x₃ ∷ qn) ⟩
         let (b , d) = fin-project (suc m) (suc n) (suc k)
             r = lookup (lookup b (x ∷ pm)) (x₁ ∷ qm)
             s = lookup (lookup d (x₂ ∷ pn)) (x₃ ∷ qn)
         in inject≤
              (fromℕ (toℕ r * suc n + toℕ s))
              (i*n+k≤m*n r s) ∎)
  where open ≡-Reasoning

tcomp-dist : ∀ {m n} → (pm qm : Cauchy m) → (pn qn : Cauchy n) →
  scompcauchy (tcompcauchy pm pn) (tcompcauchy qm qn) ≡
  tcompcauchy (scompcauchy pm qm) (scompcauchy pn qn)
tcomp-dist {m} {n} pm qm pn qn =
  begin (scompcauchy (tcompcauchy pm pn) (tcompcauchy qm qn)
           ≡⟨ refl ⟩
         tabulate {m * n} (λ k →
           lookup
             (lookup k (concatV
                         (mapV
                           (λ b →
                             mapV (λ d →
                               inject≤
                                 (fromℕ (toℕ b * n + toℕ d))
                                 (i*n+k≤m*n b d)) pn)
                           pm)))
             (concatV
               (mapV
                 (λ b →
                   mapV (λ d →
                     inject≤
                       (fromℕ (toℕ b * n + toℕ d))
                       (i*n+k≤m*n b d)) qn)
                 qm)))
           ≡⟨  finext (λ k → lookup-concat {m} {n} k pm qm pn qn) ⟩
         tabulate {m * n}
           (λ k →
             let (i , j) = fin-project m n k
                 b = lookup (lookup i pm) qm
                 d = lookup (lookup j pn) qn in
             inject≤ (fromℕ (toℕ b * n + toℕ d)) (i*n+k≤m*n b d))
           ≡⟨  sym (tabulate-concat
                 (λ {(i , j) →
                   let b = lookup (lookup i pm) qm
                       d = lookup (lookup j pn) qn in
                   inject≤ (fromℕ (toℕ b * n + toℕ d)) (i*n+k≤m*n b d)})) ⟩
         concatV
           (tabulate {m}
             (λ i →
               tabulate {n}
                 (λ j →
                   let b = lookup (lookup i pm) qm
                       d = lookup (lookup j pn) qn in
                   inject≤ (fromℕ (toℕ b * n + toℕ d)) (i*n+k≤m*n b d))))
           ≡⟨  cong concatV
                 (finext
                   (λ i →
                     tabulate-∘
                       (let b = lookup (lookup i pm) qm in
                        λ d →
                          inject≤ (fromℕ (toℕ b * n + toℕ d)) (i*n+k≤m*n b d))
                       (λ i → lookup (lookup i pn) qn))) ⟩
         concatV
           (tabulate {m}
             (λ i →
               let b = lookup (lookup i pm) qm in
               mapV
                 (λ d → inject≤ (fromℕ (toℕ b * n + toℕ d)) (i*n+k≤m*n b d))
                 (tabulate (λ i → lookup (lookup i pn) qn))))
           ≡⟨ cong concatV (tabulate-∘
                (λ b →
                  mapV
                    (λ d → inject≤ (fromℕ (toℕ b * n + toℕ d)) (i*n+k≤m*n b d))
                    (tabulate (λ i → lookup (lookup i pn) qn)))
                 (λ i → lookup (lookup i pm) qm)) ⟩
         concatV
           (mapV
             (λ b →
               mapV
                 (λ d → inject≤ (fromℕ (toℕ b * n + toℕ d)) (i*n+k≤m*n b d))
                 (tabulate (λ i → lookup (lookup i pn) qn)))
             (tabulate (λ i → lookup (lookup i pm) qm)))
           ≡⟨  refl ⟩
         tcompcauchy (scompcauchy pm qm) (scompcauchy pn qn) ∎)
  where open ≡-Reasoning

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


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

open import Definition.Typed.EqualityRelation

module Definition.LogicalRelation.Substitution.Introductions.Snd {{eqrel : EqRelSet}} where
open EqRelSet {{...}}

open import Definition.Untyped as U hiding (wk)
open import Definition.Untyped.Properties
open import Definition.Typed
open import Definition.Typed.Properties
open import Definition.Typed.Weakening as T hiding (wk; wkTerm; wkEqTerm)
open import Definition.Typed.RedSteps
open import Definition.LogicalRelation
open import Definition.LogicalRelation.ShapeView
open import Definition.LogicalRelation.Irrelevance
open import Definition.LogicalRelation.Weakening
open import Definition.LogicalRelation.Properties
open import Definition.LogicalRelation.Application
open import Definition.LogicalRelation.Substitution
open import Definition.LogicalRelation.Substitution.Properties
open import Definition.LogicalRelation.Substitution.Introductions.Pi
open import Definition.LogicalRelation.Substitution.Introductions.SingleSubst
open import Definition.LogicalRelation.Substitution.Introductions.Fst

open import Tools.Product
import Tools.PropositionalEquality as PE

snd-subst*′ : ∀ {Γ l l′ F G t t′}
             ([F] : Γ ⊩⟨ l ⟩ F)
             ([ΣFG] : Γ ⊩⟨ l′ ⟩B⟨ BΣ ⟩ Σ F ▹ G)
             ([t′] : Γ ⊩⟨ l′ ⟩ t′ ∷ Σ F ▹ G / B-intr BΣ [ΣFG])
             → Γ ⊢ t ⇒* t′ ∷ Σ F ▹ G
             → Γ ⊢ snd t ⇒* snd t′ ∷ G [ fst t ]
snd-subst*′ [F] (noemb (Bᵣ F G D ⊢F ⊢G A≡A [F]₁ [G] G-ext)) _ (id x) with
              B-PE-injectivity BΣ (whnfRed* (red D) Σₙ)
... | PE.refl , PE.refl = id (sndⱼ ⊢F ⊢G x)
snd-subst*′ {Γ = Γ} {F = F} {G = G} {t = t} {t′ = t″} [F]
            [ΣFG]₀@(noemb (Bᵣ F₁ G₁ D ⊢F ⊢G A≡A [F]₁ [G] G-ext))
            [t″]
            t⇒*t″@(_⇨_ {t′ = t′} t⇒t′ t′⇒*t″) with
              B-PE-injectivity BΣ (whnfRed* (red D) Σₙ)
... | PE.refl , PE.refl =
  let [ΣFG] = B-intr BΣ [ΣFG]₀
      [t] , _ = redSubst*Term t⇒*t″ [ΣFG] [t″]
      [t′] , _ = redSubst*Term t′⇒*t″ [ΣFG] [t″]
      [t≡t′] : Γ ⊩⟨ _ ⟩ t ≡ t′ ∷ Σ F ▹ G / [ΣFG]
      [t≡t′] = proj₂ (redSubstTerm t⇒t′ [ΣFG] [t′])
      [fstt] : Γ ⊩⟨ _ ⟩ fst t ∷ F / [F]
      [fstt] = fst″ [F] [ΣFG] [t]
      [fstt′] : Γ ⊩⟨ _ ⟩ fst t′ ∷ F / [F]
      [fstt′] = fst″ [F] [ΣFG] [t′]
      [Gfstt] : Γ ⊩⟨ _ ⟩ G [ fst t ]
      [Gfstt] = substSΠ₁ BΣ [ΣFG] [F] [fstt]
      [Gfstt′] : Γ ⊩⟨ _ ⟩ G [ fst t′ ]
      [Gfstt′] = substSΠ₁ BΣ [ΣFG] [F] [fstt′]
      [fstt′≡fstt] : Γ ⊩⟨ _ ⟩ fst t′ ≡ fst t ∷ F / [F]
      [fstt′≡fstt] = symEqTerm [F] (fst-cong″ [F] [ΣFG] [t≡t′])
      [Gfstt′≡Gfstt] : Γ ⊩⟨ _ ⟩ G [ fst t′ ] ≡ G [ fst t ] / [Gfstt′]
      [Gfstt′≡Gfstt] = substSΠ₂ BΣ [ΣFG] (reflEq [ΣFG]) [F] [F] [fstt′] [fstt] [fstt′≡fstt] [Gfstt′] [Gfstt]
  in  snd-subst ⊢F ⊢G t⇒t′ ⇨ conv* (snd-subst*′ [F] [ΣFG]₀ [t″] t′⇒*t″)
                                               (≅-eq (escapeEq [Gfstt′] [Gfstt′≡Gfstt]))
snd-subst*′ [F] (emb 0<1 x) = snd-subst*′ [F] x

-- NOTE this has a horrible interface (and implementation)
snd-subst* : ∀ {Γ l l′ F G t t′}
             ([F] : Γ ⊩⟨ l ⟩ F)
             ([ΣFG] : Γ ⊩⟨ l′ ⟩ Σ F ▹ G)
             ([t′] : Γ ⊩⟨ l′ ⟩ t′ ∷ Σ F ▹ G / [ΣFG])
             → Γ ⊢ t ⇒* t′ ∷ Σ F ▹ G
             → Γ ⊢ snd t ⇒* snd t′ ∷ G [ fst t ]
snd-subst* [F] [ΣFG] [t′] t⇒*t′ =
  let [t′]′ = irrelevanceTerm [ΣFG] (B-intr BΣ (B-elim BΣ [ΣFG])) [t′]
  in  snd-subst*′ [F] (B-elim BΣ [ΣFG]) [t′]′ t⇒*t′

snd′ : ∀ {F G t Γ l l′}
       ([ΣFG] : Γ ⊩⟨ l ⟩B⟨ BΣ ⟩ Σ F ▹ G)
       ([t] : Γ ⊩⟨ l ⟩ t ∷ Σ F ▹ G / B-intr BΣ [ΣFG])
       ([Gfst] : Γ ⊩⟨ l′ ⟩ G [ fst t ])
       → Γ ⊩⟨ l′ ⟩ snd t ∷ G [ fst t ] / [Gfst]
snd′ {F = F} {G = G} {t = t} {Γ = Γ} {l = l} {l′ = l′}
     [ΣFG]@(noemb (Bᵣ F' G' D ⊢F ⊢G A≡A [F'] [G'] G-ext))
     [t]@(Σₜ p d pProd p≅p [fstp] [sndp]) [Gfst] with
       B-PE-injectivity BΣ (whnfRed* (red D) Σₙ)
... | PE.refl , PE.refl =
  let ⊢Γ = wf ⊢F
      [p] = Σₜ p (idRedTerm:*: (⊢u-redₜ d)) pProd p≅p [fstp] [sndp]

      [fstt] , [fstt≡fstp] = redSubst*Term (PE.subst (λ x → Γ ⊢ fst t ⇒* fst p ∷ x)
                                                     (PE.sym (wk-id F))
                                                     (fst-subst* ⊢F ⊢G (redₜ d)))
                                           ([F'] id ⊢Γ) [fstp]

      [Gfstt] = [G'] id ⊢Γ [fstt]
      [Gfstp] = [G'] id ⊢Γ [fstp]
      [Gfstt≡Gfstp] = G-ext id ⊢Γ [fstt] [fstp] [fstt≡fstp]

      [sndp] = convTerm₂ [Gfstt] [Gfstp] [Gfstt≡Gfstp] [sndp]

      [sndp] : Γ ⊩⟨ l′ ⟩ snd p ∷ G [ fst t ] / [Gfst]
      [sndp] = irrelevanceTerm′ (PE.cong (λ x → x [ fst t ]) (wk-lift-id G))
                                [Gfstt] [Gfst] [sndp]
  in  proj₁ (redSubst*Term (snd-subst* (PE.subst (λ x → Γ ⊩⟨ l ⟩ x) (wk-id F) ([F'] id ⊢Γ))
                                       (B-intr BΣ [ΣFG])
                                       [p]
                                       (redₜ d))
                           [Gfst]
                           [sndp])
snd′ (emb 0<1 x) = snd′ x

snd″ : ∀ {F G t Γ l l′}
       ([ΣFG] : Γ ⊩⟨ l ⟩ Σ F ▹ G)
       ([t] : Γ ⊩⟨ l ⟩ t ∷ Σ F ▹ G / [ΣFG])
       ([Gfst] : Γ ⊩⟨ l′ ⟩ G [ fst t ])
       → Γ ⊩⟨ l′ ⟩ snd t ∷ G [ fst t ] / [Gfst]
snd″ {t = t} {Γ = Γ} {l = l} [ΣFG] [t] [Gfst] =
  let [t]′ = irrelevanceTerm [ΣFG] (B-intr BΣ (Σ-elim [ΣFG])) [t]
  in  snd′ (Σ-elim [ΣFG]) [t]′ [Gfst]

snd-cong′ : ∀ {F G t t′ Γ l l′}
            ([ΣFG] : Γ ⊩⟨ l ⟩B⟨ BΣ ⟩ Σ F ▹ G)
            ([t] : Γ ⊩⟨ l ⟩ t ∷ Σ F ▹ G / B-intr BΣ [ΣFG])
            ([t′] : Γ ⊩⟨ l ⟩ t′ ∷ Σ F ▹ G / B-intr BΣ [ΣFG])
            ([t≡t′] : Γ ⊩⟨ l ⟩ t ≡ t′ ∷ Σ F ▹ G / B-intr BΣ [ΣFG])
            ([Gfst] : Γ ⊩⟨ l′ ⟩ G [ fst t ])
            → Γ ⊩⟨ l′ ⟩ snd t ≡ snd t′ ∷ G [ fst t ] / [Gfst]
snd-cong′ {F} {G} {t} {t′} {Γ} {l} {l′}
          [ΣFG]@(noemb (Bᵣ F' G' D ⊢F ⊢G A≡A [F] [G] G-ext))
          [t]@(Σₜ p d pProd p≅p [fstp] [sndp])
          [t′]@(Σₜ p′ d′ pProd′ p′≅p′ [fstp′] [sndp′])
          [t≡t′]@(Σₜ₌ p₁ p′₁ d₁ d′₁ pProd₁ pProd′₁ p≅p′ [t]₁ [t′]₁ [fstp]₁ [fstp′]₁ [fst≡] [snd≡])
          [Gfst] with
            B-PE-injectivity BΣ (whnfRed* (red D) Σₙ)
          | whrDet*Term (redₜ d , productWhnf pProd) (redₜ d₁ , productWhnf pProd₁)
          | whrDet*Term (redₜ d′ , productWhnf pProd′) (redₜ d′₁ , productWhnf pProd′₁)
... | PE.refl , PE.refl | PE.refl | PE.refl =
  let ⊢Γ = wf ⊢F
      [p] : Γ ⊩⟨ l ⟩ p ∷ Σ F ▹ G / B-intr BΣ [ΣFG]
      [p] = Σₜ p (idRedTerm:*: (⊢u-redₜ d)) pProd p≅p [fstp] [sndp]
      [p′] : Γ ⊩⟨ l ⟩ p′ ∷ Σ F ▹ G / B-intr BΣ [ΣFG]
      [p′] = Σₜ p′ (idRedTerm:*: (⊢u-redₜ d′)) pProd′ p′≅p′ [fstp′] [sndp′]
      [F]′ = irrelevance′ (wk-id F) ([F] id ⊢Γ)

      [fstt] , [fstt≡fstp] = redSubst*Term (PE.subst (λ x → Γ ⊢ fst t ⇒* fst p ∷ x)
                                                     (PE.sym (wk-id F))
                                                     (fst-subst* ⊢F ⊢G (redₜ d)))
                                           ([F] id ⊢Γ) [fstp]
      [Gfstt≡Gfstp] = G-ext id ⊢Γ [fstt] [fstp] [fstt≡fstp]

      [sndp]′ : Γ ⊩⟨ l ⟩ snd p ∷ U.wk (lift id) G [ fst t ] / [G] id ⊢Γ [fstt]
      [sndp]′ = convTerm₂ ([G] id ⊢Γ [fstt]) ([G] id ⊢Γ [fstp]) [Gfstt≡Gfstp] [sndp]
      sndt⇒*sndp = snd-subst* [F]′ (B-intr BΣ [ΣFG]) [p] (redₜ d)
      [sndp]″ = irrelevanceTerm′ (PE.cong (λ x → x [ fst t ]) (wk-lift-id G))
                                 ([G] id ⊢Γ [fstt]) [Gfst]
                                 [sndp]′
      [sndt≡sndp] : Γ ⊩⟨ l′ ⟩ snd t ≡ snd p ∷ G [ fst t ] / [Gfst]
      [sndt≡sndp] = proj₂ (redSubst*Term sndt⇒*sndp [Gfst] [sndp]″)

      [snd≡]′ = convEqTerm₂ ([G] id ⊢Γ [fstt]) ([G] id ⊢Γ [fstp]₁) [Gfstt≡Gfstp] [snd≡]
      [sndp≡sndp′] : Γ ⊩⟨ l′ ⟩ snd p ≡ snd p′ ∷ G [ fst t ] / [Gfst]
      [sndp≡sndp′] = irrelevanceEqTerm′ (PE.cong (λ x → x [ fst t ]) (wk-lift-id G))
                                        ([G] id ⊢Γ [fstt]) [Gfst]
                                        [snd≡]′

      [fstt′] , [fstt′≡fstp′] = redSubst*Term (PE.subst (λ x → Γ ⊢ fst t′ ⇒* fst p′ ∷ x)
                                                     (PE.sym (wk-id F))
                                                     (fst-subst* ⊢F ⊢G (redₜ d′)))
                                           ([F] id ⊢Γ) [fstp′]
      [fstt≡fstt′] = irrelevanceEqTerm′ (PE.sym (wk-id F))
                                        [F]′ ([F] id ⊢Γ)
                                        (fst-cong′ [F]′ [ΣFG] [t≡t′])
      [fstt≡fstp′] = transEqTerm ([F] id ⊢Γ) [fstt≡fstt′] [fstt′≡fstp′]
      [Gfstt≡Gfstp′] = G-ext id ⊢Γ [fstt] [fstp′] [fstt≡fstp′]
      [sndp′]′ : Γ ⊩⟨ l ⟩ snd p′ ∷ U.wk (lift id) G [ fst t ] / [G] id ⊢Γ [fstt]
      [sndp′]′ = convTerm₂ ([G] id ⊢Γ [fstt]) ([G] id ⊢Γ [fstp′]) [Gfstt≡Gfstp′] [sndp′]
      [sndp′]″ = irrelevanceTerm′ (PE.cong (λ x → x [ fst t ]) (wk-lift-id G))
                                  ([G] id ⊢Γ [fstt]) [Gfst]
                                  [sndp′]′
      [Gfstt]′ = irrelevance′ (PE.cong (λ x → x [ fst t ]) (wk-lift-id G))
                              ([G] id ⊢Γ [fstt])
      [Gfstt≡Gfstt′] = irrelevanceEq″ (PE.cong (λ x → x [ fst t ]) (wk-lift-id G))
                                      (PE.cong (λ x → x [ fst t′ ]) (wk-lift-id G))
                                      ([G] id ⊢Γ [fstt]) [Gfstt]′
                                      (G-ext id ⊢Γ [fstt] [fstt′] [fstt≡fstt′])
      sndt′⇒*sndp′ : Γ ⊢ snd t′ ⇒* snd p′ ∷ G [ fst t ]
      sndt′⇒*sndp′ = conv* (snd-subst* [F]′ (B-intr BΣ [ΣFG]) [p′] (redₜ d′))
                           (≅-eq (≅-sym (escapeEq [Gfstt]′ [Gfstt≡Gfstt′])))
      [sndt′≡sndp′] : Γ ⊩⟨ l′ ⟩ snd t′ ≡ snd p′ ∷ G [ fst t ] / [Gfst]
      [sndt′≡sndp′] = proj₂ (redSubst*Term sndt′⇒*sndp′ [Gfst] [sndp′]″)

  in  transEqTerm [Gfst] [sndt≡sndp] (transEqTerm [Gfst] [sndp≡sndp′] (symEqTerm [Gfst] [sndt′≡sndp′]))
snd-cong′ {F} {G} (emb 0<1 x) = snd-cong′ x

snd-cong″ : ∀ {F G t t′ Γ l l′}
            ([ΣFG] : Γ ⊩⟨ l ⟩ Σ F ▹ G)
            ([t] : Γ ⊩⟨ l ⟩ t ∷ Σ F ▹ G / [ΣFG])
            ([t′] : Γ ⊩⟨ l ⟩ t′ ∷ Σ F ▹ G / [ΣFG])
            ([t≡t′] : Γ ⊩⟨ l ⟩ t ≡ t′ ∷ Σ F ▹ G / [ΣFG])
            ([Gfst] : Γ ⊩⟨ l′ ⟩ G [ fst t ])
            → Γ ⊩⟨ l′ ⟩ snd t ≡ snd t′ ∷ G [ fst t ] / [Gfst]
snd-cong″ {F} {G} [ΣFG] [t] [t′] [t≡t′] [Gfst] =
  let [t] = irrelevanceTerm [ΣFG] (B-intr BΣ (Σ-elim [ΣFG])) [t]
      [t′] = irrelevanceTerm [ΣFG] (B-intr BΣ (Σ-elim [ΣFG])) [t′]
      [t≡t′] = irrelevanceEqTerm [ΣFG] (B-intr BΣ (Σ-elim [ΣFG])) [t≡t′]
  in  snd-cong′ (B-elim BΣ [ΣFG]) [t] [t′] [t≡t′] [Gfst]

snd-congᵛ : ∀ {F G t t′ Γ l}
            ([Γ] : ⊩ᵛ Γ)
            ([F] : Γ ⊩ᵛ⟨ l ⟩ F / [Γ])
            ([G] : Γ ∙ F ⊩ᵛ⟨ l ⟩ G / [Γ] ∙ [F])
            ([t] : Γ ⊩ᵛ⟨ l ⟩ t ∷ Σ F ▹ G / [Γ] / Σᵛ {F} {G} [Γ] [F] [G])
            ([t′] : Γ ⊩ᵛ⟨ l ⟩ t′ ∷ Σ F ▹ G / [Γ] / Σᵛ {F} {G} [Γ] [F] [G])
            ([t≡t′] : Γ ⊩ᵛ⟨ l ⟩ t ≡ t′ ∷ Σ F ▹ G / [Γ] / Σᵛ {F} {G} [Γ] [F] [G])
          → Γ ⊩ᵛ⟨ l ⟩ snd t ≡ snd t′ ∷ G [ fst t ] / [Γ] / substS {F} {G} [Γ] [F] [G] (fstᵛ {F} {G} {t} [Γ] [F] [G] [t])
snd-congᵛ {F} {G} {t} {t′} {Γ} {l} [Γ] [F] [G] [t] [t′] [t≡t′] {Δ} {σ} ⊢Δ [σ] =
  let [ΣFG] = Σᵛ {F} {G} [Γ] [F] [G]
      [fst] = fstᵛ {F} {G} {t} [Γ] [F] [G] [t]
      [Gfst] = substS {F} {G} [Γ] [F] [G] [fst]

      ⊩σΣFG = proj₁ ([ΣFG] ⊢Δ [σ])
      ⊩σt = proj₁ ([t] ⊢Δ [σ])
      ⊩σt′ = proj₁ ([t′] ⊢Δ [σ])
      σt≡σt′ = [t≡t′] ⊢Δ [σ]

      ⊩σGfst₁ = proj₁ ([Gfst] ⊢Δ [σ])
      ⊩σGfst = irrelevance′ (singleSubstLift G (fst t)) ⊩σGfst₁

      σsnd≡₁ = snd-cong″ ⊩σΣFG ⊩σt ⊩σt′ σt≡σt′ ⊩σGfst
      σsnd≡ = irrelevanceEqTerm′ (PE.sym (singleSubstLift G (fst t))) ⊩σGfst ⊩σGfst₁ σsnd≡₁
  in  σsnd≡

-- Validity of second projection
sndᵛ : ∀ {F G t Γ l}
       ([Γ] : ⊩ᵛ Γ)
       ([F] : Γ ⊩ᵛ⟨ l ⟩ F / [Γ])
       ([G] : Γ ∙ F ⊩ᵛ⟨ l ⟩ G / [Γ] ∙ [F])
       ([t] : Γ ⊩ᵛ⟨ l ⟩ t ∷ Σ F ▹ G / [Γ] / Σᵛ {F} {G} [Γ] [F] [G])
       → Γ ⊩ᵛ⟨ l ⟩ snd t ∷ G [ fst t ] / [Γ] / substS {F} {G} [Γ] [F] [G] (fstᵛ {F} {G} {t} [Γ] [F] [G] [t])
sndᵛ {F} {G} {t} {Γ} {l} [Γ] [F] [G] [t] {Δ = Δ} {σ = σ} ⊢Δ [σ] =
  let [ΣFG] = Σᵛ {F} {G} [Γ] [F] [G]
      [Gfst] : Γ ⊩ᵛ⟨ l ⟩ G [ fst t ] / [Γ]
      [Gfst] = substS {F} {G} [Γ] [F] [G] (fstᵛ {F} {G} {t} [Γ] [F] [G] [t])

      σsnd : ∀ {Δ σ} (⊢Δ : ⊢ Δ) ([σ] : Δ ⊩ˢ σ ∷ Γ / [Γ] / ⊢Δ)
           → Δ ⊩⟨ l ⟩ subst σ (snd t) ∷ subst σ (G [ fst t ]) / proj₁ ([Gfst] ⊢Δ [σ])
      σsnd {Δ} {σ} ⊢Δ [σ] =
        let ⊩σΣFG = proj₁ ([ΣFG] ⊢Δ [σ])
            ⊩σt = proj₁ ([t] ⊢Δ [σ])
            ⊩σGfstt = proj₁ ([Gfst] ⊢Δ [σ])
            ⊩σGfstt′ = PE.subst (λ x → Δ ⊩⟨ l ⟩ x) (singleSubstLift G (fst t)) ⊩σGfstt
            ⊩σsnd = snd″ ⊩σΣFG ⊩σt ⊩σGfstt′
        in  irrelevanceTerm′
              (PE.sym (singleSubstLift G (fst t)))
              ⊩σGfstt′  ⊩σGfstt
              ⊩σsnd

  in  σsnd ⊢Δ [σ] ,
      (λ {σ′} [σ′] [σ≡σ′] →
        let [σF] = proj₁ ([F] ⊢Δ [σ])
            [σΣFG] = proj₁ ([ΣFG] ⊢Δ [σ])
            [σ′ΣFG] = proj₁ ([ΣFG] ⊢Δ [σ′])
            [σΣFG≡σ′ΣFG] = proj₂ ([ΣFG] ⊢Δ [σ]) [σ′] [σ≡σ′]
            [σt] = proj₁ ([t] ⊢Δ [σ])
            [σ′t] = proj₁ ([t] ⊢Δ [σ′])
            [σ′t] : Δ ⊩⟨ l ⟩ subst σ′ t ∷ subst σ (Σ F ▹ G) / [σΣFG]
            [σ′t] = convTerm₂ [σΣFG] [σ′ΣFG] [σΣFG≡σ′ΣFG] [σ′t]
            [σt≡σ′t] = proj₂ ([t] ⊢Δ [σ]) [σ′] [σ≡σ′]
            [σGfstt] = proj₁ ([Gfst] ⊢Δ [σ])
            [σGfstt]′ = PE.subst (λ x → _ ⊩⟨ l ⟩ x)
                                (singleSubstLift G (fst t))
                                [σGfstt]
            [sndt≡sndt′] = snd-cong″ [σΣFG] [σt] [σ′t] [σt≡σ′t] [σGfstt]′
        in  irrelevanceEqTerm′ (PE.sym (singleSubstLift G (fst t)))
                               [σGfstt]′ [σGfstt]
                               [sndt≡sndt′])


module Haskell.RangedSets.Ranges where

open import Haskell.Prim
open import Haskell.Prim.Ord
open import Haskell.Prim.Bool
open import Haskell.Prim.Maybe
open import Haskell.Prim.Enum
open import Haskell.Prim.Num
open import Haskell.Prim.Eq
open import Haskell.Prim.Functor
open import Haskell.Prim.Foldable
open import Haskell.Prim.Monoid
open import Haskell.Prim.Monad
open import Haskell.Prim.Applicative
open import Haskell.Prim.Int
open import Haskell.Prim.List
open import Haskell.Prim.Integer
open import Haskell.Prim.Real
open import Haskell.Prim.Show
open import Haskell.Prim.String
open import Haskell.Prim.Tuple

open import Haskell.RangedSets.Boundaries

{-# FOREIGN AGDA2HS
import Haskell.RangedSets.Boundaries
#-}

data Range (a : Set) ⦃ o : Ord a ⦄ ⦃ dio : DiscreteOrdered a ⦄ : Set where
    Rg : Boundary a → Boundary a → Range a
{-# COMPILE AGDA2HS Range #-}

rangeLower : ⦃ o : Ord a ⦄ → ⦃ dio : DiscreteOrdered a ⦄ → Range a → Boundary a

rangeUpper : ⦃ o : Ord a ⦄ → ⦃ dio : DiscreteOrdered a ⦄ → Range a → Boundary a

rangeIsEmpty : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a → Bool

emptyRange : ⦃  o : Ord a ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a

rangeHas : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a → a → Bool

rangeListHas : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → List (Range a) → a → Bool

fullRange : ⦃ o : Ord a ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a

singletonRange : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → a → Range a

rangeIsFull : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a → Bool

rangeOverlap : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a → Range a → Bool

rangeEncloses : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a → Range a → Bool

rangeUnion : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a → Range a → List (Range a)

rangeIntersection : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a → Range a → Range a

rangeDifference : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a → Range a → List (Range a)

rangeSingletonValue : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a → Maybe a

instance
    isRangeEq : ⦃ o : Ord a ⦄ → ⦃ dio : DiscreteOrdered a ⦄ → Eq (Range a)
    isRangeEq . _==_ r1 r2 = (rangeIsEmpty r1 && rangeIsEmpty r2) || (rangeLower r1 == rangeLower r2 && rangeUpper r1 == rangeUpper r2)
{-# COMPILE AGDA2HS isRangeEq #-}

instance
    isRangeOrd : ⦃ o : Ord a ⦄ → ⦃ dio : DiscreteOrdered a ⦄ → Ord (Range a)
    isRangeOrd . compare r1 r2 = if_then_else_ (r1 == r2) EQ
                        (if_then_else_ (rangeIsEmpty r1) LT (if_then_else_ (rangeIsEmpty r2) GT
                        (compare (rangeLower r1) (rangeUpper r1) <> compare (rangeLower r2) (rangeUpper r2))))
    isRangeOrd ._<_ x y = compare x y == LT
    isRangeOrd ._>_ x y = compare x y == GT
    isRangeOrd ._<=_ x y = (compare x y == LT) || (compare x y == EQ)
    isRangeOrd ._>=_ x y = (compare x y == GT) || (compare x y == EQ)
    isRangeOrd .max x y = if (compare x y == GT) then x else y
    isRangeOrd .min x y = if (compare x y == LT) then x else y
{-# COMPILE AGDA2HS isRangeOrd #-}

h : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → ⦃ Show a ⦄ → Boundary a → String
h BoundaryBelowAll = ""
h (BoundaryBelow x) = show x ++ " <= "
h (BoundaryAbove x) = show x ++ " < "
h BoundaryAboveAll = "show Range: lower bound is BoundaryAboveAll"
{-# COMPILE AGDA2HS h #-}

h2 : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → ⦃ Show a ⦄ → Boundary a → String
h2 BoundaryBelowAll = "show Range: upper bound is BoundaryBelowAll"
h2 (BoundaryBelow x) = " < " ++ show x
h2 (BoundaryAbove x) = " <= " ++ show x
h2 BoundaryAboveAll = ""
{-# COMPILE AGDA2HS h2 #-}

showHelper : ⦃ Show a ⦄ → ⦃  o : Ord a  ⦄ → ⦃ dio : DiscreteOrdered a ⦄ → Range a → Maybe a → String
showHelper r (Just v) = "x == " ++ show v
showHelper r Nothing = lowerBound ++ "x" ++ upperBound
         where
            lowerBound = h (rangeLower r)
            upperBound = h2 (rangeUpper r)
showHelper2 : ⦃ Show a ⦄ → ⦃  o : Ord a  ⦄ → ⦃ dio : DiscreteOrdered a ⦄ → Range a → String
showHelper2 r = if_then_else_ (rangeIsEmpty r) "Empty" (if_then_else_ (rangeIsFull r) "All x" (showHelper r (rangeSingletonValue r)))
{-# COMPILE AGDA2HS showHelper #-}
{-# COMPILE AGDA2HS showHelper2 #-}

instance
   isRangeShow : ⦃ Show a ⦄ → ⦃ o : Ord a ⦄ → ⦃ dio : DiscreteOrdered a ⦄ → Show (Range a)
   isRangeShow .showsPrec _ = showString ∘ showHelper2
   isRangeShow .showList = defaultShowList (showString ∘ showHelper2)
{-# COMPILE AGDA2HS isRangeShow #-}

rangeLower ⦃ o ⦄ ⦃ dio ⦄ (Rg x y) = x
{-# COMPILE AGDA2HS rangeLower #-}

rangeUpper ⦃ o ⦄ ⦃ dio ⦄ (Rg x y) = y
{-# COMPILE AGDA2HS rangeUpper #-}

rangeIsEmpty r@(Rg lower upper) = upper <= lower
{-# COMPILE AGDA2HS rangeIsEmpty #-}

emptyRange = Rg BoundaryAboveAll BoundaryBelowAll
{-# COMPILE AGDA2HS emptyRange #-}

rangeHas r@(Rg b1 b2) v = (v />/ b1) && not (v />/ b2)
{-# COMPILE AGDA2HS rangeHas #-}

rangeHas1 : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → a → Range a → Bool
rangeHas1 v r@(Rg b1 b2) = (v />/ b1) && not (v />/ b2)

rangeListHas (r@(Rg _ _) ∷ []) v = rangeHas r v
rangeListHas (r1@(Rg _ _) ∷ r2@(Rg _ _) ∷ []) v = (rangeHas r1 v) || (rangeHas r2 v)
rangeListHas ls v = or $ map (\r → rangeHas r v) ls
{-# COMPILE AGDA2HS rangeListHas #-}

rangeListHas1 : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → a → List (Range a) → Bool
rangeListHas1 v (r@(Rg _ _) ∷ []) = rangeHas r v
rangeListHas1 v (r1@(Rg _ _) ∷ r2@(Rg _ _) ∷ []) = (rangeHas r1 v) || (rangeHas r2 v)
rangeListHas1 v ls = or $ map (\r → rangeHas r v) ls

fullRange ⦃ o ⦄ ⦃ dio ⦄ = Rg BoundaryBelowAll BoundaryAboveAll
{-# COMPILE AGDA2HS fullRange #-}

singletonRange v = Rg (BoundaryBelow v) (BoundaryAbove v)
{-# COMPILE AGDA2HS singletonRange #-}

rangeIsFull range = (range == fullRange)
{-# COMPILE AGDA2HS rangeIsFull #-}

rangeOverlap r1 r2 =
   not (rangeIsEmpty r1) && not (rangeIsEmpty r2) && not (rangeUpper r1 <= rangeLower r2 || rangeUpper r2 <= rangeLower r1)
{-# COMPILE AGDA2HS rangeOverlap #-}

rangeEncloses r1 r2 =
   (rangeLower r1 <= rangeLower r2 && rangeUpper r2 <= rangeUpper r1)
   || rangeIsEmpty r2
{-# COMPILE AGDA2HS rangeEncloses #-}

rangeIntersection r1@(Rg l1 u1) r2@(Rg l2 u2) = if_then_else_ (rangeIsEmpty r1 || rangeIsEmpty r2) emptyRange (Rg (max l1 l2) (min u1 u2))
{-# COMPILE AGDA2HS rangeIntersection #-}

rangeU2 : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a → Range a → List (Range a)
rangeU2 r1@(Rg l1 u1) r2@(Rg l2 u2) = (if_then_else_ touching ((Rg lower upper) ∷ []) (r1 ∷ r2 ∷ []))
   where
     touching = (max l1 l2) <= (min u1 u2)
     lower = min l1 l2
     upper = max u1 u2
{-# COMPILE AGDA2HS rangeU2 #-}

rangeU1 : ⦃  o : Ord a  ⦄ → ⦃  dio : DiscreteOrdered a  ⦄ → Range a → Range a → List (Range a)
rangeU1 r1@(Rg l1 u1) r2@(Rg l2 u2) = if_then_else_ (rangeIsEmpty r2) (r1 ∷ []) (rangeU2 r1 r2)
{-# COMPILE AGDA2HS rangeU1 #-}

rangeUnion r1@(Rg l1 u1) r2@(Rg l2 u2) = if_then_else_ (rangeIsEmpty r1) (r2 ∷ []) (rangeU1 r1 r2)
{-# COMPILE AGDA2HS rangeUnion #-}

rangeDifference (Rg lower1 upper1) (Rg lower2 upper2) = if_then_else_ intersects filtered (r1 ∷ [])
   where
      intersects = ((max lower1 lower2) < (min upper1 upper2))
      r1 = Rg lower1 upper1
      list = ((Rg lower1 lower2) ∷ (Rg upper2 upper1) ∷ [])
      filtered = filter (λ x → (rangeIsEmpty x) == false) list
{-# COMPILE AGDA2HS rangeDifference #-}

rangeSingletonValue (Rg (BoundaryBelow v1) (BoundaryBelow v2)) = if_then_else_ (adjacent v1 v2) (Just v1) Nothing
rangeSingletonValue (Rg (BoundaryBelow v1) (BoundaryAbove v2)) = if_then_else_ (v1 == v2) (Just v1) Nothing
rangeSingletonValue (Rg (BoundaryAbove v1) (BoundaryBelow v2)) = adjacentBelow v2 >>= λ x → adjacentBelow x >>= λ y → if_then_else_ (v1 == y) (return x) Nothing
rangeSingletonValue (Rg (BoundaryAbove v1) (BoundaryAbove v2)) = if_then_else_ (adjacent v1 v2) (Just v2) Nothing
rangeSingletonValue (Rg _ _) = Nothing
{-# COMPILE AGDA2HS rangeSingletonValue #-}


------------------------------------------------------------------------
-- Some negative results related to weak bisimilarity and expansion
------------------------------------------------------------------------

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

open import Prelude
open import Prelude.Size

module Delay-monad.Sized.Bisimilarity.Negative
  {a} {A : Size → Type a} where

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

open import Function-universe equality-with-J hiding (id; _∘_)

open import Delay-monad.Sized
open import Delay-monad.Sized.Bisimilarity
open import Delay-monad.Sized.Termination

------------------------------------------------------------------------
-- Lemmas stating that functions of certain types cannot be defined if
-- ∀ i → A i is inhabited

-- An abbreviation. (Note that, because pattern-matching lambdas like
-- the one in this definition are—at least at the time of
-- writing—compared by "name", Agda rejects the code that one gets if
-- this abbreviation is inlined below.)

later-now : (∀ i → A i) → ∀ {i} → Delay A i
later-now x = later λ { .force {j} → now (x j) }

-- If now (x ∞) is an expansion of later-now x for every
-- x : ∀ i → A i, then ∀ i → A i is uninhabited.

Now≳later-now = (x : ∀ i → A i) → now (x ∞) ≳ later-now x

now≳later-now→uninhabited : Now≳later-now → ¬ (∀ i → A i)
now≳later-now→uninhabited =
  Now≳later-now  ↝⟨ (λ hyp x → case hyp x of λ ()) ⟩□
  ¬ (∀ i → A i)  □

-- If a variant of laterˡ⁻¹ for (fully defined) expansion can be
-- defined, then ∀ i → A i is uninhabited.

Laterˡ⁻¹-≳ = ∀ {x} {y : Delay A ∞} → later x ≳ y → force x ≳ y

laterˡ⁻¹-≳→uninhabited : Laterˡ⁻¹-≳ → ¬ (∀ i → A i)
laterˡ⁻¹-≳→uninhabited =
  Laterˡ⁻¹-≳     ↝⟨ (λ hyp _ → hyp (reflexive _)) ⟩
  Now≳later-now  ↝⟨ now≳later-now→uninhabited ⟩□
  ¬ (∀ i → A i)  □

-- If the following variants of transitivity can be proved, then
-- ∀ i → A i is uninhabited.

Transitivity-≈≳≳ = {x y z : Delay A ∞} → x ≈ y → y ≳ z → x ≳ z
Transitivity-≳≈≳ = {x y z : Delay A ∞} → x ≳ y → y ≈ z → x ≳ z

transitive-≈≳≳→uninhabited : Transitivity-≈≳≳ → ¬ (∀ i → A i)
transitive-≈≳≳→uninhabited =
  Transitivity-≈≳≳  ↝⟨ (λ trans → trans (laterʳ (reflexive _))) ⟩
  Laterˡ⁻¹-≳        ↝⟨ laterˡ⁻¹-≳→uninhabited ⟩□
  ¬ (∀ i → A i)     □

transitive-≳≈≳→uninhabited : Transitivity-≳≈≳ → ¬ (∀ i → A i)
transitive-≳≈≳→uninhabited =
  Transitivity-≳≈≳  ↝⟨ (λ trans x → later⁻¹ {y = λ { .force → later-now x }}
                                            (trans (reflexive _) (laterʳ (reflexive _)))) ⟩
  Now≳later-now     ↝⟨ now≳later-now→uninhabited ⟩□
  ¬ (∀ i → A i)     □

------------------------------------------------------------------------
-- Lemmas stating that certain size-preserving functions cannot be
-- defined if ∀ i → A i is inhabited, and related results

-- If a variant of laterˡ⁻¹ in which one occurrence of weak
-- bisimilarity is replaced by strong bisimilarity, and both arguments
-- are specialised, can be made size-preserving, then ∀ i → A i is
-- uninhabited.
--
-- This lemma is used to prove all other similar results below
-- (directly or indirectly).

Laterˡ⁻¹-∼≈ = ∀ {i} (x : ∀ i → A i) →
              [ i ] later (λ { .force {j = j} → now (x j) }) ∼ never →
              [ i ] now {A = A} (x ∞)                        ≈ never

size-preserving-laterˡ⁻¹-∼≈→uninhabited :
  Laterˡ⁻¹-∼≈ → ¬ (∀ i → A i)
size-preserving-laterˡ⁻¹-∼≈→uninhabited laterˡ⁻¹-∼≈ x =
  contradiction ∞
  where

  mutual

    now≈never : ∀ {i} → [ i ] now (x ∞) ≈ never
    now≈never = laterˡ⁻¹-∼≈ x (later now∼never)

    now∼never : ∀ {i} → [ i ] now (x ∞) ∼′ never
    force now∼never {j = j} = ⊥-elim (contradiction j)

    contradiction : Size → ⊥
    contradiction i = now≉never (now≈never {i = i})

-- A logically equivalent variant of Laterˡ⁻¹-∼≈ which it is sometimes
-- easier to work with.

Laterˡ⁻¹-∼≈′ = ∀ {i} (x : ∀ i → A i) →
               [ i ] later (record { force = λ {j} → now (x j) }) ∼ never →
               [ i ] now {A = A} (x ∞)                            ≈ never

size-preserving-laterˡ⁻¹-∼≈⇔size-preserving-laterˡ⁻¹-∼≈′ :
  Laterˡ⁻¹-∼≈ ⇔ Laterˡ⁻¹-∼≈′
size-preserving-laterˡ⁻¹-∼≈⇔size-preserving-laterˡ⁻¹-∼≈′ = record
  { to   = λ laterˡ⁻¹ x → laterˡ⁻¹ x ∘ transitive-∼ˡ (later λ { .force → now })
  ; from = λ laterˡ⁻¹ x → laterˡ⁻¹ x ∘ transitive-∼ˡ (later λ { .force → now })
  }

-- The type Laterˡ⁻¹-∼≈′ is logically equivalent to the similar type
-- Laterʳ⁻¹-∼≈′.

Laterʳ⁻¹-∼≈′ = ∀ {i} (x : ∀ i → A i) →
               [ i ] never ∼ later (record { force = λ {j} → now (x j) }) →
               [ i ] never ≈ now {A = A} (x ∞)

size-preserving-laterˡ⁻¹-∼≈′⇔size-preserving-laterʳ⁻¹-∼≈′ :
  Laterˡ⁻¹-∼≈′ ⇔ Laterʳ⁻¹-∼≈′
size-preserving-laterˡ⁻¹-∼≈′⇔size-preserving-laterʳ⁻¹-∼≈′ =
  record { to   = λ laterˡ⁻¹ {_} _ → symmetric ∘ laterˡ⁻¹ _ ∘ symmetric
         ; from = λ laterʳ⁻¹ {_} _ → symmetric ∘ laterʳ⁻¹ _ ∘ symmetric
         }

-- The type Laterʳ⁻¹-∼≈′ is logically equivalent to the similar type
-- Laterʳ⁻¹-∼≳′.

Laterʳ⁻¹-∼≳′ = ∀ {i} (x : ∀ i → A i) →
               [ i ] never ∼ later (record { force = λ {j} → now (x j) }) →
               [ i ] never ≳ now {A = A} (x ∞)

size-preserving-laterʳ⁻¹-∼≈′⇔size-preserving-laterʳ⁻¹-∼≳′ :
  Laterʳ⁻¹-∼≈′ ⇔ Laterʳ⁻¹-∼≳′
size-preserving-laterʳ⁻¹-∼≈′⇔size-preserving-laterʳ⁻¹-∼≳′ =
  record { to   = λ laterʳ⁻¹ {_} _ → ≈→-now ∘ laterʳ⁻¹ _
         ; from = λ laterʳ⁻¹ {_} _ → ≳→     ∘ laterʳ⁻¹ _
         }

-- If Laterʳ⁻¹-∼≳′ is inhabited, then ∀ i → A i is uninhabited.

size-preserving-laterʳ⁻¹-∼≳′→uninhabited : Laterʳ⁻¹-∼≳′ → ¬ (∀ i → A i)
size-preserving-laterʳ⁻¹-∼≳′→uninhabited =
  Laterʳ⁻¹-∼≳′   ↝⟨ _⇔_.from size-preserving-laterʳ⁻¹-∼≈′⇔size-preserving-laterʳ⁻¹-∼≳′ ⟩
  Laterʳ⁻¹-∼≈′   ↝⟨ _⇔_.from size-preserving-laterˡ⁻¹-∼≈′⇔size-preserving-laterʳ⁻¹-∼≈′ ⟩
  Laterˡ⁻¹-∼≈′   ↝⟨ _⇔_.from size-preserving-laterˡ⁻¹-∼≈⇔size-preserving-laterˡ⁻¹-∼≈′ ⟩
  Laterˡ⁻¹-∼≈    ↝⟨ size-preserving-laterˡ⁻¹-∼≈→uninhabited ⟩□
  ¬ (∀ i → A i)  □

-- Having a size-preserving variant of laterˡ⁻¹ is logically
-- equivalent to having a size-preserving variant of laterʳ⁻¹ (for
-- weak bisimilarity).

Laterˡ⁻¹-≈ = ∀ {i x} {y : Delay A ∞} →
             [ i ] later x ≈ y →
             [ i ] force x ≈ y

Laterʳ⁻¹-≈ = ∀ {i} {x : Delay A ∞} {y} →
             [ i ] x ≈ later y →
             [ i ] x ≈ force y

size-preserving-laterˡ⁻¹-≈⇔size-preserving-laterʳ⁻¹-≈ :
  Laterˡ⁻¹-≈ ⇔ Laterʳ⁻¹-≈
size-preserving-laterˡ⁻¹-≈⇔size-preserving-laterʳ⁻¹-≈ = record
  { to   = λ laterʳ⁻¹ → symmetric ∘ laterʳ⁻¹ ∘ symmetric
  ; from = λ laterˡ⁻¹ → symmetric ∘ laterˡ⁻¹ ∘ symmetric
  }

-- If laterˡ⁻¹ can be made size-preserving, then ∀ i → A i is
-- uninhabited.

size-preserving-laterˡ⁻¹-≈→uninhabited : Laterˡ⁻¹-≈ → ¬ (∀ i → A i)
size-preserving-laterˡ⁻¹-≈→uninhabited =
  Laterˡ⁻¹-≈     ↝⟨ (λ laterˡ⁻¹ _ → laterˡ⁻¹ ∘ ∼→) ⟩
  Laterˡ⁻¹-∼≈    ↝⟨ size-preserving-laterˡ⁻¹-∼≈→uninhabited ⟩□
  ¬ (∀ i → A i)  □

-- If laterʳ⁻¹ can be made size-preserving for expansion, then
-- ∀ i → A i is uninhabited.

Laterʳ⁻¹-≳ = ∀ {i} {x : Delay A ∞} {y} →
             [ i ] x ≳ later y →
             [ i ] x ≳ force y

size-preserving-laterʳ⁻¹-≳→uninhabited : Laterʳ⁻¹-≳ → ¬ (∀ i → A i)
size-preserving-laterʳ⁻¹-≳→uninhabited =
  Laterʳ⁻¹-≳     ↝⟨ (λ laterʳ⁻¹ {_} _ p → laterʳ⁻¹ (∼→ p)) ⟩
  Laterʳ⁻¹-∼≳′   ↝⟨ size-preserving-laterʳ⁻¹-∼≳′→uninhabited ⟩□
  ¬ (∀ i → A i)  □

-- If a variant of ⇓-respects-≈ in which _≈_ is replaced by _∼_ can be
-- made size-preserving in the second argument, then ∀ i → A i is
-- uninhabited.

⇓-Respects-∼ʳ = ∀ {i} {x y : Delay A ∞} {z} →
                x ⇓ z → [ i ] x ∼ y → Terminates i y z

size-preserving-⇓-respects-∼ʳ→uninhabited :
  ⇓-Respects-∼ʳ → ¬ (∀ i → A i)
size-preserving-⇓-respects-∼ʳ→uninhabited =
  ⇓-Respects-∼ʳ  ↝⟨ (λ resp _ → resp (laterʳ now)) ⟩
  Laterˡ⁻¹-∼≈    ↝⟨ size-preserving-laterˡ⁻¹-∼≈→uninhabited ⟩□
  ¬ (∀ i → A i)  □

-- If ⇓-respects-≈ can be made size-preserving in the second argument,
-- then ∀ i → A i is uninhabited.

⇓-Respects-≈ʳ = ∀ {i} {x y : Delay A ∞} {z} →
                x ⇓ z → [ i ] x ≈ y → Terminates i y z

size-preserving-⇓-respects-≈ʳ→uninhabited :
  ⇓-Respects-≈ʳ → ¬ (∀ i → A i)
size-preserving-⇓-respects-≈ʳ→uninhabited =
  ⇓-Respects-≈ʳ  ↝⟨ (λ trans x⇓z → trans x⇓z ∘ ∼→) ⟩
  ⇓-Respects-∼ʳ  ↝⟨ size-preserving-⇓-respects-∼ʳ→uninhabited ⟩□
  ¬ (∀ i → A i)  □

-- Having a transitivity-like proof, taking weak bisimilarity and
-- strong bisimilarity to weak bisimilarity, that preserves the size
-- of the second argument, is logically equivalent to having a
-- transitivity-like proof, taking strong bisimilarity and weak
-- bisimilarity to weak bisimilarity, that preserves the size of the
-- first argument.

Transitivity-≈∼ʳ = ∀ {i} {x y z : Delay A ∞} →
                   x ≈ y → [ i ] y ∼ z → [ i ] x ≈ z

Transitivity-∼≈ˡ = ∀ {i} {x y z : Delay A ∞} →
                   [ i ] x ∼ y → y ≈ z → [ i ] x ≈ z

size-preserving-transitivity-≈∼ʳ⇔size-preserving-transitivity-∼≈ˡ :
  Transitivity-≈∼ʳ ⇔ Transitivity-∼≈ˡ
size-preserving-transitivity-≈∼ʳ⇔size-preserving-transitivity-∼≈ˡ =
  record
    { to   = λ trans p q → symmetric (trans (symmetric q) (symmetric p))
    ; from = λ trans p q → symmetric (trans (symmetric q) (symmetric p))
    }

-- If there is a transitivity-like proof, taking weak bisimilarity and
-- strong bisimilarity to weak bisimilarity, that preserves the size
-- of the second argument, then ∀ i → A i is uninhabited.

size-preserving-transitivity-≈∼ʳ→uninhabited :
  Transitivity-≈∼ʳ → ¬ (∀ i → A i)
size-preserving-transitivity-≈∼ʳ→uninhabited =
  Transitivity-≈∼ʳ  ↝⟨ (λ trans → trans) ⟩
  ⇓-Respects-∼ʳ     ↝⟨ size-preserving-⇓-respects-∼ʳ→uninhabited ⟩
  ¬ (∀ i → A i)     □

-- If there is a transitivity-like proof, taking strong bisimilarity
-- and expansion to expansion, that preserves the size of the first
-- argument, then ∀ i → A i is uninhabited.

Transitivity-∼≳ˡ = ∀ {i} {x y z : Delay A ∞} →
                   [ i ] x ∼ y → y ≳ z → [ i ] x ≳ z

size-preserving-transitivity-∼≳ˡ→uninhabited :
  Transitivity-∼≳ˡ → ¬ (∀ i → A i)
size-preserving-transitivity-∼≳ˡ→uninhabited =
  Transitivity-∼≳ˡ  ↝⟨ (λ trans _ p → trans p (laterˡ now)) ⟩
  Laterʳ⁻¹-∼≳′      ↝⟨ size-preserving-laterʳ⁻¹-∼≳′→uninhabited ⟩
  ¬ (∀ i → A i)     □

-- Having a transitivity proof for weak bisimilarity that preserves
-- the size of the first argument is logically equivalent to having
-- one that preserves the size of the second argument.

Transitivity-≈ˡ = ∀ {i} {x y z : Delay A ∞} →
                  [ i ] x ≈ y → y ≈ z → [ i ] x ≈ z

Transitivity-≈ʳ = ∀ {i} {x y z : Delay A ∞} →
                x ≈ y → [ i ] y ≈ z → [ i ] x ≈ z

size-preserving-transitivity-≈ˡ⇔size-preserving-transitivity-≈ʳ :
  Transitivity-≈ˡ ⇔ Transitivity-≈ʳ
size-preserving-transitivity-≈ˡ⇔size-preserving-transitivity-≈ʳ = record
  { to   = λ trans p q → symmetric (trans (symmetric q) (symmetric p))
  ; from = λ trans p q → symmetric (trans (symmetric q) (symmetric p))
  }

-- If there is a transitivity proof for weak bisimilarity that
-- preserves the size of the second argument, then ∀ i → A i is
-- uninhabited.

size-preserving-transitivity-≈ʳ→uninhabited :
  Transitivity-≈ʳ → ¬ (∀ i → A i)
size-preserving-transitivity-≈ʳ→uninhabited =
  Transitivity-≈ʳ  ↝⟨ (λ trans → trans) ⟩
  ⇓-Respects-≈ʳ    ↝⟨ size-preserving-⇓-respects-≈ʳ→uninhabited ⟩□
  ¬ (∀ i → A i)    □

-- If there is a transitivity proof for expansion that preserves the
-- size of the first argument, then ∀ i → A i is uninhabited.

Transitivity-≳ˡ = ∀ {i} {x y z : Delay A ∞} →
                  [ i ] x ≳ y → y ≳ z → [ i ] x ≳ z

size-preserving-transitivity-≳ˡ→uninhabited :
  Transitivity-≳ˡ → ¬ (∀ i → A i)
size-preserving-transitivity-≳ˡ→uninhabited =
  Transitivity-≳ˡ   ↝⟨ (λ trans → trans ∘ ∼→) ⟩
  Transitivity-∼≳ˡ  ↝⟨ size-preserving-transitivity-∼≳ˡ→uninhabited ⟩□
  ¬ (∀ i → A i)     □

-- If there is a fully size-preserving transitivity proof for weak
-- bisimilarity, then ∀ i → A i is uninhabited.

Transitivity-≈ = ∀ {i} {x y z : Delay A ∞} →
                 [ i ] x ≈ y → [ i ] y ≈ z → [ i ] x ≈ z

size-preserving-transitivity-≈→uninhabited :
  Transitivity-≈ → ¬ (∀ i → A i)
size-preserving-transitivity-≈→uninhabited =
  Transitivity-≈   ↝⟨ (λ trans → trans) ⟩
  Transitivity-≈ʳ  ↝⟨ size-preserving-transitivity-≈ʳ→uninhabited ⟩□
  ¬ (∀ i → A i)    □

-- If there is a fully size-preserving transitivity proof for
-- expansion, then ∀ i → A i is uninhabited.

Transitivity-≳ = ∀ {i} {x y z : Delay A ∞} →
                 [ i ] x ≳ y → [ i ] y ≳ z → [ i ] x ≳ z

size-preserving-transitivity-≳→uninhabited :
  Transitivity-≳ → ¬ (∀ i → A i)
size-preserving-transitivity-≳→uninhabited =
  Transitivity-≳   ↝⟨ (λ trans → trans) ⟩
  Transitivity-≳ˡ  ↝⟨ size-preserving-transitivity-≳ˡ→uninhabited ⟩□
  ¬ (∀ i → A i)    □

-- There is a transitivity-like proof, taking expansion and weak
-- bisimilarity to weak bisimilarity, that preserves the size of the
-- first argument, iff there is a transitivity-like proof, taking weak
-- bisimilarity and the converse of expansion to weak bisimilarity,
-- that preserves the size of the second argument.

Transitivity-≳≈ˡ = ∀ {i} {x y z : Delay A ∞} →
                   [ i ] x ≳ y → y ≈ z → [ i ] x ≈ z

Transitivity-≈≲ʳ = ∀ {i} {x y z : Delay A ∞} →
                   x ≈ y → [ i ] y ≲ z → [ i ] x ≈ z

size-preserving-transitivity-≳≈ˡ⇔size-preserving-transitivity-≈≲ʳ :
  Transitivity-≳≈ˡ ⇔ Transitivity-≈≲ʳ
size-preserving-transitivity-≳≈ˡ⇔size-preserving-transitivity-≈≲ʳ =
  record
    { to   = λ trans x≈y y≲z → symmetric (trans y≲z (symmetric x≈y))
    ; from = λ trans x≳y y≈z → symmetric (trans (symmetric y≈z) x≳y)
    }

-- If there is a transitivity-like proof, taking expansion and weak
-- bisimilarity to weak bisimilarity, that preserves the size of the
-- first argument, then ∀ i → A i is uninhabited.

size-preserving-transitivity-≳≈ˡ→uninhabited :
  Transitivity-≳≈ˡ → ¬ (∀ i → A i)
size-preserving-transitivity-≳≈ˡ→uninhabited =
  Transitivity-≳≈ˡ  ↝⟨ _∘ ∼→ ⟩
  Transitivity-∼≈ˡ  ↝⟨ _⇔_.from size-preserving-transitivity-≈∼ʳ⇔size-preserving-transitivity-∼≈ˡ ⟩
  Transitivity-≈∼ʳ  ↝⟨ size-preserving-transitivity-≈∼ʳ→uninhabited ⟩□
  ¬ (∀ i → A i)     □

-- There is a transitivity-like proof, taking expansion and weak
-- bisimilarity to weak bisimilarity, that preserves the size of both
-- arguments, iff there is a transitivity-like proof, taking weak
-- bisimilarity and the converse of expansion to weak bisimilarity,
-- that also preserves the size of both arguments.

Transitivity-≳≈ = ∀ {i} {x y z : Delay A ∞} →
                  [ i ] x ≳ y → [ i ] y ≈ z → [ i ] x ≈ z

Transitivity-≈≲ = ∀ {i} {x y z : Delay A ∞} →
                  [ i ] x ≈ y → [ i ] y ≲ z → [ i ] x ≈ z

size-preserving-transitivity-≳≈⇔size-preserving-transitivity-≈≲ :
  Transitivity-≳≈ ⇔ Transitivity-≈≲
size-preserving-transitivity-≳≈⇔size-preserving-transitivity-≈≲ =
  record
    { to   = λ trans x≈y y≲z → symmetric (trans y≲z (symmetric x≈y))
    ; from = λ trans x≳y y≈z → symmetric (trans (symmetric y≈z) x≳y)
    }

-- If there is a transitivity-like proof, taking expansion and weak
-- bisimilarity to weak bisimilarity, that preserves the size of both
-- arguments, then ∀ i → A i is uninhabited.

size-preserving-transitivity-≳≈→uninhabited :
  Transitivity-≳≈ → ¬ (∀ i → A i)
size-preserving-transitivity-≳≈→uninhabited =
  Transitivity-≳≈   ↝⟨ (λ trans → trans) ⟩
  Transitivity-≳≈ˡ  ↝⟨ size-preserving-transitivity-≳≈ˡ→uninhabited ⟩□
  ¬ (∀ i → A i)     □

-- If there is a transitivity-like proof, taking weak bisimilarity and
-- expansion to weak bisimilarity, that preserves the size of the
-- first argument, then ∀ i → A i is uninhabited.

Transitivity-≈≳ˡ = ∀ {i} {x y z : Delay A ∞} →
                   [ i ] x ≈ y → y ≳ z → [ i ] x ≈ z

size-preserving-transitivity-≈≳ˡ→uninhabited :
  Transitivity-≈≳ˡ → ¬ (∀ i → A i)
size-preserving-transitivity-≈≳ˡ→uninhabited =
  Transitivity-≈≳ˡ  ↝⟨ (λ trans {_ _ _} x≈ly → trans x≈ly (laterˡ (reflexive _))) ⟩
  Laterʳ⁻¹-≈        ↝⟨ _⇔_.from size-preserving-laterˡ⁻¹-≈⇔size-preserving-laterʳ⁻¹-≈ ⟩
  Laterˡ⁻¹-≈        ↝⟨ size-preserving-laterˡ⁻¹-≈→uninhabited ⟩
  ¬ (∀ i → A i)     □

------------------------------------------------------------------------
-- Lemmas stating that certain types are inhabited if A ∞ is
-- uninhabited

-- If A ∞ is uninhabited, then Now≳later-now is inhabited.

uninhabited→now≳later-now : ¬ A ∞ → Now≳later-now
uninhabited→now≳later-now =
  ¬ A ∞            ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≳ y)  ↝⟨ (λ trivial _ → trivial _ _) ⟩□
  Now≳later-now    □

-- If A ∞ is uninhabited, then a variant of laterˡ⁻¹ for (fully
-- defined) expansion can be defined.

uninhabited→laterˡ⁻¹-≳ : ¬ A ∞ → Laterˡ⁻¹-≳
uninhabited→laterˡ⁻¹-≳ =
  ¬ A ∞            ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≳ y)  ↝⟨ (λ trivial {_ _} _ → trivial _ _) ⟩□
  Laterˡ⁻¹-≳       □

-- If A ∞ is uninhabited, then a transitivity-like proof taking weak
-- bisimilarity and expansion to expansion can be defined.

uninhabited→transitivity-≈≳≳ : ¬ A ∞ → Transitivity-≈≳≳
uninhabited→transitivity-≈≳≳ =
  ¬ A ∞             ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≳ y)   ↝⟨ (λ trivial {_ _ _} _ _ → trivial _ _) ⟩□
  Transitivity-≈≳≳  □

-- If A ∞ is uninhabited, then a transitivity-like proof taking
-- expansion and weak bisimilarity to expansion can be defined.

uninhabited→transitivity-≳≈≳ : ¬ A ∞ → Transitivity-≳≈≳
uninhabited→transitivity-≳≈≳ =
  ¬ A ∞             ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≳ y)   ↝⟨ (λ trivial {_ _ _} _ _ → trivial _ _) ⟩□
  Transitivity-≳≈≳  □

-- If A ∞ is uninhabited, then Laterˡ⁻¹-∼≈ is inhabited.

uninhabited→size-preserving-laterˡ⁻¹-∼≈ : ¬ A ∞ → Laterˡ⁻¹-∼≈
uninhabited→size-preserving-laterˡ⁻¹-∼≈ =
  ¬ A ∞            ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≈ y)  ↝⟨ (λ trivial {_} _ _ → trivial _ _) ⟩□
  Laterˡ⁻¹-∼≈      □

-- If A ∞ is uninhabited, then laterˡ⁻¹ can be made size-preserving.

uninhabited→size-preserving-laterˡ⁻¹ : ¬ A ∞ → Laterˡ⁻¹-≈
uninhabited→size-preserving-laterˡ⁻¹ =
  ¬ A ∞            ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≈ y)  ↝⟨ (λ trivial {_ _ _} _ → trivial _ _) ⟩□
  Laterˡ⁻¹-≈       □

-- If A ∞ is uninhabited, then a variant of ⇓-respects-≈ in which _≈_
-- is replaced by _∼_ can be made size-preserving in the second
-- argument.

uninhabited→size-preserving-⇓-respects-∼ʳ : ¬ A ∞ → ⇓-Respects-∼ʳ
uninhabited→size-preserving-⇓-respects-∼ʳ =
  ¬ A ∞            ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≈ y)  ↝⟨ (λ trivial {_ _ _ _} _ _ → trivial _ _) ⟩□
  ⇓-Respects-∼ʳ    □

-- If A ∞ is uninhabited, then ⇓-respects-≈ can be made
-- size-preserving in the second argument.

uninhabited→size-preserving-⇓-respects-≈ʳ : ¬ A ∞ → ⇓-Respects-≈ʳ
uninhabited→size-preserving-⇓-respects-≈ʳ =
  ¬ A ∞            ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≈ y)  ↝⟨ (λ trivial {_ _ _ _} _ _ → trivial _ _) ⟩□
  ⇓-Respects-≈ʳ    □

-- If A ∞ is uninhabited, then there is a transitivity-like proof,
-- taking weak bisimilarity and strong bisimilarity to weak
-- bisimilarity, that preserves the size of the second argument.

uninhabited→size-preserving-transitivity-≈∼ʳ : ¬ A ∞ → Transitivity-≈∼ʳ
uninhabited→size-preserving-transitivity-≈∼ʳ =
  ¬ A ∞             ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≈ y)   ↝⟨ (λ trivial {_ _ _ _} _ _ → trivial _ _) ⟩□
  Transitivity-≈∼ʳ  □

-- If A ∞ is uninhabited, then there is a transitivity-like proof,
-- taking strong bisimilarity and expansion to expansion, that
-- preserves the size of the first argument.

uninhabited→size-preserving-transitivity-∼≳ˡ : ¬ A ∞ → Transitivity-∼≳ˡ
uninhabited→size-preserving-transitivity-∼≳ˡ =
  ¬ A ∞             ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≳ y)   ↝⟨ (λ trivial {_ _ _ _} _ _ → trivial _ _) ⟩□
  Transitivity-∼≳ˡ  □

-- If A ∞ is uninhabited, then there is a transitivity proof for weak
-- bisimilarity that preserves the size of the second argument.

uninhabited→size-preserving-transitivity-≈ʳ : ¬ A ∞ → Transitivity-≈ʳ
uninhabited→size-preserving-transitivity-≈ʳ =
  ¬ A ∞            ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≈ y)  ↝⟨ (λ trivial {_ _ _ _} _ _ → trivial _ _) ⟩□
  Transitivity-≈ʳ  □

-- If A ∞ is uninhabited, then there is a transitivity proof for
-- expansion that preserves the size of the first argument.

uninhabited→size-preserving-transitivity-≳ˡ : ¬ A ∞ → Transitivity-≳ˡ
uninhabited→size-preserving-transitivity-≳ˡ =
  ¬ A ∞            ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≳ y)  ↝⟨ (λ trivial {_ _ _ _} _ _ → trivial _ _) ⟩□
  Transitivity-≳ˡ  □

-- If A ∞ is uninhabited, then there is a fully size-preserving
-- transitivity proof for weak bisimilarity.

uninhabited→size-preserving-transitivity-≈ : ¬ A ∞ → Transitivity-≈
uninhabited→size-preserving-transitivity-≈ =
  ¬ A ∞            ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≈ y)  ↝⟨ (λ trivial {_ _ _ _} _ _ → trivial _ _) ⟩□
  Transitivity-≈   □

-- If A ∞ is uninhabited, then there is a fully size-preserving
-- transitivity proof for expansion.

uninhabited→size-preserving-transitivity-≳ : ¬ A ∞ → Transitivity-≳
uninhabited→size-preserving-transitivity-≳ =
  ¬ A ∞            ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≳ y)  ↝⟨ (λ trivial {_ _ _ _} _ _ → trivial _ _) ⟩□
  Transitivity-≳   □

-- If A ∞ is uninhabited, then there is a transitivity-like proof,
-- taking expansion and weak bisimilarity to weak bisimilarity, that
-- preserves the size of the first argument.

uninhabited→size-preserving-transitivity-≳≈ˡ : ¬ A ∞ → Transitivity-≳≈ˡ
uninhabited→size-preserving-transitivity-≳≈ˡ =
  ¬ A ∞             ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≈ y)   ↝⟨ (λ trivial {_ _ _ _} _ _ → trivial _ _) ⟩□
  Transitivity-≳≈ˡ  □

-- If A ∞ is uninhabited, then there is a transitivity-like proof,
-- taking expansion and weak bisimilarity to weak bisimilarity, that
-- preserves the size of both arguments.

uninhabited→size-preserving-transitivity-≳≈ : ¬ A ∞ → Transitivity-≳≈
uninhabited→size-preserving-transitivity-≳≈ =
  ¬ A ∞            ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≈ y)  ↝⟨ (λ trivial {_ _ _ _} _ _ → trivial _ _) ⟩□
  Transitivity-≳≈  □

-- If A ∞ is uninhabited, then there is a transitivity-like proof,
-- taking weak bisimilarity and expansion to weak bisimilarity, that
-- preserves the size of the first argument.

uninhabited→size-preserving-transitivity-≈≳ˡ : ¬ A ∞ → Transitivity-≈≳ˡ
uninhabited→size-preserving-transitivity-≈≳ˡ =
  ¬ A ∞             ↝⟨ uninhabited→trivial ⟩
  (∀ x y → x ≈ y)   ↝⟨ (λ trivial {_ _ _ _} _ _ → trivial _ _) ⟩□
  Transitivity-≈≳ˡ  □


------------------------------------------------------------------------
-- Queue instances for the queues in Queue.Truncated
------------------------------------------------------------------------

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

import Equality.Path as P
open import Prelude
import Queue

module Queue.Truncated.Instances
  {e⁺}
  (eq : ∀ {a p} → P.Equality-with-paths a p e⁺)
  (open P.Derived-definitions-and-properties eq)

  {Q : ∀ {ℓ} → Type ℓ → Type ℓ}
  ⦃ is-queue :
      ∀ {ℓ} →
      Queue.Is-queue equality-with-J Q (λ _ → ↑ _ ⊤) ℓ ⦄
  ⦃ is-queue-with-map :
      ∀ {ℓ₁ ℓ₂} →
      Queue.Is-queue-with-map equality-with-J Q ℓ₁ ℓ₂ ⦄
  where

open Queue equality-with-J
open import Queue.Truncated eq as Q using (Queue)

open import Bijection equality-with-J using (_↔_)
open import Erased.Cubical eq hiding (map)
open import H-level.Closure equality-with-J
open import List equality-with-J as L hiding (map)
open import Maybe equality-with-J
open import Monad equality-with-J hiding (map)
import Nat equality-with-J as Nat

private

  variable
    a ℓ ℓ₁ ℓ₂ : Level
    A         : Type a
    xs        : List A

  module N = Q.Non-indexed

instance

  -- Instances.

  Queue-is-queue : Is-queue (Queue Q) Very-stableᴱ-≡ ℓ
  Queue-is-queue .Is-queue.to-List           = Q.to-List
  Queue-is-queue .Is-queue.from-List         = Q.from-List
  Queue-is-queue .Is-queue.to-List-from-List = _↔_.right-inverse-of
                                                 (Q.Queue↔List _) _
  Queue-is-queue .Is-queue.enqueue           = N.enqueue
  Queue-is-queue .Is-queue.to-List-enqueue   = N.to-List-enqueue
  Queue-is-queue .Is-queue.dequeue           = N.dequeue
  Queue-is-queue .Is-queue.to-List-dequeue   = N.to-List-dequeue
  Queue-is-queue .Is-queue.dequeue⁻¹         = N.dequeue⁻¹
  Queue-is-queue .Is-queue.to-List-dequeue⁻¹ = N.to-List-dequeue⁻¹

  Queue-is-queue-with-map : Is-queue-with-map (Queue Q) ℓ₁ ℓ₂
  Queue-is-queue-with-map .Is-queue-with-map.map         = N.map
  Queue-is-queue-with-map .Is-queue-with-map.to-List-map = N.to-List-map

  Queue-is-queue-with-unique-representations :
    Is-queue-with-unique-representations (Queue Q) ℓ
  Queue-is-queue-with-unique-representations
    .Is-queue-with-unique-representations.from-List-to-List =
    _↔_.left-inverse-of (Q.Queue↔List _) _

------------------------------------------------------------------------
-- Some examples

private

  ns       = Very-stable→Very-stableᴱ 1 $
             Decidable-equality→Very-stable-≡ Nat._≟_
  dequeue′ = dequeue ns
  to-List′ = to-List ns
  empty′   = empty ⦂ Queue Q ℕ

  example₁ : map suc (enqueue 3 empty) ≡ enqueue 4 empty′
  example₁ = _↔_.to Q.≡-for-indices↔≡ [ refl _ ]

  example₂ : dequeue′ (map (_* 2) (enqueue 3 (enqueue 5 empty))) ≡
             just (10 , enqueue 6 empty)
  example₂ =
    dequeue′ (map (_* 2) (enqueue 3 (enqueue 5 empty)))  ≡⟨ cong dequeue′ (_↔_.to Q.≡-for-indices↔≡ [ refl _ ]) ⟩
    dequeue′ (cons 10 (enqueue 6 empty))                 ≡⟨ _↔_.right-inverse-of (Queue↔Maybe[×Queue] _) _ ⟩∎
    just (10 , enqueue 6 empty)                          ∎

  example₃ :
    (do x , q ← dequeue′ (map (_* 2) (enqueue 3 (enqueue 5 empty)))
        return (enqueue (3 * x) q)) ≡
    just (enqueue 30 (enqueue 6 empty))
  example₃ =
    (do x , q ← dequeue′ (map (_* 2) (enqueue 3 (enqueue 5 empty)))
        return (enqueue (3 * x) q ⦂ Queue Q ℕ))                      ≡⟨ cong (_>>= λ _ → return (enqueue _ _)) example₂ ⟩

    (do x , q ← just (10 , enqueue 6 empty)
        return (enqueue (3 * x) (q ⦂ Queue Q ℕ)))                    ≡⟨⟩

    just (enqueue 30 (enqueue 6 empty))                              ∎

  example₄ :
    (do x , q ← dequeue′ (map (_* 2) (enqueue 3 (enqueue 5 empty)))
        let q = enqueue (3 * x) q
        return (to-List′ q)) ≡
    just (6 ∷ 30 ∷ [])
  example₄ =
    (do x , q ← dequeue′ (map (_* 2) (enqueue 3 (enqueue 5 empty)))
        return (to-List′ (enqueue (3 * x) q)))                       ≡⟨ cong (_>>= λ _ → return (to-List′ (enqueue _ _))) example₂ ⟩

    (do x , q ← just (10 , enqueue 6 empty)
        return (to-List′ (enqueue (3 * x) (q ⦂ Queue Q ℕ))))         ≡⟨⟩

    just (to-List′ (enqueue 30 (enqueue 6 empty)))                   ≡⟨ cong just $ to-List-foldl-enqueue-empty _ ⟩∎

    just (6 ∷ 30 ∷ [])                                               ∎

  example₅ :
    ∀ {xs} →
    dequeue′ (from-List (1 ∷ 2 ∷ 3 ∷ xs)) ≡
    just (1 , from-List (2 ∷ 3 ∷ xs))
  example₅ {xs = xs} =
    dequeue′ (from-List (1 ∷ 2 ∷ 3 ∷ xs))       ≡⟨ cong dequeue′ (_↔_.to Q.≡-for-indices↔≡ [ refl _ ]) ⟩
    dequeue′ (cons 1 (from-List (2 ∷ 3 ∷ xs)))  ≡⟨ _↔_.right-inverse-of (Queue↔Maybe[×Queue] _) _ ⟩∎
    just (1 , from-List (2 ∷ 3 ∷ xs))           ∎

  example₆ :
    foldr enqueue empty′ (3 ∷ 2 ∷ 1 ∷ []) ≡
    from-List (1 ∷ 2 ∷ 3 ∷ [])
  example₆ = _↔_.to Q.≡-for-indices↔≡ [ refl _ ]


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

open import Base
open import Homotopy.Pushout

module Homotopy.VanKampen {i} (d : pushout-diag i) where
open pushout-diag d

open import Homotopy.Truncation
open import Homotopy.PathTruncation
open import Homotopy.VanKampen.Guide

module _ (l : legend i C) where
  open legend l

  open import Homotopy.VanKampen.Code d l
  open import Homotopy.VanKampen.CodeToPath d l

  private
    refl⇒code : ∀ p → code p p
    refl⇒code = pushout-rec
      (λ p → code p p)
      (λ _ → ⟧a refl₀)
      (λ _ → ⟧b refl₀)
      (loc-fiber-rec l
        (λ c → transport (λ p → code p p) (glue c) (⟧a refl₀)
             ≡ ⟧b refl₀)
        ⦃ λ _ → b-code-b-is-set _ _ _ _ ⦄
        (λ n →
          transport (λ p → code p p) (glue $ loc n) (⟧a refl₀)
              ≡⟨ trans-diag code (glue $ loc n) $ ⟧a refl₀ ⟩
          transport (λ p → code p (right $ g $ loc n)) (glue $ loc n)
            (transport (a-code (f $ loc n)) (glue $ loc n) (⟧a refl₀))
              ≡⟨ ap (transport (λ p → code p (right $ g $ loc n)) (glue $ loc n))
                    $ trans-a-code-glue-loc n $ ⟧a refl₀ ⟩
          transport (λ p → code p (right $ g $ loc n)) (glue $ loc n) (aa⇒ab n (⟧a refl₀))
              ≡⟨ trans-glue-code-loc n {right $ g $ loc n} $ aa⇒ab n (⟧a refl₀) ⟩
          ab⇒bb n (aa⇒ab n (⟧a refl₀))
              ≡⟨ refl ⟩
          ⟧b refl₀ bb⟦ n ⟧a refl₀ ba⟦ n ⟧b refl₀
              ≡⟨ b-code-b-refl-refl _ _ ⟩∎
          ⟧b refl₀
              ∎))

  path⇒code : ∀ {p₁} {p₂} (q : p₁ ≡₀ p₂) → code p₁ p₂
  path⇒code {p₁} {p₂} = π₀-extend-nondep
    ⦃ code-is-set p₁ p₂ ⦄
    (λ q → transport (code p₁) q (refl⇒code p₁))

  private
    refl⇒code⇒path : ∀ p → code⇒path (refl⇒code p) ≡ refl₀ {a = p}
    refl⇒code⇒path = pushout-rec
      (λ p → code⇒path (refl⇒code p) ≡ refl₀)
      (λ _ → refl)
      (λ _ → refl)
      (λ _ → prop-has-all-paths (π₀-is-set _ _ _) _ _)

    path′⇒code⇒path : ∀ {p₁ p₂} (q : p₁ ≡ p₂) → code⇒path (path⇒code $ proj q) ≡ proj q
    path′⇒code⇒path refl = refl⇒code⇒path _

  path⇒code⇒path : ∀ {p₁ p₂} (q : p₁ ≡₀ p₂) → code⇒path (path⇒code q) ≡ q
  path⇒code⇒path {p₁} {p₂} = π₀-extend
    ⦃ λ _ → ≡-is-set $ π₀-is-set (p₁ ≡ p₂) ⦄
    (path′⇒code⇒path {p₁} {p₂})

  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

  private
    path⇒code-concat₀ : ∀ {p₁ p₂ p₃} (q : p₁ ≡₀ p₂) (r : p₂ ≡ p₃)
      → path⇒code (q ∘₀ proj r) ≡ transport (code p₁) r (path⇒code q)
    path⇒code-concat₀ q refl = ap path⇒code $ refl₀-right-unit q

    module _ {a₁} where
      ap⇒path⇒ap-split :
          (∀ {a₂} (co : a-code-a a₁ a₂) → path⇒code (aa⇒path co) ≡ co)
        × (∀ {b₂} (co : a-code-b a₁ b₂) → path⇒code (ab⇒path co) ≡ co)
      ap⇒path⇒ap-split = a-code-rec a₁
        (λ {a₂} (co : a-code-a a₁ a₂) → path⇒code (aa⇒path co) ≡ co)
        ⦃ λ {a₂} _ → ≡-is-set $ a-code-a-is-set a₁ a₂ ⦄
        (λ {b₂} (co : a-code-b a₁ b₂) → path⇒code (ab⇒path co) ≡ co)
        ⦃ λ {b₂} _ → ≡-is-set $ a-code-b-is-set a₁ b₂ ⦄
        (λ {a₂} p → π₀-extend
          {P = λ p → path⇒code (aa⇒path $ ⟧a p) ≡ ⟧a p}
          ⦃ λ _ → ≡-is-set $ a-code-a-is-set a₁ a₂ ⦄
          (λ p → let p′ = proj p in
            path⇒code (ap₀l p′)
              ≡⟨ refl ⟩
            transport (a-code a₁) (ap left p) (path⇒code $ refl₀ {a = left a₁})
              ≡⟨ trans-ap (a-code a₁) left p $ ⟧a refl₀ ⟩
            transport (a-code-a a₁) p (⟧a refl₀)
              ≡⟨ trans-a-code-a p refl₀ ⟩∎
            ⟧a proj p
              ∎) p)
        (λ {a₂} n {co} eq p → π₀-extend
          {P = λ p → path⇒code (aa⇒path $ co ab⟦ n ⟧a p) ≡ co ab⟦ n ⟧a p}
          ⦃ λ _ → ≡-is-set $ a-code-a-is-set a₁ a₂ ⦄
          (λ p → let p′ = proj p in
            path⇒code ((ab⇒path co ∘₀ p!gl n) ∘₀ ap₀l p′)
                ≡⟨ path⇒code-concat₀ (ab⇒path co ∘₀ p!gl n) (ap left p) ⟩
            transport (a-code a₁) (ap left p) (path⇒code (ab⇒path co ∘₀ p!gl n))
                ≡⟨ trans-ap (a-code a₁) left p $ path⇒code $ ab⇒path co ∘₀ p!gl n ⟩
            transport (a-code-a a₁) p (path⇒code (ab⇒path co ∘₀ p!gl n))
                ≡⟨ ap (transport (a-code-a a₁) p)
                      $ path⇒code-concat₀ (ab⇒path co) (! (glue $ loc n)) ⟩
            transport (a-code-a a₁) p (transport (a-code a₁) (! (glue $ loc n))
              $ path⇒code $ ab⇒path co)
                ≡⟨ ap (transport (a-code-a a₁) p ◯ transport (a-code a₁) (! (glue $ loc n))) eq ⟩
            transport (a-code-a a₁) p (transport (a-code a₁) (! (glue $ loc n)) co)
                ≡⟨ ap (transport (a-code-a a₁) p) $ trans-a-code-!glue-loc n co ⟩
            transport (a-code-a a₁) p (co ab⟦ n ⟧a refl₀)
                ≡⟨ trans-a-code-ba p n co refl₀ ⟩∎
            co ab⟦ n ⟧a p′
                ∎) p)
        (λ {b₂} n {co} eq p → π₀-extend
          {P = λ p → path⇒code (ab⇒path $ co aa⟦ n ⟧b p) ≡ co aa⟦ n ⟧b p}
          ⦃ λ _ → ≡-is-set $ a-code-b-is-set a₁ b₂ ⦄
          (λ p → let p′ = proj p in
            path⇒code ((aa⇒path co ∘₀ pgl n) ∘₀ ap₀r p′)
                ≡⟨ path⇒code-concat₀ (aa⇒path co ∘₀ pgl n) (ap right p) ⟩
            transport (a-code a₁) (ap right p) (path⇒code (aa⇒path co ∘₀ pgl n))
                ≡⟨ trans-ap (a-code a₁) right p $ path⇒code $ aa⇒path co ∘₀ pgl n ⟩
            transport (a-code-b a₁) p (path⇒code (aa⇒path co ∘₀ pgl n))
                ≡⟨ ap (transport (a-code-b a₁) p)
                      $ path⇒code-concat₀ (aa⇒path co) (glue $ loc n) ⟩
            transport (a-code-b a₁) p (transport (a-code a₁) (glue $ loc n)
              $ path⇒code $ aa⇒path co)
                ≡⟨ ap (transport (a-code-b a₁) p ◯ transport (a-code a₁) (glue $ loc n)) eq ⟩
            transport (a-code-b a₁) p (transport (a-code a₁) (glue $ loc n) co)
                ≡⟨ ap (transport (a-code-b a₁) p) $ trans-a-code-glue-loc n co ⟩
            transport (a-code-b a₁) p (co aa⟦ n ⟧b refl₀)
                ≡⟨ trans-a-code-ab p n co refl₀ ⟩∎
            co aa⟦ n ⟧b p′
                ∎) p)
        (λ _ _ → prop-has-all-paths (a-code-a-is-set a₁ _ _ _) _ _)
        (λ _ _ → prop-has-all-paths (a-code-b-is-set a₁ _ _ _) _ _)
        (λ _ _ _ _ → prop-has-all-paths (a-code-a-is-set a₁ _ _ _) _ _)

      ap⇒path⇒ap : ∀ {p₂} (co : a-code a₁ p₂) → path⇒code {left a₁} {p₂} (ap⇒path co) ≡ co
      ap⇒path⇒ap {p₂} = pushout-rec
        (λ x → ∀ (co : a-code a₁ x) → path⇒code {left a₁} {x} (ap⇒path co) ≡ co)
        (λ _ → π₁ ap⇒path⇒ap-split)
        (λ _ → π₂ ap⇒path⇒ap-split)
        (λ _ → funext λ _ → prop-has-all-paths (a-code-b-is-set a₁ _ _ _) _ _)
        p₂

    -- FIXME Duplicate code!
    -- Unless there's a way to handle type-conversion nicely
    -- (with definitional equality), I'd rather copy and paste.
    module _ {b₁} where
      bp⇒path⇒bp-split :
          (∀ {b₂} (co : b-code-b b₁ b₂) → path⇒code (bb⇒path co) ≡ co)
        × (∀ {a₂} (co : b-code-a b₁ a₂) → path⇒code (ba⇒path co) ≡ co)
      bp⇒path⇒bp-split = b-code-rec b₁
        (λ {b₂} (co : b-code-b b₁ b₂) → path⇒code (bb⇒path co) ≡ co)
        ⦃ λ {b₂} _ → ≡-is-set $ b-code-b-is-set b₁ b₂ ⦄
        (λ {a₂} (co : b-code-a b₁ a₂) → path⇒code (ba⇒path co) ≡ co)
        ⦃ λ {a₂} _ → ≡-is-set $ b-code-a-is-set b₁ a₂ ⦄
        (λ {b₂} p → π₀-extend
          {P = λ p → path⇒code (bb⇒path $ ⟧b p) ≡ ⟧b p}
          ⦃ λ _ → ≡-is-set $ b-code-b-is-set b₁ b₂ ⦄
          (λ p → let p′ = proj p in
            path⇒code (ap₀r p′)
              ≡⟨ refl ⟩
            transport (b-code b₁) (ap right p) (path⇒code $ refl₀ {a = right b₁})
              ≡⟨ trans-ap (b-code b₁) right p $ ⟧b refl₀ ⟩
            transport (b-code-b b₁) p (⟧b refl₀)
              ≡⟨ trans-b-code-b p refl₀ ⟩∎
            ⟧b proj p
              ∎) p)
        (λ {b₂} n {co} eq p → π₀-extend
          {P = λ p → path⇒code (bb⇒path $ co ba⟦ n ⟧b p) ≡ co ba⟦ n ⟧b p}
          ⦃ λ _ → ≡-is-set $ b-code-b-is-set b₁ b₂ ⦄
          (λ p → let p′ = proj p in
            path⇒code ((ba⇒path co ∘₀ pgl n) ∘₀ ap₀r p′)
                ≡⟨ path⇒code-concat₀ (ba⇒path co ∘₀ pgl n) (ap right p) ⟩
            transport (b-code b₁) (ap right p) (path⇒code (ba⇒path co ∘₀ pgl n))
                ≡⟨ trans-ap (b-code b₁) right p $ path⇒code $ ba⇒path co ∘₀ pgl n ⟩
            transport (b-code-b b₁) p (path⇒code (ba⇒path co ∘₀ pgl n))
                ≡⟨ ap (transport (b-code-b b₁) p)
                      $ path⇒code-concat₀ (ba⇒path co) (glue $ loc n) ⟩
            transport (b-code-b b₁) p (transport (b-code b₁) (glue $ loc n)
              $ path⇒code $ ba⇒path co)
                ≡⟨ ap (transport (b-code-b b₁) p ◯ transport (b-code b₁) (glue $ loc n)) eq ⟩
            transport (b-code-b b₁) p (transport (b-code b₁) (glue $ loc n) co)
                ≡⟨ ap (transport (b-code-b b₁) p) $ trans-b-code-glue-loc n co ⟩
            transport (b-code-b b₁) p (co ba⟦ n ⟧b refl₀)
                ≡⟨ trans-b-code-ab p n co refl₀ ⟩∎
            co ba⟦ n ⟧b p′
                ∎) p)
        (λ {a₂} n {co} eq p → π₀-extend
          {P = λ p → path⇒code (ba⇒path $ co bb⟦ n ⟧a p) ≡ co bb⟦ n ⟧a p}
          ⦃ λ _ → ≡-is-set $ b-code-a-is-set b₁ a₂ ⦄
          (λ p → let p′ = proj p in
            path⇒code ((bb⇒path co ∘₀ p!gl n) ∘₀ ap₀l p′)
                ≡⟨ path⇒code-concat₀ (bb⇒path co ∘₀ p!gl n) (ap left p) ⟩
            transport (b-code b₁) (ap left p) (path⇒code (bb⇒path co ∘₀ p!gl n))
                ≡⟨ trans-ap (b-code b₁) left p $ path⇒code $ bb⇒path co ∘₀ p!gl n ⟩
            transport (b-code-a b₁) p (path⇒code (bb⇒path co ∘₀ p!gl n))
                ≡⟨ ap (transport (b-code-a b₁) p)
                      $ path⇒code-concat₀ (bb⇒path co) (! (glue $ loc n)) ⟩
            transport (b-code-a b₁) p (transport (b-code b₁) (! (glue $ loc n))
              $ path⇒code $ bb⇒path co)
                ≡⟨ ap (transport (b-code-a b₁) p ◯ transport (b-code b₁) (! (glue $ loc n))) eq ⟩
            transport (b-code-a b₁) p (transport (b-code b₁) (! (glue $ loc n)) co)
                ≡⟨ ap (transport (b-code-a b₁) p) $ trans-b-code-!glue-loc n co ⟩
            transport (b-code-a b₁) p (co bb⟦ n ⟧a refl₀)
                ≡⟨ trans-b-code-ba p n co refl₀ ⟩∎
            co bb⟦ n ⟧a p′
                ∎) p)
        (λ _ _ → prop-has-all-paths (b-code-b-is-set b₁ _ _ _) _ _)
        (λ _ _ → prop-has-all-paths (b-code-a-is-set b₁ _ _ _) _ _)
        (λ _ _ _ _ → prop-has-all-paths (b-code-b-is-set b₁ _ _ _) _ _)

      bp⇒path⇒bp : ∀ {p₂} (co : b-code b₁ p₂) → path⇒code {right b₁} {p₂} (bp⇒path co) ≡ co
      bp⇒path⇒bp {p₂} = pushout-rec
        (λ x → ∀ (co : b-code b₁ x) → path⇒code {right b₁} {x} (bp⇒path co) ≡ co)
        (λ _ → π₂ bp⇒path⇒bp-split)
        (λ _ → π₁ bp⇒path⇒bp-split)
        (λ _ → funext λ _ → prop-has-all-paths (b-code-b-is-set b₁ _ _ _) _ _)
        p₂

  code⇒path⇒code : ∀ {p₁ p₂} (co : code p₁ p₂) → path⇒code {p₁} {p₂} (code⇒path co) ≡ co
  code⇒path⇒code {p₁} {p₂} = pushout-rec
    (λ p₁ → ∀ (co : code p₁ p₂) → path⇒code {p₁} {p₂} (code⇒path co) ≡ co)
    (λ a₁ → ap⇒path⇒ap {a₁} {p₂})
    (λ b₁ → bp⇒path⇒bp {b₁} {p₂})
    (λ c → funext λ co → prop-has-all-paths (b-code-is-set (g c) p₂ _ co) _ _)
    p₁

  van-kampen : ∀ {p₁ p₂} → (p₁ ≡₀ p₂) ≡ code p₁ p₂
  van-kampen {p₁} {p₂} =
    eq-to-path $ path⇒code {p₁} {p₂} ,
      iso-is-eq (path⇒code {p₁} {p₂}) (code⇒path {p₁} {p₂})
        (code⇒path⇒code {p₁} {p₂}) (path⇒code⇒path {p₁} {p₂})

module _ where
  open import Homotopy.VanKampen.Code d (id-legend C)

  naive-van-kampen : ∀ {p₁ p₂} → (p₁ ≡₀ p₂) ≡ code p₁ p₂
  naive-van-kampen = van-kampen (id-legend C)


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

open import HoTT
import homotopy.RelativelyConstantToSetExtendsViaSurjection as SurjExt

module homotopy.VanKampen {i j k l}
  (span : Span {i} {j} {k})
  {D : Type l} (h : D → Span.C span) (h-is-surj : is-surj h) where

  open Span span
  open import homotopy.vankampen.CodeAP span h h-is-surj
  open import homotopy.vankampen.CodeBP span h h-is-surj
  open import homotopy.vankampen.Code span h h-is-surj

  -- favonia: [pPP] means path from [P] to [P].
  encode-idp : ∀ p → code p p
  encode-idp = Pushout-elim {P = λ p → code p p}
    (λ a → q[ ⟧a idp₀ ]) (λ b → q[ ⟧b idp₀ ]) lemma where
    abstract
      lemma : ∀ c → q[ ⟧a idp₀ ] == q[ ⟧b idp₀ ] [ (λ p → code p p) ↓ glue c ]
      lemma = SurjExt.ext
        {{↓-preserves-level ⟨⟩}}
        h h-is-surj
        (λ d → from-transp (λ p → code p p) (glue (h d)) $
          transport (λ p → code p p) (glue (h d)) q[ ⟧a idp₀ ]
            =⟨ ap (λ pPP → coe pPP q[ pc-a idp₀ ]) ((! (ap2-diag code (glue (h d)))) ∙ ap2-out code (glue (h d)) (glue (h d))) ⟩
          coe (ap (λ p₀ → code p₀ (left (f (h d)))) (glue (h d)) ∙ ap (codeBP (g (h d))) (glue (h d))) q[ ⟧a idp₀ ]
            =⟨ coe-∙ (ap (λ p₀ → code p₀ (left (f (h d)))) (glue (h d))) (ap (codeBP (g (h d))) (glue (h d))) q[ pc-a idp₀ ] ⟩
          transport (codeBP (g (h d))) (glue (h d)) (transport (λ p₀ → code p₀ (left (f (h d)))) (glue (h d)) q[ ⟧a idp₀ ])
            =⟨ transp-cPA-glue d (⟧a idp₀) |in-ctx transport (λ p₁ → code (right (g (h d))) p₁) (glue (h d)) ⟩
          transport (codeBP (g (h d))) (glue (h d)) q[ ⟧b idp₀ bb⟦ d ⟧a idp₀ ]
            =⟨ transp-cBP-glue d (⟧b idp₀ bb⟦ d ⟧a idp₀) ⟩
          q[ ⟧b idp₀ bb⟦ d ⟧a idp₀ ba⟦ d ⟧b idp₀ ]
            =⟨ quot-rel (pcBBr-idp₀-idp₀ (pc-b idp₀)) ⟩
          q[ ⟧b idp₀ ]
            =∎)
        (λ _ _ _ → prop-has-all-paths-↓ {{↓-level ⟨⟩}})

  encode : ∀ {p₀ p₁} → p₀ =₀ p₁ → code p₀ p₁
  encode {p₀} {p₁} pPP = transport₀ (code p₀) {{code-is-set {p₀} {p₁}}} pPP (encode-idp p₀)

  abstract
    decode-encode-idp : ∀ p → decode {p} {p} (encode-idp p) == idp₀
    decode-encode-idp = Pushout-elim
      {P = λ p → decode {p} {p} (encode-idp p) == idp₀}
      (λ _ → idp) (λ _ → idp)
      (λ c → prop-has-all-paths-↓)

    decode-encode' : ∀ {p₀ p₁} (pPP : p₀ == p₁) → decode {p₀} {p₁} (encode [ pPP ]) == [ pPP ]
    decode-encode' idp = decode-encode-idp _

    decode-encode : ∀ {p₀ p₁} (pPP : p₀ =₀ p₁) → decode {p₀} {p₁} (encode pPP) == pPP
    decode-encode = Trunc-elim decode-encode'

  abstract
    transp-idcAA-r : ∀ {a₀ a₁} (p : a₀ == a₁) -- [idc] = identity code
      → transport (codeAA a₀) p q[ ⟧a idp₀ ] == q[ ⟧a [ p ] ]
    transp-idcAA-r idp = idp

    encode-decodeAA : ∀ {a₀ a₁} (c : precodeAA a₀ a₁)
      → encode (decodeAA q[ c ]) == q[ c ]
    encode-decodeAB : ∀ {a₀ b₁} (c : precodeAB a₀ b₁)
      → encode (decodeAB q[ c ]) == q[ c ]
    encode-decodeAA {a₀} (pc-a pA) = Trunc-elim
      {P = λ pA → encode (decodeAA q[ ⟧a pA ]) == q[ ⟧a pA ]}
      (λ pA →
        transport (codeAP a₀) (ap left pA) q[ ⟧a idp₀ ]
          =⟨ ap (λ e → coe e q[ ⟧a idp₀ ]) (∘-ap (codeAP a₀) left pA) ⟩
        transport (codeAA a₀) pA q[ ⟧a idp₀ ]
          =⟨ transp-idcAA-r pA ⟩
        q[ ⟧a [ pA ] ]
          =∎)
      pA
    encode-decodeAA {a₀} (pc-aba d pc pA) = Trunc-elim
      {P = λ pA → encode (decodeAA q[ pc ab⟦ d ⟧a pA ]) == q[ pc ab⟦ d ⟧a pA ]}
      (λ pA →
        encode (decodeAB q[ pc ] ∙₀' [ ! (glue (h d)) ∙' ap left pA ])
          =⟨ transp₀-∙₀' {{λ {p₁} → code-is-set {left a₀} {p₁}}} (decodeAB q[ pc ]) [ ! (glue (h d)) ∙' ap left pA ] (encode-idp (left a₀)) ⟩
        transport (codeAP a₀) (! (glue (h d)) ∙' ap left pA) (encode (decodeAB q[ pc ]))
          =⟨ ap (transport (codeAP a₀) (! (glue (h d)) ∙' ap left pA)) (encode-decodeAB pc) ⟩
        transport (codeAP a₀) (! (glue (h d)) ∙' ap left pA) q[ pc ]
          =⟨ transp-∙' {B = codeAP a₀} (! (glue (h d))) (ap left pA) q[ pc ] ⟩
        transport (codeAP a₀) (ap left pA) (transport (codeAP a₀) (! (glue (h d))) q[ pc ])
          =⟨ ap (transport (codeAP a₀) (ap left pA)) (transp-cAP-!glue d pc) ⟩
        transport (codeAP a₀) (ap left pA) q[ pc ab⟦ d ⟧a idp₀ ]
          =⟨ ap (λ e → coe e q[ pc ab⟦ d ⟧a idp₀ ]) (∘-ap (codeAP a₀) left pA) ⟩
        transport (codeAA a₀) pA q[ pc ab⟦ d ⟧a idp₀ ]
          =⟨ transp-cAA-r d pA (pc ab⟦ d ⟧a idp₀) ⟩
        q[ pc ab⟦ d ⟧a idp₀ aa⟦ d ⟧b idp₀ ab⟦ d ⟧a [ pA ] ]
          =⟨ quot-rel (pcAAr-cong (pcABr-idp₀-idp₀ pc) [ pA ]) ⟩
        q[ pc ab⟦ d ⟧a [ pA ] ]
          =∎)
      pA
    encode-decodeAB {a₀} (pc-aab d pc pB) = Trunc-elim
      {P = λ pB → encode (decodeAB q[ pc aa⟦ d ⟧b pB ]) == q[ pc aa⟦ d ⟧b pB ]}
      (λ pB →
        encode (decodeAA q[ pc ] ∙₀' [ glue (h d) ∙' ap right pB ])
          =⟨ transp₀-∙₀' {{λ {p₁} → code-is-set {left a₀} {p₁}}} (decodeAA q[ pc ]) [ glue (h d) ∙' ap right pB ] (encode-idp (left a₀)) ⟩
        transport (codeAP a₀) (glue (h d) ∙' ap right pB) (encode (decodeAA q[ pc ]))
          =⟨ ap (transport (codeAP a₀) (glue (h d) ∙' ap right pB)) (encode-decodeAA pc) ⟩
        transport (codeAP a₀) (glue (h d) ∙' ap right pB) q[ pc ]
          =⟨ transp-∙' {B = codeAP a₀} (glue (h d)) (ap right pB) q[ pc ] ⟩
        transport (codeAP a₀) (ap right pB) (transport (codeAP a₀) (glue (h d)) q[ pc ])
          =⟨ ap (transport (codeAP a₀) (ap right pB)) (transp-cAP-glue d pc) ⟩
        transport (codeAP a₀) (ap right pB) q[ pc aa⟦ d ⟧b idp₀ ]
          =⟨ ap (λ e → coe e q[ pc aa⟦ d ⟧b idp₀ ]) (∘-ap (codeAP a₀) right pB) ⟩
        transport (codeAB a₀) pB q[ pc aa⟦ d ⟧b idp₀ ]
          =⟨ transp-cAB-r d pB (pc aa⟦ d ⟧b idp₀) ⟩
        q[ pc aa⟦ d ⟧b idp₀ ab⟦ d ⟧a idp₀ aa⟦ d ⟧b [ pB ] ]
          =⟨ quot-rel (pcABr-cong (pcAAr-idp₀-idp₀ pc) [ pB ]) ⟩
        q[ pc aa⟦ d ⟧b [ pB ] ]
          =∎)
      pB

  abstract
    transp-idcBB-r : ∀ {b₀ b₁} (p : b₀ == b₁) -- [idc] = identity code
      → transport (codeBB b₀) p q[ ⟧b idp₀ ] == q[ ⟧b [ p ] ]
    transp-idcBB-r idp = idp

    encode-decodeBA : ∀ {b₀ a₁} (c : precodeBA b₀ a₁)
      → encode (decodeBA q[ c ]) == q[ c ]
    encode-decodeBB : ∀ {b₀ b₁} (c : precodeBB b₀ b₁)
      → encode (decodeBB q[ c ]) == q[ c ]
    encode-decodeBA {b₀} (pc-bba d pc pA) = Trunc-elim
      {P = λ pA → encode (decodeBA q[ pc bb⟦ d ⟧a pA ]) == q[ pc bb⟦ d ⟧a pA ]}
      (λ pA →
        encode (decodeBB q[ pc ] ∙₀' [ ! (glue (h d)) ∙' ap left pA ])
          =⟨ transp₀-∙₀' {{λ {p₁} → code-is-set {right b₀} {p₁}}} (decodeBB q[ pc ]) [ ! (glue (h d)) ∙' ap left pA ] (encode-idp (right b₀)) ⟩
        transport (codeBP b₀) (! (glue (h d)) ∙' ap left pA) (encode (decodeBB q[ pc ]))
          =⟨ ap (transport (codeBP b₀) (! (glue (h d)) ∙' ap left pA)) (encode-decodeBB pc) ⟩
        transport (codeBP b₀) (! (glue (h d)) ∙' ap left pA) q[ pc ]
          =⟨ transp-∙' {B = codeBP b₀} (! (glue (h d))) (ap left pA) q[ pc ] ⟩
        transport (codeBP b₀) (ap left pA) (transport (codeBP b₀) (! (glue (h d))) q[ pc ])
          =⟨ ap (transport (codeBP b₀) (ap left pA)) (transp-cBP-!glue d pc) ⟩
        transport (codeBP b₀) (ap left pA) q[ pc bb⟦ d ⟧a idp₀ ]
          =⟨ ap (λ e → coe e q[ pc bb⟦ d ⟧a idp₀ ]) (∘-ap (codeBP b₀) left pA) ⟩
        transport (codeBA b₀) pA q[ pc bb⟦ d ⟧a idp₀ ]
          =⟨ transp-cBA-r d pA (pc bb⟦ d ⟧a idp₀) ⟩
        q[ pc bb⟦ d ⟧a idp₀ ba⟦ d ⟧b idp₀ bb⟦ d ⟧a [ pA ] ]
          =⟨ quot-rel (pcBAr-cong (pcBBr-idp₀-idp₀ pc) [ pA ]) ⟩
        q[ pc bb⟦ d ⟧a [ pA ] ]
          =∎)
      pA
    encode-decodeBB {b₀} (pc-b pB) = Trunc-elim
      {P = λ pB → encode (decodeBB q[ ⟧b pB ]) == q[ ⟧b pB ]}
      (λ pB →
        transport (codeBP b₀) (ap right pB) q[ ⟧b idp₀ ]
          =⟨ ap (λ e → coe e q[ ⟧b idp₀ ]) (∘-ap (codeBP b₀) right pB) ⟩
        transport (codeBB b₀) pB q[ ⟧b idp₀ ]
          =⟨ transp-idcBB-r pB ⟩
        q[ ⟧b [ pB ] ]
          =∎)
      pB
    encode-decodeBB {b₀} (pc-bab d pc pB) = Trunc-elim
      {P = λ pB → encode (decodeBB q[ pc ba⟦ d ⟧b pB ]) == q[ pc ba⟦ d ⟧b pB ]}
      (λ pB →
        encode (decodeBA q[ pc ] ∙₀' [ glue (h d) ∙' ap right pB ])
          =⟨ transp₀-∙₀' {{λ {p₁} → code-is-set {right b₀} {p₁}}} (decodeBA q[ pc ]) [ glue (h d) ∙' ap right pB ] (encode-idp (right b₀)) ⟩
        transport (codeBP b₀) (glue (h d) ∙' ap right pB) (encode (decodeBA q[ pc ]))
          =⟨ ap (transport (codeBP b₀) (glue (h d) ∙' ap right pB)) (encode-decodeBA pc) ⟩
        transport (codeBP b₀) (glue (h d) ∙' ap right pB) q[ pc ]
          =⟨ transp-∙' {B = codeBP b₀} (glue (h d)) (ap right pB) q[ pc ] ⟩
        transport (codeBP b₀) (ap right pB) (transport (codeBP b₀) (glue (h d)) q[ pc ])
          =⟨ ap (transport (codeBP b₀) (ap right pB)) (transp-cBP-glue d pc) ⟩
        transport (codeBP b₀) (ap right pB) q[ pc ba⟦ d ⟧b idp₀ ]
          =⟨ ap (λ e → coe e q[ pc ba⟦ d ⟧b idp₀ ]) (∘-ap (codeBP b₀) right pB) ⟩
        transport (codeBB b₀) pB q[ pc ba⟦ d ⟧b idp₀ ]
          =⟨ transp-cBB-r d pB (pc ba⟦ d ⟧b idp₀) ⟩
        q[ pc ba⟦ d ⟧b idp₀ bb⟦ d ⟧a idp₀ ba⟦ d ⟧b [ pB ] ]
          =⟨ quot-rel (pcBBr-cong (pcBAr-idp₀-idp₀ pc) [ pB ]) ⟩
        q[ pc ba⟦ d ⟧b [ pB ] ]
          =∎)
      pB

  abstract
    encode-decode : ∀ {p₀ p₁} (cPP : code p₀ p₁)
      → encode {p₀} {p₁} (decode {p₀} {p₁} cPP) == cPP
    encode-decode {p₀} {p₁} = Pushout-elim
      {P = λ p₀ → ∀ p₁ → (cPP : code p₀ p₁) → encode (decode {p₀} {p₁} cPP) == cPP}
      (λ a₀ → Pushout-elim
        (λ a₁ → SetQuot-elim
          {P = λ cPP → encode (decodeAA cPP) == cPP}
          (encode-decodeAA {a₀} {a₁})
          (λ _ → prop-has-all-paths-↓))
        (λ b₁ → SetQuot-elim
          {P = λ cPP → encode (decodeAB cPP) == cPP}
          (encode-decodeAB {a₀} {b₁})
          (λ _ → prop-has-all-paths-↓))
        (λ _ → prop-has-all-paths-↓))
      (λ b₀ → Pushout-elim
        (λ a₁ → SetQuot-elim
          {P = λ cPP → encode (decodeBA cPP) == cPP}
          (encode-decodeBA {b₀} {a₁})
          (λ _ → prop-has-all-paths-↓))
        (λ b₁ → SetQuot-elim
          {P = λ cPP → encode (decodeBB cPP) == cPP}
          (encode-decodeBB {b₀} {b₁})
          (λ _ → prop-has-all-paths-↓))
        (λ _ → prop-has-all-paths-↓))
      (λ _ → prop-has-all-paths-↓ {{Π-level (λ p₁ → Π-level (λ cPP → has-level-apply (codeBP-is-set {p₁ = p₁}) _ _))}})
      p₀ p₁

  vankampen : ∀ p₀ p₁ → (p₀ =₀ p₁) ≃ code p₀ p₁
  vankampen p₀ p₁ = equiv (encode {p₀} {p₁}) (decode {p₀} {p₁}) (encode-decode {p₀} {p₁}) (decode-encode {p₀} {p₁})