/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Mario Carneiro, Jeremy Avigad
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.order.filter.ultrafilter
import Mathlib.order.filter.partial
import Mathlib.PostPort

universes u l w v u_1 u_2 u_3 u_5

namespace Mathlib

/-!
# Basic theory of topological spaces.

The main definition is the type class `topological space α` which endows a type `α` with a topology.
Then `set α` gets predicates `is_open`, `is_closed` and functions `interior`, `closure` and
`frontier`. Each point `x` of `α` gets a neighborhood filter `𝓝 x`. A filter `F` on `α` has
`x` as a cluster point if `cluster_pt x F : 𝓝 x ⊓ F ≠ ⊥`. A map `f : ι → α` clusters at `x`
along `F : filter ι` if `map_cluster_pt x F f : cluster_pt x (map f F)`. In particular
the notion of cluster point of a sequence `u` is `map_cluster_pt x at_top u`.

This file also defines locally finite families of subsets of `α`.

For topological spaces `α` and `β`, a function `f : α → β` and a point `a : α`,
`continuous_at f a` means `f` is continuous at `a`, and global continuity is
`continuous f`. There is also a version of continuity `pcontinuous` for
partially defined functions.

## Notation

* `𝓝 x`: the filter of neighborhoods of a point `x`;
* `𝓟 s`: the principal filter of a set `s`;

## Implementation notes

Topology in mathlib heavily uses filters (even more than in Bourbaki). See explanations in
<https://leanprover-community.github.io/theories/topology.html>.

## References

*  [N. Bourbaki, *General Topology*][bourbaki1966]
*  [I. M. James, *Topologies and Uniformities*][james1999]

## Tags

topological space, interior, closure, frontier, neighborhood, continuity, continuous function
-/

/-!
### Topological spaces
-/

/-- A topology on `α`. -/
class topological_space (α : Type u) where
  is_open : set α → Prop
  is_open_univ : is_open set.univ
  is_open_inter : ∀ (s t : set α), is_open s → is_open t → is_open (s ∩ t)
  is_open_sUnion : ∀ (s : set (set α)), (∀ (t : set α), t ∈ s → is_open t) → is_open (⋃₀s)

/-- A constructor for topologies by specifying the closed sets,
and showing that they satisfy the appropriate conditions. -/
def topological_space.of_closed {α : Type u} (T : set (set α)) (empty_mem : ∅ ∈ T)
    (sInter_mem : ∀ (A : set (set α)), A ⊆ T → ⋂₀A ∈ T)
    (union_mem : ∀ (A B : set α), A ∈ T → B ∈ T → A ∪ B ∈ T) : topological_space α :=
  topological_space.mk (fun (X : set α) => Xᶜ ∈ T) sorry sorry sorry

theorem topological_space_eq {α : Type u} {f : topological_space α} {g : topological_space α} :
    topological_space.is_open f = topological_space.is_open g → f = g :=
  sorry

/-- `is_open s` means that `s` is open in the ambient topological space on `α` -/
def is_open {α : Type u} [t : topological_space α] (s : set α) := topological_space.is_open t s

@[simp] theorem is_open_univ {α : Type u} [t : topological_space α] : is_open set.univ :=
  topological_space.is_open_univ t

theorem is_open_inter {α : Type u} {s₁ : set α} {s₂ : set α} [t : topological_space α]
    (h₁ : is_open s₁) (h₂ : is_open s₂) : is_open (s₁ ∩ s₂) :=
  topological_space.is_open_inter t s₁ s₂ h₁ h₂

theorem is_open_sUnion {α : Type u} [t : topological_space α] {s : set (set α)}
    (h : ∀ (t_1 : set α), t_1 ∈ s → is_open t_1) : is_open (⋃₀s) :=
  topological_space.is_open_sUnion t s h

theorem topological_space_eq_iff {α : Type u} {t : topological_space α} {t' : topological_space α} :
    t = t' ↔ ∀ (s : set α), is_open s ↔ is_open s :=
  { mp := fun (h : t = t') (s : set α) => h ▸ iff.rfl,
    mpr :=
      fun (h : ∀ (s : set α), is_open s ↔ is_open s) =>
        topological_space_eq (funext fun (x : set α) => propext (h x)) }

theorem is_open_fold {α : Type u} {s : set α} {t : topological_space α} :
    topological_space.is_open t s = is_open s :=
  rfl

theorem is_open_Union {α : Type u} {ι : Sort w} [topological_space α] {f : ι → set α}
    (h : ∀ (i : ι), is_open (f i)) : is_open (set.Union fun (i : ι) => f i) :=
  is_open_sUnion
    fun (t : set α) (H : t ∈ set.range fun (i : ι) => f i) =>
      Exists.dcases_on H fun (i : ι) (H_h : (fun (i : ι) => f i) i = t) => Eq._oldrec (h i) H_h

theorem is_open_bUnion {α : Type u} {β : Type v} [topological_space α] {s : set β} {f : β → set α}
    (h : ∀ (i : β), i ∈ s → is_open (f i)) :
    is_open (set.Union fun (i : β) => set.Union fun (H : i ∈ s) => f i) :=
  is_open_Union fun (i : β) => is_open_Union fun (hi : i ∈ s) => h i hi

theorem is_open_union {α : Type u} {s₁ : set α} {s₂ : set α} [topological_space α] (h₁ : is_open s₁)
    (h₂ : is_open s₂) : is_open (s₁ ∪ s₂) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_open (s₁ ∪ s₂))) set.union_eq_Union))
    (is_open_Union (iff.mpr bool.forall_bool { left := h₂, right := h₁ }))

@[simp] theorem is_open_empty {α : Type u} [topological_space α] : is_open ∅ :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_open ∅)) (Eq.symm set.sUnion_empty)))
    (is_open_sUnion fun (a : set α) => false.elim)

theorem is_open_sInter {α : Type u} [topological_space α] {s : set (set α)} (hs : set.finite s) :
    (∀ (t : set α), t ∈ s → is_open t) → is_open (⋂₀s) :=
  sorry

theorem is_open_bInter {α : Type u} {β : Type v} [topological_space α] {s : set β} {f : β → set α}
    (hs : set.finite s) :
    (∀ (i : β), i ∈ s → is_open (f i)) →
        is_open (set.Inter fun (i : β) => set.Inter fun (H : i ∈ s) => f i) :=
  sorry

theorem is_open_Inter {α : Type u} {β : Type v} [topological_space α] [fintype β] {s : β → set α}
    (h : ∀ (i : β), is_open (s i)) : is_open (set.Inter fun (i : β) => s i) :=
  sorry

theorem is_open_Inter_prop {α : Type u} [topological_space α] {p : Prop} {s : p → set α}
    (h : ∀ (h : p), is_open (s h)) : is_open (set.Inter s) :=
  sorry

theorem is_open_const {α : Type u} [topological_space α] {p : Prop} :
    is_open (set_of fun (a : α) => p) :=
  sorry

theorem is_open_and {α : Type u} {p₁ : α → Prop} {p₂ : α → Prop} [topological_space α] :
    is_open (set_of fun (a : α) => p₁ a) →
        is_open (set_of fun (a : α) => p₂ a) → is_open (set_of fun (a : α) => p₁ a ∧ p₂ a) :=
  is_open_inter

/-- A set is closed if its complement is open -/
def is_closed {α : Type u} [topological_space α] (s : set α) := is_open (sᶜ)

@[simp] theorem is_closed_empty {α : Type u} [topological_space α] : is_closed ∅ :=
  eq.mpr (id (is_closed.equations._eqn_1 ∅))
    (eq.mpr (id (Eq._oldrec (Eq.refl (is_open (∅ᶜ))) set.compl_empty)) is_open_univ)

@[simp] theorem is_closed_univ {α : Type u} [topological_space α] : is_closed set.univ :=
  eq.mpr (id (is_closed.equations._eqn_1 set.univ))
    (eq.mpr (id (Eq._oldrec (Eq.refl (is_open (set.univᶜ))) set.compl_univ)) is_open_empty)

theorem is_closed_union {α : Type u} {s₁ : set α} {s₂ : set α} [topological_space α] :
    is_closed s₁ → is_closed s₂ → is_closed (s₁ ∪ s₂) :=
  fun (h₁ : is_closed s₁) (h₂ : is_closed s₂) =>
    eq.mpr (id (is_closed.equations._eqn_1 (s₁ ∪ s₂)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (is_open (s₁ ∪ s₂ᶜ))) (set.compl_union s₁ s₂)))
        (is_open_inter h₁ h₂))

theorem is_closed_sInter {α : Type u} [topological_space α] {s : set (set α)} :
    (∀ (t : set α), t ∈ s → is_closed t) → is_closed (⋂₀s) :=
  sorry

theorem is_closed_Inter {α : Type u} {ι : Sort w} [topological_space α] {f : ι → set α}
    (h : ∀ (i : ι), is_closed (f i)) : is_closed (set.Inter fun (i : ι) => f i) :=
  sorry

@[simp] theorem is_open_compl_iff {α : Type u} [topological_space α] {s : set α} :
    is_open (sᶜ) ↔ is_closed s :=
  iff.rfl

@[simp] theorem is_closed_compl_iff {α : Type u} [topological_space α] {s : set α} :
    is_closed (sᶜ) ↔ is_open s :=
  eq.mpr
    (id (Eq._oldrec (Eq.refl (is_closed (sᶜ) ↔ is_open s)) (Eq.symm (propext is_open_compl_iff))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (is_open (sᶜᶜ) ↔ is_open s)) (compl_compl s)))
      (iff.refl (is_open s)))

theorem is_open_diff {α : Type u} [topological_space α] {s : set α} {t : set α} (h₁ : is_open s)
    (h₂ : is_closed t) : is_open (s \ t) :=
  is_open_inter h₁ (iff.mpr is_open_compl_iff h₂)

theorem is_closed_inter {α : Type u} {s₁ : set α} {s₂ : set α} [topological_space α]
    (h₁ : is_closed s₁) (h₂ : is_closed s₂) : is_closed (s₁ ∩ s₂) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_closed (s₁ ∩ s₂))) (is_closed.equations._eqn_1 (s₁ ∩ s₂))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (is_open (s₁ ∩ s₂ᶜ))) (set.compl_inter s₁ s₂)))
      (is_open_union h₁ h₂))

theorem is_closed_bUnion {α : Type u} {β : Type v} [topological_space α] {s : set β} {f : β → set α}
    (hs : set.finite s) :
    (∀ (i : β), i ∈ s → is_closed (f i)) →
        is_closed (set.Union fun (i : β) => set.Union fun (H : i ∈ s) => f i) :=
  sorry

theorem is_closed_Union {α : Type u} {β : Type v} [topological_space α] [fintype β] {s : β → set α}
    (h : ∀ (i : β), is_closed (s i)) : is_closed (set.Union s) :=
  sorry

theorem is_closed_Union_prop {α : Type u} [topological_space α] {p : Prop} {s : p → set α}
    (h : ∀ (h : p), is_closed (s h)) : is_closed (set.Union s) :=
  sorry

theorem is_closed_imp {α : Type u} [topological_space α] {p : α → Prop} {q : α → Prop}
    (hp : is_open (set_of fun (x : α) => p x)) (hq : is_closed (set_of fun (x : α) => q x)) :
    is_closed (set_of fun (x : α) => p x → q x) :=
  sorry

theorem is_open_neg {α : Type u} {p : α → Prop} [topological_space α] :
    is_closed (set_of fun (a : α) => p a) → is_open (set_of fun (a : α) => ¬p a) :=
  iff.mpr is_open_compl_iff

/-!
### Interior of a set
-/

/-- The interior of a set `s` is the largest open subset of `s`. -/
def interior {α : Type u} [topological_space α] (s : set α) : set α :=
  ⋃₀set_of fun (t : set α) => is_open t ∧ t ⊆ s

theorem mem_interior {α : Type u} [topological_space α] {s : set α} {x : α} :
    x ∈ interior s ↔ ∃ (t : set α), ∃ (H : t ⊆ s), is_open t ∧ x ∈ t :=
  sorry

@[simp] theorem is_open_interior {α : Type u} [topological_space α] {s : set α} :
    is_open (interior s) :=
  sorry

theorem interior_subset {α : Type u} [topological_space α] {s : set α} : interior s ⊆ s := sorry

theorem interior_maximal {α : Type u} [topological_space α] {s : set α} {t : set α} (h₁ : t ⊆ s)
    (h₂ : is_open t) : t ⊆ interior s :=
  set.subset_sUnion_of_mem { left := h₂, right := h₁ }

theorem is_open.interior_eq {α : Type u} [topological_space α] {s : set α} (h : is_open s) :
    interior s = s :=
  set.subset.antisymm interior_subset (interior_maximal (set.subset.refl s) h)

theorem interior_eq_iff_open {α : Type u} [topological_space α] {s : set α} :
    interior s = s ↔ is_open s :=
  { mp := fun (h : interior s = s) => h ▸ is_open_interior, mpr := is_open.interior_eq }

theorem subset_interior_iff_open {α : Type u} [topological_space α] {s : set α} :
    s ⊆ interior s ↔ is_open s :=
  sorry

theorem subset_interior_iff_subset_of_open {α : Type u} [topological_space α] {s : set α}
    {t : set α} (h₁ : is_open s) : s ⊆ interior t ↔ s ⊆ t :=
  { mp := fun (h : s ⊆ interior t) => set.subset.trans h interior_subset,
    mpr := fun (h₂ : s ⊆ t) => interior_maximal h₂ h₁ }

theorem interior_mono {α : Type u} [topological_space α] {s : set α} {t : set α} (h : s ⊆ t) :
    interior s ⊆ interior t :=
  interior_maximal (set.subset.trans interior_subset h) is_open_interior

@[simp] theorem interior_empty {α : Type u} [topological_space α] : interior ∅ = ∅ :=
  is_open.interior_eq is_open_empty

@[simp] theorem interior_univ {α : Type u} [topological_space α] : interior set.univ = set.univ :=
  is_open.interior_eq is_open_univ

@[simp] theorem interior_interior {α : Type u} [topological_space α] {s : set α} :
    interior (interior s) = interior s :=
  is_open.interior_eq is_open_interior

@[simp] theorem interior_inter {α : Type u} [topological_space α] {s : set α} {t : set α} :
    interior (s ∩ t) = interior s ∩ interior t :=
  sorry

theorem interior_union_is_closed_of_interior_empty {α : Type u} [topological_space α] {s : set α}
    {t : set α} (h₁ : is_closed s) (h₂ : interior t = ∅) : interior (s ∪ t) = interior s :=
  sorry

theorem is_open_iff_forall_mem_open {α : Type u} {s : set α} [topological_space α] :
    is_open s ↔ ∀ (x : α) (H : x ∈ s), ∃ (t : set α), ∃ (H : t ⊆ s), is_open t ∧ x ∈ t :=
  sorry

/-!
### Closure of a set
-/

/-- The closure of `s` is the smallest closed set containing `s`. -/
def closure {α : Type u} [topological_space α] (s : set α) : set α :=
  ⋂₀set_of fun (t : set α) => is_closed t ∧ s ⊆ t

@[simp] theorem is_closed_closure {α : Type u} [topological_space α] {s : set α} :
    is_closed (closure s) :=
  sorry

theorem subset_closure {α : Type u} [topological_space α] {s : set α} : s ⊆ closure s := sorry

theorem closure_minimal {α : Type u} [topological_space α] {s : set α} {t : set α} (h₁ : s ⊆ t)
    (h₂ : is_closed t) : closure s ⊆ t :=
  set.sInter_subset_of_mem { left := h₂, right := h₁ }

theorem is_closed.closure_eq {α : Type u} [topological_space α] {s : set α} (h : is_closed s) :
    closure s = s :=
  set.subset.antisymm (closure_minimal (set.subset.refl s) h) subset_closure

theorem is_closed.closure_subset {α : Type u} [topological_space α] {s : set α} (hs : is_closed s) :
    closure s ⊆ s :=
  closure_minimal (set.subset.refl s) hs

theorem is_closed.closure_subset_iff {α : Type u} [topological_space α] {s : set α} {t : set α}
    (h₁ : is_closed t) : closure s ⊆ t ↔ s ⊆ t :=
  { mp := set.subset.trans subset_closure, mpr := fun (h : s ⊆ t) => closure_minimal h h₁ }

theorem closure_mono {α : Type u} [topological_space α] {s : set α} {t : set α} (h : s ⊆ t) :
    closure s ⊆ closure t :=
  closure_minimal (set.subset.trans h subset_closure) is_closed_closure

theorem monotone_closure (α : Type u_1) [topological_space α] : monotone closure :=
  fun (_x _x_1 : set α) => closure_mono

theorem closure_inter_subset_inter_closure {α : Type u} [topological_space α] (s : set α)
    (t : set α) : closure (s ∩ t) ⊆ closure s ∩ closure t :=
  monotone.map_inf_le (monotone_closure α) s t

theorem is_closed_of_closure_subset {α : Type u} [topological_space α] {s : set α}
    (h : closure s ⊆ s) : is_closed s :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_closed s)) (set.subset.antisymm subset_closure h)))
    is_closed_closure

theorem closure_eq_iff_is_closed {α : Type u} [topological_space α] {s : set α} :
    closure s = s ↔ is_closed s :=
  { mp := fun (h : closure s = s) => h ▸ is_closed_closure, mpr := is_closed.closure_eq }

theorem closure_subset_iff_is_closed {α : Type u} [topological_space α] {s : set α} :
    closure s ⊆ s ↔ is_closed s :=
  { mp := is_closed_of_closure_subset, mpr := is_closed.closure_subset }

@[simp] theorem closure_empty {α : Type u} [topological_space α] : closure ∅ = ∅ :=
  is_closed.closure_eq is_closed_empty

@[simp] theorem closure_empty_iff {α : Type u} [topological_space α] (s : set α) :
    closure s = ∅ ↔ s = ∅ :=
  { mp := set.subset_eq_empty subset_closure, mpr := fun (h : s = ∅) => Eq.symm h ▸ closure_empty }

theorem set.nonempty.closure {α : Type u} [topological_space α] {s : set α} (h : set.nonempty s) :
    set.nonempty (closure s) :=
  sorry

@[simp] theorem closure_univ {α : Type u} [topological_space α] : closure set.univ = set.univ :=
  is_closed.closure_eq is_closed_univ

@[simp] theorem closure_closure {α : Type u} [topological_space α] {s : set α} :
    closure (closure s) = closure s :=
  is_closed.closure_eq is_closed_closure

@[simp] theorem closure_union {α : Type u} [topological_space α] {s : set α} {t : set α} :
    closure (s ∪ t) = closure s ∪ closure t :=
  sorry

theorem interior_subset_closure {α : Type u} [topological_space α] {s : set α} :
    interior s ⊆ closure s :=
  set.subset.trans interior_subset subset_closure

theorem closure_eq_compl_interior_compl {α : Type u} [topological_space α] {s : set α} :
    closure s = (interior (sᶜ)ᶜ) :=
  sorry

@[simp] theorem interior_compl {α : Type u} [topological_space α] {s : set α} :
    interior (sᶜ) = (closure sᶜ) :=
  sorry

@[simp] theorem closure_compl {α : Type u} [topological_space α] {s : set α} :
    closure (sᶜ) = (interior sᶜ) :=
  sorry

theorem mem_closure_iff {α : Type u} [topological_space α] {s : set α} {a : α} :
    a ∈ closure s ↔ ∀ (o : set α), is_open o → a ∈ o → set.nonempty (o ∩ s) :=
  sorry

/-- A set is dense in a topological space if every point belongs to its closure. -/
def dense {α : Type u} [topological_space α] (s : set α) := ∀ (x : α), x ∈ closure s

theorem dense_iff_closure_eq {α : Type u} [topological_space α] {s : set α} :
    dense s ↔ closure s = set.univ :=
  iff.symm set.eq_univ_iff_forall

theorem dense.closure_eq {α : Type u} [topological_space α] {s : set α} (h : dense s) :
    closure s = set.univ :=
  iff.mp dense_iff_closure_eq h

/-- The closure of a set `s` is dense if and only if `s` is dense. -/
@[simp] theorem dense_closure {α : Type u} [topological_space α] {s : set α} :
    dense (closure s) ↔ dense s :=
  sorry

theorem dense.of_closure {α : Type u} [topological_space α] {s : set α} :
    dense (closure s) → dense s :=
  iff.mp dense_closure

@[simp] theorem dense_univ {α : Type u} [topological_space α] : dense set.univ :=
  fun (x : α) => subset_closure trivial

/-- A set is dense if and only if it has a nonempty intersection with each nonempty open set. -/
theorem dense_iff_inter_open {α : Type u} [topological_space α] {s : set α} :
    dense s ↔ ∀ (U : set α), is_open U → set.nonempty U → set.nonempty (U ∩ s) :=
  sorry

theorem dense.inter_open_nonempty {α : Type u} [topological_space α] {s : set α} :
    dense s → ∀ (U : set α), is_open U → set.nonempty U → set.nonempty (U ∩ s) :=
  iff.mp dense_iff_inter_open

theorem dense.nonempty_iff {α : Type u} [topological_space α] {s : set α} (hs : dense s) :
    set.nonempty s ↔ Nonempty α :=
  sorry

theorem dense.nonempty {α : Type u} [topological_space α] [h : Nonempty α] {s : set α}
    (hs : dense s) : set.nonempty s :=
  iff.mpr (dense.nonempty_iff hs) h

theorem dense.mono {α : Type u} [topological_space α] {s₁ : set α} {s₂ : set α} (h : s₁ ⊆ s₂)
    (hd : dense s₁) : dense s₂ :=
  fun (x : α) => closure_mono h (hd x)

/-!
### Frontier of a set
-/

/-- The frontier of a set is the set of points between the closure and interior. -/
def frontier {α : Type u} [topological_space α] (s : set α) : set α := closure s \ interior s

theorem frontier_eq_closure_inter_closure {α : Type u} [topological_space α] {s : set α} :
    frontier s = closure s ∩ closure (sᶜ) :=
  sorry

/-- The complement of a set has the same frontier as the original set. -/
@[simp] theorem frontier_compl {α : Type u} [topological_space α] (s : set α) :
    frontier (sᶜ) = frontier s :=
  sorry

theorem frontier_inter_subset {α : Type u} [topological_space α] (s : set α) (t : set α) :
    frontier (s ∩ t) ⊆ frontier s ∩ closure t ∪ closure s ∩ frontier t :=
  sorry

theorem frontier_union_subset {α : Type u} [topological_space α] (s : set α) (t : set α) :
    frontier (s ∪ t) ⊆ frontier s ∩ closure (tᶜ) ∪ closure (sᶜ) ∩ frontier t :=
  sorry

theorem is_closed.frontier_eq {α : Type u} [topological_space α] {s : set α} (hs : is_closed s) :
    frontier s = s \ interior s :=
  eq.mpr (id (Eq._oldrec (Eq.refl (frontier s = s \ interior s)) (frontier.equations._eqn_1 s)))
    (eq.mpr
      (id
        (Eq._oldrec (Eq.refl (closure s \ interior s = s \ interior s)) (is_closed.closure_eq hs)))
      (Eq.refl (s \ interior s)))

theorem is_open.frontier_eq {α : Type u} [topological_space α] {s : set α} (hs : is_open s) :
    frontier s = closure s \ s :=
  eq.mpr (id (Eq._oldrec (Eq.refl (frontier s = closure s \ s)) (frontier.equations._eqn_1 s)))
    (eq.mpr
      (id (Eq._oldrec (Eq.refl (closure s \ interior s = closure s \ s)) (is_open.interior_eq hs)))
      (Eq.refl (closure s \ s)))

/-- The frontier of a set is closed. -/
theorem is_closed_frontier {α : Type u} [topological_space α] {s : set α} :
    is_closed (frontier s) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_closed (frontier s))) frontier_eq_closure_inter_closure))
    (is_closed_inter is_closed_closure is_closed_closure)

/-- The frontier of a closed set has no interior point. -/
theorem interior_frontier {α : Type u} [topological_space α] {s : set α} (h : is_closed s) :
    interior (frontier s) = ∅ :=
  sorry

theorem closure_eq_interior_union_frontier {α : Type u} [topological_space α] (s : set α) :
    closure s = interior s ∪ frontier s :=
  Eq.symm (set.union_diff_cancel interior_subset_closure)

theorem closure_eq_self_union_frontier {α : Type u} [topological_space α] (s : set α) :
    closure s = s ∪ frontier s :=
  Eq.symm (set.union_diff_cancel' interior_subset subset_closure)

/-!
### Neighborhoods
-/

/-- A set is called a neighborhood of `a` if it contains an open set around `a`. The set of all
neighborhoods of `a` forms a filter, the neighborhood filter at `a`, is here defined as the
infimum over the principal filters of all open sets containing `a`. -/
def nhds {α : Type u} [topological_space α] (a : α) : filter α :=
  infi
    fun (s : set α) =>
      infi fun (H : s ∈ set_of fun (s : set α) => a ∈ s ∧ is_open s) => filter.principal s

theorem nhds_def {α : Type u} [topological_space α] (a : α) :
    nhds a =
        infi
          fun (s : set α) =>
            infi fun (H : s ∈ set_of fun (s : set α) => a ∈ s ∧ is_open s) => filter.principal s :=
  rfl

/-- The open sets containing `a` are a basis for the neighborhood filter. See `nhds_basis_opens'`
for a variant using open neighborhoods instead. -/
theorem nhds_basis_opens {α : Type u} [topological_space α] (a : α) :
    filter.has_basis (nhds a) (fun (s : set α) => a ∈ s ∧ is_open s) fun (x : set α) => x :=
  sorry

/-- A filter lies below the neighborhood filter at `a` iff it contains every open set around `a`. -/
theorem le_nhds_iff {α : Type u} [topological_space α] {f : filter α} {a : α} :
    f ≤ nhds a ↔ ∀ (s : set α), a ∈ s → is_open s → s ∈ f :=
  sorry

/-- To show a filter is above the neighborhood filter at `a`, it suffices to show that it is above
the principal filter of some open set `s` containing `a`. -/
theorem nhds_le_of_le {α : Type u} [topological_space α] {f : filter α} {a : α} {s : set α}
    (h : a ∈ s) (o : is_open s) (sf : filter.principal s ≤ f) : nhds a ≤ f :=
  eq.mpr (id (Eq._oldrec (Eq.refl (nhds a ≤ f)) (nhds_def a)))
    (infi_le_of_le s (infi_le_of_le { left := h, right := o } sf))

theorem mem_nhds_sets_iff {α : Type u} [topological_space α] {a : α} {s : set α} :
    s ∈ nhds a ↔ ∃ (t : set α), ∃ (H : t ⊆ s), is_open t ∧ a ∈ t :=
  sorry

/-- A predicate is true in a neighborhood of `a` iff it is true for all the points in an open set
containing `a`. -/
theorem eventually_nhds_iff {α : Type u} [topological_space α] {a : α} {p : α → Prop} :
    filter.eventually (fun (x : α) => p x) (nhds a) ↔
        ∃ (t : set α), (∀ (x : α), x ∈ t → p x) ∧ is_open t ∧ a ∈ t :=
  sorry

theorem map_nhds {α : Type u} {β : Type v} [topological_space α] {a : α} {f : α → β} :
    filter.map f (nhds a) =
        infi
          fun (s : set α) =>
            infi
              fun (H : s ∈ set_of fun (s : set α) => a ∈ s ∧ is_open s) =>
                filter.principal (f '' s) :=
  filter.has_basis.eq_binfi (filter.has_basis.map f (nhds_basis_opens a))

theorem mem_of_nhds {α : Type u} [topological_space α] {a : α} {s : set α} : s ∈ nhds a → a ∈ s :=
  sorry

/-- If a predicate is true in a neighborhood of `a`, then it is true for `a`. -/
theorem filter.eventually.self_of_nhds {α : Type u} [topological_space α] {p : α → Prop} {a : α}
    (h : filter.eventually (fun (y : α) => p y) (nhds a)) : p a :=
  mem_of_nhds h

theorem mem_nhds_sets {α : Type u} [topological_space α] {a : α} {s : set α} (hs : is_open s)
    (ha : a ∈ s) : s ∈ nhds a :=
  iff.mpr mem_nhds_sets_iff
    (Exists.intro s (Exists.intro (set.subset.refl s) { left := hs, right := ha }))

theorem is_open.eventually_mem {α : Type u} [topological_space α] {a : α} {s : set α}
    (hs : is_open s) (ha : a ∈ s) : filter.eventually (fun (x : α) => x ∈ s) (nhds a) :=
  mem_nhds_sets hs ha

/-- The open neighborhoods of `a` are a basis for the neighborhood filter. See `nhds_basis_opens`
for a variant using open sets around `a` instead. -/
theorem nhds_basis_opens' {α : Type u} [topological_space α] (a : α) :
    filter.has_basis (nhds a) (fun (s : set α) => s ∈ nhds a ∧ is_open s) fun (x : set α) => x :=
  sorry

/-- If a predicate is true in a neighbourhood of `a`, then for `y` sufficiently close
to `a` this predicate is true in a neighbourhood of `y`. -/
theorem filter.eventually.eventually_nhds {α : Type u} [topological_space α] {p : α → Prop} {a : α}
    (h : filter.eventually (fun (y : α) => p y) (nhds a)) :
    filter.eventually (fun (y : α) => filter.eventually (fun (y : α) => p y) (nhds y)) (nhds a) :=
  sorry

@[simp] theorem eventually_eventually_nhds {α : Type u} [topological_space α] {p : α → Prop}
    {a : α} :
    filter.eventually (fun (y : α) => filter.eventually (fun (x : α) => p x) (nhds y)) (nhds a) ↔
        filter.eventually (fun (x : α) => p x) (nhds a) :=
  sorry

@[simp] theorem nhds_bind_nhds {α : Type u} {a : α} [topological_space α] :
    filter.bind (nhds a) nhds = nhds a :=
  filter.ext fun (s : set α) => eventually_eventually_nhds

@[simp] theorem eventually_eventually_eq_nhds {α : Type u} {β : Type v} [topological_space α]
    {f : α → β} {g : α → β} {a : α} :
    filter.eventually (fun (y : α) => filter.eventually_eq (nhds y) f g) (nhds a) ↔
        filter.eventually_eq (nhds a) f g :=
  eventually_eventually_nhds

theorem filter.eventually_eq.eq_of_nhds {α : Type u} {β : Type v} [topological_space α] {f : α → β}
    {g : α → β} {a : α} (h : filter.eventually_eq (nhds a) f g) : f a = g a :=
  filter.eventually.self_of_nhds h

@[simp] theorem eventually_eventually_le_nhds {α : Type u} {β : Type v} [topological_space α]
    [HasLessEq β] {f : α → β} {g : α → β} {a : α} :
    filter.eventually (fun (y : α) => filter.eventually_le (nhds y) f g) (nhds a) ↔
        filter.eventually_le (nhds a) f g :=
  eventually_eventually_nhds

/-- If two functions are equal in a neighbourhood of `a`, then for `y` sufficiently close
to `a` these functions are equal in a neighbourhood of `y`. -/
theorem filter.eventually_eq.eventually_eq_nhds {α : Type u} {β : Type v} [topological_space α]
    {f : α → β} {g : α → β} {a : α} (h : filter.eventually_eq (nhds a) f g) :
    filter.eventually (fun (y : α) => filter.eventually_eq (nhds y) f g) (nhds a) :=
  filter.eventually.eventually_nhds h

/-- If `f x ≤ g x` in a neighbourhood of `a`, then for `y` sufficiently close to `a` we have
`f x ≤ g x` in a neighbourhood of `y`. -/
theorem filter.eventually_le.eventually_le_nhds {α : Type u} {β : Type v} [topological_space α]
    [HasLessEq β] {f : α → β} {g : α → β} {a : α} (h : filter.eventually_le (nhds a) f g) :
    filter.eventually (fun (y : α) => filter.eventually_le (nhds y) f g) (nhds a) :=
  filter.eventually.eventually_nhds h

theorem all_mem_nhds {α : Type u} [topological_space α] (x : α) (P : set α → Prop)
    (hP : ∀ (s t : set α), s ⊆ t → P s → P t) :
    (∀ (s : set α), s ∈ nhds x → P s) ↔ ∀ (s : set α), is_open s → x ∈ s → P s :=
  sorry

theorem all_mem_nhds_filter {α : Type u} {β : Type v} [topological_space α] (x : α)
    (f : set α → set β) (hf : ∀ (s t : set α), s ⊆ t → f s ⊆ f t) (l : filter β) :
    (∀ (s : set α), s ∈ nhds x → f s ∈ l) ↔ ∀ (s : set α), is_open s → x ∈ s → f s ∈ l :=
  all_mem_nhds x (fun (s : set α) => f s ∈ l)
    fun (s t : set α) (ssubt : s ⊆ t) (h : f s ∈ l) => filter.mem_sets_of_superset h (hf s t ssubt)

theorem rtendsto_nhds {α : Type u} {β : Type v} [topological_space α] {r : rel β α} {l : filter β}
    {a : α} : filter.rtendsto r l (nhds a) ↔ ∀ (s : set α), is_open s → a ∈ s → rel.core r s ∈ l :=
  all_mem_nhds_filter a (fun (U : set α) => U) (fun (s t : set α) => id) (filter.rmap r l)

theorem rtendsto'_nhds {α : Type u} {β : Type v} [topological_space α] {r : rel β α} {l : filter β}
    {a : α} :
    filter.rtendsto' r l (nhds a) ↔ ∀ (s : set α), is_open s → a ∈ s → rel.preimage r s ∈ l :=
  sorry

theorem ptendsto_nhds {α : Type u} {β : Type v} [topological_space α] {f : β →. α} {l : filter β}
    {a : α} : filter.ptendsto f l (nhds a) ↔ ∀ (s : set α), is_open s → a ∈ s → pfun.core f s ∈ l :=
  rtendsto_nhds

theorem ptendsto'_nhds {α : Type u} {β : Type v} [topological_space α] {f : β →. α} {l : filter β}
    {a : α} :
    filter.ptendsto' f l (nhds a) ↔ ∀ (s : set α), is_open s → a ∈ s → pfun.preimage f s ∈ l :=
  rtendsto'_nhds

theorem tendsto_nhds {α : Type u} {β : Type v} [topological_space α] {f : β → α} {l : filter β}
    {a : α} : filter.tendsto f l (nhds a) ↔ ∀ (s : set α), is_open s → a ∈ s → f ⁻¹' s ∈ l :=
  all_mem_nhds_filter a (fun (U : set α) => U)
    (fun (s t : set α) (h : s ⊆ t) => set.preimage_mono h) (filter.map f l)

theorem tendsto_const_nhds {α : Type u} {β : Type v} [topological_space α] {a : α} {f : filter β} :
    filter.tendsto (fun (b : β) => a) f (nhds a) :=
  iff.mpr tendsto_nhds
    fun (s : set α) (hs : is_open s) (ha : a ∈ s) => filter.univ_mem_sets' fun (_x : β) => ha

theorem pure_le_nhds {α : Type u} [topological_space α] : pure ≤ nhds :=
  fun (a : α) (s : set α) (hs : s ∈ nhds a) => iff.mpr filter.mem_pure_sets (mem_of_nhds hs)

theorem tendsto_pure_nhds {β : Type v} {α : Type u_1} [topological_space β] (f : α → β) (a : α) :
    filter.tendsto f (pure a) (nhds (f a)) :=
  filter.tendsto.mono_right (filter.tendsto_pure_pure f a) (pure_le_nhds (f a))

theorem order_top.tendsto_at_top_nhds {β : Type v} {α : Type u_1} [order_top α]
    [topological_space β] (f : α → β) : filter.tendsto f filter.at_top (nhds (f ⊤)) :=
  filter.tendsto.mono_right (filter.tendsto_at_top_pure f) (pure_le_nhds (f ⊤))

@[simp] protected instance nhds_ne_bot {α : Type u} [topological_space α] {a : α} :
    filter.ne_bot (nhds a) :=
  filter.ne_bot_of_le (pure_le_nhds a)

/-!
### Cluster points

In this section we define [cluster points](https://en.wikipedia.org/wiki/Limit_point)
(also known as limit points and accumulation points) of a filter and of a sequence.
-/

/-- A point `x` is a cluster point of a filter `F` if 𝓝 x ⊓ F ≠ ⊥. Also known as
an accumulation point or a limit point. -/
def cluster_pt {α : Type u} [topological_space α] (x : α) (F : filter α) :=
  filter.ne_bot (nhds x ⊓ F)

theorem cluster_pt.ne_bot {α : Type u} [topological_space α] {x : α} {F : filter α}
    (h : cluster_pt x F) : filter.ne_bot (nhds x ⊓ F) :=
  h

theorem cluster_pt_iff {α : Type u} [topological_space α] {x : α} {F : filter α} :
    cluster_pt x F ↔ ∀ {U : set α}, U ∈ nhds x → ∀ {V : set α}, V ∈ F → set.nonempty (U ∩ V) :=
  filter.inf_ne_bot_iff

/-- `x` is a cluster point of a set `s` if every neighbourhood of `x` meets `s` on a nonempty
set. -/
theorem cluster_pt_principal_iff {α : Type u} [topological_space α] {x : α} {s : set α} :
    cluster_pt x (filter.principal s) ↔ ∀ (U : set α), U ∈ nhds x → set.nonempty (U ∩ s) :=
  filter.inf_principal_ne_bot_iff

theorem cluster_pt_principal_iff_frequently {α : Type u} [topological_space α] {x : α} {s : set α} :
    cluster_pt x (filter.principal s) ↔ filter.frequently (fun (y : α) => y ∈ s) (nhds x) :=
  sorry

theorem cluster_pt.of_le_nhds {α : Type u} [topological_space α] {x : α} {f : filter α}
    (H : f ≤ nhds x) [filter.ne_bot f] : cluster_pt x f :=
  eq.mpr (id (Eq._oldrec (Eq.refl (cluster_pt x f)) (cluster_pt.equations._eqn_1 x f)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (filter.ne_bot (nhds x ⊓ f))) (iff.mpr inf_eq_right H)))
      _inst_2)

theorem cluster_pt.of_le_nhds' {α : Type u} [topological_space α] {x : α} {f : filter α}
    (H : f ≤ nhds x) (hf : filter.ne_bot f) : cluster_pt x f :=
  cluster_pt.of_le_nhds H

theorem cluster_pt.of_nhds_le {α : Type u} [topological_space α] {x : α} {f : filter α}
    (H : nhds x ≤ f) : cluster_pt x f :=
  sorry

theorem cluster_pt.mono {α : Type u} [topological_space α] {x : α} {f : filter α} {g : filter α}
    (H : cluster_pt x f) (h : f ≤ g) : cluster_pt x g :=
  ne_bot_of_le_ne_bot H (inf_le_inf_left (nhds x) h)

theorem cluster_pt.of_inf_left {α : Type u} [topological_space α] {x : α} {f : filter α}
    {g : filter α} (H : cluster_pt x (f ⊓ g)) : cluster_pt x f :=
  cluster_pt.mono H inf_le_left

theorem cluster_pt.of_inf_right {α : Type u} [topological_space α] {x : α} {f : filter α}
    {g : filter α} (H : cluster_pt x (f ⊓ g)) : cluster_pt x g :=
  cluster_pt.mono H inf_le_right

theorem ultrafilter.cluster_pt_iff {α : Type u} [topological_space α] {x : α} {f : ultrafilter α} :
    cluster_pt x ↑f ↔ ↑f ≤ nhds x :=
  { mp := ultrafilter.le_of_inf_ne_bot' f, mpr := fun (h : ↑f ≤ nhds x) => cluster_pt.of_le_nhds h }

/-- A point `x` is a cluster point of a sequence `u` along a filter `F` if it is a cluster point
of `map u F`. -/
def map_cluster_pt {α : Type u} [topological_space α] {ι : Type u_1} (x : α) (F : filter ι)
    (u : ι → α) :=
  cluster_pt x (filter.map u F)

theorem map_cluster_pt_iff {α : Type u} [topological_space α] {ι : Type u_1} (x : α) (F : filter ι)
    (u : ι → α) :
    map_cluster_pt x F u ↔
        ∀ (s : set α), s ∈ nhds x → filter.frequently (fun (a : ι) => u a ∈ s) F :=
  sorry

theorem map_cluster_pt_of_comp {α : Type u} [topological_space α] {ι : Type u_1} {δ : Type u_2}
    {F : filter ι} {φ : δ → ι} {p : filter δ} {x : α} {u : ι → α} [filter.ne_bot p]
    (h : filter.tendsto φ p F) (H : filter.tendsto (u ∘ φ) p (nhds x)) : map_cluster_pt x F u :=
  filter.ne_bot_of_le (le_inf H (trans_rel_right LessEq filter.map_map (filter.map_mono h)))

/-!
### Interior, closure and frontier in terms of neighborhoods
-/

theorem interior_eq_nhds' {α : Type u} [topological_space α] {s : set α} :
    interior s = set_of fun (a : α) => s ∈ nhds a :=
  sorry

theorem interior_eq_nhds {α : Type u} [topological_space α] {s : set α} :
    interior s = set_of fun (a : α) => nhds a ≤ filter.principal s :=
  sorry

theorem mem_interior_iff_mem_nhds {α : Type u} [topological_space α] {s : set α} {a : α} :
    a ∈ interior s ↔ s ∈ nhds a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a ∈ interior s ↔ s ∈ nhds a)) interior_eq_nhds'))
    (eq.mpr
      (id
        (Eq._oldrec (Eq.refl ((a ∈ set_of fun (a : α) => s ∈ nhds a) ↔ s ∈ nhds a))
          set.mem_set_of_eq))
      (iff.refl (s ∈ nhds a)))

theorem interior_set_of_eq {α : Type u} [topological_space α] {p : α → Prop} :
    interior (set_of fun (x : α) => p x) =
        set_of fun (x : α) => filter.eventually (fun (x : α) => p x) (nhds x) :=
  interior_eq_nhds'

theorem is_open_set_of_eventually_nhds {α : Type u} [topological_space α] {p : α → Prop} :
    is_open (set_of fun (x : α) => filter.eventually (fun (y : α) => p y) (nhds x)) :=
  sorry

theorem subset_interior_iff_nhds {α : Type u} [topological_space α] {s : set α} {V : set α} :
    s ⊆ interior V ↔ ∀ (x : α), x ∈ s → V ∈ nhds x :=
  sorry

theorem is_open_iff_nhds {α : Type u} [topological_space α] {s : set α} :
    is_open s ↔ ∀ (a : α), a ∈ s → nhds a ≤ filter.principal s :=
  iff.trans (iff.symm subset_interior_iff_open)
    (eq.mpr
      (id
        (Eq._oldrec (Eq.refl (s ⊆ interior s ↔ ∀ (a : α), a ∈ s → nhds a ≤ filter.principal s))
          interior_eq_nhds))
      (iff.refl (s ⊆ set_of fun (a : α) => nhds a ≤ filter.principal s)))

theorem is_open_iff_mem_nhds {α : Type u} [topological_space α] {s : set α} :
    is_open s ↔ ∀ (a : α), a ∈ s → s ∈ nhds a :=
  iff.trans is_open_iff_nhds
    (forall_congr fun (_x : α) => imp_congr_right fun (_x_1 : _x ∈ s) => filter.le_principal_iff)

theorem is_open_iff_ultrafilter {α : Type u} [topological_space α] {s : set α} :
    is_open s ↔ ∀ (x : α), x ∈ s → ∀ (l : ultrafilter α), ↑l ≤ nhds x → s ∈ l :=
  sorry

theorem mem_closure_iff_frequently {α : Type u} [topological_space α] {s : set α} {a : α} :
    a ∈ closure s ↔ filter.frequently (fun (x : α) => x ∈ s) (nhds a) :=
  sorry

theorem filter.frequently.mem_closure {α : Type u} [topological_space α] {s : set α} {a : α} :
    filter.frequently (fun (x : α) => x ∈ s) (nhds a) → a ∈ closure s :=
  iff.mpr mem_closure_iff_frequently

/-- The set of cluster points of a filter is closed. In particular, the set of limit points
of a sequence is closed. -/
theorem is_closed_set_of_cluster_pt {α : Type u} [topological_space α] {f : filter α} :
    is_closed (set_of fun (x : α) => cluster_pt x f) :=
  sorry

theorem mem_closure_iff_cluster_pt {α : Type u} [topological_space α] {s : set α} {a : α} :
    a ∈ closure s ↔ cluster_pt a (filter.principal s) :=
  iff.trans mem_closure_iff_frequently (iff.symm cluster_pt_principal_iff_frequently)

theorem closure_eq_cluster_pts {α : Type u} [topological_space α] {s : set α} :
    closure s = set_of fun (a : α) => cluster_pt a (filter.principal s) :=
  set.ext fun (x : α) => mem_closure_iff_cluster_pt

theorem mem_closure_iff_nhds {α : Type u} [topological_space α] {s : set α} {a : α} :
    a ∈ closure s ↔ ∀ (t : set α), t ∈ nhds a → set.nonempty (t ∩ s) :=
  iff.trans mem_closure_iff_cluster_pt cluster_pt_principal_iff

theorem mem_closure_iff_nhds' {α : Type u} [topological_space α] {s : set α} {a : α} :
    a ∈ closure s ↔ ∀ (t : set α), t ∈ nhds a → ∃ (y : ↥s), ↑y ∈ t :=
  sorry

theorem mem_closure_iff_comap_ne_bot {α : Type u} [topological_space α] {A : set α} {x : α} :
    x ∈ closure A ↔ filter.ne_bot (filter.comap coe (nhds x)) :=
  sorry

theorem mem_closure_iff_nhds_basis {α : Type u} {β : Type v} [topological_space α] {a : α}
    {p : β → Prop} {s : β → set α} (h : filter.has_basis (nhds a) p s) {t : set α} :
    a ∈ closure t ↔ ∀ (i : β), p i → ∃ (y : α), ∃ (H : y ∈ t), y ∈ s i :=
  sorry

/-- `x` belongs to the closure of `s` if and only if some ultrafilter
  supported on `s` converges to `x`. -/
theorem mem_closure_iff_ultrafilter {α : Type u} [topological_space α] {s : set α} {x : α} :
    x ∈ closure s ↔ ∃ (u : ultrafilter α), s ∈ u ∧ ↑u ≤ nhds x :=
  sorry

theorem is_closed_iff_cluster_pt {α : Type u} [topological_space α] {s : set α} :
    is_closed s ↔ ∀ (a : α), cluster_pt a (filter.principal s) → a ∈ s :=
  sorry

theorem is_closed_iff_nhds {α : Type u} [topological_space α] {s : set α} :
    is_closed s ↔ ∀ (x : α), (∀ (U : set α), U ∈ nhds x → set.nonempty (U ∩ s)) → x ∈ s :=
  sorry

theorem closure_inter_open {α : Type u} [topological_space α] {s : set α} {t : set α}
    (h : is_open s) : s ∩ closure t ⊆ closure (s ∩ t) :=
  sorry

/-- The intersection of an open dense set with a dense set is a dense set. -/
theorem dense.inter_of_open_left {α : Type u} [topological_space α] {s : set α} {t : set α}
    (hs : dense s) (ht : dense t) (hso : is_open s) : dense (s ∩ t) :=
  sorry

/-- The intersection of a dense set with an open dense set is a dense set. -/
theorem dense.inter_of_open_right {α : Type u} [topological_space α] {s : set α} {t : set α}
    (hs : dense s) (ht : dense t) (hto : is_open t) : dense (s ∩ t) :=
  set.inter_comm t s ▸ dense.inter_of_open_left ht hs hto

theorem dense.inter_nhds_nonempty {α : Type u} [topological_space α] {s : set α} {t : set α}
    (hs : dense s) {x : α} (ht : t ∈ nhds x) : set.nonempty (s ∩ t) :=
  sorry

theorem closure_diff {α : Type u} [topological_space α] {s : set α} {t : set α} :
    closure s \ closure t ⊆ closure (s \ t) :=
  sorry

theorem filter.frequently.mem_of_closed {α : Type u} [topological_space α] {a : α} {s : set α}
    (h : filter.frequently (fun (x : α) => x ∈ s) (nhds a)) (hs : is_closed s) : a ∈ s :=
  is_closed.closure_subset hs (filter.frequently.mem_closure h)

theorem is_closed.mem_of_frequently_of_tendsto {α : Type u} {β : Type v} [topological_space α]
    {f : β → α} {b : filter β} {a : α} {s : set α} (hs : is_closed s)
    (h : filter.frequently (fun (x : β) => f x ∈ s) b) (hf : filter.tendsto f b (nhds a)) : a ∈ s :=
  sorry

theorem is_closed.mem_of_tendsto {α : Type u} {β : Type v} [topological_space α] {f : β → α}
    {b : filter β} {a : α} {s : set α} [filter.ne_bot b] (hs : is_closed s)
    (hf : filter.tendsto f b (nhds a)) (h : filter.eventually (fun (x : β) => f x ∈ s) b) : a ∈ s :=
  is_closed.mem_of_frequently_of_tendsto hs (filter.eventually.frequently h) hf

theorem mem_closure_of_tendsto {α : Type u} {β : Type v} [topological_space α] {f : β → α}
    {b : filter β} {a : α} {s : set α} [filter.ne_bot b] (hf : filter.tendsto f b (nhds a))
    (h : filter.eventually (fun (x : β) => f x ∈ s) b) : a ∈ closure s :=
  is_closed.mem_of_tendsto is_closed_closure hf
    (filter.eventually.mono h (set.preimage_mono subset_closure))

/-- Suppose that `f` sends the complement to `s` to a single point `a`, and `l` is some filter.
Then `f` tends to `a` along `l` restricted to `s` if and only if it tends to `a` along `l`. -/
theorem tendsto_inf_principal_nhds_iff_of_forall_eq {α : Type u} {β : Type v} [topological_space α]
    {f : β → α} {l : filter β} {s : set β} {a : α} (h : ∀ (x : β), ¬x ∈ s → f x = a) :
    filter.tendsto f (l ⊓ filter.principal s) (nhds a) ↔ filter.tendsto f l (nhds a) :=
  sorry

/-!
### Limits of filters in topological spaces
-/

/-- If `f` is a filter, then `Lim f` is a limit of the filter, if it exists. -/
def Lim {α : Type u} [topological_space α] [Nonempty α] (f : filter α) : α :=
  classical.epsilon fun (a : α) => f ≤ nhds a

/--
If `f` is a filter satisfying `ne_bot f`, then `Lim' f` is a limit of the filter, if it exists.
-/
def Lim' {α : Type u} [topological_space α] (f : filter α) [filter.ne_bot f] : α := Lim f

/--
If `F` is an ultrafilter, then `filter.ultrafilter.Lim F` is a limit of the filter, if it exists.
Note that dot notation `F.Lim` can be used for `F : ultrafilter α`.
-/
def ultrafilter.Lim {α : Type u} [topological_space α] : ultrafilter α → α :=
  fun (F : ultrafilter α) => Lim' ↑F

/-- If `f` is a filter in `β` and `g : β → α` is a function, then `lim f` is a limit of `g` at `f`,
if it exists. -/
def lim {α : Type u} {β : Type v} [topological_space α] [Nonempty α] (f : filter β) (g : β → α) :
    α :=
  Lim (filter.map g f)

/-- If a filter `f` is majorated by some `𝓝 a`, then it is majorated by `𝓝 (Lim f)`. We formulate
this lemma with a `[nonempty α]` argument of `Lim` derived from `h` to make it useful for types
without a `[nonempty α]` instance. Because of the built-in proof irrelevance, Lean will unify
this instance with any other instance. -/
theorem le_nhds_Lim {α : Type u} [topological_space α] {f : filter α} (h : ∃ (a : α), f ≤ nhds a) :
    f ≤ nhds (Lim f) :=
  classical.epsilon_spec h

/-- If `g` tends to some `𝓝 a` along `f`, then it tends to `𝓝 (lim f g)`. We formulate
this lemma with a `[nonempty α]` argument of `lim` derived from `h` to make it useful for types
without a `[nonempty α]` instance. Because of the built-in proof irrelevance, Lean will unify
this instance with any other instance. -/
theorem tendsto_nhds_lim {α : Type u} {β : Type v} [topological_space α] {f : filter β} {g : β → α}
    (h : ∃ (a : α), filter.tendsto g f (nhds a)) : filter.tendsto g f (nhds (lim f g)) :=
  le_nhds_Lim h

/-!
### Locally finite families
-/

/- locally finite family [General Topology (Bourbaki, 1995)] -/

/-- A family of sets in `set α` is locally finite if at every point `x:α`,
  there is a neighborhood of `x` which meets only finitely many sets in the family -/
def locally_finite {α : Type u} {β : Type v} [topological_space α] (f : β → set α) :=
  ∀ (x : α),
    ∃ (t : set α), ∃ (H : t ∈ nhds x), set.finite (set_of fun (i : β) => set.nonempty (f i ∩ t))

theorem locally_finite_of_finite {α : Type u} {β : Type v} [topological_space α] {f : β → set α}
    (h : set.finite set.univ) : locally_finite f :=
  sorry

theorem locally_finite_subset {α : Type u} {β : Type v} [topological_space α] {f₁ : β → set α}
    {f₂ : β → set α} (hf₂ : locally_finite f₂) (hf : ∀ (b : β), f₁ b ⊆ f₂ b) : locally_finite f₁ :=
  sorry

theorem is_closed_Union_of_locally_finite {α : Type u} {β : Type v} [topological_space α]
    {f : β → set α} (h₁ : locally_finite f) (h₂ : ∀ (i : β), is_closed (f i)) :
    is_closed (set.Union fun (i : β) => f i) :=
  sorry

/-!
### Continuity
-/

/-- A function between topological spaces is continuous if the preimage
  of every open set is open. Registered as a structure to make sure it is not unfolded by Lean. -/
structure continuous {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    (f : α → β)
    where
  is_open_preimage : ∀ (s : set β), is_open s → is_open (f ⁻¹' s)

theorem continuous_def {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    {f : α → β} : continuous f ↔ ∀ (s : set β), is_open s → is_open (f ⁻¹' s) :=
  { mp :=
      fun (hf : continuous f) (s : set β) (hs : is_open s) => continuous.is_open_preimage hf s hs,
    mpr := fun (h : ∀ (s : set β), is_open s → is_open (f ⁻¹' s)) => continuous.mk h }

theorem is_open.preimage {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    {f : α → β} (hf : continuous f) {s : set β} (h : is_open s) : is_open (f ⁻¹' s) :=
  continuous.is_open_preimage hf s h

/-- A function between topological spaces is continuous at a point `x₀`
if `f x` tends to `f x₀` when `x` tends to `x₀`. -/
def continuous_at {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    (f : α → β) (x : α) :=
  filter.tendsto f (nhds x) (nhds (f x))

theorem continuous_at.tendsto {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α → β} {x : α} (h : continuous_at f x) :
    filter.tendsto f (nhds x) (nhds (f x)) :=
  h

theorem continuous_at.preimage_mem_nhds {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α → β} {x : α} {t : set β} (h : continuous_at f x)
    (ht : t ∈ nhds (f x)) : f ⁻¹' t ∈ nhds x :=
  h ht

theorem cluster_pt.map {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    {x : α} {la : filter α} {lb : filter β} (H : cluster_pt x la) {f : α → β}
    (hfc : continuous_at f x) (hf : filter.tendsto f la lb) : cluster_pt (f x) lb :=
  ne_bot_of_le_ne_bot (iff.mpr (filter.map_ne_bot_iff f) H)
    (filter.tendsto.inf (continuous_at.tendsto hfc) hf)

theorem preimage_interior_subset_interior_preimage {α : Type u_1} {β : Type u_2}
    [topological_space α] [topological_space β] {f : α → β} {s : set β} (hf : continuous f) :
    f ⁻¹' interior s ⊆ interior (f ⁻¹' s) :=
  interior_maximal (set.preimage_mono interior_subset) (is_open.preimage hf is_open_interior)

theorem continuous_id {α : Type u_1} [topological_space α] : continuous id :=
  iff.mpr continuous_def fun (s : set α) (h : is_open s) => h

theorem continuous.comp {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α]
    [topological_space β] [topological_space γ] {g : β → γ} {f : α → β} (hg : continuous g)
    (hf : continuous f) : continuous (g ∘ f) :=
  iff.mpr continuous_def
    fun (s : set γ) (h : is_open s) => is_open.preimage hf (is_open.preimage hg h)

theorem continuous.iterate {α : Type u_1} [topological_space α] {f : α → α} (h : continuous f)
    (n : ℕ) : continuous (nat.iterate f n) :=
  nat.rec_on n continuous_id
    fun (n : ℕ) (ihn : continuous (nat.iterate f n)) => continuous.comp ihn h

theorem continuous_at.comp {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α]
    [topological_space β] [topological_space γ] {g : β → γ} {f : α → β} {x : α}
    (hg : continuous_at g (f x)) (hf : continuous_at f x) : continuous_at (g ∘ f) x :=
  filter.tendsto.comp hg hf

theorem continuous.tendsto {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    {f : α → β} (hf : continuous f) (x : α) : filter.tendsto f (nhds x) (nhds (f x)) :=
  sorry

/-- A version of `continuous.tendsto` that allows one to specify a simpler form of the limit.
E.g., one can write `continuous_exp.tendsto' 0 1 exp_zero`. -/
theorem continuous.tendsto' {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α → β} (hf : continuous f) (x : α) (y : β) (h : f x = y) :
    filter.tendsto f (nhds x) (nhds y) :=
  h ▸ continuous.tendsto hf x

theorem continuous.continuous_at {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α → β} {x : α} (h : continuous f) : continuous_at f x :=
  continuous.tendsto h x

theorem continuous_iff_continuous_at {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α → β} : continuous f ↔ ∀ (x : α), continuous_at f x :=
  sorry

theorem continuous_at_const {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {x : α} {b : β} : continuous_at (fun (a : α) => b) x :=
  tendsto_const_nhds

theorem continuous_const {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    {b : β} : continuous fun (a : α) => b :=
  iff.mpr continuous_iff_continuous_at fun (a : α) => continuous_at_const

theorem continuous_at_id {α : Type u_1} [topological_space α] {x : α} : continuous_at id x :=
  continuous.continuous_at continuous_id

theorem continuous_at.iterate {α : Type u_1} [topological_space α] {f : α → α} {x : α}
    (hf : continuous_at f x) (hx : f x = x) (n : ℕ) : continuous_at (nat.iterate f n) x :=
  nat.rec_on n continuous_at_id
    fun (n : ℕ) (ihn : continuous_at (nat.iterate f n) x) =>
      (fun (this : continuous_at (nat.iterate f n ∘ f) x) => this)
        (continuous_at.comp (Eq.symm hx ▸ ihn) hf)

theorem continuous_iff_is_closed {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α → β} :
    continuous f ↔ ∀ (s : set β), is_closed s → is_closed (f ⁻¹' s) :=
  sorry

theorem is_closed.preimage {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    {f : α → β} (hf : continuous f) {s : set β} (h : is_closed s) : is_closed (f ⁻¹' s) :=
  iff.mp continuous_iff_is_closed hf s h

theorem continuous_at_iff_ultrafilter {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α → β} {x : α} :
    continuous_at f x ↔ ∀ (g : ultrafilter α), ↑g ≤ nhds x → filter.tendsto f (↑g) (nhds (f x)) :=
  filter.tendsto_iff_ultrafilter f (nhds x) (nhds (f x))

theorem continuous_iff_ultrafilter {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α → β} :
    continuous f ↔
        ∀ (x : α) (g : ultrafilter α), ↑g ≤ nhds x → filter.tendsto f (↑g) (nhds (f x)) :=
  sorry

/-- A piecewise defined function `if p then f else g` is continuous, if both `f` and `g`
are continuous, and they coincide on the frontier (boundary) of the set `{a | p a}`. -/
theorem continuous_if {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    {p : α → Prop} {f : α → β} {g : α → β} {h : (a : α) → Decidable (p a)}
    (hp : ∀ (a : α), a ∈ frontier (set_of fun (a : α) => p a) → f a = g a) (hf : continuous f)
    (hg : continuous g) : continuous fun (a : α) => ite (p a) (f a) (g a) :=
  sorry

/-! ### Continuity and partial functions -/

/-- Continuity of a partial function -/
def pcontinuous {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    (f : α →. β) :=
  ∀ (s : set β), is_open s → is_open (pfun.preimage f s)

theorem open_dom_of_pcontinuous {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α →. β} (h : pcontinuous f) : is_open (pfun.dom f) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_open (pfun.dom f))) (Eq.symm (pfun.preimage_univ f))))
    (h set.univ is_open_univ)

theorem pcontinuous_iff' {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    {f : α →. β} :
    pcontinuous f ↔ ∀ {x : α} {y : β}, y ∈ f x → filter.ptendsto' f (nhds x) (nhds y) :=
  sorry

/-- If a continuous map `f` maps `s` to `t`, then it maps `closure s` to `closure t`. -/
theorem set.maps_to.closure {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {s : set α} {t : set β} {f : α → β} (h : set.maps_to f s t)
    (hc : continuous f) : set.maps_to f (closure s) (closure t) :=
  sorry

theorem image_closure_subset_closure_image {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α → β} {s : set α} (h : continuous f) :
    f '' closure s ⊆ closure (f '' s) :=
  set.maps_to.image_subset (set.maps_to.closure (set.maps_to_image f s) h)

theorem map_mem_closure {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β]
    {s : set α} {t : set β} {f : α → β} {a : α} (hf : continuous f) (ha : a ∈ closure s)
    (ht : ∀ (a : α), a ∈ s → f a ∈ t) : f a ∈ closure t :=
  set.maps_to.closure ht hf ha

/-!
### Function with dense range
-/

/-- `f : ι → β` has dense range if its range (image) is a dense subset of β. -/
def dense_range {β : Type u_2} [topological_space β] {κ : Type u_5} (f : κ → β) :=
  dense (set.range f)

/-- A surjective map has dense range. -/
theorem function.surjective.dense_range {β : Type u_2} [topological_space β] {κ : Type u_5}
    {f : κ → β} (hf : function.surjective f) : dense_range f :=
  sorry

theorem dense_range_iff_closure_range {β : Type u_2} [topological_space β] {κ : Type u_5}
    {f : κ → β} : dense_range f ↔ closure (set.range f) = set.univ :=
  dense_iff_closure_eq

theorem dense_range.closure_range {β : Type u_2} [topological_space β] {κ : Type u_5} {f : κ → β}
    (h : dense_range f) : closure (set.range f) = set.univ :=
  dense.closure_eq h

theorem continuous.range_subset_closure_image_dense {α : Type u_1} {β : Type u_2}
    [topological_space α] [topological_space β] {f : α → β} (hf : continuous f) {s : set α}
    (hs : dense s) : set.range f ⊆ closure (f '' s) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (set.range f ⊆ closure (f '' s))) (Eq.symm set.image_univ)))
    (eq.mpr
      (id (Eq._oldrec (Eq.refl (f '' set.univ ⊆ closure (f '' s))) (Eq.symm (dense.closure_eq hs))))
      (image_closure_subset_closure_image hf))

/-- The image of a dense set under a continuous map with dense range is a dense set. -/
theorem dense_range.dense_image {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α → β} (hf' : dense_range f) (hf : continuous f) {s : set α}
    (hs : dense s) : dense (f '' s) :=
  dense.of_closure (dense.mono (continuous.range_subset_closure_image_dense hf hs) hf')

/-- If a continuous map with dense range maps a dense set to a subset of `t`, then `t` is a dense
set. -/
theorem dense_range.dense_of_maps_to {α : Type u_1} {β : Type u_2} [topological_space α]
    [topological_space β] {f : α → β} (hf' : dense_range f) (hf : continuous f) {s : set α}
    (hs : dense s) {t : set β} (ht : set.maps_to f s t) : dense t :=
  dense.mono (set.maps_to.image_subset ht) (dense_range.dense_image hf' hf hs)

/-- Composition of a continuous map with dense range and a function with dense range has dense
range. -/
theorem dense_range.comp {β : Type u_2} {γ : Type u_3} [topological_space β] [topological_space γ]
    {κ : Type u_5} {g : β → γ} {f : κ → β} (hg : dense_range g) (hf : dense_range f)
    (cg : continuous g) : dense_range (g ∘ f) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (dense_range (g ∘ f))) (dense_range.equations._eqn_1 (g ∘ f))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (dense (set.range (g ∘ f)))) (set.range_comp g f)))
      (dense_range.dense_image hg cg hf))

theorem dense_range.nonempty_iff {β : Type u_2} [topological_space β] {κ : Type u_5} {f : κ → β}
    (hf : dense_range f) : Nonempty κ ↔ Nonempty β :=
  iff.trans (iff.symm set.range_nonempty_iff_nonempty) (dense.nonempty_iff hf)

theorem dense_range.nonempty {β : Type u_2} [topological_space β] {κ : Type u_5} {f : κ → β}
    [h : Nonempty β] (hf : dense_range f) : Nonempty κ :=
  iff.mpr (dense_range.nonempty_iff hf) h

/-- Given a function `f : α → β` with dense range and `b : β`, returns some `a : α`. -/
def dense_range.some {β : Type u_2} [topological_space β] {κ : Type u_5} {f : κ → β}
    (hf : dense_range f) (b : β) : κ :=
  Classical.choice sorry

end Mathlib


/-
Copyright (c) 2019 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author(s): Simon Hudon
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.tactic.core
import Mathlib.PostPort

universes u_1

namespace Mathlib

/-!
This file provides an alternative implementation for `apply` to fix the so-called "apply bug".

The issue arises when the goals is a Π-type -- whether it is visible or hidden behind a definition.

For instance, consider the following proof:

```
example {α β} (x y z : α → β) (h₀ : x ≤ y) (h₁ : y ≤ z) : x ≤ z :=
begin
  apply le_trans,
end
```

Because `x ≤ z` is definitionally equal to `∀ i, x i ≤ z i`, `apply` will fail. The alternative
definition, `apply'` fixes this. When `apply` would work, `apply` is used and otherwise,
a different strategy is deployed
-/

namespace tactic

/-- With `gs` a list of proof goals, `reorder_goals gs new_g` will use the `new_goals` policy
`new_g` to rearrange the dependent goals to either drop them, push them to the end of the list
or leave them in place. The `bool` values in `gs` indicates whether the goal is dependent or not. -/
def reorder_goals {α : Type u_1} (gs : List (Bool × α)) : new_goals → List α :=
  sorry


/-
Copyright (c) 2021 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Adam Topaz
-/
import category_theory.preadditive
import category_theory.preadditive.additive_functor
import data.equiv.transfer_instance

/-!
# If `C` is preadditive, `Cᵒᵖ` has a natural preadditive structure.

-/

open opposite

namespace category_theory

variables (C : Type*) [category C] [preadditive C]

instance : preadditive Cᵒᵖ :=
{ hom_group := λ X Y, equiv.add_comm_group (op_equiv X Y),
  add_comp' := λ X Y Z f f' g,
    congr_arg quiver.hom.op (preadditive.comp_add _ _ _ g.unop f.unop f'.unop),
  comp_add' := λ X Y Z f g g',
    congr_arg quiver.hom.op (preadditive.add_comp _ _ _ g.unop g'.unop f.unop), }

@[simp] lemma unop_zero (X Y : Cᵒᵖ) : (0 : X ⟶ Y).unop = 0 := rfl
@[simp] lemma unop_add {X Y : Cᵒᵖ} (f g : X ⟶ Y) : (f + g).unop = f.unop + g.unop := rfl
@[simp] lemma op_zero (X Y : C) : (0 : X ⟶ Y).op = 0 := rfl
@[simp]

variables {C} {D : Type*} [category D] [preadditive D]

instance functor.op_additive (F : C ⥤ D) [F.additive] : F.op.additive := {}

instance functor.right_op_additive (F : Cᵒᵖ ⥤ D) [F.additive] : F.right_op.additive := {}

instance functor.left_op_additive (F : C ⥤ Dᵒᵖ) [F.additive] : F.left_op.additive := {}

instance functor.unop_additive (F : Cᵒᵖ ⥤ Dᵒᵖ) [F.additive] : F.unop.additive := {}

end category_theory


/- Copyright 2019 (c) Hans-Dieter Hiep. All rights reserved. Released under MIT license as described in the file LICENSE. -/

import util

/- Each program has names. Names consist of a finite set of class names, a finite set of record names, and a finite set of symbol names. To each class name, there an associated finite set of field and method names. There is a built-in record name, boolean. -/
class names (α : Type) :=
  (decidable_name: decidable_eq α)
  (Nc: finset α)
  (Nr: finset α)
  (Ns: finset α)
  (Nf {x : α} (H: x ∈ Nc): finset α)
  (Nm {x : α} (H: x ∈ Nc): finset α)
  (Nboolean : α)
  (Hboolean : Nboolean ∈ Nr)
instance names.decidable {α : Type} [names α]:
  decidable_eq α := names.decidable_name α

/- We make use of subtypes, to demonstrate instances of fintype. Whenever we have established that these types are finite, we obtain decidable equality for functions that take these types as domain (thanks to Johannes Hölzl). This fact turns out to be crucial for semantics. -/
@[reducible]
def class_name (α : Type) [names α] :=
  {name : α // name ∈ names.Nc α}
@[reducible]
def record_name (α : Type) [names α] :=
  {name : α // name ∈ names.Nr α}
@[reducible]
def symbol_name (α : Type) [names α] :=
  {name : α // name ∈ names.Ns α}
@[reducible]
def boolean_name (α : Type) [names α] : record_name α :=
  ⟨names.Nboolean α, names.Hboolean α⟩
@[reducible]
def field_name {α : Type} [names α] (self : class_name α) :=
  {name : α // name ∈ names.Nf self.property}
@[reducible]
def method_name {α : Type} [names α] (self : class_name α) :=
  {name : α // name ∈ names.Nm self.property}

/- A type in our object language is either: a reference type (by class name), or a data type (by record name). -/
@[derive decidable_eq]
inductive type (α : Type) [names α]
| ref: class_name α → type
| data: record_name α → type

/- A variable context is a list of types; a local variable is encoded by an index in this context. Local variables do not have a name. -/
@[reducible] def context (α : Type) [names α] := list (type α)
/- Boolean is a built-in data type. -/
@[reducible] def boolean (α : Type) [names α] : type α :=
  (type.data (boolean_name α))

/- A class declaration (of some class self) consists of a map from field names to field declarations, a map from method names to method declarations, and a method name that indicates a constructor. A field declaration consists of a type. A method declaration consists of a map from parameter names to parameter declarations. A parameter declaration consists of a type. -/
structure mdecl {α : Type} [names α]
    {self : class_name α} (m : method_name self) :=
  (args : list (type α))
structure fdecl {α : Type} [names α]
    {self : class_name α} (f : field_name self) :=
  (type : type α)
-- Design choice: constructors and methods are treated uniformly.
structure cdecl {α : Type} [names α] (self : class_name α) :=
  (fdecl (f : field_name self) : fdecl f)
  (mdecl (m : method_name self) : mdecl m)
  (ctor : method_name self)

/- A symbol declaration consists of a list of types of arguments, together with a resultant type. The arity of a symbol declaration is the length of the list of argument types. -/
structure sdecl (α : Type) [names α] :=
  (args : list (type α))
  (result : type α)
def sdecl.arity {α : Type} [names α] (s : sdecl α) :=
  list.length s.args

/- A signature consists of: a map from class names to class declarations, a map from symbols names to symbol declarations, and a class name of the root object. -/
class signature (α : Type) extends names α :=
  (cdef (x : class_name α): cdecl x)
  (sdef (x : symbol_name α) : sdecl α)

/- Each parameter is associated to a type, each field is associated to a type, and each class name is associated to a constructor method name. -/
def param_types {α : Type} [signature α] {self : class_name α}
    (m : method_name self) : list (type α) :=
  ((signature.cdef self).mdecl m).args
def field_type {α : Type} [signature α] {self : class_name α}
    (f : field_name self) : type α :=
  ((signature.cdef self).fdecl f).type
def ctor {α : Type} [signature α] (self : class_name α) :
    method_name self :=
  (signature.cdef self).ctor
/- A symbol with zero arity is a constant symbol, a symbol with non-zero arity is a function symbol. Each symbol is associated to a result type. A function symbol is associated to a list of argument types. -/
@[derive decidable_eq]
structure symbol {α : Type} [signature α]
    (args : list (type α)) (result : type α)
    (s : symbol_name α) :=
  (H: args = (signature.sdef s).args)
  (G: result = (signature.sdef s).result)
def symbol.name {α : Type} [signature α]
  {args : list (type α)} {result : type α}
  {s : symbol_name α} (sym : symbol args result s) := s

/- Given a signature and an enclosing class, we consider type environments to consist of: a method name, and declared local variables. -/
@[derive decidable_eq]
structure tenv {α : Type} [signature α] (self : class_name α) :=
  (current : method_name self)
  (locals : context α)
@[reducible]
def tenv.self {α : Type} [signature α] {self : class_name α}
  (e : tenv self) : class_name α := self
@[reducible]
def tenv.args {α : Type} [signature α] {self : class_name α}
  (e : tenv self) : list (type α) := param_types e.current

/- A note on terminology. Parameter: as declared in signature. Argument: a pure expression as supplied in a method call, or as supplied to a function symbol. -/

/- Within a typing environment, we refer to numerous things. This refers to an object identity of the enclosed class. A local variable refers to the index within the declared local variables. A parameter refers to a parameter name of the current method. A field refers to a field within the enclosed class. -/
@[derive decidable_eq]
structure tvar {α : Type} [signature α] {self : class_name α}
    (e : tenv self) (ty : type α) :=
  (H : ty = type.ref e.self)
@[derive decidable_eq]
structure lvar {α : Type} [signature α] {self : class_name α}
    (e : tenv self) (ty : type α):=
  (idx : list_at ty e.locals)
@[derive decidable_eq]
structure pvar {α : Type} [signature α] {self : class_name α}
    (e : tenv self) (ty : type α) :=
  (idx : list_at ty e.args)
@[derive decidable_eq]
structure fvar {α : Type} [signature α] {self : class_name α}
    (e : tenv self) (ty : type α) :=
  (idx : field_name e.self) (H : field_type idx = ty)
-- Store variable (LHS only)
@[derive decidable_eq]
inductive svar {α : Type} [signature α] {self : class_name α}
    (e : tenv self) (ty : type α)
| fvar: fvar e ty → svar
| lvar: lvar e ty → svar
-- Read variable (RHS only)
@[derive decidable_eq]
inductive rvar {α : Type} [signature α] {self : class_name α}
    (e : tenv self) (ty : type α)
| tvar: tvar e ty → rvar
| fvar: fvar e ty → rvar
| pvar: pvar e ty → rvar
| lvar: lvar e ty → rvar

/- A pure expression within a typing environment is either: a constant, a function application, value lookup in environment,
equality check. -/
@[derive decidable_eq]
inductive pexp {α : Type} [signature α] {self : class_name α}
    (e : tenv self) : list (type α) → Type
| const {ty : type α} {c : symbol_name α}:
    symbol [] ty c → pexp [ty]
| app {l : list (type α)} {ty : type α} {f : symbol_name α}:
    symbol l ty f → pexp l → pexp [ty]
| lookup {ty : type α}:
    rvar e ty → pexp [ty]
| equal {ty : type α}:
    pexp [ty] → pexp [ty] → pexp [boolean α]
| cons {ty : type α} {l : list (type α)}:
    pexp [ty] → pexp l → pexp (ty::l)
/- The encoding of a mutual inductive type fails in Lean 3.4.2. We currently encode this directly, by considering single expressions as expressions with a length one type list. Tuples of expressions are created by constructing lists of expressions of length one lists of types. There are no expressions of empty lists of types. -/
lemma pexp_not_nil {α : Type} [signature α]
  {self : class_name α} {e : tenv self} {l : list (type α)}
  (p : pexp e l) : l ≠ [] :=
begin
  cases l, {intro, cases p}, {intro, cases a}
end

/- A statement within a typing environment is either: a skip, a sequential composition, a branch, a loop, an assignment, an asynchronous method call, an object allocation. -/
@[derive decidable_eq]
inductive stmt {α : Type} [signature α] {self : class_name α}
    (e : tenv self) : bool → Type
| ite: pexp e [boolean α] → stmt tt → stmt tt → stmt ff
| while: pexp e [boolean α] → stmt tt → stmt ff
| assign {ty : type α} (l : svar e ty):
    pexp e [ty] → stmt ff
| async (c : class_name α) (m : method_name c)
    (H : m ≠ ctor c)
    (o : rvar e (type.ref c))
    (τ : pexp e (param_types m)): stmt ff
| alloc (c : class_name α)
    (τ : pexp e (param_types (ctor c))): stmt ff
| skip: stmt tt
| seq: stmt ff → stmt tt → stmt tt
/- The encoding of a nested inductive type failed at the moment in Lean 3.4.2. Instead, we encode the nesting ourselves: the boolean argument determines which constructors are applicable. It is true if it is a list of statements, false if it is a single statement (thanks to Mario Carneiro).  -/
@[reducible]
def statement {α : Type} [signature α] {self : class_name α}
  (e : tenv self) := stmt e ff
def stmt.from_list {α : Type} [signature α]
    {self : class_name α} {e : tenv self} :
    list (statement e) → stmt e tt
| [] := stmt.skip e
| (x :: xs) := stmt.seq x (stmt.from_list xs)
def stmt.to_list {α : Type} [signature α]
    {self : class_name α} {e : tenv self} :
    stmt e tt → list (statement e)
| (stmt.skip .(e)) := []
| (stmt.seq x xs) := (x :: stmt.to_list xs)

/- A program with a given program signature associates to each method of each class a program block. A program block associated to a method consists of: local variable declarations, and a statement within a typing environment. -/
structure pblock {α : Type} [signature α]
    {self : class_name α} (m : method_name self) :=
  (locals : context α)
  (S : list (statement ⟨m,locals⟩))
def pblock.tenv {α : Type} [signature α] {self : class_name α}
    {m : method_name self} (pb : pblock m) :=
  (tenv.mk m pb.locals)
structure program (α : Type) [signature α] :=
  (body {self : class_name α} (m : method_name self): pblock m)
  (main: class_name α)


/-
Copyright (c) 2021 Adam Topaz. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Adam Topaz
-/
import category_theory.sites.plus
import category_theory.limits.concrete_category

/-!

# Sheafification

We construct the sheafification of a presheaf over a site `C` with values in `D` whenever
`D` is a concrete category for which the forgetful functor preserves the appropriate (co)limits
and reflects isomorphisms.

We generally follow the approach of https://stacks.math.columbia.edu/tag/00W1

-/

namespace category_theory

open category_theory.limits opposite

universes w v u
variables {C : Type u} [category.{v} C] {J : grothendieck_topology C}
variables {D : Type w} [category.{max v u} D]

section
variables [concrete_category.{max v u} D]

local attribute [instance]
  concrete_category.has_coe_to_sort
  concrete_category.has_coe_to_fun

/-- A concrete version of the multiequalizer, to be used below. -/
@[nolint has_inhabited_instance]
def meq {X : C} (P : Cᵒᵖ ⥤ D) (S : J.cover X) :=
{ x : Π (I : S.arrow), P.obj (op I.Y) //
  ∀ (I : S.relation), P.map I.g₁.op (x I.fst) = P.map I.g₂.op (x I.snd) }
end

namespace meq

variables [concrete_category.{max v u} D]

local attribute [instance]
  concrete_category.has_coe_to_sort
  concrete_category.has_coe_to_fun

instance {X} (P : Cᵒᵖ ⥤ D) (S : J.cover X) : has_coe_to_fun (meq P S)
  (λ x, Π (I : S.arrow), P.obj (op I.Y)) := ⟨λ x, x.1⟩

@[ext]
lemma ext {X} {P : Cᵒᵖ ⥤ D} {S : J.cover X} (x y : meq P S)
  (h : ∀ I : S.arrow, x I = y I) : x = y := subtype.ext $ funext $ h

lemma condition {X} {P : Cᵒᵖ ⥤ D} {S : J.cover X} (x : meq P S) (I : S.relation) :
  P.map I.g₁.op (x ((S.index P).fst_to I)) = P.map I.g₂.op (x ((S.index P).snd_to I)) := x.2 _

/-- Refine a term of `meq P T` with respect to a refinement `S ⟶ T` of covers. -/
def refine {X : C} {P : Cᵒᵖ ⥤ D} {S T : J.cover X} (x : meq P T) (e : S ⟶ T) :
  meq P S :=
⟨λ I, x ⟨I.Y, I.f, (le_of_hom e) _ I.hf⟩,
  λ I, x.condition ⟨I.Y₁, I.Y₂, I.Z, I.g₁, I.g₂, I.f₁, I.f₂,
    (le_of_hom e) _ I.h₁, (le_of_hom e) _ I.h₂, I.w⟩⟩

@[simp]
lemma refine_apply {X : C} {P : Cᵒᵖ ⥤ D} {S T : J.cover X} (x : meq P T) (e : S ⟶ T)
  (I : S.arrow) : x.refine e I = x ⟨I.Y, I.f, (le_of_hom e) _ I.hf⟩ := rfl

/-- Pull back a term of `meq P S` with respect to a morphism `f : Y ⟶ X` in `C`. -/
def pullback {Y X : C} {P : Cᵒᵖ ⥤ D} {S : J.cover X} (x : meq P S) (f : Y ⟶ X) :
  meq P ((J.pullback f).obj S) :=
⟨λ I, x ⟨_,I.f ≫ f, I.hf⟩, λ I, x.condition
  ⟨I.Y₁, I.Y₂, I.Z, I.g₁, I.g₂, I.f₁ ≫ f, I.f₂ ≫ f, I.h₁, I.h₂, by simp [reassoc_of I.w]⟩ ⟩

@[simp]
lemma pullback_apply {Y X : C} {P : Cᵒᵖ ⥤ D} {S : J.cover X} (x : meq P S) (f : Y ⟶ X)
  (I : ((J.pullback f).obj S).arrow) : x.pullback f I = x ⟨_, I.f ≫ f, I.hf⟩ := rfl

@[simp]
lemma pullback_refine {Y X : C} {P : Cᵒᵖ ⥤ D} {S T : J.cover X} (h : S ⟶ T)
  (f : Y ⟶ X) (x : meq P T) : (x.pullback f).refine
  ((J.pullback f).map h) = (refine x h).pullback _ := rfl

/-- Make a term of `meq P S`. -/
def mk {X : C} {P : Cᵒᵖ ⥤ D} (S : J.cover X) (x : P.obj (op X)) : meq P S :=
⟨λ I, P.map I.f.op x, λ I, by { dsimp, simp only [← comp_apply, ← P.map_comp, ← op_comp, I.w] }⟩

lemma mk_apply {X : C} {P : Cᵒᵖ ⥤ D} (S : J.cover X) (x : P.obj (op X)) (I : S.arrow) :
  mk S x I = P.map I.f.op x := rfl

variable [preserves_limits (forget D)]

/-- The equivalence between the type associated to `multiequalizer (S.index P)` and `meq P S`. -/
noncomputable
def equiv {X : C} (P : Cᵒᵖ ⥤ D) (S : J.cover X) [has_multiequalizer (S.index P)] :
  (multiequalizer (S.index P) : D) ≃ meq P S :=
limits.concrete.multiequalizer_equiv _

@[simp]
lemma equiv_apply {X : C} {P : Cᵒᵖ ⥤ D} {S : J.cover X} [has_multiequalizer (S.index P)]
  (x : multiequalizer (S.index P)) (I : S.arrow) :
equiv P S x I = multiequalizer.ι (S.index P) I x := rfl

@[simp]
lemma equiv_symm_eq_apply {X : C} {P : Cᵒᵖ ⥤ D} {S : J.cover X} [has_multiequalizer (S.index P)]
  (x : meq P S) (I : S.arrow) : multiequalizer.ι (S.index P) I ((meq.equiv P S).symm x) = x I :=
begin
  let z := (meq.equiv P S).symm x,
  rw ← equiv_apply,
  simp,
end

end meq

namespace grothendieck_topology

namespace plus

variables [concrete_category.{max v u} D]

local attribute [instance]
  concrete_category.has_coe_to_sort
  concrete_category.has_coe_to_fun

variable [preserves_limits (forget D)]
variables [∀ (X : C), has_colimits_of_shape (J.cover X)ᵒᵖ D]
variables [∀ (P : Cᵒᵖ ⥤ D) (X : C) (S : J.cover X), has_multiequalizer (S.index P)]

noncomputable theory

/-- Make a term of `(J.plus_obj P).obj (op X)` from `x : meq P S`. -/
def mk {X : C} {P : Cᵒᵖ ⥤ D} {S : J.cover X} (x : meq P S) : (J.plus_obj P).obj (op X) :=
colimit.ι (J.diagram P X) (op S) ((meq.equiv P S).symm x)

lemma res_mk_eq_mk_pullback {Y X : C} {P : Cᵒᵖ ⥤ D} {S : J.cover X} (x : meq P S) (f : Y ⟶ X) :
  (J.plus_obj P).map f.op (mk x) = mk (x.pullback f) :=
begin
  dsimp [mk, plus_obj],
  simp only [← comp_apply, colimit.ι_pre, ι_colim_map_assoc],
  simp_rw [comp_apply],
  congr' 1,
  apply_fun meq.equiv P _,
  erw equiv.apply_symm_apply,
  ext i,
  simp only [diagram_pullback_app,
    meq.pullback_apply, meq.equiv_apply, ← comp_apply],
  erw [multiequalizer.lift_ι, meq.equiv_symm_eq_apply],
  cases i, refl,
end

lemma to_plus_mk {X : C} {P : Cᵒᵖ ⥤ D} (S : J.cover X) (x : P.obj (op X)) :
  (J.to_plus P).app _ x = mk (meq.mk S x) :=
begin
  dsimp [mk, to_plus],
  let e : S ⟶ ⊤ := hom_of_le (order_top.le_top _),
  rw ← colimit.w _ e.op,
  delta cover.to_multiequalizer,
  simp only [comp_apply],
  congr' 1,
  dsimp [diagram],
  apply concrete.multiequalizer_ext,
  intros i,
  simpa only [← comp_apply, category.assoc, multiequalizer.lift_ι,
    category.comp_id, meq.equiv_symm_eq_apply],
end

lemma to_plus_apply {X : C} {P : Cᵒᵖ ⥤ D} (S : J.cover X) (x : meq P S) (I : S.arrow) :
  (J.to_plus P).app _ (x I) = (J.plus_obj P).map I.f.op (mk x) :=
begin
  dsimp only [to_plus, plus_obj],
  delta cover.to_multiequalizer,
  dsimp [mk],
  simp only [← comp_apply, colimit.ι_pre, ι_colim_map_assoc],
  simp only [comp_apply],
  dsimp only [functor.op],
  let e : (J.pullback I.f).obj (unop (op S)) ⟶ ⊤ := hom_of_le (order_top.le_top _),
  rw ← colimit.w _ e.op,
  simp only [comp_apply],
  congr' 1,
  apply concrete.multiequalizer_ext,
  intros i,
  dsimp [diagram],
  simp only [← comp_apply, category.assoc, multiequalizer.lift_ι,
    category.comp_id, meq.equiv_symm_eq_apply],
  let RR : S.relation :=
    ⟨_, _, _, i.f, 𝟙 _, I.f, i.f ≫ I.f, I.hf, sieve.downward_closed _ I.hf _, by simp⟩,
  cases I,
  erw x.condition RR,
  simpa [RR],
end

lemma to_plus_eq_mk {X : C} {P : Cᵒᵖ ⥤ D} (x : P.obj (op X)) :
  (J.to_plus P).app _ x = mk (meq.mk ⊤ x) :=
begin
  dsimp [mk, to_plus],
  delta cover.to_multiequalizer,
  simp only [comp_apply],
  congr' 1,
  apply_fun (meq.equiv P ⊤),
  ext i,
  simpa,
end

variables [∀ (X : C), preserves_colimits_of_shape (J.cover X)ᵒᵖ (forget D)]

lemma exists_rep {X : C} {P : Cᵒᵖ ⥤ D} (x : (J.plus_obj P).obj (op X)) :
  ∃ (S : J.cover X) (y : meq P S), x = mk y :=
begin
  obtain ⟨S,y,h⟩ := concrete.colimit_exists_rep (J.diagram P X) x,
  use [S.unop, meq.equiv _ _ y],
  rw ← h,
  dsimp [mk],
  simp,
end

lemma eq_mk_iff_exists {X : C} {P : Cᵒᵖ ⥤ D} {S T : J.cover X}
  (x : meq P S) (y : meq P T) : mk x = mk y ↔ (∃ (W : J.cover X) (h1 : W ⟶ S) (h2 : W ⟶ T),
    x.refine h1 = y.refine h2) :=
begin
  split,
  { intros h,
    obtain ⟨W, h1, h2, hh⟩ := concrete.colimit_exists_of_rep_eq _ _ _ h,
    use [W.unop, h1.unop, h2.unop],
    ext I,
    apply_fun (multiequalizer.ι (W.unop.index P) I) at hh,
    convert hh,
    all_goals
    { dsimp [diagram],
      simp only [← comp_apply, multiequalizer.lift_ι, category.comp_id, meq.equiv_symm_eq_apply],
      cases I, refl } },
  { rintros ⟨S,h1,h2,e⟩,
    apply concrete.colimit_rep_eq_of_exists,
    use [(op S), h1.op, h2.op],
    apply concrete.multiequalizer_ext,
    intros i,
    apply_fun (λ ee, ee i) at e,
    convert e,
    all_goals
    { dsimp [diagram],
      simp only [← comp_apply, multiequalizer.lift_ι, meq.equiv_symm_eq_apply],
      cases i, refl } },
end

/-- `P⁺` is always separated. -/
theorem sep {X : C} (P : Cᵒᵖ ⥤ D) (S : J.cover X) (x y : (J.plus_obj P).obj (op X))
  (h : ∀ (I : S.arrow), (J.plus_obj P).map I.f.op x = (J.plus_obj P).map I.f.op y) :
  x = y :=
begin
  -- First, we choose representatives for x and y.
  obtain ⟨Sx,x,rfl⟩ := exists_rep x,
  obtain ⟨Sy,y,rfl⟩ := exists_rep y,
  simp only [res_mk_eq_mk_pullback] at h,

  -- Next, using our assumption,
  -- choose covers over which the pullbacks of these representatives become equal.
  choose W h1 h2 hh using λ (I : S.arrow), (eq_mk_iff_exists _ _).mp (h I),

  -- To prove equality, it suffices to prove that there exists a cover over which
  -- the representatives become equal.
  rw eq_mk_iff_exists,

  -- Construct the cover over which the representatives become equal by combining the various
  -- covers chosen above.
  let B : J.cover X := S.bind W,
  use B,

  -- Prove that this cover refines the two covers over which our representatives are defined
  -- and use these proofs.
  let ex : B ⟶ Sx := hom_of_le begin
    rintros Y f ⟨Z,e1,e2,he2,he1,hee⟩,
    rw ← hee,
    apply le_of_hom (h1 ⟨_, _, he2⟩),
    exact he1,
  end,
  let ey : B ⟶ Sy := hom_of_le begin
    rintros Y f ⟨Z,e1,e2,he2,he1,hee⟩,
    rw ← hee,
    apply le_of_hom (h2 ⟨_, _, he2⟩),
    exact he1,
  end,
  use [ex, ey],

  -- Now prove that indeed the representatives become equal over `B`.
  -- This will follow by using the fact that our representatives become
  -- equal over the chosen covers.
  ext1 I,
  let IS : S.arrow := I.from_middle,
  specialize hh IS,
  let IW : (W IS).arrow := I.to_middle,
  apply_fun (λ e, e IW) at hh,
  convert hh,
  { let Rx : Sx.relation := ⟨I.Y, I.Y, I.Y, 𝟙 _, 𝟙 _, I.f,
      I.to_middle_hom ≫ I.from_middle_hom, _, _, by simp [I.middle_spec]⟩,
    have := x.condition Rx,
    simpa using this },
  { let Ry : Sy.relation := ⟨I.Y, I.Y, I.Y, 𝟙 _, 𝟙 _, I.f,
      I.to_middle_hom ≫ I.from_middle_hom, _, _, by simp [I.middle_spec]⟩,
    have := y.condition Ry,
    simpa using this },
end

lemma inj_of_sep (P : Cᵒᵖ ⥤ D) (hsep : ∀ (X : C) (S : J.cover X) (x y : P.obj (op X)),
  (∀ I : S.arrow, P.map I.f.op x = P.map I.f.op y) → x = y) (X : C) :
  function.injective ((J.to_plus P).app (op X)) :=
begin
  intros x y h,
  simp only [to_plus_eq_mk] at h,
  rw eq_mk_iff_exists at h,
  obtain ⟨W, h1, h2, hh⟩ := h,
  apply hsep X W,
  intros I,
  apply_fun (λ e, e I) at hh,
  exact hh
end

/-- An auxiliary definition to be used in the proof of `exists_of_sep` below.
  Given a compatible family of local sections for `P⁺`, and representatives of said sections,
  construct a compatible family of local sections of `P` over the combination of the covers
  associated to the representatives.
  The separatedness condition is used to prove compatibility among these local sections of `P`. -/
def meq_of_sep (P : Cᵒᵖ ⥤ D)
  (hsep : ∀ (X : C) (S : J.cover X) (x y : P.obj (op X)),
    (∀ I : S.arrow, P.map I.f.op x = P.map I.f.op y) → x = y)
  (X : C) (S : J.cover X)
  (s : meq (J.plus_obj P) S)
  (T : Π (I : S.arrow), J.cover I.Y)
  (t : Π (I : S.arrow), meq P (T I))
  (ht : ∀ (I : S.arrow), s I = mk (t I)) : meq P (S.bind T) :=
{ val := λ I, t I.from_middle I.to_middle,
  property := begin
    intros II,
    apply inj_of_sep P hsep,
    rw [← comp_apply, ← comp_apply, (J.to_plus P).naturality, (J.to_plus P).naturality,
      comp_apply, comp_apply],
    erw [to_plus_apply (T II.fst.from_middle) (t II.fst.from_middle) II.fst.to_middle,
         to_plus_apply (T II.snd.from_middle) (t II.snd.from_middle) II.snd.to_middle,
         ← ht, ← ht, ← comp_apply, ← comp_apply, ← (J.plus_obj P).map_comp,
         ← (J.plus_obj P).map_comp],
    rw [← op_comp, ← op_comp],
    let IR : S.relation :=
      ⟨_, _, _, II.g₁ ≫ II.fst.to_middle_hom, II.g₂ ≫ II.snd.to_middle_hom,
        II.fst.from_middle_hom, II.snd.from_middle_hom, II.fst.from_middle_condition,
        II.snd.from_middle_condition, _⟩,
    swap, { simp only [category.assoc, II.fst.middle_spec, II.snd.middle_spec], apply II.w },
    exact s.condition IR,
  end }

theorem exists_of_sep (P : Cᵒᵖ ⥤ D)
  (hsep : ∀ (X : C) (S : J.cover X) (x y : P.obj (op X)),
    (∀ I : S.arrow, P.map I.f.op x = P.map I.f.op y) → x = y)
  (X : C) (S : J.cover X)
  (s : meq (J.plus_obj P) S) :
  ∃ t : (J.plus_obj P).obj (op X), meq.mk S t = s :=
begin
  have inj : ∀ (X : C), function.injective ((J.to_plus P).app (op X)) := inj_of_sep _ hsep,

  -- Choose representatives for the given local sections.
  choose T t ht using λ I, exists_rep (s I),

  -- Construct a large cover over which we will define a representative that will
  -- provide the gluing of the given local sections.
  let B : J.cover X := S.bind T,
  choose Z e1 e2 he2 he1 hee using λ I : B.arrow, I.hf,

  -- Construct a compatible system of local sections over this large cover, using the chosen
  -- representatives of our local sections.
  -- The compatilibity here follows from the separatedness assumption.
  let w : meq P B := meq_of_sep P hsep X S s T t ht,

  -- The associated gluing will be the candidate section.
  use mk w,
  ext I,
  erw [ht, res_mk_eq_mk_pullback],

  -- Use the separatedness of `P⁺` to prove that this is indeed a gluing of our
  -- original local sections.
  apply sep P (T I),
  intros II,
  simp only [res_mk_eq_mk_pullback, eq_mk_iff_exists],

  -- It suffices to prove equality for representatives over a
  -- convenient sufficiently large cover...
  use (J.pullback II.f).obj (T I),
  let e0 : (J.pullback II.f).obj (T I) ⟶ (J.pullback II.f).obj ((J.pullback I.f).obj B) :=
    hom_of_le begin
      intros Y f hf,
      apply sieve.le_pullback_bind _ _ _ I.hf,
      { cases I,
        exact hf },
    end,
  use [e0, 𝟙 _],
  ext IV,
  dsimp only [meq.refine_apply, meq.pullback_apply, w],
  let IA : B.arrow := ⟨_, (IV.f ≫ II.f) ≫ I.f, _⟩,
  swap,
  { refine ⟨I.Y, _, _, I.hf, _, rfl⟩,
    apply sieve.downward_closed,
    convert II.hf,
    cases I, refl },
  let IB : S.arrow := IA.from_middle,
  let IC : (T IB).arrow := IA.to_middle,
  let ID : (T I).arrow := ⟨IV.Y, IV.f ≫ II.f, sieve.downward_closed (T I) II.hf IV.f⟩,
  change t IB IC = t I ID,
  apply inj IV.Y,
  erw [to_plus_apply (T I) (t I) ID, to_plus_apply (T IB) (t IB) IC, ← ht, ← ht],

  -- Conclude by constructing the relation showing equality...
  let IR : S.relation := ⟨_, _, IV.Y, IC.f, ID.f, IB.f, I.f, _, I.hf, IA.middle_spec⟩,
  convert s.condition IR,
  cases I, refl,
end

variable [reflects_isomorphisms (forget D)]

/-- If `P` is separated, then `P⁺` is a sheaf. -/
theorem is_sheaf_of_sep (P : Cᵒᵖ ⥤ D)
  (hsep : ∀ (X : C) (S : J.cover X) (x y : P.obj (op X)),
    (∀ I : S.arrow, P.map I.f.op x = P.map I.f.op y) → x = y) :
  presheaf.is_sheaf J (J.plus_obj P) :=
begin
  rw presheaf.is_sheaf_iff_multiequalizer,
  intros X S,
  apply is_iso_of_reflects_iso _ (forget D),
  rw is_iso_iff_bijective,
  split,
  { intros x y h,
    apply sep P S _ _,
    intros I,
    apply_fun (meq.equiv _ _) at h,
    apply_fun (λ e, e I) at h,
    convert h,
    { erw [meq.equiv_apply, ← comp_apply, multiequalizer.lift_ι] },
    { erw [meq.equiv_apply, ← comp_apply, multiequalizer.lift_ι] } },
  { rintros (x : (multiequalizer (S.index _) : D)),
    obtain ⟨t,ht⟩ := exists_of_sep P hsep X S (meq.equiv _ _ x),
    use t,
    apply_fun meq.equiv _ _,
    swap, { apply_instance },
    rw ← ht,
    ext i,
    dsimp,
    rw [← comp_apply, multiequalizer.lift_ι],
    refl }
end

variable (J)

/-- `P⁺⁺` is always a sheaf. -/
theorem is_sheaf_plus_plus (P : Cᵒᵖ ⥤ D) :
  presheaf.is_sheaf J (J.plus_obj (J.plus_obj P)) :=
begin
  apply is_sheaf_of_sep,
  intros X S x y,
  apply sep,
end

end plus

variables (J)
variables
  [∀ (P : Cᵒᵖ ⥤ D) (X : C) (S : J.cover X), has_multiequalizer (S.index P)]
  [∀ (X : C), has_colimits_of_shape (J.cover X)ᵒᵖ D]

/-- The sheafification of a presheaf `P`.
*NOTE:* Additional hypotheses are needed to obtain a proof that this is a sheaf! -/
def sheafify (P : Cᵒᵖ ⥤ D) : Cᵒᵖ ⥤ D := J.plus_obj (J.plus_obj P)

/-- The canonical map from `P` to its sheafification. -/
def to_sheafify (P : Cᵒᵖ ⥤ D) : P ⟶ J.sheafify P :=
J.to_plus P ≫ J.plus_map (J.to_plus P)

/-- The canonical map on sheafifications induced by a morphism. -/
def sheafify_map {P Q : Cᵒᵖ ⥤ D} (η : P ⟶ Q) : J.sheafify P ⟶ J.sheafify Q :=
J.plus_map $ J.plus_map η

@[simp]
lemma sheafify_map_id (P : Cᵒᵖ ⥤ D) : J.sheafify_map (𝟙 P) = 𝟙 (J.sheafify P) :=
by { dsimp [sheafify_map, sheafify], simp }

@[simp]
lemma sheafify_map_comp {P Q R : Cᵒᵖ ⥤ D} (η : P ⟶ Q) (γ : Q ⟶ R) :
  J.sheafify_map (η ≫ γ) = J.sheafify_map η ≫ J.sheafify_map γ :=
by { dsimp [sheafify_map, sheafify], simp }

@[simp, reassoc]
lemma to_sheafify_naturality {P Q : Cᵒᵖ ⥤ D} (η : P ⟶ Q) :
  η ≫ J.to_sheafify _ = J.to_sheafify _ ≫ J.sheafify_map η :=
by { dsimp [sheafify_map, sheafify, to_sheafify], simp }

variable (D)

/-- The sheafification of a presheaf `P`, as a functor.
*NOTE:* Additional hypotheses are needed to obtain a proof that this is a sheaf! -/
def sheafification : (Cᵒᵖ ⥤ D) ⥤ Cᵒᵖ ⥤ D := (J.plus_functor D ⋙ J.plus_functor D)

@[simp]
lemma sheafification_obj (P : Cᵒᵖ ⥤ D) : (J.sheafification D).obj P = J.sheafify P := rfl

@[simp]
lemma sheafification_map {P Q : Cᵒᵖ ⥤ D} (η : P ⟶ Q) : (J.sheafification D).map η =
  J.sheafify_map η := rfl

/-- The canonical map from `P` to its sheafification, as a natural transformation.
*Note:* We only show this is a sheaf under additional hypotheses on `D`. -/
def to_sheafification : 𝟭 _ ⟶ sheafification J D :=
J.to_plus_nat_trans D ≫ whisker_right (J.to_plus_nat_trans D) (J.plus_functor D)

@[simp]
lemma to_sheafification_app (P : Cᵒᵖ ⥤ D) : (J.to_sheafification D).app P = J.to_sheafify P := rfl

variable {D}

lemma is_iso_to_sheafify {P : Cᵒᵖ ⥤ D} (hP : presheaf.is_sheaf J P) :
  is_iso (J.to_sheafify P) :=
begin
  dsimp [to_sheafify],
  haveI : is_iso (J.to_plus P) := by { apply is_iso_to_plus_of_is_sheaf J P hP },
  haveI : is_iso ((J.plus_functor D).map (J.to_plus P)) := by { apply functor.map_is_iso },
  exact @is_iso.comp_is_iso _ _ _ _ _ (J.to_plus P)
    ((J.plus_functor D).map (J.to_plus P)) _ _,
end

/-- If `P` is a sheaf, then `P` is isomorphic to `J.sheafify P`. -/
def iso_sheafify {P : Cᵒᵖ ⥤ D} (hP : presheaf.is_sheaf J P) :
  P ≅ J.sheafify P :=
by letI := is_iso_to_sheafify J hP; exactI as_iso (J.to_sheafify P)

@[simp]
lemma iso_sheafify_hom {P : Cᵒᵖ ⥤ D} (hP : presheaf.is_sheaf J P) :
  (J.iso_sheafify hP).hom = J.to_sheafify P := rfl

/-- Given a sheaf `Q` and a morphism `P ⟶ Q`, construct a morphism from
`J.sheafifcation P` to `Q`. -/
def sheafify_lift {P Q : Cᵒᵖ ⥤ D} (η : P ⟶ Q) (hQ : presheaf.is_sheaf J Q) :
  J.sheafify P ⟶ Q := J.plus_lift (J.plus_lift η hQ) hQ

lemma to_sheafify_sheafify_lift {P Q : Cᵒᵖ ⥤ D} (η : P ⟶ Q) (hQ : presheaf.is_sheaf J Q) :
  J.to_sheafify P ≫ sheafify_lift J η hQ = η :=
by { dsimp only [sheafify_lift, to_sheafify], simp }

lemma sheafify_lift_unique {P Q : Cᵒᵖ ⥤ D} (η : P ⟶ Q) (hQ : presheaf.is_sheaf J Q)
  (γ : J.sheafify P ⟶ Q) :
  J.to_sheafify P ≫ γ = η → γ = sheafify_lift J η hQ :=
begin
  intros h,
  apply plus_lift_unique,
  apply plus_lift_unique,
  rw [← category.assoc, ← plus_map_to_plus],
  exact h,
end

@[simp]
lemma iso_sheafify_inv {P : Cᵒᵖ ⥤ D} (hP : presheaf.is_sheaf J P) :
  (J.iso_sheafify hP).inv = J.sheafify_lift (𝟙 _) hP :=
begin
  apply J.sheafify_lift_unique,
  simp [iso.comp_inv_eq],
end

lemma sheafify_hom_ext {P Q : Cᵒᵖ ⥤ D} (η γ : J.sheafify P ⟶ Q) (hQ : presheaf.is_sheaf J Q)
  (h : J.to_sheafify P ≫ η = J.to_sheafify P ≫ γ) : η = γ :=
begin
  apply J.plus_hom_ext _ _ hQ,
  apply J.plus_hom_ext _ _ hQ,
  rw [← category.assoc, ← category.assoc, ← plus_map_to_plus],
  exact h,
end

lemma sheafify_map_sheafify_lift {P Q R : Cᵒᵖ ⥤ D} (η : P ⟶ Q) (γ : Q ⟶ R)
  (hR : presheaf.is_sheaf J R) :
  J.sheafify_map η ≫ J.sheafify_lift γ hR = J.sheafify_lift (η ≫ γ) hR :=
begin
  apply J.sheafify_lift_unique,
  rw [← category.assoc, ← J.to_sheafify_naturality,
    category.assoc, to_sheafify_sheafify_lift],
end

end grothendieck_topology

variables (J)
variables
  [concrete_category.{max v u} D]
  [preserves_limits (forget D)]
  [∀ (P : Cᵒᵖ ⥤ D) (X : C) (S : J.cover X), has_multiequalizer (S.index P)]
  [∀ (X : C), has_colimits_of_shape (J.cover X)ᵒᵖ D]
  [∀ (X : C), preserves_colimits_of_shape (J.cover X)ᵒᵖ (forget D)]
  [reflects_isomorphisms (forget D)]

lemma grothendieck_topology.sheafify_is_sheaf (P : Cᵒᵖ ⥤ D) :
  presheaf.is_sheaf J (J.sheafify P) :=
grothendieck_topology.plus.is_sheaf_plus_plus _ _

variables (D)

/-- The sheafification functor, as a functor taking values in `Sheaf`. -/
@[simps obj map]
def presheaf_to_Sheaf : (Cᵒᵖ ⥤ D) ⥤ Sheaf J D :=
{ obj := λ P, ⟨J.sheafify P, J.sheafify_is_sheaf P⟩,
  map := λ P Q η, J.sheafify_map η,
  map_id' := J.sheafify_map_id,
  map_comp' := λ P Q R, J.sheafify_map_comp }

/-- The sheafification functor is left adjoint to the forgetful functor. -/
@[simps hom_equiv_apply hom_equiv_symm_apply counit_app]
def sheafification_adjunction : presheaf_to_Sheaf J D ⊣ Sheaf_to_presheaf J D :=
adjunction.mk_of_hom_equiv
{ hom_equiv := λ P Q,
  { to_fun := λ e, J.to_sheafify P ≫ e,
    inv_fun := λ e, J.sheafify_lift e Q.2,
    left_inv := λ e, (J.sheafify_lift_unique _ _ _ rfl).symm,
    right_inv := λ e, J.to_sheafify_sheafify_lift _ _ },
  hom_equiv_naturality_left_symm' := begin
    intros P Q R η γ, dsimp, symmetry,
    apply J.sheafify_map_sheafify_lift,
  end,
  hom_equiv_naturality_right' := λ P Q R η γ, by { dsimp, rw category.assoc, refl } }

@[simp]
lemma sheafification_adjunction_unit_app (P) :
  (sheafification_adjunction J D).unit.app P = J.to_sheafify P :=
begin
  ext,
  dsimp [sheafification_adjunction],
  simp,
end

variables {J D}
/-- A sheaf `P` is isomorphic to its own sheafification. -/
def sheafification_iso (P : Sheaf J D) :
  P ≅ (presheaf_to_Sheaf J D).obj P :=
{ hom := (J.iso_sheafify P.2).hom,
  inv := (J.iso_sheafify P.2).inv,
  hom_inv_id' := (J.iso_sheafify P.2).hom_inv_id,
  inv_hom_id' := (J.iso_sheafify P.2).inv_hom_id }

@[simp]
lemma sheafification_iso_hom (P : Sheaf J D) :
  (sheafification_iso P).hom = J.to_sheafify P := rfl

@[simp]
lemma sheafification_iso_inv (P : Sheaf J D) :
  (sheafification_iso P).inv = J.sheafify_lift (𝟙 P) P.property :=
by { dsimp [sheafification_iso], simp }

instance is_iso_sheafification_adjunction_counit (P : Sheaf J D) :
  is_iso ((sheafification_adjunction J D).counit.app P) :=
begin
  dsimp [sheafification_adjunction],
  rw ← sheafification_iso_inv,
  apply_instance
end

instance sheafification_reflective : is_iso (sheafification_adjunction J D).counit :=
nat_iso.is_iso_of_is_iso_app _

end category_theory


import tactic
import utilities.written_by_others.list_take_join

section tactic_in_list_explicit

meta def in_list_explicit : tactic unit := `[
  tactic.repeat `[
    tactic.try `[apply list.mem_cons_self],
    tactic.try `[apply list.mem_cons_of_mem]
  ]
]

meta def split_ile : tactic unit := `[
  split,
  {
    in_list_explicit,
  }
]

end tactic_in_list_explicit

namespace list

variables {α β : Type*} {x y z : list α}

section list_append_append

lemma length_append_append :
  list.length (x ++ y ++ z) = x.length + y.length + z.length :=
by rw [list.length_append, list.length_append]

lemma map_append_append {f : α → β} :
  list.map f (x ++ y ++ z) = list.map f x ++ list.map f y ++ list.map f z :=
by rw [list.map_append, list.map_append]

lemma filter_map_append_append {f : α → option β} :
  list.filter_map f (x ++ y ++ z) = list.filter_map f x ++ list.filter_map f y ++ list.filter_map f z :=
by rw [list.filter_map_append, list.filter_map_append]

lemma reverse_append_append :
  list.reverse (x ++ y ++ z) = z.reverse ++ y.reverse ++ x.reverse :=
by rw [list.reverse_append, list.reverse_append, list.append_assoc]

lemma mem_append_append {a : α} :
  a ∈ x ++ y ++ z  ↔  a ∈ x ∨ a ∈ y ∨ a ∈ z  :=
by rw [list.mem_append, list.mem_append, or_assoc]

lemma forall_mem_append_append {p : α → Prop} :
  (∀ a ∈ x ++ y ++ z, p a)  ↔  (∀ a ∈ x, p a) ∧ (∀ a ∈ y, p a) ∧ (∀ a ∈ z, p a)  :=
by rw [list.forall_mem_append, list.forall_mem_append, and_assoc]

lemma join_append_append {X Y Z : list (list α)} :
  list.join (X ++ Y ++ Z) = X.join ++ Y.join ++ Z.join :=
by rw [list.join_append, list.join_append]

end list_append_append

section list_repeat

lemma repeat_zero (s : α) :
  list.repeat s 0 = [] :=
rfl

lemma repeat_succ_eq_singleton_append (s : α) (n : ℕ) :
  list.repeat s n.succ = [s] ++ list.repeat s n :=
rfl

lemma repeat_succ_eq_append_singleton (s : α) (n : ℕ) :
  list.repeat s n.succ = list.repeat s n ++ [s] :=
begin
  change list.repeat s (n + 1) = list.repeat s n ++ [s],
  rw list.repeat_add,
  refl,
end

end list_repeat

section list_join

lemma join_singleton : [x].join = x :=
by rw [list.join, list.join, list.append_nil]

lemma append_join_append (L : list (list α)) :
  x ++ (list.map (λ l, l ++ x) L).join = (list.map (λ l, x ++ l) L).join ++ x :=
begin
  induction L,
  {
    rw [list.map_nil, list.join, list.append_nil, list.map_nil, list.join, list.nil_append],
  },
  {
    rw [
      list.map_cons, list.join, list.map_cons, list.join, list.append_assoc, L_ih,
      list.append_assoc, list.append_assoc
    ],
  },
end

lemma reverse_join (L : list (list α)) :
  L.join.reverse = (list.map list.reverse L).reverse.join :=
begin
  induction L,
  {
    refl,
  },
  {
    rw [
      list.join, list.reverse_append, L_ih,
      list.map_cons, list.reverse_cons, list.join_append, list.join_singleton
    ],
  },
end

private lemma cons_drop_succ {m : ℕ} (mlt : m < x.length) :
  drop m x = x.nth_le m mlt :: drop m.succ x :=
begin
  induction x with d l ih generalizing m,
  {
    exfalso,
    rw length at mlt,
    exact nat.not_lt_zero m mlt,
  },
  cases m,
  {
    simp,
  },
  rw [drop, drop, nth_le],
  apply ih,
end

lemma drop_join_of_lt {L : list (list α)} {n : ℕ} (notall : n < L.join.length) :
  ∃ m k : ℕ, ∃ mlt : m < L.length, k < (L.nth_le m mlt).length ∧
    L.join.drop n = (L.nth_le m mlt).drop k ++ (L.drop m.succ).join :=
begin
  obtain ⟨m, k, mlt, klt, left_half⟩ := take_join_of_lt notall,
  use    [m, k, mlt, klt],
  have L_two_parts := congr_arg list.join (list.take_append_drop m L),
  rw list.join_append at L_two_parts,
  have whole := list.take_append_drop n L.join,
  rw left_half at whole,
  have important := eq.trans whole L_two_parts.symm,
  rw append_assoc at important,
  have right_side := append_left_cancel important,
  have auxi : (drop m L).join = (L.nth_le m mlt :: drop m.succ L).join,
  {
    apply congr_arg,
    apply cons_drop_succ,
  },
  rw join at auxi,
  rw auxi at right_side,
  have near_result :
    take k (L.nth_le m mlt) ++ drop n L.join =
    take k (L.nth_le m mlt) ++ drop k (L.nth_le m mlt) ++ (drop m.succ L).join,
  {
    convert right_side,
    rw list.take_append_drop,
  },
  rw append_assoc at near_result,
  exact append_left_cancel near_result,
end

def n_times (l : list α) (n : ℕ) : list α :=
(list.repeat l n).join

infix ` ^ ` : 100 := n_times

end list_join

variables [decidable_eq α]

section list_index_of

lemma index_of_append_of_in {a : α} (yes_on_left : a ∈ x) :
  index_of a (x ++ y)  =  index_of a x  :=
begin
  induction x with d l ih,
  {
    exfalso,
    exact not_mem_nil a yes_on_left,
  },
  rw list.cons_append,
  by_cases ad : a = d,
  {
    rw index_of_cons_eq _ ad,
    rw index_of_cons_eq _ ad,
  },
  rw index_of_cons_ne _ ad,
  rw index_of_cons_ne _ ad,
  apply congr_arg,
  apply ih,
  exact mem_of_ne_of_mem ad yes_on_left,
end

lemma index_of_append_of_notin {a : α} (not_on_left : a ∉ x) :
  index_of a (x ++ y)  =  x.length  +  index_of a y  :=
begin
  induction x with d l ih,
  {
    rw [list.nil_append, list.length, zero_add],
  },
  rw [
    list.cons_append,
    index_of_cons_ne _ (ne_of_not_mem_cons not_on_left),
    list.length,
    ih (not_mem_of_not_mem_cons not_on_left),
    nat.succ_add
  ],
end

end list_index_of

section list_count_in

def count_in (l : list α) (a : α) : ℕ :=
list.sum (list.map (λ s, ite (s = a) 1 0) l)

lemma count_in_nil (a : α) :
  count_in [] a = 0 :=
begin
  refl,
end

lemma count_in_cons (a b : α) :
  count_in (b :: x) a  =  ite (b = a) 1 0  +  count_in x a  :=
begin
  unfold count_in,
  rw list.map_cons,
  rw list.sum_cons,
end

lemma count_in_append (a : α) :
  count_in (x ++ y) a  =  count_in x a  +  count_in y a  :=
begin
  unfold count_in,
  rw list.map_append,
  rw list.sum_append,
end

lemma count_in_repeat_eq (a : α) (n : ℕ) :
  count_in (list.repeat a n) a  =  n  :=
begin
  unfold count_in,
  induction n with m ih,
  {
    refl,
  },
  rw [list.repeat_succ, list.map_cons, list.sum_cons, ih],
  rw if_pos rfl,
  apply nat.one_add,
end

lemma count_in_repeat_neq {a b : α} (hyp : a ≠ b) (n : ℕ) :
  count_in (list.repeat a n) b  =  0  :=
begin
  unfold count_in,
  induction n with m ih,
  {
    refl,
  },
  rw [list.repeat_succ, list.map_cons, list.sum_cons, ih, add_zero],
  rw ite_eq_right_iff,
  intro impos,
  exfalso,
  exact hyp impos,
end

lemma count_in_singleton_eq (a : α) :
  count_in [a] a  =  1  :=
begin
  exact list.count_in_repeat_eq a 1,
end

lemma count_in_singleton_neq {a b : α} (hyp : a ≠ b) :
  count_in [a] b  =  0  :=
begin
  exact list.count_in_repeat_neq hyp 1,
end

lemma count_in_pos_of_in {a : α} (hyp : a ∈ x) :
  count_in x a > 0 :=
begin
  induction x with d l ih,
  {
    exfalso,
    rw list.mem_nil_iff at hyp,
    exact hyp,
  },
  by_contradiction contr,
  rw not_lt at contr,
  rw nat.le_zero_iff at contr,
  rw list.mem_cons_eq at hyp,
  unfold count_in at contr,
  unfold list.map at contr,
  simp at contr,
  cases hyp,
  {
    exact contr.left hyp.symm,
  },
  specialize ih hyp,
  have zero_in_tail : count_in l a = 0,
  {
    unfold count_in,
    exact contr.right,
  },
  rw zero_in_tail at ih,
  exact nat.lt_irrefl 0 ih,
end

lemma count_in_zero_of_notin {a : α} (hyp : a ∉ x) :
  count_in x a = 0 :=
begin
  induction x with d l ih,
  {
    refl,
  },
  unfold count_in,
  rw [list.map_cons, list.sum_cons, add_eq_zero_iff, ite_eq_right_iff],
  split,
  {
    simp only [nat.one_ne_zero],
    exact (list.ne_of_not_mem_cons hyp).symm,
  },
  {
    exact ih (list.not_mem_of_not_mem_cons hyp),
  },
end

lemma count_in_join (L : list (list α)) (a : α) :
  count_in L.join a = list.sum (list.map (λ w, count_in w a) L) :=
begin
  induction L,
  {
    refl,
  },
  {
    rw [list.join, list.count_in_append, list.map, list.sum_cons, L_ih],
  },
end

end list_count_in

end list