-- begin header
import limits
import order.filter
import topology.basic
import topology.instances.real

--set_option pp.notation false
--set_option trace.simplify.rewrite true
-- end header

/- Section
Sequence tending to a limit
-/

/-
Here we show that a sequence $(a_n)_n$ tends to a limit $ℓ$ iff the
cofinite filter on ℕ tends to the neighbourgood filter of $ℓ$ along
the function $n ↦ a_n$.
-/

/- Lemma
Let $(a_n)$ be a sequence of reals and let $ℓ$ be a real.
Then $a_n→ℓ$ if and only if `filter.tendsto a (filter.cofinite) (nhds l)`.
-/
--set_option pp.all true
lemma is_limit_iff_tendsto (a : ℕ → ℝ) (l : ℝ) :
(∀ ε, ε > 0 → ∃ N : ℕ, ∀ n, N ≤ n → | a n - l| < ε) ↔
  filter.tendsto a (filter.cofinite) (nhds l) :=
begin
  split,
  { -- a_n → l implies filter.tendsto
    intro Hl,
    intro X,
    intro HX,
    -- What we have to prove is that if that X is a nhd of l
    -- then `X ∈ (filter.map a filter.cofinite).sets`
    -- These next three lines show that WLOG X is B(l,ε)
    rw metric.mem_nhds_iff at HX,
    rcases HX with ⟨ε, Hε, Hlε⟩,
    change X ∈ filter.map a filter.cofinite,
    apply filter.mem_sets_of_superset _ Hlε,
    -- Now let's figure out what this filter.map stuff means
    -- [dsimp and unfold faffing]
    show a ⁻¹' metric.ball l ε ∈ filter.cofinite.sets,
    show a ⁻¹' metric.ball l ε ∈ filter.cofinite,
    -- Let's use the fact that a n → l
    rcases Hl ε Hε with ⟨N, HN⟩,
    let S := {n : ℕ | N ≤ n},
    -- this is the definition of a_n → l
    have HS : S ⊆ a ⁻¹' metric.ball l ε,
    { intros n Hn,
      show a n ∈ _,
      rw metric.mem_ball,
      exact HN n Hn,
    },
    suffices : S ∈ filter.cofinite.sets,
      exact filter.mem_sets_of_superset this HS,
    show set.finite (-S),
    show set.finite {n : ℕ | ¬ (N ≤ n)},
    simp only [not_le],
    exact ⟨set.fintype_lt_nat _⟩ },
  { -- filter.tendsto implies a_n → l
    intro h,
    intros ε hε,
    -- need N such that n ≥ N implies a_n → l,
<<<<<<< HEAD:src/filter2.lean
    have : metric.ball l ε ∈ (nhds l),
      exact metric.mem_nhds_iff.2 ⟨ε, hε, by refl⟩,

=======
    unfold filter.tendsto at HF,
    unfold filter.map at HF,
--    have X := HF _, --(metric.ball l ε),
>>>>>>> af595e6d3ec06d523668e63274e1ad9f6149e10e:src/filter.lean
    sorry },
end


import Mathlib.Data.Real.Basic

lemma sub_sq_nonneg (a b : ℝ) : (a-b)^2 ≥ 0 :=
  by exact sq_nonneg (a-b)

lemma sub_fundamental_ineq (a b : ℝ) : a^2 + b^2 - 2*a*b ≥ 0 :=
  calc
    _ = _ := by ring -- linarith
    _ ≥ _ := sub_sq_nonneg a b

lemma add_ge_add {a b c d : ℝ} (h₁ : a ≥ b) (h₂ : c ≥ d) : a + c ≥ b + d :=
  have h₁' : _ := Iff.mp ge_iff_le h₁
  have h₂' : _ := Iff.mp ge_iff_le h₂
  show _ from add_le_add  h₁' h₂'
  -- have rev : b + d ≤ a + c := add_le_add
  -- show _ from rev

lemma fundamental_ineq (a b : ℝ) : a^2 + b^2 ≥ 2*a*b :=
  have hs : 0 ≤ a^2 + b^2 - 2*a*b := sub_fundamental_ineq a b
  calc
    -- 0 + 2 * a * b ≤ (a^2 + b^2 - 2 * a * b) + 2 * a * b := add_le_add hs (le_refl _)
     a^2 + b^2 = (a^2 + b^2 - 2 * a * b) + 2 * a * b := by ring
             _ ≥ 0 + 2 * a * b := add_ge_add hs (le_refl _) -- linarith
             _ = 2 * a * b := by ring

    -- a^2 + b^2 = a^2 + b^2 - 2* a * b + 2* a *b := by ring
    --         _ = 2* a *b + 0                 := by ring

lemma sqrt_nonneg (a : ℝ) (pa : a ≥ 0) : a^(1/2) ≥ 0 := sorry
-- lemma sqrt_merge (a b : ℝ) : a^(1/2) * b^(1/2) = (a*b)^(1/2) := sorry
-- lemma sqrt_expand (a b : ℝ) : (a*b)^(1/2) = a^(1/2) * b^(1/2) := sorry

lemma bi_sqrt_nonneg (a b : ℝ) (ha : a ≥ 0) (hb : b ≥ 0) : (a*b)^(1/2) ≥ 0 :=
  have hab : _ := mul_nonneg ha hb
  show _ from sqrt_nonneg (a * b) hab

-- example (a b : ℝ) (pa : a ≥ 0) (pb : b ≥ 0) :
--   a + b ≥ 2*(a*b)^(1/2) :=
--   calc
--     a^2 + b^2 - 2* a *b =
--     a + b - 2*(a*b)^(1/2) := sqrt_nonneg a ∧ sqrt_nonneg b

lemma sqrt_sq (a : ℝ) (pa : a ≥ 0) : a = (a^(1/2))^2 := sorry
lemma le_sqrt_sq (a : ℝ) (pa : a ≥ 0) : a ≤ (a^(1/2))^2 := sorry

lemma sqrt_mul (a b : ℝ) : a^(1/2) * b^(1/2) = (a*b)^(1/2) := sorry

lemma mul_same (x : ℝ) (heq : a = b) : x * a = x * b := sorry

theorem fundamental_ineq' (a b : ℝ)
    (pa : a ≥ 0) (pb : b ≥ 0) : a + b ≥ 2 * (a*b)^(1/2) :=
  have ha : _ := sqrt_sq a pa
  have hb : _ := sqrt_sq b pb
  have hal : _ := le_sqrt_sq a pa
  have hbl : _ := le_sqrt_sq b pb
  have hs : _ := And.intro ha hb
  have heq : _ := Iff.mpr (add_eq_add_iff_eq_and_eq hal hbl) hs
  have hmul : _ := sqrt_mul a b
  calc
    a + b = (a^(1/2))^2 + (b^(1/2))^2 := heq
        _ ≥ 2 * a^(1/2) * b^(1/2) := fundamental_ineq _ _
        _ = _ := mul_assoc _ _ _ -- by ring
        _ = _ := mul_same 2 hmul

lemma div_same_ge (a b : ℝ) (h : x > 0) (h' : a ≥ b) : a / x ≥ b / x := sorry

theorem bi_arith_geom_ineq (a b : ℝ)
    (pa : a ≥ 0) (pb : b ≥ 0) : (a + b) / 2 ≥ (a*b)^(1/2) :=
  have hp : 2 > 0 := sorry
  have h' : _ := fundamental_ineq' a b pa pb
  calc
    _ ≥ 2 * (a * b) ^ (1 / 2) / 2 := div_same_ge _ _ hp h'
    -- _ = (a * b) ^ (1 / 2) * 2 / 2 := by ring
    _ = _ := by ring
  -- have hr :  (a + b) / 2 ≥ 2 * (a * b) ^ (1 / 2) / 2 := div_same_ge _ _ hp h'

#eval println! "hello ℓ₁"


import tactic
/-! # Two-sided inverses

We define two-sided inverses, and prove that a function
is a bijection if and only if it has a two-sided inverse.

-/

-- let X and Y be types, and let f be a function.
variables {X  Y : Type} (f : X → Y)

-- two-sided inverse
structure two_sided_inverse (f : X → Y) :=
(g : Y → X)
(hX : ∀ x : X, g (f x) = x)
(hY : ∀ y : Y, f (g y) = y)

open function

example : bijective f ↔ nonempty (two_sided_inverse f) :=
begin
  split,
  { intro hf,
    cases hf with hi hs,
    choose g hg using hs,
    let G : two_sided_inverse f :=
    { g := g,
      hX := begin
        intro x,
        apply hi,
        rw hg,
      end,
      hY := begin
        exact hg,
      end
    },
    use G,
  },
  { rintro ⟨g, hX, hY⟩,
    split,
    { intros a b hab,
      apply_fun g at hab,
      rw hX at hab,
      rw hX at hab,
      assumption
    },
    { intro y,
      use (g(y)),
      apply hY,
    }
  }
end


/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kunhao Zheng, Stanislas Polu, David Renshaw, OpenAI GPT-f
-/
import mathzoo.imports.miniF2F

open_locale nat rat real big_operators topological_space

theorem induction_12dvd4expnp1p20
  (n : ℕ) :
  12 ∣ 4^(n+1) + 20 :=
begin
  have dvd_of_dvd_add_mul_left : ∀ (a b n : ℕ), a ∣ b + a * n → a ∣ b :=
  begin
    intros a b n,
    refine (nat.dvd_add_left _).mp,
    exact (dvd_mul_right a n),
  end,
  induction n with k IH,
  { dec_trivial },
  {
    rw pow_succ,
    -- If we add 60 to RHS, then we can factor the 4 to use IH
    apply dvd_of_dvd_add_mul_left 12 (4 * 4 ^ k.succ + 20) 5,
    exact dvd_mul_of_dvd_right IH 4,
  }
end


inductive InfTree.{u} (α : Type u)
| leaf : α → InfTree α
| node : (Nat → InfTree α) → InfTree α

open InfTree

def szn.{u} {α : Type u} (n : Nat) : InfTree α → InfTree α → Nat
| leaf a, t2     => 1
| node c, leaf b => 0
| node c, node d => szn n (c n) (d n)

universe u

theorem ex1 {α : Type u} (n : Nat) (c : Nat → InfTree α) (d : Nat → InfTree α) : szn n (node c) (node d) = szn n (c n) (d n) :=
rfl

inductive BTree (α : Type u)
| leaf : α → BTree α
| node : (Bool → Bool → BTree α) → BTree α

def BTree.sz {α : Type u} : BTree α → Nat
| leaf a => 1
| node c => sz (c true true) + sz (c true false) + sz (c false true) + sz (c false false) + 1

theorem ex2 {α : Type u} (c : Bool → Bool → BTree α) : (BTree.node c).sz = (c true true).sz + (c true false).sz + (c false true).sz + (c false false).sz + 1 :=
rfl

inductive L (α : Type u)
| nil  : L α
| cons : (Unit → α) → (Unit → L α) → L α

def L.append {α} : L α → L α → L α
| nil, bs       => bs
| cons a as, bs => cons a (fun _ => append (as ()) bs)

theorem L.appendNil {α} : (as : L α) → append as nil = as
| nil       => rfl
| cons a as =>
  show cons a (fun _ => append (as ()) nil) = cons a as from
  have ih : append (as ()) nil = as () := appendNil $ as ()
  have thunkAux : (fun _ => as ()) = as := funext fun x => by
    cases x
    exact rfl
  by rw [ih, thunkAux]


import analysis.inner_product_space.spectrum

variables {𝕜 : Type*} [is_R_or_C 𝕜] [decidable_eq 𝕜]
variables {E : Type*} [inner_product_space 𝕜 E] [finite_dimensional 𝕜 E]
variables {T : E →ₗ[𝕜] E}

local notation `⟪`x`, `y`⟫` := @inner 𝕜 E _ x y

namespace inner_product_space

lemma is_self_adjoint.pos_nonneg_eigenvalues {n : ℕ} (hn : finite_dimensional.finrank 𝕜 E = n)
  (hsa : is_self_adjoint T)
  (hpos : ∀ (x : E), (is_R_or_C.re ⟪T x, x⟫ ≥ 0)) :
  ∀ (i : (fin n)), hsa.eigenvalues hn i ≥ 0 :=
begin
  intro i,
  have : hsa.eigenvalues hn i =
    is_R_or_C.re ⟪ T (hsa.eigenvector_basis hn i), hsa.eigenvector_basis hn i ⟫ :=
  begin
    simp only [inner_smul_left, inner_product_space.is_self_adjoint.apply_eigenvector_basis,
      is_R_or_C.conj_of_real, is_R_or_C.mul_re, is_R_or_C.of_real_re, sub_zero, mul_zero,
      inner_self_nonneg_im, is_R_or_C.of_real_im, inner_self_eq_norm_sq_to_K],
    rw (hsa.eigenvector_basis_orthonormal hn).1,
    norm_num,
  end,
  rw this,
  exact (hpos (hsa.eigenvector_basis hn i)),
end

end inner_product_space


-- Questions by Johan Commelin
-- Solutions by Kenny Lau, with minor additions and tinkering by Kevin Buzzard

import data.equiv.basic

universes u v w

class transportable (f : Type u → Type v) :=
(on_equiv : Π {α β : Type u} (e : equiv α β), equiv (f α) (f β))
(on_refl  : Π (α : Type u), on_equiv (equiv.refl α) = equiv.refl (f α))
(on_trans : Π {α β γ : Type u} (d : equiv α β) (e : equiv β γ), on_equiv (equiv.trans d e) = equiv.trans (on_equiv d) (on_equiv e))

local attribute [simp] transportable.on_refl transportable.on_trans

-- Our goal is an automagic proof of the following (level 20)
theorem group.transportable : transportable group := sorry

-- cool transportable fact
def transportable.trans (f : Type u → Type v) (g : Type v → Type w)
  [transportable f] [transportable g] : transportable (g ∘ f) :=
{ on_equiv := λ α β e, show g (f α) ≃ g (f β), from transportable.on_equiv g (transportable.on_equiv f e),
  on_refl  := λ α, by simp,
  on_trans := λ α β γ e₁ e₂, by simp }

-- Basic instances which we prove by hand

def Const : Type u → Type v := λ α, punit
lemma Const.transportable : (transportable Const) :=
{ on_equiv := λ α β e, equiv.punit_equiv_punit,
  on_refl  := λ α, equiv.ext _ _ $ λ ⟨⟩, rfl,
  on_trans := λ α β γ e1 e2, equiv.ext _ _ $ λ ⟨⟩, rfl }

def Fun : Type u → Type v → Type (max u v) := λ α β, α → β
lemma Fun.transportable (α : Type u) : (transportable (Fun α)) :=
{ on_equiv := λ β γ e, equiv.arrow_congr (equiv.refl α) e,
  on_refl  := λ β, equiv.ext _ _ $ λ f, rfl,
  on_trans := λ β γ δ e1 e2, equiv.ext _ _ $ λ f, funext $ λ x,
    by cases e1; cases e2; refl }

def Prod : Type u → Type v → Type (max u v) := λ α β, α × β
lemma Prod.transportable (α : Type u) : (transportable (Prod α)) :=
{ on_equiv := λ β γ e, equiv.prod_congr (equiv.refl α) e,
  on_refl  := λ β, equiv.ext _ _ $ λ ⟨x, y⟩, by simp,
  on_trans := λ β γ δ e1 e2, equiv.ext _ _ $ λ ⟨x, y⟩, by simp }

def Swap : Type u → Type v → Type (max u v) := λ α β, β × α
lemma Swap.transportable (α : Type u) : (transportable (Swap α)) :=
{ on_equiv := λ β γ e, equiv.prod_congr e (equiv.refl α),
  on_refl  := λ β, equiv.ext _ _ $ λ ⟨x, y⟩, by simp,
  on_trans := λ β γ δ e1 e2, equiv.ext _ _ $ λ ⟨x, y⟩, by simp }

-- And then we can define
def Hom1 (α : Type u) : Type v → Type (max u v) := λ β, α → β
def Hom2 (β : Type v) : Type u → Type (max u v) := λ α, α → β
def Aut : Type u → Type u := λ α, α → α

-- And hopefully automagically derive
lemma Hom1.transportable (α : Type u) : (transportable (Hom1 α)) :=
Fun.transportable α

lemma Hom2.transportable (β : Type v) : (transportable (Hom2 β)) :=
{ on_equiv := λ α γ e, equiv.arrow_congr e (equiv.refl β),
  on_refl  := λ β, equiv.ext _ _ $ λ f, rfl,
  on_trans := λ β γ δ e1 e2, equiv.ext _ _ $ λ f, funext $ λ x,
    by cases e1; cases e2; refl }

lemma Aut.transportable : (transportable Aut) :=
{ on_equiv := λ α β e, equiv.arrow_congr e e,
  on_refl  := λ α, equiv.ext _ _ $ λ f, funext $ λ x, rfl,
  on_trans := λ α β γ e1 e2, equiv.ext _ _ $ λ f, funext $ λ x,
    by cases e1; cases e2; refl }

-- If we have all these in place...
-- A bit of magic might actually be able to derive `group.transportable`
-- After all, a group just is a type plus some functions... and we can now transport functions.

--theorem group.transportable : transportable group := sorry

-- other goals -- ring, topological_ring

-- These we might need to define and prove by hand
-- level 1
def Const' : Type u → Type v := λ α, punit
lemma Const'.transportable : (transportable Const) := {
  on_equiv := λ α β H,⟨λ _,punit.star,λ _,punit.star,λ ⟨⟩,rfl,λ ⟨⟩,rfl⟩,
  on_refl := λ α, equiv.ext _ _ (λ ⟨⟩,rfl),
  on_trans := λ α β γ Hαβ Hβγ,by congr
  }
-- level 2
def Fun' : Type u → Type v → Type (max u v) := λ α β, α → β
lemma Fun'.transportable (α : Type u) : (transportable (Fun α)) := {
    on_equiv := λ β γ Hβγ,⟨
        λ f a,Hβγ (f a),
        λ f a,Hβγ.symm (f a),
        λ f, funext $ λ x, by simp,
        λ g,by funext a;simp
    ⟩,
    on_refl := λ β,by congr,
    on_trans := λ β γ δ Hβγ Hγδ,by congr
}
-- level 3
def Prod' : Type u → Type v → Type (max u v) := λ α β, α × β
lemma Prod'.transportable (α : Type u) : (transportable (Prod α)) := {
on_equiv := λ β γ Hβγ,⟨λ ⟨a,b⟩,⟨a,Hβγ b⟩,λ ⟨a,c⟩,⟨a,Hβγ.symm c⟩,λ f,begin funext,simp,end,sorry⟩,
on_refl := λ α,sorry,
on_trans := λ α β γ Hαβ Hβγ,sorry
}

lemma Swap'.transportable (α : Type u) : (transportable (Swap α)) := sorry

-- And then we can define
def Hom1' (α : Type u) : Type v → Type (max u v) := λ β, α → β
def Hom2' (β : Type v) : Type u → Type (max u v) := λ α, α → β
def Aut' : Type u → Type u := λ α, α → α

-- And hopefully automagically derive
lemma Hom1'.transportable (α : Type u) : (transportable (Hom1 α)) := sorry
lemma Hom2'.transportable (β : Type v) : (transportable (Hom1 β)) := sorry
lemma Aut'.transportable (α : Type u) : (transportable Aut) := sorry

-- If we have all these in place...
-- A bit of magic might actually be able to derive `group.transportable` on line 11.
-- After all, a group just is a type plus some functions... and we can now transport functions.

lemma distrib.transportable : transportable distrib := {
on_equiv := sorry,
on_refl := sorry,
}


/-
Copyright (c) 2018 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Isometries of emetric and metric spaces
Authors: Sébastien Gouëzel
-/
import topology.metric_space.antilipschitz

/-!
# Isometries

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

We define isometries, i.e., maps between emetric spaces that preserve
the edistance (on metric spaces, these are exactly the maps that preserve distances),
and prove their basic properties. We also introduce isometric bijections.

Since a lot of elementary properties don't require `eq_of_dist_eq_zero` we start setting up the
theory for `pseudo_metric_space` and we specialize to `metric_space` when needed.
-/

noncomputable theory

universes u v w
variables {α : Type u} {β : Type v} {γ : Type w}

open function set
open_locale topology ennreal

/-- An isometry (also known as isometric embedding) is a map preserving the edistance
between pseudoemetric spaces, or equivalently the distance between pseudometric space.  -/
def isometry [pseudo_emetric_space α] [pseudo_emetric_space β] (f : α → β) : Prop :=
∀x1 x2 : α, edist (f x1) (f x2) = edist x1 x2

/-- On pseudometric spaces, a map is an isometry if and only if it preserves nonnegative
distances. -/
lemma isometry_iff_nndist_eq [pseudo_metric_space α] [pseudo_metric_space β] {f : α → β} :
  isometry f ↔ (∀x y, nndist (f x) (f y) = nndist x y) :=
by simp only [isometry, edist_nndist, ennreal.coe_eq_coe]

/-- On pseudometric spaces, a map is an isometry if and only if it preserves distances. -/
lemma isometry_iff_dist_eq [pseudo_metric_space α] [pseudo_metric_space β] {f : α → β} :
  isometry f ↔ (∀x y, dist (f x) (f y) = dist x y) :=
by simp only [isometry_iff_nndist_eq, ← coe_nndist, nnreal.coe_eq]

/-- An isometry preserves distances. -/
alias isometry_iff_dist_eq ↔ isometry.dist_eq _

/-- A map that preserves distances is an isometry -/
alias isometry_iff_dist_eq ↔ _ isometry.of_dist_eq

/-- An isometry preserves non-negative distances. -/
alias isometry_iff_nndist_eq ↔ isometry.nndist_eq _

/-- A map that preserves non-negative distances is an isometry. -/
alias isometry_iff_nndist_eq ↔ _ isometry.of_nndist_eq

namespace isometry

section pseudo_emetric_isometry

variables [pseudo_emetric_space α] [pseudo_emetric_space β] [pseudo_emetric_space γ]
variables {f : α → β} {x y z : α}  {s : set α}

/-- An isometry preserves edistances. -/
theorem edist_eq (hf : isometry f) (x y : α) : edist (f x) (f y) = edist x y := hf x y

lemma lipschitz (h : isometry f) : lipschitz_with 1 f :=
lipschitz_with.of_edist_le $ λ x y, (h x y).le

lemma antilipschitz (h : isometry f) : antilipschitz_with 1 f :=
λ x y, by simp only [h x y, ennreal.coe_one, one_mul, le_refl]

/-- Any map on a subsingleton is an isometry -/
@[nontriviality] theorem _root_.isometry_subsingleton [subsingleton α] : isometry f :=
λx y, by rw subsingleton.elim x y; simp

/-- The identity is an isometry -/
lemma _root_.isometry_id : isometry (id : α → α) := λ x y, rfl

lemma prod_map {δ} [pseudo_emetric_space δ] {f : α → β} {g : γ → δ} (hf : isometry f)
  (hg : isometry g) : isometry (prod.map f g) :=
λ x y, by simp only [prod.edist_eq, hf.edist_eq, hg.edist_eq, prod_map]

lemma _root_.isometry_dcomp {ι} [fintype ι] {α β : ι → Type*} [Π i, pseudo_emetric_space (α i)]
  [Π i, pseudo_emetric_space (β i)] (f : Π i, α i → β i) (hf : ∀ i, isometry (f i)) :
  isometry (dcomp f) :=
λ x y, by simp only [edist_pi_def, (hf _).edist_eq]

/-- The composition of isometries is an isometry. -/
theorem comp {g : β → γ} {f : α → β} (hg : isometry g) (hf : isometry f) : isometry (g ∘ f) :=
λ x y, (hg _ _).trans (hf _ _)

/-- An isometry from a metric space is a uniform continuous map -/
protected theorem uniform_continuous (hf : isometry f) : uniform_continuous f :=
hf.lipschitz.uniform_continuous

/-- An isometry from a metric space is a uniform inducing map -/
protected theorem uniform_inducing (hf : isometry f) : uniform_inducing f :=
hf.antilipschitz.uniform_inducing hf.uniform_continuous

lemma tendsto_nhds_iff {ι : Type*} {f : α → β} {g : ι → α} {a : filter ι} {b : α}
  (hf : isometry f) :
  filter.tendsto g a (𝓝 b) ↔ filter.tendsto (f ∘ g) a (𝓝 (f b)) :=
hf.uniform_inducing.inducing.tendsto_nhds_iff

/-- An isometry is continuous. -/
protected lemma continuous (hf : isometry f) : continuous f := hf.lipschitz.continuous

/-- The right inverse of an isometry is an isometry. -/
lemma right_inv {f : α → β} {g : β → α} (h : isometry f) (hg : right_inverse g f) :
  isometry g :=
λ x y, by rw [← h, hg _, hg _]

lemma preimage_emetric_closed_ball (h : isometry f) (x : α) (r : ℝ≥0∞) :
  f ⁻¹' (emetric.closed_ball (f x) r) = emetric.closed_ball x r :=
by { ext y, simp [h.edist_eq] }

lemma preimage_emetric_ball (h : isometry f) (x : α) (r : ℝ≥0∞) :
  f ⁻¹' (emetric.ball (f x) r) = emetric.ball x r :=
by { ext y, simp [h.edist_eq] }

/-- Isometries preserve the diameter in pseudoemetric spaces. -/
lemma ediam_image (hf : isometry f) (s : set α) : emetric.diam (f '' s) = emetric.diam s :=
eq_of_forall_ge_iff $ λ d,
by simp only [emetric.diam_le_iff, ball_image_iff, hf.edist_eq]

lemma maps_to_emetric_ball (hf : isometry f) (x : α) (r : ℝ≥0∞) :
  maps_to f (emetric.ball x r) (emetric.ball (f x) r) :=
(hf.preimage_emetric_ball x r).ge

lemma maps_to_emetric_closed_ball (hf : isometry f) (x : α) (r : ℝ≥0∞) :
  maps_to f (emetric.closed_ball x r) (emetric.closed_ball (f x) r) :=
(hf.preimage_emetric_closed_ball x r).ge

/-- The injection from a subtype is an isometry -/
lemma _root_.isometry_subtype_coe {s : set α} : isometry (coe : s → α) :=
λx y, rfl

lemma comp_continuous_on_iff {γ} [topological_space γ] (hf : isometry f) {g : γ → α} {s : set γ} :
  continuous_on (f ∘ g) s ↔ continuous_on g s :=
hf.uniform_inducing.inducing.continuous_on_iff.symm

lemma comp_continuous_iff {γ} [topological_space γ] (hf : isometry f) {g : γ → α} :
  continuous (f ∘ g) ↔ continuous g :=
hf.uniform_inducing.inducing.continuous_iff.symm

end pseudo_emetric_isometry --section

section emetric_isometry
variables [emetric_space α] [pseudo_emetric_space β] {f : α → β}

/-- An isometry from an emetric space is injective -/
protected lemma injective (h : isometry f) : injective f := h.antilipschitz.injective

/-- An isometry from an emetric space is a uniform embedding -/
protected theorem uniform_embedding (hf : isometry f) : uniform_embedding f :=
hf.antilipschitz.uniform_embedding hf.lipschitz.uniform_continuous

/-- An isometry from an emetric space is an embedding -/
protected theorem embedding (hf : isometry f) : embedding f :=
hf.uniform_embedding.embedding

/-- An isometry from a complete emetric space is a closed embedding -/
theorem closed_embedding [complete_space α] [emetric_space γ]
  {f : α → γ} (hf : isometry f) : closed_embedding f :=
hf.antilipschitz.closed_embedding hf.lipschitz.uniform_continuous

end emetric_isometry --section

section pseudo_metric_isometry

variables [pseudo_metric_space α] [pseudo_metric_space β] {f : α → β}

/-- An isometry preserves the diameter in pseudometric spaces. -/
lemma diam_image (hf : isometry f) (s : set α) : metric.diam (f '' s) = metric.diam s :=
by rw [metric.diam, metric.diam, hf.ediam_image]

lemma diam_range (hf : isometry f) : metric.diam (range f) = metric.diam (univ : set α) :=
by { rw ← image_univ, exact hf.diam_image univ }

lemma preimage_set_of_dist (hf : isometry f) (x : α) (p : ℝ → Prop) :
  f ⁻¹' {y | p (dist y (f x))} = {y | p (dist y x)} :=
by { ext y, simp [hf.dist_eq] }

lemma preimage_closed_ball (hf : isometry f) (x : α) (r : ℝ) :
  f ⁻¹' (metric.closed_ball (f x) r) = metric.closed_ball x r :=
hf.preimage_set_of_dist x (≤ r)

lemma preimage_ball (hf : isometry f) (x : α) (r : ℝ) :
  f ⁻¹' (metric.ball (f x) r) = metric.ball x r :=
hf.preimage_set_of_dist x (< r)

lemma preimage_sphere (hf : isometry f) (x : α) (r : ℝ) :
  f ⁻¹' (metric.sphere (f x) r) = metric.sphere x r :=
hf.preimage_set_of_dist x (= r)

lemma maps_to_ball (hf : isometry f) (x : α) (r : ℝ) :
  maps_to f (metric.ball x r) (metric.ball (f x) r) :=
(hf.preimage_ball x r).ge

lemma maps_to_sphere (hf : isometry f) (x : α) (r : ℝ) :
  maps_to f (metric.sphere x r) (metric.sphere (f x) r) :=
(hf.preimage_sphere x r).ge

lemma maps_to_closed_ball (hf : isometry f) (x : α) (r : ℝ) :
  maps_to f (metric.closed_ball x r) (metric.closed_ball (f x) r) :=
(hf.preimage_closed_ball x r).ge

end pseudo_metric_isometry -- section

end isometry -- namespace

/-- A uniform embedding from a uniform space to a metric space is an isometry with respect to the
induced metric space structure on the source space. -/
lemma uniform_embedding.to_isometry {α β} [uniform_space α] [metric_space β] {f : α → β}
  (h : uniform_embedding f) :
  @isometry α β
    (@pseudo_metric_space.to_pseudo_emetric_space α
      (@metric_space.to_pseudo_metric_space α (h.comap_metric_space f)))
    (by apply_instance) f :=
begin
  apply isometry.of_dist_eq,
  assume x y,
  refl
end

/-- An embedding from a topological space to a metric space is an isometry with respect to the
induced metric space structure on the source space. -/
lemma embedding.to_isometry {α β} [topological_space α] [metric_space β] {f : α → β}
  (h : embedding f) :
  @isometry α β
    (@pseudo_metric_space.to_pseudo_emetric_space α
      (@metric_space.to_pseudo_metric_space α (h.comap_metric_space f)))
    (by apply_instance) f :=
begin
  apply isometry.of_dist_eq,
  assume x y,
  refl
end

/-- `α` and `β` are isometric if there is an isometric bijection between them. -/
@[nolint has_nonempty_instance] -- such a bijection need not exist
structure isometry_equiv (α β : Type*) [pseudo_emetric_space α] [pseudo_emetric_space β]
  extends α ≃ β :=
(isometry_to_fun  : isometry to_fun)

infix ` ≃ᵢ `:25 := isometry_equiv

namespace isometry_equiv

section pseudo_emetric_space
variables [pseudo_emetric_space α] [pseudo_emetric_space β] [pseudo_emetric_space γ]

instance : has_coe_to_fun (α ≃ᵢ β) (λ _, α → β) := ⟨λe, e.to_equiv⟩

lemma coe_eq_to_equiv (h : α ≃ᵢ β) (a : α) : h a = h.to_equiv a := rfl

@[simp] lemma coe_to_equiv (h : α ≃ᵢ β) : ⇑h.to_equiv = h := rfl

protected lemma isometry (h : α ≃ᵢ β) : isometry h := h.isometry_to_fun

protected lemma bijective (h : α ≃ᵢ β) : bijective h := h.to_equiv.bijective
protected lemma injective (h : α ≃ᵢ β) : injective h := h.to_equiv.injective
protected lemma surjective (h : α ≃ᵢ β) : surjective h := h.to_equiv.surjective

protected lemma edist_eq (h : α ≃ᵢ β) (x y : α) : edist (h x) (h y) = edist x y :=
h.isometry.edist_eq x y

protected lemma dist_eq {α β : Type*} [pseudo_metric_space α] [pseudo_metric_space β] (h : α ≃ᵢ β)
  (x y : α) : dist (h x) (h y) = dist x y :=
h.isometry.dist_eq x y

protected lemma nndist_eq {α β : Type*} [pseudo_metric_space α] [pseudo_metric_space β] (h : α ≃ᵢ β)
  (x y : α) : nndist (h x) (h y) = nndist x y :=
h.isometry.nndist_eq x y

protected lemma continuous (h : α ≃ᵢ β) : continuous h := h.isometry.continuous

@[simp] lemma ediam_image (h : α ≃ᵢ β) (s : set α) : emetric.diam (h '' s) = emetric.diam s :=
h.isometry.ediam_image s

lemma to_equiv_inj : ∀ ⦃h₁ h₂ : α ≃ᵢ β⦄, (h₁.to_equiv = h₂.to_equiv) → h₁ = h₂
| ⟨e₁, h₁⟩ ⟨e₂, h₂⟩ H := by { dsimp at H, subst e₁ }

@[ext] lemma ext ⦃h₁ h₂ : α ≃ᵢ β⦄ (H : ∀ x, h₁ x = h₂ x) : h₁ = h₂ :=
to_equiv_inj $ equiv.ext H

/-- Alternative constructor for isometric bijections,
taking as input an isometry, and a right inverse. -/
def mk' {α : Type u} [emetric_space α] (f : α → β) (g : β → α) (hfg : ∀ x, f (g x) = x)
  (hf : isometry f) : α ≃ᵢ β :=
{ to_fun := f,
  inv_fun := g,
  left_inv := λ x, hf.injective $ hfg _,
  right_inv := hfg,
  isometry_to_fun := hf }

/-- The identity isometry of a space. -/
protected def refl (α : Type*) [pseudo_emetric_space α] : α ≃ᵢ α :=
{ isometry_to_fun := isometry_id, .. equiv.refl α }

/-- The composition of two isometric isomorphisms, as an isometric isomorphism. -/
protected def trans (h₁ : α ≃ᵢ β) (h₂ : β ≃ᵢ γ) : α ≃ᵢ γ :=
{ isometry_to_fun  := h₂.isometry_to_fun.comp h₁.isometry_to_fun,
  .. equiv.trans h₁.to_equiv h₂.to_equiv }

@[simp] lemma trans_apply (h₁ : α ≃ᵢ β) (h₂ : β ≃ᵢ γ) (x : α) : h₁.trans h₂ x = h₂ (h₁ x) := rfl

/-- The inverse of an isometric isomorphism, as an isometric isomorphism. -/
protected def symm (h : α ≃ᵢ β) : β ≃ᵢ α :=
{ isometry_to_fun  := h.isometry.right_inv h.right_inv,
  to_equiv := h.to_equiv.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 (h : α ≃ᵢ β) : α → β := h
/-- See Note [custom simps projection] -/
def simps.symm_apply (h : α ≃ᵢ β) : β → α := h.symm

initialize_simps_projections isometry_equiv
  (to_equiv_to_fun → apply, to_equiv_inv_fun → symm_apply)

@[simp] lemma symm_symm (h : α ≃ᵢ β) : h.symm.symm = h := to_equiv_inj h.to_equiv.symm_symm

@[simp] lemma apply_symm_apply (h : α ≃ᵢ β) (y : β) : h (h.symm y) = y :=
h.to_equiv.apply_symm_apply y

@[simp] lemma symm_apply_apply (h : α ≃ᵢ β) (x : α) : h.symm (h x) = x :=
h.to_equiv.symm_apply_apply x

lemma symm_apply_eq (h : α ≃ᵢ β) {x : α} {y : β} :
  h.symm y = x ↔ y = h x :=
h.to_equiv.symm_apply_eq

lemma eq_symm_apply (h : α ≃ᵢ β) {x : α} {y : β} :
  x = h.symm y ↔ h x = y :=
h.to_equiv.eq_symm_apply

lemma symm_comp_self (h : α ≃ᵢ β) : ⇑h.symm ∘ ⇑h = id :=
funext $ assume a, h.to_equiv.left_inv a

lemma self_comp_symm (h : α ≃ᵢ β) : ⇑h ∘ ⇑h.symm = id :=
funext $ assume a, h.to_equiv.right_inv a

@[simp] lemma range_eq_univ (h : α ≃ᵢ β) : range h = univ :=
h.to_equiv.range_eq_univ

lemma image_symm (h : α ≃ᵢ β) : image h.symm = preimage h :=
image_eq_preimage_of_inverse h.symm.to_equiv.left_inv h.symm.to_equiv.right_inv

lemma preimage_symm (h : α ≃ᵢ β) : preimage h.symm = image h :=
(image_eq_preimage_of_inverse h.to_equiv.left_inv h.to_equiv.right_inv).symm

@[simp] lemma symm_trans_apply (h₁ : α ≃ᵢ β) (h₂ : β ≃ᵢ γ) (x : γ) :
  (h₁.trans h₂).symm x = h₁.symm (h₂.symm x) := rfl

lemma ediam_univ (h : α ≃ᵢ β) : emetric.diam (univ : set α) = emetric.diam (univ : set β) :=
by rw [← h.range_eq_univ, h.isometry.ediam_range]

@[simp] lemma ediam_preimage (h : α ≃ᵢ β) (s : set β) : emetric.diam (h ⁻¹' s) = emetric.diam s :=
by rw [← image_symm, ediam_image]

@[simp] lemma preimage_emetric_ball (h : α ≃ᵢ β) (x : β) (r : ℝ≥0∞) :
  h ⁻¹' (emetric.ball x r) = emetric.ball (h.symm x) r :=
by rw [← h.isometry.preimage_emetric_ball (h.symm x) r, h.apply_symm_apply]

@[simp] lemma preimage_emetric_closed_ball (h : α ≃ᵢ β) (x : β) (r : ℝ≥0∞) :
  h ⁻¹' (emetric.closed_ball x r) = emetric.closed_ball (h.symm x) r :=
by rw [← h.isometry.preimage_emetric_closed_ball (h.symm x) r, h.apply_symm_apply]

@[simp] lemma image_emetric_ball (h : α ≃ᵢ β) (x : α) (r : ℝ≥0∞) :
  h '' (emetric.ball x r) = emetric.ball (h x) r :=
by rw [← h.preimage_symm, h.symm.preimage_emetric_ball, symm_symm]

@[simp] lemma image_emetric_closed_ball (h : α ≃ᵢ β) (x : α) (r : ℝ≥0∞) :
  h '' (emetric.closed_ball x r) = emetric.closed_ball (h x) r :=
by rw [← h.preimage_symm, h.symm.preimage_emetric_closed_ball, symm_symm]

/-- The (bundled) homeomorphism associated to an isometric isomorphism. -/
@[simps to_equiv] protected def to_homeomorph (h : α ≃ᵢ β) : α ≃ₜ β :=
{ continuous_to_fun  := h.continuous,
  continuous_inv_fun := h.symm.continuous,
  to_equiv := h.to_equiv }

@[simp] lemma coe_to_homeomorph (h : α ≃ᵢ β) : ⇑(h.to_homeomorph) = h := rfl

@[simp] lemma coe_to_homeomorph_symm (h : α ≃ᵢ β) : ⇑(h.to_homeomorph.symm) = h.symm := rfl

@[simp] lemma comp_continuous_on_iff {γ} [topological_space γ] (h : α ≃ᵢ β)
  {f : γ → α} {s : set γ} :
  continuous_on (h ∘ f) s ↔ continuous_on f s :=
h.to_homeomorph.comp_continuous_on_iff _ _

@[simp] lemma comp_continuous_iff {γ} [topological_space γ] (h : α ≃ᵢ β) {f : γ → α} :
  continuous (h ∘ f) ↔ continuous f :=
h.to_homeomorph.comp_continuous_iff

@[simp] lemma comp_continuous_iff' {γ} [topological_space γ] (h : α ≃ᵢ β) {f : β → γ} :
  continuous (f ∘ h) ↔ continuous f :=
h.to_homeomorph.comp_continuous_iff'

/-- The group of isometries. -/
instance : group (α ≃ᵢ α) :=
  { one := isometry_equiv.refl _,
    mul := λ e₁ e₂, e₂.trans e₁,
    inv := isometry_equiv.symm,
    mul_assoc := λ e₁ e₂ e₃, rfl,
    one_mul := λ e, ext $ λ _, rfl,
    mul_one := λ e, ext $ λ _, rfl,
    mul_left_inv := λ e, ext e.symm_apply_apply }

@[simp] lemma coe_one : ⇑(1 : α ≃ᵢ α) = id := rfl

@[simp] lemma coe_mul (e₁ e₂ : α ≃ᵢ α) : ⇑(e₁ * e₂) = e₁ ∘ e₂ := rfl

lemma mul_apply (e₁ e₂ : α ≃ᵢ α) (x : α) : (e₁ * e₂) x = e₁ (e₂ x) := rfl

@[simp] lemma inv_apply_self (e : α ≃ᵢ α) (x: α) : e⁻¹ (e x) = x := e.symm_apply_apply x

@[simp] lemma apply_inv_self (e : α ≃ᵢ α) (x: α) : e (e⁻¹ x) = x := e.apply_symm_apply x

protected lemma complete_space [complete_space β] (e : α ≃ᵢ β) : complete_space α :=
complete_space_of_is_complete_univ $ is_complete_of_complete_image e.isometry.uniform_inducing $
  by rwa [set.image_univ, isometry_equiv.range_eq_univ, ← complete_space_iff_is_complete_univ]

lemma complete_space_iff (e : α ≃ᵢ β) : complete_space α ↔ complete_space β :=
by { split; introI H, exacts [e.symm.complete_space, e.complete_space] }

end pseudo_emetric_space

section pseudo_metric_space

variables [pseudo_metric_space α] [pseudo_metric_space β] (h : α ≃ᵢ β)

@[simp] lemma diam_image (s : set α) : metric.diam (h '' s) = metric.diam s :=
h.isometry.diam_image s

@[simp] lemma diam_preimage (s : set β) : metric.diam (h ⁻¹' s) = metric.diam s :=
by rw [← image_symm, diam_image]

lemma diam_univ : metric.diam (univ : set α) = metric.diam (univ : set β) :=
congr_arg ennreal.to_real h.ediam_univ

@[simp] lemma preimage_ball (h : α ≃ᵢ β) (x : β) (r : ℝ) :
  h ⁻¹' (metric.ball x r) = metric.ball (h.symm x) r :=
by rw [← h.isometry.preimage_ball (h.symm x) r, h.apply_symm_apply]

@[simp] lemma preimage_sphere (h : α ≃ᵢ β) (x : β) (r : ℝ) :
  h ⁻¹' (metric.sphere x r) = metric.sphere (h.symm x) r :=
by rw [← h.isometry.preimage_sphere (h.symm x) r, h.apply_symm_apply]

@[simp] lemma preimage_closed_ball (h : α ≃ᵢ β) (x : β) (r : ℝ) :
  h ⁻¹' (metric.closed_ball x r) = metric.closed_ball (h.symm x) r :=
by rw [← h.isometry.preimage_closed_ball (h.symm x) r, h.apply_symm_apply]

@[simp] lemma image_ball (h : α ≃ᵢ β) (x : α) (r : ℝ) :
  h '' (metric.ball x r) = metric.ball (h x) r :=
by rw [← h.preimage_symm, h.symm.preimage_ball, symm_symm]

@[simp] lemma image_sphere (h : α ≃ᵢ β) (x : α) (r : ℝ) :
  h '' (metric.sphere x r) = metric.sphere (h x) r :=
by rw [← h.preimage_symm, h.symm.preimage_sphere, symm_symm]

@[simp] lemma image_closed_ball (h : α ≃ᵢ β) (x : α) (r : ℝ) :
  h '' (metric.closed_ball x r) = metric.closed_ball (h x) r :=
by rw [← h.preimage_symm, h.symm.preimage_closed_ball, symm_symm]

end pseudo_metric_space

end isometry_equiv

/-- An isometry induces an isometric isomorphism between the source space and the
range of the isometry. -/
@[simps to_equiv apply { simp_rhs := tt }]
def isometry.isometry_equiv_on_range [emetric_space α] [pseudo_emetric_space β] {f : α → β}
  (h : isometry f) : α ≃ᵢ range f :=
{ isometry_to_fun := λx y, by simpa [subtype.edist_eq] using h x y,
  to_equiv := equiv.of_injective f h.injective }