{-

This file contains:

- Properties of set truncations

-}
{-# OPTIONS --safe #-}
module Cubical.HITs.SetTruncation.Properties where

open import Cubical.HITs.SetTruncation.Base

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.GroupoidLaws
open import Cubical.Foundations.Function
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Univalence
open import Cubical.Data.Sigma
open import Cubical.HITs.PropositionalTruncation
  renaming (rec to pRec ; elim to pElim) hiding (elim2 ; elim3 ; rec2 ; map)

private
  variable
     ℓ' ℓ'' : Level
    A B C D : Type 

rec : isSet B  (A  B)   A ∥₂  B
rec Bset f  x ∣₂ = f x
rec Bset f (squash₂ x y p q i j) =
  Bset _ _ (cong (rec Bset f) p) (cong (rec Bset f) q) i j

rec2 : isSet C  (A  B  C)   A ∥₂   B ∥₂  C
rec2 Cset f  x ∣₂  y ∣₂ = f x y
rec2 Cset f  x ∣₂ (squash₂ y z p q i j) =
  Cset _ _ (cong (rec2 Cset f  x ∣₂) p) (cong (rec2 Cset f  x ∣₂) q) i j
rec2 Cset f (squash₂ x y p q i j) z =
  Cset _ _ (cong  a  rec2 Cset f a z) p) (cong  a  rec2 Cset f a z) q) i j

-- Old version:
-- rec2 Cset f = rec (isSetΠ λ _ → Cset) λ x → rec Cset (f x)

-- lemma 6.9.1 in HoTT book
elim : {B :  A ∥₂  Type }
       (Bset : (x :  A ∥₂)  isSet (B x))
       (f : (a : A)  B ( a ∣₂))
       (x :  A ∥₂)  B x
elim Bset f  a ∣₂ = f a
elim Bset f (squash₂ x y p q i j) =
  isOfHLevel→isOfHLevelDep 2 Bset _ _
    (cong (elim Bset f) p) (cong (elim Bset f) q) (squash₂ x y p q) i j

elim2 : {C :  A ∥₂   B ∥₂  Type }
        (Cset : ((x :  A ∥₂) (y :  B ∥₂)  isSet (C x y)))
        (f : (a : A) (b : B)  C  a ∣₂  b ∣₂)
        (x :  A ∥₂) (y :  B ∥₂)  C x y
elim2 Cset f  x ∣₂  y ∣₂ = f x y
elim2 Cset f  x ∣₂ (squash₂ y z p q i j) =
  isOfHLevel→isOfHLevelDep 2  a  Cset  x ∣₂ a) _ _
     (cong (elim2 Cset f  x ∣₂) p) (cong (elim2 Cset f  x ∣₂) q) (squash₂ y z p q) i j
elim2 Cset f (squash₂ x y p q i j) z =
  isOfHLevel→isOfHLevelDep 2  a  Cset a z) _ _
    (cong  a  elim2 Cset f a z) p) (cong  a  elim2 Cset f a z) q) (squash₂ x y p q) i j

-- Old version:
-- elim2 Cset f = elim (λ _ → isSetΠ (λ _ → Cset _ _))
--                     (λ a → elim (λ _ → Cset _ _) (f a))

-- TODO: generalize
elim3 : {B : (x y z :  A ∥₂)  Type }
        (Bset : ((x y z :  A ∥₂)  isSet (B x y z)))
        (g : (a b c : A)  B  a ∣₂  b ∣₂  c ∣₂)
        (x y z :  A ∥₂)  B x y z
elim3 Bset g = elim2  _ _  isSetΠ  _  Bset _ _ _))
                      a b  elim  _  Bset _ _ _) (g a b))


-- the recursor for maps into groupoids following the "HIT proof" in:
-- https://arxiv.org/abs/1507.01150
-- i.e. for any type A and groupoid B we can construct a map ∥ A ∥₂ → B
-- from a map A → B satisfying the condition
--    ∀ (a b : A) (p q : a ≡ b) → cong f p ≡ cong f q
-- TODO: prove that this is an equivalence
module rec→Gpd {A : Type } {B : Type ℓ'} (Bgpd : isGroupoid B) (f : A  B)
               (congFConst :  (a b : A) (p q : a  b)  cong f p  cong f q) where

 data H : Type  where
  η : A  H
  ε :  (a b : A)   a  b   η a  η b -- prop. trunc. of a≡b
  δ :  (a b : A) (p : a  b)  ε a b  p   cong η p
  gtrunc : isGroupoid H

 -- write elimination principle for H
 module Helim {P : H  Type ℓ''} (Pgpd :  h  isGroupoid (P h))
              (η* : (a : A)  P (η a))
              (ε* :  (a b : A) (∣p∣ :  a  b )
                   PathP  i  P (ε a b ∣p∣ i)) (η* a) (η* b))
              (δ* :  (a b : A) (p : a  b)
                   PathP  i  PathP  j  P (δ a b p i j)) (η* a) (η* b))
                          (ε* a b  p ) (cong η* p)) where

  fun : (h : H)  P h
  fun (η a) = η* a
  fun (ε a b ∣p∣ i) = ε* a b ∣p∣ i
  fun (δ a b p i j) = δ* a b p i j
  fun (gtrunc x y p q α β i j k) = isOfHLevel→isOfHLevelDep 3 Pgpd
                                   (fun x) (fun y)
                                   (cong fun p) (cong fun q)
                                   (cong (cong fun) α) (cong (cong fun) β)
                                   (gtrunc x y p q α β) i j k

 module Hrec {C : Type ℓ''} (Cgpd : isGroupoid C)
             (η* : A  C)
             (ε* :  (a b : A)   a  b   η* a  η* b)
             (δ* :  (a b : A) (p : a  b)  ε* a b  p   cong η* p) where

  fun : H  C
  fun (η a) = η* a
  fun (ε a b ∣p∣ i) = ε* a b ∣p∣ i
  fun (δ a b p i j) = δ* a b p i j
  fun (gtrunc x y p q α β i j k) = Cgpd (fun x) (fun y) (cong fun p) (cong fun q)
                                   (cong (cong fun) α) (cong (cong fun) β) i j k

 module HelimProp {P : H  Type ℓ''} (Pprop :  h  isProp (P h))
                  (η* : (a : A)  P (η a)) where

  fun :  h  P h
  fun = Helim.fun  _  isSet→isGroupoid (isProp→isSet (Pprop _))) η*
                   a b ∣p∣  isOfHLevel→isOfHLevelDep 1 Pprop _ _ (ε a b ∣p∣))
                   λ a b p  isOfHLevel→isOfHLevelDep 1
                              {B = λ p  PathP  i  P (p i)) (η* a) (η* b)}
                               _  isOfHLevelPathP 1 (Pprop _) _ _) _ _ (δ a b p)

 -- The main trick: eliminating into hsets is easy
 -- i.e. H has the universal property of set truncation...
 module HelimSet {P : H  Type ℓ''} (Pset :  h  isSet (P h))
                 (η* :  a  P (η a)) where

  fun : (h : H)  P h
  fun = Helim.fun  _  isSet→isGroupoid (Pset _)) η* ε*
                   λ a b p  isOfHLevel→isOfHLevelDep 1
                             {B = λ p  PathP  i  P (p i)) (η* a) (η* b)}
                              _  isOfHLevelPathP' 1 (Pset _) _ _) _ _ (δ a b p)
   where
   ε* : (a b : A) (∣p∣ :  a  b )  PathP  i  P (ε a b ∣p∣ i)) (η* a) (η* b)
   ε* a b = pElim  _  isOfHLevelPathP' 1 (Pset _) (η* a) (η* b))
                   λ p  subst  x  PathP  i  P (x i)) (η* a) (η* b))
                               (sym (δ a b p)) (cong η* p)


 -- Now we need to prove that H is a set.
 -- We start with a  little lemma:
 localHedbergLemma : {X : Type ℓ''} (P : X  Type ℓ'')
                    (∀ x  isProp (P x))
                    ((x : X)  P x  (y : X)  P y  x  y)
                  --------------------------------------------------
                    (x : X)  P x  (y : X)  isProp (x  y)
 localHedbergLemma {X = X} P Pprop P→≡ x px y = isPropRetract
                    p  subst P p px)  py  sym (P→≡ x px x px)  P→≡ x px y py)
                   isRetract (Pprop y)
  where
  isRetract : (p : x  y)  (sym (P→≡ x px x px))  P→≡ x px y (subst P p px)  p
  isRetract = J  y' p'  (sym (P→≡ x px x px))  P→≡ x px y' (subst P p' px)  p')
                (subst  px'  sym (P→≡ x px x px)  P→≡ x px x px'  refl)
                (sym (substRefl {B = P} px)) (lCancel (P→≡ x px x px)))

 Hset : isSet H
 Hset = HelimProp.fun  _  isPropΠ λ _  isPropIsProp) baseCaseLeft
  where
  baseCaseLeft : (a₀ : A) (y : H)  isProp (η a₀  y)
  baseCaseLeft a₀ = localHedbergLemma  x  Q x .fst)  x  Q x .snd) Q→≡ _  refl 
   where
   Q : H  hProp 
   Q = HelimSet.fun  _  isSetHProp) λ b   a₀  b  , isPropPropTrunc
   -- Q (η b) = ∥ a ≡ b ∥

   Q→≡ : (x : H)  Q x .fst  (y : H)  Q y .fst  x  y
   Q→≡ = HelimSet.fun  _  isSetΠ3 λ _ _ _  gtrunc _ _)
       λ a p  HelimSet.fun  _  isSetΠ λ _  gtrunc _ _)
       λ b q  sym (ε a₀ a p)  ε a₀ b q

 -- our desired function will split through H,
 -- i.e. we get a function ∥ A ∥₂ → H → B
 fun :  A ∥₂  B
 fun = f₁  f₂
  where
  f₁ : H  B
  f₁ = Hrec.fun Bgpd f εᶠ λ _ _ _  refl
   where
   εᶠ : (a b : A)   a  b   f a  f b
   εᶠ a b = rec→Set (Bgpd _ _) (cong f) λ p q  congFConst a b p q
   -- this is the inductive step,
   -- we use that maps ∥ A ∥ → B for an hset B
   -- correspond to 2-Constant maps A → B (which cong f is by assumption)
  f₂ :  A ∥₂  H
  f₂ = rec Hset η


map : (A  B)   A ∥₂   B ∥₂
map f = rec squash₂ λ x   f x ∣₂

setTruncUniversal : isSet B  ( A ∥₂  B)  (A  B)
setTruncUniversal {B = B} Bset =
  isoToEquiv (iso  h x  h  x ∣₂) (rec Bset)  _  refl) rinv)
  where
  rinv : (f :  A ∥₂  B)  rec Bset  x  f  x ∣₂)  f
  rinv f i x =
    elim  x  isProp→isSet (Bset (rec Bset  x  f  x ∣₂) x) (f x)))
          _  refl) x i

isSetSetTrunc : isSet  A ∥₂
isSetSetTrunc a b p q = squash₂ a b p q

setTruncIdempotentIso : isSet A  Iso  A ∥₂ A
Iso.fun (setTruncIdempotentIso hA) = rec hA (idfun _)
Iso.inv (setTruncIdempotentIso hA) x =  x ∣₂
Iso.rightInv (setTruncIdempotentIso hA) _ = refl
Iso.leftInv (setTruncIdempotentIso hA) = elim  _  isSet→isGroupoid isSetSetTrunc _ _)  _  refl)

setTruncIdempotent≃ : isSet A   A ∥₂  A
setTruncIdempotent≃ {A = A} hA = isoToEquiv (setTruncIdempotentIso hA)

setTruncIdempotent : isSet A   A ∥₂  A
setTruncIdempotent hA = ua (setTruncIdempotent≃ hA)

isContr→isContrSetTrunc : isContr A  isContr ( A ∥₂)
isContr→isContrSetTrunc contr =  fst contr ∣₂
                                , elim  _  isOfHLevelPath 2 (isSetSetTrunc) _ _)
                                       λ a  cong ∣_∣₂ (snd contr a)


setTruncIso : Iso A B  Iso  A ∥₂  B ∥₂
Iso.fun (setTruncIso is) = rec isSetSetTrunc  x   Iso.fun is x ∣₂)
Iso.inv (setTruncIso is) = rec isSetSetTrunc  x   Iso.inv is x ∣₂)
Iso.rightInv (setTruncIso is) =
  elim  _  isOfHLevelPath 2 isSetSetTrunc _ _)
        λ a  cong ∣_∣₂ (Iso.rightInv is a)
Iso.leftInv (setTruncIso is) =
  elim  _  isOfHLevelPath 2 isSetSetTrunc _ _)
        λ a  cong ∣_∣₂ (Iso.leftInv is a)

setSigmaIso : {B : A  Type }  Iso  Σ A B ∥₂  Σ A  x   B x ∥₂) ∥₂
setSigmaIso {A = A} {B = B} = iso fun funinv sect retr
  where
  {- writing it out explicitly to avoid yellow highlighting -}
  fun :  Σ A B ∥₂   Σ A  x   B x ∥₂) ∥₂
  fun = rec isSetSetTrunc λ {(a , p)   a ,  p ∣₂ ∣₂}
  funinv :  Σ A  x   B x ∥₂) ∥₂   Σ A B ∥₂
  funinv = rec isSetSetTrunc  {(a , p)  rec isSetSetTrunc  p   a , p ∣₂) p})
  sect : section fun funinv
  sect = elim  _  isOfHLevelPath 2 isSetSetTrunc _ _)
              λ { (a , p)  elim {B = λ p  fun (funinv  a , p ∣₂)   a , p ∣₂}
               p  isOfHLevelPath 2 isSetSetTrunc _ _)  _  refl) p }
  retr : retract fun funinv
  retr = elim  _  isOfHLevelPath 2 isSetSetTrunc _ _)
              λ { _  refl }

sigmaElim : {B :  A ∥₂  Type } {C : Σ  A ∥₂ B   Type ℓ'}
            (Bset : (x : Σ  A ∥₂ B)  isSet (C x))
            (g : (a : A) (b : B  a ∣₂)  C ( a ∣₂ , b))
            (x : Σ  A ∥₂ B)  C x
sigmaElim {B = B} {C = C} set g (x , y) =
  elim {B = λ x  (y : B x)  C (x , y)}  _  isSetΠ λ _  set _) g x y

sigmaProdElim : {C :  A ∥₂ ×  B ∥₂  Type } {D : Σ ( A ∥₂ ×  B ∥₂) C   Type ℓ'}
                (Bset : (x : Σ ( A ∥₂ ×  B ∥₂) C)  isSet (D x))
                (g : (a : A) (b : B) (c : C ( a ∣₂ ,  b ∣₂))  D (( a ∣₂ ,  b ∣₂) , c))
                (x : Σ ( A ∥₂ ×  B ∥₂) C)  D x
sigmaProdElim {B = B} {C = C} {D = D} set g ((x , y) , c) =
  elim {B = λ x  (y :  B ∥₂) (c : C (x , y))  D ((x , y) , c)}
        _  isSetΠ λ _  isSetΠ λ _  set _)
        x  elim  _  isSetΠ λ _  set _) (g x))
       x y c

prodElim : {C :  A ∥₂ ×  B ∥₂  Type }
          ((x :  A ∥₂ ×  B ∥₂)  isSet (C x))
          ((a : A) (b : B)  C ( a ∣₂ ,  b ∣₂))
          (x :  A ∥₂ ×  B ∥₂)  C x
prodElim setC f (a , b) = elim2  x y  setC (x , y)) f a b

prodRec : {C : Type }  isSet C  (A  B  C)   A ∥₂ ×  B ∥₂  C
prodRec setC f (a , b) = rec2 setC f a b

prodElim2 : {E : ( A ∥₂ ×  B ∥₂)  ( C ∥₂ ×  D ∥₂)  Type }
          ((x :  A ∥₂ ×  B ∥₂) (y :  C ∥₂ ×  D ∥₂)  isSet (E x y))
          ((a : A) (b : B) (c : C) (d : D)  E ( a ∣₂ ,  b ∣₂) ( c ∣₂ ,  d ∣₂))
          ((x :  A ∥₂ ×  B ∥₂) (y :  C ∥₂ ×  D ∥₂)  (E x y))
prodElim2 isset f = prodElim  _  isSetΠ λ _  isset _ _)
                             λ a b  prodElim  _  isset _ _)
                                     λ c d  f a b c d

setTruncOfProdIso :  Iso  A × B ∥₂ ( A ∥₂ ×  B ∥₂)
Iso.fun setTruncOfProdIso = rec (isSet× isSetSetTrunc isSetSetTrunc) λ { (a , b)   a ∣₂ ,  b ∣₂ }
Iso.inv setTruncOfProdIso = prodRec isSetSetTrunc λ a b   a , b ∣₂
Iso.rightInv setTruncOfProdIso =
  prodElim  _  isOfHLevelPath 2 (isSet× isSetSetTrunc isSetSetTrunc) _ _) λ _ _  refl
Iso.leftInv setTruncOfProdIso =
  elim  _  isOfHLevelPath 2 isSetSetTrunc _ _) λ {(a , b)  refl}

IsoSetTruncateSndΣ : {A : Type } {B : A  Type ℓ'}  Iso  Σ A B ∥₂  Σ A  x   B x ∥₂) ∥₂
Iso.fun IsoSetTruncateSndΣ = map λ a  (fst a) ,  snd a ∣₂
Iso.inv IsoSetTruncateSndΣ = rec isSetSetTrunc (uncurry λ x  map λ b  x , b)
Iso.rightInv IsoSetTruncateSndΣ =
  elim  _  isOfHLevelPath 2 isSetSetTrunc _ _)
        (uncurry λ a  elim  _  isOfHLevelPath 2 isSetSetTrunc _ _)
        λ _  refl)
Iso.leftInv IsoSetTruncateSndΣ =
  elim  _  isOfHLevelPath 2 isSetSetTrunc _ _)
         λ _  refl

PathIdTrunc₀Iso : {a b : A}  Iso ( a ∣₂   b ∣₂)  a  b 
Iso.fun (PathIdTrunc₀Iso {b = b}) p =
  transport  i  rec {B = TypeOfHLevel _ 1} (isOfHLevelTypeOfHLevel 1)
                         a   a  b  , squash) (p (~ i)) .fst)
             refl 
Iso.inv PathIdTrunc₀Iso = pRec (squash₂ _ _) (cong ∣_∣₂)
Iso.rightInv PathIdTrunc₀Iso _ = squash _ _
Iso.leftInv PathIdTrunc₀Iso _ = squash₂ _ _ _ _