module
/-
Copyright (c) 2024 Marcus Rossel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Marcus Rossel, Kim Morrison
-/
public meta import Lean.Elab.Term
/-!
These tests are originally from the `lean-egg` repository at
https://github.com/marcusrossel/lean-egg and were written by Marcus Rossel.

They are re-used here with permission, and adapted as regression tests for the `grind` tactic.
-/

section

example (p q : Nat → Prop) : ((∀ x, p x) ∧ (∀ x, q x)) ↔ ∀ x, p x ∧ q x := by
  grind

example (p q : Nat → Nat → Prop) : ((∀ x, p 1 x) ∧ (∀ x, q 1 x)) ↔ ∀ x, p 1 x ∧ q 1 x := by
  grind

example (p q : Nat → Nat → Prop) : ((∀ x, p x 1) ∧ (∀ x, q x 1)) ↔ ∀ x, p x 1 ∧ q x 1 := by
  grind

example (p q : Nat → Nat → Prop) : ((∀ x, p x 1) ∧ (∀ x, q x 1)) ↔ ∀ x, p x 1 ∧ q x 1 := by
  grind

end

section

example : (fun x => x) 0 = 0 := by
  grind

example : (fun _ => 1) 0 = 1 := by
  grind

example : (fun x => (fun y => y) x) 0 = 0 := by
  grind

example : (fun x => (fun _ => x) x) 0 = 0 := by
  grind

example : (fun x => (fun _ => x) 0) 1 = 1 := by
  grind

example : id ((fun x => x + 1) 2) = id (2 + 1) := by
  grind

example (h : y + 1 = z) : (fun _ => y + 1) 0 = z := by
  grind

example (h : y + 1 = z) : (fun x => x + 1) y = z := by
  grind

end

section

example :
    (∀ (α : Type) (l : List α), l.length = l.length) ↔
    (∀ (α : Type) (l : List α), l.length = l.length) := by
  grind

end

section

example (h₁ : x ∧ y) (h₂ : x ∧ y → 1 = 2) : 1 = 2 := by
  grind

example (h₁ : x ∧ y) (h₂ : x ∧ y → 1 = 2) : 1 = 2 := by
  grind

example (h₁ : x ∧ y) (h₂ : y ∧ x → 1 = 2) : 1 = 2 := by
  grind

example (h₁ : x ∧ y) (h₂ : y ∧ x → 1 = 2) : 1 = 2 := by
  grind

example (h₁ : x ∧ y) (h₂ : y ∧ x → 1 = 2) : 1 = 2 := by
  grind

example {a : Nat} (h : a < b) : a % b = a := by
  grind only [=Nat.mod_eq_of_lt]

example {x : Nat} (h₁ : x = y) (h₂ : x = y → 1 = 2) : 1 = 2 := by
  grind

example {x : Nat} (h₁ : x = y) (h₂ : x = y → 1 = 2) : 1 = 2 := by
  grind

example {x : Nat} (h₁ : x = y) (h₂ : x = y → 1 = 2) : 1 = 2 := by
  grind

example (h : ∀ p : Prop, p → 1 = id 1) : 1 = id 1 := by
  grind only [id]

example (h : ∀ p : Prop, p → (1 : Int) = id 1) : (1 : Int) = id 1 := by
  grind only [id]

example {p q r : Prop} (h₁ : p) (h₂ : p ↔ q) (h₃ : q → (p ↔ r)) : p ↔ r := by
  grind

example {p q r : Prop} (h₁ : p) (h₂ : p ↔ q) (h₃ : q ↔ r) (h₄ : r → (p ↔ s)) : p ↔ s := by
  grind

end

section

example (h : 0 = 0 → 1 = 2) : 1 = 2 := by
  grind

example (h : (0 = (fun x => x) 0) → 1 = 2) : 1 = 2 := by
  grind

variable {x : Nat} {f : Nat → Nat}

example (h₁ : x = y) (h₂ : x = y → 1 = 2) : 1 = 2 := by
  grind

example (h₁ : x = y) (h₂ : x = y → 1 = 2) : 1 = 2 := by
  grind

example (h₁ : x = y) (h₂ : y = z) (h₃ : x = z → 1 = 2) : 1 = 2 := by
  grind

example (h₁ : ∀ a : Nat, f a = a) (h₂ : f x = x → 1 = 2) : 1 = 2 := by
  grind

example (h₁ : ∀ a : Nat, f a = a) (h₂ : p ∧ q) (h₃ : (f x = x) → (p ∧ q) → 1 = 2) : 1 = 2 := by
  grind

example (h₁ : ∀ a : Nat, f a = a) (h₂ : p ∧ q) (h₃ : p ∧ q ↔ r) (h₄ : (f x = x) → r → 1 = 2) :
    1 = 2 := by
  grind

end

section

example : (fun _ => 1) 0 = 1 := by
  grind

end

section

example : (fun (z : α → α → α) x => (fun _y => z) x x) = (fun x => x) := by
  grind

end

section

example : (fun x => Nat.succ x) = Nat.succ := by
  grind

example : id (fun x => Nat.succ x) = id Nat.succ := by
  grind

example : (fun x => Nat.succ x) x = Nat.succ x := by
  grind

example (f : Nat → Nat) (h : f = g) : (fun x : Nat => f x) y = g y := by
  grind

example (f : Nat → Nat) (h : f y = g) : (fun x : Nat => f x) y = g := by
  grind

elab "eta" n:num fn:ident ty:term : term => open Lean.Elab.Term in do
  let rec go (n : Nat) :=
    if n = 0
    then elabTerm fn none
    else return .lam `x (← elabTerm ty none) (.app (← go <| n - 1) (.bvar 0)) .default
  go n.getNat

example : (eta 2 Nat.succ Nat) = Nat.succ := by
  grind

example : (eta 2 Nat.succ Nat) x = Nat.succ x := by
  grind

example : id (eta 2 Nat.succ Nat) = id Nat.succ := by
  grind

example : (eta 50 Nat.succ Nat) = Nat.succ := by
  grind

example (f : α → α → α → α) : (fun a b => (fun x => (f a b) x)) = (fun a b => f a b) := by
  grind

end

section

example : true = true := by
  grind

example (h : true = false) : true = false := by
  grind

variable (f : Nat → Nat → Nat)

example (h : ∀ x y : Nat, f x y = f y x) : f 1 2 = f 2 1 := by
  grind

example (a b : Nat) (h : ∀ x y : Nat, f x y = f y x) : f a b = f b a := by
  grind

example (a b : Nat) (h : ∀ x y : Nat, f x x = f y x) : f a a = f b a := by
  grind

end

section

namespace Functional

inductive List α
  | nil : List α
  | cons : α → List α → List α

def append : List α → List α → List α
  | .nil,       bs => bs
  | .cons a as, bs => .cons a (append as bs)

theorem append_nil (as : List α) : append as .nil = as := by
  induction as with
  | nil         => grind only [append]
  | cons _ _ ih => grind only [append]

theorem append_assoc (as bs cs : List α) : append (append as bs) cs = append as (append bs cs) := by
  induction as with
  | nil         => grind only [append]
  | cons _ _ ih => grind only [append]

def reverseAux : List α → List α → List α
  | .nil,      r => r
  | .cons a l, r => reverseAux l (.cons a r)

def reverse (as : List α) : List α :=
  reverseAux as .nil

theorem reverseAux_eq_append (as bs : List α) :
    reverseAux as bs = append (reverseAux as .nil) bs := by
  induction as generalizing bs with
  | nil         => grind only [reverseAux, append]
  | cons _ _ ih => grind only [reverseAux, append_assoc, append]

theorem reverse_nil : reverse (.nil : List α) = .nil := by
  grind only [reverse, reverseAux]

theorem reverse_cons (a : α) (as : List α) :
    reverse (.cons a as) = append (reverse as) (.cons a .nil) := by
  grind only [reverse, reverseAux, reverseAux_eq_append]

theorem reverse_append (as bs : List α) :
    reverse (append as bs) = append (reverse bs) (reverse as) := by
  induction as generalizing bs with
  | nil          => grind only [reverse_nil, append_nil, append]
  | cons a as ih => grind only [append_assoc, reverse_cons, append]

def map (f : α → β) : List α → List β
  | .nil       => .nil
  | .cons a as => .cons (f a) (map f as)

def foldr (f : α → β → β) (init : β) : List α → β
  | .nil      => init
  | .cons a l => f a (foldr f init l)

def all (p : α → Bool) (xs : List α) : Bool :=
  foldr and true (map p xs)

def all' (p : α → Bool) : List α → Bool
  | .nil       => true
  | .cons x xs => (p x) && (all' p xs)

theorem all_deforestation (p : α → Bool) (xs : List α) : all p xs = all' p xs := by
  induction xs with
  | nil         => grind only [all, all', foldr, map]
  | cons _ _ ih => grind only [all, all', foldr, map]

end Functional

end

section

example (h₁ : ∀ p, p ∧ p) (h₂ : (∀ p, p ∧ p) → q = True) : q = True := by
  grind

end

section

example (a b : Nat) : a + b = b + a := by
  have h := Nat.add_comm
  grind

example (a : Nat) : (∀ x, x + 1 = 1 + x) → a + 1 = 1 + a :=
  fun h => by grind

example (a : Nat) : (∀ x, x + 1 = 1 + x) → a + 1 = 1 + a := by
  grind

example (a : Nat) : a + 1 = 1 + a := by
  grind

example (a : Nat) : (∀ x, x + 1 = 1 + x) → a + 1 = 1 + a :=
  fun _ => by grind

variable (h : ∀ x, x + 1 = 1 + x) in
example (a : Nat) : a + 1 = 1 + a := by
  grind

variable {h : ∀ x, x + 1 = 1 + x} in
example (a : Nat) : a + 1 = 1 + a := by
  grind

end

section

example : Nat → (x : Nat) → x = x := by
  intro x
  grind

example : Nat → (x : Nat) → (x_1 : Nat) → x = x := by
  intro x_1
  grind

end

section

example (f : α → γ) (g : β → δ) : List.map (Prod.map f g) [] = [] := by
  grind only [List.map]

example (f : α → γ) (g : β → δ) : List.map (Prod.map f g) [] = [] := by
  grind only [List.map]

variable {α : Type _} {β : Type _} {γ : Type _} {δ : Type _}

example (f : α → γ) (g : β → δ) : List.map (Prod.map f g) [] = [] := by
  grind only [List.map]

example (f : α → γ) (g : β → δ) : List.map (Prod.map f g) [] = [] := by
  grind only [List.map]

-- This example requires `Level.succ (Level.max _ _) = Level.max (Level.succ _) (Level.succ _)`.
example (h : ∀ γ : Type (max u v), γ = id γ) (α : Type u) (β : Type v) : (α × β) = id (α × β) := by
  grind

end

section

example : 0 = Nat.zero := by
  grind

example : 1 = Nat.succ 0 := by
  grind

example : Nat.succ 1 = Nat.succ (Nat.succ Nat.zero) := by
  grind

example : Int.ofNat (Nat.succ 1) = Int.ofNat (Nat.succ (Nat.succ Nat.zero)) := by
  grind

example (h : ∀ n, Nat.succ n = n + 1) : 1 = Nat.zero + 1 := by
  grind

example : 1 = Nat.zero + 1 := by
  grind

elab "app" n:num fn:ident arg:term : term => open Lean.Elab.Term in do
  let fn ← elabTerm fn none
  let rec go (n : Nat) := if n = 0 then elabTerm arg none else return .app fn <| ← go (n - 1)
  go n.getNat

example : (app 80 Nat.succ (nat_lit 0)) = (nat_lit 80) := by grind

example : 12345 + 67890 = 80235 := by
  grind

example : 12345 - 67890 = 0 := by
  grind

example : 67890 - 12345 = 55545 := by
  grind

example : 12345 * 67890 = 838102050 := by
  grind

example : 1234 ^ 5 = 2861381721051424 := by
  grind

example : 12345 / 67890 = 0 := by
  grind

example : 67890 / 12345 = 5 := by
  grind

example : 12345 / 0 = 0 := by
  grind

example : 67890 % 12345 = 6165 := by
  grind

example : 12345 % 67890 = 12345 := by
  grind

example : 12345 % 0 = 12345 := by
  grind

end

section

example (h : (a = b) ↔ (c = d)) : 0 = 0 := by
  grind

example (h₁ : (a = b) ↔ (c = d)) (h₂ : a = b) : c = d := by
  grind

example (h₁ : (a = b) ↔ (c = d)) (h₂ : a = b) : c = d := by
  grind

example (h₁ : (a = b) ↔ (c = d)) (h₂ : c = d) : a = b := by
  grind

example (h₁ : (a = b) ↔ (c = d)) (h₂ : a = b) (h₃ : d = e) : c = e := by
  grind

end

section

variable {I : Type u} {f : I → Type v₁} (x y : (i : I) → f i) (i : I)

instance instMul [∀ i, Mul <| f i] : Mul (∀ i : I, f i) :=
  ⟨fun f g i => f i * g i⟩

theorem mul_apply [∀ i, Mul <| f i] : (x * y) i = x i * y i :=
  rfl

example : True = True := by
  grind only [mul_apply]

end

section

variable (h : ∀ (p : Nat → Nat) (x : Nat), p x = p (x + 0))

example (f : Nat → Nat → Nat) : (f 1) x = (f 1) (x + 0) := by
  grind

example (f : Nat → Nat → Nat) : (f (nat_lit 1)) x = (f 1) (x + 0) := by
  grind

example (f : Nat → Nat → Nat) : (f 1) x = (f (nat_lit 1)) (x + 0) := by
  grind

example (f : Nat → Nat → Nat) : (f 1) x = (f 1) (x + (nat_lit 0)) := by
  grind

example (f : Nat → Nat → Nat) : (f 1) x = (f 1) (x + 0) := by
  grind

example (f : Nat → Nat → Nat) : (f 1) x = (f 1) (x + 0) := by
  grind

end

section

example : ([] : List α) = [] := by
  grind

example {l₁ l₂ : List α} : l₁ ++ l₂ = (l₂.reverse ++ l₁.reverse).reverse := by
  grind only [List.reverse_append, List.reverse_reverse]

end

section

example : True := by
  grind

example : True ↔ True := by
  grind

example (p q : Prop) (h : p ↔ q) : p ↔ q := by
  grind

example (x : Nat) : (x.add (.succ .zero) = x) ↔ ((Nat.succ .zero).add x = x) := by
  have h (x y : Nat) : x.add y = y.add x := Nat.add_comm ..
  grind

example (h : False) : False := by
  grind

example {p q r : Prop} (h₁ : p ∧ q) (h₂ : p ∧ q → r) : r := by
  grind

example (h : True) : True := by
  grind

example (h : 0 = 0) : 0 = 0 := by
  grind

example (a b : Nat) (h : a = b) : a = b := by
  grind

example (a b c : Nat) (h₁ : a = b) (h₂ : b = c) : a = c := by
  grind

example (h : p ∧ q ∧ r) : r ∧ r ∧ q ∧ p ∧ q ∧ r ∧ p := by
  grind

example (P : Nat → Prop) (hp : P Nat.zero.succ) (h : ∀ n, P n ↔ P n.succ) :
    P Nat.zero.succ.succ.succ.succ := by
  grind

end

section

example : 0 = 0 := by
  grind

example (a b c : Nat) (h₁ : a = b) (h₂ : b = c) : a = c := by
  grind

open List in
example (as bs : List α) : reverse (as ++ bs) = (reverse bs) ++ (reverse as) := by
  induction as generalizing bs with
  | nil  => grind only [reverse_nil, append_nil, List.nil_append]
  | cons => grind only [append_assoc, reverse_cons, List.cons_append]

variable (a b c d : Int)

example {p q r : Prop} (h₁ : p) (h₂ : p ↔ q) (h₃ : q → (p ↔ r)) : p ↔ r := by
  grind

end

section

example (arr : Array α) (i : Nat) (h₁ h₂ : i < arr.size) : arr[i]'h₁ = arr[i]'h₂ := by
  grind

end

section

example : true = true := by
  grind

example : True → true = true := by
  grind

example : True → False → true = true := by
  grind

abbrev P := ∀ x y : Nat, x + y = x + y

example : P := by
  grind

abbrev R := true = false

example (h : R) : true = false := by
  grind

abbrev S := ∀ _ : 0 = 0, R

example (h : S) : R := by
  grind

end

section

example : (fun x => (fun t _y => t) (fun z => x z)) = (fun (x : α → α) (_y : α) => x) := by
  grind

end

section

example : 0 = Nat.zero := by
  grind

example : 1 = Nat.succ 0 := by
  grind

example : Nat.succ 1 = Nat.succ (Nat.succ Nat.zero) := by
  grind

example : Int.ofNat (Nat.succ 1) = Int.ofNat (Nat.succ (Nat.succ Nat.zero)) := by
  grind

example (h : ∀ n, Nat.succ n = n + 1) : 1 = Nat.zero + 1 := by
  grind

end

section

example : "a" = "a" := by
  grind

example : "a\nb" = "a\nb" := by
  grind

example : "" = "" := by
  grind

example : " " = " " := by
  grind

example : "a b" = "a b" := by
  grind

example : "(" = "(" := by
  grind

example : ")" = ")" := by
  grind

example (h : "Le " ++ " an" = "Le  an") : "Le " ++ " an" = "Le  an" := by
  grind

end

section

class Group (α) extends Mul α, One α, Inv α where
  mul_assoc    (a b c : α) : (a * b) * c = a * (b * c)
  one_mul      (a : α)     : 1 * a = a
  mul_one      (a : α)     : a * 1 = a
  inv_mul_self (a : α)     : a⁻¹ * a = 1
  mul_inv_self (a : α)     : a * a⁻¹ = 1

variable [Group α] (a b x y : α)

attribute [grind _=_] Group.mul_assoc
attribute [grind] Group.one_mul
attribute [grind] Group.mul_one
attribute [grind] Group.inv_mul_self
attribute [grind] Group.mul_inv_self

example : a⁻¹ * (a * b) = b := by grind

@[grind]
theorem inv_mul_cancel_left : a⁻¹ * (a * b) = b := by grind

@[grind]
theorem mul_inv_cancel_left : a * (a⁻¹ * b) = b := by grind

end

section

example (h : ∀ [inst : Neg Int] (x : Int), @Neg.neg Int inst x = x) : (0 : Int) = (0 : Int) := by
  grind

example (h : ∀ [inst : Neg Int] (x : Int), @Neg.neg Int inst x = x) : (0 : Int) = (0 : Int) := by
  grind

example (h : ∀ (α) [inst : Neg α] (x : α), @Neg.neg α inst x = x) : (0 : Int) = (0 : Int) := by
  grind

end

section -- from `TC Proj Binders.lean`

example (h : (fun (α) [Mul α] (x y : α) => x * y) = a) : true = true := by
  grind

example (x : Nat) (h : ∀ (_ : x * y = z), z = z) : x = x := by
  grind

end

section -- from `TC Stuck.lean`

def f [Inhabited α] : α := Inhabited.default

example : 0 = 0 := by
  grind only [f]

end

section -- (failing tests) from `Thomas.lean`

example :
    ((fun x => (fun t (_y : α) => t) (fun z => x z)) (fun (z : α → α → α) x => ((fun _y => z) x) x))
    = fun _y => (fun z => z) := by
  grind

example :
    ((fun x => (fun t (_y : α) => t) (fun z => x z)) (fun (z : α → α → α) x => ((fun _y => z) x) x))
    = fun _y => (fun z => z) := by
  grind

end

section

variable (h : True ↔ ∀ (a : Nat) (_ : a > 0), True)

example : True ↔ ∀ (a : Nat) (_ : a > 0), True := by
  grind

end

section -- (failing test) from `WIP AC.lean`

example {a b c d e f g h i j k l m n o p q r s t u v w x y z : Nat} :
    a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z =
    z + y + x + w + v + u + t + s + r + q + p + o + n + m + l + k + j + i + h + g + f + e + d + c + b + a := by
  grind

end
