/-
Copyright (c) 2019 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.category_theory.endomorphism
import Mathlib.algebra.group_power.default
import Mathlib.PostPort

universes u v v₁ u₁

namespace Mathlib

/-!
# Conjugate morphisms by isomorphisms

An isomorphism `α : X ≅ Y` defines
- a monoid isomorphism `conj : End X ≃* End Y` by `α.conj f = α.inv ≫ f ≫ α.hom`;
- a group isomorphism `conj_Aut : Aut X ≃* Aut Y` by `α.conj_Aut f = α.symm ≪≫ f ≪≫ α`.

For completeness, we also define `hom_congr : (X ≅ X₁) → (Y ≅ Y₁) → (X ⟶ Y) ≃ (X₁ ⟶ Y₁)`, cf. `equiv.arrow_congr`.
-/

namespace category_theory

namespace iso

/-- If `X` is isomorphic to `X₁` and `Y` is isomorphic to `Y₁`, then
there is a natural bijection between `X ⟶ Y` and `X₁ ⟶ Y₁`. See also `equiv.arrow_congr`. -/
def hom_congr {C : Type u} [category C] {X : C} {Y : C} {X₁ : C} {Y₁ : C} (α : X ≅ X₁)
    (β : Y ≅ Y₁) : (X ⟶ Y) ≃ (X₁ ⟶ Y₁) :=
  equiv.mk (fun (f : X ⟶ Y) => inv α ≫ f ≫ hom β) (fun (f : X₁ ⟶ Y₁) => hom α ≫ f ≫ inv β) sorry
    sorry

@[simp] theorem hom_congr_apply {C : Type u} [category C] {X : C} {Y : C} {X₁ : C} {Y₁ : C}
    (α : X ≅ X₁) (β : Y ≅ Y₁) (f : X ⟶ Y) : coe_fn (hom_congr α β) f = inv α ≫ f ≫ hom β :=
  rfl

theorem hom_congr_comp {C : Type u} [category C] {X : C} {Y : C} {Z : C} {X₁ : C} {Y₁ : C} {Z₁ : C}
    (α : X ≅ X₁) (β : Y ≅ Y₁) (γ : Z ≅ Z₁) (f : X ⟶ Y) (g : Y ⟶ Z) :
    coe_fn (hom_congr α γ) (f ≫ g) = coe_fn (hom_congr α β) f ≫ coe_fn (hom_congr β γ) g :=
  sorry

@[simp] theorem hom_congr_refl {C : Type u} [category C] {X : C} {Y : C} (f : X ⟶ Y) :
    coe_fn (hom_congr (refl X) (refl Y)) f = f :=
  sorry

@[simp] theorem hom_congr_trans {C : Type u} [category C] {X₁ : C} {Y₁ : C} {X₂ : C} {Y₂ : C}
    {X₃ : C} {Y₃ : C} (α₁ : X₁ ≅ X₂) (β₁ : Y₁ ≅ Y₂) (α₂ : X₂ ≅ X₃) (β₂ : Y₂ ≅ Y₃) (f : X₁ ⟶ Y₁) :
    coe_fn (hom_congr (α₁ ≪≫ α₂) (β₁ ≪≫ β₂)) f =
        coe_fn (equiv.trans (hom_congr α₁ β₁) (hom_congr α₂ β₂)) f :=
  sorry

@[simp] theorem hom_congr_symm {C : Type u} [category C] {X₁ : C} {Y₁ : C} {X₂ : C} {Y₂ : C}
    (α : X₁ ≅ X₂) (β : Y₁ ≅ Y₂) : equiv.symm (hom_congr α β) = hom_congr (symm α) (symm β) :=
  rfl

/-- An isomorphism between two objects defines a monoid isomorphism between their
monoid of endomorphisms. -/
def conj {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) : End X ≃* End Y :=
  mul_equiv.mk (equiv.to_fun (hom_congr α α)) (equiv.inv_fun (hom_congr α α)) sorry sorry sorry

theorem conj_apply {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) (f : End X) :
    coe_fn (conj α) f = inv α ≫ f ≫ hom α :=
  rfl

@[simp] theorem conj_comp {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) (f : End X)
    (g : End X) : coe_fn (conj α) (f ≫ g) = coe_fn (conj α) f ≫ coe_fn (conj α) g :=
  mul_equiv.map_mul (conj α) g f

@[simp] theorem conj_id {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) :
    coe_fn (conj α) 𝟙 = 𝟙 :=
  mul_equiv.map_one (conj α)

@[simp] theorem refl_conj {C : Type u} [category C] {X : C} (f : End X) :
    coe_fn (conj (refl X)) f = f :=
  sorry

@[simp] theorem trans_conj {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) {Z : C} (β : Y ≅ Z)
    (f : End X) : coe_fn (conj (α ≪≫ β)) f = coe_fn (conj β) (coe_fn (conj α) f) :=
  hom_congr_trans α α β β f

@[simp] theorem symm_self_conj {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) (f : End X) :
    coe_fn (conj (symm α)) (coe_fn (conj α) f) = f :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (coe_fn (conj (symm α)) (coe_fn (conj α) f) = f))
        (Eq.symm (trans_conj α (symm α) f))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn (conj (α ≪≫ symm α)) f = f)) (self_symm_id α)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn (conj (refl X)) f = f)) (refl_conj f))) (Eq.refl f)))

@[simp] theorem self_symm_conj {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) (f : End Y) :
    coe_fn (conj α) (coe_fn (conj (symm α)) f) = f :=
  symm_self_conj (symm α) f

@[simp] theorem conj_pow {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) (f : End X) (n : ℕ) :
    coe_fn (conj α) (f ^ n) = coe_fn (conj α) f ^ n :=
  monoid_hom.map_pow (mul_equiv.to_monoid_hom (conj α)) f n

/-- `conj` defines a group isomorphisms between groups of automorphisms -/
def conj_Aut {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) : Aut X ≃* Aut Y :=
  mul_equiv.trans (mul_equiv.symm (Aut.units_End_equiv_Aut X))
    (mul_equiv.trans (units.map_equiv (conj α)) (Aut.units_End_equiv_Aut Y))

theorem conj_Aut_apply {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) (f : Aut X) :
    coe_fn (conj_Aut α) f = symm α ≪≫ f ≪≫ α :=
  sorry

@[simp] theorem conj_Aut_hom {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) (f : Aut X) :
    hom (coe_fn (conj_Aut α) f) = coe_fn (conj α) (hom f) :=
  rfl

@[simp] theorem trans_conj_Aut {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) {Z : C}
    (β : Y ≅ Z) (f : Aut X) :
    coe_fn (conj_Aut (α ≪≫ β)) f = coe_fn (conj_Aut β) (coe_fn (conj_Aut α) f) :=
  sorry

@[simp] theorem conj_Aut_mul {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) (f : Aut X)
    (g : Aut X) : coe_fn (conj_Aut α) (f * g) = coe_fn (conj_Aut α) f * coe_fn (conj_Aut α) g :=
  mul_equiv.map_mul (conj_Aut α) f g

@[simp] theorem conj_Aut_trans {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) (f : Aut X)
    (g : Aut X) : coe_fn (conj_Aut α) (f ≪≫ g) = coe_fn (conj_Aut α) f ≪≫ coe_fn (conj_Aut α) g :=
  conj_Aut_mul α g f

@[simp] theorem conj_Aut_pow {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) (f : Aut X)
    (n : ℕ) : coe_fn (conj_Aut α) (f ^ n) = coe_fn (conj_Aut α) f ^ n :=
  monoid_hom.map_pow (mul_equiv.to_monoid_hom (conj_Aut α)) f n

@[simp] theorem conj_Aut_gpow {C : Type u} [category C] {X : C} {Y : C} (α : X ≅ Y) (f : Aut X)
    (n : ℤ) : coe_fn (conj_Aut α) (f ^ n) = coe_fn (conj_Aut α) f ^ n :=
  monoid_hom.map_gpow (mul_equiv.to_monoid_hom (conj_Aut α)) f n

end iso

namespace functor

theorem map_hom_congr {C : Type u} [category C] {D : Type u₁} [category D] (F : C ⥤ D) {X : C}
    {Y : C} {X₁ : C} {Y₁ : C} (α : X ≅ X₁) (β : Y ≅ Y₁) (f : X ⟶ Y) :
    map F (coe_fn (iso.hom_congr α β) f) =
        coe_fn (iso.hom_congr (map_iso F α) (map_iso F β)) (map F f) :=
  sorry

theorem map_conj {C : Type u} [category C] {D : Type u₁} [category D] (F : C ⥤ D) {X : C} {Y : C}
    (α : X ≅ Y) (f : End X) :
    map F (coe_fn (iso.conj α) f) = coe_fn (iso.conj (map_iso F α)) (map F f) :=
  map_hom_congr F α α f

theorem map_conj_Aut {C : Type u} [category C] {D : Type u₁} [category D] (F : C ⥤ D) {X : C}
    {Y : C} (α : X ≅ Y) (f : Aut X) :
    map_iso F (coe_fn (iso.conj_Aut α) f) = coe_fn (iso.conj_Aut (map_iso F α)) (map_iso F f) :=
  sorry

end Mathlib


/-
Copyright (c) 2020 Scott Morrison, Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Bhavik Mehta
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.category_theory.limits.shapes.products
import Mathlib.category_theory.limits.preserves.basic
import Mathlib.PostPort

universes u₁ u₂ v

namespace Mathlib

/-!
# Preserving products

Constructions to relate the notions of preserving products and reflecting products
to concrete fans.

In particular, we show that `pi_comparison G f` is an isomorphism iff `G` preserves
the limit of `f`.
-/

namespace category_theory.limits

/--
The map of a fan is a limit iff the fan consisting of the mapped morphisms is a limit. This
essentially lets us commute `fan.mk` with `functor.map_cone`.
-/
def is_limit_map_cone_fan_mk_equiv {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) {J : Type v} (f : J → C) {P : C} (g : (j : J) → P ⟶ f j) : is_limit (functor.map_cone G (fan.mk P g)) ≃ is_limit (fan.mk (functor.obj G P) fun (j : J) => functor.map G (g j)) :=
  equiv.trans
    (equiv.symm
      (is_limit.postcompose_hom_equiv (discrete.nat_iso fun (j : discrete J) => iso.refl (functor.obj G (f j)))
        (functor.map_cone G (fan.mk P g))))
    (is_limit.equiv_iso_limit
      (cones.ext
        (iso.refl
          (cone.X
            (functor.obj
              (cones.postcompose (iso.hom (discrete.nat_iso fun (j : discrete J) => iso.refl (functor.obj G (f j)))))
              (functor.map_cone G (fan.mk P g)))))
        sorry))

/-- The property of preserving products expressed in terms of fans. -/
def is_limit_fan_mk_obj_of_is_limit {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) {J : Type v} (f : J → C) [preserves_limit (discrete.functor f) G] {P : C} (g : (j : J) → P ⟶ f j) (t : is_limit (fan.mk P g)) : is_limit (fan.mk (functor.obj G P) fun (j : J) => functor.map G (g j)) :=
  coe_fn (is_limit_map_cone_fan_mk_equiv G (fun (j : J) => f j) g) (preserves_limit.preserves t)

/-- The property of reflecting products expressed in terms of fans. -/
def is_limit_of_is_limit_fan_mk_obj {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) {J : Type v} (f : J → C) [reflects_limit (discrete.functor f) G] {P : C} (g : (j : J) → P ⟶ f j) (t : is_limit (fan.mk (functor.obj G P) fun (j : J) => functor.map G (g j))) : is_limit (fan.mk P g) :=
  reflects_limit.reflects
    (coe_fn (equiv.symm (is_limit_map_cone_fan_mk_equiv G (fun (j : J) => f j) fun (j : J) => g j)) t)

/--
If `G` preserves products and `C` has them, then the fan constructed of the mapped projection of a
product is a limit.
-/
def is_limit_of_has_product_of_preserves_limit {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) {J : Type v} (f : J → C) [has_product f] [preserves_limit (discrete.functor f) G] : is_limit (fan.mk (functor.obj G (∏ f)) fun (j : J) => functor.map G (pi.π f j)) :=
  is_limit_fan_mk_obj_of_is_limit G f (fun (j : J) => pi.π f j) (product_is_product fun (j : J) => f j)

/-- If `pi_comparison G f` is an isomorphism, then `G` preserves the limit of `f`. -/
def preserves_product.of_iso_comparison {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) {J : Type v} (f : J → C) [has_product f] [has_product fun (j : J) => functor.obj G (f j)] [i : is_iso (pi_comparison G f)] : preserves_limit (discrete.functor f) G :=
  preserves_limit_of_preserves_limit_cone (product_is_product f)
    (coe_fn (equiv.symm (is_limit_map_cone_fan_mk_equiv G (fun (b : J) => f b) (pi.π f)))
      (is_limit.of_point_iso (limit.is_limit (discrete.functor fun (j : J) => functor.obj G (f j)))))

/--
If `G` preserves limits, we have an isomorphism from the image of a product to the product of the
images.
-/
def preserves_product.iso {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) {J : Type v} (f : J → C) [has_product f] [has_product fun (j : J) => functor.obj G (f j)] [preserves_limit (discrete.functor f) G] : functor.obj G (∏ f) ≅ ∏ fun (j : J) => functor.obj G (f j) :=
  is_limit.cone_point_unique_up_to_iso (is_limit_of_has_product_of_preserves_limit G f)
    (limit.is_limit (discrete.functor fun (j : J) => functor.obj G (f j)))

@[simp] theorem preserves_product.iso_hom {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) {J : Type v} (f : J → C) [has_product f] [has_product fun (j : J) => functor.obj G (f j)] [preserves_limit (discrete.functor f) G] : iso.hom (preserves_product.iso G f) = pi_comparison G f :=
  rfl

protected instance pi_comparison.category_theory.is_iso {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) {J : Type v} (f : J → C) [has_product f] [has_product fun (j : J) => functor.obj G (f j)] [preserves_limit (discrete.functor f) G] : is_iso (pi_comparison G f) :=
  eq.mpr sorry (is_iso.of_iso (preserves_product.iso G f))


/-
Copyright (c) 2019 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.equiv.local_equiv
import Mathlib.topology.opens
import Mathlib.PostPort

universes u_5 u_6 l u_1 u_2 u_3 u_4

namespace Mathlib

/-!
# Local homeomorphisms

This file defines homeomorphisms between open subsets of topological spaces. An element `e` of
`local_homeomorph α β` is an extension of `local_equiv α β`, i.e., it is a pair of functions
`e.to_fun` and `e.inv_fun`, inverse of each other on the sets `e.source` and `e.target`.
Additionally, we require that these sets are open, and that the functions are continuous on them.
Equivalently, they are homeomorphisms there.

As in equivs, we register a coercion to functions, and we use `e x` and `e.symm x` throughout
instead of `e.to_fun x` and `e.inv_fun x`.

## Main definitions

`homeomorph.to_local_homeomorph`: associating a local homeomorphism to a homeomorphism, with
                                  source = target = univ
`local_homeomorph.symm`  : the inverse of a local homeomorphism
`local_homeomorph.trans` : the composition of two local homeomorphisms
`local_homeomorph.refl`  : the identity local homeomorphism
`local_homeomorph.of_set`: the identity on a set `s`
`eq_on_source`           : equivalence relation describing the "right" notion of equality for local
                           homeomorphisms

## Implementation notes

Most statements are copied from their local_equiv versions, although some care is required
especially when restricting to subsets, as these should be open subsets.

For design notes, see `local_equiv.lean`.
-/

/-- local homeomorphisms, defined on open subsets of the space -/
structure local_homeomorph (α : Type u_5) (β : Type u_6) [topological_space α] [topological_space β]
extends local_equiv α β
where
  open_source : is_open (local_equiv.source _to_local_equiv)
  open_target : is_open (local_equiv.target _to_local_equiv)
  continuous_to_fun : continuous_on (local_equiv.to_fun _to_local_equiv) (local_equiv.source _to_local_equiv)
  continuous_inv_fun : continuous_on (local_equiv.inv_fun _to_local_equiv) (local_equiv.target _to_local_equiv)

/-- A homeomorphism induces a local homeomorphism on the whole space -/
def homeomorph.to_local_homeomorph {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : α ≃ₜ β) : local_homeomorph α β :=
  local_homeomorph.mk
    (local_equiv.mk (local_equiv.to_fun (equiv.to_local_equiv (homeomorph.to_equiv e)))
      (local_equiv.inv_fun (equiv.to_local_equiv (homeomorph.to_equiv e)))
      (local_equiv.source (equiv.to_local_equiv (homeomorph.to_equiv e)))
      (local_equiv.target (equiv.to_local_equiv (homeomorph.to_equiv e))) sorry sorry sorry sorry)
    is_open_univ is_open_univ sorry sorry

namespace local_homeomorph

protected instance has_coe_to_fun {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] : has_coe_to_fun (local_homeomorph α β) :=
  has_coe_to_fun.mk (fun (e : local_homeomorph α β) => α → β)
    fun (e : local_homeomorph α β) => local_equiv.to_fun (to_local_equiv e)

/-- The inverse of a local homeomorphism -/
protected def symm {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : local_homeomorph β α :=
  mk
    (local_equiv.mk (local_equiv.to_fun (local_equiv.symm (to_local_equiv e)))
      (local_equiv.inv_fun (local_equiv.symm (to_local_equiv e)))
      (local_equiv.source (local_equiv.symm (to_local_equiv e)))
      (local_equiv.target (local_equiv.symm (to_local_equiv e))) sorry sorry sorry sorry)
    (open_target e) (open_source e) (continuous_inv_fun e) (continuous_to_fun e)

protected theorem continuous_on {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : continuous_on (⇑e) (local_equiv.source (to_local_equiv e)) :=
  continuous_to_fun e

theorem continuous_on_symm {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : continuous_on (⇑(local_homeomorph.symm e)) (local_equiv.target (to_local_equiv e)) :=
  continuous_inv_fun e

@[simp] theorem mk_coe {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_equiv α β) (a : is_open (local_equiv.source e)) (b : is_open (local_equiv.target e)) (c : continuous_on (local_equiv.to_fun e) (local_equiv.source e)) (d : continuous_on (local_equiv.inv_fun e) (local_equiv.target e)) : ⇑(mk e a b c d) = ⇑e :=
  rfl

@[simp] theorem mk_coe_symm {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_equiv α β) (a : is_open (local_equiv.source e)) (b : is_open (local_equiv.target e)) (c : continuous_on (local_equiv.to_fun e) (local_equiv.source e)) (d : continuous_on (local_equiv.inv_fun e) (local_equiv.target e)) : ⇑(local_homeomorph.symm (mk e a b c d)) = ⇑(local_equiv.symm e) :=
  rfl

/- Register a few simp lemmas to make sure that `simp` puts the application of a local
homeomorphism in its normal form, i.e., in terms of its coercion to a function. -/

@[simp] theorem to_fun_eq_coe {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : local_equiv.to_fun (to_local_equiv e) = ⇑e :=
  rfl

@[simp] theorem inv_fun_eq_coe {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : local_equiv.inv_fun (to_local_equiv e) = ⇑(local_homeomorph.symm e) :=
  rfl

@[simp] theorem coe_coe {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : ⇑(to_local_equiv e) = ⇑e :=
  rfl

@[simp] theorem coe_coe_symm {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : ⇑(local_equiv.symm (to_local_equiv e)) = ⇑(local_homeomorph.symm e) :=
  rfl

@[simp] theorem map_source {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : α} (h : x ∈ local_equiv.source (to_local_equiv e)) : coe_fn e x ∈ local_equiv.target (to_local_equiv e) :=
  local_equiv.map_source' (to_local_equiv e) h

@[simp] theorem map_target {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : β} (h : x ∈ local_equiv.target (to_local_equiv e)) : coe_fn (local_homeomorph.symm e) x ∈ local_equiv.source (to_local_equiv e) :=
  local_equiv.map_target' (to_local_equiv e) h

@[simp] theorem left_inv {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : α} (h : x ∈ local_equiv.source (to_local_equiv e)) : coe_fn (local_homeomorph.symm e) (coe_fn e x) = x :=
  local_equiv.left_inv' (to_local_equiv e) h

@[simp] theorem right_inv {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : β} (h : x ∈ local_equiv.target (to_local_equiv e)) : coe_fn e (coe_fn (local_homeomorph.symm e) x) = x :=
  local_equiv.right_inv' (to_local_equiv e) h

theorem source_preimage_target {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : local_equiv.source (to_local_equiv e) ⊆ ⇑e ⁻¹' local_equiv.target (to_local_equiv e) :=
  fun (_x : α) (h : _x ∈ local_equiv.source (to_local_equiv e)) => map_source e h

theorem eq_of_local_equiv_eq {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {e : local_homeomorph α β} {e' : local_homeomorph α β} (h : to_local_equiv e = to_local_equiv e') : e = e' := sorry

theorem eventually_left_inverse {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : α} (hx : x ∈ local_equiv.source (to_local_equiv e)) : filter.eventually (fun (y : α) => coe_fn (local_homeomorph.symm e) (coe_fn e y) = y) (nhds x) :=
  filter.eventually.mono (is_open.eventually_mem (open_source e) hx) (local_equiv.left_inv' (to_local_equiv e))

theorem eventually_left_inverse' {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : β} (hx : x ∈ local_equiv.target (to_local_equiv e)) : filter.eventually (fun (y : α) => coe_fn (local_homeomorph.symm e) (coe_fn e y) = y)
  (nhds (coe_fn (local_homeomorph.symm e) x)) :=
  eventually_left_inverse e (map_target e hx)

theorem eventually_right_inverse {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : β} (hx : x ∈ local_equiv.target (to_local_equiv e)) : filter.eventually (fun (y : β) => coe_fn e (coe_fn (local_homeomorph.symm e) y) = y) (nhds x) :=
  filter.eventually.mono (is_open.eventually_mem (open_target e) hx) (local_equiv.right_inv' (to_local_equiv e))

theorem eventually_right_inverse' {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : α} (hx : x ∈ local_equiv.source (to_local_equiv e)) : filter.eventually (fun (y : β) => coe_fn e (coe_fn (local_homeomorph.symm e) y) = y) (nhds (coe_fn e x)) :=
  eventually_right_inverse e (map_source e hx)

theorem eventually_ne_nhds_within {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : α} (hx : x ∈ local_equiv.source (to_local_equiv e)) : filter.eventually (fun (x' : α) => coe_fn e x' ≠ coe_fn e x) (nhds_within x (singleton xᶜ)) := sorry

theorem image_eq_target_inter_inv_preimage {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {s : set α} (h : s ⊆ local_equiv.source (to_local_equiv e)) : ⇑e '' s = local_equiv.target (to_local_equiv e) ∩ ⇑(local_homeomorph.symm e) ⁻¹' s :=
  local_equiv.image_eq_target_inter_inv_preimage (to_local_equiv e) h

theorem image_inter_source_eq {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) : ⇑e '' (s ∩ local_equiv.source (to_local_equiv e)) =
  local_equiv.target (to_local_equiv e) ∩ ⇑(local_homeomorph.symm e) ⁻¹' (s ∩ local_equiv.source (to_local_equiv e)) :=
  image_eq_target_inter_inv_preimage e (set.inter_subset_right s (local_equiv.source (to_local_equiv e)))

theorem symm_image_eq_source_inter_preimage {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {s : set β} (h : s ⊆ local_equiv.target (to_local_equiv e)) : ⇑(local_homeomorph.symm e) '' s = local_equiv.source (to_local_equiv e) ∩ ⇑e ⁻¹' s :=
  image_eq_target_inter_inv_preimage (local_homeomorph.symm e) h

theorem symm_image_inter_target_eq {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set β) : ⇑(local_homeomorph.symm e) '' (s ∩ local_equiv.target (to_local_equiv e)) =
  local_equiv.source (to_local_equiv e) ∩ ⇑e ⁻¹' (s ∩ local_equiv.target (to_local_equiv e)) :=
  image_inter_source_eq (local_homeomorph.symm e) s

/-- Two local homeomorphisms are equal when they have equal `to_fun`, `inv_fun` and `source`.
It is not sufficient to have equal `to_fun` and `source`, as this only determines `inv_fun` on
the target. This would only be true for a weaker notion of equality, arguably the right one,
called `eq_on_source`. -/
protected theorem ext {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (e' : local_homeomorph α β) (h : ∀ (x : α), coe_fn e x = coe_fn e' x) (hinv : ∀ (x : β), coe_fn (local_homeomorph.symm e) x = coe_fn (local_homeomorph.symm e') x) (hs : local_equiv.source (to_local_equiv e) = local_equiv.source (to_local_equiv e')) : e = e' :=
  eq_of_local_equiv_eq (local_equiv.ext h hinv hs)

-- The following lemmas are already simp via local_equiv

@[simp] theorem symm_to_local_equiv {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : to_local_equiv (local_homeomorph.symm e) = local_equiv.symm (to_local_equiv e) :=
  rfl

theorem symm_source {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : local_equiv.source (to_local_equiv (local_homeomorph.symm e)) = local_equiv.target (to_local_equiv e) :=
  rfl

theorem symm_target {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : local_equiv.target (to_local_equiv (local_homeomorph.symm e)) = local_equiv.source (to_local_equiv e) :=
  rfl

@[simp] theorem symm_symm {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : local_homeomorph.symm (local_homeomorph.symm e) = e := sorry

/-- A local homeomorphism is continuous at any point of its source -/
protected theorem continuous_at {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : α} (h : x ∈ local_equiv.source (to_local_equiv e)) : continuous_at (⇑e) x :=
  continuous_within_at.continuous_at (local_homeomorph.continuous_on e x h) (mem_nhds_sets (open_source e) h)

/-- A local homeomorphism inverse is continuous at any point of its target -/
theorem continuous_at_symm {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : β} (h : x ∈ local_equiv.target (to_local_equiv e)) : continuous_at (⇑(local_homeomorph.symm e)) x :=
  local_homeomorph.continuous_at (local_homeomorph.symm e) h

theorem tendsto_symm {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : α} (hx : x ∈ local_equiv.source (to_local_equiv e)) : filter.tendsto (⇑(local_homeomorph.symm e)) (nhds (coe_fn e x)) (nhds x) := sorry

theorem map_nhds_eq {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {x : α} (hx : x ∈ local_equiv.source (to_local_equiv e)) : filter.map (⇑e) (nhds x) = nhds (coe_fn e x) :=
  le_antisymm (local_homeomorph.continuous_at e hx)
    (filter.le_map_of_right_inverse (eventually_right_inverse' e hx) (tendsto_symm e hx))

/-- Preimage of interior or interior of preimage coincide for local homeomorphisms, when restricted
to the source. -/
theorem preimage_interior {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set β) : local_equiv.source (to_local_equiv e) ∩ ⇑e ⁻¹' interior s = local_equiv.source (to_local_equiv e) ∩ interior (⇑e ⁻¹' s) := sorry

theorem preimage_open_of_open {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {s : set β} (hs : is_open s) : is_open (local_equiv.source (to_local_equiv e) ∩ ⇑e ⁻¹' s) :=
  continuous_on.preimage_open_of_open (local_homeomorph.continuous_on e) (open_source e) hs

theorem preimage_open_of_open_symm {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {s : set α} (hs : is_open s) : is_open (local_equiv.target (to_local_equiv e) ∩ ⇑(local_homeomorph.symm e) ⁻¹' s) :=
  continuous_on.preimage_open_of_open (local_homeomorph.continuous_on (local_homeomorph.symm e)) (open_target e) hs

/-- The image of an open set in the source is open. -/
theorem image_open_of_open {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {s : set α} (hs : is_open s) (h : s ⊆ local_equiv.source (to_local_equiv e)) : is_open (⇑e '' s) :=
  eq.mpr
    (id (Eq._oldrec (Eq.refl (is_open (⇑e '' s))) (local_equiv.image_eq_target_inter_inv_preimage (to_local_equiv e) h)))
    (continuous_on.preimage_open_of_open (continuous_on_symm e) (open_target e) hs)

/-- The image of the restriction of an open set to the source is open. -/
theorem image_open_of_open' {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {s : set α} (hs : is_open s) : is_open (⇑e '' (s ∩ local_equiv.source (to_local_equiv e))) := sorry

/-- Restricting a local homeomorphism `e` to `e.source ∩ s` when `s` is open. This is sometimes hard
to use because of the openness assumption, but it has the advantage that when it can
be used then its local_equiv is defeq to local_equiv.restr -/
protected def restr_open {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) (hs : is_open s) : local_homeomorph α β :=
  mk
    (local_equiv.mk (local_equiv.to_fun (local_equiv.restr (to_local_equiv e) s))
      (local_equiv.inv_fun (local_equiv.restr (to_local_equiv e) s))
      (local_equiv.source (local_equiv.restr (to_local_equiv e) s))
      (local_equiv.target (local_equiv.restr (to_local_equiv e) s)) sorry sorry sorry sorry)
    sorry sorry sorry sorry

@[simp] theorem restr_open_to_local_equiv {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) (hs : is_open s) : to_local_equiv (local_homeomorph.restr_open e s hs) = local_equiv.restr (to_local_equiv e) s :=
  rfl

-- Already simp via local_equiv

theorem restr_open_source {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) (hs : is_open s) : local_equiv.source (to_local_equiv (local_homeomorph.restr_open e s hs)) = local_equiv.source (to_local_equiv e) ∩ s :=
  rfl

/-- Restricting a local homeomorphism `e` to `e.source ∩ interior s`. We use the interior to make
sure that the restriction is well defined whatever the set s, since local homeomorphisms are by
definition defined on open sets. In applications where `s` is open, this coincides with the
restriction of local equivalences -/
protected def restr {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) : local_homeomorph α β :=
  local_homeomorph.restr_open e (interior s) is_open_interior

@[simp] theorem restr_to_local_equiv {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) : to_local_equiv (local_homeomorph.restr e s) = local_equiv.restr (to_local_equiv e) (interior s) :=
  rfl

@[simp] theorem restr_coe {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) : ⇑(local_homeomorph.restr e s) = ⇑e :=
  rfl

@[simp] theorem restr_coe_symm {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) : ⇑(local_homeomorph.symm (local_homeomorph.restr e s)) = ⇑(local_homeomorph.symm e) :=
  rfl

theorem restr_source {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) : local_equiv.source (to_local_equiv (local_homeomorph.restr e s)) = local_equiv.source (to_local_equiv e) ∩ interior s :=
  rfl

theorem restr_target {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) : local_equiv.target (to_local_equiv (local_homeomorph.restr e s)) =
  local_equiv.target (to_local_equiv e) ∩ ⇑(local_homeomorph.symm e) ⁻¹' interior s :=
  rfl

theorem restr_source' {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) (hs : is_open s) : local_equiv.source (to_local_equiv (local_homeomorph.restr e s)) = local_equiv.source (to_local_equiv e) ∩ s := sorry

theorem restr_to_local_equiv' {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) (hs : is_open s) : to_local_equiv (local_homeomorph.restr e s) = local_equiv.restr (to_local_equiv e) s := sorry

theorem restr_eq_of_source_subset {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {e : local_homeomorph α β} {s : set α} (h : local_equiv.source (to_local_equiv e) ⊆ s) : local_homeomorph.restr e s = e := sorry

@[simp] theorem restr_univ {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {e : local_homeomorph α β} : local_homeomorph.restr e set.univ = e :=
  restr_eq_of_source_subset (set.subset_univ (local_equiv.source (to_local_equiv e)))

theorem restr_source_inter {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : set α) : local_homeomorph.restr e (local_equiv.source (to_local_equiv e) ∩ s) = local_homeomorph.restr e s := sorry

/-- The identity on the whole space as a local homeomorphism. -/
protected def refl (α : Type u_1) [topological_space α] : local_homeomorph α α :=
  homeomorph.to_local_homeomorph (homeomorph.refl α)

@[simp] theorem refl_local_equiv {α : Type u_1} [topological_space α] : to_local_equiv (local_homeomorph.refl α) = local_equiv.refl α :=
  rfl

theorem refl_source {α : Type u_1} [topological_space α] : local_equiv.source (to_local_equiv (local_homeomorph.refl α)) = set.univ :=
  rfl

theorem refl_target {α : Type u_1} [topological_space α] : local_equiv.target (to_local_equiv (local_homeomorph.refl α)) = set.univ :=
  rfl

@[simp] theorem refl_symm {α : Type u_1} [topological_space α] : local_homeomorph.symm (local_homeomorph.refl α) = local_homeomorph.refl α :=
  rfl

@[simp] theorem refl_coe {α : Type u_1} [topological_space α] : ⇑(local_homeomorph.refl α) = id :=
  rfl

/-- The identity local equiv on a set `s` -/
def of_set {α : Type u_1} [topological_space α] (s : set α) (hs : is_open s) : local_homeomorph α α :=
  mk
    (local_equiv.mk (local_equiv.to_fun (local_equiv.of_set s)) (local_equiv.inv_fun (local_equiv.of_set s))
      (local_equiv.source (local_equiv.of_set s)) (local_equiv.target (local_equiv.of_set s)) sorry sorry sorry sorry)
    hs hs sorry sorry

@[simp] theorem of_set_to_local_equiv {α : Type u_1} [topological_space α] {s : set α} (hs : is_open s) : to_local_equiv (of_set s hs) = local_equiv.of_set s :=
  rfl

theorem of_set_source {α : Type u_1} [topological_space α] {s : set α} (hs : is_open s) : local_equiv.source (to_local_equiv (of_set s hs)) = s :=
  rfl

theorem of_set_target {α : Type u_1} [topological_space α] {s : set α} (hs : is_open s) : local_equiv.target (to_local_equiv (of_set s hs)) = s :=
  rfl

@[simp] theorem of_set_coe {α : Type u_1} [topological_space α] {s : set α} (hs : is_open s) : ⇑(of_set s hs) = id :=
  rfl

@[simp] theorem of_set_symm {α : Type u_1} [topological_space α] {s : set α} (hs : is_open s) : local_homeomorph.symm (of_set s hs) = of_set s hs :=
  rfl

@[simp] theorem of_set_univ_eq_refl {α : Type u_1} [topological_space α] : of_set set.univ is_open_univ = local_homeomorph.refl α := sorry

/-- Composition of two local homeomorphisms when the target of the first and the source of
the second coincide. -/
protected def trans' {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) (h : local_equiv.target (to_local_equiv e) = local_equiv.source (to_local_equiv e')) : local_homeomorph α γ :=
  mk
    (local_equiv.mk (local_equiv.to_fun (local_equiv.trans' (to_local_equiv e) (to_local_equiv e') h))
      (local_equiv.inv_fun (local_equiv.trans' (to_local_equiv e) (to_local_equiv e') h))
      (local_equiv.source (local_equiv.trans' (to_local_equiv e) (to_local_equiv e') h))
      (local_equiv.target (local_equiv.trans' (to_local_equiv e) (to_local_equiv e') h)) sorry sorry sorry sorry)
    (open_source e) (open_target e') sorry sorry

/-- Composing two local homeomorphisms, by restricting to the maximal domain where their
composition is well defined. -/
protected def trans {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : local_homeomorph α γ :=
  local_homeomorph.trans'
    (local_homeomorph.symm
      (local_homeomorph.restr_open (local_homeomorph.symm e) (local_equiv.source (to_local_equiv e')) (open_source e')))
    (local_homeomorph.restr_open e' (local_equiv.target (to_local_equiv e)) (open_target e)) sorry

@[simp] theorem trans_to_local_equiv {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : to_local_equiv (local_homeomorph.trans e e') = local_equiv.trans (to_local_equiv e) (to_local_equiv e') :=
  rfl

@[simp] theorem coe_trans {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : ⇑(local_homeomorph.trans e e') = ⇑e' ∘ ⇑e :=
  rfl

@[simp] theorem coe_trans_symm {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : ⇑(local_homeomorph.symm (local_homeomorph.trans e e')) = ⇑(local_homeomorph.symm e) ∘ ⇑(local_homeomorph.symm e') :=
  rfl

theorem trans_symm_eq_symm_trans_symm {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : local_homeomorph.symm (local_homeomorph.trans e e') =
  local_homeomorph.trans (local_homeomorph.symm e') (local_homeomorph.symm e) := sorry

/- This could be considered as a simp lemma, but there are many situations where it makes something
simple into something more complicated. -/

theorem trans_source {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : local_equiv.source (to_local_equiv (local_homeomorph.trans e e')) =
  local_equiv.source (to_local_equiv e) ∩ ⇑e ⁻¹' local_equiv.source (to_local_equiv e') :=
  local_equiv.trans_source (to_local_equiv e) (to_local_equiv e')

theorem trans_source' {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : local_equiv.source (to_local_equiv (local_homeomorph.trans e e')) =
  local_equiv.source (to_local_equiv e) ∩
    ⇑e ⁻¹' (local_equiv.target (to_local_equiv e) ∩ local_equiv.source (to_local_equiv e')) :=
  local_equiv.trans_source' (to_local_equiv e) (to_local_equiv e')

theorem trans_source'' {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : local_equiv.source (to_local_equiv (local_homeomorph.trans e e')) =
  ⇑(local_homeomorph.symm e) '' (local_equiv.target (to_local_equiv e) ∩ local_equiv.source (to_local_equiv e')) :=
  local_equiv.trans_source'' (to_local_equiv e) (to_local_equiv e')

theorem image_trans_source {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : ⇑e '' local_equiv.source (to_local_equiv (local_homeomorph.trans e e')) =
  local_equiv.target (to_local_equiv e) ∩ local_equiv.source (to_local_equiv e') :=
  local_equiv.image_trans_source (to_local_equiv e) (to_local_equiv e')

theorem trans_target {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : local_equiv.target (to_local_equiv (local_homeomorph.trans e e')) =
  local_equiv.target (to_local_equiv e') ∩ ⇑(local_homeomorph.symm e') ⁻¹' local_equiv.target (to_local_equiv e) :=
  rfl

theorem trans_target' {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : local_equiv.target (to_local_equiv (local_homeomorph.trans e e')) =
  local_equiv.target (to_local_equiv e') ∩
    ⇑(local_homeomorph.symm e') ⁻¹' (local_equiv.source (to_local_equiv e') ∩ local_equiv.target (to_local_equiv e)) :=
  trans_source' (local_homeomorph.symm e') (local_homeomorph.symm e)

theorem trans_target'' {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : local_equiv.target (to_local_equiv (local_homeomorph.trans e e')) =
  ⇑e' '' (local_equiv.source (to_local_equiv e') ∩ local_equiv.target (to_local_equiv e)) :=
  trans_source'' (local_homeomorph.symm e') (local_homeomorph.symm e)

theorem inv_image_trans_target {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) : ⇑(local_homeomorph.symm e') '' local_equiv.target (to_local_equiv (local_homeomorph.trans e e')) =
  local_equiv.source (to_local_equiv e') ∩ local_equiv.target (to_local_equiv e) :=
  image_trans_source (local_homeomorph.symm e') (local_homeomorph.symm e)

theorem trans_assoc {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} [topological_space α] [topological_space β] [topological_space γ] [topological_space δ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) (e'' : local_homeomorph γ δ) : local_homeomorph.trans (local_homeomorph.trans e e') e'' = local_homeomorph.trans e (local_homeomorph.trans e' e'') :=
  eq_of_local_equiv_eq (local_equiv.trans_assoc (to_local_equiv e) (to_local_equiv e') (to_local_equiv e''))

@[simp] theorem trans_refl {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : local_homeomorph.trans e (local_homeomorph.refl β) = e :=
  eq_of_local_equiv_eq (local_equiv.trans_refl (to_local_equiv e))

@[simp] theorem refl_trans {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : local_homeomorph.trans (local_homeomorph.refl α) e = e :=
  eq_of_local_equiv_eq (local_equiv.refl_trans (to_local_equiv e))

theorem trans_of_set {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {s : set β} (hs : is_open s) : local_homeomorph.trans e (of_set s hs) = local_homeomorph.restr e (⇑e ⁻¹' s) := sorry

theorem trans_of_set' {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {s : set β} (hs : is_open s) : local_homeomorph.trans e (of_set s hs) = local_homeomorph.restr e (local_equiv.source (to_local_equiv e) ∩ ⇑e ⁻¹' s) := sorry

theorem of_set_trans {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {s : set α} (hs : is_open s) : local_homeomorph.trans (of_set s hs) e = local_homeomorph.restr e s := sorry

theorem of_set_trans' {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) {s : set α} (hs : is_open s) : local_homeomorph.trans (of_set s hs) e = local_homeomorph.restr e (local_equiv.source (to_local_equiv e) ∩ s) := sorry

@[simp] theorem of_set_trans_of_set {α : Type u_1} [topological_space α] {s : set α} (hs : is_open s) {s' : set α} (hs' : is_open s') : local_homeomorph.trans (of_set s hs) (of_set s' hs') = of_set (s ∩ s') (is_open_inter hs hs') := sorry

theorem restr_trans {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) (e' : local_homeomorph β γ) (s : set α) : local_homeomorph.trans (local_homeomorph.restr e s) e' = local_homeomorph.restr (local_homeomorph.trans e e') s :=
  eq_of_local_equiv_eq (local_equiv.restr_trans (to_local_equiv e) (to_local_equiv e') (interior s))

/-- `eq_on_source e e'` means that `e` and `e'` have the same source, and coincide there. They
should really be considered the same local equiv. -/
def eq_on_source {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (e' : local_homeomorph α β) :=
  local_equiv.source (to_local_equiv e) = local_equiv.source (to_local_equiv e') ∧
    set.eq_on (⇑e) (⇑e') (local_equiv.source (to_local_equiv e))

theorem eq_on_source_iff {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (e' : local_homeomorph α β) : eq_on_source e e' ↔ local_equiv.eq_on_source (to_local_equiv e) (to_local_equiv e') :=
  iff.rfl

/-- `eq_on_source` is an equivalence relation -/
protected instance setoid {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] : setoid (local_homeomorph α β) :=
  setoid.mk eq_on_source sorry

theorem eq_on_source_refl {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : e ≈ e :=
  setoid.refl e

/-- If two local homeomorphisms are equivalent, so are their inverses -/
theorem eq_on_source.symm' {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {e : local_homeomorph α β} {e' : local_homeomorph α β} (h : e ≈ e') : local_homeomorph.symm e ≈ local_homeomorph.symm e' :=
  local_equiv.eq_on_source.symm' h

/-- Two equivalent local homeomorphisms have the same source -/
theorem eq_on_source.source_eq {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {e : local_homeomorph α β} {e' : local_homeomorph α β} (h : e ≈ e') : local_equiv.source (to_local_equiv e) = local_equiv.source (to_local_equiv e') :=
  and.left h

/-- Two equivalent local homeomorphisms have the same target -/
theorem eq_on_source.target_eq {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {e : local_homeomorph α β} {e' : local_homeomorph α β} (h : e ≈ e') : local_equiv.target (to_local_equiv e) = local_equiv.target (to_local_equiv e') :=
  and.left (eq_on_source.symm' h)

/-- Two equivalent local homeomorphisms have coinciding `to_fun` on the source -/
theorem eq_on_source.eq_on {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {e : local_homeomorph α β} {e' : local_homeomorph α β} (h : e ≈ e') : set.eq_on (⇑e) (⇑e') (local_equiv.source (to_local_equiv e)) :=
  and.right h

/-- Two equivalent local homeomorphisms have coinciding `inv_fun` on the target -/
theorem eq_on_source.symm_eq_on_target {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {e : local_homeomorph α β} {e' : local_homeomorph α β} (h : e ≈ e') : set.eq_on (⇑(local_homeomorph.symm e)) (⇑(local_homeomorph.symm e')) (local_equiv.target (to_local_equiv e)) :=
  and.right (eq_on_source.symm' h)

/-- Composition of local homeomorphisms respects equivalence -/
theorem eq_on_source.trans' {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] {e : local_homeomorph α β} {e' : local_homeomorph α β} {f : local_homeomorph β γ} {f' : local_homeomorph β γ} (he : e ≈ e') (hf : f ≈ f') : local_homeomorph.trans e f ≈ local_homeomorph.trans e' f' :=
  local_equiv.eq_on_source.trans' he hf

/-- Restriction of local homeomorphisms respects equivalence -/
theorem eq_on_source.restr {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {e : local_homeomorph α β} {e' : local_homeomorph α β} (he : e ≈ e') (s : set α) : local_homeomorph.restr e s ≈ local_homeomorph.restr e' s :=
  local_equiv.eq_on_source.restr he (interior s)

/-- Composition of a local homeomorphism and its inverse is equivalent to the restriction of the
identity to the source -/
theorem trans_self_symm {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : local_homeomorph.trans e (local_homeomorph.symm e) ≈ of_set (local_equiv.source (to_local_equiv e)) (open_source e) :=
  local_equiv.trans_self_symm (to_local_equiv e)

theorem trans_symm_self {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) : local_homeomorph.trans (local_homeomorph.symm e) e ≈ of_set (local_equiv.target (to_local_equiv e)) (open_target e) :=
  trans_self_symm (local_homeomorph.symm e)

theorem eq_of_eq_on_source_univ {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {e : local_homeomorph α β} {e' : local_homeomorph α β} (h : e ≈ e') (s : local_equiv.source (to_local_equiv e) = set.univ) (t : local_equiv.target (to_local_equiv e) = set.univ) : e = e' :=
  eq_of_local_equiv_eq (local_equiv.eq_of_eq_on_source_univ (to_local_equiv e) (to_local_equiv e') h s t)

/-- The product of two local homeomorphisms, as a local homeomorphism on the product space. -/
def prod {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} [topological_space α] [topological_space β] [topological_space γ] [topological_space δ] (e : local_homeomorph α β) (e' : local_homeomorph γ δ) : local_homeomorph (α × γ) (β × δ) :=
  mk
    (local_equiv.mk (local_equiv.to_fun (local_equiv.prod (to_local_equiv e) (to_local_equiv e')))
      (local_equiv.inv_fun (local_equiv.prod (to_local_equiv e) (to_local_equiv e')))
      (local_equiv.source (local_equiv.prod (to_local_equiv e) (to_local_equiv e')))
      (local_equiv.target (local_equiv.prod (to_local_equiv e) (to_local_equiv e'))) sorry sorry sorry sorry)
    sorry sorry sorry sorry

@[simp] theorem prod_to_local_equiv {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} [topological_space α] [topological_space β] [topological_space γ] [topological_space δ] (e : local_homeomorph α β) (e' : local_homeomorph γ δ) : to_local_equiv (prod e e') = local_equiv.prod (to_local_equiv e) (to_local_equiv e') :=
  rfl

theorem prod_source {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} [topological_space α] [topological_space β] [topological_space γ] [topological_space δ] (e : local_homeomorph α β) (e' : local_homeomorph γ δ) : local_equiv.source (to_local_equiv (prod e e')) =
  set.prod (local_equiv.source (to_local_equiv e)) (local_equiv.source (to_local_equiv e')) :=
  rfl

theorem prod_target {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} [topological_space α] [topological_space β] [topological_space γ] [topological_space δ] (e : local_homeomorph α β) (e' : local_homeomorph γ δ) : local_equiv.target (to_local_equiv (prod e e')) =
  set.prod (local_equiv.target (to_local_equiv e)) (local_equiv.target (to_local_equiv e')) :=
  rfl

@[simp] theorem prod_coe {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} [topological_space α] [topological_space β] [topological_space γ] [topological_space δ] (e : local_homeomorph α β) (e' : local_homeomorph γ δ) : ⇑(prod e e') = fun (p : α × γ) => (coe_fn e (prod.fst p), coe_fn e' (prod.snd p)) :=
  rfl

theorem prod_coe_symm {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} [topological_space α] [topological_space β] [topological_space γ] [topological_space δ] (e : local_homeomorph α β) (e' : local_homeomorph γ δ) : ⇑(local_homeomorph.symm (prod e e')) =
  fun (p : β × δ) => (coe_fn (local_homeomorph.symm e) (prod.fst p), coe_fn (local_homeomorph.symm e') (prod.snd p)) :=
  rfl

@[simp] theorem prod_symm {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} [topological_space α] [topological_space β] [topological_space γ] [topological_space δ] (e : local_homeomorph α β) (e' : local_homeomorph γ δ) : local_homeomorph.symm (prod e e') = prod (local_homeomorph.symm e) (local_homeomorph.symm e') :=
  rfl

@[simp] theorem prod_trans {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} [topological_space α] [topological_space β] [topological_space γ] [topological_space δ] {η : Type u_5} {ε : Type u_6} [topological_space η] [topological_space ε] (e : local_homeomorph α β) (f : local_homeomorph β γ) (e' : local_homeomorph δ η) (f' : local_homeomorph η ε) : local_homeomorph.trans (prod e e') (prod f f') = prod (local_homeomorph.trans e f) (local_homeomorph.trans e' f') := sorry

/-- Continuity within a set at a point can be read under right composition with a local
homeomorphism, if the point is in its target -/
theorem continuous_within_at_iff_continuous_within_at_comp_right {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) {f : β → γ} {s : set β} {x : β} (h : x ∈ local_equiv.target (to_local_equiv e)) : continuous_within_at f s x ↔ continuous_within_at (f ∘ ⇑e) (⇑e ⁻¹' s) (coe_fn (local_homeomorph.symm e) x) := sorry

/-- Continuity at a point can be read under right composition with a local homeomorphism, if the
point is in its target -/
theorem continuous_at_iff_continuous_at_comp_right {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) {f : β → γ} {x : β} (h : x ∈ local_equiv.target (to_local_equiv e)) : continuous_at f x ↔ continuous_at (f ∘ ⇑e) (coe_fn (local_homeomorph.symm e) x) := sorry

/-- A function is continuous on a set if and only if its composition with a local homeomorphism
on the right is continuous on the corresponding set. -/
theorem continuous_on_iff_continuous_on_comp_right {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) {f : β → γ} {s : set β} (h : s ⊆ local_equiv.target (to_local_equiv e)) : continuous_on f s ↔ continuous_on (f ∘ ⇑e) (local_equiv.source (to_local_equiv e) ∩ ⇑e ⁻¹' s) := sorry

/-- Continuity within a set at a point can be read under left composition with a local
homeomorphism if a neighborhood of the initial point is sent to the source of the local
homeomorphism-/
theorem continuous_within_at_iff_continuous_within_at_comp_left {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) {f : γ → α} {s : set γ} {x : γ} (hx : f x ∈ local_equiv.source (to_local_equiv e)) (h : f ⁻¹' local_equiv.source (to_local_equiv e) ∈ nhds_within x s) : continuous_within_at f s x ↔ continuous_within_at (⇑e ∘ f) s x := sorry

/-- Continuity at a point can be read under left composition with a local homeomorphism if a
neighborhood of the initial point is sent to the source of the local homeomorphism-/
theorem continuous_at_iff_continuous_at_comp_left {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) {f : γ → α} {x : γ} (h : f ⁻¹' local_equiv.source (to_local_equiv e) ∈ nhds x) : continuous_at f x ↔ continuous_at (⇑e ∘ f) x := sorry

/-- A function is continuous on a set if and only if its composition with a local homeomorphism
on the left is continuous on the corresponding set. -/
theorem continuous_on_iff_continuous_on_comp_left {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : local_homeomorph α β) {f : γ → α} {s : set γ} (h : s ⊆ f ⁻¹' local_equiv.source (to_local_equiv e)) : continuous_on f s ↔ continuous_on (⇑e ∘ f) s := sorry

/-- If a local homeomorphism has source and target equal to univ, then it induces a homeomorphism
between the whole spaces, expressed in this definition. -/
def to_homeomorph_of_source_eq_univ_target_eq_univ {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (h : local_equiv.source (to_local_equiv e) = set.univ) (h' : local_equiv.target (to_local_equiv e) = set.univ) : α ≃ₜ β :=
  homeomorph.mk (equiv.mk ⇑e ⇑(local_homeomorph.symm e) sorry sorry)

@[simp] theorem to_homeomorph_coe {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (h : local_equiv.source (to_local_equiv e) = set.univ) (h' : local_equiv.target (to_local_equiv e) = set.univ) : ⇑(to_homeomorph_of_source_eq_univ_target_eq_univ e h h') = ⇑e :=
  rfl

@[simp] theorem to_homeomorph_symm_coe {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (h : local_equiv.source (to_local_equiv e) = set.univ) (h' : local_equiv.target (to_local_equiv e) = set.univ) : ⇑(homeomorph.symm (to_homeomorph_of_source_eq_univ_target_eq_univ e h h')) = ⇑(local_homeomorph.symm e) :=
  rfl

/-- A local homeomorphism whose source is all of `α` defines an open embedding of `α` into `β`.  The
converse is also true; see `open_embedding.to_local_homeomorph`. -/
theorem to_open_embedding {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (h : local_equiv.source (to_local_equiv e) = set.univ) : open_embedding (local_equiv.to_fun (to_local_equiv e)) := sorry

end local_homeomorph

namespace homeomorph

/- Register as simp lemmas that the fields of a local homeomorphism built from a homeomorphism
correspond to the fields of the original homeomorphism. -/

@[simp] theorem to_local_homeomorph_source {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : α ≃ₜ β) : local_equiv.source (local_homeomorph.to_local_equiv (to_local_homeomorph e)) = set.univ :=
  rfl

@[simp] theorem to_local_homeomorph_target {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : α ≃ₜ β) : local_equiv.target (local_homeomorph.to_local_equiv (to_local_homeomorph e)) = set.univ :=
  rfl

@[simp] theorem to_local_homeomorph_coe {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : α ≃ₜ β) : ⇑(to_local_homeomorph e) = ⇑e :=
  rfl

@[simp] theorem to_local_homeomorph_coe_symm {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : α ≃ₜ β) : ⇑(local_homeomorph.symm (to_local_homeomorph e)) = ⇑(homeomorph.symm e) :=
  rfl

@[simp] theorem refl_to_local_homeomorph {α : Type u_1} [topological_space α] : to_local_homeomorph (homeomorph.refl α) = local_homeomorph.refl α :=
  rfl

@[simp] theorem symm_to_local_homeomorph {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : α ≃ₜ β) : to_local_homeomorph (homeomorph.symm e) = local_homeomorph.symm (to_local_homeomorph e) :=
  rfl

@[simp] theorem trans_to_local_homeomorph {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] (e : α ≃ₜ β) (e' : β ≃ₜ γ) : to_local_homeomorph (homeomorph.trans e e') = local_homeomorph.trans (to_local_homeomorph e) (to_local_homeomorph e') :=
  local_homeomorph.eq_of_local_equiv_eq (equiv.trans_to_local_equiv (to_equiv e) (to_equiv e'))

end homeomorph

namespace open_embedding

/-- An open embedding of `α` into `β`, with `α` nonempty, defines a local equivalence whose source
is all of `α`.  This is mainly an auxiliary lemma for the stronger result `to_local_homeomorph`. -/
def to_local_equiv {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [Nonempty α] {f : α → β} (h : open_embedding f) : local_equiv α β :=
  set.inj_on.to_local_equiv f set.univ sorry

@[simp] theorem to_local_equiv_coe {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [Nonempty α] {f : α → β} (h : open_embedding f) : ⇑(to_local_equiv h) = f :=
  rfl

@[simp] theorem to_local_equiv_source {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [Nonempty α] {f : α → β} (h : open_embedding f) : local_equiv.source (to_local_equiv h) = set.univ :=
  rfl

@[simp] theorem to_local_equiv_target {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [Nonempty α] {f : α → β} (h : open_embedding f) : local_equiv.target (to_local_equiv h) = set.range f := sorry

theorem open_target {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [Nonempty α] {f : α → β} (h : open_embedding f) : is_open (local_equiv.target (to_local_equiv h)) := sorry

theorem continuous_inv_fun {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [Nonempty α] {f : α → β} (h : open_embedding f) : continuous_on (local_equiv.inv_fun (to_local_equiv h)) (local_equiv.target (to_local_equiv h)) := sorry

/-- An open embedding of `α` into `β`, with `α` nonempty, defines a local homeomorphism whose source
is all of `α`.  The converse is also true; see `local_homeomorph.to_open_embedding`. -/
def to_local_homeomorph {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [Nonempty α] {f : α → β} (h : open_embedding f) : local_homeomorph α β :=
  local_homeomorph.mk (to_local_equiv h) is_open_univ (open_target h) sorry (continuous_inv_fun h)

@[simp] theorem to_local_homeomorph_coe {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [Nonempty α] {f : α → β} (h : open_embedding f) : ⇑(to_local_homeomorph h) = f :=
  rfl

@[simp] theorem source {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [Nonempty α] {f : α → β} (h : open_embedding f) : local_equiv.source (local_homeomorph.to_local_equiv (to_local_homeomorph h)) = set.univ :=
  rfl

@[simp] theorem target {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [Nonempty α] {f : α → β} (h : open_embedding f) : local_equiv.target (local_homeomorph.to_local_equiv (to_local_homeomorph h)) = set.range f :=
  to_local_equiv_target h

end open_embedding

-- We close and reopen the namespace to avoid

-- picking up the unnecessary `[nonempty α]` typeclass argument

namespace open_embedding

theorem continuous_at_iff {α : Type u_1} {β : Type u_2} {γ : Type u_3} [topological_space α] [topological_space β] [topological_space γ] {f : α → β} {g : β → γ} (hf : open_embedding f) {x : α} : continuous_at (g ∘ f) x ↔ continuous_at g (f x) := sorry

end open_embedding

namespace topological_space.opens

/-- The inclusion of an open subset `s` of a space `α` into `α` is a local homeomorphism from the
subtype `s` to `α`. -/
def local_homeomorph_subtype_coe {α : Type u_1} [topological_space α] (s : opens α) [Nonempty ↥s] : local_homeomorph (↥s) α :=
  open_embedding.to_local_homeomorph sorry

@[simp] theorem local_homeomorph_subtype_coe_coe {α : Type u_1} [topological_space α] (s : opens α) [Nonempty ↥s] : ⇑(local_homeomorph_subtype_coe s) = coe :=
  rfl

@[simp] theorem local_homeomorph_subtype_coe_source {α : Type u_1} [topological_space α] (s : opens α) [Nonempty ↥s] : local_equiv.source (local_homeomorph.to_local_equiv (local_homeomorph_subtype_coe s)) = set.univ :=
  rfl

@[simp] theorem local_homeomorph_subtype_coe_target {α : Type u_1} [topological_space α] (s : opens α) [Nonempty ↥s] : local_equiv.target (local_homeomorph.to_local_equiv (local_homeomorph_subtype_coe s)) = ↑s := sorry

end topological_space.opens

namespace local_homeomorph

/-- The restriction of a local homeomorphism `e` to an open subset `s` of the domain type produces a
local homeomorphism whose domain is the subtype `s`.-/
def subtype_restr {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : topological_space.opens α) [Nonempty ↥s] : local_homeomorph (↥s) β :=
  local_homeomorph.trans (topological_space.opens.local_homeomorph_subtype_coe s) e

theorem subtype_restr_def {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : topological_space.opens α) [Nonempty ↥s] : subtype_restr e s = local_homeomorph.trans (topological_space.opens.local_homeomorph_subtype_coe s) e :=
  rfl

@[simp] theorem subtype_restr_coe {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : topological_space.opens α) [Nonempty ↥s] : ⇑(subtype_restr e s) = set.restrict ⇑e ↑s :=
  rfl

@[simp] theorem subtype_restr_source {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (e : local_homeomorph α β) (s : topological_space.opens α) [Nonempty ↥s] : local_equiv.source (to_local_equiv (subtype_restr e s)) = coe ⁻¹' local_equiv.source (to_local_equiv e) := sorry

/- This lemma characterizes the transition functions of an open subset in terms of the transition
functions of the original space. -/

theorem subtype_restr_symm_trans_subtype_restr {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (s : topological_space.opens α) [Nonempty ↥s] (f : local_homeomorph α β) (f' : local_homeomorph α β) : local_homeomorph.trans (local_homeomorph.symm (subtype_restr f s)) (subtype_restr f' s) ≈
  local_homeomorph.restr (local_homeomorph.trans (local_homeomorph.symm f) f')
    (local_equiv.target (to_local_equiv f) ∩ ⇑(local_homeomorph.symm f) ⁻¹' ↑s) := sorry


/-
Copyright (c) 2017 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Mario Carneiro
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.data.nat.lemmas
import Mathlib.Lean3Lib.init.meta.well_founded_tactics

universes u

namespace Mathlib

namespace nat

def bodd_div2 : ℕ → Bool × ℕ :=
  sorry

def div2 (n : ℕ) : ℕ :=
  prod.snd (bodd_div2 n)

def bodd (n : ℕ) : Bool :=
  prod.fst (bodd_div2 n)

@[simp] theorem bodd_zero : bodd 0 = false :=
  rfl

@[simp] theorem bodd_one : bodd 1 = tt :=
  rfl

@[simp] theorem bodd_two : bodd (bit0 1) = false :=
  rfl

@[simp] theorem bodd_succ (n : ℕ) : bodd (Nat.succ n) = bnot (bodd n) := sorry

@[simp] theorem bodd_add (m : ℕ) (n : ℕ) : bodd (m + n) = bxor (bodd m) (bodd n) := sorry

@[simp] theorem bodd_mul (m : ℕ) (n : ℕ) : bodd (m * n) = bodd m && bodd n := sorry

theorem mod_two_of_bodd (n : ℕ) : n % bit0 1 = cond (bodd n) 1 0 := sorry

@[simp] theorem div2_zero : div2 0 = 0 :=
  rfl

@[simp] theorem div2_one : div2 1 = 0 :=
  rfl

@[simp] theorem div2_two : div2 (bit0 1) = 1 :=
  rfl

@[simp] theorem div2_succ (n : ℕ) : div2 (Nat.succ n) = cond (bodd n) (Nat.succ (div2 n)) (div2 n) := sorry

theorem bodd_add_div2 (n : ℕ) : cond (bodd n) 1 0 + bit0 1 * div2 n = n := sorry

theorem div2_val (n : ℕ) : div2 n = n / bit0 1 := sorry

def bit (b : Bool) : ℕ → ℕ :=
  cond b bit1 bit0

theorem bit0_val (n : ℕ) : bit0 n = bit0 1 * n :=
  Eq.trans (Eq.trans (eq.mpr (id (Eq._oldrec (Eq.refl (n + n = 0 + n + n)) (nat.zero_add n))) (Eq.refl (n + n))) rfl)
    (nat.mul_comm n (bit0 1))

theorem bit1_val (n : ℕ) : bit1 n = bit0 1 * n + 1 :=
  congr_arg Nat.succ (bit0_val n)

theorem bit_val (b : Bool) (n : ℕ) : bit b n = bit0 1 * n + cond b 1 0 :=
  bool.cases_on b (bit0_val n) (bit1_val n)

theorem bit_decomp (n : ℕ) : bit (bodd n) (div2 n) = n :=
  Eq.trans (bit_val (bodd n) (div2 n)) (Eq.trans (nat.add_comm (bit0 1 * div2 n) (cond (bodd n) 1 0)) (bodd_add_div2 n))

def bit_cases_on {C : ℕ → Sort u} (n : ℕ) (h : (b : Bool) → (n : ℕ) → C (bit b n)) : C n :=
  eq.mpr sorry (h (bodd n) (div2 n))

@[simp] theorem bit_zero : bit false 0 = 0 :=
  rfl

def shiftl' (b : Bool) (m : ℕ) : ℕ → ℕ :=
  sorry

def shiftl : ℕ → ℕ → ℕ :=
  shiftl' false

@[simp] theorem shiftl_zero (m : ℕ) : shiftl m 0 = m :=
  rfl

@[simp] theorem shiftl_succ (m : ℕ) (n : ℕ) : shiftl m (n + 1) = bit0 (shiftl m n) :=
  rfl

def shiftr : ℕ → ℕ → ℕ :=
  sorry

def test_bit (m : ℕ) (n : ℕ) : Bool :=
  bodd (shiftr m n)

def binary_rec {C : ℕ → Sort u} (z : C 0) (f : (b : Bool) → (n : ℕ) → C n → C (bit b n)) (n : ℕ) : C n :=
  sorry

def size : ℕ → ℕ :=
  binary_rec 0 fun (_x : Bool) (_x : ℕ) => Nat.succ

def bits : ℕ → List Bool :=
  binary_rec [] fun (b : Bool) (_x : ℕ) (IH : List Bool) => b :: IH

def bitwise (f : Bool → Bool → Bool) : ℕ → ℕ → ℕ :=
  binary_rec (fun (n : ℕ) => cond (f false tt) n 0)
    fun (a : Bool) (m : ℕ) (Ia : ℕ → ℕ) =>
      binary_rec (cond (f tt false) (bit a m) 0) fun (b : Bool) (n _x : ℕ) => bit (f a b) (Ia n)

def lor : ℕ → ℕ → ℕ :=
  bitwise bor

def land : ℕ → ℕ → ℕ :=
  bitwise band

def ldiff : ℕ → ℕ → ℕ :=
  bitwise fun (a b : Bool) => a && bnot b

def lxor : ℕ → ℕ → ℕ :=
  bitwise bxor

@[simp] theorem binary_rec_zero {C : ℕ → Sort u} (z : C 0) (f : (b : Bool) → (n : ℕ) → C n → C (bit b n)) : binary_rec z f 0 = z := sorry

/- bitwise ops -/

theorem bodd_bit (b : Bool) (n : ℕ) : bodd (bit b n) = b := sorry

theorem div2_bit (b : Bool) (n : ℕ) : div2 (bit b n) = n := sorry

theorem shiftl'_add (b : Bool) (m : ℕ) (n : ℕ) (k : ℕ) : shiftl' b m (n + k) = shiftl' b (shiftl' b m n) k := sorry

theorem shiftl_add (m : ℕ) (n : ℕ) (k : ℕ) : shiftl m (n + k) = shiftl (shiftl m n) k :=
  shiftl'_add false

theorem shiftr_add (m : ℕ) (n : ℕ) (k : ℕ) : shiftr m (n + k) = shiftr (shiftr m n) k := sorry

theorem shiftl'_sub (b : Bool) (m : ℕ) {n : ℕ} {k : ℕ} : k ≤ n → shiftl' b m (n - k) = shiftr (shiftl' b m n) k := sorry

theorem shiftl_sub (m : ℕ) {n : ℕ} {k : ℕ} : k ≤ n → shiftl m (n - k) = shiftr (shiftl m n) k :=
  shiftl'_sub false

@[simp] theorem test_bit_zero (b : Bool) (n : ℕ) : test_bit (bit b n) 0 = b :=
  bodd_bit b n

theorem test_bit_succ (m : ℕ) (b : Bool) (n : ℕ) : test_bit (bit b n) (Nat.succ m) = test_bit n m := sorry

theorem binary_rec_eq {C : ℕ → Sort u} {z : C 0} {f : (b : Bool) → (n : ℕ) → C n → C (bit b n)} (h : f false 0 z = z) (b : Bool) (n : ℕ) : binary_rec z f (bit b n) = f b n (binary_rec z f n) := sorry

theorem bitwise_bit_aux {f : Bool → Bool → Bool} (h : f false false = false) : (binary_rec (cond (f tt false) (bit false 0) 0) fun (b : Bool) (n _x : ℕ) => bit (f false b) (cond (f false tt) n 0)) =
  fun (n : ℕ) => cond (f false tt) n 0 := sorry

@[simp] theorem bitwise_zero_left (f : Bool → Bool → Bool) (n : ℕ) : bitwise f 0 n = cond (f false tt) n 0 := sorry

@[simp] theorem bitwise_zero_right (f : Bool → Bool → Bool) (h : f false false = false) (m : ℕ) : bitwise f m 0 = cond (f tt false) m 0 := sorry

@[simp] theorem bitwise_zero (f : Bool → Bool → Bool) : bitwise f 0 0 = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (bitwise f 0 0 = 0)) (bitwise_zero_left f 0)))
    (bool.cases_on (f false tt) (Eq.refl (cond false 0 0)) (Eq.refl (cond tt 0 0)))

@[simp] theorem bitwise_bit {f : Bool → Bool → Bool} (h : f false false = false) (a : Bool) (m : ℕ) (b : Bool) (n : ℕ) : bitwise f (bit a m) (bit b n) = bit (f a b) (bitwise f m n) := sorry

theorem bitwise_swap {f : Bool → Bool → Bool} (h : f false false = false) : bitwise (function.swap f) = function.swap (bitwise f) := sorry

@[simp] theorem lor_bit (a : Bool) (m : ℕ) (b : Bool) (n : ℕ) : lor (bit a m) (bit b n) = bit (a || b) (lor m n) :=
  bitwise_bit rfl

@[simp] theorem land_bit (a : Bool) (m : ℕ) (b : Bool) (n : ℕ) : land (bit a m) (bit b n) = bit (a && b) (land m n) :=
  bitwise_bit rfl

@[simp] theorem ldiff_bit (a : Bool) (m : ℕ) (b : Bool) (n : ℕ) : ldiff (bit a m) (bit b n) = bit (a && bnot b) (ldiff m n) :=
  bitwise_bit rfl

@[simp] theorem lxor_bit (a : Bool) (m : ℕ) (b : Bool) (n : ℕ) : lxor (bit a m) (bit b n) = bit (bxor a b) (lxor m n) :=
  bitwise_bit rfl

@[simp] theorem test_bit_bitwise {f : Bool → Bool → Bool} (h : f false false = false) (m : ℕ) (n : ℕ) (k : ℕ) : test_bit (bitwise f m n) k = f (test_bit m k) (test_bit n k) := sorry

@[simp] theorem test_bit_lor (m : ℕ) (n : ℕ) (k : ℕ) : test_bit (lor m n) k = test_bit m k || test_bit n k :=
  test_bit_bitwise rfl

@[simp] theorem test_bit_land (m : ℕ) (n : ℕ) (k : ℕ) : test_bit (land m n) k = test_bit m k && test_bit n k :=
  test_bit_bitwise rfl

@[simp] theorem test_bit_ldiff (m : ℕ) (n : ℕ) (k : ℕ) : test_bit (ldiff m n) k = test_bit m k && bnot (test_bit n k) :=
  test_bit_bitwise rfl

@[simp] theorem test_bit_lxor (m : ℕ) (n : ℕ) (k : ℕ) : test_bit (lxor m n) k = bxor (test_bit m k) (test_bit n k) :=
  test_bit_bitwise rfl


import algebra.homology.homological_complex
import category_theory.abelian.exact
import algebra.category.Module.abelian

import tactic.interval_cases

import .test

noncomputable theory

universes v u

open category_theory category_theory.category category_theory.limits

variables {α β : Type*}
variables (V : Type u) [category.{v} V] [has_zero_morphisms V]

section

def complex_shape.not_rfl (a : complex_shape α) : Prop :=
∀ (i : α), ¬ a.rel i i

def complex_shape.not_rfl.ne {a : complex_shape α} (ha : a.not_rfl) {i i' : α} :
  a.rel i i' → i ≠ i' :=
begin
  contrapose!,
  rintro rfl,
  exact ha _,
end

structure homological_bicomplex (a : complex_shape α) (b : complex_shape β) :=
(X : α → β → V)
(d_h : Π (i : α) (j j' : β), X i j ⟶ X i j')
(shape_h' : ∀ (i : α) (j j' : β), ¬ b.rel j j' → d_h i j j' = 0)
(d_v : Π (j : β) (i i' : α), X i j ⟶ X i' j)
(shape_v' : ∀ (j : β) (i i' : α), ¬ a.rel i i' → d_v j i i' = 0)
(d_comp_d_v' : ∀ (j : β) (i₁ i₂ i₃ : α), a.rel i₁ i₂ → a.rel i₂ i₃ →
  d_v j i₁ i₂ ≫ d_v j i₂ i₃ = 0)
(d_comp_d_h' : ∀ (i : α) (j₁ j₂ j₃ : β), b.rel j₁ j₂ → b.rel j₂ j₃ →
  d_h i j₁ j₂ ≫ d_h i j₂ j₃ = 0)
(comm' : ∀ (i₁ i₂ : α) (j₁ j₂ : β), a.rel i₁ i₂ → b.rel j₁ j₂ →
  d_v j₁ i₁ i₂ ≫ d_h i₂ j₁ j₂ = d_h i₁ j₁ j₂ ≫ d_v j₂ i₁ i₂)

end

namespace homological_bicomplex

restate_axiom shape_h'
restate_axiom shape_v'
attribute [simp] shape_h shape_v

variables {V}  {γ : Type*} (a : complex_shape α) (b : complex_shape β) (c : complex_shape γ)

class has_sign :=
(sign : α → zmod 2)
(rel : ∀ (i i' : α), a.rel i i' → sign i ≠ sign i')

instance has_sign1 : has_sign (complex_shape.up ℤ) :=
{ sign := λ i, i,
  rel := λ i j,
  begin
    dsimp,
    rintro rfl h,
    norm_num at h,
  end }

instance has_sign2 : has_sign (complex_shape.up ℕ) :=
{ sign := λ i, i,
  rel := λ i j,
  begin
    dsimp,
    rintro rfl h,
    norm_num at h,
  end }

section

instance [has_sign a] (T : Type*) [has_neg T] : has_smul α T :=
{ smul := λ x f, if has_sign.sign a x = 0 then f else - f }

lemma has_sign.smul_zero [has_sign a] (T : Type*) [add_comm_group T]
  (x : α) : x • (0 : T) = 0 :=
begin
  dunfold has_smul.smul,
  dsimp,
  split_ifs;
  abel,
end

lemma has_sign.smul_eq_zero [has_sign a] (T : Type*) [add_comm_group T] (x : α) (t : T) :
  x • t = 0 ↔ t = 0 :=
begin
  split,
  { intros h,
    dunfold has_smul.smul at h,
    dsimp at h,
    split_ifs at h,
    { exact h },
    { rwa neg_eq_zero at h, }, },
  { rintro rfl, rw has_sign.smul_zero }
end

lemma has_sign.smul_comp [has_sign a] [preadditive V] (i : α)
  {v₁ v₂ v₃ : V} (f : v₁ ⟶ v₂) (g : v₂ ⟶ v₃) :
  (i • f) ≫ g = i • (f ≫ g) :=
begin
  dunfold has_smul.smul,
  dsimp,
  split_ifs,
  { refl },
  { rw preadditive.neg_comp },
end

local attribute [instance] concrete_category.has_coe_to_fun
local attribute [instance] concrete_category.has_coe_to_sort

class has_hadd :=
(add' {} : α → β → γ)
(rel_h' {} : ∀ (i₁ i₂ : α) (j : β), a.rel i₁ i₂ ↔ c.rel (add' i₁ j) (add' i₂ j))
(rel_v' {} : ∀ (i : α) (j₁ j₂ : β), b.rel j₁ j₂ ↔ c.rel (add' i j₁) (add' i j₂))
(add_cancel_h' : ∀ (i₁ i₂ : α) (j : β), add' i₁ j = add' i₂ j ↔ i₁ = i₂)
(add_cancel_v' : ∀ (i : α) (j₁ j₂ : β), add' i j₁ = add' i j₂ ↔ j₁ = j₂)
(balanced' : ∀ (i : α) (j : β) (k' : γ), c.rel (add' i j) k' →
  ((∃ i', k' = add' i' j) ∧ (∃ j', k' = add' i j')))
/--
This is wrong for example:
in down ℕ
i + 0 -> k' = i - 1
then k' = (i - 1) + 0 is fine but k' = i + j' is bad

I want i + 0 -> k' = i - 1
1 + 0 -> 0
-/

instance test1 : has_hadd (complex_shape.up ℤ) (complex_shape.up ℤ) (complex_shape.up ℤ) :=
{ add' := (+),
  rel_h' := λ _ _ _, begin
    dsimp,
    split,
    { rintro rfl, ring },
    { intros h, linarith, },
  end,
  rel_v' := λ _ _ _, begin
    dsimp,
    split,
    { rintro rfl, ring },
    { intros h, linarith, },
  end,
  add_cancel_h' := λ _ _ _, begin
    split;
    intros;
    linarith,
  end,
  add_cancel_v' := λ _ _ _, begin
    split;
    intros;
    linarith,
  end,
  balanced' := λ i j k' (h : _ = _),
  begin
    refine ⟨⟨i + 1, by linarith⟩, ⟨j + 1, by linarith⟩⟩,
  end }

instance test2 : has_hadd (complex_shape.up ℕ) (complex_shape.up ℕ) (complex_shape.up ℕ) :=
{ add' := (+),
  rel_h' := λ _ _ _, begin
    dsimp,
    split,
    { rintro rfl, ring },
    { intros h, linarith, },
  end,
  rel_v' := λ _ _ _, begin
    dsimp,
    split,
    { rintro rfl, ring },
    { intros h, linarith, },
  end,
  add_cancel_h' := λ _ _ _, begin
    split;
    intros;
    linarith,
  end,
  add_cancel_v' := λ _ _ _, begin
    split;
    intros;
    linarith,
  end,
  balanced' := λ i j k' (h : _ = _),
  begin
    refine ⟨⟨i + 1, by linarith⟩, ⟨j + 1, by linarith⟩⟩,
  end }

instance test3 : has_hadd (complex_shape.up ℕ) (complex_shape.up ℤ) (complex_shape.up ℤ) :=
{ add' := λ n z, (n : ℤ) + z,
  rel_h' := λ _ _ _, begin
    dsimp,
    split,
    { rintro rfl, norm_num, ring, },
    { intros h, linarith, },
  end,
  rel_v' := λ _ _ _, begin
    dsimp,
    split,
    { rintro rfl, ring },
    { intros h, linarith, },
  end,
  add_cancel_h' := λ _ _ _, begin
    split;
    intros;
    linarith,
  end,
  add_cancel_v' := λ _ _ _, begin
    split;
    intros;
    linarith,
  end,
  balanced' := λ i j k' (h : _ = _),
  begin
    refine ⟨⟨i + 1, by norm_num; linarith⟩, ⟨j + 1, by linarith⟩⟩,
  end }

notation (name := hadd.add) i `+[` a, b, c`]` j := (has_hadd.add' a b c i j)
variables [has_hadd a b c]

variables {a b c} [decidable_eq α] [decidable_eq β] [decidable_eq γ]

@[simp] lemma d_comp_d_v (C : homological_bicomplex V a b) (j : β) (i₁ i₂ i₃ : α) :
  C.d_v j i₁ i₂ ≫ C.d_v j i₂ i₃ = 0 :=
begin
  by_cases h₁₂ : a.rel i₁ i₂,
  { refine (em (a.rel i₂ i₃)).elim (λ h₂₃, C.d_comp_d_v' j i₁ i₂ i₃ h₁₂ h₂₃) (λ h₂₃, _),
    rw [C.shape_v j _ _ h₂₃, comp_zero], },
  rw [C.shape_v _ _ _ h₁₂, zero_comp],
end

@[simp] lemma comm (C : homological_bicomplex V a b) (j₁ j₂ : β) (i₁ i₂ : α) :
  C.d_h i₁ j₁ j₂ ≫ C.d_v j₂ i₁ i₂ =
  C.d_v j₁ i₁ i₂ ≫ C.d_h i₂ j₁ j₂ :=
begin
  by_cases ha : a.rel i₁ i₂;
  by_cases hb : b.rel j₁ j₂,
  { rw C.comm'; assumption },
  { rw [C.shape_h, C.shape_h, comp_zero, zero_comp]; assumption },
  { rw [C.shape_v, C.shape_v, comp_zero, zero_comp]; assumption },
  { rw [C.shape_v, C.shape_v, comp_zero, zero_comp]; assumption },
end

@[simp] lemma d_comp_d_h (C : homological_bicomplex V a b) (i : α) (j₁ j₂ j₃ : β) :
  C.d_h i j₁ j₂ ≫ C.d_h i j₂ j₃ = 0 :=
begin
  by_cases h₁₂ : b.rel j₁ j₂,
  { refine (em (b.rel j₂ j₃)).elim (λ h₂₃, C.d_comp_d_h' i j₁ j₂ j₃ h₁₂ h₂₃) (λ h₂₃, _),
    rw [C.shape_h i _ _ h₂₃, comp_zero], },
  rw [C.shape_h _ _ _ h₁₂, zero_comp],
end

@[simps]
def vertical_component (C : homological_bicomplex V a b) (j : β) : homological_complex V a :=
{ X := λ i, C.X i j,
  d := C.d_v j,
  shape' := λ _ _ h, C.shape_v _ _ _ h,
  d_comp_d' := λ _ _ _ _ _, C.d_comp_d_v _ _ _ _ }

@[simps]
def vertical_component_map (C : homological_bicomplex V a b) (j₁ j₂ : β) :
  C.vertical_component j₁ ⟶ C.vertical_component j₂ :=
{ f := λ i, C.d_h i j₁ j₂,
  comm' :=
  begin
    intros i₁ i₂ h₁₂,
    dsimp,
    by_cases H : b.rel j₁ j₂,
    { rw C.comm, },
    { rw [C.shape_h _ _ _ H, C.shape_h _ _ _ H, zero_comp, comp_zero], },
  end }

@[simps]
def as_vertical_complex (C : homological_bicomplex V a b) :
  homological_complex (homological_complex V a) b :=
{ X := C.vertical_component,
  d := C.vertical_component_map,
  shape' := λ j₁ j₂ h₁₂,
  begin
    ext i,
    simpa only [vertical_component_map_f, homological_complex.zero_apply] using C.shape_h _ _ _ h₁₂,
  end,
  d_comp_d' := by { intros, ext, simp } }

@[simps]
def horizontal_component (C : homological_bicomplex V a b) (i : α) : homological_complex V b :=
{ X := C.X i,
  d := C.d_h i,
  shape' := λ _ _ h, C.shape_h _ _ _ h,
  d_comp_d' := λ _ _ _ _ _, C.d_comp_d_h _ _ _ _ }

@[simps]
def horizontal_component_map (C : homological_bicomplex V a b) (i₁ i₂ : α) :
  C.horizontal_component i₁ ⟶ C.horizontal_component i₂ :=
{ f := λ j, C.d_v j i₁ i₂,
  comm' :=
  begin
    intros j₁ j₂ h₁₂,
    dsimp,
    by_cases H : a.rel i₁ i₂,
    { rw C.comm, },
    { rw [C.shape_v _ _ _ H, C.shape_v _ _ _ H, zero_comp, comp_zero], },
  end }

@[simps]
def as_horizontal_complex (C : homological_bicomplex V a b) :
  homological_complex (homological_complex V b) a :=
{ X := C.horizontal_component,
  d := C.horizontal_component_map,
  shape' := λ i₁ i₂ h₁₂,
  begin
    ext j,
    simpa only [horizontal_component_map_f, homological_complex.zero_apply] using
      C.shape_v _ _ _ h₁₂,
  end,
  d_comp_d' := by { intros, ext, simp } }

section defs_in_mod

variables {R : Type*} [comm_ring R] (C : homological_bicomplex (Module R) a b)
variables (a b c)

@[ext]
structure diagonal (k : γ) :=
(fst : α) (snd : β) (add_eq : (fst +[a, b, c] snd) = k)

open_locale direct_sum big_operators

variables {a b} [∀ k, decidable_eq $ diagonal a b c k]
def total_at (j : γ) : Module R :=
Module.of R $ ⨁ (p : diagonal a b c j), C.X p.fst p.snd

@[simps]
def diagonal.from_balancing1
  {k k' : γ} (p : diagonal a b c k) (hc : c.rel k k') :
  diagonal a b c k' :=
{ fst := (has_hadd.balanced' p.1 p.2 k' (by rwa p.add_eq : c.rel (p.1+[a,b,c]p.2) k')).1.some,
  snd := p.2,
  add_eq := begin
    generalize_proofs h,
    exact h.some_spec.symm,
  end }

@[simps]
def diagonal.from_balancing1'
  {k k' : γ} (p : diagonal a b c k) (hc : c.rel k k') :
  diagonal a b c k' :=
{ fst := a.next p.1,
  snd := p.2,
  add_eq := begin
    suffices :
      (has_hadd.balanced' p.1 p.2 k' (by rwa p.add_eq : c.rel (p.1+[a,b,c]p.2) k')).1.some =
      a.next p.1,
    { rw ← this,
      generalize_proofs h,
      exact h.some_spec.symm },
    generalize_proofs h,
    have : c.rel (p.1 +[a,b,c] p.2) (h.some +[a,b,c] p.snd),
    { rw p.add_eq,
      convert hc,
      exact h.some_spec.symm },
    rw ← has_hadd.rel_h' at this,
    rwa a.next_eq',
  end }

lemma diagonal.from_balancing1'_rel
  {k k' : γ} (p : diagonal a b c k) (hc : c.rel k k') :
  a.rel p.1 (a.next p.1) :=
begin
  have EQ1 := (p.from_balancing1' c hc).add_eq,
  simp_rw [← EQ1, ← p.add_eq] at hc,
  erw ← has_hadd.rel_h' at hc,
  exact hc,
end

lemma diagonal.from_balancing1_uniq
  {k k' : γ} (p : diagonal a b c k) (hc : c.rel k k')
  (q' : diagonal a b c k')
  (hq' : q'.2 = p.2) :
  p.from_balancing1 c hc = q' :=
begin
  ext,
  { dsimp,
    generalize_proofs h,
    have := h.some_spec,
    simp_rw ←q'.add_eq at this,
    simp_rw hq' at this,
    rw has_hadd.add_cancel_h' at this,
    convert this.symm,
    ext,
    rw [← hq', q'.add_eq], },
  { dsimp, rw [←hq'], }
end

lemma diagonal.from_balancing1_fst_eq {k k' : γ} (p : diagonal a b c k) (hc : c.rel k k') :
  (p.from_balancing1 c hc).fst = a.next p.1 :=
begin
  rw p.from_balancing1_uniq c hc(p.from_balancing1' c hc) rfl,
  refl,
end

@[simps]
def diagonal.from_balancing2
  {k k' : γ} (p : diagonal a b c k) (hc : c.rel k k') :
  diagonal a b c k' :=
{ fst := p.1,
  snd := (has_hadd.balanced' p.1 p.2 k' (by rwa p.add_eq : c.rel (p.1+[a,b,c]p.2) k')).2.some,
  add_eq := begin
    generalize_proofs h,
    exact h.some_spec.symm,
  end }

lemma diagonal.from_balancing2_uniq
  {k k' : γ} (p : diagonal a b c k) (hc : c.rel k k') (q' : diagonal a b c k')
  (hq' : q'.1 = p.1) :
  p.from_balancing2 c hc = q' :=
begin
  ext,
  { dsimp, rw [←hq'], },
  { dsimp,
    generalize_proofs h,
    have := h.some_spec,
    simp_rw ←q'.add_eq at this,
    simp_rw hq' at this,
    rw has_hadd.add_cancel_v' at this,
    convert this.symm,
    ext,
    rw [← hq', q'.add_eq], },
end

@[simps]
def diagonal.from_balancing2'
  {k k' : γ} (p : diagonal a b c k) (hc : c.rel k k') :
  diagonal a b c k' :=
{ fst := p.1,
  snd := b.next p.2,
  add_eq := begin
    suffices :
      (has_hadd.balanced' p.1 p.2 k' (by rwa p.add_eq : c.rel (p.1+[a,b,c]p.2) k')).2.some =
      b.next p.2,
    { rw ← this,
      generalize_proofs h,
      exact h.some_spec.symm },
    generalize_proofs h,
    have : c.rel (p.1 +[a,b,c] p.2) (p.1 +[a,b,c] h.some),
    { rw p.add_eq,
      convert hc,
      exact h.some_spec.symm },
    rw ← has_hadd.rel_v' at this,
    rwa b.next_eq',
  end }

lemma diagonal.from_balancing2_snd_eq {k k' : γ} (p : diagonal a b c k) (hc : c.rel k k')
  (q : diagonal a b c k') (hq : q.2 = p.2) :
  (p.from_balancing2 c hc).snd = b.next p.2 :=
begin
  rw p.from_balancing2_uniq c hc (p.from_balancing2' c hc) rfl,
  refl,
end

lemma has_sign.smul_sub [has_sign a] (i : α)
  {T : Type*} [add_comm_group T] (t t' : T) :
  i • (t - t') = i • t - i • t' :=
begin
  dunfold has_smul.smul,
  dsimp,
  split_ifs,
  { refl },
  abel,
end

lemma has_sign.next_smul [has_sign a] (i : α) (ha : a.rel i (a.next i))
  {T : Type*} [add_comm_group T] (t : T) :
  a.next i • t = - (i • t) :=
begin
  dunfold has_smul.smul,
  dsimp,
  have := has_sign.rel _ _ ha,
  split_ifs with h1 h2,
  { exfalso,
    rw [h1, h2] at this,
    exact this rfl, },
  { rw neg_neg },
  { refl, },
  { have h2' : has_sign.sign a i = 0 ∨ has_sign.sign a i = 1,
    { obtain hmm1 := (has_sign.sign a i).2,
      have hmm2 : 0 ≤ (has_sign.sign a i).1 := by linarith,
      interval_cases using hmm2 hmm1,
      left, ext, assumption,
      right, ext, assumption, },
    have h1' : has_sign.sign a (a.next i) = 0 ∨ has_sign.sign a (a.next i) = 1,
    { obtain hmm1 := (has_sign.sign a (a.next i)).2,
      have hmm2 : 0 ≤ (has_sign.sign a (a.next i)).1 := by linarith,
      interval_cases using hmm2 hmm1,
      left, ext, assumption,
      right, ext, assumption, },
    rw [h2'.resolve_left h, h1'.resolve_left h1] at this,
    exfalso,
    exact this rfl, }
end

section
variables (a) (R)
lemma has_sign.smul_apply [has_sign a] (i : α)
  {v₁ v₂ : Type*} [add_comm_group v₁] [add_comm_group v₂] [module R v₁] [module R v₂]
  (f : v₁ →ₗ[R] v₂) (x)  :
  (i • f) x = i • (f x) :=
begin
  dunfold has_smul.smul,
  dsimp,
  split_ifs,
  { refl },
  { rw linear_map.neg_apply, },
end
end

lemma has_sign.map_smul [has_sign a] (i : α)
  {v₁ v₂ : Module R} (f : v₁ ⟶ v₂) (x)  :
  f (i • x) = i • (f x) :=
begin
  dunfold has_smul.smul,
  dsimp,
  split_ifs,
  { refl },
  { rw map_neg, },
end

variable [∀ (k k' : γ), decidable $ c.rel k k']

@[reducible]
def total_d [has_sign a] (k k' : γ) :
  C.total_at c k ⟶ C.total_at c k' :=
if hc : c.rel k k'
then direct_sum.to_module _ _ _ $ λ p,
  (direct_sum.lof R _ _ (p.from_balancing1' c hc)).comp
    (C.d_v p.2 p.1 (a.next p.1)) +
  p.fst • (direct_sum.lof R _ _ (p.from_balancing2' c hc)).comp
    (C.d_h p.1 p.2 (b.next p.2))
else 0

lemma total_d_of_rel [has_sign a] (k k' : γ) (hc : c.rel k k') :
C.total_d c k k' =
direct_sum.to_module _ _ _ (λ p,
  (direct_sum.lof R _ _ (p.from_balancing1' c hc)).comp
    (C.d_v p.2 p.1 (a.next p.1)) +
  p.fst • (direct_sum.lof R _ _ (p.from_balancing2' c hc)).comp
    (C.d_h p.1 p.2 (b.next p.2))) :=
begin
  dunfold total_d,
  apply direct_sum.linear_map_ext,
  intros p,
  ext1 x,
  rw dif_pos hc,
end

lemma total_d_comp_d_of_rel [has_sign a] (k₁ k₂ k₃ : γ) (hc12 : c.rel k₁ k₂) (hc23 : c.rel k₂ k₃) :
  C.total_d c k₁ k₂ ≫ C.total_d c k₂ k₃ = 0 :=
begin
  apply direct_sum.linear_map_ext,
  intros p,
  ext1 x,
  simp only [linear_map.comp_apply, comp_apply, linear_map.zero_apply],
  rw [total_d_of_rel, total_d_of_rel];
  try { assumption },
  simp only [direct_sum.to_module_lof, linear_map.add_apply, map_add, linear_map.comp_apply,
    has_sign.map_smul, has_sign.smul_apply],
  rw [← comp_apply], erw [d_comp_d_v],
  erw has_sign.map_smul,
  simp only [direct_sum.to_module_lof, linear_map.comp_apply, linear_map.add_apply,
    has_sign.smul_apply],
  nth_rewrite 2 [← comp_apply], erw [d_comp_d_h],
  simp only [linear_map.zero_apply, map_zero, has_sign.smul_zero, add_zero, zero_add],
  simp_rw show (diagonal.from_balancing1' c p hc12).fst = a.next p.fst, from rfl,
  have EQ : diagonal.from_balancing2' c (diagonal.from_balancing1' c p hc12) hc23 =
    diagonal.from_balancing1' c (diagonal.from_balancing2' c p hc12) hc23,
  { ext; dsimp; refl, },
  rw has_sign.next_smul,
  work_on_goal 2 { exact p.from_balancing1'_rel c hc12, },
  rw [neg_add_eq_sub, ← has_sign.smul_sub, has_sign.smul_eq_zero, sub_eq_zero],
  congr' 1,
  dsimp,
  rw [← comp_apply, ← comp_apply],
  rw C.comm,
end

@[simps]
def total_complex [has_sign a] : homological_complex (Module R) c :=
{ X := λ k, C.total_at c k,
  d := λ k k', C.total_d c k k',
  shape' :=
  begin
    rintros, rw total_d, rw dif_neg, assumption
  end,
  d_comp_d' :=
  begin
    intros,
    apply total_d_comp_d_of_rel;
    assumption
  end }

end defs_in_mod

end

end homological_bicomplex


open tactic
meta def fsplit : tactic unit :=
do [c] ← target >>= get_constructors_for | tactic.fail "fsplit tactic failed, target is not an inductive datatype with only one constructor",
   mk_const c >>= fapply

structure Bijection ( U V : Type ) :=
  ( morphism : U → V )
  ( inverse  : V → U )
  ( witness_1 : ∀ u : U, inverse (morphism u) = u )
  ( witness_2 : ∀ v : V, morphism (inverse v) = v )

class Finite ( α : Type ) :=
  ( cardinality : nat )
  ( bijection : Bijection α (fin cardinality) )

lemma empty_exfalso (x : false) : empty := begin exfalso, trivial end

instance empty_is_Finite : Finite empty := {
  cardinality := 0,
  bijection := begin
                 fsplit,
                 intros,
                 induction a,
                 intros,
                 induction a,
                 apply empty_exfalso,
                 cases is_lt,
                 intros,
                 induction u,
                 intros,
                 induction v,
                 cases is_lt,
              end
}