\import Equiv
\import Equiv.Univalence
\import Function
\import HLevel
\import Homotopy.Cube
\import Homotopy.Pushout
\import Homotopy.Sphere
\import Homotopy.Suspension
\import Logic
\import Paths

\func Join (A B : \Type) => PushoutData {\Sigma A B} __.1 __.2
  \where {
    \use \level levelProp {A B : \Prop} : isProp (Join A B) => isContr'=>isProp {Join A B} (\case __ \with {
      | pinl a => transport (Contr __) (QEquiv-to-= (Join-sym B A)) (Join_Contr B (isProp=>isContr prop-isProp a))
      | pinr b => Join_Contr A (isProp=>isContr prop-isProp b)
    })

    \lemma from-|| {A B : \Prop} (x : A || B) : Join A B \elim x
      | byLeft a => jinl a
      | byRight b => jinr b
  }

\cons jinl {A B : \Type} (a : A) : Join A B => pinl a

\cons jinr {A B : \Type} (b : B) : Join A B => pinr b

\func jglue {A B : \Type} (a : A) (b : B) : jinl a = jinr b => path (pglue (a,b))

\lemma Join_Contr (A : \Type) (B : Contr) : Contr (Join A B) \cowith
  | center => jinr B.center
  | contraction x => \case \elim x \with {
    | jinl a => inv (jglue a B.center)
    | jinr b => pmap jinr (B.contraction b)
    | pglue (a,b) => Cube2.map
        (inv (jglue a B.center))      -- top edge
        (pmap jinr (B.contraction b)) -- bottom edge
        (idpe (jinr B.center))        -- left edge
        (jglue a b)                   -- right edge
        (Jl (\lam x p => inv (jglue a B.center) = pmap jinr p *> inv (jglue a x)) (inv (idp_*> _)) (B.contraction b))
  }

\func Join_Sphere0 (A : \Type) : QEquiv {Join (Sphere 0) A} {Susp A} \cowith
  | f => joinSusp
  | ret => suspJoin
  | ret_f => joinSuspJoin
  | f_sec => suspJoinSusp
  \where {
    \func joinSusp {A : \Type} (j : Join (Sphere 0) A) : Susp A
      | jinl north => north
      | jinl south => south
      | jinl (pglue () _)
      | jinr _ => south
      | pglue (north, a) i => pglue a i
      | pglue (south, _) _ => south
      | pglue (pglue () _, _) _

    \func suspJoin {A : \Type} (s : Susp A) : Join (Sphere 0) A
      | north => jinl north
      | south => jinl south
      | pglue a => jglue north a *> inv (jglue south a)

    \func joinSuspJoin {A : \Type} (j : Join (Sphere 0) A) : suspJoin (joinSusp j) = j
      | jinl north => idp
      | jinl south => idp
      | jinl (pglue () _)
      | jinr a => jglue south a
      | pglue (north, a) j => path (Cube2.map
          (jglue north a *> inv (jglue south a)) -- top edge
          (jglue north a)                        -- bottom edge
          (idpe (jinl north))                    -- left edge
          (jglue south a)                        -- right edge
          idp @ __ @ j)
      | pglue (south, a) j => path (\lam i => pglue (south,a) (I.squeeze i j))
      | pglue (pglue () _, _) _

    \func suspJoinSusp {A : \Type} (s : Susp A) : joinSusp (suspJoin s) = s
      | north => idp
      | south => idp
      | pglue a i =>
        \have t =>
          pmap joinSusp (jglue north a *> inv (jglue south a)) ==< pmap_*>-comm joinSusp (jglue north a) (inv (jglue south a)) >==
          pmerid a *> pmap joinSusp (inv (jglue south a))      ==< pmap (pmerid a *>) (pmap_inv-comm joinSusp (jglue south a)) >==
          pmerid a                                             `qed
        \in path (t @ __ @ i)
  }

\func Join-sym (A B : \Type) : QEquiv {Join A B} {Join B A} \cowith
  | f => flip
  | ret => flip
  | ret_f => flip-flip
  | f_sec => flip-flip
  \where {
    \func flip {A B : \Type} (j : Join A B) : Join B A
      | jinl a => jinr a
      | jinr b => jinl b
      | pglue (a,b) => inv (jglue b a)

    \func flip-flip {A B : \Type} (j : Join A B) : flip (flip j) = j
      | jinl a => idp
      | jinr b => idp
      | pglue (a,b) i =>
        \have p => pmap_inv-comm flip (jglue b a) *> inv_inv (jglue a b)
        \in path (p @ __ @ i)
  }

\func Join-assoc (A B C : \Type) : QEquiv {Join (Join A B) C} {Join A (Join B C)} \cowith
  | f => leftToRight
  | ret => rightToLeft
  | ret_f => leftRightLeft
  | f_sec => rightLeftRight
  \where {
    \open I

    \func leftToRight {A B C : \Type} (j : Join (Join A B) C) : Join A (Join B C)
      | jinl (jinl a) => jinl a
      | jinl (jinr b) => jinr (jinl b)
      | jinl (pglue (a,b) j) => pglue (a, jinl b) j
      | jinr c => jinr (jinr c)
      | pglue (jinl a, c) i => pglue (a, jinr c) i
      | pglue (jinr b, c) i => jinr (pglue (b,c) i)
      | pglue (pglue (a,b), c) =>
          Jl.def (\lam x p => Cube2 (jglue a x) (pmap jinr p) (jglue a (jinl b)) (idpe (jinr x)))
                 (path (\lam j => path (\lam i => pglue (a, jinl b) (squeezeR j i))))
                 (jglue b c)

    \func rightToLeft {A B C : \Type} (j : Join A (Join B C)) : Join (Join A B) C
      | jinl a => jinl (jinl a)
      | jinr (jinl b) => jinl (jinr b)
      | jinr (jinr c) => jinr c
      | jinr (pglue (b,c) j) => pglue (jinr b, c) j
      | pglue (a, jinl b) i => jinl (pglue (a,b) i)
      | pglue (a, jinr c) i => pglue (jinl a, c) i
      | pglue (a, pglue (b,c)) =>
          Jl.def (\lam x p => Cube2 (pmap jinl p) (jglue (jinl a) c) (idpe (jinl (jinl a))) (jglue x c))
                 (path (\lam j => path (\lam i => pglue (jinl a, c) (squeeze j i))))
                 (jglue a b)

    \func leftRightLeft {A B C : \Type} (j : Join (Join A B) C) : rightToLeft (leftToRight j) = j
      | jinl (jinl a) => idp
      | jinl (jinr b) => idp
      | jinl (pglue (a,b) j) => idp
      | jinr c => idp
      | pglue (jinl a, c) i => idp
      | pglue (jinr b, c) i => idp
      | pglue (pglue (a,b) j, c) i =>
        \let | s1 {y} (q : jinl b = y) : Cube2 (pmap rightToLeft (jglue a y))
                                               (pmap (\lam t => rightToLeft (jinr t)) q)
                                               (pmap jinl (jglue a b))
                                               (idpe (rightToLeft (jinr y)))
                 => path (\lam j => path (\lam i =>
                     rightToLeft (Jl.def (\lam x p => Cube2 (jglue a x) (pmap jinr p) (jglue a (jinl b)) (idpe (jinr x)))
                                         (path (\lam j => path (\lam i => pglue (a, jinl b) (squeezeR j i))))
                                          q @ j @ i)))
             | s2 {y} (q : jinl b = y) =>
                 Jl.def (\lam x p => Cube2 (pmap rightToLeft (jglue a x))
                                           (pmap (\lam t => rightToLeft (jinr t)) p)
                                           (pmap jinl (jglue a b))
                                           (idpe (rightToLeft (jinr x))))
                        (path (\lam j => path (\lam i => jinl (pglue (a,b) (squeezeR j i)))))
                        q
             | proof => coe (\lam n =>
                 coe (\lam k => Cube2 (coe (\lam l => Cube2 (path (\lam m => jinl (pglue (a,b) (squeeze (squeeze l m) n))))
                                                            (jglue (jinl a) c)
                                                            (idpe (jinl (jinl a)))
                                                            (jglue (pglue (a,b) (squeeze l n)) c))
                                           (path (\lam j => path (\lam i => pglue (jinl a, c) (squeeze j i))))
                                           right @ k)
                                      (path (\lam j => pglue (pglue (a,b) n, c) (squeeze k j)))
                                      (path (\lam m => jinl (pglue (a,b) (squeeze m n))))
                                      (idpe (pglue (pglue (a,b) n, c) k)))
                     (path (\lam j => path (\lam i => jinl (pglue (a,b) (squeeze (squeezeR j i) n)))))
                     right
                 = path (\lam j => path (pglue (pglue (a,b) (squeeze j n), c)))
               ) (Jl (\lam y q => Jl.def (\lam x p => Cube2 p p idp idp) idp q = idp) idp (jglue (jinl a) c)) right
        \in path ((Jl (\lam y q => s1 q = s2 q) idp (jglue b c) *> proof) @ __ @ j @ i)

    \func rightLeftRight {A B C : \Type} (j : Join A (Join B C)) : leftToRight (rightToLeft j) = j
      | jinl a => idp
      | jinr (jinl b) => idp
      | jinr (jinr c) => idp
      | jinr (pglue (b,c) j) => idp
      | pglue (a, jinl b) i => idp
      | pglue (a, jinr c) i => idp
      | pglue (a, pglue (b,c) j) i =>
        \let | s1 {y} (q : jinl a = y) : Cube2 (pmap (\lam t => leftToRight (jinl t)) q)
                                               (jglue a (jinr c))
                                               (idpe (jinl a))
                                               (pmap leftToRight (jglue y c))
                 => path (\lam j => path (\lam i =>
                     leftToRight (Jl.def (\lam x p => Cube2 (pmap jinl p) (jglue (jinl a) c) (idpe (jinl (jinl a))) (jglue x c))
                                         (path (\lam i => path (\lam j => pglue (jinl a, c) (squeeze i j))))
                                          q
                                  @ j @ i)))
             | s2 {y} (q : jinl a = y) =>
                 Jl.def (\lam x p => Cube2 (pmap (\lam t => leftToRight (jinl t)) p) (jglue a (jinr c)) (idpe (jinl a)) (pmap leftToRight (jglue x c)))
                        (path (\lam j => path (\lam i => pglue (a, jinr c) (squeeze j i))))
                         q
             | proof =>
                 Jl (\lam y q => coe (\lam k => Cube2 (path (\lam i => pglue (a, jinl b) (squeeze k i)))
                                                      (jglue a y)
                                                      (idpe (jinl a))
                                                      (Jl.def (\lam x p => Cube2 (jglue a x) (pmap jinr p) (jglue a (jinl b)) (idpe (jinr x)))
                                                              (path (\lam j => path (\lam i => pglue (a, jinl b) (squeezeR j i))))
                                                              q
                                                       @ k))
                                     (path (\lam j => path (\lam i => pglue (a,y) (squeeze j i))))
                                     right
                                 = path (\lam j => path (pglue (a, q @ j))))
                    (Jl (\lam y q => coe (\lam k => Cube2 (path (q @ squeeze k __))
                                                          q
                                                          (idpe (jinl a))
                                                          (path (q @ squeezeR k __)))
                                         (path (\lam j => path (q @ squeeze j __)))
                                         right
                                     = idpe q)
                        idp
                        (jglue a (jinl b)))
                    (jglue b c)
        \in path ((Jl (\lam y q => s1 q = s2 q) idp (jglue a b) *> proof) @ __ @ j @ i)
  }

\func Join_Susp (A B : \Type) : Join (Susp A) B = Susp (Join A B) =>
  Join (Susp A) B            ==< pmap (`Join B) (QEquiv-to-= (symQEquiv (Join_Sphere0 A))) >==
  Join (Join (Sphere 0) A) B ==< QEquiv-to-= (Join-assoc (Sphere 0) A B) >==
  Join (Sphere 0) (Join A B) ==< QEquiv-to-= (Join_Sphere0 (Join A B)) >==
  Susp (Join A B)            `qed

\func Join_Sphere (A : \Type) (n : Nat) : Join (Sphere n) A = iterr Susp (suc n) A \elim n
  | 0 => QEquiv-to-= (Join_Sphere0 A)
  | suc n => Join_Susp (Sphere n) A *> pmap Susp (Join_Sphere A n)