/-
Copyright (c) 2022 María Inés de Frutos-Fernández. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: María Inés de Frutos-Fernández
-/
import valuation
import topology.algebra.uniform_group

/-!
# Factorization of fractional ideals of Dedekind domains
Every nonzero fractional ideal `I` of a Dedekind domain `R` can be factored as a product
`∏_v v^{n_v}` over the maximal ideals of `R`, where the exponents `n_v` are integers. We define
`fractional_ideal.count K v I` (abbreviated as `val_v(I)` in the documentation) to be `n_v`, and we
prove some of its properties. If `I = 0`, we define `val_v(I) = 0`.

## Main definitions
- `fractional_ideal.count` : If `I` is a nonzero fractional ideal, `a ∈ R`, and `J` is an ideal of
  `R` such that `I = a⁻¹J`, then we define `val_v(I)` as `(val_v(J) - val_v(a))`. If `I = 0`, we
  set `val_v(I) = 0`.

## Main results
- `ideal.factorization` : The ideal `I` equals the finprod `∏_v v^(val_v(I))`.
- `fractional_ideal.factorization` : If `I` is a nonzero fractional ideal, `a ∈ R`, and `J` is an
ideal of `R` such that `I = a⁻¹J`, then `I` is equal to the product `∏_v v^(val_v(J) - val_v(a))`.
- `fractional_ideal.factorization_principal` : For a nonzero `k = r/s ∈ K`, the fractional ideal
`(k)` is equal to the product `∏_v v^(val_v(r) - val_v(s))`.
- `fractional_ideal.finite_factors` : If `I ≠ 0`, then `val_v(I) = 0` for all but finitely many
  maximal ideals of `R`.

## Implementation notes
Since we are only interested in nonzero fractional ideals, we chose to define `val_v(I) = 0` so that
every `val_v` is in `ℤ` and we can avoid having to use `with_top ℤ`.

## Tags
dedekind domain, fractional ideal, factorization
-/

noncomputable theory
open_locale big_operators classical

open set function

/-! ### Factorization of fractional ideals of Dedekind domains -/

variables {A : Type*} [comm_ring A] (B : submonoid A) (C : Type*) [comm_ring C] [algebra A C]

/-- If a prime `p` divides a `finprod`, then it must divide one of its factors. -/
lemma prime.exists_mem_finprod_dvd {α : Type*} {N : Type*} [comm_monoid_with_zero N] {f : α → N}
  {p : N} (hp : prime p) (hf : finite (mul_support f)) :
  p ∣  ∏ᶠ i, f i →  ∃ (a : α), p ∣ f a :=
begin
  rw finprod_eq_prod _ hf,
  intro h,
  obtain ⟨a, -, ha_dvd⟩ := prime.exists_mem_finset_dvd hp h,
  exact ⟨a, ha_dvd⟩,
end

variables {R : Type*} {K : Type*} [comm_ring R] [field K] [algebra R K]

/-- The discrete topology on the units of the fractional ideals. -/
instance ufi_ts : topological_space (units (fractional_ideal (non_zero_divisors R) K)) := ⊥

/-- The units of the fractional ideals with the discrete topology are a topological group.  -/
instance ufi_tg : topological_group (units (fractional_ideal (non_zero_divisors R) K)) :=
{ continuous_mul := continuous_of_discrete_topology,
  continuous_inv := continuous_of_discrete_topology, }

instance ufi_us : uniform_space (units (fractional_ideal (non_zero_divisors R) K)) :=
topological_group.to_uniform_space _

instance ufi_ug : uniform_group (units (fractional_ideal (non_zero_divisors R) K)) :=
topological_group_is_uniform

variables [is_fraction_ring R K]

/-- If `I` is a nonzero fractional ideal, `a ∈ R`, and `J` is an ideal of `R` such that
`I = a⁻¹J`, then `J` is nonzero. -/
lemma fractional_ideal.ideal_factor_ne_zero {I : fractional_ideal (non_zero_divisors R) K}
  (hI : I ≠ 0) {a : R} {J : ideal R}
  (haJ : I = fractional_ideal.span_singleton (non_zero_divisors R) ((algebra_map R K) a)⁻¹ * ↑J) :
  J ≠ 0 :=
begin
  intro h,
  rw [h, ideal.zero_eq_bot, fractional_ideal.coe_to_fractional_ideal_bot, mul_zero] at haJ,
  exact hI haJ,
end

/-- If `I` is a nonzero fractional ideal, `a ∈ R`, and `J` is an ideal of `R` such that
`I = a⁻¹J`, then `a` is nonzero. -/
lemma fractional_ideal.constant_factor_ne_zero {I : fractional_ideal (non_zero_divisors R) K}
  (hI : I ≠ 0) {a : R} {J : ideal R}
  (haJ : I = fractional_ideal.span_singleton (non_zero_divisors R) ((algebra_map R K) a)⁻¹ * ↑J) :
  (ideal.span{a} : ideal R) ≠ 0 :=
begin
  intro h,
  rw [ideal.zero_eq_bot, ideal.span_singleton_eq_bot] at h,
  rw [h, ring_hom.map_zero, inv_zero, fractional_ideal.span_singleton_zero, zero_mul] at haJ,
  exact hI haJ,
end

open is_dedekind_domain

variables [is_domain R] [is_dedekind_domain R] (v : maximal_spectrum R)

/-- Only finitely many maximal ideals of `R` divide a given nonzero ideal. -/
lemma ideal.finite_factors {I : ideal R} (hI : I ≠ 0) :
  finite { v : maximal_spectrum R | v.as_ideal ∣ I } :=
begin
  haveI h_fin := unique_factorization_monoid.fintype_subtype_dvd I hI,
  let f' : finset (ideal R) := finset.map
    ⟨(λ J : {x // x ∣ I}, J.val), subtype.coe_injective⟩ h_fin.elems,
  have h_eq : { v : maximal_spectrum R | v.as_ideal ∣ I } =
    { v : maximal_spectrum R | v.as_ideal ∈ f' },
  { ext v,
    rw [mem_set_of_eq, mem_set_of_eq, finset.mem_map],
    simp_rw exists_prop,
    rw [subtype.exists, embedding.coe_fn_mk],
    simp_rw [exists_and_distrib_right, exists_eq_right],
    split,
    { intro h, use h, exact fintype.complete ⟨v.as_ideal, h⟩},
    { intro h, obtain ⟨hv, -⟩ := h, exact hv, }},
  rw h_eq,
  have hv : ∀ v : maximal_spectrum R, v.as_ideal = v.as_ideal := λ v, rfl,
  have hv_inj : injective (λ (v : maximal_spectrum R), v.as_ideal),
  { intros v w hvw,
    dsimp only at hvw,
    rw [hv v, hv w] at hvw,
    ext, rw hvw, },
  exact finite.preimage_embedding ⟨(λ v : maximal_spectrum R, v.as_ideal), hv_inj⟩
    (finite_mem_finset (f')),
end

/-- Only finitely many maximal ideals of `R` divide a given nonzero principal ideal. -/
lemma finite_factors (d : R) (hd : (ideal.span{d} : ideal R) ≠ 0) :
  finite { v : maximal_spectrum R | v.as_ideal ∣ (ideal.span({d}) : ideal R)} :=
ideal.finite_factors hd

/-- For every nonzero ideal `I` of `v`, there are finitely many maximal ideals `v` such that the
multiplicity of `v` in the factorization of `I`, denoted `(val_v(I))`, is nonzero. -/
lemma associates.finite_factors (I : ideal R) (hI : I ≠ 0) :
  ∀ᶠ (v : maximal_spectrum R) in filter.cofinite,
  ((associates.mk v.as_ideal).count (associates.mk I).factors : ℤ) = 0 :=
begin
  have h_supp : {v : maximal_spectrum R |
    ¬((associates.mk v.as_ideal).count (associates.mk I).factors : ℤ) = 0} =
    { v : maximal_spectrum R | v.as_ideal ∣ I },
  { ext v,
    rw [mem_set_of_eq, mem_set_of_eq, int.coe_nat_eq_zero],
    exact associates.count_ne_zero_iff_dvd hI v.irreducible,},
  rw [filter.eventually_cofinite, h_supp],
  exact ideal.finite_factors hI,
end

/-- For every nonzero ideal `I` of `v`, there are finitely many maximal ideals `v` such that
`v^(val_v(I))` is not the unit ideal. -/
lemma ideal.finite_mul_support {I : ideal R} (hI : I ≠ 0):
  (mul_support (λ (v : maximal_spectrum R),
  v.as_ideal^(associates.mk v.as_ideal).count (associates.mk I).factors)).finite :=
begin
  have h_subset : {v : maximal_spectrum R |
    v.as_ideal^
    (associates.mk v.as_ideal).count (associates.mk I).factors ≠ 1} ⊆
    {v : maximal_spectrum R |
    ((associates.mk v.as_ideal).count (associates.mk I).factors : ℤ) ≠ 0},
  { intros v hv,
    rw mem_set_of_eq at hv ⊢,
    intro h_zero,
    rw int.coe_nat_eq_zero at h_zero,
    have hv' : v.as_ideal^
      (associates.mk v.as_ideal).count (associates.mk I).factors = 1,
    { rw [h_zero, pow_zero _],},
    exact hv hv', },
  exact finite.subset (filter.eventually_cofinite.mp (associates.finite_factors I hI)) h_subset,
end

/-- For every nonzero ideal `I` of `v`, there are finitely many maximal ideals `v` such that
`v^(val_v(I))`, regarded as a fractional ideal, is not `(1)`. -/
lemma ideal.finite_mul_support_coe {I : ideal R} (hI : I ≠ 0):
  (mul_support (λ (v : maximal_spectrum R),
  (v.as_ideal : fractional_ideal (non_zero_divisors R) K)^
  ((associates.mk v.as_ideal).count (associates.mk I).factors : ℤ))).finite :=
begin
  rw mul_support,
  simp_rw [ne.def, zpow_coe_nat, ← fractional_ideal.coe_ideal_pow,
    fractional_ideal.coe_ideal_eq_one_iff],
  exact ideal.finite_mul_support hI,
end

/-- For every nonzero ideal `I` of `v`, there are finitely many maximal ideals `v` such that
`v^-(val_v(I))` is not the unit ideal. -/
lemma ideal.finite_mul_support_inv {I : ideal R} (hI : I ≠ 0):
  (mul_support (λ (v : maximal_spectrum R),
  (v.as_ideal : fractional_ideal (non_zero_divisors R) K)^
  -((associates.mk v.as_ideal).count (associates.mk I).factors : ℤ))).finite :=
begin
  rw mul_support,
  simp_rw [zpow_neg₀, ne.def, inv_eq_one₀],
  exact ideal.finite_mul_support_coe hI,
end

/-- For every nonzero ideal `I` of `v`, `v^(val_v(I) + 1)` does not divide `∏_v v^(val_v(I))`. -/
lemma ideal.finprod_not_dvd (I : ideal R) (hI : I ≠ 0) :
¬ (v.as_ideal)^
  ((associates.mk v.as_ideal).count (associates.mk I).factors + 1) ∣
    (∏ᶠ (v : maximal_spectrum R), (v.as_ideal)^
      (associates.mk v.as_ideal).count (associates.mk I).factors) :=
begin
  have hf := ideal.finite_mul_support hI,
  have h_ne_zero : v.as_ideal ^
    (associates.mk v.as_ideal).count (associates.mk I).factors ≠ 0 := pow_ne_zero _ v.ne_bot,
  rw [← mul_finprod_cond_ne v hf, pow_add, pow_one, finprod_cond_ne _ _ hf],
  intro h_contr,
  have hv_prime : prime v.as_ideal := ideal.prime_of_is_prime v.ne_bot v.is_prime,
  obtain ⟨w, hw, hvw'⟩ :=
  prime.exists_mem_finset_dvd hv_prime ((mul_dvd_mul_iff_left h_ne_zero).mp h_contr),
  have hw_prime : prime w.as_ideal := ideal.prime_of_is_prime w.ne_bot w.is_prime,
  have hvw := prime.dvd_of_dvd_pow hv_prime hvw',
  rw [prime.dvd_prime_iff_associated hv_prime hw_prime, associated_iff_eq] at hvw,
  exact (finset.mem_erase.mp hw).1 (maximal_spectrum.ext w v (eq.symm hvw)),
end

lemma associates.finprod_ne_zero (I : ideal R) :
  associates.mk (∏ᶠ (v : maximal_spectrum R), v.as_ideal ^
    (associates.mk v.as_ideal).count (associates.mk I).factors) ≠ 0 :=
begin
  rw [associates.mk_ne_zero, finprod_def],
  split_ifs,
  { rw finset.prod_ne_zero_iff,
    intros v hv,
    apply pow_ne_zero _ v.ne_bot, },
  { exact one_ne_zero, }
end

/-- The multiplicity of `v` in `∏_v v^(val_v(I))` equals `val_v(I)`. -/
lemma ideal.finprod_count (I : ideal R) (hI : I ≠ 0) :
(associates.mk v.as_ideal).count (associates.mk (∏ᶠ (v : maximal_spectrum R), (v.as_ideal)^
    (associates.mk v.as_ideal).count (associates.mk I).factors)).factors =
    (associates.mk v.as_ideal).count (associates.mk I).factors :=
begin
  have h_ne_zero := associates.finprod_ne_zero I,
  have hv : irreducible (associates.mk v.as_ideal) := v.associates_irreducible,
  have h_dvd := finprod_mem_dvd _ (ideal.finite_mul_support hI),
  have h_not_dvd := ideal.finprod_not_dvd v I hI,
  rw [← associates.mk_dvd_mk, associates.dvd_eq_le, associates.mk_pow,
    associates.prime_pow_dvd_iff_le h_ne_zero hv] at h_dvd h_not_dvd,
  rw not_le at h_not_dvd,
  apply nat.eq_of_le_of_lt_succ h_dvd h_not_dvd,
end

/-- The ideal `I` equals the finprod `∏_v v^(val_v(I))`. -/
lemma ideal.factorization (I : ideal R) (hI : I ≠ 0) : ∏ᶠ (v : maximal_spectrum R), (v.as_ideal)^
  (associates.mk v.as_ideal).count (associates.mk I).factors = I :=
begin
  rw [← associated_iff_eq, ← associates.mk_eq_mk_iff_associated],
  apply associates.eq_of_eq_counts,
  { apply associates.finprod_ne_zero I },
  { apply associates.mk_ne_zero.mpr hI},
  intros v hv,
  obtain ⟨J, hJv⟩ := associates.exists_rep v,
  rw [← hJv, associates.irreducible_mk] at hv,
  have hJ_ne_zero : J ≠ 0 := irreducible.ne_zero hv,
  rw unique_factorization_monoid.irreducible_iff_prime at hv,
  rw ← hJv,
  apply ideal.finprod_count ⟨J, ideal.is_prime_of_prime hv, hJ_ne_zero⟩ I hI,
end

/-- The ideal `I` equals the finprod `∏_v v^(val_v(I))`, when both sides are regarded as fractional
ideals of `R`. -/
lemma ideal.factorization_coe (I : ideal R) (hI : I ≠ 0) :
  ∏ᶠ (v : maximal_spectrum R), (v.as_ideal : fractional_ideal (non_zero_divisors R) K)^
    ((associates.mk v.as_ideal).count (associates.mk I).factors : ℤ) = I :=
begin
  conv_rhs{rw ← ideal.factorization I hI},
  rw fractional_ideal.coe_ideal_finprod,
  simp_rw [fractional_ideal.coe_ideal_pow, zpow_coe_nat],
  { exact le_refl _ }
end

/-- If `I` is a nonzero fractional ideal, `a ∈ R`, and `J` is an ideal of `R` such that
`I = a⁻¹J`, then `I` is equal to the product `∏_v v^(val_v(J) - val_v(a))`. -/
lemma fractional_ideal.factorization (I : fractional_ideal (non_zero_divisors R) K) (hI : I ≠ 0)
  {a : R} {J : ideal R}
  (haJ : I = fractional_ideal.span_singleton (non_zero_divisors R) ((algebra_map R K) a)⁻¹ * ↑J) :
  ∏ᶠ (v : maximal_spectrum R), (v.as_ideal : fractional_ideal (non_zero_divisors R) K)^
    ((associates.mk v.as_ideal).count (associates.mk J).factors -
    (associates.mk v.as_ideal).count (associates.mk (ideal.span{a})).factors : ℤ) = I :=
begin
  have hJ_ne_zero : J ≠ 0 := fractional_ideal.ideal_factor_ne_zero hI haJ,
  have hJ := ideal.factorization_coe J hJ_ne_zero,
  have ha_ne_zero : ideal.span{a} ≠ 0 := fractional_ideal.constant_factor_ne_zero hI haJ,
  have ha := ideal.factorization_coe (ideal.span{a} : ideal R) ha_ne_zero,
  rw [haJ, ← fractional_ideal.div_span_singleton, fractional_ideal.div_eq_mul_inv,
    ← fractional_ideal.coe_ideal_span_singleton, ← hJ, ← ha, ← finprod_inv_distrib₀],
  simp_rw ← zpow_neg₀,
  rw ← finprod_mul_distrib,
  apply finprod_congr,
  intro v,
  rw [← zpow_add₀ ((@fractional_ideal.coe_ideal_ne_zero_iff R _ K _ _ _ _).mpr v.ne_bot),
    sub_eq_add_neg],
  apply ideal.finite_mul_support_coe hJ_ne_zero,
  apply ideal.finite_mul_support_inv ha_ne_zero,
  { apply_instance },
end

/-- For a nonzero `k = r/s ∈ K`, the fractional ideal `(k)` is equal to the product
`∏_v v^(val_v(r) - val_v(s))`. -/
lemma fractional_ideal.factorization_principal (I : fractional_ideal (non_zero_divisors R) K)
  (hI : I ≠ 0) (k : K) (hk : I = fractional_ideal.span_singleton (non_zero_divisors R) k) :
  ∏ᶠ (v : maximal_spectrum R), (v.as_ideal : fractional_ideal (non_zero_divisors R) K)^
    ((associates.mk v.as_ideal).count (associates.mk (ideal.span
    {classical.some (is_localization.mk'_surjective (non_zero_divisors R) k)} : ideal R)).factors -
    (associates.mk v.as_ideal).count (associates.mk (ideal.span { (classical.some
    (classical.some_spec (is_localization.mk'_surjective (non_zero_divisors R) k)) :
    ↥(non_zero_divisors R))} : ideal R)).factors : ℤ) = I :=
begin
  set n : R := classical.some(is_localization.mk'_surjective (non_zero_divisors R) k) with hn,
  set d : ↥(non_zero_divisors R) := (classical.some (classical.some_spec
    (is_localization.mk'_surjective (non_zero_divisors R) k))) with hd,
  have hd_ne_zero : (algebra_map R K) (d : R) ≠ 0 := map_ne_zero_of_mem_non_zero_divisors
    _ (is_fraction_ring.injective R K) d.property,
  have haJ' : I = fractional_ideal.span_singleton (non_zero_divisors R) ((algebra_map R K) d)⁻¹ *
    ↑(ideal.span {n} : ideal R),
  { rw [hk, fractional_ideal.coe_ideal_span_singleton,
      fractional_ideal.span_singleton_mul_span_singleton],
    apply congr_arg,
    rw [eq_inv_mul_iff_mul_eq₀ hd_ne_zero, mul_comm,
      ← is_localization.eq_mk'_iff_mul_eq, eq_comm],
    exact classical.some_spec (classical.some_spec (is_localization.mk'_surjective
      (non_zero_divisors R) k)), },
exact fractional_ideal.factorization I hI haJ',
end

variables (K)

/-- If `I` is a nonzero fractional ideal, `a ∈ R`, and `J` is an ideal of `R` such that `I = a⁻¹J`,
then we define `val_v(I)` as `(val_v(J) - val_v(a))`. If `I = 0`, we set `val_v(I) = 0`. -/
def fractional_ideal.count (I : fractional_ideal (non_zero_divisors R) K) : ℤ :=
dite (I = 0) (λ (hI : I = 0), 0) (λ hI : ¬ I = 0,
let a := classical.some (fractional_ideal.exists_eq_span_singleton_mul I) in let
J := classical.some (classical.some_spec (fractional_ideal.exists_eq_span_singleton_mul I))
in ((associates.mk v.as_ideal).count (associates.mk J).factors -
    (associates.mk v.as_ideal).count (associates.mk (ideal.span{a})).factors : ℤ))

/-- `val_v(I)` does not depend on the choice of `a` and `J` used to represent `I`. -/
lemma fractional_ideal.count_well_defined  {I : fractional_ideal (non_zero_divisors R) K}
  (hI : I ≠ 0)  {a : R} {J : ideal R}
  (h_aJ : I = fractional_ideal.span_singleton (non_zero_divisors R) ((algebra_map R K) a)⁻¹ * ↑J) :
  fractional_ideal.count K v I = ((associates.mk v.as_ideal).count (associates.mk J).factors -
    (associates.mk v.as_ideal).count (associates.mk (ideal.span{a})).factors : ℤ) :=
begin
  set a₁ := classical.some (fractional_ideal.exists_eq_span_singleton_mul I) with h_a₁,
  set J₁ := classical.some (classical.some_spec (fractional_ideal.exists_eq_span_singleton_mul I))
    with h_J₁,
  have h_a₁J₁ : I = fractional_ideal.span_singleton (non_zero_divisors R) ((algebra_map R K) a₁)⁻¹ *
    ↑J₁ :=
(classical.some_spec (classical.some_spec (fractional_ideal.exists_eq_span_singleton_mul I))).2,
  have h_a₁_ne_zero : a₁ ≠ 0 :=
  (classical.some_spec (classical.some_spec (fractional_ideal.exists_eq_span_singleton_mul I))).1,
  have h_J₁_ne_zero : J₁ ≠ 0 := fractional_ideal.ideal_factor_ne_zero hI h_a₁J₁,
  have h_a_ne_zero : ideal.span{a} ≠ 0 := fractional_ideal.constant_factor_ne_zero hI h_aJ,
  have h_J_ne_zero : J ≠ 0 := fractional_ideal.ideal_factor_ne_zero hI h_aJ,
  have h_a₁' : fractional_ideal.span_singleton (non_zero_divisors R) ((algebra_map R K) a₁) ≠ 0,
  { rw [ne.def, fractional_ideal.span_singleton_eq_zero_iff, ← (algebra_map R K).map_zero,
      injective.eq_iff (is_localization.injective K (le_refl (non_zero_divisors R)))],
    exact h_a₁_ne_zero,},
  have h_a' : fractional_ideal.span_singleton (non_zero_divisors R) ((algebra_map R K) a) ≠ 0,
  { rw [ne.def, fractional_ideal.span_singleton_eq_zero_iff, ← (algebra_map R K).map_zero,
      injective.eq_iff (is_localization.injective K (le_refl (non_zero_divisors R)))],
    rw [ne.def, ideal.zero_eq_bot, ideal.span_singleton_eq_bot] at h_a_ne_zero,
    exact h_a_ne_zero,},
  have hv : irreducible (associates.mk v.as_ideal),
  { rw associates.irreducible_mk,
    exact v.irreducible, },
  rw [h_a₁J₁, ← fractional_ideal.div_span_singleton, ← fractional_ideal.div_span_singleton,
    div_eq_div_iff h_a₁' h_a', ← fractional_ideal.coe_ideal_span_singleton,
    ← fractional_ideal.coe_ideal_span_singleton, ← fractional_ideal.coe_ideal_mul,
    ← fractional_ideal.coe_ideal_mul] at h_aJ,
  rw [fractional_ideal.count, dif_neg hI, sub_eq_sub_iff_add_eq_add, ← int.coe_nat_add,
    ← int.coe_nat_add, int.coe_nat_inj', ← associates.count_mul _ _ hv,
    ← associates.count_mul _ _ hv, associates.mk_mul_mk, associates.mk_mul_mk,
    fractional_ideal.coe_ideal_injective h_aJ],
  { rw [ne.def, associates.mk_eq_zero], exact h_J_ne_zero },
  { rw [ne.def, associates.mk_eq_zero, ideal.zero_eq_bot, ideal.span_singleton_eq_bot],
    exact h_a₁_ne_zero, },
  { rw [ne.def, associates.mk_eq_zero], exact h_J₁_ne_zero },
  { rw [ne.def, associates.mk_eq_zero], exact h_a_ne_zero },
end

/-- For nonzero `I, I'`, `val_v(I*I') = val_v(I) + val_v(I')`. -/
lemma fractional_ideal.count_mul {I I' : fractional_ideal (non_zero_divisors R) K} (hI : I ≠ 0)
  (hI' : I' ≠ 0) : fractional_ideal.count K v (I*I')  = fractional_ideal.count K v (I) +
  fractional_ideal.count K v (I') :=
begin
  have hv : irreducible (associates.mk v.as_ideal),
  { apply v.associates_irreducible },
  obtain ⟨a, J, ha, haJ⟩ := fractional_ideal.exists_eq_span_singleton_mul I,
  have ha_ne_zero : associates.mk (ideal.span {a} : ideal R) ≠ 0,
  { rw [ne.def, associates.mk_eq_zero, ideal.zero_eq_bot, ideal.span_singleton_eq_bot], exact ha },
  have hJ_ne_zero : associates.mk J ≠ 0,
  { rw [ne.def, associates.mk_eq_zero], exact fractional_ideal.ideal_factor_ne_zero hI haJ },
  obtain ⟨a', J', ha', haJ'⟩ := fractional_ideal.exists_eq_span_singleton_mul I',
  have ha'_ne_zero : associates.mk (ideal.span {a'} : ideal R) ≠ 0,
  { rw [ne.def, associates.mk_eq_zero, ideal.zero_eq_bot, ideal.span_singleton_eq_bot], exact ha' },
  have hJ'_ne_zero : associates.mk J' ≠ 0,
  { rw [ne.def, associates.mk_eq_zero], exact fractional_ideal.ideal_factor_ne_zero hI' haJ' },
  have h_prod : I*I' = fractional_ideal.span_singleton (non_zero_divisors R)
    ((algebra_map R K) (a*a'))⁻¹ * ↑(J*J'),
    { rw [haJ, haJ', mul_assoc, mul_comm ↑J, mul_assoc, ← mul_assoc,
      fractional_ideal.span_singleton_mul_span_singleton,
      fractional_ideal.coe_ideal_mul, ring_hom.map_mul, mul_inv₀, mul_comm ↑J] },
  rw [fractional_ideal.count_well_defined K v hI haJ,
    fractional_ideal.count_well_defined K v hI' haJ',
    fractional_ideal.count_well_defined K v (mul_ne_zero hI hI') h_prod,
    ← associates.mk_mul_mk, associates.count_mul hJ_ne_zero hJ'_ne_zero hv,
    ← ideal.span_singleton_mul_span_singleton, ← associates.mk_mul_mk,
    associates.count_mul ha_ne_zero ha'_ne_zero hv],
  push_cast,
  ring,
end

/-- For nonzero `I, I'`, `val_v(I*I') = val_v(I) + val_v(I')`. If `I` or `I'` is zero, then
`val_v(I*I') = 0`. -/
lemma fractional_ideal.count_mul' (I I' : fractional_ideal (non_zero_divisors R) K) :
  fractional_ideal.count K v (I*I')  = (if I ≠ 0 ∧ I' ≠ 0 then  fractional_ideal.count K v (I) +
    fractional_ideal.count K v (I') else 0) :=
begin
  split_ifs,
  { exact fractional_ideal.count_mul K v h.1 h.2 },
  { push_neg at h,
    by_cases hI : I = 0,
    { rw [hI, zero_mul, fractional_ideal.count, dif_pos (eq.refl _)], },
    { rw [(h hI), mul_zero, fractional_ideal.count, dif_pos (eq.refl _)], }}
end

/-- val_v(0) = 0. -/
lemma fractional_ideal.count_zero :
  fractional_ideal.count K v (0 : fractional_ideal (non_zero_divisors R) K) = 0 :=
by rw [fractional_ideal.count, dif_pos (eq.refl _)]

/-- val_v(1) = 0. -/
lemma fractional_ideal.count_one :
  fractional_ideal.count K v (1 : fractional_ideal (non_zero_divisors R) K) = 0 :=
begin
  have h_one : (1 : fractional_ideal (non_zero_divisors R) K) = fractional_ideal.span_singleton
    (non_zero_divisors R) ((algebra_map R K) (1))⁻¹ * ↑(1 : ideal R),
  { rw [(algebra_map R K).map_one, ideal.one_eq_top, fractional_ideal.coe_ideal_top, mul_one,
      inv_one, fractional_ideal.span_singleton_one], },
  rw [fractional_ideal.count_well_defined K v one_ne_zero h_one, ideal.span_singleton_one,
    ideal.one_eq_top, sub_self],
end

/-- For every `n ∈ ℕ` and every ideal `I`, `val_v(I^n) = n*val_v(I)`. -/
lemma fractional_ideal.count_pow (n : ℕ) (I : fractional_ideal (non_zero_divisors R) K) :
  fractional_ideal.count K v (I^n) = n * fractional_ideal.count K v I :=
begin
  induction n with n h,
  { rw [pow_zero, int.coe_nat_zero, zero_mul, fractional_ideal.count_one] },
  { rw pow_succ, rw fractional_ideal.count_mul',
    by_cases hI : I = 0,
    { have h_neg : ¬ (I ≠ 0 ∧ I ^ n ≠ 0),
      { rw [not_and, not_not, ne.def], intro h, exact absurd hI h, },
      rw [if_neg h_neg, hI, fractional_ideal.count_zero, mul_zero], },
    { rw [if_pos (and.intro hI (pow_ne_zero n hI)), h, nat.succ_eq_add_one], ring, }},
end

/--`val_v(v) = 1`, when `v` is regarded as a fractional ideal. -/
lemma fractional_ideal.count_self :
  fractional_ideal.count K v (v.as_ideal : fractional_ideal (non_zero_divisors R) K)  = 1 :=
begin
  have hv : (v.as_ideal : fractional_ideal (non_zero_divisors R) K) ≠ 0,
  { rw fractional_ideal.coe_ideal_ne_zero_iff,
    exact v.ne_bot  },
  have h_self : (v.as_ideal : fractional_ideal (non_zero_divisors R) K) =
    fractional_ideal.span_singleton (non_zero_divisors R) ((algebra_map R K) 1)⁻¹ *
    ↑(v.as_ideal),
  { rw [(algebra_map R K).map_one, inv_one, fractional_ideal.span_singleton_one, one_mul], },
  have hv_irred : irreducible (associates.mk v.as_ideal),
  { apply v.associates_irreducible },
  rw [fractional_ideal.count_well_defined K v hv h_self, associates.count_self hv_irred,
    ideal.span_singleton_one, ← ideal.one_eq_top, associates.mk_one, associates.factors_one,
    associates.count_zero hv_irred, int.coe_nat_zero, sub_zero, int.coe_nat_one],
end

/--`val_v(v) = n` for every `n ∈ ℕ`. -/
lemma fractional_ideal.count_pow_self (n : ℕ) :
  fractional_ideal.count K v ((v.as_ideal : fractional_ideal (non_zero_divisors R) K)^n) = n :=
by rw [fractional_ideal.count_pow, fractional_ideal.count_self, mul_one]

/--`val_v(I⁻ⁿ) = -val_v(Iⁿ)` for every `n ∈ ℤ`. -/
lemma fractional_ideal.count_inv (n : ℤ) (I : fractional_ideal (non_zero_divisors R) K) :
  fractional_ideal.count K v (I^-n) = - fractional_ideal.count K v (I^n) :=
begin
  by_cases hI : I = 0,
  {by_cases hn : n = 0,
    { rw [hn, neg_zero, zpow_zero, fractional_ideal.count_one, neg_zero], },
    { rw [hI, zero_zpow n hn, zero_zpow (-n) (neg_ne_zero.mpr hn), fractional_ideal.count_zero,
        neg_zero], }},
  { rw [eq_neg_iff_add_eq_zero,
    ←  fractional_ideal.count_mul K v (zpow_ne_zero _ hI) (zpow_ne_zero _ hI),
    ← zpow_add₀ hI, neg_add_self, zpow_zero],
    exact fractional_ideal.count_one K v, }
end

/--`val_v(Iⁿ) = n*val_v(I)` for every `n ∈ ℤ`. -/
lemma fractional_ideal.count_zpow (n : ℤ) (I : fractional_ideal (non_zero_divisors R) K) :
  fractional_ideal.count K v (I^n) = n * fractional_ideal.count K v I :=
begin
  cases n,
  { rw [int.of_nat_eq_coe, zpow_coe_nat],
    exact fractional_ideal.count_pow K v n I, },
  { rw [int.neg_succ_of_nat_coe, fractional_ideal.count_inv, zpow_coe_nat,
      fractional_ideal.count_pow], ring, }
end

/--`val_v(v) = n` for every `n ∈ ℤ`. -/
lemma fractional_ideal.count_zpow_self (n : ℤ) :
  fractional_ideal.count K v
  ((v.as_ideal : fractional_ideal (non_zero_divisors R) K)^n) = n :=
by rw [fractional_ideal.count_zpow, fractional_ideal.count_self, mul_one]

/-- If `v ≠ w` are two maximal ideals of `R`, then `val_v(w) = 0`. -/
lemma fractional_ideal.count_maximal_coprime (w : maximal_spectrum R) (hw : w ≠ v) :
  fractional_ideal.count K v (w.as_ideal : fractional_ideal (non_zero_divisors R) K) = 0 :=
begin
  have hw_fact : (w.as_ideal : fractional_ideal (non_zero_divisors R) K) =
   fractional_ideal.span_singleton
    (non_zero_divisors R) ((algebra_map R K) (1))⁻¹ * ↑(w.as_ideal),
  { rw [(algebra_map R K).map_one, inv_one, fractional_ideal.span_singleton_one, one_mul], },
  have hw_ne_zero : (w.as_ideal : fractional_ideal (non_zero_divisors R) K) ≠ 0,
  { rw fractional_ideal.coe_ideal_ne_zero_iff,
    exact w.ne_bot  },
  have hv : irreducible (associates.mk v.as_ideal) :=
  by apply v.associates_irreducible,
  have hw' : irreducible (associates.mk w.as_ideal) :=
  by apply w.associates_irreducible,
  rw [fractional_ideal.count_well_defined K v hw_ne_zero hw_fact, ideal.span_singleton_one,
    ← ideal.one_eq_top, associates.mk_one, associates.factors_one, associates.count_zero hv,
    int.coe_nat_zero, sub_zero, int.coe_nat_eq_zero, ← pow_one (associates.mk w.as_ideal),
    associates.factors_prime_pow hw', associates.count_some hv, multiset.repeat_one,
    multiset.count_eq_zero, multiset.mem_singleton],
  simp only [subtype.val_eq_coe],
  rw [associates.mk_eq_mk_iff_associated, associated_iff_eq, ← maximal_spectrum.ext_iff],
  exact ne.symm hw,
end

/-- `val_v(∏_{w ≠ v} w^{exps w}) = 0`. -/
lemma fractional_ideal.count_finprod_coprime (exps : maximal_spectrum R → ℤ) :
  fractional_ideal.count K v (∏ᶠ (w : maximal_spectrum R) (H : w ≠ v), ↑(w.as_ideal) ^ exps w) = 0 :=
begin
  apply finprod_mem_induction (λ I, fractional_ideal.count K v I = 0),
  { exact fractional_ideal.count_one K v },
  { intros I I' hI hI',
    by_cases h : I ≠ 0 ∧ I' ≠ 0,
    { rw [fractional_ideal.count_mul' K v, if_pos h, hI, hI', add_zero] },
    { rw [fractional_ideal.count_mul' K v, if_neg h], }},
  { intros w hw,
    rw [fractional_ideal.count_zpow, fractional_ideal.count_maximal_coprime K v w hw, mul_zero] }
end

/-- If `exps` is finitely supported, then `val_v(∏_w w^{exps w}) = exps v`. -/
lemma fractional_ideal.count_finprod (exps : maximal_spectrum R → ℤ)
(h_exps : ∀ᶠ (v : maximal_spectrum R) in filter.cofinite, exps v = 0 ) :
fractional_ideal.count K v (∏ᶠ (v : maximal_spectrum R),
  (v.as_ideal : fractional_ideal (non_zero_divisors R) K)^(exps v)) = exps v :=
begin
  have h_supp : (mul_support (λ (i : maximal_spectrum R), ↑(i.as_ideal) ^ exps i)).finite,
  { have h_subset : {v : maximal_spectrum R |
      (v.as_ideal : fractional_ideal (non_zero_divisors R) K) ^ exps v ≠ 1} ⊆
      {v : maximal_spectrum R | exps v ≠ 0},
    { intros v hv,
      by_contradiction h,
      rw [nmem_set_of_eq, not_not] at h,
      rw [mem_set_of_eq, h, zpow_zero] at hv,
      exact hv (eq.refl 1),},
    exact finite.subset h_exps h_subset, },
    rw [← mul_finprod_cond_ne v h_supp, fractional_ideal.count_mul,
      fractional_ideal.count_zpow_self, fractional_ideal.count_finprod_coprime, add_zero],
  { apply zpow_ne_zero,
    exact fractional_ideal.coe_ideal_ne_zero_iff.mpr v.ne_bot, },
  { rw [finprod_cond_ne _ _ h_supp, finset.prod_ne_zero_iff],
    intros w hw,
    apply zpow_ne_zero,
    exact fractional_ideal.coe_ideal_ne_zero_iff.mpr w.ne_bot, }
end

variable {K}
/-- If `I ≠ 0`, then `val_v(I) = 0` for all but finitely many maximal ideals of `R`. -/
lemma fractional_ideal.finite_factors {I : fractional_ideal (non_zero_divisors R) K} (hI : I ≠ 0)
  {a : R} {J : ideal R}
  (haJ : I = fractional_ideal.span_singleton (non_zero_divisors R) ((algebra_map R K) a)⁻¹ * ↑J) :
  ∀ᶠ v : maximal_spectrum R in filter.cofinite,
  (((associates.mk v.as_ideal).count (associates.mk J).factors : ℤ) -
    ((associates.mk v.as_ideal).count (associates.mk (ideal.span {a})).factors) = 0) :=
begin
  have ha_ne_zero : ideal.span{a} ≠ 0 := fractional_ideal.constant_factor_ne_zero hI haJ,
  have hJ_ne_zero : J ≠ 0 := fractional_ideal.ideal_factor_ne_zero hI haJ,
  rw filter.eventually_cofinite,
  have h_subset : {v : maximal_spectrum R | ¬((associates.mk v.as_ideal).count
    (associates.mk J).factors : ℤ) -
    ↑((associates.mk v.as_ideal).count (associates.mk (ideal.span {a})).factors) = 0} ⊆
    {v : maximal_spectrum R | v.as_ideal ∣ J} ∪
    {v : maximal_spectrum R | v.as_ideal ∣ (ideal.span {a})},
  { intros v hv,
    have hv_irred : irreducible v.as_ideal := v.irreducible,
    by_contradiction h_nmem,
    rw [mem_union_eq, mem_set_of_eq, mem_set_of_eq] at h_nmem,
    push_neg at h_nmem,
    rw [← associates.count_ne_zero_iff_dvd ha_ne_zero hv_irred, not_not,
    ← associates.count_ne_zero_iff_dvd hJ_ne_zero hv_irred, not_not]
      at h_nmem,
    rw [mem_set_of_eq, h_nmem.1, h_nmem.2, sub_self] at hv,
    exact hv (eq.refl 0), },
  exact finite.subset (finite.union
    (ideal.finite_factors (fractional_ideal.ideal_factor_ne_zero hI haJ))
    (ideal.finite_factors (fractional_ideal.constant_factor_ne_zero hI haJ)))
    h_subset,
end


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Floris van Doorn, Sébastien Gouëzel, Alex J. Best

! This file was ported from Lean 3 source module data.list.big_operators.lemmas
! leanprover-community/mathlib commit f694c7dead66f5d4c80f446c796a5aad14707f0e
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.List.BigOperators.Basic
import Mathlib.Algebra.Group.Opposite
import Mathlib.Algebra.GroupPower.Basic
import Mathlib.Algebra.GroupWithZero.Commute
import Mathlib.Algebra.GroupWithZero.Divisibility
import Mathlib.Algebra.Order.WithZero
import Mathlib.Algebra.Ring.Basic
import Mathlib.Algebra.Ring.Divisibility
import Mathlib.Algebra.Ring.Commute
import Mathlib.Data.Int.Units
import Mathlib.Data.Set.Basic

/-! # Lemmas about `List.sum` and `List.prod` requiring extra algebra imports -/

open MulOpposite List

variable {ι α M N P M₀ G R : Type _}

namespace Commute

theorem list_sum_left [NonUnitalNonAssocSemiring R] (b : R) (l : List R)
    (h : ∀ a ∈ l, Commute a b) : Commute l.sum b :=
  ((Commute.list_sum_right _ _) fun _x hx => (h _ hx).symm).symm
#align commute.list_sum_left Commute.list_sum_left

end Commute

namespace List

@[to_additive card_nsmul_le_sum]
theorem pow_card_le_prod [Monoid M] [Preorder M]
    [CovariantClass M M (Function.swap (· * ·)) (· ≤ ·)] [CovariantClass M M (· * ·) (· ≤ ·)]
    (l : List M) (n : M) (h : ∀ x ∈ l, n ≤ x) : n ^ l.length ≤ l.prod :=
  @prod_le_pow_card Mᵒᵈ _ _ _ _ l n h
#align list.pow_card_le_prod List.pow_card_le_prod
#align list.card_nsmul_le_sum List.card_nsmul_le_sum

@[to_additive]
theorem prod_eq_one_iff [CanonicallyOrderedMonoid M] (l : List M) :
    l.prod = 1 ↔ ∀ x ∈ l, x = (1 : M) :=
  ⟨all_one_of_le_one_le_of_prod_eq_one fun _ _ => one_le _, fun h => by
    rw [List.eq_replicate.2 ⟨_, h⟩, prod_replicate, one_pow]; exact (length l); rfl⟩
#align list.prod_eq_one_iff List.prod_eq_one_iff
#align list.sum_eq_zero_iff List.sum_eq_zero_iff

/-- If a product of integers is `-1`, then at least one factor must be `-1`. -/
theorem neg_one_mem_of_prod_eq_neg_one {l : List ℤ} (h : l.prod = -1) : (-1 : ℤ) ∈ l := by
  obtain ⟨x, h₁, h₂⟩ := exists_mem_ne_one_of_prod_ne_one (ne_of_eq_of_ne h (by decide))
  exact
    Or.resolve_left
        (Int.isUnit_iff.mp
          (prod_isUnit_iff.mp (h.symm ▸ IsUnit.neg isUnit_one : IsUnit l.prod) x h₁))
        h₂ ▸
      h₁
#align list.neg_one_mem_of_prod_eq_neg_one List.neg_one_mem_of_prod_eq_neg_one

/-- If all elements in a list are bounded below by `1`, then the length of the list is bounded
by the sum of the elements. -/
theorem length_le_sum_of_one_le (L : List ℕ) (h : ∀ i ∈ L, 1 ≤ i) : L.length ≤ L.sum := by
  induction' L with j L IH h; · simp
  rw [sum_cons, length, add_comm]
  exact add_le_add (h _ (mem_cons_self _ _)) (IH fun i hi => h i (mem_cons.2 (Or.inr hi)))
#align list.length_le_sum_of_one_le List.length_le_sum_of_one_le

theorem dvd_prod [CommMonoid M] {a} {l : List M} (ha : a ∈ l) : a ∣ l.prod := by
  let ⟨s, t, h⟩ := mem_split ha
  rw [h, prod_append, prod_cons, mul_left_comm]
  exact dvd_mul_right _ _
#align list.dvd_prod List.dvd_prod

theorem dvd_sum [Semiring R] {a} {l : List R} (h : ∀ x ∈ l, a ∣ x) : a ∣ l.sum := by
  induction' l with x l ih
  · exact dvd_zero _
  · rw [List.sum_cons]
    exact dvd_add (h _ (mem_cons_self _ _)) (ih fun x hx => h x (mem_cons_of_mem _ hx))
#align list.dvd_sum List.dvd_sum

section Alternating

variable [CommGroup α]

@[to_additive]
theorem alternatingProd_append :
    ∀ l₁ l₂ : List α,
      alternatingProd (l₁ ++ l₂) = alternatingProd l₁ * alternatingProd l₂ ^ (-1 : ℤ) ^ length l₁
  | [], l₂ => by simp
  | a :: l₁, l₂ => by
    simp_rw [cons_append, alternatingProd_cons, alternatingProd_append, length_cons, pow_succ,
      neg_mul, one_mul, zpow_neg, ← div_eq_mul_inv, div_div]
#align list.alternating_prod_append List.alternatingProd_append
#align list.alternating_sum_append List.alternatingSum_append

@[to_additive]
theorem alternatingProd_reverse :
    ∀ l : List α, alternatingProd (reverse l) = alternatingProd l ^ (-1 : ℤ) ^ (length l + 1)
  | [] => by simp only [alternatingProd_nil, one_zpow, reverse_nil]
  | a :: l =>
    by
    simp_rw [reverse_cons, alternatingProd_append, alternatingProd_reverse,
      alternatingProd_singleton, alternatingProd_cons, length_reverse, length, pow_succ, neg_mul,
      one_mul, zpow_neg, inv_inv]
    rw [mul_comm, ← div_eq_mul_inv, div_zpow]
#align list.alternating_prod_reverse List.alternatingProd_reverse
#align list.alternating_sum_reverse List.alternatingSum_reverse

end Alternating

theorem sum_map_mul_left [NonUnitalNonAssocSemiring R] (L : List ι) (f : ι → R) (r : R) :
    (L.map fun b => r * f b).sum = r * (L.map f).sum :=
  sum_map_hom L f <| AddMonoidHom.mulLeft r
#align list.sum_map_mul_left List.sum_map_mul_left

theorem sum_map_mul_right [NonUnitalNonAssocSemiring R] (L : List ι) (f : ι → R) (r : R) :
    (L.map fun b => f b * r).sum = (L.map f).sum * r :=
  sum_map_hom L f <| AddMonoidHom.mulRight r
#align list.sum_map_mul_right List.sum_map_mul_right

end List

namespace MulOpposite

open List

variable [Monoid M]

theorem op_list_prod : ∀ l : List M, op l.prod = (l.map op).reverse.prod := by
  intro l; induction l with
  | nil => rfl
  | cons x xs ih =>
    rw [List.prod_cons, List.map_cons, List.reverse_cons', List.prod_concat, op_mul, ih]
#align mul_opposite.op_list_prod MulOpposite.op_list_prod

theorem unop_list_prod (l : List Mᵐᵒᵖ) : l.prod.unop = (l.map unop).reverse.prod := by
  rw [← op_inj, op_unop, MulOpposite.op_list_prod, map_reverse, map_map, reverse_reverse,
    op_comp_unop, map_id]
#align mul_opposite.unop_list_prod MulOpposite.unop_list_prod

end MulOpposite

section MonoidHom

variable [Monoid M] [Monoid N]

/-- A morphism into the opposite monoid acts on the product by acting on the reversed elements. -/
theorem unop_map_list_prod {F : Type _} [MonoidHomClass F M Nᵐᵒᵖ] (f : F) (l : List M) :
    (f l.prod).unop = (l.map (MulOpposite.unop ∘ f)).reverse.prod := by
  rw [map_list_prod f l, MulOpposite.unop_list_prod, List.map_map]
#align unop_map_list_prod unop_map_list_prod

namespace MonoidHom

/-- A morphism into the opposite monoid acts on the product by acting on the reversed elements. -/
@[deprecated _root_.unop_map_list_prod]
protected theorem unop_map_list_prod (f : M →* Nᵐᵒᵖ) (l : List M) :
    (f l.prod).unop = (l.map (MulOpposite.unop ∘ f)).reverse.prod :=
  unop_map_list_prod f l
#align monoid_hom.unop_map_list_prod MonoidHom.unop_map_list_prod

end MonoidHom

end MonoidHom


/-
Copyright (c) 2016 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Deniz Aydin, Floris van Doorn
-/

import Mathlib.Init.Data.Nat.Lemmas
import Mathlib.Tactic.Basic
import Mathlib.Tactic.Coe
import Mathlib.Tactic.NormCast.Lemmas
open Nat

namespace Int

notation "ℤ" => Int
notation "-[1+ " n "]" => Int.negSucc n

lemma ofNat_zero : ofNat (0 : ℕ) = (0 : ℤ) := rfl

lemma ofNat_one  : ofNat (1 : ℕ) = (1 : ℤ) := rfl

/- ## Definitions of basic functions -/

lemma subNatNat_of_sub_eq_zero {m n : ℕ} (h : n - m = 0) : subNatNat m n = ofNat (m - n) := by
  show (match (n - m) with -- No `unfold` so I do this
  | 0 => ofNat (m-n)
  | succ k => -[1+ k]
  ) = ofNat (m - n)
  rw [h]

lemma subNatNat_of_sub_eq_succ {m n k : ℕ} (h : n - m = succ k) : subNatNat m n = -[1+ k] := by
  show (match n - m with
  | 0 => ofNat (m - n)
  | succ k => -[1+ k]) =
  -[1+ k]
  rw [h]

protected lemma neg_zero : -(0:ℤ) = 0 := rfl

lemma ofNat_add (n m : ℕ) : ofNat (n + m) = ofNat n + ofNat m := rfl
lemma ofNat_mul (n m : ℕ) : ofNat (n * m) = ofNat n * ofNat m := rfl
lemma ofNat_succ (n : ℕ) : ofNat (succ n) = ofNat n + 1 := rfl

lemma neg_ofNat_zero : -(ofNat 0) = 0 := rfl
lemma neg_ofNat_of_succ (n : ℕ) : -(ofNat (succ n)) = -[1+ n] := rfl
lemma neg_neg_ofNat_succ (n : ℕ) : -(-[1+ n]) = ofNat (succ n) := rfl

lemma negSucc_ofNat_coe (n : ℕ) : -[1+ n] = -↑(n + 1) := rfl

/- ## These are only for internal use -/

lemma ofNat_add_ofNat (m n : ℕ) :
    ofNat m + ofNat n = ofNat (m + n) := rfl
lemma ofNat_add_negSucc_ofNat (m n : ℕ) :
    ofNat m + -[1+ n] = subNatNat m (succ n) := rfl
lemma negSucc_ofNat_add_ofNat (m n : ℕ) :
    -[1+ m] + ofNat n = subNatNat n (succ m) := rfl
lemma negSucc_ofNat_add_negSucc_ofNat (m n : ℕ) :
    -[1+ m] + -[1+ n] = -[1+ succ (m + n)] := rfl

lemma ofNat_mul_ofNat (m n : ℕ) :
    ofNat m * ofNat n = ofNat (m * n) := rfl
lemma ofNat_mul_negSucc_ofNat (m n : ℕ) :
    ofNat m * -[1+ n] = negOfNat (m * succ n) := rfl
lemma negSucc_ofNat_ofNat (m n : ℕ) :
    -[1+ m] * ofNat n = negOfNat (succ m * n) := rfl
lemma mul_negSucc_ofNat_negSucc_ofNat (m n : ℕ) :
    -[1+ m] * -[1+ n] = ofNat (succ m * succ n) := rfl

attribute [local simp] ofNat_add_ofNat ofNat_mul_ofNat neg_ofNat_zero neg_ofNat_of_succ
  neg_neg_ofNat_succ ofNat_add_negSucc_ofNat negSucc_ofNat_add_ofNat
  negSucc_ofNat_add_negSucc_ofNat ofNat_mul_negSucc_ofNat negSucc_ofNat_ofNat
  mul_negSucc_ofNat_negSucc_ofNat

/- ## some basic functions and properties -/

protected lemma coe_nat_inj {m n : ℕ} (h : (↑m : ℤ) = ↑n) : m = n :=
Int.ofNat.inj h

lemma ofNat_eq_ofNat_iff (m n : ℕ) : ofNat m = ofNat n ↔ m = n :=
Iff.intro Int.ofNat.inj (congrArg _)

lemma negSucc_ofNat_inj_iff {m n : ℕ} : negSucc m = negSucc n ↔ m = n :=
⟨Int.negSucc.inj, λ H => by simp [H]⟩

lemma negSucc_ofNat_eq (n : ℕ) : -[1+ n] = -((↑n) + 1) := rfl

/- ## neg -/

protected lemma neg_neg : ∀ a : ℤ, -(-a) = a
| ofNat 0     => rfl
| ofNat (n+1) => rfl
| -[1+ n]     => rfl

protected lemma neg_inj {a b : ℤ} (h : -a = -b) : a = b :=
by rw [← Int.neg_neg a, ← Int.neg_neg b, h]

protected lemma sub_eq_add_neg {a b : ℤ} : a - b = a + -b := rfl

/- ## basic properties of subNatNat -/

lemma subNatNat_elim (m n : ℕ) (P : ℕ → ℕ → ℤ → Prop)
  (hp : ∀i n, P (n + i) n (ofNat i))
  (hn : ∀i m, P m (m + i + 1) (-[1+ i])) :
  P m n (subNatNat m n) :=
by
  have H : ∀ k , n - m = k → P m n (match n - m with | 0 => ofNat (m - n) | succ k => -[1+ k] : ℤ) := by
    intros k h
    cases k with
    | zero =>
        have ⟨k, h⟩ := (Nat.le.dest (Nat.le_of_sub_eq_zero h))
        rw [h.symm, Nat.add_sub_cancel_left, Nat.sub_self_add]
        apply hp
    | succ k =>
        simp
        have hle : m ≤ n := Nat.le_of_lt (Nat.lt_of_sub_eq_succ h)
        rw [Nat.sub_eq_iff_eq_add hle] at h
        rw [h, Nat.add_comm, Nat.add_sub_cancel_left]
        apply hn
  exact H _ rfl

lemma subNatNat_add_left {m n : ℕ} :
  subNatNat (m + n) m = ofNat n :=
by
  change (match m - (m + n) with
          | 0      => ofNat (m + n - m)
          | succ k => -[1+ k]) = ofNat n
  rw [Nat.sub_eq_zero_of_le (Nat.le_add_right _ _)]
  rw [Nat.add_sub_cancel_left]

lemma subNatNat_add_right {m n : ℕ} : subNatNat m (m + n + 1) = negSucc n := by
  change (match m + n + 1 - m with
          | 0      => ofNat (m - (m + n + 1))
          | succ k => -[1+ k]) = negSucc n
  simp
  rw [Nat.add_assoc, Nat.add_one, Nat.add_sub_cancel_left]

lemma subNatNat_add_add (m n k : ℕ) : subNatNat (m + k) (n + k) = subNatNat m n :=
subNatNat_elim m n (λm n i => subNatNat (m + k) (n + k) = i)
  (by
    intros i j
    rw [Nat.add_assoc, Nat.add_comm i k, ← Nat.add_assoc]
    exact subNatNat_add_left
  )
  (by
    intros i j
    rw [Nat.add_assoc j i 1, Nat.add_comm j (i+1), Nat.add_assoc, Nat.add_comm (i+1) (j+k)]
    exact subNatNat_add_right
  )

lemma subNatNat_of_le {m n : ℕ} (h : n ≤ m) : subNatNat m n = ofNat (m - n) :=
subNatNat_of_sub_eq_zero (Nat.sub_eq_zero_of_le h)

lemma subNatNat_of_lt {m n : ℕ} (h : m < n) : subNatNat m n = -[1+ pred (n - m)] := by
  rw [subNatNat_of_sub_eq_succ]
  rw [Nat.succ_pred_eq_of_pos (Nat.sub_pos_of_lt h)]

/- ## natAbs -/

lemma natAbs_ofNat (n : ℕ) : natAbs ↑n = n := rfl

lemma eq_zero_ofNatAbs_eq_zero : ∀ {a : ℤ}, natAbs a = 0 → a = 0
| (ofNat _), H => congr_arg ofNat H
| -[1+ _],   H => absurd H (succ_ne_zero _)

lemma natAbs_pos_of_ne_zero {a : ℤ} (h : a ≠ 0) : 0 < natAbs a :=
(eq_zero_or_pos _).resolve_left $ mt eq_zero_ofNatAbs_eq_zero h

lemma natAbs_zero : natAbs (0 : ℤ) = (0 : ℕ) := rfl

lemma natAbs_one : natAbs (1 : ℤ) = (1 : ℕ) := rfl

lemma natAbs_mul_self : ∀ {a : ℤ}, ↑(natAbs a * natAbs a) = a * a
| (ofNat _) => rfl
| -[1+ _]   => rfl

@[simp] lemma natAbs_neg (a : ℤ) : natAbs (-a) = natAbs a := match a with
| (ofNat zero)     => rfl
| (ofNat (succ _)) => rfl
| (negSucc _)      => rfl

lemma natAbs_eq : ∀ (a : ℤ), a = natAbs a ∨ a = -↑(natAbs a)
| (ofNat _) => Or.inl rfl
| -[1+ _]   => Or.inr rfl

lemma eq_x_or_neg (a : ℤ) : ∃n : ℕ, a = n ∨ a = -↑n := ⟨_, natAbs_eq a⟩

/- ## sign -/

def sign : ℤ → ℤ
| (n+1:ℕ) => 1
| 0       => 0
| -[1+ n] => -1

@[simp] theorem sign_zero : sign 0 = 0 := rfl
@[simp] theorem sign_one : sign 1 = 1 := rfl
@[simp] theorem sign_neg_one : sign (-1) = -1 := rfl

/- ## Quotient and remainder
There are three main conventions for integer division,
referred here as the E, F, T rounding conventions.
All three pairs satisfy the identity x % y + (x / y) * y = x
unconditionally. -/

-- E-rounding (`div` and `mod` defined in core) satisfy 0 ≤ mod x y < natAbs y for y ≠ 0

-- F-rounding: This pair satisfies fdiv x y = floor (x / y)
def fdiv : ℤ → ℤ → ℤ
| 0,       _       => 0
| (m : ℕ), (n : ℕ) => ofNat (m / n)
| (m+1:ℕ), -[1+ n] => -[1+ m / succ n]
| -[1+ m], 0       => 0
| -[1+ m], (n+1:ℕ) => -[1+ m / succ n]
| -[1+ m], -[1+ n] => ofNat (succ m / succ n)

def fmod : ℤ → ℤ → ℤ
| 0,       _       => 0
| (m : ℕ), (n : ℕ) => ofNat (m % n)
| (m+1:ℕ), -[1+ n] => subNatNat (m % succ n) n
| -[1+ m], (n : ℕ) => subNatNat n (succ (m % n))
| -[1+ m], -[1+ n] => -ofNat (succ m % succ n)

-- T-rounding: This pair satisfies quot x y = round_to_zero (x / y)
def quot : ℤ → ℤ → ℤ
| (m : ℕ), (n : ℕ) => ofNat (m / n)
| (m : ℕ), -[1+ n] => -ofNat (m / succ n)
| -[1+ m], (n : ℕ) => -ofNat (succ m / n)
| -[1+ m], -[1+ n] => ofNat (succ m / succ n)

def rem : ℤ → ℤ → ℤ
| (m : ℕ), (n : ℕ) => ofNat (m % n)
| (m : ℕ), -[1+ n] => ofNat (m % succ n)
| -[1+ m], (n : ℕ) => -ofNat (succ m % n)
| -[1+ m], -[1+ n] => -ofNat (succ m % succ n)

/- ## gcd -/
def gcd (m n : ℤ) : ℕ := Nat.gcd (natAbs m) (natAbs n)

/- # ring properties -/

/- addition -/

protected lemma add_comm : ∀ a b : ℤ, a + b = b + a
| (n : ℕ), (m : ℕ) => by simp [Nat.add_comm]
| (_ : ℕ), -[1+ _] => rfl
| -[1+ _], (_ : ℕ) => rfl
| -[1+ _], -[1+ _] => by simp [Nat.add_comm]

protected lemma add_zero : ∀ a : ℤ, a + 0 = a
| (ofNat n) => rfl
| -[1+ n]   => rfl

protected lemma zero_add (a : ℤ) : 0 + a = a := Int.add_comm a 0 ▸ Int.add_zero a

lemma subNatNat_sub {m n : ℕ} (h : n ≤ m) (k : ℕ) :
subNatNat (m - n) k = subNatNat m (k + n) := by
  rwa [← subNatNat_add_add _ _ n, Nat.sub_add_cancel]

lemma subNatNat_add (m n k : ℕ) : subNatNat (m + n) k = ofNat m + subNatNat n k :=
by
  have h := Nat.lt_or_ge n k
  cases h with
  | inl h' =>
    rw [subNatNat_of_lt h']
    simp
    rw [succ_pred_eq_of_pos (Nat.sub_pos_of_lt h')]
    apply Eq.trans
    rw [← Nat.sub_add_cancel (Nat.le_of_lt h')]
    apply subNatNat_add_add
  | inr h' =>
    rw [subNatNat_of_le (h')]
    have h₂ : k ≤ m + n := Nat.le_trans h' (le_add_left _ _)
    rw [subNatNat_of_le h₂]
    simp
    rw [Nat.add_sub_assoc h']

lemma subNatNat_add_negSucc_ofNat (m n k : ℕ) :
    subNatNat m n + -[1+ k] = subNatNat m (n + succ k) :=
by
  have h := Nat.lt_or_ge m n
  cases h with
  | inr h' =>
    rw [subNatNat_of_le h']
    simp
    rw [subNatNat_sub h', Nat.add_comm]
  | inl h' =>
    have h₂ : m < n + succ k := Nat.lt_of_lt_of_le h' (le_add_right _ _)
    have h₃ : m ≤ n + k := le_of_succ_le_succ h₂
    rw [subNatNat_of_lt h', subNatNat_of_lt h₂]
    simp [Nat.add_comm]
    rw [← add_succ, succ_pred_eq_of_pos (Nat.sub_pos_of_lt h'), add_succ, succ_sub h₃, Nat.pred_succ]
    rw [Nat.add_comm n, Nat.add_sub_assoc (Nat.le_of_lt h')]

lemma add_assoc_aux1 (m n : ℕ) :
    ∀ c : ℤ, ofNat m + ofNat n + c = ofNat m + (ofNat n + c)
| (ofNat k) => by simp [Nat.add_assoc]
| -[1+ k]   => by simp [subNatNat_add]

lemma add_assoc_aux2 (m n k : ℕ) :
  -[1+ m] + -[1+ n] + ofNat k = -[1+ m] + (-[1+ n] + ofNat k) :=
by
  simp [add_succ]
  rw [Int.add_comm, subNatNat_add_negSucc_ofNat]
  simp [add_succ, succ_add, Nat.add_comm]

protected lemma add_assoc : ∀ a b c : ℤ, a + b + c = a + (b + c)
| (ofNat m), (ofNat n), c         => add_assoc_aux1 _ _ _
| (ofNat m), b,         (ofNat k) => by
  rw [Int.add_comm, ← add_assoc_aux1, Int.add_comm k, add_assoc_aux1, Int.add_comm b]
| a,         (ofNat n), (ofNat k) => by
  rw [Int.add_comm, Int.add_comm a, ← add_assoc_aux1, Int.add_comm a, Int.add_comm (ofNat k)]
| -[1+ m],   -[1+ n],   (ofNat k) => add_assoc_aux2 _ _ _
| -[1+ m],   (ofNat n), -[1+ k]   => by
  rw [Int.add_comm, ← add_assoc_aux2, Int.add_comm n, ← add_assoc_aux2, Int.add_comm -[1+ m]]
| (ofNat m), -[1+ n],   -[1+ k]   => by
  rw [Int.add_comm, Int.add_comm (ofNat m), Int.add_comm m, ← add_assoc_aux2, Int.add_comm -[1+ k]]
| -[1+ m],   -[1+ n],   -[1+ k]   => by
  simp [add_succ, Nat.add_comm, Nat.add_left_comm, neg_ofNat_of_succ]

/- ## negation -/

lemma sub_nat_self : ∀ n, subNatNat n n = 0
| 0        => rfl
| (succ m) => by
  rw [subNatNat_of_sub_eq_zero, Nat.sub_self, ofNat_zero]
  rw [Nat.sub_self]

attribute [local simp] sub_nat_self

protected lemma add_left_neg : ∀ a : ℤ, -a + a = 0
| (ofNat 0)        => rfl
| (ofNat (succ m)) => by simp
| -[1+ m]          => by simp

protected lemma add_right_neg (a : ℤ) : a + -a = 0 :=
by rw [Int.add_comm, Int.add_left_neg]

/- ## multiplication -/

protected lemma mul_comm : ∀ a b : ℤ, a * b = b * a
| a, b => by cases a <;> cases b <;> simp [Nat.mul_comm]

lemma ofNat_mul_negOfNat (m : ℕ) : ∀ n, ofNat m * negOfNat n = negOfNat (m * n)
| 0        => rfl
| (succ n) => rfl

lemma negOfNat_mul_ofNat (m n : ℕ) : negOfNat m * ofNat n = negOfNat (m * n) := by
    rw [Int.mul_comm]
    simp [ofNat_mul_negOfNat, Nat.mul_comm]

lemma negSucc_ofNat_mul_negOfNat (m : ℕ) :
  ∀ n, -[1+ m] * negOfNat n = ofNat (succ m * n)
| 0        => rfl
| (succ n) => rfl

lemma negOfNat_mul_negSucc_ofNat (m n : ℕ) :
  negOfNat n * -[1+ m] = ofNat (n * succ m) := by
    rw [Int.mul_comm, negSucc_ofNat_mul_negOfNat, Nat.mul_comm]

attribute [local simp] ofNat_mul_negOfNat negOfNat_mul_ofNat
  negSucc_ofNat_mul_negOfNat negOfNat_mul_negSucc_ofNat

protected lemma mul_assoc : ∀ a b c : ℤ, a * b * c = a * (b * c)
| a, b, c => by cases a <;> cases b <;> cases c <;> simp [Nat.mul_assoc]

protected lemma mul_zero : ∀ (a : ℤ), a * 0 = 0
| (ofNat m) => rfl
| -[1+ m]   => rfl

protected lemma zero_mul (a : ℤ) : 0 * a = 0 :=
Int.mul_comm a 0 ▸ Int.mul_zero a

lemma negOfNat_eq_subNatNat_zero : ∀ n, negOfNat n = subNatNat 0 n
| 0        => rfl
| (succ n) => rfl

lemma ofNat_mul_subNatNat (m n k : ℕ) :
  ofNat m * subNatNat n k = subNatNat (m * n) (m * k) :=
by
  cases m with
  | zero =>
    simp [ofNat_zero, Int.zero_mul, Nat.zero_mul]
  | succ m =>
    have h := Nat.lt_or_ge n k
    cases h with
    | inl h =>
      have h' : (succ m) * n < (succ m) * k := Nat.mul_lt_mul_of_pos_left h (Nat.succ_pos m)
      rw [subNatNat_of_lt h, subNatNat_of_lt h']
      simp
      rw [succ_pred_eq_of_pos (Nat.sub_pos_of_lt h)]
      rw [← neg_ofNat_of_succ, Nat.mul_sub_left_distrib]
      rw [← succ_pred_eq_of_pos (Nat.sub_pos_of_lt h')]
      rfl
    | inr h =>
      have h' : (succ m) * k ≤ (succ m) * n := Nat.mul_le_mul_left _ h
      rw [subNatNat_of_le h, subNatNat_of_le h']
      simp
      rw [Nat.mul_sub_left_distrib]

lemma negOfNat_add : ∀ m n : ℕ, negOfNat m + negOfNat n = negOfNat (m + n)
| zero,   zero   => by simp
| zero,   succ n => by simp [Nat.zero_add]; rfl
| succ m, zero   => by simp; rfl
| succ m, succ n => by simp [Nat.succ_add]; rfl

lemma negSucc_ofNat_mul_subNatNat (m n k : ℕ) :
  -[1+ m] * subNatNat n k = subNatNat (succ m * k) (succ m * n) :=
by
  have h := Nat.lt_or_ge n k
  cases h with
  | inl h =>
    have h' : succ m * n < succ m * k := Nat.mul_lt_mul_of_pos_left h (Nat.succ_pos m)
    rw [subNatNat_of_lt h, subNatNat_of_le (Nat.le_of_lt h')]
    simp [succ_pred_eq_of_pos (Nat.sub_pos_of_lt h), Nat.mul_sub_left_distrib]
  | inr h =>
    cases Nat.lt_or_ge k n with
    | inl h' =>
      have h₁ : succ m * n > succ m * k := Nat.mul_lt_mul_of_pos_left h' (Nat.succ_pos m)
      rw [subNatNat_of_le h, subNatNat_of_lt h₁, negSucc_ofNat_ofNat,
        Nat.mul_sub_left_distrib, ← succ_pred_eq_of_pos (Nat.sub_pos_of_lt h₁)]
      rfl
    | inr h' =>
      rw [Nat.le_antisymm h h', sub_nat_self, sub_nat_self, Int.mul_zero]

attribute [local simp] ofNat_mul_subNatNat negOfNat_add negSucc_ofNat_mul_subNatNat

protected lemma distrib_left : ∀ a b c : ℤ, a * (b + c) = a * b + a * c
| (ofNat m), (ofNat n), (ofNat k) => by simp [Nat.left_distrib]
| (ofNat m), (ofNat n), -[1+ k]   => by simp [negOfNat_eq_subNatNat_zero] rw [← subNatNat_add] rfl
| (ofNat m), -[1+ n],   (ofNat k) => by simp [negOfNat_eq_subNatNat_zero] rw [Int.add_comm, ← subNatNat_add] rfl
| (ofNat m), -[1+ n],   -[1+ k]   => by simp rw [← Nat.left_distrib, succ_add] rfl
| -[1+ m],   (ofNat n), (ofNat k) => by simp [Nat.mul_comm] rw [← Nat.right_distrib, Nat.mul_comm]
| -[1+ m],   (ofNat n), -[1+ k]   => by simp [negOfNat_eq_subNatNat_zero] rw [Int.add_comm, ← subNatNat_add] rfl
| -[1+ m],   -[1+ n],   (ofNat k) => by simp [negOfNat_eq_subNatNat_zero] rw [← subNatNat_add] rfl
| -[1+ m],   -[1+ n],   -[1+ k]   => by simp rw [← Nat.left_distrib, succ_add] rfl

protected lemma distrib_right (a b c : ℤ) : (a + b) * c = a * c + b * c :=
by simp [Int.mul_comm, Int.distrib_left]

protected lemma zero_ne_one : (0 : ℤ) ≠ 1 := λ h => Int.noConfusion h fun.

lemma ofNat_sub {n m : ℕ} (h : m ≤ n) : ofNat (n - m) = ofNat n - ofNat m := by
  show ofNat (n - m) = ofNat n + negOfNat m
  match m, h with
  | 0, h =>
    rfl
  | succ m, h =>
    show ofNat (n - succ m) = subNatNat n (succ m)
    simp [subNatNat, subNatNat] -- TODO: How to avoid having to simp through rename definitions to unfold them?
    rw [Nat.sub_eq_zero_of_le h]

protected lemma add_left_comm (a b c : ℤ) : a + (b + c) = b + (a + c) :=
by rw [← Int.add_assoc, Int.add_comm a, Int.add_assoc]

protected lemma add_left_cancel {a b c : ℤ} (h : a + b = a + c) : b = c := by
  have h₁ : -a + (a + b) = -a + (a + c) := by rw [h]
  rwa [← Int.add_assoc, ← Int.add_assoc, Int.add_left_neg, Int.zero_add, Int.zero_add] at h₁

protected lemma neg_add {a b : ℤ} : - (a + b) = -a + -b := by
  have h₁ : - (a + b) = -(a + b) + (a + b) + -a + -b := by
    rw [Int.add_assoc, Int.add_comm (-a), Int.add_assoc, Int.add_assoc, ← Int.add_assoc b]
    rw [Int.add_right_neg, Int.zero_add, Int.add_right_neg, Int.add_zero]
  rwa [Int.add_left_neg, Int.zero_add] at h₁

lemma negSucc_ofNat_coe' (n : ℕ) : -[1+ n] = -↑n - 1 :=
by rw [Int.sub_eq_add_neg, ← Int.neg_add]; rfl

protected lemma subNatNat_eq_coe {m n : ℕ} : subNatNat m n = ↑m - ↑n := by
  refine subNatNat_elim m n (fun m n i => i = ↑m - ↑n) ?p ?n
  case p =>
    intros i n
    rw [Int.ofNat_add, Int.sub_eq_add_neg, Int.add_assoc, Int.add_left_comm,
      Int.add_right_neg, Int.add_zero]
  case n =>
    intros i n
    simp only [negSucc_ofNat_coe, ofNat_add, Int.sub_eq_add_neg, Int.neg_add, ← Int.add_assoc]
    rw [← @Int.sub_eq_add_neg n, ← ofNat_sub, Nat.sub_self, ofNat_zero, Int.zero_add]
    apply Nat.le_refl

theorem toNat_sub (m n : ℕ) : toNat (m - n : ℕ) = m - n := rfl

protected lemma one_mul : ∀ (a : ℤ), (1 : ℤ) * a = a
| (ofNat n) => show ofNat (1 * n) = ofNat n by rw [Nat.one_mul]
| -[1+ n]   => show -[1+ (1 * n)] = -[1+ n] by rw [Nat.one_mul]

protected lemma mul_one (a : ℤ) : a * 1 = a :=
by rw [Int.mul_comm, Int.one_mul]

protected lemma neg_eq_neg_one_mul : ∀ a : ℤ, -a = -1 * a
| (ofNat 0)     => rfl
| (ofNat (n+1)) => show _ = -[1+ (1*n)+0] by rw [Nat.one_mul] rfl
| -[1+ n]       => show _ = ofNat _ by rw [Nat.one_mul] rfl

theorem sign_mul_natAbs : ∀ (a : ℤ), sign a * natAbs a = a
| (n+1:ℕ) => Int.one_mul _
| 0       => rfl
| -[1+ n] => (Int.neg_eq_neg_one_mul _).symm

end Int


namespace hidden

/-
Inductive, aka algebraic, data types
  - sum types
  - product types and case analysis
  - parametrically polymorphic
  - sum of product types and destructuring
-/

/-
SUM TYPES

A value of such a type is "one of these OR
one of those OR one of something else." SUM
means OR in this sense.
-/

inductive romanNumeral : Type
| I     : romanNumeral
| II    : romanNumeral
| III   : romanNumeral
| IV    : romanNumeral
| V     : romanNumeral

-- The empty data type
inductive empty : Type  -- Look Ma, no terms ("uninhabited")

-- Cannot create a value of this type
def x : empty := _

/-
We can define a function from empty to empty,
however, and the reason is a function body is
written based on the *assumption* that is has
received values of specified argument types.
-/

def funny : empty → empty
| e := e

/-
Of course there's no way to actually call this
function because there is an argument of type
empty doesn't exist.
-/

#eval funny _   -- There's no way to fill in _

/-
Start of some new stuff. We will cover the
following additional material on the empty
type at the beginning of our next class.
-/

/-
Preliminary: the match...with construct. We
can pattern match on an inductively defined
object (not on functions, for example) using
the match ... with ... end construct in Lean.
When doing so it is both necessary and it is
sufficient to match each possible case that
might occur. In the following example, we
take a romanNumeral and return tt is it's
less than or equal to II, and ff otherwise.
The function is define by cases analysis but
here we use match ... with ... end to do the
case analysis. We introduce match here so we
can use it without further explanation in
the examples that follow.
-/

open romanNumeral

def lessOrEqII (n : romanNumeral) : bool :=
match n with
| I := tt
| II := tt
| _ := ff
end

/-
Even more strangely we can define a function
from the empty type to any other type at all.
-/

-- example 1 (introduces "match ... with")
def weird (e : empty) : nat :=
match e with
-- no cases to consider, so we're done!!!
end

/-
We can even make this function polymorphic
so that it returns a value of any given type
whatsoever.
-/

def strange (α : Type) (e : empty) : α :=
match e with
-- no cases to consider, so we're done!!!
end

/-
These functions look like magic. As long
as we can come up with a value of type
empty, they can produce values of any type
at all! But there is no magic.

EXERCISE: Explain why.
-/

/-
Exercise: Can you maybe just create a
function of type, say, ℕ to empty, and
use it to get an (e : empty) that you
can then use to do magic?

EXERCISE: Explain where you get stuck
if you try.
-/

-- [End of new stuff]

/-
Having seen the empty type (aka ∅)
we now see sum types with one, two,
and several constructors.
-/

-- unit type (one constructor)
inductive unit : Type
| star : unit

-- bnit is void in C, Java, etc

-- bool (two variants)
inductive bool : Type
| tt : bool
| ff -- ": bool" is inferred

-- A day type: seven variants
inductive day : Type
| sun
| mon
| tue
| wed
| thu
| fri
| sat

open day

/-
CASE ANALYSIS

We generally define functions
that consume values of sum types
by case analysis. To know what
to return, we need to know what
form of value the function got
as an argument.
-/

def next_day : day → day
| sun := mon
| mon := tue
| tue := wed
| wed := thu
| thu := fri
| fri := sat
| sat := sun

/-
The left side of a case is
usually called a pattern.
Argument values are matched to
patterns in the order in which
patterns are using what is called
a "unification" algorithm (more
later). Function evaluation
finds the first pattern in top
to bottom order that matches,
and return the result obtained
by evaluating the expression on
the right hand side of that rule
(or "equation").
-/

#reduce next_day sun
#reduce next_day sat

/-
The _ character can be used in a
pattern to match any value. All
functions in Lean must be total.
We often use _ to cover cases not
covered explicitly by other rules.
-/
def isWeekday : day → bool
| sat := bool.ff
| sun := bool.ff
| _ :=   bool.tt

/-
PRODUCT TYPES (records, structures)
-/

/-
A product type has one constructor
that takes and bundles up values
of  zero or more other types into
records, aka structures.

In a value of a product type there
a value for the first field of an
object AND a value for the second
AND a value for the third, etc. So
PRODUCT, in this sense means, AND.
-/

/-
Note: The empty type can be viewed
as a product type with zero fields.
-/

/-
We now define a product type with
one field.
-/

inductive box_nat' : Type
| mk (val : ℕ) --

/--/
To understand such a definition
you need to understand that a
constructor, C, is a specific
kind of function, namely one
that takes zero or more arguments,
a₀ ... aₙ, and simply constructs a
term, (C a₀ ... aₙ). You can think
of such a term as a box, labelled
with the constructor name, C, and
containing each argument value
supplied to the constructor.

A value of our box type can thus
be visualized as a box (term) with
a single value, the argument to
box.mk, inside.

As usual there are a few syntactic
styles for defining such types.
We illustrate the syntactic forms
and the general ideas by defining
a new type, box_nat, a value of
which you can visualize as a box
(or record or structure) with a
single value (field) of type nat.
-/

inductive box_nat'' : Type
| mk : ℕ → box_nat''

structure box_nat''' : Type :=
mk :: (val : ℕ)

structure box_nat :=  -- readable
(val : ℕ)

-- Let's create such a value
def aBox := box_nat.mk 3

-- What does the term look like?
-- Lean prints it using a record notation
#reduce aBox

/-
Given such a box, we "destructure"
(open) it using "pattern matching"
to (1) get at the argument values
used in its construction, (2) to
give temporary names to those value
so that we can compute with them.

Here we see a more interesting form of
unification. The key ideas are (1) the
algorithm determines whether a pattern
matches, (2) it binds specified names
to the values of the fields in the
object being matched. In general we
use these temporary names to write
expressions that define return values.
-/

def unbox_nat : box_nat → ℕ
-- box_nat.mk 3     -- pattern matching
--    |       |     -- n is now bound to 3
| (box_nat.mk n) := n -- return value of n

#eval unbox_nat aBox  -- it works

/-
When you use the "structure" syntax,
Lean generates a projection (accessor)
function for each field automatically.
Each such function as the same name as
that of the field it projects/accesses.
-/

#eval box_nat.val aBox
#eval aBox.val     -- Preferred notation

/-
Polymorphic types
-/

/-
We now have the same problem we had
with functions: the need for a many
copies varying only in the type of
value(s) "contained in the box". For
example we might want a box type a
value of which contains value of type
nat, or of type string, or bool, etc.

The solution is the same: make our
types *polymorphic* by having their
definitions parameterized by other
types, and by using the *values* of
these type parameters as the *types*
of other values.
-/

/-
Here's a polymorphic box type.
-/
structure box (α : Type) : Type :=
(val : α)

/-
box is now a "type builder", a
function that takes a *type), α,
(e.g., nat, bool) as an argument
and that builds and returns a type,
box α (e.g., box nat, box bool),
whose constructor, mk, take a value
of that type. Here the projection
function, val, is also polymorphic
with an implicit type parameter,
α.
-/

/-
Here are examples where we construct
values of type "box nat" and "box bool"
respectively. Note that box.mk takes no
explicit type argument.
-/
def nat_box : box nat := box.mk 3
def bool_box : box bool := box.mk bool.tt

/-
Tiny note: We defined our own version
of bool above. If we were to write tt
in this example, we'd pick up Lean's
tt, not our own, so we write bool.tt
instead, picking up the version of
bool we've defined in this namespace
(hidden).
-/

/-
So what's the type of box itself? It's
not type, but rather reflects the fact
that box takes a type (of type, Type) as
an argument and returns a type (of type,
Type) as a result.
-/

#check nat_box

-- Example uses of the "box α" type

-- box string
def str_box : box string:= box.mk "Hello, Lean!"
-- box bool
def bool_box' := box.mk bool.tt

#eval nat_box.val
#eval str_box.val
#eval bool_box.val  -- Lean doesn't know how to print

-- We can also create boxes that contain functions
#check nat.succ
#eval nat.succ 4
def fun_box : box (nat → nat) := box.mk (nat.succ)

/-
Polymorphic product types with two
fields -- the type of ordered pairs --
and two type parameters accordingly.
-/

structure prod (α β : Type) : Type :=
(fst : α)
(snd : β)

-- Self-test: What's the type of prod?

-- "Introduce" some pairs
def pair1 := prod.mk 4 "Hi"

-- prod "type" has two type arguments
#check pair1
#check prod

-- polymorphic projection functions
#eval prod.fst pair1
#eval prod.snd pair1

-- dot notation, like in C, C++, Java, etc
#eval pair1.fst
#eval pair1.snd

/-
A forward-looking example: a structure type
the values of which are pythagorean triples.
The first three fields are the lengths of 3
sides of a triangle. The value of the last
field is a proof (whatever that is in Lean)
that the values of the first three fields
satisfy the condition of being such a triple.
What this means is that you simply cannot
construct a triple of this form without a
proof that it really is Pythagorean. The
*type* of proof required as a value in the
last field is *uninhabited* (empty) if the
three numbers don't add up right.
-/
structure phythagorean_triple : Type :=
(a : ℕ)
(b : ℕ)
(c : ℕ)
(cert: a*a + b*b = c*c)

/-
Without explaining what rfl does exactly,
here's a case where the require proof is
constructed automatically (by rfl).
-/
def py_tri : phythagorean_triple :=
phythagorean_triple.mk
3 4 5
rfl

/-
Here, however, there is no such proof,
so its construction fails, and the bug
in our argument values is revealed by
this failure.
-/
def py_tri_bad : phythagorean_triple :=
phythagorean_triple.mk
3 4 6
rfl  -- can't construct proof of 25 = 36

-- Try that in Java or mere Haskell!

end hidden


/-
Copyright (c) 2021 Eric Rodriguez. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Rodriguez

! This file was ported from Lean 3 source module ring_theory.polynomial.cyclotomic.eval
! leanprover-community/mathlib commit b13c1a07b23d09d72cd393a8e72fdd88dab90fa5
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.RingTheory.Polynomial.Cyclotomic.Basic
import Mathbin.Tactic.ByContra
import Mathbin.Topology.Algebra.Polynomial
import Mathbin.NumberTheory.Padics.PadicVal
import Mathbin.Analysis.Complex.Arg

/-!
# Evaluating cyclotomic polynomials
This file states some results about evaluating cyclotomic polynomials in various different ways.
## Main definitions
* `polynomial.eval(₂)_one_cyclotomic_prime(_pow)`: `eval 1 (cyclotomic p^k R) = p`.
* `polynomial.eval_one_cyclotomic_not_prime_pow`: Otherwise, `eval 1 (cyclotomic n R) = 1`.
* `polynomial.cyclotomic_pos` : `∀ x, 0 < eval x (cyclotomic n R)` if `2 < n`.
-/

namespace Polynomial

open Finset Nat

open BigOperators

@[simp]
theorem eval_one_cyclotomic_prime {R : Type _} [CommRing R] {p : ℕ} [hn : Fact p.Prime] :
    eval 1 (cyclotomic p R) = p := by
  simp only [cyclotomic_prime, eval_X, one_pow, Finset.sum_const, eval_pow, eval_finset_sum,
    Finset.card_range, smul_one_eq_coe]
#align polynomial.eval_one_cyclotomic_prime Polynomial.eval_one_cyclotomic_prime

@[simp]
theorem eval₂_one_cyclotomic_prime {R S : Type _} [CommRing R] [Semiring S] (f : R →+* S) {p : ℕ}
    [Fact p.Prime] : eval₂ f 1 (cyclotomic p R) = p := by simp
#align polynomial.eval₂_one_cyclotomic_prime Polynomial.eval₂_one_cyclotomic_prime

@[simp]
theorem eval_one_cyclotomic_prime_pow {R : Type _} [CommRing R] {p : ℕ} (k : ℕ)
    [hn : Fact p.Prime] : eval 1 (cyclotomic (p ^ (k + 1)) R) = p := by
  simp only [cyclotomic_prime_pow_eq_geom_sum hn.out, eval_X, one_pow, Finset.sum_const, eval_pow,
    eval_finset_sum, Finset.card_range, smul_one_eq_coe]
#align polynomial.eval_one_cyclotomic_prime_pow Polynomial.eval_one_cyclotomic_prime_pow

@[simp]
theorem eval₂_one_cyclotomic_prime_pow {R S : Type _} [CommRing R] [Semiring S] (f : R →+* S)
    {p : ℕ} (k : ℕ) [Fact p.Prime] : eval₂ f 1 (cyclotomic (p ^ (k + 1)) R) = p := by simp
#align polynomial.eval₂_one_cyclotomic_prime_pow Polynomial.eval₂_one_cyclotomic_prime_pow

private theorem cyclotomic_neg_one_pos {n : ℕ} (hn : 2 < n) {R} [LinearOrderedCommRing R] :
    0 < eval (-1 : R) (cyclotomic n R) :=
  by
  haveI := NeZero.of_gt hn
  rw [← map_cyclotomic_int, ← Int.cast_one, ← Int.cast_neg, eval_int_cast_map, Int.coe_castRingHom,
    Int.cast_pos]
  suffices 0 < eval (↑(-1 : ℤ)) (cyclotomic n ℝ)
    by
    rw [← map_cyclotomic_int n ℝ, eval_int_cast_map, Int.coe_castRingHom] at this
    exact_mod_cast this
  simp only [Int.cast_one, Int.cast_neg]
  have h0 := cyclotomic_coeff_zero ℝ hn.le
  rw [coeff_zero_eq_eval_zero] at h0
  by_contra' hx
  have := intermediate_value_univ (-1) 0 (cyclotomic n ℝ).Continuous
  obtain ⟨y, hy : is_root _ y⟩ := this (show (0 : ℝ) ∈ Set.Icc _ _ by simpa [h0] using hx)
  rw [is_root_cyclotomic_iff] at hy
  rw [hy.eq_order_of] at hn
  exact hn.not_le LinearOrderedRing.orderOf_le_two
#align polynomial.cyclotomic_neg_one_pos polynomial.cyclotomic_neg_one_pos

theorem cyclotomic_pos {n : ℕ} (hn : 2 < n) {R} [LinearOrderedCommRing R] (x : R) :
    0 < eval x (cyclotomic n R) :=
  by
  induction' n using Nat.strong_induction_on with n ih
  have hn' : 0 < n := pos_of_gt hn
  have hn'' : 1 < n := one_lt_two.trans hn
  dsimp at ih
  have := prod_cyclotomic_eq_geom_sum hn' R
  apply_fun eval x  at this
  rw [← cons_self_proper_divisors hn'.ne', Finset.erase_cons_of_ne _ hn''.ne', Finset.prod_cons,
    eval_mul, eval_geom_sum] at this
  rcases lt_trichotomy 0 (∑ i in Finset.range n, x ^ i) with (h | h | h)
  · apply pos_of_mul_pos_left
    · rwa [this]
    rw [eval_prod]
    refine' Finset.prod_nonneg fun i hi => _
    simp only [Finset.mem_erase, mem_proper_divisors] at hi
    rw [geom_sum_pos_iff hn'.ne'] at h
    cases' h with hk hx
    · refine' (ih _ hi.2.2 (Nat.two_lt_of_ne _ hi.1 _)).le <;> rintro rfl
      · exact hn'.ne' (zero_dvd_iff.mp hi.2.1)
      · exact even_iff_not_odd.mp (even_iff_two_dvd.mpr hi.2.1) hk
    · rcases eq_or_ne i 2 with (rfl | hk)
      · simpa only [eval_X, eval_one, cyclotomic_two, eval_add] using hx.le
      refine' (ih _ hi.2.2 (Nat.two_lt_of_ne _ hi.1 hk)).le
      rintro rfl
      exact hn'.ne' <| zero_dvd_iff.mp hi.2.1
  · rw [eq_comm, geom_sum_eq_zero_iff_neg_one hn'.ne'] at h
    exact h.1.symm ▸ cyclotomic_neg_one_pos hn
  · apply pos_of_mul_neg_left
    · rwa [this]
    rw [geom_sum_neg_iff hn'.ne'] at h
    have h2 : 2 ∈ n.proper_divisors.erase 1 :=
      by
      rw [Finset.mem_erase, mem_proper_divisors]
      exact ⟨by decide, even_iff_two_dvd.mp h.1, hn⟩
    rw [eval_prod, ← Finset.prod_erase_mul _ _ h2]
    apply mul_nonpos_of_nonneg_of_nonpos
    · refine' Finset.prod_nonneg fun i hi => le_of_lt _
      simp only [Finset.mem_erase, mem_proper_divisors] at hi
      refine' ih _ hi.2.2.2 (Nat.two_lt_of_ne _ hi.2.1 hi.1)
      rintro rfl
      rw [zero_dvd_iff] at hi
      exact hn'.ne' hi.2.2.1
    · simpa only [eval_X, eval_one, cyclotomic_two, eval_add] using h.right.le
#align polynomial.cyclotomic_pos Polynomial.cyclotomic_pos

theorem cyclotomic_pos_and_nonneg (n : ℕ) {R} [LinearOrderedCommRing R] (x : R) :
    (1 < x → 0 < eval x (cyclotomic n R)) ∧ (1 ≤ x → 0 ≤ eval x (cyclotomic n R)) :=
  by
  rcases n with (_ | _ | _ | n) <;>
    simp only [cyclotomic_zero, cyclotomic_one, cyclotomic_two, succ_eq_add_one, eval_X, eval_one,
      eval_add, eval_sub, sub_nonneg, sub_pos, zero_lt_one, zero_le_one, imp_true_iff, imp_self,
      and_self_iff]
  · constructor <;> intro <;> linarith
  · have : 2 < n + 3 := by decide
    constructor <;> intro <;> [skip, apply le_of_lt] <;> apply cyclotomic_pos this
#align polynomial.cyclotomic_pos_and_nonneg Polynomial.cyclotomic_pos_and_nonneg

/-- Cyclotomic polynomials are always positive on inputs larger than one.
Similar to `cyclotomic_pos` but with the condition on the input rather than index of the
cyclotomic polynomial. -/
theorem cyclotomic_pos' (n : ℕ) {R} [LinearOrderedCommRing R] {x : R} (hx : 1 < x) :
    0 < eval x (cyclotomic n R) :=
  (cyclotomic_pos_and_nonneg n x).1 hx
#align polynomial.cyclotomic_pos' Polynomial.cyclotomic_pos'

/-- Cyclotomic polynomials are always nonnegative on inputs one or more. -/
theorem cyclotomic_nonneg (n : ℕ) {R} [LinearOrderedCommRing R] {x : R} (hx : 1 ≤ x) :
    0 ≤ eval x (cyclotomic n R) :=
  (cyclotomic_pos_and_nonneg n x).2 hx
#align polynomial.cyclotomic_nonneg Polynomial.cyclotomic_nonneg

theorem eval_one_cyclotomic_not_prime_pow {R : Type _} [Ring R] {n : ℕ}
    (h : ∀ {p : ℕ}, p.Prime → ∀ k : ℕ, p ^ k ≠ n) : eval 1 (cyclotomic n R) = 1 :=
  by
  rcases n.eq_zero_or_pos with (rfl | hn')
  · simp
  have hn : 1 < n := one_lt_iff_ne_zero_and_ne_one.mpr ⟨hn'.ne', (h Nat.prime_two 0).symm⟩
  rsuffices h | h : eval 1 (cyclotomic n ℤ) = 1 ∨ eval 1 (cyclotomic n ℤ) = -1
  · have := eval_int_cast_map (Int.castRingHom R) (cyclotomic n ℤ) 1
    simpa only [map_cyclotomic, Int.cast_one, h, eq_intCast] using this
  · exfalso
    linarith [cyclotomic_nonneg n (le_refl (1 : ℤ))]
  rw [← Int.natAbs_eq_natAbs_iff, Int.natAbs_one, Nat.eq_one_iff_not_exists_prime_dvd]
  intro p hp hpe
  haveI := Fact.mk hp
  have hpn : p ∣ n := by
    apply hpe.trans
    nth_rw 2 [← Int.natAbs_ofNat n]
    rw [Int.natAbs_dvd_natAbs, ← one_geom_sum, ← eval_geom_sum, ← prod_cyclotomic_eq_geom_sum hn']
    apply eval_dvd
    apply Finset.dvd_prod_of_mem
    simp [hn'.ne', hn.ne']
  have := prod_cyclotomic_eq_geom_sum hn' ℤ
  apply_fun eval 1  at this
  rw [eval_geom_sum, one_geom_sum, eval_prod, eq_comm, ←
    Finset.prod_sdiff <| @range_pow_padicValNat_subset_divisors' p _ _, Finset.prod_image] at this
  simp_rw [eval_one_cyclotomic_prime_pow, Finset.prod_const, Finset.card_range, mul_comm] at this
  rw [← Finset.prod_sdiff <| show {n} ⊆ _ from _] at this
  any_goals infer_instance
  swap
  · simp only [singleton_subset_iff, mem_sdiff, mem_erase, Ne.def, mem_divisors, dvd_refl,
      true_and_iff, mem_image, mem_range, exists_prop, not_exists, not_and]
    exact ⟨⟨hn.ne', hn'.ne'⟩, fun t _ => h hp _⟩
  rw [← Int.natAbs_ofNat p, Int.natAbs_dvd_natAbs] at hpe
  obtain ⟨t, ht⟩ := hpe
  rw [Finset.prod_singleton, ht, mul_left_comm, mul_comm, ← mul_assoc, mul_assoc] at this
  have : (p ^ padicValNat p n * p : ℤ) ∣ n := ⟨_, this⟩
  simp only [← pow_succ', ← Int.natAbs_dvd_natAbs, Int.natAbs_ofNat, Int.natAbs_pow] at this
  exact pow_succ_padicValNat_not_dvd hn'.ne' this
  · rintro x - y - hxy
    apply Nat.succ_injective
    exact Nat.pow_right_injective hp.two_le hxy
#align polynomial.eval_one_cyclotomic_not_prime_pow Polynomial.eval_one_cyclotomic_not_prime_pow

theorem sub_one_pow_totient_lt_cyclotomic_eval {n : ℕ} {q : ℝ} (hn' : 2 ≤ n) (hq' : 1 < q) :
    (q - 1) ^ totient n < (cyclotomic n ℝ).eval q :=
  by
  have hn : 0 < n := pos_of_gt hn'
  have hq := zero_lt_one.trans hq'
  have hfor : ∀ ζ' ∈ primitiveRoots n ℂ, q - 1 ≤ ‖↑q - ζ'‖ :=
    by
    intro ζ' hζ'
    rw [mem_primitiveRoots hn] at hζ'
    convert norm_sub_norm_le (↑q) ζ'
    · rw [Complex.norm_real, Real.norm_of_nonneg hq.le]
    · rw [hζ'.norm'_eq_one hn.ne']
  let ζ := Complex.exp (2 * ↑Real.pi * Complex.I / ↑n)
  have hζ : IsPrimitiveRoot ζ n := Complex.isPrimitiveRoot_exp n hn.ne'
  have hex : ∃ ζ' ∈ primitiveRoots n ℂ, q - 1 < ‖↑q - ζ'‖ :=
    by
    refine' ⟨ζ, (mem_primitiveRoots hn).mpr hζ, _⟩
    suffices ¬SameRay ℝ (q : ℂ) ζ by
      convert lt_norm_sub_of_not_sameRay this <;>
        simp only [hζ.norm'_eq_one hn.ne', Real.norm_of_nonneg hq.le, Complex.norm_real]
    rw [Complex.sameRay_iff]
    push_neg
    refine' ⟨by exact_mod_cast hq.ne', hζ.ne_zero hn.ne', _⟩
    rw [Complex.arg_of_real_of_nonneg hq.le, Ne.def, eq_comm, hζ.arg_eq_zero_iff hn.ne']
    clear_value ζ
    rintro rfl
    linarith [hζ.unique IsPrimitiveRoot.one]
  have : ¬eval (↑q) (cyclotomic n ℂ) = 0 :=
    by
    erw [cyclotomic.eval_apply q n (algebraMap ℝ ℂ)]
    simpa only [Complex.coe_algebraMap, Complex.ofReal_eq_zero] using (cyclotomic_pos' n hq').ne'
  suffices
    Units.mk0 (Real.toNNReal (q - 1)) (by simp [hq']) ^ totient n <
      Units.mk0 ‖(cyclotomic n ℂ).eval q‖₊ (by simp [this])
    by
    simp only [← Units.val_lt_val, Units.val_pow_eq_pow_val, Units.val_mk0, ← NNReal.coe_lt_coe,
      hq'.le, Real.toNNReal_lt_toNNReal_iff_of_nonneg, coe_nnnorm, Complex.norm_eq_abs,
      NNReal.coe_pow, Real.coe_toNNReal', max_eq_left, sub_nonneg] at this
    convert this
    erw [cyclotomic.eval_apply q n (algebraMap ℝ ℂ), eq_comm]
    simp only [cyclotomic_nonneg n hq'.le, Complex.coe_algebraMap, Complex.abs_ofReal, abs_eq_self]
  simp only [cyclotomic_eq_prod_X_sub_primitive_roots hζ, eval_prod, eval_C, eval_X, eval_sub,
    nnnorm_prod, Units.mk0_prod]
  convert Finset.prod_lt_prod' _ _
  swap
  · exact fun _ => Units.mk0 (Real.toNNReal (q - 1)) (by simp [hq'])
  · simp only [Complex.card_primitiveRoots, prod_const, card_attach]
  · simp only [Subtype.coe_mk, Finset.mem_attach, forall_true_left, Subtype.forall, ←
      Units.val_le_val, ← NNReal.coe_le_coe, complex.abs.nonneg, hq'.le, Units.val_mk0,
      Real.coe_toNNReal', coe_nnnorm, Complex.norm_eq_abs, max_le_iff, tsub_le_iff_right]
    intro x hx
    simpa only [and_true_iff, tsub_le_iff_right] using hfor x hx
  · simp only [Subtype.coe_mk, Finset.mem_attach, exists_true_left, Subtype.exists, ←
      NNReal.coe_lt_coe, ← Units.val_lt_val, Units.val_mk0 _, coe_nnnorm]
    simpa only [hq'.le, Real.coe_toNNReal', max_eq_left, sub_nonneg] using hex
#align polynomial.sub_one_pow_totient_lt_cyclotomic_eval Polynomial.sub_one_pow_totient_lt_cyclotomic_eval

theorem sub_one_pow_totient_le_cyclotomic_eval {q : ℝ} (hq' : 1 < q) :
    ∀ n, (q - 1) ^ totient n ≤ (cyclotomic n ℝ).eval q
  | 0 => by simp only [totient_zero, pow_zero, cyclotomic_zero, eval_one]
  | 1 => by simp only [totient_one, pow_one, cyclotomic_one, eval_sub, eval_X, eval_one]
  | n + 2 => (sub_one_pow_totient_lt_cyclotomic_eval (by decide) hq').le
#align polynomial.sub_one_pow_totient_le_cyclotomic_eval Polynomial.sub_one_pow_totient_le_cyclotomic_eval

theorem cyclotomic_eval_lt_add_one_pow_totient {n : ℕ} {q : ℝ} (hn' : 3 ≤ n) (hq' : 1 < q) :
    (cyclotomic n ℝ).eval q < (q + 1) ^ totient n :=
  by
  have hn : 0 < n := pos_of_gt hn'
  have hq := zero_lt_one.trans hq'
  have hfor : ∀ ζ' ∈ primitiveRoots n ℂ, ‖↑q - ζ'‖ ≤ q + 1 :=
    by
    intro ζ' hζ'
    rw [mem_primitiveRoots hn] at hζ'
    convert norm_sub_le (↑q) ζ'
    · rw [Complex.norm_real, Real.norm_of_nonneg (zero_le_one.trans_lt hq').le]
    · rw [hζ'.norm'_eq_one hn.ne']
  let ζ := Complex.exp (2 * ↑Real.pi * Complex.I / ↑n)
  have hζ : IsPrimitiveRoot ζ n := Complex.isPrimitiveRoot_exp n hn.ne'
  have hex : ∃ ζ' ∈ primitiveRoots n ℂ, ‖↑q - ζ'‖ < q + 1 :=
    by
    refine' ⟨ζ, (mem_primitiveRoots hn).mpr hζ, _⟩
    suffices ¬SameRay ℝ (q : ℂ) (-ζ) by
      convert norm_add_lt_of_not_sameRay this <;>
        simp [Real.norm_of_nonneg hq.le, hζ.norm'_eq_one hn.ne', -Complex.norm_eq_abs]
    rw [Complex.sameRay_iff]
    push_neg
    refine' ⟨by exact_mod_cast hq.ne', neg_ne_zero.mpr <| hζ.ne_zero hn.ne', _⟩
    rw [Complex.arg_of_real_of_nonneg hq.le, Ne.def, eq_comm]
    intro h
    rw [Complex.arg_eq_zero_iff, Complex.neg_re, neg_nonneg, Complex.neg_im, neg_eq_zero] at h
    have hζ₀ : ζ ≠ 0 := by
      clear_value ζ
      rintro rfl
      exact hn.ne' (hζ.unique IsPrimitiveRoot.zero)
    have : ζ.re < 0 ∧ ζ.im = 0 := ⟨h.1.lt_of_ne _, h.2⟩
    rw [← Complex.arg_eq_pi_iff, hζ.arg_eq_pi_iff hn.ne'] at this
    rw [this] at hζ
    linarith [hζ.unique <| IsPrimitiveRoot.neg_one 0 two_ne_zero.symm]
    · contrapose! hζ₀
      ext <;> simp [hζ₀, h.2]
  have : ¬eval (↑q) (cyclotomic n ℂ) = 0 :=
    by
    erw [cyclotomic.eval_apply q n (algebraMap ℝ ℂ)]
    simp only [Complex.coe_algebraMap, Complex.ofReal_eq_zero]
    exact (cyclotomic_pos' n hq').Ne.symm
  suffices
    Units.mk0 ‖(cyclotomic n ℂ).eval q‖₊ (by simp [this]) <
      Units.mk0 (Real.toNNReal (q + 1)) (by simp <;> linarith) ^ totient n
    by
    simp only [← Units.val_lt_val, Units.val_pow_eq_pow_val, Units.val_mk0, ← NNReal.coe_lt_coe,
      hq'.le, Real.toNNReal_lt_toNNReal_iff_of_nonneg, coe_nnnorm, Complex.norm_eq_abs,
      NNReal.coe_pow, Real.coe_toNNReal', max_eq_left, sub_nonneg] at this
    convert this
    · erw [cyclotomic.eval_apply q n (algebraMap ℝ ℂ), eq_comm]
      simp [cyclotomic_nonneg n hq'.le]
    rw [eq_comm, max_eq_left_iff]
    linarith
  simp only [cyclotomic_eq_prod_X_sub_primitive_roots hζ, eval_prod, eval_C, eval_X, eval_sub,
    nnnorm_prod, Units.mk0_prod]
  convert Finset.prod_lt_prod' _ _
  swap
  · exact fun _ => Units.mk0 (Real.toNNReal (q + 1)) (by simp <;> linarith only [hq'])
  · simp [Complex.card_primitiveRoots]
  · simp only [Subtype.coe_mk, Finset.mem_attach, forall_true_left, Subtype.forall, ←
      Units.val_le_val, ← NNReal.coe_le_coe, complex.abs.nonneg, hq'.le, Units.val_mk0,
      Real.coe_toNNReal, coe_nnnorm, Complex.norm_eq_abs, max_le_iff]
    intro x hx
    have : Complex.abs _ ≤ _ := hfor x hx
    simp [this]
  · simp only [Subtype.coe_mk, Finset.mem_attach, exists_true_left, Subtype.exists, ←
      NNReal.coe_lt_coe, ← Units.val_lt_val, Units.val_mk0 _, coe_nnnorm]
    obtain ⟨ζ, hζ, hhζ : Complex.abs _ < _⟩ := hex
    exact ⟨ζ, hζ, by simp [hhζ]⟩
#align polynomial.cyclotomic_eval_lt_add_one_pow_totient Polynomial.cyclotomic_eval_lt_add_one_pow_totient

theorem cyclotomic_eval_le_add_one_pow_totient {q : ℝ} (hq' : 1 < q) :
    ∀ n, (cyclotomic n ℝ).eval q ≤ (q + 1) ^ totient n
  | 0 => by simp
  | 1 => by simp [add_assoc, add_nonneg, zero_le_one]
  | 2 => by simp
  | n + 3 => (cyclotomic_eval_lt_add_one_pow_totient (by decide) hq').le
#align polynomial.cyclotomic_eval_le_add_one_pow_totient Polynomial.cyclotomic_eval_le_add_one_pow_totient

theorem sub_one_pow_totient_lt_natAbs_cyclotomic_eval {n : ℕ} {q : ℕ} (hn' : 1 < n) (hq : q ≠ 1) :
    (q - 1) ^ totient n < ((cyclotomic n ℤ).eval ↑q).natAbs :=
  by
  rcases hq.lt_or_lt.imp_left nat.lt_one_iff.mp with (rfl | hq')
  · rw [zero_tsub, zero_pow (Nat.totient_pos (pos_of_gt hn')), pos_iff_ne_zero, Int.natAbs_ne_zero,
      Nat.cast_zero, ← coeff_zero_eq_eval_zero, cyclotomic_coeff_zero _ hn']
    exact one_ne_zero
  rw [← @Nat.cast_lt ℝ, Nat.cast_pow, Nat.cast_sub hq'.le, Nat.cast_one, Int.cast_natAbs]
  refine' (sub_one_pow_totient_lt_cyclotomic_eval hn' (Nat.one_lt_cast.2 hq')).trans_le _
  exact (cyclotomic.eval_apply (q : ℤ) n (algebraMap ℤ ℝ)).trans_le (le_abs_self _)
#align polynomial.sub_one_pow_totient_lt_nat_abs_cyclotomic_eval Polynomial.sub_one_pow_totient_lt_natAbs_cyclotomic_eval

theorem sub_one_lt_natAbs_cyclotomic_eval {n : ℕ} {q : ℕ} (hn' : 1 < n) (hq : q ≠ 1) :
    q - 1 < ((cyclotomic n ℤ).eval ↑q).natAbs :=
  calc
    q - 1 ≤ (q - 1) ^ totient n := Nat.le_self_pow (Nat.totient_pos <| pos_of_gt hn').ne' _
    _ < ((cyclotomic n ℤ).eval ↑q).natAbs := sub_one_pow_totient_lt_natAbs_cyclotomic_eval hn' hq

#align polynomial.sub_one_lt_nat_abs_cyclotomic_eval Polynomial.sub_one_lt_natAbs_cyclotomic_eval

end Polynomial