import Mathlib
import PnP2023.Lec_02_03.InductiveTypes

/-!
Vectors, i.e., lists of a fixed length, can be defined in (at least) two ways. One way is as an indexed inductive type `Vec`, as we saw in lecture and is in the file `InductiveTypes.lean`.

A different definition is as a subtype `Vector` of lists consisting of those of a fixed length. This is the definition used in `mathlib` and is recalled below.

```lean
/-- `Vector α n` is the type of lists of length `n` with elements of type `α`. -/
def Vector (α : Type u) (n : ℕ) :=
  { l : List α // l.length = n }
```

In this lab, you will relate the two definitions by constructing functions that convert between the two definitions and prove that these functions are inverses of each other.
-/
universe u

/-- Convert a `Vector` to a `Vec` -/
def Vec.ofVector {α : Type u}: (n : ℕ) →  Vector α n → Vec α n
| _, _ => sorry

/-- Convert a `Vec` to a `Vector` -/
def Vec.toVector {α : Type u}: (n : ℕ) →  Vec α n → Vector α n
| _, _ => sorry

/-- Mapping a `Vec` to a `Vector` and back gives the original `Vec` -/
theorem Vec.ofVector.toVector {α : Type u} (n : ℕ) (v : Vec α n) :
  Vec.ofVector n (Vec.toVector n v) = v := sorry

/-- Mapping a `Vector` to a `Vec` and back gives the original `Vector` -/
theorem Vec.toVector.ofVector {α : Type u} (n : ℕ) (v : Vector α n) :
  Vec.toVector n (Vec.ofVector n v) = v := sorry


/-
This exercise will guide you through a proof that there are
infinitely many primes.
NOTE: You will need to install mathlib for this.

A good way to confirm if mathlib is installed is to see if
the following imports work.

This tells Lean to import facts about prime numbers.
-/
import tactic
import data.nat.prime

open nat

/-
We shall break up the proof into a bunch of lemmas
and use these lemmas in the final proof.

The first lemma `not_dvd_succ` says that if a ≠ 1 divides b,
then it does not divide b+1.

Lemmas/Hints which may be useful for this exercise:
  * nat.dvd_add_right
  * nat.dvd_one
  * Try to use the rewrite tactic.

(Of course, this is not the only way to prove this exercise.
Look at the file data.nat.prime to see if you can find other
exercises which may help you.

You can browse through
https://leanprover-community.github.io/mathlib_docs/data/nat/prime.html
for more facts.)
-/

lemma not_dvd_succ {a b : ℕ} (ha : a ≠ 1) (hab : a ∣ b) :
 ¬ (a ∣ b + 1) :=
begin
  sorry,
end

/-
 Before we move on to the main theorem,
 it is good to understand how the `linarith` tactic works.

`linarith` is a high-powered tactic which can be used to
 prove linear (in)equalities.

 Read : https://leanprover-community.github.io/mathlib_docs/tactics.html#linarith
-/

example (x : ℕ) (hx₁ : x > 15) (hx₂ : x ≤ 20) : (x < 25) :=
begin
  linarith,
end

example (x y z : ℕ) (h₁ : x > y + 1) (h₂ : y > 2*z + 2): (x > z) :=
begin
  linarith,
end

/- Shorter proofs can simply use the `by` keyword. -/
example (x y z : ℚ) (h1 : 2*x  < 3*y) (h2 : -4*x + 2*z < 0)
        (h3 : 12*y - 4* z < 0)  : false := by linarith

example(a b d : ℕ) (h: 2*a ≤ 3*b)(h': 1≤ a)(h'': d = 2):
d + a ≤ 5*b := by linarith

/-
Let us try to recall how the proof works:

The factorial n! of a positive integer n is divisible by
very integer from 2 to n, as it is the product of all of them.

Hence, n! + 1 is not divisible by any of the integers from 2
to n, inclusive (it gives a remainder of 1 when divided by
each).

Hence n! + 1 is either prime or divisible by a prime larger
than n.
In either case, for every positive integer n, there is at
least one prime bigger than n.
The conclusion is that the number of primes is infinite.

We break up the above sketch into little steps in the proof
below.

-/

theorem inf_many_primes : ∀ n : ℕ, ∃ p, p ≥ n ∧ prime p :=
begin
  -- assume an arbitrary n
  assume n,
  -- define N = n! + 1
  let N := n.factorial + 1,
  /- Prove first that N ≥ 2.
     The tactic `linarith` and lemmas `factorial_le`
     and `zero_le` may be helpful.-/
  have Nge2 : N ≥ 2,
  {
    sorry,
  },
  /-
  Next we prove that the numbers from 2 to n divide n!.
   The tactic `linarith` and lemmas `dvd_factorial`
   may be helpful.
  -/
  have dvd₁ : ∀ k n : ℕ, 2 ≤ k ∧ k ≤ n → k ∣ n.factorial,
  {
    sorry,
  },
  /-
  Next we prove that the numbers from 2 to n do NOT divide
  n! + 1. Use the previous lemmas we proved: `dvd₁` and
  `not_dvd_succ`.
  -/
  have dvd₂ : ∀ k n : ℕ, 2 ≤ k ∧ k ≤ n → ¬ (k ∣ n.factorial + 1),
  {
    sorry,
  },
  -- clear some hypothesis to avoid clutter
  clear dvd₁,
  /-
  The number N := n! + 1 is either prime or not.
  Note for nerds: This tactic uses *classical* logic.
  It is good to keep this in mind even if you don't care
  about it.
  -/
  by_cases (prime N),
  {
    /-
    in the case where N is prime, we can prove the main goal
    easily.
    Try using `self_le_factorial` and `linarith`.
    -/
    use N,
    sorry,
  },
  /-
  In the case where N is not prime, we first add the fact
  that there is a prime which divides it.
  This is the fact `exists_prime_and_dvd`.
  -/
  have HN := exists_prime_and_dvd Nge2,
  /- Here is another tactic which makes breaking up
    hypotheses easier. -/
  rcases HN with ⟨p,⟨Hp₁,Hp₂⟩⟩,
  -- Use p now to finish the proof.
  use p,
  sorry,
end

/-

Final words of advice:
* Keep the tactic cheatsheet open next to you if you are lost:
https://leanprover-community.github.io//img/lean-tactics.pdf
* Search for lemmas in mathlib.
* Always try to get to the goal on paper first. Once that is
  clear, then move onto formalizing.
-/


/-
Copyright (c) 2021 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics

/-!

# Sets in Lean, example sheet 3 : not in (`∉`)

The definition in Lean of `x ∉ A` is `¬ (x ∈ A)`. In other words,
`x ∉ A`, `¬ (x ∈ A)` and `(x ∈ A) → false` are all equal *by definition*
in Lean. This means that they are all interchangeable, and you can
change between them using the `change` tactic, or you can just keep this
in mind. For example, if you have a hypothesis `h : x ∉ A` and your goal
is `false`, then `apply h` will work and will change the goal to `x ∈ A`.

## Tactics

You can do all these levels just with `intros`, `apply` and `exact`!

-/

open set

variables
  (X : Type) -- Everything will be a subset of `X`
  (A B C D E : set X) -- A,B,C,D,E are subsets of `X`
  (x y z : X) -- x,y,z are elements of `X` or, more precisely, terms of type `X`

example : x ∉ A → (x ∈ A → false) :=
begin
  sorry
end

example : x ∈ A → (x ∉ A → false) :=
begin
  sorry
end

example : (∀ t, t ∈ A → t ∈ B) → x ∉ B → x ∉ A :=
begin
  sorry
end

example : x ∉ (∅ : set X) :=
begin
  sorry
end


-- This import gives us a working copy of the real numbers ℝ,
-- and functions such as abs : ℝ → ℝ
import data.real.basic

-- This next import gives us several tactics of use to mathematicians:
-- (1) norm_num [to prove basic facts about reals like 2+2 = 4]
-- (2) ring [to prove basic algebra identities like (a+b)^2 = a^2+2ab+b^2]
-- (3) linarith [to prove basic inequalities like x > 0 -> x/2 > 0]
import tactic.linarith

-- These lines switch Lean into "maths proof mode" -- don't worry about them.
-- Basically they tell Lean to use the axiom of choice and the
-- law of the excluded middle, two standard maths facts.
noncomputable theory
local attribute [instance, priority 0] classical.prop_decidable

-- the maths starts here.

-- We introduce the usual mathematical notation for absolute value
local notation `|` x `|` := abs x

theorem Q1a (x y : ℝ) : | x + y | ≤ | x | + | y | :=
begin
  -- Lean's definition of abs is abs x = max (x, -x)
  -- [or max x (-x), as the computer scientists would write it]
  unfold abs,
  -- lean's definition of max a b is "if a<=b then b else a"
  unfold max,
  -- We now have a complicated statement with three "if"s in.
  split_ifs,
  -- We now have 2^3=8 goals corresponding to all the possibilities
  -- x>=0 or x<0, y>=0 or y<0, (x+y)>=0 or (x+y)<0.
  repeat {linarith},
  -- all of them are easily solvable using the linarith tactic.
end

-- We can solve the remaining parts using part (a).
theorem Q1b (x y : ℝ) : |x + y| ≥ |x| - |y| :=
begin
  -- Apply Q1a to x+y and -y, then follow your nose.
  have h := Q1a (x + y) (-y),
  simp at h,
  linarith,
end

theorem Q1c (x y : ℝ) : |x + y| ≥ |y| - |x| :=
begin
  -- Apply Q1a to x+y and -x, then follow your nose.
  have h := Q1a (x + y) (-x),
  simp at h,
  linarith,
end

theorem Q1d (x y : ℝ) : |x - y| ≥ | |x| - |y| | :=
begin
  -- Lean prefers ≤ to ≥
  show _ ≤ _,
  -- for this one we need to apply the result that |X| ≤ B ↔ -B ≤ X and X ≤ B
  rw abs_le,
  -- Now we have two goals:
  -- first -|x - y| ≤ |x| - |y|
  -- and second |x| - |y| ≤ |x - y|.
  -- So we need to split.
  split,
  { -- -|x - y| ≤ |x| - |y|
    have h := Q1a (x - y) (-x),
    simp at *,
    linarith },
  { -- |x| - |y| ≤ |x - y|
    have h := Q1a (x - y) y,
    simp at *,
    linarith}
end

theorem Q1e (x y : ℝ) : |x| ≤ |y| + |x - y| :=
begin
  have h := Q1a y (x - y),
  simp * at *,
end

theorem Q1f (x y : ℝ) : |x| ≥ |y| - |x - y| :=
begin
  have h := Q1a (x - y) (-x),
  simp * at *,
end

theorem Q1g (x y z : ℝ) : |x - y| ≤ |x - z| + |y - z| :=
begin
  have h := Q1a (x - z) (z - y),
  -- Lean needs more hints with this one.
  -- First let's change that y - z into z - y,
  rw ←abs_neg (y - z),
  -- now use automation
  simp * at *,
end


import data.nat.gcd
import algebra.big_operators
import data.int.gcd
import algebra.euclidean_domain
import data.zmod.basic

def divides (d n: ℤ):=  ∃l:ℤ, n = d*l
def is_congruent_mod_n (a b n: ℤ) := divides n (b-a)
def relatively_prime(a b:ℤ) :=  nat.coprime (int.nat_abs a) (int.nat_abs b)

theorem relatively_prime_sum_to_one (a b : ℤ) (h : relatively_prime a b) :
∃ m n: ℤ, m* a + n * b = 1 :=
begin
  existsi [int.sign a * nat.gcd_a (int.nat_abs a) (int.nat_abs b),
           int.sign b * nat.gcd_b (int.nat_abs a) (int.nat_abs b)],
  rw [mul_right_comm, mul_comm _ a, int.mul_sign,
      mul_right_comm, mul_comm _ b, int.mul_sign,
      ← nat.gcd_eq_gcd_ab,
      show nat.gcd (int.nat_abs a) (int.nat_abs b) = 1, from h,
      int.coe_nat_one],
end

theorem divides_in_product (a b c: ℤ): a*b = c ↔ divides a c:=
  begin
    split,
    {
      intros,
      rw divides,
      cases a_1 with ell,
      exact Exists.intro b rfl,
    },
    {
      intros,
      cases a_1 with ell,
      by library_search,
      --exact Exists.intro ell (eq.symm a_1_h),
    },
  end

--Generalized Euclid Lemma --
theorem divides_product_coprime_and_not_coprime (a b n:ℤ) (c: divides n (a*b))
(d: relatively_prime a n):
divides n b:=
begin
  -- cases breaks down existential qualifiers
  cases c with l k,
  cases relatively_prime_sum_to_one a n d with m h,
  cases h with q manb_eq_one,

  -- multiplies both sides by b
  have := congr_arg (has_mul.mul b) manb_eq_one,
  rw [mul_one] at this,
  -- this : b * (m * a + q * n) = b

  -- order-changing
  rw [mul_add] at this,
  rw [mul_comm] at this,
  rw [mul_assoc] at this,
  rw k at this,
  rw [mul_comm] at this,
  rw [mul_assoc] at this,
  rw [add_comm] at this,
  rw [mul_comm] at this,
  rw [mul_assoc] at this,
  rw [mul_left_comm] at this,
  rw <- mul_add at this,

  rw divides_in_product n (q*b+l*m) b at this,
  apply this,

end

theorem cancel_out_mod_n (a b c n: ℤ) (p: is_congruent_mod_n (a*b) (a*c) n)
(q: relatively_prime a n): is_congruent_mod_n b c n :=

begin
  cases p with j k,
  rw <-mul_sub at k,
  rw eq_comm at k,
  rw divides_in_product n j (a*(c-b)) at k,
  apply divides_product_coprime_and_not_coprime a (c-b) n,
  {
    apply k,
  },
  {
    apply q,
  },

end


section propositional

variables P Q R : Prop

------------------------------------------------
-- Proposições de dupla negaço:
------------------------------------------------

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro p,
  intro np,
  contradiction,
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro nnp,
  by_cases p: P,
    --case P
    exact p,
    --case ¬P
    have ct := nnp p,
    contradiction,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  have fst := doubleneg_elim P,
  have snd := doubleneg_intro P,
  split,
    --part ¬¬P → P
    exact fst,
    --part P → ¬¬P
    exact snd,
end

------------------------------------------------
-- Comutatividade dos ∨,∧:
------------------------------------------------

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro disj,
  cases disj,
    -- case P
    right,
      exact disj,
    -- case Q
    left,
      exact disj,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro conj,
  cases conj with p q,
  split,
    -- part Q
    exact q,
    -- part P
    exact p,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos →,∨:
------------------------------------------------

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro disj,
  intro p,
  cases disj,
    --case ¬P
    have contra := disj p,
    contradiction,
    --case Q
    exact disj,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro disj,
  intro np,
  cases disj,
    --case P
    have contra := np disj,
    contradiction,
    --case Q
    exact disj,
end

------------------------------------------------
-- Proposições de contraposição:
------------------------------------------------

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro impl,
  intro nq,
  intro p,
  have q := impl p,
  have contra := nq q,
  contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro impl,
  intro p,
  by_cases q: Q,
    --case Q
    exact q,
    --case ¬Q
    have np := impl q,
    have contra := np p,
    contradiction,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  have fst := impl_as_contrapositive P Q,
  have snd := impl_as_contrapositive_converse P Q,
  split,
    --part (P → Q) → (¬Q → ¬P)
    exact fst,
    --part (¬Q → ¬P) → (P → Q)
    exact snd,
end

------------------------------------------------
-- A irrefutabilidade do LEM:
------------------------------------------------

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro n_lem,
  have lem: P ∨ ¬P,
    right,
    intro p,
    have disj: P ∨ ¬P,
      left,
      exact p,
    have ct := n_lem disj,
    contradiction,
  have ct := n_lem lem,
  contradiction,
end

------------------------------------------------
-- A lei de Peirce
------------------------------------------------

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro pierce,
  intro np,
  have impl: P → Q,
    intro p,
    contradiction,
  have p := pierce impl,
  have ct := np p,
  contradiction,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∨,∧:
------------------------------------------------

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro disj,
  intro n_conj,
  cases n_conj,
  cases disj,
    --case P
    have contra := n_conj_left disj,
    contradiction,
    --case Q
    have contra := n_conj_right disj,
    contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro conj,
  intro neg_disj,
  cases conj,
  cases neg_disj,
    --case ¬P
    have ct := neg_disj conj_left,
    contradiction,
    --case ¬Q
    have ct := neg_disj conj_right,
    contradiction,
end

------------------------------------------------
-- As leis de De Morgan para ∨,∧:
------------------------------------------------

--Adicionado Pelo Ian--
theorem disj_impl :
  (∀ p q: Prop, p → p ∨ q) :=
begin
  intro p,
  intro q,
  intro p_impl,
  left,
  exact p_impl,
end
----------------

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro neg_disj,
  split,
    --part ¬P
    intro p,
    have impl := disj_impl P Q,
    have disj := impl p,
    have ct := neg_disj disj,
    contradiction,
    --part ¬Q
    intro q,
    have impl := disj_impl Q P,
    have wr_disj := impl q,
    have disj_cm := disj_comm Q P,
    have rght_disj := disj_cm wr_disj,
    have ct := neg_disj rght_disj,
    contradiction,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro conj_negs,
  intro disj,
  cases conj_negs with np nq,
  cases disj with p q,
    --case P
    have ct := np p,
    contradiction,
    --case Q
    have ct := nq q,
    contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_conj,
  by_cases P,
    --case P
    left,
    intro q,
    have conj: P∧Q,
      split,
      --part P
      exact h,
      --part Q
      exact q,
    have ct := n_conj conj,
    contradiction,

    --case ¬P
    right,
    exact h,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro disj_negs,
  intro conj,
  cases conj with p q,
  cases disj_negs with nq np,
    --case ¬Q
    have ct := nq q,
    contradiction,
    --case ¬P
    have ct := np p,
    contradiction,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  have dmg_conj := demorgan_conj P Q,
  have dmg_conj_cnv := demorgan_conj_converse P Q,
  split,
    --part ¬(P∧Q) → (¬Q ∨ ¬P)
    exact dmg_conj,
    --part (¬Q ∨ ¬P) → ¬(P ∧ Q)
    exact dmg_conj_cnv,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  have dmg_disj := demorgan_disj P Q,
  have dmg_disj_con := demorgan_disj_converse P Q,
  split,
    --part ¬(P ∨ Q) → (¬P ∧ ¬Q)
    exact dmg_disj,
    --part (¬P ∧ ¬Q) → ¬(P ∨ Q)
    exact dmg_disj_con,
end

------------------------------------------------
-- Proposições de distributividade dos ∨,∧:
------------------------------------------------

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro conj_disj,
  cases conj_disj with p disj,
  cases disj with q r,
    --case Q
    left,
    split,
      --part P
      exact p,
      --part Q
      exact q,
    --case R
    right,
    split,
      --part P
      exact p,
      --part R
      exact r,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro disj_conj,
  cases disj_conj with conj_pq conj_pr,
    --case P∧Q
    cases conj_pq with p q,
    split,
      --part P
      exact p,
      --part Q∨R
      left,
        exact q,
    --case P∧R
    cases conj_pr with p r,
    split,
      --part P
      exact p,
      --part Q∨R
      right,
        exact r,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro distr_disj,
  split,
    --part P∨Q
    cases distr_disj with p conj_qr,
      --case P
      left,
        exact p,
      --case Q∧R
      right,
        cases conj_qr with q r,
        exact q,

    --part P∨R
    cases distr_disj with p conj_qr,
      --case P
      left,
        exact p,
      --case Q∧R
      right,
        cases conj_qr with q r,
        exact r,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro distr_conj,
  cases distr_conj with disj_pq disj_pr,
  cases disj_pq with p q,
    --case P
    left,
      exact p,
    --case Q
    cases disj_pr with p r,
      --case P
      left,
        exact p,
      --case R
      right,
        split,
          --part Q
          exact q,
          --part R
          exact r,
end

------------------------------------------------
-- Currificação
------------------------------------------------

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro cur,
  intro p,
  intro q,
  have conj : P ∧ Q,
    split,
      --part P
      exact p,
      --part Q
      exact q,

  have r := cur conj,
  exact r,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro cur,
  intro conj,
  cases conj with p q,
  have impl := cur p,
  have r := impl q,
  exact r,
end

------------------------------------------------
-- Reflexividade da →:
------------------------------------------------

theorem impl_refl :
  P → P  :=
begin
  intro p,
  exact p,
end

------------------------------------------------
-- Weakening and contraction:
------------------------------------------------

theorem weaken_disj_right :
  P → (P∨Q)  :=
begin
  intro p,
  left,
  exact p,
end

theorem weaken_disj_left :
  Q → (P∨Q)  :=
begin
  intro q,
  right,
  exact q,
end

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro conj,
  cases conj with p q,
  exact p,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro conj,
  cases conj with p q,
  exact q,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  have left := weaken_conj_left P P,
  split,
    --part P∧P → P
    exact left,
    --part P → P∧P
    intro p,
    split,
      --part P
      exact p,
      --part P
      exact p,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
    --part P∨P → P
    intro disj,
    cases disj with p p,
      --case P
      exact p,
      --case P
      exact p,
    --part P → P∨P
    have wk_disj := weaken_disj_left P P,
    exact wk_disj,
end

end propositional

----------------------------------------------------------------

section predicate

variable U : Type
variables P Q : U -> Prop

------------------------------------------------
-- As leis de De Morgan para ∃,∀:
------------------------------------------------

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intro not_exists,
  intro x,
  intro px,
  have exist: ∃ x, P x,
    existsi x,
    exact px,
  have ct := not_exists exist,
  contradiction,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro for_all,
  intro exist,
  cases exist with x px,
  have npx := for_all x,
  have ct := npx px,
  contradiction,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  have contrapos: ¬(∃x, ¬P x) → (∀x, P x),
    intros neg_exist x,
    by_contradiction npx,
    have exist: ∃x, ¬P x,
      existsi x,
      exact npx,
    exact neg_exist exist,

  have impl := contrapositive_law (¬(∃x, ¬P x)) (∀x, P x),
  cases impl with l r,
  have contrapos_conv := l contrapos,

  intro for_all,
  have neg_neg_exist := contrapos_conv for_all,
  have db_neg := doubleneg_elim (∃x, ¬P x),
  exact db_neg neg_neg_exist,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro exist,
  intro for_all,
  cases exist with x npx,
  have px := for_all x,
  have ct := npx px,
  contradiction,
end

theorem demorgan_forall_law :
  ¬(∀x, P x) ↔ (∃x, ¬P x)  :=
begin
  split,
  --part ¬(∀x, P x) → (∃x, ¬P x)
  exact demorgan_forall U P,
  --part (∃x, ¬P x) → ¬(∀x, P x)
  exact demorgan_forall_converse U P,
end

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  have dmg_exist := demorgan_exists U P,
  have dmg_exist_cn := demorgan_exists_converse U P,
  split,
    --part ¬(∃x, P x) → (∀x, ¬P x)
    exact dmg_exist,
    --part (∀x, ¬P x) → ¬(∃x, P x)
    exact dmg_exist_cn,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∃,∀:
------------------------------------------------

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro exist,
  intro for_all,
  cases exist with x px,
  have npx := for_all x,
  have ct := npx px,
  contradiction,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro for_all,
  intro exist,
  cases exist with x npx,
  have px := for_all x,
  have ct := npx px,
  contradiction,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intro n_exist,
  intro x,

  --Disapointing magic--
  by_cases P x,
    --case P x
    exact h,

    --case ¬P x
    have exist: ∃(x: U), ¬P x,
    existsi x,
    exact h,
    have ct := n_exist exist,
    contradiction,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro neg_forall,
  by_contradiction neg_exist,
  have dmg_exists := demorgan_exists U P,
  have for_all := dmg_exists neg_exist,
  exact neg_forall for_all,
end

theorem forall_as_neg_exists_law :
  (∀x, P x) ↔ ¬(∃x, ¬P x)  :=
begin
  split,
  --part (∀x, P x) → ¬(∃x, ¬P x)
  exact forall_as_neg_exists U P,
  --part ¬(∃x, ¬P x) → (∀x, P x)
  exact forall_as_neg_exists_converse U P,
end

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
  --part (∃x, P x) → ¬(∀x, ¬P x)
  exact exists_as_neg_forall U P,
  --part ¬(∀x, ¬P x) → (∃x, P x)
  exact exists_as_neg_forall_converse U P,
end

------------------------------------------------
--  Proposições de distributividade de quantificadores:
------------------------------------------------

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro exists_conj,
  cases exists_conj with x conj_x,
  cases conj_x with px qx,
  split,
    --part (∃x, P x)
    existsi x,
    exact px,
    --part (∃x, Q x)
    existsi x,
    exact qx,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro exists_disj,
  cases exists_disj with x disj_x,
  cases disj_x with px qx,
    --case P x
    left,
    existsi x,
    exact px,
    --case Q x
    right,
    existsi x,
    exact qx,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro exists_disj,
  cases exists_disj with exists_px exists_qx,
    --case (∃x, P x)
    cases exists_px with x px,
    existsi x,
    left,
    exact px,
    --case (∃x, Q x)
    cases exists_qx with x qx,
    existsi x,
    right,
    exact qx,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro forall_conj,
  split,
    --part (∀x, P x)
    intro x,
    have conj := forall_conj x,
    exact conj.left,
    --part (∀x, Q x)
    intro x,
    have conj := forall_conj x,
    exact conj.right,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro forall_conj,
  cases forall_conj with forall_px forall_qx,
  intro x,
  split,
    --part P x
    exact forall_px x,
    --part Q x
    exact forall_qx x,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intro forall_disj,
  intro x,
  cases forall_disj with forall_px forall_qx,
    --case (∀x, P x)
    left,
    exact forall_px x,
    --case (∀x, Q x)
    right,
    exact forall_qx x,
end

/- NOT THEOREMS --------------------------------

theorem forall_disj_as_disj_forall :
  (∀x, P x ∨ Q x) → (∀x, P x) ∨ (∀x, Q x)  :=
begin
end

theorem exists_conj_as_conj_exists_converse :
  (∃x, P x) ∧ (∃x, Q x) → (∃x, P x ∧ Q x)  :=
begin
end

---------------------------------------------- -/

end predicate


import ..lectures.love03_forward_proofs_demo

/-! # LoVe Homework 4: Functional Programming

Homework must be done individually. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1 (6 points): Reverse of a List

Recall the `reverse` operation and the `reverse_append` and `reverse_reverse`
lemmas from the demo of lecture 3. -/

#check reverse
#check reverse_append
#check reverse_append₂
#check reverse_reverse

/-! 1.1 (3 points). Prove the following distributive property using the
calculational style for the induction step. -/

lemma reverse_append₃ {α : Type} :
  ∀xs ys : list α, reverse (xs ++ ys) = reverse ys ++ reverse xs :=
sorry

/-! 1.2 (3 points). Prove the induction step in the proof below using the
calculational style, following this proof sketch:

      reverse (reverse (x :: xs))
    =     { by definition of `reverse` }
      reverse (reverse xs ++ [x])
    =     { using the lemma `reverse_append` }
      reverse [x] ++ reverse (reverse xs)
    =     { by the induction hypothesis }
      reverse [x] ++ xs
    =     { by definition of `++` and `reverse` }
      [x] ++ xs
    =     { by computation }
      x :: xs -/

lemma reverse_reverse₂ {α : Type} :
  ∀xs : list α, reverse (reverse xs) = xs
| []        := by refl
| (x :: xs) :=
sorry

/-! ## Question 2 (3 points): Gauss's Summation Formula -/

-- `sum_upto f n = f 0 + f 1 + ⋯ + f n`
def sum_upto (f : ℕ → ℕ) : ℕ → ℕ
| 0       := f 0
| (m + 1) := sum_upto m + f (m + 1)

/-! 3.1 (2 point). Prove the following lemma, discovered by Carl Friedrich Gauss
as a pupil.

Hint: The `mul_add` and `add_mul` lemmas might be useful to reason about
multiplication. -/

#check mul_add
#check add_mul

lemma sum_upto_eq :
  ∀m : ℕ, 2 * sum_upto (λi, i) m = m * (m + 1) :=
sorry

/-! 3.2 (1 point). Prove the following property of `sum_upto`. -/

lemma sum_upto_mul (f g : ℕ → ℕ) :
  ∀n : ℕ, sum_upto (λi, f i + g i) n = sum_upto f n + sum_upto g n :=
sorry

/-! ## Question 3 (3 points): Structures and type classes

3.1 (1 point). Using the `structure` command, define the type ℝ³,
three-dimensional Euclidean space. (A point in ℝ³ has x, y, and z coordinates.)
-/
#check ℝ

structure r3 :=
sorry

/-!
3.2 (1 point). Define an addition function on ℝ³.
-/

def r3.add (p q : r3) : r3 :=
sorry

/-!
3.3 (1 point). Write an instance that shows ℝ³ instantiates the `has_add` type class.
-/

#print has_add

end LoVe


/-
Copyright (c) 2019 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin
-/
import data.finset.basic
import data.multiset.nat_antidiagonal

/-!
# The "antidiagonal" {(0,n), (1,n-1), ..., (n,0)} as a finset.
-/

namespace finset

namespace nat

/-- The antidiagonal of a natural number `n` is
    the finset of pairs `(i,j)` such that `i+j = n`. -/
def antidiagonal (n : ℕ) : finset (ℕ × ℕ) :=
⟨multiset.nat.antidiagonal n, multiset.nat.nodup_antidiagonal n⟩

/-- A pair (i,j) is contained in the antidiagonal of `n` if and only if `i+j=n`. -/
@[simp] lemma mem_antidiagonal {n : ℕ} {x : ℕ × ℕ} :
  x ∈ antidiagonal n ↔ x.1 + x.2 = n :=
by rw [antidiagonal, mem_def, multiset.nat.mem_antidiagonal]

/-- The cardinality of the antidiagonal of `n` is `n+1`. -/
@[simp] lemma card_antidiagonal (n : ℕ) : (antidiagonal n).card = n+1 :=
by simp [antidiagonal]

/-- The antidiagonal of `0` is the list `[(0,0)]` -/
@[simp] lemma antidiagonal_zero : antidiagonal 0 = {(0, 0)} :=
rfl

lemma antidiagonal_succ {n : ℕ} :
  antidiagonal (n + 1) = insert (0, n + 1) ((antidiagonal n).map
  (function.embedding.prod_map ⟨nat.succ, nat.succ_injective⟩ (function.embedding.refl _))) :=
begin
  apply eq_of_veq,
  rw [insert_val_of_not_mem, map_val],
  {apply multiset.nat.antidiagonal_succ},
  { intro con, rcases mem_map.1 con with ⟨⟨a,b⟩, ⟨h1, h2⟩⟩,
    simp only [prod.mk.inj_iff, function.embedding.coe_prod_map, prod.map_mk] at h2,
    apply nat.succ_ne_zero a h2.1, }
end

lemma map_swap_antidiagonal {n : ℕ} :
  (antidiagonal n).map ⟨prod.swap, prod.swap_right_inverse.injective⟩ = antidiagonal n :=
begin
  ext,
  simp only [exists_prop, mem_map, mem_antidiagonal, prod.exists],
  rw add_comm,
  split,
  { rintro ⟨b, c, ⟨rfl, rfl⟩⟩,
    simp },
  { rintro rfl,
    use [a.snd, a.fst],
    simp }
end

/-- A point in the antidiagonal is determined by its first co-ordinate. -/
lemma antidiagonal_congr {n : ℕ} {p q : ℕ × ℕ} (hp : p ∈ antidiagonal n)
  (hq : q ∈ antidiagonal n) : p = q ↔ p.fst = q.fst :=
begin
  refine ⟨congr_arg prod.fst, (λ h, prod.ext h ((add_right_inj q.fst).mp _))⟩,
  rw mem_antidiagonal at hp hq,
  rw [hq, ← h, hp],
end

end nat

end finset


import inClassNotes.propositions.propositions_as_types_the_idea

/-
It's now an easy jump to using type
families indexed by two types to represent
predicates with two arguments. For example,
here's an equality predicate, representing
an equality relation, on terms of type day.
-/

open day

inductive eq_day : day → day → Prop
| eq_day_mo : eq_day mo mo
| eq_day_tu : eq_day tu tu
| eq_day_we : eq_day we we
| eq_day_th : eq_day th th
| eq_day_fr : eq_day fr fr
| eq_day_sa : eq_day sa sa
| eq_day_su : eq_day su su

/-
eq_day = { (mo,mo), (tu,tu), ..., (su,su) }
-/

open eq_day

#check eq_day mo mo   -- inhabited thus true
#check eq_day mo tu   -- not inhabited, not true
#check eq_day fr sa   -- not inhabited, not true

lemma mos_equal : eq_day mo mo := eq_day_mo
lemma mo_tu_equal : eq_day mo tu := _ -- stuck

/-
We use predicates specify sets and relations.
Unary predicates specify sets. Binary and n-ary
predicates more generally specify relations.

For example, always_rains_on : day → Prop
*with its particular proof constructors*
represents the set { mo }, as mo is the only
proposition in the family of propositions
for which there's a proof, i.e., that is
inhabited.

Similarly, our eq_day : day → day → Prop
type family / binary predicate specifies
the 7-element binary relation (a set of
pairs), { (mo,mo), ..., (su,su)}, in the
sense these are all of and the only values
for which eq_day yields a proposition for
which there's a proof.
-/

/-
We can even use our home-grown equality
predicate (a binary relation) on days to
re-write our (always_rains_on d) predicate
using eq_day so that we can finish our
proof above.
-/

inductive always_rains_on' : day → Prop
| mo_rainy' : ∀ (d : day), (eq_day d mo) → always_rains_on' d

open always_rains_on'

lemma bad_tuesdays' : always_rains_on' tu := mo_rainy' tu _  -- stuck
lemma bad_fridays' : always_rains_on' fr := mo_rainy' fr _  -- stuck
lemma bad_mondays' : always_rains_on' mo := mo_rainy' mo eq_day_mo

/-
As an aside you can of course use tactic mode.
-/

lemma bad_mondays'' : always_rains_on' mo := /- term -/
begin
apply mo_rainy' _ _,
exact eq_day_mo,
end

/-
We're now completely set up to look at
a first crucial application of these
ideas: the definition of an equality
predicate in Lean. We just saw how we
can represent an equality relation on
days as a two-place predicate.

We generalize our approach by defining
a polymorphic type family, eq. The eq
type family takes on type parameter, α,
and two *values* of that type, a1 and
a2, (just as our eq_day predicate takes
two values of type day). The resulting
proposition is (eq a b), for which Lean
gives us infix syntactic sugar, a = b.
The type parameter is implicit.

That defines our family of propositions.
They include propositions such as follow.
-/

#check eq 1 1           -- a1=1
#check eq 0 1           -- 0 = 1
#check eq tt tt         -- tt = tt
#check eq tt ff         -- tt = ff
#check eq "hi" "there"  -- "hi" = "there"
#check eq mo tu         -- mo = tu

/-
The following terms are not propositions
in Lean, for they do not even type check.
-/

#check eq 1 tt
#check eq "hi" 2
#check eq tt "Lean!"

/-
The final trick is that there is just
one constructor for eq, called "refl"
(generally written eq.refl) that takes
*one* argument, a : α, and constructs
a value of type (eq a a), aka a=a, which
we take to be a proof of equality. The
upshot is that you can generate a proof that
any value of any type is equal to itself
and there is no way to create a proof of
any other equality.
-/

#check @eq

/-
Here's Lean's exact definition of the
eq relation as a type family with some
members having proofs of equalty, and
other members not. The real key is to
define the constructors/axioms so that
you get exacty the relation you want!
-/

namespace eql

universe u

inductive eq {α : Sort u} (a : α) : α → Prop
| refl [] : eq a  -- skip notation for now

-- You can see exactly what's going on here
#check @eq
#check eq 1 1
#check 1 = 1    -- same thing
#check @eq.refl
#check eq.refl 1

end eql

example : 1 = 1 := eq.refl 1
example : 1 = 2 := _

/-
Moreover, Lean reduces arguments to calls
so you can use eq.refl to construct proofs
of propositions such as 1 + 1 = 2.
-/
example : 1 + 1 = 2 := eq.refl 2
example : "Hello, " ++ "Lean!" = "Hello, Lean!" := eq.refl "Hello, Lean!"

/-
By the way, "example", is like def, lemma
or theorem, but you don't bind a name to
a term. It's useful for show that a type
is inhabited by giving an example of a
value of that type. It's particularly
useful when you don't care which value
of a type is produced, since you're not
go to do anything else with it anyway,
given that it's unnamed.
-/

/-
A note on convenient shortcuts. The rfl
function uses inference pretty heavily.
-/

example : 1 + 1 = 4 - 2 := rfl

/-
Here's the definition of rfl from Lean's library.

def rfl {α : Sort u} {a : α} : a = a := eq.refl a

Lean has to be able to infer both the (α : Type)
and (a : α) value for this definition to work.
-/

/-
We've thus specified the binary relation, equality,
the equality predicate, on terms of any type. What
are its properties and related functions?
-/

/-
Properties of equality
-- reflexive
-- symmetric
-- transitive
An equivalence relation
-/

#check eq.refl
#check eq.symm
#check eq.trans
#check @eq.rec

inductive ev : ℕ → Prop
| ev_0 : ev 0
| ev_ss : ∀ (n : ℕ), ev n → ev (n+2)

open ev

lemma zero_even : ev 0 := ev_0
lemma two_even : ev 2 := ev_ss 0 ev_0
lemma four_even : ev 4 := ev_ss 2 two_even

lemma four_even' : ev 4 :=
  ev_ss   -- emphasizes recursive structure of proof term
    2
    (ev_ss
      0
      ev_0
    )

lemma five_hundred_even : ev 500 :=
begin
  repeat { apply ev_ss },
  _
end


import ..prooflab
import lectures.lec1_def_lem_thm

/-! # Homework 1: ...
Homework must be done individually.
Replace the placeholders (e.g., `:= sorry`) with your solutions. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace PROOFS

/-! ## Question 1 (10 points):
Use the lemma `mul_one` together with `rw` tactic to complete the proof below:
-/

#check (mul_one : ∀ (a : ℚ), a * 1 = a)

example (x y z : ℚ) (h₁ : x * 1 = y) (h₂ : y = z)  :
x = z :=
begin
sorry,
end

/-! ## Question 2 (10 points):
Use the tactic `exact` together with the lemma `symm_of_eq`, which we proved in `lec1_def_lem_thm` to complete the following proof.
-/

#check symm_of_eq

example (x : ℕ) (h : x = 2) :
  2 = x :=
begin
  sorry,
end

/-! ## Question 3 (20 points)
Use `rw` and the lemma `mul_assoc` to prove the `example` below.
-/

#check ( mul_assoc : ∀ (a b c : ℝ), a * b * c = a * (b * c) )

example (a b c : ℝ) :
  3 * a * b * c = 3 * a * (b * c) :=
begin
  sorry,
end

/-! ## Question 4 (20 points)
Complete the next proof using the following lemma:
  `sub_self x : x - x = 0`
-/

section
variable x : ℝ
#check sub_self x
end -- end of section

example (a b c d : ℝ) (h₁ : c = 1 + b * a - d) (h₂ : d = 1 + a * b) :
  c = 0 :=
begin
  rw mul_comm at h₂,
  rw h₂ at h₁,
  sorry,
end

/-! ## Question 5 (20 points)
Complete the next proof using lemmas `mul_assoc` and `mul_comm`:
-/

#check ( mul_assoc : ∀ (a b c : ℝ), a * b * c = a * (b * c) )
#check ( mul_comm : ∀ (a b : ℝ), a * b = b * a )

example (a b c : ℝ) :
  (c * b) * a = b * (a * c) :=
begin
  sorry,
end

/-! ## Question 6 (20 points)
Next exercises shows that the negation flips the strict order on real numbers. Complete the next proof using lemmas `neg_neg` and `neg_lt`:
-/

section
variables x y : ℝ
#check (neg_neg : ∀ a : ℝ, - -a = a)
#check (neg_lt.mpr : -x < y → -y < x)
#check (neg_lt : -x < y ↔ -y < x)
end
/-

-/
example  {x y : ℝ} (h : x < y)
  : - y < - x :=
begin
  sorry,
end

end PROOFS


import MyNat.Definition
namespace MyNat
open MyNat
/-!
# Proposition world.

## Level 3: `have`.

Say you have a whole bunch of propositions and implications between them, and your goal is to build
a certain proof of a certain proposition. If it helps, you can build intermediate proofs of other
propositions along the way, using the `have` command. `have q : Q := ...` is the Lean analogue of
saying "We now see that we can prove `Q`, because..." in the middle of a proof. It is often not
logically necessary, but on the other hand it is very convenient, for example it can save on
notation, or it can break proofs up into smaller steps.

In the level below, we have a proof of `P` and we want a proof of `U`; during the proof we will
construct proofs of of some of the other propositions involved. The diagram of propositions and
implications looks like this pictorially:

![diagram](../assets/propositions_diag.svg)

and so it's clear how to deduce `U` from `P`.
Indeed, we could solve this level in one move by typing

`exact l (j (h p))`

But let us instead stroll more lazily through the level.
We can start by using the `have` tactic to make a proof of `Q`:

`have q := h p`

and then we note that `j q` is a proof of `T`:

`have t : T := j q`

(note how we explicitly told Lean what proposition we thought `t` was
a proof of, with that `: T` thing before the `:=`)
and we could even define `u` to be `l t`:

`have u : U := l t`

and then finish the level with

`exact u`

## Lemma : maze

In the maze of logical implications above, if `P` is true then so is `U`.
-/
lemma maze (P Q R S T U: Prop)
(p : P)
(h : P → Q)
(i : Q → R)
(j : Q → T)
(k : S → T)
(l : T → U)
: U := by
  have q := h p
  have t := j q
  have u := l t
  exact u

/-!
Remember you can move your cursor around with the arrow keys and explore the various tactic states
in this proof in Visual Studio Code, and note that the tactic state at the beginning of `exact u` is
this mess:

```
P Q R S T U : Prop,
p : P,
h : P → Q,
i : Q → R,
j : Q → T,
k : S → T,
l : T → U,
q : Q,
t : T,
u : U
⊢ U
```

It was already bad enough to start with, and we added three more terms to it. In level 4 we will
learn about the `apply` tactic which solves the level using another technique, without leaving so
much junk behind.

Next up [Level 4](./Level4.lean.md)
-/


/-
Copyright (c) 2023 María Inés de Frutos-Fernández. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : María Inés de Frutos-Fernández
-/

import tactic
import data.real.basic

/-
# Límites de secuencias en Lean
Escribimos la definicón `ε` - `N` del límite de una secuencia de
números reales y demostramos resultados sobre límites.

NOTA: adaptado de los cursos de formalización de Kevin Buzzard.
-/

/- ### Notación lambda (λ) para funciones
Recordatorio: para representar una función `f` en Lean, utilizamos notación lambda:
`λ x, f x` es la función que asigna a `x` el valor `f (x)`. -/

def f : ℕ → ℝ := λ n, n^2 + 3 -- `f(n) = n^2 + 3`

example : f 3 = 12 :=
begin
  rw f,
  dsimp only, -- Para simplificar la aplicación de funciones
  norm_num, -- Normaliza expresiones numéricas
end

/-
## Límite de una secuencia
-/

/-- Si `a(n)` es una secuencia de números reales y `L` es un real,
 `tends_to a L` dice que el límite de `a(n)` cuando `n → ∞` es `L`. -/
def tends_to (a : ℕ → ℝ) (L : ℝ) : Prop :=
∀ ε > 0, ∃ N : ℕ, ∀ n, N ≤ n → |a n - L| < ε

/-- Este lema nos permite reescribir la definición de `tends_to` en nuestras pruebas. -/
theorem tends_to_def {a : ℕ → ℝ} {t : ℝ} :
  tends_to a t ↔ ∀ ε, 0 < ε → ∃ B : ℕ, ∀ n, B ≤ n → |a n - t| < ε :=
begin
  refl
end

/-
## Ejercicios
-/

/-- El límite de la secuencia constante con valor `c` es `c`. -/
theorem tends_to_const (c : ℝ) : tends_to (λ n, c) c :=
begin
  sorry
end

/-- Si `a(n)` tiende a `L` entonces `a(n) + c` tiende a `t + c` -/
theorem tends_to_add_const {a : ℕ → ℝ} {L : ℝ} (c : ℝ)
  (h : tends_to a L) :
  tends_to (λ n, a n + c) (L + c) :=
begin
  sorry
end

/-- Si `a(n)` tiende a `L`, entonces `-a(n)` tiende a `-L`.
Si simplificar la expresión dentro del valor absoluto te está
dando problemas, ve a la hoja `reales.lean`.
-/
theorem tends_to_neg {a : ℕ → ℝ} {L : ℝ} (ha : tends_to a L) :
  tends_to (λ n, - a n) (-L) :=
begin
  sorry
end

/-- Si `a(n)` tiende a `La` y `b(n)` tiende a `Lb` entonces `a(n) + b(n)`
tiende a `La + Lb`.
Esta demostración es más complicada que las anteriores.
-/
theorem tends_to_add {a b : ℕ → ℝ} {La Lb : ℝ}
  (ha : tends_to a La) (hb : tends_to b Lb) :
  tends_to (λ n, a n + b n) (La + Lb) :=
begin
  sorry
end

/-- Si `a(n)` tiende a `La` y `b(n)` tiende a `Lb` entonces `a(n) - b(n)`
tiende a `La - Lb`. -/
theorem tends_to_sub {a b : ℕ → ℝ} {La Lb : ℝ}
  (ha : tends_to a La) (hb : tends_to b Lb) :
  tends_to (λ n, a n - b n) (La - Lb) :=
begin
  sorry
end

/-- Si `a(n)` tiende a `L`, entonces `c*a(n)` tiende a `c*L`.
Pista: tratad el caso `c = 0` por separado, utilizando `by_cases hc : c = 0`.
-/
lemma tends_to_mul_const_left {a : ℕ → ℝ} {L c : ℝ} (h : tends_to a L) :
  tends_to (λ n, c * (a n)) (c * L) :=
begin
  sorry
end

/- Lema del sandwich. -/
theorem sandwich (a b c : ℕ → ℝ) (L : ℝ) (ha : tends_to a L) (hc : tends_to c L)
  (hab : ∀ n, a n ≤ b n) (hbc : ∀ n, b n ≤ c n) :
  tends_to b L :=
begin
  sorry
end


import Mathlib.Data.Nat.Basic
import Mathlib.Algebra.Group.Basic

/-
Already our proofs are getting fairly complicated. A common technique
in a proof is to construct helper results which then are employed to
assist with the main goal.
-/

open Function

variable {α β γ δ : Type} (f : α → β) (g : β → γ) (h : γ → δ)

#check Function.comp

-- \circ ∘
example : (h ∘ g) ∘ f = h ∘ (g ∘ f) := by
  apply funext
  intro x
  simp

#print Injective

/-
We can use `have` to declare intermediate goals
-/

example (h₁ : Injective f) (h₂ : Injective g) : Injective (g ∘ f) := by
  intro a₁ a₂ h'
  have h'' : f a₁ = f a₂ := h₂ h'
  apply h₁ h''

#print Surjective

/-
We can also use `have` to destructure types
-/

example (h₁ : Surjective f) (h₂ : Surjective g) : Surjective (g ∘ f) := by
  intro c
  have ⟨b,h'⟩ := h₂ c
  have ⟨a,h''⟩ := h₁ b
  apply Exists.intro a
  dsimp
  rw [h'',h']

#print Bijective

example (h' : Bijective f) : Injective f := by
  have ⟨inj,_⟩ := h'
  assumption

/-
`let` is similar to `have` but is meant for data-carring types while `have` is
for `Prop`
-/

def translate (f : ℤ → ℤ) (shift input : ℤ) : ℤ :=
  let g : ℤ → ℤ := fun n => f n + shift
  g input

/-
When working with `=` or another transitive relation, we can use a `calc`.
-/

example (a : ℤ) (f : ℤ → ℤ) : translate (translate f a) (-a) = f := by
  apply funext
  intro m
  calc
    translate (translate f a) (-a) m = translate f a m + (-a) := by simp [translate]
    _ = (f m + a) + (-a) := by simp [translate]
    _ = f m + (a + (-a)) := by rw [Int.add_assoc]
    _ = f m + 0 := by rw [Int.add_right_neg]
    _ = f m := by rw [Int.add_zero]

/-
More generally `calc` blocks can be used for transitive relations.
-/
example (p q r : Prop) (h : p ↔ q) (h' : q ↔ r) : p ↔ r := by
  calc
    p ↔ q := by exact h
    _ ↔ r := by exact h'

/-
Here is `Nat.add`'s definition but with a new name
-/
def plus (m n : ℕ) : ℕ :=
  match n with
  | 0 => m
  | n'+1 => .succ (plus m n')

/-
Let's check that if `m₁ ≤ m₂` then `m₁ ≤ m₂ +1`
-/

theorem leq_succ_of_leq (m n : Nat) (h : m ≤ n) : m ≤ plus n 1 := by
  match n with
  | 0 =>
    have meq0 : m = 0 := Nat.eq_zero_of_le_zero h
    rw [meq0]
    simp
  | n'+1 =>
    simp [plus]
    calc
      m ≤ n'+1 := by exact h
      _ ≤ (n'+1)+1 := by exact Nat.le_succ _

theorem succ_leq_succ_of_leq (m n a : Nat) (h : m ≤ n) : plus m a ≤ plus n a := by
  match a with
  | 0 => simp [plus]; assumption
  | b+1 =>
    simp [plus]
    apply Nat.succ_le_succ
    exact succ_leq_succ_of_leq m n b h


/-
Prove P ∨ Q ↔ Q ∨ P.
-/

theorem
or_commutes :
    ∀ P Q: Prop, P ∨ Q ↔ Q ∨ P
:=
begin

/-
Introduce the assumptions that P and Q
are arbitrary propositions.
-/
assume P Q: Prop,

/-
To prove a bi-implication, we prove the
implications in each direction. That is,
we break the overall goal into subgoals,
one for the forward implication and one
for its converse (in the other direction).
-/
apply iff.intro,

    -- now we prove the forward implication
    assume pq : P ∨ Q,
    show Q ∨ P,
    from
        begin
        /-
        The key proof technique for reasoning
        from a proof of a disjunction, P ∨ Q,
        is to consider two possible cases for
        this proof, that either P is true or
        Q is, and to show that the goal is
        true in either case. This shows that
        no matter why P ∨ Q is true, the goal
        follows.

        So, the proof strategy is first to
        assumes that P ∨ Q is true because P
        is true, which is to say you assume
        you have a proof of P, and then show
        that the goal follows. Then you assume
        P ∨ Q is true because Q is true, and
        you show that given an assumed proof
        of Q, the goal follows. The goal thus
        follows in either case.

        The tactic that we use in Lean to
        reason by cases from a disjunction is
        called "cases." As arguments you give
        it (1) the name of the disjunction to
        reason from, and (2) names to use for
        the assumed proofs of P and Q in the
        respective cases (after keyword "with").

        So here we go. Make sure you have the
        Lean Messages window open so that you
        can see how "cases" changes your tactic
        state. Note that it introduces two new
        subgoals. In the first, there is an
        assumed proof, p : P. In the second,
        there is an an assumed proof q : Q.
        -/
        cases pq with p q,
        -- now prove each case separately

            -- proof for case where P is true
            show Q ∨ P,
            from or.intro_right Q p,

            -- proof for case where Q is true
            show Q ∨ P,
            from or.intro_left P q,
        end,

    -- and now we prove the converse
    assume qp : Q ∨ P,
    show P ∨ Q,
    from
        begin
        cases qp with q p,
            show P ∨ Q,
            from or.intro_right P q,
            show P ∨ Q,
            from or.intro_left Q p,
        end,
end

/-
Homework:

Prove, ∀ P Q: Prop, ¬ P ∧ ¬ Q ↔ ¬ (P ∨ Q)

-/

theorem aDemorganLaw : ∀ P Q: Prop, ¬ P ∧ ¬ Q ↔ ¬ (P ∨ Q) :=
begin
assume P Q : Prop,
apply iff.intro,

-- forward
assume npnq,
show ¬(P ∨ Q),
from
    begin
    assume poq,
    -- proof by cases
    cases poq with p q,
    -- case where P is true
    show false, from npnq.left p,
    -- case where Q is true
    show false, from npnq.right q,
    end,

-- converse
assume poq,
show  ¬P ∧ ¬Q,
from
    begin
    -- proof by and introduction
    apply and.intro,
    -- left conjunct
    assume p, show false, from poq (or.intro_left Q p),
    -- right conjunct
    assume q, show false, from poq (or.intro_right P q)
    end,
-- QED
end


import tactic.omega
import data.real.basic
import data.real.sqrt
import data.nat.prime
import data.nat.factorial

/- Dealing with numbers:

  ℕ    natural numbers
  ℤ    integers
  ℚ    rational numbers
  ℝ    real numbers
  ℂ    complex numbers

Tactics:

  norm_num, linarith, ring, norm_cast, field_simp, omega

Also see LFTCM 2020 lecture:

  https://www.youtube.com/watch?v=iEs2U_kzYy4

-/

example : 2 + 3 = 5 := rfl
example : 1 + 2 + 3 = 6 := by refl
example : 2 + 3 = 5 := begin
  refl
end

example : 2000000 + 3000000 = 5000000 := by norm_num

example : 2 * 3 = 6 := rfl

example : 2 - 3 = 0 := rfl      -- "Totalization" of a partial function (subtraction : ℕ × ℕ → ℕ)
example : 2 / 3 = 0 := rfl

example : (2 : ℤ) - 3 = -1 := rfl
example : (2 : ℚ) - 3 = -1 := rfl
example : (2 : ℝ) - 3 = -1 := by norm_num

#print notation -
#check @has_sub.sub

#check (show has_sub ℤ, by apply_instance)
#check int.has_sub

example : nat.prime 65537 := by norm_num
example : ¬ nat.prime 65538 := by norm_num
example : nat.factorial 6 = 720 := by norm_num

-- `omega` can prove theorems in the language (ℕ, 0, 1, +, <).

example (i j n : ℕ) (h₁ : i ≤ j) (h₂ : j < n) : i + (j - i + 1) + (n - 1 - j) = n :=
begin
  omega,
  -- alternatively: prove equality of integers,
  -- using tools like `norm_cast` & `ring`
end

open real

example (r : ℝ) (hr : r*r + r - 1 = 0) (rpos : r > 0) : r = (sqrt 5 - 1) / 2 :=
begin
  have : (r - (sqrt 5 - 1) / 2) * (r - (- sqrt 5 - 1) / 2) = r*r + r - 1,
  { ring,
    norm_num,
    ring, },
  rw hr at this,
  rw mul_eq_zero at this,
  cases this with h h,
  { linarith,
},
  sorry
end

example (x : ℝ) (hx : x ≠ 0) : x / x^2 = 1 / x :=
begin
  -- field_simp,
  ring,
end

/- Exercises: From Lean for the Curious Mathematician 2020.
See https://leanprover-community.github.io/lftcm2020/exercises.html
This lecture: tuesday/numbers.lean
-/


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Johannes Hölzl, Scott Morrison, Jens Wagemaker
-/
import data.polynomial.monomial
import data.nat.with_bot

/-!
# Theory of univariate polynomials

The definitions include
`degree`, `monic`, `leading_coeff`

Results include
- `degree_mul` : The degree of the product is the sum of degrees
- `leading_coeff_add_of_degree_eq` and `leading_coeff_add_of_degree_lt` :
    The leading_coefficient of a sum is determined by the leading coefficients and degrees
-/

noncomputable theory
local attribute [instance, priority 100] classical.prop_decidable

open finsupp finset
open_locale big_operators

namespace polynomial
universes u v
variables {R : Type u} {S : Type v} {a b : R} {n m : ℕ}

section semiring
variables [semiring R] {p q r : polynomial R}

/-- `degree p` is the degree of the polynomial `p`, i.e. the largest `X`-exponent in `p`.
`degree p = some n` when `p ≠ 0` and `n` is the highest power of `X` that appears in `p`, otherwise
`degree 0 = ⊥`. -/
def degree (p : polynomial R) : with_bot ℕ := p.support.sup some

lemma degree_lt_wf : well_founded (λp q : polynomial R, degree p < degree q) :=
inv_image.wf degree (with_bot.well_founded_lt nat.lt_wf)

instance : has_well_founded (polynomial R) := ⟨_, degree_lt_wf⟩

/-- `nat_degree p` forces `degree p` to ℕ, by defining nat_degree 0 = 0. -/
def nat_degree (p : polynomial R) : ℕ := (degree p).get_or_else 0

/-- `leading_coeff p` gives the coefficient of the highest power of `X` in `p`-/
def leading_coeff (p : polynomial R) : R := coeff p (nat_degree p)

/-- a polynomial is `monic` if its leading coefficient is 1 -/
def monic (p : polynomial R) := leading_coeff p = (1 : R)

@[nontriviality] lemma monic_of_subsingleton [subsingleton R] (p : polynomial R) : monic p :=
subsingleton.elim _ _

lemma monic.def : monic p ↔ leading_coeff p = 1 := iff.rfl

instance monic.decidable [decidable_eq R] : decidable (monic p) :=
by unfold monic; apply_instance

@[simp] lemma monic.leading_coeff {p : polynomial R} (hp : p.monic) :
  leading_coeff p = 1 := hp

lemma monic.coeff_nat_degree {p : polynomial R} (hp : p.monic) : p.coeff p.nat_degree = 1 := hp

@[simp] lemma degree_zero : degree (0 : polynomial R) = ⊥ := rfl

@[simp] lemma nat_degree_zero : nat_degree (0 : polynomial R) = 0 := rfl

@[simp] lemma coeff_nat_degree : coeff p (nat_degree p) = leading_coeff p := rfl

lemma degree_eq_bot : degree p = ⊥ ↔ p = 0 :=
⟨λ h, by rw [degree, ← max_eq_sup_with_bot] at h;
  exact support_eq_empty.1 (max_eq_none.1 h),
λ h, h.symm ▸ rfl⟩

@[nontriviality] lemma degree_of_subsingleton [subsingleton R] : degree p = ⊥ :=
by rw [subsingleton.elim p 0, degree_zero]

@[nontriviality] lemma nat_degree_of_subsingleton [subsingleton R] : nat_degree p = 0 :=
by rw [subsingleton.elim p 0, nat_degree_zero]

lemma degree_eq_nat_degree (hp : p ≠ 0) : degree p = (nat_degree p : with_bot ℕ) :=
let ⟨n, hn⟩ :=
  not_forall.1 (mt option.eq_none_iff_forall_not_mem.2 (mt degree_eq_bot.1 hp)) in
have hn : degree p = some n := not_not.1 hn,
by rw [nat_degree, hn]; refl

lemma degree_eq_iff_nat_degree_eq {p : polynomial R} {n : ℕ} (hp : p ≠ 0) :
  p.degree = n ↔ p.nat_degree = n :=
by rw [degree_eq_nat_degree hp, with_bot.coe_eq_coe]

lemma degree_eq_iff_nat_degree_eq_of_pos {p : polynomial R} {n : ℕ} (hn : 0 < n) :
  p.degree = n ↔ p.nat_degree = n :=
begin
  split,
  { intro H, rwa ← degree_eq_iff_nat_degree_eq, rintro rfl,
    rw degree_zero at H, exact option.no_confusion H },
  { intro H, rwa degree_eq_iff_nat_degree_eq, rintro rfl,
    rw nat_degree_zero at H, rw H at hn, exact lt_irrefl _ hn }
end

lemma nat_degree_eq_of_degree_eq_some {p : polynomial R} {n : ℕ}
  (h : degree p = n) : nat_degree p = n :=
have hp0 : p ≠ 0, from λ hp0, by rw hp0 at h; exact option.no_confusion h,
option.some_inj.1 $ show (nat_degree p : with_bot ℕ) = n,
  by rwa [← degree_eq_nat_degree hp0]

@[simp] lemma degree_le_nat_degree : degree p ≤ nat_degree p :=
with_bot.gi_get_or_else_bot.gc.le_u_l _

lemma nat_degree_eq_of_degree_eq [semiring S] {q : polynomial S} (h : degree p = degree q) :
  nat_degree p = nat_degree q :=
by unfold nat_degree; rw h

lemma le_degree_of_ne_zero (h : coeff p n ≠ 0) : (n : with_bot ℕ) ≤ degree p :=
show @has_le.le (with_bot ℕ) _ (some n : with_bot ℕ) (p.support.sup some : with_bot ℕ),
from finset.le_sup (finsupp.mem_support_iff.2 h)

lemma le_nat_degree_of_ne_zero (h : coeff p n ≠ 0) : n ≤ nat_degree p :=
begin
  rw [← with_bot.coe_le_coe, ← degree_eq_nat_degree],
  exact le_degree_of_ne_zero h,
  { assume h, subst h, exact h rfl }
end

lemma le_nat_degree_of_mem_supp (a : ℕ) :
  a ∈ p.support → a ≤ nat_degree p:=
le_nat_degree_of_ne_zero ∘ mem_support_iff.mp

lemma supp_subset_range (h : nat_degree p < m) : p.support ⊆ finset.range m :=
λ n hn, mem_range.2 $ (le_nat_degree_of_mem_supp _ hn).trans_lt h

lemma supp_subset_range_nat_degree_succ : p.support ⊆ finset.range (nat_degree p + 1) :=
supp_subset_range (nat.lt_succ_self _)

lemma degree_le_degree (h : coeff q (nat_degree p) ≠ 0) : degree p ≤ degree q :=
begin
  by_cases hp : p = 0,
  { rw hp, exact bot_le },
  { rw degree_eq_nat_degree hp, exact le_degree_of_ne_zero h }
end

lemma degree_ne_of_nat_degree_ne {n : ℕ} :
  p.nat_degree ≠ n → degree p ≠ n :=
mt $ λ h, by rw [nat_degree, h, option.get_or_else_coe]

theorem nat_degree_le_iff_degree_le {n : ℕ} : nat_degree p ≤ n ↔ degree p ≤ n :=
with_bot.get_or_else_bot_le_iff

alias polynomial.nat_degree_le_iff_degree_le ↔ . .

lemma nat_degree_le_nat_degree (hpq : p.degree ≤ q.degree) : p.nat_degree ≤ q.nat_degree :=
with_bot.gi_get_or_else_bot.gc.monotone_l hpq

@[simp] lemma degree_C (ha : a ≠ 0) : degree (C a) = (0 : with_bot ℕ) :=
show sup (ite (a = 0) ∅ {0}) some = 0, by rw if_neg ha; refl

lemma degree_C_le : degree (C a) ≤ (0 : with_bot ℕ) :=
by by_cases h : a = 0; [rw [h, C_0], rw [degree_C h]]; [exact bot_le, exact le_refl _]

lemma degree_one_le : degree (1 : polynomial R) ≤ (0 : with_bot ℕ) :=
by rw [← C_1]; exact degree_C_le

@[simp] lemma nat_degree_C (a : R) : nat_degree (C a) = 0 :=
begin
  by_cases ha : a = 0,
  { have : C a = 0, { rw [ha, C_0] },
    rw [nat_degree, degree_eq_bot.2 this],
    refl },
  { rw [nat_degree, degree_C ha], refl }
end

@[simp] lemma nat_degree_one : nat_degree (1 : polynomial R) = 0 := nat_degree_C 1

@[simp] lemma nat_degree_nat_cast (n : ℕ) : nat_degree (n : polynomial R) = 0 :=
by simp only [←C_eq_nat_cast, nat_degree_C]

@[simp] lemma degree_monomial (n : ℕ) (ha : a ≠ 0) : degree (monomial n a) = n :=
by rw [degree, support_monomial _ _ ha]; refl

@[simp] lemma degree_C_mul_X_pow (n : ℕ) (ha : a ≠ 0) : degree (C a * X ^ n) = n :=
by rw [← single_eq_C_mul_X, degree_monomial n ha]

lemma degree_monomial_le (n : ℕ) (a : R) : degree (monomial n a) ≤ n :=
if h : a = 0 then by rw [h, (monomial n).map_zero]; exact bot_le else le_of_eq (degree_monomial n h)

lemma degree_C_mul_X_pow_le (n : ℕ) (a : R) : degree (C a * X ^ n) ≤ n :=
by { rw C_mul_X_pow_eq_monomial, apply degree_monomial_le }

@[simp] lemma nat_degree_C_mul_X_pow (n : ℕ) (a : R) (ha : a ≠ 0) : nat_degree (C a * X ^ n) = n :=
nat_degree_eq_of_degree_eq_some (degree_C_mul_X_pow n ha)

@[simp] lemma nat_degree_C_mul_X (a : R) (ha : a ≠ 0) : nat_degree (C a * X) = 1 :=
by simpa only [pow_one] using nat_degree_C_mul_X_pow 1 a ha

@[simp] lemma nat_degree_monomial (i : ℕ) (r : R) (hr : r ≠ 0) :
  nat_degree (monomial i r) = i :=
by rw [← C_mul_X_pow_eq_monomial, nat_degree_C_mul_X_pow i r hr]

lemma coeff_eq_zero_of_degree_lt (h : degree p < n) : coeff p n = 0 :=
not_not.1 (mt le_degree_of_ne_zero (not_le_of_gt h))

lemma coeff_eq_zero_of_nat_degree_lt {p : polynomial R} {n : ℕ} (h : p.nat_degree < n) :
  p.coeff n = 0 :=
begin
  apply coeff_eq_zero_of_degree_lt,
  by_cases hp : p = 0,
  { subst hp, exact with_bot.bot_lt_coe n },
  { rwa [degree_eq_nat_degree hp, with_bot.coe_lt_coe] }
end

@[simp] lemma coeff_nat_degree_succ_eq_zero {p : polynomial R} : p.coeff (p.nat_degree + 1) = 0 :=
coeff_eq_zero_of_nat_degree_lt (lt_add_one _)

-- We need the explicit `decidable` argument here because an exotic one shows up in a moment!
lemma ite_le_nat_degree_coeff (p : polynomial R) (n : ℕ) (I : decidable (n < 1 + nat_degree p)) :
  @ite _ (n < 1 + nat_degree p) I (coeff p n) 0 = coeff p n :=
begin
  split_ifs,
  { refl },
  { exact (coeff_eq_zero_of_nat_degree_lt (not_le.1 (λ w, h (nat.lt_one_add_iff.2 w)))).symm, }
end

lemma as_sum_support (p : polynomial R) :
  p = ∑ i in p.support, monomial i (p.coeff i) :=
p.sum_single.symm

lemma as_sum_support_C_mul_X_pow (p : polynomial R) :
  p = ∑ i in p.support, C (p.coeff i) * X^i :=
trans p.as_sum_support $ by simp only [C_mul_X_pow_eq_monomial]

/--
We can reexpress a sum over `p.support` as a sum over `range n`,
for any `n` satisfying `p.nat_degree < n`.
-/
lemma sum_over_range' [add_comm_monoid S] (p : polynomial R) {f : ℕ → R → S} (h : ∀ n, f n 0 = 0)
  (n : ℕ) (w : p.nat_degree < n) :
  p.sum f = ∑ (a : ℕ) in range n, f a (coeff p a) :=
finsupp.sum_of_support_subset _ (supp_subset_range w) _ $ λ n hn, h n

/--
We can reexpress a sum over `p.support` as a sum over `range (p.nat_degree + 1)`.
-/
lemma sum_over_range [add_comm_monoid S] (p : polynomial R) {f : ℕ → R → S} (h : ∀ n, f n 0 = 0) :
  p.sum f = ∑ (a : ℕ) in range (p.nat_degree + 1), f a (coeff p a) :=
sum_over_range' p h (p.nat_degree + 1) (lt_add_one _)

lemma as_sum_range' (p : polynomial R) (n : ℕ) (w : p.nat_degree < n) :
  p = ∑ i in range n, monomial i (coeff p i) :=
p.sum_single.symm.trans $ p.sum_over_range' (λ n, single_zero) _ w

lemma as_sum_range (p : polynomial R) :
  p = ∑ i in range (p.nat_degree + 1), monomial i (coeff p i) :=
p.sum_single.symm.trans $ p.sum_over_range $ λ n, single_zero

lemma as_sum_range_C_mul_X_pow (p : polynomial R) :
  p = ∑ i in range (p.nat_degree + 1), C (coeff p i) * X ^ i :=
p.as_sum_range.trans $ by simp only [C_mul_X_pow_eq_monomial]

lemma coeff_ne_zero_of_eq_degree (hn : degree p = n) :
  coeff p n ≠ 0 :=
λ h, mem_support_iff.mp (mem_of_max hn) h

lemma eq_X_add_C_of_degree_le_one (h : degree p ≤ 1) :
  p = C (p.coeff 1) * X + C (p.coeff 0) :=
ext (λ n, nat.cases_on n (by simp)
  (λ n, nat.cases_on n (by simp [coeff_C])
    (λ m, have degree p < m.succ.succ, from lt_of_le_of_lt h dec_trivial,
      by simp [coeff_eq_zero_of_degree_lt this, coeff_C, nat.succ_ne_zero, coeff_X,
        nat.succ_inj', @eq_comm ℕ 0])))

lemma eq_X_add_C_of_degree_eq_one (h : degree p = 1) :
  p = C (p.leading_coeff) * X + C (p.coeff 0) :=
(eq_X_add_C_of_degree_le_one (show degree p ≤ 1, from h ▸ le_refl _)).trans
  (by simp [leading_coeff, nat_degree_eq_of_degree_eq_some h])

lemma eq_X_add_C_of_nat_degree_le_one (h : nat_degree p ≤ 1) :
  p = C (p.coeff 1) * X + C (p.coeff 0) :=
eq_X_add_C_of_degree_le_one $ degree_le_of_nat_degree_le h

lemma exists_eq_X_add_C_of_nat_degree_le_one (h : nat_degree p ≤ 1) :
  ∃ a b, p = C a * X + C b :=
⟨p.coeff 1, p.coeff 0, eq_X_add_C_of_nat_degree_le_one h⟩

theorem degree_X_pow_le (n : ℕ) : degree (X^n : polynomial R) ≤ n :=
by simpa only [C_1, one_mul] using degree_C_mul_X_pow_le n (1:R)

theorem degree_X_le : degree (X : polynomial R) ≤ 1 :=
degree_monomial_le _ _

lemma nat_degree_X_le : (X : polynomial R).nat_degree ≤ 1 :=
nat_degree_le_of_degree_le degree_X_le

lemma support_C_mul_X_pow (c : R) (n : ℕ) : (C c * X ^ n).support ⊆ singleton n :=
begin
  rw [C_mul_X_pow_eq_monomial],
  exact support_single_subset
end

lemma mem_support_C_mul_X_pow {n a : ℕ} {c : R} (h : a ∈ (C c * X ^ n).support) : a = n :=
mem_singleton.1 $ support_C_mul_X_pow _ _ h

lemma card_support_C_mul_X_pow_le_one {c : R} {n : ℕ} : (C c * X ^ n).support.card ≤ 1 :=
begin
  rw ← card_singleton n,
  apply card_le_of_subset (support_C_mul_X_pow c n),
end

lemma card_supp_le_succ_nat_degree (p : polynomial R) : p.support.card ≤ p.nat_degree + 1 :=
begin
  rw ← finset.card_range (p.nat_degree + 1),
  exact finset.card_le_of_subset supp_subset_range_nat_degree_succ,
end

lemma le_degree_of_mem_supp (a : ℕ) :
  a ∈ p.support → ↑a ≤ degree p :=
le_degree_of_ne_zero ∘ mem_support_iff.mp

lemma nonempty_support_iff : p.support.nonempty ↔ p ≠ 0 :=
by rw [ne.def, nonempty_iff_ne_empty, ne.def, ← support_eq_empty]

lemma support_C_mul_X_pow_nonzero {c : R} {n : ℕ} (h : c ≠ 0) :
  (C c * X ^ n).support = singleton n :=
begin
  rw [C_mul_X_pow_eq_monomial],
  exact support_single_ne_zero h
end

end semiring

section nonzero_semiring
variables [semiring R] [nontrivial R] {p q : polynomial R}

@[simp] lemma degree_one : degree (1 : polynomial R) = (0 : with_bot ℕ) :=
degree_C (show (1 : R) ≠ 0, from zero_ne_one.symm)

@[simp] lemma degree_X : degree (X : polynomial R) = 1 :=
degree_monomial _ one_ne_zero

@[simp] lemma nat_degree_X : (X : polynomial R).nat_degree = 1 :=
nat_degree_eq_of_degree_eq_some degree_X

end nonzero_semiring

section ring
variables [ring R]

lemma coeff_mul_X_sub_C {p : polynomial R} {r : R} {a : ℕ} :
  coeff (p * (X - C r)) (a + 1) = coeff p a - coeff p (a + 1) * r :=
by simp [mul_sub]

lemma C_eq_int_cast (n : ℤ) : C (n : R) = n :=
(C : R →+* _).map_int_cast n

@[simp] lemma degree_neg (p : polynomial R) : degree (-p) = degree p :=
by unfold degree; rw support_neg

@[simp] lemma nat_degree_neg (p : polynomial R) : nat_degree (-p) = nat_degree p :=
by simp [nat_degree]

@[simp] lemma nat_degree_int_cast (n : ℤ) : nat_degree (n : polynomial R) = 0 :=
by simp only [←C_eq_int_cast, nat_degree_C]

end ring

section semiring
variables [semiring R]

/-- The second-highest coefficient, or 0 for constants -/
def next_coeff (p : polynomial R) : R :=
if p.nat_degree = 0 then 0 else p.coeff (p.nat_degree - 1)

@[simp]
lemma next_coeff_C_eq_zero (c : R) :
  next_coeff (C c) = 0 := by { rw next_coeff, simp }

lemma next_coeff_of_pos_nat_degree (p : polynomial R) (hp : 0 < p.nat_degree) :
  next_coeff p = p.coeff (p.nat_degree - 1) :=
by { rw [next_coeff, if_neg], contrapose! hp, simpa }

end semiring

section semiring
variables [semiring R] {p q : polynomial R} {ι : Type*}

lemma coeff_nat_degree_eq_zero_of_degree_lt (h : degree p < degree q) :
  coeff p (nat_degree q) = 0 :=
coeff_eq_zero_of_degree_lt (lt_of_lt_of_le h degree_le_nat_degree)

lemma ne_zero_of_degree_gt {n : with_bot ℕ} (h : n < degree p) : p ≠ 0 :=
mt degree_eq_bot.2 (ne.symm (ne_of_lt (lt_of_le_of_lt bot_le h)))

lemma ne_zero_of_degree_ge_degree (hpq : p.degree ≤ q.degree) (hp : p ≠ 0) : q ≠ 0 :=
polynomial.ne_zero_of_degree_gt (lt_of_lt_of_le (bot_lt_iff_ne_bot.mpr
  (by rwa [ne.def, polynomial.degree_eq_bot])) hpq : q.degree > ⊥)

lemma ne_zero_of_nat_degree_gt {n : ℕ} (h : n < nat_degree p) : p ≠ 0 :=
λ H, by simpa [H, nat.not_lt_zero] using h

lemma degree_lt_degree (h : nat_degree p < nat_degree q) : degree p < degree q :=
begin
  by_cases hp : p = 0,
  { simp [hp],
    rw bot_lt_iff_ne_bot,
    intro hq,
    simpa [hp, degree_eq_bot.mp hq, lt_irrefl] using h },
  { rw [degree_eq_nat_degree hp, degree_eq_nat_degree $ ne_zero_of_nat_degree_gt h],
    exact_mod_cast h }
end

lemma nat_degree_lt_nat_degree_iff (hp : p ≠ 0) :
  nat_degree p < nat_degree q ↔ degree p < degree q :=
⟨degree_lt_degree, begin
  intro h,
  have hq : q ≠ 0 := ne_zero_of_degree_gt h,
  rw [degree_eq_nat_degree hp, degree_eq_nat_degree hq] at h,
  exact_mod_cast h
end⟩

lemma eq_C_of_degree_le_zero (h : degree p ≤ 0) : p = C (coeff p 0) :=
begin
  ext (_|n), { simp },
  rw [coeff_C, if_neg (nat.succ_ne_zero _), coeff_eq_zero_of_degree_lt],
  exact h.trans_lt (with_bot.some_lt_some.2 n.succ_pos),
end

lemma eq_C_of_degree_eq_zero (h : degree p = 0) : p = C (coeff p 0) :=
eq_C_of_degree_le_zero (h ▸ le_refl _)

lemma degree_le_zero_iff : degree p ≤ 0 ↔ p = C (coeff p 0) :=
⟨eq_C_of_degree_le_zero, λ h, h.symm ▸ degree_C_le⟩

lemma degree_add_le (p q : polynomial R) : degree (p + q) ≤ max (degree p) (degree q) :=
calc degree (p + q) = ((p + q).support).sup some : rfl
  ... ≤ (p.support ∪ q.support).sup some : by convert sup_mono support_add
  ... = p.support.sup some ⊔ q.support.sup some : by convert sup_union
  ... = _ : with_bot.sup_eq_max _ _

@[simp] lemma leading_coeff_zero : leading_coeff (0 : polynomial R) = 0 := rfl

@[simp] lemma leading_coeff_eq_zero : leading_coeff p = 0 ↔ p = 0 :=
⟨λ h, by_contradiction $ λ hp, mt mem_support_iff.1
  (not_not.2 h) (mem_of_max (degree_eq_nat_degree hp)),
λ h, h.symm ▸ leading_coeff_zero⟩

lemma leading_coeff_ne_zero : leading_coeff p ≠ 0 ↔ p ≠ 0 :=
by rw [ne.def, leading_coeff_eq_zero]

lemma leading_coeff_eq_zero_iff_deg_eq_bot : leading_coeff p = 0 ↔ degree p = ⊥ :=
by rw [leading_coeff_eq_zero, degree_eq_bot]

lemma nat_degree_mem_support_of_nonzero (H : p ≠ 0) : p.nat_degree ∈ p.support :=
(p.mem_support_to_fun p.nat_degree).mpr ((not_congr leading_coeff_eq_zero).mpr H)

lemma nat_degree_eq_support_max' (h : p ≠ 0) :
  p.nat_degree = p.support.max' (nonempty_support_iff.mpr h) :=
(le_max' _ _ $ nat_degree_mem_support_of_nonzero h).antisymm $
  max'_le _ _ _ le_nat_degree_of_mem_supp

lemma nat_degree_C_mul_X_pow_le (a : R) (n : ℕ) : nat_degree (C a * X ^ n) ≤ n :=
nat_degree_le_iff_degree_le.2 $ degree_C_mul_X_pow_le _ _

lemma degree_add_eq_left_of_degree_lt (h : degree q < degree p) : degree (p + q) = degree p :=
le_antisymm (max_eq_left_of_lt h ▸ degree_add_le _ _) $ degree_le_degree $
  begin
    rw [coeff_add, coeff_nat_degree_eq_zero_of_degree_lt h, add_zero],
    exact mt leading_coeff_eq_zero.1 (ne_zero_of_degree_gt h)
  end

lemma degree_add_eq_right_of_degree_lt (h : degree p < degree q) : degree (p + q) = degree q :=
by rw [add_comm, degree_add_eq_left_of_degree_lt h]

lemma degree_add_C (hp : 0 < degree p) : degree (p + C a) = degree p :=
add_comm (C a) p ▸ degree_add_eq_right_of_degree_lt $ lt_of_le_of_lt degree_C_le hp

lemma degree_add_eq_of_leading_coeff_add_ne_zero (h : leading_coeff p + leading_coeff q ≠ 0) :
  degree (p + q) = max p.degree q.degree :=
le_antisymm (degree_add_le _ _) $
  match lt_trichotomy (degree p) (degree q) with
  | or.inl hlt :=
    by rw [degree_add_eq_right_of_degree_lt hlt, max_eq_right_of_lt hlt]; exact le_refl _
  | or.inr (or.inl heq) :=
    le_of_not_gt $
      assume hlt : max (degree p) (degree q) > degree (p + q),
      h $ show leading_coeff p + leading_coeff q = 0,
      begin
        rw [heq, max_self] at hlt,
        rw [leading_coeff, leading_coeff, nat_degree_eq_of_degree_eq heq, ← coeff_add],
        exact coeff_nat_degree_eq_zero_of_degree_lt hlt
      end
  | or.inr (or.inr hlt) :=
    by rw [degree_add_eq_left_of_degree_lt hlt, max_eq_left_of_lt hlt]; exact le_refl _
  end

lemma degree_erase_le (p : polynomial R) (n : ℕ) : degree (p.erase n) ≤ degree p :=
by convert sup_mono (erase_subset _ _)

lemma degree_erase_lt (hp : p ≠ 0) : degree (p.erase (nat_degree p)) < degree p :=
lt_of_le_of_ne (degree_erase_le _ _) $
  (degree_eq_nat_degree hp).symm ▸ (by convert λ h, not_mem_erase _ _ (mem_of_max h))

lemma degree_sum_le (s : finset ι) (f : ι → polynomial R) :
  degree (∑ i in s, f i) ≤ s.sup (λ b, degree (f b)) :=
finset.induction_on s (by simp only [sum_empty, sup_empty, degree_zero, le_refl]) $
  assume a s has ih,
  calc degree (∑ i in insert a s, f i) ≤ max (degree (f a)) (degree (∑ i in s, f i)) :
    by rw sum_insert has; exact degree_add_le _ _
  ... ≤ _ : by rw [sup_insert, with_bot.sup_eq_max]; exact max_le_max (le_refl _) ih

lemma degree_mul_le (p q : polynomial R) : degree (p * q) ≤ degree p + degree q :=
calc degree (p * q) ≤ (p.support).sup (λi, degree (sum q (λj a, C (coeff p i * a) * X ^ (i + j)))) :
    by simp only [single_eq_C_mul_X.symm]; exact degree_sum_le _ _
  ... ≤ p.support.sup (λi, q.support.sup (λj, degree (C (coeff p i * coeff q j) * X ^ (i + j)))) :
    finset.sup_mono_fun (assume i hi,  degree_sum_le _ _)
  ... ≤ degree p + degree q :
    begin
      refine finset.sup_le (λ a ha, finset.sup_le (λ b hb, le_trans (degree_C_mul_X_pow_le _ _) _)),
      rw [with_bot.coe_add],
      rw mem_support_iff at ha hb,
      exact add_le_add (le_degree_of_ne_zero ha) (le_degree_of_ne_zero hb)
    end

lemma degree_pow_le (p : polynomial R) : ∀ (n : ℕ), degree (p ^ n) ≤ n • (degree p)
| 0     := by rw [pow_zero, zero_nsmul]; exact degree_one_le
| (n+1) := calc degree (p ^ (n + 1)) ≤ degree p + degree (p ^ n) :
    by rw pow_succ; exact degree_mul_le _ _
  ... ≤ _ : by rw succ_nsmul; exact add_le_add (le_refl _) (degree_pow_le _)

@[simp] lemma leading_coeff_monomial (a : R) (n : ℕ) : leading_coeff (monomial n a) = a :=
begin
  by_cases ha : a = 0,
  { simp only [ha, (monomial n).map_zero, leading_coeff_zero] },
  { rw [leading_coeff, nat_degree_monomial _ _ ha],
    exact @finsupp.single_eq_same _ _ _ n a }
end

lemma leading_coeff_C_mul_X_pow (a : R) (n : ℕ) : leading_coeff (C a * X ^ n) = a :=
by rw [C_mul_X_pow_eq_monomial, leading_coeff_monomial]

@[simp]

@[simp] lemma leading_coeff_X_pow (n : ℕ) : leading_coeff ((X : polynomial R) ^ n) = 1 :=
by simpa only [C_1, one_mul] using leading_coeff_C_mul_X_pow (1 : R) n

@[simp] lemma leading_coeff_X : leading_coeff (X : polynomial R) = 1 :=
by simpa only [pow_one] using @leading_coeff_X_pow R _ 1

@[simp] lemma monic_X_pow (n : ℕ) : monic (X ^ n : polynomial R) := leading_coeff_X_pow n

@[simp] lemma monic_X : monic (X : polynomial R) := leading_coeff_X

@[simp] lemma leading_coeff_one : leading_coeff (1 : polynomial R) = 1 :=
leading_coeff_C 1

@[simp] lemma monic_one : monic (1 : polynomial R) := leading_coeff_C _

lemma monic.ne_zero {R : Type*} [semiring R] [nontrivial R] {p : polynomial R} (hp : p.monic) :
  p ≠ 0 :=
by { rintro rfl, simpa [monic] using hp }

lemma monic.ne_zero_of_ne (h : (0:R) ≠ 1) {p : polynomial R} (hp : p.monic) :
  p ≠ 0 :=
by { nontriviality R, exact hp.ne_zero }

lemma monic.ne_zero_of_polynomial_ne {r} (hp : monic p) (hne : q ≠ r) : p ≠ 0 :=
by { haveI := nontrivial.of_polynomial_ne hne, exact hp.ne_zero }

lemma leading_coeff_add_of_degree_lt (h : degree p < degree q) :
  leading_coeff (p + q) = leading_coeff q :=
have coeff p (nat_degree q) = 0, from coeff_nat_degree_eq_zero_of_degree_lt h,
by simp only [leading_coeff, nat_degree_eq_of_degree_eq (degree_add_eq_right_of_degree_lt h),
  this, coeff_add, zero_add]

lemma leading_coeff_add_of_degree_eq (h : degree p = degree q)
  (hlc : leading_coeff p + leading_coeff q ≠ 0) :
  leading_coeff (p + q) = leading_coeff p + leading_coeff q :=
have nat_degree (p + q) = nat_degree p,
  by apply nat_degree_eq_of_degree_eq;
    rw [degree_add_eq_of_leading_coeff_add_ne_zero hlc, h, max_self],
by simp only [leading_coeff, this, nat_degree_eq_of_degree_eq h, coeff_add]

@[simp] lemma coeff_mul_degree_add_degree (p q : polynomial R) :
  coeff (p * q) (nat_degree p + nat_degree q) = leading_coeff p * leading_coeff q :=
calc coeff (p * q) (nat_degree p + nat_degree q) =
    ∑ x in nat.antidiagonal (nat_degree p + nat_degree q),
    coeff p x.1 * coeff q x.2 : coeff_mul _ _ _
... = coeff p (nat_degree p) * coeff q (nat_degree q) :
  begin
    refine finset.sum_eq_single (nat_degree p, nat_degree q) _ _,
    { rintro ⟨i,j⟩ h₁ h₂, rw nat.mem_antidiagonal at h₁,
      by_cases H : nat_degree p < i,
      { rw [coeff_eq_zero_of_degree_lt
          (lt_of_le_of_lt degree_le_nat_degree (with_bot.coe_lt_coe.2 H)), zero_mul] },
      { rw not_lt_iff_eq_or_lt at H, cases H,
        { subst H, rw add_left_cancel_iff at h₁, dsimp at h₁, subst h₁, exfalso, exact h₂ rfl },
        { suffices : nat_degree q < j,
          { rw [coeff_eq_zero_of_degree_lt
              (lt_of_le_of_lt degree_le_nat_degree (with_bot.coe_lt_coe.2 this)), mul_zero] },
          { by_contra H', rw not_lt at H',
            exact ne_of_lt (nat.lt_of_lt_of_le
              (nat.add_lt_add_right H j) (nat.add_le_add_left H' _)) h₁ } } } },
    { intro H, exfalso, apply H, rw nat.mem_antidiagonal }
  end

lemma degree_mul' (h : leading_coeff p * leading_coeff q ≠ 0) :
  degree (p * q) = degree p + degree q :=
have hp : p ≠ 0 := by refine mt _ h; exact λ hp, by rw [hp, leading_coeff_zero, zero_mul],
have hq : q ≠ 0 := by refine mt _ h; exact λ hq, by rw [hq, leading_coeff_zero, mul_zero],
le_antisymm (degree_mul_le _ _)
begin
  rw [degree_eq_nat_degree hp, degree_eq_nat_degree hq],
  refine le_degree_of_ne_zero _,
  rwa coeff_mul_degree_add_degree
end

lemma degree_mul_monic (hq : monic q) : degree (p * q) = degree p + degree q :=
if hp : p = 0 then by simp [hp]
else degree_mul' $ by rwa [hq.leading_coeff, mul_one, ne.def, leading_coeff_eq_zero]

lemma nat_degree_mul' (h : leading_coeff p * leading_coeff q ≠ 0) :
  nat_degree (p * q) = nat_degree p + nat_degree q :=
have hp : p ≠ 0 := mt leading_coeff_eq_zero.2 (λ h₁, h $ by rw [h₁, zero_mul]),
have hq : q ≠ 0 := mt leading_coeff_eq_zero.2 (λ h₁, h $ by rw [h₁, mul_zero]),
nat_degree_eq_of_degree_eq_some $
  by rw [degree_mul' h, with_bot.coe_add, degree_eq_nat_degree hp, degree_eq_nat_degree hq]

lemma leading_coeff_mul' (h : leading_coeff p * leading_coeff q ≠ 0) :
  leading_coeff (p * q) = leading_coeff p * leading_coeff q :=
begin
  unfold leading_coeff,
  rw [nat_degree_mul' h, coeff_mul_degree_add_degree],
  refl
end

lemma leading_coeff_pow' : leading_coeff p ^ n ≠ 0 →
  leading_coeff (p ^ n) = leading_coeff p ^ n :=
nat.rec_on n (by simp) $
λ n ih h,
have h₁ : leading_coeff p ^ n ≠ 0 :=
  λ h₁, h $ by rw [pow_succ, h₁, mul_zero],
have h₂ : leading_coeff p * leading_coeff (p ^ n) ≠ 0 :=
  by rwa [pow_succ, ← ih h₁] at h,
by rw [pow_succ, pow_succ, leading_coeff_mul' h₂, ih h₁]

lemma degree_pow' : ∀ {n : ℕ}, leading_coeff p ^ n ≠ 0 →
  degree (p ^ n) = n • (degree p)
| 0     := λ h, by rw [pow_zero, ← C_1] at *;
  rw [degree_C h, zero_nsmul]
| (n+1) := λ h,
have h₁ : leading_coeff p ^ n ≠ 0 := λ h₁, h $
  by rw [pow_succ, h₁, mul_zero],
have h₂ : leading_coeff p * leading_coeff (p ^ n) ≠ 0 :=
  by rwa [pow_succ, ← leading_coeff_pow' h₁] at h,
by rw [pow_succ, degree_mul' h₂, succ_nsmul, degree_pow' h₁]

lemma nat_degree_pow' {n : ℕ} (h : leading_coeff p ^ n ≠ 0) :
  nat_degree (p ^ n) = n * nat_degree p :=
if hp0 : p = 0 then
  if hn0 : n = 0 then by simp *
  else by rw [hp0, zero_pow (nat.pos_of_ne_zero hn0)]; simp
else
have hpn : p ^ n ≠ 0, from λ hpn0,  have h1 : _ := h,
  by rw [← leading_coeff_pow' h1, hpn0, leading_coeff_zero] at h;
  exact h rfl,
option.some_inj.1 $ show (nat_degree (p ^ n) : with_bot ℕ) = (n * nat_degree p : ℕ),
  by rw [← degree_eq_nat_degree hpn, degree_pow' h, degree_eq_nat_degree hp0,
    ← with_bot.coe_nsmul]; simp

theorem leading_coeff_mul_monic {p q : polynomial R} (hq : monic q) :
  leading_coeff (p * q) = leading_coeff p :=
decidable.by_cases
  (λ H : leading_coeff p = 0, by rw [H, leading_coeff_eq_zero.1 H, zero_mul, leading_coeff_zero])
  (λ H : leading_coeff p ≠ 0,
    by rw [leading_coeff_mul', hq.leading_coeff, mul_one];
      rwa [hq.leading_coeff, mul_one])

@[simp] theorem leading_coeff_mul_X_pow {p : polynomial R} {n : ℕ} :
  leading_coeff (p * X ^ n) = leading_coeff p :=
leading_coeff_mul_monic (monic_X_pow n)

@[simp] theorem leading_coeff_mul_X {p : polynomial R} :
  leading_coeff (p * X) = leading_coeff p :=
leading_coeff_mul_monic monic_X

lemma nat_degree_mul_le {p q : polynomial R} : nat_degree (p * q) ≤ nat_degree p + nat_degree q :=
begin
  apply nat_degree_le_of_degree_le,
  apply le_trans (degree_mul_le p q),
  rw with_bot.coe_add,
  refine add_le_add _ _; apply degree_le_nat_degree,
end

lemma subsingleton_of_monic_zero (h : monic (0 : polynomial R)) :
  (∀ p q : polynomial R, p = q) ∧ (∀ a b : R, a = b) :=
by rw [monic.def, leading_coeff_zero] at h;
  exact ⟨λ p q, by rw [← mul_one p, ← mul_one q, ← C_1, ← h, C_0, mul_zero, mul_zero],
    λ a b, by rw [← mul_one a, ← mul_one b, ← h, mul_zero, mul_zero]⟩

lemma zero_le_degree_iff {p : polynomial R} : 0 ≤ degree p ↔ p ≠ 0 :=
by rw [ne.def, ← degree_eq_bot];
  cases degree p; exact dec_trivial

lemma degree_nonneg_iff_ne_zero : 0 ≤ degree p ↔ p ≠ 0 :=
⟨λ h0p hp0, absurd h0p (by rw [hp0, degree_zero]; exact dec_trivial),
  λ hp0, le_of_not_gt (λ h, by simp [gt, degree_eq_bot, *] at *)⟩

lemma nat_degree_eq_zero_iff_degree_le_zero : p.nat_degree = 0 ↔ p.degree ≤ 0 :=
by rw [← nonpos_iff_eq_zero, nat_degree_le_iff_degree_le, with_bot.coe_zero]

theorem degree_le_iff_coeff_zero (f : polynomial R) (n : with_bot ℕ) :
  degree f ≤ n ↔ ∀ m : ℕ, n < m → coeff f m = 0 :=
⟨λ (H : finset.sup (f.support) some ≤ n) m (Hm : n < (m : with_bot ℕ)), decidable.of_not_not $ λ H4,
  have H1 : m ∉ f.support,
    from λ H2, not_lt_of_ge ((finset.sup_le_iff.1 H) m H2 : ((m : with_bot ℕ) ≤ n)) Hm,
  H1 $ (finsupp.mem_support_to_fun f m).2 H4,
λ H, finset.sup_le $ λ b Hb, decidable.of_not_not $ λ Hn,
  (finsupp.mem_support_to_fun f b).1 Hb $ H b $ lt_of_not_ge Hn⟩

theorem degree_lt_iff_coeff_zero (f : polynomial R) (n : ℕ) :
  degree f < n ↔ ∀ m : ℕ, n ≤ m → coeff f m = 0 :=
begin
  refine ⟨λ hf m hm, coeff_eq_zero_of_degree_lt (lt_of_lt_of_le hf (with_bot.coe_le_coe.2 hm)), _⟩,
  simp only [degree, finset.sup_lt_iff (with_bot.bot_lt_coe n), mem_support_iff,
    with_bot.some_eq_coe, with_bot.coe_lt_coe, ← @not_le ℕ],
  exact λ h m, mt (h m),
end

lemma degree_lt_degree_mul_X (hp : p ≠ 0) : p.degree < (p * X).degree :=
by haveI := nontrivial.of_polynomial_ne hp; exact
have leading_coeff p * leading_coeff X ≠ 0, by simpa,
by erw [degree_mul' this, degree_eq_nat_degree hp,
    degree_X, ← with_bot.coe_one, ← with_bot.coe_add, with_bot.coe_lt_coe];
  exact nat.lt_succ_self _

lemma nat_degree_pos_iff_degree_pos :
  0 < nat_degree p ↔ 0 < degree p :=
lt_iff_lt_of_le_iff_le nat_degree_le_iff_degree_le

lemma eq_C_of_nat_degree_le_zero (h : nat_degree p ≤ 0) : p = C (coeff p 0) :=
eq_C_of_degree_le_zero $ degree_le_of_nat_degree_le h

lemma eq_C_of_nat_degree_eq_zero (h : nat_degree p = 0) : p = C (coeff p 0) :=
eq_C_of_nat_degree_le_zero h.le

lemma ne_zero_of_coe_le_degree (hdeg : ↑n ≤ p.degree) : p ≠ 0 :=
by rw ← degree_nonneg_iff_ne_zero; exact trans (by exact_mod_cast n.zero_le) hdeg

lemma le_nat_degree_of_coe_le_degree (hdeg : ↑n ≤ p.degree) :
  n ≤ p.nat_degree :=
with_bot.coe_le_coe.mp ((degree_eq_nat_degree $ ne_zero_of_coe_le_degree hdeg) ▸ hdeg)

end semiring

section nonzero_semiring
variables [semiring R] [nontrivial R] {p q : polynomial R}

@[simp] lemma degree_X_pow (n : ℕ) : degree ((X : polynomial R) ^ n) = n :=
by rw [X_pow_eq_monomial, degree_monomial _ (@one_ne_zero R _ _)]

@[simp] lemma nat_degree_X_pow (n : ℕ) : nat_degree ((X : polynomial R) ^ n) = n :=
nat_degree_eq_of_degree_eq_some (degree_X_pow n)

theorem not_is_unit_X : ¬ is_unit (X : polynomial R) :=
λ ⟨⟨_, g, hfg, hgf⟩, rfl⟩, @zero_ne_one R _ _ $ by { rw [← coeff_one_zero, ← hgf], simp }

@[simp] lemma degree_mul_X : degree (p * X) = degree p + 1 := by simp [degree_mul_monic monic_X]

@[simp] lemma degree_mul_X_pow : degree (p * X ^ n) = degree p + n :=
by simp [degree_mul_monic (monic_X_pow n)]

end nonzero_semiring

section ring
variables [ring R] {p q : polynomial R}

lemma degree_sub_le (p q : polynomial R) : degree (p - q) ≤ max (degree p) (degree q) :=
by simpa only [sub_eq_add_neg, degree_neg q] using degree_add_le p (-q)

lemma degree_sub_lt (hd : degree p = degree q)
  (hp0 : p ≠ 0) (hlc : leading_coeff p = leading_coeff q) :
  degree (p - q) < degree p :=
have hp : single (nat_degree p) (leading_coeff p) + p.erase (nat_degree p) = p :=
  finsupp.single_add_erase _ _,
have hq : single (nat_degree q) (leading_coeff q) + q.erase (nat_degree q) = q :=
  finsupp.single_add_erase _ _,
have hd' : nat_degree p = nat_degree q := by unfold nat_degree; rw hd,
have hq0 : q ≠ 0 := mt degree_eq_bot.2 (hd ▸ mt degree_eq_bot.1 hp0),
calc degree (p - q) = degree (erase (nat_degree q) p + -erase (nat_degree q) q) :
  by conv {to_lhs, rw [← hp, ← hq, hlc, hd', add_sub_add_left_eq_sub, sub_eq_add_neg]}
... ≤ max (degree (erase (nat_degree q) p)) (degree (erase (nat_degree q) q))
  : degree_neg (erase (nat_degree q) q) ▸ degree_add_le _ _
... < degree p : max_lt_iff.2 ⟨hd' ▸ degree_erase_lt hp0, hd.symm ▸ degree_erase_lt hq0⟩

lemma nat_degree_X_sub_C_le {r : R} : (X - C r).nat_degree ≤ 1 :=
nat_degree_le_iff_degree_le.2 $ le_trans (degree_sub_le _ _) $ max_le degree_X_le $
le_trans degree_C_le $ with_bot.coe_le_coe.2 zero_le_one

lemma degree_sum_fin_lt {n : ℕ} (f : fin n → R) :
  degree (∑ i : fin n, C (f i) * X ^ (i : ℕ)) < n :=
begin
  haveI : is_commutative (with_bot ℕ) max := ⟨max_comm⟩,
  haveI : is_associative (with_bot ℕ) max := ⟨max_assoc⟩,
  calc  (∑ i, C (f i) * X ^ (i : ℕ)).degree
      ≤ finset.univ.fold (⊔) ⊥ (λ i, (C (f i) * X ^ (i : ℕ)).degree) : degree_sum_le _ _
  ... = finset.univ.fold max ⊥ (λ i, (C (f i) * X ^ (i : ℕ)).degree) :
    (@finset.fold_hom _ _ _ (⊔) _ _ _ ⊥ finset.univ _ _ _ id (with_bot.sup_eq_max)).symm
  ... < n : (finset.fold_max_lt (n : with_bot ℕ)).mpr ⟨with_bot.bot_lt_some _, _⟩,

  rintros ⟨i, hi⟩ -,
  calc (C (f ⟨i, hi⟩) * X ^ i).degree
      ≤ (C _).degree + (X ^ i).degree : degree_mul_le _ _
  ... ≤ 0 + i : add_le_add degree_C_le (degree_X_pow_le i)
  ... = i : zero_add _
  ... < n : with_bot.some_lt_some.mpr hi,
end

lemma degree_sub_eq_left_of_degree_lt (h : degree q < degree p) : degree (p - q) = degree p :=
by { rw ← degree_neg q at h, rw [sub_eq_add_neg, degree_add_eq_left_of_degree_lt h] }

lemma degree_sub_eq_right_of_degree_lt (h : degree p < degree q) : degree (p - q) = degree q :=
by { rw ← degree_neg q at h, rw [sub_eq_add_neg, degree_add_eq_right_of_degree_lt h, degree_neg] }

end ring

section nonzero_ring
variables [nontrivial R] [ring R]

@[simp] lemma degree_X_sub_C (a : R) : degree (X - C a) = 1 :=
have degree (C a) < degree (X : polynomial R),
from calc degree (C a) ≤ 0 : degree_C_le
                   ... < 1 : with_bot.some_lt_some.mpr zero_lt_one
                   ... = degree X : degree_X.symm,
by rw [degree_sub_eq_left_of_degree_lt this, degree_X]

@[simp] lemma degree_X_add_C (a : R) : degree (X + C a) = 1 :=
have degree (C a) < degree (X : polynomial R),
from calc degree (C a) ≤ 0 : degree_C_le
                   ... < 1 : with_bot.some_lt_some.mpr zero_lt_one
                   ... = degree X : degree_X.symm,
by rw [degree_add_eq_left_of_degree_lt this, degree_X]

@[simp] lemma nat_degree_X_sub_C (x : R) : (X - C x).nat_degree = 1 :=
nat_degree_eq_of_degree_eq_some $ degree_X_sub_C x

@[simp]
lemma next_coeff_X_sub_C (c : R) : next_coeff (X - C c) = - c :=
by simp [next_coeff_of_pos_nat_degree]

lemma degree_X_pow_sub_C {n : ℕ} (hn : 0 < n) (a : R) :
  degree ((X : polynomial R) ^ n - C a) = n :=
have degree (C a) < degree ((X : polynomial R) ^ n),
  from calc degree (C a) ≤ 0 : degree_C_le
  ... < degree ((X : polynomial R) ^ n) : by rwa [degree_X_pow];
    exact with_bot.coe_lt_coe.2 hn,
by rw [degree_sub_eq_left_of_degree_lt this, degree_X_pow]

lemma X_pow_sub_C_ne_zero {n : ℕ} (hn : 0 < n) (a : R) :
  (X : polynomial R) ^ n - C a ≠ 0 :=
mt degree_eq_bot.2 (show degree ((X : polynomial R) ^ n - C a) ≠ ⊥,
  by rw degree_X_pow_sub_C hn a; exact dec_trivial)

theorem X_sub_C_ne_zero (r : R) : X - C r ≠ 0 :=
pow_one (X : polynomial R) ▸ X_pow_sub_C_ne_zero zero_lt_one r

theorem zero_nmem_multiset_map_X_sub_C {α : Type*} (m : multiset α) (f : α → R) :
  (0 : polynomial R) ∉ m.map (λ a, X - C (f a)) :=
λ mem, let ⟨a, _, ha⟩ := multiset.mem_map.mp mem in X_sub_C_ne_zero _ ha

lemma nat_degree_X_pow_sub_C {n : ℕ} {r : R} :
  (X ^ n - C r).nat_degree = n :=
begin
  by_cases hn : n = 0,
  { rw [hn, pow_zero, ←C_1, ←ring_hom.map_sub, nat_degree_C] },
  { exact nat_degree_eq_of_degree_eq_some (degree_X_pow_sub_C (pos_iff_ne_zero.mpr hn) r) },
end

@[simp] lemma leading_coeff_X_pow_sub_C {n : ℕ} (hn : 0 < n) {r : R} :
  (X ^ n - C r).leading_coeff = 1 :=
by rw [leading_coeff, nat_degree_X_pow_sub_C, coeff_sub, coeff_X_pow_self,
  coeff_C, if_neg (pos_iff_ne_zero.mp hn), sub_zero]

@[simp] lemma leading_coeff_X_pow_sub_one {n : ℕ} (hn : 0 < n) :
  (X ^ n - 1 : polynomial R).leading_coeff = 1 :=
leading_coeff_X_pow_sub_C hn

end nonzero_ring

section no_zero_divisors
variables [semiring R] [no_zero_divisors R] {p q : polynomial R}

@[simp] lemma degree_mul : degree (p * q) = degree p + degree q :=
if hp0 : p = 0 then by simp only [hp0, degree_zero, zero_mul, with_bot.bot_add]
else if hq0 : q = 0 then  by simp only [hq0, degree_zero, mul_zero, with_bot.add_bot]
else degree_mul' $ mul_ne_zero (mt leading_coeff_eq_zero.1 hp0)
    (mt leading_coeff_eq_zero.1 hq0)

@[simp] lemma degree_pow [nontrivial R] (p : polynomial R) (n : ℕ) :
  degree (p ^ n) = n • (degree p) :=
by induction n; [simp only [pow_zero, degree_one, zero_nsmul],
simp only [*, pow_succ, succ_nsmul, degree_mul]]

@[simp] lemma leading_coeff_mul (p q : polynomial R) : leading_coeff (p * q) =
  leading_coeff p * leading_coeff q :=
begin
  by_cases hp : p = 0,
  { simp only [hp, zero_mul, leading_coeff_zero] },
  { by_cases hq : q = 0,
    { simp only [hq, mul_zero, leading_coeff_zero] },
    { rw [leading_coeff_mul'],
      exact mul_ne_zero (mt leading_coeff_eq_zero.1 hp) (mt leading_coeff_eq_zero.1 hq) } }
end

@[simp] lemma leading_coeff_X_add_C [nontrivial R] (a b : R) (ha : a ≠ 0):
  leading_coeff (C a * X + C b) = a :=
begin
  rw [add_comm, leading_coeff_add_of_degree_lt],
  { simp },
  { simpa [degree_C ha] using lt_of_le_of_lt degree_C_le (with_bot.coe_lt_coe.2 zero_lt_one)}
end

/-- `polynomial.leading_coeff` bundled as a `monoid_hom` when `R` has `no_zero_divisors`, and thus
  `leading_coeff` is multiplicative -/
def leading_coeff_hom : polynomial R →* R :=
{ to_fun := leading_coeff,
  map_one' := by simp,
  map_mul' := leading_coeff_mul }

@[simp] lemma leading_coeff_hom_apply (p : polynomial R) :
  leading_coeff_hom p = leading_coeff p := rfl

@[simp] lemma leading_coeff_pow (p : polynomial R) (n : ℕ) :
  leading_coeff (p ^ n) = leading_coeff p ^ n :=
(leading_coeff_hom : polynomial R →* R).map_pow p n

end no_zero_divisors

end polynomial


import algebra.big_operators
import data.real.basic

open finset
open_locale big_operators

def Sum (f : ℕ → ℝ) (s : finset ℕ) :=
∑ k in s, f k

lemma SumDistr (f : ℕ → ℝ) (s : finset ℕ) (c : ℝ) : Sum (λ k, c * (f k)) s  = c * Sum f s :=
begin
  unfold Sum,
  rw mul_sum,
end

lemma SumAssoc (a : ℕ → ℝ) (b : ℕ → ℝ) (s : finset ℕ) : Sum (λ k, a k + b k) s = Sum a s + Sum b s :=
begin
  exact sum_add_distrib,
end

lemma SumComm (f : ℕ → ℝ) (l1 : list ℕ) (p : ℕ → ℕ) (h : l1 ~ (l1.map p) ) :
Sum f l1.to_finset = Sum f (l1.map p).to_finset :=
begin
  have h1 : l1.to_finset = (l1.map p).to_finset,
  { exact list.to_finset_eq_of_perm l1 (l1.map p) h,  },
  rw h1,
end

lemma GaussSum (n : ℕ) : 2 * Sum (λ k, k) (range (n+1)) = n * (n + 1) :=
begin
unfold Sum,
induction n with n IH,
{  simp, },
rw sum_range_succ,
rw mul_add,
rw IH,
norm_num,
ring,
end

lemma AuxRev (a : ℝ) (b : ℝ) (n : ℕ) (s = range (n+1)) :
Sum (λ k, a + b*(n-k)) s = Sum (λ k, a + b*k) s :=
begin
  unfold Sum,
  apply eq_of_sub_eq_zero,
  rw ← sum_sub_distrib,
  simp,
  rw ← mul_sum,
  rw ← mul_sum,
  rw ← mul_sub,
  rw sum_sub_distrib,
  rw sub_sub,
  rw ← two_mul,
  rw mul_sum,
  rw ← sum_sub_distrib,
  simp,
  right,
  rw sub_eq_zero,
  rw H,
  rw card_range,
  rw ← mul_sum,
  rw eq_comm,
  rw mul_comm ↑(n + 1) ↑n,
  rw ← Sum,
  rw GaussSum n,
  refl,
end

lemma SumArithProg (a : ℝ) (b : ℝ) (n : ℕ) (s = range (n+1)) :
Sum (λ k, a + b*k) s = (a + (1/2)*b*n) * (n + 1) :=
begin
  have h1 : Sum (λ k, a + b*k) s = Sum (λ k, a + b*(n-k)) s,
  { rw (AuxRev a b n s H), },

  have h2 : Sum (λ k, a + b*(n-k)) s = Sum (λ k, a + b*n - b*k) s,
  { ring_nf, },

  have h3 : Sum (λ k, a + b*k) s + Sum (λ k, a + b*n - b*k) s = Sum (λ k, 2*a + b*n) s,
  { rw ← SumAssoc, simp, rw ← add_assoc, rw two_mul, },

  have h4 : Sum (λ k, 1) s = n + 1,
  { unfold Sum, rw H, rw sum_const, rw card_range, simp, },

  have h5 : Sum (λ k, 2*a + b*n) s = (2*a + b*n) * (n + 1),
  { rw ← mul_one (2*a + b*n), rw SumDistr, rw mul_one, rw h4, },

  rw ← h2 at h3,
  rw ← h1 at h3,
  rw ← two_mul at h3,
  rw h5 at h3,
  rw mul_comm at h3,

  have h6 : Sum (λ k, a + b*k) s = ((2*a + b*n) * (n + 1)) / 2,
  { apply eq_div_of_mul_eq, norm_num, exact h3,},

  rw h6,
  ring,
end

lemma id220 (f : ℕ → ℝ) (s1 : finset ℕ ) (s2 : finset ℕ ) :
Sum f s1 + Sum f s2 = Sum f (s1 ∩ s2) + Sum f (s1 ∪ s2) :=
begin
  rw add_comm (Sum f (s1 ∩ s2)),
  unfold Sum,
  rw sum_union_inter,
end

lemma id221 (f : ℕ → ℝ) (s : finset ℕ) (snat : finset ℕ) (s ⊆ snat) :
 Sum f s = Sum (λ k, f k * (ite (k ∈ s) 1 0)) snat :=
begin
  have h : Sum (λ k, ((ite (k ∈ s) (f k) 0))) snat = Sum (λ k, (f k * (ite (k ∈ s) 1 0))) snat,
  { apply congr, apply congr, refl, apply funext, intro x, exact (mul_boole (x ∈ s) (f x)).symm, refl, },
  rw ← h,
  unfold Sum,
  rw sum_ite,
  rw sum_const_zero,
  rw add_zero,
  have h1 : filter (λ (x : ℕ), x ∈ s) snat = s,
  { exact inf_eq_right.mpr H, },
  rw h1,
end

lemma id222 (s1 : finset ℕ) (s2 : finset ℕ) (k : ℕ) :
 (ite (k ∈ s1) 1 0) + (ite (k ∈ s2) 1 0) = (ite (k ∈ s1 ∩ s2) 1 0) + (ite (k ∈ s1 ∪ s2) 1 0) :=
begin
  simp,
  by_cases h1 : (k ∈ s1),
  { by_cases h2 : (k ∈ s2),
    { rw (if_pos h1), rw (if_pos h2), rw (if_pos (and.intro h1 h2)), rw (if_pos (or.inl h1)), },
    rw (if_pos h1), rw (if_neg h2), have hb : (k ∈ s1 ∧ k ∈ s2) = false,
   { apply and_eq_of_eq_false_right, simp, exact h2, },
    rw (if_neg (not_of_eq_false hb)), rw (if_pos (or.inl h1)), },
  by_cases h2 : (k ∈ s2),
    { rw (if_neg h1), rw (if_pos h2), have hb : (k ∈ s1 ∧ k ∈ s2) = false,
   { apply and_eq_of_eq_false_left, simp, exact h1, },
    rw (if_neg (not_of_eq_false hb)), rw (if_pos (or.inr h2)), },
  rw (if_neg h1), rw (if_neg h2),  have hb : (k ∈ s1 ∧ k ∈ s2) = false,
   { apply and_eq_of_eq_false_right, simp, exact h2, },
    rw (if_neg (not_of_eq_false hb)), have ho : (k ∈ s1 ∨ k ∈ s2) = false,
  { simp, intro notor, apply or.elim notor h1 h2, }, rw (if_neg (not_of_eq_false ho)),
end

lemma id223 (f : ℕ → ℝ) (n : ℕ) : Sum f (range (n+1)) = f 0 + Sum f ( (range (n+1)) \ (range 1)) :=
begin
  unfold Sum,
  rw add_comm (f 0),
  rw ← (sum_range_one f),
  rw sum_sdiff,
  norm_num,
end

lemma id224 (f : ℕ → ℝ) (n : ℕ) :
Sum f (range (n+1)) + f (n+1) = f 0 + Sum (λ k, f (k+1)) (range (n+1)) :=
begin
  unfold Sum,
  rw add_comm (f 0),
  rw ← sum_range_succ',
  rw ← sum_range_succ,
end

lemma id225 (a : ℕ) (x : ℝ) (n : ℕ) (s = range (n+1)) (h : x ≠ 1) :
 Sum (λ k, a * x^k) s = (a - a * x^(n+1)) / (1 - x) :=
begin
  have h1 : Sum (λ k, a * x^k) s + (a * x^(n+1)) =
  (a * x^0) + Sum (λ k, a * x^(k+1)) s,
  {rw H, rw id224, },

  have h2 :  Sum (λ k, a * x^(k+1)) s = x * Sum (λ k, a * x^k) s,
  { rw ← SumDistr, unfold Sum, apply congr, refl, apply funext, intro k, rw pow_add, ring, },

  rw h2 at h1,
  rw [pow_zero, mul_one, ← sub_eq_zero, add_comm, ← sub_sub] at h1,

  have h3 : Sum (λ (k : ℕ), ↑a * x ^ k) s - x * Sum (λ (k : ℕ), ↑a * x ^ k) s =
   0 - ↑a * x ^ (n + 1) + ↑a,
  { rw ← sub_eq_zero, rw zero_sub, rw ← h1, ring, },

  apply eq_div_of_mul_eq,
  { rw sub_ne_zero, rw ne_comm, exact h, },
  rw [mul_comm, sub_mul, one_mul],
  rw h3,
  ring,
end

lemma id226 (x : ℝ) (n : ℕ) (h : x ≠ 1) (s = range (n + 1)) (f : ℕ → ℝ) (H2 : f = (λ b, b * x^b)) :
Sum (λ k, k * x^k) s = (x - (n + 1) * x^(n+1) + n * x^(n+2)) / (1 - x)^2 :=
begin
  have h1 : Sum f (range (n + 1)) + f (n + 1) =
  f 0 + Sum (λ k, f (k+1)) (range (n + 1)),
  { rw id224, },

  have h2 : Sum (λ k, f (k+1)) s = Sum (λ k, k * x^(k+1)) s + Sum (λ k, ↑1 * x^(k+1)) s,
  { rw H2, rw ← SumAssoc, apply congr, apply congr, refl, apply funext, intro k, simp, ring, refl, },

  have h3 : Sum (λ k, k * x^(k+1)) s = x * Sum (λ k, k * x^k) s,
  { rw ← SumDistr, unfold Sum, apply congr, refl, apply funext, intro k, rw pow_add, ring, },

  have haux : Sum (λ k, ↑1 * x^(k+1)) s = x * Sum (λ k, ↑1 * x^k) s,
  { rw ← SumDistr, unfold Sum, apply congr, refl, apply funext, intro k, rw pow_add, ring, },
  have h4 : Sum (λ k, ↑1 * x^(k+1)) s = (x - 1 * x^(n+1+1)) / (1 - x),
  { rw haux, rw pow_add, rw pow_one, rw id225, rw ← mul_div_assoc, rw mul_sub, norm_num, rw mul_comm, exact H, exact h, },

  have h5 : Sum (λ k, k * x^k) s + f (n+1) =
  x * Sum (λ k, k * x^k) s + (x - 1 * x^(n+2)) / (1 - x),
  { rw H, rw ← H2, rw h1, norm_num, rw ← H, rw h2, rw h3, rw h4, rw H2, norm_num, },

  have h6 : Sum (λ k, k * x^k) s - x * Sum (λ k, k * x^k) s =
  (x - 1 * x^(n+2)) / (1 - x) - ((n + 1) * x^(n+1)),
  { apply eq_sub_of_add_eq, rw add_comm, rw add_sub, apply eq.symm,
  apply eq_sub_of_add_eq, rw add_comm, rw ← h5, rw add_comm, rw H2, simp, },

  have h7 : Sum f s - x * Sum f s = (1 - x) * Sum f s,
  { ring,},
  rw ← H2 at h6,
  rw h7 at h6,
  have h8 : (1 - x) ≠ 0, { rw sub_ne_zero, rw ne_comm, exact h, },
  rw mul_comm at h6,
  rw ← (div_eq_iff_mul_eq h8) at h6,
  rw ← H2,

  calc
  Sum f s =
  ((x - 1 * x ^ (n + 2)) / (1 - x) - (↑n + 1) * x ^ (n + 1)) / (1 - x) :
  by { rw h6, }
  ...                       = (x - x ^ (n + 2)) / (1 - x)^2 - (↑n + 1) * x ^ (n + 1) / (1 - x) :
  by { rw sub_div, rw pow_two, rw div_div_eq_div_mul, rw one_mul, }
  ...                       = (x - x ^ (n + 2)) / (1 - x)^2 - (((↑n + 1) * x ^ (n + 1)) * (1-x)) / ((1 - x) * (1 - x)) :
  by { simp, rw (mul_div_mul_right ((↑n + 1) * x ^ (n + 1))), exact h8, }
  ...                       = ((x - x ^ (n + 2)) -  ((↑n + 1) * x ^ (n + 1)) * (1-x)) / (1 - x)^2 :
  by { rw ← pow_two, rw div_sub_div_same, }
  ...                       = ((x - x ^ (n + 2)) - ((↑n + 1) * x ^ (n + 1)) + ((↑n + 1) * x ^ (n+1+1)) ) / (1 - x)^2 :
  by { rw mul_sub, rw mul_one, rw mul_assoc, rw ← sub_add, rw pow_add x (n+1), rw pow_one, }
  ...                       = (x - (↑n + 1) * x ^ (n + 1) + (↑n + 1) * x ^ (n+2) - x ^ (n + 2) ) / (1 - x)^2 :
  by ring
  ...                       = (x - (n + 1) * x^(n+1) + n * x^(n+2)) / (1 - x)^2 :
  by ring,
end