import Imports.AllImports

-- start_def problem_details
/--
function_signature: "def find_fibonacci(n: int) -> int"
docstring: |
    Given an integer n, your task is to find the nth Fibonacci number.
    The Fibonacci sequence is defined as follows:
    - F(0) = 1
    - F(1) = 1
    - F(n) = F(n-1) + F(n-2) for n > 1
    Note: This is also same as problem 55 (so we don't use it in few-shot prompts)
test_cases:
  - input:
      0
    expected_output: 1
  - input:
      1
    expected_output: 1
  - input:
      2
    expected_output: 2
  - input:
      3
    expected_output: 3
  - input:
      4
    expected_output: 5
  - input:
      5
    expected_output: 8
-/
-- end_def problem_details

-- start_def problem_spec
def problem_spec
-- function signature
(impl: Nat → Nat)
-- inputs
(n: Nat) :=
-- spec
let spec (result: Nat) :=
fibonacci_non_computable n result
-- program terminates
∃ result, impl n = result ∧
-- return value satisfies spec
spec result
-- end_def problem_spec

-- start_def generated_spec
def generated_spec
-- function signature
(impl: Nat → Nat)
-- inputs
(n: Nat) : Prop :=
-- spec
let spec (result: Nat) :=
match n with
| 0 => result = 0
| 1 => result = 1
| n' + 2 => result = impl n' + impl (n' + 1)
-- return value satisfies spec
∃ result, impl n = result ∧
spec result
-- end_def generated_spec

-- start_def iso_helper_lemmas
lemma fib0_unique (n : ℕ) (h : fibonacci_non_computable 0 n) : n = 0 :=
-- end_def iso_helper_lemmas
-- start_def iso_helper_lemmas_proof
by
cases h
rfl
-- end_def iso_helper_lemmas_proof

-- start_def iso_helper_lemmas
lemma fib1_unique (n : ℕ) (h : fibonacci_non_computable 1 n) : n = 1 :=
-- end_def iso_helper_lemmas
-- start_def iso_helper_lemmas_proof
by
cases h
rfl
-- end_def iso_helper_lemmas_proof

-- start_def iso_helper_lemmas
lemma fib_rec_unique (n m : ℕ):
  fibonacci_non_computable (n + 2) m ↔
  ∃ f₁ f₂, fibonacci_non_computable n f₁ ∧
  fibonacci_non_computable (n + 1) f₂ ∧
  m = f₁ + f₂ :=
-- end_def iso_helper_lemmas
-- start_def iso_helper_lemmas_proof
by
apply Iff.intro
intro h
cases h
rename_i f₁ f₂ h₁ h₂
use f₁
use f₂
intro h
obtain ⟨f₁, f₂, h₁, h₂, h_eq⟩ := h
rw [h_eq]
exact fibonacci_non_computable.step _ _ _ h₁ h₂
-- end_def iso_helper_lemmas_proof

-- start_def iso_helper_lemmas
lemma fib_inversion (n f₁ f₂ : ℕ)
(h : fibonacci_non_computable n f₁) (h' : fibonacci_non_computable n f₂) :
  f₁ = f₂ :=
-- end_def iso_helper_lemmas
-- start_def iso_helper_lemmas_proof
by
revert f₁ f₂
induction' n using Nat.strong_induction_on with n' ih
intro f₁ f₂
by_cases h_n'_lt_1: n' < 2
intro h h'
-- if n' < 1 then
have h_n'_eq_0: n' = 0 ∨ n' = 1 := by
  interval_cases n'
  all_goals simp
cases h_n'_eq_0
rename_i h_n'_eq_0
simp [h_n'_eq_0] at *
cases h
cases h'
rfl
rename_i h_n'_eq_1
simp [h_n'_eq_1] at *
clear h_n'_eq_1
cases h
cases h'
rfl
set n'' := n' - 2
have h_n''_eq_n_plus_2: n' = n'' + 2 := by
  rw [Nat.sub_add_cancel]
  linarith
simp [h_n''_eq_n_plus_2] at *
clear h_n''_eq_n_plus_2 h_n'_lt_1
intro h h'
rw [fib_rec_unique] at h
obtain ⟨f₁', f₂', h₁, h₂, h_eq⟩ := h
rw [fib_rec_unique] at h'
obtain ⟨f₁'', f₂'', h₁', h₂', h_eq'⟩ := h'
have h_fib_inv_n'' := ih n'' (by linarith) f₁'' f₁' h₁' h₁
have h_fib_inv_n''_plus_1 := ih (n'' + 1) (by linarith) f₂'' f₂' h₂' h₂
linarith
-- end_def iso_helper_lemmas_proof

-- start_def iso_helper_lemmas
lemma fib_rec_unique_seq (n : ℕ)
(f : Nat → Nat)
(h_f_0: f 0 = 0)
(h_f_1: f 1 = 1)
(h_f_step: ∀ n, f (n + 2) = f n + f (n + 1))
: fibonacci_non_computable n (f n) :=
-- end_def iso_helper_lemmas
-- start_def iso_helper_lemmas_proof
by
induction' n using Nat.strong_induction_on with n' ih
by_cases h_n'_lt_1: n' < 2
-- if n' < 1 then
have h_n'_eq_0: n' = 0 ∨ n' = 1 := by
  interval_cases n'
  all_goals simp
cases h_n'_eq_0
rename_i h_n'_eq_0
simp [h_n'_eq_0, h_f_0, fibonacci_non_computable.base0]
rename_i h_n'_eq_1
simp [h_n'_eq_1, h_f_1, fibonacci_non_computable.base1]
set n'' := n' - 2
have h_n''_eq_n_plus_2: n' = n'' + 2 := by
  rw [Nat.sub_add_cancel]
  linarith
have h_n''_lt_n': n'' < n' := by
  linarith
have h_fib_n'':= h_f_step n''
have h_fib_n''_non_computable := ih n'' h_n''_lt_n'
have h_fib_n''_plus_1_non_computable := ih (n'' + 1) (by linarith)
have h_fib_n''_plus_2_non_computable :=
  fibonacci_non_computable.step _ _ _ h_fib_n''_non_computable h_fib_n''_plus_1_non_computable
rw [←h_fib_n''] at h_fib_n''_plus_2_non_computable
rw [←h_n''_eq_n_plus_2] at h_fib_n''_plus_2_non_computable
assumption
-- end_def iso_helper_lemmas_proof

-- start_def spec_isomorphism
theorem spec_isomorphism:
∀ impl,
(∀ n, problem_spec impl n) ↔
(∀ n, generated_spec impl n) :=
-- end_def spec_isomorphism
-- start_def spec_isomorphism_proof
by
unfold problem_spec
unfold generated_spec
intro impl
apply Iff.intro
intro h_prob_spec
intro n
have h_impl_n_eq_0 := h_prob_spec n
simp at h_impl_n_eq_0
simp
by_cases h_n_lt_1: n < 2
-- if n = 0 then
have h_n_eq_0_or_1: n = 0 ∨ n = 1 := by
  interval_cases n
  all_goals simp
cases h_n_eq_0_or_1
rename_i h_n_eq_0
simp [h_n_eq_0] at *
apply fib0_unique
assumption
rename_i h_n_eq_1
simp [h_n_eq_1] at *
apply fib1_unique
assumption
set n' := n - 2
have h_n'_eq_n_plus_2: n = n' + 2 := by
  rw [Nat.sub_add_cancel]
  linarith
simp [h_n'_eq_n_plus_2] at *
clear h_n'_eq_n_plus_2 h_n_lt_1
have h_fib_n' := h_prob_spec n'
have h_fib_n'_1 := h_prob_spec (n' + 1)
simp at h_fib_n'
simp at h_fib_n'_1
have h_fib_combination := fibonacci_non_computable.step _ _ _ h_fib_n' h_fib_n'_1
have h_fib_inv := fib_inversion (n' + 2) _ _ h_fib_combination h_impl_n_eq_0
linarith
intro h_generated_spec
simp
intro n
have h_impl_n': ∀ n, impl (n + 2) = impl n + impl (n + 1) := by
  intro n
  have h_impl_temp := h_generated_spec (n + 2)
  simp at h_impl_temp
  assumption
have h_impl_0 := h_generated_spec 0
have h_impl_1 := h_generated_spec 1
simp at h_impl_0
simp at h_impl_1
simp [(fib_rec_unique_seq n impl h_impl_0 h_impl_1 h_impl_n')]
--end_def spec_isomorphism_proof

-- start_def implementation_signature
def implementation (n: Nat) : Nat :=
-- end_def implementation_signature
-- start_def implementation
match n with
| 0 => 0
| 1 => 1
| n' + 2 => implementation n' + implementation (n' + 1)
-- end_def implementation

-- Uncomment the following test cases after implementing the function
-- start_def test_cases
#test implementation 0 = 0
#test implementation 1 = 1
#test implementation 2 = 1
#test implementation 3 = 2
#test implementation 4 = 3
#test implementation 5 = 5
#test implementation 6 = 8
#test implementation 7 = 13
-- end_def test_cases

-- start_def correctness_helper_lemmas
lemma fib_comp_to_non_comp (n : ℕ)
(f : Nat → Nat)
(h_f_0: f 0 = 0)
(h_f_1: f 1 = 1)
(h_f_step: ∀ n, f (n + 2) = f n + f (n + 1))
: fibonacci_non_computable n (f n) :=
-- end_def correctness_helper_lemmas
-- start_def correctness_helper_lemmas_proof
by
induction' n using Nat.strong_induction_on with n' ih
by_cases h_n'_lt_1: n' < 2
-- if n' < 2 then
have h_n'_eq_0: n' = 0 ∨ n' = 1 := by
  interval_cases n'
  all_goals simp
cases h_n'_eq_0
rename_i h_n'_eq_0
simp [h_n'_eq_0, h_f_0, fibonacci_non_computable.base0]
rename_i h_n'_eq_1
simp [h_n'_eq_1, h_f_1, fibonacci_non_computable.base1]
set n'' := n' - 2
have h_n''_eq_n_plus_2: n' = n'' + 2 := by
  rw [Nat.sub_add_cancel]
  linarith
have h_n''_lt_n': n'' < n' := by
  linarith
have h_fib_n'':= h_f_step n''
have h_fib_n''_non_computable := ih n'' h_n''_lt_n'
have h_fib_n''_plus_1_non_computable := ih (n'' + 1) (by linarith)
have h_fib_n''_plus_2_non_computable :=
  fibonacci_non_computable.step _ _ _ h_fib_n''_non_computable h_fib_n''_plus_1_non_computable
rw [←h_fib_n''] at h_fib_n''_plus_2_non_computable
rw [←h_n''_eq_n_plus_2] at h_fib_n''_plus_2_non_computable
assumption
-- end_def correctness_helper_lemmas_proof

-- start_def correctness_definition
theorem correctness
(n: Nat)
: problem_spec implementation n
:=
-- end_def correctness_definition
-- start_def correctness_proof
by
unfold problem_spec
let result := implementation n
use result
simp [result]
have h_impl_n : ∀ n', implementation (n' + 2) = implementation n' + implementation (n' + 1) :=
by
  intro n'
  rw [implementation]
have h_impl_0 : implementation 0 = 0 := by
  rw [implementation]
have h_impl_1 : implementation 1 = 1 := by
  rw [implementation]
apply fib_comp_to_non_comp
all_goals assumption
-- end_def correctness_proof
