\import Algebra.Monoid
\import Arith.Nat
\import Category
\import Category.Functor
\import Category.Limit
\import Equiv
\import Function.Meta ($)
\import HLevel
\import Logic
\import Logic.Meta
\import Meta
\import Order.Lattice
\import Order.PartialOrder
\import Paths
\import Paths.Meta
\import Relation.Equivalence
\import Set

\record SetHom (Dom Cod : BaseSet)
  | \coerce func : Dom -> Cod

\instance SetCat : Cat \Set
  | Hom X Y => X -> Y
  | id _ => \lam x => x
  | o g f => \lam x => g (f x)
  | id-left => idp
  | id-right => idp
  | o-assoc => idp
  | univalence => Cat.makeUnivalence $ later \lam (e : Iso) =>
    \let p => path (iso e.f e.hinv (\lam x => path ((e.hinv_f @ __) x)) (\lam y => path ((e.f_hinv @ __) y)))
    \in (p, simp_coe (\lam d => idp))

\sfunc SIP_Set (Str : \Set -> \hType (\suc \lp)) (isHom : \Pi {x y : \Set} -> Str x -> Str y -> (x -> y) -> \hType (\suc \lp))
               (st : \Pi {X : \Set} {S1 S2 : Str X} -> isHom S1 S2 (\lam x => x) -> isHom S2 S1 (\lam x => x) -> S1 = S2)
               {X Y : \Set} (e : Iso {SetCat} {X} {Y}) (S1 : Str X) (S2 : Str Y) (p : isHom S1 S2 e.f) (q : isHom S2 S1 e.hinv)
  : \Sigma (p : X = Y) (Path (\lam i => Str (p @ i)) S1 S2) (\Pi (x : X) -> transport (\lam Z => Z) p x = e.f x)
  => \have (p,q,s) => SIP SetCat Str isHom st e S1 S2 p q
     \in (p, q, \lam x => inv (transport_pi (\lam _ => X) (\lam Z => Z) p (\lam z => z) x) *> path (\lam i => (s @ i) x))

\instance SetCoproduct {J : \Type} (F : J -> \Set) : Product {J} {SetCat.op} F
  | apex => Trunc0 (\Sigma (j : J) (F j))
  | proj j x => in0 (j,x)
  | tupleMap f (in0 (j,x)) => f j x
  | tupleBeta => idp
  | tupleEq e => ext \case \elim __ \with {
    | in0 (j,x) => pmap (__ x) (e j)
  }

\instance SetCoequalizer {X Y : \Set} (f g : X -> Y) : Equalizer {SetCat.op} f g
  | apex => Quotient (\lam y y' => \Sigma (x : X) (f x = y) (g x = y'))
  | eql => in~
  | equal => ext (\lam x => path (~-equiv _ _ (later (x,idp,idp))))
  | isEqualizer Z => \new QEquiv {
    | ret (h,p) => \case __ \with {
      | in~ y => h y
      | ~-equiv y y' (x,fx=y,gx=y') => inv (pmap h fx=y) *> pmap (__ x) p *> pmap h gx=y'
    }
    | ret_f h => ext \case \elim __ \with {
      | in~ y => idp
    }
    | f_sec (h,p) => ext idp
  }

\instance SetBicat : BicompleteCat
  | Cat => SetCat
  | limit F => \new Limit {
    | apex => Cone F (\Sigma)
    | coneMap j x => coneMap {x} j ()
    | coneCoh h => ext (\lam s => pmap (__ ()) (coneCoh {s}  h))
    | isLimit Z => \new QEquiv {
      | ret c z => \new Cone {
        | coneMap j _ => coneMap {c} j z
        | coneCoh h => ext (\lam _ => pmap (__ z) (coneCoh {c} h ))
      }
      | ret_f => idpe
      | f_sec => idpe
    }
  }
  | colimit {J : Precat} G => limits<=pr+eq {SetCat.op} (\lam J G => SetCoproduct G) (\lam {X} {Y} f g => SetCoequalizer f g) {J.op} (Functor.op {G})
  \where {
    \lemma cone-isLim (c : Cone { | D => SetCat }) (e : Equiv (conePullback c (\Sigma))) : Limit { | Cone => c } \cowith
      | isLimit Z =>
        \have coneMap (z : Z) (c' : Cone c.G Z) : Cone c.G (\Sigma) => \new Cone {
          | coneMap j _ => c'.coneMap j z
          | coneCoh h => ext \lam _ => path (\lam i => c'.coneCoh h i z)
        } \in \new QEquiv {
            | ret (c' : Cone) z => e.ret (coneMap z c') ()
            | ret_f h => ext \lam z =>
                \have t => e.ret_f (\lam _ => h z)
                \in path (\lam i => t i ())
            | f_sec (c' : Cone) => exts \lam j => ext \lam z =>
                \have t => e.f_ret (coneMap z c')
                \in path (\lam i => Cone.coneMap {t i} j ())
          }
  }

\type SetColimit {J : SmallPrecat} (F : Functor J SetCat)
  => Quotient {\Sigma (j : J) (F j)} \lam s s' => \Sigma (p : Hom s.1 s'.1) (F.Func p s.2 = s'.2)
  \where {
    \func inC {J : SmallPrecat} {F : Functor J SetCat} (s : \Sigma (j : J) (F j)) : SetColimit F => in~ s

    \lemma ~-cequiv {J : SmallPrecat} {F : Functor J SetCat} {s s' : \Sigma (j : J) (F j)} (f : Hom s.1 s'.1) (p : F.Func f s.2 = s'.2)
      : in~ s = {SetColimit F} in~ s'
      => path (\lam i => ~-equiv s s' (f,p) i)

    \lemma unext-ub {J : JoinSemilattice} {F : Functor J SetCat} {s s' : \Sigma (j : J) (F j)} (p : inC s = inC s')
      :  (j : J) (p : s.1 <= j) (q : s'.1 <= j) (F.Func p s.2 = F.Func q s'.2)
      => Quotient.equalityClosure (\new Equivalence (\Sigma (j : J) (F j)) _ {
        | ~-transitive => later \lam (inP s) (inP t) => inP (s.1  t.1, s.2 <=∘ join-left, t.3 <=∘ join-right, Func-app F *> pmap (F.Func join-left) s.4 *> inv (Func-app F) *> poset-app *> Func-app F *> pmap (F.Func join-right) t.4 *> inv (Func-app F))
        | ~-reflexive {x} => later $ inP (x.1, <=-refl, <=-refl, idp)
        | ~-symmetric => later \lam (inP s) => inP (s.1, s.3, s.2, inv s.4)
      }) (later \lam {x} {y} s => inP (x.1  y.1, join-left, join-right, Func-app F *> pmap (F.Func join-right) s.2)) (path \lam i => p i)

    \lemma Func-app {J : SmallPrecat} (F : Functor J SetCat) {j1 j2 j3 : J} {f : Hom j1 j2} {g : Hom j2 j3} {a : F j1} : F.Func (g  f) a = F.Func g (F.Func f a)
      => pmap (__ a) F.Func-o

    \lemma poset-app {J : Poset} {F : Functor J SetCat} {j j' : J} {f g : j <= j'} {a : F j} : F.Func f a = F.Func g a
      => pmap (F.Func __ a) prop-pi

    \lemma poset-id {J : Poset} {F : Functor J SetCat} {j : J} {p : j <= j} {a : F j} : F.Func p a = a
      => poset-app *> pmap (__ a) F.Func-id

    \func inMap (j : J) (a : F j) : SetColimit F
      => in~ (j,a)

    \lemma inMap-coh {j j' : J} (f : Hom j j') (a : F j) : inMap j a = inMap j' (F.Func f a)
      => ~-cequiv f idp
  }

\func NatFunctor {C : Precat} (F : Nat -> C) (f : \Pi {n : Nat} -> Hom (F n) (F (suc n))) : Functor NatSemiring C \cowith
  | F => F
  | Func p => natHom (_, <=_exists p)
  | Func-id => pmap natHom (sigma-isProp _ (0,idp)) *> natHom_id
  | Func-o => pmap natHom (sigma-isProp _ _) *> natHom-comp
  \where {
    \lemma sigma-isProp {n m : Nat} : isProp (\Sigma (k : Nat) (n + k = m))
      => \lam s t => ext $ NatSemiring.cancel-left n $ s.2 *> inv t.2

    \func natHom {n m : Nat} (s : \Sigma (k : Nat) (n + k = m)) : Hom (F n) (F m) \elim m, s
      | _, (0, idp) => id (F n)
      | 0, (suc k, ())
      | suc m, (suc k, q) => f  natHom (k, pmap pred q)

    \lemma natHom_id {n : Nat} : natHom (0,idp) = id (F n) \elim n
      | 0 => idp
      | suc n => idp

    \lemma natHom-comp {n m k : Nat} {s : \Sigma (l : Nat) (n + l = m)} {t : \Sigma (l : Nat) (m + l = k)}
      : natHom (s.1 + t.1, inv +-assoc *> pmap (`+ _) s.2 *> t.2) = natHom t  natHom s \elim k, t
      | 0, (0, idp) => pmap natHom (sigma-isProp _ _) *> inv id-left
      | suc k, (0, idp) => pmap natHom (sigma-isProp _ _) *> inv id-left
      | 0, (suc m, ())
      | suc k, (suc m, q) => pmap (f ) (pmap natHom (sigma-isProp _ _) *> natHom-comp) *> inv o-assoc
  }