\import Category
\import Category.Adjoint
\import Category.Functor
\import Category.Product
\import Category.Slice
\import Data.Bool
\import Equiv
\import Function.Meta
\import HLevel
\import Logic
\import Logic.Meta
\import Meta
\import Paths
\import Paths.Meta
\import Set (in0)

\class Cone {J : SmallPrecat} {D : Precat} (G : Functor J D) (\classifying apex : D)
  | coneMap (j : J) : Hom apex (G j)
  | coneCoh {j j' : J} (h : Hom j j') : G.Func h  coneMap j = coneMap j'
  \where {
    \func map {C D : Precat} (F : Functor C D) {J : SmallPrecat} {G : Functor J C} (c : Cone G) : Cone (Comp F G) \cowith
      | apex => F c.apex
      | coneMap j => Func (c.coneMap j)
      | coneCoh h => inv Func-o *> pmap Func (c.coneCoh h)

    \func premap {J J' : SmallPrecat} (F : Functor J J') (c : Cone {J'} {}) : Cone (Comp c.G F) \cowith
      | apex => c.apex
      | coneMap j => c.coneMap (F j)
      | coneCoh h => c.coneCoh (F.Func h)

    \func mapEquiv {C D : Precat} (F : FullyFaithfulFunctor C D) {J : Precat} {G : Functor J C} (X : C) : QEquiv {Cone G X} {Cone (Comp F G) (F X)} (\lam c => map F c) \cowith
      | ret (c : Cone) => \new Cone {
        | coneMap j => F.inverse (c.coneMap j)
        | coneCoh h => F.isFaithful $ run {
          rewrite F.Func-o,
          repeat {2} (rewrite F.inverse-right),
          c.coneCoh h
        }
      }
      | ret_f c => exts (\lam j => F.inverse-left)
      | f_sec c => exts (\lam j => F.inverse-right)
  }

\func conePullback {J : SmallPrecat} {D : Precat} {F : Functor J D} (C : Cone F) (z : D) (f : Hom z C.apex) : Cone F z \cowith
  | coneMap j => coneMap j  f
  | coneCoh h => inv o-assoc *> pmap (`∘ f) (coneCoh h)

\class Limit \extends Cone {
  | isLimit (z : D) : Equiv (conePullback \this z)
  | limMap {z : D} (c : Cone G z) : Hom z apex
  | limBeta {z : D} (c : Cone G z) (j : J) : coneMap j  limMap c = c.coneMap j
  | limUnique {z : D} {f g : Hom z apex} (p : \Pi (j : J) -> coneMap j  f = coneMap j  g) : f = g

  \default limMap \as limMap-impl {z} c : Hom z apex
  => ret {isLimit z} c

  \default limBeta \as limBeta-impl {z} (c : Cone G z) j : coneMap j  limMap-impl c = c.coneMap j
  => path (\lam i => coneMap {Equiv.f_ret {isLimit z} c @ i} j)

  \default limUnique {z} {f} {g} p => Equiv.isInj {isLimit z} (exts p)

  \default isLimit z => \new QEquiv {
    | ret => limMap
    | ret_f f => limUnique (limBeta (conePullback _ z f))
    | f_sec c => exts (limBeta c)
  }

  \lemma limUniqueBeta {z : D} {c : Cone G z} {g : Hom z apex} (p : \Pi (j : J) -> c.coneMap j = coneMap j  g) : limMap c = g =>
    limUnique (\lam j => limBeta c j *> p j)
} \where {
  \use \level levelProp {J : SmallPrecat} {D : Precat} (G : Functor J D) (apex : D)
                        (cm : \Pi (j : J) -> Hom apex (G j)) (L L' : Limit G apex cm) : L = L' =>
    exts (\lam c => L.limUnique (\lam j => L.limBeta c j *> inv (L'.limBeta c j)))

  \lemma iso_lim {J : SmallPrecat} {D : Precat} {G : Functor J D} (c : Cone G) (L : Limit G) (e : Iso (L.limMap c)) : Limit { | Cone => c } \cowith
    | limMap c' => e.hinv  limMap c'
    | limBeta c' j => pmap (`∘ _) (inv (L.limBeta c j)) *> rewriteEq e.f_hinv (rewriteEq (L.limBeta c' j) idp)
    | limUnique {z} {f} {g} p => e.isMono $ L.limUnique (\lam j => inv o-assoc *> pmap (`∘ f) (L.limBeta c j) *> p j *> pmap (`∘ g) (inv (L.limBeta c j)) *> o-assoc)

  \lemma lim_iso {J : SmallPrecat} {D : Precat} {G : Functor J D} (L L' : Limit G) : Iso (L.limMap L') \cowith
    | hinv => L'.limMap L
    | hinv_f => limUnique \lam j => inv o-assoc *> pmap (`∘ _) (L'.limBeta L j) *> L.limBeta L' j *> inv id-right
    | f_hinv => limUnique \lam j => inv o-assoc *> pmap (`∘ _) (L.limBeta L' j) *> L'.limBeta L j *> inv id-right

  \func transFuncMap {D : Precat} (L L' : Limit { | D => D }) (H : Functor L.J L'.J) (a : NatTrans (Comp L'.G H) L.G) : Hom L' L =>
    limMap (\new Cone {
      | coneMap j => a j  L'.coneMap (H j)
      | coneCoh f => inv o-assoc *> pmap (`∘ _) (inv (a.natural f)) *> o-assoc *> pmap (_ ) (L'.coneCoh (H.Func f))
    })
}

\meta Colimit G => Limit (Functor.op {G})

\class Diagram (G : Graph) (D : Precat) (F : G -> D) (Func : \Pi {x y : G} -> G.E x y -> Hom (F x) (F y)) {
  \func functor : Functor G.FreeCat D \cowith
    | F => F
    | Func {x y : G} (h : G.Paths x y) : Hom (F x) (F y) \elim h {
      | Graph.empty idp => id (F x)
      | Graph.cons e h => Func h  Diagram.Func e
    }
    | Func-id => idp
    | Func-o {x y z : G} {q : G.Paths y z} {p : G.Paths x y} : Func (G.concat p q) = Func q  Func p \elim p {
      | Graph.empty idp => inv id-right
      | Graph.cons e p => rewrite Func-o o-assoc
    }
}

\class DiagramCone \extends Diagram, Cone
  | J => G.FreeCat
  | Cone.D => Diagram.D
  | Cone.G => Diagram.functor
  | diagramCoh {g g' : G} (e : G.E g g') : Func e  coneMap g = coneMap g'
  | coneCoh => coneCoh-lem apex coneMap diagramCoh
  \where {
    \lemma coneCoh-lem {D : Diagram} (apex : D.D) (coneMap : \Pi (j : G.FreeCat) -> Hom apex (D.functor j)) (p : \Pi {g g' : G} (e : G.E g g') -> Func e  coneMap g = coneMap g') {j j' : D.G.FreeCat} (h : G.FreeCat.Hom j j') : D.functor.Func h  coneMap j = coneMap j' \elim h
      | Graph.empty idp => id-left
      | Graph.cons e h => o-assoc *> pmap (_ ) (p e) *> coneCoh-lem apex coneMap p h

    \func pullback {G : Graph} {D : Diagram G} {C : DiagramCone { | Diagram => D }} (z : D.D) (f : Hom z C.apex) : DiagramCone \cowith
      | Diagram => D
      | apex => z
      | coneMap j => coneMap j  f
      | diagramCoh e => inv o-assoc *> pmap (`o f) (diagramCoh e)

    \func equiv {D : Diagram} (Z : D.D) : QEquiv {DiagramCone { | Diagram => D | apex => Z }} {Cone D.functor Z} (\lam d => d) \cowith
      | ret (c : Cone) => \new DiagramCone {
        | coneMap => c.coneMap
        | diagramCoh e => rewrite id-left in c.coneCoh (Graph.cons e (Graph.empty idp))
      }
      | ret_f d => idp
      | f_sec c => idp
  }

\class LimitDiagram \extends DiagramCone, Limit
  | isLimitDiagram (Z : D) : Equiv (DiagramCone.pullback Z)
  | isLimit Z => transEquiv (isLimitDiagram Z) (DiagramCone.equiv Z)

\class Product {J : \Type} {D : Precat} (G : J -> D) (\classifying apex : D) {
  | proj (j : J) : Hom apex (G j)
  | tupleMap {Z : D} (f : \Pi (j : J) -> Hom Z (G j)) : Hom Z apex
  | tupleBeta {Z : D} {f : \Pi (j : J) -> Hom Z (G j)} {j : J} : proj j  tupleMap f = f j
  | tupleEq {Z : D} {h1 h2 : Hom Z apex} : (\Pi (j : J) -> proj j  h1 = proj j  h2) -> h1 = h2

  \lemma isProduct (Z : D) : Equiv (\lam (h : Hom Z apex) => proj __  h) => \new QEquiv {
    | ret => tupleMap
    | ret_f h => tupleEq (\lam j => tupleBeta)
    | f_sec f => ext (\lam j => tupleBeta)
  }

  \lemma tupleMapComp {W Z : D} (f : \Pi (j : J) -> Hom Z (G j)) (h : Hom W Z) : tupleMap f  h = tupleMap (f __  h)
    => tupleEq \lam j => inv o-assoc *> pmap (`∘ h) tupleBeta *> inv tupleBeta

  \lemma tupleEta {Z : D} {f : Hom Z apex} : tupleMap (\lam j => proj j  f) = f
    => tupleEq \lam j => tupleBeta

  \func toLimit : Limit {DiscretePrecat J} {D} \cowith
    | G => functor G
    | apex => apex
    | coneMap => proj
    | coneCoh {j} {j'} (in0 idp) => id-left
    | isLimit Z => transEquiv {_} {_} {Cone (functor G) Z} (isProduct Z) (\new QEquiv {
      | f p => \new Cone {
        | coneMap => p
        | coneCoh {j} {j'} (in0 idp) => id-left
      }
      | ret => coneMap {__}
      | ret_f p => idp
      | f_sec c => idp
    })
} \where {
  \func functor {J : \Type} {D : Precat} (G : J -> D) : Functor (DiscretePrecat J) D \cowith
    | F => G
    | Func => DiscretePrecat.map G
    | Func-id => idp
    | Func-o {x} {y} {z} {in0 idp} {in0 idp} => inv id-left

  \use \coerce fromLimit {J : \Type} {D : Precat} (L : Limit {DiscretePrecat J} {D}) : Product L.G L.apex L.coneMap \cowith
    | tupleMap c => L.limMap (cone c)
    | tupleBeta => L.limBeta _ _
    | tupleEq {z} {h1} {h2} f => inv (ret_f {L.isLimit z} h1) *> pmap (ret {L.isLimit z}) (exts f) *> ret_f {L.isLimit z} h2
    \where {
      \func cone {J : \Type} {D : Precat} {Z : D} {G : Functor (DiscretePrecat J) D} (c : \Pi (j : J) -> Hom Z (G j)) : Cone G Z \cowith
        | coneMap => c
        | coneCoh {_} {j'} (in0 p) => \case \elim j', \elim p \with {
          | _, idp => pmap (`∘ _) Func-id *> id-left
        }
    }
}

\class Equalizer {D : Precat} {X Y : D} (f g : Hom X Y) (\classifying apex : D) (eql : Hom apex X) (equal : f  eql = g  eql) {
  | isEqualizer (Z : D) : Equiv {Hom Z apex} {\Sigma (h : Hom Z X) (f  h = g  h)} (\lam h => (eql  h, inv o-assoc *> pmap (`∘ h) equal *> o-assoc))

  \func eqMap {Z : D} (h : Hom Z X) (p : f  h = g  h) : Hom Z apex => ret {isEqualizer Z} (h,p)

  \lemma eqBeta {Z : D} (h : Hom Z X) (p : f  h = g  h) : eql  eqMap h p = h =>
    pmap __.1 (Equiv.f_ret {isEqualizer Z} (h,p))

  \lemma eqMono {Z : D} {h1 h2 : Hom Z apex} (p : eql  h1 = eql  h2) : h1 = h2 =>
    inv (ret_f {isEqualizer Z} h1) *> pmap ret (ext p) *> ret_f {isEqualizer Z} h2

  \func toLimit : Limit {Shape} {D} \cowith
    | G => functor f g
    | apex => apex
    | coneMap x => \case \elim x \with {
      | false => eql
      | true => g  eql
    }
    | coneCoh {x} {y} h => \case \elim x, \elim y, \elim h \with {
      | false, true, arrow1 => equal
      | false, true, arrow2 => idp
      | false, false, id_false => id-left
      | true, true, id_true => id-left
    }
    | isLimit Z =>
      \have equiv => transEquiv {_} {_} {Cone (functor f g) Z} (isEqualizer Z) (\new QEquiv {
        | f p => \new Cone {
          | coneMap b => \case \elim b \with {
            | false => p.1
            | true => g  p.1
          }
          | coneCoh {j} {j'} h => \case \elim j, \elim j', \elim h \with {
            | false, true, arrow1 => p.2
            | false, true, arrow2 => idp
            | false, false, id_false => id-left
            | true, true, id_true => id-left
          }
        }
        | ret (c : Cone) => (c.coneMap false, c.coneCoh arrow1 *> inv (c.coneCoh arrow2))
        | ret_f p => ext idp
        | f_sec (c : Cone) => exts \case \elim __ \with {
          | false => idp
          | true => c.coneCoh arrow2
        }
      })
      \in transport (Equiv __) (ext \lam h => exts \case \elim __ \with {
        | false => idp
        | true => inv o-assoc
      }) equiv
} \where {
  \data Arrows (x y : Bool) \with
    | false, true => { arrow1 | arrow2 }
    | false, false => id_false
    | true, true => id_true

  \func map {D : Precat} {X Y : D} (f g : Hom X Y) {x y : Bool} (a : Arrows x y) : Hom (if x Y X) (if y Y X) \elim x, y, a
    | false, true, arrow1 => f
    | false, true, arrow2 => g
    | false, false, id_false => id X
    | true, true, id_true => id Y

  \func Shape : Precat Bool \cowith
    | Hom => Arrows
    | id (x : Bool) : Arrows x x \with {
      | false => id_false
      | true => id_true
    }
    | o {x y z : Bool} (g : Arrows y z) (f : Arrows x y) : Arrows x z \elim x, y, z, g, f {
      | false, _, false, _, _ => id_false
      | true, _, true, _, _ => id_true
      | false, false, true, a, _ => a
      | false, true, true, _, a => a
      | true, false, false, _, ()
    }
    | id-left {x} {y} {f} => cases (x,y,f) idp
    | id-right {x} {y} {f} => cases (x,y,f) idp
    | o-assoc {x} {y} {z} {w} {h} {g} {f} => cases (x,y,z,w,h,g,f) idp

  \func functor {D : Precat} {x y : D} (f g : Hom x y) : Functor Shape D \cowith
    | F => if __ y x
    | Func => map f g
    | Func-id {x} => cases x idp
    | Func-o {x} {y} {z} {h} {k} => cases (x,y,z,h,k) (inv id-left) \with {
      | false, false, true, arrow1, id_false => inv id-right
      | false, false, true, arrow2, id_false => inv id-right
    }

  \use \coerce fromLimit {D : Precat} {x y : D} {f g : Hom x y} (L : Limit (functor f g)) : Equalizer f g
    => \have cone {z : D} (h : Hom z x) (p : f  h = g  h) => \new Cone (functor f g) z {
         | coneMap => \case \elim __ \with {
           | false => h
           | true => g  h
         }
         | coneCoh {j} {j'} k => cases (j,j',k) \with {
           | false, true, arrow1 => p
           | false, true, arrow2 => idp
           | false, false, id_false => id-left
           | true, true, id_true => id-left
         }
       } \in \new Equalizer {
         | apex => L.apex
         | eql => L.coneMap false
         | equal => L.coneCoh arrow1 *> inv (L.coneCoh arrow2)
         | isEqualizer Z => \new QEquiv {
           | ret (h,p) => L.limMap (cone h p)
           | ret_f h => L.limUnique \case \elim __ \with {
             | false => L.limBeta _ false
             | true => L.limBeta _ true *> inv o-assoc *> pmap (`∘ h) (L.coneCoh arrow2)
           }
           | f_sec (h,p) => ext (L.limBeta (cone h p) false)
         }
    }

  \func unique {D : Precat} {x y : D} {f g : Hom x y} (E E' : Equalizer f g) : Iso {D} {E} {E'} \cowith
    | f => eqMap eql equal
    | hinv => eqMap eql equal
    | hinv_f => eqMono $ rewriteEq (E.eqBeta E'.eql equal) (eqBeta _ _ *> inv id-right)
    | f_hinv => eqMono $ rewriteEq (E'.eqBeta E.eql equal) (eqBeta _ _ *> inv id-right)

  \lemma unique-map {D : Precat} {x y : D} {f g : Hom x y} (E E' : Equalizer f g) : E'.eql  Iso.f {unique E E'} = E.eql =>
    eqBeta eql equal

  \lemma mono=>equalizer {D : Precat} {x y e : D} {f g : Hom x y} {eql : Hom e x} (equal : f  eql = g  eql)
                         (m : Mono eql) (p : \Pi {z : D} (h : Hom z x) -> f  h = g  h ->  (k : Hom z e) (eql  k = h))
    : \Pi (z : D) -> Equiv {Hom z e} {\Sigma (h : Hom z x) (f  h = g  h)} (\lam h => (eql  h, inv o-assoc *> pmap (`∘ h) equal *> o-assoc)) =>
    \lam z => \new ESEquiv {
      | isSurjMap t => TruncP.map (p t.1 t.2) (\lam q => (q.1, ext q.2))
      | Embedding => Embedding.fromInjection (\lam q => m.isMono $ pmap __.1 q)
    }

  \func id-equalizer {D : Precat} {X Y : D} {f : Hom X Y} : Equalizer f f X (id X)\cowith
    | equal => idp
    | isEqualizer _ => \new QEquiv {
      | ret (h, _) => h
      | ret_f _ => id-left
      | f_sec _ => exts id-left
    }

  \func equalizer-iso {D : Precat} {X Y : D} {f : Hom X Y} (eq : Equalizer f f) : Iso {D} {eq} {X} eq.eql
  \cowith
    | hinv => unique id-equalizer eq
    | hinv_f => unfold $ eqMono $ rewriteI o-assoc $ rewrite (eqBeta {eq} _ _, id-left, id-right) idp
    | f_hinv => unique-map id-equalizer eq
}

\class Pullback {D : Precat} {x y z : D} (f : Hom x z) (g : Hom y z) (\classifying apex : D) {
  | pbProj1 : Hom apex x
  | pbProj2 : Hom apex y
  | pbCoh : f  pbProj1 = g  pbProj2
  | pbMap {w : D} (p1 : Hom w x) (p2 : Hom w y) : f  p1 = g  p2 -> Hom w apex
  | pbBeta1 {w : D} {p1 : Hom w x} {p2 : Hom w y} {c : f  p1 = g  p2} : pbProj1  pbMap p1 p2 c = p1
  | pbBeta2 {w : D} {p1 : Hom w x} {p2 : Hom w y} {c : f  p1 = g  p2} : pbProj2  pbMap p1 p2 c = p2
  | pbEta {w : D} {h1 h2 : Hom w apex} : pbProj1  h1 = pbProj1  h2 -> pbProj2  h1 = pbProj2  h2 -> h1 = h2

  \lemma pbMap-comp {a b : D} {p1 : Hom b x} {p2 : Hom b y} {c : f  p1 = g  p2} {h : Hom a b} : pbMap p1 p2 c  h = pbMap (p1  h) (p2  h) (inv o-assoc *> pmap (`∘ h) c *> o-assoc) =>
    pbEta (inv o-assoc *> rewrite pbBeta1 idp *> inv pbBeta1) (inv o-assoc *> rewrite pbBeta2 idp *> inv pbBeta2)

  \func flip : Pullback g f apex pbProj2 pbProj1 (inv pbCoh) \cowith
    | pbMap p1 p2 c => pbMap p2 p1 (inv c)
    | pbBeta1 => pbBeta2
    | pbBeta2 => pbBeta1
    | pbEta q1 q2 => pbEta q2 q1

  \func toLimit : LimitDiagram \cowith
    | Diagram => diagram f g
    | apex => apex
    | coneMap => \case \elim __ \with {
      | 0 => pbProj1
      | 1 => pbProj2
      | 2 => f  pbProj1
    }
    | diagramCoh {g} {g'} e => \case \elim g, \elim g', \elim e \with {
      | 0, 2, _ => idp
      | 1, 2, _ => inv pbCoh
    }
    | isLimitDiagram z => \new QEquiv {
      | ret (c : DiagramCone) => pbMap (c.coneMap 0) (c.coneMap 1) (c.diagramCoh {0} {2} () *> inv (c.diagramCoh {1} {2} ()))
      | ret_f h => pbEta pbBeta1 pbBeta2
      | f_sec c => exts \case \elim __ \with {
        | 0 => pbBeta1
        | 1 => pbBeta2
        | 2 => o-assoc *> rewrite pbBeta1 (diagramCoh {c} ())
      }
    }
} \where {
  \instance Shape : Graph => \new Graph (Fin 3) \case __, __ \with {
    | 0, 2 => \Sigma
    | 1, 2 => \Sigma
    | _, _ => Empty
  }

  \func diagram {D : Precat} {x y z : D} (f : Hom x z) (g : Hom y z) : Diagram Shape D \cowith
    | F => \case __ \with {
      | 0 => x
      | 1 => y
      | 2 => z
    }
    | Func {a} {b} e => \case \elim a, \elim b, \elim e \with {
      | 0, 2, _ => f
      | 1, 2, _ => g
    }

  \use \coerce fromLimit {D : Precat} {x y z : D} {f : Hom x z} {g : Hom y z} (L : Limit (Diagram.functor {diagram f g})) : Pullback f g L.apex
    => \let | d : DiagramCone => ret {DiagramCone.equiv {diagram f g} L.apex} L
            | d' {w} p1 p2 c => \new DiagramCone {
              | Diagram => diagram f g
              | apex => w
              | coneMap => \case \elim __ \with {
                | 0 => p1
                | 1 => p2
                | 2 => f  p1
              }
              | diagramCoh {g} {g'} e => \case \elim g, \elim g', \elim e \with {
                | 0, 2, _ => idp
                | 1, 2, _ => inv c
              }
            }
       \in \new Pullback {
        | pbProj1 => L.coneMap 0
        | pbProj2 => L.coneMap 1
        | pbCoh => d.diagramCoh {0} {2} () *> inv (d.diagramCoh {1} {2} ())
         | pbMap {w} p1 p2 c => L.limMap (d' p1 p2 c)
         | pbBeta1 {w} {p1} {p2} {c} => L.limBeta (d' p1 p2 c) 0
         | pbBeta2 {w} {p1} {p2} {c} => L.limBeta (d' p1 p2 c) 1
         | pbEta {w} {h1} {h2} p q => inv (ret_f {L.isLimit w} h1) *> pmap (ret {L.isLimit w}) (exts \case \elim __ \with {
           | 0 => p
           | 1 => q
           | 2 => pmap (`∘ _) (inv (d.diagramCoh {0} {2} ())) *> o-assoc *> pmap (f ) p *> inv o-assoc *> pmap (`∘ _) (d.diagramCoh {0} {2} ())
         }) *> ret_f {L.isLimit w} h2
      }

  \func fromIso (P : Pullback {}) (e : Iso {P.D} { | cod => P }) : Pullback P.f P.g e.dom \cowith
    | pbProj1 => pbProj1  e
    | pbProj2 => pbProj2  e
    | pbCoh => rewriteEq pbCoh o-assoc
    | pbMap p1 p2 c => e.hinv  pbMap p1 p2 c
    | pbBeta1 => rewriteEq e.f_hinv $ rewrite id-right pbBeta1
    | pbBeta2 => rewriteEq e.f_hinv $ rewrite id-right pbBeta2
    | pbEta s1 s2 => e.isMono $ pbEta (rewriteEq s1 o-assoc) (rewriteEq s2 o-assoc)

  \func unique {D : Precat} {x y z : D} (f : Hom x z) (g : Hom y z) (P P' : Pullback f g) : Iso {D} {P} {P'}
  \cowith
    | f => p-map P P'
    | hinv => p-map P' P
    | hinv_f => hinv' P P'
    | f_hinv => hinv' P' P
    \where {
      \func p-map{D : Precat} {x y z : D} {f : Hom x z} {g : Hom y z} (P P' : Pullback f g) : Hom P P' =>
        pbMap {P'} P.pbProj1 P.pbProj2 P.pbCoh

      \func p-beta1 {D : Precat} {x y z : D} {f : Hom x z} {g : Hom y z} (P P' : Pullback f g)
        : pbProj1 {P}  (p-map P' P) = pbProj1 {P'} => pbBeta1 {P} {P'} {pbProj1 {P'}} {pbProj2 {P'}} {pbCoh {P'}}

      \func p-beta2 {D : Precat} {x y z : D} {f : Hom x z} {g : Hom y z} (P P' : Pullback f g)
        : pbProj2 {P}  (p-map P' P) = pbProj2 {P'} => pbBeta2 {P} {P'} {pbProj1 {P'}} {pbProj2 {P'}} {pbCoh {P'}}

      \func hinv'{D : Precat} {x y z : D} {f : Hom x z} {g : Hom y z} (P P' : Pullback f g)
        : (p-map P' P)  (p-map P P') = id P => pbEta {P} {P} {(p-map P' P)  (p-map P P')} {id P}
          (rewriteI o-assoc $ rewrite (p-beta1 P P', p-beta1 P' P) (inv id-right))
          (rewriteI o-assoc $ rewrite (p-beta2 P P', p-beta2 P' P) (inv id-right))
    }
}

\func limits<=pr+eq {D : Precat}
  (pr : \Pi (J : \hType) (G : J -> D) -> Product G)
  (eq : \Pi {X Y : D} (f g : Hom X Y) -> Equalizer f g)
  {J : SmallPrecat} (G : Functor J D) : Limit G
  => \have | X : Product => pr J G
           | Y : Product => pr (\Sigma (j j' : J) (Hom j j')) (\lam t => G t.2)
           | eql : Equalizer => eq {X.apex} {Y.apex}
              (Y.tupleMap (\lam t => G.Func t.3  X.proj t.1))
              (Y.tupleMap (\lam t => X.proj t.2))
     \in \new Limit {
      | apex => eql.apex
      | coneMap j => X.proj j  eql.eql
      | coneCoh {j} {j'} h => inv o-assoc *> inv (pmap (`∘ eql.eql) Y.tupleBeta) *> o-assoc *> pmap (Y.proj (j,j',h) ) eql.equal *> inv o-assoc *> pmap (`∘ eql.eql) Y.tupleBeta
      | isLimit Z => \new QEquiv {
        | ret (c : Cone) => eql.eqMap (X.tupleMap c.coneMap) (Y.tupleMapComp _ _ *> pmap Y.tupleMap (ext (\lam j => o-assoc *> pmap (_ ) X.tupleBeta *> c.coneCoh j.3 *> inv X.tupleBeta)) *> inv (Y.tupleMapComp _ _))
        | ret_f h => eql.eqMono (eql.eqBeta _ _ *> path (\lam i => X.tupleMap (\lam j => o-assoc {_} {_} {_} {_} {_} {X.proj j} @ i)) *> X.tupleEq (\lam j => X.tupleBeta))
        | f_sec (c : Cone) => exts \lam j => o-assoc *> pmap (_ ) (eql.eqBeta _ _) *> X.tupleBeta
      }
    }


\class PrecatWithPullbacks \extends Precat
  | pullback {x y z : Ob} (f : Hom x z) (g : Hom y z) : Pullback f g

\func pullbackFunctor {C : PrecatWithPullbacks} {x y : C} (f : Hom x y) : Functor (SlicePrecat y) (SlicePrecat x) =>
  \let | F (t : SlicePrecat y) : SlicePrecat x => (pullback f t.2, map t.2)
       | Func {a} {b} (g : Hom a b) : Hom (F a) (F b) =>
         \let p : Pullback => pullback f a.2
         \in (pbMap p.pbProj1 (g.1  p.pbProj2) (p.pbCoh *> pmap (`∘ p.pbProj2) (inv g.2) *> o-assoc), pbBeta1)
  \in \new Functor {
    | F => F
    | Func => Func
    | Func-id => ext $ pbEta (pbBeta1 *> inv id-right) (pbBeta2 *> id-left *> inv id-right)
    | Func-o => ext $ pbEta (pbBeta1 *> inv (pmap (`∘ _) pbBeta1 *> later pbBeta1) *> o-assoc) (pbBeta2 *> inv (pmap (`∘ _) pbBeta2 *> o-assoc *> pmap (_ ) pbBeta2 *> inv o-assoc) *> o-assoc)
  } \where
  \func map {z : C} (g : Hom z y) : Hom (pullback f g) x => pbProj1 {pullback f g}

\meta terminal-obj C => Product {Empty} {C} absurd

\func terminal-obj-iso {C : Precat} (a b : terminal-obj C) : Iso {C} {a} {b}
\cowith
  | f => tupleMap (\case __)
  | hinv => tupleMap (\case __)
  | hinv_f => tupleEq (\case __)
  | f_hinv => tupleEq (\case __)

\func terminalMap' {C : Precat} {t : terminal-obj C} {x : C} : Hom x t => tupleMap (\case __)

\func terminal-ind {C : Precat} (a : C) (e : \Pi (b : C) -> Contr (Hom b a))
  : terminal-obj C
  \cowith
    | apex => a
    | proj j => absurd j
    | tupleMap {Z} _ => Contr.center {e Z}
    | tupleBeta {_} {_} {j} => absurd j
    | tupleEq {Z} {p} {q} _ => isContr=>isProp (e Z) p q

\lemma terminal-unique' {C : Precat} {terminal : terminal-obj C} {x : C} {f g : Hom x terminal} : f = g => tupleEq (\case __)

\func terminal-prop {C : Cat} : isProp (terminal-obj C)
  => \lam a a' => exts (C.univalence.ret (unfold $ terminal-obj-iso a a'), \lam j => absurd j, \lam {Z} f => terminal-unique')

\class PrecatWithTerminal \extends Precat {
  | terminal : terminal-obj \this

  \func terminalMap {x : Ob} : Hom x terminal => tupleMap (\case __)

  \lemma terminal-unique {x : Ob} {f g : Hom x terminal} : f = g => tupleEq (\case __)
}

\class PrecatWithBprod \extends Precat {
  | Bprod (x y : Ob) : Product (x :: y :: nil)

  \func proj1 {x y : Ob} => proj {Bprod x y} 0

  \func proj2 {x y : Ob} => proj {Bprod x y} 1

  \func pair {x y z : Ob} (f : Hom z x) (g : Hom z y) : Hom z (Bprod x y)
    => tupleMap \case \elim __ \with {
      | 0 => f
      | 1 => g
    }

  \func prodMap {x y x' y' : Ob} (f : Hom x y) (f' : Hom x' y') : Hom (Bprod x x') (Bprod y y') =>
    pair (f  proj1) (f'  proj2)

  \lemma prodMap-comp{x y x' y' z z' : Ob} (f : Hom x y) (g : Hom y z) (f' : Hom x' y') (g' : Hom y' z')
    : prodMap (g  f) (g'  f') = prodMap g g'  prodMap f f' => pair-unique
      (rewriteI o-assoc $ rewrite (beta1 _ _, beta1 _ _) $ rewrite {2} o-assoc $ rewrite (beta1 _ _) o-assoc)
      (rewriteI o-assoc $ rewrite (beta2 _ _, beta2 _ _) $ rewrite {2} o-assoc $ rewrite (beta2 _ _) o-assoc)

  \func prodMap-split-right {x y x' y' : Ob} (f : Hom x y) (g : Hom x' y')
    : prodMap f g = prodMap f (id y')  prodMap (id x) g
    => rewriteI (prodMap-comp _ _ _ _) $ rewrite (id-left, id-right) idp

  \func prod-id-left {x y z w : Ob} (g : Hom y z) (f : Hom x y)
    : prodMap (id w) (g  f) = prodMap (id w) g  prodMap (id w) f => rewriteI {2} id-left $ prodMap-comp _ _ _ _

  \func prod-id-right {x y z w : Ob} (g : Hom y z) (f : Hom x y)
    : prodMap (g  f) (id w) = prodMap g (id w)  prodMap f (id w) => rewriteI {5} id-right $ prodMap-comp _ _ _ _

  \func prod-id {x y : Ob} : prodMap (id x) (id y) = id (Bprod x y) =>
    unfold prodMap $ rewrite (id-left, id-left) pair-proj

  \lemma beta1 {x y z : Ob} (f : Hom z x) (g : Hom z y) : proj1  pair f g = f => tupleBeta {Bprod x y}

  \lemma beta2 {x y z : Ob} (f : Hom z x) (g : Hom z y) : proj2  pair f g = g => tupleBeta {Bprod x y}

  \lemma pair-unique {x y z : Ob} {h1 h2 : Hom z (Bprod x y)} (p1 : proj1  h1 = proj1  h2) (p2 : proj2  h1 = proj2  h2) : h1 = h2 =>
    tupleEq $ \case \elim __ \with {
      | 0 => p1
      | 1 => p2
    }

  \lemma pair-comp {x y z w : Ob} {f : Hom x y} {g : Hom y z} {h : Hom y w} : pair g h  f = pair (g  f) (h  f) =>
    pair-unique (rewriteEq (beta1 g h) $ inv (beta1 _ _)) (rewriteEq (beta2 g h) $ inv (beta2 _ _))

  \lemma pair-proj {x y : Ob} : pair proj1 proj2 = id (Bprod x y) =>
    pair-unique (beta1 _ _ *> inv id-right) (beta2 _ _ *> inv id-right)

  \func diagonal (x : Ob) : Hom x (Bprod x x) => pair (id x) (id x)
    \where
      \lemma isSplitMono (x : Ob) : SplitMono (diagonal x) proj1 \cowith
        | hinv_f => beta1 _ _

  \func associator {x y z : Ob} : Hom (Bprod (Bprod x y) z) (Bprod x (Bprod y z)) =>
    pair (proj1  proj1) (pair (proj2  proj1) proj2)

  \func associator-iso {x y z : Ob} : Iso (associator {_} {x} {y} {z}) \cowith
    | hinv => pair (pair proj1 (proj1  proj2)) (proj2  proj2)
    | hinv_f => rewrite (pair-comp,pair-comp,beta1,o-assoc,beta2,beta1,o-assoc,beta2,beta2,inv pair-comp,pair-proj,id-left) pair-proj
    | f_hinv => rewrite (pair-comp,o-assoc,beta1,beta1,pair-comp,beta2,o-assoc,beta1,beta2,inv pair-comp,pair-proj,id-left) pair-proj

  \lemma associtor-prod {x y z : Ob} {x' y' z' : Ob} (f : Hom x x') (g : Hom y y') (h : Hom z z')
    : prodMap f (prodMap g h)  associator = associator  prodMap (prodMap f g) h =>
    pair-unique
      (run {
        rewrite (inv o-assoc, beta1 _ _, o-assoc, beta1 _ _),
        rewriteI {2} o-assoc ,
        rewrite (beta1 _ _, o-assoc, beta1 _ _),
        rewriteI {2} o-assoc,
        rewrite (beta1 _ _),
        inv o-assoc
    })
      (run {
        rewriteI o-assoc,
        rewrite (beta2 _ _, o-assoc, beta2 _ _, inv o-assoc, beta2 _ _, pair-comp, o-assoc, beta1 _ _, o-assoc, beta2 _ _, pair-comp),
        rewrite (o-assoc, beta1 _ _, beta2 _ _),
        rewriteI {2} o-assoc,
        rewrite (beta2 _ _, o-assoc),
        idp
      })

  \func bprodFunctorRight (Y : Ob) : Functor \this \this
  \cowith
    | F X => Bprod X Y
    | Func f => prodMap f (id Y)
    | Func-id => unfold prodMap $ rewrite (id-left, id-left) pair-proj
    | Func-o => prod-id-right _ _

  \func change {x y : Ob} : Hom (Bprod x y) (Bprod y x) => pair proj2 proj1

  \func change-iso {x y : Ob} : Iso (change {_} {x} {y})
  \cowith
    | hinv => change
    | hinv_f => change-inv
    | f_hinv => change-inv

  \lemma change-inv {x y : Ob} : change  change = id (Bprod x y) =>
    pair-unique (rewriteI o-assoc $ rewrite (beta1 _ _, beta2 _ _, id-right) idp)
        (rewriteI o-assoc $ rewrite (beta2 _ _, beta1 _ _, id-right) idp)

  \lemma change-prod {x y x' y' : Ob} {f : Hom x x'} {g : Hom y y'} : change  prodMap f g = prodMap g f  change =>
    pair-unique (rewrite (inv o-assoc, beta1 _ _, beta2 _ _, inv o-assoc, beta1 _ _, o-assoc, beta1 _ _) idp)
        (rewrite (inv o-assoc, beta2 _ _, beta1 _ _, inv o-assoc, beta2 _ _, o-assoc, beta2 _ _) idp)

  \func bprod-comm {x y : Ob} : Iso (change {_} {x} {y})\cowith
    | hinv => change {_} {y} {x}
    | hinv_f => change-inv {_} {x} {y}
    | f_hinv => change-inv {_} {y} {x}

  \func bprodBiFunctor : Functor (ProductPrecat \this \this) \this
  \cowith
    | F x => Bprod x.1 x.2
    | Func f => prodMap f.1 f.2
    | Func-id => prod-id
    | Func-o {_} {_} {_} {g} {f} => prodMap-comp f.1 g.1 f.2 g.2
}

\class CartesianPrecat \extends PrecatWithTerminal, PrecatWithBprod {
  \func terminal-prod-left {X : Ob} : Iso {\this} {X} {Bprod terminal X}  \cowith
    | f => pair terminalMap (id X)
    | hinv => proj2
    | hinv_f => beta2 _ _
    | f_hinv => pair-unique (rewrite (inv o-assoc, beta1 _ _, id-right) terminal-unique)
        (rewrite (inv o-assoc, beta2 _ _, id-right, id-left) idp)
}

\class FinCompletePrecat \extends PrecatWithPullbacks, CartesianPrecat {
  \default Bprod x y => \new Product {
    | apex => pullback (terminal.tupleMap (\case __)) (tupleMap (\case __))
    | proj => \case \elim __ \with {
      | 0 => pbProj1
      | 1 => pbProj2
    }
    | tupleMap f => pbMap (f 0) (f 1) $ tupleEq (\case __)
    | tupleBeta {_} {_} {j} => \case \elim j \with {
      | 0 => pbBeta1
      | 1 => pbBeta2
    }
    | tupleEq f => pbEta (f 0) (f 1)
  }
}

\class CompletePrecat \extends FinCompletePrecat {
  | limit {J : Precat} (G : Functor J \this) : Limit G
  \default pullback f g => Pullback.fromLimit (limit (Diagram.functor {Pullback.diagram f g}))
  \default terminal => Product.fromLimit (limit (Product.functor absurd))
  \default Bprod x y => Product.fromLimit (limit (Product.functor (x :: y :: nil)))

  \func product {J : \Type} (G : J -> Ob) : Product G => Product.fromLimit (limit (Product.functor G))

  \func equalizer {x y : Ob} (f g : Hom x y) : Equalizer f g => Equalizer.fromLimit (limit (Equalizer.functor f g))

  \func op : CocompletePrecat \cowith
    | Precat => Precat.op
    | colimit (G : Functor) => limit G.op
} \where {
  \func applyEquiv {C : CompletePrecat} (E : CatEquiv C) {J : SmallPrecat} (G : Functor J E.D) : Limit G
    => \have lim : Limit => limit (Comp E.LAdj G)
       \in \new Limit {
        | apex => E lim
        | coneMap j => E.eta-iso.hinv  E.Func (lim.coneMap j)
        | coneCoh h => rewrite (inv (pmap E.Func (lim.coneCoh h)), Func-o, inv o-assoc, inv o-assoc)
                               (pmap (`∘ _) (inv (NatTrans.natural {E.eta.iso E.eta-iso} (G.Func h))))
        | limMap c => E.isAdjoint $ lim.limMap (Cone.map E.LAdj c)
        | limBeta c j => o-assoc *> pmap (_ ) (inv o-assoc *> pmap (`∘ _) (inv E.Func-o *> pmap E.Func (lim.limBeta (Cone.map E.LAdj c) j)) *> inv (E.eta.natural (coneMap j))) *> inv o-assoc *> pmap (`∘ _) E.eta-iso.hinv_f *> id-left
        | limUnique p => Equiv.isInj {symQEquiv E.eta_eps_equiv} $ lim.limUnique \lam j => inv o-assoc *> pmap (`∘ _) (inv (E.epsilon.natural _)) *> o-assoc *> pmap (_ ) (inv Func-o *> pmap E.LAdj.Func (E.eta-iso.reverse.isMono (inv o-assoc *> p j *> o-assoc)) *> Func-o) *> inv o-assoc *> pmap (`∘ _) (E.epsilon.natural _) *> o-assoc
      }
}

\class CompleteCat \extends CompletePrecat, Cat {
  \func op : CocompleteCat \cowith
    | Cat => Cat.op
    | colimit (G : Functor) => limit G.op
}

\class CocompletePrecat \extends Precat {
  | colimit {J : Precat} (G : Functor J \this) : Colimit G

  \func op : CompletePrecat \cowith
    | Precat => Precat.op
    | limit (G : Functor) => colimit G.op
} \where {
  \func applyEquiv {C : CocompletePrecat} (E : CatEquiv C) {J : SmallPrecat} (G : Functor J E.D) : Colimit G =>
    CompletePrecat.applyEquiv {C.op} E.op G.op
}

\class CocompleteCat \extends CocompletePrecat, Cat {
  \func op : CompleteCat \cowith
    | Cat => Cat.op
    | limit (G : Functor) => colimit G.op
}

\class BicompleteCat \extends CompleteCat, CocompleteCat {
  \func op : BicompleteCat \cowith
    | Cat => Cat.op
    | limit (G : Functor) => colimit G.op
    | colimit (G : Functor) => limit G.op
}

\func PreservesLimit {J C D : Precat} (G : Functor C D) (F : Functor J C) =>
  \Pi (L : Limit F) -> Limit { | Cone => Cone.map G L }

\func ReflectsLimit {J C D : Precat} (G : Functor C D) (F : Functor J C) =>
  \Pi (c : Cone F) -> Limit { | Cone => Cone.map G c } -> Limit { | Cone => c }

\func CreatesLimit {J C D : Precat} (G : Functor C D) (F : Functor J C) =>
  Limit (Comp G F) -> \Sigma (Limit F) (PreservesLimit G F) (ReflectsLimit G F)

\func isRegularMono {C : Precat} {x y : C} (f : Hom x y) =>
  TruncP (Equalizer { | D => C | X => y | apex => x | eql => f })

\lemma regularMono_Mono {C : Precat} {x y : C} {f : Hom x y} (reg : isRegularMono f) : Mono f \elim reg
  | inP (E : Equalizer) => \new Mono f E.eqMono

\lemma regularMono_pullback (P : Pullback {}) (m : isRegularMono P.g) : isRegularMono P.pbProj1 => {?}

{- | If both squares are pullbacks, then the outter rectangle is a pullback.
  Q  -->  P  --> P.y
  |       |       |
  |       |       |
  Q.x --> P.x --> P.z
 -}
\func pullback-lemma (P : Pullback {}) (Q : Pullback {P.D} { | y => P | z => P.x | g => P.pbProj1 }) : Pullback (P.f  Q.f) P.g Q Q.pbProj1 (P.pbProj2  Q.pbProj2) \cowith
  | pbCoh => rewriteEq (Q.pbCoh,P.pbCoh) o-assoc
  | pbMap p1 p2 c => Q.pbMap p1 (P.pbMap (Q.f  p1) p2 $ inv o-assoc *> c) $ inv P.pbBeta1
  | pbBeta1 => Q.pbBeta1
  | pbBeta2 => rewriteEq Q.pbBeta2 P.pbBeta2
  | pbEta p1 p2 => Q.pbEta p1 $ P.pbEta (inv o-assoc *> rewriteI Q.pbCoh (rewriteEq p1 (inv o-assoc) *> pmap (`∘ _) Q.pbCoh) *> o-assoc) (inv o-assoc *> p2 *> o-assoc)

{- | If the right square and the outter rectangle are pullbacks, then the left square is a pullback.
  Q  -->  P  --> P.y
  |       |       |
  |       |       |
  Q.x --> P.x --> P.z
 -}
\func pullback-lemma-conv (P : Pullback {}) (Q : Pullback {P.D} { | y => P.y | z => P.z | g => P.g }) (t : Hom Q.x P.x) (fs=Qf : P.f  t = Q.f) : Pullback t P.pbProj1 Q Q.pbProj1 \cowith
  | pbProj2 => P.pbMap (t  Q.pbProj1) Q.pbProj2 (inv o-assoc *> pmap (`∘ _) fs=Qf *> Q.pbCoh)
  | pbCoh => inv P.pbBeta1
  | pbMap p1 p2 c => Q.pbMap p1 (P.pbProj2  p2) $ pmap (`∘ p1) (inv fs=Qf) *> o-assoc *> pmap (P.f ) c *> inv o-assoc *> pmap (`∘ p2) P.pbCoh *> o-assoc
  | pbBeta1 => Q.pbBeta1
  | pbBeta2 {_} {_} {_} {c} => pbEta (inv o-assoc *> rewrite P.pbBeta1 (rewriteEq Q.pbBeta1 c)) (inv o-assoc *> rewrite P.pbBeta2 Q.pbBeta2)
  | pbEta p1 p2 => Q.pbEta p1 $ rewrite P.pbBeta2 in o-assoc *> pmap (P.pbProj2 ) p2 *> inv o-assoc

\lemma splitMono_regular (f : SplitMono {}) : isRegularMono f.f => inP (\new Equalizer {
  | Y => f.cod
  | f => f.f  f.hinv
  | g => id f.cod
  | equal => rewriteEq f.hinv_f (id-right *> inv id-left)
  | isEqualizer Z => \new QEquiv {
    | ret (h,p) => f.hinv  h
    | ret_f h => rewriteEq f.hinv_f id-left
    | f_sec (h,p) => ext (inv o-assoc *> p *> id-left)
  }
})

\func isRegularEpi {C : Precat} {x y : C} (f : Hom x y) => isRegularMono {C.op} f