(* Formalizing and Proving Theorems in Coq --- Homework 3, part c.
   curated by Tobias Kappé, May 2022.

   There are no exercises in this file; it just serves to demonstrate some
   applications of the lia tactic. *)

Require Import Coq.micromega.Lia.

Lemma add_zero_right (n: nat):
  n + 0 = n
.
Proof.
  lia.
Qed.

Lemma add_zero_left (n: nat):
  0 + n = n
.
Proof.
  lia.
Qed.

Lemma add_succ (n m: nat):
  S n + m = n + S m
.
Proof.
  lia.
Qed.

Lemma add_commute (n m: nat):
  n + m = m + n
.
Proof.
  lia.
Qed.

Lemma add_associate (n m k: nat):
  (n + m) + k = n + (m + k)
.
Proof.
  lia.
Qed.

Lemma add_associate_more (n m k l: nat):
  ((n + m) + k) + l = n + (m + (k + l))
.
Proof.
  lia.
Qed.

Lemma add_exchange (p q r s: nat):
  (p + q) + (r + s) =
  (p + r) + (q + s)
.
Proof.
  lia.
Qed.

Lemma mul_zero (n: nat):
  0 * n = 0
.
Proof.
  lia.
Qed.

Lemma mul_succ (n m: nat):
  S n * m = m + n * m
.
Proof.
  lia.
Qed.

Lemma mul_commute (n m: nat):
  n * m = m * n
.
Proof.
  lia.
Qed.

Fixpoint sum_odd (n: nat): nat :=
  match n with
  | 0 => 0
  | S n' =>
    S (sum_odd n' + 2 * n')
  end
.

Lemma sum_odd_is_square (n: nat):
  sum_odd n = n * n
.
Proof.
  induction n; simpl.
  - auto.
  - lia.
Qed.

Lemma mul_distribute_right (n m k: nat):
  (n + m) * k = n * k + m * k
.
Proof.
  lia.
Qed.

Fixpoint accumulate (n: nat): nat :=
  match n with
  | 0 => 0
  | S n' => n + (accumulate n')
  end
.

Lemma gauss_correct (n: nat):
  2 * (accumulate n) = n * (S n)
.
Proof.
  induction n; simpl.
  - auto.
  - lia.
Qed.

Fixpoint fib (n: nat): nat :=
  match n with
  | 0 => 0
  | S n' =>
    match n' with
    | 0 => 1
    | S n'' =>
      fib n' + fib n''
    end
  end
.

Lemma fib_plus_two (n: nat):
  fib (S (S n)) = fib n + fib (S n)
.
Proof.
  simpl.
  lia.
Qed.

Lemma fib_multiply (n m: nat):
  fib (n + S m) =
  fib (S n) * fib (S m) + fib n * fib m
.
Proof.
  revert n.
  induction m; intros.
  - simpl fib at 3.
    simpl fib at 4.
    Search (_ * 1).
    rewrite PeanoNat.Nat.mul_1_r.
    Search (_ * 0).
    rewrite PeanoNat.Nat.mul_0_r.
    Search (_ + 0).
    rewrite PeanoNat.Nat.add_0_r.
    f_equal.
    lia.
  - rewrite <- add_succ, IHm.
    repeat rewrite fib_plus_two.
    lia.
Qed.

Lemma less_than_equal_trans (n m k: nat):
  n <= m -> m <= k -> n <= k
.
Proof.
  lia.
Qed.

Lemma less_than_equal_shift (n m: nat):
  n <= m -> S n <= S m
.
Proof.
  lia.
Qed.

Lemma less_than_equal_zero (n: nat):
  n <= 0 -> n = 0
.
Proof.
  lia.
Qed.

Lemma less_than_equal_succ (n m: nat):
  n <= S m -> n = S m \/ n <= m
.
Proof.
  lia.
Qed.

Lemma less_than_equal_mono_add_left (n m k: nat):
  n <= m -> n + k <= m + k
.
Proof.
  lia.
Qed.

Lemma less_than_equal_mono_add (n m k l: nat):
  n <= m ->
  k <= l ->
  n + k <= m + l
.
Proof.
  lia.
Qed.

Lemma less_than_equal_mono_mul (n m k: nat):
  n <= m -> n * k <= m * k
.
Proof.
  Fail lia.
Admitted.

Fixpoint lucas (n: nat): nat :=
  match n with
  | 0 => 2
  | S n' =>
    match n' with
    | 0 => 1
    | S n'' => (lucas n') + (lucas n'')
    end
  end
.

Lemma lucas_plus_two (n: nat):
  lucas (S (S n)) = lucas n + lucas (S n)
.
Proof.
  simpl.
  lia.
Qed.

Definition holds_up_to (P: nat -> Prop) (n: nat) :=
  forall (m: nat), m <= n -> P m
.

Lemma strong_induction
  (P: nat -> Prop)
  (HBase: P 0)
  (HStep: forall (n: nat), holds_up_to P n -> P (S n))
  (n: nat)
:
  P n
.
Proof.
  enough (forall m, m <= n -> P m); auto.
  induction n; intros.
  - now rewrite less_than_equal_zero.
  - destruct (less_than_equal_succ m n); auto.
    subst.
    apply HStep.
    unfold holds_up_to.
    apply IHn.
Qed.

Lemma pair_induction
  (P: nat -> Prop)
  (HBaseZero: P 0)
  (HBaseOne: P 1)
  (HStep: forall (n: nat), P n -> P (S n) -> P (S (S n)))
  (n: nat)
:
  P n
.
Proof.
  induction n using strong_induction; auto.
  unfold holds_up_to in H.
  destruct n; auto.
Qed.

Lemma lucas_vs_fibonnaci (n: nat):
  lucas (S n) = (fib n) + (fib (S (S n)))
.
Proof.
  induction n using pair_induction; auto.
  rewrite lucas_plus_two.
  rewrite IHn, IHn0.
  repeat rewrite fib_plus_two.
  lia.
Qed.
