{-# OPTIONS --cubical --safe --postfix-projections #-}

open import Prelude hiding (_×_)

module Categories.HSets { : Level} where

open import Categories
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Univalence
open import Data.Sigma.Properties
open import Categories.Product
open import Categories.Exponential

hSetPreCategory : PreCategory (ℓsuc ) 
hSetPreCategory .PreCategory.Ob = hSet _
hSetPreCategory .PreCategory.Hom (X , _) (Y , _) = X  Y
hSetPreCategory .PreCategory.Id = id
hSetPreCategory .PreCategory.Comp f g = f  g
hSetPreCategory .PreCategory.assoc-Comp _ _ _ = refl
hSetPreCategory .PreCategory.Comp-Id _ = refl
hSetPreCategory .PreCategory.Id-Comp _ = refl
hSetPreCategory .PreCategory.Hom-Set {X} {Y} = hLevelPi 2 λ _  Y .snd

open PreCategory hSetPreCategory public


_⟨×⟩_ : isSet A  isSet B  isSet (A Prelude.× B)
xs ⟨×⟩ ys = isOfHLevelΣ 2 xs (const ys)

module _ {X Y : Ob} where
  iso-iso : (X  Y)  (fst X  fst Y)
  iso-iso .fun (f , g , f∘g , g∘f) = iso f g  x i  g∘f i x)  x i  f∘g i x)
  iso-iso .inv (iso f g g∘f f∘g) = f , g  ,  i x  f∘g x i) ,  i x  g∘f x i)
  iso-iso .rightInv _ = refl
  iso-iso .leftInv  _ = refl

  univ⇔ : (X  Y)  (X  Y)
  univ⇔ = equivToIso (≃ΣProp≡  _  isPropIsSet))  trans-⇔ 
          equivToIso univalence  trans-⇔ 
          sym-⇔ (iso⇔equiv (snd X))  trans-⇔ 
          sym-⇔ iso-iso

hSetCategory : Category (ℓsuc ) 
hSetCategory .Category.preCategory = hSetPreCategory
hSetCategory .Category.univalent = isoToEquiv univ⇔

hSetProd : HasProducts hSetCategory
hSetProd .HasProducts.product X Y .Product.obj = (X .fst Prelude.× Y .fst) , X .snd ⟨×⟩ Y .snd
hSetProd .HasProducts.product X Y .Product.proj₁ = fst
hSetProd .HasProducts.product X Y .Product.proj₂ = snd
hSetProd .HasProducts.product X Y .Product.ump f g .fst z = f z , g z
hSetProd .HasProducts.product X Y .Product.ump f g .snd .fst .fst = refl
hSetProd .HasProducts.product X Y .Product.ump f g .snd .fst .snd = refl
hSetProd .HasProducts.product X Y .Product.ump f g .snd .snd (f≡ , g≡) i x = f≡ (~ i) x , g≡ (~ i) x

hSetExp : HasExponentials hSetCategory hSetProd
hSetExp  X Y .Exponential.obj = (X .fst  Y .fst) , hLevelPi 2 λ _  Y .snd
hSetExp  X Y .Exponential.eval (f , x) = f x
hSetExp  X Y .Exponential.uniq X₁ f .fst = curry f
hSetExp  X Y .Exponential.uniq X₁ f .snd .fst = refl
hSetExp  X Y .Exponential.uniq X₁ f .snd .snd {y} x = cong curry (sym x)

open import Categories.Pullback

hSetHasPullbacks : HasPullbacks hSetCategory
hSetHasPullbacks {X = X} {Y = Y} {Z = Z} f g .Pullback.P = ∃[ ab ] (f (fst ab)  g (snd ab)) , isOfHLevelΣ 2 (X .snd ⟨×⟩ Y .snd) λ xy  isProp→isSet (Z .snd (f (xy .fst)) (g (xy .snd)))
hSetHasPullbacks f g .Pullback.p₁ ((x , _) , _) = x
hSetHasPullbacks f g .Pullback.p₂ ((_ , y) , _) = y
hSetHasPullbacks f g .Pullback.commute = funExt snd
hSetHasPullbacks f g .Pullback.ump {A = A} h₁ h₂ p .fst x = (h₁ x , h₂ x) , λ i  p i x
hSetHasPullbacks f g .Pullback.ump {A = A} h₁ h₂ p .snd .fst .fst = refl
hSetHasPullbacks f g .Pullback.ump {A = A} h₁ h₂ p .snd .fst .snd = refl
hSetHasPullbacks {Z = Z} f g .Pullback.ump {A = A} h₁ h₂ p .snd .snd {i} (p₁e , p₂e) = funExt  x  ΣProp≡  _  Z .snd _ _) λ j  p₁e (~ j) x , p₂e (~ j) x)

hSetTerminal : Terminal
hSetTerminal .fst = Lift _  , isProp→isSet λ _ _  refl
hSetTerminal .snd .fst x .lower = tt
hSetTerminal .snd .snd y = funExt λ _  refl

hSetInitial : Initial
hSetInitial .fst = Lift _  , λ ()
hSetInitial .snd .fst ()
hSetInitial .snd .snd y i ()

open import HITs.PropositionalTruncation
open import Categories.Coequalizer

∃!′ : (A : Type a)  (A  Type b)  Type (a ℓ⊔ b)
∃!′ A P =  Σ A P  Prelude.× AtMostOne P

lemma23 :  {p} {P : A  hProp p}  ∃!′ A (fst  P)  Σ A (fst  P)
lemma23 {P = P} (x , y) = rec  xs ys  ΣProp≡ (snd  P) (y (xs .fst) (ys .fst) (xs .snd) (ys .snd))) id x

module _ {A : Type a} {P : A  Type b} (R :  x  P x  hProp c) where
  uniqueChoice : (Π[ x  A ] (∃!′ (P x)  u  R x u .fst))) 
                 Σ[ f  Π[ x  A ] P x ] Π[ x  A ] (R x (f x) .fst)
  uniqueChoice H = fst  mid , snd  mid
    where
    mid : Π[ x  A ] Σ[ u  P x ] (R x u .fst)
    mid x = lemma23 {P = R x} (H x)

open import HITs.PropositionalTruncation.Sugar

module CoeqProofs {X Y : Ob} (f : X  Y) where
  KernelPair : Pullback hSetCategory {X = X} {Z = Y} {Y = X} f f
  KernelPair = hSetHasPullbacks f f

  Im : Ob
  Im = ∃[ b ]  fiber f b  , isOfHLevelΣ 2 (Y .snd) λ _  isProp→isSet squash

  im : X  Im
  im x = f x ,  x , refl 

  open Pullback KernelPair

  lem :  {H : Ob} (h : X  H)  h  p₁  h  p₂  Σ[ f  (Im  H) ] Π[ x  Im .fst ] (∀ y  im y  x  h y  f x)
  lem {H = H} h eq = uniqueChoice R prf
    where
    R : Im .fst  H .fst  hProp _
    R w x .fst =  y  im y  w  h y  x
    R w x .snd = hLevelPi 1 λ _  hLevelPi 1 λ _  H .snd _ _

    prf : Π[ x  Im .fst ] ∃!′ (H .fst)  u   y  im y  x  h y  u)
    prf (xy , p) .fst =  { (z , r)  h z , λ y imy≡xyp  cong (_$ ((y , z) , cong fst imy≡xyp ; sym r)) eq }) ∥$∥ p
    prf (xy , p) .snd x₁ x₂ Px₁ Px₂ = rec (H .snd x₁ x₂)  { (z , zs)  sym (Px₁ z (ΣProp≡  _  squash) zs)) ; Px₂ z (ΣProp≡  _  squash) zs) } ) p

  lem₂ :  (H : Ob) (h : X  H) (i : Im  H) (x : Im .fst) (hi : h  i  im) (eq : h  p₁  h  p₂)  i x  lem {H = H} h eq .fst x
  lem₂ H h i x hi eq = rec (H .snd _ _)  { (y , ys)  (cong i (ΣProp≡  _  squash) (sym ys)) ; sym (cong (_$ y) hi)) ; lem {H = H} h eq .snd x y (ΣProp≡  _  squash) ys) }) (x .snd)

  hSetCoeq : Coequalizer hSetCategory {X = P} {Y = X} p₁ p₂
  hSetCoeq .Coequalizer.obj = Im
  hSetCoeq .Coequalizer.arr = im
  hSetCoeq .Coequalizer.equality = funExt λ x  ΣProp≡  _  squash) λ i  commute i x
  hSetCoeq .Coequalizer.coequalize {H = H} {h = h} eq = lem {H = H} h eq .fst
  hSetCoeq .Coequalizer.universal {H = H} {h = h} {eq = eq} = funExt λ x  lem {H = H} h eq .snd (im x) x refl
  hSetCoeq .Coequalizer.unique {H = H} {h = h} {i = i} {eq = eq} prf = funExt λ x  lem₂ H h i x prf eq
open CoeqProofs using (hSetCoeq) public

module PullbackSurjProofs {X Y : Ob} (f : X  Y) (fSurj : Surjective f) where
  KernelPair : Pullback hSetCategory {X = X} {Z = Y} {Y = X} f f
  KernelPair = hSetHasPullbacks f f

  open Pullback KernelPair

  p₁surj : Surjective p₁
  p₁surj y =  ((y , y) , refl) , refl 

  p₂surj : Surjective p₂
  p₂surj y =  ((y , y) , refl) , refl 

open import Relation.Binary
open import Cubical.HITs.SetQuotients

module _ {A : Type a} {R : A  A  Type b} {C : Type c}
         (isSetC : isSet C)
         (f : A  C)
         (coh :  x y  R x y  f x  f y)
         where

  recQuot : A / R  C
  recQuot [ a ] = f a
  recQuot (eq/ a b r i) = coh a b r i
  recQuot (squash/ xs ys x y i j) = isSetC (recQuot xs) (recQuot ys) (cong recQuot x) (cong recQuot y) i j

open import Path.Reasoning

module ExtactProofs {X : Ob} {R : X .fst  X .fst  hProp }
  (symR : Symmetric  x y  R x y .fst))
  (transR : Transitive  x y  R x y .fst))
  (reflR : Reflexive  x y  R x y .fst))
  where
   : X .fst  X .fst  Type 
   x y = R x y .fst

  Src : Ob
  Src .fst = ∃[ x,y ] (uncurry  x,y)
  Src .snd = isOfHLevelΣ 2 (X .snd ⟨×⟩ X .snd) λ _  isProp→isSet (R _ _ .snd)

  pr₁ pr₂ : Src  X
  pr₁ = fst  fst
  pr₂ = snd  fst

  ROb : Ob
  ROb = X .fst /  , squash/

  CR : Coequalizer hSetCategory {X = Src} {Y = X} pr₁ pr₂
  CR .Coequalizer.obj = ROb
  CR .Coequalizer.arr = [_]
  CR .Coequalizer.equality = funExt λ { ((x , y) , x~y)  eq/ x y x~y}
  CR .Coequalizer.coequalize {H = H} {h = h} e = recQuot (H .snd) h λ x y x~y  cong (_$ ((x , y) , x~y)) e
  CR .Coequalizer.universal {H = H} {h = h} {eq = e} = refl
  CR .Coequalizer.unique {H = H} {h = h} {i = i} {eq = e} p = funExt (elimSetQuotientsProp  _  H .snd _ _) λ x j  p (~ j) x)