/-
Copyright (c) 2019 Patrick Massot. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Patrick Massot

! This file was ported from Lean 3 source module topology.uniform_space.abstract_completion
! leanprover-community/mathlib commit 34ee86e6a59d911a8e4f89b68793ee7577ae79c7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Topology.UniformSpace.UniformEmbedding
import Mathbin.Topology.UniformSpace.Equiv

/-!
# Abstract theory of Hausdorff completions of uniform spaces

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

This file characterizes Hausdorff completions of a uniform space α as complete Hausdorff spaces
equipped with a map from α which has dense image and induce the original uniform structure on α.
Assuming these properties we "extend" uniformly continuous maps from α to complete Hausdorff spaces
to the completions of α. This is the universal property expected from a completion.
It is then used to extend uniformly continuous maps from α to α' to maps between
completions of α and α'.

This file does not construct any such completion, it only study consequences of their existence.
The first advantage is that formal properties are clearly highlighted without interference from
construction details. The second advantage is that this framework can then be used to compare
different completion constructions. See `topology/uniform_space/compare_reals` for an example.
Of course the comparison comes from the universal property as usual.

A general explicit construction of completions is done in `uniform_space/completion`, leading
to a functor from uniform spaces to complete Hausdorff uniform spaces that is left adjoint to the
inclusion, see `uniform_space/UniformSpace` for the category packaging.

## Implementation notes

A tiny technical advantage of using a characteristic predicate such as the properties listed in
`abstract_completion` instead of stating the universal property is that the universal property
derived from the predicate is more universe polymorphic.

## References

We don't know any traditional text discussing this. Real world mathematics simply silently
identify the results of any two constructions that lead to something one could reasonably
call a completion.

## Tags

uniform spaces, completion, universal property
-/

noncomputable section

attribute [local instance] Classical.propDecidable

open Filter Set Function

universe u

#print AbstractCompletion /-
/-- A completion of `α` is the data of a complete separated uniform space (from the same universe)
and a map from `α` with dense range and inducing the original uniform structure on `α`. -/
structure AbstractCompletion (α : Type u) [UniformSpace α] where
  Space : Type u
  coe : α → space
  uniformStruct : UniformSpace space
  complete : CompleteSpace space
  separation : SeparatedSpace space
  UniformInducing : UniformInducing coe
  dense : DenseRange coe
#align abstract_completion AbstractCompletion
-/

attribute [local instance]
  AbstractCompletion.uniformStruct AbstractCompletion.complete AbstractCompletion.separation

namespace AbstractCompletion

variable {α : Type _} [UniformSpace α] (pkg : AbstractCompletion α)

-- mathport name: exprhatα
local notation "hatα" => pkg.Space

-- mathport name: exprι
local notation "ι" => pkg.coe

#print AbstractCompletion.ofComplete /-
/-- If `α` is complete, then it is an abstract completion of itself. -/
def ofComplete [SeparatedSpace α] [CompleteSpace α] : AbstractCompletion α :=
  mk α id inferInstance inferInstance inferInstance uniformInducing_id denseRange_id
#align abstract_completion.of_complete AbstractCompletion.ofComplete
-/

#print AbstractCompletion.closure_range /-
theorem closure_range : closure (range ι) = univ :=
  pkg.dense.closure_range
#align abstract_completion.closure_range AbstractCompletion.closure_range
-/

#print AbstractCompletion.denseInducing /-
theorem denseInducing : DenseInducing ι :=
  ⟨pkg.UniformInducing.Inducing, pkg.dense⟩
#align abstract_completion.dense_inducing AbstractCompletion.denseInducing
-/

#print AbstractCompletion.uniformContinuous_coe /-
theorem uniformContinuous_coe : UniformContinuous ι :=
  UniformInducing.uniformContinuous pkg.UniformInducing
#align abstract_completion.uniform_continuous_coe AbstractCompletion.uniformContinuous_coe
-/

#print AbstractCompletion.continuous_coe /-
theorem continuous_coe : Continuous ι :=
  pkg.uniformContinuous_coe.Continuous
#align abstract_completion.continuous_coe AbstractCompletion.continuous_coe
-/

#print AbstractCompletion.induction_on /-
@[elab_as_elim]
theorem induction_on {p : hatα → Prop} (a : hatα) (hp : IsClosed { a | p a }) (ih : ∀ a, p (ι a)) :
    p a :=
  isClosed_property pkg.dense hp ih a
#align abstract_completion.induction_on AbstractCompletion.induction_on
-/

variable {β : Type _}

#print AbstractCompletion.funext /-
protected theorem funext [TopologicalSpace β] [T2Space β] {f g : hatα → β} (hf : Continuous f)
    (hg : Continuous g) (h : ∀ a, f (ι a) = g (ι a)) : f = g :=
  funext fun a => pkg.inductionOn a (isClosed_eq hf hg) h
#align abstract_completion.funext AbstractCompletion.funext
-/

variable [UniformSpace β]

section Extend

#print AbstractCompletion.extend /-
/-- Extension of maps to completions -/
protected def extend (f : α → β) : hatα → β :=
  if UniformContinuous f then pkg.DenseInducing.extend f else fun x => f (pkg.dense.some x)
#align abstract_completion.extend AbstractCompletion.extend
-/

variable {f : α → β}

/- warning: abstract_completion.extend_def -> AbstractCompletion.extend_def is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] {f : α -> β}, (UniformContinuous.{u1, u2} α β _inst_1 _inst_2 f) -> (Eq.{max (succ u1) (succ u2)} ((AbstractCompletion.Space.{u1} α _inst_1 pkg) -> β) (AbstractCompletion.extend.{u1, u2} α _inst_1 pkg β _inst_2 f) (DenseInducing.extend.{u1, u1, u2} α (AbstractCompletion.Space.{u1} α _inst_1 pkg) β (UniformSpace.toTopologicalSpace.{u1} α _inst_1) (UniformSpace.toTopologicalSpace.{u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg)) (AbstractCompletion.coe.{u1} α _inst_1 pkg) (UniformSpace.toTopologicalSpace.{u2} β _inst_2) (AbstractCompletion.denseInducing.{u1} α _inst_1 pkg) f))
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : UniformSpace.{u2} α] (pkg : AbstractCompletion.{u2} α _inst_1) {β : Type.{u1}} [_inst_2 : UniformSpace.{u1} β] {f : α -> β}, (UniformContinuous.{u2, u1} α β _inst_1 _inst_2 f) -> (Eq.{max (succ u2) (succ u1)} ((AbstractCompletion.space.{u2} α _inst_1 pkg) -> β) (AbstractCompletion.extend.{u2, u1} α _inst_1 pkg β _inst_2 f) (DenseInducing.extend.{u2, u2, u1} α (AbstractCompletion.space.{u2} α _inst_1 pkg) β (UniformSpace.toTopologicalSpace.{u2} α _inst_1) (UniformSpace.toTopologicalSpace.{u2} (AbstractCompletion.space.{u2} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u2} α _inst_1 pkg)) (AbstractCompletion.coe.{u2} α _inst_1 pkg) (UniformSpace.toTopologicalSpace.{u1} β _inst_2) (AbstractCompletion.denseInducing.{u2} α _inst_1 pkg) f))
Case conversion may be inaccurate. Consider using '#align abstract_completion.extend_def AbstractCompletion.extend_defₓ'. -/
theorem extend_def (hf : UniformContinuous f) : pkg.extend f = pkg.DenseInducing.extend f :=
  if_pos hf
#align abstract_completion.extend_def AbstractCompletion.extend_def

#print AbstractCompletion.extend_coe /-
theorem extend_coe [T2Space β] (hf : UniformContinuous f) (a : α) : (pkg.extend f) (ι a) = f a :=
  by
  rw [pkg.extend_def hf]
  exact pkg.dense_inducing.extend_eq hf.continuous a
#align abstract_completion.extend_coe AbstractCompletion.extend_coe
-/

variable [CompleteSpace β]

/- warning: abstract_completion.uniform_continuous_extend -> AbstractCompletion.uniformContinuous_extend is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] {f : α -> β} [_inst_3 : CompleteSpace.{u2} β _inst_2], UniformContinuous.{u1, u2} (AbstractCompletion.Space.{u1} α _inst_1 pkg) β (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) _inst_2 (AbstractCompletion.extend.{u1, u2} α _inst_1 pkg β _inst_2 f)
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : UniformSpace.{u2} α] (pkg : AbstractCompletion.{u2} α _inst_1) {β : Type.{u1}} [_inst_2 : UniformSpace.{u1} β] {f : α -> β} [_inst_3 : CompleteSpace.{u1} β _inst_2], UniformContinuous.{u2, u1} (AbstractCompletion.space.{u2} α _inst_1 pkg) β (AbstractCompletion.uniformStruct.{u2} α _inst_1 pkg) _inst_2 (AbstractCompletion.extend.{u2, u1} α _inst_1 pkg β _inst_2 f)
Case conversion may be inaccurate. Consider using '#align abstract_completion.uniform_continuous_extend AbstractCompletion.uniformContinuous_extendₓ'. -/
theorem uniformContinuous_extend : UniformContinuous (pkg.extend f) :=
  by
  by_cases hf : UniformContinuous f
  · rw [pkg.extend_def hf]
    exact uniformContinuous_uniformly_extend pkg.uniform_inducing pkg.dense hf
  · change UniformContinuous (ite _ _ _)
    rw [if_neg hf]
    exact uniformContinuous_of_const fun a b => by congr
#align abstract_completion.uniform_continuous_extend AbstractCompletion.uniformContinuous_extend

/- warning: abstract_completion.continuous_extend -> AbstractCompletion.continuous_extend is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] {f : α -> β} [_inst_3 : CompleteSpace.{u2} β _inst_2], Continuous.{u1, u2} (AbstractCompletion.Space.{u1} α _inst_1 pkg) β (UniformSpace.toTopologicalSpace.{u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg)) (UniformSpace.toTopologicalSpace.{u2} β _inst_2) (AbstractCompletion.extend.{u1, u2} α _inst_1 pkg β _inst_2 f)
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : UniformSpace.{u2} α] (pkg : AbstractCompletion.{u2} α _inst_1) {β : Type.{u1}} [_inst_2 : UniformSpace.{u1} β] {f : α -> β} [_inst_3 : CompleteSpace.{u1} β _inst_2], Continuous.{u2, u1} (AbstractCompletion.space.{u2} α _inst_1 pkg) β (UniformSpace.toTopologicalSpace.{u2} (AbstractCompletion.space.{u2} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u2} α _inst_1 pkg)) (UniformSpace.toTopologicalSpace.{u1} β _inst_2) (AbstractCompletion.extend.{u2, u1} α _inst_1 pkg β _inst_2 f)
Case conversion may be inaccurate. Consider using '#align abstract_completion.continuous_extend AbstractCompletion.continuous_extendₓ'. -/
theorem continuous_extend : Continuous (pkg.extend f) :=
  pkg.uniformContinuous_extend.Continuous
#align abstract_completion.continuous_extend AbstractCompletion.continuous_extend

variable [SeparatedSpace β]

/- warning: abstract_completion.extend_unique -> AbstractCompletion.extend_unique is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] {f : α -> β} [_inst_3 : CompleteSpace.{u2} β _inst_2] [_inst_4 : SeparatedSpace.{u2} β _inst_2], (UniformContinuous.{u1, u2} α β _inst_1 _inst_2 f) -> (forall {g : (AbstractCompletion.Space.{u1} α _inst_1 pkg) -> β}, (UniformContinuous.{u1, u2} (AbstractCompletion.Space.{u1} α _inst_1 pkg) β (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) _inst_2 g) -> (forall (a : α), Eq.{succ u2} β (f a) (g (AbstractCompletion.coe.{u1} α _inst_1 pkg a))) -> (Eq.{max (succ u1) (succ u2)} ((AbstractCompletion.Space.{u1} α _inst_1 pkg) -> β) (AbstractCompletion.extend.{u1, u2} α _inst_1 pkg β _inst_2 f) g))
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : UniformSpace.{u2} α] (pkg : AbstractCompletion.{u2} α _inst_1) {β : Type.{u1}} [_inst_2 : UniformSpace.{u1} β] {f : α -> β} [_inst_3 : CompleteSpace.{u1} β _inst_2] [_inst_4 : SeparatedSpace.{u1} β _inst_2], (UniformContinuous.{u2, u1} α β _inst_1 _inst_2 f) -> (forall {g : (AbstractCompletion.space.{u2} α _inst_1 pkg) -> β}, (UniformContinuous.{u2, u1} (AbstractCompletion.space.{u2} α _inst_1 pkg) β (AbstractCompletion.uniformStruct.{u2} α _inst_1 pkg) _inst_2 g) -> (forall (a : α), Eq.{succ u1} β (f a) (g (AbstractCompletion.coe.{u2} α _inst_1 pkg a))) -> (Eq.{max (succ u2) (succ u1)} ((AbstractCompletion.space.{u2} α _inst_1 pkg) -> β) (AbstractCompletion.extend.{u2, u1} α _inst_1 pkg β _inst_2 f) g))
Case conversion may be inaccurate. Consider using '#align abstract_completion.extend_unique AbstractCompletion.extend_uniqueₓ'. -/
theorem extend_unique (hf : UniformContinuous f) {g : hatα → β} (hg : UniformContinuous g)
    (h : ∀ a : α, f a = g (ι a)) : pkg.extend f = g :=
  by
  apply pkg.funext pkg.continuous_extend hg.continuous
  simpa only [pkg.extend_coe hf] using h
#align abstract_completion.extend_unique AbstractCompletion.extend_unique

/- warning: abstract_completion.extend_comp_coe -> AbstractCompletion.extend_comp_coe is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] [_inst_3 : CompleteSpace.{u2} β _inst_2] [_inst_4 : SeparatedSpace.{u2} β _inst_2] {f : (AbstractCompletion.Space.{u1} α _inst_1 pkg) -> β}, (UniformContinuous.{u1, u2} (AbstractCompletion.Space.{u1} α _inst_1 pkg) β (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) _inst_2 f) -> (Eq.{max (succ u1) (succ u2)} ((AbstractCompletion.Space.{u1} α _inst_1 pkg) -> β) (AbstractCompletion.extend.{u1, u2} α _inst_1 pkg β _inst_2 (Function.comp.{succ u1, succ u1, succ u2} α (AbstractCompletion.Space.{u1} α _inst_1 pkg) β f (AbstractCompletion.coe.{u1} α _inst_1 pkg))) f)
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : UniformSpace.{u2} α] (pkg : AbstractCompletion.{u2} α _inst_1) {β : Type.{u1}} [_inst_2 : UniformSpace.{u1} β] [_inst_3 : CompleteSpace.{u1} β _inst_2] [_inst_4 : SeparatedSpace.{u1} β _inst_2] {f : (AbstractCompletion.space.{u2} α _inst_1 pkg) -> β}, (UniformContinuous.{u2, u1} (AbstractCompletion.space.{u2} α _inst_1 pkg) β (AbstractCompletion.uniformStruct.{u2} α _inst_1 pkg) _inst_2 f) -> (Eq.{max (succ u2) (succ u1)} ((AbstractCompletion.space.{u2} α _inst_1 pkg) -> β) (AbstractCompletion.extend.{u2, u1} α _inst_1 pkg β _inst_2 (Function.comp.{succ u2, succ u2, succ u1} α (AbstractCompletion.space.{u2} α _inst_1 pkg) β f (AbstractCompletion.coe.{u2} α _inst_1 pkg))) f)
Case conversion may be inaccurate. Consider using '#align abstract_completion.extend_comp_coe AbstractCompletion.extend_comp_coeₓ'. -/
@[simp]
theorem extend_comp_coe {f : hatα → β} (hf : UniformContinuous f) : pkg.extend (f ∘ ι) = f :=
  funext fun x =>
    pkg.inductionOn x (isClosed_eq pkg.continuous_extend hf.Continuous) fun y =>
      pkg.extend_coe (hf.comp <| pkg.uniformContinuous_coe) y
#align abstract_completion.extend_comp_coe AbstractCompletion.extend_comp_coe

end Extend

section MapSec

variable (pkg' : AbstractCompletion β)

-- mathport name: exprhatβ
local notation "hatβ" => pkg'.Space

-- mathport name: exprι'
local notation "ι'" => pkg'.coe

#print AbstractCompletion.map /-
/-- Lifting maps to completions -/
protected def map (f : α → β) : hatα → hatβ :=
  pkg.extend (ι' ∘ f)
#align abstract_completion.map AbstractCompletion.map
-/

-- mathport name: exprmap
local notation "map" => pkg.map pkg'

variable (f : α → β)

/- warning: abstract_completion.uniform_continuous_map -> AbstractCompletion.uniformContinuous_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) (f : α -> β), UniformContinuous.{u1, u2} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.Space.{u2} β _inst_2 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u2} β _inst_2 pkg') (AbstractCompletion.map.{u1, u2} α _inst_1 pkg β _inst_2 pkg' f)
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : UniformSpace.{u2} α] (pkg : AbstractCompletion.{u2} α _inst_1) {β : Type.{u1}} [_inst_2 : UniformSpace.{u1} β] (pkg' : AbstractCompletion.{u1} β _inst_2) (f : α -> β), UniformContinuous.{u2, u1} (AbstractCompletion.space.{u2} α _inst_1 pkg) (AbstractCompletion.space.{u1} β _inst_2 pkg') (AbstractCompletion.uniformStruct.{u2} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} β _inst_2 pkg') (AbstractCompletion.map.{u2, u1} α _inst_1 pkg β _inst_2 pkg' f)
Case conversion may be inaccurate. Consider using '#align abstract_completion.uniform_continuous_map AbstractCompletion.uniformContinuous_mapₓ'. -/
theorem uniformContinuous_map : UniformContinuous (map f) :=
  pkg.uniformContinuous_extend
#align abstract_completion.uniform_continuous_map AbstractCompletion.uniformContinuous_map

/- warning: abstract_completion.continuous_map -> AbstractCompletion.continuous_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) (f : α -> β), Continuous.{u1, u2} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.Space.{u2} β _inst_2 pkg') (UniformSpace.toTopologicalSpace.{u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg)) (UniformSpace.toTopologicalSpace.{u2} (AbstractCompletion.Space.{u2} β _inst_2 pkg') (AbstractCompletion.uniformStruct.{u2} β _inst_2 pkg')) (AbstractCompletion.map.{u1, u2} α _inst_1 pkg β _inst_2 pkg' f)
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : UniformSpace.{u2} α] (pkg : AbstractCompletion.{u2} α _inst_1) {β : Type.{u1}} [_inst_2 : UniformSpace.{u1} β] (pkg' : AbstractCompletion.{u1} β _inst_2) (f : α -> β), Continuous.{u2, u1} (AbstractCompletion.space.{u2} α _inst_1 pkg) (AbstractCompletion.space.{u1} β _inst_2 pkg') (UniformSpace.toTopologicalSpace.{u2} (AbstractCompletion.space.{u2} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u2} α _inst_1 pkg)) (UniformSpace.toTopologicalSpace.{u1} (AbstractCompletion.space.{u1} β _inst_2 pkg') (AbstractCompletion.uniformStruct.{u1} β _inst_2 pkg')) (AbstractCompletion.map.{u2, u1} α _inst_1 pkg β _inst_2 pkg' f)
Case conversion may be inaccurate. Consider using '#align abstract_completion.continuous_map AbstractCompletion.continuous_mapₓ'. -/
theorem continuous_map : Continuous (map f) :=
  pkg.continuous_extend
#align abstract_completion.continuous_map AbstractCompletion.continuous_map

variable {f}

/- warning: abstract_completion.map_coe -> AbstractCompletion.map_coe is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {f : α -> β}, (UniformContinuous.{u1, u2} α β _inst_1 _inst_2 f) -> (forall (a : α), Eq.{succ u2} (AbstractCompletion.Space.{u2} β _inst_2 pkg') (AbstractCompletion.map.{u1, u2} α _inst_1 pkg β _inst_2 pkg' f (AbstractCompletion.coe.{u1} α _inst_1 pkg a)) (AbstractCompletion.coe.{u2} β _inst_2 pkg' (f a)))
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : UniformSpace.{u2} α] (pkg : AbstractCompletion.{u2} α _inst_1) {β : Type.{u1}} [_inst_2 : UniformSpace.{u1} β] (pkg' : AbstractCompletion.{u1} β _inst_2) {f : α -> β}, (UniformContinuous.{u2, u1} α β _inst_1 _inst_2 f) -> (forall (a : α), Eq.{succ u1} (AbstractCompletion.space.{u1} β _inst_2 pkg') (AbstractCompletion.map.{u2, u1} α _inst_1 pkg β _inst_2 pkg' f (AbstractCompletion.coe.{u2} α _inst_1 pkg a)) (AbstractCompletion.coe.{u1} β _inst_2 pkg' (f a)))
Case conversion may be inaccurate. Consider using '#align abstract_completion.map_coe AbstractCompletion.map_coeₓ'. -/
@[simp]
theorem map_coe (hf : UniformContinuous f) (a : α) : map f (ι a) = ι' (f a) :=
  pkg.extend_coe (pkg'.uniformContinuous_coe.comp hf) a
#align abstract_completion.map_coe AbstractCompletion.map_coe

/- warning: abstract_completion.map_unique -> AbstractCompletion.map_unique is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {f : α -> β} {g : (AbstractCompletion.Space.{u1} α _inst_1 pkg) -> (AbstractCompletion.Space.{u2} β _inst_2 pkg')}, (UniformContinuous.{u1, u2} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.Space.{u2} β _inst_2 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u2} β _inst_2 pkg') g) -> (forall (a : α), Eq.{succ u2} (AbstractCompletion.Space.{u2} β _inst_2 pkg') (AbstractCompletion.coe.{u2} β _inst_2 pkg' (f a)) (g (AbstractCompletion.coe.{u1} α _inst_1 pkg a))) -> (Eq.{max (succ u1) (succ u2)} ((AbstractCompletion.Space.{u1} α _inst_1 pkg) -> (AbstractCompletion.Space.{u2} β _inst_2 pkg')) (AbstractCompletion.map.{u1, u2} α _inst_1 pkg β _inst_2 pkg' f) g)
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : UniformSpace.{u2} α] (pkg : AbstractCompletion.{u2} α _inst_1) {β : Type.{u1}} [_inst_2 : UniformSpace.{u1} β] (pkg' : AbstractCompletion.{u1} β _inst_2) {f : α -> β} {g : (AbstractCompletion.space.{u2} α _inst_1 pkg) -> (AbstractCompletion.space.{u1} β _inst_2 pkg')}, (UniformContinuous.{u2, u1} (AbstractCompletion.space.{u2} α _inst_1 pkg) (AbstractCompletion.space.{u1} β _inst_2 pkg') (AbstractCompletion.uniformStruct.{u2} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} β _inst_2 pkg') g) -> (forall (a : α), Eq.{succ u1} (AbstractCompletion.space.{u1} β _inst_2 pkg') (AbstractCompletion.coe.{u1} β _inst_2 pkg' (f a)) (g (AbstractCompletion.coe.{u2} α _inst_1 pkg a))) -> (Eq.{max (succ u2) (succ u1)} ((AbstractCompletion.space.{u2} α _inst_1 pkg) -> (AbstractCompletion.space.{u1} β _inst_2 pkg')) (AbstractCompletion.map.{u2, u1} α _inst_1 pkg β _inst_2 pkg' f) g)
Case conversion may be inaccurate. Consider using '#align abstract_completion.map_unique AbstractCompletion.map_uniqueₓ'. -/
theorem map_unique {f : α → β} {g : hatα → hatβ} (hg : UniformContinuous g)
    (h : ∀ a, ι' (f a) = g (ι a)) : map f = g :=
  pkg.funext (pkg.continuous_map _ _) hg.Continuous <|
    by
    intro a
    change pkg.extend (ι' ∘ f) _ = _
    simp only [(· ∘ ·), h]
    rw [pkg.extend_coe (hg.comp pkg.uniform_continuous_coe)]
#align abstract_completion.map_unique AbstractCompletion.map_unique

#print AbstractCompletion.map_id /-
@[simp]
theorem map_id : pkg.map pkg id = id :=
  pkg.map_unique pkg uniformContinuous_id fun a => rfl
#align abstract_completion.map_id AbstractCompletion.map_id
-/

variable {γ : Type _} [UniformSpace γ]

#print AbstractCompletion.extend_map /-
theorem extend_map [CompleteSpace γ] [SeparatedSpace γ] {f : β → γ} {g : α → β}
    (hf : UniformContinuous f) (hg : UniformContinuous g) :
    pkg'.extend f ∘ map g = pkg.extend (f ∘ g) :=
  pkg.funext (pkg'.continuous_extend.comp (pkg.continuous_map pkg' _)) pkg.continuous_extend
    fun a => by rw [pkg.extend_coe (hf.comp hg), comp_app, pkg.map_coe pkg' hg, pkg'.extend_coe hf]
#align abstract_completion.extend_map AbstractCompletion.extend_map
-/

variable (pkg'' : AbstractCompletion γ)

/- warning: abstract_completion.map_comp -> AbstractCompletion.map_comp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {γ : Type.{u3}} [_inst_3 : UniformSpace.{u3} γ] (pkg'' : AbstractCompletion.{u3} γ _inst_3) {g : β -> γ} {f : α -> β}, (UniformContinuous.{u2, u3} β γ _inst_2 _inst_3 g) -> (UniformContinuous.{u1, u2} α β _inst_1 _inst_2 f) -> (Eq.{max (succ u1) (succ u3)} ((AbstractCompletion.Space.{u1} α _inst_1 pkg) -> (AbstractCompletion.Space.{u3} γ _inst_3 pkg'')) (Function.comp.{succ u1, succ u2, succ u3} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.Space.{u2} β _inst_2 pkg') (AbstractCompletion.Space.{u3} γ _inst_3 pkg'') (AbstractCompletion.map.{u2, u3} β _inst_2 pkg' γ _inst_3 pkg'' g) (AbstractCompletion.map.{u1, u2} α _inst_1 pkg β _inst_2 pkg' f)) (AbstractCompletion.map.{u1, u3} α _inst_1 pkg γ _inst_3 pkg'' (Function.comp.{succ u1, succ u2, succ u3} α β γ g f)))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u3}} [_inst_2 : UniformSpace.{u3} β] (pkg' : AbstractCompletion.{u3} β _inst_2) {γ : Type.{u2}} [_inst_3 : UniformSpace.{u2} γ] (pkg'' : AbstractCompletion.{u2} γ _inst_3) {g : β -> γ} {f : α -> β}, (UniformContinuous.{u3, u2} β γ _inst_2 _inst_3 g) -> (UniformContinuous.{u1, u3} α β _inst_1 _inst_2 f) -> (Eq.{max (succ u1) (succ u2)} ((AbstractCompletion.space.{u1} α _inst_1 pkg) -> (AbstractCompletion.space.{u2} γ _inst_3 pkg'')) (Function.comp.{succ u1, succ u3, succ u2} (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.space.{u3} β _inst_2 pkg') (AbstractCompletion.space.{u2} γ _inst_3 pkg'') (AbstractCompletion.map.{u3, u2} β _inst_2 pkg' γ _inst_3 pkg'' g) (AbstractCompletion.map.{u1, u3} α _inst_1 pkg β _inst_2 pkg' f)) (AbstractCompletion.map.{u1, u2} α _inst_1 pkg γ _inst_3 pkg'' (Function.comp.{succ u1, succ u3, succ u2} α β γ g f)))
Case conversion may be inaccurate. Consider using '#align abstract_completion.map_comp AbstractCompletion.map_compₓ'. -/
theorem map_comp {g : β → γ} {f : α → β} (hg : UniformContinuous g) (hf : UniformContinuous f) :
    pkg'.map pkg'' g ∘ pkg.map pkg' f = pkg.map pkg'' (g ∘ f) :=
  pkg.extend_map pkg' (pkg''.uniformContinuous_coe.comp hg) hf
#align abstract_completion.map_comp AbstractCompletion.map_comp

end MapSec

section Compare

-- We can now compare two completion packages for the same uniform space
variable (pkg' : AbstractCompletion α)

#print AbstractCompletion.compare /-
/-- The comparison map between two completions of the same uniform space. -/
def compare : pkg.Space → pkg'.Space :=
  pkg.extend pkg'.coe
#align abstract_completion.compare AbstractCompletion.compare
-/

#print AbstractCompletion.uniformContinuous_compare /-
theorem uniformContinuous_compare : UniformContinuous (pkg.compare pkg') :=
  pkg.uniformContinuous_extend
#align abstract_completion.uniform_continuous_compare AbstractCompletion.uniformContinuous_compare
-/

#print AbstractCompletion.compare_coe /-
theorem compare_coe (a : α) : pkg.compare pkg' (pkg.coe a) = pkg'.coe a :=
  pkg.extend_coe pkg'.uniformContinuous_coe a
#align abstract_completion.compare_coe AbstractCompletion.compare_coe
-/

#print AbstractCompletion.inverse_compare /-
theorem inverse_compare : pkg.compare pkg' ∘ pkg'.compare pkg = id :=
  by
  have uc := pkg.uniform_continuous_compare pkg'
  have uc' := pkg'.uniform_continuous_compare pkg
  apply pkg'.funext (uc.comp uc').Continuous continuous_id
  intro a
  rw [comp_app, pkg'.compare_coe pkg, pkg.compare_coe pkg']
  rfl
#align abstract_completion.inverse_compare AbstractCompletion.inverse_compare
-/

#print AbstractCompletion.compareEquiv /-
/-- The uniform bijection between two completions of the same uniform space. -/
def compareEquiv : pkg.Space ≃ᵤ pkg'.Space
    where
  toFun := pkg.compare pkg'
  invFun := pkg'.compare pkg
  left_inv := congr_fun (pkg'.inverse_compare pkg)
  right_inv := congr_fun (pkg.inverse_compare pkg')
  uniformContinuous_toFun := uniformContinuous_compare _ _
  uniformContinuous_invFun := uniformContinuous_compare _ _
#align abstract_completion.compare_equiv AbstractCompletion.compareEquiv
-/

/- warning: abstract_completion.uniform_continuous_compare_equiv -> AbstractCompletion.uniformContinuous_compareEquiv is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) (pkg' : AbstractCompletion.{u1} α _inst_1), UniformContinuous.{u1, u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.Space.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (coeFn.{succ u1, succ u1} (UniformEquiv.{u1, u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.Space.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg')) (fun (_x : UniformEquiv.{u1, u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.Space.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg')) => (AbstractCompletion.Space.{u1} α _inst_1 pkg) -> (AbstractCompletion.Space.{u1} α _inst_1 pkg')) (UniformEquiv.hasCoeToFun.{u1, u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.Space.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg')) (AbstractCompletion.compareEquiv.{u1} α _inst_1 pkg pkg'))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) (pkg' : AbstractCompletion.{u1} α _inst_1), UniformContinuous.{u1, u1} (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (FunLike.coe.{succ u1, succ u1, succ u1} (UniformEquiv.{u1, u1} (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg')) (AbstractCompletion.space.{u1} α _inst_1 pkg) (fun (_x : AbstractCompletion.space.{u1} α _inst_1 pkg) => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : AbstractCompletion.space.{u1} α _inst_1 pkg) => AbstractCompletion.space.{u1} α _inst_1 pkg') _x) (EmbeddingLike.toFunLike.{succ u1, succ u1, succ u1} (UniformEquiv.{u1, u1} (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg')) (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.space.{u1} α _inst_1 pkg') (EquivLike.toEmbeddingLike.{succ u1, succ u1, succ u1} (UniformEquiv.{u1, u1} (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg')) (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.space.{u1} α _inst_1 pkg') (UniformEquiv.instEquivLikeUniformEquiv.{u1, u1} (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg')))) (AbstractCompletion.compareEquiv.{u1} α _inst_1 pkg pkg'))
Case conversion may be inaccurate. Consider using '#align abstract_completion.uniform_continuous_compare_equiv AbstractCompletion.uniformContinuous_compareEquivₓ'. -/
theorem uniformContinuous_compareEquiv : UniformContinuous (pkg.compareEquiv pkg') :=
  pkg.uniformContinuous_compare pkg'
#align abstract_completion.uniform_continuous_compare_equiv AbstractCompletion.uniformContinuous_compareEquiv

/- warning: abstract_completion.uniform_continuous_compare_equiv_symm -> AbstractCompletion.uniformContinuous_compareEquiv_symm is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) (pkg' : AbstractCompletion.{u1} α _inst_1), UniformContinuous.{u1, u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg') (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (coeFn.{succ u1, succ u1} (UniformEquiv.{u1, u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg') (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg)) (fun (_x : UniformEquiv.{u1, u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg') (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg)) => (AbstractCompletion.Space.{u1} α _inst_1 pkg') -> (AbstractCompletion.Space.{u1} α _inst_1 pkg)) (UniformEquiv.hasCoeToFun.{u1, u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg') (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg)) (UniformEquiv.symm.{u1, u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.Space.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (AbstractCompletion.compareEquiv.{u1} α _inst_1 pkg pkg')))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) (pkg' : AbstractCompletion.{u1} α _inst_1), UniformContinuous.{u1, u1} (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (FunLike.coe.{succ u1, succ u1, succ u1} (UniformEquiv.{u1, u1} (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg)) (AbstractCompletion.space.{u1} α _inst_1 pkg') (fun (_x : AbstractCompletion.space.{u1} α _inst_1 pkg') => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : AbstractCompletion.space.{u1} α _inst_1 pkg') => AbstractCompletion.space.{u1} α _inst_1 pkg) _x) (EmbeddingLike.toFunLike.{succ u1, succ u1, succ u1} (UniformEquiv.{u1, u1} (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg)) (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.space.{u1} α _inst_1 pkg) (EquivLike.toEmbeddingLike.{succ u1, succ u1, succ u1} (UniformEquiv.{u1, u1} (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg)) (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.space.{u1} α _inst_1 pkg) (UniformEquiv.instEquivLikeUniformEquiv.{u1, u1} (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg)))) (UniformEquiv.symm.{u1, u1} (AbstractCompletion.space.{u1} α _inst_1 pkg) (AbstractCompletion.space.{u1} α _inst_1 pkg') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg') (AbstractCompletion.compareEquiv.{u1} α _inst_1 pkg pkg')))
Case conversion may be inaccurate. Consider using '#align abstract_completion.uniform_continuous_compare_equiv_symm AbstractCompletion.uniformContinuous_compareEquiv_symmₓ'. -/
theorem uniformContinuous_compareEquiv_symm : UniformContinuous (pkg.compareEquiv pkg').symm :=
  pkg'.uniformContinuous_compare pkg
#align abstract_completion.uniform_continuous_compare_equiv_symm AbstractCompletion.uniformContinuous_compareEquiv_symm

end Compare

section Prod

variable (pkg' : AbstractCompletion β)

-- mathport name: exprhatβ
local notation "hatβ" => pkg'.Space

-- mathport name: exprι'
local notation "ι'" => pkg'.coe

/- warning: abstract_completion.prod -> AbstractCompletion.prod is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α], (AbstractCompletion.{u1} α _inst_1) -> (forall {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β], (AbstractCompletion.{u2} β _inst_2) -> (AbstractCompletion.{max u1 u2} (Prod.{u1, u2} α β) (Prod.uniformSpace.{u1, u2} α β _inst_1 _inst_2)))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α], (AbstractCompletion.{u1} α _inst_1) -> (forall {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β], (AbstractCompletion.{u2} β _inst_2) -> (AbstractCompletion.{max u2 u1} (Prod.{u1, u2} α β) (instUniformSpaceProd.{u1, u2} α β _inst_1 _inst_2)))
Case conversion may be inaccurate. Consider using '#align abstract_completion.prod AbstractCompletion.prodₓ'. -/
/-- Products of completions -/
protected def prod : AbstractCompletion (α × β)
    where
  Space := hatα × hatβ
  coe p := ⟨ι p.1, ι' p.2⟩
  uniformStruct := Prod.uniformSpace
  complete := by infer_instance
  separation := by infer_instance
  UniformInducing := UniformInducing.prod pkg.UniformInducing pkg'.UniformInducing
  dense := pkg.dense.Prod_map pkg'.dense
#align abstract_completion.prod AbstractCompletion.prod

end Prod

section Extension₂

variable (pkg' : AbstractCompletion β)

-- mathport name: exprhatβ
local notation "hatβ" => pkg'.Space

-- mathport name: exprι'
local notation "ι'" => pkg'.coe

variable {γ : Type _} [UniformSpace γ]

open Function

#print AbstractCompletion.extend₂ /-
/-- Extend two variable map to completions. -/
protected def extend₂ (f : α → β → γ) : hatα → hatβ → γ :=
  curry <| (pkg.Prod pkg').extend (uncurry f)
#align abstract_completion.extend₂ AbstractCompletion.extend₂
-/

section SeparatedSpace

variable [SeparatedSpace γ] {f : α → β → γ}

/- warning: abstract_completion.extension₂_coe_coe -> AbstractCompletion.extension₂_coe_coe is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {γ : Type.{u3}} [_inst_3 : UniformSpace.{u3} γ] [_inst_4 : SeparatedSpace.{u3} γ _inst_3] {f : α -> β -> γ}, (UniformContinuous.{max u1 u2, u3} (Prod.{u1, u2} α β) γ (Prod.uniformSpace.{u1, u2} α β _inst_1 _inst_2) _inst_3 (Function.uncurry.{u1, u2, u3} α β γ f)) -> (forall (a : α) (b : β), Eq.{succ u3} γ (AbstractCompletion.extend₂.{u1, u2, u3} α _inst_1 pkg β _inst_2 pkg' γ _inst_3 f (AbstractCompletion.coe.{u1} α _inst_1 pkg a) (AbstractCompletion.coe.{u2} β _inst_2 pkg' b)) (f a b))
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : UniformSpace.{u2} α] (pkg : AbstractCompletion.{u2} α _inst_1) {β : Type.{u3}} [_inst_2 : UniformSpace.{u3} β] (pkg' : AbstractCompletion.{u3} β _inst_2) {γ : Type.{u1}} [_inst_3 : UniformSpace.{u1} γ] [_inst_4 : SeparatedSpace.{u1} γ _inst_3] {f : α -> β -> γ}, (UniformContinuous.{max u3 u2, u1} (Prod.{u2, u3} α β) γ (instUniformSpaceProd.{u2, u3} α β _inst_1 _inst_2) _inst_3 (Function.uncurry.{u2, u3, u1} α β γ f)) -> (forall (a : α) (b : β), Eq.{succ u1} γ (AbstractCompletion.extend₂.{u2, u3, u1} α _inst_1 pkg β _inst_2 pkg' γ _inst_3 f (AbstractCompletion.coe.{u2} α _inst_1 pkg a) (AbstractCompletion.coe.{u3} β _inst_2 pkg' b)) (f a b))
Case conversion may be inaccurate. Consider using '#align abstract_completion.extension₂_coe_coe AbstractCompletion.extension₂_coe_coeₓ'. -/
theorem extension₂_coe_coe (hf : UniformContinuous <| uncurry f) (a : α) (b : β) :
    pkg.extend₂ pkg' f (ι a) (ι' b) = f a b :=
  show (pkg.Prod pkg').extend (uncurry f) ((pkg.Prod pkg').coe (a, b)) = uncurry f (a, b) from
    (pkg.Prod pkg').extend_coe hf _
#align abstract_completion.extension₂_coe_coe AbstractCompletion.extension₂_coe_coe

end SeparatedSpace

variable {f : α → β → γ}

variable [CompleteSpace γ] (f)

/- warning: abstract_completion.uniform_continuous_extension₂ -> AbstractCompletion.uniformContinuous_extension₂ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {γ : Type.{u3}} [_inst_3 : UniformSpace.{u3} γ] (f : α -> β -> γ) [_inst_4 : CompleteSpace.{u3} γ _inst_3], UniformContinuous₂.{u1, u2, u3} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.Space.{u2} β _inst_2 pkg') γ (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u2} β _inst_2 pkg') _inst_3 (AbstractCompletion.extend₂.{u1, u2, u3} α _inst_1 pkg β _inst_2 pkg' γ _inst_3 f)
but is expected to have type
  forall {α : Type.{u3}} [_inst_1 : UniformSpace.{u3} α] (pkg : AbstractCompletion.{u3} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {γ : Type.{u1}} [_inst_3 : UniformSpace.{u1} γ] (f : α -> β -> γ) [_inst_4 : CompleteSpace.{u1} γ _inst_3], UniformContinuous₂.{u3, u2, u1} (AbstractCompletion.space.{u3} α _inst_1 pkg) (AbstractCompletion.space.{u2} β _inst_2 pkg') γ (AbstractCompletion.uniformStruct.{u3} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u2} β _inst_2 pkg') _inst_3 (AbstractCompletion.extend₂.{u3, u2, u1} α _inst_1 pkg β _inst_2 pkg' γ _inst_3 f)
Case conversion may be inaccurate. Consider using '#align abstract_completion.uniform_continuous_extension₂ AbstractCompletion.uniformContinuous_extension₂ₓ'. -/
theorem uniformContinuous_extension₂ : UniformContinuous₂ (pkg.extend₂ pkg' f) :=
  by
  rw [uniformContinuous₂_def, AbstractCompletion.extend₂, uncurry_curry]
  apply uniform_continuous_extend
#align abstract_completion.uniform_continuous_extension₂ AbstractCompletion.uniformContinuous_extension₂

end Extension₂

section Map₂

variable (pkg' : AbstractCompletion β)

-- mathport name: exprhatβ
local notation "hatβ" => pkg'.Space

-- mathport name: exprι'
local notation "ι'" => pkg'.coe

variable {γ : Type _} [UniformSpace γ] (pkg'' : AbstractCompletion γ)

-- mathport name: exprhatγ
local notation "hatγ" => pkg''.Space

-- mathport name: exprι''
local notation "ι''" => pkg''.coe

-- mathport name: «expr ∘₂ »
local notation f " ∘₂ " g => bicompr f g

#print AbstractCompletion.map₂ /-
/-- Lift two variable maps to completions. -/
protected def map₂ (f : α → β → γ) : hatα → hatβ → hatγ :=
  pkg.extend₂ pkg' (pkg''.coe ∘₂ f)
#align abstract_completion.map₂ AbstractCompletion.map₂
-/

/- warning: abstract_completion.uniform_continuous_map₂ -> AbstractCompletion.uniformContinuous_map₂ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {γ : Type.{u3}} [_inst_3 : UniformSpace.{u3} γ] (pkg'' : AbstractCompletion.{u3} γ _inst_3) (f : α -> β -> γ), UniformContinuous₂.{u1, u2, u3} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.Space.{u2} β _inst_2 pkg') (AbstractCompletion.Space.{u3} γ _inst_3 pkg'') (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u2} β _inst_2 pkg') (AbstractCompletion.uniformStruct.{u3} γ _inst_3 pkg'') (AbstractCompletion.map₂.{u1, u2, u3} α _inst_1 pkg β _inst_2 pkg' γ _inst_3 pkg'' f)
but is expected to have type
  forall {α : Type.{u3}} [_inst_1 : UniformSpace.{u3} α] (pkg : AbstractCompletion.{u3} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {γ : Type.{u1}} [_inst_3 : UniformSpace.{u1} γ] (pkg'' : AbstractCompletion.{u1} γ _inst_3) (f : α -> β -> γ), UniformContinuous₂.{u3, u2, u1} (AbstractCompletion.space.{u3} α _inst_1 pkg) (AbstractCompletion.space.{u2} β _inst_2 pkg') (AbstractCompletion.space.{u1} γ _inst_3 pkg'') (AbstractCompletion.uniformStruct.{u3} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u2} β _inst_2 pkg') (AbstractCompletion.uniformStruct.{u1} γ _inst_3 pkg'') (AbstractCompletion.map₂.{u3, u2, u1} α _inst_1 pkg β _inst_2 pkg' γ _inst_3 pkg'' f)
Case conversion may be inaccurate. Consider using '#align abstract_completion.uniform_continuous_map₂ AbstractCompletion.uniformContinuous_map₂ₓ'. -/
theorem uniformContinuous_map₂ (f : α → β → γ) : UniformContinuous₂ (pkg.zipWith pkg' pkg'' f) :=
  pkg.uniformContinuous_extension₂ pkg' _
#align abstract_completion.uniform_continuous_map₂ AbstractCompletion.uniformContinuous_map₂

/- warning: abstract_completion.continuous_map₂ -> AbstractCompletion.continuous_map₂ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {γ : Type.{u3}} [_inst_3 : UniformSpace.{u3} γ] (pkg'' : AbstractCompletion.{u3} γ _inst_3) {δ : Type.{u4}} [_inst_4 : TopologicalSpace.{u4} δ] {f : α -> β -> γ} {a : δ -> (AbstractCompletion.Space.{u1} α _inst_1 pkg)} {b : δ -> (AbstractCompletion.Space.{u2} β _inst_2 pkg')}, (Continuous.{u4, u1} δ (AbstractCompletion.Space.{u1} α _inst_1 pkg) _inst_4 (UniformSpace.toTopologicalSpace.{u1} (AbstractCompletion.Space.{u1} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u1} α _inst_1 pkg)) a) -> (Continuous.{u4, u2} δ (AbstractCompletion.Space.{u2} β _inst_2 pkg') _inst_4 (UniformSpace.toTopologicalSpace.{u2} (AbstractCompletion.Space.{u2} β _inst_2 pkg') (AbstractCompletion.uniformStruct.{u2} β _inst_2 pkg')) b) -> (Continuous.{u4, u3} δ (AbstractCompletion.Space.{u3} γ _inst_3 pkg'') _inst_4 (UniformSpace.toTopologicalSpace.{u3} (AbstractCompletion.Space.{u3} γ _inst_3 pkg'') (AbstractCompletion.uniformStruct.{u3} γ _inst_3 pkg'')) (fun (d : δ) => AbstractCompletion.map₂.{u1, u2, u3} α _inst_1 pkg β _inst_2 pkg' γ _inst_3 pkg'' f (a d) (b d)))
but is expected to have type
  forall {α : Type.{u3}} [_inst_1 : UniformSpace.{u3} α] (pkg : AbstractCompletion.{u3} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {γ : Type.{u1}} [_inst_3 : UniformSpace.{u1} γ] (pkg'' : AbstractCompletion.{u1} γ _inst_3) {δ : Type.{u4}} [_inst_4 : TopologicalSpace.{u4} δ] {f : α -> β -> γ} {a : δ -> (AbstractCompletion.space.{u3} α _inst_1 pkg)} {b : δ -> (AbstractCompletion.space.{u2} β _inst_2 pkg')}, (Continuous.{u4, u3} δ (AbstractCompletion.space.{u3} α _inst_1 pkg) _inst_4 (UniformSpace.toTopologicalSpace.{u3} (AbstractCompletion.space.{u3} α _inst_1 pkg) (AbstractCompletion.uniformStruct.{u3} α _inst_1 pkg)) a) -> (Continuous.{u4, u2} δ (AbstractCompletion.space.{u2} β _inst_2 pkg') _inst_4 (UniformSpace.toTopologicalSpace.{u2} (AbstractCompletion.space.{u2} β _inst_2 pkg') (AbstractCompletion.uniformStruct.{u2} β _inst_2 pkg')) b) -> (Continuous.{u4, u1} δ (AbstractCompletion.space.{u1} γ _inst_3 pkg'') _inst_4 (UniformSpace.toTopologicalSpace.{u1} (AbstractCompletion.space.{u1} γ _inst_3 pkg'') (AbstractCompletion.uniformStruct.{u1} γ _inst_3 pkg'')) (fun (d : δ) => AbstractCompletion.map₂.{u3, u2, u1} α _inst_1 pkg β _inst_2 pkg' γ _inst_3 pkg'' f (a d) (b d)))
Case conversion may be inaccurate. Consider using '#align abstract_completion.continuous_map₂ AbstractCompletion.continuous_map₂ₓ'. -/
theorem continuous_map₂ {δ} [TopologicalSpace δ] {f : α → β → γ} {a : δ → hatα} {b : δ → hatβ}
    (ha : Continuous a) (hb : Continuous b) :
    Continuous fun d : δ => pkg.zipWith pkg' pkg'' f (a d) (b d) :=
  ((pkg.uniformContinuous_map₂ pkg' pkg'' f).Continuous.comp (Continuous.prod_mk ha hb) : _)
#align abstract_completion.continuous_map₂ AbstractCompletion.continuous_map₂

/- warning: abstract_completion.map₂_coe_coe -> AbstractCompletion.map₂_coe_coe is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : UniformSpace.{u1} α] (pkg : AbstractCompletion.{u1} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {γ : Type.{u3}} [_inst_3 : UniformSpace.{u3} γ] (pkg'' : AbstractCompletion.{u3} γ _inst_3) (a : α) (b : β) (f : α -> β -> γ), (UniformContinuous₂.{u1, u2, u3} α β γ _inst_1 _inst_2 _inst_3 f) -> (Eq.{succ u3} (AbstractCompletion.Space.{u3} γ _inst_3 pkg'') (AbstractCompletion.map₂.{u1, u2, u3} α _inst_1 pkg β _inst_2 pkg' γ _inst_3 pkg'' f (AbstractCompletion.coe.{u1} α _inst_1 pkg a) (AbstractCompletion.coe.{u2} β _inst_2 pkg' b)) (AbstractCompletion.coe.{u3} γ _inst_3 pkg'' (f a b)))
but is expected to have type
  forall {α : Type.{u3}} [_inst_1 : UniformSpace.{u3} α] (pkg : AbstractCompletion.{u3} α _inst_1) {β : Type.{u2}} [_inst_2 : UniformSpace.{u2} β] (pkg' : AbstractCompletion.{u2} β _inst_2) {γ : Type.{u1}} [_inst_3 : UniformSpace.{u1} γ] (pkg'' : AbstractCompletion.{u1} γ _inst_3) (a : α) (b : β) (f : α -> β -> γ), (UniformContinuous₂.{u3, u2, u1} α β γ _inst_1 _inst_2 _inst_3 f) -> (Eq.{succ u1} (AbstractCompletion.space.{u1} γ _inst_3 pkg'') (AbstractCompletion.map₂.{u3, u2, u1} α _inst_1 pkg β _inst_2 pkg' γ _inst_3 pkg'' f (AbstractCompletion.coe.{u3} α _inst_1 pkg a) (AbstractCompletion.coe.{u2} β _inst_2 pkg' b)) (AbstractCompletion.coe.{u1} γ _inst_3 pkg'' (f a b)))
Case conversion may be inaccurate. Consider using '#align abstract_completion.map₂_coe_coe AbstractCompletion.map₂_coe_coeₓ'. -/
theorem map₂_coe_coe (a : α) (b : β) (f : α → β → γ) (hf : UniformContinuous₂ f) :
    pkg.zipWith pkg' pkg'' f (ι a) (ι' b) = ι'' (f a b) :=
  pkg.extension₂_coe_coe pkg' (pkg''.uniformContinuous_coe.comp hf) a b
#align abstract_completion.map₂_coe_coe AbstractCompletion.map₂_coe_coe

end Map₂

end AbstractCompletion


universes v
/-
matcher for the following patterns
```
| "hello" => _
| "world" => _
| a       => _
``` -/
def matchString (C : String → Sort v) (s : String)
    (h₁ : Unit → C "hello")
    (h₂ : Unit → C "world")
    (h₃ : ∀ s,   C s)
    : C s :=
dite (s = "hello")
  (fun h => @Eq.ndrec _ _ (fun x => C x) (h₁ ()) _ h.symm)
  (fun _ => dite (s = "world")
    (fun h => @Eq.ndrec _ _ (fun x => C x) (h₂ ()) _ h.symm)
    (fun _ => h₃ s))

theorem matchString.Eq1 (C : String → Sort v)
    (h₁ : Unit → C "hello")
    (h₂ : Unit → C "world")
    (h₃ : ∀ s,   C s)
    : matchString C "hello" h₁ h₂ h₃ = h₁ () :=
difPos rfl

axiom neg1 : "world" ≠ "hello"

theorem matchString.Eq2 (C : String → Sort v)
    (h₁ : Unit → C "hello")
    (h₂ : Unit → C "world")
    (h₃ : ∀ s,   C s)
    : matchString C "world" h₁ h₂ h₃ = h₂ () :=
have aux₁ : matchString C "world" h₁ h₂ h₃ = if h : "world" = "world" then @Eq.rec _ _ (fun x _ => C x) (h₂ ()) _ h.symm else h₃ "world" from difNeg neg1;
have aux₂ : (if h : "world" = "world" then @Eq.rec _ _ (fun x _ => C x) (h₂ ()) _ h.symm else h₃ "world" : C "world") = h₂ () from difPos rfl;
Eq.trans aux₁ aux₂

theorem matchString.Eq3 (C : String → Sort v)
    (h₁ : Unit → C "hello")
    (h₂ : Unit → C "world")
    (h₃ : ∀ s,   C s)
    (s : String) (n₁ : s ≠ "hello") (n₂ : s ≠ "world")
    : matchString C s h₁ h₂ h₃ = h₃ s :=
have aux₁ : matchString C s h₁ h₂ h₃ = if h : s = "world" then @Eq.rec _ _ (fun x _ => C x) (h₂ ()) _ h.symm else h₃ s from difNeg n₁;
have aux₂ : (if h : s = "world" then @Eq.rec _ _ (fun x _ => C x) (h₂ ()) _ h.symm else h₃ s : C s) = h₃ s from difNeg n₂;
Eq.trans aux₁ aux₂


--import algebra.category.Module.abelian
--import algebra.category.Module.subobject
import algebra.homology.short_complex.homology
import algebra.homology.short_complex.preadditive
import algebra.homology.short_complex.abelian
import linear_algebra.quotient
import algebra.category.Group.abelian

noncomputable theory

open category_theory category_theory.category AddCommGroup category_theory.limits

universes u

instance : category_with_homology (AddCommGroup.{u}) := infer_instance

lemma AddCommGroup.is_zero_iff (M : AddCommGroup.{u}) : is_zero M ↔ ∀ (x : M), x = 0 :=
begin
  rw is_zero.iff_id_eq_zero,
  split,
  { intros h x,
    change (𝟙 M : M → M) x = 0,
    simp only [h, AddCommGroup.zero_apply], },
  { intro h,
    ext x,
    exact h x, },
end

namespace category_theory

namespace short_complex

variables (S : short_complex (AddCommGroup.{u}))

lemma AddCommGroup_image_le_kernel : add_monoid_hom.range S.f ≤ add_monoid_hom.ker S.g :=
begin
  rintros x₂ ⟨x₁, h⟩,
  simp only [add_monoid_hom.mem_ker, ← h, ← comp_apply, S.zero, add_monoid_hom.zero_apply],
end

def AddCommGroup_f' : S.X₁ ⟶ of (add_monoid_hom.ker S.g) :=
add_monoid_hom.cod_restrict S.f _ (λ x, S.AddCommGroup_image_le_kernel (by simp))

def AddCommGroup_homology := of ((add_monoid_hom.ker S.g) ⧸ add_monoid_hom.range S.AddCommGroup_f')

def AddCommGroup_homology_π' : of (add_monoid_hom.ker S.g) ⟶ S.AddCommGroup_homology :=
quotient_add_group.mk' (add_monoid_hom.range S.AddCommGroup_f')

@[simp, reassoc, elementwise]
lemma AddCommGroup_f'_comp_homology_π' : S.AddCommGroup_f' ≫ S.AddCommGroup_homology_π' = 0 :=
begin
  ext,
  dsimp [AddCommGroup_f', AddCommGroup_homology_π'],
  simp only [comp_apply, quotient_add_group.mk'_apply, quotient_add_group.eq_zero_iff,
    add_monoid_hom.mem_range, exists_apply_eq_apply],
end

namespace AddCommGroup_left_homology_data

def i : AddCommGroup.of (add_monoid_hom.ker S.g) ⟶ S.X₂ :=
(add_monoid_hom.ker S.g).subtype

lemma wi : i S ≫ S.g = 0 := by { ext x₂, exact x₂.2, }

@[simps]
def _root_.AddCommGroup.kernel_cone {X Y : AddCommGroup.{u}} (f : X ⟶ Y) :
  kernel_fork f := @kernel_fork.of_ι _ _ _ _ _ f (AddCommGroup.of _) (add_monoid_hom.ker f).subtype
    (by tidy)

def _root_.AddCommGroup.kernel_is_limit {X Y : AddCommGroup.{u}} (f : X ⟶ Y) :
  is_limit (AddCommGroup.kernel_cone f) :=
limits.kernel_fork.is_limit.of_ι _ _
  (λ A x hx,
    { to_fun := λ a, ⟨x a, by rw [add_monoid_hom.mem_ker, ← comp_apply, hx, zero_apply]⟩,
      map_zero' := by tidy,
      map_add' := by tidy, })
  (λ A x hx, begin
    ext,
    refl,
  end)
  (λ A x hx b hb, begin
    ext x,
    convert congr_arg (λ (φ : A →+ X), φ x) hb,
  end)

def hi : is_limit (kernel_fork.of_ι (i S) (wi S)) := AddCommGroup.kernel_is_limit S.g

lemma f'_eq_AddCommGroup_f' : (hi S).lift (kernel_fork.of_ι S.f S.zero) = S.AddCommGroup_f' := rfl

lemma wπ : (hi S).lift (kernel_fork.of_ι S.f S.zero) ≫ S.AddCommGroup_homology_π' = 0 :=
by simp only [f'_eq_AddCommGroup_f', AddCommGroup_f'_comp_homology_π']

@[simps]
def _root_.AddCommGroup.cokernel_cocone {X Y : AddCommGroup.{u}} (f : X ⟶ Y) :
  cokernel_cofork f :=
@cokernel_cofork.of_π _ _ _ _ _ f (AddCommGroup.of (Y ⧸ add_monoid_hom.range f))
    (quotient_add_group.mk' (add_monoid_hom.range f)) (by tidy)

def _root_.AddCommGroup.cokernel_is_colimit {X Y : AddCommGroup.{u}} (f : X ⟶ Y) :
  is_colimit (AddCommGroup.cokernel_cocone f) :=
limits.cokernel_cofork.is_colimit.of_π _ _
  (λ A x hx, quotient_add_group.lift _ x (λ y hy, begin
    obtain ⟨z, rfl⟩ := hy,
    rw [← comp_apply, hx, zero_apply],
  end))
  (λ A x hx, by { ext, refl, })
  (λ A x hx b hb, begin
    ext y,
    obtain ⟨z, rfl⟩ := quotient_add_group.mk'_surjective _ y,
    simpa only [← comp_apply, hb],
  end)

def hπ : is_colimit (cokernel_cofork.of_π _ (wπ S)) :=
is_colimit.of_iso_colimit (AddCommGroup.cokernel_is_colimit S.AddCommGroup_f')
  (cofork.ext (iso.refl _) (by tidy))

end AddCommGroup_left_homology_data

@[simps]
def AddCommGroup_left_homology_data : S.left_homology_data :=
{ K := AddCommGroup.of (add_monoid_hom.ker S.g),
  H := AddCommGroup.of S.AddCommGroup_homology,
  i := AddCommGroup_left_homology_data.i S,
  π := S.AddCommGroup_homology_π',
  wi := AddCommGroup_left_homology_data.wi S,
  hi := AddCommGroup_left_homology_data.hi S,
  wπ := AddCommGroup_left_homology_data.wπ S,
  hπ := AddCommGroup_left_homology_data.hπ S, }

@[simp]
lemma AddCommGroup_left_homology_data_f' :
  S.AddCommGroup_left_homology_data.f' = S.AddCommGroup_f' := rfl

def AddCommGroup_homology_iso : S.homology ≅ S.AddCommGroup_homology :=
S.AddCommGroup_left_homology_data.homology_iso

lemma AddCommGroup_bijective_homology_iso_inv :
  function.bijective S.AddCommGroup_homology_iso.inv :=
concrete_category.bijective_of_is_iso ((forget AddCommGroup).map S.AddCommGroup_homology_iso.inv)

lemma AddCommGroup_bijective_homology_iso_hom :
  function.bijective S.AddCommGroup_homology_iso.hom :=
concrete_category.bijective_of_is_iso ((forget AddCommGroup).map S.AddCommGroup_homology_iso.hom)

def AddCommGroup_homology_π : of (add_monoid_hom.ker S.g) ⟶ S.homology :=
S.AddCommGroup_homology_π' ≫ S.AddCommGroup_homology_iso.inv

@[simp, reassoc, elementwise]
lemma AddCommGroup_homology_π'_comp_homology_iso_inv :
  S.AddCommGroup_homology_π' ≫ S.AddCommGroup_homology_iso.inv = S.AddCommGroup_homology_π := rfl

@[simp, reassoc, elementwise]
lemma AddCommGroup_f'_comp_homology_π : S.AddCommGroup_f' ≫ S.AddCommGroup_homology_π = 0 :=
begin
  ext,
  dsimp only [AddCommGroup_homology_π],
  rw [AddCommGroup_f'_comp_homology_π'_assoc, zero_comp],
end

lemma AddCommGroup_surjective_homology_π' : function.surjective S.AddCommGroup_homology_π' :=
quotient_add_group.mk'_surjective _

lemma AddCommGroup_surjective_homology_π : function.surjective S.AddCommGroup_homology_π  :=
function.surjective.comp S.AddCommGroup_bijective_homology_iso_inv.2
  S.AddCommGroup_surjective_homology_π'

lemma AddCommGroup_ker_homology_π'_eq_range_f' :
  add_monoid_hom.ker S.AddCommGroup_homology_π' = add_monoid_hom.range S.AddCommGroup_f' :=
quotient_add_group.ker_mk _

lemma AddCommGroup_homology_π'_eq_zero_iff (z : add_monoid_hom.ker S.g) :
  S.AddCommGroup_homology_π' z = 0 ↔ z.1 ∈ (add_monoid_hom.range S.f) :=
begin
  change z ∈ add_monoid_hom.ker S.AddCommGroup_homology_π' ↔ _,
  rw AddCommGroup_ker_homology_π'_eq_range_f',
  split,
  { rintro ⟨x₁, hx₁⟩,
    rw ← hx₁,
    exact ⟨x₁, rfl⟩, },
  { rintro ⟨x₁, hx₁⟩,
    exact ⟨x₁, by { ext, exact hx₁, }⟩, },
end

lemma AddCommGroup_ker_homology_π_eq_ker_homology_π' :
  add_monoid_hom.ker S.AddCommGroup_homology_π = add_monoid_hom.ker S.AddCommGroup_homology_π' :=
begin
  dsimp only [AddCommGroup_homology_π],
  ext x₂,
  split,
  { intro hx₂,
    apply S.AddCommGroup_bijective_homology_iso_inv.1,
    simpa only [map_zero S.AddCommGroup_homology_iso.inv] using hx₂, },
  { intro hx₂,
    simp only [add_monoid_hom.mem_ker] at hx₂ ⊢,
    rw [comp_apply, hx₂, map_zero S.AddCommGroup_homology_iso.inv], },
end

lemma AddCommGroup_homology_π_eq_zero_iff (z : add_monoid_hom.ker S.g) :
  S.AddCommGroup_homology_π z = 0 ↔ z.1 ∈ (add_monoid_hom.range S.f) :=
begin
  change z ∈ add_monoid_hom.ker S.AddCommGroup_homology_π ↔ _,
  rw S.AddCommGroup_ker_homology_π_eq_ker_homology_π',
  exact S.AddCommGroup_homology_π'_eq_zero_iff z,
end

lemma AddCommGroup_homology_ext_iff (z z' : add_monoid_hom.ker S.g) :
  S.AddCommGroup_homology_π z = S.AddCommGroup_homology_π z' ↔ ∃ (x₁ : S.X₁), z.1 = z'.1 + S.f x₁ :=
begin
  split,
  { intro h,
    have eq : S.AddCommGroup_homology_π (z - z') = 0,
    { simp only [map_sub, h, sub_self], },
    rw S.AddCommGroup_homology_π_eq_zero_iff at eq,
    obtain ⟨x₁, hx₁⟩ := eq,
    use x₁,
    simp only [hx₁, subtype.val_eq_coe, add_subgroup_class.coe_sub, add_sub_cancel'_right], },
  { rintro ⟨x₁, hx₁⟩,
    rw [show z = z' + S.AddCommGroup_f' x₁, by { ext, exact hx₁, }],
    simp only [map_add, AddCommGroup_f'_comp_homology_π_apply, add_monoid_hom.zero_apply, add_zero], },
end

--@[ext]
lemma AddCommGroup_homology_ext (z z' : add_monoid_hom.ker S.g)
  (h : ∃ (x₁ : S.X₁), z.1 = z'.1 + S.f x₁) :
    S.AddCommGroup_homology_π z = S.AddCommGroup_homology_π z' :=
by simp only [S.AddCommGroup_homology_ext_iff, h]

variable (S)

lemma AddCommGroup_element_homology_is_zero_iff' (z : S.AddCommGroup_homology) :
  z = 0 ↔ ∃ (x₁ : S.X₁), z = S.AddCommGroup_homology_π' (S.AddCommGroup_f' x₁) :=
begin
  split,
  { rintro rfl,
    exact ⟨0, by simp only [map_zero]⟩, },
  { rintro ⟨x₁, hx₁⟩,
    simp only [hx₁, AddCommGroup_f'_comp_homology_π'_apply, zero_apply], },
end

lemma AddCommGroup_element_homology_is_zero_iff (z : S.homology) :
  z = 0 ↔ ∃ (x₁ : S.X₁), z = S.AddCommGroup_homology_π (S.AddCommGroup_f' x₁) :=
by simp only [AddCommGroup_f'_comp_homology_π_apply, add_monoid_hom.zero_apply, exists_const]

lemma AddCommGroup_exact_iff : S.exact ↔
  ∀ (x₂ : S.X₂) (hx₂ : S.g x₂ = 0), ∃ (x₁ : S.X₁), S.f x₁ = x₂ :=
begin
  rw [S.AddCommGroup_left_homology_data.exact_iff, AddCommGroup.is_zero_iff],
  split,
  { intros h x₂ hx₂,
    have eq : S.AddCommGroup_homology_π' ⟨x₂, hx₂⟩ = 0 := h _,
    rw AddCommGroup_homology_π'_eq_zero_iff at eq,
    obtain ⟨x₁, hx₁⟩ := eq,
    exact ⟨x₁, hx₁⟩, },
  { intros h γ,
    obtain ⟨⟨x₂, hx₂⟩, rfl⟩ := S.AddCommGroup_surjective_homology_π' γ,
    obtain ⟨x₁, rfl⟩ := h x₂ hx₂,
    simp only [S.AddCommGroup_homology_π'_eq_zero_iff, add_monoid_hom.mem_range, exists_apply_eq_apply], },
end

variables {S}

lemma AddCommGroup_map_from_homology_ext {A : AddCommGroup.{u}} (f f' : S.homology ⟶ A)
  (eq : ∀ (x₂ : add_monoid_hom.ker S.g), f (S.AddCommGroup_homology_π x₂) = f' (S.AddCommGroup_homology_π x₂)) :
  f = f' :=
begin
  ext,
  obtain ⟨x₂, rfl⟩ := S.AddCommGroup_surjective_homology_π x,
  exact eq x₂,
end

variables {S₁ S₂ : short_complex AddCommGroup.{u}} (φ φ' : S₁ ⟶ S₂)

@[simps]
def AddCommGroup_map_ker : of (add_monoid_hom.ker S₁.g) ⟶ of (add_monoid_hom.ker S₂.g) :=
add_monoid_hom.cod_restrict (φ.τ₂.comp (add_monoid_hom.ker S₁.g).subtype) _
  (begin
    rintro ⟨x₁, hx₁⟩,
    dsimp,
    rw add_monoid_hom.mem_ker at hx₁,
    rw [add_monoid_hom.mem_ker, ← comp_apply, φ.comm₂₃, comp_apply, hx₁, map_zero φ.τ₃],
  end)

/-@[simps]
def AddCommGroup_map_homology : S₁.AddCommGroup_homology ⟶ S₂.AddCommGroup_homology :=
submodule.liftq _ (AddCommGroup_map_ker φ ≫ S₂.AddCommGroup_homology_π')
begin
  rintros _ ⟨x₁, rfl⟩,
  simp only [linear_map.mem_ker, AddCommGroup.coe_comp, function.comp_app,
    AddCommGroup_homology_π'_eq_zero_iff],
  refine ⟨φ.τ₁ x₁, _⟩,
  dsimp [AddCommGroup_f'],
  simp only [← comp_apply, φ.comm₁₂],
end

@[simps]
def AddCommGroup_left_homology_map_data : left_homology_map_data φ S₁.AddCommGroup_left_homology_data
  S₂.AddCommGroup_left_homology_data :=
{ φK := AddCommGroup_map_ker φ,
  φH := AddCommGroup_map_homology φ,
  commi' := rfl,
  commf'' := begin
    simp only [AddCommGroup_left_homology_data_f'],
    ext x₁,
    dsimp [AddCommGroup_f'],
    simp only [← comp_apply, φ.comm₁₂],
  end,
  commπ' := by { ext x₁, rcases x₁ with ⟨x₁, hx₁⟩, refl, }, }

@[simp, reassoc, elementwise]
lemma AddCommGroup_homology_π_comp_homology_map :
  S₁.AddCommGroup_homology_π ≫ homology_map φ = AddCommGroup_map_ker φ ≫ S₂.AddCommGroup_homology_π :=
begin
  dsimp only [AddCommGroup_homology_π],
  rw (AddCommGroup_left_homology_map_data φ).homology_map_eq,
  have eq := (AddCommGroup_left_homology_map_data φ).commπ,
  dsimp only [AddCommGroup_left_homology_map_data, AddCommGroup_left_homology_data_π,
    AddCommGroup_homology_iso] at ⊢ eq,
  simp only [← reassoc_of eq, assoc, iso.inv_hom_id_assoc],
end -/

end short_complex

end category_theory


/-
Copyright (c) 2020 Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bhavik Mehta, Andrew Yang
-/
import category_theory.limits.shapes.pullbacks
import category_theory.limits.preserves.basic

/-!
# Preserving pullbacks

Constructions to relate the notions of preserving pullbacks and reflecting pullbacks to concrete
pullback cones.

In particular, we show that `pullback_comparison G f g` is an isomorphism iff `G` preserves
the pullback of `f` and `g`.

The dual is also given.

## TODO

* Generalise to wide pullbacks

-/

noncomputable theory

universes v₁ v₂ u₁ u₂

open category_theory category_theory.category category_theory.limits

namespace category_theory.limits

section pullback

variables {C : Type u₁} [category.{v₁} C]
variables {D : Type u₂} [category.{v₂} D]
variables (G : C ⥤ D)
variables {W X Y Z : C} {f : X ⟶ Z} {g : Y ⟶ Z} {h : W ⟶ X} {k : W ⟶ Y} (comm : h ≫ f = k ≫ g)

/-- The map of a pullback cone is a limit iff the fork consisting of the mapped morphisms is a
limit. This essentially lets us commute `pullback_cone.mk` with `functor.map_cone`. -/
def is_limit_map_cone_pullback_cone_equiv :
  is_limit (G.map_cone (pullback_cone.mk h k comm)) ≃
    is_limit (pullback_cone.mk (G.map h) (G.map k) (by simp only [← G.map_comp, comm])
      : pullback_cone (G.map f) (G.map g)) :=
(is_limit.whisker_equivalence_equiv walking_cospan_equiv.{v₂ v₁}).trans $
  (is_limit.postcompose_hom_equiv (diagram_iso_cospan.{v₂} _) _).symm.trans $
  is_limit.equiv_iso_limit $ cones.ext (iso.refl _) $
    (by rintro (_|_|_); dsimp; simpa only [category.comp_id, category.id_comp, ← G.map_comp])

/-- The property of preserving pullbacks expressed in terms of binary fans. -/
def is_limit_pullback_cone_map_of_is_limit [preserves_limit (cospan f g) G]
  (l : is_limit (pullback_cone.mk h k comm)) :
  is_limit (pullback_cone.mk (G.map h) (G.map k) _) :=
is_limit_map_cone_pullback_cone_equiv G comm (preserves_limit.preserves l)

/-- The property of reflecting pullbacks expressed in terms of binary fans. -/
def is_limit_of_is_limit_pullback_cone_map [reflects_limit (cospan f g) G]
  (l : is_limit (pullback_cone.mk (G.map h) (G.map k) _)) :
  is_limit (pullback_cone.mk h k comm) :=
reflects_limit.reflects ((is_limit_map_cone_pullback_cone_equiv G comm).symm l)

variables (f g) [preserves_limit (cospan f g) G]

/-- If `G` preserves pullbacks and `C` has them, then the pullback cone constructed of the mapped
morphisms of the pullback cone is a limit. -/
def is_limit_of_has_pullback_of_preserves_limit [has_pullback f g] :
  is_limit (pullback_cone.mk (G.map pullback.fst) (G.map pullback.snd) _) :=
is_limit_pullback_cone_map_of_is_limit G _ (pullback_is_pullback f g)

/-- If `F` preserves the pullback of `f, g`, it also preserves the pullback of `g, f`. -/
def preserves_pullback_symmetry : preserves_limit (cospan g f) G :=
{ preserves := λ c hc,
  begin
    apply (is_limit.whisker_equivalence_equiv walking_cospan_equiv.{v₂ v₁}).symm.to_fun,
    apply (is_limit.postcompose_hom_equiv (diagram_iso_cospan.{v₂} _) _).to_fun,
    apply is_limit.of_iso_limit _ (pullback_cone.iso_mk _).symm,
    apply pullback_cone.flip_is_limit,
    apply (is_limit_map_cone_pullback_cone_equiv _ _).to_fun,
    { apply_with preserves_limit.preserves { instances := ff },
      { dsimp [walking_cospan_equiv], apply_instance },
      apply pullback_cone.flip_is_limit,
      apply is_limit.of_iso_limit _ (pullback_cone.iso_mk _),
      exact (is_limit.postcompose_hom_equiv (diagram_iso_cospan.{v₁} _) _).inv_fun hc },
    { exact (c.π.naturality walking_cospan.hom.inr).symm.trans
      (c.π.naturality walking_cospan.hom.inl : _) }
  end }

variables [has_pullback f g] [has_pullback (G.map f) (G.map g)]

/-- If `G` preserves the pullback of `(f,g)`, then the pullback comparison map for `G` at `(f,g)` is
an isomorphism. -/
def preserves_pullback.iso :
  G.obj (pullback f g) ≅ pullback (G.map f) (G.map g) :=
is_limit.cone_point_unique_up_to_iso
  (is_limit_of_has_pullback_of_preserves_limit G f g)
  (limit.is_limit _)

@[reassoc] lemma preserves_pullback.iso_hom_fst :
  (preserves_pullback.iso G f g).hom ≫ pullback.fst = G.map pullback.fst :=
by simp [preserves_pullback.iso]

@[reassoc] lemma preserves_pullback.iso_hom_snd :
  (preserves_pullback.iso G f g).hom ≫ pullback.snd = G.map pullback.snd :=
by simp [preserves_pullback.iso]

@[simp, reassoc] lemma preserves_pullback.iso_inv_fst :
  (preserves_pullback.iso G f g).inv ≫ G.map pullback.fst = pullback.fst :=
by simp [preserves_pullback.iso, iso.inv_comp_eq]

@[simp, reassoc]

end pullback

section pushout

variables {C : Type u₁} [category.{v₁} C]
variables {D : Type u₂} [category.{v₂} D]
variables (G : C ⥤ D)
variables {W X Y Z : C} {h : X ⟶ Z} {k : Y ⟶ Z} {f : W ⟶ X} {g : W ⟶ Y} (comm : f ≫ h = g ≫ k)

/-- The map of a pushout cocone is a colimit iff the cofork consisting of the mapped morphisms is a
colimit. This essentially lets us commute `pushout_cocone.mk` with `functor.map_cocone`. -/
def is_colimit_map_cocone_pushout_cocone_equiv :
  is_colimit (G.map_cocone (pushout_cocone.mk h k comm)) ≃
    is_colimit (pushout_cocone.mk (G.map h) (G.map k) (by simp only [← G.map_comp, comm])
      : pushout_cocone (G.map f) (G.map g)) :=
(is_colimit.whisker_equivalence_equiv walking_span_equiv.{v₂ v₁}).trans $
  (is_colimit.precompose_hom_equiv (diagram_iso_span.{v₂} _).symm _).symm.trans $
  is_colimit.equiv_iso_colimit $ cocones.ext (iso.refl _) $
    (by rintro (_|_|_); dsimp; simpa only [category.comp_id, category.id_comp, ← G.map_comp])

/-- The property of preserving pushouts expressed in terms of binary cofans. -/
def is_colimit_pushout_cocone_map_of_is_colimit [preserves_colimit (span f g) G]
  (l : is_colimit (pushout_cocone.mk h k comm)) :
  is_colimit (pushout_cocone.mk (G.map h) (G.map k) _) :=
is_colimit_map_cocone_pushout_cocone_equiv G comm (preserves_colimit.preserves l)

/-- The property of reflecting pushouts expressed in terms of binary cofans. -/
def is_colimit_of_is_colimit_pushout_cocone_map [reflects_colimit (span f g) G]
  (l : is_colimit (pushout_cocone.mk (G.map h) (G.map k) _)) :
  is_colimit (pushout_cocone.mk h k comm) :=
reflects_colimit.reflects ((is_colimit_map_cocone_pushout_cocone_equiv G comm).symm l)

variables (f g) [preserves_colimit (span f g) G]

/-- If `G` preserves pushouts and `C` has them, then the pushout cocone constructed of the mapped
morphisms of the pushout cocone is a colimit. -/
def is_colimit_of_has_pushout_of_preserves_colimit [has_pushout f g] :
  is_colimit (pushout_cocone.mk (G.map pushout.inl) (G.map pushout.inr) _) :=
is_colimit_pushout_cocone_map_of_is_colimit G _ (pushout_is_pushout f g)

/-- If `F` preserves the pushout of `f, g`, it also preserves the pushout of `g, f`. -/
def preserves_pushout_symmetry : preserves_colimit (span g f) G :=
{ preserves := λ c hc,
  begin
    apply (is_colimit.whisker_equivalence_equiv walking_span_equiv.{v₂ v₁}).symm.to_fun,
    apply (is_colimit.precompose_hom_equiv (diagram_iso_span.{v₂} _).symm _).to_fun,
    apply is_colimit.of_iso_colimit _ (pushout_cocone.iso_mk _).symm,
    apply pushout_cocone.flip_is_colimit,
    apply (is_colimit_map_cocone_pushout_cocone_equiv _ _).to_fun,
    { apply_with preserves_colimit.preserves { instances := ff },
      { dsimp [walking_span_equiv], apply_instance },
      apply pushout_cocone.flip_is_colimit,
      apply is_colimit.of_iso_colimit _ (pushout_cocone.iso_mk _),
      exact (is_colimit.precompose_hom_equiv (diagram_iso_span.{v₁} _) _).inv_fun hc },
    { exact (c.ι.naturality walking_span.hom.snd).trans
      (c.ι.naturality walking_span.hom.fst).symm }
  end }

variables [has_pushout f g]  [has_pushout (G.map f) (G.map g)]

/-- If `G` preserves the pushout of `(f,g)`, then the pushout comparison map for `G` at `(f,g)` is
an isomorphism. -/
def preserves_pushout.iso :
  pushout (G.map f) (G.map g) ≅ G.obj (pushout f g) :=
is_colimit.cocone_point_unique_up_to_iso
  (colimit.is_colimit _)
  (is_colimit_of_has_pushout_of_preserves_colimit G f g)

@[reassoc] lemma preserves_pushout.inl_iso_hom :
  pushout.inl ≫ (preserves_pushout.iso G f g).hom = G.map pushout.inl :=
by { delta preserves_pushout.iso, simp }

@[reassoc] lemma preserves_pushout.inr_iso_hom :
  pushout.inr ≫ (preserves_pushout.iso G f g).hom = G.map pushout.inr :=
by { delta preserves_pushout.iso, simp }

@[simp, reassoc] lemma preserves_pushout.inl_iso_inv :
  G.map pushout.inl ≫ (preserves_pushout.iso G f g).inv = pushout.inl :=
by simp [preserves_pushout.iso, iso.comp_inv_eq]

@[simp, reassoc] lemma preserves_pushout.inr_iso_inv :
  G.map pushout.inr ≫ (preserves_pushout.iso G f g).inv = pushout.inr :=
by simp [preserves_pushout.iso, iso.comp_inv_eq]

end pushout

section

variables {C : Type u₁} [category.{v₁} C]
variables {D : Type u₂} [category.{v₁} D]
variables (G : C ⥤ D)

section pullback

variables {X Y Z : C} {f : X ⟶ Z} {g : Y ⟶ Z}
variables [has_pullback f g] [has_pullback (G.map f) (G.map g)]

/-- If the pullback comparison map for `G` at `(f,g)` is an isomorphism, then `G` preserves the
pullback of `(f,g)`. -/
def preserves_pullback.of_iso_comparison [i : is_iso (pullback_comparison G f g)] :
  preserves_limit (cospan f g) G :=
begin
  apply preserves_limit_of_preserves_limit_cone (pullback_is_pullback f g),
  apply (is_limit_map_cone_pullback_cone_equiv _ _).symm _,
  apply is_limit.of_point_iso (limit.is_limit (cospan (G.map f) (G.map g))),
  apply i,
end

variable [preserves_limit (cospan f g) G]

@[simp]
lemma preserves_pullback.iso_hom :
  (preserves_pullback.iso G f g).hom = pullback_comparison G f g := rfl

instance : is_iso (pullback_comparison G f g) :=
begin
  rw ← preserves_pullback.iso_hom,
  apply_instance
end

end pullback

section pushout

variables {X Y Z : C} {f : X ⟶ Y} {g : X ⟶ Z}
variables [has_pushout f g] [has_pushout (G.map f) (G.map g)]

/-- If the pushout comparison map for `G` at `(f,g)` is an isomorphism, then `G` preserves the
pushout of `(f,g)`. -/
def preserves_pushout.of_iso_comparison [i : is_iso (pushout_comparison G f g)] :
  preserves_colimit (span f g) G :=
begin
  apply preserves_colimit_of_preserves_colimit_cocone (pushout_is_pushout f g),
  apply (is_colimit_map_cocone_pushout_cocone_equiv _ _).symm _,
  apply is_colimit.of_point_iso (colimit.is_colimit (span (G.map f) (G.map g))),
  apply i,
end

variable [preserves_colimit (span f g) G]

@[simp]
lemma preserves_pushout.iso_hom :
  (preserves_pushout.iso G f g).hom = pushout_comparison G f g := rfl

instance : is_iso (pushout_comparison G f g) :=
begin
  rw ← preserves_pushout.iso_hom,
  apply_instance
end

end pushout

end

end category_theory.limits


import Categories.Colimit
import Categories.Comma

set_option autoImplicit false

namespace Mathematics

-- https://ncatlab.org/nlab/show/cobordism+category
-- https://people.math.umass.edu/~weston/oldpapers/cobord.pdf

-- we do not require existence of all coproducts here,
-- but require that for φ : ∂ A + ∂ B ≅ ∂ (A + B)
-- we have φ ∘ inl (∂ A) (∂ B) = ∂ (inl A B) (and so on for “inr”)

-- possible lack of all coproducts leaves some flexibility, because, for example,
-- if we require all manifolds to have some fixed (for each manifold) dimension,
-- then there will be no coproducts of manifolds with different dimensions,
-- while we still can form category with all continuous maps between them

-- also note that there is no initial object in such C iff it is empty
structure Cobordism (C : Category) :=
(boundary : Functor C C)
(ι        : Natural boundary 1)
(square   : ∀ x, isInitial C (boundary (boundary x)))
(additive : isAdditive boundary)

notation "∂" => Cobordism.boundary

def Cob {C : Category} [HasCoproducts C] (Γ : Cobordism C) (a b : C.obj) :=
Σ (u v : C.obj), a + ∂ Γ u ≅ b + ∂ Γ v

section
  variable {C : Category} [HasCoproducts C] (Γ : Cobordism C)

  def Cob.refl (a : C.obj) : Cob Γ a a :=
  ⟨a, a, Iso.refl _⟩

  def Cob.symm {a b : C.obj} (φ : Cob Γ a b) : Cob Γ b a :=
  ⟨φ.2.1, φ.1, Iso.symm φ.2.2⟩

  -- probably there should be easier way to obtain this isomorphism
  def Cob.trans {a b c : C.obj} (φ : Cob Γ a b) (ψ : Cob Γ b c) : Cob Γ a c :=
  ⟨φ.1 + ψ.1, φ.2.1 + ψ.2.1, coproductApLeft (additiveIso Γ.additive)⁻¹
                           ⬝ coproductAssoc _ _ _
                           ⬝ coproductApRight φ.2.2
                           ⬝ (coproductAssoc _ _ _)⁻¹
                           ⬝ coproductApLeft (coproductComm _ _)
                           ⬝ coproductAssoc _ _ _
                           ⬝ coproductApRight ψ.2.2
                           ⬝ (coproductAssoc _ _ _)⁻¹
                           ⬝ coproductApLeft ((coproductComm _ _)⁻¹ ⬝ additiveIso Γ.additive)⟩

  def Cob.boundary {a b : C.obj} (φ : Cob Γ a b) : ∂ Γ a ≅ ∂ Γ b :=
    (coproductInitialLeft _ _ (Γ.square φ.1))⁻¹ ⬝ additiveIso Γ.additive
  ⬝ functorIso _ φ.2.2 ⬝ (additiveIso Γ.additive)⁻¹ ⬝ coproductInitialLeft _ _ (Γ.square _)

  def Cob.idem (m : C.obj) : Cob Γ (∂ Γ m) (∂ Γ (∂ Γ m)) :=
  ⟨∂ Γ m, m, coproductInitialLeft _ _ (Γ.square m) ⬝ (coproductInitialRight _ _ (Γ.square m))⁻¹⟩
end

def Cob.isomorphism {C : Category} [HasCoproducts C] {Γ : Cobordism C} {a b : C.obj} (φ ψ : Cob Γ a b) :=
{ w : φ.1 ≅ ψ.1 × φ.2.1 ≅ ψ.2.1 // madd 1 ((∂ Γ).map w.2.1) ∘ φ.2.2.1 = ψ.2.2.1 ∘ madd 1 ((∂ Γ).map w.1.1) }

def Cob.isorefl {C : Category} [HasCoproducts C] {Γ : Cobordism C} {a b : C.obj} (φ : Cob Γ a b) : Cob.isomorphism φ φ :=
⟨(Iso.refl _, Iso.refl _), begin
                             show madd 1 (Γ.boundary.map 1) ∘ _ = _ ∘ madd 1 (Γ.boundary.map 1);
                             rw [Γ.boundary.idm, Γ.boundary.idm]; apply Eq.trans;
                             apply congrArg (· ∘ _); apply maddId; apply Eq.symm;
                             apply Eq.trans; apply congrArg; apply maddId;
                             apply Eq.trans; apply C.rid; apply Eq.symm (C.lid _)
                           end⟩

def isClosed {C : Category} (Γ : Cobordism C) (m : C.obj) := ∂ Γ m ≅ ∂ Γ (∂ Γ m)
def bounds {C : Category} [HasCoproducts C] (Γ : Cobordism C) (m : C.obj) := Cob Γ m (∂ Γ (∂ Γ m))

def boundaryInital {C : Category} (Γ : Cobordism C) {x : C.obj} (H : isInitial C x) : isInitial C (∂ Γ x) :=
begin apply initialIso (Γ.square (∂ Γ x)); apply functorIso; apply initialUniq; apply Γ.square; exact H end

def boundaryInitialIso {C : Category} (Γ : Cobordism C) {x : C.obj} (H : isInitial C x) : ∂ Γ x ≅ x :=
begin apply initialUniq; apply boundaryInital Γ H; exact H end

def boundaryZero {C : Category} [HasInitial C] (Γ : Cobordism C) : ∂ Γ 0 ≅ 0 :=
boundaryInitialIso Γ HasInitial.property

section
  variable {J C : Category} [HasInitial C] [HasColimits J C]

  open HasColimits (colim)

  def boundaryApply (c : Cocone J C) : Cocone J C :=
  ⟨(Δ C 0, colim c.1.1), ⟨λ _, (HasInitial.property _).inh, λ _, (HasInitial.property _).prop _ _⟩⟩

  def boundaryMap {D₁ D₂ : Cocone J C} (f : Cocone.mor D₁ D₂) : Cocone.mor (boundaryApply D₁) (boundaryApply D₂) :=
  begin
    apply Subtype.mk _ _;
    { constructor;
      { apply HasColimits.recur D₁.1.1 ⟨_, _⟩; apply Subtype.mk _ _;
        { intro x; exact (HasColimits.cone D₂.1.1).val x ∘ f.1.2 x };
        { intro i j g; rw [C.assoc, f.1.2.2, ←C.assoc];
          apply congrArg (· ∘ _); apply (HasColimits.cone _).2 } };
      { apply Natural.id } };
    { intro; apply (HasInitial.property _).prop }
  end

  def boundary : Functor (𝐶𝑜𝑐𝑜𝑛𝑒 J C) (𝐶𝑜𝑐𝑜𝑛𝑒 J C) :=
  { apply := boundaryApply,
    map   := boundaryMap,
    idm   := begin
               intros; apply Subtype.eq; apply Prod.eq;
               { apply HasColimits.uniq _ _;
                 intro; apply Eq.trans; apply C.lid;
                 apply Eq.symm (C.rid _) };
               { rfl }
             end,
    com   := begin
               intros; apply Subtype.eq; apply congr₂;
               { apply HasColimits.uniq _ _; intro;
                 show C.com (C.com (Subtype.val _) (Subtype.val _)) _ = _;
                 rw [C.assoc, (HasColimits.property _ _).2.1,
                    ←C.assoc, (HasColimits.property _ _).2.1];
                 apply C.assoc };
               { apply Subtype.eq; funext _; apply (HasInitial.property _).prop }
             end }

  def boundarySquare : ∀ x, isInitial (𝐶𝑜𝑐𝑜𝑛𝑒 J C) (boundary (boundary x)) :=
  begin
    intro H N; constructor;
    { apply Subtype.mk (_, _) _; apply (colimZero HasInitial.property (HasColimits.property _) _).inh;
      apply Subtype.mk _ _; intros; apply (HasInitial.property _).inh;
      repeat { intros; apply (HasInitial.property _).prop } };
    { intro f g; apply Subtype.eq; apply Prod.eq;
      apply (colimZero HasInitial.property (HasColimits.property _) _).prop;
      apply Subtype.eq; funext _; apply (HasInitial.property _).prop }
  end

  def boundaryNat : @Natural (𝐶𝑜𝑐𝑜𝑛𝑒 J C) (𝐶𝑜𝑐𝑜𝑛𝑒 J C) boundary 1 :=
  ⟨λ w, ⟨((HasColimits.property _ w.cone).val,
         ⟨λ _, (HasInitial.property _).inh, λ _, (HasInitial.property _).prop _ _⟩),
          λ _, (HasInitial.property _).prop _ _⟩,
   begin
     intros a b f; apply Subtype.eq; apply Prod.eq;
     { show C.com (Subtype.val _) (Subtype.val _) = C.com f.1.1 (Subtype.val _);
       apply HasColimits.prop a.1.1 ⟨b.1.2, ⟨_, _⟩⟩ _ _ _ _;
       { intro x; exact f.1.1 ∘ a.2.1 x };
       { intro i j g; rw [C.assoc]; apply congrArg; apply a.2.2 };
       { intro x; rw [C.assoc]; apply Eq.trans;
         apply congrArg; apply HasColimits.recurβ;
         simp [*]; rw [←C.assoc]; apply Eq.trans; apply congrArg (· ∘ _);
         apply HasColimits.recurβ; apply Eq.symm; apply f.2 };
       { intro x; rw [C.assoc]; apply congrArg; apply HasColimits.recurβ } };
     { apply Subtype.eq; funext _; simp [*];
       apply (HasInitial.property _).prop }
   end⟩

  variable [HasCoproducts C]

  def Cocone.add (D₁ D₂ : Cocone J C) : Cocone J C :=
  ⟨(Functor.add D₁.1.1 D₂.1.1, D₁.1.2 + D₂.1.2), ⟨λ x, madd (D₁.2.val x) (D₂.2.val x),
   begin
     intros; simp [*]; apply Eq.trans; apply maddCom; apply congr₂;
     apply D₁.2.property; apply D₂.2.property
   end⟩⟩

  def Cocone.inl (D₁ D₂ : Cocone J C) : Cocone.mor D₁ (Cocone.add D₁ D₂) :=
  ⟨(HasCoproducts.inl _ _, Natural.inl _ _), λ _, Eq.symm (maddInl _ _)⟩

  def Cocone.inr (D₁ D₂ : Cocone J C) : Cocone.mor D₂ (Cocone.add D₁ D₂) :=
  ⟨(HasCoproducts.inr _ _, Natural.inr _ _), λ _, Eq.symm (maddInr _ _)⟩

  def Cocone.hasCoproducts (D₁ D₂ : Cocone J C) : isCoproduct (𝐶𝑜𝑐𝑜𝑛𝑒 J C) (Cocone.inl D₁ D₂) (Cocone.inr D₁ D₂) :=
  begin
    intro c f₁ f₂; apply Subtype.mk _ _;
    { apply Subtype.mk _ _; constructor;
      { apply HasCoproducts.recur f₁.1.1 f₂.1.1 };
      { apply Natural.recur _ _ f₁.1.2 f₂.1.2 };
      { intros; apply Eq.trans; apply maddRec;
        apply Eq.symm; apply Eq.trans; apply Eq.symm; apply HasCoproducts.comm;
        apply congr₂ <;> apply Eq.symm; apply f₁.2; apply f₂.2 } }; constructor;
    { constructor <;> apply Subtype.eq <;> apply Prod.eq;
      apply HasCoproducts.recurβ₁; apply Natural.recurβ₁;
      apply HasCoproducts.recurβ₂; apply Natural.recurβ₂ };
    { intro φ ⟨H₁, H₂⟩; apply Subtype.eq; subst H₁; subst H₂; apply Prod.eq;
      { apply HasCoproducts.uniq <;> rfl };
      { apply Natural.uniq <;> rfl } }
  end

  instance : HasCoproducts (𝐶𝑜𝑐𝑜𝑛𝑒 J C) :=
  { μ        := Cocone.add,
    inl      := Cocone.inl,
    inr      := Cocone.inr,
    property := Cocone.hasCoproducts }

  def boundaryAdditive : @isAdditive (𝐶𝑜𝑐𝑜𝑛𝑒 J C) (𝐶𝑜𝑐𝑜𝑛𝑒 J C) boundary :=
  begin
    apply additiveCriteria _ _ _ _;
    intro D₁ D₂; apply Cocone.iso _ _ _ _; apply colimAdd;
    { exists Natural.recur _ _ (Natural.id _) (Natural.id _); apply Subtype.mk (Natural.inl _ _);
      constructor <;> apply Subtype.eq <;> funext _;
      { apply (HasInitial.property _).prop };
      { apply HasCoproducts.prop;
        repeat { apply (HasInitial.property _).prop }
        repeat { apply (HasInitial.property _).inh } } };
    { intro; apply HasCoproducts.prop;
      repeat { apply (HasInitial.property _).prop }
      repeat { apply (HasInitial.property _).inh } };
    { intro; apply (HasInitial.property _).prop };
    { intro x y; apply Subtype.eq; apply Prod.eq;
      { apply HasColimits.prop x.1.1 ⟨_, _⟩;
        { intro; show C.com (C.com _ _) _ = _;
          apply Eq.trans; apply congrArg (· ∘ _);
          apply HasCoproducts.recurβ₁; apply HasColimits.recurβ };
        { intro; apply HasColimits.recurβ } };
      { apply Subtype.eq; funext _; apply (HasInitial.property _).prop } };
    { intro x y; apply Subtype.eq; apply Prod.eq;
      { apply HasColimits.prop y.1.1 ⟨_, _⟩;
        { intro; show C.com (C.com _ _) _ = _;
          apply Eq.trans; apply congrArg (· ∘ _);
          apply HasCoproducts.recurβ₂; apply HasColimits.recurβ };
        { intro; apply HasColimits.recurβ } };
      { apply Subtype.eq; funext _; apply (HasInitial.property _).prop } }
  end

  -- this is generalization of an example linked below
  -- https://mathoverflow.net/a/59696
  def Cocone.cobord : Cobordism (𝐶𝑜𝑐𝑜𝑛𝑒 J C) :=
  { boundary := boundary,
    ι        := boundaryNat,
    square   := boundarySquare,
    additive := boundaryAdditive }
end

section
  variable {C : Category} [HasInitial C]

  def Natural.initial (F : Functor C C) : Natural (Δ C 0) F :=
  ⟨λ x, (HasInitial.property (F x)).inh, λ _, (HasInitial.property _).prop _ _⟩

  def Cobordism.trivial : Cobordism C :=
  { boundary := Δ C 0,
    ι        := Natural.initial 1,
    square   := λ _, HasInitial.property,
    additive :=
    begin
      intro a b c i j H x f₁ f₂; exists (HasInitial.property _).inh; constructor;
      { constructor <;> apply (HasInitial.property _).prop };
      intros; apply (HasInitial.property _).prop
    end }
end


import tactic

import group_theory.index
import group_theory.p_group
import group_theory.quotient_group
import group_theory.coset
import group_theory.specific_groups.cyclic
import group_theory.abelianization
import data.finite.basic
import data.finite.card

/-
lemma if_p_then_centre_nontrivial (n : ℕ) [fact (nat.prime n)] {H : Type*} [fintype H] [nontrivial H] [group H] : is_p_group n H → nontrivial (subgroup.center H) :=
begin
  intro h1,
  apply center_nontrivial h1,
end
-/

--open p_group

open fintype
open is_p_group
open group

lemma group.center_eq_top_of_comm {H : Type*} [group H] (h : ∀ a b : H, a * b = b * a) : subgroup.center H = ⊤ :=
begin
  rw subgroup.eq_top_iff',
  intros x y,
  exact (h y x),
end

lemma group.center_eq_top_of_comm_comm_group {H : Type*} [comm_group H] : subgroup.center H = ⊤ :=
begin
  exact comm_group.center_eq_top,
end

lemma group.of_comm_center_eq_top {H : Type*} [group H] (h : (subgroup.center H = ⊤)) : (∀ a b : H, a * b = b * a) :=
begin
  rw subgroup.eq_top_iff' at h,
  intros x y,
  rw (h x),
end

variables (p : ℕ) [fact (nat.prime p)]
variables (G : Type*) [fintype G] [group G]

--def P : Type := {p : ℕ // prime p}  -- it's already defined, namely, as nat.primes

def order_psq : Prop := card G = p^2

namespace order_psq

lemma has_ord_psq : order_psq p G → ∃ q : ℕ , card G = q^2 :=
begin
  intro h1,
  use p,
  apply h1,
end

section G_has_order_psq

variables (hG : order_psq p G)
include hG

lemma p_group : is_p_group p G :=
begin
  exact of_card hG,
end

lemma psq_center_nontrivial [nontrivial G] : nontrivial (subgroup.center G) :=
center_nontrivial (p_group p G hG)

--def ZG : Type* := G ⧸ subgroup.center G --quotient_group.quotient.group (G) (subgroup.center G) --G / (subgroup.center G)

/-
lemma centre_index_not_prime (H : Type*) [group H] [fintype H] [h0 : fintype (H ⧸ (subgroup.center H))] [nZ : fact (subgroup.center H).normal ] : ¬ ( prime (card (H ⧸ (subgroup.center H)))) :=
begin
  intro h1,
  have h2 : is_cyclic (H ⧸ subgroup.center H),
  {
    apply is_cyclic_of_prime_card _,
    exact h0,
    library_search, -- the output of library_search isn't closing the goal for some reason
  },
  --obtain ⟨ g , hg ⟩ : _,
  cases h2 with g,
  cases g with a ha,

  have h4 : (∀ x : G, ∃ y1 : H ⧸ subgroup.center H, ∃ y2 : subgroup.center H , x = y1 * y2),
  {
    sorry,
  },

  sorry,
end
-/

open_locale classical

@[instance] def quotient_with_center_is_fintype : finite (G ⧸ subgroup.center G) :=
infer_instance

/-
lemma center_index_not_prime_BAD : ¬ nat.prime (nat.card (G ⧸ subgroup.center G)) :=
begin
  sorry,
end
-/

lemma center_index_not_prime : ¬ nat.prime (fintype.card (G ⧸ subgroup.center G)) :=
begin
  intro h1,
  haveI : fact(nat.prime (card (G ⧸ subgroup.center G))) := ⟨h1⟩,
  haveI := is_cyclic_of_prime_card (rfl : card (G ⧸ subgroup.center G) = _),
  have h2 := commutative_of_cyclic_center_quotient (quotient_group.mk' (subgroup.center G)) (by simp),
  --have h3 : subgroup.center G = ⊤,
  --{
  --  exact group.center_eq_top_of_comm h2,
  --},
  have h4 : card (G ⧸ subgroup.center G) = 1,  -- That `card` is actually `fintype.card`
  {
    simp_rw [group.center_eq_top_of_comm h2, ← nat.card_eq_fintype_card],
    -- Need to go to `nat.card`
    --rw nat.card_eq_fintype_card.symm,
    exact subgroup.index_top,
  },
  rw h4 at h1,
  --revert h1,
  --change ¬ nat.prime (1 : ℕ),
  exact nat.not_prime_one h1,
end

lemma center_index_not_prime_2 : ¬ nat.prime (fintype.card (G ⧸ subgroup.center G)) :=
begin
  intro h1,
  haveI : fact(nat.prime (card (G ⧸ subgroup.center G))) := ⟨h1⟩,
  haveI := is_cyclic_of_prime_card (rfl : card (G ⧸ subgroup.center G) = _),
  haveI := comm_group_of_cycle_center_quotient (quotient_group.mk' (subgroup.center G)) (by simp),
  have h4 : card (G ⧸ subgroup.center G) = 1,
  {
    sorry,
  },
  rw h4 at h1,
  exact nat.not_prime_one h1,
end

/-
lemma center_index_not_prime_2 : ¬ nat.prime (fintype.card (G ⧸ subgroup.center G)) :=
begin
  intro h1,
  haveI : fact(nat.prime (card (G ⧸ subgroup.center G))) := ⟨h1⟩,
  haveI := is_cyclic_of_prime_card (rfl : card (G ⧸ subgroup.center G) = _),
  have h2 := commutative_of_cyclic_center_quotient (quotient_group.mk' (subgroup.center G)) (by simp),
  haveI := comm_group (G ⧸ (subgroup.center G)),
  --have h3 : subgroup.center G = ⊤,
  --{
  --  exact group.center_eq_top_of_comm h2,
  --},
  have h4 : card (G ⧸ subgroup.center G) = 1,  -- That `card` is actually `fintype.card`
  {
    rw ← comm_group.center_eq_top,
    rw ← nat.card_eq_fintype_card,
    -- Need to go to `nat.card`
    --rw nat.card_eq_fintype_card.symm,
    exact subgroup.index_top,
  },
  rw h4 at h1,
  --revert h1,
  --change ¬ nat.prime (1 : ℕ),
  exact nat.not_prime_one h1,
end
-/

/-
theorem order_psq_are_abelian [nontrivial G] : (∀ x1 x2 : G, x1 * x2 = x2 * x1) :=
begin
  apply group.of_comm_center_eq_top,
  have h1 : (card (subgroup.center G) ∣ card (G)),
  {
    exact (subgroup.center G).card_subgroup_dvd_card,
  },
  have h2 : (card G = p^2),
  {
    exact hG,
    --apply hG,
  },
  rw h2 at h1,
  rw nat.dvd_prime_pow at h1,
  rcases h1 with ⟨k, hk1, hk2⟩,
  swap,
  apply fact.out,
  interval_cases k,
  {
    exfalso,
    have h31 : nontrivial (subgroup.center G),
    {
      exact center_nontrivial (p_group p G hG),
    },
    rw subgroup.nontrivial_iff_exists_ne_one at h31,
    have h32 : (1:G) ∈ subgroup.center G,
    {
      --finish,
      --exact subgroup.has_one (subgroup.center G),
      sorry,
    },
    have h33 : (card (subgroup.center G) ≠ (1 : ℕ)),
    {
      intro hh,
      rw card_eq_one_iff at hh,
      cases h31 with x hhx,
      cases hhx with h34 hx,
      cases hh with y hy,
      cases y,
      have h35 : y_val = x,
      {
        norm_cast at *,
      },
      --cases h31 with y hh1,
      --cases hh1 with h34 hh2,
    },
    contradiction,
  },
  {
    exfalso,
    have h41 : card ()
    sorry,
  },
  {
    sorry,
  },
end
-/

theorem order_psq_are_abelian [nontrivial G] : (∀ x1 x2 : G, x1 * x2 = x2 * x1) :=
begin
  apply group.of_comm_center_eq_top,
  have h1 : (card (subgroup.center G) ∣ card (G)),
  {
    exact (subgroup.center G).card_subgroup_dvd_card,
  },
  have h2 : (card G = p^2),
  {
    exact hG,
    --apply hG,
  },
  rw h2 at h1,
  rw nat.dvd_prime_pow at h1,
  rcases h1 with ⟨k, hk1, hk2⟩,
  swap,
  apply fact.out,
  interval_cases k,
  {
    exfalso,
    have h31 : nontrivial (subgroup.center G),
    {
      exact center_nontrivial (p_group p G hG),
    },
    rw subgroup.nontrivial_iff_exists_ne_one at h31,

  },
  {
    exfalso,
    have h41 : card ()
    sorry,
  },
  {
    sorry,
  },
end

end G_has_order_psq

end order_psq


/- This defines subtype lemmas -/

universe variable u

namespace subtype

variable {α : Type u}
variable {P : α → Prop}

@[simp]
theorem mk_eq_mk (x : α) (p : P x) (y : α) (q : P y) : mk x p = mk y q ↔ x = y :=
begin
  apply iff.intro,
  {
    intro g,
    injection g with h
  },
  {
    intro p,
    simp [p],
  }
end

end subtype