import Lean

set_option cbv.warning false

/-! # cbv_simproc for Nat.fib using fast doubling

This test demonstrates a cbv_simproc ported from Mathlib's `norm_num` extension that evaluates `Nat.fib.
-/

open Lean Meta Sym.Simp

-- Define fib
def Nat.fib : Nat → Nat
  | 0 => 0
  | 1 => 1
  | n + 2 => Nat.fib n + Nat.fib (n + 1)

theorem Nat.fib_zero : Nat.fib 0 = 0 := rfl
theorem Nat.fib_one : Nat.fib 1 = 1 := rfl
theorem Nat.fib_two : Nat.fib 2 = 1 := rfl

def IsFibAux (n a b : Nat) := Nat.fib n = a ∧ Nat.fib (n + 1) = b

theorem isFibAux_zero : IsFibAux 0 0 1 :=
  ⟨Nat.fib_zero, Nat.fib_one⟩

theorem isFibAux_one : IsFibAux 1 1 1 :=
  ⟨Nat.fib_one, Nat.fib_two⟩

theorem fib_add_two {n : Nat} : Nat.fib (n + 2) = Nat.fib n + Nat.fib (n + 1) := by grind [Nat.fib]

theorem fib_add (m n : Nat) : Nat.fib (m + n + 1) = Nat.fib m * Nat.fib n + Nat.fib (m + 1) * Nat.fib (n + 1) := by
  induction n generalizing m with
  | zero => grind [Nat.fib]
  | succ n ih =>
    specialize ih (m + 1)
    rw [Nat.add_assoc m 1 n, Nat.add_comm 1 n] at ih
    simp only [fib_add_two, ih]
    grind

theorem fib_two_mul (n : Nat) : Nat.fib (2 * n) = Nat.fib n * (2 * Nat.fib (n + 1) - Nat.fib n) := by
  cases n
  · simp [Nat.fib]
  · rw [Nat.two_mul, ← Nat.add_assoc, fib_add, fib_add_two, Nat.two_mul]
    have add_tsub_cancel_right : ∀ (a b : Nat), a + b - b = a := by grind
    simp only [← Nat.add_assoc, add_tsub_cancel_right]
    grind

theorem fib_two_mul_add_one (n : Nat) : Nat.fib (2 * n + 1) = Nat.fib (n + 1) ^ 2 + Nat.fib n ^ 2 := by
  rw [Nat.two_mul, fib_add]
  grind [Nat.two_mul, fib_add]

theorem isFibAux_two_mul {n a b n' a' b' : Nat} (H : IsFibAux n a b)
    (hn : 2 * n = n') (h1 : a * (2 * b - a) = a') (h2 : a * a + b * b = b') :
    IsFibAux n' a' b' :=
  ⟨by rw [← hn, fib_two_mul, H.1, H.2, ← h1],
   by rw [← hn, fib_two_mul_add_one, H.1, H.2, Nat.pow_two, Nat.pow_two, Nat.add_comm, h2]⟩

theorem fib_two_mul_add_two (n : Nat) :
    Nat.fib (2 * n + 2) = Nat.fib (n + 1) * (2 * Nat.fib n + Nat.fib (n + 1)) := by
  rw [fib_add_two, fib_two_mul, fib_two_mul_add_one]
  induction n <;> grind [Nat.fib]

theorem isFibAux_two_mul_add_one {n a b n' a' b' : Nat} (H : IsFibAux n a b)
    (hn : 2 * n + 1 = n') (h1 : a * a + b * b = a') (h2 : b * (2 * a + b) = b') :
    IsFibAux n' a' b' :=
  ⟨by rw [← hn, fib_two_mul_add_one, H.1, H.2, Nat.pow_two, Nat.pow_two, Nat.add_comm, h1],
   by rw [← hn, fib_two_mul_add_two, H.1, H.2, h2]⟩

theorem isFibAux_two_mul_done {n a b n' a' : Nat} (H : IsFibAux n a b)
    (hn : 2 * n = n') (h : a * (2 * b - a) = a') : Nat.fib n' = a' :=
  (isFibAux_two_mul H hn h rfl).1

theorem isFibAux_two_mul_add_one_done {n a b n' a' : Nat} (H : IsFibAux n a b)
    (hn : 2 * n + 1 = n') (h : a * a + b * b = a') : Nat.fib n' = a' :=
  (isFibAux_two_mul_add_one H hn h rfl).1

namespace FibSimproc

partial def proveNatFibAux (n : Nat) : Nat × Nat × Expr :=
  match n with
  | 0 => ⟨0, 1, mkConst ``isFibAux_zero⟩
  | 1 => ⟨1, 1, mkConst ``isFibAux_one⟩
  | n =>
    let half := n / 2
    let ⟨a, b, H⟩ := proveNatFibAux half
    let en := mkNatLit half
    let ea := mkNatLit a
    let eb := mkNatLit b
    if n % 2 == 0 then
      let a' := a * (2 * b - a)
      let b' := a * a + b * b
      let en' := mkNatLit n
      let ea' := mkNatLit a'
      let eb' := mkNatLit b'
      let hn := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) en'
      let h1 := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) ea'
      let h2 := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) eb'
      let proof := mkAppN (mkConst ``isFibAux_two_mul) #[en, ea, eb, en', ea', eb', H, hn, h1, h2]
      ⟨a', b', proof⟩
    else
      let a' := a * a + b * b
      let b' := b * (2 * a + b)
      let en' := mkNatLit n
      let ea' := mkNatLit a'
      let eb' := mkNatLit b'
      let hn := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) en'
      let h1 := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) ea'
      let h2 := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) eb'
      let proof := mkAppN (mkConst ``isFibAux_two_mul_add_one) #[en, ea, eb, en', ea', eb', H, hn, h1, h2]
      ⟨a', b', proof⟩

def proveNatFib (n : Nat) : Nat × Expr :=
  match n with
  | 0 => ⟨0, mkConst ``Nat.fib_zero⟩
  | 1 => ⟨1, mkConst ``Nat.fib_one⟩
  | 2 => ⟨1, mkConst ``Nat.fib_two⟩
  | n =>
    let half := n / 2
    let ⟨a, b, H⟩ := proveNatFibAux half
    let en := mkNatLit half
    let ea := mkNatLit a
    let eb := mkNatLit b
    if n % 2 == 0 then
      let result := a * (2 * b - a)
      let en' := mkNatLit n
      let eresult := mkNatLit result
      let hn := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) en'
      let h := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) eresult
      let proof := mkAppN (mkConst ``isFibAux_two_mul_done) #[en, ea, eb, en', eresult, H, hn, h]
      ⟨result, proof⟩
    else
      let result := a * a + b * b
      let en' := mkNatLit n
      let eresult := mkNatLit result
      let hn := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) en'
      let h := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Nat) eresult
      let proof := mkAppN (mkConst ``isFibAux_two_mul_add_one_done) #[en, ea, eb, en', eresult, H, hn, h]
      ⟨result, proof⟩

end FibSimproc

section
cbv_simproc cbv_eval evalFib (Nat.fib _) := fun e => do
  let_expr Nat.fib arg := e | return .rfl
  let some n := Sym.getNatValue? arg | return .rfl
  let ⟨result, proof⟩ := FibSimproc.proveNatFib n
  let resultExpr ← Sym.share (mkNatLit result)
  return .step resultExpr proof (done := true)

theorem fib_0 : Nat.fib 0 = 0 := by cbv
theorem fib_1 : Nat.fib 1 = 1 := by cbv
theorem fib_2 : Nat.fib 2 = 1 := by cbv
theorem fib_10 : Nat.fib 10 = 55 := by cbv
theorem fib_20 : Nat.fib 20 = 6765 := by cbv
theorem fib_30 : Nat.fib 30 = 832040 := by cbv
theorem fib_50 : Nat.fib 50 = 12586269025 := by cbv
theorem fib_100 : Nat.fib 100 = 354224848179261915075 := by cbv

theorem fib_sum : Nat.fib 10 + Nat.fib 20 = 6820 := by cbv

/--
info: theorem fib_sum : Nat.fib 10 + Nat.fib 20 = 6820 :=
of_eq_true
  (Eq.trans
    (congrFun'
      (congrArg Eq
        (Eq.trans
          (congr
            (congrArg HAdd.hAdd
              (isFibAux_two_mul_done
                (isFibAux_two_mul_add_one (isFibAux_two_mul isFibAux_one (Eq.refl 2) (Eq.refl 1) (Eq.refl 2))
                  (Eq.refl 5) (Eq.refl 5) (Eq.refl 8))
                (Eq.refl 10) (Eq.refl 55)))
            (isFibAux_two_mul_done
              (isFibAux_two_mul
                (isFibAux_two_mul_add_one (isFibAux_two_mul isFibAux_one (Eq.refl 2) (Eq.refl 1) (Eq.refl 2))
                  (Eq.refl 5) (Eq.refl 5) (Eq.refl 8))
                (Eq.refl 10) (Eq.refl 55) (Eq.refl 89))
              (Eq.refl 20) (Eq.refl 6765)))
          (Eq.refl 6820)))
      6820)
    (eq_self 6820))
-/
#guard_msgs in
#print fib_sum

end
