import number_theory.padics.padic_integers

--OUTPUT 1
theorem p_adic_units (p : ℕ) (x : ℕ) (h : coprime x p) : is_unit (x : ℤ_[p]) :=
begin
  have h1 : (x : ℤ_[p]) = 0, from by auto [not_coprime_zero],
  have h2 : ¬(x : ℤ_[p]) = 0, from by auto [h, h1],
  have h3 : (x : ℤ_[p]) ≠ 0, from by auto [h2],
  have h4 : (x : ℤ_[p]) ≠ 0 := by auto [h3],
  have h5 : (x : ℤ_[p]) ≠ 0, from by auto [h4, h3],
  show is_unit _, from by auto [h5],
end

--OUTPUT 2
theorem p_adic_units (p : ℕ) (x : ℕ) : coprime p x → (∃ y : ℕ, x * y = 1) :=
begin
  assume h1 : coprime p x,
  have h2 : x ∣ 1, from by auto [h1],
  have h3 : ∃ y : ℕ, x * y = 1, from by auto [dvd_def],
  show ∃ y : ℕ, x * y = 1, from h3,
end

--OUTPUT 3
theorem p_adic_units (p : ℕ) (hp : prime p) (x : ℕ) (hx : coprime p x) : x ∈ units (padic_int p) :=
begin
  have h : ¬(x ∈ units (padic_int p)) → ¬(coprime p x), by auto [padic_int.units_eq_norm_one, padic_int.norm_le],
  have h2 : ¬(x ∈ units (padic_int p)) → (x ∣ p), from by auto [padic_int.norm_lt_iff],
  by_contradiction,
  have h3 : ¬(x ∈ units (padic_int p)), from by auto [h, h2],
  show false, from by auto [h3] using [hx],
end

--OUTPUT 4
theorem p_adic_units : ∀ (p : ℕ) (x : ℕ), prime p → coprime x p → x ∈ units (integers_mod p) :=
begin
  assume p x h1 h2,
  have h3 : x ∈ units (integers_mod p) ↔ (p ∣ x) = ff, from by auto [units_iff_coprime],
  have h4 : ¬(p ∣ x) = ff, from by auto [not_iff_not_of_iff, h3, h2],
  show x ∈ units (integers_mod p), from by auto [h3, h4],
end

--OUTPUT 5
theorem padic_units (p : ℕ) [h : nat.prime p] (x : ℕ) : nat.coprime p x → x ∈ units (padic_int p) :=
begin
  intro hx,
  have h1 : x ∉ units (padic_int p), from by auto [padic_int.norm_eq_one_iff_units, not_not_intro, hx],
  have h2 : x ∈ padic_int.val_semiring.nonunits, from by auto [h1, not_not_intro],
  have h3 : x ∈ padic_int.val_semiring.nonunits ↔ x ∈ padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_semiring.ideal.map (padic_int.val_semiring.ideal.comap (padic_int.val_sem
end --Needs more than 2000 tokens!

--OUTPUT 6
theorem p_adic_units (p : ℕ) [prime p] (x : ℕ) : coprime p x → is_unit (p_adic_int p x) :=
begin
  intro h1,
  have h2 : p_adic_int p x ≠ 0, from by auto [p_adic_int],
  have h3 : p_adic_norm p (p_adic_int p x) = 1, from by auto [h1, p_adic_norm, p_adic_int, p_adic_norm_def, p_adic_int_def, p_adic_int_eq_dvd, prime.dvd_not_coprime],
  show is_unit (p_adic_int p x), from by auto [h2, h3, p_adic_norm],
end

--OUTPUT 7
theorem p_adic_units (p : ℕ) (h : prime p) (x : ℕ) (hx : nat.coprime x p) : is_unit (p_adic_int.mk_rat x) :=
begin
  have h1 : (p_adic_int.mk_rat x).norm = 1, from by auto [hx, p_adic_int.norm_mk_rat, h],
  have h2 : (p_adic_int.mk_rat x).norm ≠ 1, from by auto [hx, p_adic_int.norm_mk_rat],
  have h3 : (p_adic_int.mk_rat x).norm < 1, from by auto [p_adic_int.norm_mk_rat],
  have h4 : p ∣ x, from by auto [hx, p_adic_int.norm_mk_rat, h],
  have h5 : (p_adic_int.mk_rat x) * (p_adic_int.mk_rat p) = p_adic_int.mk_rat x * p_adic_int.mk_rat p, from by auto [p_adic_int.mk_rat, rat.mk_eq_mk],
  have h6 : (p_adic_int.mk_rat x) * (p_adic_int.mk_rat p) = p_adic_int.mk_rat (x * p), from by auto [rat.mk_eq_mk, p_adic_int.mk_rat],
  have h7 : (p_adic_int.mk_rat x) * (p_adic_int.mk_rat p) = p_adic_int.mk_rat x, from by auto [nat.mul_comm, rat.mk_eq_mk, p_adic_int.mk_rat],
  show is_unit (p_adic_int.mk_rat x), from by auto [hx, p_adic_int.is_unit_mk_rat, h, h1, h2, h3, h4, h5, h6, h7],
end

--OUTPUT 8
theorem p_adic_units (p : ℕ) (hp : prime p) (x : ℕ) (hx : coprime p x) : x ∈ units (ℤ p) :=
begin
  have h1 : x ∈ units (ℤ p) ↔ ∣x∣ p = 1, from by auto [units_iff_norm_eq_one],
  have h2 : ¬ x ∈ units (ℤ p) ↔ ¬ ∣x∣ p = 1, from by auto [not_iff],
  have h3 : ¬ x ∈ units (ℤ p) ↔ ∣x∣ p ≠ 1, from by auto [not_iff],
  have h4 : ¬ x ∈ units (ℤ p) ↔ ∣x∣ p < 1, from by auto [norm_lt_one, not_iff],
  have h5 : ¬ x ∈ units (ℤ p) → ∣x∣ p < 1, from by auto [h4.symm],
  have h6 : ¬ ∣x∣ p < 1 → x ∈ units (ℤ p), from by auto [h4, not_iff],
  have h7 : ¬ ∣x∣ p < 1 → p ∣ x, from by auto [hx, prime.not_dvd_iff_ne_one, not_iff, norm_lt_one, dvd_iff_norm_le_one],
  have h8 : ∣x∣ p < 1 → ¬ p ∣ x, from by auto [hx, prime.not_dvd_iff_ne_one, not_iff, norm_le_one, norm_lt_one, not_lt],
  have h9 : ¬ p ∣ x → x ∈ units (ℤ p), from by auto [hx, prime.not_dvd_iff_ne_one, not_iff, norm_le_one, dvd_iff_norm_le_one, h6],

  have h10 : ¬ p ∣ x ↔ ¬ x ∈ units (ℤ p), from by auto [h7, h8, h9],
  have h11 : ¬ p ∣ x ↔ x ∈ units (ℤ p), from by auto [not_iff, h10],

  have h12 : ¬ p ∣ x → x ∈ units (ℤ p), from by auto [h11.symm],
  have h13 : ¬ x ∈ units (ℤ p) → p ∣ x, from by auto [h11],
  have h14 : ¬ x ∈ units (ℤ p) → ¬ p ∣ x, from by auto [hx, prime.not_dvd_iff_ne_one, not_iff, h13],

  show x ∈ units (ℤ p), from by auto [hx, prime.not_dvd_iff_ne_one, not_iff, h12, h14],
end

/- FEW SHOT PROMPTS TO CODEX(START)
/--`theorem`
Power Set is Closed under Intersection
Let $S$ be a set.

Let $\powerset S$ be the power set of $S$.

Then:
:$\forall A, B \in \powerset S: A \cap B \in \powerset S$
`proof`
Let $A, B \in \powerset S$.

Then by the definition of power set, $A \subseteq S$ and $B \subseteq S$.

From Intersection is Subset we have that $A \cap B \subseteq A$.

It follows from Subset Relation is Transitive that $A \cap B \subseteq S$.

Thus $A \cap B \in \powerset S$ and closure is proved.
{{qed}}
-/
theorem power_set_intersection_closed {α : Type*} (S : set α) : ∀ A B ∈ 𝒫 S, (A ∩ B) ∈ 𝒫 S :=
begin
  assume (A : set α) (hA : A ∈ 𝒫 S) (B : set α) (hB : B ∈ 𝒫 S),
  have h1 : (A ⊆ S) ∧ (B ⊆ S), from by auto [set.subset_of_mem_powerset, set.subset_of_mem_powerset],
  have h2 : (A ∩ B) ⊆ A, from by auto [set.inter_subset_left],
  have h3 : (A ∩ B) ⊆ S, from by auto [set.subset.trans],
  show (A ∩ B) ∈  𝒫 S, from by auto [set.mem_powerset],
end

/--`theorem`
Square of Sum
 :$\forall x, y \in \R: \paren {x + y}^2 = x^2 + 2 x y + y^2$
`proof`
Follows from the distribution of multiplication over addition:

{{begin-eqn}}
{{eqn | l = \left({x + y}\right)^2
      | r = \left({x + y}\right) \cdot \left({x + y}\right)
}}
{{eqn | r = x \cdot \left({x + y}\right) + y \cdot \left({x + y}\right)
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x \cdot x + x \cdot y + y \cdot x + y \cdot y
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x^2 + 2xy + y^2
      | c =
}}
{{end-eqn}}
{{qed}}
-/
theorem square_of_sum (x y : ℝ) : (x + y)^2 = (x^2 + 2*x*y + y^2) :=
begin
  calc (x + y)^2 = (x+y)*(x+y) : by auto [sq]
  ... = x*(x+y) + y*(x+y) : by auto [add_mul]
  ... = x*x + x*y + y*x + y*y : by auto [mul_comm, add_mul] using [ring]
  ... = x^2 + 2*x*y + y^2 : by auto [sq, mul_comm] using [ring]
end

/--`theorem`
Identity of Group is Unique
Let $\struct {G, \circ}$ be a group. Then there is a unique identity element $e \in G$.
`proof`
From Group has Latin Square Property, there exists a unique $x \in G$ such that:
:$a x = b$

and there exists a unique $y \in G$ such that:
:$y a = b$

Setting $b = a$, this becomes:

There exists a unique $x \in G$ such that:
:$a x = a$

and there exists a unique $y \in G$ such that:
:$y a = a$

These $x$ and $y$ are both $e$, by definition of identity element.
{{qed}}
-/
theorem group_identity_unique {G : Type*} [group G] : ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a :=
begin
  have h1 : ∀ a b : G, ∃! x : G, a * x = b, from by auto using [use (a⁻¹ * b)],
  have h2 : ∀ a b : G, ∃! y : G, y * a = b, from by auto using [use b * a⁻¹],

  have h3 : ∀ a : G, ∃! x : G, a * x = a, from by auto [h1],
  have h4 : ∀ a : G, ∃! y : G, y * a = a, from by auto [h2],

  have h5 : ∀ a : G, classical.some (h3 a).exists = (1 : G), from by auto [exists_unique.unique, h3, classical.some_spec, exists_unique.exists, mul_one],
  have h6 : ∀ a : G, classical.some (h4 a).exists = (1 : G), from by auto [exists_unique.unique, h4, classical.some_spec, exists_unique.exists, one_mul],

  show ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a, from by auto [h3, h4, exists_unique.unique, classical.some_spec, exists_unique.exists] using [use (1 : G)],
end

/--`theorem`
p-adic units
Given a prime number $p$ and a natural number $x$, if $x$ is coprime to $p$, then $x$ is a unit in the $p$-adic integers.
`proof`
Let us prove the contrapositive, that is, is $x$ is not a unit of $\mathbb{Z}_p$, then $x$ is not coprime to $p$.
We know that any element of $\mathbb{Z}_p$ is a unit if and only if it has norm 1, thus $\Vert x \Vert \neq 1$. Also, since every element of $\mathbb{Z}_p$ must have norm less than or equal to 1, we conclude that $\Vert x \Vert < 1$.
But this must imply that $p | x$.
This completes our proof.

QED
-/
theorem
FEW SHOT PROMPTS TO CODEX(END)-/


import tactic
open list

variable {α : Type}

@[simp]
lemma drop_eq_nil_of_le
  {l : list α}
  {k : ℕ}
  (h : length l ≤ k)
  : drop k l = [] :=
by simpa [←length_eq_zero] using nat.sub_eq_zero_of_le h

theorem drop_nil :
  ∀ n, drop n [] = ([] : list α) :=
λ _, list.drop_eq_nil_of_le (nat.zero_le _)


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

universes u₁ u₂ v

namespace Mathlib

/-!
# Preserving binary products

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

In particular, we show that `prod_comparison G X Y` is an isomorphism iff `G` preserves
the product of `X` and `Y`.
-/

namespace category_theory.limits

/--
The map of a binary fan is a limit iff the fork consisting of the mapped morphisms is a limit. This
essentially lets us commute `binary_fan.mk` with `functor.map_cone`.
-/
def is_limit_map_cone_binary_fan_equiv {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) {P : C} {X : C} {Y : C} (f : P ⟶ X) (g : P ⟶ Y) : is_limit (functor.map_cone G (binary_fan.mk f g)) ≃ is_limit (binary_fan.mk (functor.map G f) (functor.map G g)) :=
  equiv.trans
    (equiv.symm
      (is_limit.postcompose_hom_equiv (diagram_iso_pair (pair X Y ⋙ G)) (functor.map_cone G (binary_fan.mk f g))))
    (is_limit.equiv_iso_limit
      (cones.ext
        (iso.refl
          (cone.X
            (functor.obj (cones.postcompose (iso.hom (diagram_iso_pair (pair X Y ⋙ G))))
              (functor.map_cone G (binary_fan.mk f g)))))
        sorry))

/-- The property of preserving products expressed in terms of binary fans. -/
def map_is_limit_of_preserves_of_is_limit {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) {P : C} {X : C} {Y : C} (f : P ⟶ X) (g : P ⟶ Y) [preserves_limit (pair X Y) G] (l : is_limit (binary_fan.mk f g)) : is_limit (binary_fan.mk (functor.map G f) (functor.map G g)) :=
  coe_fn (is_limit_map_cone_binary_fan_equiv G f g) (preserves_limit.preserves l)

/-- The property of reflecting products expressed in terms of binary fans. -/
def is_limit_of_reflects_of_map_is_limit {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) {P : C} {X : C} {Y : C} (f : P ⟶ X) (g : P ⟶ Y) [reflects_limit (pair X Y) G] (l : is_limit (binary_fan.mk (functor.map G f) (functor.map G g))) : is_limit (binary_fan.mk f g) :=
  reflects_limit.reflects (coe_fn (equiv.symm (is_limit_map_cone_binary_fan_equiv G f g)) l)

/--
If `G` preserves binary products and `C` has them, then the binary fan constructed of the mapped
morphisms of the binary product cone is a limit.
-/
def is_limit_of_has_binary_product_of_preserves_limit {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) (X : C) (Y : C) [has_binary_product X Y] [preserves_limit (pair X Y) G] : is_limit (binary_fan.mk (functor.map G prod.fst) (functor.map G prod.snd)) :=
  map_is_limit_of_preserves_of_is_limit G prod.fst prod.snd (prod_is_prod X Y)

/--
If the product comparison map for `G` at `(X,Y)` is an isomorphism, then `G` preserves the
pair of `(X,Y)`.
-/
def preserves_pair.of_iso_comparison {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) (X : C) (Y : C) [has_binary_product X Y] [has_binary_product (functor.obj G X) (functor.obj G Y)] [i : is_iso (prod_comparison G X Y)] : preserves_limit (pair X Y) G :=
  preserves_limit_of_preserves_limit_cone (prod_is_prod X Y)
    (coe_fn (equiv.symm (is_limit_map_cone_binary_fan_equiv G prod.fst prod.snd))
      (is_limit.of_point_iso (limit.is_limit (pair (functor.obj G X) (functor.obj G Y)))))

/--
If `G` preserves the product of `(X,Y)`, then the product comparison map for `G` at `(X,Y)` is
an isomorphism.
-/
def preserves_pair.iso {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) (X : C) (Y : C) [has_binary_product X Y] [has_binary_product (functor.obj G X) (functor.obj G Y)] [preserves_limit (pair X Y) G] : functor.obj G (X ⨯ Y) ≅ functor.obj G X ⨯ functor.obj G Y :=
  is_limit.cone_point_unique_up_to_iso (is_limit_of_has_binary_product_of_preserves_limit G X Y)
    (limit.is_limit (pair (functor.obj G X) (functor.obj G Y)))

@[simp] theorem preserves_pair.iso_hom {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) (X : C) (Y : C) [has_binary_product X Y] [has_binary_product (functor.obj G X) (functor.obj G Y)] [preserves_limit (pair X Y) G] : iso.hom (preserves_pair.iso G X Y) = prod_comparison G X Y :=
  rfl

protected instance prod_comparison.category_theory.is_iso {C : Type u₁} [category C] {D : Type u₂} [category D] (G : C ⥤ D) (X : C) (Y : C) [has_binary_product X Y] [has_binary_product (functor.obj G X) (functor.obj G Y)] [preserves_limit (pair X Y) G] : is_iso (prod_comparison G X Y) :=
  eq.mpr sorry (is_iso.of_iso (preserves_pair.iso G X Y))


import logic.equiv.basic
import tactic.simp_result

open tactic

-- Check that we can walk.
example : true :=
by { simp_result { trivial } }

-- Comparison without `dsimp_result`:
example : true :=
begin
  exact (id trivial),
  (do `(id trivial) ← result, skip),
  success_if_fail { (do `(trivial) ← result, skip) },
end

-- Check that `dsimp_result` removes unnecessary `id`s.
example : true :=
begin
  dsimp_result { exact (id trivial) },
  success_if_fail { (do `(id trivial) ← result, skip) },
  (do `(trivial) ← result, skip),
end

-- Comparison without `dsimp_result`:
example (a : ℕ) (b : list ℕ) (h : b.length < a) : ℕ :=
begin
  revert a,
  intros a h,
  exact 0,
  (do `((λ (h : list.length _ < _), 0) _) ← result, skip),
  success_if_fail { (do `(0) ← result, skip) },
end

-- Check that `dsimp_result` does beta-reductions after `revert`.
example (a : ℕ) (b : list ℕ) (h : b.length < a) : ℕ :=
begin
  dsimp_result
  { revert a,
    intros a h,
    exact 0, },
  success_if_fail { (do `((λ (h : list.length _ < _), 0) _) ← result, skip), },
  (do `(0) ← result, skip),
end

-- This test tactic internally sets `pp.all ff`, and `pp.proofs tt`.
-- This isn't very robust, as the user setting any other `pp` options
-- will cause tests to break, but I don't think it needs to be.
meta def guard_result_pp (s : string) : tactic unit :=
do
  o ← get_options,
  set_options ((o.set_bool `pp.all ff).set_bool `pp.proofs tt),
  r ← (to_string <$> (result >>= pp)),
  guard (r = s) <|> fail format!"result was {r} but expected {s}"

-- Comparison without `simp_result`:
example {α β : Type} (e : α ≃ β) (a : α) : β :=
begin
  exact e (e.symm (e a)),
  guard_result_pp "⇑e (⇑(equiv.symm e) (⇑e a))",
end

-- Check that `simp_result` applies non-definitional simplifications to the result.
example {α β : Type} (e : α ≃ β) (a : α) : β :=
begin
  simp_result { exact e (e.symm (e a)) },
  guard_result_pp "⇑e a",
end

-- Check that `simp_result only [...]` behaves as expected.
example {α β : Type} (e : α ≃ β) (a : α) : β :=
begin
  simp_result only [equiv.apply_symm_apply] { exact e (e.symm (e a)) },
  guard_result_pp "⇑e a",
end

-- Check that `simp_result only []` does not simplify.
-- (Note the `simp_result` succeeds even if no simplification occurs.)
example {α β : Type} (e : α ≃ β) (a : α) : β :=
begin
  simp_result only [] { exact e (e.symm (e a)) },
  guard_result_pp "⇑e (⇑(equiv.symm e) (⇑e a))",
end

-- Comparison without `simp_result`
example {α : Type} (a b : α) (h : a = b) : ℕ :=
begin
  subst h,
  exact 0,
  guard_result_pp "eq.rec 0 h",
end

-- Check that we can remove `eq.rec` transports through constant families
-- introduced by irrelevant use of `subst`.
example {α : Type} (a b : α) (h : a = b) : ℕ :=
begin
  simp_result only [eq_rec_constant]
  { subst h,
    exact 0, },
  guard_result_pp "0",
end

-- Check that `simp_result` performs simplifications on all results.
example : ℕ × ℕ :=
begin
  split,
  simp_result
  { exact id 0,
    exact id 1, },
  guard_result_pp "(0, 1)",
end

-- Check that `simp_result` can cope with incomplete goals.
example {α β : Type} (e : α ≃ β) (a : α) : β :=
begin
  simp_result { apply e.to_fun, apply e.inv_fun, apply e.to_fun, },
  guard_result_pp "⇑e ?m_1",
  exact a,
end

-- Check that we can:
-- * cope with metavariables in the result
-- * perform beta redex after `revert`
-- * simplify `eq.rec` after `subst`
example {α β : Type} (e : α ≃ β) (S : has_mul α) : has_mul β :=
begin
  fconstructor,
  simp_result
  { have mul := S.mul,
    have e' := equiv.arrow_congr e (equiv.arrow_congr e e),
    have h : mul = e'.symm (e' mul) := by simp,
    revert h,
    generalize : e' mul = mul',
    intro h,
    subst h, },
  exact mul',
  guard_result_pp "{mul := ⇑(equiv.arrow_congr e (equiv.arrow_congr e e)) has_mul.mul}",
end


/-
Copyright (c) 2016 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura

Theorems that require decidability hypotheses are in the namespace "decidable".
Classical versions are in the namespace "classical".

Note: in the presence of automation, this whole file may be unnecessary. On the other hand,
maybe it is useful for writing automation.
-/
import data.prod tactic.cache

/-
    miscellany

    TODO: move elsewhere
-/

section miscellany

variables {α : Type*} {β : Type*}

def empty.elim {C : Sort*} : empty → C.

instance : subsingleton empty := ⟨λa, a.elim⟩

instance : decidable_eq empty := λa, a.elim

@[priority 0] instance decidable_eq_of_subsingleton
  {α} [subsingleton α] : decidable_eq α
| a b := is_true (subsingleton.elim a b)

/- Add an instance to "undo" coercion transitivity into a chain of coercions, because
   most simp lemmas are stated with respect to simple coercions and will not match when
   part of a chain. -/
@[simp] theorem coe_coe {α β γ} [has_coe α β] [has_coe_t β γ]
  (a : α) : (a : γ) = (a : β) := rfl

@[simp] theorem coe_fn_coe_trans
  {α β γ} [has_coe α β] [has_coe_t_aux β γ] [has_coe_to_fun γ]
  (x : α) : @coe_fn α _ x = @coe_fn β _ x := rfl

@[simp] theorem coe_fn_coe_base
  {α β} [has_coe α β] [has_coe_to_fun β]
  (x : α) : @coe_fn α _ x = @coe_fn β _ x := rfl

@[simp] theorem coe_sort_coe_trans
  {α β γ} [has_coe α β] [has_coe_t_aux β γ] [has_coe_to_sort γ]
  (x : α) : @coe_sort α _ x = @coe_sort β _ x := rfl

@[simp] theorem coe_sort_coe_base
  {α β} [has_coe α β] [has_coe_to_sort β]
  (x : α) : @coe_sort α _ x = @coe_sort β _ x := rfl

/-- `pempty` is the universe-polymorphic analogue of `empty`. -/
@[derive decidable_eq]
inductive {u} pempty : Sort u

def pempty.elim {C : Sort*} : pempty → C.

instance subsingleton_pempty : subsingleton pempty := ⟨λa, a.elim⟩

end miscellany

/-
    propositional connectives
-/

@[simp] theorem false_ne_true : false ≠ true
| h := h.symm ▸ trivial

section propositional
variables {a b c d : Prop}

/- implies -/

theorem iff_of_eq (e : a = b) : a ↔ b := e ▸ iff.rfl

theorem iff_iff_eq : (a ↔ b) ↔ a = b := ⟨propext, iff_of_eq⟩

@[simp] theorem imp_self : (a → a) ↔ true := iff_true_intro id

theorem imp_intro {α β} (h : α) (h₂ : β) : α := h

theorem imp_false : (a → false) ↔ ¬ a := iff.rfl

theorem imp_and_distrib {α} : (α → b ∧ c) ↔ (α → b) ∧ (α → c) :=
⟨λ h, ⟨λ ha, (h ha).left, λ ha, (h ha).right⟩,
 λ h ha, ⟨h.left ha, h.right ha⟩⟩

@[simp] theorem and_imp : (a ∧ b → c) ↔ (a → b → c) :=
iff.intro (λ h ha hb, h ⟨ha, hb⟩) (λ h ⟨ha, hb⟩, h ha hb)

theorem iff_def : (a ↔ b) ↔ (a → b) ∧ (b → a) :=
iff_iff_implies_and_implies _ _

theorem iff_def' : (a ↔ b) ↔ (b → a) ∧ (a → b) :=
iff_def.trans and.comm

@[simp] theorem imp_true_iff {α : Sort*} : (α → true) ↔ true :=
iff_true_intro $ λ_, trivial

@[simp] theorem imp_iff_right (ha : a) : (a → b) ↔ b :=
⟨λf, f ha, imp_intro⟩

/- not -/

theorem not.elim {α : Sort*} (H1 : ¬a) (H2 : a) : α := absurd H2 H1

@[reducible] theorem not.imp {a b : Prop} (H2 : ¬b) (H1 : a → b) : ¬a := mt H1 H2

theorem not_not_of_not_imp : ¬(a → b) → ¬¬a :=
mt not.elim

theorem not_of_not_imp {α} : ¬(α → b) → ¬b :=
mt imp_intro

theorem dec_em (p : Prop) [decidable p] : p ∨ ¬p := decidable.em p

theorem by_contradiction {p} [decidable p] : (¬p → false) → p :=
decidable.by_contradiction

@[simp] theorem not_not [decidable a] : ¬¬a ↔ a :=
iff.intro by_contradiction not_not_intro

theorem of_not_not [decidable a] : ¬¬a → a :=
by_contradiction

theorem of_not_imp [decidable a] (h : ¬ (a → b)) : a :=
by_contradiction (not_not_of_not_imp h)

theorem not.imp_symm [decidable a] (h : ¬a → b) (hb : ¬b) : a :=
by_contradiction $ hb ∘ h

theorem not_imp_comm [decidable a] [decidable b] : (¬a → b) ↔ (¬b → a) :=
⟨not.imp_symm, not.imp_symm⟩

theorem imp.swap : (a → b → c) ↔ (b → a → c) :=
⟨function.swap, function.swap⟩

theorem imp_not_comm : (a → ¬b) ↔ (b → ¬a) :=
imp.swap

/- and -/

theorem not_and_of_not_left (b : Prop) : ¬a → ¬(a ∧ b) :=
mt and.left

theorem not_and_of_not_right (a : Prop) {b : Prop} : ¬b → ¬(a ∧ b) :=
mt and.right

theorem and.imp_left (h : a → b) : a ∧ c → b ∧ c :=
and.imp h id

theorem and.imp_right (h : a → b) : c ∧ a → c ∧ b :=
and.imp id h

lemma and.right_comm : (a ∧ b) ∧ c ↔ (a ∧ c) ∧ b :=
by simp [and.left_comm, and.comm]

lemma and.rotate : a ∧ b ∧ c ↔ b ∧ c ∧ a :=
by simp [and.left_comm, and.comm]

theorem and_not_self_iff (a : Prop) : a ∧ ¬ a ↔ false :=
iff.intro (assume h, (h.right) (h.left)) (assume h, h.elim)

theorem not_and_self_iff (a : Prop) : ¬ a ∧ a ↔ false :=
iff.intro (assume ⟨hna, ha⟩, hna ha) false.elim

theorem and_iff_left_of_imp {a b : Prop} (h : a → b) : (a ∧ b) ↔ a :=
iff.intro and.left (λ ha, ⟨ha, h ha⟩)

theorem and_iff_right_of_imp {a b : Prop} (h : b → a) : (a ∧ b) ↔ b :=
iff.intro and.right (λ hb, ⟨h hb, hb⟩)

lemma and.congr_right_iff : (a ∧ b ↔ a ∧ c) ↔ (a → (b ↔ c)) :=
⟨λ h ha, by simp [ha] at h; exact h, and_congr_right⟩

/- or -/

theorem or_of_or_of_imp_of_imp (h₁ : a ∨ b) (h₂ : a → c) (h₃ : b → d) : c ∨ d :=
or.imp h₂ h₃ h₁

theorem or_of_or_of_imp_left (h₁ : a ∨ c) (h : a → b) : b ∨ c :=
or.imp_left h h₁

theorem or_of_or_of_imp_right (h₁ : c ∨ a) (h : a → b) : c ∨ b :=
or.imp_right h h₁

theorem or.elim3 (h : a ∨ b ∨ c) (ha : a → d) (hb : b → d) (hc : c → d) : d :=
or.elim h ha (assume h₂, or.elim h₂ hb hc)

theorem or_imp_distrib : (a ∨ b → c) ↔ (a → c) ∧ (b → c) :=
⟨assume h, ⟨assume ha, h (or.inl ha), assume hb, h (or.inr hb)⟩,
  assume ⟨ha, hb⟩, or.rec ha hb⟩

theorem or_iff_not_imp_left [decidable a] : a ∨ b ↔ (¬ a → b) :=
⟨or.resolve_left, λ h, dite _ or.inl (or.inr ∘ h)⟩

theorem or_iff_not_imp_right [decidable b] : a ∨ b ↔ (¬ b → a) :=
or.comm.trans or_iff_not_imp_left

theorem not_imp_not [decidable a] : (¬ a → ¬ b) ↔ (b → a) :=
⟨assume h hb, by_contradiction $ assume na, h na hb, mt⟩

/- distributivity -/

theorem and_or_distrib_left : a ∧ (b ∨ c) ↔ (a ∧ b) ∨ (a ∧ c) :=
⟨λ ⟨ha, hbc⟩, hbc.imp (and.intro ha) (and.intro ha),
 or.rec (and.imp_right or.inl) (and.imp_right or.inr)⟩

theorem or_and_distrib_right : (a ∨ b) ∧ c ↔ (a ∧ c) ∨ (b ∧ c) :=
(and.comm.trans and_or_distrib_left).trans (or_congr and.comm and.comm)

theorem or_and_distrib_left : a ∨ (b ∧ c) ↔ (a ∨ b) ∧ (a ∨ c) :=
⟨or.rec (λha, and.intro (or.inl ha) (or.inl ha)) (and.imp or.inr or.inr),
 and.rec $ or.rec (imp_intro ∘ or.inl) (or.imp_right ∘ and.intro)⟩

theorem and_or_distrib_right : (a ∧ b) ∨ c ↔ (a ∨ c) ∧ (b ∨ c) :=
(or.comm.trans or_and_distrib_left).trans (and_congr or.comm or.comm)

/- iff -/

theorem iff_of_true (ha : a) (hb : b) : a ↔ b :=
⟨λ_, hb, λ _, ha⟩

theorem iff_of_false (ha : ¬a) (hb : ¬b) : a ↔ b :=
⟨ha.elim, hb.elim⟩

theorem iff_true_left (ha : a) : (a ↔ b) ↔ b :=
⟨λ h, h.1 ha, iff_of_true ha⟩

theorem iff_true_right (ha : a) : (b ↔ a) ↔ b :=
iff.comm.trans (iff_true_left ha)

theorem iff_false_left (ha : ¬a) : (a ↔ b) ↔ ¬b :=
⟨λ h, mt h.2 ha, iff_of_false ha⟩

theorem iff_false_right (ha : ¬a) : (b ↔ a) ↔ ¬b :=
iff.comm.trans (iff_false_left ha)

theorem not_or_of_imp [decidable a] (h : a → b) : ¬ a ∨ b :=
if ha : a then or.inr (h ha) else or.inl ha

theorem imp_iff_not_or [decidable a] : (a → b) ↔ (¬ a ∨ b) :=
⟨not_or_of_imp, or.neg_resolve_left⟩

theorem imp_or_distrib [decidable a] : (a → b ∨ c) ↔ (a → b) ∨ (a → c) :=
by simp [imp_iff_not_or, or.comm, or.left_comm]

theorem imp_or_distrib' [decidable b] : (a → b ∨ c) ↔ (a → b) ∨ (a → c) :=
by by_cases b; simp [h, or_iff_right_of_imp ((∘) false.elim)]

theorem not_imp_of_and_not : a ∧ ¬ b → ¬ (a → b)
| ⟨ha, hb⟩ h := hb $ h ha

@[simp] theorem not_imp [decidable a] : ¬(a → b) ↔ a ∧ ¬b :=
⟨λ h, ⟨of_not_imp h, not_of_not_imp h⟩, not_imp_of_and_not⟩

theorem peirce (a b : Prop) [decidable a] : ((a → b) → a) → a :=
if ha : a then λ h, ha else λ h, h ha.elim

theorem peirce' {a : Prop} (H : ∀ b : Prop, (a → b) → a) : a := H _ id

theorem not_iff_not [decidable a] [decidable b] : (¬ a ↔ ¬ b) ↔ (a ↔ b) :=
by rw [@iff_def (¬ a), @iff_def' a]; exact and_congr not_imp_not not_imp_not

theorem not_iff_comm [decidable a] [decidable b] : (¬ a ↔ b) ↔ (¬ b ↔ a) :=
by rw [@iff_def (¬ a), @iff_def (¬ b)]; exact and_congr not_imp_comm imp_not_comm

theorem not_iff [decidable a] [decidable b] : ¬ (a ↔ b) ↔ (¬ a ↔ b) :=
by split; intro h; [split, skip]; intro h'; [by_contradiction,intro,skip];
   try { refine h _; simp [*] }; rw [h',not_iff_self] at h; exact h

theorem iff_not_comm [decidable a] [decidable b] : (a ↔ ¬ b) ↔ (b ↔ ¬ a) :=
by rw [@iff_def a, @iff_def b]; exact and_congr imp_not_comm not_imp_comm

theorem iff_iff_and_or_not_and_not [decidable b] : (a ↔ b) ↔ (a ∧ b) ∨ (¬ a ∧ ¬ b) :=
by { split; intro h,
     { rw h; by_cases b; [left,right]; split; assumption },
     { cases h with h h; cases h; split; intro; { contradiction <|> assumption } } }

@[simp] theorem not_and_not_right [decidable b] : ¬(a ∧ ¬b) ↔ (a → b) :=
⟨λ h ha, h.imp_symm $ and.intro ha, λ h ⟨ha, hb⟩, hb $ h ha⟩

@[inline] def decidable_of_iff (a : Prop) (h : a ↔ b) [D : decidable a] : decidable b :=
decidable_of_decidable_of_iff D h

@[inline] def decidable_of_iff' (b : Prop) (h : a ↔ b) [D : decidable b] : decidable a :=
decidable_of_decidable_of_iff D h.symm

def decidable_of_bool : ∀ (b : bool) (h : b ↔ a), decidable a
| tt h := is_true (h.1 rfl)
| ff h := is_false (mt h.2 bool.ff_ne_tt)

/- de morgan's laws -/

theorem not_and_of_not_or_not (h : ¬ a ∨ ¬ b) : ¬ (a ∧ b)
| ⟨ha, hb⟩ := or.elim h (absurd ha) (absurd hb)

theorem not_and_distrib [decidable a] : ¬ (a ∧ b) ↔ ¬a ∨ ¬b :=
⟨λ h, if ha : a then or.inr (λ hb, h ⟨ha, hb⟩) else or.inl ha, not_and_of_not_or_not⟩

theorem not_and_distrib' [decidable b] : ¬ (a ∧ b) ↔ ¬a ∨ ¬b :=
⟨λ h, if hb : b then or.inl (λ ha, h ⟨ha, hb⟩) else or.inr hb, not_and_of_not_or_not⟩

@[simp] theorem not_and : ¬ (a ∧ b) ↔ (a → ¬ b) := and_imp

theorem not_and' : ¬ (a ∧ b) ↔ b → ¬a :=
not_and.trans imp_not_comm

theorem not_or_distrib : ¬ (a ∨ b) ↔ ¬ a ∧ ¬ b :=
⟨λ h, ⟨λ ha, h (or.inl ha), λ hb, h (or.inr hb)⟩,
 λ ⟨h₁, h₂⟩ h, or.elim h h₁ h₂⟩

theorem or_iff_not_and_not [decidable a] [decidable b] : a ∨ b ↔ ¬ (¬a ∧ ¬b) :=
by rw [← not_or_distrib, not_not]

theorem and_iff_not_or_not [decidable a] [decidable b] : a ∧ b ↔ ¬ (¬ a ∨ ¬ b) :=
by rw [← not_and_distrib, not_not]

end propositional

/- equality -/

section equality
variables {α : Sort*} {a b : α}

@[simp] theorem heq_iff_eq : a == b ↔ a = b :=
⟨eq_of_heq, heq_of_eq⟩

theorem proof_irrel_heq {p q : Prop} (hp : p) (hq : q) : hp == hq :=
have p = q, from propext ⟨λ _, hq, λ _, hp⟩,
by subst q; refl

theorem ne_of_mem_of_not_mem {α β} [has_mem α β] {s : β} {a b : α}
  (h : a ∈ s) : b ∉ s → a ≠ b :=
mt $ λ e, e ▸ h

theorem eq_equivalence : equivalence (@eq α) :=
⟨eq.refl, @eq.symm _, @eq.trans _⟩

lemma heq_of_eq_mp :
  ∀ {α β : Sort*} {a : α} {a' : β} (e : α = β) (h₂ : (eq.mp e a) = a'), a == a'
| α ._ a a' rfl h := eq.rec_on h (heq.refl _)

lemma rec_heq_of_heq {β} {C : α → Sort*} {x : C a} {y : β} (eq : a = b) (h : x == y) :
  @eq.rec α a C x b eq == y :=
by subst eq; exact h

end equality

/-
  quantifiers
-/

section quantifiers
variables {α : Sort*} {p q : α → Prop} {b : Prop}

def Exists.imp := @exists_imp_exists

theorem forall_swap {α β} {p : α → β → Prop} : (∀ x y, p x y) ↔ ∀ y x, p x y :=
⟨function.swap, function.swap⟩

theorem exists_swap {α β} {p : α → β → Prop} : (∃ x y, p x y) ↔ ∃ y x, p x y :=
⟨λ ⟨x, y, h⟩, ⟨y, x, h⟩, λ ⟨y, x, h⟩, ⟨x, y, h⟩⟩

@[simp] theorem exists_imp_distrib : ((∃ x, p x) → b) ↔ ∀ x, p x → b :=
⟨λ h x hpx, h ⟨x, hpx⟩, λ h ⟨x, hpx⟩, h x hpx⟩

--theorem forall_not_of_not_exists (h : ¬ ∃ x, p x) : ∀ x, ¬ p x :=
--forall_imp_of_exists_imp h

theorem not_exists_of_forall_not (h : ∀ x, ¬ p x) : ¬ ∃ x, p x :=
exists_imp_distrib.2 h

@[simp] theorem not_exists : (¬ ∃ x, p x) ↔ ∀ x, ¬ p x :=
exists_imp_distrib

theorem not_forall_of_exists_not : (∃ x, ¬ p x) → ¬ ∀ x, p x
| ⟨x, hn⟩ h := hn (h x)

theorem not_forall {p : α → Prop}
    [decidable (∃ x, ¬ p x)] [∀ x, decidable (p x)] :
  (¬ ∀ x, p x) ↔ ∃ x, ¬ p x :=
⟨not.imp_symm $ λ nx x, nx.imp_symm $ λ h, ⟨x, h⟩,
 not_forall_of_exists_not⟩

@[simp] theorem not_forall_not [decidable (∃ x, p x)] :
  (¬ ∀ x, ¬ p x) ↔ ∃ x, p x :=
by haveI := decidable_of_iff (¬ ∃ x, p x) not_exists;
   exact not_iff_comm.1 not_exists

@[simp] theorem not_exists_not [∀ x, decidable (p x)] :
  (¬ ∃ x, ¬ p x) ↔ ∀ x, p x :=
by simp

@[simp] theorem forall_true_iff : (α → true) ↔ true :=
iff_true_intro (λ _, trivial)

-- Unfortunately this causes simp to loop sometimes, so we
-- add the 2 and 3 cases as simp lemmas instead
theorem forall_true_iff' (h : ∀ a, p a ↔ true) : (∀ a, p a) ↔ true :=
iff_true_intro (λ _, of_iff_true (h _))

@[simp] theorem forall_2_true_iff {β : α → Sort*} : (∀ a, β a → true) ↔ true :=
forall_true_iff' $ λ _, forall_true_iff

@[simp] theorem forall_3_true_iff {β : α → Sort*} {γ : Π a, β a → Sort*} :
  (∀ a (b : β a), γ a b → true) ↔ true :=
forall_true_iff' $ λ _, forall_2_true_iff

@[simp] theorem forall_const (α : Sort*) [inhabited α] : (α → b) ↔ b :=
⟨λ h, h (arbitrary α), λ hb x, hb⟩

@[simp] theorem exists_const (α : Sort*) [inhabited α] : (∃ x : α, b) ↔ b :=
⟨λ ⟨x, h⟩, h, λ h, ⟨arbitrary α, h⟩⟩

theorem forall_and_distrib : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
⟨λ h, ⟨λ x, (h x).left, λ x, (h x).right⟩, λ ⟨h₁, h₂⟩ x, ⟨h₁ x, h₂ x⟩⟩

theorem exists_or_distrib : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
⟨λ ⟨x, hpq⟩, hpq.elim (λ hpx, or.inl ⟨x, hpx⟩) (λ hqx, or.inr ⟨x, hqx⟩),
 λ hepq, hepq.elim (λ ⟨x, hpx⟩, ⟨x, or.inl hpx⟩) (λ ⟨x, hqx⟩, ⟨x, or.inr hqx⟩)⟩

@[simp] theorem exists_and_distrib_left {q : Prop} {p : α → Prop} :
  (∃x, q ∧ p x) ↔ q ∧ (∃x, p x) :=
⟨λ ⟨x, hq, hp⟩, ⟨hq, x, hp⟩, λ ⟨hq, x, hp⟩, ⟨x, hq, hp⟩⟩

@[simp] theorem exists_and_distrib_right {q : Prop} {p : α → Prop} :
  (∃x, p x ∧ q) ↔ (∃x, p x) ∧ q :=
by simp [and_comm]

@[simp] theorem forall_eq {a' : α} : (∀a, a = a' → p a) ↔ p a' :=
⟨λ h, h a' rfl, λ h a e, e.symm ▸ h⟩

@[simp] theorem exists_eq {a' : α} : ∃ a, a = a' := ⟨_, rfl⟩

@[simp] theorem exists_eq_left {a' : α} : (∃ a, a = a' ∧ p a) ↔ p a' :=
⟨λ ⟨a, e, h⟩, e ▸ h, λ h, ⟨_, rfl, h⟩⟩

@[simp] theorem exists_eq_right {a' : α} : (∃ a, p a ∧ a = a') ↔ p a' :=
(exists_congr $ by exact λ a, and.comm).trans exists_eq_left

@[simp] theorem forall_eq' {a' : α} : (∀a, a' = a → p a) ↔ p a' :=
by simp [@eq_comm _ a']

@[simp] theorem exists_eq_left' {a' : α} : (∃ a, a' = a ∧ p a) ↔ p a' :=
by simp [@eq_comm _ a']

@[simp] theorem exists_eq_right' {a' : α} : (∃ a, p a ∧ a' = a) ↔ p a' :=
by simp [@eq_comm _ a']

theorem forall_or_of_or_forall (h : b ∨ ∀x, p x) (x) : b ∨ p x :=
h.imp_right $ λ h₂, h₂ x

theorem forall_or_distrib_left {q : Prop} {p : α → Prop} [decidable q] :
  (∀x, q ∨ p x) ↔ q ∨ (∀x, p x) :=
⟨λ h, if hq : q then or.inl hq else or.inr $ λ x, (h x).resolve_left hq,
  forall_or_of_or_forall⟩

@[simp] theorem exists_prop {p q : Prop} : (∃ h : p, q) ↔ p ∧ q :=
⟨λ ⟨h₁, h₂⟩, ⟨h₁, h₂⟩, λ ⟨h₁, h₂⟩, ⟨h₁, h₂⟩⟩

@[simp] theorem exists_false : ¬ (∃a:α, false) := assume ⟨a, h⟩, h

theorem Exists.fst {p : b → Prop} : Exists p → b
| ⟨h, _⟩ := h

theorem Exists.snd {p : b → Prop} : ∀ h : Exists p, p h.fst
| ⟨_, h⟩ := h

@[simp] theorem forall_prop_of_true {p : Prop} {q : p → Prop} (h : p) : (∀ h' : p, q h') ↔ q h :=
@forall_const (q h) p ⟨h⟩

@[simp] theorem exists_prop_of_true {p : Prop} {q : p → Prop} (h : p) : (∃ h' : p, q h') ↔ q h :=
@exists_const (q h) p ⟨h⟩

@[simp] theorem forall_prop_of_false {p : Prop} {q : p → Prop} (hn : ¬ p) : (∀ h' : p, q h') ↔ true :=
iff_true_intro $ λ h, hn.elim h

@[simp] theorem exists_prop_of_false {p : Prop} {q : p → Prop} : ¬ p → ¬ (∃ h' : p, q h') :=
mt Exists.fst

end quantifiers

/- classical versions -/

namespace classical
variables {α : Sort*} {p : α → Prop}

local attribute [instance] prop_decidable

protected theorem not_forall : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) := not_forall

protected theorem forall_or_distrib_left {q : Prop} {p : α → Prop} :
  (∀x, q ∨ p x) ↔ q ∨ (∀x, p x) :=
forall_or_distrib_left

theorem cases {p : Prop → Prop} (h1 : p true) (h2 : p false) : ∀a, p a :=
assume a, cases_on a h1 h2

theorem or_not {p : Prop} : p ∨ ¬ p :=
by_cases or.inl or.inr

protected theorem or_iff_not_imp_left {p q : Prop} : p ∨ q ↔ (¬ p → q) :=
or_iff_not_imp_left

protected theorem or_iff_not_imp_right {p q : Prop} : q ∨ p ↔ (¬ p → q) :=
or_iff_not_imp_right

/- use shortened names to avoid conflict when classical namespace is open -/
noncomputable theorem dec (p : Prop) : decidable p := by apply_instance
noncomputable theorem dec_pred (p : α → Prop) : decidable_pred p := by apply_instance
noncomputable theorem dec_rel (p : α → α → Prop) : decidable_rel p := by apply_instance
noncomputable theorem dec_eq (α : Sort*) : decidable_eq α := by apply_instance

@[elab_as_eliminator]
noncomputable def {u} rec_on {C : Sort u} (h : ∃ a, p a) (H : ∀ a, p a → C) : C :=
H (classical.some h) (classical.some_spec h)

lemma some_spec2 {α : Type*} {p : α → Prop} {h : ∃a, p a}
  (q : α → Prop) (hpq : ∀a, p a → q a) : q (some h) :=
hpq _ $ some_spec _

end classical

/-
   bounded quantifiers
-/

section bounded_quantifiers
variables {α : Sort*} {r p q : α → Prop} {P Q : ∀ x, p x → Prop} {b : Prop}

theorem bex_def : (∃ x (h : p x), q x) ↔ ∃ x, p x ∧ q x :=
⟨λ ⟨x, px, qx⟩, ⟨x, px, qx⟩, λ ⟨x, px, qx⟩, ⟨x, px, qx⟩⟩

theorem bex.elim {b : Prop} : (∃ x h, P x h) → (∀ a h, P a h → b) → b
| ⟨a, h₁, h₂⟩ h' := h' a h₁ h₂

theorem bex.intro (a : α) (h₁ : p a) (h₂ : P a h₁) : ∃ x (h : p x), P x h :=
⟨a, h₁, h₂⟩

theorem ball_congr (H : ∀ x h, P x h ↔ Q x h) :
  (∀ x h, P x h) ↔ (∀ x h, Q x h) :=
forall_congr $ λ x, forall_congr (H x)

theorem bex_congr (H : ∀ x h, P x h ↔ Q x h) :
  (∃ x h, P x h) ↔ (∃ x h, Q x h) :=
exists_congr $ λ x, exists_congr (H x)

theorem ball.imp_right (H : ∀ x h, (P x h → Q x h))
  (h₁ : ∀ x h, P x h) (x h) : Q x h :=
H _ _ $ h₁ _ _

theorem bex.imp_right (H : ∀ x h, (P x h → Q x h)) :
  (∃ x h, P x h) → ∃ x h, Q x h
| ⟨x, h, h'⟩ := ⟨_, _, H _ _ h'⟩

theorem ball.imp_left (H : ∀ x, p x → q x)
  (h₁ : ∀ x, q x → r x) (x) (h : p x) : r x :=
h₁ _ $ H _ h

theorem bex.imp_left (H : ∀ x, p x → q x) :
  (∃ x (_ : p x), r x) → ∃ x (_ : q x), r x
| ⟨x, hp, hr⟩ := ⟨x, H _ hp, hr⟩

theorem ball_of_forall (h : ∀ x, p x) (x) (_ : q x) : p x :=
h x

theorem forall_of_ball (H : ∀ x, p x) (h : ∀ x, p x → q x) (x) : q x :=
h x $ H x

theorem bex_of_exists (H : ∀ x, p x) : (∃ x, q x) → ∃ x (_ : p x), q x
| ⟨x, hq⟩ := ⟨x, H x, hq⟩

theorem exists_of_bex : (∃ x (_ : p x), q x) → ∃ x, q x
| ⟨x, _, hq⟩ := ⟨x, hq⟩

@[simp] theorem bex_imp_distrib : ((∃ x h, P x h) → b) ↔ (∀ x h, P x h → b) :=
by simp

theorem not_bex : (¬ ∃ x h, P x h) ↔ ∀ x h, ¬ P x h :=
bex_imp_distrib

theorem not_ball_of_bex_not : (∃ x h, ¬ P x h) → ¬ ∀ x h, P x h
| ⟨x, h, hp⟩ al := hp $ al x h

theorem not_ball [decidable (∃ x h, ¬ P x h)] [∀ x h, decidable (P x h)] :
  (¬ ∀ x h, P x h) ↔ (∃ x h, ¬ P x h) :=
⟨not.imp_symm $ λ nx x h, nx.imp_symm $ λ h', ⟨x, h, h'⟩,
 not_ball_of_bex_not⟩

theorem ball_true_iff (p : α → Prop) : (∀ x, p x → true) ↔ true :=
iff_true_intro (λ h hrx, trivial)

theorem ball_and_distrib : (∀ x h, P x h ∧ Q x h) ↔ (∀ x h, P x h) ∧ (∀ x h, Q x h) :=
iff.trans (forall_congr $ λ x, forall_and_distrib) forall_and_distrib

theorem bex_or_distrib : (∃ x h, P x h ∨ Q x h) ↔ (∃ x h, P x h) ∨ (∃ x h, Q x h) :=
iff.trans (exists_congr $ λ x, exists_or_distrib) exists_or_distrib

end bounded_quantifiers

namespace classical
local attribute [instance] prop_decidable

theorem not_ball {α : Sort*} {p : α → Prop} {P : Π (x : α), p x → Prop} :
  (¬ ∀ x h, P x h) ↔ (∃ x h, ¬ P x h) := _root_.not_ball

end classical

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

attribute [simp] nonempty_of_inhabited

lemma exists_true_iff_nonempty {α : Sort*} : (∃a:α, true) ↔ nonempty α :=
iff.intro (λ⟨a, _⟩, ⟨a⟩) (λ⟨a⟩, ⟨a, trivial⟩)

@[simp] lemma nonempty_Prop {p : Prop} : nonempty p ↔ p :=
iff.intro (assume ⟨h⟩, h) (assume h, ⟨h⟩)

lemma not_nonempty_iff_imp_false {p : Prop} : ¬ nonempty α ↔ α → false :=
⟨λ h a, h ⟨a⟩, λ h ⟨a⟩, h a⟩

@[simp] lemma nonempty_sigma : nonempty (Σa:α, γ a) ↔ (∃a:α, nonempty (γ a)) :=
iff.intro (assume ⟨⟨a, c⟩⟩, ⟨a, ⟨c⟩⟩) (assume ⟨a, ⟨c⟩⟩, ⟨⟨a, c⟩⟩)

@[simp] lemma nonempty_subtype {α : Sort u} {p : α → Prop} : nonempty (subtype p) ↔ (∃a:α, p a) :=
iff.intro (assume ⟨⟨a, h⟩⟩, ⟨a, h⟩) (assume ⟨a, h⟩, ⟨⟨a, h⟩⟩)

@[simp] lemma nonempty_prod : nonempty (α × β) ↔ (nonempty α ∧ nonempty β) :=
iff.intro (assume ⟨⟨a, b⟩⟩, ⟨⟨a⟩, ⟨b⟩⟩) (assume ⟨⟨a⟩, ⟨b⟩⟩, ⟨⟨a, b⟩⟩)

@[simp] lemma nonempty_pprod {α : Sort u} {β : Sort v} :
  nonempty (pprod α β) ↔ (nonempty α ∧ nonempty β) :=
iff.intro (assume ⟨⟨a, b⟩⟩, ⟨⟨a⟩, ⟨b⟩⟩) (assume ⟨⟨a⟩, ⟨b⟩⟩, ⟨⟨a, b⟩⟩)

@[simp] lemma nonempty_sum : nonempty (α ⊕ β) ↔ (nonempty α ∨ nonempty β) :=
iff.intro
  (assume ⟨h⟩, match h with sum.inl a := or.inl ⟨a⟩ | sum.inr b := or.inr ⟨b⟩ end)
  (assume h, match h with or.inl ⟨a⟩ := ⟨sum.inl a⟩ | or.inr ⟨b⟩ := ⟨sum.inr b⟩ end)

@[simp] lemma nonempty_psum {α : Sort u} {β : Sort v} :
  nonempty (psum α β) ↔ (nonempty α ∨ nonempty β) :=
iff.intro
  (assume ⟨h⟩, match h with psum.inl a := or.inl ⟨a⟩ | psum.inr b := or.inr ⟨b⟩ end)
  (assume h, match h with or.inl ⟨a⟩ := ⟨psum.inl a⟩ | or.inr ⟨b⟩ := ⟨psum.inr b⟩ end)

@[simp] lemma nonempty_psigma {α : Sort u} {β : α → Sort v} :
  nonempty (psigma β) ↔ (∃a:α, nonempty (β a)) :=
iff.intro (assume ⟨⟨a, c⟩⟩, ⟨a, ⟨c⟩⟩) (assume ⟨a, ⟨c⟩⟩, ⟨⟨a, c⟩⟩)

@[simp] lemma nonempty_empty : ¬ nonempty empty :=
assume ⟨h⟩, h.elim

@[simp] lemma nonempty_ulift : nonempty (ulift α) ↔ nonempty α :=
iff.intro (assume ⟨⟨a⟩⟩, ⟨a⟩) (assume ⟨a⟩, ⟨⟨a⟩⟩)

@[simp] lemma nonempty_plift {α : Sort u} : nonempty (plift α) ↔ nonempty α :=
iff.intro (assume ⟨⟨a⟩⟩, ⟨a⟩) (assume ⟨a⟩, ⟨⟨a⟩⟩)

@[simp] lemma nonempty.forall {α : Sort u} {p : nonempty α → Prop} :
  (∀h:nonempty α, p h) ↔ (∀a, p ⟨a⟩) :=
iff.intro (assume h a, h _) (assume h ⟨a⟩, h _)

@[simp] lemma nonempty.exists {α : Sort u} {p : nonempty α → Prop} :
  (∃h:nonempty α, p h) ↔ (∃a, p ⟨a⟩) :=
iff.intro (assume ⟨⟨a⟩, h⟩, ⟨a, h⟩) (assume ⟨a, h⟩, ⟨⟨a⟩, h⟩)

lemma classical.nonempty_pi {α : Sort u} {β : α → Sort v} :
  nonempty (Πa:α, β a) ↔ (∀a:α, nonempty (β a)) :=
iff.intro (assume ⟨f⟩ a, ⟨f a⟩) (assume f, ⟨assume a, classical.choice $ f a⟩)

end nonempty


import .pasting_pushouts

open category_theory.category
local notation f ` ∘ `:80 g:80 := g ≫ f

/-

       a₁ → b₂         a₁ → b₂
       ↓               ↓    ↓
  a₀ → b₁   ↓     a₀ → b₁ → c₁
  ↓    ↓          ↓         ↓
  b₀ → c₀ → d     b₀   →    d'

-/

universes v u

namespace category_theory

section

parameters {C : Type u} [category.{v} C]

parameters {a₀ a₁ b₀ b₁ b₂ c₀ c₁ d d' : C}
parameters {f₀ : a₀ ⟶ b₀} {f₁ : a₀ ⟶ b₁} {f₂ : a₁ ⟶ b₁} {f₃ : a₁ ⟶ b₂}
parameters {g₀ : b₀ ⟶ c₀} {g₁ : b₁ ⟶ c₀} {g₂ : b₁ ⟶ c₁} {g₃ : b₂ ⟶ c₁}
parameters {h₀ : c₀ ⟶ d} {h₁ : b₂ ⟶ d} {h₀' : b₀ ⟶ d'} {h₁' : c₁ ⟶ d'}
parameters
  (po₀ : Is_pushout f₀ f₁ g₀ g₁)
  (po₁ : Is_pushout (g₁ ∘ f₂) f₃ h₀ h₁)
  (po₂ : Is_pushout f₂ f₃ g₂ g₃)
  (po₃ : Is_pushout f₀ (g₂ ∘ f₁) h₀' h₁')
include po₀ po₁ po₂ po₃

def Is_pushout_assoc : d ≅ d' :=
begin
  refine {
    hom := po₁.induced (po₀.induced h₀' (h₁' ∘ g₂) _) (h₁' ∘ g₃) _,
    inv := po₃.induced (h₀ ∘ g₀) (po₂.induced (h₀ ∘ g₁) h₁ _) _,
    hom_inv_id' := _,
    inv_hom_id' := _,
  },
  { rw po₃.commutes, simp },
  -- TODO: Is_pushout.commutes_assoc
  { simp, rw [←assoc, ←assoc], rw po₂.commutes },
  { rw [←assoc, po₁.commutes] },
  { simp, rw [←assoc, ←assoc], rw po₀.commutes },
  { apply po₁.uniqueness; rw ←assoc; simp,
    { apply po₀.uniqueness; rw ←assoc; simp } },
  { apply po₃.uniqueness; rw ←assoc; simp,
    { apply po₂.uniqueness; rw ←assoc; simp } }
end

@[simp] lemma Is_pushout_assoc_i₀ : Is_pushout_assoc.hom ∘ h₀ ∘ g₀ = h₀' :=
by change Is_pushout.induced _ _ _ _ ∘ _ ∘ _ = _; simp

@[simp] lemma Is_pushout_assoc_i₁ : Is_pushout_assoc.hom ∘ h₁ = h₁' ∘ g₃ :=
by change Is_pushout.induced _ _ _ _ ∘ _ = _ ∘ _; simp

parameters {x : C} {k : d ⟶ x} {k' : d' ⟶ x}

lemma Is_pushout_assoc_uniqueness
  (hk₀ : k ∘ h₀ ∘ g₀ = k' ∘ h₀')
  (hk₁ : k ∘ h₀ ∘ g₁ = k' ∘ h₁' ∘ g₂)
  (hk₂ : k ∘ h₁ = k' ∘ h₁' ∘ g₃) :
  k = k' ∘ Is_pushout_assoc.hom :=
begin
  change k = k' ∘ Is_pushout.induced _ _ _ _,
  apply po₁.uniqueness; rw ←assoc,
  { apply po₀.uniqueness; conv { to_rhs, rw ←assoc },
    { rw hk₀, simp },
    { rw hk₁, simp } },
  { rw hk₂, simp }
end

end

end category_theory


import for_mathlib.snake_lemma

namespace category_theory

local notation x `⟶[`D`]` y := D.map (snake_diagram.hom x y)

namespace snake_lemma

open category_theory.limits

universes v u
variables {A : Type u} [category.{v} A] [abelian A]
  {F G : snake_diagram ⥤ A} (η : F ⟶ G)

namespace δ_natural_setup

@[reassoc]
lemma aux1 (hF : is_snake_input F) (hG : is_snake_input G) :
  η.app (0, 2) ≫ hG.to_kernel = hF.to_kernel ≫
  kernel.lift _ (kernel.ι _ ≫ η.app _) begin
    simp only [category.assoc, ← η.naturality, kernel.condition_assoc, zero_comp],
  end :=
begin
  apply equalizer.hom_ext,
  dsimp [is_snake_input.to_kernel],
  simp,
end

@[reassoc]
lemma aux2 (hF : is_snake_input F) (hG : is_snake_input G) :
  kernel.lift ((1, 2) ⟶[G] (2, 2)) (kernel.ι ((1, 2) ⟶[F] (2, 2)) ≫
    η.app (1, 2)) begin
      simp only [category.assoc, ← η.naturality, kernel.condition_assoc, zero_comp],
    end ≫ inv hG.cokernel_to_top_right_kernel_to_right_kernel =
  inv hF.cokernel_to_top_right_kernel_to_right_kernel ≫
    cokernel.desc _ (kernel.lift _ (kernel.ι _ ≫ η.app _) begin
      simp only [category.assoc, ← η.naturality, kernel.condition_assoc, zero_comp],
    end ≫ cokernel.π _) begin
      dsimp [is_snake_input.to_top_right_kernel],
      simp only [← category.assoc], let t := _, change t ≫ _ = _,
      have ht : t = η.app _ ≫ kernel.lift ((1,1) ⟶[G] (2,2)) ((1,0) ⟶[G] (1,1)) _,
      { apply equalizer.hom_ext, simp, },
      rw [ht, category.assoc, cokernel.condition, comp_zero],
    end :=
begin
  simp only [is_iso.eq_inv_comp, is_iso.comp_inv_eq, category.assoc],
  dsimp [is_snake_input.cokernel_to_top_right_kernel_to_right_kernel],
  apply equalizer.hom_ext,
  simp only [le_refl, and_true, category.assoc, nat_trans.naturality,
    kernel.condition_assoc, zero_comp, true_and,
    cokernel.condition, comp_zero, equalizer_as_kernel, kernel.lift_ι],
  apply coequalizer.hom_ext,
  simp only [category.assoc, nat_trans.naturality, cokernel.π_desc_assoc,
    kernel.lift_ι_assoc, kernel.lift_ι],
end

@[reassoc]
lemma aux3 (hF : is_snake_input F) (hG : is_snake_input G) :
  kernel.lift hG.bottom_left_cokernel_to (kernel.ι hF.bottom_left_cokernel_to ≫
    cokernel.desc ((1, 0) ⟶[F] (2, 1)) (η.app (2, 1) ≫
    cokernel.π ((1, 0) ⟶[G] (2, 1))) begin
      simp only [category.assoc, η.naturality_assoc, cokernel.condition, comp_zero],
    end) begin
      dsimp [is_snake_input.bottom_left_cokernel_to],
      simp only [category.assoc], let t := _, change _ ≫ t = _,
      have ht : t = cokernel.desc ((1,0) ⟶[F] (2,1)) ((2,1) ⟶[F] (2,2)) _ ≫ η.app _,
      { apply coequalizer.hom_ext, simp, },
      rw [ht, kernel.condition_assoc, zero_comp],
    end ≫
    inv hG.left_cokernel_to_kernel_bottom_left_cokernel_to =
  inv hF.left_cokernel_to_kernel_bottom_left_cokernel_to ≫
  cokernel.desc _ (η.app _ ≫ cokernel.π _) begin
    simp only [category.assoc, η.naturality_assoc, cokernel.condition, comp_zero],
  end :=
begin
  rw [is_iso.comp_inv_eq, category.assoc (inv _), is_iso.eq_inv_comp],
  dsimp [is_snake_input.left_cokernel_to_kernel_bottom_left_cokernel_to],
  apply coequalizer.hom_ext, apply equalizer.hom_ext,
  simp only [nat_trans.naturality_assoc, category.assoc, cokernel.π_desc_assoc,
    cokernel.π_desc, kernel.lift_ι, kernel.lift_ι_assoc],
end

end δ_natural_setup

open δ_natural_setup

theorem δ_natural (hF : is_snake_input F) (hG : is_snake_input G) :
  η.app (0,2) ≫ hG.δ = hF.δ ≫ η.app (3,0) :=
begin
  dsimp [is_snake_input.δ],
  simp only [category.assoc],
  rw aux1_assoc η hF hG,
  rw aux2_assoc η hF hG,
  simp_rw cancel_epi,
  apply coequalizer.hom_ext,
  dsimp [is_snake_input.δ_aux],
  simp only [cokernel.π_desc_assoc, category.assoc],
  simp only [← category.assoc], let t := _, change (t ≫ _) ≫ _ = _,
  let s := _, change _ = ((s ≫ _) ≫ _) ≫ _,
  have ht : t = s ≫ kernel.lift _ (kernel.ι _ ≫
    cokernel.desc _ (η.app _ ≫ cokernel.π _) begin
      simp only [category.assoc, η.naturality_assoc, cokernel.condition, comp_zero],
    end) _,
  rotate 2,
  { dsimp [is_snake_input.bottom_left_cokernel_to], simp only [category.assoc],
    let t := _, change _ ≫ t = _,
    have ht : t = cokernel.desc ((1,0) ⟶[F] (2,1)) ((2,1) ⟶[F] (2,2)) _ ≫ η.app _,
    { apply coequalizer.hom_ext, dsimp, simp, },
    rw [ht, kernel.condition_assoc, zero_comp] },
  { dsimp [t, s],
    apply equalizer.hom_ext,
    simp },
  rw ht, clear ht, clear t, dsimp [s], clear s,
  simp only [category.assoc], congr' 1,
  rw aux3_assoc η hF hG, congr' 1,
  dsimp [is_snake_input.cokernel_to],
  apply coequalizer.hom_ext,
  simp,
end

end snake_lemma

end category_theory


/-
Copyright (c) 2022 Heather Macbeth. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Heather Macbeth, Frédéric Dupuis
-/

import Mathlib.Logic.Equiv.LocalEquiv

/-!
This is a test file for the tactic `mfld_set_tac`. Because this tactic applies a simp-set which
mostly contains lemmas in advanced parts of mathlib, it is currently impossible to truly test it
in realistic conditions. Instead, we create stub definitions and lemmas on objects such as
`LocalHomeomorph`, label them with `mfld_simps` and run tests on those.
-/

open Lean Meta Elab Tactic

/-! ## Syntax of objects and lemmas needed for testing `MfldSetTac` -/
section stub_lemmas

structure LocalHomeomorph (α : Type u) (β : Type u) extends LocalEquiv α β

instance LocalHomeomorph.has_coe_to_fun : CoeFun (LocalHomeomorph α β) (λ _ => α → β) := sorry

def LocalHomeomorph.symm (e : LocalHomeomorph α β) : LocalHomeomorph β α := sorry

@[mfld_simps] lemma LocalHomeomorph.left_inv (e : LocalHomeomorph α β) {x : α}
  (h : x ∈ e.toLocalEquiv.source) :
  e.symm (e x) = x :=
sorry

@[mfld_simps] theorem LocalHomeomorph.symm_to_LocalEquiv (e : LocalHomeomorph α β) :
  e.symm.toLocalEquiv = e.toLocalEquiv.symm :=
sorry

@[mfld_simps] lemma LocalHomeomorph.coe_coe (e : LocalHomeomorph α β) :
  (e.toLocalEquiv : α → β) = e :=
sorry

@[mfld_simps] lemma LocalHomeomorph.coe_coe_symm (e : LocalHomeomorph α β) :
  (e.toLocalEquiv.symm : β → α) = (e.symm : β → α) :=
sorry

structure ModelWithCorners (𝕜 E H : Type u) extends LocalEquiv H E :=
(source_eq : source = Set.univ)

attribute [mfld_simps] ModelWithCorners.source_eq

def ModelWithCorners.symm (I : ModelWithCorners 𝕜 E H) : LocalEquiv E H := sorry

instance ModelWithCorners.has_coe_to_fun : CoeFun (ModelWithCorners 𝕜 E H) (λ _ => H → E) := sorry

@[mfld_simps] lemma ModelWithCorners.left_inv (I : ModelWithCorners 𝕜 E H) (x : H) :
  I.symm (I x) = x :=
sorry

@[mfld_simps] lemma ModelWithCorners.to_local_equiv_coe (I : ModelWithCorners 𝕜 E H) :
  (I.toLocalEquiv : H → E) = I :=
sorry

@[mfld_simps] lemma ModelWithCorners.to_local_equiv_coe_symm (I : ModelWithCorners 𝕜 E H) :
  (I.toLocalEquiv.symm : E → H) = I.symm :=
sorry

end stub_lemmas

/-! ## Tests for `MfldSetTac` -/
section tests

example (e : LocalEquiv α β) (e' : LocalEquiv β γ) :
  (e.trans e').source = e.source ∩ Set.preimage e (e.target ∩ e'.source) := by
  mfld_set_tac

example (e : LocalEquiv α β) : (e.trans e.symm).source = e.source := by mfld_set_tac

example (s : Set α) (f : LocalHomeomorph α β) :
  f.symm.toLocalEquiv.source ∩ (f.toLocalEquiv.target ∩ Set.preimage f.symm s)
  = f.symm.toLocalEquiv.source ∩ Set.preimage f.symm s := by mfld_set_tac

example
  {I : ModelWithCorners 𝕜 E H}
  {I' : ModelWithCorners 𝕜 E' H'}
  {I'' : ModelWithCorners 𝕜 E'' H''}
  (e₁ : LocalHomeomorph M H)
  (e₂ : LocalHomeomorph M' H')
  (e₃ : LocalHomeomorph M'' H'')
  {f : M → M'}
  {g : M' → M''} :
  (Set.preimage (f ∘ ((e₁.toLocalEquiv.trans I.toLocalEquiv).symm))
      (e₂.toLocalEquiv.trans I'.toLocalEquiv).source) ⊆
    {y : E |
    ((e₃.toLocalEquiv.trans I''.toLocalEquiv) ∘
          (g ∘ f) ∘ ((e₁.toLocalEquiv.trans I.toLocalEquiv).symm)) y
    = (((e₃.toLocalEquiv.trans I''.toLocalEquiv : M'' → E'') ∘
             g ∘ ((e₂.toLocalEquiv.trans I'.toLocalEquiv).symm)) ∘
          (e₂.toLocalEquiv.trans I'.toLocalEquiv : M' → E') ∘
            f ∘ ((e₁.toLocalEquiv.trans I.toLocalEquiv).symm)) y} := by
  mfld_set_tac

end tests


example (p q r : Prop) (hp : p) : p ∨ q ∨ r :=
  by repeat { { left, assumption } <|> right <|> assumption }

example (p q r : Prop) (hq : q) : p ∨ q ∨ r :=
  by repeat { { left, assumption } <|> right <|> assumption }

example (p q r : Prop) (hr : r) : p ∨ q ∨ r :=
  by repeat { { left, assumption } <|> right <|> assumption }