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

open import lib.Basics
open import lib.types.FunctionSeq
open import lib.types.TLevel
open import lib.types.Pi
open import lib.types.Pointed
open import lib.types.Sigma
open import lib.NType2

module lib.types.Truncation where

module _ {i} where

  postulate  -- HIT
    Trunc : (n : ℕ₋₂) (A : Type i) → Type i
    [_] : {n : ℕ₋₂} {A : Type i} → A → Trunc n A
    instance Trunc-level : {n : ℕ₋₂} {A : Type i} → has-level n (Trunc n A)

  module TruncElim {n : ℕ₋₂} {A : Type i} {j} {P : Trunc n A → Type j}
    {{p : (x : Trunc n A) → has-level n (P x)}} (d : (a : A) → P [ a ]) where

    postulate  -- HIT
      f : Π (Trunc n A) P
      [_]-β : ∀ a → f [ a ] ↦ d a
    {-# REWRITE [_]-β #-}

open TruncElim public renaming (f to Trunc-elim)

module TruncRec {i j} {n : ℕ₋₂} {A : Type i} {B : Type j} {{p : has-level n B}}
  (d : A → B) where

  private
    module M = TruncElim {{λ x → p}} d

  f : Trunc n A → B
  f = M.f

open TruncRec public renaming (f to Trunc-rec)

module TruncRecType {i j} {n : ℕ₋₂} {A : Type i} (d : A → n -Type j) where

  open TruncRec {{n -Type-level j}} d public

  flattening-Trunc : Σ (Trunc (S n) A) (fst ∘ f) ≃ Trunc (S n) (Σ A (fst ∘ d))
  flattening-Trunc = equiv to from to-from from-to where

    to-aux : (x : Trunc (S n) A) → (fst (f x) → Trunc (S n) (Σ A (fst ∘ d)))
    to-aux = Trunc-elim (λ a b → [ (a , b) ])

    to : Σ (Trunc (S n) A) (fst ∘ f) → Trunc (S n) (Σ A (fst ∘ d))
    to (x , y) = to-aux x y

    from-aux : Σ A (fst ∘ d) → Σ (Trunc (S n) A) (fst ∘ f)
    from-aux (a , b) = ([ a ] , b)

    from : Trunc (S n) (Σ A (fst ∘ d)) → Σ (Trunc (S n) A) (fst ∘ f)
    from = Trunc-rec {{Σ-level ⟨⟩ (λ x → raise-level _ (snd (f x)))}}
                     from-aux  where

    to-from : (x : Trunc (S n) (Σ A (fst ∘ d))) → to (from x) == x
    to-from = Trunc-elim (λ _ → idp)

    from-to-aux : (a : Trunc (S n) A) (b : fst (f a)) → from (to-aux a b) == (a , b)
    from-to-aux = Trunc-elim {{λ _ → Π-level (λ _ → =-preserves-level (Σ-level ⟨⟩ (λ x → raise-level _ (snd (f x)))))}}
                             (λ a b → idp)

    from-to : (x : Σ (Trunc (S n) A) (fst ∘ f)) → from (to x) == x
    from-to (a , b) = from-to-aux a b

⊙Trunc : ∀ {i} → ℕ₋₂ → Ptd i → Ptd i
⊙Trunc n ⊙[ A , a ] = ⊙[ Trunc n A , [ a ] ]

⊙Trunc-rec : ∀ {i j} {n : ℕ₋₂} {X : Ptd i} {Y : Ptd j} {{Y-level : has-level n (de⊙ Y)}}
  → X ⊙→ Y
  → ⊙Trunc n X ⊙→ Y
⊙Trunc-rec {{Y-level}} f = Trunc-rec {{Y-level}} (fst f) , snd f

⊙Trunc-rec-post-⊙∘ : ∀ {i j k} {n : ℕ₋₂} {X : Ptd i} {Y : Ptd j} {Z : Ptd k}
  {{Y-level : has-level n (de⊙ Y)}} {{Z-level : has-level n (de⊙ Z)}}
  (g : Y ⊙→ Z) (f : X ⊙→ Y)
  → ⊙Trunc-rec {n = n} {{Z-level}} (g ⊙∘ f) == g ⊙∘ ⊙Trunc-rec {{Y-level}} f
⊙Trunc-rec-post-⊙∘ {{Y-level}} {{Z-level}} g f =
  ⊙λ=' (Trunc-elim {{λ x → =-preserves-level Z-level}} (λ x → idp)) idp

module _ {i} {A : Type i} where

  [_]₀ : A → Trunc 0 A
  [_]₀ = [_] {n = 0}

  [_]₁ : A → Trunc 1 A
  [_]₁ = [_] {n = 1}

  [_]₂ : A → Trunc 2 A
  [_]₂ = [_] {n = 2}

module _ {i} {n : ℕ₋₂} {A : Type i} where

  private
    Trunc= : (a b : Trunc (S n) A) → n -Type i
    Trunc= = Trunc-elim (λ a → Trunc-elim ((λ b → (Trunc n (a == b) , Trunc-level))))

  {- t is for truncated -}
  _=ₜ_ : (a b : Trunc (S n) A) → Type i
  _=ₜ_ a b = fst (Trunc= a b)

  =ₜ-level : (a b : Trunc (S n) A) → has-level n (a =ₜ b)
  =ₜ-level a b = snd (Trunc= a b)

  =ₜ-refl : (a : Trunc (S n) A) → a =ₜ a
  =ₜ-refl = Trunc-elim {{λ x → raise-level _ (=ₜ-level x x)}}
                       (λ a → [ idp ])

  =ₜ-equiv : (a b : Trunc (S n) A) → (a == b) ≃ (a =ₜ b)
  =ₜ-equiv a b = to a b , to-is-equiv a b where

    to : (a b : Trunc (S n) A) → (a == b → a =ₜ b)
    to a b p = transport (λ c → a =ₜ c) p (=ₜ-refl a)

    from-aux : (a b : A) → a == b → [ a ] == [ b ] :> Trunc (S n) A
    from-aux _ _ = ap [_]

    from : (a b : Trunc (S n) A) → a =ₜ b → a == b
    from = Trunc-elim (λ a → Trunc-elim (λ b → Trunc-rec (from-aux a b)))

    to-from-aux : (a b : A) → (p : a == b) → to _ _ (from-aux a b p) == [ p ]
    to-from-aux a .a idp = idp

    to-from : (a b : Trunc (S n) A) (x : a =ₜ b) → to a b (from a b x) == x
    to-from = Trunc-elim {{λ x → Π-level (λ y → Π-level (λ _ → =-preserves-level (raise-level _ (=ₜ-level x y))))}}
              (λ a → Trunc-elim {{λ x → Π-level (λ _ → =-preserves-level (raise-level _ (=ₜ-level [ a ] x)))}}
              (λ b → Trunc-elim
              (to-from-aux a b)))

    from-to-aux : (a : Trunc (S n) A) → from a a (=ₜ-refl a) == idp
    from-to-aux = Trunc-elim (λ _ → idp)

    from-to : (a b : Trunc (S n) A) (p : a == b) → from a b (to a b p) == p
    from-to a .a idp = from-to-aux a

    adj : (ta tb : Trunc (S n) A) (p : ta == tb)
      → ap (to ta tb) (from-to ta tb p) == to-from ta tb (to ta tb p)
    adj ta .ta idp =
      Trunc-elim {P = λ ta → ap (to ta ta) (from-to ta ta idp) == to-from ta ta (to ta ta idp)}
                 {{λ x → =-preserves-level $ =-preserves-level $ raise-level _ $ =ₜ-level x x}}
                 (λ _ → idp)
                 ta

    to-is-equiv : ∀ a b → is-equiv (to a b)
    to-is-equiv a b =
      record
      { g = from a b
      ; f-g = to-from a b
      ; g-f = from-to a b
      ; adj = adj a b
      }

  =ₜ-path : (a b : Trunc (S n) A) → (a == b) == (a =ₜ b)
  =ₜ-path a b = ua (=ₜ-equiv a b)

{- Universal property -}

abstract
  Trunc-rec-is-equiv : ∀ {i j} (n : ℕ₋₂) (A : Type i) (B : Type j)
    {{p : has-level n B}} → is-equiv (Trunc-rec {{p}} :> ((A → B) → (Trunc n A → B)))
  Trunc-rec-is-equiv n A B {{p}} = is-eq _ (λ f → f ∘ [_])
    (λ f → λ= (Trunc-elim (λ a → idp))) (λ f → idp)

Trunc-preserves-level : ∀ {i} {A : Type i} {n : ℕ₋₂} (m : ℕ₋₂)
 → has-level n A → has-level n (Trunc m A)
Trunc-preserves-level {n = ⟨-2⟩} _ p = has-level-in
  ([ contr-center p ] , Trunc-elim (λ a → ap [_] (contr-path p a)))
Trunc-preserves-level ⟨-2⟩ _ = contr-has-level Trunc-level
Trunc-preserves-level {n = (S n)} (S m) c = has-level-in (λ t₁ t₂ →
  Trunc-elim
    {{λ s₁ → prop-has-level-S {A = has-level n (s₁ == t₂)} has-level-is-prop}}
    (λ a₁ → Trunc-elim
      {{λ s₂ → prop-has-level-S {A = has-level n ([ a₁ ] == s₂)} has-level-is-prop}}
      (λ a₂ → equiv-preserves-level
      ((=ₜ-equiv [ a₁ ] [ a₂ ])⁻¹)
               {{Trunc-preserves-level {n = n} m (has-level-apply c a₁ a₂)}})
              t₂)
    t₁)

{- an n-type is equivalent to its n-truncation -}
unTrunc-equiv : ∀ {i} {n : ℕ₋₂} (A : Type i)
  {{_ : has-level n A}} → Trunc n A ≃ A
unTrunc-equiv A = equiv f [_] (λ _ → idp) g-f where
  f = Trunc-rec (idf _)
  g-f = Trunc-elim (λ _ → idp)

⊙unTrunc-equiv : ∀ {i} {n : ℕ₋₂} (X : Ptd i)
  {{_ : has-level n (de⊙ X)}} → ⊙Trunc n X ⊙≃ X
⊙unTrunc-equiv {n = n} X = ≃-to-⊙≃ (unTrunc-equiv (de⊙ X)) idp

-- Equivalence associated to the universal property
Trunc-extend-equiv : ∀ {i j} (n : ℕ₋₂) (A : Type i) (B : Type j)
  {{p : has-level n B}} → (A → B) ≃ (Trunc n A → B)
Trunc-extend-equiv n A B = (Trunc-rec , Trunc-rec-is-equiv n A B)

Trunc-fmap : ∀ {i j} {n : ℕ₋₂} {A : Type i} {B : Type j} → ((A → B) → (Trunc n A → Trunc n B))
Trunc-fmap f = Trunc-rec ([_] ∘ f)

⊙Trunc-fmap : ∀ {i j} {n : ℕ₋₂} {X : Ptd i} {Y : Ptd j} → ((X ⊙→ Y) → (⊙Trunc n X ⊙→ ⊙Trunc n Y))
⊙Trunc-fmap F = Trunc-fmap (fst F) , ap [_] (snd F)

–>-unTrunc-equiv-natural : ∀ {i} {j} {n : ℕ₋₂}
  {A : Type i} {B : Type j} (f : A → B)
  {{_ : has-level n A}} {{_ : has-level n B}}
  → –> (unTrunc-equiv {n = n} B) ∘ Trunc-fmap f ∼
    f ∘ –> (unTrunc-equiv {n = n} A)
–>-unTrunc-equiv-natural f = Trunc-elim (λ a → idp)

⊙–>-⊙unTrunc-equiv-natural : ∀ {i} {j} {n : ℕ₋₂}
  {X : Ptd i} {Y : Ptd j} (f : X ⊙→ Y)
  {{_ : has-level n (de⊙ X)}} {{_ : has-level n (de⊙ Y)}}
  → ⊙–> (⊙unTrunc-equiv {n = n} Y) ⊙∘ ⊙Trunc-fmap f ==
    f ⊙∘ ⊙–> (⊙unTrunc-equiv {n = n} X)
⊙–>-⊙unTrunc-equiv-natural (f' , idp) =
  ⊙λ=' (–>-unTrunc-equiv-natural f') idp

⊙–>-⊙unTrunc-equiv-natural-=⊙∘ : ∀ {i} {n : ℕ₋₂}
  {X Y : Ptd i} (f : X ⊙→ Y)
  {{_ : has-level n (de⊙ X)}} {{_ : has-level n (de⊙ Y)}}
  → ⊙–> (⊙unTrunc-equiv {n = n} Y) ◃⊙∘
    ⊙Trunc-fmap f ◃⊙idf
    =⊙∘
    f ◃⊙∘
    ⊙–> (⊙unTrunc-equiv {n = n} X) ◃⊙idf
⊙–>-⊙unTrunc-equiv-natural-=⊙∘ f =
  =⊙∘-in (⊙–>-⊙unTrunc-equiv-natural f)

Trunc-fmap2 : ∀ {i j k} {n : ℕ₋₂} {A : Type i} {B : Type j} {C : Type k}
  → ((A → B → C) → (Trunc n A → Trunc n B → Trunc n C))
Trunc-fmap2 f = Trunc-rec (λ a → Trunc-fmap (f a))

⊙Trunc-rec-⊙Trunc-fmap : ∀ {i j k} {n : ℕ₋₂} {X : Ptd i} {Y : Ptd j} {Z : Ptd k}
  {{Z-level : has-level n (de⊙ Z)}}
  (g : Y ⊙→ Z) (f : X ⊙→ Y)
  → ⊙Trunc-rec {n = n} {{Z-level}} g ⊙∘ ⊙Trunc-fmap f ==
    ⊙Trunc-rec {n = n} {{Z-level}} (g ⊙∘ f)
⊙Trunc-rec-⊙Trunc-fmap {{Z-level}} g f =
  ⊙λ=' (Trunc-elim {{λ tx → =-preserves-level Z-level}} (λ x → idp)) $
  ap (_∙ snd g) (∘-ap (Trunc-elim (fst g)) [_] (snd f))

-- XXX What is the naming convention?
Trunc-fpmap : ∀ {i j} {n : ℕ₋₂} {A : Type i} {B : Type j} {f g : A → B}
  → f ∼ g → Trunc-fmap {n = n} f ∼ Trunc-fmap g
Trunc-fpmap h = Trunc-elim (ap [_] ∘ h)

Trunc-fmap-idf : ∀ {i} {n : ℕ₋₂} {A : Type i}
  → ∀ x → Trunc-fmap {n = n} (idf A) x == x
Trunc-fmap-idf = Trunc-elim (λ _ → idp)

⊙Trunc-fmap-⊙idf : ∀ {i} {n : ℕ₋₂} {X : Ptd i}
  → ⊙Trunc-fmap (⊙idf X) ⊙∼ ⊙idf (⊙Trunc n X)
⊙Trunc-fmap-⊙idf = Trunc-fmap-idf , idp

transport-Trunc : ∀ {i} {n : ℕ₋₂} {A B : Type i}
  (p : A == B)
  → transport (Trunc n) p == Trunc-fmap (coe p)
transport-Trunc p@idp = ! (λ= Trunc-fmap-idf)

⊙transport-⊙Trunc : ∀ {i} {n : ℕ₋₂} {X Y : Ptd i}
  (p : X == Y)
  → ⊙transport (⊙Trunc n) p == ⊙Trunc-fmap (⊙coe p)
⊙transport-⊙Trunc p@idp = ! (⊙λ= ⊙Trunc-fmap-⊙idf)

Trunc-fmap-cst : ∀ {i} {j} {n : ℕ₋₂} {A : Type i} {B : Type j}
  → ∀ (b : B) → Trunc-fmap {n = n} {A = A} (cst b) ∼ cst [ b ]
Trunc-fmap-cst b = Trunc-elim (λ a → idp)

⊙Trunc-fmap-cst : ∀ {i} {j} {n : ℕ₋₂} {X : Ptd i} {Y : Ptd j}
  → ⊙Trunc-fmap {n = n} (⊙cst {X = X} {Y = Y}) ⊙∼ ⊙cst
⊙Trunc-fmap-cst {Y = Y} = Trunc-fmap-cst (pt Y) , idp

Trunc-fmap-∘ : ∀ {i j k} {n : ℕ₋₂} {A : Type i} {B : Type j} {C : Type k}
  (g : B → C) (f : A → B)
  → ∀ x → Trunc-fmap {n = n} g (Trunc-fmap f x) == Trunc-fmap (g ∘ f) x
Trunc-fmap-∘ g f = Trunc-elim (λ _ → idp)

⊙Trunc-fmap-⊙∘ : ∀ {i j k} {n : ℕ₋₂} {A : Ptd i} {B : Ptd j} {C : Ptd k}
  (g : B ⊙→ C) (f : A ⊙→ B)
  → ⊙Trunc-fmap {n = n} g ⊙∘ ⊙Trunc-fmap f ⊙∼ ⊙Trunc-fmap (g ⊙∘ f)
⊙Trunc-fmap-⊙∘ {n = n} g f =
  Trunc-fmap-∘ (fst g) (fst f) , pres-pt-path
  where
  pres-pt-path : snd (⊙Trunc-fmap {n = n} g ⊙∘ ⊙Trunc-fmap f) == snd (⊙Trunc-fmap (g ⊙∘ f))
  pres-pt-path =
    ap (Trunc-fmap (fst g)) (ap [_] (snd f)) ∙ ap [_] (snd g)
      =⟨ ap (_∙ ap [_] (snd g))
            (∘-ap (Trunc-fmap (fst g)) [_] (snd f) ∙
             ap-∘ [_] (fst g) (snd f)) ⟩
    ap [_] (ap (fst g) (snd f)) ∙ ap [_] (snd g)
      =⟨ ∙-ap [_] (ap (fst g) (snd f)) (snd g) ⟩
    ap [_] (ap (fst g) (snd f) ∙ snd g) =∎

⊙Trunc-fmap-seq : ∀ {i} {n : ℕ₋₂} {X Y : Ptd i}
  → X ⊙–→ Y
  → ⊙Trunc n X ⊙–→ ⊙Trunc n Y
⊙Trunc-fmap-seq ⊙idf-seq = ⊙idf-seq
⊙Trunc-fmap-seq (f ◃⊙∘ fs) = ⊙Trunc-fmap f ◃⊙∘ ⊙Trunc-fmap-seq fs

⊙Trunc-fmap-seq-⊙∘ : ∀ {i} {n : ℕ₋₂} {X Y : Ptd i} (fs : X ⊙–→ Y)
  → ⊙Trunc-fmap {n = n} (⊙compose fs) ◃⊙idf =⊙∘ ⊙Trunc-fmap-seq fs
⊙Trunc-fmap-seq-⊙∘ ⊙idf-seq = =⊙∘-in (⊙λ= (⊙Trunc-fmap-⊙idf))
⊙Trunc-fmap-seq-⊙∘ (f ◃⊙∘ fs) = =⊙∘-in $
  ⊙Trunc-fmap (f ⊙∘ ⊙compose fs)
    =⟨ ! $ ⊙λ= $ ⊙Trunc-fmap-⊙∘ f (⊙compose fs) ⟩
  ⊙Trunc-fmap f ⊙∘ ⊙Trunc-fmap (⊙compose fs)
    =⟨ ap (⊙Trunc-fmap f ⊙∘_) (=⊙∘-out (⊙Trunc-fmap-seq-⊙∘ fs)) ⟩
  ⊙Trunc-fmap f ⊙∘ ⊙compose (⊙Trunc-fmap-seq fs) =∎

⊙Trunc-fmap-seq-=⊙∘ : ∀ {i} {n : ℕ₋₂} {X Y : Ptd i} {fs gs : X ⊙–→ Y}
  → fs =⊙∘ gs
  → ⊙Trunc-fmap-seq {n = n} fs =⊙∘ ⊙Trunc-fmap-seq {n = n} gs
⊙Trunc-fmap-seq-=⊙∘ {fs = fs} {gs = gs} p =
  ⊙Trunc-fmap-seq fs
    =⊙∘⟨ !⊙∘ (⊙Trunc-fmap-seq-⊙∘ fs) ⟩
  ⊙Trunc-fmap (⊙compose fs) ◃⊙idf
    =⊙∘₁⟨ ap ⊙Trunc-fmap (=⊙∘-out p) ⟩
  ⊙Trunc-fmap (⊙compose gs) ◃⊙idf
    =⊙∘⟨ ⊙Trunc-fmap-seq-⊙∘ gs ⟩
  ⊙Trunc-fmap-seq gs ∎⊙∘

Trunc-csmap : ∀ {i₀ i₁ j₀ j₁} {n : ℕ₋₂}
  {A₀ : Type i₀} {A₁ : Type i₁} {B₀ : Type j₀} {B₁ : Type j₁}
  {f : A₀ → B₀} {g : A₁ → B₁} {hA : A₀ → A₁} {hB : B₀ → B₁}
  → CommSquare f g hA hB
  → CommSquare (Trunc-fmap {n = n} f) (Trunc-fmap g) (Trunc-fmap hA) (Trunc-fmap hB)
Trunc-csmap (comm-sqr cs) = comm-sqr $ Trunc-elim (ap [_] ∘ cs)

{- Pushing concatenatation through _=ₜ_ -}
module _ {i} {n : ℕ₋₂} {A : Type i} where

  {- concatenation in _=ₜ_ -}
  _∙ₜ_ : {ta tb tc : Trunc (S n) A}
    → ta =ₜ tb → tb =ₜ tc → ta =ₜ tc
  _∙ₜ_ {ta = ta} {tb = tb} {tc = tc} =
    Trunc-elim {P = λ ta → C ta tb tc}
      {{λ ta → level ta tb tc}}
      (λ a → Trunc-elim {P = λ tb → C [ a ] tb tc}
         {{λ tb → level [ a ] tb tc}}
         (λ b → Trunc-elim {P = λ tc → C [ a ] [ b ] tc}
                  {{λ tc → level [ a ] [ b ] tc}}
                  (λ c → Trunc-fmap2 _∙_)
                  tc)
         tb)
      ta
    where
    C : (ta tb tc : Trunc (S n) A) → Type i
    C ta tb tc = ta =ₜ tb → tb =ₜ tc → ta =ₜ tc
    level : (ta tb tc : Trunc (S n) A) → has-level (S n) (C ta tb tc)
    level ta tb tc =
      raise-level _ $
      Π-level $ λ _ →
      Π-level $ λ _ →
      =ₜ-level ta tc

  {- inversion in _=ₜ_ -}
  !ₜ : {ta tb : Trunc (S n) A}
    → ta =ₜ tb
    → tb =ₜ ta
  !ₜ {ta = ta} {tb = tb} =
    Trunc-elim {P = λ ta → C ta tb}
      {{λ ta → level ta tb}}
      (λ a → Trunc-elim {P = λ tb → C [ a ] tb}
        {{λ tb → level [ a ] tb}}
        (λ b → Trunc-fmap !)
        tb)
      ta
    where
    C : (ta tb : Trunc (S n) A) → Type i
    C ta tb = ta =ₜ tb → tb =ₜ ta
    level : (ta tb : Trunc (S n) A) → has-level (S n) (C ta tb)
    level ta tb = raise-level _ $ Π-level $ λ _ → =ₜ-level tb ta

  ∙ₜ-assoc : {ta tb tc td : Trunc (S n) A}
    (tp : ta =ₜ tb) (tq : tb =ₜ tc) (tr : tc =ₜ td)
    → _∙ₜ_ {ta} (_∙ₜ_ {ta} tp tq) tr
      == _∙ₜ_ {ta} tp (_∙ₜ_ {tb} tq tr)
  ∙ₜ-assoc {ta = ta} {tb = tb} {tc = tc} {td = td} =
    Trunc-elim {P = λ ta → C ta tb tc td}
      {{λ ta → C-level ta tb tc td}}
      (λ a → Trunc-elim {P = λ tb → C [ a ] tb tc td}
        {{λ tb → C-level [ a ] tb tc td}}
        (λ b → Trunc-elim {P = λ tc → C [ a ] [ b ] tc td}
          {{λ tc → C-level [ a ] [ b ] tc td}}
          (λ c → Trunc-elim {P = λ td → C [ a ] [ b ] [ c ] td}
            {{λ td → C-level [ a ] [ b ] [ c ] td}}
            (λ d tp tq tr → Trunc-elim
              {P = λ tp → D [ a ] [ b ] [ c ] [ d ] tp tq tr}
              {{λ tp → D-level [ a ] [ b ] [ c ] [ d ] tp tq tr}}
              (λ p → Trunc-elim
                {P = λ tq → D [ a ] [ b ] [ c ] [ d ] [ p ] tq tr}
                {{λ tq → D-level [ a ] [ b ] [ c ] [ d ] [ p ] tq tr}}
                (λ q → Trunc-elim
                  {P = λ tr → D [ a ] [ b ] [ c ] [ d ] [ p ] [ q ] tr}
                  {{λ tr → D-level [ a ] [ b ] [ c ] [ d ] [ p ] [ q ] tr}}
                  (λ r → ap [_] (∙-assoc p q r))
                  tr)
                tq)
              tp)
            td)
          tc)
        tb)
      ta
    where
    D : (ta tb tc td : Trunc (S n) A)
      → ta =ₜ tb → tb =ₜ tc → tc =ₜ td
      → Type i
    D ta tb tc td tp tq tr =
         _∙ₜ_ {ta} (_∙ₜ_ {ta} tp tq) tr
      == _∙ₜ_ {ta} tp (_∙ₜ_ {tb} tq tr)
    C : (ta tb tc td : Trunc (S n) A) → Type i
    C ta tb tc td = ∀ tp tq tr → D ta tb tc td tp tq tr
    D-level : (ta tb tc td : Trunc (S n) A)
      (tp : ta =ₜ tb) (tq : tb =ₜ tc) (tr : tc =ₜ td)
      → has-level n (D ta tb tc td tp tq tr)
    D-level ta tb tc td tp tq tr = =-preserves-level (=ₜ-level ta td)
    C-level : (ta tb tc td : Trunc (S n) A) → has-level (S n) (C ta tb tc td)
    C-level ta tb tc td =
      raise-level _ $
      Π-level $ λ tp →
      Π-level $ λ tq →
      Π-level $ λ tr →
      D-level ta tb tc td tp tq tr

  abstract
    ∙ₜ-assoc-pentagon : {ta tb tc td te : Trunc (S n) A}
      (tp : ta =ₜ tb) (tq : tb =ₜ tc) (tr : tc =ₜ td) (ts : td =ₜ te)
      → ∙ₜ-assoc {ta} (_∙ₜ_ {ta} tp tq) tr ts ◃∙
        ∙ₜ-assoc {ta} tp tq (_∙ₜ_ {tc} tr ts) ◃∎
        =ₛ
        ap (λ u → _∙ₜ_ {ta} u ts) (∙ₜ-assoc {ta} tp tq tr) ◃∙
        ∙ₜ-assoc {ta} tp (_∙ₜ_ {tb} tq tr) ts ◃∙
        ap (_∙ₜ_ {ta} tp) (∙ₜ-assoc {tb} tq tr ts) ◃∎
    ∙ₜ-assoc-pentagon {ta} {tb} {tc} {td} {te} = core ta tb tc td te
      where
      P : (ta tb tc td te : Trunc (S n) A)
        (tp : ta =ₜ tb) (tq : tb =ₜ tc) (tr : tc =ₜ td) (ts : td =ₜ te)
        → Type i
      P ta tb tc td te tp tq tr ts =
        ∙ₜ-assoc {ta} (_∙ₜ_ {ta} tp tq) tr ts ◃∙
        ∙ₜ-assoc {ta} tp tq (_∙ₜ_ {tc} tr ts) ◃∎
        =ₛ
        ap (λ u → _∙ₜ_ {ta} u ts) (∙ₜ-assoc {ta} tp tq tr) ◃∙
        ∙ₜ-assoc {ta} tp (_∙ₜ_ {tb} tq tr) ts ◃∙
        ap (_∙ₜ_ {ta} tp) (∙ₜ-assoc {tb} tq tr ts) ◃∎
      P-level : ∀ ta tb tc td te →
        (tp : ta =ₜ tb) (tq : tb =ₜ tc) (tr : tc =ₜ td) (ts : td =ₜ te)
        → has-level n (P ta tb tc td te tp tq tr ts)
      P-level ta tb tc td te tp tq tr ts =
        =ₛ-level $ raise-level _ $ raise-level _ $ =ₜ-level ta te
      Q : (ta tb tc td te : Trunc (S n) A) → Type i
      Q ta tb tc td te = ∀ tp tq tr ts → P ta tb tc td te tp tq tr ts
      Q-level : ∀ ta tb tc td te → has-level (S n) (Q ta tb tc td te)
      Q-level ta tb tc td te =
        raise-level n $
        Π-level $ λ tp →
        Π-level $ λ tq →
        Π-level $ λ tr →
        Π-level $ λ ts →
        P-level ta tb tc td te tp tq tr ts
      core' : ∀ {a} {b} {c} {d} {e} p q r s → P [ a ] [ b ] [ c ] [ d ] [ e ] [ p ] [ q ] [ r ] [ s ]
      core' idp idp r s = =ₛ-in idp
      core : ∀ ta tb tc td te → Q ta tb tc td te
      core ta tb tc td te =
        Trunc-elim {P = λ ta → Q ta tb tc td te} {{λ ta → Q-level ta tb tc td te}} (λ a →
          Trunc-elim {P = λ tb → Q [ a ] tb tc td te} {{λ tb → Q-level [ a ] tb tc td te}} (λ b →
            Trunc-elim {P = λ tc → Q [ a ] [ b ] tc td te} {{λ tc → Q-level [ a ] [ b ] tc td te}} (λ c →
              Trunc-elim {P = λ td → Q [ a ] [ b ] [ c ] td te} {{λ td → Q-level [ a ] [ b ] [ c ] td te}} (λ d →
                Trunc-elim {P = λ te → Q [ a ] [ b ] [ c ] [ d ] te} {{λ te → Q-level [ a ] [ b ] [ c ] [ d ] te}} (λ e →
                  let R = P [ a ] [ b ] [ c ] [ d ] [ e ]
                      R-level = P-level [ a ] [ b ] [ c ] [ d ] [ e ]
                  in λ tp tq tr ts →
                  Trunc-elim {P = λ tp → R tp tq tr ts} {{λ tp → R-level tp tq tr ts }} (λ p →
                    Trunc-elim {P = λ tq → R [ p ] tq tr ts} {{λ tq → R-level [ p ] tq tr ts}} (λ q →
                      Trunc-elim {P = λ tr → R [ p ] [ q ] tr ts} {{λ tr → R-level [ p ] [ q ] tr ts}} (λ r →
                        Trunc-elim {P = λ ts → R [ p ] [ q ] [ r ] ts} {{λ ts → R-level [ p ] [ q ] [ r ] ts}} (λ s →
                          core' p q r s
                        ) ts
                      ) tr
                    ) tq
                  ) tp
                ) te
              ) td
            ) tc
          ) tb
        ) ta

  –>-=ₜ-equiv-pres-∙ : {ta tb tc : Trunc (S n) A}
    (p : ta == tb) (q : tb == tc)
    →  –> (=ₜ-equiv ta tc) (p ∙ q)
       ==
       _∙ₜ_ {ta = ta} (–> (=ₜ-equiv ta tb) p) (–> (=ₜ-equiv tb tc) q)
  –>-=ₜ-equiv-pres-∙ {ta = ta} idp idp =
    Trunc-elim
      {P = λ ta → –> (=ₜ-equiv ta ta) idp
              == _∙ₜ_ {ta = ta} (–> (=ₜ-equiv ta ta) idp)
                                (–> (=ₜ-equiv ta ta) idp)}
      {{λ ta → raise-level _ $ =-preserves-level $ =ₜ-level ta ta}}
      (λ a → idp)
      ta

  !ₜ-=ₜ-refl : ∀ (ta : Trunc (S n) A)
    → !ₜ {ta = ta} (=ₜ-refl ta) == =ₜ-refl ta
  !ₜ-=ₜ-refl =
    Trunc-elim
      {P = λ ta → !ₜ {ta = ta} (=ₜ-refl ta) == =ₜ-refl ta}
      {{λ ta → raise-level _ $ =-preserves-level $ =ₜ-level ta ta}}
      (λ a → idp)

  –>-=ₜ-equiv-pres-! : {ta tb : Trunc (S n) A}
    (p : ta == tb)
    → –> (=ₜ-equiv tb ta) (! p) == !ₜ {ta = ta} (–> (=ₜ-equiv ta tb) p)
  –>-=ₜ-equiv-pres-! {ta = ta} p@idp = ! (!ₜ-=ₜ-refl ta)

  abstract
    –>-=ₜ-equiv-pres-∙-coh : {ta tb tc td : Trunc (S n) A}
      (p : ta == tb) (q : tb == tc) (r : tc == td)
      → –>-=ₜ-equiv-pres-∙ (p ∙ q) r ◃∙
        ap (λ u → _∙ₜ_ {ta = ta} u (–> (=ₜ-equiv tc td) r)) (–>-=ₜ-equiv-pres-∙ p q) ◃∙
        ∙ₜ-assoc {ta = ta} (–> (=ₜ-equiv ta tb) p) (–> (=ₜ-equiv tb tc) q) (–> (=ₜ-equiv tc td) r) ◃∎
        =ₛ
        ap (–> (=ₜ-equiv ta td)) (∙-assoc p q r) ◃∙
        –>-=ₜ-equiv-pres-∙ p (q ∙ r) ◃∙
        ap (_∙ₜ_ {ta = ta} (–> (=ₜ-equiv ta tb) p)) (–>-=ₜ-equiv-pres-∙ q r) ◃∎
    –>-=ₜ-equiv-pres-∙-coh {ta = ta} idp idp idp =
      Trunc-elim
        {P = λ ta → P ta ta ta ta idp idp idp}
        {{λ ta → =ₛ-level $ raise-level (S (S n)) $ raise-level (S n) $ raise-level n $ =ₜ-level ta ta}}
        (λ a → =ₛ-in idp)
        ta
      where
      P : (ta tb tc td : Trunc (S n) A) (p : ta == tb) (q : tb == tc) (r : tc == td) → Type i
      P ta tb tc td p q r =
        –>-=ₜ-equiv-pres-∙ (p ∙ q) r ◃∙
        ap (λ u → _∙ₜ_ {ta = ta} u (–> (=ₜ-equiv tc td) r)) (–>-=ₜ-equiv-pres-∙ p q) ◃∙
        ∙ₜ-assoc {ta = ta} (–> (=ₜ-equiv ta tb) p) (–> (=ₜ-equiv tb tc) q) (–> (=ₜ-equiv tc td) r) ◃∎
        =ₛ
        ap (–> (=ₜ-equiv ta td)) (∙-assoc p q r) ◃∙
        –>-=ₜ-equiv-pres-∙ p (q ∙ r) ◃∙
        ap (_∙ₜ_ {ta = ta} (–> (=ₜ-equiv ta tb) p)) (–>-=ₜ-equiv-pres-∙ q r) ◃∎

{- naturality of =ₜ-equiv -}

module _ {i j} {n : ℕ₋₂} {A : Type i} {B : Type j} (f : A → B) where
  =ₜ-fmap : ∀ (a₀ a₁ : Trunc (S n) A)
    → a₀ =ₜ a₁ → Trunc-fmap f a₀ =ₜ Trunc-fmap f a₁
  =ₜ-fmap = Trunc-elim
    {P = λ a₀ → ∀ a₁ → a₀ =ₜ a₁ → Trunc-fmap f a₀ =ₜ Trunc-fmap f a₁}
    {{λ a₀ → Π-level λ a₁ → Π-level λ _ → raise-level _ $ =ₜ-level (Trunc-fmap f a₀) (Trunc-fmap f a₁)}}
    (λ a₀ → Trunc-elim
      {P = λ a₁ → [ a₀ ] =ₜ a₁ → [ f a₀ ] =ₜ Trunc-fmap f a₁}
      {{λ a₁ → Π-level λ _ → raise-level _ $ =ₜ-level [ f a₀ ] (Trunc-fmap f a₁)}}
      (λ a₁ → Trunc-fmap (ap f)))

module _ {i j} {n : ℕ₋₂} {A : Type i} {B : Type j} (f : A → B) where
  =ₜ-equiv-nat : ∀ (a₀ a₁ : Trunc (S n) A) (p : a₀ == a₁)
     →  –> (=ₜ-equiv (Trunc-fmap f a₀) (Trunc-fmap f a₁)) (ap (Trunc-fmap f) p)
     == =ₜ-fmap f a₀ a₁ (–> (=ₜ-equiv a₀ a₁) p)
  =ₜ-equiv-nat a₀ .a₀ idp =
    Trunc-elim
      {P = λ a
        →  –> (=ₜ-equiv (Trunc-fmap f a) (Trunc-fmap f a)) idp
        == =ₜ-fmap f a a (–> (=ₜ-equiv a a) idp)}
      {{λ a → raise-level _ $ =-preserves-level $ =ₜ-level (Trunc-fmap f a) (Trunc-fmap f a)}}
      (λ _ → idp)
      a₀

{- Truncation preserves equivalences - more convenient than univalence+ap
 - when we need to know the forward or backward function explicitly -}
module _ {i j} {n : ℕ₋₂} {A : Type i} {B : Type j} where

  Trunc-isemap : {f : A → B} → is-equiv f → is-equiv (Trunc-fmap {n = n} f)
  Trunc-isemap {f-orig} ie = is-eq f g f-g g-f where
    f = Trunc-fmap f-orig
    g = Trunc-fmap (is-equiv.g ie)

    f-g : ∀ tb → f (g tb) == tb
    f-g = Trunc-elim (ap [_] ∘ is-equiv.f-g ie)

    g-f : ∀ ta → g (f ta) == ta
    g-f = Trunc-elim (ap [_] ∘ is-equiv.g-f ie)

  Trunc-emap : A ≃ B → Trunc n A ≃ Trunc n B
  Trunc-emap (f , f-ie) = Trunc-fmap f , Trunc-isemap f-ie

Trunc-csemap : ∀ {i₀ i₁ j₀ j₁} {n : ℕ₋₂}
  {A₀ : Type i₀} {A₁ : Type i₁} {B₀ : Type j₀} {B₁ : Type j₁}
  {f : A₀ → B₀} {g : A₁ → B₁} {hA : A₀ → A₁} {hB : B₀ → B₁}
  → CommSquareEquiv f g hA hB
  → CommSquareEquiv (Trunc-fmap {n = n} f) (Trunc-fmap g) (Trunc-fmap hA) (Trunc-fmap hB)
Trunc-csemap (cs , hA-ise , hB-ise) = Trunc-csmap cs , Trunc-isemap hA-ise , Trunc-isemap hB-ise

transport-Trunc' : ∀ {i j} {A : Type i} {n : ℕ₋₂} (P : A → Type j)
  {x y : A} (p : x == y) (b : P x)
  → transport (Trunc n ∘ P) p [ b ] == [ transport P p b ]
transport-Trunc' _ idp _ = idp

Trunc-fuse : ∀ {i} (A : Type i) (m n : ℕ₋₂)
  → Trunc m (Trunc n A) ≃ Trunc (minT m n) A
Trunc-fuse A m n = equiv
  (Trunc-rec {{raise-level-≤T (minT≤l m n) Trunc-level}}
    (Trunc-rec {{raise-level-≤T (minT≤r m n) Trunc-level}}
      [_]))
  (Trunc-rec ([_] ∘ [_]))
  (Trunc-elim (λ _ → idp))
  (Trunc-elim (Trunc-elim
       {{λ _ → =-preserves-level (Trunc-preserves-level _ Trunc-level)}}
       (λ _ → idp)))
  where
      instance
        l : has-level (minT m n) (Trunc m (Trunc n A))
        l with (minT-out m n)
        l | inl p = transport (λ k → has-level k (Trunc m (Trunc n A)))
                              (! p) Trunc-level
        l | inr q = Trunc-preserves-level _
                      (transport (λ k → has-level k (Trunc n A))
                                 (! q) Trunc-level)

Trunc-fuse-≤ : ∀ {i} (A : Type i) {m n : ℕ₋₂} (m≤n : m ≤T n)
  → Trunc m (Trunc n A) ≃ Trunc m A
Trunc-fuse-≤ A m≤n = equiv
  (Trunc-rec (Trunc-rec {{raise-level-≤T m≤n Trunc-level}}
      [_]))
  (Trunc-rec ([_] ∘ [_]))
  (Trunc-elim (λ _ → idp))
  (Trunc-elim (Trunc-elim
       {{λ _ → =-preserves-level (Trunc-preserves-level _ Trunc-level)}}
       (λ _ → idp)))

{- Truncating a binary product is equivalent to truncating its components -}
Trunc-×-econv : ∀ {i} {j} (n : ℕ₋₂) (A : Type i) (B : Type j)
  → Trunc n (A × B) ≃ Trunc n A × Trunc n B
Trunc-×-econv n A B = equiv f g f-g g-f
  where
  f : Trunc n (A × B) → Trunc n A × Trunc n B
  f = Trunc-rec (λ {(a , b) → [ a ] , [ b ]})

  g : Trunc n A × Trunc n B → Trunc n (A × B)
  g (ta , tb) = Trunc-rec (λ a → Trunc-rec (λ b → [ a , b ]) tb) ta

  f-g : ∀ p → f (g p) == p
  f-g (ta , tb) = Trunc-elim
    {P = λ ta → f (g (ta , tb)) == (ta , tb)}
    (λ a → Trunc-elim
      {P = λ tb → f (g ([ a ] , tb)) == ([ a ] , tb)}
      (λ b → idp)
      tb)
    ta

  g-f : ∀ tab → g (f tab) == tab
  g-f = Trunc-elim
    {P = λ tab → g (f tab) == tab}
    (λ ab → idp)

Trunc-×-conv : ∀ {i} {j} (n : ℕ₋₂) (A : Type i) (B : Type j)
  → Trunc n (A × B) == Trunc n A × Trunc n B
Trunc-×-conv n A B = ua (Trunc-×-econv n A B)


module Tactic.Nat.Subtract.Auto where

open import Prelude
open import Builtin.Reflection
open import Tactic.Reflection.Quote
open import Tactic.Reflection.Meta

open import Tactic.Nat.Reflect
open import Tactic.Nat.NF
open import Tactic.Nat.Auto
open import Tactic.Nat.Simpl.Lemmas
open import Tactic.Nat.Subtract.Exp
open import Tactic.Nat.Subtract.Reflect
open import Tactic.Nat.Subtract.Lemmas
open import Tactic.Nat.Less.Lemmas

autosub-proof : ∀ eqn ρ → Maybe (⟦ eqn ⟧eqn ρ)
autosub-proof (e₁ :≡ e₂) ρ with normSub e₁ == normSub e₂
autosub-proof (e₁ :≡ e₂) ρ | no _   = nothing
autosub-proof (e₁ :≡ e₂) ρ | yes eq = just (liftNFSubEq e₁ e₂ ρ (cong (λ n → ⟦ n ⟧sn ρ) eq))
autosub-proof (e₁ :< e₂) ρ with cancel (normSub e₁) (normSub e₂) | simplifySubLess e₁ e₂ ρ
autosub-proof (e₁ :< e₂) ρ | [] , (suc n , []) ∷ nf | simp =
  let sk : SubNF
      sk = (suc n , []) ∷ nf
      k  = (    n , []) ∷ nf in
  just $ simp $ LessNat.diff (⟦ k ⟧sns ρ) $
    ns-sound sk (atomEnvS ρ) ⟨≡⟩
    auto ⟨≡⟩ʳ (λ z → suc (z + 0)) $≡ lem-eval-sns-nS k ρ
autosub-proof (e₁ :< e₂) ρ | _  , _ | simp = nothing

autosub-tactic : Type → TC Term
autosub-tactic t = do
  ensureNoMetas t
  just (eqn , Γ) ← termToSubEqn t
    where nothing → typeError $ strErr "Invalid goal:" ∷ termErr t ∷ []
  pure $
    getProof (quote cantProve) t $
      def (quote autosub-proof)
          ( vArg (` eqn)
          ∷ vArg (quotedEnv Γ)
          ∷ [] )


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

module PiEquiv where

open import Data.Empty
open import Data.Unit
open import Data.Sum using (_⊎_; inj₁; inj₂)
open import Data.Product using (_×_; Σ; _,_; proj₁; proj₂)
open import Relation.Binary.PropositionalEquality
  using (_≡_; refl; sym; trans; cong; cong₂)
open import Function using (_∘_; id)

open import Equiv
open import EquivEquiv
open _≋_
open import TypeEquiv as TE
open import TypeEquivEquiv
-- open import TypeEquivCat
open import PiU
open import PiLevel0
open import PiLevel1

open import Data.Sum.Properties2
open import Data.SumProd.Properties

------------------------------------------------------------------------------
-- A combinator t₁ ⟷ t₂ denotes an equivalence to types
-- note how we don't have to look at the types at all.

⟦_⟧ : U → Set
⟦ ZERO ⟧        = ⊥
⟦ ONE ⟧         = ⊤
⟦ PLUS t₁ t₂ ⟧  = ⟦ t₁ ⟧ ⊎ ⟦ t₂ ⟧
⟦ TIMES t₁ t₂ ⟧ = ⟦ t₁ ⟧ × ⟦ t₂ ⟧

eval : {t₁ t₂ : U} → (t₁ ⟷ t₂) → ⟦ t₁ ⟧ → ⟦ t₂ ⟧
eval unite₊l (inj₁ ())
eval unite₊l (inj₂ v) = v
eval uniti₊l v = inj₂ v
eval unite₊r (inj₁ x) = x
eval unite₊r (inj₂ ())
eval uniti₊r v = inj₁ v
eval swap₊ (inj₁ v) = inj₂ v
eval swap₊ (inj₂ v) = inj₁ v
eval assocl₊ (inj₁ v) = inj₁ (inj₁ v)
eval assocl₊ (inj₂ (inj₁ v)) = inj₁ (inj₂ v)
eval assocl₊ (inj₂ (inj₂ v)) = inj₂ v
eval assocr₊ (inj₁ (inj₁ v)) = inj₁ v
eval assocr₊ (inj₁ (inj₂ v)) = inj₂ (inj₁ v)
eval assocr₊ (inj₂ v) = inj₂ (inj₂ v)
eval unite⋆l (tt , v) = v
eval uniti⋆l v = (tt , v)
eval unite⋆r (v , tt) = v
eval uniti⋆r v = v , tt
eval swap⋆ (v₁ , v₂) = (v₂ , v₁)
eval assocl⋆ (v₁ , (v₂ , v₃)) = ((v₁ , v₂) , v₃)
eval assocr⋆ ((v₁ , v₂) , v₃) = (v₁ , (v₂ , v₃))
eval absorbr (x , _) = x
eval absorbl (_ , y) = y
eval factorzl ()
eval factorzr ()
eval dist (inj₁ v₁ , v₃) = inj₁ (v₁ , v₃)
eval dist (inj₂ v₂ , v₃) = inj₂ (v₂ , v₃)
eval factor (inj₁ (v₁ , v₃)) = (inj₁ v₁ , v₃)
eval factor (inj₂ (v₂ , v₃)) = (inj₂ v₂ , v₃)
eval distl (v , inj₁ x) = inj₁ (v , x)
eval distl (v , inj₂ y) = inj₂ (v , y)
eval factorl (inj₁ (x , y)) = x , inj₁ y
eval factorl (inj₂ (x , y)) = x , inj₂ y
eval id⟷ v = v
eval (c₁ ◎ c₂) v = eval c₂ (eval c₁ v)
eval (c₁ ⊕ c₂) (inj₁ v) = inj₁ (eval c₁ v)
eval (c₁ ⊕ c₂) (inj₂ v) = inj₂ (eval c₂ v)
eval (c₁ ⊗ c₂) (v₁ , v₂) = (eval c₁ v₁ , eval c₂ v₂)

-- useful to have the backwards eval too

evalB : {t₁ t₂ : U} → (t₁ ⟷ t₂) → ⟦ t₂ ⟧ → ⟦ t₁ ⟧
evalB unite₊l x = inj₂ x
evalB uniti₊l (inj₁ ())
evalB uniti₊l (inj₂ y) = y
evalB unite₊r v = inj₁ v
evalB uniti₊r (inj₁ x) = x
evalB uniti₊r (inj₂ ())
evalB swap₊ (inj₁ x) = inj₂ x
evalB swap₊ (inj₂ y) = inj₁ y
evalB assocl₊ (inj₁ (inj₁ x)) = inj₁ x
evalB assocl₊ (inj₁ (inj₂ y)) = inj₂ (inj₁ y)
evalB assocl₊ (inj₂ y) = inj₂ (inj₂ y)
evalB assocr₊ (inj₁ x) = inj₁ (inj₁ x)
evalB assocr₊ (inj₂ (inj₁ x)) = inj₁ (inj₂ x)
evalB assocr₊ (inj₂ (inj₂ y)) = inj₂ y
evalB unite⋆l x = tt , x
evalB uniti⋆l (tt , x) = x
evalB unite⋆r v = v , tt
evalB uniti⋆r (v , tt) = v
evalB swap⋆ (x , y) = y , x
evalB assocl⋆ ((x , y) , z) = x , y , z
evalB assocr⋆ (x , y , z) = (x , y) , z
evalB absorbr ()
evalB absorbl ()
evalB factorzr (_ , x) = x
evalB factorzl (x , _) = x
evalB dist (inj₁ (x , y)) = inj₁ x , y
evalB dist (inj₂ (x , y)) = inj₂ x , y
evalB factor (inj₁ x , z) = inj₁ (x , z)
evalB factor (inj₂ y , z) = inj₂ (y , z)
evalB distl (inj₁ (x , y)) = x , inj₁ y
evalB distl (inj₂ (x , y)) = x , inj₂ y
evalB factorl (v , inj₁ x) = inj₁ (v , x)
evalB factorl (v , inj₂ y) = inj₂ (v , y)
evalB id⟷ x = x
evalB (c₀ ◎ c₁) x = evalB c₀ (evalB c₁ x)
evalB (c₀ ⊕ c₁) (inj₁ x) = inj₁ (evalB c₀ x)
evalB (c₀ ⊕ c₁) (inj₂ y) = inj₂ (evalB c₁ y)
evalB (c₀ ⊗ c₁) (x , y) = evalB c₀ x , evalB c₁ y

-- should probably prove that these are inverses!
-- to a certain extent, no need, because here's
-- the right way to do it:

c2equiv : {t₁ t₂ : U} → (c : t₁ ⟷ t₂) → ⟦ t₁ ⟧ ≃ ⟦ t₂ ⟧
c2equiv unite₊l = TE.unite₊equiv
c2equiv uniti₊l = TE.uniti₊equiv
c2equiv unite₊r = TE.unite₊′equiv
c2equiv uniti₊r = TE.uniti₊′equiv
c2equiv swap₊ = TE.swap₊equiv
c2equiv assocl₊ = TE.assocl₊equiv
c2equiv assocr₊ = TE.assocr₊equiv
c2equiv unite⋆l = TE.unite⋆equiv
c2equiv uniti⋆l = TE.uniti⋆equiv
c2equiv unite⋆r = TE.unite⋆′equiv
c2equiv uniti⋆r = TE.uniti⋆′equiv
c2equiv swap⋆ = TE.swap⋆equiv
c2equiv assocl⋆ = TE.assocl⋆equiv
c2equiv assocr⋆ = TE.assocr⋆equiv
c2equiv absorbr = TE.distzequiv
c2equiv absorbl = TE.distzrequiv
c2equiv factorzr = TE.factorzrequiv
c2equiv factorzl = TE.factorzequiv
c2equiv dist = TE.distequiv
c2equiv factor = TE.factorequiv
c2equiv distl = TE.distlequiv
c2equiv factorl = TE.factorlequiv
c2equiv id⟷ = id≃
c2equiv (c ◎ c₁) = c2equiv c₁ ● c2equiv c
c2equiv (c ⊕ c₁) = (c2equiv c) ⊎≃ (c2equiv c₁)
c2equiv (c ⊗ c₁) = (c2equiv c) ×≃ (c2equiv c₁)

-- and these are 'coherent'
-- first with evaluation:

lemma0 : {t₁ t₂ : U} → (c : t₁ ⟷ t₂) → (v : ⟦ t₁ ⟧) →
  eval c v ≡ proj₁ (c2equiv c) v
lemma0 unite₊l (inj₁ ())
lemma0 unite₊l (inj₂ y) = refl
lemma0 uniti₊l v = refl
lemma0 unite₊r (inj₁ x) = refl
lemma0 unite₊r (inj₂ ())
lemma0 uniti₊r v = refl
lemma0 PiLevel0.swap₊ (inj₁ x) = refl
lemma0 PiLevel0.swap₊ (inj₂ y) = refl
lemma0 PiLevel0.assocl₊ (inj₁ x) = refl
lemma0 PiLevel0.assocl₊ (inj₂ (inj₁ x)) = refl
lemma0 PiLevel0.assocl₊ (inj₂ (inj₂ y)) = refl
lemma0 PiLevel0.assocr₊ (inj₁ (inj₁ x)) = refl
lemma0 PiLevel0.assocr₊ (inj₁ (inj₂ y)) = refl
lemma0 PiLevel0.assocr₊ (inj₂ y) = refl
lemma0 unite⋆l v = refl -- yay for η !
lemma0 uniti⋆l v = refl
lemma0 unite⋆r v = refl
lemma0 uniti⋆r v = refl
lemma0 PiLevel0.swap⋆ v = refl
lemma0 PiLevel0.assocl⋆ v = refl
lemma0 PiLevel0.assocr⋆ v = refl
lemma0 absorbr v = refl
lemma0 absorbl v = refl
lemma0 factorzr ()
lemma0 factorzl ()
lemma0 PiLevel0.dist (inj₁ x , proj₂) = refl
lemma0 PiLevel0.dist (inj₂ y , proj₂) = refl
lemma0 PiLevel0.factor (inj₁ x) = refl
lemma0 PiLevel0.factor (inj₂ y) = refl
lemma0 PiLevel0.distl (proj₁ , inj₁ x) = refl
lemma0 PiLevel0.distl (proj₁ , inj₂ y) = refl
lemma0 PiLevel0.factorl (inj₁ x) = refl
lemma0 PiLevel0.factorl (inj₂ y) = refl
lemma0 id⟷ v = refl
lemma0 (c ◎ c₁) v =
  trans (cong (eval c₁) (lemma0 c v)) (
  trans (lemma0 c₁ (proj₁ (c2equiv c) v))
        (sym (β₁ v)))
lemma0 (c ⊕ c₁) (inj₁ x) =
  trans (cong inj₁ (lemma0 c x)) (sym (β⊎₁ (inj₁ x)))
lemma0 (c ⊕ c₁) (inj₂ y) =
 trans (cong inj₂ (lemma0 c₁ y)) (sym (β⊎₁ (inj₂ y)))
lemma0 (c ⊗ c₁) (x , y) =
  trans (cong₂ _,_ (lemma0 c x) (lemma0 c₁ y)) (sym (β×₁ (x , y)))

lemma1 : {t₁ t₂ : U} → (c : t₁ ⟷ t₂) → (v : ⟦ t₂ ⟧) →
  evalB c v ≡ proj₁ (sym≃ (c2equiv c)) v
lemma1 PiLevel0.unite₊l v = refl
lemma1 PiLevel0.uniti₊l (inj₁ ())
lemma1 PiLevel0.uniti₊l (inj₂ y) = refl
lemma1 PiLevel0.unite₊r v = refl
lemma1 uniti₊r (inj₁ x) = refl
lemma1 uniti₊r (inj₂ ())
lemma1 PiLevel0.swap₊ (inj₁ x) = refl
lemma1 PiLevel0.swap₊ (inj₂ y) = refl
lemma1 PiLevel0.assocl₊ (inj₁ (inj₁ x)) = refl
lemma1 PiLevel0.assocl₊ (inj₁ (inj₂ y)) = refl
lemma1 PiLevel0.assocl₊ (inj₂ y) = refl
lemma1 PiLevel0.assocr₊ (inj₁ x) = refl
lemma1 PiLevel0.assocr₊ (inj₂ (inj₁ x)) = refl
lemma1 PiLevel0.assocr₊ (inj₂ (inj₂ y)) = refl
lemma1 PiLevel0.unite⋆l v = refl
lemma1 PiLevel0.uniti⋆l (tt , x) = refl
lemma1 PiLevel0.unite⋆r v = refl
lemma1 uniti⋆r (x , tt) = refl
lemma1 PiLevel0.swap⋆ (x , y) = refl
lemma1 PiLevel0.assocl⋆ ((x , y) , z) = refl
lemma1 PiLevel0.assocr⋆ (x , y , z) = refl
lemma1 absorbr ()
lemma1 absorbl ()
lemma1 PiLevel0.factorzr (_ , ())
lemma1 factorzl (() , v)
lemma1 PiLevel0.dist (inj₁ (proj₁ , proj₂)) = refl
lemma1 PiLevel0.dist (inj₂ (proj₁ , proj₂)) = refl
lemma1 PiLevel0.factor (inj₁ x , proj₂) = refl
lemma1 PiLevel0.factor (inj₂ y , proj₂) = refl
lemma1 PiLevel0.distl (inj₁ (proj₁ , proj₂)) = refl
lemma1 PiLevel0.distl (inj₂ (proj₁ , proj₂)) = refl
lemma1 PiLevel0.factorl (v , inj₁ x) = refl
lemma1 PiLevel0.factorl (v , inj₂ y) = refl
lemma1 id⟷ v = refl
lemma1 (c₀ ◎ c₁) v = trans (
  trans (cong (evalB c₀) (lemma1 c₁ v)) (lemma1 c₀ (gg (c2equiv c₁) v)) )
  (sym (β₂ v))
lemma1 (c₀ ⊕ c₁) (inj₁ x) = trans (cong inj₁ (lemma1 c₀ x)) (sym (β⊎₂ (inj₁ x))) -- cong inj₁ (lemma1 c₀ x)
lemma1 (c₀ ⊕ c₁) (inj₂ y) = trans (cong inj₂ (lemma1 c₁ y)) (sym (β⊎₂ (inj₂ y)))
lemma1 (c₀ ⊗ c₁) (x , y) = trans (cong₂ _,_ (lemma1 c₀ x) (lemma1 c₁ y)) (sym (β×₂ (x , y)))

-- and with reverse
!≡sym≃ : {t₁ t₂ : U} → (c : t₁ ⟷ t₂) →
  c2equiv (! c) ≋ sym≃ (c2equiv c)
!≡sym≃ unite₊l = id≋
!≡sym≃ uniti₊l = id≋
!≡sym≃ unite₊r = id≋
!≡sym≃ uniti₊r = id≋
!≡sym≃ PiLevel0.swap₊ = id≋
!≡sym≃ PiLevel0.assocl₊ = id≋
!≡sym≃ PiLevel0.assocr₊ = id≋
!≡sym≃ unite⋆l = id≋
!≡sym≃ uniti⋆l = id≋
!≡sym≃ unite⋆r = id≋
!≡sym≃ uniti⋆r = id≋
!≡sym≃ PiLevel0.swap⋆ = id≋
!≡sym≃ PiLevel0.assocl⋆ = id≋
!≡sym≃ PiLevel0.assocr⋆ = id≋
!≡sym≃ absorbr = id≋
!≡sym≃ absorbl = id≋
!≡sym≃ PiLevel0.factorzr = id≋
!≡sym≃ factorzl = id≋
!≡sym≃ PiLevel0.dist = id≋
!≡sym≃ PiLevel0.factor = id≋
!≡sym≃ PiLevel0.distl = id≋
!≡sym≃ PiLevel0.factorl = id≋
!≡sym≃ id⟷ = id≋
!≡sym≃ (c ◎ c₁) = trans≋ (EquivEquiv._◎_ (!≡sym≃ c) (!≡sym≃ c₁)) (sym≋ sym≃●)
!≡sym≃ (c ⊕ c₁) = trans≋ ((!≡sym≃ c) ⊎≋ (!≡sym≃ c₁)) (sym≋ sym≃-distrib⊎)
!≡sym≃ (c ⊗ c₁) = trans≋ ((!≡sym≃ c) ×≋ (!≡sym≃ c₁)) (sym≋ sym≃-distrib×)

left-inv : {t₁ t₂ : U} (c : t₁ ⟷ t₂) →
  (c2equiv (! c) ● c2equiv c) ≋ id≃
left-inv c =
  let p = c2equiv c in
  trans≋ (!≡sym≃ c EquivEquiv.◎ id≋) (linv≋ p)

right-inv : {t₁ t₂ : U} (c : t₁ ⟷ t₂) →
  (c2equiv c ● c2equiv (! c)) ≋ id≃
right-inv c =
  let p = c2equiv c in
  trans≋ (id≋ EquivEquiv.◎ (!≡sym≃ c)) (rinv≋ p)

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

cc2equiv : {t₁ t₂ : U} {c₁ c₂ : t₁ ⟷ t₂} (ce : c₁ ⇔ c₂) →
  c2equiv c₁ ≋ c2equiv c₂
cc2equiv assoc◎l = ●-assoc
cc2equiv assoc◎r = ●-assocl
cc2equiv assocl⊕l = assocl₊-nat
cc2equiv assocl⊕r = sym≋ assocl₊-nat
cc2equiv assocl⊗l = assocl⋆-nat
cc2equiv assocl⊗r = sym≋ assocl⋆-nat
cc2equiv assocr⊕r = assocr₊-nat
cc2equiv assocr⊗l = sym≋ assocr⋆-nat
cc2equiv assocr⊗r = assocr⋆-nat
cc2equiv assocr⊕l = sym≋ assocr₊-nat
cc2equiv dist⇔l = dist-nat
cc2equiv dist⇔r = sym≋ dist-nat
cc2equiv distl⇔l = distl-nat
cc2equiv distl⇔r = sym≋ distl-nat
cc2equiv factor⇔l = factor-nat
cc2equiv factor⇔r = sym≋ factor-nat
cc2equiv factorl⇔l = factorl-nat
cc2equiv factorl⇔r = sym≋ factorl-nat
cc2equiv idl◎l = rid≋
cc2equiv idl◎r = sym≋ rid≋
cc2equiv idr◎l = lid≋
cc2equiv idr◎r = sym≋ lid≋
cc2equiv (linv◎l {c = c}) = left-inv c
cc2equiv (linv◎r {c = c}) = sym≋ (left-inv c)
cc2equiv (rinv◎l {c = c}) = right-inv c
cc2equiv (rinv◎r {c = c}) = sym≋ (right-inv c)
cc2equiv (unite₊l⇔l {c₁ = c₁} {c₂}) = sym≋ (unite₊-nat {f = c2equiv c₂})
cc2equiv unite₊l⇔r = unite₊-nat
cc2equiv uniti₊l⇔l = sym≋ uniti₊-nat
cc2equiv uniti₊l⇔r = uniti₊-nat
cc2equiv unite₊r⇔l = sym≋ unite₊′-nat
cc2equiv unite₊r⇔r = unite₊′-nat
cc2equiv uniti₊r⇔l = sym≋ uniti₊′-nat
cc2equiv uniti₊r⇔r = uniti₊′-nat
cc2equiv swapl₊⇔ = sym≋ swap₊-nat
cc2equiv swapr₊⇔ = swap₊-nat
cc2equiv unitel⋆⇔l = sym≋ unite⋆-nat
cc2equiv uniter⋆⇔l = unite⋆-nat
cc2equiv unitil⋆⇔l = sym≋ uniti⋆-nat
cc2equiv unitir⋆⇔l = uniti⋆-nat
cc2equiv unitel⋆⇔r = sym≋ unite⋆′-nat
cc2equiv uniter⋆⇔r = unite⋆′-nat
cc2equiv unitil⋆⇔r = sym≋ uniti⋆′-nat
cc2equiv unitir⋆⇔r = uniti⋆′-nat
cc2equiv swapl⋆⇔ = sym≋ swap⋆-nat
cc2equiv swapr⋆⇔ = swap⋆-nat
cc2equiv id⇔ = id≋
cc2equiv (trans⇔ ce ce₁) = trans≋ (cc2equiv ce) (cc2equiv ce₁)
cc2equiv (ce ⊡ ce₁) = (cc2equiv ce₁) EquivEquiv.◎ (cc2equiv ce)
cc2equiv (resp⊕⇔ ce ce₁) = cc2equiv ce ⊎≋ cc2equiv ce₁
cc2equiv (resp⊗⇔ ce ce₁) = cc2equiv ce ×≋ cc2equiv ce₁
cc2equiv id⟷⊕id⟷⇔ = [id,id]≋id
cc2equiv split⊕-id⟷ = sym≋ [id,id]≋id
cc2equiv hom⊕◎⇔ = ⊎●≋●⊎
cc2equiv hom◎⊕⇔ = sym≋ ⊎●≋●⊎
cc2equiv id⟷⊗id⟷⇔ = id×id≋id
cc2equiv split⊗-id⟷ = sym≋ id×id≋id
cc2equiv hom⊗◎⇔ = ×●≋●×
cc2equiv hom◎⊗⇔ = sym≋ ×●≋●×
cc2equiv triangle⊕l = unite-assocr₊-coh
cc2equiv triangle⊕r = sym≋ unite-assocr₊-coh
cc2equiv triangle⊗l = unite-assocr⋆-coh
cc2equiv triangle⊗r = sym≋ unite-assocr⋆-coh
cc2equiv unite₊l-coh-l = unite₊l-coh
cc2equiv unite₊l-coh-r = sym≋ unite₊l-coh
cc2equiv unite⋆l-coh-l = unite⋆l-coh
cc2equiv unite⋆l-coh-r = sym≋ unite⋆l-coh
cc2equiv pentagon⊕l = assocr₊-coh
cc2equiv pentagon⊕r = sym≋ assocr₊-coh
cc2equiv pentagon⊗l = assocr⋆-coh
cc2equiv pentagon⊗r = sym≋ assocr⋆-coh
cc2equiv hexagonr⊕l = assocr₊-swap₊-coh
cc2equiv hexagonr⊕r = sym≋ assocr₊-swap₊-coh
cc2equiv hexagonl⊕l = assocl₊-swap₊-coh
cc2equiv hexagonl⊕r = sym≋ assocl₊-swap₊-coh
cc2equiv hexagonr⊗l = assocr⋆-swap⋆-coh
cc2equiv hexagonr⊗r = sym≋ assocr⋆-swap⋆-coh
cc2equiv hexagonl⊗l = assocl⋆-swap⋆-coh
cc2equiv hexagonl⊗r = sym≋ assocl⋆-swap⋆-coh
cc2equiv absorbl⇔l = distzr-nat
cc2equiv absorbl⇔r = sym≋ distzr-nat
cc2equiv absorbr⇔l = distz-nat
cc2equiv absorbr⇔r = sym≋ distz-nat
cc2equiv factorzl⇔l = factorz-nat
cc2equiv factorzl⇔r = sym≋ factorz-nat
cc2equiv factorzr⇔l = factorzr-nat
cc2equiv factorzr⇔r = sym≋ factorzr-nat
cc2equiv swap₊distl⇔l = A×[B⊎C]≃[A×C]⊎[A×B]
cc2equiv swap₊distl⇔r = sym≋ A×[B⊎C]≃[A×C]⊎[A×B]
cc2equiv dist-swap⋆⇔l = [A⊎B]×C≃[C×A]⊎[C×B]
cc2equiv dist-swap⋆⇔r = sym≋ [A⊎B]×C≃[C×A]⊎[C×B]
cc2equiv assocl₊-dist-dist⇔l = [A⊎B⊎C]×D≃[A×D⊎B×D]⊎C×D
cc2equiv assocl₊-dist-dist⇔r = sym≋ [A⊎B⊎C]×D≃[A×D⊎B×D]⊎C×D
cc2equiv assocl⋆-distl⇔l = A×B×[C⊎D]≃[A×B]×C⊎[A×B]×D
cc2equiv assocl⋆-distl⇔r = sym≋ A×B×[C⊎D]≃[A×B]×C⊎[A×B]×D
cc2equiv absorbr0-absorbl0⇔ = 0×0≃0
cc2equiv absorbl0-absorbr0⇔ = sym≋ 0×0≃0
cc2equiv absorbr⇔distl-absorb-unite = 0×[A⊎B]≃0
cc2equiv distl-absorb-unite⇔absorbr = sym≋ 0×[A⊎B]≃0
cc2equiv unite⋆r0-absorbr1⇔ = 0×1≃0
cc2equiv absorbr1-unite⋆r-⇔ = sym≋ 0×1≃0
cc2equiv absorbl≡swap⋆◎absorbr = A×0≃0
cc2equiv swap⋆◎absorbr≡absorbl = sym≋ A×0≃0
cc2equiv absorbr⇔[assocl⋆◎[absorbr⊗id⟷]]◎absorbr = 0×A×B≃0
cc2equiv [assocl⋆◎[absorbr⊗id⟷]]◎absorbr⇔absorbr = sym≋ 0×A×B≃0
cc2equiv [id⟷⊗absorbr]◎absorbl⇔assocl⋆◎[absorbl⊗id⟷]◎absorbr = A×0×B≃0
cc2equiv assocl⋆◎[absorbl⊗id⟷]◎absorbr⇔[id⟷⊗absorbr]◎absorbl = sym≋ A×0×B≃0
cc2equiv elim⊥-A[0⊕B]⇔l = A×[0+B]≃A×B
cc2equiv elim⊥-A[0⊕B]⇔r = sym≋ A×[0+B]≃A×B
cc2equiv elim⊥-1[A⊕B]⇔l = 1×[A⊎B]≃A⊎B
cc2equiv elim⊥-1[A⊕B]⇔r = sym≋ 1×[A⊎B]≃A⊎B
cc2equiv fully-distribute⇔l = [A⊎B]×[C⊎D]≃[[A×C⊎B×C]⊎A×D]⊎B×D
cc2equiv fully-distribute⇔r = sym≋ [A⊎B]×[C⊎D]≃[[A×C⊎B×C]⊎A×D]⊎B×D

--
-- These programs really are equivalent.  Here's two ways to see that:

-- 1. they give the same results as programs:

≋⇒≡ : {t₁ t₂ : U} {c₁ c₂ : t₁ ⟷ t₂} (ce : c₁ ⇔ c₂) →
  eval c₁ ∼ eval c₂
≋⇒≡ {c₁ = c₁} {c₂} ce =
  trans∼ (lemma0 c₁) (
  trans∼ (_≋_.f≡ (cc2equiv ce))
         (sym∼ (lemma0 c₂)))

-- 2. in fact, you can run one forward, then the other
--    backward, and that's the identity

ping-pong : {t₁ t₂ : U} {c₁ c₂ : t₁ ⟷ t₂} (ce : c₁ ⇔ c₂) →
  evalB c₂ ∘ eval c₁ ∼ id
ping-pong {c₁ = c₁} {c₂} ce =
  trans∼ (cong₂∘ (lemma1 c₂) (lemma0 c₁)) (
  trans∼ (cong∘r (proj₁ (c2equiv c₁)) (_≋_.f≡ (flip≋ (cc2equiv (2! ce))) )) (
  trans∼(sym∼ β₁)
         (_≋_.f≡ (linv≋ (c2equiv c₁)))))


open import Nat
open import Prelude
open import Hazelnut-core

module Hazelnut-sensible where
  -- theorem 1: action sensibility
  synthmovelem : {Γ : ·ctx} {e e' : ê} {t : τ̇} {δ : direction} →
                 (e + move δ +>e e') →
                 (Γ ⊢ e ◆e => t) →
                 (Γ ⊢ e' ◆e => t)
  synthmovelem EMAscFirstChild d2 = d2
  synthmovelem EMAscParent1 d2 = d2
  synthmovelem EMAscParent2 d2 = d2
  synthmovelem EMAscNextSib d2 = d2
  synthmovelem EMAscPrevSib d2 = d2
  synthmovelem EMLamFirstChild d2 = d2
  synthmovelem EMLamParent d2 = d2
  synthmovelem EMPlusFirstChild d2 = d2
  synthmovelem EMPlusParent1 d2 = d2
  synthmovelem EMPlusParent2 d2 = d2
  synthmovelem EMPlusNextSib d2 = d2
  synthmovelem EMPlusPrevSib d2 = d2
  synthmovelem EMApFirstChild d2 = d2
  synthmovelem EMApParent1 d2 = d2
  synthmovelem EMApParent2 d2 = d2
  synthmovelem EMApNextSib d2 = d2
  synthmovelem EMApPrevSib d2 = d2
  synthmovelem EMFHoleFirstChild d2 = d2
  synthmovelem EMFHoleParent d2 = d2

  -- movements preserve analytic types up to erasure. this lemma seems
  -- silly because all it seems to do in each case is return the second
  -- derivation D. the pattern matching here, though, constrains what that
  -- derivation may be in each case, and is therefore actually
  -- non-trivial. it's just that most of the work is happening in the
  -- implicit arguments.
  anamovelem : {Γ : ·ctx} {δ : direction} {e e' : ê} {t : τ̇}
            (p : e + move δ +>e e') →
            (Γ ⊢ e ◆e <= t) →
            (Γ ⊢ e' ◆e <= t)
  anamovelem EMAscFirstChild d2 = d2
  anamovelem EMAscParent1 d2 = d2
  anamovelem EMAscParent2 d2 = d2
  anamovelem EMAscNextSib d2 = d2
  anamovelem EMAscPrevSib d2 = d2
  anamovelem EMLamFirstChild d2 = d2
  anamovelem EMLamParent d2 = d2
  anamovelem EMPlusFirstChild d2 = d2
  anamovelem EMPlusParent1 d2 = d2
  anamovelem EMPlusParent2 d2 = d2
  anamovelem EMPlusNextSib d2 = d2
  anamovelem EMPlusPrevSib d2 = d2
  anamovelem EMApFirstChild d2 = d2
  anamovelem EMApParent1 d2 = d2
  anamovelem EMApParent2 d2 = d2
  anamovelem EMApNextSib d2 = d2
  anamovelem EMApPrevSib d2 = d2
  anamovelem EMFHoleFirstChild d2 = d2
  anamovelem EMFHoleParent d2 = d2

  mutual
    -- if an action transforms an zexp in a synthetic posistion to another
    -- zexp, they have the same type up erasure of focus.
    actsense1 : {Γ : ·ctx} {e e' : ê} {t t' : τ̇} {α : action} →
                (Γ ⊢ e => t ~ α ~> e' => t') →
                (Γ ⊢ (e  ◆e) => t) →
                 Γ ⊢ (e' ◆e) => t'
    actsense1 (SAMove x) D2 = synthmovelem x D2
    actsense1 SADel D2 = SEHole
    actsense1 SAConAsc D2 = SAsc (ASubsume D2 TCRefl)
    actsense1 (SAConVar p) D2 = SVar p
    actsense1 (SAConLam p) D2 = SAsc (ALam p (ASubsume SEHole TCRefl))
    actsense1 SAConAp1 D2 = SAp D2 (ASubsume SEHole TCHole1)
    actsense1 SAConAp2 D2 = SApHole D2 (ASubsume SEHole TCRefl)
    actsense1 (SAConAp3 x) D2 = SApHole (SFHole D2) (ASubsume SEHole TCRefl)
    actsense1 SAConArg D2 = SApHole SEHole (ASubsume D2 TCHole2)
    actsense1 SAConNumlit D2 = SNum
    actsense1 (SAConPlus1 TCRefl) D2 = SPlus (ASubsume D2 TCRefl) (ASubsume SEHole TCHole1)
    actsense1 (SAConPlus1 TCHole2) D2 = SPlus (ASubsume D2 TCHole1) (ASubsume SEHole TCHole1)
    actsense1 (SAConPlus2 x) D2 = SPlus (ASubsume (SFHole D2) TCHole1) (ASubsume SEHole TCHole1)
    actsense1 (SAFinish x) D2 = x
    actsense1 (SAZipAsc1 x) (SAsc D2) = SAsc (actsense2 x D2)
    actsense1 (SAZipAsc2 x x₁) _ = SAsc x₁
    actsense1 (SAZipAp1 x D1 x₁) D2 = SAp (actsense1 D1 x) x₁
    actsense1 (SAZipAp2 x D1 x₁) D2 = SApHole (actsense1 D1 x) x₁
    actsense1 (SAZipAp3 x x₁) (SAp D2 x₃)     with synthunicity x D2
    ... | refl = SAp x (actsense2 x₁ x₃)
    actsense1 (SAZipAp3 x x₁) (SApHole D2 x₂) with synthunicity x D2
    ... | ()
    actsense1 (SAZipAp4 x x₁) (SAp D2 x₂)     with synthunicity x D2
    ... | ()
    actsense1 (SAZipAp4 x x₁) (SApHole D2 x₂)  = SApHole x (actsense2 x₁ x₂)
    actsense1 (SAZipPlus1 x) (SPlus x₁ x₂) = SPlus (actsense2 x x₁) x₂
    actsense1 (SAZipPlus2 x) (SPlus x₁ x₂) = SPlus x₁ (actsense2 x x₂)
    actsense1 (SAZipHole1 x D1 x₁) D2 = SFHole (actsense1 D1 x)
    actsense1 (SAZipHole2 x D1) D2 = SEHole

    -- if an action transforms an zexp in an analytic posistion to another
    -- zexp, they have the same type up erasure of focus.
    actsense2  : {Γ : ·ctx} {e e' : ê} {t : τ̇} {α : action} →
                  (Γ ⊢ e ~ α ~> e' ⇐ t) →
                  (Γ ⊢ (e ◆e) <= t) →
                  (Γ ⊢ (e' ◆e) <= t)
    actsense2 (AASubsume x act p) D2 = ASubsume (actsense1 act x) p
    actsense2 (AAMove x) D2 = anamovelem x D2
    actsense2 AADel _ = ASubsume SEHole TCHole1
    actsense2 AAConAsc D2 = ASubsume (SAsc D2) TCRefl
    actsense2 (AAConVar x₁ p) D2 = ASubsume (SFHole (SVar p)) TCHole1
    actsense2 (AAConLam1 p) (ASubsume SEHole TCHole1) = ALam p (ASubsume SEHole TCHole1)
    actsense2 (AAConLam2 p n~) (ASubsume SEHole TCRefl) = abort (n~ TCHole2)
    actsense2 (AAConLam2 p x₁) (ASubsume SEHole TCHole1) = ASubsume (SFHole (SAsc (ALam p (ASubsume SEHole TCRefl)))) TCHole1
    actsense2 (AAConLam2 p n~) (ASubsume SEHole TCHole2) = abort (n~ TCHole2)
    actsense2 (AAConNumlit _) _ = ASubsume (SFHole SNum) TCHole1
    actsense2 (AAFinish x) _ = x
    actsense2 (AAZipLam _ _ ) (ASubsume () _)

    actsense2 (AAZipLam x₁ (AASubsume {p = p} x₂ x₃ x₄)) (ALam x₅ D2) = ALam x₅ (actsense2 (AASubsume {p = p} x₂ x₃ x₄) D2)
    actsense2 (AAZipLam x₁ (AAMove x₂)) (ALam x₃ D2) = ALam x₃ (anamovelem x₂ D2)
    actsense2 (AAZipLam x₁ AADel) (ALam x₂ D2) = ALam x₂ (ASubsume SEHole TCHole1)
    actsense2 (AAZipLam x₁ AAConAsc) (ALam x₂ (ASubsume x₃ x₄)) = ALam x₂ (ASubsume (SAsc (ASubsume x₃ x₄)) TCRefl)
    actsense2 (AAZipLam x₁ AAConAsc) (ALam x₂ (ALam x₃ D2)) = ALam x₂ (ASubsume (SAsc (ALam x₃ D2)) TCRefl)
    actsense2 (AAZipLam x₁ (AAConVar x₃ p)) (ALam x₄ D2) = ALam x₄ (ASubsume (SFHole (SVar p)) TCHole1)
    actsense2 (AAZipLam x₁ (AAConLam1 x₃)) (ALam x₄ D2) = ALam x₄ (ALam x₃ (ASubsume SEHole TCHole1))
    actsense2 (AAZipLam x₁ (AAConLam2 x₃ x₄)) (ALam x₅ D2) = ALam x₅ (ASubsume (SFHole (SAsc (ALam x₃ (ASubsume SEHole TCRefl))))
                                                                        TCHole1)
    actsense2 (AAZipLam x₁ (AAConNumlit x₂)) (ALam x₃ D2) = ALam x₃ (ASubsume (SFHole SNum) TCHole1)
    actsense2 (AAZipLam x₁ (AAFinish x₂)) (ALam x₃ D2) = ALam x₃ x₂
    actsense2 (AAZipLam x₁ (AAZipLam x₃ D1)) (ALam x₄ (ASubsume () x₆))
    actsense2 (AAZipLam x₁ (AAZipLam x₃ D1)) (ALam x₄ (ALam x₅ D2)) = ALam x₄ (ALam x₃ (actsense2 D1 D2))


module Monads.ExceptT where

open import Class.Monad
open import Class.Monad.Except
open import Class.Monad.State
open import Class.MonadTrans
open import Data.Sum
open import Function
open import Level

private
  variable
    a : Level

ExceptT : (M : Set a -> Set a) -> Set a -> Set a -> Set a
ExceptT M E A = M (E ⊎ A)

ExceptT-MonadTrans : {E : Set a} -> MonadTrans (λ (M : Set a -> Set a) -> ExceptT M E)
ExceptT-MonadTrans = record { embed = λ x -> x >>= (return ∘ inj₂) }

module _ {M : Set a -> Set a} {{_ : Monad M}} {E : Set a} where

  ExceptT-Monad : Monad (ExceptT M E)
  ExceptT-Monad = record { _>>=_ = helper ; return = λ x → (return $ inj₂ x) }
    where
      helper : ∀ {A B} -> ExceptT M E A -> (A -> ExceptT M E B) -> ExceptT M E B
      helper x f = x >>= λ { (inj₁ y) -> return $ inj₁ y ; (inj₂ y) -> f y }

  private
    throwError' : ∀ {A : Set a} -> E -> ExceptT M E A
    throwError' = return ∘ inj₁

    catchError' : ∀ {A} -> ExceptT M E A -> (E -> ExceptT M E A) -> ExceptT M E A
    catchError' x f = x >>= λ { (inj₁ x) → f x ; (inj₂ y) → return {{ExceptT-Monad}} y }

  ExceptT-MonadExcept : MonadExcept (ExceptT M E) {{ExceptT-Monad}} E
  ExceptT-MonadExcept = record { throwError = throwError' ; catchError = catchError' }

  ExceptT-MonadState : ∀ {S} {{_ : MonadState M S}} -> MonadState (ExceptT M E) {{ExceptT-Monad}} S
  ExceptT-MonadState = record
    { get = embed {{ExceptT-MonadTrans}} get
    ; put = embed {{ExceptT-MonadTrans}} ∘ put }


open import Nat
open import Prelude
open import contexts
open import dynamics-core

open import canonical-value-forms

module canonical-boxed-forms where
  canonical-boxed-forms-num : ∀{Δ d} →
                            Δ , ∅ ⊢ d :: num →
                            d boxedval →
                            Σ[ n ∈ Nat ] (d == N n)
  canonical-boxed-forms-num (TAVar _) (BVVal ())
  canonical-boxed-forms-num wt (BVVal v) = canonical-value-forms-num wt v

  -- this type gives somewhat nicer syntax for the output of the canonical
  -- forms lemma for boxed values at arrow type
  data cbf-arr : (Δ : hctx) (d : ihexp) (τ1 τ2 : htyp) → Set where
    CBFALam : ∀{Δ d τ1 τ2} →
      Σ[ x ∈ Nat ] Σ[ d' ∈ ihexp ]
        ((d == (·λ x ·[ τ1 ] d')) ×
         (Δ , ■ (x , τ1) ⊢ d' :: τ2)
        )
        → cbf-arr Δ d τ1 τ2
    CBFACastArr : ∀{Δ d τ1 τ2} →
      Σ[ d' ∈ ihexp ] Σ[ τ1' ∈ htyp ] Σ[ τ2' ∈ htyp ]
        ((d == (d' ⟨ τ1' ==> τ2' ⇒ τ1 ==> τ2 ⟩)) ×
         (τ1' ==> τ2' ≠ τ1 ==> τ2) ×
         (Δ , ∅ ⊢ d' :: τ1' ==> τ2') ×
         (d' boxedval)
        )
        → cbf-arr Δ d τ1 τ2

  canonical-boxed-forms-arr : ∀{Δ d τ1 τ2 } →
                              Δ , ∅ ⊢ d :: (τ1 ==> τ2)  →
                              d boxedval →
                              cbf-arr Δ d τ1 τ2
  canonical-boxed-forms-arr (TAVar x₁) (BVVal ())
  canonical-boxed-forms-arr (TALam f wt) (BVVal v) = CBFALam (canonical-value-forms-arr (TALam f wt) v)
  canonical-boxed-forms-arr (TAAp wt wt₁) (BVVal ())
  canonical-boxed-forms-arr (TAEHole x x₁) (BVVal ())
  canonical-boxed-forms-arr (TANEHole x wt x₁) (BVVal ())
  canonical-boxed-forms-arr (TACast wt x) (BVVal ())
  canonical-boxed-forms-arr (TACast wt x) (BVArrCast x₁ bv) = CBFACastArr (_ , _ , _ , refl , x₁ , wt , bv)
  canonical-boxed-forms-arr (TAFailedCast x x₁ x₂ x₃) (BVVal ())

  -- this type gives somewhat nicer syntax for the output of the canonical
  -- forms lemma for boxed values at sum type
  data cbf-sum : (Δ : hctx) (d : ihexp) (τ1 τ2 : htyp) → Set where
    CBFSInl : ∀{Δ d τ1 τ2} →
      Σ[ d' ∈ ihexp ]
        ((d == (inl τ2 d')) ×
         (Δ , ∅ ⊢ d' :: τ1) ×
         (d boxedval)
        )
        → cbf-sum Δ d τ1 τ2
    CBFSInr : ∀{Δ d τ1 τ2} →
      Σ[ d' ∈ ihexp ]
        ((d == (inr τ1 d')) ×
         (Δ , ∅ ⊢ d' :: τ2) ×
         (d boxedval)
        )
        → cbf-sum Δ d τ1 τ2
    CBFSCastSum : ∀{Δ d τ1 τ2} →
      Σ[ d' ∈ ihexp ] Σ[ τ1' ∈ htyp ] Σ[ τ2' ∈ htyp ]
        ((d == (d' ⟨ τ1' ⊕ τ2' ⇒ τ1 ⊕ τ2 ⟩)) ×
         (τ1' ⊕ τ2' ≠ τ1 ⊕ τ2) ×
         (Δ , ∅ ⊢ d' :: τ1' ⊕ τ2') ×
         (d' boxedval)
        )
        → cbf-sum Δ d τ1 τ2

  canonical-boxed-forms-sum : ∀{Δ d τ1 τ2 } →
                              Δ , ∅ ⊢ d :: (τ1 ⊕ τ2)  →
                              d boxedval →
                              cbf-sum Δ d τ1 τ2
  canonical-boxed-forms-sum (TAInl wt) x = CBFSInl (_ , refl , wt , x)
  canonical-boxed-forms-sum (TAInr wt) x = CBFSInr (_ , refl , wt , x)
  canonical-boxed-forms-sum (TACast wt x₁) (BVSumCast x bv) = CBFSCastSum (_ , _ , _ , refl , x , wt , bv)
  canonical-boxed-forms-sum (TAVar x₁) (BVVal ())
  canonical-boxed-forms-sum (TAAp wt wt₁) (BVVal ())
  canonical-boxed-forms-sum (TACase wt _ wt₁ _ wt₂) (BVVal ())
  canonical-boxed-forms-sum (TAFst wt) (BVVal ())
  canonical-boxed-forms-sum (TASnd wt) (BVVal ())
  canonical-boxed-forms-sum (TAEHole x₁ x₂) (BVVal ())
  canonical-boxed-forms-sum (TANEHole x₁ wt x₂) (BVVal ())
  canonical-boxed-forms-sum (TACast wt x₁) (BVVal ())
  canonical-boxed-forms-sum (TAFailedCast wt x₁ x₂ x₃) (BVVal ())

  -- this type gives somewhat nicer syntax for the output of the canonical
  -- forms lemma for boxed values at product type
  data cbf-prod : (Δ : hctx) (d : ihexp) (τ1 τ2 : htyp) → Set where
    CBFPPair : ∀{Δ d τ1 τ2} →
      Σ[ d1 ∈ ihexp ] Σ[ d2 ∈ ihexp ]
        ((d == ⟨ d1 , d2 ⟩) ×
         (Δ , ∅ ⊢ d1 :: τ1) ×
         (Δ , ∅ ⊢ d2 :: τ2) ×
         (d1 boxedval) ×
         (d2 boxedval)
        )
        → cbf-prod Δ d τ1 τ2
    CBFPCastProd : ∀{Δ d τ1 τ2} →
      Σ[ d' ∈ ihexp ] Σ[ τ1' ∈ htyp ] Σ[ τ2' ∈ htyp ]
        ((d == (d' ⟨ τ1' ⊠ τ2' ⇒ τ1 ⊠ τ2 ⟩)) ×
         (τ1' ⊠ τ2' ≠ τ1 ⊠ τ2) ×
         (Δ , ∅ ⊢ d' :: τ1' ⊠ τ2') ×
         (d' boxedval)
        )
        → cbf-prod Δ d τ1 τ2
  canonical-boxed-forms-prod : ∀{Δ d τ1 τ2 } →
                               Δ , ∅ ⊢ d :: (τ1 ⊠ τ2)  →
                               d boxedval →
                               cbf-prod Δ d τ1 τ2
  canonical-boxed-forms-prod (TAPair wt wt₁) (BVVal (VPair x x₁)) = CBFPPair (_ , _ , refl , wt , wt₁ , BVVal x , BVVal x₁)
  canonical-boxed-forms-prod (TAPair wt wt₁) (BVPair bv bv₁) = CBFPPair (_ , _ , refl , wt , wt₁ , bv , bv₁)
  canonical-boxed-forms-prod (TACast wt x) (BVProdCast x₁ bv) = CBFPCastProd (_ , _ , _ , refl , x₁ , wt , bv)

  canonical-boxed-forms-hole : ∀{Δ d} →
                               Δ , ∅ ⊢ d :: ⦇-⦈ →
                               d boxedval →
                               Σ[ d' ∈ ihexp ] Σ[ τ' ∈ htyp ]
                                 ((d == d' ⟨ τ' ⇒ ⦇-⦈ ⟩) ×
                                  (τ' ground) ×
                                  (Δ , ∅ ⊢ d' :: τ'))
  canonical-boxed-forms-hole (TAVar x₁) (BVVal ())
  canonical-boxed-forms-hole (TAAp wt wt₁) (BVVal ())
  canonical-boxed-forms-hole (TAEHole x x₁) (BVVal ())
  canonical-boxed-forms-hole (TANEHole x wt x₁) (BVVal ())
  canonical-boxed-forms-hole (TACast wt x) (BVVal ())
  canonical-boxed-forms-hole (TACast wt x) (BVHoleCast x₁ bv) = _ , _ , refl , x₁ , wt
  canonical-boxed-forms-hole (TAFailedCast x x₁ x₂ x₃) (BVVal ())

  canonical-boxed-forms-coverage : ∀{Δ d τ} →
                                   Δ , ∅ ⊢ d :: τ →
                                   d boxedval →
                                   τ ≠ num →
                                   ((τ1 : htyp) (τ2 : htyp) → τ ≠ (τ1 ==> τ2)) →
                                   ((τ1 : htyp) (τ2 : htyp) → τ ≠ (τ1 ⊕ τ2)) →
                                   ((τ1 : htyp) (τ2 : htyp) → τ ≠ (τ1 ⊠ τ2)) →
                                   τ ≠ ⦇-⦈ →
                                   ⊥
  canonical-boxed-forms-coverage TANum bv nn na ns np nh = nn refl
  canonical-boxed-forms-coverage (TAPlus wt wt₁) bv nn na ns np nh = nn refl
  canonical-boxed-forms-coverage (TALam x wt) bv nn na ns np nh = na _ _ refl
  canonical-boxed-forms-coverage (TAAp wt wt₁) (BVVal ()) nn na ns np nh
  canonical-boxed-forms-coverage (TAInl wt) bv nn na ns np nh = ns _ _ refl
  canonical-boxed-forms-coverage (TAInr wt) bv nn na ns np nh = ns _ _ refl
  canonical-boxed-forms-coverage (TACase wt _ wt₁ _ wt₂) (BVVal ()) nn na ns np nh
  canonical-boxed-forms-coverage (TAEHole x x₁) (BVVal ()) nn na ns np nh
  canonical-boxed-forms-coverage (TANEHole x wt x₁) (BVVal ()) nn na ns np nh
  canonical-boxed-forms-coverage (TACast wt x) (BVArrCast x₁ bv) nn na ns np nh = na _ _ refl
  canonical-boxed-forms-coverage (TACast wt x) (BVSumCast x₁ bv) nn na ns np nh = ns _ _ refl
  canonical-boxed-forms-coverage (TACast wt x) (BVProdCast x₁ bv) nn na ns np nh = np _ _ refl
  canonical-boxed-forms-coverage (TACast wt x) (BVHoleCast x₁ bv) nn na ns np nh = nh refl
  canonical-boxed-forms-coverage (TAFailedCast wt x x₁ x₂) (BVVal ()) nn na ns np nh
  canonical-boxed-forms-coverage (TAPair wt wt₁) bv nn na ns np nh = np _ _ refl
  canonical-boxed-forms-coverage (TAFst wt) (BVVal ()) nn na ns np nh
  canonical-boxed-forms-coverage (TASnd wt) (BVVal ()) nn na ns np nh


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

syntax PDiff | PD

type
  * : 0-ary

term
  zero  : * | 𝟘
  add   : *  *  ->  * | _⊕_ l20
  one   : * | 𝟙
  mult  : *  *  ->  * | _⊗_ l20
  neg   : *  ->  * | ⊖_ r50
  pd : *.*  *  ->  * | ∂_∣_

theory
  (𝟘U⊕ᴸ) a |> add (zero, a) = a
  (𝟘U⊕ᴿ) a |> add (a, zero) = a
  (⊕A) a b c |> add (add(a, b), c) = add (a, add(b, c))
  (⊕C) a b |> add(a, b) = add(b, a)
  (𝟙U⊗ᴸ) a |> mult (one, a) = a
  (𝟙U⊗ᴿ) a |> mult (a, one) = a
  (⊗A) a b c |> mult (mult(a, b), c) = mult (a, mult(b, c))
  (⊗D⊕ᴸ) a b c |> mult (a, add (b, c)) = add (mult(a, b), mult(a, c))
  (⊗D⊕ᴿ) a b c |> mult (add (a, b), c) = add (mult(a, c), mult(b, c))
  (𝟘X⊗ᴸ) a |> mult (zero, a) = zero
  (𝟘X⊗ᴿ) a |> mult (a, zero) = zero
  (⊖N⊕ᴸ) a |> add (neg (a), a) = zero
  (⊖N⊕ᴿ) a |> add (a, neg (a)) = zero
  (⊗C) a b |> mult(a, b) = mult(b, a)
  (∂⊕) a : * |> x : * |- d0 (add (x, a)) = one
  (∂⊗) a : * |> x : * |- d0 (mult(a, x)) = a
  (∂C) f : (*,*).* |> x : *  y : * |- d1 (d0 (f[x,y])) = d0 (d1 (f[x,y]))
  (∂Ch₂) f : (*,*).*  g h : *.* |> x : * |- d0 (f[g[x], h[x]]) = add (mult(pd(z. f[z, h[x]], g[x]), d0(g[x])), mult(pd(z. f[g[x], z], h[x]), d0(h[x])))
  (∂Ch₁) f g : *.* |> x : * |- d0 (f[g[x]]) = mult (pd (z. f[z], g[x]), d0(g[x]))
-}

module PDiff.Syntax where

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

open import SOAS.Metatheory.Syntax

open import PDiff.Signature

private
  variable
    Γ Δ Π : Ctx
    α : *T
    𝔛 : Familyₛ

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

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

    𝟘    : PD * Γ
    _⊕_  : PD * Γ → PD * Γ → PD * Γ
    𝟙    : PD * Γ
    _⊗_  : PD * Γ → PD * Γ → PD * Γ
    ⊖_   : PD * Γ → PD * Γ
    ∂_∣_ : PD * (* ∙ Γ) → PD * Γ → PD * Γ

  infixl 20 _⊕_
  infixl 30 _⊗_
  infixr 50 ⊖_

  open import SOAS.Metatheory.MetaAlgebra ⅀F 𝔛

  PDᵃ : MetaAlg PD
  PDᵃ = record
    { 𝑎𝑙𝑔 = λ where
      (zeroₒ ⋮ _)     → 𝟘
      (addₒ  ⋮ a , b) → _⊕_  a b
      (oneₒ  ⋮ _)     → 𝟙
      (multₒ ⋮ a , b) → _⊗_  a b
      (negₒ  ⋮ a)     → ⊖_   a
      (pdₒ   ⋮ a , b) → ∂_∣_ a b
    ; 𝑣𝑎𝑟 = var ; 𝑚𝑣𝑎𝑟 = λ 𝔪 mε → mvar 𝔪 (tabulate mε) }

  module PDᵃ = MetaAlg PDᵃ

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

    open MetaAlg 𝒜ᵃ

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

    𝕤𝕖𝕞  𝟘         = 𝑎𝑙𝑔 (zeroₒ ⋮ tt)
    𝕤𝕖𝕞 (_⊕_  a b) = 𝑎𝑙𝑔 (addₒ  ⋮ 𝕤𝕖𝕞 a , 𝕤𝕖𝕞 b)
    𝕤𝕖𝕞  𝟙         = 𝑎𝑙𝑔 (oneₒ  ⋮ tt)
    𝕤𝕖𝕞 (_⊗_  a b) = 𝑎𝑙𝑔 (multₒ ⋮ 𝕤𝕖𝕞 a , 𝕤𝕖𝕞 b)
    𝕤𝕖𝕞 (⊖_   a)   = 𝑎𝑙𝑔 (negₒ  ⋮ 𝕤𝕖𝕞 a)
    𝕤𝕖𝕞 (∂_∣_ a b) = 𝑎𝑙𝑔 (pdₒ   ⋮ 𝕤𝕖𝕞 a , 𝕤𝕖𝕞 b)

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

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

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

      open MetaAlg⇒ gᵃ⇒

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

      𝕤𝕖𝕞! 𝟘 = sym ⟨𝑎𝑙𝑔⟩
      𝕤𝕖𝕞! (_⊕_ a b) rewrite 𝕤𝕖𝕞! a | 𝕤𝕖𝕞! b = sym ⟨𝑎𝑙𝑔⟩
      𝕤𝕖𝕞! 𝟙 = sym ⟨𝑎𝑙𝑔⟩
      𝕤𝕖𝕞! (_⊗_ a b) rewrite 𝕤𝕖𝕞! a | 𝕤𝕖𝕞! b = sym ⟨𝑎𝑙𝑔⟩
      𝕤𝕖𝕞! (⊖_ a) rewrite 𝕤𝕖𝕞! a = sym ⟨𝑎𝑙𝑔⟩
      𝕤𝕖𝕞! (∂_∣_ a b) rewrite 𝕤𝕖𝕞! a | 𝕤𝕖𝕞! b = sym ⟨𝑎𝑙𝑔⟩

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

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

-- Derived operations
∂₀_ : {𝔛 : Familyₛ} → PD 𝔛 * (* ∙ Γ) → PD 𝔛 * (* ∙ Γ)
∂₀_ {𝔛 = 𝔛} e = ∂ Theory.𝕨𝕜 𝔛 e ∣ x₀
∂₁_ : {𝔛 : Familyₛ} → PD 𝔛 * (* ∙ * ∙ Γ) → PD 𝔛 * (* ∙ * ∙ Γ)
∂₁_ {𝔛 = 𝔛} e = ∂ Theory.𝕨𝕜 𝔛 e ∣ x₁
infix 10 ∂₀_ ∂₁_


{-# OPTIONS --safe --without-K #-}
module Generics.All where

open import Generics.Prelude
open import Generics.Telescope
open import Generics.Desc
open import Generics.Mu

private
  variable
    P   : Telescope ⊤
    p   : ⟦ P ⟧tel tt
    V I : ExTele P
    ℓ c : Level
    n   : ℕ

levelAllIndArg : ConDesc P V I → Level → Level
levelAllIndArg (var _) c = c
levelAllIndArg (π {ℓ} _ _ C) c = ℓ ⊔ levelAllIndArg C c
levelAllIndArg (A ⊗ B) c = levelAllIndArg A c ⊔ levelAllIndArg B c

AllIndArg
  : {X : ⟦ P , I ⟧xtel → Set ℓ}
    (Pr : ∀ {i} → X (p , i) → Set c)
    (C : ConDesc P V I)
  → ∀ {v} → ⟦ C ⟧IndArg X (p , v) → Set (levelAllIndArg C c)
AllIndArg Pr (var _) x = Pr x
AllIndArg Pr (π (n , ai) S C) x = (s : < relevance ai > S _) → AllIndArg Pr C (app< n , ai > x s)
AllIndArg Pr (A ⊗ B) (xa , xb) = AllIndArg Pr A xa × AllIndArg Pr B xb

AllIndArgω
  : {X : ⟦ P , I ⟧xtel → Set ℓ}
    (Pr : ∀ {i} → X (p , i) → Setω)
    (C : ConDesc P V I)
  → ∀ {v} → ⟦ C ⟧IndArg X (p , v) → Setω
AllIndArgω Pr (var _) x = Pr x
AllIndArgω Pr (π (n , ai) S C) x = (s : < relevance ai > S _) → AllIndArgω Pr C (app< n , ai > x s)
AllIndArgω Pr (A ⊗ B) (xa , xb) = AllIndArgω Pr A xa ×ω AllIndArgω Pr B xb

levelAllCon : ConDesc P V I → Level → Level
levelAllCon (var _) c = lzero
levelAllCon (π {ℓ} _ _ C) c = levelAllCon C c
levelAllCon (A ⊗ B) c = levelAllIndArg A c ⊔ levelAllCon B c

AllCon
  : {X : ⟦ P , I ⟧xtel → Set ℓ}
    (Pr : ∀ {i} → X (p , i) → Set c)
    (C : ConDesc P V I)
  → ∀ {v i} → ⟦ C ⟧Con X (p , v , i) → Set (levelAllCon C c)
AllCon Pr (var _) x = ⊤
AllCon Pr (π _ _ C) (_ , x) = AllCon Pr C x
AllCon Pr (A ⊗ B) (xa , xb) = AllIndArg Pr A xa × AllCon Pr B xb

AllConω
  : {X : ⟦ P , I ⟧xtel → Set ℓ}
    (Pr : ∀ {i} → X (p , i) → Setω)
    (C : ConDesc P V I)
  → ∀ {v i} → ⟦ C ⟧Con X (p , v , i) → Setω
AllConω Pr (var f) x = ⊤ω
AllConω Pr (π ia S C) (_ , x) = AllConω Pr C x
AllConω Pr (A ⊗ B) (xa , xb) = AllIndArgω Pr A xa ×ω AllConω Pr B xb

AllData : {X  : ⟦ P , I ⟧xtel → Set ℓ}
          (Pr : ∀ {i} → X (p , i) → Set c)
          (D : DataDesc P I n)
        → ∀ {i} ((k , x) : ⟦ D ⟧Data X (p , i))
        → Set (levelAllCon (lookupCon D k) c)
AllData Pr D (k , x) = AllCon Pr (lookupCon D k) x

AllDataω : {X  : ⟦ P , I ⟧xtel → Set ℓ}
           (Pr : ∀ {i} → X (p , i) → Setω)
           (D : DataDesc P I n)
         → ∀ {i} (x : ⟦ D ⟧Data X (p , i))
         → Setω
AllDataω Pr D (k , x) = AllConω Pr (lookupCon D k) x


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

open import HoTT
open import groups.SuspAdjointLoop
open import groups.ToOmega
open import cohomology.Theory

{- A spectrum (family (Eₙ | n : ℤ) such that ΩEₙ₊₁ = Eₙ)
 - gives rise to a cohomology theory C with Cⁿ(S⁰) = π₁(Eₙ₊₁). -}

module cohomology.SpectrumModel
  {i} (E : ℤ → Ptd i) (spectrum : (n : ℤ) → ⊙Ω (E (succ n)) ⊙≃ E n) where

module SpectrumModel where

  {- Definition of cohomology group C -}
  module _ (n : ℤ) (X : Ptd i) where
    C : Group i
    C = Trunc-⊙→Ω-group X (E (succ n))

    {- convenient abbreviations -}
    CEl = Group.El C
    ⊙CEl = Group.⊙El C
    Cid = Group.ident C

    {- before truncation -}
    uCEl = X ⊙→ ⊙Ω (E (succ n))

  {- Cⁿ(X) is an abelian group -}
  C-is-abelian : (n : ℤ) (X : Ptd i) → is-abelian (C n X)
  C-is-abelian n X =
    iso-preserves-abelian (Trunc-⊙→Ω-group-emap-codom X (spectrum (succ n))) $
      Trunc-group-abelian (⊙→Ω-group-structure _ _) $ λ {(f , fpt) (g , gpt) →
        ⊙λ=' (λ x → Ω^2-∙-comm (f x) (g x)) (pt-lemma fpt gpt)}
    where
    pt-lemma : ∀ {i} {A : Type i} {x : A} {p q : idp {a = x} == idp {a = x}}
      (α : p == idp) (β : q == idp)
      →  ⊙∘-pt (fst ⊙Ω-∙) (⊙fanout-pt α β) idp
      == ⊙∘-pt (fst ⊙Ω-∙) (⊙fanout-pt β α) idp
       [ _== idp ↓ Ω^2-∙-comm p q ]
    pt-lemma idp idp = idp

  {- C-fmap, the functorial action of C:
   - contravariant functor from pointed spaces to abelian groups -}
  module _ (n : ℤ) {X Y : Ptd i} where

    C-fmap : X ⊙→ Y → (C n Y →ᴳ C n X)
    C-fmap f = Trunc-⊙→Ω-group-fmap-dom f (E (succ n))

    CEl-fmap : X ⊙→ Y → CEl n Y → CEl n X
    CEl-fmap F = GroupHom.f (C-fmap F)

    ⊙CEl-fmap : X ⊙→ Y → ⊙CEl n Y ⊙→ ⊙CEl n X
    ⊙CEl-fmap F = GroupHom.⊙f (C-fmap F)

  {- C-fmap is a functor from pointed spaces to abelian groups -}
  module _ (n : ℤ) {X : Ptd i} where

    C-fmap-idf : ∀ x → CEl-fmap n {X} {X} (⊙idf X) x == x
    C-fmap-idf x = ap (λ h → GroupHom.f h x) (Trunc-⊙→Ω-group-fmap-dom-idf (E (succ n)))

    C-fmap-∘ : {Y Z : Ptd i} (g : Y ⊙→ Z) (f : X ⊙→ Y)
      → ∀ x → CEl-fmap n (g ⊙∘ f) x == CEl-fmap n f (CEl-fmap n g x)
    C-fmap-∘ g f x = ap (λ h → GroupHom.f h x) (Trunc-⊙→Ω-group-fmap-dom-∘ g f (E (succ n)))

  -- Eilenberg-Steenrod Axioms

  {- Susp Axiom -}
  private
    C-Susp' : {E₁ E₀ : Ptd i} (iso : ⊙Ω E₁ ⊙≃ E₀) (X : Ptd i)
      → Trunc-⊙→Ω-group (⊙Susp X) E₁ ≃ᴳ Trunc-⊙→Ω-group X E₀
    C-Susp' {E₁ = E₁} iso X = Trunc-⊙→Ω-group-emap-codom X iso
                          ∘eᴳ Trunc-⊙→Ω-iso-Trunc-⊙→Ω X E₁

    -- This can be further simplified
    C-Susp-fmap' : {E₁ E₀ : Ptd i} (iso : ⊙Ω E₁ ⊙≃ E₀) {X Y : Ptd i} (f : X ⊙→ Y)
      → CommSquareᴳ
          (Trunc-⊙→Ω-group-fmap-dom (⊙Susp-fmap f) E₁)
          (Trunc-⊙→Ω-group-fmap-dom f E₀)
          (fst (C-Susp' iso Y)) (fst (C-Susp' iso X))
    C-Susp-fmap' {E₁} {E₀} iso {X} {Y} f = comm-sqrᴳ λ x →
        GroupHom.f (Trunc-⊙→Ω-group-fmap-codom X (fst iso))
          (GroupIso.f (Trunc-⊙→Ω-iso-Trunc-⊙→Ω X E₁)
            (GroupHom.f (Trunc-⊙→Ω-group-fmap-dom (⊙Susp-fmap f) E₁) x))
          =⟨ Trunc-⊙→Ω-iso-Trunc-⊙→Ω-nat-dom f E₁
            |in-ctx (λ h → GroupHom.f h x)
            |in-ctx GroupHom.f (Trunc-⊙→Ω-group-fmap-codom X (fst iso)) ⟩
        GroupHom.f (Trunc-⊙→Ω-group-fmap-codom X (fst iso))
          (GroupHom.f (Trunc-⊙→Ω-group-fmap-dom f (⊙Ω E₁))
            (GroupIso.f (Trunc-⊙→Ω-iso-Trunc-⊙→Ω Y E₁) x))
          =⟨ ! $ Trunc-⊙→Ω-group-fmap-nat f (fst iso)
            |in-ctx GroupHom.f
            |in-ctx (λ h → h (GroupIso.f (Trunc-⊙→Ω-iso-Trunc-⊙→Ω Y E₁) x)) ⟩
        GroupHom.f (Trunc-⊙→Ω-group-fmap-dom f E₀)
          (GroupHom.f (Trunc-⊙→Ω-group-fmap-codom Y (fst iso))
            (GroupIso.f (Trunc-⊙→Ω-iso-Trunc-⊙→Ω Y E₁) x))
          =∎

  C-Susp : (n : ℤ) (X : Ptd i) → C (succ n) (⊙Susp X) ≃ᴳ C n X
  C-Susp n X = C-Susp' (spectrum (succ n)) X

  C-Susp-fmap : (n : ℤ) {X Y : Ptd i} (f : X ⊙→ Y)
    → CommSquareᴳ
        (C-fmap (succ n) (⊙Susp-fmap f)) (C-fmap n f)
        (fst (C-Susp n Y)) (fst (C-Susp n X))
  C-Susp-fmap n f = C-Susp-fmap' (spectrum (succ n)) f

  {- Non-truncated Exactness Axiom -}
  module _ (n : ℤ) {X Y : Ptd i} where

    {- precomposing [⊙cfcod' f] and then [f] gives [0] -}
    im-sub-ker-lemma : (f : X ⊙→ Y) (g : uCEl n (⊙Cofiber f))
      → (g ⊙∘ ⊙cfcod' f) ⊙∘ f == ⊙cst
    im-sub-ker-lemma (f , fpt) (g , gpt) = ⊙λ='
      (λ x → ap g (! (cfglue' f x)) ∙ gpt)
      (↓-idf=cst-in
        (ap (g ∘ cfcod) fpt
         ∙ ap g (ap cfcod (! fpt) ∙ ! (cfglue (pt X))) ∙ gpt
           =⟨ lemma cfcod g fpt (! (cfglue (pt X))) gpt ⟩
         ap g (! (cfglue (pt X))) ∙ gpt
           =⟨ ! (∙-unit-r _) ⟩
         (ap g (! (cfglue (pt X))) ∙ gpt) ∙ idp ∎))
      where
      lemma : ∀ {i j k} {A : Type i} {B : Type j} {C : Type k}
        {a₁ a₂ : A} {b : B} {c : C} (f : A → B) (g : B → C)
        (p : a₁ == a₂) (q : f a₁ == b) (r : g b == c)
        → ap (g ∘ f) p ∙ ap g (ap f (! p) ∙ q) ∙ r == ap g q ∙ r
      lemma f g idp idp idp = idp

    {- if g ⊙∘ f is constant then g factors as h ⊙∘ ⊙cfcod' f -}
    ker-sub-im-lemma : (f : X ⊙→ Y) (g : uCEl n Y)
      → g ⊙∘ f == ⊙cst
      → Σ (uCEl n (⊙Cofiber f)) (λ h → h ⊙∘ ⊙cfcod' f == g)
    ker-sub-im-lemma (f , fpt) (h , hpt) p =
      ((g , ! q ∙ hpt) ,
       pair= idp (! (∙-assoc q (! q) hpt) ∙ ap (_∙ hpt) (!-inv-r q)))
      where
      g : Cofiber f → Ω (E (succ n))
      g = CofiberRec.f idp h (! ∘ app= (ap fst p))

      q : h (pt Y) == g (cfbase' f)
      q = ap g (snd (⊙cfcod' (f , fpt)))

  {- Truncated Exactness Axiom -}
  module _ (n : ℤ) {X Y : Ptd i} where

    abstract
      C-im-sub-ker : (f : X ⊙→ Y)
        → im-propᴳ (C-fmap n (⊙cfcod' f)) ⊆ᴳ ker-propᴳ (C-fmap n f)
      C-im-sub-ker f =
        im-sub-ker-in (C-fmap n (⊙cfcod' f)) (C-fmap n f) $
          Trunc-elim
            (ap [_] ∘ im-sub-ker-lemma n f)

    abstract
      C-ker-sub-im : (f : X ⊙→ Y)
        → ker-propᴳ (C-fmap n f) ⊆ᴳ im-propᴳ (C-fmap n (⊙cfcod' f))
      C-ker-sub-im f =
        Trunc-elim
          {{λ _ → Π-level (λ _ → raise-level _ Trunc-level)}}
          (λ h tp → Trunc-rec (lemma h) (–> (Trunc=-equiv _ _) tp))
        where
        lemma : (h : uCEl n Y) → h ⊙∘ f == ⊙cst
          → Trunc -1 (Σ (CEl n (⊙Cofiber f))
                          (λ tk → fst (⊙CEl-fmap n (⊙cfcod' f)) tk == [ h ]))
        lemma h p = [ [ fst wit ] , ap [_] (snd wit) ]
          where
          wit : Σ (uCEl n (⊙Cofiber f)) (λ k → k ⊙∘ ⊙cfcod' f == h)
          wit = ker-sub-im-lemma n f h p

    C-exact : (f : X ⊙→ Y) → is-exact (C-fmap n (⊙cfcod' f)) (C-fmap n f)
    C-exact f = record {im-sub-ker = C-im-sub-ker f; ker-sub-im = C-ker-sub-im f}

  {- Additivity Axiom -}
  module _ (n : ℤ) {A : Type i} (X : A → Ptd i)
    (ac : has-choice 0 A i)
    where

    into : CEl n (⊙BigWedge X) → Trunc 0 (Π A (uCEl n ∘ X))
    into = Trunc-rec (λ H → [ (λ a → H ⊙∘ ⊙bwin a) ])

    module Out' (K : Π A (uCEl n ∘ X)) = BigWedgeRec
      idp
      (fst ∘ K)
      (! ∘ snd ∘ K)

    out : Trunc 0 (Π A (uCEl n ∘ X)) → CEl n (⊙BigWedge X)
    out = Trunc-rec (λ K → [ Out'.f K , idp ])

    into-out : ∀ y → into (out y) == y
    into-out = Trunc-elim
      (λ K → ap [_] (λ= (λ a → pair= idp $
        ap (Out'.f K) (! (bwglue a)) ∙ idp
          =⟨ ∙-unit-r _ ⟩
        ap (Out'.f K) (! (bwglue a))
          =⟨ ap-! (Out'.f K) (bwglue a) ⟩
        ! (ap (Out'.f K) (bwglue a))
          =⟨ ap ! (Out'.glue-β K a) ⟩
        ! (! (snd (K a)))
          =⟨ !-! (snd (K a)) ⟩
        snd (K a) ∎)))

    out-into : ∀ x → out (into x) == x
    out-into = Trunc-elim
      {P = λ tH → out (into tH) == tH}
      (λ {(h , hpt) → ap [_] $ ⊙λ=' (out-into-fst (h , hpt)) (↓-idf=cst-in (! (!-inv-l hpt)))})
      where
      lemma : ∀ {i j} {A : Type i} {B : Type j} (f : A → B)
        {a₁ a₂ : A} {b : B} (p : a₁ == a₂) (q : f a₁ == b)
        → ! q ∙ ap f p == ! (ap f (! p) ∙ q)
      lemma f idp idp = idp

      out-into-fst : (H : ⊙BigWedge X ⊙→ ⊙Ω (E (succ n)))
        → ∀ w → Out'.f (λ a → H ⊙∘ ⊙bwin a) w == fst H w
      out-into-fst (h , hpt) = BigWedge-elim
        (! hpt)
        (λ _ _ → idp)
        (λ a → ↓-='-in' $
           ! hpt ∙ ap h (bwglue a)
             =⟨ lemma h (bwglue a) hpt ⟩
           ! (ap h (! (bwglue a)) ∙ hpt)
             =⟨ ! (Out'.glue-β (λ a → (h , hpt) ⊙∘ ⊙bwin a) a) ⟩
           ap (Out'.f (λ a → (h , hpt) ⊙∘ ⊙bwin a)) (bwglue a) ∎)

    abstract
      C-additive-is-equiv : is-equiv (GroupHom.f (Πᴳ-fanout (C-fmap n ∘ ⊙bwin {X = X})))
      C-additive-is-equiv = transport is-equiv
        (λ= $ Trunc-elim
          (λ _ → idp))
        ((ac (uCEl n ∘ X)) ∘ise (is-eq into out into-out out-into))

open SpectrumModel

spectrum-cohomology : CohomologyTheory i
spectrum-cohomology = record {
  C = C;
  C-fmap = C-fmap;
  C-fmap-idf = C-fmap-idf;
  C-fmap-∘ = C-fmap-∘;
  C-is-abelian = C-is-abelian;
  C-Susp = C-Susp;
  C-Susp-fmap = C-Susp-fmap;
  C-exact = C-exact;
  C-additive-is-equiv = C-additive-is-equiv}

spectrum-C-S⁰ : (n : ℤ) → C n (⊙Lift ⊙S⁰) ≃ᴳ πS 0 (E (succ n))
spectrum-C-S⁰ n = Trunc-⊙Bool→Ω-iso-π₁ (E (succ n)) ∘eᴳ Trunc-⊙→Ω-group-emap-dom ⊙lift-equiv (E (succ n))


postulate
  C : Set
  anything : C

record I : Set where
  constructor c
  field
    {f} : C

data Wrap : (i : I) → Set where
  wrap : ∀ {i} → Wrap i

works : ∀ {j} → Wrap j → C
works {c {._}} (wrap {c {x}}) = anything

fails : ∀ {j} → Wrap j → C
fails {c {._}} (wrap {c}) = anything

-- Failed to infer the value of dotted pattern
-- when checking that the pattern ._ has type C

fails' : ∀ {j} → Wrap j → C
fails' .{c {_}} (wrap {c}) = anything

works2 : ∀ {j} → Wrap j → C
works2 {c} (wrap {c {._}}) = anything

fails2 : ∀ {j} → Wrap j → C
fails2 {c {._}} (wrap {c}) = anything


{-# OPTIONS --safe --experimental-lossy-unification #-}
module Cubical.ZCohomology.Groups.KleinBottle where

open import Cubical.ZCohomology.Base
open import Cubical.ZCohomology.GroupStructure
open import Cubical.ZCohomology.Properties
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Pointed
open import Cubical.Foundations.Function
open import Cubical.Foundations.GroupoidLaws
open import Cubical.HITs.SetTruncation renaming (rec to sRec ; rec2 to pRec2 ; elim to sElim ; elim2 to sElim2 ; map to sMap)
open import Cubical.HITs.PropositionalTruncation renaming (rec to pRec ; ∣_∣ to ∣_∣₁)
open import Cubical.HITs.Truncation renaming (elim to trElim ; rec to trRec ; elim2 to trElim2)
open import Cubical.Data.Nat hiding (+-assoc)
open import Cubical.Algebra.Group renaming (Int to IntGroup ; Bool to BoolGroup ; Unit to UnitGroup)

open import Cubical.Foundations.Equiv.HalfAdjoint
open import Cubical.Foundations.Transport

open import Cubical.ZCohomology.Groups.Unit
open import Cubical.ZCohomology.Groups.Sn

open import Cubical.Data.Sigma

open import Cubical.Foundations.Isomorphism
open import Cubical.HITs.S1
open import Cubical.HITs.Sn
open import Cubical.Foundations.Equiv
open import Cubical.Homotopy.Connected

open import Cubical.Data.Empty renaming (rec to ⊥-rec)
open import Cubical.Data.Bool
open import Cubical.Data.Int renaming (+-comm to +-commℤ ; _+_ to _+ℤ_)

open import Cubical.HITs.KleinBottle
open import Cubical.Data.Empty
open import Cubical.Foundations.Path

open import Cubical.Homotopy.Loopspace

open IsGroupHom
open Iso

characFunSpace𝕂² : ∀ {ℓ} (A : Type ℓ) →
               Iso (KleinBottle → A)
                   (Σ[ x ∈ A ] Σ[ p ∈ x ≡ x ] Σ[ q ∈ x ≡ x ] p ∙∙ q ∙∙ p ≡ q)
fun (characFunSpace𝕂² A) f =
  (f point) ,
  ((cong f line1) ,
   (cong f line2 ,
   fst (Square≃doubleComp
         (cong f line2) (cong f line2)
         (sym (cong f line1)) (cong f line1))
         (λ i j → f (square i j))))
inv (characFunSpace𝕂² A) (x , p , q , sq) point = x
inv (characFunSpace𝕂² A) (x , p , q , sq) (line1 i) = p i
inv (characFunSpace𝕂² A) (x , p , q , sq) (line2 i) = q i
inv (characFunSpace𝕂² A) (x , p , q , sq) (square i j) =
  invEq (Square≃doubleComp q q (sym p) p) sq i j
rightInv (characFunSpace𝕂² A) (x , (p , (q , sq))) =
  ΣPathP (refl , (ΣPathP (refl , (ΣPathP (refl , secEq (Square≃doubleComp q q (sym p) p) sq)))))
leftInv (characFunSpace𝕂² A) f _ point = f point
leftInv (characFunSpace𝕂² A) f _ (line1 i) = f (line1 i)
leftInv (characFunSpace𝕂² A) f _ (line2 i) = f (line2 i)
leftInv (characFunSpace𝕂² A) f z (square i j) =
  retEq (Square≃doubleComp
          (cong f line2) (cong f line2)
          (sym (cong f line1)) (cong f line1))
          (λ i j → f (square i j)) z i j
private
  movePathLem : ∀ {ℓ} {A : Type ℓ} {x : A} (p q : x ≡ x) → isComm∙ (A , x)
             → (p ∙∙ q ∙∙ p ≡ q) ≡ ((p ∙ p) ∙ q ≡ q)
  movePathLem p q comm =
    cong (_≡ q) (doubleCompPath-elim' p q p ∙∙ cong (p ∙_) (comm q p) ∙∙ assoc _ _ _)

  movePathLem2 : ∀ {ℓ} {A : Type ℓ} {x : A} (p q : x ≡ x)
             → (((p ∙ p) ∙ q) ∙ sym q ≡ q ∙ sym q) ≡ (p ∙ p ≡ refl)
  movePathLem2 p q =
    cong₂ _≡_ (sym (assoc (p ∙ p) q (sym q)) ∙∙ cong ((p ∙ p) ∙_) (rCancel q) ∙∙ sym (rUnit (p ∙ p)))
              (rCancel q)

  movePathIso : ∀ {ℓ} {A : Type ℓ} {x : A} (p q : x ≡ x) → isComm∙ (A , x)
                → Iso (p ∙∙ q ∙∙ p ≡ q) (p ∙ p ≡ refl)
  movePathIso {x = x} p q comm =
    compIso (pathToIso (movePathLem p q comm))
      (compIso (helper (p ∙ p))
               (pathToIso (movePathLem2 p q)))
    where
    helper : (p : x ≡ x) → Iso (p ∙ q ≡ q) ((p ∙ q) ∙ sym q ≡ q ∙ sym q)
    helper p = congIso (equivToIso (_ , compPathr-isEquiv (sym q)))

------ H¹(𝕂²) ≅ 0 --------------
H⁰-𝕂² : GroupIso (coHomGr 0 KleinBottle) IntGroup
fun (fst H⁰-𝕂²) = sRec isSetInt λ f → f point
inv (fst H⁰-𝕂²) x = ∣ (λ _ → x) ∣₂
rightInv (fst H⁰-𝕂²) _ = refl
leftInv (fst H⁰-𝕂²) =
  sElim (λ _ → isOfHLevelPath 2 setTruncIsSet _ _)
        λ f → cong ∣_∣₂ (funExt (λ {point → refl
                                 ; (line1 i) j → isSetInt (f point) (f point) refl (cong f line1) j i
                                 ; (line2 i) j → isSetInt (f point) (f point) refl (cong f line2) j i
                                 ; (square i j) z → helper f i j z}))
  where
  helper : (f : KleinBottle → Int)
        → Cube (λ j z → isSetInt (f point) (f point) refl (cong  f line2) z j)
                (λ j z → isSetInt (f point) (f point) refl (cong  f line2) z j)
                (λ i z → isSetInt (f point) (f point) refl (cong  f line1) z (~ i))
                (λ i z → isSetInt (f point) (f point) refl (cong  f line1) z i)
                refl
                λ i j → f (square i j)
  helper f = isGroupoid→isGroupoid' (isOfHLevelSuc 2 isSetInt) _ _ _ _ _ _
snd H⁰-𝕂² =
  makeIsGroupHom (sElim2 (λ _ _ → isOfHLevelPath 2 isSetInt _ _) λ _ _ → refl)

------ H¹(𝕂¹) ≅ ℤ ------------
{-
Step one :
H¹(𝕂²) := ∥ 𝕂² → K₁ ∥₂
        ≡ ∥ Σ[ x ∈ K₁ ] Σ[ p ∈ x ≡ x ] Σ[ q ∈ x ≡ x ] (p ∙∙ q ∙∙ p ≡ q) ∥₂    (characFunSpace𝕂²)
        ≡ ∥ Σ[ x ∈ K₁ ] Σ[ p ∈ x ≡ x ] Σ[ q ∈ x ≡ x ] p ∙ p ≡ refl ∥₂         (movePathIso, using commutativity of ΩK₂)
        ≡ ∥ Σ[ x ∈ K₁ ] (x ≡ x) ∥₂                                             (p ∙ p ≡ refl forces p ≡ refl. Also, p ∙ p ≡ refl is an hProp)
-}

nilpotent→≡0 : (x : Int) → x +ℤ x ≡ 0 → x ≡ 0
nilpotent→≡0 (pos zero) p = refl
nilpotent→≡0 (pos (suc n)) p =
  ⊥-rec (negsucNotpos _ _
        (sym (cong (_- 1) (cong sucInt (sym (helper2 n)) ∙ p))))
  where
  helper2 : (n : ℕ) → pos (suc n) +pos n ≡ pos (suc (n + n))
  helper2 zero = refl
  helper2 (suc n) = cong sucInt (sym (sucInt+pos n (pos (suc n))))
                 ∙∙ cong (sucInt ∘ sucInt) (helper2 n)
                 ∙∙ cong (pos ∘ suc ∘ suc) (sym (+-suc n n))
nilpotent→≡0 (negsuc n) p = ⊥-rec (negsucNotpos _ _ (helper2 n p))
  where
  helper2 : (n : ℕ) → (negsuc n +negsuc n) ≡ pos 0 → negsuc n ≡ pos (suc n)
  helper2 n p = cong (negsuc n +ℤ_) (sym (helper3 n))
              ∙ +-assoc (negsuc n) (negsuc n) (pos (suc n))
              ∙∙ cong (_+ℤ (pos (suc n))) p
              ∙∙ cong sucInt (+-commℤ (pos 0) (pos n))
    where
    helper3 : (n : ℕ) → negsuc n +pos (suc n) ≡ 0
    helper3 zero = refl
    helper3 (suc n) = cong sucInt (sucInt+pos n (negsuc (suc n))) ∙ helper3 n

nilpotent→≡refl : (x : coHomK 1) (p : x ≡ x) → p ∙ p ≡ refl → p ≡ refl
nilpotent→≡refl =
  trElim (λ _ → isGroupoidΠ2 λ _ _ → isOfHLevelPlus {n = 1} 2 (isOfHLevelTrunc 3 _ _ _ _))
         (toPropElim (λ _ → isPropΠ2 λ _ _ → isOfHLevelTrunc 3 _ _ _ _)
          λ p pId → sym (rightInv (Iso-Kn-ΩKn+1 0) p)
                  ∙∙ cong (Kn→ΩKn+1 0) (nilpotent→≡0 (ΩKn+1→Kn 0 p)
                                                       (sym (ΩKn+1→Kn-hom 0 p p)
                                                        ∙ cong (ΩKn+1→Kn 0) pId))
                  ∙∙ Kn→ΩKn+10ₖ 0)

Iso-H¹-𝕂²₁ : Iso (Σ[ x ∈ coHomK 1 ] Σ[ p ∈ x ≡ x ] Σ[ q ∈ x ≡ x ] p ∙ p ≡ refl)
                  (Σ[ x ∈ coHomK 1 ] x ≡ x)
fun Iso-H¹-𝕂²₁ (x , (_ , (q , _))) = x , q
inv Iso-H¹-𝕂²₁ (x , q) = x , (refl , (q , (sym (rUnit refl))))
rightInv Iso-H¹-𝕂²₁ _ = refl
leftInv Iso-H¹-𝕂²₁ (x , (p , (q , P))) =
  ΣPathP (refl ,
   (ΣPathP (sym (nilpotent→≡refl x p P)
     , toPathP (Σ≡Prop (λ _ → isOfHLevelTrunc 3 _ _ _ _)
               (transportRefl q)))))

{- But this is precisely the type (minus set-truncation) of H¹(S¹) -}
Iso-H¹-𝕂²₂ : Iso (Σ[ x ∈ coHomK 1 ] x ≡ x) (S¹ → coHomK 1)
Iso-H¹-𝕂²₂ = invIso IsoFunSpaceS¹

H¹-𝕂²≅ℤ : GroupIso (coHomGr 1 KleinBottle) IntGroup
H¹-𝕂²≅ℤ = compGroupIso theGroupIso (Hⁿ-Sⁿ≅ℤ 0)
  where
  theIso : Iso (coHom 1 KleinBottle) (coHom 1 S¹)
  theIso =
    setTruncIso (
    compIso (characFunSpace𝕂² (coHomK 1))
      (compIso
         (Σ-cong-iso-snd (λ x → Σ-cong-iso-snd
                            λ p → Σ-cong-iso-snd
                              λ q → movePathIso p q (isCommΩK-based 1 x)))
         (compIso Iso-H¹-𝕂²₁
                  Iso-H¹-𝕂²₂)))

  is-hom : IsGroupHom (coHomGr 1 KleinBottle .snd) (fun theIso) (coHomGr 1 S¹ .snd)
  is-hom =
    makeIsGroupHom
      (sElim2 (λ _ _ → isOfHLevelPath 2 setTruncIsSet _ _)
        λ f g → cong ∣_∣₂ (funExt λ {base → refl ; (loop i) → refl}))

  theGroupIso : GroupIso (coHomGr 1 KleinBottle) (coHomGr 1 S¹)
  theGroupIso = (theIso , is-hom)

------ H²(𝕂²) ≅ ℤ/2ℤ (represented here by BoolGroup) -------
-- It suffices to show that H²(Klein) is equivalent to Bool as types

{-
Step one :
H²(𝕂²) := ∥ 𝕂² → K₂ ∥₂
        ≡ ∥ Σ[ x ∈ K₂ ] Σ[ p ∈ x ≡ x ] Σ[ q ∈ x ≡ x ] (p ∙∙ q ∙∙ p ≡ q) ∥₂    (characFunSpace𝕂²)
        ≡ ∥ Σ[ x ∈ K₂ ] Σ[ p ∈ x ≡ x ] Σ[ q ∈ x ≡ x ] p ∙ p ≡ refl ∥₂         (movePathIso, using commutativity of ΩK₂)
        ≡ ∥ Σ[ p ∈ x ≡ x ] p ∙ p ≡ refl ∥₂                                    (connectedness of K₂)
-}

Iso-H²-𝕂²₁ : Iso ∥ Σ[ x ∈ coHomK 2 ] Σ[ p ∈ x ≡ x ] Σ[ q ∈ x ≡ x ] p ∙ p ≡ refl ∥₂
                  ∥ Σ[ p ∈ 0ₖ 2 ≡ 0ₖ 2 ] p ∙ p ≡ refl ∥₂
fun Iso-H²-𝕂²₁ =
  sRec setTruncIsSet
    (uncurry (trElim (λ _ → is2GroupoidΠ λ _ → isOfHLevelPlus {n = 2} 2 setTruncIsSet)
                     (sphereElim _ (λ _ → isSetΠ λ _ → setTruncIsSet)
                                 λ y → ∣ fst y , snd (snd y) ∣₂)))
inv Iso-H²-𝕂²₁ =
  sMap λ p → (0ₖ 2) , ((fst p) , (refl , (snd p)))
rightInv Iso-H²-𝕂²₁ =
  sElim (λ _ → isOfHLevelPath 2 setTruncIsSet _ _)
        λ p → refl
leftInv Iso-H²-𝕂²₁ =
  sElim (λ _ → isOfHLevelPath 2 setTruncIsSet _ _)
        (uncurry (trElim (λ _ → is2GroupoidΠ λ _ → isOfHLevelPlus {n = 1} 3 (setTruncIsSet _ _))
                 (sphereToPropElim _
                   (λ _ → isPropΠ λ _ → setTruncIsSet _ _)
                   λ {(p , (q , sq))
                     → trRec (setTruncIsSet _ _)
                              (λ qid → cong ∣_∣₂ (ΣPathP (refl , (ΣPathP (refl , (ΣPathP (sym qid  , refl)))))))
                              (fun (PathIdTruncIso _)
                                       (isContr→isProp (isConnectedPathKn 1 (0ₖ 2) (0ₖ 2)) ∣ q ∣ ∣ refl ∣))})))

{- Step two :  ∥ Σ[ p ∈ x ≡ x ] p ∙ p ≡ refl ∥₂ ≡ ∥ Σ[ x ∈ K₁ ] x + x ≡ 0 ∥₂ -}
Iso-H²-𝕂²₂ : Iso ∥ (Σ[ p ∈ 0ₖ 2 ≡ 0ₖ 2 ] p ∙ p ≡ refl) ∥₂ ∥ Σ[ x ∈ coHomK 1 ] x +ₖ x ≡ 0ₖ 1 ∥₂
Iso-H²-𝕂²₂ = setTruncIso (Σ-cong-iso {B' = λ x → x +ₖ x ≡ 0ₖ 1} (invIso (Iso-Kn-ΩKn+1 1))
                                    λ p → compIso (congIso (invIso (Iso-Kn-ΩKn+1 1)))
                                                   (pathToIso λ i → ΩKn+1→Kn-hom 1 p p i ≡ 0ₖ 1))

{- Step three :
∥ Σ[ x ∈ K₁ ] x + x ≡ 0 ∥₂ ≡ Bool
We begin by defining the a map Σ[ x ∈ K₁ ] x + x ≡ 0 → Bool. For a point
(0 , p) we map it to true if winding(p) is even and false if winding(p) is odd.
We also have to show that this map respects the loop
-}

ΣKₙNilpot→Bool :  Σ[ x ∈ coHomK 1 ] x +ₖ x ≡ 0ₖ 1 → Bool
ΣKₙNilpot→Bool = uncurry (trElim (λ _ → isGroupoidΠ λ _ → isOfHLevelSuc 2 isSetBool)
                        λ {base p → isEven (ΩKn+1→Kn 0 p)
                        ; (loop i) p → hcomp (λ k → λ { (i = i0) → respectsLoop p k
                                                        ; (i = i1) → isEven (ΩKn+1→Kn 0 p)})
                        (isEven (ΩKn+1→Kn 0 (transp (λ j → ∣ (loop ∙ loop) (i ∨ j) ∣ ≡ 0ₖ 1) i
                                                      p)))})
  where
  isEven-2 : (x : Int) → isEven (-2 +ℤ x) ≡ isEven x
  isEven-2 (pos zero) = refl
  isEven-2 (pos (suc zero)) = refl
  isEven-2 (pos (suc (suc n))) =
      cong isEven (cong sucInt (sucInt+pos _ _)
              ∙∙ sucInt+pos _ _
              ∙∙ +-commℤ 0 (pos n))
    ∙ lossy n
    where
    lossy : (n : ℕ) → isEven (pos n) ≡ isEven (pos n)
    lossy n = refl
  isEven-2 (negsuc zero) = refl
  isEven-2 (negsuc (suc n)) =
      cong isEven (predInt+negsuc n _
               ∙ +-commℤ -3 (negsuc n))
    ∙ lossy2 n
      where
      lossy2 : (n : ℕ) → isEven (negsuc (suc (suc (suc n)))) ≡ isEven (pos n)
      lossy2 n = refl
  respectsLoop : (p : 0ₖ 1 ≡ 0ₖ 1)
              → isEven (ΩKn+1→Kn 0 (transport (λ i → ∣ (loop ∙ loop) i ∣ ≡ 0ₖ 1) p))
               ≡ isEven (ΩKn+1→Kn 0 p)
  respectsLoop p =
       cong isEven (cong (ΩKn+1→Kn 0) (cong (transport (λ i → ∣ (loop ∙ loop) i ∣ ≡ 0ₖ 1))
                                             (lUnit p)))
    ∙∙ cong isEven (cong (ΩKn+1→Kn 0)
                             λ j → transp (λ i → ∣ (loop ∙ loop) (i ∨ j) ∣ ≡ 0ₖ 1) j
                                           ((λ i → ∣ (loop ∙ loop) (~ i ∧ j) ∣) ∙ p))
    ∙∙ cong isEven (ΩKn+1→Kn-hom 0 (sym (cong ∣_∣ (loop ∙ loop))) p)
     ∙ isEven-2 (ΩKn+1→Kn 0 p)

{-
We show that for any x : Int we have ∣ (0ₖ 1 , Kn→ΩKn+1 0 x) ∣₂ ≡ ∣ (0ₖ 1 , refl) ∣₂ when x is even
and ∣ (0ₖ 1 , Kn→ΩKn+1 0 x) ∣₂ ≡ ∣ (0ₖ 1 , cong ∣_∣ loop) ∣₂ when x is odd

This is done by induction on x. For the inductive step we define a multiplication _*_ on ∥ Σ[ x ∈ coHomK 1 ] x +ₖ x ≡ 0ₖ 1 ∥₂
which is just ∣ (0 , p) ∣₂ * ∣ (0 , q) ∣₂ ≡ ∣ (0 , p ∙ q) ∣₂ when x is 0
-}

private
  _*_ : ∥ Σ[ x ∈ coHomK 1 ] x +ₖ x ≡ 0ₖ 1 ∥₂ → ∥ Σ[ x ∈ coHomK 1 ] x +ₖ x ≡ 0ₖ 1 ∥₂ → ∥ Σ[ x ∈ coHomK 1 ] x +ₖ x ≡ 0ₖ 1 ∥₂
  _*_ = sRec (isSetΠ (λ _ → setTruncIsSet)) λ a → sRec setTruncIsSet λ b → *' (fst a) (fst b) (snd a) (snd b)
    where
    *' : (x y : coHomK 1) (p : x +ₖ x ≡ 0ₖ 1) (q : y +ₖ y ≡ 0ₖ 1) → ∥ Σ[ x ∈ coHomK 1 ] x +ₖ x ≡ 0ₖ 1 ∥₂
    *' =
      trElim2 (λ _ _ → isGroupoidΠ2 λ _ _ → isOfHLevelSuc 2 setTruncIsSet)
              (wedgeconFun _ _
                (λ _ _ → isSetΠ2 λ _ _ → setTruncIsSet)
                (λ x p q → ∣ ∣ x ∣ , cong₂ _+ₖ_ p q ∣₂)
                (λ y p q → ∣ ∣ y ∣ , sym (rUnitₖ 1 (∣ y ∣ +ₖ ∣ y ∣)) ∙ cong₂ _+ₖ_ p q ∣₂)
                (funExt λ p → funExt λ q → cong ∣_∣₂ (ΣPathP (refl , (sym (lUnit _))))))

  *=∙ : (p q : 0ₖ 1 ≡ 0ₖ 1) → ∣ 0ₖ 1 , p ∣₂ * ∣ 0ₖ 1 , q ∣₂ ≡ ∣ 0ₖ 1 , p ∙ q ∣₂
  *=∙ p q = cong ∣_∣₂ (ΣPathP (refl , sym (∙≡+₁ p q)))

isEvenNegsuc : (n : ℕ) → isEven (pos (suc n)) ≡ true → isEven (negsuc n) ≡ true
isEvenNegsuc zero p = ⊥-rec (true≢false (sym p))
isEvenNegsuc (suc n) p = p

¬isEvenNegSuc : (n : ℕ) → isEven (pos (suc n)) ≡ false → isEven (negsuc n) ≡ false
¬isEvenNegSuc zero p = refl
¬isEvenNegSuc (suc n) p = p

evenCharac : (x : Int) → isEven x ≡ true
    → Path ∥ Σ[ x ∈ coHomK 1 ] x +ₖ x ≡ 0ₖ 1 ∥₂
            ∣ (0ₖ 1 , Kn→ΩKn+1 0 x) ∣₂
            ∣ (0ₖ 1 , refl) ∣₂
evenCharac (pos zero) isisEven i = ∣ (0ₖ 1) , (rUnit refl (~ i)) ∣₂
evenCharac (pos (suc zero)) isisEven = ⊥-rec (true≢false (sym isisEven))
evenCharac (pos (suc (suc zero))) isisEven =
    cong ∣_∣₂ ((λ i → 0ₖ 1 , rUnit (cong ∣_∣ ((lUnit loop (~ i)) ∙ loop)) (~ i))
  ∙ (ΣPathP (cong ∣_∣ loop , λ i j → ∣ (loop ∙ loop) (i ∨ j) ∣)))
evenCharac (pos (suc (suc (suc n)))) isisEven =
     (λ i → ∣ 0ₖ 1 , Kn→ΩKn+1-hom 0 (pos (suc n)) 2 i ∣₂)
  ∙∙ sym (*=∙ (Kn→ΩKn+1 0 (pos (suc n))) (Kn→ΩKn+1 0 (pos 2)))
  ∙∙ (cong₂ _*_ (evenCharac (pos (suc n)) isisEven) (evenCharac 2 refl))

evenCharac (negsuc zero) isisEven = ⊥-rec (true≢false (sym isisEven))
evenCharac (negsuc (suc zero)) isisEven =
  cong ∣_∣₂ ((λ i → 0ₖ 1
                  , λ i₁ → hfill (doubleComp-faces (λ i₂ → ∣ base ∣) (λ _ → ∣ base ∣) i₁)
                                  (inS ∣ compPath≡compPath' (sym loop) (sym loop) i i₁ ∣) (~ i))
          ∙ ΣPathP ((cong ∣_∣ (sym loop)) , λ i j → ∣ (sym loop ∙' sym loop) (i ∨ j) ∣))
evenCharac (negsuc (suc (suc n))) isisEven =
     cong ∣_∣₂ (λ i → 0ₖ 1 , Kn→ΩKn+1-hom 0 (negsuc n) -2 i)
  ∙∙ sym (*=∙ (Kn→ΩKn+1 0 (negsuc n)) (Kn→ΩKn+1 0 -2))
  ∙∙ cong₂ _*_ (evenCharac (negsuc n) (isEvenNegsuc n isisEven)) (evenCharac -2 refl)

oddCharac : (x : Int) → isEven x ≡ false
    → Path ∥ Σ[ x ∈ coHomK 1 ] x +ₖ x ≡ 0ₖ 1 ∥₂
            ∣ (0ₖ 1 , Kn→ΩKn+1 0 x) ∣₂
            ∣ (0ₖ 1 , cong ∣_∣ loop) ∣₂
oddCharac (pos zero) isOdd = ⊥-rec (true≢false isOdd)
oddCharac (pos (suc zero)) isOdd i =
  ∣ (0ₖ 1 , λ j → hfill (doubleComp-faces (λ i₂ → ∣ base ∣) (λ _ → ∣ base ∣) j)
                         (inS ∣ lUnit loop (~ i) j ∣) (~ i)) ∣₂
oddCharac (pos (suc (suc n))) isOdd =
  (λ i → ∣ 0ₖ 1 , Kn→ΩKn+1-hom 0 (pos n) 2 i ∣₂)
  ∙∙ sym (*=∙ (Kn→ΩKn+1 0 (pos n)) (Kn→ΩKn+1 0 2))
  ∙∙ cong₂ _*_ (oddCharac (pos n) isOdd) (evenCharac 2 refl)
oddCharac (negsuc zero) isOdd =
    cong ∣_∣₂ ((λ i → 0ₖ 1 , rUnit (sym (cong ∣_∣ loop)) (~ i))
  ∙ ΣPathP (cong ∣_∣ (sym loop) , λ i j → ∣ hcomp (λ k → λ { (i = i0) → loop (~ j ∧ k)
                                                           ; (i = i1) → loop j
                                                           ; (j = i1) → base})
                                                 (loop (j ∨ ~ i)) ∣))
oddCharac (negsuc (suc zero)) isOdd = ⊥-rec (true≢false isOdd)
oddCharac (negsuc (suc (suc n))) isOdd =
     cong ∣_∣₂ (λ i → 0ₖ 1 , Kn→ΩKn+1-hom 0 (negsuc n) -2 i)
  ∙∙ sym (*=∙ (Kn→ΩKn+1 0 (negsuc n)) (Kn→ΩKn+1 0 -2))
  ∙∙ cong₂ _*_ (oddCharac (negsuc n) (¬isEvenNegSuc n isOdd)) (evenCharac (negsuc 1) refl)

{- We now have all we need to establish the Iso -}
Bool→ΣKₙNilpot : Bool → ∥ Σ[ x ∈ coHomK 1 ] x +ₖ x ≡ 0ₖ 1 ∥₂
Bool→ΣKₙNilpot false = ∣ 0ₖ 1 , cong ∣_∣ loop ∣₂
Bool→ΣKₙNilpot true = ∣ 0ₖ 1 , refl ∣₂

testIso : Iso ∥ Σ[ x ∈ coHomK 1 ] x +ₖ x ≡ 0ₖ 1 ∥₂ Bool
fun testIso = sRec isSetBool ΣKₙNilpot→Bool
inv testIso = Bool→ΣKₙNilpot
rightInv testIso false = refl
rightInv testIso true = refl
leftInv testIso =
  sElim (λ _ → isOfHLevelPath 2 setTruncIsSet _ _)
        (uncurry (trElim
          (λ _ → isGroupoidΠ λ _ → isOfHLevelPlus {n = 1} 2 (setTruncIsSet _ _))
          (toPropElim (λ _ → isPropΠ (λ _ → setTruncIsSet _ _))
          (λ p → path p (isEven (ΩKn+1→Kn 0 p)) refl))))
  where
  path : (p : 0ₖ 1 ≡ 0ₖ 1) (b : Bool) → (isEven (ΩKn+1→Kn 0 p) ≡ b)
       → Bool→ΣKₙNilpot (ΣKₙNilpot→Bool (∣ base ∣ , p)) ≡ ∣ ∣ base ∣ , p ∣₂
  path p false q =
       (cong Bool→ΣKₙNilpot q)
    ∙∙ sym (oddCharac (ΩKn+1→Kn 0 p) q)
    ∙∙ cong ∣_∣₂ λ i → 0ₖ 1 , rightInv (Iso-Kn-ΩKn+1 0) p i
  path p true q =
       cong Bool→ΣKₙNilpot q
    ∙∙ sym (evenCharac (ΩKn+1→Kn 0 p) q)
    ∙∙ cong ∣_∣₂ λ i → 0ₖ 1 , rightInv (Iso-Kn-ΩKn+1 0) p i

H²-𝕂²≅Bool : GroupIso (coHomGr 2 KleinBottle) BoolGroup
H²-𝕂²≅Bool = invGroupIso (≅Bool theIso)
  where
  theIso : Iso _ _
  theIso =
    compIso (setTruncIso
               (compIso (characFunSpace𝕂² (coHomK 2))
                          (Σ-cong-iso-snd
                            λ x → Σ-cong-iso-snd
                              λ p → Σ-cong-iso-snd
                                λ q → (movePathIso p q (isCommΩK-based 2 x)))))
      (compIso Iso-H²-𝕂²₁
        (compIso
          Iso-H²-𝕂²₂
          testIso))

------ Hⁿ(𝕂²) ≅ 0 , n ≥ 3 ------
isContrHⁿ-𝕂² : (n : ℕ) → isContr (coHom (3 + n) KleinBottle)
isContrHⁿ-𝕂² n =
  isOfHLevelRetractFromIso 0
    (setTruncIso (characFunSpace𝕂² (coHomK _)))
    isContrΣ-help
  where
  helper : (x : coHomK (3 + n))(p : x ≡ x) → (refl ≡ p) → (q : x ≡ x) → (refl ≡ q)
      → (P : p ∙∙ q ∙∙ p ≡ q)
      → Path ∥ (Σ[ x ∈ coHomK (3 + n) ] Σ[ p ∈ x ≡ x ] Σ[ q ∈ x ≡ x ] p ∙∙ q ∙∙ p ≡ q) ∥₂
              ∣ x , p , q , P ∣₂
              ∣ 0ₖ _ , refl , refl , sym (rUnit refl) ∣₂
  helper =
    trElim (λ _ → isProp→isOfHLevelSuc (4 + n) (isPropΠ4 λ _ _ _ _ → isPropΠ λ _ → setTruncIsSet _ _))
      (sphereToPropElim _ (λ _ → isPropΠ4 λ _ _ _ _ → isPropΠ λ _ → setTruncIsSet _ _)
        λ p → J (λ p _ → (q : 0ₖ _ ≡ 0ₖ _) → (refl ≡ q)
                        → (P : p ∙∙ q ∙∙ p ≡ q)
                        → Path ∥ (Σ[ x ∈ coHomK (3 + n) ] Σ[ p ∈ x ≡ x ] Σ[ q ∈ x ≡ x ] p ∙∙ q ∙∙ p ≡ q) ∥₂
                                ∣ 0ₖ _ , p , q , P ∣₂
                                ∣ 0ₖ _ , refl , refl , sym (rUnit refl) ∣₂)
                λ q → J (λ q _ → (P : refl ∙∙ q ∙∙ refl ≡ q)
                                → Path ∥ (Σ[ x ∈ coHomK (3 + n) ] Σ[ p ∈ x ≡ x ] Σ[ q ∈ x ≡ x ] p ∙∙ q ∙∙ p ≡ q) ∥₂
                                        ∣ 0ₖ _ , refl , q , P ∣₂
                                        ∣ 0ₖ _ , refl , refl , sym (rUnit refl) ∣₂)
                         λ P → trRec (isProp→isOfHLevelSuc n (setTruncIsSet _ _))
                                      (λ P≡rUnitrefl i → ∣ 0ₖ (3 + n) , refl , refl , P≡rUnitrefl i ∣₂)
                                      (fun (PathIdTruncIso _)
                                                 (isContr→isProp (isConnectedPath _ (isConnectedPathKn (2 + n) _ _)
                                                                     (refl ∙∙ refl ∙∙ refl) refl)
                                                                     ∣ P ∣ ∣ sym (rUnit refl) ∣)))

  isContrΣ-help : isContr ∥ (Σ[ x ∈ coHomK (3 + n) ] Σ[ p ∈ x ≡ x ] Σ[ q ∈ x ≡ x ] p ∙∙ q ∙∙ p ≡ q) ∥₂
  fst isContrΣ-help = ∣ 0ₖ _ , refl , refl , sym (rUnit refl) ∣₂
  snd isContrΣ-help =
    sElim (λ _ → isOfHLevelPath 2 setTruncIsSet _ _)
      λ {(x , p , q , P)
        → trRec (isProp→isOfHLevelSuc (suc n) (setTruncIsSet _ _))
            (λ pId → trRec (isProp→isOfHLevelSuc (suc n) (setTruncIsSet _ _))
                      (λ qId → sym (helper x p pId q qId P))
                      (fun (PathIdTruncIso (2 + n))
                                 (isContr→isProp (isConnectedPathKn (2 + n) _ _) ∣ refl ∣ ∣ q ∣)))
                 (fun (PathIdTruncIso (2 + n))
                            (isContr→isProp (isConnectedPathKn (2 + n) _ _) ∣ refl ∣ ∣ p ∣))}

Hⁿ⁺³-𝕂²≅0 : (n : ℕ) → GroupIso (coHomGr (3 + n) KleinBottle) UnitGroup
Hⁿ⁺³-𝕂²≅0 n = contrGroupIsoUnit (isContrHⁿ-𝕂² n)