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

open import lib.Basics
open import lib.types.Group
open import lib.NType2
open import lib.types.EilenbergMacLane1.Core

module lib.types.EilenbergMacLane1.DoubleElim where

private

  emloop-emloop-helper :  {i j k} {A : Type i} {B : Type j} {C : A  B  Type k}
    {a : A} (p : a == a)
    {b₀ b₁ : B} (q : b₀ == b₁)
    (f : (b : B)  C a b)
    (g : (b : B)  C a b)
    (r₀ : f b₀ == g b₀ [  a  C a b₀)  p ])
    (r₁ : f b₁ == g b₁ [  a  C a b₁)  p ])
     ↓-ap-in  Z  Z)  a  C a b₀) r₀ ∙'ᵈ ↓-ap-in  Z  Z) (C a) (apd g q) ==
      ↓-ap-in  Z  Z) (C a) (apd f q) ∙ᵈ ↓-ap-in  Z  Z)  a  C a b₁) r₁
        [  p  f b₀ == g b₁ [  Z  Z)  p ])  ap-comm' C p q ]
     r₀ == r₁ [  b  f b == g b [  a  C a b)  p ])  q ]
  emloop-emloop-helper {C = C} p {b₀} idp f g r₀ r₁ s =
    –>-is-inj (↓-ap-equiv  Z  Z)  a  C a b₀)) r₀ r₁ $
    (! (▹idp (↓-ap-in  Z  Z)  a  C a b₀) r₀)) 
    s 
    idp◃ (↓-ap-in  Z  Z)  a  C a b₀) r₁))

module _ {i j} (G : Group i) (H : Group j) where

  private
    module G = Group G
    module H = Group H

  module EM₁Level₁DoubleElim {k} {P : EM₁ G  EM₁ H  Type k}
    {{P-level :  {x} {y}  has-level 1 (P x y)}}
    (embase-embase* : P embase embase)
    (embase-emloop* :  h  embase-embase* == embase-embase* [ P embase  emloop h ])
    (emloop-embase* :  g  embase-embase* == embase-embase* [  x  P x embase)  emloop g ])
    (embase-emloop-comp* :  h₁ h₂ 
      embase-emloop* (H.comp h₁ h₂) == embase-emloop* h₁ ∙ᵈ embase-emloop* h₂
        [  p  embase-embase* == embase-embase* [ P embase  p ])  emloop-comp h₁ h₂ ])
    (emloop-comp-embase* :  g₁ g₂ 
      emloop-embase* (G.comp g₁ g₂) == emloop-embase* g₁ ∙ᵈ emloop-embase* g₂
        [  p  embase-embase* == embase-embase* [  x  P x embase)  p ])  emloop-comp g₁ g₂ ])
    (emloop-emloop* :  g h 
      ↓-ap-in  Z  Z)  a  P a embase) (emloop-embase* g) ∙'ᵈ
      ↓-ap-in  Z  Z) (P embase) (embase-emloop* h)
      ==
      ↓-ap-in  Z  Z) (P embase) (embase-emloop* h) ∙ᵈ
      ↓-ap-in  Z  Z)  a  P a embase) (emloop-embase* g)
        [  p  embase-embase* == embase-embase* [  Z  Z)  p ])  ap-comm' P (emloop g) (emloop h) ])
    where

    private
      module Embase =
        EM₁Level₁Elim {P = P embase}
                      {{P-level {embase}}}
                      embase-embase*
                      embase-emloop*
                      embase-emloop-comp*

      P' : embase' G == embase  EM₁ H  Type k
      P' p y = Embase.f y == Embase.f y [  x  P x y)  p ]

      P'-level :  p y  has-level 0 (P' p y)
      P'-level _ y = ↓-level (P-level {embase} {y})

      emloop-emloop** :  g h  emloop-embase* g == emloop-embase* g [ P' (emloop g)  emloop h ]
      emloop-emloop** g h =
        emloop-emloop-helper
          {C = P}
          (emloop g) (emloop h)
          Embase.f Embase.f
          (emloop-embase* g) (emloop-embase* g)
          (transport!
             u 
              ↓-ap-in  Z  Z)  a  P a embase) (emloop-embase* g) ∙'ᵈ
              ↓-ap-in  Z  Z) (P embase) u
              ==
              ↓-ap-in  Z  Z) (P embase) u ∙ᵈ
              ↓-ap-in  Z  Z)  a  P a embase) (emloop-embase* g)
                [  p  embase-embase* == embase-embase* [  Z  Z)  p ])  ap-comm' P (emloop g) (emloop h) ])
            (Embase.emloop-β h)
            (emloop-emloop* g h))

      module Emloop (g : G.El) =
        EM₁SetElim {P = P' (emloop g)}
                   {{λ {x}  P'-level (emloop g) x}}
                   (emloop-embase* g)
                   (emloop-emloop** g)

      module EmloopComp (g₁ g₂ : G.El) =
        EM₁PropElim {P = λ y  Emloop.f (G.comp g₁ g₂) y ==
                               Emloop.f g₁ y ∙ᵈ Emloop.f g₂ y
                                 [  x  P' x y)  emloop-comp g₁ g₂ ]}
                    {{λ {y}  ↓-level (P'-level _ y)}}
                    (emloop-comp-embase* g₁ g₂)

      module DoubleElim (y : EM₁ H) =
        EM₁Level₁Elim {P = λ x  P x y}
                      {{λ {x}  P-level {x} {y}}}
                      (Embase.f y)
                       g  Emloop.f g y)
                       g₁ g₂  EmloopComp.f g₁ g₂ y)

    abstract
      f :  x y  P x y
      f x y = DoubleElim.f y x