/-
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.fintype.big_operators
import data.nat.with_bot
import data.polynomial.monomial
import data.polynomial.coeff

/-!
# Theory of univariate polynomials

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

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

open finsupp finset
open_locale big_operators classical polynomial

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

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

/-- `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 : R[X]) : with_bot ℕ := p.support.max

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

instance : has_well_founded R[X] := ⟨_, degree_lt_wf⟩

/-- `nat_degree p` forces `degree p` to ℕ, by defining nat_degree 0 = 0. -/
def nat_degree (p : R[X]) : ℕ := (degree p).unbot' 0

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

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

@[nontriviality] lemma monic_of_subsingleton [subsingleton R] (p : R[X]) : 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 : R[X]} (hp : p.monic) :
  leading_coeff p = 1 := hp

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

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

@[simp] lemma nat_degree_zero : nat_degree (0 : R[X]) = 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, support_eq_empty.1 (finset.max_eq_bot.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 : R[X]} {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 : R[X]} {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 : R[X]} {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_unbot'_bot.gc.le_u_l _

lemma nat_degree_eq_of_degree_eq [semiring S] {q : S[X]} (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 (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 degree_eq_of_le_of_coeff_ne_zero (pn : p.degree ≤ n) (p1 : p.coeff n ≠ 0) :
  p.degree = n :=
pn.antisymm (le_degree_of_ne_zero p1)

lemma nat_degree_eq_of_le_of_coeff_ne_zero (pn : p.nat_degree ≤ n) (p1 : p.coeff n ≠ 0) :
  p.nat_degree = n :=
pn.antisymm (le_nat_degree_of_ne_zero p1)

lemma degree_mono [semiring S] {f : R[X]} {g : S[X]}
  (h : f.support ⊆ g.support) : f.degree ≤ g.degree := finset.sup_mono h

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, with_bot.unbot'_coe]

theorem nat_degree_le_iff_degree_le {n : ℕ} : nat_degree p ≤ n ↔ degree p ≤ n :=
with_bot.unbot'_bot_le_iff

lemma nat_degree_lt_iff_degree_lt (hp : p ≠ 0) :
  p.nat_degree < n ↔ p.degree < ↑n :=
with_bot.unbot'_lt_iff $ degree_eq_bot.not.mpr hp

alias nat_degree_le_iff_degree_le ↔ ..

lemma nat_degree_le_nat_degree [semiring S] {q : S[X]} (hpq : p.degree ≤ q.degree) :
  p.nat_degree ≤ q.nat_degree :=
with_bot.gi_unbot'_bot.gc.monotone_l hpq

lemma nat_degree_lt_nat_degree {p q : R[X]} (hp : p ≠ 0) (hpq : p.degree < q.degree) :
  p.nat_degree < q.nat_degree :=
begin
  by_cases hq : q = 0, { exact (not_lt_bot $ hq.subst hpq).elim },
  rwa [degree_eq_nat_degree hp, degree_eq_nat_degree hq, with_bot.coe_lt_coe] at hpq
end

@[simp] lemma degree_C (ha : a ≠ 0) : degree (C a) = (0 : with_bot ℕ) :=
by rw [degree, ← monomial_zero_left, support_monomial 0 ha, max_eq_sup_coe, sup_singleton,
    with_bot.coe_zero]

lemma degree_C_le : degree (C a) ≤ 0 :=
begin
  by_cases h : a = 0,
  { rw [h, C_0], exact bot_le },
  { rw [degree_C h], exact le_rfl }
end

lemma degree_C_lt : degree (C a) < 1 := degree_C_le.trans_lt $ with_bot.coe_lt_coe.mpr zero_lt_one

lemma degree_one_le : degree (1 : R[X]) ≤ (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 : R[X]) = 0 := nat_degree_C 1

@[simp] lemma nat_degree_nat_cast (n : ℕ) : nat_degree (n : R[X]) = 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 n ha]; refl

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

lemma degree_C_mul_X (ha : a ≠ 0) : degree (C a * X) = 1 :=
by simpa only [pow_one] using degree_C_mul_X_pow 1 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 }

lemma degree_C_mul_X_le (a : R) : degree (C a * X) ≤ 1 :=
by simpa only [pow_one] using degree_C_mul_X_pow_le 1 a

@[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 [decidable_eq R] (i : ℕ) (r : R) :
  nat_degree (monomial i r) = if r = 0 then 0 else i :=
begin
  split_ifs with hr,
  { simp [hr] },
  { rw [← C_mul_X_pow_eq_monomial, nat_degree_C_mul_X_pow i r hr] }
end

lemma nat_degree_monomial_le (a : R) {m : ℕ} : (monomial m a).nat_degree ≤ m :=
begin
  rw polynomial.nat_degree_monomial,
  split_ifs,
  exacts [nat.zero_le _, rfl.le],
end

lemma nat_degree_monomial_eq (i : ℕ) {r : R} (r0 : r ≠ 0) :
  (monomial i r).nat_degree = i :=
eq.trans (nat_degree_monomial _ _) (if_neg r0)

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 : R[X]} {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

lemma ext_iff_nat_degree_le {p q : R[X]} {n : ℕ} (hp : p.nat_degree ≤ n) (hq : q.nat_degree ≤ n) :
  p = q ↔ (∀ i ≤ n, p.coeff i = q.coeff i) :=
begin
  refine iff.trans polynomial.ext_iff _,
  refine forall_congr (λ i, ⟨λ h _, h, λ h, _⟩),
  refine (le_or_lt i n).elim h (λ k, _),
  refine (coeff_eq_zero_of_nat_degree_lt (hp.trans_lt k)).trans
    (coeff_eq_zero_of_nat_degree_lt (hq.trans_lt k)).symm,
end

lemma ext_iff_degree_le {p q : R[X]} {n : ℕ} (hp : p.degree ≤ n) (hq : q.degree ≤ n) :
  p = q ↔ (∀ i ≤ n, p.coeff i = q.coeff i)  :=
ext_iff_nat_degree_le (nat_degree_le_of_degree_le hp) (nat_degree_le_of_degree_le hq)

@[simp] lemma coeff_nat_degree_succ_eq_zero {p : R[X]} : 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 : R[X]) (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 : R[X]) :
  p = ∑ i in p.support, monomial i (p.coeff i) :=
(sum_monomial_eq p).symm

lemma as_sum_support_C_mul_X_pow (p : R[X]) :
  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 : R[X]) {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) :=
begin
  rcases p,
  have := supp_subset_range w,
  simp only [polynomial.sum, support, coeff, nat_degree, degree] at ⊢ this,
  exact finsupp.sum_of_support_subset _ this _ (λ n hn, h n)
end

/--
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 : R[X]) {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 _)

-- TODO this is essentially a duplicate of `sum_over_range`, and should be removed.
lemma sum_fin [add_comm_monoid S]
  (f : ℕ → R → S) (hf : ∀ i, f i 0 = 0) {n : ℕ} {p : R[X]} (hn : p.degree < n) :
  ∑ (i : fin n), f i (p.coeff i) = p.sum f :=
begin
  by_cases hp : p = 0,
  { rw [hp, sum_zero_index, finset.sum_eq_zero], intros i _, exact hf i },
  rw [sum_over_range' _ hf n ((nat_degree_lt_iff_degree_lt hp).mpr hn),
    fin.sum_univ_eq_sum_range (λ i, f i (p.coeff i))],
end

lemma as_sum_range' (p : R[X]) (n : ℕ) (w : p.nat_degree < n) :
  p = ∑ i in range n, monomial i (coeff p i) :=
p.sum_monomial_eq.symm.trans $ p.sum_over_range' monomial_zero_right _ w

lemma as_sum_range (p : R[X]) :
  p = ∑ i in range (p.nat_degree + 1), monomial i (coeff p i) :=
p.sum_monomial_eq.symm.trans $ p.sum_over_range $ monomial_zero_right

lemma as_sum_range_C_mul_X_pow (p : R[X]) :
  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_rfl)).trans
  (by simp only [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 monic.eq_X_add_C (hm : p.monic) (hnd : p.nat_degree = 1) : p = X + C (p.coeff 0) :=
by rw [←one_mul X, ←C_1, ←hm.coeff_nat_degree, hnd, ←eq_X_add_C_of_nat_degree_le_one hnd.le]

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 : R[X]) ≤ n :=
by simpa only [C_1, one_mul] using degree_C_mul_X_pow_le n (1:R)

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

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

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' n c 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' n c),
end

lemma card_supp_le_succ_nat_degree (p : R[X]) : 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]

end semiring

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

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

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

@[simp] lemma nat_degree_X : (X : R[X]).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 : R[X]} {r : R} {a : ℕ} :
  coeff (p * (X - C r)) (a + 1) = coeff p a - coeff p (a + 1) * r :=
by simp [mul_sub]

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

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

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

@[simp] lemma leading_coeff_neg (p : R[X]) : (-p).leading_coeff = -p.leading_coeff :=
by rw [leading_coeff, leading_coeff, nat_degree_neg, coeff_neg]

end ring

section semiring
variables [semiring R]

/-- The second-highest coefficient, or 0 for constants -/
def next_coeff (p : R[X]) : 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 : R[X]) (hp : 0 < p.nat_degree) :
  next_coeff p = p.coeff (p.nat_degree - 1) :=
by { rw [next_coeff, if_neg], contrapose! hp, simpa }

variables {p q : R[X]} {ι : 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_rfl)

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 : R[X]) : degree (p + q) ≤ max (degree p) (degree q) :=
calc degree (p + q) = ((p + q).support).sup some : rfl
  ... ≤ (p.support ∪ q.support).sup some : sup_mono support_add
  ... = p.support.sup some ⊔ q.support.sup some : sup_union

lemma degree_add_le_of_degree_le {p q : R[X]} {n : ℕ} (hp : degree p ≤ n)
  (hq : degree q ≤ n) : degree (p + q) ≤ n :=
(degree_add_le p q).trans $ max_le hp hq

lemma nat_degree_add_le (p q : R[X]) :
  nat_degree (p + q) ≤ max (nat_degree p) (nat_degree q) :=
begin
  cases le_max_iff.1 (degree_add_le p q);
  simp [nat_degree_le_nat_degree h]
end

lemma nat_degree_add_le_of_degree_le {p q : R[X]} {n : ℕ} (hp : nat_degree p ≤ n)
  (hq : nat_degree q ≤ n) : nat_degree (p + q) ≤ n :=
(nat_degree_add_le p q).trans $ max_le hp hq

@[simp] lemma leading_coeff_zero : leading_coeff (0 : R[X]) = 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 :=
by { rw mem_support_iff, exact (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 nat_degree_add_eq_left_of_nat_degree_lt (h : nat_degree q < nat_degree p) :
  nat_degree (p + q) = nat_degree p :=
nat_degree_eq_of_degree_eq (degree_add_eq_left_of_degree_lt (degree_lt_degree h))

lemma nat_degree_add_eq_right_of_nat_degree_lt (h : nat_degree p < nat_degree q) :
  nat_degree (p + q) = nat_degree q :=
nat_degree_eq_of_degree_eq (degree_add_eq_right_of_degree_lt (degree_lt_degree 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_rfl
  | 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_rfl
  end

lemma degree_erase_le (p : R[X]) (n : ℕ) : degree (p.erase n) ≤ degree p :=
by { rcases p, simp only [erase, degree, coeff, support], convert sup_mono (erase_subset _ _) }

lemma degree_erase_lt (hp : p ≠ 0) : degree (p.erase (nat_degree p)) < degree p :=
begin
  apply lt_of_le_of_ne (degree_erase_le _ _),
  rw [degree_eq_nat_degree hp, degree, support_erase],
  exact λ h, not_mem_erase _ _ (mem_of_max h),
end

lemma degree_update_le (p : R[X]) (n : ℕ) (a : R) :
  degree (p.update n a) ≤ max (degree p) n :=
begin
  rw [degree, support_update],
  split_ifs,
  { exact (finset.max_mono (erase_subset _ _)).trans (le_max_left _ _) },
  { rw [max_insert, max_comm],
    exact le_rfl },
end

lemma degree_sum_le (s : finset ι) (f : ι → R[X]) :
  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, sup_eq_max]; exact max_le_max le_rfl ih

lemma degree_mul_le (p q : R[X]) : 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)))) :
    begin
      simp only [← C_mul_X_pow_eq_monomial.symm],
      convert degree_sum_le _ _,
      exact mul_eq_sum_sum
    end
  ... ≤ 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 : R[X]) : ∀ (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_rfl (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, if_neg ha, coeff_monomial], simp }
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]

lemma leading_coeff_C_mul_X (a : R) : leading_coeff (C a * X) = a :=
by simpa only [pow_one] using leading_coeff_C_mul_X_pow a 1

@[simp]

@[simp] lemma leading_coeff_X_pow (n : ℕ) : leading_coeff ((X : R[X]) ^ 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 : R[X]) = 1 :=
by simpa only [pow_one] using @leading_coeff_X_pow R _ 1

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

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

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

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

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

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

lemma monic_of_nat_degree_le_of_coeff_eq_one (n : ℕ) (pn : p.nat_degree ≤ n) (p1 : p.coeff n = 1) :
  monic p :=
begin
  nontriviality,
  refine (congr_arg _ $ nat_degree_eq_of_le_of_coeff_ne_zero pn _).trans p1,
  exact ne_of_eq_of_ne p1 one_ne_zero,
end

lemma monic_of_degree_le_of_coeff_eq_one (n : ℕ) (pn : p.degree ≤ n) (p1 : p.coeff n = 1) :
  monic p :=
monic_of_nat_degree_le_of_coeff_eq_one n (nat_degree_le_of_degree_le pn) p1

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 : R[X]) :
  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 monic.degree_mul (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 monomial_nat_degree_leading_coeff_eq_self (h : p.support.card ≤ 1) :
  monomial p.nat_degree p.leading_coeff = p :=
begin
  rcases card_support_le_one_iff_monomial.1 h with ⟨n, a, rfl⟩,
  by_cases ha : a = 0;
  simp [ha]
end

lemma C_mul_X_pow_eq_self (h : p.support.card ≤ 1) :
  C p.leading_coeff * X^p.nat_degree = p :=
by rw [C_mul_X_pow_eq_monomial, monomial_nat_degree_leading_coeff_eq_self h]

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_monic_mul {p q : R[X]} (hp : monic p) :
  leading_coeff (p * q) = leading_coeff q :=
begin
  rcases eq_or_ne q 0 with rfl|H,
  { simp },
  { rw [leading_coeff_mul', hp.leading_coeff, one_mul],
    rwa [hp.leading_coeff, one_mul, ne.def, leading_coeff_eq_zero] }
end

theorem leading_coeff_mul_monic {p q : R[X]} (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 : R[X]} {n : ℕ} :
  leading_coeff (p * X ^ n) = leading_coeff p :=
leading_coeff_mul_monic (monic_X_pow n)

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

lemma nat_degree_mul_le {p q : R[X]} : 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 nat_degree_pow_le {p : R[X]} {n : ℕ} : (p ^ n).nat_degree ≤ n * p.nat_degree :=
begin
  induction n with i hi,
  { simp },
  { rw [pow_succ, nat.succ_mul, add_comm],
    apply le_trans nat_degree_mul_le,
    exact add_le_add_left hi _ }
end

@[simp] lemma coeff_pow_mul_nat_degree (p : R[X]) (n : ℕ) :
  (p ^ n).coeff (n * p.nat_degree) = p.leading_coeff ^ n :=
begin
  induction n with i hi,
  { simp },
  { rw [pow_succ', pow_succ', nat.succ_mul],
    by_cases hp1 : p.leading_coeff ^ i = 0,
    { rw [hp1, zero_mul],
      by_cases hp2 : p ^ i = 0,
      { rw [hp2, zero_mul, coeff_zero] },
      { apply coeff_eq_zero_of_nat_degree_lt,
        have h1 : (p ^ i).nat_degree < i * p.nat_degree,
        { apply lt_of_le_of_ne nat_degree_pow_le (λ h, hp2 _),
          rw [←h, hp1] at hi,
          exact leading_coeff_eq_zero.mp hi },
        calc (p ^ i * p).nat_degree ≤ (p ^ i).nat_degree + p.nat_degree : nat_degree_mul_le
                                ... < i * p.nat_degree + p.nat_degree : add_lt_add_right h1 _ } },
    { rw [←nat_degree_pow' hp1, ←leading_coeff_pow' hp1],
      exact coeff_mul_degree_add_degree _ _ } }
end

lemma zero_le_degree_iff : 0 ≤ degree p ↔ p ≠ 0 :=
by rw [← not_lt, nat.with_bot.lt_zero_iff, degree_eq_bot]

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 : R[X]) (n : with_bot ℕ) :
  degree f ≤ n ↔ ∀ m : ℕ, n < m → coeff f m = 0 :=
by simp only [degree, finset.max, finset.sup_le_iff, mem_support_iff, ne.def, ← not_le,
    not_imp_comm]

theorem degree_lt_iff_coeff_zero (f : R[X]) (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 ℕ, max_eq_sup_coe],
  exact λ h m, mt (h m),
end

lemma degree_smul_le (a : R) (p : R[X]) : degree (a • p) ≤ degree p :=
begin
  apply (degree_le_iff_coeff_zero _ _).2 (λ m hm, _),
  rw degree_lt_iff_coeff_zero at hm,
  simp [hm m le_rfl],
end

lemma nat_degree_smul_le (a : R) (p : R[X]) : nat_degree (a • p) ≤ nat_degree p :=
nat_degree_le_nat_degree (degree_smul_le a p)

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 :=
zero_le_degree_iff.mp $ (with_bot.coe_le_coe.mpr n.zero_le).trans 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)

lemma degree_sum_fin_lt {n : ℕ} (f : fin n → R) :
  degree (∑ i : fin n, C (f i) * X ^ (i : ℕ)) < n :=
(degree_sum_le _ _).trans_lt $ (finset.sup_lt_iff $ with_bot.bot_lt_coe n).2 $
  λ k hk, (degree_C_mul_X_pow_le _ _).trans_lt $ with_bot.coe_lt_coe.2 k.is_lt

lemma degree_linear_le : degree (C a * X + C b) ≤ 1 :=
degree_add_le_of_degree_le (degree_C_mul_X_le _) $ le_trans degree_C_le nat.with_bot.coe_nonneg

lemma degree_linear_lt : degree (C a * X + C b) < 2 :=
degree_linear_le.trans_lt $ with_bot.coe_lt_coe.mpr one_lt_two

lemma degree_C_lt_degree_C_mul_X (ha : a ≠ 0) : degree (C b) < degree (C a * X) :=
by simpa only [degree_C_mul_X ha] using degree_C_lt

@[simp] lemma degree_linear (ha : a ≠ 0) : degree (C a * X + C b) = 1 :=
by rw [degree_add_eq_left_of_degree_lt $ degree_C_lt_degree_C_mul_X ha, degree_C_mul_X ha]

lemma nat_degree_linear_le : nat_degree (C a * X + C b) ≤ 1 :=
nat_degree_le_of_degree_le degree_linear_le

@[simp] lemma nat_degree_linear (ha : a ≠ 0) : nat_degree (C a * X + C b) = 1 :=
nat_degree_eq_of_degree_eq_some $ degree_linear ha

@[simp] lemma leading_coeff_linear (ha : a ≠ 0): leading_coeff (C a * X + C b) = a :=
by rw [add_comm, leading_coeff_add_of_degree_lt (degree_C_lt_degree_C_mul_X ha),
       leading_coeff_C_mul_X]

lemma degree_quadratic_le : degree (C a * X ^ 2 + C b * X + C c) ≤ 2 :=
by simpa only [add_assoc] using degree_add_le_of_degree_le (degree_C_mul_X_pow_le 2 a)
  (le_trans degree_linear_le $ with_bot.coe_le_coe.mpr one_le_two)

lemma degree_quadratic_lt : degree (C a * X ^ 2 + C b * X + C c) < 3 :=
degree_quadratic_le.trans_lt $ with_bot.coe_lt_coe.mpr $ lt_add_one 2

lemma degree_linear_lt_degree_C_mul_X_sq (ha : a ≠ 0) :
  degree (C b * X + C c) < degree (C a * X ^ 2) :=
by simpa only [degree_C_mul_X_pow 2 ha] using degree_linear_lt

@[simp] lemma degree_quadratic (ha : a ≠ 0) : degree (C a * X ^ 2 + C b * X + C c) = 2 :=
begin
  rw [add_assoc, degree_add_eq_left_of_degree_lt $ degree_linear_lt_degree_C_mul_X_sq ha,
      degree_C_mul_X_pow 2 ha],
  refl
end

lemma nat_degree_quadratic_le : nat_degree (C a * X ^ 2 + C b * X + C c) ≤ 2 :=
nat_degree_le_of_degree_le degree_quadratic_le

@[simp] lemma nat_degree_quadratic (ha : a ≠ 0) : nat_degree (C a * X ^ 2 + C b * X + C c) = 2 :=
nat_degree_eq_of_degree_eq_some $ degree_quadratic ha

@[simp] lemma leading_coeff_quadratic (ha : a ≠ 0) :
  leading_coeff (C a * X ^ 2 + C b * X + C c) = a :=
by rw [add_assoc, add_comm, leading_coeff_add_of_degree_lt $
         degree_linear_lt_degree_C_mul_X_sq ha, leading_coeff_C_mul_X_pow]

lemma degree_cubic_le : degree (C a * X ^ 3 + C b * X ^ 2 + C c * X + C d) ≤ 3 :=
by simpa only [add_assoc] using degree_add_le_of_degree_le (degree_C_mul_X_pow_le 3 a)
  (le_trans degree_quadratic_le $ with_bot.coe_le_coe.mpr $ nat.le_succ 2)

lemma degree_cubic_lt : degree (C a * X ^ 3 + C b * X ^ 2 + C c * X + C d) < 4 :=
degree_cubic_le.trans_lt $ with_bot.coe_lt_coe.mpr $ lt_add_one 3

lemma degree_quadratic_lt_degree_C_mul_X_cb (ha : a ≠ 0) :
  degree (C b * X ^ 2 + C c * X + C d) < degree (C a * X ^ 3) :=
by simpa only [degree_C_mul_X_pow 3 ha] using degree_quadratic_lt

@[simp] lemma degree_cubic (ha : a ≠ 0) : degree (C a * X ^ 3 + C b * X ^ 2 + C c * X + C d) = 3 :=
begin
  rw [add_assoc, add_assoc, ← add_assoc (C b * X ^ 2), degree_add_eq_left_of_degree_lt $
        degree_quadratic_lt_degree_C_mul_X_cb ha, degree_C_mul_X_pow 3 ha],
  refl
end

lemma nat_degree_cubic_le : nat_degree (C a * X ^ 3 + C b * X ^ 2 + C c * X + C d) ≤ 3 :=
nat_degree_le_of_degree_le degree_cubic_le

@[simp] lemma nat_degree_cubic (ha : a ≠ 0) :
  nat_degree (C a * X ^ 3 + C b * X ^ 2 + C c * X + C d) = 3 :=
nat_degree_eq_of_degree_eq_some $ degree_cubic ha

@[simp] lemma leading_coeff_cubic (ha : a ≠ 0):
  leading_coeff (C a * X ^ 3 + C b * X ^ 2 + C c * X + C d) = a :=
by rw [add_assoc, add_assoc, ← add_assoc (C b * X ^ 2), add_comm, leading_coeff_add_of_degree_lt $
         degree_quadratic_lt_degree_C_mul_X_cb ha, leading_coeff_C_mul_X_pow]

end semiring

section nontrivial_semiring
variables [semiring R] [nontrivial R] {p q : R[X]}

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

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

/-  This lemma explicitly does not require the `nontrivial R` assumption. -/
lemma nat_degree_X_pow_le {R : Type*} [semiring R] (n : ℕ) :
  (X ^ n : R[X]).nat_degree ≤ n :=
begin
  nontriviality R,
  rwa polynomial.nat_degree_X_pow,
end

theorem not_is_unit_X : ¬ is_unit (X : R[X]) :=
λ ⟨⟨_, g, hfg, hgf⟩, rfl⟩, zero_ne_one' R $
by { change g * monomial 1 1 = 1 at hgf, rw [← coeff_one_zero, ← hgf], simp }

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

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

end nontrivial_semiring

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

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

lemma nat_degree_sub_le (p q : R[X]) : nat_degree (p - q) ≤ max (nat_degree p) (nat_degree q) :=
by simpa only [← nat_degree_neg q] using nat_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 : monomial (nat_degree p) (leading_coeff p) + p.erase (nat_degree p) = p :=
  monomial_add_erase _ _,
have hq : monomial (nat_degree q) (leading_coeff q) + q.erase (nat_degree q) = q :=
  monomial_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 degree_X_sub_C_le (r : R) : (X - C r).degree ≤ 1 :=
(degree_sub_le _ _).trans (max_le degree_X_le (degree_C_le.trans zero_le_one))

lemma nat_degree_X_sub_C_le (r : R) : (X - C r).nat_degree ≤ 1 :=
nat_degree_le_iff_degree_le.2 $ degree_X_sub_C_le r

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] }

lemma nat_degree_sub_eq_left_of_nat_degree_lt (h : nat_degree q < nat_degree p) :
  nat_degree (p - q) = nat_degree p :=
nat_degree_eq_of_degree_eq (degree_sub_eq_left_of_degree_lt (degree_lt_degree h))

lemma nat_degree_sub_eq_right_of_nat_degree_lt (h : nat_degree p < nat_degree q) :
  nat_degree (p - q) = nat_degree q :=
nat_degree_eq_of_degree_eq (degree_sub_eq_right_of_degree_lt (degree_lt_degree h))

end ring

section nonzero_ring
variables [nontrivial R]

section semiring
variable [semiring R]

@[simp] lemma degree_X_add_C (a : R) : degree (X + C a) = 1 :=
have degree (C a) < degree (X : R[X]),
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_add_C (x : R) : (X + C x).nat_degree = 1 :=
nat_degree_eq_of_degree_eq_some $ degree_X_add_C x

@[simp]
lemma next_coeff_X_add_C [semiring S] (c : S) : next_coeff (X + C c) = c :=
begin
  nontriviality S,
  simp [next_coeff_of_pos_nat_degree]
end

lemma degree_X_pow_add_C {n : ℕ} (hn : 0 < n) (a : R) :
  degree ((X : R[X]) ^ n + C a) = n :=
have degree (C a) < degree ((X : R[X]) ^ n),
  from degree_C_le.trans_lt $ by rwa [degree_X_pow, with_bot.coe_pos],
by rw [degree_add_eq_left_of_degree_lt this, degree_X_pow]

lemma X_pow_add_C_ne_zero {n : ℕ} (hn : 0 < n) (a : R) :
  (X : R[X]) ^ n + C a ≠ 0 :=
mt degree_eq_bot.2 (show degree ((X : R[X]) ^ n + C a) ≠ ⊥,
  by rw degree_X_pow_add_C hn a; exact dec_trivial)

theorem X_add_C_ne_zero (r : R) : X + C r ≠ 0 :=
pow_one (X : R[X]) ▸ X_pow_add_C_ne_zero zero_lt_one r

theorem zero_nmem_multiset_map_X_add_C {α : Type*} (m : multiset α) (f : α → R) :
  (0 : R[X]) ∉ m.map (λ a, X + C (f a)) :=
λ mem, let ⟨a, _, ha⟩ := multiset.mem_map.mp mem in X_add_C_ne_zero _ ha

lemma nat_degree_X_pow_add_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_add, nat_degree_C] },
  { exact nat_degree_eq_of_degree_eq_some (degree_X_pow_add_C (pos_iff_ne_zero.mpr hn) r) },
end

lemma X_pow_add_C_ne_one {n : ℕ} (hn : 0 < n) (a : R) : (X : R[X]) ^ n + C a ≠ 1 :=
λ h, hn.ne' $ by simpa only [nat_degree_X_pow_add_C, nat_degree_one] using congr_arg nat_degree h

theorem X_add_C_ne_one (r : R) : X + C r ≠ 1 :=
pow_one (X : R[X]) ▸ X_pow_add_C_ne_one zero_lt_one r

end semiring
end nonzero_ring

section semiring
variable [semiring R]

@[simp] lemma leading_coeff_X_pow_add_C {n : ℕ} (hn : 0 < n) {r : R} :
  (X ^ n + C r).leading_coeff = 1 :=
begin
  nontriviality R,
  rw [leading_coeff, nat_degree_X_pow_add_C, coeff_add, coeff_X_pow_self,
    coeff_C, if_neg (pos_iff_ne_zero.mp hn), add_zero]
end

@[simp] lemma leading_coeff_X_add_C [semiring S] (r : S) :
  (X + C r).leading_coeff = 1 :=
by rw [←pow_one (X : S[X]), leading_coeff_X_pow_add_C zero_lt_one]

@[simp] lemma leading_coeff_X_pow_add_one {n : ℕ} (hn : 0 < n) :
  (X ^ n + 1 : R[X]).leading_coeff = 1 :=
leading_coeff_X_pow_add_C hn

@[simp] lemma leading_coeff_pow_X_add_C (r : R) (i : ℕ) :
  leading_coeff ((X + C r) ^ i) = 1 :=
by { nontriviality, rw leading_coeff_pow'; simp }

end semiring

section ring
variable [ring R]

@[simp] lemma leading_coeff_X_pow_sub_C {n : ℕ} (hn : 0 < n) {r : R} :
  (X ^ n - C r).leading_coeff = 1 :=
by rw [sub_eq_add_neg, ←map_neg C r, leading_coeff_X_pow_add_C hn]; apply_instance

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

variables [nontrivial R]

@[simp] lemma degree_X_sub_C (a : R) : degree (X - C a) = 1 :=
by rw [sub_eq_add_neg, ←map_neg C a, degree_X_add_C]

@[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 [ring S] (c : S) : next_coeff (X - C c) = - c :=
by rw [sub_eq_add_neg, ←map_neg C c, next_coeff_X_add_C]

lemma degree_X_pow_sub_C {n : ℕ} (hn : 0 < n) (a : R) :
  degree ((X : R[X]) ^ n - C a) = n :=
by rw [sub_eq_add_neg, ←map_neg C a, degree_X_pow_add_C hn]; apply_instance

lemma X_pow_sub_C_ne_zero {n : ℕ} (hn : 0 < n) (a : R) :
  (X : R[X]) ^ n - C a ≠ 0 :=
by { rw [sub_eq_add_neg, ←map_neg C a], exact X_pow_add_C_ne_zero hn _ }

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

theorem zero_nmem_multiset_map_X_sub_C {α : Type*} (m : multiset α) (f : α → R) :
  (0 : R[X]) ∉ 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 :=
by rw [sub_eq_add_neg, ←map_neg C r, nat_degree_X_pow_add_C]

@[simp] lemma leading_coeff_X_sub_C [ring S] (r : S) :
  (X - C r).leading_coeff = 1 :=
by rw [sub_eq_add_neg, ←map_neg C r, leading_coeff_X_add_C]

end ring

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

@[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)

/-- `degree` as a monoid homomorphism between `R[X]` and `multiplicative (with_bot ℕ)`.
  This is useful to prove results about multiplication and degree. -/
def degree_monoid_hom [nontrivial R] : R[X] →* multiplicative (with_bot ℕ) :=
{ to_fun := degree,
  map_one' := degree_one,
  map_mul' := λ _ _, degree_mul }

@[simp] lemma degree_pow [nontrivial R] (p : R[X]) (n : ℕ) :
  degree (p ^ n) = n • (degree p) :=
map_pow (@degree_monoid_hom R _ _ _) _ _

@[simp] lemma leading_coeff_mul (p q : R[X]) : 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

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

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

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

end no_zero_divisors

end polynomial


import tutorial_world.level16_exfalso --hide
open IncidencePlane --hide

/- Tactic : by_cases

## Summary

Generates two goals corresponding to a given statement being
true or false

## Details

Suppose that we want to prove a statement `P x`, where `x`
is some number. We may know how to prove it when `x ≤ 5`
and also when `x > 5`, but using a different method.
In this situation, using `by_cases h : x ≤ 5,` will produce
two new goals, the first one with `h : x ≤ 5` in the context
and the second one with `h : ¬ x ≤ 5`.
-/

/-
# Tutorial World

## Level 17: the `by_cases` tactic.

In this level, we introduce the `by_cases` tactic. Mathematicians would use it to provide a **proof by cases**.
This is useful when we need to split a proof into different cases.
For example, if we are asked to solve a goal of the form `⊢ P ∨ ¬ P`, then `by_cases h : P` will split the goal into two cases,
assuming `h : P` in the first branch, and `h : ¬ P` in the second branch. With that being said, let's try to solve this level!

[**Tip:** You may want to write the `∈` symbol to solve this level. To do so, type **`\in`** and then hit the space bar. Analogously,
you can write the `∉` symbol by typing **`\notin`** and then hitting the space bar.]

-/

/- Hint : Click here for a hint, in case you get stuck.
Starting the proof by typing `by_cases h : A ∈ r` will split the goal into two cases, assuming `h : A ∈ r` in the first branch,
and h : A ∉ r` in the second branch. Then, try to look for a tactic that changes the goal from `⊢ P ∨ Q` to either `⊢ P` or `⊢ Q`.
Still bewildered? Click on "View source" (located on the top right corner of the game screen) to see the solution.
-/

variables {Ω : Type} [IncidencePlane Ω] --hide

/- Lemma : no-side-bar
Either a point is in a line or it is not.
-/
lemma point_in_line_or_not {A : Ω}	{r : Line Ω} : A ∈ r ∨ A ∉ r :=
begin

  by_cases h : A ∈ r,
  {
    left,
    exact h,
  },
  {
    right,
    exact h,
  },

end


/-
Copyright (c) 2020 Aaron Anderson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Aaron Anderson, Jalex Stark
-/
import linear_algebra.matrix
import data.rel
import combinatorics.simple_graph.basic

/-!
# Adjacency Matrices

This module defines the adjacency matrix of a graph, and provides theorems connecting graph
properties to computational properties of the matrix.

## Main definitions

* `adj_matrix` is the adjacency matrix of a `simple_graph` with coefficients in a given semiring.

-/

open_locale big_operators matrix
open finset matrix simple_graph

universes u v
variables {α : Type u} [fintype α]
variables (R : Type v) [semiring R]

namespace simple_graph

variables (G : simple_graph α) (R) [decidable_rel G.adj]

/-- `adj_matrix G R` is the matrix `A` such that `A i j = (1 : R)` if `i` and `j` are
  adjacent in the simple graph `G`, and otherwise `A i j = 0`. -/
def adj_matrix : matrix α α R
| i j := if (G.adj i j) then 1 else 0

variable {R}

@[simp]
lemma adj_matrix_apply (v w : α) : G.adj_matrix R v w = if (G.adj v w) then 1 else 0 := rfl

@[simp]
theorem transpose_adj_matrix : (G.adj_matrix R)ᵀ = G.adj_matrix R :=
by { ext, simp [edge_symm] }

@[simp]
lemma adj_matrix_dot_product (v : α) (vec : α → R) :
  dot_product (G.adj_matrix R v) vec = ∑ u in G.neighbor_finset v, vec u :=
by simp [neighbor_finset_eq_filter, dot_product, sum_filter]

@[simp]
lemma dot_product_adj_matrix (v : α) (vec : α → R) :
  dot_product vec (G.adj_matrix R v) = ∑ u in G.neighbor_finset v, vec u :=
by simp [neighbor_finset_eq_filter, dot_product, sum_filter, sum_apply]

@[simp]
lemma adj_matrix_mul_vec_apply (v : α) (vec : α → R) :
  ((G.adj_matrix R).mul_vec vec) v = ∑ u in G.neighbor_finset v, vec u :=
by rw [mul_vec, adj_matrix_dot_product]

@[simp]
lemma adj_matrix_vec_mul_apply (v : α) (vec : α → R) :
  ((G.adj_matrix R).vec_mul vec) v = ∑ u in G.neighbor_finset v, vec u :=
begin
  rw [← dot_product_adj_matrix, vec_mul],
  refine congr rfl _, ext,
  rw [← transpose_apply (adj_matrix R G) x v, transpose_adj_matrix],
end

@[simp]
lemma adj_matrix_mul_apply (M : matrix α α R) (v w : α) :
  (G.adj_matrix R ⬝ M) v w = ∑ u in G.neighbor_finset v, M u w :=
by simp [mul_apply, neighbor_finset_eq_filter, sum_filter]

@[simp]

variable (R)
theorem trace_adj_matrix : matrix.trace α R R (G.adj_matrix R) = 0 := by simp
variable {R}

theorem adj_matrix_mul_self_apply_self (i : α) :
  ((G.adj_matrix R) ⬝ (G.adj_matrix R)) i i = degree G i :=
by simp [degree]

variable {G}

@[simp]
lemma adj_matrix_mul_vec_const_apply {r : R} {v : α} :
  (G.adj_matrix R).mul_vec (function.const _ r) v = G.degree v * r :=
by simp [degree]

lemma adj_matrix_mul_vec_const_apply_of_regular {d : ℕ} {r : R} (hd : G.is_regular_of_degree d)
  {v : α} :
  (G.adj_matrix R).mul_vec (function.const _ r) v = (d * r) :=
by simp [hd v]

end simple_graph


/-
Copyright (c) 2020 James Arthur. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: James Arthur, Chris Hughes, Shing Tak Lam
-/
import analysis.special_functions.trigonometric.deriv
import analysis.special_functions.log.basic

/-!
# Inverse of the sinh function

In this file we prove that sinh is bijective and hence has an
inverse, arsinh.

## Main definitions

- `real.arsinh`: The inverse function of `real.sinh`.

- `real.sinh_equiv`, `real.sinh_order_iso`, `real.sinh_homeomorph`: `real.sinh` as an `equiv`,
  `order_iso`, and `homeomorph`, respectively.

## Main Results

- `real.sinh_surjective`, `real.sinh_bijective`: `real.sinh` is surjective and bijective;

- `real.arsinh_injective`, `real.arsinh_surjective`, `real.arsinh_bijective`: `real.arsinh` is
  injective, surjective, and bijective;

- `real.continuous_arsinh`, `real.differentiable_arsinh`, `real.cont_diff_arsinh`: `real.arsinh` is
  continuous, differentiable, and continuously differentiable; we also provide dot notation
  convenience lemmas like `filter.tendsto.arsinh` and `cont_diff_at.arsinh`.

## Tags

arsinh, arcsinh, argsinh, asinh, sinh injective, sinh bijective, sinh surjective
-/
noncomputable theory

open function filter set
open_locale topological_space

namespace real

variables {x y : ℝ}

/-- `arsinh` is defined using a logarithm, `arsinh x = log (x + sqrt(1 + x^2))`. -/
@[pp_nodot] def arsinh (x : ℝ) := log (x + sqrt (1 + x^2))

lemma exp_arsinh (x : ℝ) : exp (arsinh x) = x + sqrt (1 + x^2) :=
begin
  apply exp_log,
  rw [← neg_lt_iff_pos_add'],
  calc -x ≤ sqrt (x ^ 2) : le_sqrt_of_sq_le (neg_pow_bit0 _ _).le
  ... < sqrt (1 + x ^ 2) : sqrt_lt_sqrt (sq_nonneg _) (lt_one_add _)
end

@[simp] lemma arsinh_zero : arsinh 0 = 0 := by simp [arsinh]

@[simp] lemma arsinh_neg (x : ℝ) : arsinh (-x) = -arsinh x :=
begin
  rw [← exp_eq_exp, exp_arsinh, exp_neg, exp_arsinh],
  apply eq_inv_of_mul_eq_one_left,
  rw [neg_sq, neg_add_eq_sub, add_comm x, mul_comm, ← sq_sub_sq, sq_sqrt, add_sub_cancel],
  exact add_nonneg zero_le_one (sq_nonneg _)
end

/-- `arsinh` is the right inverse of `sinh`. -/
@[simp] lemma sinh_arsinh (x : ℝ) : sinh (arsinh x) = x :=
by { rw [sinh_eq, ← arsinh_neg, exp_arsinh, exp_arsinh, neg_sq], field_simp }

@[simp] lemma cosh_arsinh (x : ℝ) : cosh (arsinh x) = sqrt (1 + x^2) :=
by rw [← sqrt_sq (cosh_pos _).le, cosh_sq', sinh_arsinh]

/-- `sinh` is surjective, `∀ b, ∃ a, sinh a = b`. In this case, we use `a = arsinh b`. -/
lemma sinh_surjective : surjective sinh := left_inverse.surjective sinh_arsinh

/-- `sinh` is bijective, both injective and surjective. -/
lemma sinh_bijective : bijective sinh := ⟨sinh_injective, sinh_surjective⟩

/-- `arsinh` is the left inverse of `sinh`. -/
@[simp] lemma arsinh_sinh (x : ℝ) : arsinh (sinh x) = x :=
right_inverse_of_injective_of_left_inverse sinh_injective sinh_arsinh x

/-- `real.sinh` as an `equiv`. -/
@[simps] def sinh_equiv : ℝ ≃ ℝ :=
{ to_fun := sinh,
  inv_fun := arsinh,
  left_inv := arsinh_sinh,
  right_inv := sinh_arsinh }

/-- `real.sinh` as an `order_iso`. -/
@[simps { fully_applied := ff }] def sinh_order_iso : ℝ ≃o ℝ :=
{ to_equiv := sinh_equiv,
  map_rel_iff' := @sinh_le_sinh }

/-- `real.sinh` as a `homeomorph`. -/
@[simps { fully_applied := ff }] def sinh_homeomorph : ℝ ≃ₜ ℝ := sinh_order_iso.to_homeomorph

lemma arsinh_bijective : bijective arsinh := sinh_equiv.symm.bijective
lemma arsinh_injective : injective arsinh := sinh_equiv.symm.injective
lemma arsinh_surjective : surjective arsinh := sinh_equiv.symm.surjective

lemma arsinh_strict_mono : strict_mono arsinh := sinh_order_iso.symm.strict_mono

@[simp] lemma arsinh_inj : arsinh x = arsinh y ↔ x = y := arsinh_injective.eq_iff
@[simp] lemma arsinh_le_arsinh : arsinh x ≤ arsinh y ↔ x ≤ y := sinh_order_iso.symm.le_iff_le
@[simp] lemma arsinh_lt_arsinh : arsinh x < arsinh y ↔ x < y := sinh_order_iso.symm.lt_iff_lt

@[simp] lemma arsinh_eq_zero_iff : arsinh x = 0 ↔ x = 0 :=
arsinh_injective.eq_iff' arsinh_zero

@[simp] lemma arsinh_nonneg_iff : 0 ≤ arsinh x ↔ 0 ≤ x :=
by rw [← sinh_le_sinh, sinh_zero, sinh_arsinh]

@[simp] lemma arsinh_nonpos_iff : arsinh x ≤ 0 ↔ x ≤ 0 :=
by rw [← sinh_le_sinh, sinh_zero, sinh_arsinh]

@[simp] lemma arsinh_pos_iff : 0 < arsinh x ↔ 0 < x :=
lt_iff_lt_of_le_iff_le arsinh_nonpos_iff

@[simp] lemma arsinh_neg_iff : arsinh x < 0 ↔ x < 0 :=
lt_iff_lt_of_le_iff_le arsinh_nonneg_iff

lemma has_strict_deriv_at_arsinh (x : ℝ) : has_strict_deriv_at arsinh (sqrt (1 + x ^ 2))⁻¹ x :=
begin
  convert sinh_homeomorph.to_local_homeomorph.has_strict_deriv_at_symm (mem_univ x)
    (cosh_pos _).ne' (has_strict_deriv_at_sinh _),
  exact (cosh_arsinh _).symm
end

lemma has_deriv_at_arsinh (x : ℝ) : has_deriv_at arsinh (sqrt (1 + x ^ 2))⁻¹ x :=
(has_strict_deriv_at_arsinh x).has_deriv_at

lemma differentiable_arsinh : differentiable ℝ arsinh :=
λ x, (has_deriv_at_arsinh x).differentiable_at

lemma cont_diff_arsinh {n : with_top ℕ} : cont_diff ℝ n arsinh :=
sinh_homeomorph.cont_diff_symm_deriv (λ x, (cosh_pos x).ne') has_deriv_at_sinh cont_diff_sinh

@[continuity] lemma continuous_arsinh : continuous arsinh := sinh_homeomorph.symm.continuous

end real

open real

lemma filter.tendsto.arsinh {α : Type*} {l : filter α} {f : α → ℝ} {a : ℝ}
  (h : tendsto f l (𝓝 a)) : tendsto (λ x, arsinh (f x)) l (𝓝 (arsinh a)) :=
(continuous_arsinh.tendsto _).comp h

section continuous

variables {X : Type*} [topological_space X] {f : X → ℝ} {s : set X} {a : X}

lemma continuous_at.arsinh (h : continuous_at f a) : continuous_at (λ x, arsinh (f x)) a := h.arsinh

lemma continuous_within_at.arsinh (h : continuous_within_at f s a) :
  continuous_within_at (λ x, arsinh (f x)) s a :=
h.arsinh

lemma continuous_on.arsinh (h : continuous_on f s) : continuous_on (λ x, arsinh (f x)) s :=
λ x hx, (h x hx).arsinh

lemma continuous.arsinh (h : continuous f) : continuous (λ x, arsinh (f x)) :=
continuous_arsinh.comp h

end continuous

section fderiv

variables {E : Type*} [normed_group E] [normed_space ℝ E] {f : E → ℝ} {s : set E} {a : E}
  {f' : E →L[ℝ] ℝ} {n : with_top ℕ}

lemma has_strict_fderiv_at.arsinh (hf : has_strict_fderiv_at f f' a) :
  has_strict_fderiv_at (λ x, arsinh (f x)) ((sqrt (1 + (f a) ^ 2))⁻¹ • f') a :=
(has_strict_deriv_at_arsinh _).comp_has_strict_fderiv_at a hf

lemma has_fderiv_at.arsinh (hf : has_fderiv_at f f' a) :
  has_fderiv_at (λ x, arsinh (f x)) ((sqrt (1 + (f a) ^ 2))⁻¹ • f') a :=
(has_deriv_at_arsinh _).comp_has_fderiv_at a hf

lemma has_fderiv_within_at.arsinh (hf : has_fderiv_within_at f f' s a) :
  has_fderiv_within_at (λ x, arsinh (f x)) ((sqrt (1 + (f a) ^ 2))⁻¹ • f') s a :=
(has_deriv_at_arsinh _).comp_has_fderiv_within_at a hf

lemma differentiable_at.arsinh (h : differentiable_at ℝ f a) :
  differentiable_at ℝ (λ x, arsinh (f x)) a :=
(differentiable_arsinh _).comp a h

lemma differentiable_within_at.arsinh (h : differentiable_within_at ℝ f s a) :
  differentiable_within_at ℝ (λ x, arsinh (f x)) s a :=
(differentiable_arsinh _).comp_differentiable_within_at a h

lemma differentiable_on.arsinh (h : differentiable_on ℝ f s) :
  differentiable_on ℝ (λ x, arsinh (f x)) s :=
λ x hx, (h x hx).arsinh

lemma differentiable.arsinh (h : differentiable ℝ f) :
  differentiable ℝ (λ x, arsinh (f x)) :=
differentiable_arsinh.comp h

lemma cont_diff_at.arsinh (h : cont_diff_at ℝ n f a) :
  cont_diff_at ℝ n (λ x, arsinh (f x)) a :=
cont_diff_arsinh.cont_diff_at.comp a h

lemma cont_diff_within_at.arsinh (h : cont_diff_within_at ℝ n f s a) :
  cont_diff_within_at ℝ n (λ x, arsinh (f x)) s a :=
cont_diff_arsinh.cont_diff_at.comp_cont_diff_within_at a h

lemma cont_diff.arsinh (h : cont_diff ℝ n f) : cont_diff ℝ n (λ x, arsinh (f x)) :=
cont_diff_arsinh.comp h

lemma cont_diff_on.arsinh (h : cont_diff_on ℝ n f s) : cont_diff_on ℝ n (λ x, arsinh (f x)) s :=
λ x hx, (h x hx).arsinh

end fderiv

section deriv

variables {f : ℝ → ℝ} {s : set ℝ} {a f' : ℝ}

lemma has_strict_deriv_at.arsinh (hf : has_strict_deriv_at f f' a) :
  has_strict_deriv_at (λ x, arsinh (f x)) ((sqrt (1 + (f a) ^ 2))⁻¹ • f') a :=
(has_strict_deriv_at_arsinh _).comp a hf

lemma has_deriv_at.arsinh (hf : has_deriv_at f f' a) :
  has_deriv_at (λ x, arsinh (f x)) ((sqrt (1 + (f a) ^ 2))⁻¹ • f') a :=
(has_deriv_at_arsinh _).comp a hf

lemma has_deriv_within_at.arsinh (hf : has_deriv_within_at f f' s a) :
  has_deriv_within_at (λ x, arsinh (f x)) ((sqrt (1 + (f a) ^ 2))⁻¹ • f') s a :=
(has_deriv_at_arsinh _).comp_has_deriv_within_at a hf

end deriv


def Set (α : Type u) := α → Prop
def Set.in (s : Set α) (a : α) := s a

notation:50 a " ∈ " s:50 => Set.in s a

def Set.pred (p : α → Prop) : Set α := p

notation "{" a "|" p "}" => Set.pred (fun a => p)

theorem ex1 : (1, 3) ∈ { (n, m) | n < 2 ∧ m < 5 } := by
  simp [Set.in, Set.pred]

def Set.union (s₁ s₂ : Set α) : Set α :=
  { a | a ∈ s₁ ∨ a ∈ s₂ }

infix:65 " ∪ " => Set.union

def Set.inter (s₁ s₂ : Set α) : Set α :=
  { a | a ∈ s₁ ∧ a ∈ s₂ }

infix:70 " ∩ " => Set.inter

instance (s : Set α) [h : Decidable (s a)] : Decidable (a ∈ Set.pred s) :=
  h

instance (s₁ s₂ : Set α) [Decidable (a ∈ s₁)] [Decidable (a ∈ s₂)] : Decidable (a ∈ s₁ ∩ s₂) :=
  inferInstanceAs (Decidable (_ ∧ _))

instance (s₁ s₂ : Set α) [Decidable (a ∈ s₁)] [Decidable (a ∈ s₂)] : Decidable (a ∈ s₁ ∪ s₂) :=
  inferInstanceAs (Decidable (_ ∨ _))

theorem ex2 : (1, 3) ∈ { (x, y) | x < y } :=
  by decide

theorem ex3 : (10000, 300000) ∈ { (x, y) | x < y } ∩ { (x, y) | x = 10000 } :=
  by decide


import data.real.basic

#check mul_lt_mul_right
#check @lt_of_lt_of_le

-- BEGIN
lemma my_lemma : ∀ {x y ε : ℝ},
  0 < ε → ε ≤ 1 → abs x < ε → abs y < ε → abs (x * y) < ε :=
begin
  intros x y ε epos ele1 xlt ylt,
  calc
    abs (x * y) = abs x * abs y : by rw abs_mul
    ... ≤ abs x * ε             : mul_le_mul_of_nonneg_left (le_of_lt ylt) (abs_nonneg x)
    ... < 1 * ε                 : mul_lt_mul_of_pos_right (lt_of_le_of_lt' ele1 xlt) epos
    ... = ε                     : by rw one_mul,
end
-- END


import tactic --hide

/-
## The `cases` tactic

`cases` is a very general-purpose tactic for "deconstructing" hypotheses.
If `h` is an assumption which somehow "bundles up" two pieces of information,
then  <mark style ="background-color : #ebdef0 ">`cases h with h1 h2`</mark>  will make the
assumption `h` vanish and will replace it with the two "components" which made the proof of `h` in the first place.
An example of this occurring is if you have the assumption `h : P ∧ Q` which contains a
proof of `P` and a proof of `Q`.

For example, if you have an assumption

```
h : P ∧ Q
```

then

`cases h with hP hQ,`

will delete `h` and replace it with

```
hP : P
hQ : Q
```
Try it out below.

-/

/- Lemma
If $P,Q$ are logical statements and $P ∧ Q$ is true then so is $P$.
-/

lemma cases_and_example (P Q : Prop) (h : P ∧ Q) : P :=
begin
  cases h with hP hQ,
  exact hP,
end

/- Tactic : cases

If you have a hypothesis

```
h : P ∧ Q
```

then

`cases h with hP hQ,`

will delete `h` and replace it with

```
hP : P
hQ : Q
```

You can also use it for "or's". Specifically, if you have `h : P ∨ Q` then `cases h` will
give you two goals, one where `P` is true and one where `Q` is true.

-/