\import Category
\import Category.Adjoint
\import Category.Coreflection
\import Category.Functor
\import Category.Limit
\import Category.Product
\import Equiv
\import Function.Meta
\import Logic.Unique
\import Meta
\import Paths
\import Paths.Meta
\import Set.SetCategory

\func isExponential {C : CartesianPrecat} (X : C) => RightAdjoint C C { | LAdj => C.bprodFunctorRight X }

\func isExponentiable {C : CartesianPrecat} (Y : C) => \Pi (X : C) -> Coreflection (C.bprodFunctorRight X) Y

\class CartesianClosedPrecat \extends CartesianPrecat {
  | exp (X : Ob) : isExponential X

  \func is-exponentiable (X : Ob) : isExponentiable X
    => \lam Y => coreflection {RightAdjointCoreflection.fromAdjoint (exp Y)} X

  \func transpose {X Y Z : Ob} (f : Hom (Bprod X Y) Z) : Hom X (exp Y Z)
    => Func f  eta X

  \func antitranspose {X Y Z : Ob} (f : Hom X (exp Y Z)) : Hom (Bprod X Y) Z
    => epsilon {exp Y} Z  prodMap f (id Y)

  \func eval-map {Y Z : Ob} : Hom (Bprod (exp Y Z) Y) Z
    => epsilon {exp Y} Z

  \func eval-transpose {X Y Z : Ob} (g : Hom (Bprod X Y) Z) : eval-map  prodMap (transpose g) (id Y) = g
    => RightAdjoint.eta_epsilon-equiv.ret_f g

  \func eval-map-eq {X Y : Ob} (f : Hom X Y) : eval-map  prodMap (name f) (id X)  pair terminalMap (id X) = f
    =>
      run {
        unfold name,
        rewrite (eval-transpose _),
        unfold,
        rewrite (o-assoc, beta2 _ _, id-right),
        idp
      }

  \func internal-comp {X Y Z : Ob} : Hom (Bprod (exp Y Z) (exp X Y)) (exp X Z)
    => transpose $ (eval-map  prodMap (id (exp Y Z)) eval-map)  associator

  \func name {X Y : Ob} (f : Hom X Y) : Hom terminal (exp X Y)
    => transpose $ f  terminal-prod-left.hinv

  \func global-elements-iso {X Y : Ob} : Equiv {Hom X Y} {Hom terminal (exp X Y)} name
    => \new QEquiv {
      | ret f => antitranspose f  terminal-prod-left.f
      | ret_f _ => unfold name $ rewrite (eval-transpose _, o-assoc, terminal-prod-left.hinv_f, id-right) idp
      | f_sec y => unfold name $ rewrite (o-assoc, terminal-prod-left.f_hinv, id-right) $ RightAdjoint.eta_epsilon-equiv.f_ret y
    }

  \func internal-homFunctor : Functor (ProductPrecat (Precat.op {\this}) \this) \this \cowith
    | F (X, Y) => exp X Y
    | Func (f, g) => transpose $ g  eval-map  prodMap (id _) f
    | Func-id => rewrite (id-left, prod-id, id-right) eta_epsilon-left
    | Func-o {(a1, b1)} {(a2, b2)} {(a3, b3)} {(f,g)} {(p,q)} => run {
      RightAdjoint.eta_epsilon-equiv.adjointInv,
      unfold,
      rewrite (prod-id-right, inv o-assoc, eval-transpose),
      o-assoc *> o-assoc *> pmap (g ) (later $ run {
        rewriteI prodMap-comp,
        rewrite (id-left, id-right),
        rewrite {2} prodMap-split-right,
        pmap (q  (_  __)) (prod-id-left p f) *> inv o-assoc *> inv o-assoc *> pmap (`∘ _) (inv (eval-transpose _)) *> o-assoc
      }) *> inv (o-assoc *> o-assoc)
    }
}

\open PrecatWithBprod

\instance SetCartesianClosed : CartesianClosedPrecat SetBicat
  | CartesianPrecat => SetBicat
  | exp X => RightAdjointCoreflection.toAdjoint (exp-coreflection X)
  \where {
    \func exp-coreflection (X : SetBicat) : RightAdjointCoreflection SetBicat SetBicat (SetBicat.bprodFunctorRight X)
    \cowith
      | coreflection => power-coreflection X

    \func power-coreflection (X Z : SetBicat) : Coreflection (SetBicat.bprodFunctorRight X) Z
    \cowith
      | Coreflected => X -> Z
      | corefl-map => apply
      | isCoreflection {_} => \new QEquiv {
        | ret => curry
        | ret_f f => ext (\lam _ _ => unfold $ unfold (apply, curry) $ rewrite (proj1-prodMap-applied f (id X) _, proj2-prodMap f (id X) _, proj1-unname-pair, proj2-unname-pair) idp)
        | f_sec => f_sec
      }
      \where {
        \func apply (f : Product.apex {Bprod {SetBicat} (X -> Z) X}) : Z => (proj1 {SetBicat} f) (proj2 {SetBicat} f)

        \func curry {Y : SetBicat} (f : Product.apex {Bprod {SetBicat} Y X} -> Z) : Y -> X -> Z =>
          \lam (y : Y) (x : X) => f (unname' $ pair {SetBicat} (name' y) (name' x))

        \func f_sec {Y : SetBicat} (g : Product.apex {Bprod {SetBicat} Y X} -> Z)
          : (\lam x => apply (prodMap {SetBicat} (curry g) (id X) x)) = g =>
          ext (\lam f => unfold apply $ rewrite (proj1-prodMap-applied (curry g) (id X) f, proj2-prodMap (curry g) (id X) f) $
                                        curry-eq g f)

        \func curry-eq {Y : SetBicat} (g : Product.apex {Bprod {SetBicat} Y X} -> Z) (f : Product.apex {Bprod {SetBicat} Y X})
          : curry g (proj1 {SetBicat} f) (proj2 {SetBicat} f) = g f =>
          unfold curry $ pmap g $ bprod-ext (proj1-unname-pair (proj1 {SetBicat} f) (proj2 {SetBicat} f)) (proj2-unname-pair (proj1 {SetBicat} f) (proj2 {SetBicat} f))
      }

    \func from_terminal : Hom {SetBicat} terminal.apex (\Sigma) =>
      \lam _ => ()

    \func to_terminal : Hom {SetBicat} (\Sigma) terminal.apex =>
      PrecatWithTerminal.terminalMap {SetBicat}

    \func name' {X : SetBicat} (x : X) : Hom {SetBicat} terminal.apex X => \lam _ => x

    \func unname' {X : SetBicat} (f : Hom {SetBicat} terminal.apex X) : X => (f SetBicat. to_terminal) ()

    \func terminal-obj-prop : isProp terminal.apex =>
      \lam a a' => path (\lam i => (PrecatWithTerminal.terminal-unique {SetBicat} {\Sigma} {\lam _ => a} {\lam _ => a'} @ i) ())

    \func global-elements-iso {X : SetBicat} : QEquiv {X} {Hom {SetBicat} terminal.apex X} name' unname'
    \cowith
      | ret_f _ => idp
      | f_sec _ => unfold (name', unname') $ ext (\lam a => unfold $ rewrite (terminal-obj-prop (to_terminal ()) a) idp)

    \func name-inj {X : SetBicat} {x y : X} (c : name' x = name' y) : x = y => Equiv.isInj {global-elements-iso} c

    \func name-f {X Y : SetBicat} (f : X -> Y) (x : X) : f SetBicat. name' x = name' (f x) =>
      inv $ Equiv.adjointInv {global-elements-iso} idp

    \func unname-adjoint {X : SetBicat} {f : X} {g : Hom {SetBicat} terminal.apex X} (eq : name' f = g) : f = unname' g =>
      Equiv.adjoint {global-elements-iso {X}} eq

    \func bprod-ext {X Y : SetBicat} {f g : Product.apex {Bprod {SetBicat} X Y}} (eq1 : proj1 {SetBicat} f = proj1 {SetBicat} g)
                    (eq2 : proj2 {SetBicat} f = proj2 {SetBicat} g) : f = g =>
      name-inj (pair-unique {SetBicat} {X} {Y} {terminal.apex} (rewrite (name-f (proj1 {SetBicat}) f, name-f (proj1 {SetBicat}) g) $ pmap name' eq1)
          (rewrite (name-f (proj2 {SetBicat}) f, name-f (proj2 {SetBicat}) g) $ pmap name' eq2))

    \func proj1-pair-applied {X Y Z : SetBicat} (f : Hom {SetBicat} Z X) (g : Hom {SetBicat} Z Y)
                             (z : Z)
      : proj1 {SetBicat} (pair {SetBicat} f g z) = f z =>
      \let s : proj1 {SetBicat}  {SetBicat} pair {SetBicat} f g = f => rewrite (beta1 _ _) idp  \in
        path (\lam i => (s @ i) z)

    \func proj1-prodMap-applied {X Y Z W : SetBicat} (f : X -> Z) (g : Y -> W) (p : Product.apex {Bprod {SetBicat} X Y})
      : proj1 {SetBicat} (prodMap {SetBicat} f g p) = f (proj1 {SetBicat} p) =>
      \let s : proj1 {SetBicat}  {SetBicat} (prodMap {SetBicat} f g) = f  {SetBicat} proj1 {SetBicat} => beta1 _ _ \in
        path (\lam i => (s @ i) p)

    \func proj2-pair-applied {X Y Z : SetBicat} (f : Hom {SetBicat} Z X) (g : Hom {SetBicat} Z Y)
                             (z : Z)
      : proj2 {SetBicat} (pair {SetBicat} f g z) = g z =>
      \let s : proj2 {SetBicat}  {SetBicat} pair {SetBicat} f g = g => rewrite (beta2 _ _) idp  \in
        path (\lam i => (s @ i) z)

    \func proj2-prodMap {X Y Z W : SetBicat} (f : X -> Z) (g : Y -> W) (p : Product.apex {Bprod {SetBicat} X Y})
      : proj2 {SetBicat} (prodMap {SetBicat} f g p) = g (proj2 {SetBicat} p) =>
      \let help : proj2 {SetBicat}  {SetBicat} (prodMap {SetBicat} f g) = g  {SetBicat} proj2 {SetBicat} => beta2 _ _ \in
        path (\lam i => (help @ i) p)

    \func proj1-unname-pair {A B : SetBicat} (a : A) (b : B)
      : proj1 {SetBicat} (unname' (pair {SetBicat} (name' a) (name' b))) = a =>
      unfold unname' $ rewrite (proj1-pair-applied (name' a) (name' b) _) idp

    \func proj2-unname-pair {A B : SetBicat} (a : A) (b : B)
      : proj2 {SetBicat} (unname' (pair {SetBicat} (name' a) (name' b))) = b =>
      unfold unname' $ rewrite (proj2-pair-applied (name' a) (name' b) _) idp
  }