\import Data.Bool
\import Function.Meta ($)
\import Logic
\import Logic.Meta
\import Meta
\import Order.Directed
\import Order.Lattice
\import Order.PartialOrder
\import Paths
\import Paths.Meta
\import Set
\import Set.SetHom
\import Set.Filter
\import Set.Subset

\class TopSpace \extends BaseSet {
  | isOpen : Set E -> \Prop
  | open-top : isOpen top
  | open-inter {U V : Set E} : isOpen U -> isOpen V -> isOpen (U  V)
  | open-Union {S : Set (Set E)} :  {U : S} (isOpen U) -> isOpen (Set.Union S)

  \lemma cover-open {U : Set E} (c :  {x : U}  (V : isOpen) (V x) (V  U)) : isOpen U
    => \have uo => open-Union {_} {\lam V => \Sigma (isOpen V) (V  U)} __.1
       \in transport isOpen (ext \lam x => ext (\lam (inP (V,(_,V<=U),Vx)) => V<=U Vx, \lam Ux => TruncP.map (c Ux) \lam (V,Vo,Vx,V<=U) => (V, (Vo, V<=U), Vx))) uo

  \lemma open-IUnion {J : \hType} (g : J -> Given isOpen) : isOpen \lam x =>  (j : J) ((g j).1 x)
    => \have uo => open-Union {_} {\lam U =>  (j : J) ((g j).1 = U)} \lam (inP (j,p)) => transport isOpen p (g j).2
       \in transport isOpen (ext \lam x => ext (\lam (inP (U, inP (j,p), Ux)) => inP (j, rewrite p Ux), \lam (inP (j,c)) => inP ((g j).1, inP (j, idp), c))) uo

  \lemma open-bottom : isOpen bottom
    => cover-open \lam e => bottom-empty e

  \lemma open-union {U V : Set E} (Uo : isOpen U) (Vo : isOpen V) : isOpen (U  V)
    => cover-open $ later \case \elim __ \with {
      | inP (true,Ux) => inP (U, Uo, Ux, join-left)
      | inP (false,Vx) => inP (V, Vo, Vx, join-right)
    }

  \type \infix 4 <=<T (V U : Set E) : \Prop
    => \Pi (y : E) ->  (W : isOpen) (W y) (Given (V  W) -> W  U)

  \func IsRegular =>  {U : isOpen} {x : U}  (V : isOpen) (V x) (V <=<T U)

  \func IsLimit {I : DirectedSet} (f : I -> E) (l : E) : \Prop
    =>  {U : isOpen} (U l)  (N : I)  {n} (N <= n -> U (f n))

  \func IsFilterLimit (F : SetFilter E) (l : E) : \Prop
    =>  {U : isOpen} (U l) (F U)

  \func IsLimitPoint (x : E) (U : Set E) : \Prop
    => \Pi {V : Set E} -> isOpen V -> V x ->  (y : U) (V y)

  \type IsClosed (U : Set E) : \Prop
    => \Pi {x : E} -> IsLimitPoint x U -> U x

  \lemma closed-limit {U : Set E} (Uc : IsClosed U) {I : DirectedSet} {f : I -> E} {x : E} (xl : IsLimit f x) {N : I} (Uf : \Pi {n : I} -> N <= n -> U (f n)) : U x
    => Uc \lam Vo Vx => \case xl Vo Vx \with {
      | inP (K,Vf) => \case isDirected N K \with {
        | inP (n,N<=n,K<=n) => inP (f n, Uf N<=n, Vf K<=n)
      }
    }

  \lemma closed-limit0 {U : Set E} (Uc : IsClosed U) {I : DirectedSet} {f : I -> E} {x : E} (xl : IsLimit f x) (Uf : \Pi (n : I) -> U (f n)) : U x
    => \case I.isInhabitted \with {
      | inP N => closed-limit Uc xl {N} \lam {n} _ => Uf n
    }

  \lemma closed-inter {U V : Set E} (Uc : IsClosed U) (Vc : IsClosed V) : IsClosed (U  V)
    => \lam xl => (Uc (\lam {W} Wo Wx => TruncP.map (xl Wo Wx) \lam s => (s.1, s.2.1, s.3)),
                   Vc (\lam {W} Wo Wx => TruncP.map (xl Wo Wx) \lam s => (s.1, s.2.2, s.3)))
}

\func DiscreteTopSpace (X : \Set) : TopSpace X \cowith
  | isOpen _ => \Sigma
  | open-top => ()
  | open-inter _ _ => ()
  | open-Union _ => ()

\func NFilter {X : TopSpace} (x : X) : ProperFilter X \cowith
  | F U =>  (V : isOpen) (V x) (V  U)
  | filter-mono (inP (W,Wo,Wx,q)) p => inP (W, Wo, Wx, q <=∘ p)
  | filter-top => inP (top, open-top, (), <=-refl)
  | filter-meet (inP (U,Uo,Ux,p)) (inP (V,Vo,Vx,q)) => inP (U  V, open-inter Uo Vo, (Ux,Vx), MeetSemilattice.meet-monotone p q)
  | isProper (inP (V,Vo,Vx,p)) => inP (x, p Vx)

\func IsWeaklyDenseSet {X : TopSpace} (S : Set X) : \Prop
  => \Pi {U : Set X} -> isOpen U -> Not (Given (y : S) (U y)) -> Not (Given U)

\func IsDenseSet {X : TopSpace} (S : Set X) : \Prop
  => \Pi {x : X} {U : Set X} -> isOpen U -> U x ->  (y : S) (U y)

\lemma denseSet->weaklyDense {X : TopSpace} {S : Set X} (d : IsDenseSet S) : IsWeaklyDenseSet S
  => \lam Uo c (x,Ux) => \case d Uo Ux \with {
    | inP s => c s
  }

\record ContMap \extends SetHom {
  \override Dom : TopSpace
  \override Cod : TopSpace

  | func-cont {U : Cod -> \Prop} : isOpen U -> isOpen \lam x => U (func x)

  \func IsWeaklyDense : \Prop
    => IsWeaklyDenseSet (\lam y =>  (x : Dom) (func x = y))

  \func IsDense : \Prop
    => IsDenseSet (\lam y =>  (x : Dom) (func x = y))

  \lemma dense->weaklyDense (d : IsDense) : IsWeaklyDense
    => denseSet->weaklyDense d

  \func IsTopEmbedding : \Prop
    =>  {U : isOpen}  (V : isOpen) (U = func ^-1 V)

  \lemma topEmbedding-char : IsTopEmbedding <->  {U : isOpen} {x : U}  (V : isOpen) (V (func x)) (func ^-1 V  U)
    => (\lam e Uo Ux => \case e Uo \with {
      | inP (V,Vo,p) => inP (V, Vo, rewrite p in Ux, rewrite p <=-refl)
    }, \lam e {U} Uo => inP (_, open-Union {_} {\lam V => \Sigma (isOpen V) (func ^-1 V  U)} __.1, ext \lam x => ext (\lam Ux => \case e Uo Ux \with {
      | inP (V,Vo,Vfx,p) => inP (V, (Vo, p), Vfx)
    }, \lam (inP (V,(Vo,p),Vfx)) => p Vfx)))

  \func IsDenseTopEmbedding : \Prop
    => \Sigma IsDense IsTopEmbedding
} \where {
  \func id {X : TopSpace} : ContMap X X \cowith
    | func x => x
    | func-cont Uo => Uo

  \func compose \alias \infixl 8  {X Y Z : TopSpace} (g : ContMap Y Z) (f : ContMap X Y) : ContMap X Z \cowith
    | func x => g (f x)
    | func-cont Uo => f.func-cont (g.func-cont Uo)

  \func const {Y X : TopSpace} (x : X) : ContMap Y X \cowith
    | func _ => x
    | func-cont _ => Y.cover-open \lam Ux => inP (top, open-top, (), \lam _ => Ux)
}

\lemma limit-transport {X : TopSpace} {I : DirectedSet} {f g : I -> X} {l : X} (fl : X.IsLimit f l) (p : \Pi (n : I) -> g n = f n) : X.IsLimit g l
  => transportInv (X.IsLimit __ _) (ext p) fl

\lemma const-limit {I : DirectedSet} {X : TopSpace} {x : X} : X.IsLimit (\lam (_ : I) => x) x
  => \lam _ Ux => \case I.isInhabitted \with {
    | inP N => inP (N, \lam _ => Ux)
  }

\lemma cont-limit {I : DirectedSet} {X Y : TopSpace} {f : I -> X} {l : X} (p : X.IsLimit f l) (g : ContMap X Y) : Y.IsLimit (\lam n => g (f n)) (g l)
  => \lam Uo Ugl => p (g.func-cont Uo) Ugl

\func IsCont {X Y : TopSpace} (f : X -> Y) : \Prop
  => ContMap X Y f

\func IsContAt {X Y : TopSpace} (f : X -> Y) (x : X) : \Prop
  =>  {V : Y.isOpen} (V (f x))  (U : X.isOpen) (U x) (U  f ^-1 V)

\lemma cont-char {X Y : TopSpace} {f : X -> Y} : IsCont f <-> (\Pi (x : X) -> IsContAt f x)
  => (\lam fc x Vo Vfx => inP (_, func-cont {fc} Vo, Vfx, <=-refl), \lam fc => \new ContMap {
    | func-cont {V} Vo => X.cover-open \lam Vfx => fc _ Vo Vfx
  })

\lemma contAt-comp {X Y Z : TopSpace} (f : X -> Y) (g : Y -> Z) {x : X} (fc : IsContAt f x) (gc : IsContAt g (f x)) : IsContAt (\lam x => g (f x)) x
  => \lam {W} Wo Wgfx => \case gc Wo Wgfx \with {
    | inP (V,Vo,Vfx,q) => \case fc Vo Vfx \with {
      | inP (U,Uo,Ux,p) => inP (U, Uo, Ux, p <=∘ \lam Vfx => q Vfx)
    }
  }

\lemma contAt-left {X Y Z : TopSpace} {f : X -> Y} (g : ContMap Y Z) {x : X} (fc : IsContAt f x) : IsContAt (\lam x => g (f x)) x
  => contAt-comp f g fc (cont-char.1 g (f x))

\lemma contAt-right {X Y Z : TopSpace} (f : ContMap X Y) (g : Y -> Z) {x : X} (gc : IsContAt g (f x)) : IsContAt (\lam x => g (f x)) x
  => contAt-comp f g (cont-char.1 f x) gc

\lemma contAt-limit {I : DirectedSet} {X Y : TopSpace} {f : I -> X} {l : X} (p : X.IsLimit f l) {g : X -> Y} (gc : IsContAt g l) : Y.IsLimit (\lam n => g (f n)) (g l)
  => \lam {V} Vo Vgl => \case gc Vo Vgl \with {
    | inP (U,Uo,Ul,q) => \case p Uo Ul \with {
      | inP (N,g) => inP (N, \lam N<=n => q $ g N<=n)
    }
  }

\lemma cont-limit-point {X Y : TopSpace} (f : ContMap X Y) {S : Set Y} {x : X} (xl : X.IsLimitPoint x (f ^-1 S)) : Y.IsLimitPoint (f x) S
  => \lam Vo Vfx => \case xl (f.func-cont Vo) Vfx \with {
    | inP (x,Sfx,Vfx) => inP (f x, Sfx, Vfx)
  }

\lemma cont-closed {X Y : TopSpace} (f : ContMap X Y) {S : Set Y} (Sc : Y.IsClosed S) : X.IsClosed (f ^-1 S)
  => \lam xl => Sc (cont-limit-point f xl)

\class HausdorffTopSpace \extends TopSpace {
  | isHausdorff {x y : E} :  {U V : isOpen} (U x) (V y)  (U  V) -> x = y

  \lemma limit-unique {I : DirectedSet} {f : I -> E} {x y : E} (lx : IsLimit f x) (ly : IsLimit f y) : x = y
    => isHausdorff \lam Uo Vo Ux Vy => \case lx Uo Ux, ly Vo Vy \with {
      | inP (N,g), inP (M,h) => \case isDirected N M \with {
        | inP (K,N<=K,M<=K) => inP (f K, (g N<=K, h M<=K))
      }
    }
}

\class StronglyHausdorffTopSpace \extends HausdorffTopSpace, SeparatedSet {
  | isStronglyHausdorff {x y : E} :  {U V : isOpen} (U x) (V y) (Not (Not (Given (U  V)))) -> x = y
  | isHausdorff p => isStronglyHausdorff \lam Uo Vo Ux Vy q => \case p Uo Vo Ux Vy \with {
    | inP r => q r
  }
  | separatedEq {x} p => isStronglyHausdorff \lam Uo Vo Ux Vy q => p \lam x=y => q (x, (Ux, rewrite x=y Vy))
}

\lemma denseSet-lift-unique {X : TopSpace} {Y : HausdorffTopSpace} {S : Set X} (Sd : IsDenseSet S) (f g : ContMap X Y) (p : \Pi {x : X} -> S x -> f x = g x) {x : X} : f x = g x
  => isHausdorff \lam {U} Uo Vo Ufx Vgx => \case Sd {x} (open-inter (f.func-cont Uo) (g.func-cont Vo)) (Ufx,Vgx) \with {
    | inP (y,Sy,(Ufy,Vgy)) => inP (g y, (transport U (p Sy) Ufy, Vgy))
  }

\lemma dense-lift-unique {X Y : TopSpace} {Z : HausdorffTopSpace} (f : ContMap X Y) (fd : f.IsDense) (g h : ContMap Y Z) (p : \Pi (x : X) -> g (f x) = h (f x)) (y : Y) : g y = h y
  => denseSet-lift-unique fd g h (\lam (inP (x,q)) => rewriteI q (p x))

\lemma weaklyDenseSet-lift-unique {X : TopSpace} {Y : StronglyHausdorffTopSpace} {S : Set X} (Sd : IsWeaklyDenseSet S) (f g : ContMap X Y) (f=g : \Pi {x : X} -> S x -> f x = g x) {x : X} : f x = g x
  => isStronglyHausdorff \lam {U} Uo Vo Ufx Vgx p => Sd (open-inter (f.func-cont Uo) (g.func-cont Vo)) (\lam s => p (g s.1, (transport U (f=g s.2) s.3.1, s.3.2))) (x,(Ufx,Vgx))

\lemma weaklyDense-lift-unique {X Y : TopSpace} {Z : StronglyHausdorffTopSpace} (f : ContMap X Y) (fd : f.IsWeaklyDense) (g h : ContMap Y Z) (p : \Pi (x : X) -> g (f x) = h (f x)) (y : Y) : g y = h y
  => weaklyDenseSet-lift-unique fd g h (\lam (inP (x,q)) => rewriteI q (p x))

\func TopTransfer {X : \Set} {Y : TopSpace} (f : X -> Y) : TopSpace X \cowith
  | isOpen U =>  (V : isOpen) (U = f ^-1 V)
  | open-top => inP (top, open-top, idp)
  | open-inter (inP (V,Vo,p)) (inP (V',V'o,p')) => inP (V  V', open-inter Vo V'o, pmap2 () p p')
  | open-Union {S} h => inP (Set.Union \lam V => \Sigma (isOpen V) (f ^-1 V  Set.Union S), open-Union __.1, <=-antisymmetric (\lam (inP (U,SU,Ux)) => \case h SU \with {
    | inP (V,Vo,p) => inP (V, (Vo, rewriteI p $ Set.Union-cond SU), rewrite p in Ux)
  }) (\lam (inP (V,(Vo,p),Vfx)) => p Vfx))

\lemma TopTransfer-map {X : \Set} {Y : TopSpace} (f : X -> Y) : ContMap (TopTransfer f) Y f \cowith
  | func-cont {U} Uo => inP (U, Uo, idp)

\lemma TopTransfer-lift {X Y : TopSpace} {U : Set Y} (f : ContMap X Y) (p : \Pi (x : X) -> U (f x)) : ContMap X (TopTransfer {Set.Total U} __.1) (\lam x => (f x, p x)) \cowith
  | func-cont (inP (V,Vo,q)) => transport isOpen (ext \lam x => rewrite q idp) (f.func-cont Vo)

\func TopSub {X : TopSpace} (S : Set X) : TopSpace (Set.Total S)
  => TopTransfer __.1

\lemma TopSub-func {X Y : TopSpace} (f : ContMap X Y) {U : Set X} {V : Set Y} (p :  {x : U} (V (f x))) : ContMap (TopSub U) (TopSub V) \lam s => (f s.1, p s.2) \cowith
  | func-cont {_} (inP (V,Vo,idp)) => inP (f ^-1 V, func-cont Vo, idp)

\lemma TopSub-inc {X : TopSpace} {U V : Set X} (p : U  V) : ContMap (TopSub U) (TopSub V) \lam s => (s.1, p s.2)
  => TopSub-func ContMap.id $ later (p __)

\lemma TopSub-limit {X : TopSpace} {S : Set X} {I : DirectedSet} {f : I -> Set.Total S} {x : Set.Total S} (l : X.IsLimit (\lam n => (f n).1) x.1) : TopSpace.IsLimit {TopSub S} f x
  => \lam {_} (inP (V,Vo,idp)) => l Vo

\func IsRelativelyHausdorff {X Y : TopSpace} (f : X -> Y) : \Prop
  => \Pi {x x' : X} ->  {U V : X.isOpen} (U x) (V x')  (U  V) -> f x = f x' -> x = x'

\lemma denseSet-relative-lift-unique {X Y Z : TopSpace} (p : Y -> Z) (ph : IsRelativelyHausdorff p) {S : Set X} (Sd : IsDenseSet S) (f g : ContMap X Y) (dp : \Pi {x : X} -> S x -> f x = g x) (dq : \Pi {x : X} -> p (f x) = p (g x)) {x : X} : f x = g x
  => ph (\lam {U} Uo Vo Ufx Vgx => \case Sd {x} (open-inter (f.func-cont Uo) (g.func-cont Vo)) (Ufx,Vgx) \with {
    | inP (y,Sy,(Ufy,Vgy)) => inP (g y, (transport U (dp Sy) Ufy, Vgy))
  }) dq

\lemma dense-relative-lift-unique {X Y Z W : TopSpace} (p : Z -> W) (ph : IsRelativelyHausdorff p) (f : ContMap X Y) (fd : f.IsDense) (g h : ContMap Y Z) (dp : \Pi (x : X) -> g (f x) = h (f x)) (dq : \Pi {y : Y} -> p (g y) = p (h y)) (y : Y) : g y = h y
  => denseSet-relative-lift-unique p ph fd g h (\lam (inP (y,q)) => rewriteI q (dp y)) dq