{-# OPTIONS --cubical #-}
open import Cubical.Foundations.Prelude
open import Cubical.Foundations.GroupoidLaws as GL
open import Cubical.Foundations.Structure
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Pointed hiding (⋆)
open import Cubical.Foundations.Function
open import Cubical.Foundations.Isomorphism
open import Cubical.Data.Sigma
open import Cubical.Algebra.Semigroup
open import Cubical.Algebra.Monoid
open import Cubical.Algebra.Group
open import Cubical.Algebra.Group.Morphisms
open import Cubical.Algebra.Group.MorphismProperties
open import Cubical.Relation.Binary hiding (Rel)
open import Cubical.HITs.SetQuotients as SQ
private variable
  ℓ ℓ' : Level
module _ (G : Group ℓ) where
  open GroupStr (str G)
  open import Cubical.HITs.EilenbergMacLane1 as EM
  
  loop-id : emloop 1g ≡ refl
  loop-id =
    emloop 1g                              ≡⟨ lUnit (emloop 1g) ⟩
    refl ∙ emloop 1g                       ≡⟨ cong (_∙ emloop 1g) (sym (lCancel (emloop 1g)) ) ⟩
    (emloop 1g ⁻¹ ∙ emloop 1g) ∙ emloop 1g ≡⟨ sym (assoc _ _ _) ⟩
    emloop 1g ⁻¹ ∙ (emloop 1g ∙ emloop 1g) ≡⟨ cong (sym (emloop 1g) ∙_) (sym (emloop-comp G 1g 1g) ∙ cong emloop (·IdL 1g)) ⟩
    emloop 1g ⁻¹ ∙ emloop 1g               ≡⟨ rCancel _ ⟩
    refl                                   ∎
subst2≡ : {A : Type ℓ} {x x' y y' : A} (p : x ≡ x') (q : y ≡ y') (r : x ≡ y) → subst2 _≡_ p q r ≡ sym p ∙ r ∙ q
subst2≡ p q r = J (λ _ p → subst2 _≡_ p q r ≡ sym p ∙ r ∙ q) lem p
  where
  lem'' : subst2 _≡_ refl refl r ≡ r
  lem'' = transportRefl r 
  lem' : subst2 _≡_ refl q r ≡ r ∙ q
  lem' = J (λ _ q → subst2 _≡_ refl q r ≡ r ∙ q) (lem'' ∙ rUnit r) q
  lem : subst2 _≡_ refl q r ≡ sym refl ∙ r ∙ q
  lem = lem' ∙ cong₂ _∙_ (lUnit r) refl ∙ sym (assoc _ _ _)
subst2≡Refl : {A : Type ℓ} {x y z : A} (p : y ≡ x) (q : y ≡ z) → subst2 _≡_ p q refl ≡ sym p ∙ q
subst2≡Refl p q =
  subst2 _≡_ p q refl ≡⟨ subst2≡ p q refl ⟩
  sym p ∙ refl ∙ q    ≡⟨ cong₂ _∙_ refl (sym (lUnit q)) ⟩
  sym p ∙ q           ∎
record Span (X Y : Type ℓ) : Type (ℓ-max ℓ (ℓ-suc ℓ')) where
  field
    total : Type ℓ'
    src   : total → X
    tgt   : total → Y
Graph : (X : Type ℓ) → Type (ℓ-max ℓ (ℓ-suc ℓ'))
Graph {ℓ' = ℓ'} X = Span {ℓ' = ℓ'} X X
module _ (G : Group ℓ) where
  open Span
  open GroupStr (str G)
  
  record isCongruence (_∼_ : ⟨ G ⟩ → ⟨ G ⟩ → Type ℓ) : Type ℓ where
    field
      equivalence : BinaryRelation.isEquivRel _∼_
      preservesProd : {x x' y y' : ⟨ G ⟩} → x ∼ x' → y ∼ y' → (x · y) ∼ (x' · y')
      
  open isCongruence public
  
  Congruence : Type _
  Congruence = Σ (⟨ G ⟩ → ⟨ G ⟩ → Type ℓ) isCongruence
  isReflexive : (R : Congruence) {x : ⟨ G ⟩} → fst R x x
  isReflexive R {x} = snd R .equivalence .BinaryRelation.isEquivRel.reflexive x
  isTransitive : (R : Congruence) {x y z : ⟨ G ⟩} → fst R x y → fst R y z → fst R x z
  isTransitive R p q = snd R .equivalence .BinaryRelation.isEquivRel.transitive _ _ _ p q
  isSymmetric : (R : Congruence) {x y : ⟨ G ⟩} → fst R x y → fst R y x
  isSymmetric R p = snd R .equivalence .BinaryRelation.isEquivRel.symmetric _ _ p
  preservesProduct : (R : Congruence) {x x' y y' : ⟨ G ⟩} → fst R x x' → fst R y y' → fst R (x · y) (x' · y')
  preservesProduct R = snd R .preservesProd
  preserves≡ : (R : Congruence) → {x y : ⟨ G ⟩} → x ≡ y → fst R x y
  preserves≡ R {x = x} p = subst (fst R x) p (isReflexive R)
  
  preservesInverse : (R : Congruence) → {x y : ⟨ G ⟩} → fst R x y → fst R (inv x) (inv y)
  preservesInverse R {x} {y} p =
    
    preserves≡ R (sym (·IdL (inv x))) ··
    preserves≡ R (cong (λ y → y · inv x) (sym (·InvL y))) ··
    preserves≡ R (sym (·Assoc _ _ _)) ··
    preservesProduct R (isReflexive R) (preservesProduct R (isSymmetric R p) (isReflexive R)) ··
    preservesProduct R (isReflexive R) (preserves≡ R (·InvR x)) ··
    preserves≡ R (·IdR (inv y))
    where
    infixr 30 _··_
    _··_ = isTransitive R
  module _ (R : Graph {ℓ' = ℓ} ⟨ G ⟩) where
    
    data freeCongruence : ⟨ G ⟩ → ⟨ G ⟩ → Type ℓ where
      fcBase : (r : total R) → freeCongruence (src R r) (tgt R r)
      
      fcRefl : {x : ⟨ G ⟩} → freeCongruence x x
      fcTrans : {x y z : ⟨ G ⟩} → freeCongruence x y → freeCongruence y z → freeCongruence x z
      fcSym : {x y : ⟨ G ⟩} → freeCongruence x y → freeCongruence y x
      fcProd : {x x' y y' : ⟨ G ⟩} → freeCongruence x x' → freeCongruence y y' → freeCongruence (x · y) (x' · y')
    
    freeCongruenceIsCongruence : isCongruence freeCongruence
    BinaryRelation.isEquivRel.reflexive (equivalence freeCongruenceIsCongruence) _ = fcRefl
    BinaryRelation.isEquivRel.symmetric (equivalence freeCongruenceIsCongruence) _ _ = fcSym
    BinaryRelation.isEquivRel.transitive (equivalence freeCongruenceIsCongruence) _ _ _ = fcTrans
    preservesProd freeCongruenceIsCongruence = fcProd
    
    freeCongruenceCongruence : Congruence
    freeCongruenceCongruence = freeCongruence , freeCongruenceIsCongruence
  module _ (R : Congruence) where
    
    quotient : Group ℓ
    quotient = (⟨ G ⟩ / fst R) , grp
      where
      open isCongruence (snd R)
      grp : GroupStr (⟨ G ⟩ / fst R)
      GroupStr.1g grp = [ 1g ]
      GroupStr._·_ grp x y = rec2 squash/ (λ x y → [ x · y ]) (λ _ _ _ r → eq/ _ _ (preservesProduct R r (isReflexive R))) (λ _ _ _ r → eq/ _ _ (preservesProduct R (isReflexive R) r)) x y
      GroupStr.inv grp x = SQ.rec squash/ (λ x → [ inv x ]) (λ _ _ r → eq/ _ _ (preservesInverse R r)) x
      IsSemigroup.is-set (IsMonoid.isSemigroup (IsGroup.isMonoid (GroupStr.isGroup grp))) = squash/
      IsSemigroup.·Assoc (IsMonoid.isSemigroup (IsGroup.isMonoid (GroupStr.isGroup grp))) = SQ.elimProp3 (λ _ _ _ → squash/ _ _) λ x y z → cong [_] (·Assoc x y z)
      IsMonoid.·IdR (IsGroup.isMonoid (GroupStr.isGroup grp)) = SQ.elimProp (λ _ → squash/ _ _) λ x → cong [_] (·IdR x)
      IsMonoid.·IdL (IsGroup.isMonoid (GroupStr.isGroup grp)) = SQ.elimProp (λ _ → squash/ _ _) λ x → cong [_] (·IdL x)
      IsGroup.·InvR (GroupStr.isGroup grp) = SQ.elimProp (λ _ → squash/ _ _) λ x → cong [_] (·InvR x)
      IsGroup.·InvL (GroupStr.isGroup grp) = SQ.elimProp (λ _ → squash/ _ _) λ x → cong [_] (·InvL x)
    
    
    
      
      
      
      
      
      
  
  quotientRec : (R : Graph {ℓ' = ℓ} ⟨ G ⟩) {H : Group ℓ} (f : GroupHom G H) → ((r : total R) → fst f (src R r) ≡ fst f (tgt R r)) → GroupHom (quotient (freeCongruenceCongruence R)) H
  quotientRec R {H} f rel = f/ , igh
    where
    isSetH : isSet ⟨ H ⟩
    isSetH = snd H .GroupStr.is-set
    R* = freeCongruenceCongruence R
    G/R* = quotient R*
    fR* : {x y : ⟨ G ⟩} (r : fst R* x y) → fst f x ≡ fst f y
    fR* (fcBase r) = rel r
    fR* fcRefl = refl
    fR* (fcTrans r s) = fR* r ∙ fR* s
    fR* (fcSym r) = sym (fR* r)
    fR* (fcProd r s) = f .snd .IsGroupHom.pres· _ _ ∙ cong₂ (snd H .GroupStr._·_) (fR* r) (fR* s) ∙ sym (f .snd .IsGroupHom.pres· _ _)
    f/ : ⟨ quotient R* ⟩ → ⟨ H ⟩
    f/ x = SQ.rec isSetH (fst f) (λ _ _ → fR*) x
    open IsGroupHom
    igh : IsGroupHom (snd G/R*) f/ (snd H)
    pres· igh x y = SQ.elimProp2
      {P = λ x y → f/ (snd G/R* .GroupStr._·_ x y) ≡ snd H .GroupStr._·_ (f/ x) (f/ y)}
      (λ _ _ → isSetH _ _)
      (snd f .pres·)
      x y
    pres1 igh = snd f .pres1
    presinv igh x = SQ.elimProp
      {P = λ x → f/ (snd G/R* .GroupStr.inv x) ≡ snd H .GroupStr.inv (f/ x)}
      (λ x → isSetH _ _)
      (snd f .presinv)
      x
open import Cubical.HITs.FreeGroup as FG hiding (assoc)
Presentation : {ℓ : Level} → Type _
Presentation {ℓ} = TypeWithStr ℓ (λ X → Graph {ℓ' = ℓ} (FreeGroup X))
_* : Presentation → Group ℓ
P * = freeGroupGroup ⟨ P ⟩
∣_∣ : {ℓ : Level} → Presentation {ℓ} → Group ℓ
∣ P ∣ = quotient (P *) (freeCongruenceCongruence (P *) (str P))
module _ {ℓ : Level} (P : Presentation {ℓ}) where
  open Span (str P)
  
  1P   = GroupStr.1g (str ∣ P ∣)
  _·P_ = GroupStr._·_ (str ∣ P ∣)
  invP = GroupStr.inv (str ∣ P ∣)
  
  Rel = total
  
  
    
    
    
  
    
    
    
    
    
    
    
    
    
    
    
    
      
      
    
    
    
    
    
    
    
  
  data 1Delooping : Type ℓ where
    ⋆ : 1Delooping
    gen : (a : ⟨ P ⟩) → ⋆ ≡ ⋆
  
  1Delooping-elim :
    (A : 1Delooping → Type ℓ)
    (Apt : A ⋆) →
    ((a : ⟨ P ⟩) → PathP (λ i → cong A (gen a) i) Apt Apt) →
    (x : 1Delooping) → A x
  1Delooping-elim A Apt Agen ⋆ = Apt
  1Delooping-elim A Apt Agen (gen a i) = Agen a i
  postulate
    
    
    isGroupoid1Delooping : isGroupoid 1Delooping
  
  Ω1Delooping : Group ℓ
  Ω1Delooping = makeGroup {G = ⋆ ≡ ⋆} refl _∙_ sym (isGroupoid1Delooping ⋆ ⋆) assoc (λ p → sym (rUnit p)) (λ p → sym (lUnit p)) rCancel lCancel
  
  gen* : GroupHom (P *) Ω1Delooping
  gen* = FG.rec gen
  
  path : (u : ⟨ P * ⟩) → ⋆ ≡ ⋆
  path = fst gen*
  
  data Delooping : Type ℓ where
    inj : 1Delooping → Delooping
    rel : (r : Rel) → cong inj (path (src r)) ≡ cong inj (path (tgt r))
    gpd : isGroupoid Delooping
  
  pathD :
    (A : Delooping → Type ℓ)
    (Apt : A (inj ⋆))
    (Agen : (a : ⟨ P ⟩) → PathP (λ i → A (inj (gen a i))) Apt Apt) →
    (u : ⟨ P * ⟩) → PathP (λ i → A (inj (path u i))) Apt Apt
  pathD A Apt Agen u = cong (1Delooping-elim (λ x → A (inj x)) Apt Agen) (path u)
  
  Delooping-elim :
    (A : Delooping → Type ℓ)
    (Apt : A (inj ⋆))
    (Agen : (a : ⟨ P ⟩) → PathP (λ i → A (inj (gen a i))) Apt Apt)
    (Arel : (r : Rel) → PathP (λ i → PathP (λ j → A (rel r i j)) Apt Apt) (pathD A Apt Agen (src r)) (pathD A Apt Agen (tgt r))) →
    ((x : Delooping) → isGroupoid (A x)) → (x : Delooping) → A x
  Delooping-elim A Apt Agen Arel Agpd (inj x) = 1Delooping-elim (λ x → A (inj x)) Apt Agen x
  Delooping-elim A Apt Agen Arel Agpd (rel r i j) = Arel r i j
  Delooping-elim A Apt Agen Arel Agpd (gpd x y p q P Q i j k) = isOfHLevel→isOfHLevelDep 3 Agpd (f x) (f y) (cong f p) (cong f q) (cong (cong f) P) (cong (cong f) Q) (gpd x y p q P Q) i j k
    where
    f = Delooping-elim A Apt Agen Arel Agpd
  open import Cubical.HITs.EilenbergMacLane1 as EM
  
  
  theorem : Delooping ≃ EM₁ ∣ P ∣
  theorem = isoToEquiv e
    where
    
    f1 : 1Delooping → EM₁ ∣ P ∣
    f1 ⋆ = embase
    f1 (gen a i) = emloop [ η a ] i
    
    pathToEM : (u : ⟨ P * ⟩) → cong f1 (path u) ≡ emloop [ u ]
    pathToEM = FG.elimProp
      {B = λ u → cong f1 (path u) ≡ emloop [ u ]}
      (λ _ → emsquash _ _ _ _)
      (λ a → refl)
      (λ u v p q → 
         cong f1 (path u ∙ path v)                     ≡⟨ cong-∙ f1 (path u) (path v) ⟩
         cong f1 (path u) ∙ cong f1 (path v)           ≡⟨ cong₂ _∙_ p q ⟩
         emloop [ u ] ∙ emloop [ v ]                   ≡⟨ sym (emloop-comp ∣ P ∣ [ u ] [ v ]) ⟩
         emloop (GroupStr._·_ (snd ∣ P ∣) [ u ] [ v ]) ≡⟨ refl ⟩
         emloop [ u · v ]                              ∎
      )
      (sym (emloop-1g ∣ P ∣))
      (λ u p → 
         cong f1 (sym (path u)) ≡⟨ refl ⟩
         sym (cong f1 (path u)) ≡⟨ cong sym p ⟩
         sym (emloop [ u ])     ≡⟨ sym (emloop-sym ∣ P ∣ [ u ]) ⟩
         emloop [ inv u ]       ∎
      )
    
    f1rel : (r : Rel) → cong f1 (path (src r)) ≡ cong f1 (path (tgt r))
    f1rel r = pathToEM (src r) ∙ cong emloop (eq/ _ _ (fcBase r)) ∙ sym (pathToEM (tgt r))
    
    f : Delooping → EM₁ ∣ P ∣
    f (inj x) = f1 x
    f (rel r i j) = f1rel r i j
    f (gpd x y p q P Q i j k) = emsquash (f x) (f y) (cong f p) (cong f q) (λ i j → f (P i j)) (λ i j → f (Q i j)) i j k
    
    Ωgroup : {ℓ : Level} (A : Pointed ℓ) → isGroupoid ⟨ A ⟩ → Group ℓ
    Ωgroup A Gpd = makeGroup {G = pt A ≡ pt A} refl (λ p q → p ∙ q) sym (Gpd _ _) GL.assoc (λ p → sym (GL.rUnit p)) (λ p → sym (GL.lUnit p)) GL.rCancel GL.lCancel
    
    ΩBP : Group ℓ
    ΩBP = Ωgroup (Delooping , inj ⋆) gpd
    
    injGrp : GroupHom Ω1Delooping ΩBP
    injGrp = (cong inj) , igh
      where
      open IsGroupHom
      igh : IsGroupHom (snd Ω1Delooping) (cong inj) (snd ΩBP)
      pres· igh = cong-∙ inj
      pres1 igh = refl
      presinv igh p = refl
    
    loopHom : GroupHom (∣ P ∣) ΩBP
    loopHom = quotientRec _ (str P) (compGroupHom gen* injGrp) rel
    
    loop : ⟨ ∣ P ∣ ⟩ → ⟨ ΩBP ⟩
    loop = fst loopHom
    
    loop· : (u v : ⟨ ∣ P ∣ ⟩) → loop (u ·P v) ≡ loop u ∙ loop v
    loop· = IsGroupHom.pres· (snd loopHom)
    
    g : EM₁ ∣ P ∣ → Delooping
    g = EM.elimGroupoid
      ∣ P ∣
      (λ _ → gpd)
      (inj ⋆)
      (λ u → loop u)
      (λ u v → toPathP (lem u v))
      where
      lem : (u v : ⟨ ∣ P ∣ ⟩) → transport (λ i → loop u i ≡ loop (u ·P v) i) refl ≡ loop v
      lem u v =
        transport (λ i → loop u i ≡ loop (u ·P v) i) refl ≡⟨ subst2≡ (loop u) (loop (u ·P v)) refl ⟩
        sym (loop u) ∙ refl ∙ loop (u ·P v)               ≡⟨ cong (λ p → sym (loop u) ∙ p) (sym (lUnit _)) ⟩
        sym (loop u) ∙ loop (u ·P v)                      ≡⟨ cong (λ p → sym (loop u) ∙ p) (loop· u v) ⟩
        sym (loop u) ∙ (loop u ∙ loop v)                  ≡⟨ assoc _ _ _ ⟩
        (sym (loop u) ∙ loop u) ∙ loop v                  ≡⟨ cong (λ p → p ∙ loop v) (lCancel (loop u)) ⟩
        refl ∙ loop v                                     ≡⟨ sym (lUnit (loop v)) ⟩
        loop v                                            ∎
    fg : (x : EM₁ ∣ P ∣) → f (g x) ≡ x
    fg = EM.elimSet ∣ P ∣ (λ x → emsquash (f (g x)) x) refl λ x → toPathP (lem x)
      where
      lem' : (x : ⟨ ∣ P ∣ ⟩) → cong f (loop x) ≡ emloop x
      lem' = SQ.elimProp (λ _ → emsquash _ _ _ _) word
        where
        word : (u : ⟨ P * ⟩) → cong f (loop [ u ]) ≡ emloop [ u ]
        word = FG.elimProp
          (λ _ → emsquash _ _ _ _)
          (λ _ → refl)
          (λ u v p q → 
            cong f (loop [ u · v ])                   ≡⟨ refl ⟩
            cong f (loop ([ u ] ·P [ v ]))            ≡⟨ cong (cong f) (loop· [ u ] [ v ]) ⟩
            cong f (loop [ u ] ∙ loop [ v ])          ≡⟨ cong-∙ f (loop [ u ]) (loop [ v ]) ⟩
            cong f (loop [ u ]) ∙ cong f (loop [ v ]) ≡⟨ cong₂ _∙_ p q ⟩
            emloop [ u ] ∙ emloop [ v ]               ≡⟨ sym (emloop-comp _ [ u ] [ v ]) ⟩
            emloop ([ u ] ·P [ v ])                   ≡⟨ refl ⟩
            emloop [ u · v ]                          ∎
          )
          (sym (emloop-1g ∣ P ∣))
          (λ u p → 
            cong f (loop [ inv u ])                 ≡⟨ refl ⟩
            cong f (loop (invP [ u ]))              ≡⟨ refl ⟩
            cong f (sym (loop [ u ]))               ≡⟨ refl ⟩
            sym (cong f (loop [ u ]))               ≡⟨ cong sym p ⟩
            sym (emloop [ u ])                      ≡⟨ sym (emloop-sym _ [ u ]) ⟩
            emloop (invP [ u ])                     ≡⟨ refl ⟩
            emloop [ inv u ]                        ∎
          )
      lem : (x : ⟨ ∣ P ∣ ⟩) → subst2 _≡_ (cong (f ∘ g) (emloop x)) (emloop x) refl ≡ refl
      lem x =
        subst2 _≡_ (cong (f ∘ g) (emloop x)) (emloop x) refl ≡⟨ subst2≡Refl (cong (f ∘ g) (emloop x)) (emloop x) ⟩
        sym (cong (f ∘ g) (emloop x)) ∙ emloop x             ≡⟨ refl ⟩
        sym (cong f (cong g (emloop x))) ∙ emloop x          ≡⟨ refl ⟩
        sym (cong f (loop x)) ∙ emloop x                     ≡⟨ cong₂ _∙_ (cong sym (lem' x)) refl ⟩
        sym (emloop x) ∙ emloop x                            ≡⟨ lCancel _ ⟩
        refl                                                 ∎
    gf : (x : Delooping) → g (f x) ≡ x
    gf = Delooping-elim (λ x → g (f x) ≡ x) refl gf-gen gf-rel (λ x → isSet→isGroupoid (gpd (g (f x)) x))
      where
      gf-gen' : (a : ⟨ P ⟩) → cong g (emloop [ η a ]) ≡ cong inj (gen a)
      gf-gen' a =
        cong g (emloop [ η a ]) ≡⟨ refl ⟩
        loop [ η a ]            ≡⟨ refl ⟩
        cong inj (gen a)        ∎
      gf-gen : (a : ⟨ P ⟩) → PathP (λ i → g (f1 (gen a i)) ≡ inj (gen a i)) refl refl
      gf-gen a i j = gf-gen' a j i
      gf-rel' : (r : Rel) → Cube
        (pathD (λ x → g (f x) ≡ x) refl gf-gen (src r))
        (pathD (λ x → g (f x) ≡ x) refl gf-gen (tgt r))
        refl
        refl
        (cong (cong g) (f1rel r))
        (rel r)
      gf-rel' r = isGroupoid→isGroupoid' gpd _ _ _ _ _ _
      gf-rel : (r : Rel) →
        PathP (λ i → PathP (λ j → g (f1rel r i j) ≡ rel r i j) refl refl)
          (pathD (λ x → g (f x) ≡ x) refl gf-gen (src r))
          (pathD (λ x → g (f x) ≡ x) refl gf-gen (tgt r))
      gf-rel r = gf-rel' r
    open Iso
    e : Iso Delooping (EM₁ ∣ P ∣)
    fun e = f
    Iso.inv e = g
    rightInv e = fg
    leftInv e = gf