import Mathlib.Tactic

/-!
# Proofs

Basics of proving in Lean, bridging the gap between programming and theorem proving.
-/

namespace ZeroToQED.Proving

-- ANCHOR: programming_and_proving
-- A function computes values - it has computational content
def factorial : Nat → Nat
  | 0 => 1
  | n + 1 => (n + 1) * factorial n

-- A theorem proves properties - it has no computational content at runtime
theorem factorial_pos : ∀ n, 0 < factorial n := by
  intro n
  induction n with
  | zero => simp [factorial]
  | succ n ih => simp [factorial]; omega
-- ANCHOR_END: programming_and_proving

-- ANCHOR: safe_div
-- Proofs as function arguments: checked at compile time, erased at runtime
def safeDiv (n : Nat) (d : Nat) (h : d ≠ 0) : Nat := n / d

-- The proof argument h vanishes in the compiled code
#eval safeDiv 10 3 (by decide)  -- 3
-- ANCHOR_END: safe_div

-- ANCHOR: first_proof
-- Your very first proof: 1 + 1 = 2
theorem one_plus_one : 1 + 1 = 2 := by
  rfl

-- Without tactics, just a direct proof term
theorem one_plus_one' : 1 + 1 = 2 := rfl
-- ANCHOR_END: first_proof

-- ANCHOR: equality_basics
-- Equality is reflexive: everything equals itself
theorem eq_self (n : Nat) : n = n := by
  rfl

-- Equality is symmetric: if a = b then b = a
theorem eq_symm (a b : Nat) (h : a = b) : b = a := by
  rw [h]

-- Equality is transitive: if a = b and b = c then a = c
theorem eq_trans (a b c : Nat) (h1 : a = b) (h2 : b = c) : a = c := by
  rw [h1, h2]
-- ANCHOR_END: equality_basics

-- ANCHOR: goal_state_demo
-- Demonstrating how the goal state changes
theorem add_zero (n : Nat) : n + 0 = n := by
  rfl

theorem zero_add (n : Nat) : 0 + n = n := by
  simp
-- ANCHOR_END: goal_state_demo

-- ANCHOR: rfl_examples
-- rfl works when both sides compute to the same value
theorem two_times_three : 2 * 3 = 6 := by rfl

theorem list_length : [1, 2, 3].length = 3 := by rfl

theorem string_append : "hello " ++ "world" = "hello world" := by rfl

theorem bool_and : true && false = false := by rfl

def double (n : Nat) : Nat := n + n

theorem double_two : double 2 = 4 := by rfl
-- ANCHOR_END: rfl_examples

-- ANCHOR: trivial_examples
theorem true_is_true : True := by
  trivial

theorem one_le_two : 1 ≤ 2 := by
  trivial

theorem and_true : True ∧ True := by
  trivial
-- ANCHOR_END: trivial_examples

-- ANCHOR: simp_examples
theorem add_zero_simp (n : Nat) : n + 0 = n := by
  simp

theorem zero_add_simp (n : Nat) : 0 + n = n := by
  simp

theorem silly_arithmetic : (1 + 0) * (2 + 0) + 0 = 2 := by
  simp

theorem list_append_nil {α : Type*} (xs : List α) : xs ++ [] = xs := by
  simp

theorem use_hypothesis (a b : Nat) (h : a = b) : a + 1 = b + 1 := by
  simp [h]
-- ANCHOR_END: simp_examples

-- ANCHOR: intro_apply
-- intro: move assumptions from goal into context
theorem imp_self (P : Prop) : P → P := by
  intro hp
  exact hp

-- apply: use a lemma whose conclusion matches the goal
theorem imp_trans (P Q R : Prop) : (P → Q) → (Q → R) → P → R := by
  intro hpq hqr hp
  apply hqr
  apply hpq
  exact hp

-- intro with universal quantifiers
theorem forall_self (P : Nat → Prop) : (∀ n, P n) → (∀ n, P n) := by
  intro h n
  exact h n
-- ANCHOR_END: intro_apply

-- ANCHOR: have_example
-- have: introduce intermediate results
theorem have_demo (a b c : Nat) (h1 : a = b) (h2 : b = c) : a = c := by
  have step : a = b := h1
  rw [step, h2]

theorem sum_example (n : Nat) : n + n = 2 * n := by
  have h : 2 * n = n + n := by ring
  rw [h]
-- ANCHOR_END: have_example

-- ANCHOR: cases_example
theorem bool_cases (b : Bool) : b = true ∨ b = false := by
  cases b with
  | true => left; rfl
  | false => right; rfl

theorem nat_zero_or_succ (n : Nat) : n = 0 ∨ n ≥ 1 := by
  cases n with
  | zero => left; rfl
  | succ m => right; simp

theorem option_destruct (o : Option Nat) : o = none ∨ ∃ n, o = some n := by
  cases o with
  | none => left; rfl
  | some n => right; exact ⟨n, rfl⟩
-- ANCHOR_END: cases_example

-- ANCHOR: induction_example
theorem sum_twice (n : Nat) : n + n = 2 * n := by
  induction n with
  | zero => rfl
  | succ n ih => omega

theorem length_append {α : Type*} (xs ys : List α) :
    (xs ++ ys).length = xs.length + ys.length := by
  induction xs with
  | nil => simp
  | cons x xs ih => simp [ih]; ring
-- ANCHOR_END: induction_example

-- ANCHOR: omega_example
theorem omega_simple (n : Nat) (h : n < 10) : n < 100 := by
  omega

theorem omega_transitive (a b c : Int) (h1 : a < b) (h2 : b < c) : a < c := by
  omega

theorem omega_sum (x y : Nat) (h : x + y = 10) : x ≤ 10 := by
  omega
-- ANCHOR_END: omega_example

-- ANCHOR: decide_example
theorem three_lt_five : (3 : Nat) < 5 := by
  decide

theorem bool_compute : (true && false) = false := by
  decide

theorem list_membership : 3 ∈ [1, 2, 3, 4, 5] := by
  decide

theorem fin_in_bounds : (2 : Fin 5).val < 5 := by
  decide
-- ANCHOR_END: decide_example

-- ANCHOR: proof_workflow
theorem worked_example (n : Nat) : n + 0 = 0 + n := by
  simp

theorem worked_example2 (a b : Nat) (h : a = b) : a + 1 = b + 1 := by
  rw [h]
-- ANCHOR_END: proof_workflow

-- ANCHOR: combining_tactics
theorem combined_proof (n : Nat) (h : n > 0) : n - 1 + 1 = n := by
  omega

theorem list_nonempty (xs : List Nat) (h : xs ≠ []) : xs.length > 0 := by
  cases xs with
  | nil => contradiction
  | cons x xs' => simp
-- ANCHOR_END: combining_tactics

/-!
## Exercises

Practice problems for the tactics introduced in this chapter.
-/

-- ANCHOR: exercises
-- Exercise 1: Use rfl to prove this computation
theorem ex_rfl : 3 * 4 = 12 := by
  rfl

-- Exercise 2: Use simp to simplify this expression
theorem ex_simp (n : Nat) : n * 1 + 0 = n := by
  simp

-- Exercise 3: Use intro and exact
theorem ex_intro_exact (P Q : Prop) (h : P) (hpq : P → Q) : Q := by
  exact hpq h

-- Exercise 4: Use cases to prove this about booleans
theorem ex_bool_not_not (b : Bool) : !!b = b := by
  cases b <;> rfl

-- Exercise 5: Use induction to prove addition is commutative
theorem ex_add_comm (n m : Nat) : n + m = m + n := by
  induction n with
  | zero => simp
  | succ n ih => omega

-- Exercise 6: Use omega to prove this inequality
theorem ex_omega (x y : Nat) (h1 : x ≤ 5) (h2 : y ≤ 3) : x + y ≤ 8 := by
  omega

-- Exercise 7: Combine multiple tactics
theorem ex_combined (xs : List Nat) : ([] ++ xs).length = xs.length := by
  simp

-- Exercise 8: Prove implication transitivity
theorem ex_imp_chain (A B C D : Prop) : (A → B) → (B → C) → (C → D) → A → D := by
  intro hab hbc hcd ha
  exact hcd (hbc (hab ha))

-- Exercise 9: Use cases on a natural number
theorem ex_nat_lt (n : Nat) : n = 0 ∨ 0 < n := by
  cases n with
  | zero => left; rfl
  | succ m => right; omega

-- Exercise 10: Prove a property about list reversal
theorem ex_reverse_nil : ([] : List Nat).reverse = [] := by
  rfl
-- ANCHOR_END: exercises

-- ANCHOR: liars_trap
-- Try to prove something false. Every tactic will fail.
theorem liar : 0 = 1 := by
  sorry  -- Try: rfl, simp, omega, decide. Nothing works.

-- The goal state shows: ⊢ 0 = 1
-- This goal is unprovable because it is false.
-- ANCHOR_END: liars_trap

-- ANCHOR: demorgan_project
-- De Morgan's Law: ¬(P ∧ Q) → (¬P ∨ ¬Q)
theorem demorgan (P Q : Prop) (h : ¬(P ∧ Q)) : ¬P ∨ ¬Q := by
  by_cases hp : P
  · -- Case: P is true
    right
    intro hq
    apply h
    constructor
    · exact hp
    · exact hq
  · -- Case: P is false
    left
    exact hp
-- ANCHOR_END: demorgan_project

-- ANCHOR: godel_gentzen
-- Gödel (1933) and Gentzen (1936) independently discovered that classical
-- logic embeds into intuitionistic logic via double-negation translation.
-- A classical proof of P becomes a constructive proof of ¬¬P.
-- Truth is preserved; only the computational witness is lost.
def DoubleNegation (P : Prop) : Prop := ¬¬P

-- Classical axioms become provable under double negation
theorem lem_double_neg (P : Prop) : DoubleNegation (P ∨ ¬P) :=
  fun h => h (Or.inr (fun hp => h (Or.inl hp)))

-- Double negation is a monad: pure and bind
theorem dn_pure {P : Prop} (h : P) : DoubleNegation P :=
  fun hnp => hnp h

theorem dn_bind {P Q : Prop} (h : DoubleNegation P) (f : P → DoubleNegation Q) :
    DoubleNegation Q :=
  fun hnq => h (fun hp => f hp hnq)
-- ANCHOR_END: godel_gentzen

end ZeroToQED.Proving
