--
-- 4. Quantifiers and Equality
--

-- The Universal Quantifier

example (α : Type) (p q : α → Prop) : (∀ x : α, p x ∧ q x) → ∀ y : α, p y :=
  fun h : ∀ x : α, p x ∧ q x =>
  fun y : α =>
  show p y from (h y).left

variable (α : Type) (r : α → α → Prop)
variable (trans_r : ∀ {x y z}, r x y → r y z → r x z)

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

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

-- Equality

#check Eq.refl
#check Eq.symm
#check Eq.trans

universe u
#check @Eq.refl.{u}
#check @Eq.symm.{u}
#check @Eq.trans.{u}

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

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

variable (α β : Type)

example (f : α → β) (a : α) : (fun x => f x) a = f a := Eq.refl _
example (f : α → β) (a : α) : (fun x => f x) a = f a := rfl

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

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

variable (α : Type)
variable (a b : α)
variable (f g : α → Nat)
variable (h₁ : a = b)
variable (h₂ : f = g)

example : f a = f b := congrArg f h₁
example : f a = g a := congrFun h₂ a

variable (a : Nat) in
example : a + 0 = a := Nat.add_zero a

-- Calculational Proofs
variable (a b c d : Nat) in
variable (h1 : a = b) in
variable (h2 : b = c + 1) in
variable (h3 : c = d) in
variable (h4 : e = 1 + d) in
theorem T : a = e :=
  calc
    a = b     := h1
    _ = c + 1 := h2
    _ = d + 1 := congrArg Nat.succ h3
    _ = 1 + d := Nat.add_comm d 1
    _ = e     := h4.symm

-- The Existential Quantifier
example : ∃ x : Nat, x > 0 :=
  have h : 1 > 0 := Nat.zero_lt_succ 0
  Exists.intro 1 h

variable (α : Type) (p q : α → Prop) in
example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
  Exists.elim h
    fun w =>
    fun hw : p w ∧ q w => 
    Exists.intro w ⟨hw.right, hw.left⟩

#check @Exists.elim.{u}

#check Sigma

variable (α : Type) (p q : α → Prop) in
example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
  match h with
  | ⟨w, hw⟩ => ⟨w, hw.right, hw.left⟩


-- More on the Proof Language
variable (f : Nat → Nat)
variable (h : ∀ x : Nat, f x ≤ f (x + 1))

example : f 0 ≤ f 3 :=
  have : f 0 ≤ f 1 := h 0
  have : f 0 ≤ f 2 := Nat.le_trans (by assumption) (h 1)
  show f 0 ≤ f 3 from Nat.le_trans (by assumption) (h 2)

example : f 0 ≥ f 1 → f 1 ≥ f 2 → f 0 = f 2 :=
  fun _ : f 0 ≥ f 1 =>
  fun _ : f 1 ≥ f 2 =>
  have : f 0 ≥ f 2 := Nat.le_trans ‹f 1 ≥ f 2› ‹f 0 ≥ f 1›
  have : f 0 ≤ f 2 := Nat.le_trans (h 0) (h 1)
  show f 0 = f 2 from Nat.le_antisymm this ‹f 0 ≥ f 2›

-- Exercises
variable (α : Type) (p q : α → Prop)

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
  have h1 : (∀ (x : α), p x ∧ q x) → (∀ (x : α), p x) ∧ ∀ (x : α), q x :=
    fun h =>
      have h11 : ∀ (x : α), p x :=
        fun x : α => (h x).left
      have h12 : ∀ (x : α), q x :=
        fun x : α => (h x).right
      And.intro h11 h12
  have h2 : ((∀ (x : α), p x) ∧ ∀ (x : α), q x) → ∀ (x : α), p x ∧ q x :=
    fun h =>
      fun x : α =>
        ⟨h.left x, h.right x⟩
  Iff.intro h1 h2
example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
  fun h1 =>
  fun h2 =>
  fun x : α =>
    show q x from (h1 x) (h2 x)

example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
  fun h =>
    h.elim
      (fun hp =>
        fun x => Or.inl (hp x))
      (fun hq =>
        fun x => Or.inr (hq x))

variable (α : Type) (p q : α → Prop)
variable (r : Prop)

example : α → ((∀ x : α, r) ↔ r) :=
  fun hα =>
    have h1 := fun h => h hα
    have h2 := fun hr => fun _ => hr
    Iff.intro h1 h2
open Classical in
example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
  have h1 : (∀ (x : α), p x ∨ r) → (∀ (x : α), p x) ∨ r :=
    fun h =>
      sorry
  have h2 : (∀ (x : α), p x) ∨ r → ∀ (x : α), p x ∨ r :=
    fun h =>
      h.elim
        (fun hl =>
          fun hx =>
            Or.inl (hl hx))
        (fun hr =>
          fun hx =>
            Or.inr hr)
  Iff.intro h1 h2

example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
  have h1 : (∀ (x : α), r → p x) → r → ∀ (x : α), p x :=
    fun h =>
      fun hr =>
        fun x =>
          (h x) hr
  have h2 : (r → ∀ (x : α), p x) → ∀ (x : α), r → p x :=
    fun h =>
      fun x =>
        fun hr =>
          (h hr) x
  Iff.intro h1 h2


variable (men : Type) (barber : men)
variable (shaves : men → men → Prop)

open Classical in
example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : False :=
  have hbarber : shaves barber barber ↔ ¬shaves barber barber :=
    h barber
  (em (shaves barber barber)).elim
    (fun hbarberself =>
      have hnbarbarself := hbarber.mp hbarberself
      hnbarbarself hbarberself)
    (fun hnbarbarself =>
      hnbarbarself (hbarber.mpr hnbarbarself))
