/-!
# Tests of elabAsElim elaborator and the `elab_as_elim` attribute
-/

-- For debugging:
-- set_option trace.Elab.app.elab_as_elim true

inductive Vec (α : Type u) : Nat → Type u
  | nil : Vec α 0
  | cons : α → Vec α n → Vec α (n+1)

def f1 (xs : Vec α n) : Nat :=
  Vec.casesOn xs 0 fun _ _ => 1

/-! Under-applied eliminator, and expected type isn't a pi type. -/
/--
error: failed to elaborate eliminator, insufficient number of arguments, expected type:
  Nat
-/
#guard_msgs in
def f2 (xs : Vec α n) : Nat :=
  xs.casesOn 0

def f3 (x : Nat) : Nat → (Nat → Nat) → Nat :=
  x.casesOn

/-! Under-applied eliminator, expected type's binders do not unify with remaining arguments. -/
/--
error: failed to elaborate eliminator, insufficient number of arguments, expected type:
  (Nat → Nat) → Nat → Nat
-/
#guard_msgs in
def f3' (x : Nat) : (Nat → Nat) → Nat → Nat :=
  x.casesOn

def f4 (xs : List Nat) : xs ≠ [] → xs.length > 0 :=
  xs.casesOn (by intros; contradiction) (by intros; simp +arith)

def f5 (xs : List Nat) (h : xs ≠ []) : xs.length > 0 :=
  xs.casesOn (by intros; contradiction) (by intros; simp +arith) h

def f6 (x : Nat) :=
  2 * x.casesOn 0 id

example : f6 (x+1) = 2*x := rfl

/-- error: failed to elaborate eliminator, unused named arguments: [a] -/
#guard_msgs in
def f7 (xs : Vec α n) : Nat :=
  xs.casesOn (a := 10) 0

def f8 (xs : List Nat) : xs ≠ [] → xs.length > 0 :=
  @List.casesOn _ (fun xs => xs ≠ [] → xs.length > 0) xs (by dsimp; intros; contradiction) (by dsimp; intros; simp +arith)

def f5' (xs : List Nat) (h : xs ≠ []) : xs.length > 0 :=
  xs.casesOn (fun h => absurd rfl h) (fun _ _ _ => Nat.zero_lt_succ ..) h

example (h₁ : a = b) (h₂ : b = c) : a = c :=
  Eq.rec h₂ h₁.symm

@[elab_as_elim] theorem subst {p : (b : α) → a = b → Prop} (h₁ : a = b) (h₂ : p a rfl) : p b h₁ := by
  cases h₁
  assumption

example (h₁ : a = b) (h₂ : b = c) : a = c :=
  subst h₁.symm h₂

theorem not_or_not : (¬p ∨ ¬q) → ¬(p ∧ q) := λ h ⟨hp, hq⟩ =>
  h.rec (λ h1 => h1 hp) (λ h2 => h2 hq)

structure Point where
  x : Nat

theorem PointExt_lean4 (p : Point) : forall (q : Point) (h1 : Point.x p = Point.x q), p = q :=
  Point.recOn p <|
   fun z1 q => Point.recOn q $
   fun z2 (hA : Point.x (Point.mk z1) = Point.x (Point.mk z2)) => congrArg Point.mk hA

inductive pos_num : Type
  | one  : pos_num
  | bit1 : pos_num → pos_num
  | bit0 : pos_num → pos_num

inductive num : Type
  | zero  : num
  | pos   : pos_num → num

inductive znum : Type
  | zero : znum
  | pos  : pos_num → znum
  | neg  : pos_num → znum

def pos_num.pred' : pos_num → num
  | one    => .zero
  | bit0 n => num.pos (num.casesOn (pred' n) one bit1)
  | bit1 n => num.pos (bit0 n)

protected def znum.bit1 : znum → znum
  | zero    => pos .one
  | pos n => pos (pos_num.bit1 n)
  | neg n => neg (num.casesOn (pos_num.pred' n) .one pos_num.bit1)

example (h : False) : a = c :=
  h.rec

example (h : False) : a = c :=
  h.elim

noncomputable def f : Nat → Nat :=
  Nat.rec 0 (fun x _ => x)

example : ∀ x, x ≥ 0 :=
  Nat.rec (Nat.le_refl 0) (fun _ ih => Nat.le_succ_of_le ih)

/-!
Tests of `@[elab_as_elim]` when the motive is not type correct.
-/

@[elab_as_elim]
def Foo.induction {P : (α : Type) → Prop} (α : Type) : P α := sorry

/--
error: failed to elaborate eliminator, motive is not type correct:
  fun x => T = T
-/
#guard_msgs in
example {n : Type} {T : n} : T = T := Foo.induction n

/--
error: failed to elaborate eliminator, motive is not type correct:
  fun x => T✝ = T✝
-/
#guard_msgs in
example {n : Type} : {T : n} → T = T := Foo.induction n

example {n : Type} : (T : n) → T = T := Foo.induction n

-- Disable implicit lambda
example {n : Type} : {T : n} → T = T := @(Foo.induction n)

/-!
A "motive is not type correct" regression test.
The `isEmptyElim` was failing due to being under-applied and the named `(α := α)` argument
having postponed elaboration. This fix is that `α` now elaborates eagerly.
-/

class IsEmpty (α : Sort u) : Prop where
  protected false : α → False

@[elab_as_elim]
def isEmptyElim [IsEmpty α] {p : α → Sort _} (a : α) : p a :=
  (IsEmpty.false a).elim

def Set (α : Type u) := α → Prop
def Set.univ {α : Type _} : Set α := fun _ => True
instance : Membership α (Set α) := ⟨fun s x => s x⟩
def Set.pi {α : ι → Type _} (s : Set ι) (t : (i : ι) → Set (α i)) : Set ((i : ι) → α i) := fun f => ∀ i ∈ s, f i ∈ t i

example {α : Type u} [IsEmpty α] {β : α → Type v} (x : (a : α) → β a) (s : (i : α) → Set (β i)) :
    x ∈ Set.univ.pi s := isEmptyElim (α := α)

-- Simplified version:
example {α : Type _} [IsEmpty α] :
  id (α → False) := isEmptyElim (α := α)

/-!
From mathlib, regression test. Need to eagerly elaborate the `n ≤ m` argument to deduce `m`
before computing the motive.
-/

@[elab_as_elim]
def leRecOn {C : Nat → Sort _} {n : Nat} : ∀ {m}, n ≤ m → (∀ {k}, C k → C (k + 1)) → C n → C m :=
  sorry

theorem leRecOn_self {C : Nat → Sort _} {n} {next : ∀ {k}, C k → C (k + 1)} (x : C n) :
    (leRecOn n.le_refl next x : C n) = x :=
  sorry

/-!
Issue https://github.com/leanprover/lean4/issues/4347

`False.rec` has `motive` as an explicit argument.
-/

example (h : False) : Nat := False.rec (fun _ => Nat) h
example (h : False) : Nat := False.rec _ h
example (h : False) : Nat := h.rec
example (h : False) : Nat := h.rec _

/-!
Check that the overapplied arguments given to the eliminator are not permuted.
In this example, `h0` and `h1` used to be reversed, leading to a kernel typechecking error.
-/
example (n : Nat) (h0 : n ≠ 0) (h1 : n ≠ 1) : n - 2 ≠ n - 1 :=
  Nat.recOn n (by simp) (by rintro (_ | _) <;> simp) h0 h1

/-!
Check that eliminators need at least one discriminant
-/

/--
error: unexpected eliminator resulting type
  p
-/
#guard_msgs in
@[elab_as_elim]
theorem mySillyEliminator {p : Prop} (h : p) : p := h
