\import Equiv
\import Equiv.Path
\import Function.Meta ($)
\import Logic
\import Meta
\import Paths
\import Paths.Meta
\import Set

\class Precat (Ob : \Type) {
  | Hom : Ob -> Ob -> \Set
  | id {X : Ob} : Hom X X
  | \fixl 8 o \alias \infixl 8  {X Y Z : Ob} : Hom Y Z -> Hom X Y -> Hom X Z
  | id-left {X Y : Ob} {f : Hom X Y} : id  f = f
  | id-right {X Y : Ob} {f : Hom X Y} : f  id = f
  | o-assoc {X Y Z W : Ob} {h : Hom Z W} {g : Hom Y Z} {f : Hom X Y} : (h  g)  f = h  (g  f)

  \func \infixl 8 >> {x y z : Ob} (f : Hom x y) (g : Hom y z) => g  f

  \protected \func op : Precat \cowith
    | Ob => Ob
    | Hom x y => Hom y x
    | id => id
    | o g f => o f g
    | id-left => id-right
    | id-right => id-left
    | o-assoc => inv o-assoc

  \func idtoiso {a b : Ob} (p : a = b) : Iso {_} {a} {b} \elim p
    | idp => idIso

  \func =_hom {a b : Ob} (p : a = b) : Hom a b
    => (idtoiso p).f

  \lemma transport_Hom {x1 y1 x2 y2 : Ob} (p1 : x1 = y1) (p2 : x2 = y2) {g : Hom x1 x2} {f : Hom y1 y2}
                       (h : transport (Hom x2) p2 id  g = f  transport (Hom x1) p1 id)
    : coe (\lam i => Hom (p1 @ i) (p2 @ i)) g right = f \elim p1, p2
    | idp, idp => inv id-left *> h *> id-right

  \lemma transport_Hom-left {x y z : Ob} (p : x = y) {g : Hom x z} {f : Hom y z} (h : g = f  transport (Hom x) p id) : transport (Hom __ z) p g = f \elim p
    | idp => h *> id-right

  \lemma transport_Hom-right {x y z : Ob} (p : x = y) {g : Hom z x} {f : Hom z y} (h : transport (Hom x) p id  g = f) : transport (Hom z) p g = f \elim p
    | idp => inv id-left *> h
}

\open Precat (>>)

\record Map {C : Precat} {dom cod : C} (\coerce f : Hom dom cod)

\record Mono \extends Map
  | isMono {x : C} {g h : Hom x dom} : f  g = f  h -> g = h
  \where {
    \func comp {C : Precat} {x y z : C} (g : Mono {C} {y} {z}) (f : Mono {C} {x} {y}) : Mono {C} {x} {z} (g.f  f) \cowith
      | isMono p => f.isMono (g.isMono (inv o-assoc *> p *> o-assoc))
  }

\func IsEpi {C : Precat} {x y : C} (f : Hom x y) => \Pi {z : C} {g h : Hom y z} -> g  f = h  f -> g = h

\record SplitMono \extends Mono {
  | hinv : Hom cod dom
  | hinv_f : hinv  f = id
  | isMono {_} {g} {h} gf=hf =>
    g               ==< inv id-left >==
    g >> id         ==< pmap (g >>) (inv hinv_f) >==
    g >> (f >> hinv) ==< o-assoc >==
    (g >> f) >> hinv ==< pmap (o hinv) gf=hf >==
    (h >> f) >> hinv ==< inv o-assoc >==
    h >> (f >> hinv) ==< pmap (h >>) hinv_f >==
    h >> id         ==< id-left >==
    h               `qed

  \lemma adjointMap {z : C} {g : Hom z dom} {h : Hom z cod} (p : f  g = h) : g = hinv  h
    => inv id-left *> pmap ( g) (inv hinv_f) *> o-assoc *> pmap (hinv ) p
}

\record Iso \extends SplitMono {
  | f_hinv : f  hinv = id

  \lemma adjointMapInv {z : C} {g : Hom z dom} {h : Hom z cod} (p : g = hinv  h) : f  g = h
    => pmap (f ) p *> inv o-assoc *> pmap ( h) f_hinv *> id-left

  \lemma adjointMap' {z : C} {g : Hom cod z} {h : Hom dom z} (p : g  f = h) : g = h  hinv
    => inv id-right *> pmap (g ) (inv f_hinv) *> inv o-assoc *> pmap ( hinv) p

  \lemma -o_Equiv {z : C} : IsEquiv {Hom cod z} {Hom dom z} (__  f)
    => inP \new QEquiv {
         | ret h => h  hinv
         | ret_f h => o-assoc *> pmap (h ) f_hinv *> id-right
         | f_sec h => o-assoc *> pmap (h ) hinv_f *> id-right
       }

  \lemma o-_Equiv {z : C} : IsEquiv {Hom z dom} {Hom z cod} (f  __)
    => inP \new QEquiv {
         | ret h => hinv  h
         | ret_f h => inv o-assoc *> pmap ( _) hinv_f *> id-left
         | f_sec h => inv o-assoc *> pmap ( _) f_hinv *> id-left
       }

  \protected \func reverse : Iso => \new Iso hinv f f_hinv hinv_f

  \protected \func op : Iso => \new Iso {C.op} f hinv f_hinv hinv_f
} \where {
    \use \func equals {C : Precat} {x y : C} (e e' : Iso {C} {x} {y}) (p : e.f = e'.f) : e = e'
      => ext (p, isMono {e} (e.f_hinv *> inv e'.f_hinv *> inv (pmap (e'.hinv >>) p)))

    \use \level levelProp {C : Precat} {x y : C} (f : Hom x y) (e e' : Iso f) => equals e e' idp

    \lemma hinv-unique {C : Precat} {x y : C} (e : SplitMono {C} {x} {y}) (e' : Iso {C} {x} {y}) (p : e.f = e'.f) : e.hinv = e'.hinv
      => inv (o-assoc *> pmap (_ ) (pmap ( _) p *> e'.f_hinv) *> id-right) *> pmap ( _) e.hinv_f *> id-left

    \lemma rightFactor {C : Precat} {x y z : C} (f : Hom x y) (e2 : Mono {C} {y} {z}) (e3 : Iso (e2.f  f)) : Iso f \cowith
      | hinv => e3.hinv  e2
      | hinv_f => o-assoc *> e3.hinv_f
      | f_hinv => e2.isMono $ inv o-assoc *> inv o-assoc *> pmap ( _) e3.f_hinv *> id-left *> inv id-right

    \lemma leftFactor {C : Precat} {x y z : C} {f : Hom x y} (e : IsEpi f) (g : Hom y z) (e3 : Iso (g  f)) : Iso g
      => Iso.op {rightFactor {C.op} g (\new Mono f e) e3.op}

    \lemma composite {C : Precat} {x y z : C} (f : Iso {C} {x} {y}) (g : Iso {C} {y} {z}) : Iso (g.f  f.f) \cowith
      | hinv => f.hinv  g.hinv
      | hinv_f => o-assoc *> pmap (_ ) (inv o-assoc *> pmap ( _) g.hinv_f *> id-left) *> f.hinv_f
      | f_hinv => o-assoc *> pmap (_ ) (inv o-assoc *> pmap ( _) f.f_hinv *> id-left) *> g.f_hinv
}

\func idIso {C : Precat} {x : C} : Iso (id {_} {x}) \cowith
  | hinv => id
  | f_hinv => id-left
  | hinv_f => id-right

\func oIso {C : Precat} {x y z : C} (e : Iso {C} {x} {y}) (f : Iso {C} {y} {z}) : Iso {C} {x} {z} \cowith
  | f => f.f  e.f
  | hinv => e.hinv  f.hinv
  | f_hinv => o-assoc *> pmap (_ ) (inv o-assoc *> pmap ( _) e.f_hinv *> id-left) *> f.f_hinv
  | hinv_f => o-assoc *> pmap (_ ) (inv o-assoc *> pmap ( _) f.hinv_f *> id-left) *> e.hinv_f

\class Cat \extends Precat {
  | univalence {a b : Ob} : IsEquiv (idtoiso {_} {a} {b})

  \protected \func op : Cat \cowith
    | Precat => Precat.op
    | univalence => univalenceFromQEquiv {Precat.op} (\lam {a} {b} => transQEquiv (IsEquiv.toQEquiv univalence) $ later \new QEquiv {
      | f (e : Iso) => e.op.reverse
      | ret (e : Iso) => e.op.reverse
      | ret_f => idpe
      | f_sec => idpe
    }) (\lam {c} => idp)

  \func isotoid {a b : Ob} (e : Iso {\this} {a} {b}) : a = b
    => IsEquiv.ret univalence e

  \lemma idtoiso_isotoid {a b : Ob} {e : Iso {\this} {a} {b}} : (idtoiso (isotoid e)).f = e.f
    => path \lam i => (IsEquiv.f_ret univalence i).f

  \lemma transport_iso (e : Iso {\this}) : transport (Hom e.dom) (isotoid e) id = e
    => Jl (\lam b p => transport (Hom e.dom) p id = idtoiso p) idp (isotoid e) *> path (\lam i => (IsEquiv.f_ret univalence i).f)

  \func univalenceToTransport (e : Iso {\this}) : \Sigma (p : e.dom = e.cod) (transport (Hom e.dom) p id = e)
    => (isotoid e, transport_iso e)


  \lemma transport_Hom_iso (e1 e2 : Iso {\this}) {g : Hom e1.dom e2.dom} {f : Hom e1.cod e2.cod}
                           (h : g >> e2.f = e1.f >> f)
    : coe (\lam i => Hom (isotoid e1 @ i) (isotoid e2 @ i)) g right = f
    => transport_Hom (isotoid e1) (isotoid e2) (pmap (g >>) (transport_iso e2) *> h *> pmap (>> f) (inv (transport_iso e1)))

  \lemma transport_Hom_iso-left (e : Iso {\this}) {z : Ob} (g : Hom e.dom z) {f : Hom e.cod z} (h : g = e.f >> f) : transport (Hom __ z) (isotoid e) g = f
    => transport_Hom-left (isotoid e) (h *> pmap (>> f) (inv (transport_iso e)))

  \lemma transport_Hom_iso-right (e : Iso {\this}) {z : Ob} (g : Hom z e.dom) {f : Hom z e.cod} (h : g >> e.f = f) : transport (Hom z) (isotoid e) g = f
    => transport_Hom-right (isotoid e) (pmap (g >>) (transport_iso e) *> h)
} \where {
  \lemma univalenceFromQEquiv {C : Precat} (e : \Pi {a b : C} -> QEquiv {a = b} {Iso {C} {a} {b}}) (i : \Pi {a : C} -> (e (idpe a)).f = id) {a b : C} : IsEquiv (C.idtoiso {a} {b})
    => \have t : e.f = C.idtoiso {a} {b} => ext (\case \elim b, \elim __ \with {
      | b, idp => Iso.equals _ _ i
    }) \in transport (IsEquiv __) t (inP e)

  \lemma makeUnivalence {C : Precat} (c : \Pi (e : Iso {C}) -> \Sigma (p : e.dom = e.cod) (transport (Hom e.dom) p id = e)) {a b : C} : IsEquiv (C.idtoiso {a} {b})
    => inP $ pathEquiv (\lam a b => Iso {C} {a} {b}) (\lam {a} {b} => \new Retraction {
      | f => C.idtoiso
      | sec e => (c e).1
      | f_sec (e : Iso) => Iso.equals _ _ (Jl (\lam b p => C.=_hom p = transport (Hom e.dom) p id) idp (c e).1 *> (c e).2)
    })

  \lemma makeUnivalenceFromPath {C : Precat} (c : \Pi (e : Iso {C}) -> \Sigma (p : e.dom = e.cod) (Path (\lam i => Hom e.dom (p i)) id e)) {a b : C} : IsEquiv (C.idtoiso {a} {b})
    => makeUnivalence \lam e => ((c e).1, pathOver.conv (c e).2)
}

\func DiscretePrecat (X : \Type) : Precat X \cowith
  | Hom x y => Trunc0 (x = y)
  | id => in0 idp
  | o {x y z : X} (t : Trunc0 (y = z)) (s : Trunc0 (x = y)) : Trunc0 (x = z) \elim t, s {
    | in0 y=z, in0 x=y => in0 (x=y *> y=z)
  }
  | id-left {_} {_} {p} => cases p idp
  | id-right {_} {_} {p} => cases p (pmap in0 (idp_*> _))
  | o-assoc {_} {_} {_} {_} {p} {q} {r} => cases (p,q,r) (pmap in0 (inv (*>-assoc _ _ _)))
  \where {
    \func map {X : \Type} {D : Precat} (f : X -> D) {x y : X} (h : Hom {DiscretePrecat X} x y) : Hom (f x) (f y) \elim h
      | in0 idp => id
  }

\sfunc SIP (C : Cat) (Str : C -> \Type) (isHom : \Pi {x y : C} -> Str x -> Str y -> Hom x y -> \Type)
           (st : \Pi {x : C} {S1 S2 : Str x} -> isHom S1 S2 id -> isHom S2 S1 id -> S1 = S2)
           {x y : C} (e : Iso {C} {x} {y}) (S1 : Str x) (S2 : Str y) (p : isHom S1 S2 e) (q : isHom S2 S1 e.hinv)
  : \Sigma (p : x = y) (Path (\lam i => Str (p i)) S1 S2) (transport (Hom x) p id = e)
  => \case \elim y, \elim e, \elim S2, p, q, Cat.univalenceToTransport e \with {
    | y, (_,g,gf,fg), S2, p, q, (idp,idp) => (idp, st p $ transport (isHom S2 S1) (inv id-right *> gf) q, idp)
  }

\sfunc SIP-gen (C : Cat)
  (Str : C -> \Type) (dHom : \Pi {x y : C} -> Str x -> Str y -> Hom x y -> \Type) (dHom_id : \Pi {x : C} {S : Str x} -> dHom S S id)
  (isIso : \Pi {x y : C} {S1 : Str x} {S2 : Str y} {f : Hom x y} -> dHom S1 S2 f -> \Type)
  (st : \Pi {x : C} {S1 S2 : Str x} {f : dHom S1 S2 id} -> isIso f -> \Sigma (p : S1 = S2) (Path (\lam i => dHom S1 (p i) id) dHom_id f))
  {x y : C} (e : Iso {C} {x} {y}) (S1 : Str x) (S2 : Str y) (p : dHom S1 S2 e) (pi : isIso p)
  : \Sigma (r1 : x = y) (r2 : Path (\lam i => Str (r1 i)) S1 S2) (r3 : Path (\lam i => Hom x (r1 i)) id e) (Path (\lam i => dHom S1 (r2 i) (r3 i)) dHom_id p)
  => \case \elim y, \elim e, \elim S2, \elim p, \elim pi, Cat.univalenceToTransport e \with {
    | y, (_,g,g=id,_), S2, p, pi, (idp,idp) => (idp, (st pi).1, idp, (st pi).2)
  }

\sfunc SIP-comb (C : Cat) (D : C -> Cat)
  (Str : C -> \Type) (dHom : \Pi {x y : C} -> Str x -> Str y -> Hom x y -> \Type)
  (isIso : \Pi {x y : C} {S1 : Str x} {S2 : Str y} {f : Hom x y} -> dHom S1 S2 f -> \Type)
  (dHom_id : \Pi {x : C} {S : Str x} -> dHom S S id) (dHom_idIso : \Pi {x : C} {S : Str x} -> isIso (dHom_id {x} {S}))
  (do : \Pi {x : C} -> Embedding {Str x} {D x}) (dh : \Pi {x : C} {S1 S2 : Str x} (f : dHom S1 S2 id) (fi : isIso f) -> Iso {D x} {do S1} {do S2})
  (dh_id : \Pi {x : C} {S : Str x} -> (dh (dHom_id {x} {S}) dHom_idIso).f = id)
  (dhi : \Pi {x : C} {S1 S2 : Str x} (f g : dHom S1 S2 id) (fi : isIso f) (gi : isIso g) -> (dh f fi).f = (dh g gi).f -> f = g)
  {x y : C} (e : Iso {C} {x} {y}) (S1 : Str x) (S2 : Str y) (p : dHom S1 S2 e) (pi : isIso p)
  : \Sigma (r1 : x = y) (r2 : Path (\lam i => Str (r1 i)) S1 S2) (r3 : Path (\lam i => Hom x (r1 i)) id e) (Path (\lam i => dHom S1 (r2 i) (r3 i)) dHom_id p)
  => SIP-gen C Str dHom dHom_id isIso (\lam {x} {S1} fi =>
    \let | S1=S2 => do.pmap-isEquiv.ret $ Cat.isotoid (dh _ fi)
         | g {S : Str x} (p : S1 = S) => transport (dHom S1 __ id) p dHom_id
         | gi {S : Str x} (p : S1 = S) => Jl (\lam S S1=S => isIso (g S1=S)) dHom_idIso p
    \in (S1=S2, pathOver $ dhi (g S1=S2) _ (gi S1=S2) fi $ Jl (\lam S p => (dh (g p) (gi p)).f = transport (Hom (do S1)) (do.pmap-isEquiv p) id) dh_id S1=S2 *>
                  pmap (transport (Hom _) __ _) (do.pmap-isEquiv.f_ret _) *> Cat.transport_iso _)) e S1 S2 p pi

\class Graph (\coerce V : \Set) (E : V -> V -> \Set) {
  \data Paths (x y : V)
    | empty (x = y)
    | cons {z : V} (E x z) (Paths z y)

  \func concat {x y z : V} (p : Paths x y) (q : Paths y z) : Paths x z \elim p
    | empty idp => q
    | cons e p => cons e (concat p q)

  \lemma concat_idp {x y : V} {p : Paths x y} : concat p (empty idp) = p \elim p
    | empty idp => idp
    | cons e p => pmap (cons e) concat_idp

  \lemma concat-assoc {x y z w : V} {p : Paths x y} {q : Paths y z} {r : Paths z w} : concat (concat p q) r = concat p (concat q r) \elim p
    | empty idp => idp
    | cons e p => pmap (cons e) concat-assoc

  \func FreeCat : Cat V \cowith
    | Hom => Paths
    | id => empty idp
    | o q p => concat p q
    | id-left => concat_idp
    | id-right => idp
    | o-assoc => inv concat-assoc
    | univalence => Cat.makeUnivalence $ later \case \elim __ \with {
      | (x, y, empty idp, _, _, _) => (idp,idp)
      | (x, y, cons e p, _, (), _)
    }
}

\func TrivialCat : Cat => (\new Graph (\Sigma) (\lam _ _ => Empty)).FreeCat