/-
Copyright (c) 2022 Yakov Pechersky. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yakov Pechersky, Floris van Doorn
-/
import data.pnat.basic

/-!
# Explicit least witnesses to existentials on positive natural numbers

Implemented via calling out to `nat.find`.

-/

namespace pnat

variables {p q : ℕ+ → Prop} [decidable_pred p] [decidable_pred q] (h : ∃ n, p n)

instance decidable_pred_exists_nat :
  decidable_pred (λ n' : ℕ, ∃ (n : ℕ+) (hn : n' = n), p n) := λ n',
decidable_of_iff' (∃ (h : 0 < n'), p ⟨n', h⟩) $ subtype.exists.trans $
  by simp_rw [subtype.coe_mk, @exists_comm (_ < _) (_ = _), exists_prop, exists_eq_left']

include h

/-- The `pnat` version of `nat.find_x` -/
protected def find_x : {n // p n ∧ ∀ m : ℕ+, m < n → ¬p m} :=
begin
  have : ∃ (n' : ℕ) (n : ℕ+) (hn' : n' = n), p n, from exists.elim h (λ n hn, ⟨n, n, rfl, hn⟩),
  have n := nat.find_x this,
  refine ⟨⟨n, _⟩, _, λ m hm pm, _⟩,
  { obtain ⟨n', hn', -⟩ := n.prop.1,
    rw hn',
    exact n'.prop },
  { obtain ⟨n', hn', pn'⟩ := n.prop.1,
    simpa [hn', subtype.coe_eta] using pn' },
  { exact n.prop.2 m hm ⟨m, rfl, pm⟩ }
end

/--
If `p` is a (decidable) predicate on `ℕ+` and `hp : ∃ (n : ℕ+), p n` is a proof that
there exists some positive natural number satisfying `p`, then `pnat.find hp` is the
smallest positive natural number satisfying `p`. Note that `pnat.find` is protected,
meaning that you can't just write `find`, even if the `pnat` namespace is open.

The API for `pnat.find` is:

* `pnat.find_spec` is the proof that `pnat.find hp` satisfies `p`.
* `pnat.find_min` is the proof that if `m < pnat.find hp` then `m` does not satisfy `p`.
* `pnat.find_min'` is the proof that if `m` does satisfy `p` then `pnat.find hp ≤ m`.
-/
protected def find : ℕ+ :=
pnat.find_x h

protected

protected theorem find_min : ∀ {m : ℕ+}, m < pnat.find h → ¬p m :=
(pnat.find_x h).prop.right

protected theorem find_min' {m : ℕ+} (hm : p m) : pnat.find h ≤ m :=
le_of_not_lt (λ l, pnat.find_min h l hm)

variables {n m : ℕ+}

lemma find_eq_iff : pnat.find h = m ↔ p m ∧ ∀ n < m, ¬ p n :=
begin
  split,
  { rintro rfl, exact ⟨pnat.find_spec h, λ _, pnat.find_min h⟩ },
  { rintro ⟨hm, hlt⟩,
    exact le_antisymm (pnat.find_min' h hm) (not_lt.1 $ imp_not_comm.1 (hlt _) $ pnat.find_spec h) }
end

@[simp] lemma find_lt_iff (n : ℕ+) : pnat.find h < n ↔ ∃ m < n, p m :=
⟨λ h2, ⟨pnat.find h, h2, pnat.find_spec h⟩, λ ⟨m, hmn, hm⟩, (pnat.find_min' h hm).trans_lt hmn⟩

@[simp] lemma find_le_iff (n : ℕ+) : pnat.find h ≤ n ↔ ∃ m ≤ n, p m :=
by simp only [exists_prop, ← lt_add_one_iff, find_lt_iff]

@[simp] lemma le_find_iff (n : ℕ+) : n ≤ pnat.find h ↔ ∀ m < n, ¬ p m :=
by simp_rw [← not_lt, find_lt_iff, not_exists]

@[simp] lemma lt_find_iff (n : ℕ+) : n < pnat.find h ↔ ∀ m ≤ n, ¬ p m :=
by simp only [← add_one_le_iff, le_find_iff, add_le_add_iff_right]

@[simp] lemma find_eq_one : pnat.find h = 1 ↔ p 1 :=
by simp [find_eq_iff]

@[simp] lemma one_le_find : 1 < pnat.find h ↔ ¬ p 1 :=
not_iff_not.mp $ by simp

theorem find_mono (h : ∀ n, q n → p n)
  {hp : ∃ n, p n} {hq : ∃ n, q n} :
  pnat.find hp ≤ pnat.find hq :=
pnat.find_min' _ (h _ (pnat.find_spec hq))

lemma find_le {h : ∃ n, p n} (hn : p n) : pnat.find h ≤ n :=
(pnat.find_le_iff _ _).2 ⟨n, le_rfl, hn⟩

lemma find_comp_succ (h : ∃ n, p n) (h₂ : ∃ n, p (n + 1)) (h1 : ¬ p 1) :
  pnat.find h = pnat.find h₂ + 1 :=
begin
  refine (find_eq_iff _).2 ⟨pnat.find_spec h₂, λ n, pnat.rec_on n _ _⟩,
  { simp [h1] },
  intros m IH hm,
  simp only [add_lt_add_iff_right, lt_find_iff] at hm,
  exact hm _ le_rfl
end

end pnat


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Johannes Hölzl, Yury Kudryashov
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.real.nnreal
import Mathlib.data.set.intervals.default
import Mathlib.PostPort

universes u_1 u_3

namespace Mathlib

/-!
# Extended non-negative reals

We define `ennreal := with_no ℝ≥0` to be the type of extended nonnegative real numbers, i.e., the
interval `[0, +∞]`. This type is used as the codomain of a `measure_theory.measure`, and of the
extended distance `edist` in a `emetric_space`. In this file we define some algebraic operations and
a linear order on `ennreal` and prove basic properties of these operations, order, and conversions
to/from `ℝ`, `ℝ≥0`, and `ℕ`.

## Main definitions

* `ennreal`: the extended nonnegative real numbers `[0, ∞]`; defined as `with_top ℝ≥0`; it is
  equipped with the following structures:

  - coercion from `ℝ≥0` defined in the natural way;

  - the natural structure of a complete dense linear order: `↑p ≤ ↑q ↔ p ≤ q` and `∀ a, a ≤ ∞`;

  - `a + b` is defined so that `↑p + ↑q = ↑(p + q)` for `(p q : ℝ≥0)` and `a + ∞ = ∞ + a = ∞`;

  - `a * b` is defined so that `↑p * ↑q = ↑(p * q)` for `(p q : ℝ≥0)`, `0 * ∞ = ∞ * 0 = 0`, and `a *
    ∞ = ∞ * a = ∞` for `a ≠ 0`;

  - `a - b` is defined as the minimal `d` such that `a ≤ d + b`; this way we have
    `↑p - ↑q = ↑(p - q)`, `∞ - ↑p = ∞`, `↑p - ∞ = ∞ - ∞ = 0`; note that there is no negation, only
    subtraction;

  - `a⁻¹` is defined as `Inf {b | 1 ≤ a * b}`. This way we have `(↑p)⁻¹ = ↑(p⁻¹)` for
    `p : ℝ≥0`, `p ≠ 0`, `0⁻¹ = ∞`, and `∞⁻¹ = 0`.

  - `a / b` is defined as `a * b⁻¹`.

  The addition and multiplication defined this way together with `0 = ↑0` and `1 = ↑1` turn
  `ennreal` into a canonically ordered commutative semiring of characteristic zero.

* Coercions to/from other types:

  - coercion `ℝ≥0 → ennreal` is defined as `has_coe`, so one can use `(p : ℝ≥0)` in a context that
    expects `a : ennreal`, and Lean will apply `coe` automatically;

  - `ennreal.to_nnreal` sends `↑p` to `p` and `∞` to `0`;

  - `ennreal.to_real := coe ∘ ennreal.to_nnreal` sends `↑p`, `p : ℝ≥0` to `(↑p : ℝ)` and `∞` to `0`;

  - `ennreal.of_real := coe ∘ nnreal.of_real` sends `x : ℝ` to `↑⟨max x 0, _⟩`

  - `ennreal.ne_top_equiv_nnreal` is an equivalence between `{a : ennreal // a ≠ 0}` and `ℝ≥0`.

## Implementation notes

We define a `can_lift ennreal ℝ≥0` instance, so one of the ways to prove theorems about an `ennreal`
number `a` is to consider the cases `a = ∞` and `a ≠ ∞`, and use the tactic `lift a to ℝ≥0 using ha`
in the second case. This instance is even more useful if one already has `ha : a ≠ ∞` in the
context, or if we have `(f : α → ennreal) (hf : ∀ x, f x ≠ ∞)`.

## Notations

* `ℝ≥0`: type of nonnegative real numbers `[0, ∞)`; defined in `data.real.nnreal`;
* `∞`: a localized notation in `ennreal` for `⊤ : ennreal`.

-/

/-- The extended nonnegative real numbers. This is usually denoted [0, ∞],
  and is relevant as the codomain of a measure. -/
def ennreal :=
  with_top nnreal

namespace ennreal

protected instance inhabited : Inhabited ennreal :=
  { default := 0 }

protected instance has_coe : has_coe nnreal ennreal :=
  has_coe.mk some

protected instance nnreal.can_lift : can_lift ennreal nnreal :=
  can_lift.mk coe (fun (r : ennreal) => r ≠ ⊤) sorry

@[simp] theorem none_eq_top : none = ⊤ :=
  rfl

@[simp] theorem some_eq_coe (a : nnreal) : some a = ↑a :=
  rfl

/-- `to_nnreal x` returns `x` if it is real, otherwise 0. -/
protected def to_nnreal : ennreal → nnreal :=
  sorry

/-- `to_real x` returns `x` if it is real, `0` otherwise. -/
protected def to_real (a : ennreal) : ℝ :=
  ↑(ennreal.to_nnreal a)

/-- `of_real x` returns `x` if it is nonnegative, `0` otherwise. -/
protected def of_real (r : ℝ) : ennreal :=
  ↑(nnreal.of_real r)

@[simp] theorem to_nnreal_coe {r : nnreal} : ennreal.to_nnreal ↑r = r :=
  rfl

@[simp] theorem coe_to_nnreal {a : ennreal} : a ≠ ⊤ → ↑(ennreal.to_nnreal a) = a :=
  fun (ᾰ : a ≠ ⊤) =>
    option.cases_on a (fun (ᾰ : none ≠ ⊤) => idRhs (↑(ennreal.to_nnreal none) = none) (false.elim (ᾰ rfl)))
      (fun (a : nnreal) (ᾰ : some a ≠ ⊤) => idRhs (↑(ennreal.to_nnreal (some a)) = ↑(ennreal.to_nnreal (some a))) rfl) ᾰ

@[simp] theorem of_real_to_real {a : ennreal} (h : a ≠ ⊤) : ennreal.of_real (ennreal.to_real a) = a := sorry

@[simp] theorem to_real_of_real {r : ℝ} (h : 0 ≤ r) : ennreal.to_real (ennreal.of_real r) = r := sorry

theorem to_real_of_real' {r : ℝ} : ennreal.to_real (ennreal.of_real r) = max r 0 :=
  rfl

theorem coe_to_nnreal_le_self {a : ennreal} : ↑(ennreal.to_nnreal a) ≤ a := sorry

theorem coe_nnreal_eq (r : nnreal) : ↑r = ennreal.of_real ↑r := sorry

theorem of_real_eq_coe_nnreal {x : ℝ} (h : 0 ≤ x) : ennreal.of_real x = ↑{ val := x, property := h } := sorry

@[simp] theorem of_real_coe_nnreal {p : nnreal} : ennreal.of_real ↑p = ↑p :=
  Eq.symm (coe_nnreal_eq p)

@[simp] theorem coe_zero : ↑0 = 0 :=
  rfl

@[simp] theorem coe_one : ↑1 = 1 :=
  rfl

@[simp] theorem to_real_nonneg {a : ennreal} : 0 ≤ ennreal.to_real a := sorry

@[simp] theorem top_to_nnreal : ennreal.to_nnreal ⊤ = 0 :=
  rfl

@[simp] theorem top_to_real : ennreal.to_real ⊤ = 0 :=
  rfl

@[simp] theorem one_to_real : ennreal.to_real 1 = 1 :=
  rfl

@[simp] theorem one_to_nnreal : ennreal.to_nnreal 1 = 1 :=
  rfl

@[simp] theorem coe_to_real (r : nnreal) : ennreal.to_real ↑r = ↑r :=
  rfl

@[simp] theorem zero_to_nnreal : ennreal.to_nnreal 0 = 0 :=
  rfl

@[simp] theorem zero_to_real : ennreal.to_real 0 = 0 :=
  rfl

@[simp] theorem of_real_zero : ennreal.of_real 0 = 0 := sorry

@[simp] theorem of_real_one : ennreal.of_real 1 = 1 := sorry

theorem of_real_to_real_le {a : ennreal} : ennreal.of_real (ennreal.to_real a) ≤ a :=
  dite (a = ⊤) (fun (ha : a = ⊤) => Eq.symm ha ▸ le_top) fun (ha : ¬a = ⊤) => le_of_eq (of_real_to_real ha)

theorem forall_ennreal {p : ennreal → Prop} : (∀ (a : ennreal), p a) ↔ (∀ (r : nnreal), p ↑r) ∧ p ⊤ := sorry

theorem forall_ne_top {p : ennreal → Prop} : (∀ (a : ennreal), a ≠ ⊤ → p a) ↔ ∀ (r : nnreal), p ↑r :=
  option.ball_ne_none

theorem exists_ne_top {p : ennreal → Prop} : (∃ (a : ennreal), ∃ (H : a ≠ ⊤), p a) ↔ ∃ (r : nnreal), p ↑r :=
  option.bex_ne_none

theorem to_nnreal_eq_zero_iff (x : ennreal) : ennreal.to_nnreal x = 0 ↔ x = 0 ∨ x = ⊤ := sorry

theorem to_real_eq_zero_iff (x : ennreal) : ennreal.to_real x = 0 ↔ x = 0 ∨ x = ⊤ := sorry

@[simp] theorem coe_ne_top {r : nnreal} : ↑r ≠ ⊤ :=
  with_top.coe_ne_top

@[simp] theorem top_ne_coe {r : nnreal} : ⊤ ≠ ↑r :=
  with_top.top_ne_coe

@[simp] theorem of_real_ne_top {r : ℝ} : ennreal.of_real r ≠ ⊤ := sorry

@[simp] theorem of_real_lt_top {r : ℝ} : ennreal.of_real r < ⊤ :=
  iff.mpr lt_top_iff_ne_top of_real_ne_top

@[simp] theorem top_ne_of_real {r : ℝ} : ⊤ ≠ ennreal.of_real r := sorry

@[simp] theorem zero_ne_top : 0 ≠ ⊤ :=
  coe_ne_top

@[simp] theorem top_ne_zero : ⊤ ≠ 0 :=
  top_ne_coe

@[simp] theorem one_ne_top : 1 ≠ ⊤ :=
  coe_ne_top

@[simp] theorem top_ne_one : ⊤ ≠ 1 :=
  top_ne_coe

@[simp] theorem coe_eq_coe {r : nnreal} {q : nnreal} : ↑r = ↑q ↔ r = q :=
  with_top.coe_eq_coe

@[simp] theorem coe_le_coe {r : nnreal} {q : nnreal} : ↑r ≤ ↑q ↔ r ≤ q :=
  with_top.coe_le_coe

@[simp] theorem coe_lt_coe {r : nnreal} {q : nnreal} : ↑r < ↑q ↔ r < q :=
  with_top.coe_lt_coe

theorem coe_mono : monotone coe :=
  fun (_x _x_1 : nnreal) => iff.mpr coe_le_coe

@[simp] theorem coe_eq_zero {r : nnreal} : ↑r = 0 ↔ r = 0 :=
  coe_eq_coe

@[simp] theorem zero_eq_coe {r : nnreal} : 0 = ↑r ↔ 0 = r :=
  coe_eq_coe

@[simp] theorem coe_eq_one {r : nnreal} : ↑r = 1 ↔ r = 1 :=
  coe_eq_coe

@[simp] theorem one_eq_coe {r : nnreal} : 1 = ↑r ↔ 1 = r :=
  coe_eq_coe

@[simp] theorem coe_nonneg {r : nnreal} : 0 ≤ ↑r ↔ 0 ≤ r :=
  coe_le_coe

@[simp] theorem coe_pos {r : nnreal} : 0 < ↑r ↔ 0 < r :=
  coe_lt_coe

@[simp] theorem coe_add {r : nnreal} {p : nnreal} : ↑(r + p) = ↑r + ↑p :=
  with_top.coe_add

@[simp] theorem coe_mul {r : nnreal} {p : nnreal} : ↑(r * p) = ↑r * ↑p :=
  with_top.coe_mul

@[simp] theorem coe_bit0 {r : nnreal} : ↑(bit0 r) = bit0 ↑r :=
  coe_add

@[simp] theorem coe_bit1 {r : nnreal} : ↑(bit1 r) = bit1 ↑r := sorry

theorem coe_two : ↑(bit0 1) = bit0 1 := sorry

protected theorem zero_lt_one : 0 < 1 :=
  canonically_ordered_semiring.zero_lt_one

@[simp] theorem one_lt_two : 1 < bit0 1 := sorry

@[simp] theorem zero_lt_two : 0 < bit0 1 :=
  lt_trans ennreal.zero_lt_one one_lt_two

theorem two_ne_zero : bit0 1 ≠ 0 :=
  ne.symm (ne_of_lt zero_lt_two)

theorem two_ne_top : bit0 1 ≠ ⊤ :=
  coe_two ▸ coe_ne_top

/-- The set of `ennreal` numbers that are not equal to `∞` is equivalent to `ℝ≥0`. -/
def ne_top_equiv_nnreal : ↥(set_of fun (a : ennreal) => a ≠ ⊤) ≃ nnreal :=
  equiv.mk (fun (x : ↥(set_of fun (a : ennreal) => a ≠ ⊤)) => ennreal.to_nnreal ↑x)
    (fun (x : nnreal) => { val := ↑x, property := coe_ne_top }) sorry sorry

theorem cinfi_ne_top {α : Type u_1} [has_Inf α] (f : ennreal → α) : (infi fun (x : Subtype fun (x : ennreal) => x ≠ ⊤) => f ↑x) = infi fun (x : nnreal) => f ↑x :=
  Eq.symm
    (infi_congr (⇑(equiv.symm ne_top_equiv_nnreal)) (equiv.surjective (equiv.symm ne_top_equiv_nnreal))
      fun (x : nnreal) => rfl)

theorem infi_ne_top {α : Type u_1} [complete_lattice α] (f : ennreal → α) : (infi fun (x : ennreal) => infi fun (H : x ≠ ⊤) => f x) = infi fun (x : nnreal) => f ↑x := sorry

theorem csupr_ne_top {α : Type u_1} [has_Sup α] (f : ennreal → α) : (supr fun (x : Subtype fun (x : ennreal) => x ≠ ⊤) => f ↑x) = supr fun (x : nnreal) => f ↑x :=
  cinfi_ne_top f

theorem supr_ne_top {α : Type u_1} [complete_lattice α] (f : ennreal → α) : (supr fun (x : ennreal) => supr fun (H : x ≠ ⊤) => f x) = supr fun (x : nnreal) => f ↑x :=
  infi_ne_top fun (x : ennreal) => f x

theorem infi_ennreal {α : Type u_1} [complete_lattice α] {f : ennreal → α} : (infi fun (n : ennreal) => f n) = (infi fun (n : nnreal) => f ↑n) ⊓ f ⊤ := sorry

theorem supr_ennreal {α : Type u_1} [complete_lattice α] {f : ennreal → α} : (supr fun (n : ennreal) => f n) = (supr fun (n : nnreal) => f ↑n) ⊔ f ⊤ :=
  infi_ennreal

@[simp] theorem add_top {a : ennreal} : a + ⊤ = ⊤ :=
  with_top.add_top

@[simp] theorem top_add {a : ennreal} : ⊤ + a = ⊤ :=
  with_top.top_add

/-- Coercion `ℝ≥0 → ennreal` as a `ring_hom`. -/
def of_nnreal_hom : nnreal →+* ennreal :=
  ring_hom.mk coe coe_one sorry coe_zero sorry

@[simp] theorem coe_of_nnreal_hom : ⇑of_nnreal_hom = coe :=
  rfl

@[simp] theorem coe_indicator {α : Type u_1} (s : set α) (f : α → nnreal) (a : α) : ↑(set.indicator s f a) = set.indicator s (fun (x : α) => ↑(f x)) a :=
  add_monoid_hom.map_indicator (↑of_nnreal_hom) s f a

@[simp] theorem coe_pow {r : nnreal} (n : ℕ) : ↑(r ^ n) = ↑r ^ n :=
  ring_hom.map_pow of_nnreal_hom r n

@[simp] theorem add_eq_top {a : ennreal} {b : ennreal} : a + b = ⊤ ↔ a = ⊤ ∨ b = ⊤ :=
  with_top.add_eq_top

@[simp] theorem add_lt_top {a : ennreal} {b : ennreal} : a + b < ⊤ ↔ a < ⊤ ∧ b < ⊤ :=
  with_top.add_lt_top

theorem to_nnreal_add {r₁ : ennreal} {r₂ : ennreal} (h₁ : r₁ < ⊤) (h₂ : r₂ < ⊤) : ennreal.to_nnreal (r₁ + r₂) = ennreal.to_nnreal r₁ + ennreal.to_nnreal r₂ := sorry

/- rw has trouble with the generic lt_top_iff_ne_top and bot_lt_iff_ne_bot
(contrary to erw). This is solved with the next lemmas -/

protected theorem lt_top_iff_ne_top {a : ennreal} : a < ⊤ ↔ a ≠ ⊤ :=
  lt_top_iff_ne_top

protected theorem bot_lt_iff_ne_bot {a : ennreal} : 0 < a ↔ a ≠ 0 :=
  bot_lt_iff_ne_bot

theorem not_lt_top {x : ennreal} : ¬x < ⊤ ↔ x = ⊤ :=
  eq.mpr (id (Eq._oldrec (Eq.refl (¬x < ⊤ ↔ x = ⊤)) (propext lt_top_iff_ne_top)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (¬x ≠ ⊤ ↔ x = ⊤)) (propext not_not))) (iff.refl (x = ⊤)))

theorem add_ne_top {a : ennreal} {b : ennreal} : a + b ≠ ⊤ ↔ a ≠ ⊤ ∧ b ≠ ⊤ := sorry

theorem mul_top {a : ennreal} : a * ⊤ = ite (a = 0) 0 ⊤ := sorry

theorem top_mul {a : ennreal} : ⊤ * a = ite (a = 0) 0 ⊤ := sorry

@[simp] theorem top_mul_top : ⊤ * ⊤ = ⊤ :=
  with_top.top_mul_top

theorem top_pow {n : ℕ} (h : 0 < n) : ⊤ ^ n = ⊤ := sorry

theorem mul_eq_top {a : ennreal} {b : ennreal} : a * b = ⊤ ↔ a ≠ 0 ∧ b = ⊤ ∨ a = ⊤ ∧ b ≠ 0 :=
  with_top.mul_eq_top_iff

theorem mul_lt_top {a : ennreal} {b : ennreal} : a < ⊤ → b < ⊤ → a * b < ⊤ :=
  with_top.mul_lt_top

theorem mul_ne_top {a : ennreal} {b : ennreal} : a ≠ ⊤ → b ≠ ⊤ → a * b ≠ ⊤ := sorry

theorem ne_top_of_mul_ne_top_left {a : ennreal} {b : ennreal} (h : a * b ≠ ⊤) (hb : b ≠ 0) : a ≠ ⊤ := sorry

theorem ne_top_of_mul_ne_top_right {a : ennreal} {b : ennreal} (h : a * b ≠ ⊤) (ha : a ≠ 0) : b ≠ ⊤ :=
  ne_top_of_mul_ne_top_left (eq.mpr (id (Eq._oldrec (Eq.refl (b * a ≠ ⊤)) (mul_comm b a))) h) ha

theorem lt_top_of_mul_lt_top_left {a : ennreal} {b : ennreal} (h : a * b < ⊤) (hb : b ≠ 0) : a < ⊤ :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a < ⊤)) (propext ennreal.lt_top_iff_ne_top)))
    (ne_top_of_mul_ne_top_left (eq.mp (Eq._oldrec (Eq.refl (a * b < ⊤)) (propext ennreal.lt_top_iff_ne_top)) h) hb)

theorem lt_top_of_mul_lt_top_right {a : ennreal} {b : ennreal} (h : a * b < ⊤) (ha : a ≠ 0) : b < ⊤ :=
  lt_top_of_mul_lt_top_left (eq.mpr (id (Eq._oldrec (Eq.refl (b * a < ⊤)) (mul_comm b a))) h) ha

theorem mul_lt_top_iff {a : ennreal} {b : ennreal} : a * b < ⊤ ↔ a < ⊤ ∧ b < ⊤ ∨ a = 0 ∨ b = 0 := sorry

@[simp] theorem mul_pos {a : ennreal} {b : ennreal} : 0 < a * b ↔ 0 < a ∧ 0 < b := sorry

theorem pow_eq_top {a : ennreal} (n : ℕ) : a ^ n = ⊤ → a = ⊤ := sorry

theorem pow_ne_top {a : ennreal} (h : a ≠ ⊤) {n : ℕ} : a ^ n ≠ ⊤ :=
  mt (pow_eq_top n) h

theorem pow_lt_top {a : ennreal} : a < ⊤ → ∀ (n : ℕ), a ^ n < ⊤ :=
  eq.mpr (id (imp_congr_eq (propext lt_top_iff_ne_top) (forall_congr_eq fun (n : ℕ) => propext lt_top_iff_ne_top)))
    (eq.mp (Eq.refl (a ≠ ⊤ → ∀ (n : ℕ), a ^ n ≠ ⊤)) pow_ne_top)

@[simp] theorem coe_finset_sum {α : Type u_1} {s : finset α} {f : α → nnreal} : ↑(finset.sum s fun (a : α) => f a) = finset.sum s fun (a : α) => ↑(f a) :=
  ring_hom.map_sum of_nnreal_hom f s

@[simp] theorem coe_finset_prod {α : Type u_1} {s : finset α} {f : α → nnreal} : ↑(finset.prod s fun (a : α) => f a) = finset.prod s fun (a : α) => ↑(f a) :=
  ring_hom.map_prod of_nnreal_hom f s

@[simp] theorem bot_eq_zero : ⊥ = 0 :=
  rfl

@[simp] theorem coe_lt_top {r : nnreal} : ↑r < ⊤ :=
  with_top.coe_lt_top r

@[simp] theorem not_top_le_coe {r : nnreal} : ¬⊤ ≤ ↑r :=
  with_top.not_top_le_coe r

theorem zero_lt_coe_iff {p : nnreal} : 0 < ↑p ↔ 0 < p :=
  coe_lt_coe

@[simp] theorem one_le_coe_iff {r : nnreal} : 1 ≤ ↑r ↔ 1 ≤ r :=
  coe_le_coe

@[simp] theorem coe_le_one_iff {r : nnreal} : ↑r ≤ 1 ↔ r ≤ 1 :=
  coe_le_coe

@[simp] theorem coe_lt_one_iff {p : nnreal} : ↑p < 1 ↔ p < 1 :=
  coe_lt_coe

@[simp] theorem one_lt_coe_iff {p : nnreal} : 1 < ↑p ↔ 1 < p :=
  coe_lt_coe

@[simp] theorem coe_nat (n : ℕ) : ↑↑n = ↑n :=
  with_top.coe_nat n

@[simp] theorem of_real_coe_nat (n : ℕ) : ennreal.of_real ↑n = ↑n := sorry

@[simp] theorem nat_ne_top (n : ℕ) : ↑n ≠ ⊤ :=
  with_top.nat_ne_top n

@[simp] theorem top_ne_nat (n : ℕ) : ⊤ ≠ ↑n :=
  with_top.top_ne_nat n

@[simp] theorem one_lt_top : 1 < ⊤ :=
  coe_lt_top

theorem le_coe_iff {a : ennreal} {r : nnreal} : a ≤ ↑r ↔ ∃ (p : nnreal), a = ↑p ∧ p ≤ r :=
  with_top.le_coe_iff

theorem coe_le_iff {a : ennreal} {r : nnreal} : ↑r ≤ a ↔ ∀ (p : nnreal), a = ↑p → r ≤ p :=
  with_top.coe_le_iff

theorem lt_iff_exists_coe {a : ennreal} {b : ennreal} : a < b ↔ ∃ (p : nnreal), a = ↑p ∧ ↑p < b :=
  with_top.lt_iff_exists_coe

@[simp] theorem coe_finset_sup {α : Type u_1} {s : finset α} {f : α → nnreal} : ↑(finset.sup s f) = finset.sup s fun (x : α) => ↑(f x) :=
  finset.comp_sup_eq_sup_comp_of_is_total coe coe_mono rfl

theorem pow_le_pow {a : ennreal} {n : ℕ} {m : ℕ} (ha : 1 ≤ a) (h : n ≤ m) : a ^ n ≤ a ^ m := sorry

@[simp] theorem max_eq_zero_iff {a : ennreal} {b : ennreal} : max a b = 0 ↔ a = 0 ∧ b = 0 := sorry

@[simp] theorem max_zero_left {a : ennreal} : max 0 a = a :=
  max_eq_right (zero_le a)

@[simp] theorem max_zero_right {a : ennreal} : max a 0 = a :=
  max_eq_left (zero_le a)

-- TODO: why this is not a `rfl`? There is some hidden diamond here.

@[simp] theorem sup_eq_max {a : ennreal} {b : ennreal} : a ⊔ b = max a b :=
  eq_of_forall_ge_iff fun (c : ennreal) => iff.trans sup_le_iff (iff.symm max_le_iff)

protected theorem pow_pos {a : ennreal} : 0 < a → ∀ (n : ℕ), 0 < a ^ n :=
  canonically_ordered_semiring.pow_pos

protected theorem pow_ne_zero {a : ennreal} : a ≠ 0 → ∀ (n : ℕ), a ^ n ≠ 0 :=
  eq.mpr (id (Eq.refl (a ≠ 0 → ∀ (n : ℕ), a ^ n ≠ 0)))
    (eq.mp (imp_congr_eq (propext pos_iff_ne_zero) (forall_congr_eq fun (n : ℕ) => propext pos_iff_ne_zero))
      ennreal.pow_pos)

@[simp] theorem not_lt_zero {a : ennreal} : ¬a < 0 :=
  eq.mpr (id (Eq.trans (propext not_lt) (propext ((fun {α : Type} (a : α) => iff_true_intro (zero_le a)) a)))) trivial

theorem add_lt_add_iff_left {a : ennreal} {b : ennreal} {c : ennreal} : a < ⊤ → (a + c < a + b ↔ c < b) :=
  with_top.add_lt_add_iff_left

theorem add_lt_add_iff_right {a : ennreal} {b : ennreal} {c : ennreal} : a < ⊤ → (c + a < b + a ↔ c < b) :=
  with_top.add_lt_add_iff_right

theorem lt_add_right {a : ennreal} {b : ennreal} (ha : a < ⊤) (hb : 0 < b) : a < a + b :=
  eq.mp (Eq._oldrec (Eq.refl (a + 0 < a + b)) (add_zero a))
    (eq.mp (Eq._oldrec (Eq.refl (0 < b)) (Eq.symm (propext (add_lt_add_iff_left ha)))) hb)

theorem le_of_forall_pos_le_add {a : ennreal} {b : ennreal} : (∀ (ε : nnreal), 0 < ε → b < ⊤ → a ≤ b + ↑ε) → a ≤ b := sorry

theorem lt_iff_exists_rat_btwn {a : ennreal} {b : ennreal} : a < b ↔ ∃ (q : ℚ), 0 ≤ q ∧ a < ↑(nnreal.of_real ↑q) ∧ ↑(nnreal.of_real ↑q) < b := sorry

theorem lt_iff_exists_real_btwn {a : ennreal} {b : ennreal} : a < b ↔ ∃ (r : ℝ), 0 ≤ r ∧ a < ennreal.of_real r ∧ ennreal.of_real r < b := sorry

theorem lt_iff_exists_nnreal_btwn {a : ennreal} {b : ennreal} : a < b ↔ ∃ (r : nnreal), a < ↑r ∧ ↑r < b :=
  with_top.lt_iff_exists_coe_btwn

theorem lt_iff_exists_add_pos_lt {a : ennreal} {b : ennreal} : a < b ↔ ∃ (r : nnreal), 0 < r ∧ a + ↑r < b := sorry

theorem coe_nat_lt_coe {r : nnreal} {n : ℕ} : ↑n < ↑r ↔ ↑n < r :=
  coe_nat n ▸ coe_lt_coe

theorem coe_lt_coe_nat {r : nnreal} {n : ℕ} : ↑r < ↑n ↔ r < ↑n :=
  coe_nat n ▸ coe_lt_coe

theorem coe_nat_lt_coe_nat {m : ℕ} {n : ℕ} : ↑m < ↑n ↔ m < n :=
  coe_nat n ▸ iff.trans coe_nat_lt_coe nat.cast_lt

theorem coe_nat_ne_top {n : ℕ} : ↑n ≠ ⊤ :=
  coe_nat n ▸ coe_ne_top

theorem coe_nat_mono : strict_mono coe :=
  fun (_x _x_1 : ℕ) => iff.mpr coe_nat_lt_coe_nat

theorem coe_nat_le_coe_nat {m : ℕ} {n : ℕ} : ↑m ≤ ↑n ↔ m ≤ n :=
  strict_mono.le_iff_le coe_nat_mono

protected instance char_zero : char_zero ennreal :=
  char_zero.mk (strict_mono.injective coe_nat_mono)

protected theorem exists_nat_gt {r : ennreal} (h : r ≠ ⊤) : ∃ (n : ℕ), r < ↑n := sorry

theorem add_lt_add {a : ennreal} {b : ennreal} {c : ennreal} {d : ennreal} (ac : a < c) (bd : b < d) : a + b < c + d := sorry

theorem coe_min {r : nnreal} {p : nnreal} : ↑(min r p) = min ↑r ↑p :=
  monotone.map_min coe_mono

theorem coe_max {r : nnreal} {p : nnreal} : ↑(max r p) = max ↑r ↑p :=
  monotone.map_max coe_mono

theorem le_of_top_imp_top_of_to_nnreal_le {a : ennreal} {b : ennreal} (h : a = ⊤ → b = ⊤) (h_nnreal : a ≠ ⊤ → b ≠ ⊤ → ennreal.to_nnreal a ≤ ennreal.to_nnreal b) : a ≤ b := sorry

theorem coe_Sup {s : set nnreal} : bdd_above s → ↑(Sup s) = supr fun (a : nnreal) => supr fun (H : a ∈ s) => ↑a :=
  with_top.coe_Sup

theorem coe_Inf {s : set nnreal} : set.nonempty s → ↑(Inf s) = infi fun (a : nnreal) => infi fun (H : a ∈ s) => ↑a :=
  with_top.coe_Inf

@[simp] theorem top_mem_upper_bounds {s : set ennreal} : ⊤ ∈ upper_bounds s :=
  fun (x : ennreal) (hx : x ∈ s) => le_top

theorem coe_mem_upper_bounds {r : nnreal} {s : set nnreal} : ↑r ∈ upper_bounds (coe '' s) ↔ r ∈ upper_bounds s := sorry

theorem mul_le_mul {a : ennreal} {b : ennreal} {c : ennreal} {d : ennreal} : a ≤ b → c ≤ d → a * c ≤ b * d :=
  canonically_ordered_semiring.mul_le_mul

theorem mul_lt_mul {a : ennreal} {b : ennreal} {c : ennreal} {d : ennreal} (ac : a < c) (bd : b < d) : a * b < c * d := sorry

theorem mul_left_mono {a : ennreal} : monotone (Mul.mul a) :=
  fun (b c : ennreal) => mul_le_mul (le_refl a)

theorem mul_right_mono {a : ennreal} : monotone fun (x : ennreal) => x * a :=
  fun (b c : ennreal) (h : b ≤ c) => mul_le_mul h (le_refl a)

theorem max_mul {a : ennreal} {b : ennreal} {c : ennreal} : max a b * c = max (a * c) (b * c) :=
  monotone.map_max mul_right_mono

theorem mul_max {a : ennreal} {b : ennreal} {c : ennreal} : a * max b c = max (a * b) (a * c) :=
  monotone.map_max mul_left_mono

theorem mul_eq_mul_left {a : ennreal} {b : ennreal} {c : ennreal} : a ≠ 0 → a ≠ ⊤ → (a * b = a * c ↔ b = c) := sorry

theorem mul_eq_mul_right {a : ennreal} {b : ennreal} {c : ennreal} : c ≠ 0 → c ≠ ⊤ → (a * c = b * c ↔ a = b) :=
  mul_comm c a ▸ mul_comm c b ▸ mul_eq_mul_left

theorem mul_le_mul_left {a : ennreal} {b : ennreal} {c : ennreal} : a ≠ 0 → a ≠ ⊤ → (a * b ≤ a * c ↔ b ≤ c) := sorry

theorem mul_le_mul_right {a : ennreal} {b : ennreal} {c : ennreal} : c ≠ 0 → c ≠ ⊤ → (a * c ≤ b * c ↔ a ≤ b) :=
  mul_comm c a ▸ mul_comm c b ▸ mul_le_mul_left

theorem mul_lt_mul_left {a : ennreal} {b : ennreal} {c : ennreal} : a ≠ 0 → a ≠ ⊤ → (a * b < a * c ↔ b < c) := sorry

theorem mul_lt_mul_right {a : ennreal} {b : ennreal} {c : ennreal} : c ≠ 0 → c ≠ ⊤ → (a * c < b * c ↔ a < b) :=
  mul_comm c a ▸ mul_comm c b ▸ mul_lt_mul_left

protected instance has_sub : Sub ennreal :=
  { sub := fun (a b : ennreal) => Inf (set_of fun (d : ennreal) => a ≤ d + b) }

theorem coe_sub {r : nnreal} {p : nnreal} : ↑(p - r) = ↑p - ↑r := sorry

@[simp] theorem top_sub_coe {r : nnreal} : ⊤ - ↑r = ⊤ := sorry

@[simp] theorem sub_eq_zero_of_le {a : ennreal} {b : ennreal} (h : a ≤ b) : a - b = 0 :=
  le_antisymm (Inf_le (le_add_left h)) (zero_le (a - b))

@[simp] theorem sub_self {a : ennreal} : a - a = 0 :=
  sub_eq_zero_of_le (le_refl a)

@[simp] theorem zero_sub {a : ennreal} : 0 - a = 0 :=
  le_antisymm (Inf_le (zero_le (0 + a))) (zero_le (0 - a))

@[simp] theorem sub_infty {a : ennreal} : a - ⊤ = 0 := sorry

theorem sub_le_sub {a : ennreal} {b : ennreal} {c : ennreal} {d : ennreal} (h₁ : a ≤ b) (h₂ : d ≤ c) : a - c ≤ b - d :=
  Inf_le_Inf fun (e : ennreal) (h : b ≤ e + d) => le_trans (le_trans h₁ h) (add_le_add (le_refl e) h₂)

@[simp] theorem add_sub_self {a : ennreal} {b : ennreal} : b < ⊤ → a + b - b = a := sorry

@[simp] theorem add_sub_self' {a : ennreal} {b : ennreal} (h : a < ⊤) : a + b - a = b :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a + b - a = b)) (add_comm a b)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (b + a - a = b)) (add_sub_self h))) (Eq.refl b))

theorem add_right_inj {a : ennreal} {b : ennreal} {c : ennreal} (h : a < ⊤) : a + b = a + c ↔ b = c := sorry

theorem add_left_inj {a : ennreal} {b : ennreal} {c : ennreal} (h : a < ⊤) : b + a = c + a ↔ b = c :=
  eq.mpr (id (Eq._oldrec (Eq.refl (b + a = c + a ↔ b = c)) (add_comm b a)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a + b = c + a ↔ b = c)) (add_comm c a)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (a + b = a + c ↔ b = c)) (propext (add_right_inj h)))) (iff.refl (b = c))))

@[simp] theorem sub_add_cancel_of_le {a : ennreal} {b : ennreal} : b ≤ a → a - b + b = a := sorry

@[simp] theorem add_sub_cancel_of_le {a : ennreal} {b : ennreal} (h : b ≤ a) : b + (a - b) = a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (b + (a - b) = a)) (add_comm b (a - b))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a - b + b = a)) (sub_add_cancel_of_le h))) (Eq.refl a))

theorem sub_add_self_eq_max {a : ennreal} {b : ennreal} : a - b + b = max a b := sorry

theorem le_sub_add_self {a : ennreal} {b : ennreal} : a ≤ a - b + b :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a ≤ a - b + b)) sub_add_self_eq_max)) (le_max_left a b)

@[simp] protected theorem sub_le_iff_le_add {a : ennreal} {b : ennreal} {c : ennreal} : a - b ≤ c ↔ a ≤ c + b :=
  { mp := fun (h : a - b ≤ c) => le_trans le_sub_add_self (add_le_add_right h b), mpr := fun (h : a ≤ c + b) => Inf_le h }

protected theorem sub_le_iff_le_add' {a : ennreal} {b : ennreal} {c : ennreal} : a - b ≤ c ↔ a ≤ b + c :=
  add_comm c b ▸ ennreal.sub_le_iff_le_add

theorem sub_eq_of_add_eq {a : ennreal} {b : ennreal} {c : ennreal} : b ≠ ⊤ → a + b = c → c - b = a :=
  fun (hb : b ≠ ⊤) (hc : a + b = c) => hc ▸ add_sub_self (iff.mpr lt_top_iff_ne_top hb)

protected theorem sub_le_of_sub_le {a : ennreal} {b : ennreal} {c : ennreal} (h : a - b ≤ c) : a - c ≤ b :=
  iff.mpr ennreal.sub_le_iff_le_add
    (eq.mpr (id (Eq._oldrec (Eq.refl (a ≤ b + c)) (add_comm b c))) (iff.mp ennreal.sub_le_iff_le_add h))

protected theorem sub_lt_self {a : ennreal} {b : ennreal} : a ≠ ⊤ → a ≠ 0 → 0 < b → a - b < a := sorry

@[simp] theorem sub_eq_zero_iff_le {a : ennreal} {b : ennreal} : a - b = 0 ↔ a ≤ b := sorry

@[simp] theorem zero_lt_sub_iff_lt {a : ennreal} {b : ennreal} : 0 < a - b ↔ b < a := sorry

theorem lt_sub_iff_add_lt {a : ennreal} {b : ennreal} {c : ennreal} : a < b - c ↔ a + c < b := sorry

theorem sub_le_self (a : ennreal) (b : ennreal) : a - b ≤ a :=
  iff.mpr ennreal.sub_le_iff_le_add (le_add_right (le_refl a))

@[simp] theorem sub_zero {a : ennreal} : a - 0 = a := sorry

/-- A version of triangle inequality for difference as a "distance". -/
theorem sub_le_sub_add_sub {a : ennreal} {b : ennreal} {c : ennreal} : a - c ≤ a - b + (b - c) :=
  iff.mpr ennreal.sub_le_iff_le_add
    (trans_rel_left LessEq (le_trans le_sub_add_self (add_le_add_left le_sub_add_self (a - b)))
      (Eq.symm (add_assoc (a - b) (b - c) c)))

theorem sub_sub_cancel {a : ennreal} {b : ennreal} (h : a < ⊤) (h2 : b ≤ a) : a - (a - b) = b := sorry

theorem sub_right_inj {a : ennreal} {b : ennreal} {c : ennreal} (ha : a < ⊤) (hb : b ≤ a) (hc : c ≤ a) : a - b = a - c ↔ b = c := sorry

theorem sub_mul {a : ennreal} {b : ennreal} {c : ennreal} (h : 0 < b → b < a → c ≠ ⊤) : (a - b) * c = a * c - b * c := sorry

theorem mul_sub {a : ennreal} {b : ennreal} {c : ennreal} (h : 0 < c → c < b → a ≠ ⊤) : a * (b - c) = a * b - a * c := sorry

theorem sub_mul_ge {a : ennreal} {b : ennreal} {c : ennreal} : a * c - b * c ≤ (a - b) * c := sorry

/-- A product of finite numbers is still finite -/
theorem prod_lt_top {α : Type u_1} {s : finset α} {f : α → ennreal} (h : ∀ (a : α), a ∈ s → f a < ⊤) : (finset.prod s fun (a : α) => f a) < ⊤ :=
  with_top.prod_lt_top h

/-- A sum of finite numbers is still finite -/
theorem sum_lt_top {α : Type u_1} {s : finset α} {f : α → ennreal} : (∀ (a : α), a ∈ s → f a < ⊤) → (finset.sum s fun (a : α) => f a) < ⊤ :=
  with_top.sum_lt_top

/-- A sum of finite numbers is still finite -/
theorem sum_lt_top_iff {α : Type u_1} {s : finset α} {f : α → ennreal} : (finset.sum s fun (a : α) => f a) < ⊤ ↔ ∀ (a : α), a ∈ s → f a < ⊤ :=
  with_top.sum_lt_top_iff

/-- A sum of numbers is infinite iff one of them is infinite -/
theorem sum_eq_top_iff {α : Type u_1} {s : finset α} {f : α → ennreal} : (finset.sum s fun (x : α) => f x) = ⊤ ↔ ∃ (a : α), ∃ (H : a ∈ s), f a = ⊤ :=
  with_top.sum_eq_top_iff

/-- seeing `ennreal` as `ℝ≥0` does not change their sum, unless one of the `ennreal` is
infinity -/
theorem to_nnreal_sum {α : Type u_1} {s : finset α} {f : α → ennreal} (hf : ∀ (a : α), a ∈ s → f a < ⊤) : ennreal.to_nnreal (finset.sum s fun (a : α) => f a) = finset.sum s fun (a : α) => ennreal.to_nnreal (f a) := sorry

/-- seeing `ennreal` as `real` does not change their sum, unless one of the `ennreal` is infinity -/
theorem to_real_sum {α : Type u_1} {s : finset α} {f : α → ennreal} (hf : ∀ (a : α), a ∈ s → f a < ⊤) : ennreal.to_real (finset.sum s fun (a : α) => f a) = finset.sum s fun (a : α) => ennreal.to_real (f a) := sorry

theorem of_real_sum_of_nonneg {α : Type u_1} {s : finset α} {f : α → ℝ} (hf : ∀ (i : α), i ∈ s → 0 ≤ f i) : ennreal.of_real (finset.sum s fun (i : α) => f i) = finset.sum s fun (i : α) => ennreal.of_real (f i) := sorry

protected theorem Ico_eq_Iio {y : ennreal} : set.Ico 0 y = set.Iio y := sorry

theorem mem_Iio_self_add {x : ennreal} {ε : ennreal} : x ≠ ⊤ → 0 < ε → x ∈ set.Iio (x + ε) :=
  fun (xt : x ≠ ⊤) (ε0 : 0 < ε) =>
    lt_add_right (eq.mpr (id (Eq._oldrec (Eq.refl (x < ⊤)) (propext lt_top_iff_ne_top))) xt) ε0

theorem not_mem_Ioo_self_sub {x : ennreal} {y : ennreal} {ε : ennreal} : x = 0 → ¬x ∈ set.Ioo (x - ε) y := sorry

theorem mem_Ioo_self_sub_add {x : ennreal} {ε₁ : ennreal} {ε₂ : ennreal} : x ≠ ⊤ → x ≠ 0 → 0 < ε₁ → 0 < ε₂ → x ∈ set.Ioo (x - ε₁) (x + ε₂) :=
  fun (xt : x ≠ ⊤) (x0 : x ≠ 0) (ε0 : 0 < ε₁) (ε0' : 0 < ε₂) =>
    { left := ennreal.sub_lt_self xt x0 ε0,
      right := lt_add_right (eq.mpr (id (Eq._oldrec (Eq.refl (x < ⊤)) (propext lt_top_iff_ne_top))) xt) ε0' }

@[simp] theorem bit0_inj {a : ennreal} {b : ennreal} : bit0 a = bit0 b ↔ a = b := sorry

@[simp] theorem bit0_eq_zero_iff {a : ennreal} : bit0 a = 0 ↔ a = 0 := sorry

@[simp] theorem bit0_eq_top_iff {a : ennreal} : bit0 a = ⊤ ↔ a = ⊤ :=
  eq.mpr (id (Eq._oldrec (Eq.refl (bit0 a = ⊤ ↔ a = ⊤)) (bit0.equations._eqn_1 a)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a + a = ⊤ ↔ a = ⊤)) (propext add_eq_top)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (a = ⊤ ∨ a = ⊤ ↔ a = ⊤)) (propext (or_self (a = ⊤))))) (iff.refl (a = ⊤))))

@[simp] theorem bit1_inj {a : ennreal} {b : ennreal} : bit1 a = bit1 b ↔ a = b := sorry

@[simp] theorem bit1_ne_zero {a : ennreal} : bit1 a ≠ 0 := sorry

@[simp] theorem bit1_eq_one_iff {a : ennreal} : bit1 a = 1 ↔ a = 0 := sorry

@[simp] theorem bit1_eq_top_iff {a : ennreal} : bit1 a = ⊤ ↔ a = ⊤ := sorry

protected instance has_inv : has_inv ennreal :=
  has_inv.mk fun (a : ennreal) => Inf (set_of fun (b : ennreal) => 1 ≤ a * b)

protected instance div_inv_monoid : div_inv_monoid ennreal :=
  div_inv_monoid.mk monoid.mul sorry monoid.one sorry sorry has_inv.inv fun (a b : ennreal) => monoid.mul a (b⁻¹)

@[simp] theorem inv_zero : 0⁻¹ = ⊤ := sorry

@[simp] theorem inv_top : ⊤⁻¹ = 0 := sorry

@[simp] theorem coe_inv {r : nnreal} (hr : r ≠ 0) : ↑(r⁻¹) = (↑r⁻¹) := sorry

theorem coe_inv_le {r : nnreal} : ↑(r⁻¹) ≤ (↑r⁻¹) := sorry

theorem coe_inv_two : ↑(bit0 1⁻¹) = (bit0 1⁻¹) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (↑(bit0 1⁻¹) = (bit0 1⁻¹))) (coe_inv (ne_of_gt zero_lt_two))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (↑(bit0 1)⁻¹ = (bit0 1⁻¹))) coe_two)) (Eq.refl (bit0 1⁻¹)))

@[simp] theorem coe_div {r : nnreal} {p : nnreal} (hr : r ≠ 0) : ↑(p / r) = ↑p / ↑r := sorry

@[simp] theorem inv_one : 1⁻¹ = 1 := sorry

@[simp] theorem div_one {a : ennreal} : a / 1 = a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a / 1 = a)) (div_eq_mul_inv a 1)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a * (1⁻¹) = a)) inv_one))
      (eq.mpr (id (Eq._oldrec (Eq.refl (a * 1 = a)) (mul_one a))) (Eq.refl a)))

protected theorem inv_pow {a : ennreal} {n : ℕ} : a ^ n⁻¹ = a⁻¹ ^ n := sorry

@[simp] theorem inv_inv {a : ennreal} : a⁻¹⁻¹ = a := sorry

theorem inv_involutive : function.involutive fun (a : ennreal) => a⁻¹ :=
  fun (a : ennreal) => inv_inv

theorem inv_bijective : function.bijective fun (a : ennreal) => a⁻¹ :=
  function.involutive.bijective inv_involutive

@[simp] theorem inv_eq_inv {a : ennreal} {b : ennreal} : a⁻¹ = (b⁻¹) ↔ a = b :=
  function.injective.eq_iff (and.left inv_bijective)

@[simp] theorem inv_eq_top {a : ennreal} : a⁻¹ = ⊤ ↔ a = 0 :=
  inv_zero ▸ inv_eq_inv

theorem inv_ne_top {a : ennreal} : a⁻¹ ≠ ⊤ ↔ a ≠ 0 := sorry

@[simp] theorem inv_lt_top {x : ennreal} : x⁻¹ < ⊤ ↔ 0 < x := sorry

theorem div_lt_top {x : ennreal} {y : ennreal} (h1 : x < ⊤) (h2 : 0 < y) : x / y < ⊤ :=
  mul_lt_top h1 (iff.mpr inv_lt_top h2)

@[simp] theorem inv_eq_zero {a : ennreal} : a⁻¹ = 0 ↔ a = ⊤ :=
  inv_top ▸ inv_eq_inv

theorem inv_ne_zero {a : ennreal} : a⁻¹ ≠ 0 ↔ a ≠ ⊤ := sorry

@[simp] theorem inv_pos {a : ennreal} : 0 < (a⁻¹) ↔ a ≠ ⊤ :=
  iff.trans pos_iff_ne_zero inv_ne_zero

@[simp] theorem inv_lt_inv {a : ennreal} {b : ennreal} : a⁻¹ < (b⁻¹) ↔ b < a := sorry

theorem inv_lt_iff_inv_lt {a : ennreal} {b : ennreal} : a⁻¹ < b ↔ b⁻¹ < a := sorry

theorem lt_inv_iff_lt_inv {a : ennreal} {b : ennreal} : a < (b⁻¹) ↔ b < (a⁻¹) := sorry

@[simp] theorem inv_le_inv {a : ennreal} {b : ennreal} : a⁻¹ ≤ (b⁻¹) ↔ b ≤ a := sorry

theorem inv_le_iff_inv_le {a : ennreal} {b : ennreal} : a⁻¹ ≤ b ↔ b⁻¹ ≤ a := sorry

theorem le_inv_iff_le_inv {a : ennreal} {b : ennreal} : a ≤ (b⁻¹) ↔ b ≤ (a⁻¹) := sorry

@[simp] theorem inv_lt_one {a : ennreal} : a⁻¹ < 1 ↔ 1 < a :=
  iff.trans inv_lt_iff_inv_lt (eq.mpr (id (Eq._oldrec (Eq.refl (1⁻¹ < a ↔ 1 < a)) inv_one)) (iff.refl (1 < a)))

@[simp] theorem div_top {a : ennreal} : a / ⊤ = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a / ⊤ = 0)) (div_eq_mul_inv a ⊤)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a * (⊤⁻¹) = 0)) inv_top))
      (eq.mpr (id (Eq._oldrec (Eq.refl (a * 0 = 0)) (mul_zero a))) (Eq.refl 0)))

@[simp] theorem top_div_coe {p : nnreal} : ⊤ / ↑p = ⊤ := sorry

theorem top_div_of_ne_top {a : ennreal} (h : a ≠ ⊤) : ⊤ / a = ⊤ := sorry

theorem top_div_of_lt_top {a : ennreal} (h : a < ⊤) : ⊤ / a = ⊤ :=
  top_div_of_ne_top (has_lt.lt.ne h)

theorem top_div {a : ennreal} : ⊤ / a = ite (a = ⊤) 0 ⊤ := sorry

@[simp] theorem zero_div {a : ennreal} : 0 / a = 0 :=
  zero_mul (a⁻¹)

theorem div_eq_top {a : ennreal} {b : ennreal} : a / b = ⊤ ↔ a ≠ 0 ∧ b = 0 ∨ a = ⊤ ∧ b ≠ ⊤ := sorry

theorem le_div_iff_mul_le {a : ennreal} {b : ennreal} {c : ennreal} (h0 : b ≠ 0 ∨ c ≠ 0) (ht : b ≠ ⊤ ∨ c ≠ ⊤) : a ≤ c / b ↔ a * b ≤ c := sorry

theorem div_le_iff_le_mul {a : ennreal} {b : ennreal} {c : ennreal} (hb0 : b ≠ 0 ∨ c ≠ ⊤) (hbt : b ≠ ⊤ ∨ c ≠ 0) : a / b ≤ c ↔ a ≤ c * b := sorry

theorem div_le_of_le_mul {a : ennreal} {b : ennreal} {c : ennreal} (h : a ≤ b * c) : a / c ≤ b := sorry

protected theorem div_lt_iff {a : ennreal} {b : ennreal} {c : ennreal} (h0 : b ≠ 0 ∨ c ≠ 0) (ht : b ≠ ⊤ ∨ c ≠ ⊤) : c / b < a ↔ c < a * b :=
  lt_iff_lt_of_le_iff_le (le_div_iff_mul_le h0 ht)

theorem mul_lt_of_lt_div {a : ennreal} {b : ennreal} {c : ennreal} (h : a < b / c) : a * c < b := sorry

theorem inv_le_iff_le_mul {a : ennreal} {b : ennreal} : (b = ⊤ → a ≠ 0) → (a = ⊤ → b ≠ 0) → (a⁻¹ ≤ b ↔ 1 ≤ a * b) := sorry

@[simp] theorem le_inv_iff_mul_le {a : ennreal} {b : ennreal} : a ≤ (b⁻¹) ↔ a * b ≤ 1 := sorry

theorem mul_inv_cancel {a : ennreal} (h0 : a ≠ 0) (ht : a ≠ ⊤) : a * (a⁻¹) = 1 := sorry

theorem inv_mul_cancel {a : ennreal} (h0 : a ≠ 0) (ht : a ≠ ⊤) : a⁻¹ * a = 1 :=
  mul_comm a (a⁻¹) ▸ mul_inv_cancel h0 ht

theorem mul_le_iff_le_inv {a : ennreal} {b : ennreal} {r : ennreal} (hr₀ : r ≠ 0) (hr₁ : r ≠ ⊤) : r * a ≤ b ↔ a ≤ r⁻¹ * b := sorry

theorem le_of_forall_nnreal_lt {x : ennreal} {y : ennreal} (h : ∀ (r : nnreal), ↑r < x → ↑r ≤ y) : x ≤ y := sorry

theorem eq_top_of_forall_nnreal_le {x : ennreal} (h : ∀ (r : nnreal), ↑r ≤ x) : x = ⊤ :=
  top_unique (le_of_forall_nnreal_lt fun (r : nnreal) (hr : ↑r < ⊤) => h r)

theorem div_add_div_same {a : ennreal} {b : ennreal} {c : ennreal} : a / c + b / c = (a + b) / c :=
  Eq.symm (right_distrib a b (c⁻¹))

theorem div_self {a : ennreal} (h0 : a ≠ 0) (hI : a ≠ ⊤) : a / a = 1 :=
  mul_inv_cancel h0 hI

theorem mul_div_cancel {a : ennreal} {b : ennreal} (h0 : a ≠ 0) (hI : a ≠ ⊤) : b / a * a = b := sorry

theorem mul_div_cancel' {a : ennreal} {b : ennreal} (h0 : a ≠ 0) (hI : a ≠ ⊤) : a * (b / a) = b :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a * (b / a) = b)) (mul_comm a (b / a))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (b / a * a = b)) (mul_div_cancel h0 hI))) (Eq.refl b))

theorem mul_div_le {a : ennreal} {b : ennreal} : a * (b / a) ≤ b := sorry

theorem inv_two_add_inv_two : bit0 1⁻¹ + (bit0 1⁻¹) = 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (bit0 1⁻¹ + (bit0 1⁻¹) = 1)) (Eq.symm (two_mul (bit0 1⁻¹)))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (bit0 1 * (bit0 1⁻¹) = 1)) (Eq.symm (div_eq_mul_inv (bit0 1) (bit0 1)))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (bit0 1 / bit0 1 = 1)) (div_self two_ne_zero two_ne_top))) (Eq.refl 1)))

theorem add_halves (a : ennreal) : a / bit0 1 + a / bit0 1 = a := sorry

@[simp] theorem div_zero_iff {a : ennreal} {b : ennreal} : a / b = 0 ↔ a = 0 ∨ b = ⊤ := sorry

@[simp] theorem div_pos_iff {a : ennreal} {b : ennreal} : 0 < a / b ↔ a ≠ 0 ∧ b ≠ ⊤ := sorry

theorem half_pos {a : ennreal} (h : 0 < a) : 0 < a / bit0 1 := sorry

theorem one_half_lt_one : bit0 1⁻¹ < 1 :=
  iff.mpr inv_lt_one one_lt_two

theorem half_lt_self {a : ennreal} (hz : a ≠ 0) (ht : a ≠ ⊤) : a / bit0 1 < a := sorry

theorem sub_half {a : ennreal} (h : a ≠ ⊤) : a - a / bit0 1 = a / bit0 1 := sorry

theorem one_sub_inv_two : 1 - (bit0 1⁻¹) = (bit0 1⁻¹) := sorry

theorem exists_inv_nat_lt {a : ennreal} (h : a ≠ 0) : ∃ (n : ℕ), ↑n⁻¹ < a := sorry

theorem exists_nat_pos_mul_gt {a : ennreal} {b : ennreal} (ha : a ≠ 0) (hb : b ≠ ⊤) : ∃ (n : ℕ), ∃ (H : n > 0), b < ↑n * a := sorry

theorem exists_nat_mul_gt {a : ennreal} {b : ennreal} (ha : a ≠ 0) (hb : b ≠ ⊤) : ∃ (n : ℕ), b < ↑n * a :=
  Exists.imp (fun (n : ℕ) => Exists.snd) (exists_nat_pos_mul_gt ha hb)

theorem exists_nat_pos_inv_mul_lt {a : ennreal} {b : ennreal} (ha : a ≠ ⊤) (hb : b ≠ 0) : ∃ (n : ℕ), ∃ (H : n > 0), ↑n⁻¹ * a < b := sorry

theorem exists_nnreal_pos_mul_lt {a : ennreal} {b : ennreal} (ha : a ≠ ⊤) (hb : b ≠ 0) : ∃ (n : nnreal), ∃ (H : n > 0), ↑n * a < b := sorry

theorem to_real_add {a : ennreal} {b : ennreal} (ha : a ≠ ⊤) (hb : b ≠ ⊤) : ennreal.to_real (a + b) = ennreal.to_real a + ennreal.to_real b := sorry

theorem to_real_add_le {a : ennreal} {b : ennreal} : ennreal.to_real (a + b) ≤ ennreal.to_real a + ennreal.to_real b := sorry

theorem of_real_add {p : ℝ} {q : ℝ} (hp : 0 ≤ p) (hq : 0 ≤ q) : ennreal.of_real (p + q) = ennreal.of_real p + ennreal.of_real q := sorry

theorem of_real_add_le {p : ℝ} {q : ℝ} : ennreal.of_real (p + q) ≤ ennreal.of_real p + ennreal.of_real q :=
  iff.mpr coe_le_coe nnreal.of_real_add_le

@[simp] theorem to_real_le_to_real {a : ennreal} {b : ennreal} (ha : a ≠ ⊤) (hb : b ≠ ⊤) : ennreal.to_real a ≤ ennreal.to_real b ↔ a ≤ b := sorry

@[simp] theorem to_real_lt_to_real {a : ennreal} {b : ennreal} (ha : a ≠ ⊤) (hb : b ≠ ⊤) : ennreal.to_real a < ennreal.to_real b ↔ a < b := sorry

theorem to_real_max {a : ennreal} {b : ennreal} (hr : a ≠ ⊤) (hp : b ≠ ⊤) : ennreal.to_real (max a b) = max (ennreal.to_real a) (ennreal.to_real b) := sorry

theorem to_nnreal_pos_iff {a : ennreal} : 0 < ennreal.to_nnreal a ↔ 0 < a ∧ a ≠ ⊤ := sorry

theorem to_real_pos_iff {a : ennreal} : 0 < ennreal.to_real a ↔ 0 < a ∧ a ≠ ⊤ :=
  iff.trans nnreal.coe_pos to_nnreal_pos_iff

theorem of_real_le_of_real {p : ℝ} {q : ℝ} (h : p ≤ q) : ennreal.of_real p ≤ ennreal.of_real q := sorry

theorem of_real_le_of_le_to_real {a : ℝ} {b : ennreal} (h : a ≤ ennreal.to_real b) : ennreal.of_real a ≤ b :=
  has_le.le.trans (of_real_le_of_real h) of_real_to_real_le

@[simp] theorem of_real_le_of_real_iff {p : ℝ} {q : ℝ} (h : 0 ≤ q) : ennreal.of_real p ≤ ennreal.of_real q ↔ p ≤ q := sorry

@[simp] theorem of_real_lt_of_real_iff {p : ℝ} {q : ℝ} (h : 0 < q) : ennreal.of_real p < ennreal.of_real q ↔ p < q := sorry

theorem of_real_lt_of_real_iff_of_nonneg {p : ℝ} {q : ℝ} (hp : 0 ≤ p) : ennreal.of_real p < ennreal.of_real q ↔ p < q := sorry

@[simp] theorem of_real_pos {p : ℝ} : 0 < ennreal.of_real p ↔ 0 < p := sorry

@[simp] theorem of_real_eq_zero {p : ℝ} : ennreal.of_real p = 0 ↔ p ≤ 0 := sorry

theorem of_real_le_iff_le_to_real {a : ℝ} {b : ennreal} (hb : b ≠ ⊤) : ennreal.of_real a ≤ b ↔ a ≤ ennreal.to_real b := sorry

theorem of_real_lt_iff_lt_to_real {a : ℝ} {b : ennreal} (ha : 0 ≤ a) (hb : b ≠ ⊤) : ennreal.of_real a < b ↔ a < ennreal.to_real b := sorry

theorem le_of_real_iff_to_real_le {a : ennreal} {b : ℝ} (ha : a ≠ ⊤) (hb : 0 ≤ b) : a ≤ ennreal.of_real b ↔ ennreal.to_real a ≤ b := sorry

theorem to_real_le_of_le_of_real {a : ennreal} {b : ℝ} (hb : 0 ≤ b) (h : a ≤ ennreal.of_real b) : ennreal.to_real a ≤ b :=
  (fun (ha : a ≠ ⊤) => iff.mp (le_of_real_iff_to_real_le ha hb) h) (ne_top_of_le_ne_top of_real_ne_top h)

theorem lt_of_real_iff_to_real_lt {a : ennreal} {b : ℝ} (ha : a ≠ ⊤) : a < ennreal.of_real b ↔ ennreal.to_real a < b := sorry

theorem of_real_mul {p : ℝ} {q : ℝ} (hp : 0 ≤ p) : ennreal.of_real (p * q) = ennreal.of_real p * ennreal.of_real q := sorry

theorem of_real_inv_of_pos {x : ℝ} (hx : 0 < x) : ennreal.of_real x⁻¹ = ennreal.of_real (x⁻¹) := sorry

theorem of_real_div_of_pos {x : ℝ} {y : ℝ} (hy : 0 < y) : ennreal.of_real (x / y) = ennreal.of_real x / ennreal.of_real y := sorry

theorem to_real_of_real_mul (c : ℝ) (a : ennreal) (h : 0 ≤ c) : ennreal.to_real (ennreal.of_real c * a) = c * ennreal.to_real a := sorry

@[simp] theorem to_nnreal_mul_top (a : ennreal) : ennreal.to_nnreal (a * ⊤) = 0 := sorry

@[simp] theorem to_nnreal_top_mul (a : ennreal) : ennreal.to_nnreal (⊤ * a) = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (ennreal.to_nnreal (⊤ * a) = 0)) (mul_comm ⊤ a)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (ennreal.to_nnreal (a * ⊤) = 0)) (to_nnreal_mul_top a))) (Eq.refl 0))

@[simp] theorem to_real_mul_top (a : ennreal) : ennreal.to_real (a * ⊤) = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (ennreal.to_real (a * ⊤) = 0)) (to_real.equations._eqn_1 (a * ⊤))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (↑(ennreal.to_nnreal (a * ⊤)) = 0)) (to_nnreal_mul_top a)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (↑0 = 0)) nnreal.coe_zero)) (Eq.refl 0)))

@[simp] theorem to_real_top_mul (a : ennreal) : ennreal.to_real (⊤ * a) = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (ennreal.to_real (⊤ * a) = 0)) (mul_comm ⊤ a))) (to_real_mul_top a)

theorem to_real_eq_to_real {a : ennreal} {b : ennreal} (ha : a < ⊤) (hb : b < ⊤) : ennreal.to_real a = ennreal.to_real b ↔ a = b := sorry

/-- `ennreal.to_nnreal` as a `monoid_hom`. -/
def to_nnreal_hom : ennreal →* nnreal :=
  monoid_hom.mk ennreal.to_nnreal sorry sorry

theorem to_nnreal_mul {a : ennreal} {b : ennreal} : ennreal.to_nnreal (a * b) = ennreal.to_nnreal a * ennreal.to_nnreal b :=
  monoid_hom.map_mul to_nnreal_hom a b

theorem to_nnreal_pow (a : ennreal) (n : ℕ) : ennreal.to_nnreal (a ^ n) = ennreal.to_nnreal a ^ n :=
  monoid_hom.map_pow to_nnreal_hom a n

theorem to_nnreal_prod {ι : Type u_1} {s : finset ι} {f : ι → ennreal} : ennreal.to_nnreal (finset.prod s fun (i : ι) => f i) = finset.prod s fun (i : ι) => ennreal.to_nnreal (f i) :=
  monoid_hom.map_prod to_nnreal_hom (fun (i : ι) => f i) s

/-- `ennreal.to_real` as a `monoid_hom`. -/
def to_real_hom : ennreal →* ℝ :=
  monoid_hom.comp (↑nnreal.to_real_hom) to_nnreal_hom

theorem to_real_mul {a : ennreal} {b : ennreal} : ennreal.to_real (a * b) = ennreal.to_real a * ennreal.to_real b :=
  monoid_hom.map_mul to_real_hom a b

theorem to_real_pow (a : ennreal) (n : ℕ) : ennreal.to_real (a ^ n) = ennreal.to_real a ^ n :=
  monoid_hom.map_pow to_real_hom a n

theorem to_real_prod {ι : Type u_1} {s : finset ι} {f : ι → ennreal} : ennreal.to_real (finset.prod s fun (i : ι) => f i) = finset.prod s fun (i : ι) => ennreal.to_real (f i) :=
  monoid_hom.map_prod to_real_hom (fun (i : ι) => f i) s

theorem of_real_prod_of_nonneg {α : Type u_1} {s : finset α} {f : α → ℝ} (hf : ∀ (i : α), i ∈ s → 0 ≤ f i) : ennreal.of_real (finset.prod s fun (i : α) => f i) = finset.prod s fun (i : α) => ennreal.of_real (f i) := sorry

theorem infi_add {a : ennreal} {ι : Sort u_3} {f : ι → ennreal} : infi f + a = infi fun (i : ι) => f i + a :=
  le_antisymm (le_infi fun (i : ι) => add_le_add (infi_le f i) (le_refl a))
    (iff.mp ennreal.sub_le_iff_le_add
      (le_infi fun (i : ι) => iff.mpr ennreal.sub_le_iff_le_add (infi_le (fun (i : ι) => f i + a) i)))

theorem supr_sub {a : ennreal} {ι : Sort u_3} {f : ι → ennreal} : (supr fun (i : ι) => f i) - a = supr fun (i : ι) => f i - a := sorry

theorem sub_infi {a : ennreal} {ι : Sort u_3} {f : ι → ennreal} : (a - infi fun (i : ι) => f i) = supr fun (i : ι) => a - f i := sorry

theorem Inf_add {a : ennreal} {s : set ennreal} : Inf s + a = infi fun (b : ennreal) => infi fun (H : b ∈ s) => b + a := sorry

theorem add_infi {ι : Sort u_3} {f : ι → ennreal} {a : ennreal} : a + infi f = infi fun (b : ι) => a + f b := sorry

theorem infi_add_infi {ι : Sort u_3} {f : ι → ennreal} {g : ι → ennreal} (h : ∀ (i j : ι), ∃ (k : ι), f k + g k ≤ f i + g j) : infi f + infi g = infi fun (a : ι) => f a + g a := sorry

theorem infi_sum {α : Type u_1} {ι : Sort u_3} {f : ι → α → ennreal} {s : finset α} [Nonempty ι] (h : ∀ (t : finset α) (i j : ι), ∃ (k : ι), ∀ (a : α), a ∈ t → f k a ≤ f i a ∧ f k a ≤ f j a) : (infi fun (i : ι) => finset.sum s fun (a : α) => f i a) = finset.sum s fun (a : α) => infi fun (i : ι) => f i a := sorry

theorem infi_mul {ι : Sort u_1} [Nonempty ι] {f : ι → ennreal} {x : ennreal} (h : x ≠ ⊤) : infi f * x = infi fun (i : ι) => f i * x := sorry

theorem mul_infi {ι : Sort u_1} [Nonempty ι] {f : ι → ennreal} {x : ennreal} (h : x ≠ ⊤) : x * infi f = infi fun (i : ι) => x * f i := sorry

/-! `supr_mul`, `mul_supr` and variants are in `topology.instances.ennreal`. -/

theorem supr_coe_nat : (supr fun (n : ℕ) => ↑n) = ⊤ :=
  iff.mpr (supr_eq_top fun (n : ℕ) => ↑n)
    fun (b : ennreal) (hb : b < ⊤) => ennreal.exists_nat_gt (iff.mp lt_top_iff_ne_top hb)

/-- `le_of_add_le_add_left` is normally applicable to `ordered_cancel_add_comm_monoid`,
but it holds in `ennreal` with the additional assumption that `a < ∞`. -/
theorem le_of_add_le_add_left {a : ennreal} {b : ennreal} {c : ennreal} : a < ⊤ → a + b ≤ a + c → b ≤ c := sorry


-- https://leanprover.github.io/theorem_proving_in_lean/

import tactic

-- 1.3. About this Book

-- Structural Proof
lemma add_commutative₁ (p q : Prop) :
p ∧ q → q ∧ p :=
assume hpq : p ∧ q,
have hp : p, from and.left hpq,
have hq : q, from and.right hpq,
show q ∧ p, from and.intro hq hp

-- Tatical Proof
lemma add_commutative₂ (p q : Prop) :
p ∧ q → q ∧ p :=
begin
intro hpq,
apply and.intro,
{
    exact and.right hpq,
},
{
    exact and.left hpq
}
end

theorem add_commutative :
∀ (p q : Prop), p ∧ q → q ∧ p :=
begin
intros p q,
-- apply add_commutative₁
apply add_commutative₂
end

-- 2.1. Simple Type Theory

constant m : nat
constant n : ℕ
constant z : ℤ

#check m
#check n
#check m + n
#check m * (n + 0)
#check z * (-1)

constants b1 b2 : bool

#check b1
#check b1 && b2
#check b1 || b2
#check tt
#check ff
#eval tt && ff
#eval tt || ff

constant f₀ : nat → nat
constant f₀' : nat -> nat
constant f₀'' : ℕ → ℕ
constant p₀ : nat × nat
constant q₀ : prod nat nat
constant g₀ : nat → nat → nat
constant g₀' : nat → (nat → nat)
constant h₀ : nat × nat → nat

constant Fun : (nat → nat) → nat   -- a "functional"

#check f₀
#check f₀ n
#check g₀ m n
#check g₀ m
#check (m, n)
#check p₀.1
#check p₀.2
#check (m, n).1
#check (p₀.1, n)
#check Fun f₀

-- 2.2. Types as Objects

#check nat
#check bool
#check nat → bool
#check nat × bool
#check nat → nat
#check nat × nat → nat
#check nat → nat → nat
#check nat → (nat → nat)
#check nat → nat → bool
#check (nat → nat) → nat

#check Prop -- Type
#check Prop → Prop -- Type

constants α β γ: Type
constant F : Type → Type
constant G : Type → Type → Type

#check α
#check F α
#check F nat
#check G α
#check G α β
#check G α nat

#check @prod
#check prod α β
#check prod nat nat

#check list α
#check list nat

#check Type     -- Type : Type 1
#check Type 1   -- Type 1 : Type 2
#check Type 2   -- Type 2 : Type 3
#check Type 3   -- Type 3 : Type 4
#check Type 4   -- Type 4 : Type 5

#check Sort     -- Prop : Type
#check Sort 1   -- Type : Type 1
#check Sort 2   -- Type 1 : Type 2
#check Sort 3   -- Type 2 : Type 3
#check Sort 4   -- Type 3 : Type 4

universe u
universe v

-- #check u     -- unknown identifier 'u'
#check Sort u   -- Sort u : Type u
#check Type u   -- Type u : Type (u+1)

constant instance_sort_u : Sort u

#check instance_sort_u -- instance_u : Sort u_1

constant instance_type_u : Type u

#check instance_type_u -- instance_type_u : Type u_1

constant instance_type_u₂ : Type u

#check instance_type_u₂ -- instance_type_u₂ : Type u_1

-- 2.3. Function Abstraction and Evaluation

#check fun x : nat, x + 5
#check λ x : nat, x + 5

#eval (λ x : nat, x + 5) 3 -- 8

constants α₁ α₂ : α
constants β₁ β₂: β

constant f : α → α
constant g : α → β
constant h : α → β → α
constant j : β → γ
constant p : α → α → bool

#check fun x : α, f x                      -- α → α
#check λ x : α, f x                        -- α → α
#check λ x : α, f (f x)                    -- α → α
#check λ x : α, h x β₁                     -- α → α
#check λ y : β, h α₁ y                     -- β → α
#check λ x : α, p (f (f x)) (h (f α₁) β₂)  -- α → bool
#check λ x : α, λ y : β, h (f x) y         -- α → β → α
#check λ (x : α) (y : β), h (f x) y        -- α → β → α
#check λ x y, h (f x) y                    -- α → β → α

constants (a : α) (b : β)

#check a
#check b

#check λ x : α, x        -- α → α
#check λ x : α, b        -- α → β
#check λ x : α, g (f x)  -- α → γ
#check λ x, g (f x)

#check λ b : β, λ x : α, x     -- β → α → α
#check λ (b : β) (x : α), x    -- β → α → α
#check λ (g : β → γ) (f : α → β) (x : α), g (f x)
                              -- (β → γ) → (α → β) → α → γ

#check λ (α β : Type) (b : β) (x : α), x
#check λ (α β γ : Type) (g : β → γ) (f : α → β) (x : α), g (f x)

#check (λ x : α, x) a                    -- α
#check (λ x : α, b) a                    -- β
#check (λ x : α, b) (h a b)              -- β
#check (λ x : α, g (f x)) (h (h a b) b)  -- β

#reduce (λ x : α, x) a                   -- a
#reduce (λ x : α, b) a                   -- b
#reduce (λ x : α, b) (h a b)             -- b
#reduce (λ x : α, g (f x)) (h (h a b) b) -- g (f (h (h a b) b))

#reduce (λ (Q R S : Type) (v : R → S) (u : Q → R) (x : Q),
       v (u x)) α β γ j g a              -- j (g a)

#print "reducing pairs"
#reduce (m, n).1        -- m
#reduce (m, n).2        -- n

#print "reducing boolean expressions"
#reduce tt && ff        -- ff
#reduce ff && b1        -- ff
#reduce b1 && ff        -- b1 && ff

#print "reducing arithmetic expressions"
#reduce n + 0           -- n
#reduce n + 2           -- nat.succ (nat.succ n)
#reduce 2 + 3           -- 5

-- #reduce 12345 * 54321   -- deep recursion was detected at 'replace' (potential solution: increase stack space in your system)
#eval 12345 * 54321

-- 2.4. Introducing Definitions

-- def `def_name` : `type` := `definition`

def foo : (ℕ → ℕ) → ℕ := λ f, f 0

#check foo
#print foo

def foo' := λ f : ℕ → ℕ, f 0

#check foo'
#print foo'

def double (x : ℕ) : ℕ := x + x
-- is equivalent to
def double' : ℕ → ℕ := λ x : ℕ, x + x

#print double
#check double 3
#reduce double 3                 -- 6
#reduce (double 3) - (double' 3) -- 0

def curry (α β γ : Type) (f : α × β → γ) : α → β → γ := sorry
def uncurry (α β γ : Type) (f : α → β → γ) : α × β → γ := sorry

-- 2.5. Local Definitions

#check let y := 2 + 2 in y * y     -- ℕ
#reduce  let y := 2 + 2 in y * y   -- 16

def double_and_square (x : ℕ) : ℕ :=
let y := x + x in y * y

#reduce double_and_square 2   -- 16

#check   let y := 2 + 2, z := y + y in z * z   -- ℕ
#reduce  let y := 2 + 2, z := y + y in z * z   -- 64

def foobar := let a := nat in λ x : a, x + 2

-- def foobar' := (λ a, λ x : a, x + 2) nat   -- error

-- 2.6. Variables and Sections

section var
    variables (α β γ : Type)
    variables (g : β → γ) (f : α → β) (h : α → α)
    variable x : α

    def compose := g (f x)
    def do_twice := h (h x)
    def do_thrice := h (h (h x))

    #print compose
    #print do_twice
    #print do_thrice
end var

-- 2.7. Namespaces

namespace foo
  def a_number : ℕ := 5
  def function (x : ℕ) : ℕ := x + 7

  def fa : ℕ := function a_number
  def ffa : ℕ := function (function a_number)

  def some_name_only_inside_foo : ℕ := 42

  #print "inside foo"

  #check a_number
  #check function
  #check fa
  #check ffa
  #check foo.fa
end foo

#print "outside the namespace"

-- #check some_name_only_inside_foo -- error
#check foo.some_name_only_inside_foo

#check foo.a_number
#check foo.function
#check foo.fa
#check foo.ffa

section
    open foo

    #print "opened foo"

    #check a_number
    #check function
    #check fa
    #check foo.fa
end

-- #check a_number -- error

section
    open list

    #check @nil
    #check @cons
    #check @append
end

namespace blah
    def a : ℕ := 5
    def f (x : ℕ) : ℕ := x + 7
    def fa : ℕ := f a

    -- namespaces can be nested
    namespace inner
        def nothing : ℕ := 0
    end inner
end blah

#check blah.a
#check blah.f
#check blah.inner.nothing

-- namespaces can be re-opened

namespace blah
  def ffa : ℕ := f (f a)
end blah

-- 2.8. Dependent Types

-- a Pi type, or dependent function type
-- Π `\Pi`

-- the type Π x : α, β x denotes the type of functions f with the property that,
-- for each a : α, f a is an element of β a.

namespace list_pi

    constant list   : Type u → Type u

    constant cons   : Π α : Type u, α → list α → list α
    constant nil    : Π α : Type u, list α
    constant head   : Π α : Type u, list α → α
    constant tail   : Π α : Type u, list α → list α
    constant append : Π α : Type u, list α → list α → list α

end list_pi

namespace vec_pi
    constant vec : Type u → ℕ → Type u

    constant empty : Π α : Type u, vec α 0
    constant empty₁ : ∀ α : Type u, vec α 0

    constant cons :
        Π (α : Type u) (n : ℕ), α → vec α n → vec α (n + 1)
    constant cons₁ :
        ∀ (α : Type u) (n : ℕ) (a : α), vec α n → vec α (n + 1)
    constant cons₂ :
        ∀ (α : Type u) (n : ℕ) (a : α) (van : vec α n), vec α (n + 1)

    constant append :
        Π (α : Type u) (n m : ℕ), vec α m → vec α n → vec α (n + m)
    constant append₀ :
        Π (α : Type u) (n m : ℕ) (van : vec α n) (vam : vec α m), vec α (n + m)

    -- They are equivalent
    #check cons
    #check cons₁
    #check cons₂

    -- They are equivalent
    #check append
    #check append₀
end vec_pi

-- Sigma types a.k.a dependent products

-- Pi types Π x : α, β x generalize the notion of
-- a function type α → β by allowing β to depend on α

-- Sigma types Σ x : α, β x generalize the cartesian product α × β
-- in the same way:
-- in the expression sigma.mk a b,
-- the type of the second element of the pair, b : β a,
-- depends on the first element of the pair, a : α.

namespace sigma_type
    variable α : Type
    variable β : α → Type
    variable a : α
    variable b : β a

    #check sigma.mk a b      -- ⟨a, b⟩ : Σ (a : α), β a
    #check (sigma.mk a b).1  -- ⟨a, b⟩.fst : α
    #check (sigma.mk a b).2  -- β (sigma.fst (sigma.mk a b))
    -- ⟨a, b⟩.snd : (λ (a : α), β a) ⟨a, b⟩.fst

    #reduce  (sigma.mk a b).1  -- a
    #reduce  (sigma.mk a b).2  -- b
end sigma_type

-- 2.9. Implicit Arguments

namespace implicit_arguments
    constant list : Type u → Type u

    namespace listish
        constant cons   : Π α : Type u, α → list α → list α
        constant nil    : Π α : Type u, list α
        constant append : Π α : Type u, list α → list α → list α

        -- Lean allows us to specify that this argument should,
        -- by default, be left implicit.
        -- This is done by putting the arguments in curly braces
        constant consᵢ   : Π {α : Type u}, α → list α → list α
        constant nilᵢ    : Π {α : Type u}, list α
        constant appendᵢ : Π {α : Type u}, list α → list α → list α

    end listish

    open listish

    variable  α : Type
    variable  a : α
    variables l1 l2 : list α

    #check cons α a (nil α)
    #check append α (cons α a (nil α)) l1
    #check append α (append α (cons α a (nil α)) l1) l2

    #check cons _ a (nil _)
    #check append _ (cons _ a (nil _)) l1
    #check append _ (append _ (cons _ a (nil _)) l1) l2

    #check consᵢ a nilᵢ
    #check appendᵢ (consᵢ a nilᵢ) l1
    #check appendᵢ (appendᵢ (consᵢ a nilᵢ) l1) l2

    namespace for_def
        def ident {α : Type u} (x : α) := x

        variables γ δ : Type u
        variables (c : γ) (d : δ)

        #check ident      -- ?M_1 → ?M_1
        #check @ident     -- ident : Π {α : Type u_1}, α → α
        #check ident c    -- γ
        #check ident d    -- δ
    end for_def

    namespace for_var
        variable { σ : Type u }
        variable x : σ
        def ident := x

        variables γ δ : Type u
        variables (c : γ) (d : δ)

        #check ident      -- ?M_1 → ?M_1
        #check @ident     -- ident : Π {σ : Type u_1}, σ → σ
        #check ident c    -- γ
        #check ident d    -- δ
    end for_var
end implicit_arguments

-- The process of instantiating these “holes,” or “placeholders,”
-- in a term is often known as elaboration.

#check (2 : ℤ)

namespace ex_02_01
    def double (x : ℕ) : ℕ := x + x

    def do_twice (f : ℕ → ℕ) (x : ℕ) : ℕ := f (f x)

    def Do_Twice : ((ℕ → ℕ) → (ℕ → ℕ)) → (ℕ → ℕ) → (ℕ → ℕ)
    := λ op f, op (op f)

    #reduce Do_Twice do_twice double 2
    #reduce (pow 2 4) * 2

    example :
    (Do_Twice do_twice double 2) = (double (double (double (double 2)))) :=
    by refl

end ex_02_01

namespace ex_02_02

    def curry {α β γ : Type} (f : α × β → γ) : α → β → γ :=
    λ a b, f (a, b)

    def uncurry {α β γ : Type} (f : α → β → γ) : α × β → γ :=
    λ ab, f ab.1 ab.2
    -- λ ⟨a, b⟩, f a b

    constant op : α × β → γ

    -- #check curry
    -- #check curry op

    lemma curry_uncurry {α β γ : Type} (f : α → β → γ) :
        curry (uncurry f) = f :=
    by refl

    #check (a, b)

    lemma uncurry_curry {α β γ : Type} (f : α × β → γ) :
        uncurry (curry f) = f :=
    -- funext (λ ⟨a, b⟩, rfl)
    begin
        -- simp only [curry, uncurry],
        rewrite curry,
        rewrite uncurry,
        apply funext,
        intro x,
        rewrite prod.mk.eta
    end

end ex_02_02

namespace ex_02_03

    constant vec : Type u → ℕ → Type u

    constant vec_add {n : ℕ} : vec ℕ n → vec ℕ n → vec ℕ n

    constant vec_reverse {α : Type} {n : ℕ} : vec α n → vec α n

    variables (v1 v2 : vec ℕ 5) (v3 : vec ℕ 7) (v4 : vec string 5)

    #check vec_add v1 v2
    -- #check vec_add v1 v3 -- errror: type mismatch
    -- #check vec_add v1 v4 -- errror: type mismatch

    #check vec_reverse v1
    #check vec_reverse v3
    #check vec_reverse v4
    #check vec_reverse (vec_add v1 v2)

end ex_02_03

namespace ex_02_04

    constant vec : Type u → ℕ → Type u

    constant matrix : Type u → ℕ → ℕ → Type u

    constant matrix_add {α : Type} {m n : ℕ} : matrix α m n → matrix α m n → matrix α m n

    -- https://www.mathsisfun.com/algebra/matrix-multiplying.html :)
    constant matrix_mul {α : Type} {m n p : ℕ} : matrix α m n → matrix α n p → matrix α m p

    constant matrix_vec_mul {α : Type} {m n p : ℕ} : matrix α m n → vec α n → vec α m

    variables (m1 : matrix ℕ 3 4) (m2 : matrix ℕ 4 5) (m3 : vec ℕ 3) (m4 : vec ℕ 4)

    #check matrix_add m1 m1
    -- #check matrix_add m1 m2 -- errror: type mismatch
    #check matrix_mul m1 m2
    -- #check matrix_mul m1 m1 -- errror: type mismatch
    --#check matrix_mul m1 m3 -- errror: type mismatch
    -- #check matrix_vec_mul m1 m3 -- errror: type mismatch
    #check matrix_vec_mul m1 m4

end ex_02_04

-- 3. Propositions and Proofs
namespace chap_03

-- 3.1. Propositions as Types

constant and : Prop → Prop → Prop
constant or : Prop → Prop → Prop
constant not : Prop → Prop
constant implies : Prop → Prop → Prop

constants p q r s : Prop
#check and p q                      -- Prop
#check or (and p q) r               -- Prop
#check implies (and p q) (and q p)  -- Prop

#check Prop -- Prop : Type
#check Type -- Type : Type 1

constant Proof : Prop → Type

constant local_and_comm : Π p q : Prop,
  Proof (implies (and p q) (and q p))

#check local_and_comm p q

constant local_modus_ponens :
  Π p q : Prop, Proof (implies p q) →  Proof p → Proof q

constant local_implies_intro :
  Π p q : Prop, (Proof p → Proof q) → Proof (implies p q)

-- Calculus of Constructions

-- Curry-Howard isomorphism a.k.a. propositions-as-types paradigm

-- Prop, like the other type universes, is closed under the arrow constructor:
-- if we have p q : Prop, then p → q : Prop.

#check p → q -- Prop

#check α → β -- Type

#check Type u → Type v -- Type u → Type v : Type (max (u+1) (v+1))

-- Prop are definitionally proof-irrelevant types

-- 3.2. Working with Propositions as Types

theorem t1 : p → q → p := λ hp : p, λ hq : q, hp

#print t1
-- theorem chap_03.t1 : p → q → p :=
-- λ (hp : p) (hq : q), hp

-- Lean provides the alternative syntax assume for such a lambda abstraction:

theorem t1₂ : p → q → p :=
assume hp : p,
assume hq : q,
hp

#check t1₂ -- t1₂ : p → q → p

-- Lean also allows us to specify the type of the final term hp, explicitly,
-- with a show statement.

theorem t1₃ : p → q → p :=
assume hp : p,
assume hq : q,
show p, from hp

#check t1₃ -- t1₃ : p → q → p

-- As with ordinary definitions, we can move the lambda-abstracted variables
-- to the left of the colon:

theorem t1₄ (hp : p) (hq : q) : p := hp

#check t1₄    -- t1₄ : p → q → p

-- the axiom command is alternative syntax for constant.
-- Declaring a “constant” hp : p is tantamount to declaring that p is true,
-- as witnessed by hp.
axiom hp : p

#check hp -- hp : p

#check p
#check q
#check hp
#check t1₄
#check t1₄ hp

theorem t1₅ (p q : Prop) (hp : p) (hq : q) : p := hp

-- we can apply the theorem t1 just as a function application.
theorem t2 : q → p := t1₄ hp

theorem t1₆ (p q : Prop) (hp : p) (hq : q) : p := hp

theorem t1₇ : ∀ (p q : Prop), p → q → p :=
λ (p q : Prop) (hp : p) (hq : q), hp

#check t1₇

-- If p and q have been declared as variables,
-- Lean will generalize them for us automatically:

variables p q : Prop

theorem t1₈ : p → q → p := λ (hp : p) (hq : q), hp

#check t1₈ -- t1₈ : ∀ (p q : Prop), p → q → p

variable  hp : p

theorem t1₉ : q → p := λ (hq : q), hp

#check t1₉ -- t1₉ : ∀ (p q : Prop), q → p

theorem t1ₐ (p q : Prop) (hp : p) (hq : q) : p := hp

variables r s : Prop

-- we can apply t1ₐ to different pairs of propositions,
-- to obtain different instances of the general theorem.

#check t1ₐ p q                -- p → q → p
#check t1ₐ r s                -- r → s → r
#check t1ₐ (r → s) (s → r)    -- (r → s) → (s → r) → r → s

variable h : r → s
#check t1ₐ (r → s) (s → r) h  -- (s → r) → r → s

def compose (α β γ : Type) (g : β → γ) (f : α → β) (x : α) :
  γ :=
g (f x)

theorem t_compose (h₁ : q → r) (h₂ : p → q) : p → r :=
assume h₃ : p,
show r, from h₁ (h₂ h₃)

theorem t_compose₁ (h₁ : q → r) (h₂ : p → q) : p → r :=
λ h₃ : p,
h₁ (h₂ h₃)

#check t_compose₁

-- 3.3. Propositional Logic

#check true
#check false
#check tt
#check ff
#print notation ¬ -- \not, \neg
#check p ∧ q -- \and
#check p ∨ q -- \or
#check p -> q
#check p → q -- \to, \r, \imp
#check p ↔ q -- \iff, \lr

#check p → q → p ∧ q
#check ¬p → p ↔ false
#check p ∨ q → q ∨ p

#print notation *  -- _ `*`:70 _:70 := has_mul.mul #1 #0
#print notation +  -- _ `+`:65 _:65 := has_add.add #1 #0
#print notation ¬  -- `¬`:40 _:40 := not #0
#print notation ∧ -- _ `∧`:35 _:34 := and #1 #0
#print notation ∨ -- _ `∨`:30 _:29 := or #1 #0
#print notation -> -- _ `->`:25 _:24 := #1 → #2
#print notation ↔  -- _ `↔`:20 _:20 := iff #1 #0

-- 3.3.1. Conjunction

example (hp : p) (hq : q) : p ∧ q := and.intro hp hq

#check assume (hp : p) (hq : q), and.intro hp hq
#check λ (hp : p) (hq : q), and.intro hp hq

#check and.intro -- ?M_1 → ?M_2 → ?M_1 ∧ ?M_2
#check and.elim_left -- ?M_1 ∧ ?M_2 → ?M_1
#check and.elim_right -- ?M_1 ∧ ?M_2 → ?M_2

example (h : p ∧ q) : p := and.elim_left h
example (h : p ∧ q) : q := and.elim_right h

example (h : p ∧ q) : q ∧ p :=
and.intro (and.right h) (and.left h)

-- and-introduction and and-elimination are similar to
-- the pairing and projection operations for the cartesian product

-- The similarity between ∧ and × is another instance of
-- the Curry-Howard isomorphism, but in contrast to implication
--  and the function space constructor,
-- ∧ and × are treated separately in Lean.

#check λ (p q : Prop) (hp : p) (hq : q), and.intro hp hq

#check λ (p q : Prop), (p, q)

-- #check λ (p q : Prop) (hp : p) (hq : q), (hp, hq) -- error

-- namespace hmn

-- variables m n : Prop
-- variable hm : m
-- variable hn : n

-- -- #check (hm, hn) --error

-- end hmn

-- the canonical way to construct an element is to apply and.intro to suitable arguments
-- hp : p and hq : q.
-- Lean allows us to use anonymous constructor notation ⟨arg1, arg2, ...⟩
-- when the relevant type is an inductive type and can be inferred from the context.
-- In particular, we can often write ⟨hp, hq⟩ instead of and.intro hp hq

#check λ (p q : Prop) (hp : p) (hq : q), (⟨hp, hq⟩ : p ∧ q)

example (p q : Prop) (hp : p) (hq : q) : p ∧ q := ⟨hp, hq⟩

-- Given an expression e of an inductive type foo,
-- the notation e.bar is shorthand for foo.bar e.

variable l : list ℕ

#check list.head l
#check l.head

example : list.head l == l.head :=
begin
refl
end

-- example (h : p ∧ q) : q ∧ p := and.intro (and.right h) (and.left h)

example (h : p ∧ q) : q ∧ p := ⟨ h.right, h.left ⟩

example (h : p ∧ q) : q ∧ p ∧ q:=
⟨h.right, ⟨h.left, h.right⟩⟩
-- is equivalent to
example (h : p ∧ q) : q ∧ p ∧ q:=
⟨h.right, h.left, h.right⟩

-- 3.3.2. Disjunction

example (hp : p) : p ∨ q := or.intro_left q hp
example (hq : q) : p ∨ q := or.intro_right p hq

example (h : p ∨ q) : q ∨ p :=
or.elim h
  (assume hp : p,
    show q ∨ p, from or.intro_right q hp)
  (assume hq : q,
    show q ∨ p, from or.intro_left p hq)

example : p ∨ q → q ∨ p :=
begin
    intro h,
    apply or.elim h,
    {
        apply or.intro_right
    },
    {
        apply or.intro_left
    }
end

example : p ∨ q → q ∨ p :=
assume h : p ∨ q,
or.elim h
  (assume hp : p,
    show q ∨ p, from or.intro_right q hp)
  (assume hq : q,
    show q ∨ p, from or.intro_left p hq)

example : p ∨ q → q ∨ p :=
λ h : p ∨ q,
or.elim h
  (λ hp : p, or.intro_right q hp)
  (λ hq : q, or.intro_left p hq)

example (h : p ∨ q) : q ∨ p :=
or.elim h (λ hp, or.inr hp) (λ hq, or.inl hq)

example (h : p ∨ q) : q ∨ p :=
h.elim
  (assume hp : p, or.inr hp)
  (assume hq : q, or.inl hq)

example (h : p ∨ q) : q ∨ p :=
h.elim
  (λ hp : p, or.inr hp)
  (λ hq : q, or.inl hq)

/-
inductive or (a b : Prop) : Prop
| inl (h : a) : or
| inr (h : b) : or

namespace or
  lemma elim (h₁ : a ∨ b) (h₂ : a → c) (h₃ : b → c) : c :=
  or.rec h₂ h₃ h₁
end or
-/

example (h : p ∨ q) : q ∨ p :=
begin
apply h.elim,
{
    intro hp,
    apply or.inr hp
},
{
    intro hq,
    apply or.inl hq
}
end

-- 3.3.3. Negation and Falsity

-- The type of ¬q is q → false

example (hpq : p → q) (hnq : ¬q) : ¬p := sorry

lemma nameless (hpq : p → q) (hnq : ¬q) : ¬p := sorry

#check nameless

def nameless₁ (hpq : p → q) (hnq : ¬q) : ¬p := sorry

def nameless₂ : (p → q) → (¬q) → ¬p := sorry

-- Play with `hpq` and `hnq` directly
def nameless₁' (hpq : p → q) (hnq : ¬q) : ¬p := (let play_with := (nameless₁ p q) in play_with hpq hnq)

-- Need Lambda to introduce binding names
def nameless₂' : (p → q) → (¬q) → ¬p :=  (let play_with := (nameless₁ p q) in λ hpq, λ hnq, (play_with hpq hnq)) -- need Lambda to introduce names for the type

lemma contrapositive_structural (hpq : p → q) (hnq : ¬q) : ¬p :=
assume hp : p,
show false, from hnq (hpq hp)

#print contrapositive_structural
-- theorem chap_03.contrapositive_structural : ∀ (p q : Prop), (p → q) → ¬q → ¬p :=
-- λ (p q : Prop) (hpq : p → q) (hnq : ¬q) (hp : p), show false, from hnq (hpq hp)

namespace classical

open classical

lemma contrapositive_structural_by_contradiction_sorry (hpq : p → q) (hnq : ¬q) : ¬p :=
by_contradiction
(assume hnnp : ¬¬p,
have hp : p, from sorry,
have hq : q, from sorry,
show false, from sorry)

lemma contrapositive_structural_by_contradiction (hpq : p → q) (hnq : ¬q) : ¬p :=
by_contradiction
(assume hnnp : ¬¬p,
have hp : p, from by_contradiction hnnp,
have hq : q, from hpq hp,
show false, from hnq hq)

#print contrapositive_structural_by_contradiction
-- theorem chap_03.contrapositive_structural_by_contradiction : ∀ (p q : Prop), (p → q) → ¬q → ¬p :=
-- λ (p q : Prop) (hpq : p → q) (hnq : ¬q),
--   by_contradiction
--     (λ (hnnp : ¬¬p), have hp : p, from by_contradiction hnnp, have hq : q, from hpq hp, show false, from hnq hq)

end classical

lemma contrapositive_tactic (hpq : p → q) (hnq : ¬q) : ¬p :=
begin
intro hp,
apply hnq,
apply hpq,
exact hp
end

#print contrapositive_tactic
-- theorem chap_03.contrapositive_tactic : ∀ (p q : Prop), (p → q) → ¬q → ¬p :=
-- λ (p q : Prop) (hpq : p → q) (hnq : ¬q), id (λ (hp : p), hnq (hpq hp))

lemma contrapositive_term (hpq : p → q) (hnq : q → false) : p → false :=
λ hp, hnq (hpq hp)

#print contrapositive_term
-- theorem chap_03.contrapositive_term : ∀ (p q : Prop), (p → q) → (q → false) → p → false :=
-- λ (p q : Prop) (hpq : p → q) (hnq : q → false) (hp : p), hnq (hpq hp)

lemma contrapositive_calc (hpq : p → q) (hnq : q → false) : p → false :=
calc p → q       : hpq
...    → false   : hnq

#print contrapositive_calc
-- theorem chap_03.contrapositive_calc : ∀ (p q : Prop), (p → q) → (q → false) → p → false :=
-- λ (p q : Prop) (hpq : p → q) (hnq : q → false), implies.trans hpq hnq

lemma contrapositive_calc₂ (p_to_q : p → q) (q_to_false : q → false) : p → false :=
calc p → q       : p_to_q
...    → false   : q_to_false

#print contrapositive_calc₂

theorem not_not_not_tactic
  (P : Prop) :
  ¬ ¬ ¬ P → ¬ P :=
begin
intro hnnnp,
intro hp,
apply hnnnp,
intro hnp,
apply hnp,
exact hp
end

theorem not_not_not_term
  (P : Prop) :
  ¬ ¬ ¬ P → ¬ P :=
λ hnnnp hp, hnnnp (λ hnp, hnp hp)

theorem not_not_not_calc_fancy
  (P : Prop) :
  ¬ ¬ ¬ P → ¬ P :=
calc ¬ ¬ ¬ P → ¬ P : iff.elim_left (not_non_contradictory_iff_absurd P)

theorem not_not' (P : Prop) : ¬ (¬ P) → P :=
begin
  cases (classical.em P) with hp hnp,
  {
    intro hnnp,
    exact hp,
  },
  {
    intro hnnp,
    exfalso,
    apply hnnp,
    exact hnp
  }
end

theorem not_not_not_calc
  (P : Prop) :
  ¬ ¬ ¬ P → ¬ P :=
calc ¬ ¬ ¬ P  → ¬ P : not_not' (¬ P)

theorem not_not_not_structural
  (P : Prop) :
  ¬ ¬ ¬ P → ¬ P :=
assume hnnnp : ¬¬¬P,
assume hp : P,
have hnnp : ¬¬P, from (assume hnp: ¬P, hnp hp), -- from not_not_intro hp,
show false, from hnnnp hnnp

-- false.elim : ∀ {C : Prop}, false → C
-- This rule is sometimes called ex falso (short for ex falso sequitur quodlibet), or the principle of explosion.
example (hp : p) (hnp : ¬p) : q := false.elim (hnp hp)
-- absurd : ∀ {a b : Prop}, a → ¬a → b
example (hp : p) (hnp : ¬p) : q := absurd hp hnp

example (hnp : ¬p) (hq : q) (hqp : q → p) : r :=
absurd (hqp hq) hnp

-- 3.4. Introducing Auxiliary Subgoals

theorem and_swap : p ∧ q ↔ q ∧ p :=
iff.intro
  (assume h : p ∧ q,
    show q ∧ p, from and.intro (and.right h) (and.left h))
  (assume h : q ∧ p,
    show p ∧ q, from and.intro (and.right h) (and.left h))

#check and_swap p q    -- p ∧ q ↔ q ∧ p

theorem and_swap_term : p ∧ q ↔ q ∧ p :=
⟨(λ h : p ∧ q, ⟨h.right, h.left⟩),
  (λ h : q ∧ p, ⟨h.right, h.left⟩)⟩

theorem and_swap_tactic : p ∧ q ↔ q ∧ p :=
begin
split,
{
  intro h,
  apply and.intro h.right h.left
},
{
  intro h,
  apply and.intro h.right h.left
}
end

theorem and_swap_structural : p ∧ q ↔ q ∧ p :=
have lr : p ∧ q → q ∧ p, from (assume h : p ∧ q, ⟨h.right, h.left⟩),
have rl : q ∧ p → p ∧ q , from (assume h : q ∧ p, ⟨h.right, h.left⟩),
show p ∧ q ↔ q ∧ p, from iff.intro lr rl

theorem and_swap_calc : p ∧ q ↔ q ∧ p :=
have lr : p ∧ q → q ∧ p := calc p ∧ q → q ∧ p : (λ h : p ∧ q, ⟨h.right, h.left⟩),
have rl : q ∧ p → p ∧ q := calc q ∧ p → p ∧ q : (λ h : q ∧ p, ⟨h.right, h.left⟩),
show p ∧ q ↔ q ∧ p, from iff.intro lr rl

-- Because they represent a form of modus ponens, iff.elim_left and iff.elim_right
-- can be abbreviated iff.mp and iff.mpr, respectively. I

example (h : p ∧ q) : q ∧ p := iff.mp (and_swap p q) h

example (h : p ∧ q) : q ∧ p := (and_swap p q).mp h

-- the have construct introduces an auxiliary subgoal in a proof

-- Internally, the expression `have h : p, from s, t` produces the term `(λ (h : p), t) s`.
-- In other words, s is a proof of p, t is a proof of the desired conclusion assuming h : p,
-- and the two are combined by a lambda abstraction and application.

-- How to write proofs: a quick guide https://deopurkar.github.io/teaching/algebra1/cheng.pdf
-- Introduction to mathematical arguments https://deopurkar.github.io/teaching/algebra1/hutchings.pdf
-- Coq cheat sheet http://www.inf.ed.ac.uk/teaching/courses/tspl/cheatsheet.pdf

example (h : p ∧ q) : q ∧ p :=
have hp : p, from and.left h,
suffices hq : q, from and.intro hq hp, -- prove q → what we're prooving
show q, from and.right h -- prove q

-- 3.5. Classical Logic

-- The introduction and elimination rules we have seen so far are all constructive,
-- which is to say, they reflect a computational understanding of the logical connectives based on
-- the propositions-as-types correspondence.

-- Ordinary classical logic adds to this the law of the "excluded middle", p ∨ ¬p.

namespace classical

open classical

#check em p -- em p : p ∨ ¬p

theorem dne {p : Prop} (h : ¬¬p) : p :=
or.elim (em p)
  (assume hp : p, hp)
  (assume hnp : ¬p, absurd hnp h)

example (h : ¬¬p) : p :=
classical.by_cases
  (assume h1 : p, h1)
  (assume h1 : ¬p, absurd h1 h)

example (h : ¬¬p) : p :=
by_contradiction
  (assume h1 : ¬p,
    show false, from h h1)

example (h : ¬(p ∧ q)) : ¬p ∨ ¬q :=
or.elim (em p)
  (assume hp : p,
    or.inr
      (show ¬q, from
        assume hq : q,
        h ⟨hp, hq⟩))
  (assume hp : ¬p,
    or.inl hp)

-- example (h : ¬(p ∧ q)) : ¬p ∨ ¬q :=
-- or.elim _ _ _

end classical

namespace ex_03_01

example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
begin
split,
{
  intro hpqr,
  apply and.intro,
  {
    apply hpqr.left.left
  },
  {
    apply and.intro hpqr.left.right hpqr.right
  }
},
{
  intro hpqr,
  apply and.intro,
  {
    apply and.intro hpqr.left hpqr.right.left
  },
  {
    apply hpqr.right.right
  }
}
end

-- example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
-- iff.intro
--   (assume hpqr : (p ∧ q) ∧ r, ⟨ _, _ ⟩)
--   (assume hpqr : p ∧ (q ∧ r), ⟨ _, _ ⟩)

example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
iff.intro
  (assume hpqr : (p ∧ q) ∧ r, ⟨ hpqr.left.left, ⟨ hpqr.left.right, hpqr.right ⟩ ⟩)
  (assume hpqr : p ∧ (q ∧ r), ⟨ ⟨ hpqr.left, hpqr.right.left ⟩, hpqr.right.right ⟩)

example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
begin
split,
{
  intro hpqr,
  apply hpqr.cases_on,
  {
    intro hpq,
    apply hpq.cases_on,
    {
      intro hp,
      apply or.intro_left (q ∨ r),
      exact hp
    },
    {
      intro hq,
      apply or.intro_right p,
      apply or.intro_left r,
      exact hq
    }
  },
  {
    sorry
  }
},
{
  sorry
}
end

-- example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
-- iff.intro
--   (assume hpqr : (p ∨ q) ∨ r, or.elim hpqr _ _)
--   (assume hpqr : p ∨ (q ∨ r), sorry)

-- copied from lemma or.assoc
example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
iff.intro
  (or.rec (or.imp_right or.inl) (λ h, or.inr (or.inr h)))
  (or.rec (λ h, or.inl (or.inl h)) (or.imp_left or.inr))

example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
begin
split,
{
  apply or.rec,
  {
    apply or.imp_right,
    apply or.inl,
  },
  {
    intro hr,
    apply or.inr,
    apply or.inr,
    exact hr
  }
},
{
  apply or.rec,
  {
    sorry
  },
  {
    sorry
  }
}
end

-- example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
-- iff.intro
--   (or.rec _ sorry)
--   (or.rec sorry sorry)

-- don't know how to synthesize placeholder
-- context:
-- p q r : Prop
-- ⊢ p ∨ q → p ∨ q ∨ r

/-

apply or.elim,

5 goals
p q r : Prop,
hpqr : p ∧ (q ∨ r)
⊢ ?m_1 ∨ ?m_2

p q r : Prop,
hpqr : p ∧ (q ∨ r)
⊢ ?m_1 → p ∧ q ∨ p ∧ r

p q r : Prop,
hpqr : p ∧ (q ∨ r)
⊢ ?m_1 → p ∧ q ∨ p ∧ r

p q r : Prop,
hpqr : p ∧ (q ∨ r)
⊢ Prop

-/

example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
split,
{
  intro  hp_qr,
  apply or.elim,
  {
    /-
    1 goal
    p q r : Prop,
    hpqr : p ∧ (q ∨ r)
    ⊢ ?m_1 ∨ ?m_2
    -/
    exact hp_qr.right,
  },
  {
    sorry
  },
  {
    sorry
  },
  -- apply or.elim hp_qr.right,
  -- {
  --   intro hq,
  --   apply or.inl,
  --   apply and.intro,
  --   {
  --     exact hp_qr.left,
  --   },
  --   {
  --     exact hq,
  --   }
  -- },
},
{
  sorry
}
end

example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
iff.intro
  (
    assume hp_qr : p ∧ (q ∨ r),
    have hp : p, from hp_qr.left,
    or.rec sorry sorry hp_qr.right
    -- or.elim hp_qr.right sorry sorry
  )
  (sorry)

example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
have lr : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r), from sorry,
have rl : (p ∧ q) ∨ (p ∧ r) → p ∧ (q ∨ r), from sorry,
show p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r), from iff.intro lr rl

example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
have lr : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r), from
  (
    assume hp_qr : p ∧ (q ∨ r),
    have hp : p, from hp_qr.left,
    have hqr : q ∨ r, from hp_qr.right,
    have hq2c : q → (p ∧ q) ∨ (p ∧ r), from ( assume hq : q, or.inl ⟨hp, hq⟩ ),
    have hr2c : r → (p ∧ q) ∨ (p ∧ r), from ( assume hr : r, or.inr ⟨hp, hr⟩ ),
    or.elim hqr hq2c hr2c
  ),
have rl : (p ∧ q) ∨ (p ∧ r) → p ∧ (q ∨ r), from
  (
    assume hpqpr : (p ∧ q) ∨ (p ∧ r),
    -- have hcq2p : p ∧ q → p, from (assume hpq : p ∧ q, hpq.left),
    -- have hcr2p : p ∧ r → p, from (assume hpr : p ∧ r, hpr.left),
    -- have hp : p, from or.elim hpqpr hcq2p hcr2p,
    -- have hp : p, from or.elim hpqpr (λ hpq : p ∧ q, hpq.left) (λ hpr : p ∧ r, hpr.left),
    -- have hqr : q ∨ r, from or.elim hpqpr (λ hpq : p ∧ q, or.inl hpq.right) (λ hpr : p ∧ r, or.inr hpr.right),

    -- have hp : p, from or.elim hpqpr (λ hpq, hpq.left) (λ hpr, hpr.left),
    -- have hqr : q ∨ r, from or.elim hpqpr (λ hpq, or.inl hpq.right) (λ hpr, or.inr hpr.right),

    or.elim hpqpr (λ hpq, ⟨hpq.left, (or.inl hpq.right)⟩) (λ hpr, ⟨hpr.left, or.inr hpr.right⟩)
  ),
show p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r), from iff.intro lr rl

example : (p → (q → r)) ↔ (p ∧ q → r) :=
iff.intro
  (
    assume hpqr : (p → (q → r)),
    show (p ∧ q → r), from (λ hpq, hpqr hpq.left hpq.right)
  )
  (
    assume hpqr : (p ∧ q → r),
    show (p → (q → r)), from (λ hp hq, hpqr ⟨hp, hq⟩)
  )

example : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
iff.intro
  (
    assume hnpq : ¬(p ∨ q),
    have hnp : ¬p, from (λ hp, hnpq (or.inl hp)),
    have hnq : ¬q, from (λ hq, hnpq (or.inr hq)),
    show ¬p ∧ ¬q, from ⟨hnp, hnq⟩
  )
  (
    assume hnpnq : ¬p ∧ ¬q,
    show ¬(p ∨ q), from (λ hpq, hpq.elim hnpnq.left hnpnq.right)
  )

example : (¬p ∨ q) → (p → q) :=
assume hnpq : (¬p ∨ q),
show (p → q), from (λ hp, or.elim hnpq (λ hnp, absurd hp hnp) (λ hq, hq))

example : (p → q) → (¬q → ¬p) :=
assume hp2q : (p → q),
show (¬q → ¬p), from (λ hnq, (λ hp, absurd (hp2q hp) hnq))

end ex_03_01

namespace ex_03_02

open classical

example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
assume hp2rs : (p → r ∨ s),
have hnr2ps : ¬r → (p → s), from (
  assume hnr,
  assume hp,
  by_contradiction
  (
    assume hns : ¬s,
    or.elim (hp2rs hp) (λ hr, absurd hr hnr) (λ hs, absurd hs hns)
  )
),
have hrnr : r ∨ ¬r, from em r,
have hrpr : r → (p → r), from (λ hr hp, hr),
show ((p → r) ∨ (p → s)), from or.elim hrnr (λ hr, or.inl (hrpr hr)) (λ hnr, or.inr (hnr2ps hnr))

end ex_03_02

-- https://leanprover.zulipchat.com/#narrow/stream/113489-new-members/topic/Natural.20Numbers.20Game/near/199965171
example (n : ℕ) : nat.succ n ≠ 0.

namespace ex_03_03

example : ¬(p ↔ ¬p) :=
begin
  intro h,
  cases h with hp_np hnp_p,
  have hnp : ¬p, from λ hp : p, (hp_np hp) hp,
  exact hnp (hnp_p hnp)
end

example : ¬(p ↔ ¬p) :=
begin
  assume h,
  obtain ⟨p_to_np, np_to_p⟩ := h,
  have hnp : ¬p, from λ hp : p, (p_to_np hp) hp,
  show false, from hnp (np_to_p hnp)
end

example : ¬(p ↔ ¬p) :=
  λ h,
    have hnp : ¬p, from λ hp : p, (h.mp hp) hp,
  hnp (h.mpr hnp)

example : ¬(p ↔ ¬p) :=
  λ h,
  (λ hp : p, (h.mp hp) hp) (h.mpr (λ hp : p, (h.mp hp) hp))

end ex_03_03

end chap_03

namespace chap_04

variables (α : Type) (p q : α → Prop)

example : (∀ x : α, p x ∧ q x) → ∀ y : α, p y :=
assume h : ∀ x : α, p x ∧ q x,
assume y : α,
show p y, from (h y).left

example : (∀ x : α, p x ∧ q x) → ∀ x : α, p x  :=
assume h : ∀ x : α, p x ∧ q x,
assume z : α,
show p z, from and.elim_left (h z)

variables (r : α → α → Prop)

section explicit

variable  trans_r : ∀ x y z, r x y → r y z → r x z

variables a b c : α
variables (hab : r a b) (hbc : r b c)

#check trans_r
#check trans_r a b c
#check trans_r a b c hab
#check trans_r a b c hab hbc

end explicit

section implicit

variable  trans_r : ∀ {x y z}, r x y → r y z → r x z

variables a b c : α
variables (hab : r a b) (hbc : r b c)

#check trans_r
#check trans_r
#check trans_r hab
#check trans_r hab hbc

end implicit

section equivalence

variable refl_r : ∀ x, r x x
variable symm_r : ∀ {x y}, r x y → r y x
variable trans_r : ∀ {x y z}, r x y → r y z → r x z

example (a b c d : α) (hab : r a b) (hcb : r c b) (hcd : r c d) :
  r a d :=
trans_r (trans_r hab (symm_r hcb)) hcd

end equivalence

#check eq.refl    -- ∀ (a : ?M_1), a = a
#check eq.symm    -- ?M_2 = ?M_3 → ?M_3 = ?M_2
#check eq.trans   -- ?M_2 = ?M_3 → ?M_3 = ?M_4 → ?M_2 = ?M_4

#check @eq.refl.{u}   -- ∀ {α : Sort u} (a : α), a = a
#check @eq.symm.{u}   -- ∀ {α : Sort u} {a b : α}, a = b → b = a
#check @eq.trans.{u}  -- ∀ {α : Sort u} {a b c : α},
                      --   a = b → b = c → a = c

section equality

variables (a b c d : α)
variables (hab : a = b) (hcb : c = b) (hcd : c = d)

example : a = d :=
eq.trans (eq.trans hab (eq.symm hcb)) hcd

example : a = d := (hab.trans hcb.symm).trans hcd

example (f : α → β) (a : α) : (λ x, f x) a = f a := eq.refl _
example (f : α → β) (a : α) : (λ x, f x) a = f a := eq.refl (f a)

example (a : α) (b : α) : (a, b).1 = a := eq.refl _
example (a : α) (b : α) : (a, b).1 = a := eq.refl a

example : 2 + 3 = 5 := eq.refl _
example : 2 + 3 = 5 := eq.refl 5

example : 2 + 3 = 5 := by apply eq.refl
example : 2 + 3 = 5 := by refl

example (f : α → β) (a : α) : (λ x, f x) a = f a := rfl
example (a : α) (b : α) : (a, b).1 = a := rfl
example : 2 + 3 = 5 := rfl

example (α : Type u) (a b : α) (p : α → Prop)
  (h1 : a = b) (h2 : p a) : p b :=
eq.subst h1 h2

example (α : Type u) (a b : α) (p : α → Prop)
  (h1 : a = b) (h2 : p a) : p b :=
h1 ▸ h2

example (α : Type u) (a b : α) (is_apple : α → Prop)
  (a_b_eq : a = b) (a_is_apple : is_apple a) : is_apple b :=
a_b_eq ▸ a_is_apple

variables f g : α → ℕ
variable a_b_eq : a = b
variable f_g_eq : f = g

example : f a = f b := congr_arg f a_b_eq
example : f a = g a := congr_fun f_g_eq a
example : f a = g b := congr f_g_eq a_b_eq

example : g a = f b := congr f_g_eq.symm a_b_eq

example : g b = f a := congr f_g_eq.symm a_b_eq.symm

end equality

section identities

variables a b c d : ℤ

example : a + 0 = a := add_zero a
example : 0 + a = a := zero_add a
example : a * 1 = a := mul_one a
example : 1 * a = a := one_mul a
example : -a + a = 0 := neg_add_self a
example : a + -a = 0 := add_neg_self a
example : a - a = 0 := sub_self a
example : a + b = b + a := add_comm a b
example : a + b + c = a + (b + c) := add_assoc a b c
example : a * b = b * a := mul_comm a b
example : a * b * c = a * (b * c) := mul_assoc a b c
example : a * (b + c) = a * b + a * c := mul_add a b c
example : a * (b + c) = a * b + a * c := left_distrib a b c
example : (a + b) * c = a * c + b * c := add_mul a b c
example : (a + b) * c = a * c + b * c := right_distrib a b c
example : a * (b - c) = a * b - a * c := mul_sub a b c
example : (a - b) * c = a * c - b * c := sub_mul a b c

end identities

section calculation
variables x y z : ℤ

example (x y z : ℕ) : x * (y + z) = x * y + x * z := mul_add x y z
example (x y z : ℕ) : (x + y) * z = x * z + y * z := add_mul x y z
example (x y z : ℕ) : x + y + z = x + (y + z) := add_assoc x y z

example (x y : ℕ) :
  (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
have h1 : (x + y) * (x + y) = (x + y) * x + (x + y) * y,
  from mul_add (x + y) x y,
have h2 : (x + y) * (x + y) = x * x + y * x + (x * y + y * y),
  from (add_mul x y x) ▸ (add_mul x y y) ▸ h1,
h2.trans (add_assoc (x * x + y * x) (x * y) (y * y)).symm
end calculation

/-

Notice that the second implicit parameter to eq.subst, which provides the context in which the substitution is to occur, has type α → Prop. Inferring this predicate therefore requires an instance of higher-order unification. In full generality, the problem of determining whether a higher-order unifier exists is undecidable, and Lean can at best provide imperfect and approximate solutions to the problem. As a result, eq.subst doesn’t always do what you want it to.

-/

section calc_mode

variables (a b c d e : ℕ)
variable h1 : a = b
variable h2 : b = c + 1
variable h3 : c = d
variable h4 : e = 1 + d

example : a = e :=
calc
  a     = b      : h1
    ... = c + 1  : h2
    ... = d + 1  : congr_arg _ h3
    ... = 1 + d  : add_comm d (1 : ℕ)
    ... =  e     : eq.symm h4

-- section variables and variables that only appear in a tactic command or block are not automatically added to the context.
include h1 h2 h3 h4

example : a = e :=
calc
  a     = b      : by rw h1
    ... = c + 1  : by rw h2
    ... = d + 1  : by rw h3
    ... = 1 + d  : by rw add_comm
    ... =  e     : by rw h4

example : a = e :=
calc
  a     = d + 1  : by rw [h1, h2, h3]
    ... = 1 + d  : by rw add_comm
    ... =  e     : by rw h4

example : a = e := by rw [h1, h2, h3, add_comm, h4]

/-
simplify tactic failed to simplify
-/
-- example : a = e := by simp_rw [h1, h2, h3, add_comm, h4]

example : a = e := by simp_rw [h1, h2, h3, h4, add_comm]

example : a = e := by simp only [h1, h2, h3, add_comm, h4]

example : a = e := by simp [h1, h2, h3, add_comm, h4]

end calc_mode

section calc_trans

example (a b c d : ℕ)
  (h1 : a = b) (h2 : b ≤ c) (h3 : c + 1 < d) : a < d :=
calc
  a     = b     : h1
    ... < b + 1 : nat.lt_succ_self b
    ... ≤ c + 1 : nat.succ_le_succ h2
    ... < d     : h3

example (x y : ℕ) :
  (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
calc
  (x + y) * (x + y) = (x + y) * x + (x + y) * y  : by rw mul_add
    ... = x * x + y * x + (x + y) * y            : by rw add_mul
    ... = x * x + y * x + (x * y + y * y)        : by rw add_mul
    ... = x * x + y * x + x * y + y * y          : by rw ←add_assoc

example (x y : ℕ) :
  (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
by rw [mul_add, add_mul, add_mul, ←add_assoc]

example (x y : ℕ) :
  (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
by ring

end calc_trans

namespace existential

open nat

example : ∃ x : ℕ, x > 0 :=
have h : 1 > 0, from zero_lt_succ 0,
exists.intro 1 h

example (x : ℕ) (h : x > 0) : ∃ y, y < x :=
exists.intro 0 h

example (x y z : ℕ) (hxy : x < y) (hyz : y < z) :
  ∃ w, x < w ∧ w < z :=
exists.intro y (and.intro hxy hyz)

example (x y z : ℕ) (hxy : x < y) (hyz : y < z) :
  ∃ w, x < w ∧ w < z :=
begin
 apply exists.intro y,
 exact and.intro hxy hyz
end

#check @exists.intro

example : ∃ x : ℕ, x > 0 :=
⟨1, zero_lt_succ 0⟩

example (x : ℕ) (h : x > 0) : ∃ y, y < x :=
⟨0, h⟩

example (x y z : ℕ) (hxy : x < y) (hyz : y < z) :
  ∃ w, x < w ∧ w < z :=
⟨y, hxy, hyz⟩

section implicit

variable g : ℕ → ℕ → ℕ
variable hg : g 0 0 = 0

theorem gex1 : ∃ x, g x x = x := ⟨0, hg⟩
theorem gex2 : ∃ x, g x 0 = x := ⟨0, hg⟩
theorem gex3 : ∃ x, g 0 0 = x := ⟨0, hg⟩
theorem gex4 : ∃ x, g x x = 0 := ⟨0, hg⟩

set_option pp.implicit true  -- display implicit arguments
#print gex1
#print gex2
#print gex3
#print gex4

end implicit

namespace elim

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
exists.elim h
  (assume w,
    assume hw : p w ∧ q w,
    show ∃ x, q x ∧ p x, from ⟨w, hw.right, hw.left⟩)

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
match h with ⟨w, hw⟩ :=
  ⟨w, hw.right, hw.left⟩
end

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
begin
  obtain ⟨w, hw⟩ := h,
  exact ⟨w, hw.right, hw.left⟩
end

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
begin
  obtain ⟨w, hwl, hwr⟩ := h,
  exact ⟨w, hwr, hwl⟩
end

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
let ⟨w, hpw, hqw⟩ := h in ⟨w, hqw, hpw⟩

example : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x :=
assume ⟨w, hpw, hqw⟩, ⟨w, hqw, hpw⟩

def is_even (a : nat) := ∃ b, a = 2 * b

theorem even_plus_even {a b : nat}
  (h1 : is_even a) (h2 : is_even b) : is_even (a + b) :=
exists.elim h1 (assume w1, assume hw1 : a = 2 * w1,
exists.elim h2 (assume w2, assume hw2 : b = 2 * w2,
  exists.intro (w1 + w2)
    (calc
      a + b = 2 * w1 + 2 * w2  : by rw [hw1, hw2]
        ... = 2*(w1 + w2)      : by rw mul_add)))

theorem even_plus_even' {a b : nat}
  (h1 : is_even a) (h2 : is_even b) : is_even (a + b) :=
match h1, h2 with
  ⟨w1, hw1⟩, ⟨w2, hw2⟩ := ⟨w1 + w2, by rw [hw1, hw2, mul_add]⟩
end

theorem even_plus_even'' {a b : nat}
  (h1 : is_even a) (h2 : is_even b) : is_even (a + b) :=
begin
  obtain ⟨w1, hw1⟩ := h1,
  obtain ⟨w2, hw2⟩ := h2,
  exact ⟨w1 + w2, by rw [hw1, hw2, mul_add]⟩
end

theorem even_plus_even''' {a b : nat}
  (h1 : is_even a) (h2 : is_even b) : is_even (a + b) :=
begin
  obtain ⟨wa, hwa⟩ : is_even a := h1,
  obtain ⟨wb, hwb⟩ : is_even b := h2,
  use wa + wb,
  calc
      a + b = 2 * wa + 2 * wb  : by rw [hwa, hwb]
        ... = 2 * (wa + wb)    : by rw mul_add
end

namespace classical

open classical

example (h : ¬ ∀ x, ¬ p x) : ∃ x, p x :=
by_contradiction
  (assume h1 : ¬ ∃ x, p x,
    have h2 : ∀ x, ¬ p x, from
      assume x,
      assume h3 : p x,
      have h4 : ∃ x, p x, from ⟨x, h3⟩,
      show false, from h1 h4,
    show false, from h h2)

namespace identities

variable a : α
variable h : Prop

example : (∃ x : α, h) → h := sorry
example : h → (∃ x : α, h) := sorry
example : (∃ x, p x ∧ h) ↔ (∃ x, p x) ∧ h := sorry
example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) := sorry

example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) := sorry
example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) := sorry
example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) := sorry
example : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) := sorry

example : (∀ x, p x → h) ↔ (∃ x, p x) → h := sorry
example : (∃ x, p x → h) ↔ (∀ x, p x) → h := sorry
example : (∃ x, h → p x) ↔ (h → ∃ x, p x) := sorry

example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
iff.intro
  (assume ⟨a, (h1 : p a ∨ q a)⟩,
    or.elim h1
      (assume hpa : p a, or.inl ⟨a, hpa⟩)
      (assume hqa : q a, or.inr ⟨a, hqa⟩))
  (assume h : (∃ x, p x) ∨ (∃ x, q x),
    or.elim h
      (assume ⟨a, hpa⟩, ⟨a, (or.inl hpa)⟩)
      (assume ⟨a, hqa⟩, ⟨a, (or.inr hqa)⟩))

-- ERROR
-- example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
-- begin
-- exact iff.intro
--   (assume ⟨a, (h1 : p a ∨ q a)⟩,
--     or.elim h1
--       (assume hpa : p a, or.inl ⟨a, hpa⟩)
--       (assume hqa : q a, or.inr ⟨a, hqa⟩))
--   (assume h : (∃ x, p x) ∨ (∃ x, q x),
--     or.elim h
--       (assume ⟨a, hpa⟩, ⟨a, (or.inl hpa)⟩)
--       (assume ⟨a, hqa⟩, ⟨a, (or.inr hqa)⟩))
-- end

example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
begin
  apply iff.intro,
  {
    assume h,
    obtain ⟨a, ha⟩ := h,
    apply ha.elim,
    {
      assume hpa,
      exact or.inl ⟨a, hpa⟩
    },
    {
      assume hqa,
      exact or.inr ⟨a, hqa⟩
    }
  },
  {
    sorry
  }
  -- (assume ⟨a, (h1 : p a ∨ q a)⟩,
  --   or.elim h1
  --     (assume hpa : p a, or.inl ⟨a, hpa⟩)
  --     (assume hqa : q a, or.inr ⟨a, hqa⟩))
  -- (assume h : (∃ x, p x) ∨ (∃ x, q x),
  --   or.elim h
  --     (assume ⟨a, hpa⟩, ⟨a, (or.inl hpa)⟩)
  --     (assume ⟨a, hqa⟩, ⟨a, (or.inr hqa)⟩))
end

example : (∃ x, p x → h) ↔ (∀ x, p x) → h :=
iff.intro
  (assume ⟨b, (hb : p b → h)⟩,
    assume h2 : ∀ x, p x,
    show h, from  hb (h2 b))
  (assume h1 : (∀ x, p x) → h,
    show ∃ x, p x → h, from
      classical.by_cases
        (assume hap : ∀ x, p x, ⟨a, λ h', h1 hap⟩)
        (assume hnap : ¬ ∀ x, p x,
          by_contradiction
            (assume hnex : ¬ ∃ x, p x → h,
              have hap : ∀ x, p x, from
                assume x,
                by_contradiction
                  (assume hnp : ¬ p x,
                    have hex : ∃ x, p x → h,
                      from ⟨x, (assume hp, absurd hp hnp)⟩,
                    show false, from hnex hex),
              show false, from hnap hap)))

variable f : ℕ → ℕ
variable hf : ∀ x : ℕ, f x ≤ f (x + 1)

example : f 0 ≤ f 3 :=
have f 0 ≤ f 1, from hf 0,
have f 0 ≤ f 2, from le_trans this (hf 1),
show f 0 ≤ f 3, from le_trans this (hf 2)

example : f 0 ≤ f 3 :=
have f 0 ≤ f 1, from hf 0,
have f 0 ≤ f 2, from le_trans (by assumption) (hf 1),
show f 0 ≤ f 3, from le_trans (by assumption) (hf 2)

example : f 0 ≥ f 1 → f 1 ≥ f 2 → f 0 = f 2 :=
assume : f 0 ≥ f 1,
assume : f 1 ≥ f 2,
have f 0 ≥ f 2, from le_trans this ‹f 0 ≥ f 1›,
have f 0 ≤ f 2, from le_trans (hf 0) (hf 1),
show f 0 = f 2, from le_antisymm this ‹f 0 ≥ f 2›

example : f 0 ≤ f 3 :=
have f 0 ≤ f 1, from hf 0,
have f 1 ≤ f 2, from hf 1,
have f 2 ≤ f 3, from hf 2,
show f 0 ≤ f 3, from le_trans ‹f 0 ≤ f 1›
                       (le_trans ‹f 1 ≤ f 2› ‹f 2 ≤ f 3›)

example (hf : ∀ x : ℕ, f x ≤ f (x + 1)) : f 0 ≤ f 3 :=
begin
have : f 0 ≤ f 1, from hf 0,
have : f 1 ≤ f 2, from hf 1,
have : f 2 ≤ f 3, from hf 2,
show  f 0 ≤ f 3, from le_trans ‹f 0 ≤ f 1›
                       (le_trans ‹f 1 ≤ f 2› ‹f 2 ≤ f 3›)
end
-- \f< ›
example (n : ℕ) : ℕ := ‹ℕ›

example : f 0 ≥ f 1 → f 0 = f 1 :=
assume : f 0 ≥ f 1,
show f 0 = f 1, from le_antisymm (hf 0) this

example (hf : ∀ x : ℕ, f x ≤ f (x + 1)): f 0 ≥ f 1 → f 0 = f 1 :=
begin
assume : f 0 ≥ f 1,
show f 0 = f 1, from le_antisymm (hf 0) this
end

end identities

end classical

end elim

end existential

namespace ex_04_01

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
begin
  apply iff.intro,
  {
    assume hapq,
    apply and.intro,
    {
      assume x,
      exact (hapq x).left
    },
    {
      assume x,
      exact (hapq x).right
    }
  },
  {
    assume hapaq,
    assume x,
    apply and.intro,
    {
      exact hapaq.left x,
    },
    {
      exact hapaq.right x,
    }
  }
end

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
iff.intro
  (assume : ∀ x, p x ∧ q x, ⟨
    (assume x, (this x).left),
    (assume x, (this x).right)
  ⟩)
  (
    assume : (∀ x, p x) ∧ ∀ x, q x,
    assume x,
    ⟨this.left x, this.right x⟩
  )

example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
assume hpq hp x, (hpq x) (hp x)

example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
assume hpq x,
or.elim hpq
  (assume hp, or.inl (hp x))
  (assume hq, or.inr (hq x))

end ex_04_01

end chap_04


import data.int.order.basic
import data.nat.pow

open nat

example {a b c d e : nat} (h1 : a ≤ b) (h2 : c ≤ d) (h3 : 0 ≤ e) :
a + c * e + a + c + 0 ≤ b + d * e + b + d + e :=
add_le_add (add_le_add (add_le_add (add_le_add h1 (mul_le_mul_of_nonneg_right h2 h3)) h1 ) h2) h3

example {a b c d e : nat} (h1 : a ≤ b) (h2 : c ≤ d) (h3 : 0 ≤ e) :
a + c * e + a + c + 0 ≤ b + d * e + b + d + e :=
by apply_rules [add_le_add, mul_le_mul_of_nonneg_right]

@[user_attribute]
meta def mono_rules : user_attribute :=
{ name := `mono_rules,
  descr := "lemmas usable to prove monotonicity" }
attribute [mono_rules] add_le_add mul_le_mul_of_nonneg_right

example {a b c d e : nat} (h1 : a ≤ b) (h2 : c ≤ d) (h3 : 0 ≤ e) :
a + c * e + a + c + 0 ≤ b + d * e + b + d + e :=
by apply_rules with mono_rules

example {a b c d e : nat} (h1 : a ≤ b) (h2 : c ≤ d) (h3 : 0 ≤ e) :
a + c * e + a + c + 0 ≤ b + d * e + b + d + e :=
by apply_rules with mono_rules

-- test that metavariables created for implicit arguments don't get stuck
example (P : ℕ → Type) (f : Π {n : ℕ}, P n → P (n + 1)) (g : P 0) : P 2 :=
begin
  apply_rules [f, g],
end

/-
Test that there are no conflicts between attribute and declaration names.
After #13227 there is little chance of ambiguity here, but this is still a valid test.
-/

@[user_attribute]
meta def p_rules_attr : user_attribute :=
{ name := `p_rules,
  descr := "testing" }

constant P : ℕ → Prop

axiom p_rules : P 0

@[p_rules] axiom foo : P 10

example : P 0 := by success_if_fail {apply_rules with p_rules}; apply_rules [p_rules]
example : P 10 := by apply_rules with p_rules 60

attribute [p_rules] pow_lt_pow_of_lt_left

open nat

-- This tests for the following bug:
-- https://leanprover.zulipchat.com/#narrow/stream/113488-general/topic/namespace.20affects.20behaviour.20of.20.60apply_list_expr.60
example {x y : ℤ} (n : ℕ) (h1 : x < y) (h2 : 0 ≤ x) (h3 : 0 < n) : x ^ n < y ^ n :=
by apply_rules with p_rules