\import Algebra.Meta
\import Category
\import Category.CartesianClosed
\import Category.Coreflection
\import Category.Limit
\import Equiv
\import Function (isInj)
\import Function.Meta
\import Logic
\import Meta
\import Paths
\import Paths.Meta
\import Set.Category
\open PrecatWithBprod

\class ToposPrecat \extends FinCompletePrecat, CartesianClosedPrecat {
  -- subobject classifier map

  | subobj-classifier \alias omega : Ob
  | true-map : Hom terminal omega

  -- characteristic maps

  | char-map {S B : Ob} (m : Mono {\this} {S} {B}) : Hom B omega
  | char-pullback {S B : Ob} (m : Mono {\this} {S} {B}) : Pullback {\this} (char-map m) true-map S m terminalMap
  | char-unique {S B : Ob} {m : Mono {\this} {S} {B}} {phi : Hom B omega}
                (phi-pull : Pullback phi true-map S m terminalMap) : phi = char-map m

  -- omega is exponentiable

  | p-exponential : isExponentiable omega

  \default p-exponential => CartesianClosedPrecat.is-exponentiable omega

  \func power \alias P (B : Ob) : Ob => p-exponential B

  \func belongs {B : Ob} : Hom (Bprod (P B) B) omega => corefl-map {p-exponential B}

  \func p-transpose {A B : Ob} (f : Hom (Bprod A B) omega) : Hom A (P B) => ret {isCoreflection {p-exponential B}} f

  \func p-transpose-univ {A B : Ob} (f : Hom (Bprod A B) omega) : f = belongs  (prodMap (p-transpose f) (id B)) =>
    inv $ Equiv.f_ret {isCoreflection {p-exponential B}} f

  \func p-transpose-unique {A B : Ob} {f : Hom (Bprod A B) omega} {g : Hom A (P B)} (comm : f = belongs  prodMap g (id B))
    : g = p-transpose f => Equiv.adjoint {isCoreflection {p-exponential B}} (inv comm)

  \func anti-transpose  {A B : Ob} (g : Hom A (P B)) : Hom (Bprod A B) omega =>
    belongs  (prodMap g (id B))

  \func antitranspose-eq {A B : Ob} (f : Hom (Bprod B A) omega) : f = anti-transpose (p-transpose f) =>
    p-transpose-univ f

  \func transpose-inj {A B : Ob} {f g : Hom (Bprod A B) omega} (e : p-transpose f = p-transpose g)
    : f = g => p-transpose-univ f *> pmap (belongs  prodMap __ (id B)) e *> (inv $ p-transpose-univ g)

  \func internal-equality \alias eq (B : Ob) : Hom (Bprod B B) omega =>
    char-map (diagonal.isSplitMono B)

  \func singleton  (B : Ob) : Hom B (P B) => p-transpose (eq B)

  -- 'char-rel' gives the characteristic map of the functional relation corresponding to f.

  \func char-rel {B C : Ob} (f : Hom B C) : Hom (Bprod B C) omega => eq C  prodMap f (id C)

  \func eqq-p-transpose-s {B X : Ob} (b : Hom X B) : singleton B  b = p-transpose (char-rel b) =>
    p-transpose-unique {\this} {_} {_} {char-rel b} {singleton B  b} $
    rewrite (prod-id-right _ _, inv o-assoc, inv $ p-transpose-univ _) idp

  \func singleton-mono (B : Ob) : Mono (singleton B)
  \cowith
    | isMono {X} {b} {b'} sb=sb' =>
      \let
        c : char-rel b = char-rel b' => transpose-inj ((inv $ eqq-p-transpose-s b) *> sb=sb' *> eqq-p-transpose-s b')
        | b-square => full-square b
        | b'-square : Pullback {\this} (char-rel b) true-map X (pair (id X) b') =>
          transport (Pullback {\this} __ true-map X (pair (id X) b')) (inv c) (full-square b')
        | h-iso : Iso {\this} {X} {X} => Pullback.unique (char-rel b) true-map b-square b'-square
        | pb~pb*h : pair (id X) b = pair (id X) b'  h-iso => inv (Pullback.unique.p-beta1 b'-square b-square)
        | c' : proj1  (pair (id X) b'  h-iso.f) = h-iso.f => rewriteI o-assoc $ rewrite (beta1 _ _) id-left
        | h~1 : id X = h-iso.f => inv (beta1 _ _) *> pmap (proj1  __) pb~pb*h *> c'
        | b~b'h : b = b'  h-iso.f => inv (beta2 _ _) *> pmap (proj2  __) pb~pb*h *> (rewriteI o-assoc $ rewrite (beta2 _ _) idp)
      \in
        b~b'h *> rewriteI h~1 id-right
    \where {
      \func full-square {X : Ob} (b : Hom X B)
        : Pullback {\this} (char-rel b) true-map X (pair (id X) b) => pullback-lemma right-square left-square

      \func right-square
        : Pullback {\this} {Bprod B B} {terminal} {omega} (eq B) true-map B (diagonal B) terminalMap =>
        char-pullback (diagonal.isSplitMono B)

      \func left-square{X : Ob} {b : Hom X B}
        : Pullback (prodMap b (id B)) (diagonal B) X (pair (id X) b) b
      \cowith
        | pbCoh => pair-unique
            (run {
              rewriteI o-assoc,
              rewrite (beta1 _ _, o-assoc, beta1 _ _, id-right),
              rewriteI o-assoc,
              rewrite (beta1 _ _, id-left),
              idp
            })
            (run {
              rewriteI o-assoc,
              rewrite (beta2 _ _),
              rewriteI o-assoc, rewrite (beta2 _ _, o-assoc, beta2 _ _) equation
            })
        | pbMap p1 _ _ => proj1  p1
        | pbBeta1 {w} {p1} {p2} {c} =>
          \let
            left : proj2  (diagonal B  p2) = p2 => rewriteI o-assoc $ rewrite (beta2 _ _) equation
            | right : proj2  (prodMap b (id B)  p1) = proj2  p1 => rewriteI o-assoc $ rewrite (beta2 _ _) equation \in
            pair-unique
                (run {
                  rewriteI o-assoc,
                  rewrite (beta1 _ _ ),
                  equation
                })
                (run {rewriteI o-assoc,
                      rewrite (beta2 _ _),
                      pbBeta2' w p1 p2 c *> inv left *> pmap (proj2  __) (inv c) *> right
                })
        | pbBeta2 {w} {p1} {p2} {c} => pbBeta2' w p1 p2 c
        | pbEta {_} {h1} {h2} c _ =>
          \let p2 : h1 = proj1  (pair (id X) b  h1) => rewriteI o-assoc $ rewrite (beta1 _ _) equation
               | p1 : proj1  (pair (id X) b  h2) = h2  => rewriteI o-assoc $ rewrite (beta1 _ _) equation
          \in p2 *> pmap (proj1  __) c *> p1

      \func pbBeta2' {B X : Ob} {b : Hom X B} (w : Ob) (p1 : Hom w (Bprod X B))
                     (p2 : Hom w B) (c : prodMap b (id B)  p1 = diagonal B  p2) : b  (proj1  p1) = p2 =>
        \let left : proj1  (diagonal B  p2) = p2 => rewriteI o-assoc $ rewrite (beta1 _ _) equation
             | right : proj1  (prodMap b (id B)  p1) = b  (proj1  p1) =>
               rewriteI o-assoc $ rewrite (beta1 _ _) o-assoc
        \in
          inv right *> pmap (proj1  __) c *> left
    }

  \func is-singleton {B : Ob} : Hom (P B) omega => char-map (singleton-mono B)

  \func true-over-obj {B : Ob} : Hom B omega => true-map  terminalMap

  \func monic+epi=iso{X Y : Ob} (m : Mono {\this} {X} {Y}) (m-is-epi : isEpi {\this} {X} {Y} m)
    : Iso {\this} {X} {Y} m =>
    \let equalizer => monic-is-regular m
         | true=char-m : true-over-obj = char-map m => m-is-epi (Equalizer.equal {equalizer} : true-over-obj  m.f = char-map m  m)
         | equalizer-f : Equalizer (char-map m) (char-map m) => transport (Equalizer __ (char-map m) X m) true=char-m equalizer
    \in
      Equalizer.equalizer-iso equalizer-f

  \func monic-is-regular  {X Y : Ob} (m : Mono {\this} {X} {Y}) : Equalizer true-over-obj (char-map m) X m =>
    \new Equalizer {
      | equal =>
        rewrite (pbCoh {char-pullback m}, o-assoc) $
        rewrite terminal-unique idp
      | isEqualizer _ => \new QEquiv {
        | ret (h, com) => pbMap {char-pullback m} h terminalMap
            (rewriteI com $ rewrite o-assoc $ rewrite terminal-unique idp)
        | ret_f _ => pbEta {char-pullback m} (rewrite (pbBeta1 {char-pullback m}) $ idp)
            (rewrite (pbBeta2 {char-pullback m}) terminal-unique)
        | f_sec (h, com) => exts $ rewrite (pbBeta1 {char-pullback m}) idp
      }
    }

  \func global (X : Ob) => Hom terminal X

  \func name {A : Ob} (phi : Hom A omega) : global (P A) => p-transpose (phi  terminal-prod-left.hinv)

  -- Topos is cartesian closed

  {-
  Consider P (B x C) as the type of binary relations between elements of B and C.
  Then, 'image' takes a relation S and an element b of B and returns the subset of C that corresponds to b.
  -}

  \func image {B C : Ob} : Hom (Bprod (P $ Bprod B C) B) (P C) =>
    p-transpose $ belongs {_} {Bprod B C}  associator-iso.f

  -- 'single-image-test' takes a relation S and element b of B and tests if the image of relation is a singleton subset.

  \func is-image-single {B C : Ob} : Hom (Bprod (P $ Bprod B C) B) omega => is-singleton  image

  {-
  The idea: a relation S between B and C is functional iff all elements of B have a single image under S.
  'all-with-single-img' returns the subset consisinf of elements of B with singleton images.
  -}

  \func all-with-single-img {B C : Ob} : Hom (P $ Bprod B C) (P B) => p-transpose is-image-single

  -- Finally, the apex of the pullback gives the type of functional relations between B and C.

  \func graphs {B C : Ob} : Pullback (name (true-over-obj {\this} {B})) (all-with-single-img {\this} {B} {C}) =>
    pullback (name (true-over-obj {\this} {B})) (all-with-single-img {\this} {B} {C})
    \where {
      \func apex (B C : Ob) : Ob => Pullback.apex {graphs {_} {B} {C} }

      \func map {B C : Ob} : Hom (graphs.apex B C) (P $ Bprod B C) => Pullback.pbProj2 {graphs {_} {B} {C}}
    }

  \lemma terminate {B C : Ob} {f : Hom B C} : terminalMap  f = terminalMap => terminal-unique

  \func eval {B C : Ob} : Hom (Bprod (graphs.apex B C) B) C =>
    pbMap {char-pullback $ singleton-mono C} (image  prodMap graphs.map (id B)) terminalMap comm
    \where {
      \func comm  : char-map (singleton-mono C)  (image  prodMap graphs.map (id B)) = true-map  terminalMap =>
        run {
          rewriteI o-assoc ,
          rewrite (antitranspose-eq _ : char-map (singleton-mono C)  image = belongs  prodMap all-with-single-img (id B)),
          rewrite o-assoc,
          rewriteI (prod-id-right, pbCoh {graphs}),
          rewrite prod-id-right,
          rewriteI (o-assoc, antitranspose-eq _),
          rewrite (o-assoc, o-assoc, terminate) idp
        }
    }

  \func h {A B C : Ob} (f : Hom (Bprod A B) C) : Hom A (P $ Bprod B C) =>
    p-transpose (char-rel f  associator-iso.hinv)

  \func ptranspose-eqq-eq {A B C : Ob} (f : Hom (Bprod A B) C)
    : p-transpose (char-rel f) = image  prodMap (h f) (id B) =>
    run {
      inv,
      p-transpose-unique {\this} {_} {_} {char-rel f} {_},
      rewrite prod-id-right,
      rewriteI (o-assoc, antitranspose-eq {\this} _),
      rewrite o-assoc,
      rewrite (inv $ associtor-prod _ _ _,  inv o-assoc, prod-id, inv $ antitranspose-eq _),
      rewrite (o-assoc, Iso.hinv_f {associator-iso}, id-right) idp
    }

  \lemma singleton-is-single {C : Ob} : is-singleton  singleton C = true-over-obj =>
    pbCoh {char-pullback (singleton-mono C)}

  \func exp-adjoint (B : Ob) : RightAdjointCoreflection \this \this (bprodFunctorRight B)
  \cowith
    | coreflection => exp B
    \where {
      \func exp (B : Ob) (C : Ob) : Coreflection (bprodFunctorRight B) C \cowith
        | Coreflected => graphs.apex B C
        | corefl-map => eval
        | isCoreflection => \new QEquiv {
          | ret => transpose'
          | ret_f _ => transpose-unique' idp
          | f_sec g => inv $ transpose-univ' g
        }

      \func transpose' {A B C : Ob} (f : Hom (Bprod A B) C) : Hom A (graphs.apex B C) =>
        pbMap terminalMap (h f) (unfold (Pullback.f, Pullback.g) $
        rewrite (step5, step4, step3, step2, step1, eqq-p-transpose-s _, ptranspose-eqq-eq f) idp)
        \where {
          \func step1 : true-over-obj  f = is-singleton  (singleton C  f) =>
            rewriteI o-assoc $ rewrite singleton-is-single idp

          \func step2 : is-image-single  prodMap (h f) (id B) = is-singleton  (image  prodMap (h f) (id B) ) =>
            rewriteI o-assoc $ idp

          \func step3 : all-with-single-img  (h f) = p-transpose (is-image-single  prodMap (h f) (id B)) =>
            p-transpose-unique $ unfold $ rewrite prod-id-right $ rewriteI (o-assoc, antitranspose-eq _) idp

          \func step4 : true-over-obj {_} {B}  proj2 {_} {A} {B} = true-over-obj  f =>
            unfold true-over-obj $ rewrite (o-assoc, terminate, o-assoc, terminate) idp

          \func step5
            : name (true-over-obj {_} {B})  terminalMap = p-transpose (true-over-obj {_} {B}  proj2 {_} {A} {B}) =>
            run {
              p-transpose-unique {\this} {_} {_} {true-over-obj {_} {B}  proj2 {_} {A} {B}} {name (true-over-obj {_} {B})  terminalMap},
              rewrite (prod-id-right, inv o-assoc, inv $ antitranspose-eq _),
              rewrite (o-assoc, terminate, o-assoc, o-assoc, terminate),
              idp
            }
        }

      \func transpose-univ' {A B C : Ob} (f : Hom (Bprod A B) C) : f = eval  prodMap (transpose' f) (id B) =>
        \let lem  : singleton C  f = image  prodMap (graphs.map  transpose' f) (id B) =>
          rewrite (pbBeta2 {graphs}, eqq-p-transpose-s _) (ptranspose-eqq-eq _)
        \in
          pbEta {char-pullback (singleton-mono C)}
              (rewrite (inv o-assoc, pbBeta1 {char-pullback (singleton-mono C)}) $ rewrite (o-assoc, inv $ prod-id-right _ _) lem)
              terminal-unique

      \func transpose-unique' {A B C : Ob} {f : Hom (Bprod A B) C} {g : Hom A (graphs.apex B C)}
                              (s : f = eval  prodMap g (id B)) : transpose' f = g =>
        pbEta {graphs} terminal-unique (run {
          unfold,
          rewrite (pbBeta2 {graphs}),
          unfold h, inv,
          p-transpose-unique {\this} {_} {_} {eq C  prodMap f (id C)  hinv {associator-iso}} {pbProj2  g},
          rewrite step2, rewrite (o-assoc, associator-iso.f_hinv, id-right),
          idp
        })
        \where {
          \func step1 : singleton C  f = image  (prodMap (graphs.map {_} {B} {C}  g) (id B)) =>
            rewrite (s, inv o-assoc, pbBeta1 {char-pullback (singleton-mono C)}, o-assoc, inv $ prod-id-right _ _) idp

          \func step2 : char-rel f = belongs {_} {Bprod B C}  prodMap (graphs.map  g) (id (Bprod B C))  associator =>
            run {
              transpose-inj,
              rewrite (inv $ eqq-p-transpose-s _, step1),
              p-transpose-unique {\this} {_} {_} {belongs  prodMap (graphs.map  g) (id (Bprod B C))  associator} {image  prodMap (graphs.map  g) (id B)},
              rewrite {2} (prod-id-right _ _),
              rewriteI o-assoc,
              rewrite (inv $ antitranspose-eq _ : belongs  prodMap image (id C) = belongs  associator-iso.f),
              rewrite {2} o-assoc ,
              rewrite (inv $ associtor-prod _ _ _, prod-id),
              o-assoc
            }
        }
    }

  \default exp (B : Ob) : isExponential {\this} B => RightAdjointCoreflection.toAdjointCounit (exp-adjoint B)
}

\lemma sigma-prop-ext {A : \Prop} (a : A) : A = (\Sigma) => exts (\lam _ => (), \lam _ => a)

\lemma sigma-prop-ext-inv {A : \Prop} (eq : A = (\Sigma)) : A => propExt.conv eq ()

\open PrecatWithTerminal
\open SetCartesianClosed

\instance SetTopos : ToposPrecat \Set
  | FinCompletePrecat => SetBicat
  | CartesianClosedPrecat => SetCartesianClosed
  | subobj-classifier => \Prop
  | true-map => \lam _ => \Sigma
  | char-map => IsElement
  | char-pullback m => \new Pullback {
    | pbCoh => exts (\lam _ => sigma-prop-ext (SetTopos.isContained _ idp))
    | pbMap {_} p1 _ eq =>
      \lam w1 => cases (isElement-char {_} {_} {_} {_} {p1} {eq} w1) \with {
        | SetTopos.isContained a p => a
      }
    | pbBeta1 {_} {p1} {_} {eq} => ext (\lam w1 => cases (isElement-char {_} {_} {_} {_} {p1} {eq} w1) \with {
      | SetTopos.isContained a p => p
    })
    | pbBeta2 => terminal-unique {SetBicat}
    | pbEta {_} {h1} {h2} eq _ => ext (\lam x => \let s : Mono.f {m} (h1 x) = Mono.f {m} (h2 x) => path (\lam i => (eq @ i) x) \in mono-is-inj m s)
  }
  | char-unique {S} {_} {m} {phi} pullback =>
    ext (\lam b =>
        ext (\lam p =>
                 \let preimage : terminal.apex -> S => pbMap {pullback} (name' b) (terminalMap {SetBicat}) (unfold (unfold name' $ exts (\lam _ => sigma-prop-ext p))) \in
                   SetTopos.isContained (unname' preimage)
                       (\let b-image : Mono.f {m}  {SetBicat} preimage = name' b => pbBeta1 {pullback} \in name-inj
                           (rewriteI (name-f (Mono.f {m})) $ rewrite (global-elements-iso.f_ret preimage) b-image)),
             \let ss : phi  {SetBicat} (Mono.f {m}) = terminalMap  {SetBicat} true-map => pbCoh {pullback}
                  | ss-impl (x : S) : phi (Mono.f {m} x) = (\Sigma) => path (\lam i => (ss @ i) x) \in
               \lam c => cases c \with {
                 | SetTopos.isContained a p => rewriteI p $ sigma-prop-ext-inv (ss-impl a)
               }
        ))
  \where {
    \func mono-is-inj {A B : SetBicat} (m : Mono {SetBicat} {A} {B}) : isInj m.f => \lam {a} {b} p =>
        run {
          name-inj,
          m.isMono,
          rewrite (name-f m.f a, name-f m.f b),
          pmap name' p
        }

    \data IsElement {A B : SetBicat} (m : Mono {SetBicat} {A} {B}) (b : B)
      | isContained (a : A) (m.f a = b)
      \where {
        \use \level isProp {A B : SetBicat} (m : Mono {SetBicat} {A} {B}) (b : B) (x y : IsElement m b) : x = y
        \elim x, y
          | isContained a p, isContained c p1 =>
            \let a=c : a = c => mono-is-inj m (p *> inv p1)
                 | p=p1 : transport {A} (\lam (x : A) => m.f x = b) a=c p = p1 => prop-isProp _ _
            \in \case\elim a, \elim c, \elim p, \elim p1, \elim a=c, \elim p=p1 \with {
              | a, c, _, _, idp, idp => idp
            }
      }

    \func isElement-char {S B : SetBicat} {m : Mono {SetBicat} {S} {B}} {w : SetBicat} {p1 : w -> B}
                         {eq : (\lam x => IsElement m (p1 x)) = (\lam _ => \Sigma)} (x : w) : IsElement m (p1 x) =>
      \let s : IsElement m (p1 x) = (\Sigma) => path (\lam i => (eq @ i) x) \in
        sigma-prop-ext-inv s
  }