\import Algebra.Meta
\import Category
\import Equiv
\import Function.Meta
\import Logic.Meta
\import Meta
\import Paths
\import Paths.Meta

\class Functor (C D : Precat) (\classifying F : C -> D) {
  | Func {X Y : C} : Hom X Y -> Hom (F X) (F Y)
  | Func-id {X : C} : Func (id X) = id (F X)
  | Func-o {X Y Z : C} {g : Hom Y Z} {f : Hom X Y} : Func (g  f) = Func g  Func f

  \func op : Functor C.op D.op F \cowith
    | Func f => Func f
    | Func-id => Func-id
    | Func-o => Func-o

  \func Func-iso (e : Iso {C}) : Iso {D} {F e.dom} {F e.cod} (Func e) \cowith
    | hinv => Func e.hinv
    | hinv_f => inv Func-o *> pmap Func e.hinv_f *> Func-id
    | f_hinv => inv Func-o *> pmap Func e.f_hinv *> Func-id
}

\func Id {C : Precat} : Functor C C \cowith
  | F X => X
  | Func h => h
  | Func-id => idp
  | Func-o => idp

\func Comp {C D E : Precat} (G : Functor D E) (F : Functor C D) : Functor C E \cowith
  | F X => G (F X)
  | Func f => G.Func (F.Func f)
  | Func-id => pmap G.Func F.Func-id *> G.Func-id
  | Func-o => pmap G.Func F.Func-o *> G.Func-o

\func Const {C D : Precat} (d : D) : Functor C D \cowith
  | F _ => d
  | Func _ => id d
  | Func-id => idp
  | Func-o => inv id-left
  \where {
    \func natTrans {C D : Precat} {d d' : D} (f : Hom d d') : NatTrans {C} (Const d) (Const d') \cowith
      | trans _ => f
      | natural g => id-right *> inv id-left
  }

\record NatTrans \plevels obj >= hom >= iobj >= ihom {C : Precat \levels (iobj,ihom) _} {D : Precat \levels (obj,hom) _} (F G : Functor C D) {
  | \coerce trans (X : C) : Hom (F X) (G X)
  | natural {X Y : C} (f : Hom X Y) : trans Y  F.Func f = G.Func f  trans X

  \func op : NatTrans G.op F.op \cowith
    | trans => trans
    | natural f => inv (natural f)

  \lemma iso (e : \Pi {X : C} -> Iso (trans X)) : NatTrans G F (\lam X => e.hinv) \cowith
    | natural f => e.isMono $ rewrite (inv o-assoc, e.f_hinv, id-left, inv o-assoc, natural f, o-assoc, e.f_hinv, id-right) idp
} \where {
  \func Comp-left {C D E : Precat} (a : NatTrans {D} {E}) (H : Functor C D) : NatTrans (Comp a.F H) (Comp a.G H) \cowith
    | trans x => a (H x)
    | natural f => a.natural (H.Func f)

  \func Comp-right {C D E : Precat} (H : Functor D E) (a : NatTrans {C} {D}) : NatTrans (Comp H a.F) (Comp H a.G) \cowith
    | trans x => H.Func (a x)
    | natural f => inv H.Func-o *> pmap H.Func (a.natural f) *> H.Func-o
}

\instance FunctorPrecat  {C D : SmallPrecat} : Precat (Functor C D)
  | Hom => NatTrans
  | id F => \new NatTrans {
    | trans X => id (F X)
    | natural f => id-left *> inv id-right
  }
  | o b a => \new NatTrans {
    | trans X => b X  a X
    | natural f => run {
      rewrite (o-assoc, natural {a} f),
      rewriteI o-assoc,
      rewrite (natural {b} f),
      o-assoc
    }
  }
  | id-left => exts (\lam X => id-left)
  | id-right => exts (\lam X => id-right)
  | o-assoc => exts (\lam X => o-assoc)

\instance FunctorCat {C : SmallPrecat} {D : SmallCat} : Cat (Functor C D)
  | Precat => FunctorPrecat
  | univalence => Cat.makeUnivalence $ later \lam e =>
    (exts (\lam X => D.isotoid (mapIso e X), \lam {X} {Y} h => Cat.transport_Hom_iso (mapIso e X) (mapIso e Y) (natural h)),
     simp_coe $ simp_coe \lam X => Cat.transport_iso (mapIso e X))
  \where {
    \func mapIso {C D : SmallPrecat} (e : Iso {FunctorPrecat {C} {D}}) (X : C) : Iso {D} \cowith
      | dom => e.dom X
      | cod => e.cod X
      | f => e.f X
      | hinv => e.hinv X
      | hinv_f => path (\lam i => trans {e.hinv_f @ i} X)
      | f_hinv => path (\lam i => trans {e.f_hinv @ i} X)
  }

\instance FamPrecat {J : \Type} (D : Precat) : Precat (J -> D)
  | Hom F G => \Pi {j : J} -> Hom (F j) (G j)
  | id F {j} => id (F j)
  | o g f {j} => g  f
  | id-left => ext (\lam {j} => id-left)
  | id-right => ext (\lam {j} => id-right)
  | o-assoc => ext (\lam {j} => o-assoc)

\instance FamCat {J : \Type} (D : Cat) : Cat (J -> D)
  | Precat => FamPrecat D
  | univalence => Cat.makeUnivalence $ later \lam e => (ext \lam j => D.isotoid (mapIso e j), simp_coe \lam {j} => Cat.transport_iso (mapIso e j))
  \where {
    \func mapIso {J : \Type} {D : Precat} (e : Iso {FamPrecat {J} D}) (j : J) : Iso {D} \cowith
      | dom => e.dom j
      | cod => e.cod j
      | f => e.f
      | hinv => e.hinv
      | hinv_f => path (e.hinv_f @ __)
      | f_hinv => path (e.f_hinv @ __)
  }

\class FaithfulFunctor \extends Functor {
  | isFaithful {X Y : C} {f g : Hom X Y} : Func f = Func g -> f = g

  \func op : FaithfulFunctor \cowith
    | Functor => Functor.op
    | isFaithful p => isFaithful p
}

\class FullFunctor \extends Functor {
  | isFull {X Y : C} (f : Hom (F X) (F Y)) :  (g : Hom X Y) (Func g = f)

  \func op : FullFunctor \cowith
    | Functor => Functor.op
    | isFull f => isFull f
}

\class FullyFaithfulFunctor \extends FullFunctor, FaithfulFunctor {
  | isFullyFaithful {X Y : C} : Equiv (Func {_} {X} {Y})
  | isFull f => isSurjMap {ESEquiv.fromEquiv isFullyFaithful} f
  | isFaithful p => isFullyFaithful.isInj p

  \func op : FullyFaithfulFunctor \cowith
    | Functor => Functor.op
    | isFullyFaithful => isFullyFaithful

  \func inverse {X Y : C} (f : Hom (F X) (F Y)) : Hom X Y
    => isFullyFaithful.ret f

  \lemma inverse-left {X Y : C} {f : Hom X Y} : inverse (Func f) = f
    => isFullyFaithful.ret_f f

  \lemma inverse-right {X Y : C} {f : Hom (F X) (F Y)} : Func (inverse f) = f
    => isFullyFaithful.f_ret f

  \lemma inverse-id {x : C} : inverse (id (F x)) = id x
    => pmap inverse (inv Func-id) *> inverse-left

  \lemma inverse-o {x y z : C} {g : Hom (F y) (F z)} {f : Hom (F x) (F y)} : inverse (g  f) = inverse g  inverse f
    => inv (pmap inverse Func-o *> cong inverse-right inverse-right) *> inverse-left
}