/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl

Some big operators for lists and finite sets.
-/
import data.list.basic data.list.perm data.finset
import algebra.group algebra.ordered_group algebra.group_power

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

theorem directed.finset_le {r : α → α → Prop} [is_trans α r]
  {ι} (hι : nonempty ι) {f : ι → α} (D : directed r f) (s : finset ι) :
  ∃ z, ∀ i ∈ s, r (f i) (f z) :=
show ∃ z, ∀ i ∈ s.1, r (f i) (f z), from
multiset.induction_on s.1 (let ⟨z⟩ := hι in ⟨z, λ _, false.elim⟩) $
λ i s ⟨j, H⟩, let ⟨k, h₁, h₂⟩ := D i j in
⟨k, λ a h, or.cases_on (multiset.mem_cons.1 h)
  (λ h, h.symm ▸ h₁)
  (λ h, trans (H _ h) h₂)⟩

namespace finset
variables {s s₁ s₂ : finset α} {a : α} {f g : α → β}

/-- `prod s f` is the product of `f x` as `x` ranges over the elements of the finite set `s`. -/
@[to_additive finset.sum]
protected def prod [comm_monoid β] (s : finset α) (f : α → β) : β := (s.1.map f).prod
attribute [to_additive finset.sum.equations._eqn_1] finset.prod.equations._eqn_1

@[to_additive finset.sum_eq_fold]
theorem prod_eq_fold [comm_monoid β] (s : finset α) (f : α → β) : s.prod f = s.fold (*) 1 f := rfl

section comm_monoid
variables [comm_monoid β]

@[simp, to_additive finset.sum_empty]
lemma prod_empty {α : Type u} {f : α → β} : (∅:finset α).prod f = 1 := rfl

@[simp, to_additive finset.sum_insert]
lemma prod_insert [decidable_eq α] : a ∉ s → (insert a s).prod f = f a * s.prod f := fold_insert

@[simp, to_additive finset.sum_singleton]
lemma prod_singleton : (singleton a).prod f = f a :=
eq.trans fold_singleton $ mul_one _

@[simp] lemma prod_const_one : s.prod (λx, (1 : β)) = 1 :=
by simp only [finset.prod, multiset.map_const, multiset.prod_repeat, one_pow]
@[simp] lemma sum_const_zero {β} {s : finset α} [add_comm_monoid β] : s.sum (λx, (0 : β)) = 0 :=
@prod_const_one _ (multiplicative β) _ _
attribute [to_additive finset.sum_const_zero] prod_const_one

@[simp, to_additive finset.sum_image]
lemma prod_image [decidable_eq α] {s : finset γ} {g : γ → α} :
  (∀x∈s, ∀y∈s, g x = g y → x = y) → (s.image g).prod f = s.prod (λx, f (g x)) :=
fold_image

@[simp, to_additive sum_map]
lemma prod_map [comm_monoid γ] (s : finset α) (e : α ↪ β) (f : β → γ):
  (s.map e).prod f = s.prod (λa, f (e a)) :=
by rw [finset.prod, finset.map_val, multiset.map_map]; refl

@[congr, to_additive finset.sum_congr]
lemma prod_congr (h : s₁ = s₂) : (∀x∈s₂, f x = g x) → s₁.prod f = s₂.prod g :=
by rw [h]; exact fold_congr
attribute [congr] finset.sum_congr

@[to_additive finset.sum_union_inter]
lemma prod_union_inter [decidable_eq α] : (s₁ ∪ s₂).prod f * (s₁ ∩ s₂).prod f = s₁.prod f * s₂.prod f :=
fold_union_inter

@[to_additive finset.sum_union]
lemma prod_union [decidable_eq α] (h : s₁ ∩ s₂ = ∅) : (s₁ ∪ s₂).prod f = s₁.prod f * s₂.prod f :=
by rw [←prod_union_inter, h]; exact (mul_one _).symm

@[to_additive finset.sum_sdiff]
lemma prod_sdiff [decidable_eq α] (h : s₁ ⊆ s₂) : (s₂ \ s₁).prod f * s₁.prod f = s₂.prod f :=
by rw [←prod_union (sdiff_inter_self _ _), sdiff_union_of_subset h]

@[to_additive finset.sum_bind]
lemma prod_bind [decidable_eq α] {s : finset γ} {t : γ → finset α} :
  (∀x∈s, ∀y∈s, x ≠ y → t x ∩ t y = ∅) → (s.bind t).prod f = s.prod (λx, (t x).prod f) :=
by haveI := classical.dec_eq γ; exact
finset.induction_on s (λ _, by simp only [bind_empty, prod_empty])
  (assume x s hxs ih hd,
  have hd' : ∀x∈s, ∀y∈s, x ≠ y → t x ∩ t y = ∅,
    from assume _ hx _ hy, hd _ (mem_insert_of_mem hx) _ (mem_insert_of_mem hy),
  have t x ∩ finset.bind s t = ∅,
    from eq_empty_of_forall_not_mem $ assume a,
      by rw [mem_inter, mem_bind];
      rintro ⟨h₁, y, hys, hy₂⟩;
      exact eq_empty_iff_forall_not_mem.1
        (hd _ (mem_insert_self _ _) _ (mem_insert_of_mem hys) (assume h, hxs (h.symm ▸ hys)))
        _ (mem_inter_of_mem h₁ hy₂),
  by simp only [bind_insert, prod_insert hxs, prod_union this, ih hd'])

@[to_additive finset.sum_product]
lemma prod_product {s : finset γ} {t : finset α} {f : γ×α → β} :
  (s.product t).prod f = s.prod (λx, t.prod $ λy, f (x, y)) :=
begin
  haveI := classical.dec_eq α, haveI := classical.dec_eq γ,
  rw [product_eq_bind, prod_bind (λ x hx y hy h, eq_empty_of_forall_not_mem _)],
  { congr, funext, exact prod_image (λ _ _ _ _ H, (prod.mk.inj H).2) },
  simp only [mem_inter, mem_image], rintro ⟨_, _⟩ ⟨⟨_, _, _⟩, ⟨_, _, _⟩⟩, apply h, cc
end

@[to_additive finset.sum_sigma]
lemma prod_sigma {σ : α → Type*}
  {s : finset α} {t : Πa, finset (σ a)} {f : sigma σ → β} :
  (s.sigma t).prod f = s.prod (λa, (t a).prod $ λs, f ⟨a, s⟩) :=
by haveI := classical.dec_eq α; haveI := (λ a, classical.dec_eq (σ a)); exact
calc (s.sigma t).prod f =
       (s.bind (λa, (t a).image (λs, sigma.mk a s))).prod f : by rw sigma_eq_bind
  ... = s.prod (λa, ((t a).image (λs, sigma.mk a s)).prod f) :
    prod_bind $ assume a₁ ha a₂ ha₂ h, eq_empty_of_forall_not_mem $
    by simp only [mem_inter, mem_image];
    rintro ⟨_, _⟩ ⟨⟨_, _, _⟩, ⟨_, _, _⟩⟩; apply h; cc
  ... = (s.prod $ λa, (t a).prod $ λs, f ⟨a, s⟩) :
    prod_congr rfl $ λ _ _, prod_image $ λ _ _ _ _ _, by cc

@[to_additive finset.sum_image']
lemma prod_image' [decidable_eq α] {s : finset γ} {g : γ → α} (h : γ → β)
  (eq : ∀c∈s, f (g c) = (s.filter (λc', g c' = g c)).prod h) :
  (s.image g).prod f = s.prod h :=
begin
  letI := classical.dec_eq γ,
  rw [← image_bind_filter_eq s g] {occs := occurrences.pos [2]},
  rw [finset.prod_bind],
  { refine finset.prod_congr rfl (assume a ha, _),
    rcases finset.mem_image.1 ha with ⟨b, hb, rfl⟩,
    exact eq b hb },
  assume a₀ _ a₁ _ ne,
  refine disjoint_iff_inter_eq_empty.1 (disjoint_iff_ne.2 _),
  assume c₀ h₀ c₁ h₁,
  rcases mem_filter.1 h₀ with ⟨h₀, rfl⟩,
  rcases mem_filter.1 h₁ with ⟨h₁, rfl⟩,
  exact mt (congr_arg g) ne
end

@[to_additive finset.sum_add_distrib]
lemma prod_mul_distrib : s.prod (λx, f x * g x) = s.prod f * s.prod g :=
eq.trans (by rw one_mul; refl) fold_op_distrib

@[to_additive finset.sum_comm]
lemma prod_comm [decidable_eq γ] {s : finset γ} {t : finset α} {f : γ → α → β} :
  s.prod (λx, t.prod $ f x) = t.prod (λy, s.prod $ λx, f x y) :=
finset.induction_on s (by simp only [prod_empty, prod_const_one]) $
λ _ _ H ih, by simp only [prod_insert H, prod_mul_distrib, ih]

lemma prod_hom [comm_monoid γ] (g : β → γ) [is_monoid_hom g] : s.prod (λx, g (f x)) = g (s.prod f) :=
eq.trans (by rw is_monoid_hom.map_one g; refl) (fold_hom (λ _ _, is_monoid_hom.map_mul g))

@[to_additive finset.sum_hom_rel]
lemma prod_hom_rel [comm_monoid γ] {r : β → γ → Prop} {f : α → β} {g : α → γ} {s : finset α}
  (h₁ : r 1 1) (h₂ : ∀a b c, r b c → r (f a * b) (g a * c)) : r (s.prod f) (s.prod g) :=
begin
  letI := classical.dec_eq α,
  refine finset.induction_on s h₁ (assume a s has ih, _),
  rw [prod_insert has, prod_insert has],
  exact h₂ a (s.prod f) (s.prod g) ih,
end

@[to_additive finset.sum_subset]
lemma prod_subset (h : s₁ ⊆ s₂) (hf : ∀x∈s₂, x ∉ s₁ → f x = 1) : s₁.prod f = s₂.prod f :=
by haveI := classical.dec_eq α; exact
have (s₂ \ s₁).prod f = (s₂ \ s₁).prod (λx, 1),
  from prod_congr rfl $ by simpa only [mem_sdiff, and_imp],
by rw [←prod_sdiff h]; simp only [this, prod_const_one, one_mul]

@[to_additive sum_filter]
lemma prod_filter (p : α → Prop) [decidable_pred p] (f : α → β) :
  (s.filter p).prod f = s.prod (λa, if p a then f a else 1) :=
calc (s.filter p).prod f = (s.filter p).prod (λa, if p a then f a else 1) :
    prod_congr rfl (assume a h, by rw [if_pos (mem_filter.1 h).2])
  ... = s.prod (λa, if p a then f a else 1) :
    begin
      refine prod_subset (filter_subset s) (assume x hs h, _),
      rw [mem_filter, not_and] at h,
      exact if_neg (h hs)
    end

@[to_additive finset.sum_eq_single]
lemma prod_eq_single {s : finset α} {f : α → β} (a : α)
  (h₀ : ∀b∈s, b ≠ a → f b = 1) (h₁ : a ∉ s → f a = 1) : s.prod f = f a :=
by haveI := classical.dec_eq α;
from classical.by_cases
  (assume : a ∈ s,
    calc s.prod f = ({a} : finset α).prod f :
      begin
        refine (prod_subset _ _).symm,
        { intros _ H, rwa mem_singleton.1 H },
        { simpa only [mem_singleton] }
      end
      ... = f a : prod_singleton)
  (assume : a ∉ s,
    (prod_congr rfl $ λ b hb, h₀ b hb $ by rintro rfl; cc).trans $
      prod_const_one.trans (h₁ this).symm)

@[to_additive finset.sum_attach]
lemma prod_attach {f : α → β} : s.attach.prod (λx, f x.val) = s.prod f :=
by haveI := classical.dec_eq α; exact
calc s.attach.prod (λx, f x.val) = ((s.attach).image subtype.val).prod f :
    by rw [prod_image]; exact assume x _ y _, subtype.eq
  ... = _ : by rw [attach_image_val]

@[to_additive finset.sum_bij]
lemma prod_bij {s : finset α} {t : finset γ} {f : α → β} {g : γ → β}
  (i : Πa∈s, γ) (hi : ∀a ha, i a ha ∈ t) (h : ∀a ha, f a = g (i a ha))
  (i_inj : ∀a₁ a₂ ha₁ ha₂, i a₁ ha₁ = i a₂ ha₂ → a₁ = a₂) (i_surj : ∀b∈t, ∃a ha, b = i a ha) :
  s.prod f = t.prod g :=
by haveI := classical.prop_decidable; exact
calc s.prod f = s.attach.prod (λx, f x.val) : prod_attach.symm
  ... = s.attach.prod (λx, g (i x.1 x.2)) : prod_congr rfl $ assume x hx, h _ _
  ... = (s.attach.image $ λx:{x // x ∈ s}, i x.1 x.2).prod g :
    (prod_image $ assume (a₁:{x // x ∈ s}) _ a₂ _ eq, subtype.eq $ i_inj a₁.1 a₂.1 a₁.2 a₂.2 eq).symm
  ... = t.prod g :
      prod_subset
        (by simp only [subset_iff, mem_image, mem_attach]; rintro _ ⟨⟨_, _⟩, _, rfl⟩; solve_by_elim)
        (assume b hb hb1, false.elim $ hb1 $ by rcases i_surj b hb with ⟨a, ha, rfl⟩;
          exact mem_image.2 ⟨⟨_, _⟩, mem_attach _ _, rfl⟩)

@[to_additive finset.sum_bij_ne_zero]
lemma prod_bij_ne_one {s : finset α} {t : finset γ} {f : α → β} {g : γ → β}
  (i : Πa∈s, f a ≠ 1 → γ) (hi₁ : ∀a h₁ h₂, i a h₁ h₂ ∈ t)
  (hi₂ : ∀a₁ a₂ h₁₁ h₁₂ h₂₁ h₂₂, i a₁ h₁₁ h₁₂ = i a₂ h₂₁ h₂₂ → a₁ = a₂)
  (hi₃ : ∀b∈t, g b ≠ 1 → ∃a h₁ h₂, b = i a h₁ h₂)
  (h : ∀a h₁ h₂, f a = g (i a h₁ h₂)) :
  s.prod f = t.prod g :=
by haveI := classical.prop_decidable; exact
calc s.prod f = (s.filter $ λx, f x ≠ 1).prod f :
    (prod_subset (filter_subset _) $ by simp only [not_imp_comm, mem_filter]; exact λ _, and.intro).symm
  ... = (t.filter $ λx, g x ≠ 1).prod g :
    prod_bij (assume a ha, i a (mem_filter.mp ha).1 (mem_filter.mp ha).2)
      (assume a ha, (mem_filter.mp ha).elim $ λh₁ h₂, mem_filter.mpr
        ⟨hi₁ a h₁ h₂, λ hg, h₂ (hg ▸ h a h₁ h₂)⟩)
      (assume a ha, (mem_filter.mp ha).elim $ h a)
      (assume a₁ a₂ ha₁ ha₂,
        (mem_filter.mp ha₁).elim $ λha₁₁ ha₁₂, (mem_filter.mp ha₂).elim $ λha₂₁ ha₂₂, hi₂ a₁ a₂ _ _ _ _)
      (assume b hb, (mem_filter.mp hb).elim $ λh₁ h₂,
        let ⟨a, ha₁, ha₂, eq⟩ := hi₃ b h₁ h₂ in ⟨a, mem_filter.mpr ⟨ha₁, ha₂⟩, eq⟩)
  ... = t.prod g :
    (prod_subset (filter_subset _) $ by simp only [not_imp_comm, mem_filter]; exact λ _, and.intro)

@[to_additive finset.exists_ne_zero_of_sum_ne_zero]
lemma exists_ne_one_of_prod_ne_one : s.prod f ≠ 1 → ∃a∈s, f a ≠ 1 :=
by haveI := classical.dec_eq α; exact
finset.induction_on s (λ H, (H rfl).elim) (assume a s has ih h,
  classical.by_cases
    (assume ha : f a = 1,
      let ⟨a, ha, hfa⟩ := ih (by rwa [prod_insert has, ha, one_mul] at h) in
      ⟨a, mem_insert_of_mem ha, hfa⟩)
    (assume hna : f a ≠ 1,
      ⟨a, mem_insert_self _ _, hna⟩))

@[to_additive finset.sum_range_succ]
lemma prod_range_succ (f : ℕ → β) (n : ℕ) :
  (range (nat.succ n)).prod f = f n * (range n).prod f :=
by rw [range_succ, prod_insert not_mem_range_self]

lemma prod_range_succ' (f : ℕ → β) :
  ∀ n : ℕ, (range (nat.succ n)).prod f = (range n).prod (f ∘ nat.succ) * f 0
| 0       := (prod_range_succ _ _).trans $ mul_comm _ _
| (n + 1) := by rw [prod_range_succ (λ m, f (nat.succ m)), mul_assoc, ← prod_range_succ'];
                 exact prod_range_succ _ _

@[simp] lemma prod_const (b : β) : s.prod (λ a, b) = b ^ s.card :=
by haveI := classical.dec_eq α; exact
finset.induction_on s rfl (λ a s has ih,
by rw [prod_insert has, card_insert_of_not_mem has, pow_succ, ih])

lemma prod_pow (s : finset α) (n : ℕ) (f : α → β) :
  s.prod (λ x, f x ^ n) = s.prod f ^ n :=
by haveI := classical.dec_eq α; exact
finset.induction_on s (by simp) (by simp [_root_.mul_pow] {contextual := tt})

lemma prod_nat_pow (s : finset α) (n : ℕ) (f : α → ℕ) :
  s.prod (λ x, f x ^ n) = s.prod f ^ n :=
by haveI := classical.dec_eq α; exact
finset.induction_on s (by simp) (by simp [nat.mul_pow] {contextual := tt})

@[to_additive finset.sum_involution]
lemma prod_involution {s : finset α} {f : α → β} :
  ∀ (g : Π a ∈ s, α)
  (h₁ : ∀ a ha, f a * f (g a ha) = 1)
  (h₂ : ∀ a ha, f a ≠ 1 → g a ha ≠ a)
  (h₃ : ∀ a ha, g a ha ∈ s)
  (h₄ : ∀ a ha, g (g a ha) (h₃ a ha) = a),
  s.prod f = 1 :=
by haveI := classical.dec_eq α;
haveI := classical.dec_eq β; exact
finset.strong_induction_on s
  (λ s ih g h₁ h₂ h₃ h₄,
    if hs : s = ∅ then hs.symm ▸ rfl
    else let ⟨x, hx⟩ := exists_mem_of_ne_empty hs in
      have hmem : ∀ y ∈ (s.erase x).erase (g x hx), y ∈ s,
        from λ y hy, (mem_of_mem_erase (mem_of_mem_erase hy)),
      have g_inj : ∀ {x hx y hy}, g x hx = g y hy → x = y,
        from λ x hx y hy h, by rw [← h₄ x hx, ← h₄ y hy]; simp [h],
      have ih': (erase (erase s x) (g x hx)).prod f = (1 : β) :=
        ih ((s.erase x).erase (g x hx))
          ⟨subset.trans (erase_subset _ _) (erase_subset _ _),
            λ h, not_mem_erase (g x hx) (s.erase x) (h (h₃ x hx))⟩
          (λ y hy, g y (hmem y hy))
          (λ y hy, h₁ y (hmem y hy))
          (λ y hy, h₂ y (hmem y hy))
          (λ y hy, mem_erase.2 ⟨λ (h : g y _ = g x hx), by simpa [g_inj h] using hy,
            mem_erase.2 ⟨λ (h : g y _ = x),
              have y = g x hx, from h₄ y (hmem y hy) ▸ by simp [h],
              by simpa [this] using hy, h₃ y (hmem y hy)⟩⟩)
          (λ y hy, h₄ y (hmem y hy)),
      if hx1 : f x = 1
      then ih' ▸ eq.symm (prod_subset hmem
        (λ y hy hy₁,
          have y = x ∨ y = g x hx, by simp [hy] at hy₁; tauto,
          this.elim (λ h, h.symm ▸ hx1)
            (λ h, h₁ x hx ▸ h ▸ hx1.symm ▸ (one_mul _).symm)))
      else by rw [← insert_erase hx, prod_insert (not_mem_erase _ _),
        ← insert_erase (mem_erase.2 ⟨h₂ x hx hx1, h₃ x hx⟩),
        prod_insert (not_mem_erase _ _), ih', mul_one, h₁ x hx])

@[to_additive finset.sum_eq_zero]
lemma prod_eq_one [comm_monoid β] {f : α → β} {s : finset α} (h : ∀x∈s, f x = 1) : s.prod f = 1 :=
calc s.prod f = s.prod (λx, 1) : finset.prod_congr rfl h
  ... = 1 : finset.prod_const_one

end comm_monoid

lemma sum_hom [add_comm_monoid β] [add_comm_monoid γ] (g : β → γ) [is_add_monoid_hom g] :
  s.sum (λx, g (f x)) = g (s.sum f) :=
eq.trans (by rw is_add_monoid_hom.map_zero g; refl) (fold_hom (λ _ _, is_add_monoid_hom.map_add g))
attribute [to_additive finset.sum_hom] prod_hom

lemma sum_smul [add_comm_monoid β] (s : finset α) (n : ℕ) (f : α → β) :
  s.sum (λ x, add_monoid.smul n (f x)) = add_monoid.smul n (s.sum f) :=
@prod_pow _ (multiplicative β) _ _ _ _
attribute [to_additive finset.sum_smul] prod_pow

@[simp] lemma sum_const [add_comm_monoid β] (b : β) :
  s.sum (λ a, b) = add_monoid.smul s.card b :=
@prod_const _ (multiplicative β) _ _ _
attribute [to_additive finset.sum_const] prod_const

lemma sum_range_succ' [add_comm_monoid β] (f : ℕ → β) :
  ∀ n : ℕ, (range (nat.succ n)).sum f = (range n).sum (f ∘ nat.succ) + f 0 :=
@prod_range_succ' (multiplicative β) _ _
attribute [to_additive finset.sum_range_succ'] prod_range_succ'

lemma sum_nat_cast [add_comm_monoid β] [has_one β] (s : finset α) (f : α → ℕ) :
  ↑(s.sum f) = s.sum (λa, f a : α → β) :=
(sum_hom _).symm

lemma le_sum_of_subadditive [add_comm_monoid α] [ordered_comm_monoid β]
  (f : α → β) (h_zero : f 0 = 0) (h_add : ∀x y, f (x + y) ≤ f x + f y) (s : finset γ) (g : γ → α) :
  f (s.sum g) ≤ s.sum (λc, f (g c)):=
begin
  refine le_trans (multiset.le_sum_of_subadditive f h_zero h_add _) _,
  rw [multiset.map_map],
  refl
end

lemma abs_sum_le_sum_abs [discrete_linear_ordered_field α] {f : β → α} {s : finset β} :
  abs (s.sum f) ≤ s.sum (λa, abs (f a)) :=
le_sum_of_subadditive _ abs_zero abs_add s f

section comm_group
variables [comm_group β]

@[simp, to_additive finset.sum_neg_distrib]
lemma prod_inv_distrib : s.prod (λx, (f x)⁻¹) = (s.prod f)⁻¹ :=
prod_hom has_inv.inv

end comm_group

@[simp] theorem card_sigma {σ : α → Type*} (s : finset α) (t : Π a, finset (σ a)) :
  card (s.sigma t) = s.sum (λ a, card (t a)) :=
multiset.card_sigma _ _

lemma card_bind [decidable_eq β] {s : finset α} {t : α → finset β}
  (h : ∀ x ∈ s, ∀ y ∈ s, x ≠ y → t x ∩ t y = ∅) :
  (s.bind t).card = s.sum (λ u, card (t u)) :=
calc (s.bind t).card = (s.bind t).sum (λ _, 1) : by simp
... = s.sum (λ a, (t a).sum (λ _, 1)) : finset.sum_bind h
... = s.sum (λ u, card (t u)) : by simp

lemma card_bind_le [decidable_eq β] {s : finset α} {t : α → finset β} :
  (s.bind t).card ≤ s.sum (λ a, (t a).card) :=
by haveI := classical.dec_eq α; exact
finset.induction_on s (by simp)
  (λ a s has ih,
    calc ((insert a s).bind t).card ≤ (t a).card + (s.bind t).card :
    by rw bind_insert; exact finset.card_union_le _ _
    ... ≤ (insert a s).sum (λ a, card (t a)) :
    by rw sum_insert has; exact add_le_add_left ih _)

lemma gsmul_sum [add_comm_group β] {f : α → β} {s : finset α} (z : ℤ) :
  gsmul z (s.sum f) = s.sum (λa, gsmul z (f a)) :=
(finset.sum_hom (gsmul z)).symm

end finset

namespace finset
variables {s s₁ s₂ : finset α} {f g : α → β} {b : β} {a : α}

@[simp] lemma sum_sub_distrib [add_comm_group β] : s.sum (λx, f x - g x) = s.sum f - s.sum g :=
sum_add_distrib.trans $ congr_arg _ sum_neg_distrib

section ordered_cancel_comm_monoid
variables [decidable_eq α] [ordered_cancel_comm_monoid β]

lemma sum_le_sum : (∀x∈s, f x ≤ g x) → s.sum f ≤ s.sum g :=
finset.induction_on s (λ _, le_refl _) $ assume a s ha ih h,
  have f a + s.sum f ≤ g a + s.sum g,
    from add_le_add (h _ (mem_insert_self _ _)) (ih $ assume x hx, h _ $ mem_insert_of_mem hx),
  by simpa only [sum_insert ha]

lemma zero_le_sum (h : ∀x∈s, 0 ≤ f x) : 0 ≤ s.sum f := le_trans (by rw [sum_const_zero]) (sum_le_sum h)

lemma sum_le_zero (h : ∀x∈s, f x ≤ 0) : s.sum f ≤ 0 := le_trans (sum_le_sum h) (by rw [sum_const_zero])

end ordered_cancel_comm_monoid

section semiring
variables [semiring β]

lemma sum_mul : s.sum f * b = s.sum (λx, f x * b) :=
(sum_hom (λx, x * b)).symm

lemma mul_sum : b * s.sum f = s.sum (λx, b * f x) :=
(sum_hom (λx, b * x)).symm

end semiring

section comm_semiring
variables [decidable_eq α] [comm_semiring β]

lemma prod_eq_zero (ha : a ∈ s) (h : f a = 0) : s.prod f = 0 :=
calc s.prod f = (insert a (erase s a)).prod f : by rw insert_erase ha
  ... = 0 : by rw [prod_insert (not_mem_erase _ _), h, zero_mul]

lemma prod_sum {δ : α → Type*} [∀a, decidable_eq (δ a)]
  {s : finset α} {t : Πa, finset (δ a)} {f : Πa, δ a → β} :
  s.prod (λa, (t a).sum (λb, f a b)) =
    (s.pi t).sum (λp, s.attach.prod (λx, f x.1 (p x.1 x.2))) :=
begin
  induction s using finset.induction with a s ha ih,
  { rw [pi_empty, sum_singleton], refl },
  { have h₁ : ∀x ∈ t a, ∀y ∈ t a, ∀h : x ≠ y,
        image (pi.cons s a x) (pi s t) ∩ image (pi.cons s a y) (pi s t) = ∅,
    { assume x hx y hy h,
      apply eq_empty_of_forall_not_mem,
      simp only [mem_inter, mem_image],
      rintro p₁ ⟨⟨p₂, hp, eq⟩, ⟨p₃, hp₃, rfl⟩⟩,
      have : pi.cons s a x p₂ a (mem_insert_self _ _) = pi.cons s a y p₃ a (mem_insert_self _ _),
      { rw [eq] },
      rw [pi.cons_same, pi.cons_same] at this,
      exact h this },
    rw [prod_insert ha, pi_insert ha, ih, sum_mul, sum_bind h₁],
    refine sum_congr rfl (λ b _, _),
    have h₂ : ∀p₁∈pi s t, ∀p₂∈pi s t, pi.cons s a b p₁ = pi.cons s a b p₂ → p₁ = p₂, from
      assume p₁ h₁ p₂ h₂ eq, injective_pi_cons ha eq,
    rw [sum_image h₂, mul_sum],
    refine sum_congr rfl (λ g _, _),
    rw [attach_insert, prod_insert, prod_image],
    { simp only [pi.cons_same],
      congr', ext ⟨v, hv⟩, congr',
      exact (pi.cons_ne (by rintro rfl; exact ha hv)).symm },
    { exact λ _ _ _ _, subtype.eq ∘ subtype.mk.inj },
    { simp only [mem_image], rintro ⟨⟨_, hm⟩, _, rfl⟩, exact ha hm } }
end

end comm_semiring

section integral_domain /- add integral_semi_domain to support nat and ennreal -/
variables [decidable_eq α] [integral_domain β]

lemma prod_eq_zero_iff : s.prod f = 0 ↔ (∃a∈s, f a = 0) :=
finset.induction_on s ⟨not.elim one_ne_zero, λ ⟨_, H, _⟩, H.elim⟩ $ λ a s ha ih,
by rw [prod_insert ha, mul_eq_zero_iff_eq_zero_or_eq_zero,
  bex_def, exists_mem_insert, ih, ← bex_def]

end integral_domain

section ordered_comm_monoid
variables [decidable_eq α] [ordered_comm_monoid β]

lemma sum_le_sum' : (∀x∈s, f x ≤ g x) → s.sum f ≤ s.sum g :=
finset.induction_on s (λ _, le_refl _) $ assume a s ha ih h,
  have f a + s.sum f ≤ g a + s.sum g,
    from add_le_add' (h _ (mem_insert_self _ _)) (ih $ assume x hx, h _ $ mem_insert_of_mem hx),
  by simpa only [sum_insert ha]

lemma zero_le_sum' (h : ∀x∈s, 0 ≤ f x) : 0 ≤ s.sum f := le_trans (by rw [sum_const_zero]) (sum_le_sum' h)

lemma sum_le_zero' (h : ∀x∈s, f x ≤ 0) : s.sum f ≤ 0 := le_trans (sum_le_sum' h) (by rw [sum_const_zero])

lemma sum_le_sum_of_subset_of_nonneg
  (h : s₁ ⊆ s₂) (hf : ∀x∈s₂, x ∉ s₁ → 0 ≤ f x) : s₁.sum f ≤ s₂.sum f :=
calc s₁.sum f ≤ (s₂ \ s₁).sum f + s₁.sum f :
    le_add_of_nonneg_left' $ zero_le_sum' $ by simpa only [mem_sdiff, and_imp]
  ... = (s₂ \ s₁ ∪ s₁).sum f : (sum_union (sdiff_inter_self _ _)).symm
  ... = s₂.sum f : by rw [sdiff_union_of_subset h]

lemma sum_eq_zero_iff_of_nonneg : (∀x∈s, 0 ≤ f x) → (s.sum f = 0 ↔ ∀x∈s, f x = 0) :=
finset.induction_on s (λ _, ⟨λ _ _, false.elim, λ _, rfl⟩) $ λ a s ha ih H,
have ∀ x ∈ s, 0 ≤ f x, from λ _, H _ ∘ mem_insert_of_mem,
by rw [sum_insert ha,
  add_eq_zero_iff' (H _ $ mem_insert_self _ _) (zero_le_sum' this),
  forall_mem_insert, ih this]

lemma single_le_sum (hf : ∀x∈s, 0 ≤ f x) {a} (h : a ∈ s) : f a ≤ s.sum f :=
have (singleton a).sum f ≤ s.sum f,
  from sum_le_sum_of_subset_of_nonneg
  (λ x e, (mem_singleton.1 e).symm ▸ h) (λ x h _, hf x h),
by rwa sum_singleton at this

end ordered_comm_monoid

section canonically_ordered_monoid
variables [decidable_eq α] [canonically_ordered_monoid β] [@decidable_rel β (≤)]

lemma sum_le_sum_of_subset (h : s₁ ⊆ s₂) : s₁.sum f ≤ s₂.sum f :=
sum_le_sum_of_subset_of_nonneg h $ assume x h₁ h₂, zero_le _

lemma sum_le_sum_of_ne_zero (h : ∀x∈s₁, f x ≠ 0 → x ∈ s₂) : s₁.sum f ≤ s₂.sum f :=
calc s₁.sum f = (s₁.filter (λx, f x = 0)).sum f + (s₁.filter (λx, f x ≠ 0)).sum f :
    by rw [←sum_union, filter_union_filter_neg_eq]; apply filter_inter_filter_neg_eq
  ... ≤ s₂.sum f : add_le_of_nonpos_of_le'
      (sum_le_zero' $ by simp only [mem_filter, and_imp]; exact λ _ _, le_of_eq)
      (sum_le_sum_of_subset $ by simpa only [subset_iff, mem_filter, and_imp])

end canonically_ordered_monoid

@[simp] lemma card_pi [decidable_eq α] {δ : α → Type*}
  (s : finset α) (t : Π a, finset (δ a)) :
  (s.pi t).card = s.prod (λ a, card (t a)) :=
multiset.card_pi _ _

@[simp] lemma prod_range_id_eq_fact (n : ℕ) : ((range n.succ).erase 0).prod (λ x, x) = nat.fact n :=
calc ((range n.succ).erase 0).prod (λ x, x) = (range n).prod nat.succ :
eq.symm (prod_bij (λ x _, nat.succ x)
  (λ a h₁, mem_erase.2 ⟨nat.succ_ne_zero _, mem_range.2 $ nat.succ_lt_succ $ by simpa using h₁⟩)
  (by simp) (λ _ _ _ _, nat.succ_inj)
  (λ b h,
    have b.pred.succ = b, from nat.succ_pred_eq_of_pos $
      by simp [nat.pos_iff_ne_zero] at *; tauto,
    ⟨nat.pred b, mem_range.2 $ nat.lt_of_succ_lt_succ (by simp [*] at *), this.symm⟩))
... = nat.fact n : by induction n; simp [*, range_succ]

end finset

section geom_sum
open finset

theorem geom_sum_mul_add [semiring α] (x : α) :
  ∀ (n : ℕ), ((range n).sum (λ i, (x+1)^i)) * x + 1 = (x+1)^n
| 0     := by simp
| (n+1) := calc (range (n + 1)).sum (λi, (x + 1) ^ i) * x + 1 =
        (x + 1)^n * x + (((range n).sum (λ i, (x+1)^i)) * x + 1) :
    by simp [range_add_one, add_mul]
  ... = (x + 1)^n * x + (x + 1)^n :
    by rw geom_sum_mul_add n
  ... = (x + 1) ^ (n + 1) :
    by simp [pow_add, mul_add]

theorem geom_sum_mul [ring α] (x : α) (n : ℕ) :
  ((range n).sum (λ i, x^i)) * (x-1) = x^n-1 :=
have _ := geom_sum_mul_add (x-1) n,
by rw [sub_add_cancel] at this; rw [← this, add_sub_cancel]

theorem geom_sum [division_ring α] {x : α} (h : x ≠ 1) (n : ℕ) :
  (range n).sum (λ i, x^i) = (x^n-1)/(x-1) :=
have x - 1 ≠ 0, by simp [*, -sub_eq_add_neg, sub_eq_iff_eq_add] at *,
by rw [← geom_sum_mul, mul_div_cancel _ this]

lemma geom_sum_inv [division_ring α] {x : α} (hx1 : x ≠ 1) (hx0 : x ≠ 0) (n : ℕ) :
  (range n).sum (λ m, x⁻¹ ^ m) = (x - 1)⁻¹ * (x - x⁻¹ ^ n * x) :=
have h₁ : x⁻¹ ≠ 1, by rwa [inv_eq_one_div, ne.def, div_eq_iff_mul_eq hx0, one_mul],
have h₂ : x⁻¹ - 1 ≠ 0, from mt sub_eq_zero.1 h₁,
have h₃ : x - 1 ≠ 0, from mt sub_eq_zero.1 hx1,
have h₄ : x * x⁻¹ ^ n = x⁻¹ ^ n * x,
  from nat.cases_on n (by simp)
  (λ _, by conv { to_rhs, rw [pow_succ', mul_assoc, inv_mul_cancel hx0, mul_one] };
    rw [pow_succ, ← mul_assoc, mul_inv_cancel hx0, one_mul]),
by rw [geom_sum h₁, div_eq_iff_mul_eq h₂, ← domain.mul_left_inj h₃,
    ← mul_assoc, ← mul_assoc, mul_inv_cancel h₃];
  simp [mul_add, add_mul, mul_inv_cancel hx0, mul_assoc, h₄]

end geom_sum

namespace finset
section gauss_sum

/-- Gauss' summation formula -/
lemma sum_range_id_mul_two :
  ∀(n : ℕ), (finset.range n).sum (λi, i) * 2 = n * (n - 1)
| 0       := rfl
| 1       := rfl
| ((n + 1) + 1) :=
  begin
    rw [sum_range_succ, add_mul, sum_range_id_mul_two (n + 1), mul_comm, two_mul,
      nat.add_sub_cancel, nat.add_sub_cancel, mul_comm _ n],
    simp only [add_mul, one_mul, add_comm, add_assoc, add_left_comm]
  end

/-- Gauss' summation formula -/
lemma sum_range_id (n : ℕ) : (finset.range n).sum (λi, i) = (n * (n - 1)) / 2 :=
by rw [← sum_range_id_mul_two n, nat.mul_div_cancel]; exact dec_trivial

end gauss_sum
end finset

section group

open list
variables [group α] [group β]

@[to_additive is_add_group_hom.sum]
theorem is_group_hom.prod (f : α → β) [is_group_hom f] (l : list α) :
  f (prod l) = prod (map f l) :=
by induction l; simp only [*, is_group_hom.mul f, is_group_hom.one f, prod_nil, prod_cons, map]

theorem is_group_anti_hom.prod (f : α → β) [is_group_anti_hom f] (l : list α) :
  f (prod l) = prod (map f (reverse l)) :=
by induction l with hd tl ih; [exact is_group_anti_hom.one f,
  simp only [prod_cons, is_group_anti_hom.mul f, ih, reverse_cons, map_append, prod_append, map_singleton, prod_cons, prod_nil, mul_one]]

theorem inv_prod : ∀ l : list α, (prod l)⁻¹ = prod (map (λ x, x⁻¹) (reverse l)) :=
λ l, @is_group_anti_hom.prod _ _ _ _ _ inv_is_group_anti_hom l -- TODO there is probably a cleaner proof of this

end group

section comm_group
variables [comm_group α] [comm_group β] (f : α → β) [is_group_hom f]

@[to_additive is_add_group_hom.multiset_sum]
lemma is_group_hom.multiset_prod (m : multiset α) : f m.prod = (m.map f).prod :=
quotient.induction_on m $ assume l, by simp [is_group_hom.prod f l]

@[to_additive is_add_group_hom.finset_sum]
lemma is_group_hom.finset_prod (g : γ → α) (s : finset γ) : f (s.prod g) = s.prod (f ∘ g) :=
show f (s.val.map g).prod = (s.val.map (f ∘ g)).prod, by rw [is_group_hom.multiset_prod f]; simp

end comm_group

@[to_additive is_add_group_hom_finset_sum]
lemma is_group_hom_finset_prod {α β γ} [group α] [comm_group β] (s : finset γ)
  (f : γ → α → β) [∀c, is_group_hom (f c)] : is_group_hom (λa, s.prod (λc, f c a)) :=
⟨assume a b, by simp only [λc, is_group_hom.mul (f c), finset.prod_mul_distrib]⟩

attribute [instance] is_group_hom_finset_prod is_add_group_hom_finset_sum

namespace multiset
variables [decidable_eq α]

@[simp] lemma to_finset_sum_count_eq (s : multiset α) :
  s.to_finset.sum (λa, s.count a) = s.card :=
multiset.induction_on s rfl
  (assume a s ih,
    calc (to_finset (a :: s)).sum (λx, count x (a :: s)) =
      (to_finset (a :: s)).sum (λx, (if x = a then 1 else 0) + count x s) :
        finset.sum_congr rfl $ λ _ _, by split_ifs;
        [simp only [h, count_cons_self, nat.one_add], simp only [count_cons_of_ne h, zero_add]]
      ... = card (a :: s) :
      begin
        by_cases a ∈ s.to_finset,
        { have : (to_finset s).sum (λx, ite (x = a) 1 0) = (finset.singleton a).sum (λx, ite (x = a) 1 0),
          { apply (finset.sum_subset _ _).symm,
            { intros _ H, rwa mem_singleton.1 H },
            { exact λ _ _ H, if_neg (mt finset.mem_singleton.2 H) } },
          rw [to_finset_cons, finset.insert_eq_of_mem h, finset.sum_add_distrib, ih, this, finset.sum_singleton, if_pos rfl, add_comm, card_cons] },
        { have ha : a ∉ s, by rwa mem_to_finset at h,
          have : (to_finset s).sum (λx, ite (x = a) 1 0) = (to_finset s).sum (λx, 0), from
            finset.sum_congr rfl (λ x hx, if_neg $ by rintro rfl; cc),
          rw [to_finset_cons, finset.sum_insert h, if_pos rfl, finset.sum_add_distrib, this, finset.sum_const_zero, ih, count_eq_zero_of_not_mem ha, zero_add, add_comm, card_cons] }
      end)

end multiset


import advent_of_code_2019.common
import tactic.norm_num
import tactic.suggest
import tactic.linarith

namespace day1

def fuel_required (mass : ℤ) : ℤ := (mass / 3) - 2

example : fuel_required 12 = 2 := by refl
example : fuel_required 14 = 2 := by refl
--example : fuel_required 1969 = 654 := by norm_num
--example : fuel_required 100756 = 335830 := by norm_num

def part_one : list ℤ → ℤ := list.sum ∘ list.map fuel_required

-- set_option trace.class_instances true

def nat_of_int (a : ℤ) : ℕ := begin
  cases a, exact a, exact 0
end

lemma nat_of_int_of_nat {x} : nat_of_int (int.of_nat x) = x := by refl

-- lemma le_nat_of_int : a < 0 →

lemma coe_nat_of_int {x : ℤ} (h : x ≥ 0) : ↑(nat_of_int x) = x :=
begin
  cases x,
  rw nat_of_int_of_nat,
  refl,
  cases h,
end

lemma additional_fuel_wf (m : ℤ) (h : 0 < m) :
nat_of_int (fuel_required m + 2) < nat_of_int (m + 2)
:= begin
  dunfold fuel_required,
  apply int.lt_of_coe_nat_lt_coe_nat,
  rw coe_nat_of_int, rw coe_nat_of_int,
  calc m / 3 - 2 + 2 = m / 3 : by simp
  ... ≤ m : by { apply int.div_le_self, apply int.le_of_lt, assumption }
  ... < m + 2 : by simp,
  linarith,
  have rr : m / 3 - 2 + 2 = m / 3 := by simp, rw rr,
  norm_num,
  apply int.div_nonneg,
  exact le_of_lt h,
  norm_num,
end

@[reducible]
def additional_fuel : ℤ → ℤ
| m := if h : 0 < m then have _ := additional_fuel_wf, m + additional_fuel (fuel_required m) else 0
using_well_founded {
  dec_tac := `[apply this, assumption],
  rel_tac := λ _ _, `[exact ⟨_, measure_wf (nat_of_int ∘ (+2))⟩] }

example : additional_fuel 14 = 16 := begin
  unfold1 additional_fuel,
  rw if_pos,
  unfold1 fuel_required additional_fuel,
  rw if_pos,
  unfold1 fuel_required additional_fuel,
  rw if_neg,
  repeat {norm_num},
end
#eval additional_fuel 1969

def part_two : list ℤ → ℤ := list.sum ∘ list.map (additional_fuel ∘ fuel_required)

end day1

def main : io unit :=
batch (both day1.part_one day1.part_two ∘ lines.list)


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

! This file was ported from Lean 3 source module init.logic
! leanprover-community/mathlib commit 4a03bdeb31b3688c31d02d7ff8e0ff2e5d6174db
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
prelude
import Leanbin.Init.Core

universe u v w

#print optParam_eq /-
@[simp]
theorem optParam_eq (α : Sort u) (default : α) : optParam α default = α :=
  rfl
#align opt_param_eq optParam_eq
-/

#print id /-
@[inline]
def id {α : Sort u} (a : α) : α :=
  a
#align id id
-/

#print flip /-
def flip {α : Sort u} {β : Sort v} {φ : Sort w} (f : α → β → φ) : β → α → φ := fun b a => f a b
#align flip flip
-/

#print Implies /-
/-- implication -/
def Implies (a b : Prop) :=
  a → b
#align implies Implies
-/

#print Implies.trans /-
/-- Implication `→` is transitive. If `P → Q` and `Q → R` then `P → R`. -/
@[trans]
theorem Implies.trans {p q r : Prop} (h₁ : Implies p q) (h₂ : Implies q r) : Implies p r :=
  fun hp => h₂ (h₁ hp)
#align implies.trans Implies.trans
-/

#print trivial /-
theorem trivial : True :=
  ⟨⟩
#align trivial trivial
-/

#print absurd /-
/-- We can't have `a` and `¬a`, that would be absurd!-/
@[inline]
def absurd {a : Prop} {b : Sort v} (h₁ : a) (h₂ : ¬a) : b :=
  False.ndrec b (h₂ h₁)
#align absurd absurd
-/

#print Not.intro /-
theorem Not.intro {a : Prop} (h : a → False) : ¬a :=
  h
#align not.intro Not.intro
-/

#print mt /-
/-- Modus tollens. If an implication is true, then so is its contrapositive. -/
theorem mt {a b : Prop} (h₁ : a → b) (h₂ : ¬b) : ¬a := fun ha : a => h₂ (h₁ ha)
#align mt mt
-/

/-! not -/

#print not_false /-
theorem not_false : ¬False :=
  id
#align not_false not_false
-/

#print NonContradictory /-
def NonContradictory (a : Prop) : Prop :=
  ¬¬a
#align non_contradictory NonContradictory
-/

#print not_not_intro /-
theorem not_not_intro {a : Prop} (ha : a) : ¬¬a := fun hna : ¬a => absurd ha hna
#align non_contradictory_intro not_not_intro
-/

/-! false -/

#print False.elim /-
@[inline]
def False.elim {C : Sort u} (h : False) : C :=
  False.ndrec C h
#align false.elim False.elim
-/

/-! eq -/

#print proof_irrel /-
-- proof irrelevance is built in
theorem proof_irrel {a : Prop} (h₁ h₂ : a) : h₁ = h₂ :=
  rfl
#align proof_irrel proof_irrel
-/

#print id.def /-
@[simp]
theorem id.def {α : Sort u} (a : α) : id a = a :=
  rfl
#align id.def id.def
-/

#print Eq.mp /-
@[inline]
def Eq.mp {α β : Sort u} : α = β → α → β :=
  Eq.recOn
#align eq.mp Eq.mp
-/

#print Eq.mpr /-
@[inline]
def Eq.mpr {α β : Sort u} : α = β → β → α := fun h₁ h₂ => Eq.recOn (Eq.symm h₁) h₂
#align eq.mpr Eq.mpr
-/

#print Eq.substr /-
@[elab_as_elim]
theorem Eq.substr {α : Sort u} {p : α → Prop} {a b : α} (h₁ : b = a) : p a → p b :=
  Eq.subst (Eq.symm h₁)
#align eq.substr Eq.substr
-/

#print congr /-
theorem congr {α : Sort u} {β : Sort v} {f₁ f₂ : α → β} {a₁ a₂ : α} (h₁ : f₁ = f₂) (h₂ : a₁ = a₂) :
    f₁ a₁ = f₂ a₂ :=
  Eq.subst h₁ (Eq.subst h₂ rfl)
#align congr congr
-/

#print congr_fun /-
theorem congr_fun {α : Sort u} {β : α → Sort v} {f g : ∀ x, β x} (h : f = g) (a : α) : f a = g a :=
  Eq.subst h (Eq.refl (f a))
#align congr_fun congr_fun
-/

#print congr_arg /-
theorem congr_arg {α : Sort u} {β : Sort v} {a₁ a₂ : α} (f : α → β) : a₁ = a₂ → f a₁ = f a₂ :=
  congr rfl
#align congr_arg congr_arg
-/

#print trans_rel_left /-
theorem trans_rel_left {α : Sort u} {a b c : α} (r : α → α → Prop) (h₁ : r a b) (h₂ : b = c) :
    r a c :=
  h₂ ▸ h₁
#align trans_rel_left trans_rel_left
-/

#print trans_rel_right /-
theorem trans_rel_right {α : Sort u} {a b c : α} (r : α → α → Prop) (h₁ : a = b) (h₂ : r b c) :
    r a c :=
  h₁.symm ▸ h₂
#align trans_rel_right trans_rel_right
-/

#print of_eq_true /-
theorem of_eq_true {p : Prop} (h : p = True) : p :=
  h.symm ▸ trivial
#align of_eq_true of_eq_true
-/

#print not_of_eq_false /-
theorem not_of_eq_false {p : Prop} (h : p = False) : ¬p := fun hp => h ▸ hp
#align not_of_eq_false not_of_eq_false
-/

#print cast /-
@[inline]
def cast {α β : Sort u} (h : α = β) (a : α) : β :=
  Eq.ndrec a h
#align cast cast
-/

#print cast_proof_irrel /-
theorem cast_proof_irrel {α β : Sort u} (h₁ h₂ : α = β) (a : α) : cast h₁ a = cast h₂ a :=
  rfl
#align cast_proof_irrel cast_proof_irrel
-/

#print cast_eq /-
theorem cast_eq {α : Sort u} (h : α = α) (a : α) : cast h a = a :=
  rfl
#align cast_eq cast_eq
-/

/-! ne -/

#print Ne /-
@[reducible]
def Ne {α : Sort u} (a b : α) :=
  ¬a = b
#align ne Ne
-/

#print Ne.def /-
@[simp]
theorem Ne.def {α : Sort u} (a b : α) : (a ≠ b) = ¬a = b :=
  rfl
#align ne.def Ne.def
-/

namespace Ne

variable {α : Sort u}

variable {a b : α}

#print Ne.intro /-
theorem intro (h : a = b → False) : a ≠ b :=
  h
#align ne.intro Ne.intro
-/

#print Ne.elim /-
theorem elim (h : a ≠ b) : a = b → False :=
  h
#align ne.elim Ne.elim
-/

#print Ne.irrefl /-
theorem irrefl (h : a ≠ a) : False :=
  h rfl
#align ne.irrefl Ne.irrefl
-/

#print Ne.symm /-
theorem symm (h : a ≠ b) : b ≠ a := fun h₁ : b = a => h h₁.symm
#align ne.symm Ne.symm
-/

end Ne

#print false_of_ne /-
theorem false_of_ne {α : Sort u} {a : α} : a ≠ a → False :=
  Ne.irrefl
#align false_of_ne false_of_ne
-/

section

variable {p : Prop}

#print ne_false_of_self /-
theorem ne_false_of_self : p → p ≠ False := fun (hp : p) (heq : p = False) => HEq ▸ hp
#align ne_false_of_self ne_false_of_self
-/

#print ne_true_of_not /-
theorem ne_true_of_not : ¬p → p ≠ True := fun (hnp : ¬p) (heq : p = True) => (HEq ▸ hnp) trivial
#align ne_true_of_not ne_true_of_not
-/

#print true_ne_false /-
theorem true_ne_false : ¬True = False :=
  ne_false_of_self trivial
#align true_ne_false true_ne_false
-/

end

attribute [refl] HEq.refl

section

variable {α β φ : Sort u} {a a' : α} {b b' : β} {c : φ}

#print HEq.elim /-
def HEq.elim {α : Sort u} {a : α} {p : α → Sort v} {b : α} (h₁ : HEq a b) : p a → p b :=
  Eq.recOn (eq_of_hEq h₁)
#align heq.elim HEq.elim
-/

#print HEq.subst /-
theorem HEq.subst {p : ∀ T : Sort u, T → Prop} : HEq a b → p α a → p β b :=
  HEq.recOn
#align heq.subst HEq.subst
-/

#print HEq.symm /-
@[symm]
theorem HEq.symm (h : HEq a b) : HEq b a :=
  HEq.recOn h (HEq.refl a)
#align heq.symm HEq.symm
-/

theorem hEq_of_eq (h : a = a') : HEq a a' :=
  Eq.subst h (HEq.refl a)
#align heq_of_eq hEq_of_eq

#print HEq.trans /-
@[trans]
theorem HEq.trans (h₁ : HEq a b) (h₂ : HEq b c) : HEq a c :=
  HEq.subst h₂ h₁
#align heq.trans HEq.trans
-/

@[trans]
theorem hEq_of_hEq_of_eq (h₁ : HEq a b) (h₂ : b = b') : HEq a b' :=
  HEq.trans h₁ (hEq_of_eq h₂)
#align heq_of_heq_of_eq hEq_of_hEq_of_eq

@[trans]
theorem hEq_of_eq_of_hEq (h₁ : a = a') (h₂ : HEq a' b) : HEq a b :=
  HEq.trans (hEq_of_eq h₁) h₂
#align heq_of_eq_of_heq hEq_of_eq_of_hEq

theorem type_eq_of_hEq (h : HEq a b) : α = β :=
  HEq.recOn h (Eq.refl α)
#align type_eq_of_heq type_eq_of_hEq

end

theorem eq_rec_hEq {α : Sort u} {φ : α → Sort v} :
    ∀ {a a' : α} (h : a = a') (p : φ a), HEq (Eq.recOn h p : φ a') p
  | a, _, rfl, p => HEq.refl p
#align eq_rec_heq eq_rec_hEq

theorem hEq_of_eq_rec_left {α : Sort u} {φ : α → Sort v} :
    ∀ {a a' : α} {p₁ : φ a} {p₂ : φ a'} (e : a = a') (h₂ : (Eq.recOn e p₁ : φ a') = p₂), HEq p₁ p₂
  | a, _, p₁, p₂, rfl, h => Eq.recOn h (HEq.refl p₁)
#align heq_of_eq_rec_left hEq_of_eq_rec_left

theorem hEq_of_eq_rec_right {α : Sort u} {φ : α → Sort v} :
    ∀ {a a' : α} {p₁ : φ a} {p₂ : φ a'} (e : a' = a) (h₂ : p₁ = Eq.recOn e p₂), HEq p₁ p₂
  | a, _, p₁, p₂, rfl, h =>
    have : p₁ = p₂ := h
    this ▸ HEq.refl p₁
#align heq_of_eq_rec_right hEq_of_eq_rec_right

theorem of_hEq_true {a : Prop} (h : HEq a True) : a :=
  of_eq_true (eq_of_hEq h)
#align of_heq_true of_hEq_true

#print eq_rec_compose /-
theorem eq_rec_compose :
    ∀ {α β φ : Sort u} (p₁ : β = φ) (p₂ : α = β) (a : α),
      (Eq.recOn p₁ (Eq.recOn p₂ a : β) : φ) = Eq.recOn (Eq.trans p₂ p₁) a
  | α, _, _, rfl, rfl, a => rfl
#align eq_rec_compose eq_rec_compose
-/

theorem cast_hEq : ∀ {α β : Sort u} (h : α = β) (a : α), HEq (cast h a) a
  | α, _, rfl, a => HEq.refl a
#align cast_heq cast_hEq

/-! and -/

variable {a b c d : Prop}

theorem And.elim (h₁ : a ∧ b) (h₂ : a → b → c) : c :=
  And.ndrec h₂ h₁
#align and.elim And.elimₓ

/- warning: and.swap clashes with and.symm -> And.symm
Case conversion may be inaccurate. Consider using '#align and.swap And.symmₓ'. -/
#print And.symm /-
theorem And.symm : a ∧ b → b ∧ a := fun ⟨ha, hb⟩ => ⟨hb, ha⟩
#align and.swap And.symm
-/

#print And.symm /-
theorem And.symm : a ∧ b → b ∧ a :=
  And.symm
#align and.symm And.symm
-/

/-! or -/

namespace Or

#print Or.elim /-
theorem elim (h₁ : a ∨ b) (h₂ : a → c) (h₃ : b → c) : c :=
  Or.ndrec h₂ h₃ h₁
#align or.elim Or.elim
-/

end Or

#print not_not_em /-
theorem not_not_em (a : Prop) : ¬¬(a ∨ ¬a) := fun not_em : ¬(a ∨ ¬a) =>
  have neg_a : ¬a := fun pos_a : a => absurd (Or.inl pos_a) not_em
  absurd (Or.inr neg_a) not_em
#align non_contradictory_em not_not_em
-/

/- warning: or.swap clashes with or.symm -> Or.symm
Case conversion may be inaccurate. Consider using '#align or.swap Or.symmₓ'. -/
#print Or.symm /-
theorem Or.symm : a ∨ b → b ∨ a :=
  Or.ndrec Or.inr Or.inl
#align or.swap Or.symm
-/

#print Or.symm /-
theorem Or.symm : a ∨ b → b ∨ a :=
  Or.symm
#align or.symm Or.symm
-/

/-! xor -/

#print Xor' /-
def Xor' (a b : Prop) :=
  a ∧ ¬b ∨ b ∧ ¬a
#align xor Xor'
-/

/-! iff -/

#print Iff /-
/-- `iff P Q`, with notation `P ↔ Q`, is the proposition asserting that `P` and `Q` are equivalent,
that is, have the same truth value. -/
structure Iff (a b : Prop) : Prop where intro ::
  mp : a → b
  mpr : b → a
#align iff Iff
-/

theorem Iff.elim : ((a → b) → (b → a) → c) → (a ↔ b) → c :=
  Iff.ndrec
#align iff.elim Iff.elimₓ

attribute [recursor 5] Iff.elim

/- warning: iff.elim_left clashes with iff.mp -> Iff.mp
Case conversion may be inaccurate. Consider using '#align iff.elim_left Iff.mpₓ'. -/
#print Iff.mp /-
theorem Iff.mp : (a ↔ b) → a → b :=
  Iff.mp
#align iff.elim_left Iff.mp
-/

/- warning: iff.elim_right clashes with iff.mpr -> Iff.mpr
Case conversion may be inaccurate. Consider using '#align iff.elim_right Iff.mprₓ'. -/
#print Iff.mpr /-
theorem Iff.mpr : (a ↔ b) → b → a :=
  Iff.mpr
#align iff.elim_right Iff.mpr
-/

#print iff_iff_implies_and_implies /-
theorem iff_iff_implies_and_implies (a b : Prop) : (a ↔ b) ↔ (a → b) ∧ (b → a) :=
  Iff.intro (fun h => And.intro h.mp h.mpr) fun h => Iff.intro h.left h.right
#align iff_iff_implies_and_implies iff_iff_implies_and_implies
-/

#print Iff.refl /-
@[refl]
theorem Iff.refl (a : Prop) : a ↔ a :=
  Iff.intro (fun h => h) fun h => h
#align iff.refl Iff.refl
-/

#print Iff.rfl /-
theorem Iff.rfl {a : Prop} : a ↔ a :=
  Iff.refl a
#align iff.rfl Iff.rfl
-/

#print Iff.trans /-
@[trans]
theorem Iff.trans (h₁ : a ↔ b) (h₂ : b ↔ c) : a ↔ c :=
  Iff.intro (fun ha => Iff.mp h₂ (Iff.mp h₁ ha)) fun hc => Iff.mpr h₁ (Iff.mpr h₂ hc)
#align iff.trans Iff.trans
-/

#print Iff.symm /-
@[symm]
theorem Iff.symm (h : a ↔ b) : b ↔ a :=
  Iff.intro (Iff.mpr h) (Iff.mp h)
#align iff.symm Iff.symm
-/

#print Iff.comm /-
theorem Iff.comm : (a ↔ b) ↔ (b ↔ a) :=
  Iff.intro Iff.symm Iff.symm
#align iff.comm Iff.comm
-/

#print Eq.to_iff /-
theorem Eq.to_iff {a b : Prop} (h : a = b) : a ↔ b :=
  Eq.recOn h Iff.rfl
#align eq.to_iff Eq.to_iff
-/

#print neq_of_not_iff /-
theorem neq_of_not_iff {a b : Prop} : ¬(a ↔ b) → a ≠ b := fun h₁ h₂ =>
  have : a ↔ b := Eq.subst h₂ (Iff.refl a)
  absurd this h₁
#align neq_of_not_iff neq_of_not_iff
-/

/- warning: not_iff_not_of_iff clashes with not_congr -> not_congr
Case conversion may be inaccurate. Consider using '#align not_iff_not_of_iff not_congrₓ'. -/
#print not_congr /-
theorem not_congr (h₁ : a ↔ b) : ¬a ↔ ¬b :=
  Iff.intro (fun (hna : ¬a) (hb : b) => hna (Iff.mpr h₁ hb)) fun (hnb : ¬b) (ha : a) =>
    hnb (Iff.mp h₁ ha)
#align not_iff_not_of_iff not_congr
-/

#print of_iff_true /-
theorem of_iff_true (h : a ↔ True) : a :=
  Iff.mp (Iff.symm h) trivial
#align of_iff_true of_iff_true
-/

#print not_of_iff_false /-
theorem not_of_iff_false : (a ↔ False) → ¬a :=
  Iff.mp
#align not_of_iff_false not_of_iff_false
-/

#print iff_true_intro /-
theorem iff_true_intro (h : a) : a ↔ True :=
  Iff.intro (fun hl => trivial) fun hr => h
#align iff_true_intro iff_true_intro
-/

#print iff_false_intro /-
theorem iff_false_intro (h : ¬a) : a ↔ False :=
  Iff.intro h (False.ndrec a)
#align iff_false_intro iff_false_intro
-/

#print not_not_not /-
theorem not_not_not (a : Prop) : ¬¬¬a ↔ ¬a :=
  Iff.intro (fun (hl : ¬¬¬a) (ha : a) => hl (not_not_intro ha)) absurd
#align not_non_contradictory_iff_absurd not_not_not
-/

theorem imp_congr (h₁ : a ↔ c) (h₂ : b ↔ d) : a → b ↔ c → d :=
  Iff.intro (fun hab hc => Iff.mp h₂ (hab (Iff.mpr h₁ hc))) fun hcd ha =>
    Iff.mpr h₂ (hcd (Iff.mp h₁ ha))
#align imp_congr imp_congrₓ

theorem imp_congr_ctx (h₁ : a ↔ c) (h₂ : c → (b ↔ d)) : a → b ↔ c → d :=
  Iff.intro
    (fun hab hc =>
      have ha : a := Iff.mpr h₁ hc
      have hb : b := hab ha
      Iff.mp (h₂ hc) hb)
    fun hcd ha =>
    have hc : c := Iff.mp h₁ ha
    have hd : d := hcd hc
    Iff.mpr (h₂ hc) hd
#align imp_congr_ctx imp_congr_ctxₓ

theorem imp_congr_right (h : a → (b ↔ c)) : a → b ↔ a → c :=
  Iff.intro (fun hab ha => Iff.mp (h ha) (hab ha)) fun hab ha => Iff.mpr (h ha) (hab ha)
#align imp_congr_right imp_congr_rightₓ

/- warning: not_not_intro clashes with non_contradictory_intro -> not_not_intro
Case conversion may be inaccurate. Consider using '#align not_not_intro not_not_introₓ'. -/
#print not_not_intro /-
theorem not_not_intro (ha : a) : ¬¬a := fun hna : ¬a => hna ha
#align not_not_intro not_not_intro
-/

#print not_of_not_not_not /-
theorem not_of_not_not_not (h : ¬¬¬a) : ¬a := fun ha => absurd (not_not_intro ha) h
#align not_of_not_not_not not_of_not_not_not
-/

#print not_true /-
@[simp]
theorem not_true : ¬True ↔ False :=
  iff_false_intro (not_not_intro trivial)
#align not_true not_true
-/

#print not_false_iff /-
@[simp]
theorem not_false_iff : ¬False ↔ True :=
  iff_true_intro not_false
#align not_false_iff not_false_iff
-/

#print not_congr /-
@[congr]
theorem not_congr (h : a ↔ b) : ¬a ↔ ¬b :=
  Iff.intro (fun h₁ h₂ => h₁ (Iff.mpr h h₂)) fun h₁ h₂ => h₁ (Iff.mp h h₂)
#align not_congr not_congr
-/

#print ne_self_iff_false /-
theorem ne_self_iff_false {α : Sort u} (a : α) : Not (a = a) ↔ False :=
  Iff.intro false_of_ne False.elim
#align ne_self_iff_false ne_self_iff_false
-/

#print eq_self_iff_true /-
@[simp]
theorem eq_self_iff_true {α : Sort u} (a : α) : a = a ↔ True :=
  iff_true_intro rfl
#align eq_self_iff_true eq_self_iff_true
-/

theorem hEq_self_iff_true {α : Sort u} (a : α) : HEq a a ↔ True :=
  iff_true_intro (HEq.refl a)
#align heq_self_iff_true hEq_self_iff_true

/- warning: iff_not_self -> iff_not_self is a dubious translation:
lean 3 declaration is
  forall (a : Prop), Iff (Iff a (Not a)) False
but is expected to have type
  forall {a : Prop}, Not (Iff a (Not a))
Case conversion may be inaccurate. Consider using '#align iff_not_self iff_not_selfₓ'. -/
@[simp]
theorem iff_not_self (a : Prop) : (a ↔ ¬a) ↔ False :=
  iff_false_intro fun h =>
    have h' : ¬a := fun ha => (Iff.mp h ha) ha
    h' (Iff.mpr h h')
#align iff_not_self iff_not_self

/- warning: not_iff_self -> not_iff_self is a dubious translation:
lean 3 declaration is
  forall (a : Prop), Iff (Iff (Not a) a) False
but is expected to have type
  forall {a : Prop}, Not (Iff (Not a) a)
Case conversion may be inaccurate. Consider using '#align not_iff_self not_iff_selfₓ'. -/
@[simp]
theorem not_iff_self (a : Prop) : (¬a ↔ a) ↔ False :=
  iff_false_intro fun h =>
    have h' : ¬a := fun ha => (Iff.mpr h ha) ha
    h' (Iff.mp h h')
#align not_iff_self not_iff_self

#print true_iff_false /-
theorem true_iff_false : (True ↔ False) ↔ False :=
  iff_false_intro fun h => Iff.mp h trivial
#align true_iff_false true_iff_false
-/

#print false_iff_true /-
theorem false_iff_true : (False ↔ True) ↔ False :=
  iff_false_intro fun h => Iff.mpr h trivial
#align false_iff_true false_iff_true
-/

#print false_of_true_iff_false /-
theorem false_of_true_iff_false : (True ↔ False) → False := fun h => Iff.mp h trivial
#align false_of_true_iff_false false_of_true_iff_false
-/

#print false_of_true_eq_false /-
theorem false_of_true_eq_false : True = False → False := fun h => h ▸ trivial
#align false_of_true_eq_false false_of_true_eq_false
-/

#print true_eq_false_of_false /-
theorem true_eq_false_of_false : False → True = False :=
  False.elim
#align true_eq_false_of_false true_eq_false_of_false
-/

#print eq_comm /-
theorem eq_comm {α : Sort u} {a b : α} : a = b ↔ b = a :=
  ⟨Eq.symm, Eq.symm⟩
#align eq_comm eq_comm
-/

/-! and simp rules -/

/- warning: and.imp -> And.imp is a dubious translation:
lean 3 declaration is
  forall {a : Prop} {b : Prop} {c : Prop} {d : Prop}, (a -> c) -> (b -> d) -> (And a b) -> (And c d)
but is expected to have type
  forall {a : Prop} {b : Prop} {c : Prop} {d : Prop}, (a -> b) -> (c -> d) -> (And a c) -> (And b d)
Case conversion may be inaccurate. Consider using '#align and.imp And.impₓ'. -/
theorem And.imp (hac : a → c) (hbd : b → d) : a ∧ b → c ∧ d := fun ⟨ha, hb⟩ => ⟨hac ha, hbd hb⟩
#align and.imp And.imp

theorem and_implies (hac : a → c) (hbd : b → d) : a ∧ b → c ∧ d :=
  And.imp hac hbd
#align and_implies and_implies

/- warning: and_congr -> and_congr is a dubious translation:
lean 3 declaration is
  forall {a : Prop} {b : Prop} {c : Prop} {d : Prop}, (Iff a c) -> (Iff b d) -> (Iff (And a b) (And c d))
but is expected to have type
  forall {a : Prop} {b : Prop} {c : Prop} {d : Prop}, (Iff a b) -> (Iff c d) -> (Iff (And a c) (And b d))
Case conversion may be inaccurate. Consider using '#align and_congr and_congrₓ'. -/
@[congr]
theorem and_congr (h₁ : a ↔ c) (h₂ : b ↔ d) : a ∧ b ↔ c ∧ d :=
  Iff.intro (And.imp (Iff.mp h₁) (Iff.mp h₂)) (And.imp (Iff.mpr h₁) (Iff.mpr h₂))
#align and_congr and_congr

#print and_congr_right /-
theorem and_congr_right (h : a → (b ↔ c)) : a ∧ b ↔ a ∧ c :=
  Iff.intro (fun ⟨ha, hb⟩ => ⟨ha, Iff.mp (h ha) hb⟩) fun ⟨ha, hc⟩ => ⟨ha, Iff.mpr (h ha) hc⟩
#align and_congr_right and_congr_right
-/

#print and_comm /-
theorem and_comm : a ∧ b ↔ b ∧ a :=
  Iff.intro And.symm And.symm
#align and.comm and_comm
-/

#print and_comm' /-
theorem and_comm' (a b : Prop) : a ∧ b ↔ b ∧ a :=
  and_comm
#align and_comm and_comm'
-/

#print and_assoc /-
theorem and_assoc : (a ∧ b) ∧ c ↔ a ∧ b ∧ c :=
  Iff.intro (fun ⟨⟨ha, hb⟩, hc⟩ => ⟨ha, ⟨hb, hc⟩⟩) fun ⟨ha, ⟨hb, hc⟩⟩ => ⟨⟨ha, hb⟩, hc⟩
#align and.assoc and_assoc
-/

#print and_assoc' /-
theorem and_assoc' (a b : Prop) : (a ∧ b) ∧ c ↔ a ∧ b ∧ c :=
  and_assoc
#align and_assoc and_assoc'
-/

#print and_left_comm /-
theorem and_left_comm : a ∧ b ∧ c ↔ b ∧ a ∧ c :=
  Iff.trans (Iff.symm and_assoc) (Iff.trans (and_congr and_comm (Iff.refl c)) and_assoc)
#align and.left_comm and_left_comm
-/

theorem and_iff_left {a b : Prop} (hb : b) : a ∧ b ↔ a :=
  Iff.intro And.left fun ha => ⟨ha, hb⟩
#align and_iff_left and_iff_leftₓ

#print and_iff_right /-
theorem and_iff_right {a b : Prop} (ha : a) : a ∧ b ↔ b :=
  Iff.intro And.right (And.intro ha)
#align and_iff_right and_iff_right
-/

#print and_true_iff /-
@[simp]
theorem and_true_iff (a : Prop) : a ∧ True ↔ a :=
  and_iff_left trivial
#align and_true and_true_iff
-/

#print true_and_iff /-
@[simp]
theorem true_and_iff (a : Prop) : True ∧ a ↔ a :=
  and_iff_right trivial
#align true_and true_and_iff
-/

#print and_false_iff /-
@[simp]
theorem and_false_iff (a : Prop) : a ∧ False ↔ False :=
  iff_false_intro And.right
#align and_false and_false_iff
-/

#print false_and_iff /-
@[simp]
theorem false_and_iff (a : Prop) : False ∧ a ↔ False :=
  iff_false_intro And.left
#align false_and false_and_iff
-/

#print not_and_self_iff /-
@[simp]
theorem not_and_self_iff (a : Prop) : ¬a ∧ a ↔ False :=
  iff_false_intro fun h => And.elim h fun h₁ h₂ => absurd h₂ h₁
#align not_and_self not_and_self_iff
-/

#print and_not_self_iff /-
@[simp]
theorem and_not_self_iff (a : Prop) : a ∧ ¬a ↔ False :=
  iff_false_intro fun ⟨h₁, h₂⟩ => absurd h₁ h₂
#align and_not_self and_not_self_iff
-/

#print and_self_iff /-
@[simp]
theorem and_self_iff (a : Prop) : a ∧ a ↔ a :=
  Iff.intro And.left fun h => ⟨h, h⟩
#align and_self and_self_iff
-/

/-! or simp rules -/

theorem Or.imp (h₂ : a → c) (h₃ : b → d) : a ∨ b → c ∨ d :=
  Or.ndrec (fun h => Or.inl (h₂ h)) fun h => Or.inr (h₃ h)
#align or.imp Or.impₓ

#print Or.imp_left /-
theorem Or.imp_left (h : a → b) : a ∨ c → b ∨ c :=
  Or.imp h id
#align or.imp_left Or.imp_left
-/

#print Or.imp_right /-
theorem Or.imp_right (h : a → b) : c ∨ a → c ∨ b :=
  Or.imp id h
#align or.imp_right Or.imp_right
-/

/- warning: or_congr -> or_congr is a dubious translation:
lean 3 declaration is
  forall {a : Prop} {b : Prop} {c : Prop} {d : Prop}, (Iff a c) -> (Iff b d) -> (Iff (Or a b) (Or c d))
but is expected to have type
  forall {a : Prop} {b : Prop} {c : Prop} {d : Prop}, (Iff a b) -> (Iff c d) -> (Iff (Or a c) (Or b d))
Case conversion may be inaccurate. Consider using '#align or_congr or_congrₓ'. -/
@[congr]
theorem or_congr (h₁ : a ↔ c) (h₂ : b ↔ d) : a ∨ b ↔ c ∨ d :=
  Iff.intro (Or.imp (Iff.mp h₁) (Iff.mp h₂)) (Or.imp (Iff.mpr h₁) (Iff.mpr h₂))
#align or_congr or_congr

#print or_comm /-
theorem or_comm : a ∨ b ↔ b ∨ a :=
  Iff.intro Or.symm Or.symm
#align or.comm or_comm
-/

#print or_comm' /-
theorem or_comm' (a b : Prop) : a ∨ b ↔ b ∨ a :=
  or_comm
#align or_comm or_comm'
-/

#print or_assoc /-
theorem or_assoc : (a ∨ b) ∨ c ↔ a ∨ b ∨ c :=
  Iff.intro (Or.ndrec (Or.imp_right Or.inl) fun h => Or.inr (Or.inr h))
    (Or.ndrec (fun h => Or.inl (Or.inl h)) (Or.imp_left Or.inr))
#align or.assoc or_assoc
-/

#print or_assoc' /-
theorem or_assoc' (a b : Prop) : (a ∨ b) ∨ c ↔ a ∨ b ∨ c :=
  or_assoc
#align or_assoc or_assoc'
-/

/- warning: or.left_comm clashes with or_left_comm -> or_left_comm
Case conversion may be inaccurate. Consider using '#align or.left_comm or_left_commₓ'. -/
#print or_left_comm /-
theorem or_left_comm : a ∨ b ∨ c ↔ b ∨ a ∨ c :=
  Iff.trans (Iff.symm or_assoc) (Iff.trans (or_congr or_comm (Iff.refl c)) or_assoc)
#align or.left_comm or_left_comm
-/

#print or_iff_right_of_imp /-
theorem or_iff_right_of_imp (ha : a → b) : a ∨ b ↔ b :=
  Iff.intro (Or.ndrec ha id) Or.inr
#align or_iff_right_of_imp or_iff_right_of_imp
-/

theorem or_iff_left_of_imp (hb : b → a) : a ∨ b ↔ a :=
  Iff.intro (Or.ndrec id hb) Or.inl
#align or_iff_left_of_imp or_iff_left_of_impₓ

#print or_true_iff /-
@[simp]
theorem or_true_iff (a : Prop) : a ∨ True ↔ True :=
  iff_true_intro (Or.inr trivial)
#align or_true or_true_iff
-/

#print true_or_iff /-
@[simp]
theorem true_or_iff (a : Prop) : True ∨ a ↔ True :=
  iff_true_intro (Or.inl trivial)
#align true_or true_or_iff
-/

#print or_false_iff /-
@[simp]
theorem or_false_iff (a : Prop) : a ∨ False ↔ a :=
  Iff.intro (Or.ndrec id False.elim) Or.inl
#align or_false or_false_iff
-/

#print false_or_iff /-
@[simp]
theorem false_or_iff (a : Prop) : False ∨ a ↔ a :=
  Iff.trans or_comm (or_false_iff a)
#align false_or false_or_iff
-/

#print or_self_iff /-
@[simp]
theorem or_self_iff (a : Prop) : a ∨ a ↔ a :=
  Iff.intro (Or.ndrec id id) Or.inl
#align or_self or_self_iff
-/

#print not_or_of_not /-
theorem not_or_of_not {a b : Prop} : ¬a → ¬b → ¬(a ∨ b)
  | hna, hnb, Or.inl ha => absurd ha hna
  | hna, hnb, Or.inr hb => absurd hb hnb
#align not_or not_or_of_not
-/

/-! or resolution rulse -/

#print Or.resolve_left /-
theorem Or.resolve_left {a b : Prop} (h : a ∨ b) (na : ¬a) : b :=
  Or.elim h (fun ha => absurd ha na) id
#align or.resolve_left Or.resolve_left
-/

#print Or.neg_resolve_left /-
theorem Or.neg_resolve_left {a b : Prop} (h : ¬a ∨ b) (ha : a) : b :=
  Or.elim h (fun na => absurd ha na) id
#align or.neg_resolve_left Or.neg_resolve_left
-/

#print Or.resolve_right /-
theorem Or.resolve_right {a b : Prop} (h : a ∨ b) (nb : ¬b) : a :=
  Or.elim h id fun hb => absurd hb nb
#align or.resolve_right Or.resolve_right
-/

#print Or.neg_resolve_right /-
theorem Or.neg_resolve_right {a b : Prop} (h : a ∨ ¬b) (hb : b) : a :=
  Or.elim h id fun nb => absurd hb nb
#align or.neg_resolve_right Or.neg_resolve_right
-/

/-! iff simp rules -/

#print iff_true_iff /-
@[simp]
theorem iff_true_iff (a : Prop) : (a ↔ True) ↔ a :=
  Iff.intro (fun h => Iff.mpr h trivial) iff_true_intro
#align iff_true iff_true_iff
-/

#print true_iff_iff /-
@[simp]
theorem true_iff_iff (a : Prop) : (True ↔ a) ↔ a :=
  Iff.trans Iff.comm (iff_true_iff a)
#align true_iff true_iff_iff
-/

#print iff_false_iff /-
@[simp]
theorem iff_false_iff (a : Prop) : (a ↔ False) ↔ ¬a :=
  Iff.intro Iff.mp iff_false_intro
#align iff_false iff_false_iff
-/

#print false_iff_iff /-
@[simp]
theorem false_iff_iff (a : Prop) : (False ↔ a) ↔ ¬a :=
  Iff.trans Iff.comm (iff_false_iff a)
#align false_iff false_iff_iff
-/

#print iff_self_iff /-
@[simp]
theorem iff_self_iff (a : Prop) : (a ↔ a) ↔ True :=
  iff_true_intro Iff.rfl
#align iff_self iff_self_iff
-/

@[congr]
theorem iff_congr (h₁ : a ↔ c) (h₂ : b ↔ d) : (a ↔ b) ↔ (c ↔ d) :=
  (iff_iff_implies_and_implies a b).trans
    ((and_congr (imp_congr h₁ h₂) (imp_congr h₂ h₁)).trans (iff_iff_implies_and_implies c d).symm)
#align iff_congr iff_congrₓ

/-! implies simp rule -/

#print imp_true_iff /-
@[simp]
theorem imp_true_iff (α : Sort u) : α → True ↔ True :=
  Iff.intro (fun h => trivial) fun ha h => trivial
#align implies_true_iff imp_true_iff
-/

#print false_imp_iff /-
theorem false_imp_iff (a : Prop) : False → a ↔ True :=
  Iff.intro (fun h => trivial) fun ha h => False.elim h
#align false_implies_iff false_imp_iff
-/

#print true_imp_iff /-
theorem true_imp_iff (α : Prop) : True → α ↔ α :=
  Iff.intro (fun h => h trivial) fun h h' => h
#align true_implies_iff true_imp_iff
-/

#print Exists /-
/-- The existential quantifier.

To prove a goal of the form `⊢ ∃ x, p x`, you can provide a witness `y` with the tactic `existsi y`.
If you are working in a project that depends on mathlib, then we recommend the `use` tactic
instead.
You'll then be left with the goal `⊢ p y`.

To extract a witness `x` and proof `hx : p x` from a hypothesis `h : ∃ x, p x`,
use the tactic `cases h with x hx`. See also the mathlib tactics `obtain` and `rcases`.
-/
inductive Exists {α : Sort u} (p : α → Prop) : Prop
  | intro (w : α) (h : p w) : Exists
#align Exists Exists
-/

attribute [intro] Exists.intro

/- warning: exists.intro clashes with Exists.intro -> Exists.intro
Case conversion may be inaccurate. Consider using '#align exists.intro Exists.introₓ'. -/
#print Exists.intro /-
-- This is a `def`, so that it can be used as pattern in the equation compiler.
@[match_pattern]
def Exists.intro {α : Sort u} {p : α → Prop} (w : α) (h : p w) : ∃ x, p x :=
  ⟨w, h⟩
#align exists.intro Exists.intro
-/

#print Exists.elim /-
theorem Exists.elim {α : Sort u} {p : α → Prop} {b : Prop} (h₁ : ∃ x, p x) (h₂ : ∀ a : α, p a → b) :
    b :=
  Exists.ndrec h₂ h₁
#align exists.elim Exists.elim
-/

/-! exists unique -/

#print ExistsUnique /-
def ExistsUnique {α : Sort u} (p : α → Prop) :=
  ∃ x, p x ∧ ∀ y, p y → y = x
#align exists_unique ExistsUnique
-/

#print ExistsUnique.intro /-
@[intro]
theorem ExistsUnique.intro {α : Sort u} {p : α → Prop} (w : α) (h₁ : p w) (h₂ : ∀ y, p y → y = w) :
    ∃! x, p x :=
  Exists.intro w ⟨h₁, h₂⟩
#align exists_unique.intro ExistsUnique.intro
-/

#print ExistsUnique.elim /-
@[recursor 4]
theorem ExistsUnique.elim {α : Sort u} {p : α → Prop} {b : Prop} (h₂ : ∃! x, p x)
    (h₁ : ∀ x, p x → (∀ y, p y → y = x) → b) : b :=
  Exists.elim h₂ fun w hw => h₁ w (And.left hw) (And.right hw)
#align exists_unique.elim ExistsUnique.elim
-/

theorem existsUnique_of_exists_of_unique {α : Sort u} {p : α → Prop} (hex : ∃ x, p x)
    (hunique : ∀ y₁ y₂, p y₁ → p y₂ → y₁ = y₂) : ∃! x, p x :=
  Exists.elim hex fun x px => ExistsUnique.intro x px fun y => fun this : p y => hunique y x this px
#align exists_unique_of_exists_of_unique existsUnique_of_exists_of_unique

#print ExistsUnique.exists /-
theorem ExistsUnique.exists {α : Sort u} {p : α → Prop} (h : ∃! x, p x) : ∃ x, p x :=
  Exists.elim h fun x hx => ⟨x, And.left hx⟩
#align exists_of_exists_unique ExistsUnique.exists
-/

#print ExistsUnique.unique /-
theorem ExistsUnique.unique {α : Sort u} {p : α → Prop} (h : ∃! x, p x) {y₁ y₂ : α} (py₁ : p y₁)
    (py₂ : p y₂) : y₁ = y₂ :=
  ExistsUnique.elim h fun x => fun this : p x => fun unique : ∀ y, p y → y = x =>
    show y₁ = y₂ from Eq.trans (Unique _ py₁) (Eq.symm (Unique _ py₂))
#align unique_of_exists_unique ExistsUnique.unique
-/

/-! exists, forall, exists unique congruences -/

#print forall_congr' /-
@[congr]
theorem forall_congr' {α : Sort u} {p q : α → Prop} (h : ∀ a, p a ↔ q a) : (∀ a, p a) ↔ ∀ a, q a :=
  Iff.intro (fun p a => Iff.mp (h a) (p a)) fun q a => Iff.mpr (h a) (q a)
#align forall_congr forall_congr'
-/

/- warning: exists_imp_exists clashes with Exists.imp -> Exists.imp
Case conversion may be inaccurate. Consider using '#align exists_imp_exists Exists.impₓ'. -/
#print Exists.imp /-
theorem Exists.imp {α : Sort u} {p q : α → Prop} (h : ∀ a, p a → q a) (p : ∃ a, p a) : ∃ a, q a :=
  Exists.elim p fun a hp => ⟨a, h a hp⟩
#align exists_imp_exists Exists.imp
-/

#print exists_congr /-
@[congr]
theorem exists_congr {α : Sort u} {p q : α → Prop} (h : ∀ a, p a ↔ q a) : Exists p ↔ ∃ a, q a :=
  Iff.intro (Exists.imp fun a => Iff.mp (h a)) (Exists.imp fun a => Iff.mpr (h a))
#align exists_congr exists_congr
-/

@[congr]
theorem existsUnique_congr {α : Sort u} {p₁ p₂ : α → Prop} (h : ∀ x, p₁ x ↔ p₂ x) :
    ExistsUnique p₁ ↔ ∃! x, p₂ x :=
  --
    exists_congr
    fun x => and_congr (h x) (forall_congr' fun y => imp_congr (h y) Iff.rfl)
#align exists_unique_congr existsUnique_congr

#print forall_not_of_not_exists /-
theorem forall_not_of_not_exists {α : Sort u} {p : α → Prop} : (¬∃ x, p x) → ∀ x, ¬p x :=
  fun hne x hp => hne ⟨x, hp⟩
#align forall_not_of_not_exists forall_not_of_not_exists
-/

/-! decidable -/

#print Decidable.decide /-
def Decidable.decide (p : Prop) [h : Decidable p] : Bool :=
  Decidable.casesOn h (fun h₁ => Bool.false) fun h₂ => Bool.true
#align decidable.to_bool Decidable.decide
-/

export Decidable (isTrue isFalse decide)

#print decide_True' /-
@[simp]
theorem decide_True' (h : Decidable True) : @decide True h = true :=
  Decidable.casesOn h (fun h => False.elim (Iff.mp not_true h)) fun _ => rfl
#align to_bool_true_eq_tt decide_True'
-/

#print decide_False' /-
@[simp]
theorem decide_False' (h : Decidable False) : @decide False h = false :=
  Decidable.casesOn h (fun h => rfl) fun h => False.elim h
#align to_bool_false_eq_ff decide_False'
-/

instance Decidable.true : Decidable True :=
  isTrue trivial
#align decidable.true Decidable.true

instance Decidable.false : Decidable False :=
  isFalse not_false
#align decidable.false Decidable.false

#print dite /-
-- We use "dependent" if-then-else to be able to communicate the if-then-else condition
-- to the branches
@[inline]
def dite {α : Sort u} (c : Prop) [h : Decidable c] : (c → α) → (¬c → α) → α := fun t e =>
  Decidable.recOn h e t
#align dite dite
-/

#print ite /-
/-- if-then-else -/
@[inline]
def ite {α : Sort u} (c : Prop) [h : Decidable c] (t e : α) : α :=
  Decidable.recOn h (fun hnc => e) fun hc => t
#align ite ite
-/

namespace Decidable

variable {p q : Prop}

#print Decidable.recOn_true /-
def recOn_true [h : Decidable p] {h₁ : p → Sort u} {h₂ : ¬p → Sort u} (h₃ : p) (h₄ : h₁ h₃) :
    Decidable.recOn h h₂ h₁ :=
  Decidable.recOn h (fun h => False.ndrec _ (h h₃)) fun h => h₄
#align decidable.rec_on_true Decidable.recOn_true
-/

#print Decidable.recOn_false /-
def recOn_false [h : Decidable p] {h₁ : p → Sort u} {h₂ : ¬p → Sort u} (h₃ : ¬p) (h₄ : h₂ h₃) :
    Decidable.recOn h h₂ h₁ :=
  Decidable.recOn h (fun h => h₄) fun h => False.ndrec _ (h₃ h)
#align decidable.rec_on_false Decidable.recOn_false
-/

#print Decidable.byCases /-
def byCases {q : Sort u} [φ : Decidable p] : (p → q) → (¬p → q) → q :=
  dite _
#align decidable.by_cases Decidable.byCases
-/

#print Decidable.em /-
/-- Law of Excluded Middle. -/
theorem em (p : Prop) [Decidable p] : p ∨ ¬p :=
  byCases Or.inl Or.inr
#align decidable.em Decidable.em
-/

#print Decidable.by_contradiction /-
theorem by_contradiction [Decidable p] (h : ¬p → False) : p :=
  if h₁ : p then h₁ else False.ndrec _ (h h₁)
#align decidable.by_contradiction Decidable.by_contradiction
-/

#print Decidable.of_not_not /-
theorem of_not_not [Decidable p] : ¬¬p → p := fun hnn => by_contradiction fun hn => absurd hn hnn
#align decidable.of_not_not Decidable.of_not_not
-/

#print Decidable.not_not_iff /-
theorem not_not_iff (p) [Decidable p] : ¬¬p ↔ p :=
  Iff.intro of_not_not not_not_intro
#align decidable.not_not_iff Decidable.not_not_iff
-/

#print Decidable.not_and_iff_or_not /-
theorem not_and_iff_or_not (p q : Prop) [d₁ : Decidable p] [d₂ : Decidable q] :
    ¬(p ∧ q) ↔ ¬p ∨ ¬q :=
  Iff.intro
    (fun h =>
      match d₁ with
      | is_true h₁ =>
        match d₂ with
        | is_true h₂ => absurd (And.intro h₁ h₂) h
        | is_false h₂ => Or.inr h₂
      | is_false h₁ => Or.inl h₁)
    fun h ⟨hp, hq⟩ => Or.elim h (fun h => h hp) fun h => h hq
#align decidable.not_and_iff_or_not Decidable.not_and_iff_or_not
-/

#print Decidable.not_or_iff_and_not /-
theorem not_or_iff_and_not (p q) [d₁ : Decidable p] [d₂ : Decidable q] : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
  Iff.intro
    (fun h =>
      match d₁ with
      | is_true h₁ => False.elim <| h (Or.inl h₁)
      | is_false h₁ =>
        match d₂ with
        | is_true h₂ => False.elim <| h (Or.inr h₂)
        | is_false h₂ => ⟨h₁, h₂⟩)
    fun ⟨np, nq⟩ h => Or.elim h np nq
#align decidable.not_or_iff_and_not Decidable.not_or_iff_and_not
-/

end Decidable

section

variable {p q : Prop}

#print decidable_of_decidable_of_iff /-
def decidable_of_decidable_of_iff (hp : Decidable p) (h : p ↔ q) : Decidable q :=
  if hp : p then isTrue (Iff.mp h hp) else isFalse (Iff.mp (not_congr h) hp)
#align decidable_of_decidable_of_iff decidable_of_decidable_of_iff
-/

#print decidable_of_decidable_of_eq /-
def decidable_of_decidable_of_eq (hp : Decidable p) (h : p = q) : Decidable q :=
  decidable_of_decidable_of_iff hp h.to_iff
#align decidable_of_decidable_of_eq decidable_of_decidable_of_eq
-/

#print Or.by_cases /-
/-- A version of `or.elim` in `Type`. If both `p` and `q` are true, `h₁` is used. -/
protected def Or.by_cases [Decidable p] {α : Sort u} (h : p ∨ q) (h₁ : p → α) (h₂ : q → α) : α :=
  if hp : p then h₁ hp else h₂ (h.resolve_left hp)
#align or.by_cases Or.by_cases
-/

end

section

variable {p q : Prop}

instance [Decidable p] [Decidable q] : Decidable (p ∧ q) :=
  if hp : p then if hq : q then isTrue ⟨hp, hq⟩ else isFalse fun h : p ∧ q => hq (And.right h)
  else isFalse fun h : p ∧ q => hp (And.left h)

instance [Decidable p] [Decidable q] : Decidable (p ∨ q) :=
  if hp : p then isTrue (Or.inl hp)
  else if hq : q then isTrue (Or.inr hq) else isFalse (Or.ndrec hp hq)

instance [Decidable p] : Decidable ¬p :=
  if hp : p then isFalse (absurd hp) else isTrue hp

instance Implies.decidable [Decidable p] [Decidable q] : Decidable (p → q) :=
  if hp : p then if hq : q then isTrue fun h => hq else isFalse fun h : p → q => absurd (h hp) hq
  else isTrue fun h => absurd h hp
#align implies.decidable Implies.decidable

instance [Decidable p] [Decidable q] : Decidable (p ↔ q) :=
  if hp : p then if hq : q then isTrue ⟨fun _ => hq, fun _ => hp⟩ else isFalse fun h => hq (h.1 hp)
  else
    if hq : q then isFalse fun h => hp (h.2 hq)
    else isTrue <| ⟨fun h => absurd h hp, fun h => absurd h hq⟩

instance [Decidable p] [Decidable q] : Decidable (Xor' p q) :=
  if hp : p then
    if hq : q then
      isFalse (Or.ndrec (fun ⟨_, h⟩ => h hq : ¬(p ∧ ¬q)) (fun ⟨_, h⟩ => h hp : ¬(q ∧ ¬p)))
    else isTrue <| Or.inl ⟨hp, hq⟩
  else
    if hq : q then isTrue <| Or.inr ⟨hq, hp⟩
    else isFalse (Or.ndrec (fun ⟨h, _⟩ => hp h : ¬(p ∧ ¬q)) (fun ⟨h, _⟩ => hq h : ¬(q ∧ ¬p)))

instance existsPropDecidable {p} (P : p → Prop) [Dp : Decidable p] [DP : ∀ h, Decidable (P h)] :
    Decidable (∃ h, P h) :=
  if h : p then decidable_of_decidable_of_iff (DP h) ⟨fun h2 => ⟨h, h2⟩, fun ⟨h', h2⟩ => h2⟩
  else isFalse (mt (fun ⟨h, _⟩ => h) h)
#align exists_prop_decidable existsPropDecidable

instance forallPropDecidable {p} (P : p → Prop) [Dp : Decidable p] [DP : ∀ h, Decidable (P h)] :
    Decidable (∀ h, P h) :=
  if h : p then decidable_of_decidable_of_iff (DP h) ⟨fun h2 _ => h2, fun al => al h⟩
  else isTrue fun h2 => absurd h2 h
#align forall_prop_decidable forallPropDecidable

end

instance {α : Sort u} [DecidableEq α] (a b : α) : Decidable (a ≠ b) :=
  Implies.decidable

theorem Bool.false_ne_true : false = true → False :=
  fun.
#align bool.ff_ne_tt Bool.false_ne_true

#print IsDecEq /-
def IsDecEq {α : Sort u} (p : α → α → Bool) : Prop :=
  ∀ ⦃x y : α⦄, p x y = true → x = y
#align is_dec_eq IsDecEq
-/

#print IsDecRefl /-
def IsDecRefl {α : Sort u} (p : α → α → Bool) : Prop :=
  ∀ x, p x x = true
#align is_dec_refl IsDecRefl
-/

open Decidable

instance : DecidableEq Bool
  | ff, ff => isTrue rfl
  | ff, tt => isFalse Bool.false_ne_true
  | tt, ff => isFalse (Ne.symm Bool.false_ne_true)
  | tt, tt => isTrue rfl

#print decidableEq_of_bool_pred /-
def decidableEq_of_bool_pred {α : Sort u} {p : α → α → Bool} (h₁ : IsDecEq p) (h₂ : IsDecRefl p) :
    DecidableEq α := fun x y : α =>
  if hp : p x y = true then isTrue (h₁ hp)
  else isFalse fun hxy : x = y => absurd (h₂ y) (@Eq.recOn _ _ (fun z => ¬p z y = true) _ hxy hp)
#align decidable_eq_of_bool_pred decidableEq_of_bool_pred
-/

#print decidableEq_inl_refl /-
theorem decidableEq_inl_refl {α : Sort u} [h : DecidableEq α] (a : α) :
    h a a = isTrue (Eq.refl a) :=
  match h a a with
  | is_true e => rfl
  | is_false n => absurd rfl n
#align decidable_eq_inl_refl decidableEq_inl_refl
-/

#print decidableEq_inr_neg /-
theorem decidableEq_inr_neg {α : Sort u} [h : DecidableEq α] {a b : α} :
    ∀ n : a ≠ b, h a b = isFalse n := fun n =>
  match h a b with
  | is_true e => absurd e n
  | is_false n₁ => proof_irrel n n₁ ▸ Eq.refl (isFalse n)
#align decidable_eq_inr_neg decidableEq_inr_neg
-/

/-! inhabited -/

#print Inhabited /-
class Inhabited (α : Sort u) where
  default : α
#align inhabited Inhabited
-/

export Inhabited (default)

/- warning: arbitrary clashes with inhabited.default -> Inhabited.default
Case conversion may be inaccurate. Consider using '#align arbitrary Inhabited.defaultₓ'. -/
#print Inhabited.default /-
@[inline]
irreducible_def Inhabited.default (α : Sort u) [Inhabited α] : α :=
  default
#align arbitrary Inhabited.default
-/

instance Prop.inhabited : Inhabited Prop :=
  ⟨True⟩
#align prop.inhabited Prop.inhabited

instance Pi.inhabited (α : Sort u) {β : α → Sort v} [∀ x, Inhabited (β x)] : Inhabited (∀ x, β x) :=
  ⟨fun a => default⟩
#align pi.inhabited Pi.inhabited

instance : Inhabited Bool :=
  ⟨false⟩

instance : Inhabited True :=
  ⟨trivial⟩

#print Nonempty /-
class inductive Nonempty (α : Sort u) : Prop
  | intro (val : α) : Nonempty
#align nonempty Nonempty
-/

#print Nonempty.elim /-
protected theorem Nonempty.elim {α : Sort u} {p : Prop} (h₁ : Nonempty α) (h₂ : α → p) : p :=
  Nonempty.ndrec h₂ h₁
#align nonempty.elim Nonempty.elim
-/

#print instNonempty /-
instance (priority := 100) instNonempty {α : Sort u} [Inhabited α] : Nonempty α :=
  ⟨default⟩
#align nonempty_of_inhabited instNonempty
-/

#print nonempty_of_exists /-
theorem nonempty_of_exists {α : Sort u} {p : α → Prop} : (∃ x, p x) → Nonempty α
  | ⟨w, h⟩ => ⟨w⟩
#align nonempty_of_exists nonempty_of_exists
-/

/-! subsingleton -/

#print Subsingleton /-
class inductive Subsingleton (α : Sort u) : Prop
  | intro (h : ∀ a b : α, a = b) : Subsingleton
#align subsingleton Subsingleton
-/

#print Subsingleton.elim /-
protected theorem Subsingleton.elim {α : Sort u} [h : Subsingleton α] : ∀ a b : α, a = b :=
  Subsingleton.ndrec (fun p => p) h
#align subsingleton.elim Subsingleton.elim
-/

#print Subsingleton.helim /-
protected theorem Subsingleton.helim {α β : Sort u} [h : Subsingleton α] (h : α = β) :
    ∀ (a : α) (b : β), HEq a b :=
  Eq.recOn h fun a b : α => hEq_of_eq (Subsingleton.elim a b)
#align subsingleton.helim Subsingleton.helim
-/

instance subsingleton_prop (p : Prop) : Subsingleton p :=
  ⟨fun a b => proof_irrel a b⟩
#align subsingleton_prop subsingleton_prop

instance (p : Prop) : Subsingleton (Decidable p) :=
  Subsingleton.intro fun d₁ =>
    match d₁ with
    | is_true t₁ => fun d₂ =>
      match d₂ with
      | is_true t₂ => Eq.recOn (proof_irrel t₁ t₂) rfl
      | is_false f₂ => absurd t₁ f₂
    | is_false f₁ => fun d₂ =>
      match d₂ with
      | is_true t₂ => absurd t₂ f₁
      | is_false f₂ => Eq.recOn (proof_irrel f₁ f₂) rfl

#print rec_subsingleton /-
protected theorem rec_subsingleton {p : Prop} [h : Decidable p] {h₁ : p → Sort u} {h₂ : ¬p → Sort u}
    [h₃ : ∀ h : p, Subsingleton (h₁ h)] [h₄ : ∀ h : ¬p, Subsingleton (h₂ h)] :
    Subsingleton (Decidable.recOn h h₂ h₁) :=
  match h with
  | is_true h => h₃ h
  | is_false h => h₄ h
#align rec_subsingleton rec_subsingleton
-/

#print if_pos /-
theorem if_pos {c : Prop} [h : Decidable c] (hc : c) {α : Sort u} {t e : α} : ite c t e = t :=
  match h with
  | is_true hc => rfl
  | is_false hnc => absurd hc hnc
#align if_pos if_pos
-/

#print if_neg /-
theorem if_neg {c : Prop} [h : Decidable c] (hnc : ¬c) {α : Sort u} {t e : α} : ite c t e = e :=
  match h with
  | is_true hc => absurd hc hnc
  | is_false hnc => rfl
#align if_neg if_neg
-/

#print if_t_t /-
@[simp]
theorem if_t_t (c : Prop) [h : Decidable c] {α : Sort u} (t : α) : ite c t t = t :=
  match h with
  | is_true hc => rfl
  | is_false hnc => rfl
#align if_t_t if_t_t
-/

#print imp_of_if_pos /-
theorem imp_of_if_pos {c t e : Prop} [Decidable c] (h : ite c t e) : c → t := fun hc =>
  Eq.recOn (if_pos hc : ite c t e = t) h
#align implies_of_if_pos imp_of_if_pos
-/

#print imp_of_if_neg /-
theorem imp_of_if_neg {c t e : Prop} [Decidable c] (h : ite c t e) : ¬c → e := fun hnc =>
  Eq.recOn (if_neg hnc : ite c t e = e) h
#align implies_of_if_neg imp_of_if_neg
-/

#print if_ctx_congr /-
theorem if_ctx_congr {α : Sort u} {b c : Prop} [dec_b : Decidable b] [dec_c : Decidable c]
    {x y u v : α} (h_c : b ↔ c) (h_t : c → x = u) (h_e : ¬c → y = v) : ite b x y = ite c u v :=
  match dec_b, dec_c with
  | is_false h₁, is_false h₂ => h_e h₂
  | is_true h₁, is_true h₂ => h_t h₂
  | is_false h₁, is_true h₂ => absurd h₂ (Iff.mp (not_congr h_c) h₁)
  | is_true h₁, is_false h₂ => absurd h₁ (Iff.mpr (not_congr h_c) h₂)
#align if_ctx_congr if_ctx_congr
-/

#print if_congr /-
theorem if_congr {α : Sort u} {b c : Prop} [dec_b : Decidable b] [dec_c : Decidable c] {x y u v : α}
    (h_c : b ↔ c) (h_t : x = u) (h_e : y = v) : ite b x y = ite c u v :=
  @if_ctx_congr α b c dec_b dec_c x y u v h_c (fun h => h_t) fun h => h_e
#align if_congr if_congr
-/

#print if_true /-
@[simp]
theorem if_true {α : Sort u} {h : Decidable True} (t e : α) : @ite α True h t e = t :=
  if_pos trivial
#align if_true if_true
-/

#print if_false /-
@[simp]
theorem if_false {α : Sort u} {h : Decidable False} (t e : α) : @ite α False h t e = e :=
  if_neg not_false
#align if_false if_false
-/

#print if_ctx_congr_prop /-
theorem if_ctx_congr_prop {b c x y u v : Prop} [dec_b : Decidable b] [dec_c : Decidable c]
    (h_c : b ↔ c) (h_t : c → (x ↔ u)) (h_e : ¬c → (y ↔ v)) : ite b x y ↔ ite c u v :=
  match dec_b, dec_c with
  | is_false h₁, is_false h₂ => h_e h₂
  | is_true h₁, is_true h₂ => h_t h₂
  | is_false h₁, is_true h₂ => absurd h₂ (Iff.mp (not_congr h_c) h₁)
  | is_true h₁, is_false h₂ => absurd h₁ (Iff.mpr (not_congr h_c) h₂)
#align if_ctx_congr_prop if_ctx_congr_prop
-/

#print if_congr_prop /-
@[congr]
theorem if_congr_prop {b c x y u v : Prop} [dec_b : Decidable b] [dec_c : Decidable c] (h_c : b ↔ c)
    (h_t : x ↔ u) (h_e : y ↔ v) : ite b x y ↔ ite c u v :=
  if_ctx_congr_prop h_c (fun h => h_t) fun h => h_e
#align if_congr_prop if_congr_prop
-/

#print if_ctx_simp_congr_prop /-
theorem if_ctx_simp_congr_prop {b c x y u v : Prop} [dec_b : Decidable b] (h_c : b ↔ c)
    (h_t : c → (x ↔ u)) (h_e : ¬c → (y ↔ v)) :
    ite b x y ↔ @ite Prop c (decidable_of_decidable_of_iff dec_b h_c) u v :=
  @if_ctx_congr_prop b c x y u v dec_b (decidable_of_decidable_of_iff dec_b h_c) h_c h_t h_e
#align if_ctx_simp_congr_prop if_ctx_simp_congr_prop
-/

#print if_simp_congr_prop /-
@[congr]
theorem if_simp_congr_prop {b c x y u v : Prop} [dec_b : Decidable b] (h_c : b ↔ c) (h_t : x ↔ u)
    (h_e : y ↔ v) : ite b x y ↔ @ite Prop c (decidable_of_decidable_of_iff dec_b h_c) u v :=
  @if_ctx_simp_congr_prop b c x y u v dec_b h_c (fun h => h_t) fun h => h_e
#align if_simp_congr_prop if_simp_congr_prop
-/

#print dif_pos /-
@[simp]
theorem dif_pos {c : Prop} [h : Decidable c] (hc : c) {α : Sort u} {t : c → α} {e : ¬c → α} :
    dite c t e = t hc :=
  match h with
  | is_true hc => rfl
  | is_false hnc => absurd hc hnc
#align dif_pos dif_pos
-/

#print dif_neg /-
@[simp]
theorem dif_neg {c : Prop} [h : Decidable c] (hnc : ¬c) {α : Sort u} {t : c → α} {e : ¬c → α} :
    dite c t e = e hnc :=
  match h with
  | is_true hc => absurd hc hnc
  | is_false hnc => rfl
#align dif_neg dif_neg
-/

#print dif_ctx_congr /-
@[congr]
theorem dif_ctx_congr {α : Sort u} {b c : Prop} [dec_b : Decidable b] [dec_c : Decidable c]
    {x : b → α} {u : c → α} {y : ¬b → α} {v : ¬c → α} (h_c : b ↔ c)
    (h_t : ∀ h : c, x (Iff.mpr h_c h) = u h) (h_e : ∀ h : ¬c, y (Iff.mpr (not_congr h_c) h) = v h) :
    @dite α b dec_b x y = @dite α c dec_c u v :=
  match dec_b, dec_c with
  | is_false h₁, is_false h₂ => h_e h₂
  | is_true h₁, is_true h₂ => h_t h₂
  | is_false h₁, is_true h₂ => absurd h₂ (Iff.mp (not_congr h_c) h₁)
  | is_true h₁, is_false h₂ => absurd h₁ (Iff.mpr (not_congr h_c) h₂)
#align dif_ctx_congr dif_ctx_congr
-/

#print dif_ctx_simp_congr /-
theorem dif_ctx_simp_congr {α : Sort u} {b c : Prop} [dec_b : Decidable b] {x : b → α} {u : c → α}
    {y : ¬b → α} {v : ¬c → α} (h_c : b ↔ c) (h_t : ∀ h : c, x (Iff.mpr h_c h) = u h)
    (h_e : ∀ h : ¬c, y (Iff.mpr (not_congr h_c) h) = v h) :
    @dite α b dec_b x y = @dite α c (decidable_of_decidable_of_iff dec_b h_c) u v :=
  @dif_ctx_congr α b c dec_b (decidable_of_decidable_of_iff dec_b h_c) x u y v h_c h_t h_e
#align dif_ctx_simp_congr dif_ctx_simp_congr
-/

#print dif_eq_if /-
-- Remark: dite and ite are "defally equal" when we ignore the proofs.
theorem dif_eq_if (c : Prop) [h : Decidable c] {α : Sort u} (t : α) (e : α) :
    (dite c (fun h => t) fun h => e) = ite c t e :=
  match h with
  | is_true hc => rfl
  | is_false hnc => rfl
#align dif_eq_if dif_eq_if
-/

instance {c t e : Prop} [d_c : Decidable c] [d_t : Decidable t] [d_e : Decidable e] :
    Decidable (if c then t else e) :=
  match d_c with
  | is_true hc => d_t
  | is_false hc => d_e

instance {c : Prop} {t : c → Prop} {e : ¬c → Prop} [d_c : Decidable c] [d_t : ∀ h, Decidable (t h)]
    [d_e : ∀ h, Decidable (e h)] : Decidable (if h : c then t h else e h) :=
  match d_c with
  | is_true hc => d_t hc
  | is_false hc => d_e hc

#print AsTrue /-
def AsTrue (c : Prop) [Decidable c] : Prop :=
  if c then True else False
#align as_true AsTrue
-/

#print AsFalse /-
def AsFalse (c : Prop) [Decidable c] : Prop :=
  if c then False else True
#align as_false AsFalse
-/

#print AsTrue.get /-
theorem AsTrue.get {c : Prop} [h₁ : Decidable c] (h₂ : AsTrue c) : c :=
  match h₁, h₂ with
  | is_true h_c, h₂ => h_c
  | is_false h_c, h₂ => False.elim h₂
#align of_as_true AsTrue.get
-/

#print ULift /-
/-- Universe lifting operation -/
structure ULift.{r, s} (α : Type s) : Type max s r where up ::
  down : α
#align ulift ULift
-/

namespace ULift

#print ULift.up_down /-
/-- Bijection between α and ulift.{v} α -/
theorem up_down {α : Type u} : ∀ b : ULift.{v} α, up (down b) = b
  | up a => rfl
#align ulift.up_down ULift.up_down
-/

#print ULift.down_up /-
theorem down_up {α : Type u} (a : α) : down (up.{v} a) = a :=
  rfl
#align ulift.down_up ULift.down_up
-/

end ULift

#print PLift /-
/-- Universe lifting operation from Sort to Type -/
structure PLift (α : Sort u) : Type u where up ::
  down : α
#align plift PLift
-/

namespace PLift

#print PLift.up_down /-
/-- Bijection between α and plift α -/
theorem up_down {α : Sort u} : ∀ b : PLift α, up (down b) = b
  | up a => rfl
#align plift.up_down PLift.up_down
-/

#print PLift.down_up /-
theorem down_up {α : Sort u} (a : α) : down (up a) = a :=
  rfl
#align plift.down_up PLift.down_up
-/

end PLift

#print let_value_eq /-
/-- Equalities for rewriting let-expressions -/
theorem let_value_eq {α : Sort u} {β : Sort v} {a₁ a₂ : α} (b : α → β) :
    a₁ = a₂ →
      (let x : α := a₁
        b x) =
        let x : α := a₂
        b x :=
  fun h => Eq.recOn h rfl
#align let_value_eq let_value_eq
-/

/- warning: let_value_heq -> let_value_heq is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u2}} {β : α -> Sort.{u1}} {a₁ : α} {a₂ : α} (b : forall (x : α), β x), (Eq.{u2} α a₁ a₂) -> (HEq.{u1} (β a₁) (let x : α := a₁; b x) (β a₂) (let x : α := a₂; b x))
but is expected to have type
  forall {α : Sort.{u1}} {β : α -> Sort.{u2}} {a₁ : α} {a₂ : α} (b : forall (x : α), β x), (Eq.{u1} α a₁ a₂) -> (HEq.{u2} (β a₁) (let x : α := a₁; b x) (β a₂) (let x : α := a₂; b x))
Case conversion may be inaccurate. Consider using '#align let_value_heq let_value_heqₓ'. -/
theorem let_value_heq {α : Sort v} {β : α → Sort u} {a₁ a₂ : α} (b : ∀ x : α, β x) :
    a₁ = a₂ →
      HEq
        (let x : α := a₁
        b x)
        (let x : α := a₂
        b x) :=
  fun h => Eq.recOn h (HEq.refl (b a₁))
#align let_value_heq let_value_heq

/- warning: let_body_eq -> let_body_eq is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u2}} {β : α -> Sort.{u1}} (a : α) {b₁ : forall (x : α), β x} {b₂ : forall (x : α), β x}, (forall (x : α), Eq.{u1} (β x) (b₁ x) (b₂ x)) -> (Eq.{u1} (β a) (let x : α := a; b₁ x) (let x : α := a; b₂ x))
but is expected to have type
  forall {α : Sort.{u1}} {β : α -> Sort.{u2}} (a : α) {b₁ : forall (x : α), β x} {b₂ : forall (x : α), β x}, (forall (x : α), Eq.{u2} (β x) (b₁ x) (b₂ x)) -> (Eq.{u2} (let x : α := a; β x) (let x : α := a; b₁ x) (let x : α := a; b₂ x))
Case conversion may be inaccurate. Consider using '#align let_body_eq let_body_eqₓ'. -/
theorem let_body_eq {α : Sort v} {β : α → Sort u} (a : α) {b₁ b₂ : ∀ x : α, β x} :
    (∀ x, b₁ x = b₂ x) →
      (let x : α := a
        b₁ x) =
        let x : α := a
        b₂ x :=
  fun h => h a
#align let_body_eq let_body_eq

/- warning: let_eq -> let_eq is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u2}} {β : Sort.{u1}} {a₁ : α} {a₂ : α} {b₁ : α -> β} {b₂ : α -> β}, (Eq.{u2} α a₁ a₂) -> (forall (x : α), Eq.{u1} β (b₁ x) (b₂ x)) -> (Eq.{u1} β (let x : α := a₁; b₁ x) (let x : α := a₂; b₂ x))
but is expected to have type
  forall {α : Sort.{u1}} {β : Sort.{u2}} {a₁ : α} {a₂ : α} {b₁ : α -> β} {b₂ : α -> β}, (Eq.{u1} α a₁ a₂) -> (forall (x : α), Eq.{u2} β (b₁ x) (b₂ x)) -> (Eq.{u2} β (let x : α := a₁; b₁ x) (let x : α := a₂; b₂ x))
Case conversion may be inaccurate. Consider using '#align let_eq let_eqₓ'. -/
theorem let_eq {α : Sort v} {β : Sort u} {a₁ a₂ : α} {b₁ b₂ : α → β} :
    a₁ = a₂ →
      (∀ x, b₁ x = b₂ x) →
        (let x : α := a₁
          b₁ x) =
          let x : α := a₂
          b₂ x :=
  fun h₁ h₂ => Eq.recOn h₁ (h₂ a₁)
#align let_eq let_eq

section Relation

variable {α : Sort u} {β : Sort v} (r : β → β → Prop)

-- mathport name: «expr ≺ »
local infixl:50 "≺" => r

#print Reflexive /-
def Reflexive :=
  ∀ x, x≺x
#align reflexive Reflexive
-/

#print Symmetric /-
def Symmetric :=
  ∀ ⦃x y⦄, x≺y → y≺x
#align symmetric Symmetric
-/

#print Transitive /-
def Transitive :=
  ∀ ⦃x y z⦄, x≺y → y≺z → x≺z
#align transitive Transitive
-/

#print Equivalence /-
def Equivalence :=
  Reflexive r ∧ Symmetric r ∧ Transitive r
#align equivalence Equivalence
-/

#print Total /-
def Total :=
  ∀ x y, x≺y ∨ y≺x
#align total Total
-/

#print Equivalence.mk /-
theorem Equivalence.mk (rfl : Reflexive r) (symm : Symmetric r) (trans : Transitive r) :
    Equivalence r :=
  ⟨rfl, symm, trans⟩
#align mk_equivalence Equivalence.mk
-/

#print Irreflexive /-
def Irreflexive :=
  ∀ x, ¬x≺x
#align irreflexive Irreflexive
-/

#print AntiSymmetric /-
def AntiSymmetric :=
  ∀ ⦃x y⦄, x≺y → y≺x → x = y
#align anti_symmetric AntiSymmetric
-/

#print EmptyRelation /-
def EmptyRelation := fun a₁ a₂ : α => False
#align empty_relation EmptyRelation
-/

#print Subrelation /-
def Subrelation (q r : β → β → Prop) :=
  ∀ ⦃x y⦄, q x y → r x y
#align subrelation Subrelation
-/

#print InvImage /-
def InvImage (f : α → β) : α → α → Prop := fun a₁ a₂ => f a₁≺f a₂
#align inv_image InvImage
-/

#print InvImage.trans /-
theorem InvImage.trans (f : α → β) (h : Transitive r) : Transitive (InvImage r f) :=
  fun (a₁ a₂ a₃ : α) (h₁ : InvImage r f a₁ a₂) (h₂ : InvImage r f a₂ a₃) => h h₁ h₂
#align inv_image.trans InvImage.trans
-/

#print InvImage.irreflexive /-
theorem InvImage.irreflexive (f : α → β) (h : Irreflexive r) : Irreflexive (InvImage r f) :=
  fun (a : α) (h₁ : InvImage r f a a) => h (f a) h₁
#align inv_image.irreflexive InvImage.irreflexive
-/

end Relation

section Binary

variable {α : Type u} {β : Type v}

variable (f : α → α → α)

variable (inv : α → α)

variable (one : α)

-- mathport name: f
local notation a "*" b => f a b

-- mathport name: inv
local notation a "⁻¹" => inv a

variable (g : α → α → α)

-- mathport name: g
local notation a "+" b => g a b

#print Commutative /-
def Commutative :=
  ∀ a b, (a*b) = b*a
#align commutative Commutative
-/

#print Associative /-
def Associative :=
  ∀ a b c, ((a*b)*c) = a*b*c
#align associative Associative
-/

#print LeftIdentity /-
def LeftIdentity :=
  ∀ a, (one*a) = a
#align left_identity LeftIdentity
-/

#print RightIdentity /-
def RightIdentity :=
  ∀ a, (a*one) = a
#align right_identity RightIdentity
-/

#print RightInverse /-
def RightInverse :=
  ∀ a, (a*a⁻¹) = one
#align right_inverse RightInverse
-/

#print LeftCancelative /-
def LeftCancelative :=
  ∀ a b c, ((a*b) = a*c) → b = c
#align left_cancelative LeftCancelative
-/

#print RightCancelative /-
def RightCancelative :=
  ∀ a b c, ((a*b) = c*b) → a = c
#align right_cancelative RightCancelative
-/

#print LeftDistributive /-
def LeftDistributive :=
  ∀ a b c, (a*b+c) = (a*b)+a*c
#align left_distributive LeftDistributive
-/

#print RightDistributive /-
def RightDistributive :=
  ∀ a b c, ((a+b)*c) = (a*c)+b*c
#align right_distributive RightDistributive
-/

#print RightCommutative /-
def RightCommutative (h : β → α → β) :=
  ∀ b a₁ a₂, h (h b a₁) a₂ = h (h b a₂) a₁
#align right_commutative RightCommutative
-/

#print LeftCommutative /-
def LeftCommutative (h : α → β → β) :=
  ∀ a₁ a₂ b, h a₁ (h a₂ b) = h a₂ (h a₁ b)
#align left_commutative LeftCommutative
-/

#print left_comm /-
theorem left_comm : Commutative f → Associative f → LeftCommutative f := fun hcomm hassoc =>
  fun a b c =>
  calc
    (a*b*c) = (a*b)*c := Eq.symm (hassoc a b c)
    _ = (b*a)*c := (hcomm a b ▸ rfl)
    _ = b*a*c := hassoc b a c

#align left_comm left_comm
-/

#print right_comm /-
theorem right_comm : Commutative f → Associative f → RightCommutative f := fun hcomm hassoc =>
  fun a b c =>
  calc
    ((a*b)*c) = a*b*c := hassoc a b c
    _ = a*c*b := (hcomm b c ▸ rfl)
    _ = (a*c)*b := Eq.symm (hassoc a c b)

#align right_comm right_comm
-/

end Binary