\import Category
\import Category.Adjoint
\import Category.Functor
\import Category.Slice
\import Data.Bool
\import Equiv \hiding (Map)
\import Function.Meta
\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
    => Equiv.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.inv  limMap c'
    | limBeta c' j => pmap (`∘ _) (inv (L.limBeta c j)) *> rewriteEq e.f_inv (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
    | inv => L'.limMap L
    | inv_f => limUnique $ \lam j => inv o-assoc *> pmap (`∘ _) (L'.limBeta L j) *> L.limBeta L' j *> inv id-right
    | f_inv => 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 => rewriteF id-left (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 (Equiv.ret_f {L.isLimit z} h1) *> pmap (Equiv.ret {L.isLimit z}) (exts f) *> Equiv.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
    => Equiv.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 (Equiv.ret_f {isEqualizer Z} h1) *> pmap Equiv.ret (ext p) *> Equiv.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
    | inv => eqMap eql equal
    | inv_f => eqMono $ rewriteEq (E.eqBeta E'.eql equal) (eqBeta _ _ *> inv id-right)
    | f_inv => 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 {
      | isSurj 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)
    }
}

\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 \elim __ \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 => Equiv.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 (Equiv.ret_f {L.isLimit w} h1) *> pmap (Equiv.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} ())
         }) *> Equiv.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.inv  pbMap p1 p2 c
    | pbBeta1 => rewriteEq e.f_inv $ rewrite id-right pbBeta1
    | pbBeta2 => rewriteEq e.f_inv $ rewrite id-right pbBeta2
    | pbEta s1 s2 => e.isMono $ pbEta (rewriteEq s1 o-assoc) (rewriteEq s2 o-assoc)
}

\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}

\class CartesianPrecat \extends Precat {
  | terminal : Product {Empty} {\this} absurd
  | Bprod (x y : Ob) : Product (x :: y :: nil)

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

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

  \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 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
        | inv_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
    | inv => pair (pair proj1 (proj1  proj2)) (proj2  proj2)
    | inv_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_inv => rewrite (pair-comp,o-assoc,beta1,beta1,pair-comp,beta2,o-assoc,beta1,beta2,inv pair-comp,pair-proj,id-left) pair-proj
}

\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.inv  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.inv_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 $ rewriteF P.pbBeta2 $ 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.inv
    | g => id f.cod
    | equal => rewriteEq f.inv_f (id-right *> inv id-left)
    | isEqualizer Z => \new QEquiv {
      | ret (h,p) => f.inv  h
      | ret_f h => rewriteEq f.inv_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