import for_mathlib.boolean_subalgebra
import for_mathlib.closure
import for_mathlib.order
import o_minimal.order
import o_minimal.tame.basic

/- Definition of an *o-minimal* structure. -/

namespace o_minimal

set_option old_structure_cmd true   -- TODO: Is this needed?

variables {R : Type*} [DUNLO R]

lemma interval_or_point.def_set {S : struc R} [definable_constants S] [is_definable_le S R] :
  ∀ {i : set R}, interval_or_point i → def_set S i
| _ (interval_or_point.pt _) := def_val_const
| _ interval_or_point.Iii := def_set_univ _
| _ (interval_or_point.Ioi a) := def_set.Ioi a
| _ (interval_or_point.Iio b) := def_set.Iio b
| _ (interval_or_point.Ioo a b _) := def_set.Ioo a b

/-- Tame sets are definable. -/
lemma tame.def_set {S : struc R} [definable_constants S] [is_definable_le S R]
  {s : set R} (h : tame s) : def_set S s :=
tame.induction (def_set_empty _) (λ s _ i ih, i.def_set.union ih) h

/-- An o-minimal structure is one in which every definable subset of R is tame. -/
def is_o_minimal {S : struc R} [definable_constants S] [is_definable_le S R] : Prop := ∀ (s : set R), def_set S s → tame s

/-- An o-minimal structure on a DUNLO `R` is a structure `S` for which:
1. `S` has definable constants.
2. The `≤` relation on `R` is definable in `S`.
3. Every definable subset of `R` is tame (a union of points and open intervals).

Below we verify that this definition agrees with the one of [vdD].
-/
class o_minimal (S : struc R) extends definable_constants S, is_definable_le S R : Prop :=
(tame_of_def : ∀ {s : set R}, def_set S s → tame s)

/-- Version of `o_minimal.tame_of_def` with explicit structure argument.
This is useful because otherwise `S` can only be inferred from the proof of definability,
which we might want to produce automatically. -/
lemma tame_of_def (S : struc R) [o_minimal S] {s : set R} : def_set S s → tame s :=
o_minimal.tame_of_def

/-- Our definition of an o-minimal structure is equivalent to the one in [vdD:1.3.2]. -/
lemma o_minimal_iff_vdD (S : struc R) : o_minimal S ↔
  (def_set S {p : R × R | p.1 < p.2} ∧ ∀ (s : set R), def_set S s ↔ tame s) :=
⟨λ h, by exactI ⟨definable_lt', λ s, ⟨o_minimal.tame_of_def, tame.def_set⟩⟩,
 λ ⟨h₁, h₂⟩,
 { definable_val := λ r, (h₂ _).mpr (tame_single (interval_or_point.pt r)),
   definable_le' := (is_definable_le_of_definable_lt h₁).1,
   tame_of_def := λ s, (h₂ s).mp }⟩

-- TODO: for_mathlib
lemma function.const_injective {α β : Type*} [H : nonempty α] :
  function.injective (function.const α : β → α → β) :=
let ⟨a⟩ := H in λ b₁ b₂ h, congr_fun h a

/-- An alternate constructor expressed in terms of low-level definability. -/
lemma o_minimal.mk' (S : struc R)
  (definable_lt : S.definable {x : finvec 2 R | x 0 < x 1})
  (definable_const : ∀ (r : R), S.definable {x : finvec 1 R | x 0 = r})
  (tame_of_definable :
    ∀ (s : set (finvec 1 R)), S.definable s → tame {r | (λ _, r : finvec 1 R) ∈ s}) :
  o_minimal S :=
{ definable_val := begin
    intro r,
    unfold def_val def_set,
    convert definable_const r,
    apply set.ext,
    simp_rw [set.mem_image],
    change ∀ (x : fin 1 → R), _ ↔ x 0 = r,
    rw (equiv.fun_unique (fin 1) R).forall_congr_left',
    intro r',
    simp only [equiv.fun_unique, equiv.coe_fn_symm_mk, set.mem_singleton_iff,
      exists_eq_left, has_coordinates.self_coords, function.const_injective.eq_iff],
    exact eq_comm
  end,
  definable_le' := begin
    refine (is_definable_le_of_definable_lt _).1,
    unfold def_val def_set,
    convert definable_lt,
    apply set.ext,
    simp_rw [set.mem_image],
    change ∀ (x : finvec 2 R), _ ↔ x 0 < x 1,
    rw finvec.finvec_two_equiv_prod_self.forall_congr_left',
    rintro ⟨x₀, x₁⟩,
    change _ ↔ x₀ < x₁,
    simp [has_coordinates.prod_coords, finvec.finvec_two_equiv_prod_self,
      finvec.append.inj_iff, function.const_injective.eq_iff, ←and_assoc]
  end,
  tame_of_def := begin
    intros s hs,
    convert tame_of_definable _ hs,
    ext x,
    simp [function.const_injective.eq_iff]
  end }

end o_minimal


import classes.context_sensitive.basics.inclusion
import classes.unrestricted.closure_properties.concatenation

variables {T : Type}

private def wrap_CS_rule₁ {N₁ : Type} (N₂ : Type) (r : csrule T N₁) :
  csrule T (nnn T N₁ N₂) :=
csrule.mk
  (list.map (wrap_symbol₁ N₂) r.context_left)
  (sum.inl (some (sum.inl r.input_nonterminal)))
  (list.map (wrap_symbol₁ N₂) r.context_right)
  (list.map (wrap_symbol₁ N₂) r.output_string)

private def wrap_CS_rule₂ {N₂ : Type} (N₁ : Type) (r : csrule T N₂) :
  csrule T (nnn T N₁ N₂) :=
csrule.mk
  (list.map (wrap_symbol₂ N₁) r.context_left)
  (sum.inl (some (sum.inr r.input_nonterminal)))
  (list.map (wrap_symbol₂ N₁) r.context_right)
  (list.map (wrap_symbol₂ N₁) r.output_string)

private def CS_rules_for_terminals₁ (N₂ : Type) (g : CS_grammar T) :
  list (csrule T (nnn T g.nt N₂)) :=
list.map (λ t, csrule.mk [] (sum.inr (sum.inl t)) [] [symbol.terminal t]) (all_used_terminals (grammar_of_csg g))

private def CS_rules_for_terminals₂ (N₁ : Type) (g : CS_grammar T) :
  list (csrule T (nnn T N₁ g.nt)) :=
list.map (λ t, csrule.mk [] (sum.inr (sum.inr t)) [] [symbol.terminal t]) (all_used_terminals (grammar_of_csg g))

private def big_CS_grammar (g₁ g₂ : CS_grammar T) : CS_grammar T :=
CS_grammar.mk
  (nnn T g₁.nt g₂.nt)
  (sum.inl none)
  ((csrule.mk [] (sum.inl none) [] [
    symbol.nonterminal (sum.inl (some (sum.inl g₁.initial))),
    symbol.nonterminal (sum.inl (some (sum.inr g₂.initial)))]
  ) :: (
    (list.map (wrap_CS_rule₁ g₂.nt) g₁.rules ++ list.map (wrap_CS_rule₂ g₁.nt) g₂.rules) ++
    (CS_rules_for_terminals₁ g₂.nt g₁ ++ CS_rules_for_terminals₂ g₁.nt g₂)
  ))

private lemma big_CS_grammar_same_language (g₁ g₂ : CS_grammar T) :
  CS_language (big_CS_grammar g₁ g₂) = grammar_language (big_grammar (grammar_of_csg g₁) (grammar_of_csg g₂)) :=
begin
  rw CS_language_eq_grammar_language,
  congr,
  unfold big_CS_grammar,
  unfold grammar_of_csg,
  unfold big_grammar,
  dsimp only [list.map],
  congr,
  repeat {
    rw list.map_append,
  },
  apply congr_arg2,
  {
    apply congr_arg2;
    {
      rw list.map_map,
      rw list.map_map,
      apply congr_arg2,
      {
        ext,
        cases x,
        change _ = grule.mk _ _ _ _,
        finish,
      },
      refl,
    },
  },
  {
    apply congr_arg2,
    {
      unfold rules_for_terminals₁,
      unfold CS_rules_for_terminals₁,
      rw list.map_map,
      unfold grammar_of_csg,
      finish,
    },
    {
      unfold rules_for_terminals₂,
      unfold CS_rules_for_terminals₂,
      rw list.map_map,
      unfold grammar_of_csg,
      finish,
    },
  },
end

private theorem bonus_CS_of_CS_c_CS (L₁ : language T) (L₂ : language T) :
  is_CS L₁  ∧  is_CS L₂   →   is_CS (L₁ * L₂)   :=
begin
  rintro ⟨⟨g₁, eq_L₁⟩, ⟨g₂, eq_L₂⟩⟩,
  rw CS_language_eq_grammar_language g₁ at eq_L₁,
  rw CS_language_eq_grammar_language g₂ at eq_L₂,

  use big_CS_grammar g₁ g₂,
  rw big_CS_grammar_same_language,

  apply set.eq_of_subset_of_subset,
  {
    intros w hyp,
    rw ←eq_L₁,
    rw ←eq_L₂,
    exact in_concatenated_of_in_big hyp,
  },
  {
    intros w hyp,
    rw ←eq_L₁ at hyp,
    rw ←eq_L₂ at hyp,
    exact in_big_of_in_concatenated hyp,
  },
end


/-
Copyright (c) 2022 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
import Lean

/-!
# `casesm`, `cases_type`, `constructorm` tactics

These tactics implement repeated `cases` / `constructor` on anything satisfying a predicate.
-/

namespace Mathlib.Tactic
open Lean Meta Elab Tactic

/--
Core tactic for `casesm` and `cases_type`. Calls `cases` on all fvars in `g` for which
`matcher ldecl.type` returns true.
* `recursive`: if true, it calls itself repeatedly on the resulting subgoals
* `allowSplit`: if false, it will skip any hypotheses where `cases` returns more than one subgoal.
* `throwOnNoMatch`: if true, then throws an error if no match is found
-/
partial def casesMatching (g : MVarId) (matcher : Expr → MetaM Bool)
    (recursive := false) (allowSplit := true) (throwOnNoMatch := !recursive) :
    MetaM (List MVarId) := do
  let result := (← go g).toList
  if throwOnNoMatch && result == [g] then
    throwError "no match"
  else
    return result
  where
  /-- Auxiliary for `casesMatching`. Accumulates generated subgoals in `acc`. -/
  go (g : MVarId) (acc : Array MVarId := #[]) : MetaM (Array MVarId) :=
    g.withContext do
      for ldecl in ← getLCtx do
        if ldecl.isImplementationDetail then continue
        if ← matcher ldecl.type then
          let mut acc := acc
          let subgoals ← if allowSplit then
            g.cases ldecl.fvarId
          else
            let s ← saveState
            let subgoals ← g.cases ldecl.fvarId
            if subgoals.size > 1 then
              s.restore
              continue
            else
              pure subgoals
          for subgoal in subgoals do
            if recursive then
              acc ← go subgoal.mvarId acc
            else
              acc := acc.push subgoal.mvarId
          return acc
      return (acc.push g)

/-- Elaborate a list of terms with holes into a list of patterns. -/
def elabPatterns (pats : Array Term) : TermElabM (Array AbstractMVarsResult) :=
  withTheReader Term.Context (fun ctx ↦ { ctx with ignoreTCFailures := true }) <|
  Term.withoutErrToSorry <|
  pats.mapM fun p ↦ Term.withoutModifyingElabMetaStateWithInfo do
    withRef p <| abstractMVars (← Term.elabTerm p none)

/-- Returns true if any of the patterns match the expression. -/
def matchPatterns (pats : Array AbstractMVarsResult) (e : Expr) : MetaM Bool := do
  let e ← instantiateMVars e
  pats.anyM fun p ↦ return (← Conv.matchPattern? p e) matches some (_, #[])

/--
* `casesm p` applies the `cases` tactic to a hypothesis `h : type`
  if `type` matches the pattern `p`.
* `casesm p_1, ..., p_n` applies the `cases` tactic to a hypothesis `h : type`
  if `type` matches one of the given patterns.
* `casesm* p` is a more efficient and compact version of `· repeat casesm p`.
  It is more efficient because the pattern is compiled once.

Example: The following tactic destructs all conjunctions and disjunctions in the current context.
```
casesm* _ ∨ _, _ ∧ _
```
-/
elab (name := casesM) "casesm" recursive:"*"? ppSpace pats:term,+ : tactic => do
  let pats ← elabPatterns pats.getElems
  liftMetaTactic (casesMatching · (matchPatterns pats) recursive.isSome)

/-- Common implementation of `cases_type` and `cases_type!`. -/
def elabCasesType (heads : Array Ident)
    (recursive := false) (allowSplit := true) : TacticM Unit := do
  let heads ← heads.mapM resolveGlobalConstNoOverloadWithInfo
  let matcher ty := pure <|
    if let .const n .. := ty.headBeta.getAppFn then heads.contains n else false
  liftMetaTactic (casesMatching · matcher recursive allowSplit)

/--
* `cases_type I` applies the `cases` tactic to a hypothesis `h : (I ...)`
* `cases_type I_1 ... I_n` applies the `cases` tactic to a hypothesis
  `h : (I_1 ...)` or ... or `h : (I_n ...)`
* `cases_type* I` is shorthand for `· repeat cases_type I`
* `cases_type! I` only applies `cases` if the number of resulting subgoals is <= 1.

Example: The following tactic destructs all conjunctions and disjunctions in the current goal.
```
cases_type* Or And
```
-/
elab (name := casesType) "cases_type" recursive:"*"? ppSpace heads:(colGt ident)+ : tactic =>
  elabCasesType heads recursive.isSome true

@[inherit_doc casesType]
elab (name := casesType!) "cases_type!" recursive:"*"? ppSpace heads:(colGt ident)+ : tactic =>
  elabCasesType heads recursive.isSome false

/--
Core tactic for `constructorm`. Calls `constructor` on all subgoals for which
`matcher ldecl.type` returns true.
* `recursive`: if true, it calls itself repeatedly on the resulting subgoals
* `throwOnNoMatch`: if true, throws an error if no match is found
-/
partial def constructorMatching (g : MVarId) (matcher : Expr → MetaM Bool)
    (recursive := false) (throwOnNoMatch := !recursive): MetaM (List MVarId) := do
  let result ←
    (if recursive then (do
      let result ← go g
      pure result.toList)
     else
      (g.withContext do
          if ← matcher (← g.getType) then g.constructor else pure [g]))
  if throwOnNoMatch && [g] == result then
    throwError "no match"
  else
    return result
where
  /-- Auxiliary for `constructorMatching`. Accumulates generated subgoals in `acc`. -/
  go (g : MVarId) (acc : Array MVarId := #[]) : MetaM (Array MVarId) :=
    g.withContext do
      if ← matcher (← g.getType) then
        let mut acc := acc
        for g' in ← g.constructor do
          acc ← go g' acc
        return acc
      return (acc.push g)

/--
* `constructorm p_1, ..., p_n` applies the `constructor` tactic to the main goal
  if `type` matches one of the given patterns.
* `constructorm* p` is a more efficient and compact version of `· repeat constructorm p`.
  It is more efficient because the pattern is compiled once.

Example: The following tactic proves any theorem like `True ∧ (True ∨ True)` consisting of
and/or/true:
```
constructorm* _ ∨ _, _ ∧ _, True
```
-/
elab (name := constructorM) "constructorm" recursive:"*"? ppSpace pats:term,+ : tactic => do
  let pats ← elabPatterns pats.getElems
  liftMetaTactic (constructorMatching · (matchPatterns pats) recursive.isSome)


import Mathlib.Tactic.CasesM
import Std.Tactic.GuardExpr

example (h : a ∧ b ∨ c ∧ d) (h2 : e ∧ f) : True := by
  casesm* _∨_, _∧_
  · clear ‹a› ‹b› ‹e› ‹f›; (fail_if_success clear ‹c›); trivial
  · clear ‹c› ‹d› ‹e› ‹f›; trivial

example (h : a ∧ b ∨ c ∧ d) : True := by
  casesm* _∧_
  clear ‹a ∧ b ∨ c ∧ d›; trivial

example (h : a ∧ b ∨ c ∨ d) : True := by
  casesm* _∨_
  · clear ‹a ∧ b›; trivial
  · clear ‹c›; trivial
  · clear ‹d›; trivial

example (h : a ∧ b ∨ c ∨ d) : True := by
  casesm _∨_
  · clear ‹a ∧ b›; trivial
  · clear ‹c ∨ d›; trivial

example (h : a ∧ b ∨ c ∨ d) : True := by
  cases_type And Or
  · clear ‹a ∧ b›; trivial
  · clear ‹c ∨ d›; trivial

example (h : a ∧ b ∨ c ∨ d) : True := by
  cases_type* And -- no match expected
  · clear ‹a ∧ b ∨ c ∨ d›; trivial

example (h : a ∧ b ∨ c ∨ d) : True := by
  cases_type Or
  · clear ‹a ∧ b›; trivial
  · clear ‹c ∨ d›; trivial

example (h : a ∧ b ∨ c ∨ d) : True := by
  cases_type* Or
  · clear ‹a ∧ b›; trivial
  · clear ‹c›; trivial
  · clear ‹d›; trivial

example (h : a ∧ b ∨ c ∨ d) : True := by
  cases_type!* And Or -- no match expected
  · clear ‹a ∧ b ∨ c ∨ d›; trivial

example (h : a ∧ b ∧ (c ∨ d)) : True := by
  cases_type! And Or
  · clear ‹a› ‹b ∧ (c ∨ d)›; trivial

example (h : a ∧ b ∧ (c ∨ d)) : True := by
  cases_type!* And Or
  · clear ‹a› ‹b› ‹c ∨ d›; trivial

inductive Test : Nat → Prop
  | foo : Test 0
  | bar : False → Test (n + 1)

example (_ : Test n) (h2 : Test (m + 1)) : True := by
  cases_type!* Test
  · clear ‹Test n› ‹False›; trivial

example (_ : Test n) (h2 : Test (m + 1)) : True := by
  cases_type Test
  · clear ‹Test (m + 1)›; trivial
  · clear ‹False› ‹Test (m + 1)›; trivial

example (_ : Test n) (h2 : Test (m + 1)) : True := by
  cases_type* Test
  · clear ‹False›; trivial
  · clear ‹False›; clear ‹False›; trivial

example : True ∧ True ∧ True := by
  constructorm* True, _∨_ -- no match expected
  guard_target = True ∧ True ∧ True
  constructorm _∧_
  · guard_target = True; constructorm True
  · guard_target = True ∧ True; constructorm* True, _∧_

section AuxDecl
variable {p q r : Prop}
variable (h : p ∧ q ∨ p ∧ r)

-- Make sure that we don't try to work on auxiliary declarations.
-- In this case, there will be an auxiliary recursive declaration for
-- `foo` itself that `casesm (_ ∧ _)` could potentially match.
theorem foo : p ∧ p :=
by cases h
   · casesm (_ ∧ _)
     constructor <;> assumption
   · casesm (_ ∧ _)
     constructor <;> assumption

end AuxDecl


/-
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, Patrick Massot
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.maps
import Mathlib.PostPort

universes u v w x u_1 u_2 u_3 u_4

namespace Mathlib

/-!
# Constructions of new topological spaces from old ones

This file constructs products, sums, subtypes and quotients of topological spaces
and sets up their basic theory, such as criteria for maps into or out of these
constructions to be continuous; descriptions of the open sets, neighborhood filters,
and generators of these constructions; and their behavior with respect to embeddings
and other specific classes of maps.

## Implementation note

The constructed topologies are defined using induced and coinduced topologies
along with the complete lattice structure on topologies. Their universal properties
(for example, a map `X → Y × Z` is continuous if and only if both projections
`X → Y`, `X → Z` are) follow easily using order-theoretic descriptions of
continuity. With more work we can also extract descriptions of the open sets,
neighborhood filters and so on.

## Tags

product, sum, disjoint union, subspace, quotient space

-/

protected instance subtype.topological_space {α : Type u} {p : α → Prop} [t : topological_space α] :
    topological_space (Subtype p) :=
  topological_space.induced coe t

protected instance quot.topological_space {α : Type u} {r : α → α → Prop}
    [t : topological_space α] : topological_space (Quot r) :=
  topological_space.coinduced (Quot.mk r) t

protected instance quotient.topological_space {α : Type u} {s : setoid α}
    [t : topological_space α] : topological_space (quotient s) :=
  topological_space.coinduced quotient.mk t

protected instance prod.topological_space {α : Type u} {β : Type v} [t₁ : topological_space α]
    [t₂ : topological_space β] : topological_space (α × β) :=
  topological_space.induced prod.fst t₁ ⊓ topological_space.induced prod.snd t₂

protected instance sum.topological_space {α : Type u} {β : Type v} [t₁ : topological_space α]
    [t₂ : topological_space β] : topological_space (α ⊕ β) :=
  topological_space.coinduced sum.inl t₁ ⊔ topological_space.coinduced sum.inr t₂

protected instance sigma.topological_space {α : Type u} {β : α → Type v}
    [t₂ : (a : α) → topological_space (β a)] : topological_space (sigma β) :=
  supr fun (a : α) => topological_space.coinduced (sigma.mk a) (t₂ a)

protected instance Pi.topological_space {α : Type u} {β : α → Type v}
    [t₂ : (a : α) → topological_space (β a)] : topological_space ((a : α) → β a) :=
  infi fun (a : α) => topological_space.induced (fun (f : (a : α) → β a) => f a) (t₂ a)

protected instance ulift.topological_space {α : Type u} [t : topological_space α] :
    topological_space (ulift α) :=
  topological_space.induced ulift.down t

/-- The image of a dense set under `quotient.mk` is a dense set. -/
theorem dense.quotient {α : Type u} [setoid α] [topological_space α] {s : set α} (H : dense s) :
    dense (quotient.mk '' s) :=
  dense_range.dense_image (function.surjective.dense_range (surjective_quotient_mk α))
    continuous_coinduced_rng H

/-- The composition of `quotient.mk` and a function with dense range has dense range. -/
theorem dense_range.quotient {α : Type u} {β : Type v} [setoid α] [topological_space α] {f : β → α}
    (hf : dense_range f) : dense_range (quotient.mk ∘ f) :=
  dense_range.comp (function.surjective.dense_range (surjective_quotient_mk α)) hf
    continuous_coinduced_rng

protected instance subtype.discrete_topology {α : Type u} {p : α → Prop} [topological_space α]
    [discrete_topology α] : discrete_topology (Subtype p) :=
  discrete_topology.mk
    (bot_unique
      fun (s : set (Subtype p)) (hs : topological_space.is_open ⊥ s) =>
        Exists.intro (coe '' s)
          { left := is_open_discrete (coe '' s),
            right := set.preimage_image_eq s subtype.coe_injective })

protected instance sum.discrete_topology {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] [hα : discrete_topology α] [hβ : discrete_topology β] :
    discrete_topology (α ⊕ β) :=
  sorry

protected instance sigma.discrete_topology {α : Type u} {β : α → Type v}
    [(a : α) → topological_space (β a)] [h : ∀ (a : α), discrete_topology (β a)] :
    discrete_topology (sigma β) :=
  sorry

/-
The 𝓝 filter and the subspace topology.
-/

theorem mem_nhds_subtype {α : Type u} [topological_space α] (s : set α)
    (a : Subtype fun (x : α) => x ∈ s) (t : set (Subtype fun (x : α) => x ∈ s)) :
    t ∈ nhds a ↔ ∃ (u : set α), ∃ (H : u ∈ nhds ↑a), coe ⁻¹' u ⊆ t :=
  mem_nhds_induced coe a t

theorem nhds_subtype {α : Type u} [topological_space α] (s : set α)
    (a : Subtype fun (x : α) => x ∈ s) : nhds a = filter.comap coe (nhds ↑a) :=
  nhds_induced coe a

theorem continuous_fst {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    continuous prod.fst :=
  continuous_inf_dom_left continuous_induced_dom

theorem continuous_at_fst {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {p : α × β} : continuous_at prod.fst p :=
  continuous.continuous_at continuous_fst

theorem continuous_snd {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    continuous prod.snd :=
  continuous_inf_dom_right continuous_induced_dom

theorem continuous_at_snd {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {p : α × β} : continuous_at prod.snd p :=
  continuous.continuous_at continuous_snd

theorem continuous.prod_mk {α : Type u} {β : Type v} {γ : Type w} [topological_space α]
    [topological_space β] [topological_space γ] {f : γ → α} {g : γ → β} (hf : continuous f)
    (hg : continuous g) : continuous fun (x : γ) => (f x, g x) :=
  continuous_inf_rng (continuous_induced_rng hf) (continuous_induced_rng hg)

theorem continuous.prod_map {α : Type u} {β : Type v} {γ : Type w} {δ : Type x}
    [topological_space α] [topological_space β] [topological_space γ] [topological_space δ]
    {f : γ → α} {g : δ → β} (hf : continuous f) (hg : continuous g) :
    continuous fun (x : γ × δ) => (f (prod.fst x), g (prod.snd x)) :=
  continuous.prod_mk (continuous.comp hf continuous_fst) (continuous.comp hg continuous_snd)

theorem filter.eventually.prod_inl_nhds {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] {p : α → Prop} {a : α}
    (h : filter.eventually (fun (x : α) => p x) (nhds a)) (b : β) :
    filter.eventually (fun (x : α × β) => p (prod.fst x)) (nhds (a, b)) :=
  continuous_at_fst h

theorem filter.eventually.prod_inr_nhds {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] {p : β → Prop} {b : β}
    (h : filter.eventually (fun (x : β) => p x) (nhds b)) (a : α) :
    filter.eventually (fun (x : α × β) => p (prod.snd x)) (nhds (a, b)) :=
  continuous_at_snd h

theorem filter.eventually.prod_mk_nhds {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] {pa : α → Prop} {a : α}
    (ha : filter.eventually (fun (x : α) => pa x) (nhds a)) {pb : β → Prop} {b : β}
    (hb : filter.eventually (fun (y : β) => pb y) (nhds b)) :
    filter.eventually (fun (p : α × β) => pa (prod.fst p) ∧ pb (prod.snd p)) (nhds (a, b)) :=
  filter.eventually.and (filter.eventually.prod_inl_nhds ha b)
    (filter.eventually.prod_inr_nhds hb a)

theorem continuous_swap {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    continuous prod.swap :=
  continuous.prod_mk continuous_snd continuous_fst

theorem continuous_uncurry_left {α : Type u} {β : Type v} {γ : Type w} [topological_space α]
    [topological_space β] [topological_space γ] {f : α → β → γ} (a : α)
    (h : continuous (function.uncurry f)) : continuous (f a) :=
  (fun (this : continuous (function.uncurry f ∘ fun (b : β) => (a, b))) => this)
    (continuous.comp h (continuous.prod_mk continuous_const continuous_id'))

theorem continuous_uncurry_right {α : Type u} {β : Type v} {γ : Type w} [topological_space α]
    [topological_space β] [topological_space γ] {f : α → β → γ} (b : β)
    (h : continuous (function.uncurry f)) : continuous fun (a : α) => f a b :=
  (fun (this : continuous (function.uncurry f ∘ fun (a : α) => (a, b))) => this)
    (continuous.comp h (continuous.prod_mk continuous_id' continuous_const))

theorem continuous_curry {α : Type u} {β : Type v} {γ : Type w} [topological_space α]
    [topological_space β] [topological_space γ] {g : α × β → γ} (a : α) (h : continuous g) :
    continuous (function.curry g a) :=
  (fun (this : continuous (g ∘ fun (b : β) => (a, b))) => this)
    (continuous.comp h (continuous.prod_mk continuous_const continuous_id'))

theorem is_open.prod {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {s : set α} {t : set β} (hs : is_open s) (ht : is_open t) : is_open (set.prod s t) :=
  is_open_inter (is_open.preimage continuous_fst hs) (is_open.preimage continuous_snd ht)

theorem nhds_prod_eq {α : Type u} {β : Type v} [topological_space α] [topological_space β] {a : α}
    {b : β} : nhds (a, b) = filter.prod (nhds a) (nhds b) :=
  sorry

theorem mem_nhds_prod_iff {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {a : α} {b : β} {s : set (α × β)} :
    s ∈ nhds (a, b) ↔
        ∃ (u : set α), ∃ (H : u ∈ nhds a), ∃ (v : set β), ∃ (H : v ∈ nhds b), set.prod u v ⊆ s :=
  sorry

theorem filter.has_basis.prod_nhds {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] {ιa : Type u_1} {ιb : Type u_2} {pa : ιa → Prop} {pb : ιb → Prop}
    {sa : ιa → set α} {sb : ιb → set β} {a : α} {b : β} (ha : filter.has_basis (nhds a) pa sa)
    (hb : filter.has_basis (nhds b) pb sb) :
    filter.has_basis (nhds (a, b)) (fun (i : ιa × ιb) => pa (prod.fst i) ∧ pb (prod.snd i))
        fun (i : ιa × ιb) => set.prod (sa (prod.fst i)) (sb (prod.snd i)) :=
  sorry

protected instance prod.discrete_topology {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] [discrete_topology α] [discrete_topology β] : discrete_topology (α × β) :=
  discrete_topology.mk (eq_of_nhds_eq_nhds fun (_x : α × β) => sorry)

theorem prod_mem_nhds_sets {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {s : set α} {t : set β} {a : α} {b : β} (ha : s ∈ nhds a) (hb : t ∈ nhds b) :
    set.prod s t ∈ nhds (a, b) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (set.prod s t ∈ nhds (a, b))) nhds_prod_eq))
    (filter.prod_mem_prod ha hb)

theorem nhds_swap {α : Type u} {β : Type v} [topological_space α] [topological_space β] (a : α)
    (b : β) : nhds (a, b) = filter.map prod.swap (nhds (b, a)) :=
  sorry

theorem filter.tendsto.prod_mk_nhds {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] {γ : Type u_1} {a : α} {b : β} {f : filter γ} {ma : γ → α} {mb : γ → β}
    (ha : filter.tendsto ma f (nhds a)) (hb : filter.tendsto mb f (nhds b)) :
    filter.tendsto (fun (c : γ) => (ma c, mb c)) f (nhds (a, b)) :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (filter.tendsto (fun (c : γ) => (ma c, mb c)) f (nhds (a, b))))
        nhds_prod_eq))
    (filter.tendsto.prod_mk ha hb)

theorem filter.eventually.curry_nhds {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] {p : α × β → Prop} {x : α} {y : β}
    (h : filter.eventually (fun (x : α × β) => p x) (nhds (x, y))) :
    filter.eventually (fun (x' : α) => filter.eventually (fun (y' : β) => p (x', y')) (nhds y))
        (nhds x) :=
  filter.eventually.curry
    (eq.mp
      (Eq._oldrec (Eq.refl (filter.eventually (fun (x : α × β) => p x) (nhds (x, y)))) nhds_prod_eq)
      h)

theorem continuous_at.prod {α : Type u} {β : Type v} {γ : Type w} [topological_space α]
    [topological_space β] [topological_space γ] {f : α → β} {g : α → γ} {x : α}
    (hf : continuous_at f x) (hg : continuous_at g x) :
    continuous_at (fun (x : α) => (f x, g x)) x :=
  filter.tendsto.prod_mk_nhds hf hg

theorem continuous_at.prod_map {α : Type u} {β : Type v} {γ : Type w} {δ : Type x}
    [topological_space α] [topological_space β] [topological_space γ] [topological_space δ]
    {f : α → γ} {g : β → δ} {p : α × β} (hf : continuous_at f (prod.fst p))
    (hg : continuous_at g (prod.snd p)) :
    continuous_at (fun (p : α × β) => (f (prod.fst p), g (prod.snd p))) p :=
  continuous_at.prod (continuous_at.comp hf (continuous.continuous_at continuous_fst))
    (continuous_at.comp hg (continuous.continuous_at continuous_snd))

theorem continuous_at.prod_map' {α : Type u} {β : Type v} {γ : Type w} {δ : Type x}
    [topological_space α] [topological_space β] [topological_space γ] [topological_space δ]
    {f : α → γ} {g : β → δ} {x : α} {y : β} (hf : continuous_at f x) (hg : continuous_at g y) :
    continuous_at (fun (p : α × β) => (f (prod.fst p), g (prod.snd p))) (x, y) :=
  (fun (hf : continuous_at f (prod.fst (x, y))) =>
      (fun (hg : continuous_at g (prod.snd (x, y))) => continuous_at.prod_map hf hg) hg)
    hf

theorem prod_generate_from_generate_from_eq {α : Type u_1} {β : Type u_2} {s : set (set α)}
    {t : set (set β)} (hs : ⋃₀s = set.univ) (ht : ⋃₀t = set.univ) :
    prod.topological_space =
        topological_space.generate_from
          (set_of
            fun (g : set (α × β)) =>
              ∃ (u : set α), ∃ (H : u ∈ s), ∃ (v : set β), ∃ (H : v ∈ t), g = set.prod u v) :=
  sorry

theorem prod_eq_generate_from {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] :
    prod.topological_space =
        topological_space.generate_from
          (set_of
            fun (g : set (α × β)) =>
              ∃ (s : set α), ∃ (t : set β), is_open s ∧ is_open t ∧ g = set.prod s t) :=
  sorry

theorem is_open_prod_iff {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {s : set (α × β)} :
    is_open s ↔
        ∀ (a : α) (b : β),
          (a, b) ∈ s →
            ∃ (u : set α),
              ∃ (v : set β), is_open u ∧ is_open v ∧ a ∈ u ∧ b ∈ v ∧ set.prod u v ⊆ s :=
  sorry

theorem continuous_uncurry_of_discrete_topology_left {α : Type u} {β : Type v} {γ : Type w}
    [topological_space α] [topological_space β] [topological_space γ] [discrete_topology α]
    {f : α → β → γ} (h : ∀ (a : α), continuous (f a)) : continuous (function.uncurry f) :=
  sorry

/-- Given a neighborhood `s` of `(x, x)`, then `(x, x)` has a square open neighborhood
  that is a subset of `s`. -/
theorem exists_nhds_square {α : Type u} [topological_space α] {s : set (α × α)} {x : α}
    (hx : s ∈ nhds (x, x)) : ∃ (U : set α), is_open U ∧ x ∈ U ∧ set.prod U U ⊆ s :=
  sorry

/-- The first projection in a product of topological spaces sends open sets to open sets. -/
theorem is_open_map_fst {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    is_open_map prod.fst :=
  sorry

/-- The second projection in a product of topological spaces sends open sets to open sets. -/
theorem is_open_map_snd {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    is_open_map prod.snd :=
  sorry

/-- A product set is open in a product space if and only if each factor is open, or one of them is
empty -/
theorem is_open_prod_iff' {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {s : set α} {t : set β} : is_open (set.prod s t) ↔ is_open s ∧ is_open t ∨ s = ∅ ∨ t = ∅ :=
  sorry

theorem closure_prod_eq {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {s : set α} {t : set β} : closure (set.prod s t) = set.prod (closure s) (closure t) :=
  sorry

theorem map_mem_closure2 {α : Type u} {β : Type v} {γ : Type w} [topological_space α]
    [topological_space β] [topological_space γ] {s : set α} {t : set β} {u : set γ} {f : α → β → γ}
    {a : α} {b : β} (hf : continuous fun (p : α × β) => f (prod.fst p) (prod.snd p))
    (ha : a ∈ closure s) (hb : b ∈ closure t) (hu : ∀ (a : α) (b : β), a ∈ s → b ∈ t → f a b ∈ u) :
    f a b ∈ closure u :=
  sorry

theorem is_closed.prod {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {s₁ : set α} {s₂ : set β} (h₁ : is_closed s₁) (h₂ : is_closed s₂) :
    is_closed (set.prod s₁ s₂) :=
  sorry

/-- The product of two dense sets is a dense set. -/
theorem dense.prod {α : Type u} {β : Type v} [topological_space α] [topological_space β] {s : set α}
    {t : set β} (hs : dense s) (ht : dense t) : dense (set.prod s t) :=
  fun (x : α × β) =>
    eq.mpr (id (Eq._oldrec (Eq.refl (x ∈ closure (set.prod s t))) closure_prod_eq))
      { left := hs (prod.fst x), right := ht (prod.snd x) }

/-- If `f` and `g` are maps with dense range, then `prod.map f g` has dense range. -/
theorem dense_range.prod_map {β : Type v} {γ : Type w} [topological_space β] [topological_space γ]
    {ι : Type u_1} {κ : Type u_2} {f : ι → β} {g : κ → γ} (hf : dense_range f)
    (hg : dense_range g) : dense_range (prod.map f g) :=
  sorry

theorem inducing.prod_mk {α : Type u} {β : Type v} {γ : Type w} {δ : Type x} [topological_space α]
    [topological_space β] [topological_space γ] [topological_space δ] {f : α → β} {g : γ → δ}
    (hf : inducing f) (hg : inducing g) :
    inducing fun (x : α × γ) => (f (prod.fst x), g (prod.snd x)) :=
  sorry

theorem embedding.prod_mk {α : Type u} {β : Type v} {γ : Type w} {δ : Type x} [topological_space α]
    [topological_space β] [topological_space γ] [topological_space δ] {f : α → β} {g : γ → δ}
    (hf : embedding f) (hg : embedding g) :
    embedding fun (x : α × γ) => (f (prod.fst x), g (prod.snd x)) :=
  sorry

protected theorem is_open_map.prod {α : Type u} {β : Type v} {γ : Type w} {δ : Type x}
    [topological_space α] [topological_space β] [topological_space γ] [topological_space δ]
    {f : α → β} {g : γ → δ} (hf : is_open_map f) (hg : is_open_map g) :
    is_open_map fun (p : α × γ) => (f (prod.fst p), g (prod.snd p)) :=
  sorry

protected theorem open_embedding.prod {α : Type u} {β : Type v} {γ : Type w} {δ : Type x}
    [topological_space α] [topological_space β] [topological_space γ] [topological_space δ]
    {f : α → β} {g : γ → δ} (hf : open_embedding f) (hg : open_embedding g) :
    open_embedding fun (x : α × γ) => (f (prod.fst x), g (prod.snd x)) :=
  open_embedding_of_embedding_open
    (embedding.prod_mk (open_embedding.to_embedding hf) (open_embedding.to_embedding hg))
    (is_open_map.prod (open_embedding.is_open_map hf) (open_embedding.is_open_map hg))

theorem embedding_graph {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {f : α → β} (hf : continuous f) : embedding fun (x : α) => (x, f x) :=
  embedding_of_embedding_compose (continuous.prod_mk continuous_id hf) continuous_fst embedding_id

theorem continuous_inl {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    continuous sum.inl :=
  continuous_sup_rng_left continuous_coinduced_rng

theorem continuous_inr {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    continuous sum.inr :=
  continuous_sup_rng_right continuous_coinduced_rng

theorem continuous_sum_rec {α : Type u} {β : Type v} {γ : Type w} [topological_space α]
    [topological_space β] [topological_space γ] {f : α → γ} {g : β → γ} (hf : continuous f)
    (hg : continuous g) : continuous (sum.rec f g) :=
  sorry

theorem is_open_sum_iff {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {s : set (α ⊕ β)} : is_open s ↔ is_open (sum.inl ⁻¹' s) ∧ is_open (sum.inr ⁻¹' s) :=
  iff.rfl

theorem is_open_map_sum {α : Type u} {β : Type v} {γ : Type w} [topological_space α]
    [topological_space β] [topological_space γ] {f : α ⊕ β → γ}
    (h₁ : is_open_map fun (a : α) => f (sum.inl a))
    (h₂ : is_open_map fun (b : β) => f (sum.inr b)) : is_open_map f :=
  sorry

theorem embedding_inl {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    embedding sum.inl :=
  sorry

theorem embedding_inr {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    embedding sum.inr :=
  sorry

theorem is_open_range_inl {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    is_open (set.range sum.inl) :=
  sorry

theorem is_open_range_inr {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    is_open (set.range sum.inr) :=
  sorry

theorem open_embedding_inl {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    open_embedding sum.inl :=
  open_embedding.mk
    (embedding.mk (embedding.to_inducing embedding_inl) (embedding.inj embedding_inl))
    is_open_range_inl

theorem open_embedding_inr {α : Type u} {β : Type v} [topological_space α] [topological_space β] :
    open_embedding sum.inr :=
  open_embedding.mk
    (embedding.mk (embedding.to_inducing embedding_inr) (embedding.inj embedding_inr))
    is_open_range_inr

theorem embedding_subtype_coe {α : Type u} [topological_space α] {p : α → Prop} : embedding coe :=
  embedding.mk (inducing.mk rfl) subtype.coe_injective

theorem continuous_subtype_val {α : Type u} [topological_space α] {p : α → Prop} :
    continuous subtype.val :=
  continuous_induced_dom

theorem continuous_subtype_coe {α : Type u} [topological_space α] {p : α → Prop} : continuous coe :=
  continuous_subtype_val

theorem is_open.open_embedding_subtype_coe {α : Type u} [topological_space α] {s : set α}
    (hs : is_open s) : open_embedding coe :=
  open_embedding.mk (embedding.mk (inducing.mk rfl) subtype.coe_injective)
    (Eq.symm subtype.range_coe ▸ hs)

theorem is_open.is_open_map_subtype_coe {α : Type u} [topological_space α] {s : set α}
    (hs : is_open s) : is_open_map coe :=
  open_embedding.is_open_map (is_open.open_embedding_subtype_coe hs)

theorem is_open_map.restrict {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {f : α → β} (hf : is_open_map f) {s : set α} (hs : is_open s) :
    is_open_map (set.restrict f s) :=
  is_open_map.comp hf (is_open.is_open_map_subtype_coe hs)

theorem is_closed.closed_embedding_subtype_coe {α : Type u} [topological_space α] {s : set α}
    (hs : is_closed s) : closed_embedding coe :=
  closed_embedding.mk (embedding.mk (inducing.mk rfl) subtype.coe_injective)
    (Eq.symm subtype.range_coe ▸ hs)

theorem continuous_subtype_mk {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {p : α → Prop} {f : β → α} (hp : ∀ (x : β), p (f x)) (h : continuous f) :
    continuous fun (x : β) => { val := f x, property := hp x } :=
  continuous_induced_rng h

theorem continuous_inclusion {α : Type u} [topological_space α] {s : set α} {t : set α}
    (h : s ⊆ t) : continuous (set.inclusion h) :=
  continuous_subtype_mk (fun (x : ↥s) => set.inclusion._proof_1 h x) continuous_subtype_coe

theorem continuous_at_subtype_coe {α : Type u} [topological_space α] {p : α → Prop}
    {a : Subtype p} : continuous_at coe a :=
  iff.mp continuous_iff_continuous_at continuous_subtype_coe a

theorem map_nhds_subtype_coe_eq {α : Type u} [topological_space α] {p : α → Prop} {a : α} (ha : p a)
    (h : (set_of fun (a : α) => p a) ∈ nhds a) :
    filter.map coe (nhds { val := a, property := ha }) = nhds a :=
  sorry

theorem nhds_subtype_eq_comap {α : Type u} [topological_space α] {p : α → Prop} {a : α} {h : p a} :
    nhds { val := a, property := h } = filter.comap coe (nhds a) :=
  nhds_induced coe { val := a, property := h }

theorem tendsto_subtype_rng {α : Type u} [topological_space α] {β : Type u_1} {p : α → Prop}
    {b : filter β} {f : β → Subtype p} {a : Subtype p} :
    filter.tendsto f b (nhds a) ↔ filter.tendsto (fun (x : β) => ↑(f x)) b (nhds ↑a) :=
  sorry

theorem continuous_subtype_nhds_cover {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] {ι : Sort u_1} {f : α → β} {c : ι → α → Prop}
    (c_cover : ∀ (x : α), ∃ (i : ι), (set_of fun (x : α) => c i x) ∈ nhds x)
    (f_cont : ∀ (i : ι), continuous fun (x : Subtype (c i)) => f ↑x) : continuous f :=
  sorry

theorem continuous_subtype_is_closed_cover {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] {ι : Type u_1} {f : α → β} (c : ι → α → Prop)
    (h_lf : locally_finite fun (i : ι) => set_of fun (x : α) => c i x)
    (h_is_closed : ∀ (i : ι), is_closed (set_of fun (x : α) => c i x))
    (h_cover : ∀ (x : α), ∃ (i : ι), c i x)
    (f_cont : ∀ (i : ι), continuous fun (x : Subtype (c i)) => f ↑x) : continuous f :=
  sorry

theorem closure_subtype {α : Type u} [topological_space α] {p : α → Prop}
    {x : Subtype fun (a : α) => p a} {s : set (Subtype fun (a : α) => p a)} :
    x ∈ closure s ↔ ↑x ∈ closure (coe '' s) :=
  closure_induced fun (x y : Subtype fun (a : α) => p a) => subtype.eq

theorem quotient_map_quot_mk {α : Type u} [topological_space α] {r : α → α → Prop} :
    quotient_map (Quot.mk r) :=
  { left := quot.exists_rep, right := rfl }

theorem continuous_quot_mk {α : Type u} [topological_space α] {r : α → α → Prop} :
    continuous (Quot.mk r) :=
  continuous_coinduced_rng

theorem continuous_quot_lift {α : Type u} {β : Type v} [topological_space α] [topological_space β]
    {r : α → α → Prop} {f : α → β} (hr : ∀ (a b : α), r a b → f a = f b) (h : continuous f) :
    continuous (Quot.lift f hr) :=
  continuous_coinduced_dom h

theorem quotient_map_quotient_mk {α : Type u} [topological_space α] {s : setoid α} :
    quotient_map quotient.mk :=
  quotient_map_quot_mk

theorem continuous_quotient_mk {α : Type u} [topological_space α] {s : setoid α} :
    continuous quotient.mk :=
  continuous_coinduced_rng

theorem continuous_quotient_lift {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] {s : setoid α} {f : α → β} (hs : ∀ (a b : α), a ≈ b → f a = f b)
    (h : continuous f) : continuous (quotient.lift f hs) :=
  continuous_coinduced_dom h

theorem continuous_pi {α : Type u} {ι : Type u_1} {π : ι → Type u_2} [topological_space α]
    [(i : ι) → topological_space (π i)] {f : α → (i : ι) → π i}
    (h : ∀ (i : ι), continuous fun (a : α) => f a i) : continuous f :=
  continuous_infi_rng fun (i : ι) => continuous_induced_rng (h i)

theorem continuous_apply {ι : Type u_1} {π : ι → Type u_2} [(i : ι) → topological_space (π i)]
    (i : ι) : continuous fun (p : (i : ι) → π i) => p i :=
  continuous_infi_dom continuous_induced_dom

/-- Embedding a factor into a product space (by fixing arbitrarily all the other coordinates) is
continuous. -/
theorem continuous_update {ι : Type u_1} {π : ι → Type u_2} [DecidableEq ι]
    [(i : ι) → topological_space (π i)] {i : ι} {f : (i : ι) → π i} :
    continuous fun (x : π i) => function.update f i x :=
  sorry

theorem nhds_pi {ι : Type u_1} {π : ι → Type u_2} [t : (i : ι) → topological_space (π i)]
    {a : (i : ι) → π i} :
    nhds a = infi fun (i : ι) => filter.comap (fun (x : (i : ι) → π i) => x i) (nhds (a i)) :=
  sorry

theorem tendsto_pi {α : Type u} {ι : Type u_1} {π : ι → Type u_2}
    [t : (i : ι) → topological_space (π i)] {f : α → (i : ι) → π i} {g : (i : ι) → π i}
    {u : filter α} :
    filter.tendsto f u (nhds g) ↔ ∀ (x : ι), filter.tendsto (fun (i : α) => f i x) u (nhds (g x)) :=
  sorry

theorem is_open_set_pi {ι : Type u_1} {π : ι → Type u_2} [(a : ι) → topological_space (π a)]
    {i : set ι} {s : (a : ι) → set (π a)} (hi : set.finite i)
    (hs : ∀ (a : ι), a ∈ i → is_open (s a)) : is_open (set.pi i s) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_open (set.pi i s))) (set.pi_def i s)))
    (is_open_bInter hi fun (a : ι) (ha : a ∈ i) => is_open.preimage (continuous_apply a) (hs a ha))

theorem set_pi_mem_nhds {ι : Type u_1} {π : ι → Type u_2} [(a : ι) → topological_space (π a)]
    {i : set ι} {s : (a : ι) → set (π a)} {x : (a : ι) → π a} (hi : set.finite i)
    (hs : ∀ (a : ι), a ∈ i → s a ∈ nhds (x a)) : set.pi i s ∈ nhds x :=
  sorry

theorem pi_eq_generate_from {ι : Type u_1} {π : ι → Type u_2} [(a : ι) → topological_space (π a)] :
    Pi.topological_space =
        topological_space.generate_from
          (set_of
            fun (g : set ((a : ι) → π a)) =>
              ∃ (s : (a : ι) → set (π a)),
                ∃ (i : finset ι), (∀ (a : ι), a ∈ i → is_open (s a)) ∧ g = set.pi (↑i) s) :=
  sorry

theorem pi_generate_from_eq {ι : Type u_1} {π : ι → Type u_2} {g : (a : ι) → set (set (π a))} :
    Pi.topological_space =
        topological_space.generate_from
          (set_of
            fun (t : set ((a : ι) → π a)) =>
              ∃ (s : (a : ι) → set (π a)),
                ∃ (i : finset ι), (∀ (a : ι), a ∈ i → s a ∈ g a) ∧ t = set.pi (↑i) s) :=
  sorry

theorem pi_generate_from_eq_fintype {ι : Type u_1} {π : ι → Type u_2}
    {g : (a : ι) → set (set (π a))} [fintype ι] (hg : ∀ (a : ι), ⋃₀g a = set.univ) :
    Pi.topological_space =
        topological_space.generate_from
          (set_of
            fun (t : set ((a : ι) → π a)) =>
              ∃ (s : (a : ι) → set (π a)), (∀ (a : ι), s a ∈ g a) ∧ t = set.pi set.univ s) :=
  sorry

theorem continuous_sigma_mk {ι : Type u_1} {σ : ι → Type u_2} [(i : ι) → topological_space (σ i)]
    {i : ι} : continuous (sigma.mk i) :=
  continuous_supr_rng continuous_coinduced_rng

theorem is_open_sigma_iff {ι : Type u_1} {σ : ι → Type u_2} [(i : ι) → topological_space (σ i)]
    {s : set (sigma σ)} : is_open s ↔ ∀ (i : ι), is_open (sigma.mk i ⁻¹' s) :=
  sorry

theorem is_closed_sigma_iff {ι : Type u_1} {σ : ι → Type u_2} [(i : ι) → topological_space (σ i)]
    {s : set (sigma σ)} : is_closed s ↔ ∀ (i : ι), is_closed (sigma.mk i ⁻¹' s) :=
  is_open_sigma_iff

theorem is_open_map_sigma_mk {ι : Type u_1} {σ : ι → Type u_2} [(i : ι) → topological_space (σ i)]
    {i : ι} : is_open_map (sigma.mk i) :=
  sorry

theorem is_open_range_sigma_mk {ι : Type u_1} {σ : ι → Type u_2} [(i : ι) → topological_space (σ i)]
    {i : ι} : is_open (set.range (sigma.mk i)) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_open (set.range (sigma.mk i)))) (Eq.symm set.image_univ)))
    (is_open_map_sigma_mk set.univ is_open_univ)

theorem is_closed_map_sigma_mk {ι : Type u_1} {σ : ι → Type u_2} [(i : ι) → topological_space (σ i)]
    {i : ι} : is_closed_map (sigma.mk i) :=
  sorry

theorem is_closed_sigma_mk {ι : Type u_1} {σ : ι → Type u_2} [(i : ι) → topological_space (σ i)]
    {i : ι} : is_closed (set.range (sigma.mk i)) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (is_closed (set.range (sigma.mk i)))) (Eq.symm set.image_univ)))
    (is_closed_map_sigma_mk set.univ is_closed_univ)

theorem open_embedding_sigma_mk {ι : Type u_1} {σ : ι → Type u_2}
    [(i : ι) → topological_space (σ i)] {i : ι} : open_embedding (sigma.mk i) :=
  open_embedding_of_continuous_injective_open continuous_sigma_mk sigma_mk_injective
    is_open_map_sigma_mk

theorem closed_embedding_sigma_mk {ι : Type u_1} {σ : ι → Type u_2}
    [(i : ι) → topological_space (σ i)] {i : ι} : closed_embedding (sigma.mk i) :=
  closed_embedding_of_continuous_injective_closed continuous_sigma_mk sigma_mk_injective
    is_closed_map_sigma_mk

theorem embedding_sigma_mk {ι : Type u_1} {σ : ι → Type u_2} [(i : ι) → topological_space (σ i)]
    {i : ι} : embedding (sigma.mk i) :=
  closed_embedding.to_embedding closed_embedding_sigma_mk

/-- A map out of a sum type is continuous if its restriction to each summand is. -/
theorem continuous_sigma {β : Type v} {ι : Type u_1} {σ : ι → Type u_2}
    [(i : ι) → topological_space (σ i)] [topological_space β] {f : sigma σ → β}
    (h : ∀ (i : ι), continuous fun (a : σ i) => f (sigma.mk i a)) : continuous f :=
  continuous_supr_dom fun (i : ι) => continuous_coinduced_dom (h i)

theorem continuous_sigma_map {ι : Type u_1} {σ : ι → Type u_2} [(i : ι) → topological_space (σ i)]
    {κ : Type u_3} {τ : κ → Type u_4} [(k : κ) → topological_space (τ k)] {f₁ : ι → κ}
    {f₂ : (i : ι) → σ i → τ (f₁ i)} (hf : ∀ (i : ι), continuous (f₂ i)) :
    continuous (sigma.map f₁ f₂) :=
  sorry

theorem is_open_map_sigma {β : Type v} {ι : Type u_1} {σ : ι → Type u_2}
    [(i : ι) → topological_space (σ i)] [topological_space β] {f : sigma σ → β}
    (h : ∀ (i : ι), is_open_map fun (a : σ i) => f (sigma.mk i a)) : is_open_map f :=
  sorry

/-- The sum of embeddings is an embedding. -/
theorem embedding_sigma_map {ι : Type u_1} {σ : ι → Type u_2} [(i : ι) → topological_space (σ i)]
    {τ : ι → Type u_3} [(i : ι) → topological_space (τ i)] {f : (i : ι) → σ i → τ i}
    (hf : ∀ (i : ι), embedding (f i)) : embedding (sigma.map id f) :=
  sorry

theorem continuous_ulift_down {α : Type u} [topological_space α] : continuous ulift.down :=
  continuous_induced_dom

theorem continuous_ulift_up {α : Type u} [topological_space α] : continuous ulift.up :=
  continuous_induced_rng continuous_id

theorem mem_closure_of_continuous {α : Type u} {β : Type v} [topological_space α]
    [topological_space β] {f : α → β} {a : α} {s : set α} {t : set β} (hf : continuous f)
    (ha : a ∈ closure s) (h : set.maps_to f s (closure t)) : f a ∈ closure t :=
  set.mem_of_mem_of_subset
    (set.mem_of_mem_of_subset (set.mem_image_of_mem f ha) (image_closure_subset_closure_image hf))
    (closure_minimal (set.maps_to.image_subset h) is_closed_closure)

theorem mem_closure_of_continuous2 {α : Type u} {β : Type v} {γ : Type w} [topological_space α]
    [topological_space β] [topological_space γ] {f : α → β → γ} {a : α} {b : β} {s : set α}
    {t : set β} {u : set γ} (hf : continuous fun (p : α × β) => f (prod.fst p) (prod.snd p))
    (ha : a ∈ closure s) (hb : b ∈ closure t)
    (h : ∀ (a : α), a ∈ s → ∀ (b : β), b ∈ t → f a b ∈ closure u) : f a b ∈ closure u :=
  sorry

end Mathlib


/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison
-/
import algebra.category.Group.preadditive
import group_theory.quotient_group
import category_theory.limits.concrete_category
import category_theory.limits.shapes.kernels
import category_theory.limits.shapes.concrete_category

/-!
# The category of additive commutative groups has all colimits.

This file uses a "pre-automated" approach, just as for `Mon/colimits.lean`.
It is a very uniform approach, that conceivably could be synthesised directly
by a tactic that analyses the shape of `add_comm_group` and `monoid_hom`.

TODO:
In fact, in `AddCommGroup` there is a much nicer model of colimits as quotients
of finitely supported functions, and we really should implement this as well (or instead).
-/

universes u v

open category_theory
open category_theory.limits

-- [ROBOT VOICE]:
-- You should pretend for now that this file was automatically generated.
-- It follows the same template as colimits in Mon.

namespace AddCommGroup.colimits
/-!
We build the colimit of a diagram in `AddCommGroup` by constructing the
free group on the disjoint union of all the abelian groups in the diagram,
then taking the quotient by the abelian group laws within each abelian group,
and the identifications given by the morphisms in the diagram.
-/

variables {J : Type v} [small_category J] (F : J ⥤ AddCommGroup.{v})

/--
An inductive type representing all group expressions (without relations)
on a collection of types indexed by the objects of `J`.
-/
inductive prequotient
-- There's always `of`
| of : Π (j : J) (x : F.obj j), prequotient
-- Then one generator for each operation
| zero : prequotient
| neg : prequotient → prequotient
| add : prequotient → prequotient → prequotient

instance : inhabited (prequotient F) := ⟨prequotient.zero⟩

open prequotient

/--
The relation on `prequotient` saying when two expressions are equal
because of the abelian group laws, or
because one element is mapped to another by a morphism in the diagram.
-/
inductive relation : prequotient F → prequotient F → Prop
-- Make it an equivalence relation:
| refl : Π (x), relation x x
| symm : Π (x y) (h : relation x y), relation y x
| trans : Π (x y z) (h : relation x y) (k : relation y z), relation x z
-- There's always a `map` relation
| map : Π (j j' : J) (f : j ⟶ j') (x : F.obj j), relation (of j' (F.map f x)) (of j x)
-- Then one relation per operation, describing the interaction with `of`
| zero : Π (j), relation (of j 0) zero
| neg : Π (j) (x : F.obj j), relation (of j (-x)) (neg (of j x))
| add : Π (j) (x y : F.obj j), relation (of j (x + y)) (add (of j x) (of j y))
-- Then one relation per argument of each operation
| neg_1 : Π (x x') (r : relation x x'), relation (neg x) (neg x')
| add_1 : Π (x x' y) (r : relation x x'), relation (add x y) (add x' y)
| add_2 : Π (x y y') (r : relation y y'), relation (add x y) (add x y')
-- And one relation per axiom
| zero_add      : Π (x), relation (add zero x) x
| add_zero      : Π (x), relation (add x zero) x
| add_left_neg  : Π (x), relation (add (neg x) x) zero
| add_comm      : Π (x y), relation (add x y) (add y x)
| add_assoc     : Π (x y z), relation (add (add x y) z) (add x (add y z))

/--
The setoid corresponding to group expressions modulo abelian group relations and identifications.
-/
def colimit_setoid : setoid (prequotient F) :=
{ r := relation F, iseqv := ⟨relation.refl, relation.symm, relation.trans⟩ }
attribute [instance] colimit_setoid

/--
The underlying type of the colimit of a diagram in `AddCommGroup`.
-/
@[derive inhabited]
def colimit_type : Type v := quotient (colimit_setoid F)

instance : add_comm_group (colimit_type F) :=
{ zero :=
  begin
    exact quot.mk _ zero
  end,
  neg :=
  begin
    fapply @quot.lift,
    { intro x,
      exact quot.mk _ (neg x) },
    { intros x x' r,
      apply quot.sound,
      exact relation.neg_1 _ _ r },
  end,
  add :=
  begin
    fapply @quot.lift _ _ ((colimit_type F) → (colimit_type F)),
    { intro x,
      fapply @quot.lift,
      { intro y,
        exact quot.mk _ (add x y) },
      { intros y y' r,
        apply quot.sound,
        exact relation.add_2 _ _ _ r } },
    { intros x x' r,
      funext y,
      induction y,
      dsimp,
      apply quot.sound,
      { exact relation.add_1 _ _ _ r },
      { refl } },
  end,
  zero_add := λ x,
  begin
    induction x,
    dsimp,
    apply quot.sound,
    apply relation.zero_add,
    refl,
  end,
  add_zero := λ x,
  begin
    induction x,
    dsimp,
    apply quot.sound,
    apply relation.add_zero,
    refl,
  end,
  add_left_neg := λ x,
  begin
    induction x,
    dsimp,
    apply quot.sound,
    apply relation.add_left_neg,
    refl,
  end,
  add_comm := λ x y,
  begin
    induction x,
    induction y,
    dsimp,
    apply quot.sound,
    apply relation.add_comm,
    refl,
    refl,
  end,
  add_assoc := λ x y z,
  begin
    induction x,
    induction y,
    induction z,
    dsimp,
    apply quot.sound,
    apply relation.add_assoc,
    refl,
    refl,
    refl,
  end, }

@[simp] lemma quot_zero : quot.mk setoid.r zero = (0 : colimit_type F) := rfl
@[simp] lemma quot_neg (x) :
  quot.mk setoid.r (neg x) = (-(quot.mk setoid.r x) : colimit_type F) := rfl
@[simp] lemma quot_add (x y) :
  quot.mk setoid.r (add x y) = ((quot.mk setoid.r x) + (quot.mk setoid.r y) : colimit_type F) := rfl

/-- The bundled abelian group giving the colimit of a diagram. -/
def colimit : AddCommGroup := AddCommGroup.of (colimit_type F)

/-- The function from a given abelian group in the diagram to the colimit abelian group. -/
def cocone_fun (j : J) (x : F.obj j) : colimit_type F :=
quot.mk _ (of j x)

/-- The group homomorphism from a given abelian group in the diagram to the colimit abelian
group. -/
def cocone_morphism (j : J) : F.obj j ⟶ colimit F :=
{ to_fun := cocone_fun F j,
  map_zero' := by apply quot.sound; apply relation.zero,
  map_add' := by intros; apply quot.sound; apply relation.add }

@[simp] lemma cocone_naturality {j j' : J} (f : j ⟶ j') :
  F.map f ≫ (cocone_morphism F j') = cocone_morphism F j :=
begin
  ext,
  apply quot.sound,
  apply relation.map,
end

@[simp] lemma cocone_naturality_components (j j' : J) (f : j ⟶ j') (x : F.obj j):
  (cocone_morphism F j') (F.map f x) = (cocone_morphism F j) x :=
by { rw ←cocone_naturality F f, refl }

/-- The cocone over the proposed colimit abelian group. -/
def colimit_cocone : cocone F :=
{ X := colimit F,
  ι :=
  { app := cocone_morphism F } }.

/-- The function from the free abelian group on the diagram to the cone point of any other
cocone. -/
@[simp] def desc_fun_lift (s : cocone F) : prequotient F → s.X
| (of j x)  := (s.ι.app j) x
| zero      := 0
| (neg x)   := -(desc_fun_lift x)
| (add x y) := desc_fun_lift x + desc_fun_lift y

/-- The function from the colimit abelian group to the cone point of any other cocone. -/
def desc_fun (s : cocone F) : colimit_type F → s.X :=
begin
  fapply quot.lift,
  { exact desc_fun_lift F s },
  { intros x y r,
    induction r; try { dsimp },
    -- refl
    { refl },
    -- symm
    { exact r_ih.symm },
    -- trans
    { exact eq.trans r_ih_h r_ih_k },
    -- map
    { simp, },
    -- zero
    { simp, },
    -- neg
    { simp, },
    -- add
    { simp, },
    -- neg_1
    { rw r_ih, },
    -- add_1
    { rw r_ih, },
    -- add_2
    { rw r_ih, },
    -- zero_add
    { rw zero_add, },
    -- add_zero
    { rw add_zero, },
    -- add_left_neg
    { rw add_left_neg, },
    -- add_comm
    { rw add_comm, },
    -- add_assoc
    { rw add_assoc, },
  }
end

/-- The group homomorphism from the colimit abelian group to the cone point of any other cocone. -/
def desc_morphism (s : cocone F) : colimit F ⟶ s.X :=
{ to_fun := desc_fun F s,
  map_zero' := rfl,
  map_add' := λ x y, by { induction x; induction y; refl }, }

/-- Evidence that the proposed colimit is the colimit. -/
def colimit_cocone_is_colimit : is_colimit (colimit_cocone F) :=
{ desc := λ s, desc_morphism F s,
  uniq' := λ s m w,
  begin
    ext,
    induction x,
    induction x,
    { have w' := congr_fun (congr_arg (λ f : F.obj x_j ⟶ s.X, (f : F.obj x_j → s.X)) (w x_j)) x_x,
      erw w',
      refl, },
    { simp *, },
    { simp *, },
    { simp *, },
    refl
  end }.

instance has_colimits_AddCommGroup : has_colimits AddCommGroup :=
{ has_colimits_of_shape := λ J 𝒥, by exactI
  { has_colimit := λ F, has_colimit.mk
    { cocone := colimit_cocone F,
      is_colimit := colimit_cocone_is_colimit F } } }

end AddCommGroup.colimits

namespace AddCommGroup

open quotient_add_group

/--
The categorical cokernel of a morphism in `AddCommGroup`
agrees with the usual group-theoretical quotient.
-/
noncomputable def cokernel_iso_quotient {G H : AddCommGroup} (f : G ⟶ H) :
  cokernel f ≅ AddCommGroup.of (quotient (add_monoid_hom.range f)) :=
{ hom := cokernel.desc f (mk' _)
    (by { ext, apply quotient.sound, fsplit, exact -x,
          simp only [add_zero, add_monoid_hom.map_neg], }),
  inv := add_monoid_hom.of (quotient_add_group.lift _ (cokernel.π f)
    (by { intros x H_1, cases H_1, induction H_1_h,
          simp only [cokernel.condition_apply, zero_apply]})),
  -- obviously can take care of the next goals, but it is really slow
  hom_inv_id' := begin
    ext1, simp only [coequalizer_as_cokernel, category.comp_id, cokernel.π_desc_assoc], ext1, refl,
  end,
  inv_hom_id' := begin
    ext1, induction x,
    { simp only [colimit.ι_desc_apply, coe_id, add_monoid_hom.coe_of, lift_quot_mk,
                 cofork.of_π_ι_app, coe_comp], refl },
    { refl }
  end, }

end AddCommGroup


example (h : ∀ x, x + 1 = x.succ) : (x + 1) + 1 = 1 + (x + 1) := by
  rewrite [h]
  done