\import Algebra.Meta
\import Equiv
\import Equiv.Sigma
\import Equiv.Univalence
\import Function
\import Homotopy.Pointed
\import Homotopy.Square
\import Paths

\class Pushout (square : Square)
  | pushout-univ {Z : \Type} : Equiv {square.Y -> Z} {Square { | Y => Z | U => square.U | V => square.V | X => square.X | uv => square.uv | ux => square.ux }} (Square.push square)
  \where {
    -- | Pushouts are functorial
    \func map (p : Pushout) (s : Square)
              (f : p.square.U -> s.U)
              (g : p.square.V -> s.V)
              (h : p.square.X -> s.X)
              (e1 : \Pi (u : p.square.U) -> s.uv (f u) = g (p.square.uv u))
              (e2 : \Pi (u : p.square.U) -> s.ux (f u) = h (p.square.ux u))
              : p.square.Y -> s.Y
      => p.pushout-univ.ret (\new Square {
        | vy v => s.vy (g v)
        | xy x => s.xy (h x)
        | sqcomm u => pmap s.vy (inv (e1 u)) <* s.sqcomm (f u) *> pmap s.xy (e2 u)
      })

    -- | {map} preserves identity maps
    \func map_id (p : Pushout) (y : p.square.Y) : map p p.square id id id (\lam _ => idp) (\lam _ => idp) = id
      => p.pushout-univ.ret_f (\lam x => x)
  }

\data PushoutData {A B C : \Type} (f : A -> B) (g : A -> C)
  | pinl B
  | pinr C
  | pglue (a : A) : pinl (f a) = pinr (g a)
  \where {
    \func ppglue {A B C : \Type} {f : A -> B} {g : A -> C} (a : A) : pinl (f a) = {PushoutData f g} pinr (g a) => path (pglue a)

    \func rec {A B C : \Type} {f : A -> B} {g : A -> C} {Z : \Type} (lm : B -> Z) (rm : C -> Z) (gm : \Pi (a : A) -> lm (f a) = rm (g a)) (x : PushoutData f g) : Z \elim x
      | pinl b => lm b
      | pinr c => rm c
      | pglue a => gm a
      \where {
        \func map {A B C : \Type} {f : A -> B} {g : A -> C} {Y Z : \Type} (h : Y -> Z) (lm : B -> Y) (rm : C -> Y) (gm : \Pi (a : A) -> lm (f a) = rm (g a)) (x : PushoutData f g)
          : h (rec lm rm gm x) = rec (h `o` lm) (h `o` rm) (\lam a => pmap h (gm a)) x \elim x
          | pinl b => idp
          | pinr c => idp
          | pglue a i => idp

        \func equiv {A B C : \Type} {f : A -> B} {g : A -> C} (Z : \Type)
          : Equiv {PushoutData f g -> Z} {\Sigma (lm : B -> Z) (rm : C -> Z) (gm : \Pi (a : A) -> lm (f a) = rm (g a))}
          => \new QEquiv {
            | f h => (\lam b => h (pinl b), \lam c => h (pinr c), \lam a => path (\lam i => h (pglue a i)))
            | ret p => rec p.1 p.2 p.3
            | ret_f h => path (\lam i x => (\case \elim x \return rec (\lam b => h (pinl b)) (\lam c => h (pinr c)) (\lam a => path (\lam i => h (pglue a i))) x = h x \with {
              | pinl b => idp
              | pinr c => idp
              | pglue a i => idp
            }) @ i)
            | f_sec => idpe
          }
      }

    -- | The total space of a fibration over a pushout is equivalent to a pushout of fibers of this fibration.
    \func flattening {A B C : \Type} {f : A -> B} {g : A -> C} (F : PushoutData f g -> \Type)
      : total = (\Sigma (w : PushoutData f g) (F w))
      => QEquiv-to-= totalPushoutLeft *> path TotalPushout *> QEquiv-to-= totalPushoutRight
      \where {
        \func total
          => PushoutData {\Sigma (x : A) (F (pinl (f x)))}
                         {\Sigma (y : B) (F (pinl y))}
                         {\Sigma (z : C) (F (pinr z))}
                         (\lam p => (f p.1, p.2))
                         (\lam p => (g p.1, transport F (ppglue p.1) p.2))

        \data TotalPushout (j : I)
          | tinl (y : B) (F (pinl y))
          | tinr (z : C) (F (pinr z))
          | tglue (x : A) (i : I) (w : F (pglue x (I.squeeze i j))) \elim i {
            | left => tinl (f x) w
            | right => tinr (g x) (coe2 (\lam i => F (pglue x i)) j w right)
          }

        \func totalPushoutLeft : QEquiv {total} {TotalPushout left} \cowith
          | f => LR
          | ret => RL
          | ret_f t => \case \elim t \with {
            | pinl (y,t) => idp
            | pinr (z,t) => idp
            | pglue (x,t) i => idp
          }
          | f_sec t => \case \elim t \with {
            | tinl y t => idp
            | tinr y t => idp
            | tglue x i t => idp
          }
          \where {
            \func LR (t : total) : TotalPushout left \elim t
              | pinl (y,t) => tinl y t
              | pinr (z,t) => tinr z t
              | pglue (x,t) i => tglue x i t

            \func RL (t : TotalPushout left) : total \elim t
              | tinl y t => pinl (y,t)
              | tinr z t => pinr (z,t)
              | tglue x i t => pglue (x,t) i
          }

        \func totalPushoutRight : QEquiv {TotalPushout right} {\Sigma (w : PushoutData f g) (F w)} \cowith
          | f => LR
          | ret => RL
          | ret_f t => \case \elim t \with {
            | tinl y t => idp
            | tinr z t => idp
            | tglue x i t => idp
          }
          | f_sec t => \case \elim t \with {
            | (pinl y, t) => idp
            | (pinr z, t) => idp
            | (pglue x i, t) => idp
          }
          \where {
            \func LR (t : TotalPushout right) : \Sigma (w : PushoutData f g) (F w) \elim t
              | tinl y t => (pinl y, t)
              | tinr z t => (pinr z, t)
              | tglue x i t => (pglue x i, t)

            \func RL (t : \Sigma (w : PushoutData f g) (F w)) : TotalPushout right \elim t
              | (pinl y, t) => tinl y t
              | (pinr z, t) => tinr z t
              | (pglue x i, t) => tglue x i t
          }
      }
  }

\open PushoutData

\instance PushoutPointed {A B C : \Type} {P : Pointed B} {f : A -> B} {g : A -> C} : Pointed (PushoutData f g)
  | base => pinl base

\func pushoutData {A B C : \Type} (f : A -> B) (g : A -> C) : Pushout \cowith
  | square {
    | U => A
    | V => B
    | X => C
    | Y => PushoutData f g
    | ux => g
    | vy => pinl
    | uv => f
    | xy => pinr
    | sqcomm a => path (pglue a)
  }
  | pushout-univ {Z} => \new QEquiv {
    | ret (s : Square) => rec s.vy s.xy s.sqcomm
    | ret_f h => path (\lam i p => (\case \elim p \return rec (h `o` pinl) (h `o` pinr) (\lam a => path (\lam j => h (pglue a j))) p = h p \with {
      | pinl _ => idp
      | pinr _ => idp
      | pglue _ _ => idp
    }) @ i)
    | f_sec _ => idp
  }

-- | A pushout of an embedding is an embedding and such a pushout square is also a pullback square
\class EmbeddingPushout \noclassifying (A B C : \Type) (f : A -> B) (g : Embedding {A} {C}) {
  \func PO => PushoutData f g

  \func code (b0 : B) (w : PO) : \hType \elim w
    | pinl b => b0 = b
    | pinr c => \Sigma (a : A) (b0 = f a) (c = g a)
    | pglue a => QEquiv-to-= (equiv b0 a)

  \lemma equiv (b0 : B) (a : A) : Equiv {b0 = f a} {\Sigma (a' : A) (b0 = f a') (g a = g a')} (a, __, idp) => equation
                                                                  {symQEquiv (contr-left (lsigma a))}
    (\Sigma (p : \Sigma (a' : A) (a = a')) (b0 = f p.1))          {sigma-left (sigma-right (g.pmap-isEquiv {a} {__}))}
    (\Sigma (p : \Sigma (a' : A) (g a = g a')) (b0 = f p.1))  {\new QEquiv { | B => \Sigma (a' : A) (b0 = f a') (g a = g a') | f p => (p.1.1, p.2, p.1.2) | ret q => ((q.1,q.3),q.2) | ret_f => idpe | f_sec => idpe }}

  \func encode (b0 : B) {w : PO} (p : pinl b0 = w) : code b0 w => transport (code b0) p idp

  \func decode (b0 : B) {w : PO} (d : code b0 w) : pinl b0 = w \elim w
    | pinl b => pmap pinl d
    | pinr c => pmap pinl d.2 *> ppglue d.1 *> inv (pmap pinr d.3)
    | pglue a i =>
        \let t => transport_pi2 (code b0) (pinl b0 =) (ppglue a) (pmap pinl) (\lam x => pmap pinl x.2 *> ppglue x.1 *> inv (pmap pinr x.3))
                                (\lam p => Jl (\lam _ q => transport (pinl b0 =) q (pmap pinl p) = pmap pinl p *> q) idp (ppglue a))
        \in (pathOver t @ i) d

  \func encode_decode-left (b0 b : B) (d : code b0 (pinl b)) : encode b0 {pinl b} (decode b0 d) = d \elim d
    | idp => idp

  \func encode_decode-right (b0 : B) (c : C) (d : code b0 (pinr c)) : encode b0 {pinr c} (decode b0 d) = d \elim d
   | (a,idp,idp) => pmap (encode (f a)) (idp_*> (ppglue a))

  \func decode_enode (b0 : B) {w : PO} (p : pinl b0 = w) : decode b0 (encode b0 p) = p \elim p
    | idp => idp

  \lemma pushout-embedding : Embedding {B} {PO} pinl \cowith
    | isEmb b b' => \new Retraction {
      | sec => encode b
      | f_sec => decode_enode b
    }

  \lemma pullback-path-equiv (b : B) (c : C) : Equiv {\Sigma (a : A) (b = f a) (c = g a)} {pinl b = {PO} pinr c} (decode b)
    => \new QEquiv {
      | ret => encode b
      | ret_f => encode_decode-right b c
      | f_sec => decode_enode b
    }
}