/-
Copyright (c) 2018 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Jeremy Avigad, Simon Hudon
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.pfunctor.multivariate.basic
import Mathlib.PostPort

universes u u_1 l

namespace Mathlib

/-!
# Multivariate quotients of polynomial functors.

Basic definition of multivariate QPF. QPFs form a compositional framework
for defining inductive and coinductive types, their quotients and nesting.

The idea is based on building ever larger functors. For instance, we can define
a list using a shape functor:

```lean
inductive list_shape (a b : Type)
| nil : list_shape
| cons : a -> b -> list_shape
```

This shape can itself be decomposed as a sum of product which are themselves
QPFs. It follows that the shape is a QPF and we can take its fixed point
and create the list itself:

```lean
def list (a : Type) := fix list_shape a -- not the actual notation
```

We can continue and define the quotient on permutation of lists and create
the multiset type:

```lean
def multiset (a : Type) := qpf.quot list.perm list a -- not the actual notion
```

And `multiset` is also a QPF. We can then create a novel data type (for Lean):

```lean
inductive tree (a : Type)
| node : a -> multiset tree -> tree
```

An unordered tree. This is currently not supported by Lean because it nests
an inductive type inside of a quotient. We can go further and define
unordered, possibly infinite trees:

```lean
coinductive tree' (a : Type)
| node : a -> multiset tree' -> tree'
```

by using the `cofix` construct. Those options can all be mixed and
matched because they preserve the properties of QPF. The latter example,
`tree'`, combines fixed point, co-fixed point and quotients.

## Related modules

 * constructions
   * fix
   * cofix
   * quot
   * comp
   * sigma / pi
   * prj
   * const

each proves that some operations on functors preserves the QPF structure

##reference

 * [Jeremy Avigad, Mario M. Carneiro and Simon Hudon, *Data Types as Quotients of Polynomial Functors*][avigad-carneiro-hudon2019]
-/

/--
Multivariate quotients of polynomial functors.
-/
class mvqpf {n : ℕ} (F : typevec n → Type u_1) [mvfunctor F] where
  P : mvpfunctor n
  abs : {α : typevec n} → mvpfunctor.obj P α → F α
  repr : {α : typevec n} → F α → mvpfunctor.obj P α
  abs_repr : ∀ {α : typevec n} (x : F α), abs (repr x) = x
  abs_map :
    ∀ {α β : typevec n} (f : typevec.arrow α β) (p : mvpfunctor.obj P α),
      abs (mvfunctor.map f p) = mvfunctor.map f (abs p)

namespace mvqpf

/-!
### Show that every mvqpf is a lawful mvfunctor.
-/

protected theorem id_map {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] [q : mvqpf F]
    {α : typevec n} (x : F α) : mvfunctor.map typevec.id x = x :=
  sorry

@[simp] theorem comp_map {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] [q : mvqpf F]
    {α : typevec n} {β : typevec n} {γ : typevec n} (f : typevec.arrow α β) (g : typevec.arrow β γ)
    (x : F α) : mvfunctor.map (typevec.comp g f) x = mvfunctor.map g (mvfunctor.map f x) :=
  sorry

protected instance is_lawful_mvfunctor {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F]
    [q : mvqpf F] : is_lawful_mvfunctor F :=
  is_lawful_mvfunctor.mk mvqpf.id_map comp_map

/- Lifting predicates and relations -/

theorem liftp_iff {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] [q : mvqpf F] {α : typevec n}
    (p : {i : fin2 n} → α i → Prop) (x : F α) :
    mvfunctor.liftp p x ↔
        ∃ (a : mvpfunctor.A (P F)),
          ∃ (f : typevec.arrow (mvpfunctor.B (P F) a) α),
            x = abs (sigma.mk a f) ∧ ∀ (i : fin2 n) (j : mvpfunctor.B (P F) a i), p (f i j) :=
  sorry

theorem liftr_iff {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] [q : mvqpf F] {α : typevec n}
    (r : {i : fin2 n} → α i → α i → Prop) (x : F α) (y : F α) :
    mvfunctor.liftr r x y ↔
        ∃ (a : mvpfunctor.A (P F)),
          ∃ (f₀ : typevec.arrow (mvpfunctor.B (P F) a) α),
            ∃ (f₁ : typevec.arrow (mvpfunctor.B (P F) a) α),
              x = abs (sigma.mk a f₀) ∧
                y = abs (sigma.mk a f₁) ∧
                  ∀ (i : fin2 n) (j : mvpfunctor.B (P F) a i), r (f₀ i j) (f₁ i j) :=
  sorry

theorem mem_supp {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] [q : mvqpf F] {α : typevec n}
    (x : F α) (i : fin2 n) (u : α i) :
    u ∈ mvfunctor.supp x i ↔
        ∀ (a : mvpfunctor.A (P F)) (f : typevec.arrow (mvpfunctor.B (P F) a) α),
          abs (sigma.mk a f) = x → u ∈ f i '' set.univ :=
  sorry

theorem supp_eq {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] [q : mvqpf F] {α : typevec n}
    {i : fin2 n} (x : F α) :
    mvfunctor.supp x i =
        set_of
          fun (u : α i) =>
            ∀ (a : mvpfunctor.A (P F)) (f : typevec.arrow (mvpfunctor.B (P F) a) α),
              abs (sigma.mk a f) = x → u ∈ f i '' set.univ :=
  set.ext fun (x_1 : α i) => mem_supp x i x_1

theorem has_good_supp_iff {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] [q : mvqpf F]
    {α : typevec n} (x : F α) :
    (∀ (p : (i : fin2 n) → α i → Prop),
          mvfunctor.liftp p x ↔ ∀ (i : fin2 n) (u : α i), u ∈ mvfunctor.supp x i → p i u) ↔
        ∃ (a : mvpfunctor.A (P F)),
          ∃ (f : typevec.arrow (mvpfunctor.B (P F) a) α),
            abs (sigma.mk a f) = x ∧
              ∀ (i : fin2 n) (a' : mvpfunctor.A (P F))
                (f' : typevec.arrow (mvpfunctor.B (P F) a') α),
                abs (sigma.mk a' f') = x → f i '' set.univ ⊆ f' i '' set.univ :=
  sorry

/-- A qpf is said to be uniform if every polynomial functor
representing a single value all have the same range. -/
def is_uniform {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] (q : mvqpf F) :=
  ∀ {α : typevec n} (a a' : mvpfunctor.A (P F)) (f : typevec.arrow (mvpfunctor.B (P F) a) α)
    (f' : typevec.arrow (mvpfunctor.B (P F) a') α),
    abs (sigma.mk a f) = abs (sigma.mk a' f') → ∀ (i : fin2 n), f i '' set.univ = f' i '' set.univ

/-- does `abs` preserve `liftp`? -/
def liftp_preservation {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] (q : mvqpf F) :=
  ∀ {α : typevec n} (p : {i : fin2 n} → α i → Prop) (x : mvpfunctor.obj (P F) α),
    mvfunctor.liftp p (abs x) ↔ mvfunctor.liftp p x

/-- does `abs` preserve `supp`? -/
def supp_preservation {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] (q : mvqpf F) :=
  ∀ {α : typevec n} (x : mvpfunctor.obj (P F) α), mvfunctor.supp (abs x) = mvfunctor.supp x

theorem supp_eq_of_is_uniform {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] [q : mvqpf F]
    (h : is_uniform q) {α : typevec n} (a : mvpfunctor.A (P F))
    (f : typevec.arrow (mvpfunctor.B (P F) a) α) (i : fin2 n) :
    mvfunctor.supp (abs (sigma.mk a f)) i = f i '' set.univ :=
  sorry

theorem liftp_iff_of_is_uniform {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] [q : mvqpf F]
    (h : is_uniform q) {α : typevec n} (x : F α) (p : (i : fin2 n) → α i → Prop) :
    mvfunctor.liftp p x ↔ ∀ (i : fin2 n) (u : α i), u ∈ mvfunctor.supp x i → p i u :=
  sorry

theorem supp_map {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F] [q : mvqpf F] (h : is_uniform q)
    {α : typevec n} {β : typevec n} (g : typevec.arrow α β) (x : F α) (i : fin2 n) :
    mvfunctor.supp (mvfunctor.map g x) i = g i '' mvfunctor.supp x i :=
  sorry

theorem supp_preservation_iff_uniform {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F]
    [q : mvqpf F] : supp_preservation q ↔ is_uniform q :=
  sorry

theorem supp_preservation_iff_liftp_preservation {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F]
    [q : mvqpf F] : supp_preservation q ↔ liftp_preservation q :=
  sorry

theorem liftp_preservation_iff_uniform {n : ℕ} {F : typevec n → Type u_1} [mvfunctor F]
    [q : mvqpf F] : liftp_preservation q ↔ is_uniform q :=
  sorry

end Mathlib


/-
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

! This file was ported from Lean 3 source module topology.local_homeomorph
! leanprover-community/mathlib commit dc6c365e751e34d100e80fe6e314c3c3e0fd2988
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Logic.Equiv.LocalEquiv
import Mathlib.Topology.Sets.Opens

/-!
# Local homeomorphisms

This file defines homeomorphisms between open subsets of topological spaces. An element `e` of
`LocalHomeomorph α β` is an extension of `LocalEquiv α β`, i.e., it is a pair of functions
`e.toFun` and `e.invFun`, 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.toFun x` and `e.invFun x`.

## Main definitions

`Homeomorph.toLocalHomeomorph`: associating a local homeomorphism to a homeomorphism, with
                                  source = target = univ
`LocalHomeomorph.symm`  : the inverse of a local homeomorphism
`LocalHomeomorph.trans` : the composition of two local homeomorphisms
`LocalHomeomorph.refl`  : the identity local homeomorphism
`LocalHomeomorph.ofSet`: 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 `LocalEquiv` versions, although some care is required
especially when restricting to subsets, as these should be open subsets.

For design notes, see `LocalEquiv.lean`.

### Local coding conventions

If a lemma deals with the intersection of a set with either source or target of a `LocalEquiv`,
then it should use `e.source ∩ s` or `e.target ∩ t`, not `s ∩ e.source` or `t ∩ e.target`.
-/

open Function Set Filter Topology

open TopologicalSpace (SecondCountableTopology)

variable {α : Type _} {β : Type _} {γ : Type _} {δ : Type _} [TopologicalSpace α]
  [TopologicalSpace β] [TopologicalSpace γ] [TopologicalSpace δ]

/-- local homeomorphisms, defined on open subsets of the space -/
-- porting note: commented @[nolint has_nonempty_instance]
structure LocalHomeomorph (α : Type _) (β : Type _) [TopologicalSpace α]
  [TopologicalSpace β] extends LocalEquiv α β where
  open_source : IsOpen source
  open_target : IsOpen target
  continuous_toFun : ContinuousOn toFun source
  continuous_invFun : ContinuousOn invFun target
#align local_homeomorph LocalHomeomorph

namespace LocalHomeomorph

variable (e : LocalHomeomorph α β) (e' : LocalHomeomorph β γ)

/-- Coercion of a local homeomorphisms to a function. We don't use `e.toFun` because it is actually
`e.toLocalEquiv.toFun`, so `simp` will apply lemmas about `toLocalEquiv`. While we may want to
switch to this behavior later, doing it mid-port will break a lot of proofs.  -/
@[coe] def toFun' : α → β := e.toFun

/-- Coercion of a `LocalHomeomorph` to function. Note that a `LocalHomeomorph` is not `FunLike`. -/
instance : CoeFun (LocalHomeomorph α β) fun _ => α → β :=
  ⟨fun e => e.toFun'⟩

/-- The inverse of a local homeomorphism -/
protected def symm : LocalHomeomorph β α where
  toLocalEquiv := e.toLocalEquiv.symm
  open_source := e.open_target
  open_target := e.open_source
  continuous_toFun := e.continuous_invFun
  continuous_invFun := e.continuous_toFun
#align local_homeomorph.symm LocalHomeomorph.symm

/-- See Note [custom simps projection]. We need to specify this projection explicitly in this case,
  because it is a composition of multiple projections. -/
def Simps.apply (e : LocalHomeomorph α β) : α → β := e
#align local_homeomorph.simps.apply LocalHomeomorph.Simps.apply

/-- See Note [custom simps projection] -/
def Simps.symm_apply (e : LocalHomeomorph α β) : β → α := e.symm
#align local_homeomorph.simps.symm_apply LocalHomeomorph.Simps.symm_apply

initialize_simps_projections LocalHomeomorph (toFun → apply, invFun → symm_apply)

protected theorem continuousOn : ContinuousOn e e.source :=
  e.continuous_toFun
#align local_homeomorph.continuous_on LocalHomeomorph.continuousOn

theorem continuousOn_symm : ContinuousOn e.symm e.target :=
  e.continuous_invFun
#align local_homeomorph.continuous_on_symm LocalHomeomorph.continuousOn_symm

@[simp, mfld_simps]
theorem mk_coe (e : LocalEquiv α β) (a b c d) : (LocalHomeomorph.mk e a b c d : α → β) = e :=
  rfl
#align local_homeomorph.mk_coe LocalHomeomorph.mk_coe

@[simp, mfld_simps]
theorem mk_coe_symm (e : LocalEquiv α β) (a b c d) :
    ((LocalHomeomorph.mk e a b c d).symm : β → α) = e.symm :=
  rfl
#align local_homeomorph.mk_coe_symm LocalHomeomorph.mk_coe_symm

theorem toLocalEquiv_injective : Injective (toLocalEquiv : LocalHomeomorph α β → LocalEquiv α β)
  | ⟨_, _, _, _, _⟩, ⟨_, _, _, _, _⟩, rfl => rfl
#align local_homeomorph.to_local_equiv_injective LocalHomeomorph.toLocalEquiv_injective

/- 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, mfld_simps]
theorem toFun_eq_coe (e : LocalHomeomorph α β) : e.toFun = e :=
  rfl
#align local_homeomorph.to_fun_eq_coe LocalHomeomorph.toFun_eq_coe

@[simp, mfld_simps]
theorem invFun_eq_coe (e : LocalHomeomorph α β) : e.invFun = e.symm :=
  rfl
#align local_homeomorph.inv_fun_eq_coe LocalHomeomorph.invFun_eq_coe

@[simp, mfld_simps]
theorem coe_coe : (e.toLocalEquiv : α → β) = e :=
  rfl
#align local_homeomorph.coe_coe LocalHomeomorph.coe_coe

@[simp, mfld_simps]
theorem coe_coe_symm : (e.toLocalEquiv.symm : β → α) = e.symm :=
  rfl
#align local_homeomorph.coe_coe_symm LocalHomeomorph.coe_coe_symm

@[simp, mfld_simps]
theorem map_source {x : α} (h : x ∈ e.source) : e x ∈ e.target :=
  e.map_source' h
#align local_homeomorph.map_source LocalHomeomorph.map_source

@[simp, mfld_simps]
theorem map_target {x : β} (h : x ∈ e.target) : e.symm x ∈ e.source :=
  e.map_target' h
#align local_homeomorph.map_target LocalHomeomorph.map_target

@[simp, mfld_simps]
theorem left_inv {x : α} (h : x ∈ e.source) : e.symm (e x) = x :=
  e.left_inv' h
#align local_homeomorph.left_inv LocalHomeomorph.left_inv

@[simp, mfld_simps]
theorem right_inv {x : β} (h : x ∈ e.target) : e (e.symm x) = x :=
  e.right_inv' h
#align local_homeomorph.right_inv LocalHomeomorph.right_inv

theorem eq_symm_apply {x : α} {y : β} (hx : x ∈ e.source) (hy : y ∈ e.target) :
    x = e.symm y ↔ e x = y :=
  e.toLocalEquiv.eq_symm_apply hx hy
#align local_homeomorph.eq_symm_apply LocalHomeomorph.eq_symm_apply

protected theorem mapsTo : MapsTo e e.source e.target := fun _ => e.map_source
#align local_homeomorph.maps_to LocalHomeomorph.mapsTo

protected theorem symm_mapsTo : MapsTo e.symm e.target e.source :=
  e.symm.mapsTo
#align local_homeomorph.symm_maps_to LocalHomeomorph.symm_mapsTo

protected theorem leftInvOn : LeftInvOn e.symm e e.source := fun _ => e.left_inv
#align local_homeomorph.left_inv_on LocalHomeomorph.leftInvOn

protected theorem rightInvOn : RightInvOn e.symm e e.target := fun _ => e.right_inv
#align local_homeomorph.right_inv_on LocalHomeomorph.rightInvOn

protected theorem invOn : InvOn e.symm e e.source e.target :=
  ⟨e.leftInvOn, e.rightInvOn⟩
#align local_homeomorph.inv_on LocalHomeomorph.invOn

protected theorem injOn : InjOn e e.source :=
  e.leftInvOn.injOn
#align local_homeomorph.inj_on LocalHomeomorph.injOn

protected theorem bijOn : BijOn e e.source e.target :=
  e.invOn.bijOn e.mapsTo e.symm_mapsTo
#align local_homeomorph.bij_on LocalHomeomorph.bijOn

protected theorem surjOn : SurjOn e e.source e.target :=
  e.bijOn.surjOn
#align local_homeomorph.surj_on LocalHomeomorph.surjOn

/-- A homeomorphism induces a local homeomorphism on the whole space -/
@[simps! (config := mfld_cfg)]
def _root_.Homeomorph.toLocalHomeomorph (e : α ≃ₜ β) : LocalHomeomorph α β where
  toLocalEquiv := e.toEquiv.toLocalEquiv
  open_source := isOpen_univ
  open_target := isOpen_univ
  continuous_toFun := e.continuous.continuousOn
  continuous_invFun := e.symm.continuous.continuousOn
#align homeomorph.to_local_homeomorph Homeomorph.toLocalHomeomorph

/-- Replace `toLocalEquiv` field to provide better definitional equalities. -/
def replaceEquiv (e : LocalHomeomorph α β) (e' : LocalEquiv α β) (h : e.toLocalEquiv = e') :
    LocalHomeomorph α β where
  toLocalEquiv := e'
  open_source := h ▸ e.open_source
  open_target := h ▸ e.open_target
  continuous_toFun := h ▸ e.continuous_toFun
  continuous_invFun := h ▸ e.continuous_invFun
#align local_homeomorph.replace_equiv LocalHomeomorph.replaceEquiv

theorem replaceEquiv_eq_self (e : LocalHomeomorph α β) (e' : LocalEquiv α β)
    (h : e.toLocalEquiv = e') : e.replaceEquiv e' h = e := by
  cases e
  subst e'
  rfl
#align local_homeomorph.replace_equiv_eq_self LocalHomeomorph.replaceEquiv_eq_self

theorem source_preimage_target : e.source ⊆ e ⁻¹' e.target :=
  e.mapsTo
#align local_homeomorph.source_preimage_target LocalHomeomorph.source_preimage_target

@[deprecated toLocalEquiv_injective]
theorem eq_of_localEquiv_eq {e e' : LocalHomeomorph α β} (h : e.toLocalEquiv = e'.toLocalEquiv) :
    e = e' := toLocalEquiv_injective h
#align local_homeomorph.eq_of_local_equiv_eq LocalHomeomorph.eq_of_localEquiv_eq

theorem eventually_left_inverse (e : LocalHomeomorph α β) {x} (hx : x ∈ e.source) :
    ∀ᶠ y in 𝓝 x, e.symm (e y) = y :=
  (e.open_source.eventually_mem hx).mono e.left_inv'
#align local_homeomorph.eventually_left_inverse LocalHomeomorph.eventually_left_inverse

theorem eventually_left_inverse' (e : LocalHomeomorph α β) {x} (hx : x ∈ e.target) :
    ∀ᶠ y in 𝓝 (e.symm x), e.symm (e y) = y :=
  e.eventually_left_inverse (e.map_target hx)
#align local_homeomorph.eventually_left_inverse' LocalHomeomorph.eventually_left_inverse'

theorem eventually_right_inverse (e : LocalHomeomorph α β) {x} (hx : x ∈ e.target) :
    ∀ᶠ y in 𝓝 x, e (e.symm y) = y :=
  (e.open_target.eventually_mem hx).mono e.right_inv'
#align local_homeomorph.eventually_right_inverse LocalHomeomorph.eventually_right_inverse

theorem eventually_right_inverse' (e : LocalHomeomorph α β) {x} (hx : x ∈ e.source) :
    ∀ᶠ y in 𝓝 (e x), e (e.symm y) = y :=
  e.eventually_right_inverse (e.map_source hx)
#align local_homeomorph.eventually_right_inverse' LocalHomeomorph.eventually_right_inverse'

theorem eventually_ne_nhdsWithin (e : LocalHomeomorph α β) {x} (hx : x ∈ e.source) :
    ∀ᶠ x' in 𝓝[≠] x, e x' ≠ e x :=
  eventually_nhdsWithin_iff.2 <|
    (e.eventually_left_inverse hx).mono fun x' hx' =>
      mt fun h => by rw [mem_singleton_iff, ← e.left_inv hx, ← h, hx']
#align local_homeomorph.eventually_ne_nhds_within LocalHomeomorph.eventually_ne_nhdsWithin

theorem nhdsWithin_source_inter {x} (hx : x ∈ e.source) (s : Set α) : 𝓝[e.source ∩ s] x = 𝓝[s] x :=
  nhdsWithin_inter_of_mem (mem_nhdsWithin_of_mem_nhds <| IsOpen.mem_nhds e.open_source hx)
#align local_homeomorph.nhds_within_source_inter LocalHomeomorph.nhdsWithin_source_inter

theorem nhdsWithin_target_inter {x} (hx : x ∈ e.target) (s : Set β) : 𝓝[e.target ∩ s] x = 𝓝[s] x :=
  e.symm.nhdsWithin_source_inter hx s
#align local_homeomorph.nhds_within_target_inter LocalHomeomorph.nhdsWithin_target_inter

theorem image_eq_target_inter_inv_preimage {s : Set α} (h : s ⊆ e.source) :
    e '' s = e.target ∩ e.symm ⁻¹' s :=
  e.toLocalEquiv.image_eq_target_inter_inv_preimage h
#align local_homeomorph.image_eq_target_inter_inv_preimage LocalHomeomorph.image_eq_target_inter_inv_preimage

theorem image_source_inter_eq' (s : Set α) : e '' (e.source ∩ s) = e.target ∩ e.symm ⁻¹' s :=
  e.toLocalEquiv.image_source_inter_eq' s
#align local_homeomorph.image_source_inter_eq' LocalHomeomorph.image_source_inter_eq'

theorem image_source_inter_eq (s : Set α) :
    e '' (e.source ∩ s) = e.target ∩ e.symm ⁻¹' (e.source ∩ s) :=
  e.toLocalEquiv.image_source_inter_eq s
#align local_homeomorph.image_source_inter_eq LocalHomeomorph.image_source_inter_eq

theorem symm_image_eq_source_inter_preimage {s : Set β} (h : s ⊆ e.target) :
    e.symm '' s = e.source ∩ e ⁻¹' s :=
  e.symm.image_eq_target_inter_inv_preimage h
#align local_homeomorph.symm_image_eq_source_inter_preimage LocalHomeomorph.symm_image_eq_source_inter_preimage

theorem symm_image_target_inter_eq (s : Set β) :
    e.symm '' (e.target ∩ s) = e.source ∩ e ⁻¹' (e.target ∩ s) :=
  e.symm.image_source_inter_eq _
#align local_homeomorph.symm_image_target_inter_eq LocalHomeomorph.symm_image_target_inter_eq

theorem source_inter_preimage_inv_preimage (s : Set α) :
    e.source ∩ e ⁻¹' (e.symm ⁻¹' s) = e.source ∩ s :=
  e.toLocalEquiv.source_inter_preimage_inv_preimage s
#align local_homeomorph.source_inter_preimage_inv_preimage LocalHomeomorph.source_inter_preimage_inv_preimage

theorem target_inter_inv_preimage_preimage (s : Set β) :
    e.target ∩ e.symm ⁻¹' (e ⁻¹' s) = e.target ∩ s :=
  e.symm.source_inter_preimage_inv_preimage _
#align local_homeomorph.target_inter_inv_preimage_preimage LocalHomeomorph.target_inter_inv_preimage_preimage

theorem source_inter_preimage_target_inter (s : Set β) :
    e.source ∩ e ⁻¹' (e.target ∩ s) = e.source ∩ e ⁻¹' s :=
  e.toLocalEquiv.source_inter_preimage_target_inter s
#align local_homeomorph.source_inter_preimage_target_inter LocalHomeomorph.source_inter_preimage_target_inter

theorem image_source_eq_target (e : LocalHomeomorph α β) : e '' e.source = e.target :=
  e.toLocalEquiv.image_source_eq_target
#align local_homeomorph.image_source_eq_target LocalHomeomorph.image_source_eq_target

theorem symm_image_target_eq_source (e : LocalHomeomorph α β) : e.symm '' e.target = e.source :=
  e.symm.image_source_eq_target
#align local_homeomorph.symm_image_target_eq_source LocalHomeomorph.symm_image_target_eq_source

/-- Two local homeomorphisms are equal when they have equal `toFun`, `invFun` and `source`.
It is not sufficient to have equal `toFun` and `source`, as this only determines `invFun` on
the target. This would only be true for a weaker notion of equality, arguably the right one,
called `eq_on_source`. -/
@[ext]
protected theorem ext (e' : LocalHomeomorph α β) (h : ∀ x, e x = e' x)
    (hinv : ∀ x, e.symm x = e'.symm x) (hs : e.source = e'.source) : e = e' :=
  toLocalEquiv_injective (LocalEquiv.ext h hinv hs)
#align local_homeomorph.ext LocalHomeomorph.ext

protected theorem ext_iff {e e' : LocalHomeomorph α β} :
    e = e' ↔ (∀ x, e x = e' x) ∧ (∀ x, e.symm x = e'.symm x) ∧ e.source = e'.source :=
  ⟨by
    rintro rfl
    exact ⟨fun x => rfl, fun x => rfl, rfl⟩, fun h => e.ext e' h.1 h.2.1 h.2.2⟩
#align local_homeomorph.ext_iff LocalHomeomorph.ext_iff

@[simp, mfld_simps]
theorem symm_toLocalEquiv : e.symm.toLocalEquiv = e.toLocalEquiv.symm :=
  rfl
#align local_homeomorph.symm_to_local_equiv LocalHomeomorph.symm_toLocalEquiv

-- The following lemmas are already simp via `LocalEquiv`
theorem symm_source : e.symm.source = e.target :=
  rfl
#align local_homeomorph.symm_source LocalHomeomorph.symm_source

theorem symm_target : e.symm.target = e.source :=
  rfl
#align local_homeomorph.symm_target LocalHomeomorph.symm_target

@[simp, mfld_simps] theorem symm_symm : e.symm.symm = e := rfl
#align local_homeomorph.symm_symm LocalHomeomorph.symm_symm

/-- A local homeomorphism is continuous at any point of its source -/
protected theorem continuousAt {x : α} (h : x ∈ e.source) : ContinuousAt e x :=
  (e.continuousOn x h).continuousAt (e.open_source.mem_nhds h)
#align local_homeomorph.continuous_at LocalHomeomorph.continuousAt

/-- A local homeomorphism inverse is continuous at any point of its target -/
theorem continuousAt_symm {x : β} (h : x ∈ e.target) : ContinuousAt e.symm x :=
  e.symm.continuousAt h
#align local_homeomorph.continuous_at_symm LocalHomeomorph.continuousAt_symm

theorem tendsto_symm {x} (hx : x ∈ e.source) : Tendsto e.symm (𝓝 (e x)) (𝓝 x) := by
  simpa only [ContinuousAt, e.left_inv hx] using e.continuousAt_symm (e.map_source hx)
#align local_homeomorph.tendsto_symm LocalHomeomorph.tendsto_symm

theorem map_nhds_eq {x} (hx : x ∈ e.source) : map e (𝓝 x) = 𝓝 (e x) :=
  le_antisymm (e.continuousAt hx) <|
    le_map_of_right_inverse (e.eventually_right_inverse' hx) (e.tendsto_symm hx)
#align local_homeomorph.map_nhds_eq LocalHomeomorph.map_nhds_eq

theorem symm_map_nhds_eq {x} (hx : x ∈ e.source) : map e.symm (𝓝 (e x)) = 𝓝 x :=
  (e.symm.map_nhds_eq <| e.map_source hx).trans <| by rw [e.left_inv hx]
#align local_homeomorph.symm_map_nhds_eq LocalHomeomorph.symm_map_nhds_eq

theorem image_mem_nhds {x} (hx : x ∈ e.source) {s : Set α} (hs : s ∈ 𝓝 x) : e '' s ∈ 𝓝 (e x) :=
  e.map_nhds_eq hx ▸ Filter.image_mem_map hs
#align local_homeomorph.image_mem_nhds LocalHomeomorph.image_mem_nhds

theorem map_nhdsWithin_eq (e : LocalHomeomorph α β) {x} (hx : x ∈ e.source) (s : Set α) :
    map e (𝓝[s] x) = 𝓝[e '' (e.source ∩ s)] e x :=
  calc
    map e (𝓝[s] x) = map e (𝓝[e.source ∩ s] x) :=
      congr_arg (map e) (e.nhdsWithin_source_inter hx _).symm
    _ = 𝓝[e '' (e.source ∩ s)] e x :=
      (e.leftInvOn.mono <| inter_subset_left _ _).map_nhdsWithin_eq (e.left_inv hx)
        (e.continuousAt_symm (e.map_source hx)).continuousWithinAt
        (e.continuousAt hx).continuousWithinAt
#align local_homeomorph.map_nhds_within_eq LocalHomeomorph.map_nhdsWithin_eq

theorem map_nhdsWithin_preimage_eq (e : LocalHomeomorph α β) {x} (hx : x ∈ e.source) (s : Set β) :
    map e (𝓝[e ⁻¹' s] x) = 𝓝[s] e x := by
  rw [e.map_nhdsWithin_eq hx, e.image_source_inter_eq', e.target_inter_inv_preimage_preimage,
    e.nhdsWithin_target_inter (e.map_source hx)]
#align local_homeomorph.map_nhds_within_preimage_eq LocalHomeomorph.map_nhdsWithin_preimage_eq

theorem eventually_nhds (e : LocalHomeomorph α β) {x : α} (p : β → Prop) (hx : x ∈ e.source) :
    (∀ᶠ y in 𝓝 (e x), p y) ↔ ∀ᶠ x in 𝓝 x, p (e x) :=
  Iff.trans (by rw [e.map_nhds_eq hx]) eventually_map
#align local_homeomorph.eventually_nhds LocalHomeomorph.eventually_nhds

theorem eventually_nhds' (e : LocalHomeomorph α β) {x : α} (p : α → Prop) (hx : x ∈ e.source) :
    (∀ᶠ y in 𝓝 (e x), p (e.symm y)) ↔ ∀ᶠ x in 𝓝 x, p x := by
  rw [e.eventually_nhds _ hx]
  refine' eventually_congr ((e.eventually_left_inverse hx).mono fun y hy => _)
  rw [hy]
#align local_homeomorph.eventually_nhds' LocalHomeomorph.eventually_nhds'

theorem eventually_nhdsWithin (e : LocalHomeomorph α β) {x : α} (p : β → Prop) {s : Set α}
    (hx : x ∈ e.source) : (∀ᶠ y in 𝓝[e.symm ⁻¹' s] e x, p y) ↔ ∀ᶠ x in 𝓝[s] x, p (e x) := by
  refine' Iff.trans _ eventually_map
  rw [e.map_nhdsWithin_eq hx, e.image_source_inter_eq', e.nhdsWithin_target_inter (e.mapsTo hx)]
#align local_homeomorph.eventually_nhds_within LocalHomeomorph.eventually_nhdsWithin

theorem eventually_nhdsWithin' (e : LocalHomeomorph α β) {x : α} (p : α → Prop) {s : Set α}
    (hx : x ∈ e.source) : (∀ᶠ y in 𝓝[e.symm ⁻¹' s] e x, p (e.symm y)) ↔ ∀ᶠ x in 𝓝[s] x, p x := by
  rw [e.eventually_nhdsWithin _ hx]
  refine'
    eventually_congr
      ((eventually_nhdsWithin_of_eventually_nhds <| e.eventually_left_inverse hx).mono fun y hy =>
        _)
  rw [hy]
#align local_homeomorph.eventually_nhds_within' LocalHomeomorph.eventually_nhdsWithin'

/-- This lemma is useful in the manifold library in the case that `e` is a chart. It states that
  locally around `e x` the set `e.symm ⁻¹' s` is the same as the set intersected with the target
  of `e` and some other neighborhood of `f x` (which will be the source of a chart on `γ`).  -/
theorem preimage_eventuallyEq_target_inter_preimage_inter {e : LocalHomeomorph α β} {s : Set α}
    {t : Set γ} {x : α} {f : α → γ} (hf : ContinuousWithinAt f s x) (hxe : x ∈ e.source)
    (ht : t ∈ 𝓝 (f x)) : e.symm ⁻¹' s =ᶠ[𝓝 (e x)] (e.target ∩ e.symm ⁻¹' (s ∩ f ⁻¹' t) : Set β) :=
  by
  rw [eventuallyEq_set, e.eventually_nhds _ hxe]
  filter_upwards [e.open_source.mem_nhds hxe,
    mem_nhdsWithin_iff_eventually.mp (hf.preimage_mem_nhdsWithin ht)]
  intro y hy hyu
  simp_rw [mem_inter_iff, mem_preimage, mem_inter_iff, e.mapsTo hy, true_and_iff, iff_self_and,
    e.left_inv hy, iff_true_intro hyu]
#align local_homeomorph.preimage_eventually_eq_target_inter_preimage_inter LocalHomeomorph.preimage_eventuallyEq_target_inter_preimage_inter

theorem preimage_open_of_open {s : Set β} (hs : IsOpen s) : IsOpen (e.source ∩ e ⁻¹' s) :=
  e.continuousOn.preimage_open_of_open e.open_source hs
#align local_homeomorph.preimage_open_of_open LocalHomeomorph.preimage_open_of_open

/-!
### `LocalHomeomorph.IsImage` relation

We say that `t : Set β` is an image of `s : Set α` under a local homeomorphism `e` if any of the
following equivalent conditions hold:

* `e '' (e.source ∩ s) = e.target ∩ t`;
* `e.source ∩ e ⁻¹ t = e.source ∩ s`;
* `∀ x ∈ e.source, e x ∈ t ↔ x ∈ s` (this one is used in the definition).

This definition is a restatement of `LocalEquiv.IsImage` for local homeomorphisms. In this section
we transfer API about `LocalEquiv.IsImage` to local homeomorphisms and add a few
`LocalHomeomorph`-specific lemmas like `LocalHomeomorph.IsImage.closure`.
-/

/-- We say that `t : Set β` is an image of `s : Set α` under a local homeomorphism `e` if any of the
following equivalent conditions hold:

* `e '' (e.source ∩ s) = e.target ∩ t`;
* `e.source ∩ e ⁻¹ t = e.source ∩ s`;
* `∀ x ∈ e.source, e x ∈ t ↔ x ∈ s` (this one is used in the definition).
-/
def IsImage (s : Set α) (t : Set β) : Prop :=
  ∀ ⦃x⦄, x ∈ e.source → (e x ∈ t ↔ x ∈ s)
#align local_homeomorph.is_image LocalHomeomorph.IsImage

namespace IsImage

variable {e} {s : Set α} {t : Set β} {x : α} {y : β}

theorem toLocalEquiv (h : e.IsImage s t) : e.toLocalEquiv.IsImage s t :=
  h
#align local_homeomorph.is_image.to_local_equiv LocalHomeomorph.IsImage.toLocalEquiv

theorem apply_mem_iff (h : e.IsImage s t) (hx : x ∈ e.source) : e x ∈ t ↔ x ∈ s :=
  h hx
#align local_homeomorph.is_image.apply_mem_iff LocalHomeomorph.IsImage.apply_mem_iff

protected theorem symm (h : e.IsImage s t) : e.symm.IsImage t s :=
  h.toLocalEquiv.symm
#align local_homeomorph.is_image.symm LocalHomeomorph.IsImage.symm

theorem symm_apply_mem_iff (h : e.IsImage s t) (hy : y ∈ e.target) : e.symm y ∈ s ↔ y ∈ t :=
  h.symm hy
#align local_homeomorph.is_image.symm_apply_mem_iff LocalHomeomorph.IsImage.symm_apply_mem_iff

@[simp]
theorem symm_iff : e.symm.IsImage t s ↔ e.IsImage s t :=
  ⟨fun h => h.symm, fun h => h.symm⟩
#align local_homeomorph.is_image.symm_iff LocalHomeomorph.IsImage.symm_iff

protected theorem mapsTo (h : e.IsImage s t) : MapsTo e (e.source ∩ s) (e.target ∩ t) :=
  h.toLocalEquiv.mapsTo
#align local_homeomorph.is_image.maps_to LocalHomeomorph.IsImage.mapsTo

theorem symm_mapsTo (h : e.IsImage s t) : MapsTo e.symm (e.target ∩ t) (e.source ∩ s) :=
  h.symm.mapsTo
#align local_homeomorph.is_image.symm_maps_to LocalHomeomorph.IsImage.symm_mapsTo

theorem image_eq (h : e.IsImage s t) : e '' (e.source ∩ s) = e.target ∩ t :=
  h.toLocalEquiv.image_eq
#align local_homeomorph.is_image.image_eq LocalHomeomorph.IsImage.image_eq

theorem symm_image_eq (h : e.IsImage s t) : e.symm '' (e.target ∩ t) = e.source ∩ s :=
  h.symm.image_eq
#align local_homeomorph.is_image.symm_image_eq LocalHomeomorph.IsImage.symm_image_eq

theorem iff_preimage_eq : e.IsImage s t ↔ e.source ∩ e ⁻¹' t = e.source ∩ s :=
  LocalEquiv.IsImage.iff_preimage_eq
#align local_homeomorph.is_image.iff_preimage_eq LocalHomeomorph.IsImage.iff_preimage_eq

alias iff_preimage_eq ↔ preimage_eq of_preimage_eq
#align local_homeomorph.is_image.preimage_eq LocalHomeomorph.IsImage.preimage_eq
#align local_homeomorph.is_image.of_preimage_eq LocalHomeomorph.IsImage.of_preimage_eq

theorem iff_symm_preimage_eq : e.IsImage s t ↔ e.target ∩ e.symm ⁻¹' s = e.target ∩ t :=
  symm_iff.symm.trans iff_preimage_eq
#align local_homeomorph.is_image.iff_symm_preimage_eq LocalHomeomorph.IsImage.iff_symm_preimage_eq

alias iff_symm_preimage_eq ↔ symm_preimage_eq of_symm_preimage_eq
#align local_homeomorph.is_image.symm_preimage_eq LocalHomeomorph.IsImage.symm_preimage_eq
#align local_homeomorph.is_image.of_symm_preimage_eq LocalHomeomorph.IsImage.of_symm_preimage_eq

theorem iff_symm_preimage_eq' :
    e.IsImage s t ↔ e.target ∩ e.symm ⁻¹' (e.source ∩ s) = e.target ∩ t := by
  rw [iff_symm_preimage_eq, ← image_source_inter_eq, ← image_source_inter_eq']
#align local_homeomorph.is_image.iff_symm_preimage_eq' LocalHomeomorph.IsImage.iff_symm_preimage_eq'

alias iff_symm_preimage_eq' ↔ symm_preimage_eq' of_symm_preimage_eq'
#align local_homeomorph.is_image.symm_preimage_eq' LocalHomeomorph.IsImage.symm_preimage_eq'
#align local_homeomorph.is_image.of_symm_preimage_eq' LocalHomeomorph.IsImage.of_symm_preimage_eq'

theorem iff_preimage_eq' : e.IsImage s t ↔ e.source ∩ e ⁻¹' (e.target ∩ t) = e.source ∩ s :=
  symm_iff.symm.trans iff_symm_preimage_eq'
#align local_homeomorph.is_image.iff_preimage_eq' LocalHomeomorph.IsImage.iff_preimage_eq'

alias iff_preimage_eq' ↔ preimage_eq' of_preimage_eq'
#align local_homeomorph.is_image.preimage_eq' LocalHomeomorph.IsImage.preimage_eq'
#align local_homeomorph.is_image.of_preimage_eq' LocalHomeomorph.IsImage.of_preimage_eq'

theorem of_image_eq (h : e '' (e.source ∩ s) = e.target ∩ t) : e.IsImage s t :=
  LocalEquiv.IsImage.of_image_eq h
#align local_homeomorph.is_image.of_image_eq LocalHomeomorph.IsImage.of_image_eq

theorem of_symm_image_eq (h : e.symm '' (e.target ∩ t) = e.source ∩ s) : e.IsImage s t :=
  LocalEquiv.IsImage.of_symm_image_eq h
#align local_homeomorph.is_image.of_symm_image_eq LocalHomeomorph.IsImage.of_symm_image_eq

protected theorem compl (h : e.IsImage s t) : e.IsImage (sᶜ) (tᶜ) := fun _ hx => (h hx).not
#align local_homeomorph.is_image.compl LocalHomeomorph.IsImage.compl

protected theorem inter {s' t'} (h : e.IsImage s t) (h' : e.IsImage s' t') :
    e.IsImage (s ∩ s') (t ∩ t') := fun _ hx => (h hx).and (h' hx)
#align local_homeomorph.is_image.inter LocalHomeomorph.IsImage.inter

protected theorem union {s' t'} (h : e.IsImage s t) (h' : e.IsImage s' t') :
    e.IsImage (s ∪ s') (t ∪ t') := fun _ hx => (h hx).or (h' hx)
#align local_homeomorph.is_image.union LocalHomeomorph.IsImage.union

protected theorem diff {s' t'} (h : e.IsImage s t) (h' : e.IsImage s' t') :
    e.IsImage (s \ s') (t \ t') :=
  h.inter h'.compl
#align local_homeomorph.is_image.diff LocalHomeomorph.IsImage.diff

theorem leftInvOn_piecewise {e' : LocalHomeomorph α β} [∀ i, Decidable (i ∈ s)]
    [∀ i, Decidable (i ∈ t)] (h : e.IsImage s t) (h' : e'.IsImage s t) :
    LeftInvOn (t.piecewise e.symm e'.symm) (s.piecewise e e') (s.ite e.source e'.source) :=
  h.toLocalEquiv.leftInvOn_piecewise h'
#align local_homeomorph.is_image.left_inv_on_piecewise LocalHomeomorph.IsImage.leftInvOn_piecewise

theorem inter_eq_of_inter_eq_of_eqOn {e' : LocalHomeomorph α β} (h : e.IsImage s t)
    (h' : e'.IsImage s t) (hs : e.source ∩ s = e'.source ∩ s) (Heq : EqOn e e' (e.source ∩ s)) :
    e.target ∩ t = e'.target ∩ t :=
  h.toLocalEquiv.inter_eq_of_inter_eq_of_eqOn h' hs Heq
#align local_homeomorph.is_image.inter_eq_of_inter_eq_of_eq_on LocalHomeomorph.IsImage.inter_eq_of_inter_eq_of_eqOn

theorem symm_eqOn_of_inter_eq_of_eqOn {e' : LocalHomeomorph α β} (h : e.IsImage s t)
    (hs : e.source ∩ s = e'.source ∩ s) (Heq : EqOn e e' (e.source ∩ s)) :
    EqOn e.symm e'.symm (e.target ∩ t) :=
  h.toLocalEquiv.symm_eq_on_of_inter_eq_of_eqOn hs Heq
#align local_homeomorph.is_image.symm_eq_on_of_inter_eq_of_eq_on LocalHomeomorph.IsImage.symm_eqOn_of_inter_eq_of_eqOn

theorem map_nhdsWithin_eq (h : e.IsImage s t) (hx : x ∈ e.source) : map e (𝓝[s] x) = 𝓝[t] e x := by
  rw [e.map_nhdsWithin_eq hx, h.image_eq, e.nhdsWithin_target_inter (e.map_source hx)]
#align local_homeomorph.is_image.map_nhds_within_eq LocalHomeomorph.IsImage.map_nhdsWithin_eq

protected theorem closure (h : e.IsImage s t) : e.IsImage (closure s) (closure t) := fun x hx => by
  simp only [mem_closure_iff_nhdsWithin_neBot, ← h.map_nhdsWithin_eq hx, map_neBot_iff]
#align local_homeomorph.is_image.closure LocalHomeomorph.IsImage.closure

protected theorem interior (h : e.IsImage s t) : e.IsImage (interior s) (interior t) := by
  simpa only [closure_compl, compl_compl] using h.compl.closure.compl
#align local_homeomorph.is_image.interior LocalHomeomorph.IsImage.interior

protected theorem frontier (h : e.IsImage s t) : e.IsImage (frontier s) (frontier t) :=
  h.closure.diff h.interior
#align local_homeomorph.is_image.frontier LocalHomeomorph.IsImage.frontier

theorem isOpen_iff (h : e.IsImage s t) : IsOpen (e.source ∩ s) ↔ IsOpen (e.target ∩ t) :=
  ⟨fun hs => h.symm_preimage_eq' ▸ e.symm.preimage_open_of_open hs, fun hs =>
    h.preimage_eq' ▸ e.preimage_open_of_open hs⟩
#align local_homeomorph.is_image.is_open_iff LocalHomeomorph.IsImage.isOpen_iff

/-- Restrict a `LocalHomeomorph` to a pair of corresponding open sets. -/
@[simps toLocalEquiv]
def restr (h : e.IsImage s t) (hs : IsOpen (e.source ∩ s)) : LocalHomeomorph α β where
  toLocalEquiv := h.toLocalEquiv.restr
  open_source := hs
  open_target := h.isOpen_iff.1 hs
  continuous_toFun := e.continuousOn.mono (inter_subset_left _ _)
  continuous_invFun := e.symm.continuousOn.mono (inter_subset_left _ _)
#align local_homeomorph.is_image.restr LocalHomeomorph.IsImage.restr

end IsImage

theorem isImage_source_target : e.IsImage e.source e.target :=
  e.toLocalEquiv.isImage_source_target
#align local_homeomorph.is_image_source_target LocalHomeomorph.isImage_source_target

theorem isImage_source_target_of_disjoint (e' : LocalHomeomorph α β)
    (hs : Disjoint e.source e'.source) (ht : Disjoint e.target e'.target) :
    e.IsImage e'.source e'.target :=
  e.toLocalEquiv.isImage_source_target_of_disjoint e'.toLocalEquiv hs ht
#align local_homeomorph.is_image_source_target_of_disjoint LocalHomeomorph.isImage_source_target_of_disjoint

/-- Preimage of interior or interior of preimage coincide for local homeomorphisms, when restricted
to the source. -/
theorem preimage_interior (s : Set β) :
    e.source ∩ e ⁻¹' interior s = e.source ∩ interior (e ⁻¹' s) :=
  (IsImage.of_preimage_eq rfl).interior.preimage_eq
#align local_homeomorph.preimage_interior LocalHomeomorph.preimage_interior

theorem preimage_closure (s : Set β) : e.source ∩ e ⁻¹' closure s = e.source ∩ closure (e ⁻¹' s) :=
  (IsImage.of_preimage_eq rfl).closure.preimage_eq
#align local_homeomorph.preimage_closure LocalHomeomorph.preimage_closure

theorem preimage_frontier (s : Set β) :
    e.source ∩ e ⁻¹' frontier s = e.source ∩ frontier (e ⁻¹' s) :=
  (IsImage.of_preimage_eq rfl).frontier.preimage_eq
#align local_homeomorph.preimage_frontier LocalHomeomorph.preimage_frontier

theorem preimage_open_of_open_symm {s : Set α} (hs : IsOpen s) : IsOpen (e.target ∩ e.symm ⁻¹' s) :=
  e.symm.continuousOn.preimage_open_of_open e.open_target hs
#align local_homeomorph.preimage_open_of_open_symm LocalHomeomorph.preimage_open_of_open_symm

/-- The image of an open set in the source is open. -/
theorem image_open_of_open {s : Set α} (hs : IsOpen s) (h : s ⊆ e.source) : IsOpen (e '' s) := by
  have : e '' s = e.target ∩ e.symm ⁻¹' s := e.toLocalEquiv.image_eq_target_inter_inv_preimage h
  rw [this]
  exact e.continuousOn_symm.preimage_open_of_open e.open_target hs
#align local_homeomorph.image_open_of_open LocalHomeomorph.image_open_of_open

/-- The image of the restriction of an open set to the source is open. -/
theorem image_open_of_open' {s : Set α} (hs : IsOpen s) : IsOpen (e '' (e.source ∩ s)) :=
  image_open_of_open _ (IsOpen.inter e.open_source hs) (inter_subset_left _ _)
#align local_homeomorph.image_open_of_open' LocalHomeomorph.image_open_of_open'

/-- A `LocalEquiv` with continuous open forward map and an open source is a `LocalHomeomorph`. -/
def ofContinuousOpenRestrict (e : LocalEquiv α β) (hc : ContinuousOn e e.source)
    (ho : IsOpenMap (e.source.restrict e)) (hs : IsOpen e.source) : LocalHomeomorph α β where
  toLocalEquiv := e
  open_source := hs
  open_target := by simpa only [range_restrict, e.image_source_eq_target] using ho.isOpen_range
  continuous_toFun := hc
  continuous_invFun := e.image_source_eq_target ▸ ho.continuousOn_image_of_leftInvOn e.leftInvOn
#align local_homeomorph.of_continuous_open_restrict LocalHomeomorph.ofContinuousOpenRestrict

/-- A `LocalEquiv` with continuous open forward map and an open source is a `LocalHomeomorph`. -/
def ofContinuousOpen (e : LocalEquiv α β) (hc : ContinuousOn e e.source) (ho : IsOpenMap e)
    (hs : IsOpen e.source) : LocalHomeomorph α β :=
  ofContinuousOpenRestrict e hc (ho.restrict hs) hs
#align local_homeomorph.of_continuous_open LocalHomeomorph.ofContinuousOpen

/-- 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 restrOpen (s : Set α) (hs : IsOpen s) : LocalHomeomorph α β :=
  (@IsImage.of_symm_preimage_eq α β _ _ e s (e.symm ⁻¹' s) rfl).restr
    (IsOpen.inter e.open_source hs)
#align local_homeomorph.restr_open LocalHomeomorph.restrOpen

@[simp, mfld_simps]
theorem restrOpen_toLocalEquiv (s : Set α) (hs : IsOpen s) :
    (e.restrOpen s hs).toLocalEquiv = e.toLocalEquiv.restr s :=
  rfl
#align local_homeomorph.restr_open_to_local_equiv LocalHomeomorph.restrOpen_toLocalEquiv

-- Already simp via `LocalEquiv`
theorem restrOpen_source (s : Set α) (hs : IsOpen s) : (e.restrOpen s hs).source = e.source ∩ s :=
  rfl
#align local_homeomorph.restr_open_source LocalHomeomorph.restrOpen_source

/-- 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 -/
@[simps! (config := mfld_cfg) apply symm_apply, simps! (config := .lemmasOnly) source target]
protected def restr (s : Set α) : LocalHomeomorph α β :=
  e.restrOpen (interior s) isOpen_interior
#align local_homeomorph.restr LocalHomeomorph.restr

@[simp, mfld_simps]
theorem restr_toLocalEquiv (s : Set α) :
    (e.restr s).toLocalEquiv = e.toLocalEquiv.restr (interior s) :=
  rfl
#align local_homeomorph.restr_to_local_equiv LocalHomeomorph.restr_toLocalEquiv

theorem restr_source' (s : Set α) (hs : IsOpen s) : (e.restr s).source = e.source ∩ s := by
  rw [e.restr_source, hs.interior_eq]
#align local_homeomorph.restr_source' LocalHomeomorph.restr_source'

theorem restr_toLocalEquiv' (s : Set α) (hs : IsOpen s) :
    (e.restr s).toLocalEquiv = e.toLocalEquiv.restr s := by
  rw [e.restr_toLocalEquiv, hs.interior_eq]
#align local_homeomorph.restr_to_local_equiv' LocalHomeomorph.restr_toLocalEquiv'

theorem restr_eq_of_source_subset {e : LocalHomeomorph α β} {s : Set α} (h : e.source ⊆ s) :
    e.restr s = e :=
  toLocalEquiv_injective <| LocalEquiv.restr_eq_of_source_subset <| interior_maximal h e.open_source
#align local_homeomorph.restr_eq_of_source_subset LocalHomeomorph.restr_eq_of_source_subset

@[simp, mfld_simps]
theorem restr_univ {e : LocalHomeomorph α β} : e.restr univ = e :=
  restr_eq_of_source_subset (subset_univ _)
#align local_homeomorph.restr_univ LocalHomeomorph.restr_univ

theorem restr_source_inter (s : Set α) : e.restr (e.source ∩ s) = e.restr s := by
  refine' LocalHomeomorph.ext _ _ (fun x => rfl) (fun x => rfl) _
  simp [e.open_source.interior_eq, ← inter_assoc]
#align local_homeomorph.restr_source_inter LocalHomeomorph.restr_source_inter

/-- The identity on the whole space as a local homeomorphism. -/
@[simps! (config := mfld_cfg) apply, simps! (config := .lemmasOnly) source target]
protected def refl (α : Type _) [TopologicalSpace α] : LocalHomeomorph α α :=
  (Homeomorph.refl α).toLocalHomeomorph
#align local_homeomorph.refl LocalHomeomorph.refl

@[simp, mfld_simps]
theorem refl_localEquiv : (LocalHomeomorph.refl α).toLocalEquiv = LocalEquiv.refl α :=
  rfl
#align local_homeomorph.refl_local_equiv LocalHomeomorph.refl_localEquiv

@[simp, mfld_simps]
theorem refl_symm : (LocalHomeomorph.refl α).symm = LocalHomeomorph.refl α :=
  rfl
#align local_homeomorph.refl_symm LocalHomeomorph.refl_symm

section

variable {s : Set α} (hs : IsOpen s)

/-- The identity local equiv on a set `s` -/
@[simps! (config := mfld_cfg) apply, simps! (config := .lemmasOnly) source target]
def ofSet (s : Set α) (hs : IsOpen s) : LocalHomeomorph α α where
  toLocalEquiv := LocalEquiv.ofSet s
  open_source := hs
  open_target := hs
  continuous_toFun := continuous_id.continuousOn
  continuous_invFun := continuous_id.continuousOn
#align local_homeomorph.of_set LocalHomeomorph.ofSet

@[simp, mfld_simps]
theorem ofSet_toLocalEquiv : (ofSet s hs).toLocalEquiv = LocalEquiv.ofSet s :=
  rfl
#align local_homeomorph.of_set_to_local_equiv LocalHomeomorph.ofSet_toLocalEquiv

@[simp, mfld_simps]
theorem ofSet_symm : (ofSet s hs).symm = ofSet s hs :=
  rfl
#align local_homeomorph.of_set_symm LocalHomeomorph.ofSet_symm

@[simp, mfld_simps]
theorem ofSet_univ_eq_refl : ofSet univ isOpen_univ = LocalHomeomorph.refl α := by ext <;> simp
#align local_homeomorph.of_set_univ_eq_refl LocalHomeomorph.ofSet_univ_eq_refl

end

/-- Composition of two local homeomorphisms when the target of the first and the source of
the second coincide. -/
protected def trans' (h : e.target = e'.source) : LocalHomeomorph α γ where
  toLocalEquiv := LocalEquiv.trans' e.toLocalEquiv e'.toLocalEquiv h
  open_source := e.open_source
  open_target := e'.open_target
  continuous_toFun :=  e'.continuousOn.comp e.continuousOn <| h ▸ e.mapsTo
  continuous_invFun := e.continuousOn_symm.comp e'.continuousOn_symm <| h.symm ▸ e'.symm_mapsTo
#align local_homeomorph.trans' LocalHomeomorph.trans'

/-- Composing two local homeomorphisms, by restricting to the maximal domain where their
composition is well defined. -/
protected def trans : LocalHomeomorph α γ :=
  LocalHomeomorph.trans' (e.symm.restrOpen e'.source e'.open_source).symm
    (e'.restrOpen e.target e.open_target) (by simp [inter_comm])
#align local_homeomorph.trans LocalHomeomorph.trans

@[simp, mfld_simps]
theorem trans_toLocalEquiv : (e.trans e').toLocalEquiv = e.toLocalEquiv.trans e'.toLocalEquiv :=
  rfl
#align local_homeomorph.trans_to_local_equiv LocalHomeomorph.trans_toLocalEquiv

@[simp, mfld_simps]
theorem coe_trans : (e.trans e' : α → γ) = e' ∘ e :=
  rfl
#align local_homeomorph.coe_trans LocalHomeomorph.coe_trans

@[simp, mfld_simps]
theorem coe_trans_symm : ((e.trans e').symm : γ → α) = e.symm ∘ e'.symm :=
  rfl
#align local_homeomorph.coe_trans_symm LocalHomeomorph.coe_trans_symm

theorem trans_apply {x : α} : (e.trans e') x = e' (e x) :=
  rfl
#align local_homeomorph.trans_apply LocalHomeomorph.trans_apply

theorem trans_symm_eq_symm_trans_symm : (e.trans e').symm = e'.symm.trans e.symm := rfl
#align local_homeomorph.trans_symm_eq_symm_trans_symm LocalHomeomorph.trans_symm_eq_symm_trans_symm

/- 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 : (e.trans e').source = e.source ∩ e ⁻¹' e'.source :=
  LocalEquiv.trans_source e.toLocalEquiv e'.toLocalEquiv
#align local_homeomorph.trans_source LocalHomeomorph.trans_source

theorem trans_source' : (e.trans e').source = e.source ∩ e ⁻¹' (e.target ∩ e'.source) :=
  LocalEquiv.trans_source' e.toLocalEquiv e'.toLocalEquiv
#align local_homeomorph.trans_source' LocalHomeomorph.trans_source'

theorem trans_source'' : (e.trans e').source = e.symm '' (e.target ∩ e'.source) :=
  LocalEquiv.trans_source'' e.toLocalEquiv e'.toLocalEquiv
#align local_homeomorph.trans_source'' LocalHomeomorph.trans_source''

theorem image_trans_source : e '' (e.trans e').source = e.target ∩ e'.source :=
  LocalEquiv.image_trans_source e.toLocalEquiv e'.toLocalEquiv
#align local_homeomorph.image_trans_source LocalHomeomorph.image_trans_source

theorem trans_target : (e.trans e').target = e'.target ∩ e'.symm ⁻¹' e.target :=
  rfl
#align local_homeomorph.trans_target LocalHomeomorph.trans_target

theorem trans_target' : (e.trans e').target = e'.target ∩ e'.symm ⁻¹' (e'.source ∩ e.target) :=
  trans_source' e'.symm e.symm
#align local_homeomorph.trans_target' LocalHomeomorph.trans_target'

theorem trans_target'' : (e.trans e').target = e' '' (e'.source ∩ e.target) :=
  trans_source'' e'.symm e.symm
#align local_homeomorph.trans_target'' LocalHomeomorph.trans_target''

theorem inv_image_trans_target : e'.symm '' (e.trans e').target = e'.source ∩ e.target :=
  image_trans_source e'.symm e.symm
#align local_homeomorph.inv_image_trans_target LocalHomeomorph.inv_image_trans_target

theorem trans_assoc (e'' : LocalHomeomorph γ δ) :
    (e.trans e').trans e'' = e.trans (e'.trans e'') :=
  toLocalEquiv_injective <| e.1.trans_assoc _ _
#align local_homeomorph.trans_assoc LocalHomeomorph.trans_assoc

@[simp, mfld_simps]
theorem trans_refl : e.trans (LocalHomeomorph.refl β) = e :=
  toLocalEquiv_injective e.1.trans_refl
#align local_homeomorph.trans_refl LocalHomeomorph.trans_refl

@[simp, mfld_simps]
theorem refl_trans : (LocalHomeomorph.refl α).trans e = e :=
  toLocalEquiv_injective e.1.refl_trans
#align local_homeomorph.refl_trans LocalHomeomorph.refl_trans

theorem trans_ofSet {s : Set β} (hs : IsOpen s) : e.trans (ofSet s hs) = e.restr (e ⁻¹' s) :=
  LocalHomeomorph.ext _ _ (fun _ => rfl) (fun _ => rfl) <| by
    rw [trans_source, restr_source, ofSet_source, ← preimage_interior, hs.interior_eq]
#align local_homeomorph.trans_of_set LocalHomeomorph.trans_ofSet

theorem trans_of_set' {s : Set β} (hs : IsOpen s) :
    e.trans (ofSet s hs) = e.restr (e.source ∩ e ⁻¹' s) := by rw [trans_ofSet, restr_source_inter]
#align local_homeomorph.trans_of_set' LocalHomeomorph.trans_of_set'

theorem ofSet_trans {s : Set α} (hs : IsOpen s) : (ofSet s hs).trans e = e.restr s :=
  LocalHomeomorph.ext _ _ (fun x => rfl) (fun x => rfl) <| by simp [hs.interior_eq, inter_comm]
#align local_homeomorph.of_set_trans LocalHomeomorph.ofSet_trans

theorem ofSet_trans' {s : Set α} (hs : IsOpen s) : (ofSet s hs).trans e = e.restr (e.source ∩ s) :=
  by rw [ofSet_trans, restr_source_inter]
#align local_homeomorph.of_set_trans' LocalHomeomorph.ofSet_trans'

@[simp, mfld_simps]
theorem ofSet_trans_ofSet {s : Set α} (hs : IsOpen s) {s' : Set α} (hs' : IsOpen s') :
    (ofSet s hs).trans (ofSet s' hs') = ofSet (s ∩ s') (IsOpen.inter hs hs') := by
  rw [(ofSet s hs).trans_ofSet hs']
  ext <;> simp [hs'.interior_eq]
#align local_homeomorph.of_set_trans_of_set LocalHomeomorph.ofSet_trans_ofSet

theorem restr_trans (s : Set α) : (e.restr s).trans e' = (e.trans e').restr s :=
  toLocalEquiv_injective <| LocalEquiv.restr_trans e.toLocalEquiv e'.toLocalEquiv (interior s)
#align local_homeomorph.restr_trans LocalHomeomorph.restr_trans

/-- Postcompose a local homeomorphism with an homeomorphism.
We modify the source and target to have better definitional behavior. -/
@[simps! (config := { fullyApplied := false })]
def transHomeomorph (e' : β ≃ₜ γ) : LocalHomeomorph α γ where
  toLocalEquiv := e.toLocalEquiv.transEquiv e'.toEquiv
  open_source := e.open_source
  open_target := e.open_target.preimage e'.symm.continuous
  continuous_toFun := e'.continuous.comp_continuousOn e.continuousOn
  continuous_invFun := e.symm.continuousOn.comp e'.symm.continuous.continuousOn fun _ => id
#align local_homeomorph.trans_homeomorph LocalHomeomorph.transHomeomorph

theorem trans_equiv_eq_trans (e' : β ≃ₜ γ) : e.transHomeomorph e' = e.trans e'.toLocalHomeomorph :=
  toLocalEquiv_injective <| LocalEquiv.transEquiv_eq_trans _ _
#align local_homeomorph.trans_equiv_eq_trans LocalHomeomorph.trans_equiv_eq_trans

/-- Precompose a local homeomorphism with an homeomorphism.
We modify the source and target to have better definitional behavior. -/
@[simps! (config := { fullyApplied := false })]
def _root_.Homeomorph.transLocalHomeomorph (e : α ≃ₜ β) : LocalHomeomorph α γ where
  toLocalEquiv := e.toEquiv.transLocalEquiv e'.toLocalEquiv
  open_source := e'.open_source.preimage e.continuous
  open_target := e'.open_target
  continuous_toFun := e'.continuousOn.comp e.continuous.continuousOn fun _ => id
  continuous_invFun := e.symm.continuous.comp_continuousOn e'.symm.continuousOn
#align homeomorph.trans_local_homeomorph Homeomorph.transLocalHomeomorph

theorem _root_.Homeomorph.transLocalHomeomorph_eq_trans (e : α ≃ₜ β) :
    e.transLocalHomeomorph e' = e.toLocalHomeomorph.trans e' :=
  toLocalEquiv_injective <| Equiv.transLocalEquiv_eq_trans _ _
#align homeomorph.trans_local_homeomorph_eq_trans Homeomorph.transLocalHomeomorph_eq_trans

/-- `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 EqOnSource (e e' : LocalHomeomorph α β) : Prop :=
  e.source = e'.source ∧ EqOn e e' e.source
#align local_homeomorph.eq_on_source LocalHomeomorph.EqOnSource

theorem eqOnSource_iff (e e' : LocalHomeomorph α β) :
    EqOnSource e e' ↔ LocalEquiv.EqOnSource e.toLocalEquiv e'.toLocalEquiv :=
  Iff.rfl
#align local_homeomorph.eq_on_source_iff LocalHomeomorph.eqOnSource_iff

/-- `eq_on_source` is an equivalence relation -/
instance eqOnSourceSetoid : Setoid (LocalHomeomorph α β) :=
  { LocalEquiv.eqOnSourceSetoid.comap toLocalEquiv with r := EqOnSource }

theorem eqOnSource_refl : e ≈ e := Setoid.refl _
#align local_homeomorph.eq_on_source_refl LocalHomeomorph.eqOnSource_refl

/-- If two local homeomorphisms are equivalent, so are their inverses -/
theorem EqOnSource.symm' {e e' : LocalHomeomorph α β} (h : e ≈ e') : e.symm ≈ e'.symm :=
  LocalEquiv.EqOnSource.symm' h
#align local_homeomorph.eq_on_source.symm' LocalHomeomorph.EqOnSource.symm'

/-- Two equivalent local homeomorphisms have the same source -/
theorem EqOnSource.source_eq {e e' : LocalHomeomorph α β} (h : e ≈ e') : e.source = e'.source :=
  h.1
#align local_homeomorph.eq_on_source.source_eq LocalHomeomorph.EqOnSource.source_eq

/-- Two equivalent local homeomorphisms have the same target -/
theorem EqOnSource.target_eq {e e' : LocalHomeomorph α β} (h : e ≈ e') : e.target = e'.target :=
  h.symm'.1
#align local_homeomorph.eq_on_source.target_eq LocalHomeomorph.EqOnSource.target_eq

/-- Two equivalent local homeomorphisms have coinciding `toFun` on the source -/
theorem EqOnSource.eqOn {e e' : LocalHomeomorph α β} (h : e ≈ e') : EqOn e e' e.source :=
  h.2
#align local_homeomorph.eq_on_source.eq_on LocalHomeomorph.EqOnSource.eqOn

/-- Two equivalent local homeomorphisms have coinciding `invFun` on the target -/
theorem EqOnSource.symm_eqOn_target {e e' : LocalHomeomorph α β} (h : e ≈ e') :
    EqOn e.symm e'.symm e.target :=
  h.symm'.2
#align local_homeomorph.eq_on_source.symm_eq_on_target LocalHomeomorph.EqOnSource.symm_eqOn_target

/-- Composition of local homeomorphisms respects equivalence -/
theorem EqOnSource.trans' {e e' : LocalHomeomorph α β} {f f' : LocalHomeomorph β γ} (he : e ≈ e')
    (hf : f ≈ f') : e.trans f ≈ e'.trans f' :=
  LocalEquiv.EqOnSource.trans' he hf
#align local_homeomorph.eq_on_source.trans' LocalHomeomorph.EqOnSource.trans'

/-- Restriction of local homeomorphisms respects equivalence -/
theorem EqOnSource.restr {e e' : LocalHomeomorph α β} (he : e ≈ e') (s : Set α) :
    e.restr s ≈ e'.restr s :=
  LocalEquiv.EqOnSource.restr he _
#align local_homeomorph.eq_on_source.restr LocalHomeomorph.EqOnSource.restr

theorem Set.EqOn.restr_eqOn_source {e e' : LocalHomeomorph α β}
    (h : EqOn e e' (e.source ∩ e'.source)) : e.restr e'.source ≈ e'.restr e.source := by
  constructor
  · rw [e'.restr_source' _ e.open_source]
    rw [e.restr_source' _ e'.open_source]
    exact Set.inter_comm _ _
  · rw [e.restr_source' _ e'.open_source]
    refine' (EqOn.trans _ h).trans _ <;> simp only [mfld_simps, eqOn_refl]
#align local_homeomorph.set.eq_on.restr_eq_on_source LocalHomeomorph.Set.EqOn.restr_eqOn_source

/-- Composition of a local homeomorphism and its inverse is equivalent to the restriction of the
identity to the source -/
theorem trans_self_symm : e.trans e.symm ≈ LocalHomeomorph.ofSet e.source e.open_source :=
  LocalEquiv.trans_self_symm _
#align local_homeomorph.trans_self_symm LocalHomeomorph.trans_self_symm

theorem eq_of_eq_on_source_univ {e e' : LocalHomeomorph α β} (h : e ≈ e') (s : e.source = univ)
    (t : e.target = univ) : e = e' :=
  toLocalEquiv_injective <| LocalEquiv.eq_of_eq_on_source_univ _ _ h s t
#align local_homeomorph.eq_of_eq_on_source_univ LocalHomeomorph.eq_of_eq_on_source_univ

section Prod

/-- The product of two local homeomorphisms, as a local homeomorphism on the product space. -/
@[simps! (config := mfld_cfg) toLocalEquiv apply,
  simps! (config := .lemmasOnly) source target symm_apply]
def prod (e : LocalHomeomorph α β) (e' : LocalHomeomorph γ δ) :
    LocalHomeomorph (α × γ) (β × δ) where
  open_source := e.open_source.prod e'.open_source
  open_target := e.open_target.prod e'.open_target
  continuous_toFun := e.continuousOn.prod_map e'.continuousOn
  continuous_invFun := e.continuousOn_symm.prod_map e'.continuousOn_symm
  toLocalEquiv := e.toLocalEquiv.prod e'.toLocalEquiv
#align local_homeomorph.prod LocalHomeomorph.prod

@[simp, mfld_simps]
theorem prod_symm (e : LocalHomeomorph α β) (e' : LocalHomeomorph γ δ) :
    (e.prod e').symm = e.symm.prod e'.symm :=
  rfl
#align local_homeomorph.prod_symm LocalHomeomorph.prod_symm

@[simp]
theorem refl_prod_refl {α β : Type _} [TopologicalSpace α] [TopologicalSpace β] :
    (LocalHomeomorph.refl α).prod (LocalHomeomorph.refl β) = LocalHomeomorph.refl (α × β) :=
  LocalHomeomorph.ext _ _ (fun _ => rfl) (fun _ => rfl) univ_prod_univ
#align local_homeomorph.refl_prod_refl LocalHomeomorph.refl_prod_refl

@[simp, mfld_simps]
theorem prod_trans {η : Type _} {ε : Type _} [TopologicalSpace η] [TopologicalSpace ε]
    (e : LocalHomeomorph α β) (f : LocalHomeomorph β γ) (e' : LocalHomeomorph δ η)
    (f' : LocalHomeomorph η ε) : (e.prod e').trans (f.prod f') = (e.trans f).prod (e'.trans f') :=
  toLocalEquiv_injective <| e.1.prod_trans ..
#align local_homeomorph.prod_trans LocalHomeomorph.prod_trans

theorem prod_eq_prod_of_nonempty {e₁ e₁' : LocalHomeomorph α β} {e₂ e₂' : LocalHomeomorph γ δ}
    (h : (e₁.prod e₂).source.Nonempty) : e₁.prod e₂ = e₁'.prod e₂' ↔ e₁ = e₁' ∧ e₂ = e₂' := by
  obtain ⟨⟨x, y⟩, -⟩ := id h
  haveI : Nonempty α := ⟨x⟩
  haveI : Nonempty β := ⟨e₁ x⟩
  haveI : Nonempty γ := ⟨y⟩
  haveI : Nonempty δ := ⟨e₂ y⟩
  simp_rw [LocalHomeomorph.ext_iff, prod_apply, prod_symm_apply, prod_source, Prod.ext_iff,
    Set.prod_eq_prod_iff_of_nonempty h, forall_and, Prod.forall, forall_const,
    and_assoc, and_left_comm]
#align local_homeomorph.prod_eq_prod_of_nonempty LocalHomeomorph.prod_eq_prod_of_nonempty

theorem prod_eq_prod_of_nonempty' {e₁ e₁' : LocalHomeomorph α β} {e₂ e₂' : LocalHomeomorph γ δ}
    (h : (e₁'.prod e₂').source.Nonempty) : e₁.prod e₂ = e₁'.prod e₂' ↔ e₁ = e₁' ∧ e₂ = e₂' := by
  rw [eq_comm, prod_eq_prod_of_nonempty h, eq_comm, @eq_comm _ e₂']
#align local_homeomorph.prod_eq_prod_of_nonempty' LocalHomeomorph.prod_eq_prod_of_nonempty'

end Prod

section Piecewise

/-- Combine two `LocalHomeomorph`s using `Set.piecewise`. The source of the new `LocalHomeomorph`
is `s.ite e.source e'.source = e.source ∩ s ∪ e'.source \ s`, and similarly for target.  The
function sends `e.source ∩ s` to `e.target ∩ t` using `e` and `e'.source \ s` to `e'.target \ t`
using `e'`, and similarly for the inverse function. To ensure that the maps `toFun` and `invFun`
are inverse of each other on the new `source` and `target`, the definition assumes that the sets `s`
and `t` are related both by `e.is_image` and `e'.is_image`. To ensure that the new maps are
continuous on `source`/`target`, it also assumes that `e.source` and `e'.source` meet `frontier s`
on the same set and `e x = e' x` on this intersection. -/
@[simps! (config := { fullyApplied := false }) toLocalEquiv apply]
def piecewise (e e' : LocalHomeomorph α β) (s : Set α) (t : Set β) [∀ x, Decidable (x ∈ s)]
    [∀ y, Decidable (y ∈ t)] (H : e.IsImage s t) (H' : e'.IsImage s t)
    (Hs : e.source ∩ frontier s = e'.source ∩ frontier s)
    (Heq : EqOn e e' (e.source ∩ frontier s)) : LocalHomeomorph α β where
  toLocalEquiv := e.toLocalEquiv.piecewise e'.toLocalEquiv s t H H'
  open_source := e.open_source.ite e'.open_source Hs
  open_target :=
    e.open_target.ite e'.open_target <| H.frontier.inter_eq_of_inter_eq_of_eqOn H'.frontier Hs Heq
  continuous_toFun := continuousOn_piecewise_ite e.continuousOn e'.continuousOn Hs Heq
  continuous_invFun :=
    continuousOn_piecewise_ite e.continuousOn_symm e'.continuousOn_symm
      (H.frontier.inter_eq_of_inter_eq_of_eqOn H'.frontier Hs Heq)
      (H.frontier.symm_eqOn_of_inter_eq_of_eqOn Hs Heq)
#align local_homeomorph.piecewise LocalHomeomorph.piecewise

@[simp]
theorem symm_piecewise (e e' : LocalHomeomorph α β) {s : Set α} {t : Set β} [∀ x, Decidable (x ∈ s)]
    [∀ y, Decidable (y ∈ t)] (H : e.IsImage s t) (H' : e'.IsImage s t)
    (Hs : e.source ∩ frontier s = e'.source ∩ frontier s)
    (Heq : EqOn e e' (e.source ∩ frontier s)) :
    (e.piecewise e' s t H H' Hs Heq).symm =
      e.symm.piecewise e'.symm t s H.symm H'.symm
        (H.frontier.inter_eq_of_inter_eq_of_eqOn H'.frontier Hs Heq)
        (H.frontier.symm_eqOn_of_inter_eq_of_eqOn Hs Heq) :=
  rfl
#align local_homeomorph.symm_piecewise LocalHomeomorph.symm_piecewise

/-- Combine two `LocalHomeomorph`s with disjoint sources and disjoint targets. We reuse
`LocalHomeomorph.piecewise` then override `toLocalEquiv` to `LocalEquiv.disjointUnion`.
This way we have better definitional equalities for `source` and `target`. -/
def disjointUnion (e e' : LocalHomeomorph α β) [∀ x, Decidable (x ∈ e.source)]
    [∀ y, Decidable (y ∈ e.target)] (Hs : Disjoint e.source e'.source)
    (Ht : Disjoint e.target e'.target) : LocalHomeomorph α β :=
  (e.piecewise e' e.source e.target e.isImage_source_target
        (e'.isImage_source_target_of_disjoint e Hs.symm Ht.symm)
        (by rw [e.open_source.inter_frontier_eq, (Hs.symm.frontier_right e'.open_source).inter_eq])
        (by
          rw [e.open_source.inter_frontier_eq]
          exact eqOn_empty _ _)).replaceEquiv
    (e.toLocalEquiv.disjointUnion e'.toLocalEquiv Hs Ht)
    (LocalEquiv.disjointUnion_eq_piecewise _ _ _ _).symm
#align local_homeomorph.disjoint_union LocalHomeomorph.disjointUnion

end Piecewise

section Pi

variable {ι : Type _} [Fintype ι] {Xi Yi : ι → Type _} [∀ i, TopologicalSpace (Xi i)]
  [∀ i, TopologicalSpace (Yi i)] (ei : ∀ i, LocalHomeomorph (Xi i) (Yi i))

/-- The product of a finite family of `LocalHomeomorph`s. -/
@[simps toLocalEquiv]
def pi : LocalHomeomorph (∀ i, Xi i) (∀ i, Yi i) where
  toLocalEquiv := LocalEquiv.pi fun i => (ei i).toLocalEquiv
  open_source := isOpen_set_pi finite_univ fun i _ => (ei i).open_source
  open_target := isOpen_set_pi finite_univ fun i _ => (ei i).open_target
  continuous_toFun := continuousOn_pi.2 fun i =>
    (ei i).continuousOn.comp (continuous_apply _).continuousOn fun _f hf => hf i trivial
  continuous_invFun := continuousOn_pi.2 fun i =>
    (ei i).continuousOn_symm.comp (continuous_apply _).continuousOn fun _f hf => hf i trivial
#align local_homeomorph.pi LocalHomeomorph.pi

end Pi

section Continuity

/-- 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 continuousWithinAt_iff_continuousWithinAt_comp_right {f : β → γ} {s : Set β} {x : β}
    (h : x ∈ e.target) :
    ContinuousWithinAt f s x ↔ ContinuousWithinAt (f ∘ e) (e ⁻¹' s) (e.symm x) := by
  simp_rw [ContinuousWithinAt, ← @tendsto_map'_iff _ _ _ _ e,
    e.map_nhdsWithin_preimage_eq (e.map_target h), (· ∘ ·), e.right_inv h]
#align local_homeomorph.continuous_within_at_iff_continuous_within_at_comp_right LocalHomeomorph.continuousWithinAt_iff_continuousWithinAt_comp_right

/-- Continuity at a point can be read under right composition with a local homeomorphism, if the
point is in its target -/
theorem continuousAt_iff_continuousAt_comp_right {f : β → γ} {x : β} (h : x ∈ e.target) :
    ContinuousAt f x ↔ ContinuousAt (f ∘ e) (e.symm x) := by
  rw [← continuousWithinAt_univ, e.continuousWithinAt_iff_continuousWithinAt_comp_right h,
    preimage_univ, continuousWithinAt_univ]
#align local_homeomorph.continuous_at_iff_continuous_at_comp_right LocalHomeomorph.continuousAt_iff_continuousAt_comp_right

/-- 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 continuousOn_iff_continuousOn_comp_right {f : β → γ} {s : Set β} (h : s ⊆ e.target) :
    ContinuousOn f s ↔ ContinuousOn (f ∘ e) (e.source ∩ e ⁻¹' s) := by
  simp only [← e.symm_image_eq_source_inter_preimage h, ContinuousOn, ball_image_iff]
  refine' forall₂_congr fun x hx => _
  rw [e.continuousWithinAt_iff_continuousWithinAt_comp_right (h hx),
    e.symm_image_eq_source_inter_preimage h, inter_comm, continuousWithinAt_inter]
  exact IsOpen.mem_nhds e.open_source (e.map_target (h hx))
#align local_homeomorph.continuous_on_iff_continuous_on_comp_right LocalHomeomorph.continuousOn_iff_continuousOn_comp_right

/-- 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 continuousWithinAt_iff_continuousWithinAt_comp_left {f : γ → α} {s : Set γ} {x : γ}
    (hx : f x ∈ e.source) (h : f ⁻¹' e.source ∈ 𝓝[s] x) :
    ContinuousWithinAt f s x ↔ ContinuousWithinAt (e ∘ f) s x := by
  refine' ⟨(e.continuousAt hx).comp_continuousWithinAt, fun fe_cont => _⟩
  rw [← continuousWithinAt_inter' h] at fe_cont⊢
  have : ContinuousWithinAt (e.symm ∘ e ∘ f) (s ∩ f ⁻¹' e.source) x :=
    haveI : ContinuousWithinAt e.symm univ (e (f x)) :=
      (e.continuousAt_symm (e.map_source hx)).continuousWithinAt
    ContinuousWithinAt.comp this fe_cont (subset_univ _)
  exact this.congr (fun y hy => by simp [e.left_inv hy.2]) (by simp [e.left_inv hx])
#align local_homeomorph.continuous_within_at_iff_continuous_within_at_comp_left LocalHomeomorph.continuousWithinAt_iff_continuousWithinAt_comp_left

/-- 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 continuousAt_iff_continuousAt_comp_left {f : γ → α} {x : γ} (h : f ⁻¹' e.source ∈ 𝓝 x) :
    ContinuousAt f x ↔ ContinuousAt (e ∘ f) x := by
  have hx : f x ∈ e.source := (mem_of_mem_nhds h : _)
  have h' : f ⁻¹' e.source ∈ 𝓝[univ] x := by rwa [nhdsWithin_univ]
  rw [← continuousWithinAt_univ, ← continuousWithinAt_univ,
    e.continuousWithinAt_iff_continuousWithinAt_comp_left hx h']
#align local_homeomorph.continuous_at_iff_continuous_at_comp_left LocalHomeomorph.continuousAt_iff_continuousAt_comp_left

/-- 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 continuousOn_iff_continuousOn_comp_left {f : γ → α} {s : Set γ} (h : s ⊆ f ⁻¹' e.source) :
    ContinuousOn f s ↔ ContinuousOn (e ∘ f) s :=
  forall₂_congr fun _x hx =>
    e.continuousWithinAt_iff_continuousWithinAt_comp_left (h hx)
      (mem_of_superset self_mem_nhdsWithin h)
#align local_homeomorph.continuous_on_iff_continuous_on_comp_left LocalHomeomorph.continuousOn_iff_continuousOn_comp_left

/-- A function is continuous if and only if its composition with a local homeomorphism
on the left is continuous and its image is contained in the source. -/
theorem continuous_iff_continuous_comp_left {f : γ → α} (h : f ⁻¹' e.source = univ) :
    Continuous f ↔ Continuous (e ∘ f) := by
  simp only [continuous_iff_continuousOn_univ]
  exact e.continuousOn_iff_continuousOn_comp_left (Eq.symm h).subset
#align local_homeomorph.continuous_iff_continuous_comp_left LocalHomeomorph.continuous_iff_continuous_comp_left

end Continuity

/-- The homeomorphism obtained by restricting a `LocalHomeomorph` to a subset of the source. -/
@[simps]
def homeomorphOfImageSubsetSource {s : Set α} {t : Set β} (hs : s ⊆ e.source) (ht : e '' s = t) :
    s ≃ₜ t :=
  have h₁ : MapsTo e s t := mapsTo'.2 ht.subset
  have h₂ : t ⊆ e.target := ht ▸ e.image_source_eq_target ▸ image_subset e hs
  have h₃ : MapsTo e.symm t s := ht ▸ ball_image_iff.2 <| fun _x hx =>
      (e.left_inv (hs hx)).symm ▸ hx
  { toFun := MapsTo.restrict e s t h₁
    invFun := MapsTo.restrict e.symm t s h₃
    left_inv := fun a => Subtype.ext (e.left_inv (hs a.2))
    right_inv := fun b => Subtype.eq <| e.right_inv (h₂ b.2)
    continuous_toFun := (e.continuousOn.mono hs).restrict_mapsTo h₁
    continuous_invFun := (e.continuousOn_symm.mono h₂).restrict_mapsTo h₃ }
#align local_homeomorph.homeomorph_of_image_subset_source LocalHomeomorph.homeomorphOfImageSubsetSource

/-- A local homeomrphism defines a homeomorphism between its source and target. -/
@[simps!] -- porting note: new `simps`
def toHomeomorphSourceTarget : e.source ≃ₜ e.target :=
  e.homeomorphOfImageSubsetSource subset_rfl e.image_source_eq_target
#align local_homeomorph.to_homeomorph_source_target LocalHomeomorph.toHomeomorphSourceTarget

theorem secondCountableTopology_source [SecondCountableTopology β] (e : LocalHomeomorph α β) :
    SecondCountableTopology e.source :=
  e.toHomeomorphSourceTarget.secondCountableTopology
#align local_homeomorph.second_countable_topology_source LocalHomeomorph.secondCountableTopology_source

theorem nhds_eq_comap_inf_principal {x} (hx : x ∈ e.source) :
    𝓝 x = comap e (𝓝 (e x)) ⊓ 𝓟 e.source := by
  lift x to e.source using hx
  rw [← e.open_source.nhdsWithin_eq x.2, ← map_nhds_subtype_val, ← map_comap_setCoe_val,
    e.toHomeomorphSourceTarget.nhds_eq_comap, nhds_subtype_eq_comap]
  simp only [(· ∘ ·), toHomeomorphSourceTarget_apply_coe, comap_comap]

/-- If a local homeomorphism has source and target equal to univ, then it induces a homeomorphism
between the whole spaces, expressed in this definition. -/
@[simps (config := mfld_cfg) apply symm_apply] -- porting note: todo: add a `LocalEquiv` version
def toHomeomorphOfSourceEqUnivTargetEqUniv (h : e.source = (univ : Set α)) (h' : e.target = univ) :
    α ≃ₜ β where
  toFun := e
  invFun := e.symm
  left_inv x :=
    e.left_inv <| by
      rw [h]
      exact mem_univ _
  right_inv x :=
    e.right_inv <| by
      rw [h']
      exact mem_univ _
  continuous_toFun := by
    simpa only [continuous_iff_continuousOn_univ, h] using e.continuousOn
  continuous_invFun := by
    simpa only [continuous_iff_continuousOn_univ, h'] using e.continuousOn_symm
#align local_homeomorph.to_homeomorph_of_source_eq_univ_target_eq_univ LocalHomeomorph.toHomeomorphOfSourceEqUnivTargetEqUniv

/-- A local homeomorphism whose source is all of `α` defines an open embedding of `α` into `β`.  The
converse is also true; see `OpenEmbedding.toLocalHomeomorph`. -/
theorem to_openEmbedding (h : e.source = Set.univ) : OpenEmbedding e := by
  apply openEmbedding_of_continuous_injective_open
  · apply continuous_iff_continuousOn_univ.mpr
    rw [← h]
    exact e.continuousOn
  · apply Set.injective_iff_injOn_univ.mpr
    rw [← h]
    exact e.injOn
  · intro U hU
    simpa only [h, subset_univ, mfld_simps] using e.image_open_of_open hU
#align local_homeomorph.to_open_embedding LocalHomeomorph.to_openEmbedding

end LocalHomeomorph

namespace Homeomorph

variable (e : α ≃ₜ β) (e' : β ≃ₜ γ)

/- Register as simp lemmas that the fields of a local homeomorphism built from a homeomorphism
correspond to the fields of the original homeomorphism. -/
@[simp, mfld_simps]
theorem refl_toLocalHomeomorph : (Homeomorph.refl α).toLocalHomeomorph = LocalHomeomorph.refl α :=
  rfl
#align homeomorph.refl_to_local_homeomorph Homeomorph.refl_toLocalHomeomorph

@[simp, mfld_simps]
theorem symm_toLocalHomeomorph : e.symm.toLocalHomeomorph = e.toLocalHomeomorph.symm :=
  rfl
#align homeomorph.symm_to_local_homeomorph Homeomorph.symm_toLocalHomeomorph

@[simp, mfld_simps]
theorem trans_toLocalHomeomorph :
    (e.trans e').toLocalHomeomorph = e.toLocalHomeomorph.trans e'.toLocalHomeomorph :=
  LocalHomeomorph.toLocalEquiv_injective <| Equiv.trans_toLocalEquiv _ _
#align homeomorph.trans_to_local_homeomorph Homeomorph.trans_toLocalHomeomorph

end Homeomorph

namespace OpenEmbedding

variable (f : α → β) (h : OpenEmbedding f)

/-- An open embedding of `α` into `β`, with `α` nonempty, defines a local homeomorphism whose source
is all of `α`.  The converse is also true; see `LocalHomeomorph.to_openEmbedding`. -/
@[simps! (config := mfld_cfg) apply source target]
noncomputable def toLocalHomeomorph [Nonempty α] : LocalHomeomorph α β :=
  LocalHomeomorph.ofContinuousOpen ((h.toEmbedding.inj.injOn univ).toLocalEquiv _ _)
    h.continuous.continuousOn h.isOpenMap isOpen_univ
#align open_embedding.to_local_homeomorph OpenEmbedding.toLocalHomeomorph

theorem continuousAt_iff {f : α → β} {g : β → γ} (hf : OpenEmbedding f) {x : α} :
    ContinuousAt (g ∘ f) x ↔ ContinuousAt g (f x) :=
  hf.tendsto_nhds_iff'
#align open_embedding.continuous_at_iff OpenEmbedding.continuousAt_iff

end OpenEmbedding

namespace TopologicalSpace.Opens

open TopologicalSpace

variable (s : Opens α) [Nonempty s]

/-- The inclusion of an open subset `s` of a space `α` into `α` is a local homeomorphism from the
subtype `s` to `α`. -/
noncomputable def localHomeomorphSubtypeCoe : LocalHomeomorph s α :=
  OpenEmbedding.toLocalHomeomorph _ s.2.openEmbedding_subtype_val
#align topological_space.opens.local_homeomorph_subtype_coe TopologicalSpace.Opens.localHomeomorphSubtypeCoe

@[simp, mfld_simps]
theorem localHomeomorphSubtypeCoe_coe : (s.localHomeomorphSubtypeCoe : s → α) = (↑) :=
  rfl
#align topological_space.opens.local_homeomorph_subtype_coe_coe TopologicalSpace.Opens.localHomeomorphSubtypeCoe_coe

@[simp, mfld_simps]
theorem localHomeomorphSubtypeCoe_source : s.localHomeomorphSubtypeCoe.source = Set.univ :=
  rfl
#align topological_space.opens.local_homeomorph_subtype_coe_source TopologicalSpace.Opens.localHomeomorphSubtypeCoe_source

@[simp, mfld_simps]
theorem localHomeomorphSubtypeCoe_target : s.localHomeomorphSubtypeCoe.target = s := by
  simp only [localHomeomorphSubtypeCoe, Subtype.range_coe_subtype, mfld_simps]
  rfl
#align topological_space.opens.local_homeomorph_subtype_coe_target TopologicalSpace.Opens.localHomeomorphSubtypeCoe_target

end TopologicalSpace.Opens

namespace LocalHomeomorph

open TopologicalSpace

variable (e : LocalHomeomorph α β)

variable (s : Opens α) [Nonempty s]

/-- 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`.-/
noncomputable def subtypeRestr : LocalHomeomorph s β :=
  s.localHomeomorphSubtypeCoe.trans e
#align local_homeomorph.subtype_restr LocalHomeomorph.subtypeRestr

theorem subtypeRestr_def : e.subtypeRestr s = s.localHomeomorphSubtypeCoe.trans e :=
  rfl
#align local_homeomorph.subtype_restr_def LocalHomeomorph.subtypeRestr_def

@[simp, mfld_simps]
theorem subtypeRestr_coe :
    ((e.subtypeRestr s : LocalHomeomorph s β) : s → β) = Set.restrict ↑s (e : α → β) :=
  rfl
#align local_homeomorph.subtype_restr_coe LocalHomeomorph.subtypeRestr_coe

@[simp, mfld_simps]
theorem subtypeRestr_source : (e.subtypeRestr s).source = (↑) ⁻¹' e.source := by
  simp only [subtypeRestr_def, mfld_simps]
#align local_homeomorph.subtype_restr_source LocalHomeomorph.subtypeRestr_source

/- This lemma characterizes the transition functions of an open subset in terms of the transition
functions of the original space. -/
theorem subtypeRestr_symm_trans_subtypeRestr (f f' : LocalHomeomorph α β) :
    (f.subtypeRestr s).symm.trans (f'.subtypeRestr s) ≈
      (f.symm.trans f').restr (f.target ∩ f.symm ⁻¹' s) := by
  simp only [subtypeRestr_def, trans_symm_eq_symm_trans_symm]
  have openness₁ : IsOpen (f.target ∩ f.symm ⁻¹' s) := f.preimage_open_of_open_symm s.2
  rw [← ofSet_trans _ openness₁, ← trans_assoc, ← trans_assoc]
  refine' EqOnSource.trans' _ (eqOnSource_refl _)
  -- f' has been eliminated !!!
  have sets_identity : f.symm.source ∩ (f.target ∩ f.symm ⁻¹' s) = f.symm.source ∩ f.symm ⁻¹' s :=
    by mfld_set_tac
  have openness₂ : IsOpen (s : Set α) := s.2
  rw [ofSet_trans', sets_identity, ← trans_of_set' _ openness₂, trans_assoc]
  refine' EqOnSource.trans' (eqOnSource_refl _) _
  -- f has been eliminated !!!
  refine' Setoid.trans (trans_symm_self s.localHomeomorphSubtypeCoe) _
  simp only [mfld_simps, Setoid.refl]
#align local_homeomorph.subtype_restr_symm_trans_subtype_restr LocalHomeomorph.subtypeRestr_symm_trans_subtypeRestr

end LocalHomeomorph


import order.monotone.basic
import init.data.fin.basic
import order.hom.basic
import tactic
import set_theory.cardinal.finite
import set_theory.ordinal.basic
import combinatorics.pigeonhole
import finramsey

noncomputable theory
open finset

variables {α β : Type*} [linear_order α]

noncomputable theory
open_locale classical

@[reducible] def p_edge (C : finset β) (p : ℕ) := powerset_len p C

lemma p_edge_card {C : finset β} {p : ℕ} {edge: finset β } (h: edge ∈ p_edge C p) : edge.card = p:=
begin
  apply (finset.mem_powerset_len.1 h).2,
end

@[reducible] def p_edge_in_ss {β : Type*}  (C : finset β) (ss : finset β ) (p : ℕ)
: finset (finset β ):=
finset.filter (λ e, e ⊆ ss) (p_edge C p)
--{e ∈ p_edge C p | e ⊆ ss}

lemma p_edge_in_ss_subset (C : finset β ) (ss : finset β) (p : ℕ):
p_edge_in_ss C ss p ⊆ p_edge C p:=
begin
  apply finset.filter_subset,
end

lemma p_edge_in_ss_card {C ss : finset β} {p : ℕ} {edge : finset β} (h: edge ∈ p_edge_in_ss C ss p):
edge.card = p:= p_edge_card ((p_edge_in_ss_subset C ss p) h)

lemma p_edge_in_ss_p_edge_ss {C : finset β} {ss : finset β} {p : ℕ} {edge : finset β}
(h: edge ∈ p_edge_in_ss C ss p): edge ∈ p_edge ss p:=
finset.mem_powerset_len.2 ⟨(finset.mem_filter.1 h).2, p_edge_in_ss_card h⟩

lemma p_nonempty (C ss : finset β) {p : ℕ} (h: 0 < p) : ∀ edge ∈ p_edge_in_ss C ss p, finset.nonempty edge:=
begin
  -- haveI : linear_order α := is_well_order.linear_order (well_ordering_rel),
  intros e einss, have:= p_edge_in_ss_card einss, apply finset.card_pos.1, linarith,
end

def p_edge_from_in_ss (C : finset α) (ss : finset α) (p : ℕ) (v : α) (h: ∀ edge ∈ p_edge_in_ss C ss p, finset.nonempty edge):
finset (finset α):= -- all p-edges from a given vertex within a subset
finset.filter (λ edge, if mem: edge ∈ p_edge_in_ss C ss p then finset.min' edge (h edge mem) = v else false) (p_edge_in_ss C ss p)

lemma p_edge_from_ss_ss {C : finset α} {ss : finset α} {p : ℕ} {ppos: 0 < p} {v : α} {edge : finset α}
(h: edge ∈ p_edge_from_in_ss C ss p v (p_nonempty C ss ppos)): edge ⊆ ss:=
begin
  cases finset.mem_filter.1 h with edge_in_ss prop, cases finset.mem_filter.1 edge_in_ss, exact right,
end

lemma p_edge_from_card {C : finset α} {ss : finset α} {p : ℕ} {ppos: 0 < p} {v : α} {edge : finset α}
(h: edge ∈ p_edge_from_in_ss C ss p v (p_nonempty C ss ppos)): edge.card = p:=
begin
  cases mem_filter.1 h with edge_in_ss edge_min_v, exact p_edge_in_ss_card edge_in_ss,
end

lemma edge_nonempty {C : finset α} {ss : finset α} {p : ℕ} {edge : finset α} (ppos : 0 < p)
(h: edge ∈ p_edge_in_ss C ss p): edge.nonempty:=
begin
  apply finset.card_pos.1, have:= p_edge_in_ss_card h, linarith,
end

lemma p_edge_from_mem {C : finset α} {ss : finset α} {p : ℕ} {ppos : 0 < p} {v : α} {edge : finset α}
(h: edge ∈ p_edge_from_in_ss C ss p v (p_nonempty C ss ppos)): v ∈ edge:=
begin
  cases mem_filter.1 h with edge_in_ss edge_min_v,
  rw dif_pos edge_in_ss at edge_min_v, rw ← edge_min_v,
  exact finset.min'_mem edge (edge_nonempty ppos edge_in_ss),
end

lemma p_edge_from_min [linear_order α] {C : finset α} {ss : finset α} {p : ℕ} {ppos: 0 < p} {v : α} {edge : finset α}
(h: edge ∈ p_edge_from_in_ss C ss p v (p_nonempty C ss ppos)) (edge_none: edge.nonempty):edge.min' edge_none = v:=
begin
  cases mem_filter.1 h with edge_in_ss edge_min_v,
  rw dif_pos edge_in_ss at edge_min_v, apply edge_min_v,
end

lemma p_edge_ss_from_v_in_ss {C : finset α} {ss : finset α} {p : ℕ} {ppos : 0 < p} {edge : finset α}
(h: edge ∈ p_edge ss p) (ss_p: ss ⊆ C):
∃ v ∈ ss, edge ∈ p_edge_from_in_ss C ss p v (p_nonempty C ss ppos):=
begin
  have:edge ∈ p_edge_in_ss C ss p, rw finset.mem_filter, rw finset.mem_powerset_len,
  rw finset.mem_powerset_len at h, exact ⟨⟨finset.subset.trans h.1 ss_p, h.2⟩, h.1⟩,
  have edge_none:=edge_nonempty ppos this,
  use finset.min' edge edge_none, refine ⟨(finset.mem_filter.1 this).2 (finset.min'_mem edge edge_none), _⟩,
  apply finset.mem_filter.2, refine ⟨this, _⟩, rw dif_pos this,
end

lemma p_edge_from_ss_edge_from_ss {C : finset α} {ss_1 : finset α} {ss_2 : finset α} {p : ℕ} {ppos : 0 < p} {v : α}
(h_ss: ss_1 ⊆ ss_2) {edge : finset α} (h: edge ∈ p_edge_from_in_ss C ss_1 p v (p_nonempty C ss_1 ppos)):
edge ∈ p_edge_from_in_ss C ss_2 p v (p_nonempty C ss_2 ppos):=
begin
  apply finset.mem_filter.2, cases mem_filter.1 h with edge_in_ss edge_min_v, rw dif_pos edge_in_ss at edge_min_v,
  have: edge ∈ p_edge_in_ss C ss_2 p, apply finset.mem_filter.2, cases mem_filter.1 edge_in_ss,
  exact ⟨left, finset.subset.trans right h_ss⟩, simp [this, edge_min_v],
end

def p_fav_in_ss (C : finset α) (ss : finset α) {p : ℕ} {c : ℕ} (f : finset α → fin c) (ppos: 0 < p) (v : α):=
∃ color : fin c, ∀ edge ∈ p_edge_from_in_ss C ss p v (p_nonempty C ss ppos), f edge = color

lemma f_aux_match {c : ℕ} (f: finset α → fin c) {edge: finset α} {v_1 : α} (v_1_in_edge: v_1 ∈ edge) (color : fin c):
(λ edge : finset α, f (insert v_1 edge)) (edge.erase v_1) = color → f edge = color:=
begin
  dsimp, rw finset.insert_erase v_1_in_edge, simp,
end

lemma genprocession (p k c : ℕ)
(ramsey: ∀ k : ℕ, ∃ (n₀ : ℕ), ∀ (C : finset α), n₀ ≤ C.card → ∀ (f : finset α → fin c),
 ∃ (color : fin c) (clique : finset α) (H : clique ⊆ C),
  k ≤ clique.card ∧ ∀ edge, edge ∈ p_edge clique p → f edge = color)
: --write fav first
∃ n₀ : ℕ, ∀ C : finset α, n₀ ≤ C.card → ∀ f : finset α → fin c, ∃ Y ⊆ C,
Y.card = k ∧ ∀ v ∈ Y, p_fav_in_ss C Y f (nat.zero_lt_succ p) v:=
begin
  induction k with k ih,
  {
    use 0, intros C Ccard f, use finset.empty,
    refine ⟨finset.empty_subset C, finset.card_empty, _⟩, intros v vinempty,
    have:= finset.not_mem_empty v, contradiction,
  },
  cases ih with n₁ hn₁, cases ramsey n₁ with n₀ hn₀, use n₀+1, intros C Ccard f,
  have C_none: C.nonempty, apply finset.card_pos.1, linarith,
  set v_1:= C.min' C_none, set C_right:= C.erase (v_1),
  have C_right_card: n₀ ≤ C_right.card,
  linarith[finset.card_erase_of_mem (finset.min'_mem C C_none), le_tsub_of_add_le_right Ccard],
  set f_aux: finset α → fin c:= λ edge, f (insert v_1 edge),
  rcases hn₀ C_right C_right_card f_aux with ⟨color, clique, ⟨clique_ss, clique_card, clique_color⟩⟩,
  rcases hn₁ clique clique_card f with ⟨favs, favs_ss, favs_card, favs_colors⟩,
  have favs_ss_C_right: favs ⊆ C_right:= λ x xinf, (clique_ss (favs_ss xinf)),
  have favs_ss_C: favs ⊆ C:= λ x xinf, (finset.mem_erase.1 (favs_ss_C_right xinf)).2,
  set procession:=finset.cons v_1 favs (finset.not_mem_mono favs_ss_C_right (finset.not_mem_erase v_1 C)),
  use procession, have proc_ss_C:=finset.cons_subset.2 ⟨finset.min'_mem C C_none, favs_ss_C⟩,
   refine ⟨proc_ss_C, _, _⟩,
  rw ← favs_card, apply finset.card_cons _, intros v vinp,
  cases finset.mem_cons.1 vinp with v_v1 v_favs, rw v_v1, use color, intros edge edge_from_proc_v,
  have emv_card: (edge.erase(v_1)).card = p,{
    have edge_card:= p_edge_from_card edge_from_proc_v,
    have:= finset.card_erase_of_mem (p_edge_from_mem edge_from_proc_v), rw this, rw edge_card,
    exact nat.succ_sub_one p,
  },
  have emv_in_p_C_right: edge.erase(v_1) ∈ p_edge_in_ss C_right clique p, apply finset.mem_filter.2,
  split, {
    apply finset.mem_powerset_len.2, refine ⟨_, emv_card⟩,
    exact finset.erase_subset_erase v_1 (finset.subset.trans (p_edge_from_ss_ss edge_from_proc_v) proc_ss_C),
  },
  have proc_erase_ss_clique: procession.erase(v_1) ⊆ clique, apply finset.subset.trans _ favs_ss, apply finset.subset_of_eq,
  apply finset.erase_cons _, apply finset.subset.trans _ proc_erase_ss_clique,
  exact finset.erase_subset_erase v_1 (p_edge_from_ss_ss edge_from_proc_v),
  exact f_aux_match f (p_edge_from_mem edge_from_proc_v) color (clique_color (edge.erase v_1) (p_edge_in_ss_p_edge_ss emv_in_p_C_right)),
  cases favs_colors v v_favs with v_color h_v_color, use v_color, intros edge edge_in_proc,
  have edge_card:= p_edge_from_card edge_in_proc, have edge_ss_proc:= p_edge_from_ss_ss edge_in_proc,
  have edge_none: edge.nonempty, apply finset.card_pos.1, rw edge_card, exact nat.zero_lt_succ _,
  have v_min:= p_edge_from_min edge_in_proc edge_none,
  have v_1_not_mem: v_1 ∉ edge,
  {
    intros v_1_edge,
    have v_1_lt_v: v_1 < v := finset.min'_lt_of_mem_erase_min' C C_none (favs_ss_C_right v_favs),
    apply absurd v_1_edge (finset.not_mem_of_lt_min v_1_lt_v _),
    rw ←finset.coe_min' edge_none, rw v_min,
  },
  have edge_ss_favs: edge ⊆ favs,
  {
    intros v v_edge, cases finset.mem_cons.1 (edge_ss_proc v_edge) with v_eq_v_1 v_favs,
    rw v_eq_v_1 at v_edge, contradiction, exact v_favs,
  },
  have edge_in_from_favs: edge ∈ p_edge_from_in_ss clique favs p.succ v (p_nonempty _ _ (nat.zero_lt_succ p)),
  {
    apply finset.mem_filter.2, have e_in_favs : edge ∈ p_edge_in_ss clique favs p.succ,
    {
      apply finset.mem_filter.2, split, apply finset.mem_powerset_len.2,
      refine ⟨finset.subset.trans edge_ss_favs favs_ss, edge_card⟩, exact edge_ss_favs,
    },
    rw dif_pos e_in_favs, exact ⟨e_in_favs, v_min⟩,
  },
  exact h_v_color edge edge_in_from_favs,
end

theorem genramsey (p c k : ℕ) :
∃ n₀ : ℕ, ∀ C : finset β , n₀ ≤ C.card → ∀ f : finset β → fin c, ∃ color : fin c,
∃ clique ⊆ C, (k ≤ clique.card ∧ ∀ edge ∈ p_edge clique p, f edge = color):=
begin
  revert k, haveI : linear_order β := is_well_order.linear_order (well_ordering_rel),
  cases nat.eq_zero_or_eq_succ_pred c with c0 cp,
  {
    intro k, use 0, rw c0, intros C C_card f, have:=f C, apply fin_zero_elim this,
  },
  haveI: inhabited (fin c), rw cp, apply fin.inhabited,
  haveI: nonempty (fin c), apply nonempty_of_inhabited,
  induction p with p hp, intro k, use k,
  intros C card f, set fe:= f ∅,
  use fe, use C, refine ⟨finset.subset.refl C, card, _⟩, intros edge edge_p_c,
  rw finset.card_eq_zero.1 (finset.mem_powerset_len.1 edge_p_c).2,
  intro k, cases genprocession p (c*k) c hp with n₀ hn₀, use n₀, intros C C_card f,
  rcases hn₀ C C_card f with ⟨Y, Y_ss, Y_card, Y_fav⟩,
  have y2finc: fintype.card (fin c) * k ≤ Y.card, rw fintype.card_fin c, linarith,
  choose! fn h_fn using Y_fav,
  cases finset.exists_le_card_fiber_of_mul_le_card_to_type fn y2finc with color hc,
  set clique:= finset.filter (λ (x : β), fn x = color) Y,
  have csuby: clique ⊆ Y:= finset.filter_subset (λ (x : β), fn x = color) Y,
  use [color, clique], refine ⟨finset.subset.trans csuby Y_ss, hc, _⟩, intros edge edge_in_clique,
  rcases p_edge_ss_from_v_in_ss edge_in_clique (finset.subset.trans csuby Y_ss) with ⟨v, vclique, hv⟩, swap, exact nat.zero_lt_succ p,
  rw finset.mem_filter at vclique, rw ←vclique.2, exact h_fn v vclique.1 edge (p_edge_from_ss_edge_from_ss csuby hv),
end


/-
Copyright (c) 2022 Rémy Degenne. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Rémy Degenne
-/

import measure_theory.function.conditional_expectation.basic

/-!

# Conditional expectation of indicator functions

This file proves some results about the conditional expectation of an indicator function and
as a corollary, also proves several results about the behaviour of the conditional expectation on
a restricted measure.

## Main result

* `measure_theory.condexp_indicator`: If `s` is a `m`-measurable set, then the conditional
  expectation of the indicator function of `s` is almost everywhere equal to the indicator
  of `s` of the conditional expectation. Namely, `𝔼[s.indicator f | m] = s.indicator 𝔼[f | m]` a.e.

-/

noncomputable theory
open topological_space measure_theory.Lp filter continuous_linear_map
open_locale nnreal ennreal topology big_operators measure_theory

namespace measure_theory

variables {α 𝕜 E : Type*} {m m0 : measurable_space α}
  [normed_add_comm_group E] [normed_space ℝ E] [complete_space E]
  {μ : measure α} {f : α → E} {s : set α}

lemma condexp_ae_eq_restrict_zero (hs : measurable_set[m] s) (hf : f =ᵐ[μ.restrict s] 0) :
  μ[f | m] =ᵐ[μ.restrict s] 0 :=
begin
  by_cases hm : m ≤ m0,
  swap, { simp_rw condexp_of_not_le hm, },
  by_cases hμm : sigma_finite (μ.trim hm),
  swap, { simp_rw condexp_of_not_sigma_finite hm hμm, },
  haveI : sigma_finite (μ.trim hm) := hμm,
  haveI : sigma_finite ((μ.restrict s).trim hm),
  { rw ← restrict_trim hm _ hs,
    exact restrict.sigma_finite _ s, },
  by_cases hf_int : integrable f μ,
  swap, { rw condexp_undef hf_int, },
  refine ae_eq_of_forall_set_integral_eq_of_sigma_finite' hm _ _ _ _ _,
  { exact λ t ht hμt, integrable_condexp.integrable_on.integrable_on, },
  { exact λ t ht hμt, (integrable_zero _ _ _).integrable_on, },
  { intros t ht hμt,
    rw [measure.restrict_restrict (hm _ ht), set_integral_condexp hm hf_int (ht.inter hs),
      ← measure.restrict_restrict (hm _ ht)],
    refine set_integral_congr_ae (hm _ ht) _,
    filter_upwards [hf] with x hx h using hx, },
  { exact strongly_measurable_condexp.ae_strongly_measurable', },
  { exact strongly_measurable_zero.ae_strongly_measurable', },
end

/-- Auxiliary lemma for `condexp_indicator`. -/
lemma condexp_indicator_aux (hs : measurable_set[m] s) (hf : f =ᵐ[μ.restrict sᶜ] 0) :
  μ[s.indicator f | m] =ᵐ[μ] s.indicator (μ[f | m]) :=
begin
  by_cases hm : m ≤ m0,
  swap, { simp_rw [condexp_of_not_le hm, set.indicator_zero'], },
  have hsf_zero : ∀ g : α → E, g =ᵐ[μ.restrict sᶜ] 0 → s.indicator g =ᵐ[μ] g,
    from λ g, indicator_ae_eq_of_restrict_compl_ae_eq_zero (hm _ hs),
  refine ((hsf_zero (μ[f | m]) (condexp_ae_eq_restrict_zero hs.compl hf)).trans _).symm,
  exact condexp_congr_ae (hsf_zero f hf).symm,
end

/-- The conditional expectation of the indicator of a function over an `m`-measurable set with
respect to the σ-algebra `m` is a.e. equal to the indicator of the conditional expectation. -/
lemma condexp_indicator (hf_int : integrable f μ) (hs : measurable_set[m] s) :
  μ[s.indicator f | m] =ᵐ[μ] s.indicator (μ[f | m]) :=
begin
  by_cases hm : m ≤ m0,
  swap, { simp_rw [condexp_of_not_le hm, set.indicator_zero'], },
  by_cases hμm : sigma_finite (μ.trim hm),
  swap, { simp_rw [condexp_of_not_sigma_finite hm hμm, set.indicator_zero'], },
  haveI : sigma_finite (μ.trim hm) := hμm,
  -- use `have` to perform what should be the first calc step because of an error I don't
  -- understand
  have : s.indicator (μ[f|m]) =ᵐ[μ] s.indicator (μ[s.indicator f + sᶜ.indicator f|m]),
    by rw set.indicator_self_add_compl s f,
  refine (this.trans _).symm,
  calc s.indicator (μ[s.indicator f + sᶜ.indicator f|m])
      =ᵐ[μ] s.indicator (μ[s.indicator f|m] + μ[sᶜ.indicator f|m]) :
    begin
      have : μ[s.indicator f + sᶜ.indicator f|m] =ᵐ[μ] μ[s.indicator f|m] + μ[sᶜ.indicator f|m],
        from condexp_add (hf_int.indicator (hm _ hs)) (hf_int.indicator (hm _ hs.compl)),
      filter_upwards [this] with x hx,
      classical,
      rw [set.indicator_apply, set.indicator_apply, hx],
    end
  ... = s.indicator (μ[s.indicator f|m]) + s.indicator (μ[sᶜ.indicator f|m]) :
    s.indicator_add' _ _
  ... =ᵐ[μ] s.indicator (μ[s.indicator f|m]) + s.indicator (sᶜ.indicator (μ[sᶜ.indicator f|m])) :
    begin
      refine filter.eventually_eq.rfl.add _,
      have : sᶜ.indicator (μ[sᶜ.indicator f|m]) =ᵐ[μ] μ[sᶜ.indicator f|m],
      { refine (condexp_indicator_aux hs.compl _).symm.trans _,
        { exact indicator_ae_eq_restrict_compl (hm _ hs.compl), },
        { rw [set.indicator_indicator, set.inter_self], }, },
      filter_upwards [this] with x hx,
      by_cases hxs : x ∈ s,
      { simp only [hx, hxs, set.indicator_of_mem], },
      { simp only [hxs, set.indicator_of_not_mem, not_false_iff], },
    end
  ... =ᵐ[μ] s.indicator (μ[s.indicator f|m]) :
    by rw [set.indicator_indicator, set.inter_compl_self, set.indicator_empty', add_zero]
  ... =ᵐ[μ] μ[s.indicator f|m] :
    begin
      refine (condexp_indicator_aux hs _).symm.trans _,
      { exact indicator_ae_eq_restrict_compl (hm _ hs), },
      { rw [set.indicator_indicator, set.inter_self], },
    end
end

lemma condexp_restrict_ae_eq_restrict (hm : m ≤ m0) [sigma_finite (μ.trim hm)]
  (hs_m : measurable_set[m] s) (hf_int : integrable f μ) :
  (μ.restrict s)[f | m] =ᵐ[μ.restrict s] μ[f | m] :=
begin
  haveI : sigma_finite ((μ.restrict s).trim hm),
  { rw ← restrict_trim hm _ hs_m, apply_instance, },
  rw ae_eq_restrict_iff_indicator_ae_eq (hm _ hs_m),
  swap, { apply_instance, },
  refine eventually_eq.trans _ (condexp_indicator hf_int hs_m),
  refine ae_eq_condexp_of_forall_set_integral_eq hm (hf_int.indicator (hm _ hs_m)) _ _ _,
  { intros t ht hμt,
    rw [← integrable_indicator_iff (hm _ ht), set.indicator_indicator, set.inter_comm,
      ← set.indicator_indicator],
    suffices h_int_restrict : integrable (t.indicator ((μ.restrict s)[f|m])) (μ.restrict s),
    { rw [integrable_indicator_iff (hm _ hs_m), integrable_on],
      rw [integrable_indicator_iff (hm _ ht), integrable_on] at h_int_restrict ⊢,
      exact h_int_restrict, },
    exact integrable_condexp.indicator (hm _ ht), },
  { intros t ht hμt,
    calc ∫ x in t, s.indicator ((μ.restrict s)[f|m]) x ∂μ
        = ∫ x in t, ((μ.restrict s)[f|m]) x ∂(μ.restrict s) :
      by rw [integral_indicator (hm _ hs_m), measure.restrict_restrict (hm _ hs_m),
        measure.restrict_restrict (hm _ ht), set.inter_comm]
    ... = ∫ x in t, f x ∂(μ.restrict s) : set_integral_condexp hm hf_int.integrable_on ht
    ... = ∫ x in t, s.indicator f x ∂μ :
      by rw [integral_indicator (hm _ hs_m), measure.restrict_restrict (hm _ hs_m),
        measure.restrict_restrict (hm _ ht), set.inter_comm], },
  { exact (strongly_measurable_condexp.indicator hs_m).ae_strongly_measurable', },
end

/-- If the restriction to a `m`-measurable set `s` of a σ-algebra `m` is equal to the restriction
to `s` of another σ-algebra `m₂` (hypothesis `hs`), then `μ[f | m] =ᵐ[μ.restrict s] μ[f | m₂]`. -/
lemma condexp_ae_eq_restrict_of_measurable_space_eq_on {m m₂ m0 : measurable_space α}
  {μ : measure α} (hm : m ≤ m0) (hm₂ : m₂ ≤ m0)
  [sigma_finite (μ.trim hm)] [sigma_finite (μ.trim hm₂)]
  (hs_m : measurable_set[m] s) (hs : ∀ t, measurable_set[m] (s ∩ t) ↔ measurable_set[m₂] (s ∩ t)) :
  μ[f | m] =ᵐ[μ.restrict s] μ[f | m₂] :=
begin
  rw ae_eq_restrict_iff_indicator_ae_eq (hm _ hs_m),
  have hs_m₂ : measurable_set[m₂] s,
  { rwa [← set.inter_univ s, ← hs set.univ, set.inter_univ], },
  by_cases hf_int : integrable f μ,
  swap, { simp_rw condexp_undef hf_int, },
  refine ((condexp_indicator hf_int hs_m).symm.trans _).trans (condexp_indicator hf_int hs_m₂),
  refine ae_eq_of_forall_set_integral_eq_of_sigma_finite' hm₂
    (λ s hs hμs, integrable_condexp.integrable_on)
    (λ s hs hμs, integrable_condexp.integrable_on) _ _
    strongly_measurable_condexp.ae_strongly_measurable',
  swap,
  { have : strongly_measurable[m] (μ[s.indicator f | m]) := strongly_measurable_condexp,
    refine this.ae_strongly_measurable'.ae_strongly_measurable'_of_measurable_space_le_on
      hm hs_m (λ t, (hs t).mp) _,
    exact condexp_ae_eq_restrict_zero hs_m.compl (indicator_ae_eq_restrict_compl (hm _ hs_m)), },
  intros t ht hμt,
  have : ∫ x in t, μ[s.indicator f|m] x ∂μ = ∫ x in s ∩ t, μ[s.indicator f|m] x ∂μ,
  { rw ← integral_add_compl (hm _ hs_m) integrable_condexp.integrable_on,
    suffices : ∫ x in sᶜ, μ[s.indicator f|m] x ∂μ.restrict t = 0,
      by rw [this, add_zero, measure.restrict_restrict (hm _ hs_m)],
    rw measure.restrict_restrict (measurable_set.compl (hm _ hs_m)),
    suffices : μ[s.indicator f|m] =ᵐ[μ.restrict sᶜ] 0,
    { rw [set.inter_comm, ← measure.restrict_restrict (hm₂ _ ht)],
      calc ∫ (x : α) in t, μ[s.indicator f|m] x ∂μ.restrict sᶜ
          = ∫ (x : α) in t, 0 ∂μ.restrict sᶜ : begin
            refine set_integral_congr_ae (hm₂ _ ht) _,
            filter_upwards [this] with x hx h using hx,
          end
      ... = 0 : integral_zero _ _, },
    refine condexp_ae_eq_restrict_zero hs_m.compl _,
    exact indicator_ae_eq_restrict_compl (hm _ hs_m), },
  have hst_m : measurable_set[m] (s ∩ t) := (hs _).mpr (hs_m₂.inter ht),
  simp_rw [this, set_integral_condexp hm₂ (hf_int.indicator (hm _ hs_m)) ht,
    set_integral_condexp hm (hf_int.indicator (hm _ hs_m)) hst_m,
    integral_indicator (hm _ hs_m), measure.restrict_restrict (hm _ hs_m),
    ← set.inter_assoc, set.inter_self],
end

end measure_theory