import number_theory.arithmetic_function
import algebra.squarefree
import algebra.order.floor
import data.list.intervals
import tactic
import measure_theory.integral.interval_integral
import general
import defs
import lemmas_on_defs
import multiplicativity

noncomputable theory
open nat finset function filter
open_locale topological_space interval big_operators filter asymptotics arithmetic_function

namespace squarefree_sums

lemma tμ_ind {p i : ℕ} (hp : nat.prime p) :
tμ (p ^ (i + 1)) = tμ (p ^ i) + sμ (p ^ (i + 1)) :=
begin
  unfold tμ,
  unfold sμ,
  simp,
  unfold sμ',

  rw nat.divisors_prime_pow hp (i + 1),
  rw ← Ico_eq_range,
  have munion : finset.map {to_fun := pow p, inj' := _} (finset.Ico 0 (i + 1 + 1)) =
  (finset.map {to_fun := pow p, inj' := _} (finset.Ico 0 (i + 1))) ∪
  (finset.map {to_fun := pow p, inj' := _} (finset.Ico (i + 1) (i + 1 + 1))), {
    have : (finset.Ico 0 (i + 1 + 1)) = (finset.Ico 0 (i + 1)) ∪ (finset.Ico (i + 1) (i + 1 + 1)), {
      simp,
      rw range_succ,
      rw finset.union_singleton,
    },
    rw this,
    rw map_union,
  },
  have udisjoint : disjoint
  (finset.map {to_fun := pow p, inj' := _} (finset.Ico 0 (i + 1)))
  (finset.map {to_fun := pow p, inj' := _} (finset.Ico (i + 1) (i + 1 + 1))), {
    rw disjoint_iff_ne,
    intros a ha b hb,
    rw finset.mem_map at ha,
    rcases ha with ⟨ia, hia, via⟩,
    rw finset.mem_map at hb,
    rcases hb with ⟨ib, hib, vib⟩,
    simp at via,
    simp at vib,
    by_contradiction H,
    rw ← H at vib,
    rw ← vib at via,
    rw ← exp_eq_iff_pow_eq hp.two_le at via,
    simp at hia,
    simp at hib,
    linarith [vib, hia, hib, via],
  },
  rw munion,
  rw finset.sum_union udisjoint,
  simp,
  rw nat.divisors_prime_pow hp i,
  simp,
end

lemma barrr {i p : ℕ} (hp : nat.prime p) : 2 ≤ i → squarefree_nat (p^i) = 0 :=
begin
  intros hi,
  unfold squarefree_nat,
  simp,
  unfold squarefree_nat',
  simp,
  by_contradiction H,
  have : p * p ∣ p ^ i, {
    obtain ⟨i', hi'⟩ : ∃ i', i = 2 + i', exact two_add hi,
    rw hi',
    rw pow_add,
    rw pow_two,
    exact dvd_mul_right (p * p) (p ^ i'),
  },
  unfold squarefree at H,
  specialize H p this,
  simp at H,
  rw H at hp,
  exact not_prime_one hp,
end

--------------------------------------------------------------------------
--         THEOREM 1: SQUAREFREE CAN BE WRITTEN AS SUM OF MU
--------------------------------------------------------------------------
lemma case_0 (p : ℕ) (hp : nat.prime p) : squarefree_nat p = tμ 1 + sμ p :=
begin
  unfold squarefree_nat,
  unfold sμ,
  simp,
  unfold squarefree_nat',
  unfold sμ',
  simp [(prime_iff.mp hp).squarefree, is_multiplicative_tμ, ssqrt_prime hp],
end

lemma case_1 (p : ℕ) (hp : nat.prime p) (hi : squarefree_nat p = tμ p) : squarefree_nat (p ^ 2) = tμ p + sμ (p ^ 2) :=
begin
  rw barrr hp (calc 2 ≤ 2 : by linarith),
  rw ← hi,
  unfold sμ,
  unfold squarefree_nat,
  simp,
  unfold squarefree_nat',
  unfold sμ',
  simp [(prime_iff.mp hp).squarefree],
  rw pow_two p,
  rw ssqrt_eq,
  rw moebius_of_prime hp,
  ring,
end

theorem squarefree_nat_eq_tμ : squarefree_nat = tμ :=
begin
  rw arithmetic_function.is_multiplicative.eq_iff_eq_on_prime_powers squarefree_nat is_multiplicative_squarefree_nat tμ is_multiplicative_tμ,
  intros p i hp,
  induction i with i hi,
  simp [is_multiplicative_tμ, is_multiplicative_squarefree_nat],
  rw tμ_ind hp,

  by_cases c1 : i = 0,
  simp [c1, case_0 p hp],

  by_cases c2 : i = 1,
  simp [c2] at hi,
  simp [c2, case_1 p hp hi],

  have : 2 ≤ i, exact two_le_nat_iff_not_zero_one.mpr ⟨c1, c2⟩,
  rw ← hi,
  simp [barrr hp this, barrr hp (le_of_lt (calc 2 ≤ i : this ... < i.succ : lt_succ_self i))],

  simp [sμ_prime_pow_le_three p (i + 1) hp (by linarith)],
end

--------------------------------------------------------------------
--       REWRITE tμ IN A WAY MORE CONDUCIVE TO SUM MANIPULATION
--------------------------------------------------------------------

lemma blah {n : ℕ} : μ (ssqrt n) = ite (square n) (μ (sqrt n)) 0 :=
begin
  unfold ssqrt,
  by_cases square n,
  simp [h],
  simp [h],
end

lemma ifififif {n : ℕ} : set.bij_on (λ i, sqrt i) (finset.filter square n.divisors) (finset.filter (λ x, x ^ 2 ∣ n) (finset.Icc 1 n)) :=
begin
  refine ⟨(λ x hx, _), ⟨_, _⟩⟩,
  simp only [mem_filter, mem_coe, ne.def, mem_divisors] at hx,
  simp only [coe_filter, coe_Icc, set.mem_sep_eq, set.mem_Icc],
  obtain ⟨⟨hx_dvd_n, hn_ne_zero⟩, ⟨x', hx'⟩⟩ := hx,
  have hx_ne_zero : x ≠ 0,
  { by_contradiction H, rw H at hx_dvd_n, exact hn_ne_zero (zero_dvd_iff.mp hx_dvd_n), },
  have one_le_x : 1 ≤ x, exact one_le_iff_ne_zero.mpr hx_ne_zero,
  refine ⟨⟨_, _⟩, _⟩,
  rw [←sqrt_one],
  exact sqrt_le_sqrt one_le_x,
  calc sqrt x ≤ x : x.sqrt_le_self
    ... ≤ n : le_of_dvd (zero_lt_iff.mpr hn_ne_zero) hx_dvd_n,
  rw pow_two at hx',
  rwa [hx', sqrt_eq x', pow_two, ←hx'],

  intros x hx y hy hf,
  simp only [mem_filter, mem_coe, ne.def, mem_divisors] at hx,
  simp only [mem_filter, mem_coe, ne.def, mem_divisors] at hy,
  obtain ⟨⟨hy_dvd_n, hn_ne_zero⟩, ⟨x', hx'⟩⟩ := hx,
  obtain ⟨⟨hy_dvd_n, _⟩, ⟨y', hy'⟩⟩ := hy,
  simp only at hf,
  rw pow_two at hx',
  rw pow_two at hy',
  rw [hx', hy', sqrt_eq x', sqrt_eq y'] at hf,
  rw hf at hx',
  calc x = y' * y' : hx' ... = y : hy'.symm,

  intros y hy,
  simp only [coe_filter, coe_Icc, set.mem_sep_eq, set.mem_Icc] at hy,
  obtain ⟨⟨hone_le_y, hy_le_n⟩, ⟨y', hy'⟩⟩ := hy,
  rw pow_two at hy',
  simp only [coe_filter, set.mem_sep_eq, set.mem_image, mem_coe, ne.def, mem_divisors],
  use y * y,
  exact ⟨⟨⟨dvd.intro y' (eq.symm hy'), (by linarith)⟩, (by {use y, rw pow_two} )⟩, sqrt_eq y⟩,
end

lemma finset.sum_bij_on
(f : ℕ → ℤ) (g : ℕ → ℕ) (s t : finset ℕ) (hg : set.bij_on g t s) :
∑ i in t, f (g i) = ∑ i in s, f i :=
begin
  rw [←finsum_mem_coe_finset, ←finsum_mem_coe_finset],
  exact finsum_mem_eq_of_bij_on g hg (λ _ _, rfl),
end

lemma rw_tμ {n : ℕ}: tμ n = ∑ d in finset.Icc 1 n, ite (d ^ 2 ∣ n) (μ d) 0 :=
begin
  unfold tμ,
  simp,
  unfold sμ,
  simp,
  unfold sμ',
  conv {
    to_lhs,
    congr,
    skip,
    funext,
    rw blah,
  },
  rw [sum_ite, sum_ite],
  simp,
  rw [←finsum_mem_coe_finset, ←finsum_mem_coe_finset],
  exact finsum_mem_eq_of_bij_on _ ifififif (λ _ _, rfl),
end

end squarefree_sums


/-
  Stalks of the structure sheaf are local rings.
-/

import ring_theory.ideals
import to_mathlib.localization.local_rings
import to_mathlib.localization.localization_of_iso
import to_mathlib.localization.localization_alt
import spectrum_of_a_ring.structure_presheaf_stalks
import spectrum_of_a_ring.structure_sheaf
import spectrum_of_a_ring.structure_presheaf_stalks
import sheaves.stalk_of_rings
import sheaves.sheaf_of_rings_on_standard_basis

universe u

open localization_alt

noncomputable theory

variables {R : Type u} [comm_ring R]
variables (P : Spec R)

def O := structure_sheaf R

def O_P := stalk_of_rings O.F P

-- We know it is a ring.

instance O_P.is_comm_ring : comm_ring (O_P P) :=
by simp [O_P]; by apply_instance

-- Now we use what we proved about the stalks on basis and the fact
-- that the extension doesn't change stalks to prove the property.

lemma structure_sheaf.stalk_local : local_ring (O_P P) :=
begin
  have Hbij := to_stalk_extension.bijective
    (D_fs_standard_basis R)
    (structure_presheaf_on_basis R)
    P,
  haveI Hhom := to_stalk_extension.is_ring_hom
    (D_fs_standard_basis R)
    (structure_presheaf_on_basis R)
    P,
  haveI Hhom2 := strucutre_presheaf_stalks.φP.is_ring_hom P,
  have Hloc := strucutre_presheaf_stalks.stalk_local.localization P,
  have Hloc' := is_localization_data.of_iso
    (-P.1 : set R)
    (strucutre_presheaf_stalks.φ P)
    Hloc
    (to_stalk_extension _ _ P)
    Hbij
    Hhom,
  have : is_ring_hom (to_stalk_extension Bstd strucutre_presheaf_stalks.F P ∘ strucutre_presheaf_stalks.φ P),
    exact @is_ring_hom.comp _ _ _ _
      (strucutre_presheaf_stalks.φ P) _ _ _
      (to_stalk_extension _ _ P)
      Hhom,
  exact local_ring.of_is_localization_data_at_prime P.2 Hloc',
end


import data.real.basic

-- ℕ = {0,1,2,...}

example (n : ℕ) : (1 : ℝ) ≤ 1.5^n :=
begin
  have h : (1 : ℝ) ≤ 1.5 := by norm_num,
  exact one_le_pow_of_one_le h n,
end
/-
simp -- A = B または A ↔ Bの形を解く
norm_num -- 数値を用いた1.5 < 1.7のような形を解く
linarith -- 線形不等式を解く
library_search -- 必要となりそうな補題を探す(1 ≤ x → 1 ≤ x^nの形なら使える)
-/

example (n : ℕ) : (1 : ℝ) ≤ 1.5^n :=
begin
  apply one_le_pow_of_one_le,
  norm_num,
end


/-
Copyright (c) 2020 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin

! This file was ported from Lean 3 source module ring_theory.roots_of_unity
! leanprover-community/mathlib commit 92ca63f0fb391a9ca5f22d2409a6080e786d99f7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.CharP.Two
import Mathbin.Algebra.NeZero
import Mathbin.Algebra.GcdMonoid.IntegrallyClosed
import Mathbin.Data.Polynomial.RingDivision
import Mathbin.FieldTheory.Finite.Basic
import Mathbin.FieldTheory.Minpoly.IsIntegrallyClosed
import Mathbin.FieldTheory.Separable
import Mathbin.GroupTheory.SpecificGroups.Cyclic
import Mathbin.NumberTheory.Divisors
import Mathbin.RingTheory.IntegralDomain
import Mathbin.Tactic.Zify

/-!
# Roots of unity and primitive roots of unity

We define roots of unity in the context of an arbitrary commutative monoid,
as a subgroup of the group of units. We also define a predicate `is_primitive_root` on commutative
monoids, expressing that an element is a primitive root of unity.

## Main definitions

* `roots_of_unity n M`, for `n : ℕ+` is the subgroup of the units of a commutative monoid `M`
  consisting of elements `x` that satisfy `x ^ n = 1`.
* `is_primitive_root ζ k`: an element `ζ` is a primitive `k`-th root of unity if `ζ ^ k = 1`,
  and if `l` satisfies `ζ ^ l = 1` then `k ∣ l`.
* `primitive_roots k R`: the finset of primitive `k`-th roots of unity in an integral domain `R`.
* `is_primitive_root.aut_to_pow`: the monoid hom that takes an automorphism of a ring to the power
  it sends that specific primitive root, as a member of `(zmod n)ˣ`.

## Main results

* `roots_of_unity.is_cyclic`: the roots of unity in an integral domain form a cyclic group.
* `is_primitive_root.zmod_equiv_zpowers`: `zmod k` is equivalent to
  the subgroup generated by a primitive `k`-th root of unity.
* `is_primitive_root.zpowers_eq`: in an integral domain, the subgroup generated by
  a primitive `k`-th root of unity is equal to the `k`-th roots of unity.
* `is_primitive_root.card_primitive_roots`: if an integral domain
   has a primitive `k`-th root of unity, then it has `φ k` of them.

## Implementation details

It is desirable that `roots_of_unity` is a subgroup,
and it will mainly be applied to rings (e.g. the ring of integers in a number field) and fields.
We therefore implement it as a subgroup of the units of a commutative monoid.

We have chosen to define `roots_of_unity n` for `n : ℕ+`, instead of `n : ℕ`,
because almost all lemmas need the positivity assumption,
and in particular the type class instances for `fintype` and `is_cyclic`.

On the other hand, for primitive roots of unity, it is desirable to have a predicate
not just on units, but directly on elements of the ring/field.
For example, we want to say that `exp (2 * pi * I / n)` is a primitive `n`-th root of unity
in the complex numbers, without having to turn that number into a unit first.

This creates a little bit of friction, but lemmas like `is_primitive_root.is_unit` and
`is_primitive_root.coe_units_iff` should provide the necessary glue.

-/

open Classical BigOperators Polynomial

noncomputable section

open Polynomial

open Finset

variable {M N G R S F : Type _}

variable [CommMonoid M] [CommMonoid N] [DivisionCommMonoid G]

section rootsOfUnity

variable {k l : ℕ+}

/-- `roots_of_unity k M` is the subgroup of elements `m : Mˣ` that satisfy `m ^ k = 1` -/
def rootsOfUnity (k : ℕ+) (M : Type _) [CommMonoid M] : Subgroup Mˣ
    where
  carrier := { ζ | ζ ^ (k : ℕ) = 1 }
  one_mem' := one_pow _
  mul_mem' ζ ξ hζ hξ := by simp_all only [Set.mem_setOf_eq, mul_pow, one_mul]
  inv_mem' ζ hζ := by simp_all only [Set.mem_setOf_eq, inv_pow, inv_one]
#align roots_of_unity rootsOfUnity

@[simp]
theorem mem_rootsOfUnity (k : ℕ+) (ζ : Mˣ) : ζ ∈ rootsOfUnity k M ↔ ζ ^ (k : ℕ) = 1 :=
  Iff.rfl
#align mem_roots_of_unity mem_rootsOfUnity

theorem mem_roots_of_unity' (k : ℕ+) (ζ : Mˣ) : ζ ∈ rootsOfUnity k M ↔ (ζ : M) ^ (k : ℕ) = 1 :=
  by
  rw [mem_rootsOfUnity]
  norm_cast
#align mem_roots_of_unity' mem_roots_of_unity'

theorem rootsOfUnity.coe_injective {n : ℕ+} : Function.Injective (coe : rootsOfUnity n M → M) :=
  Units.ext.comp fun x y => Subtype.ext
#align roots_of_unity.coe_injective rootsOfUnity.coe_injective

/-- Make an element of `roots_of_unity` from a member of the base ring, and a proof that it has
a positive power equal to one. -/
@[simps coe_coe]
def rootsOfUnity.mkOfPowEq (ζ : M) {n : ℕ+} (h : ζ ^ (n : ℕ) = 1) : rootsOfUnity n M :=
  ⟨Units.ofPowEqOne ζ n h n.NeZero, Units.pow_ofPowEqOne _ _⟩
#align roots_of_unity.mk_of_pow_eq rootsOfUnity.mkOfPowEq

@[simp]
theorem rootsOfUnity.coe_mkOfPowEq {ζ : M} {n : ℕ+} (h : ζ ^ (n : ℕ) = 1) :
    (rootsOfUnity.mkOfPowEq _ h : M) = ζ :=
  rfl
#align roots_of_unity.coe_mk_of_pow_eq rootsOfUnity.coe_mkOfPowEq

theorem rootsOfUnity_le_of_dvd (h : k ∣ l) : rootsOfUnity k M ≤ rootsOfUnity l M :=
  by
  obtain ⟨d, rfl⟩ := h
  intro ζ h
  simp_all only [mem_rootsOfUnity, PNat.mul_coe, pow_mul, one_pow]
#align roots_of_unity_le_of_dvd rootsOfUnity_le_of_dvd

theorem map_rootsOfUnity (f : Mˣ →* Nˣ) (k : ℕ+) : (rootsOfUnity k M).map f ≤ rootsOfUnity k N :=
  by
  rintro _ ⟨ζ, h, rfl⟩
  simp_all only [← map_pow, mem_rootsOfUnity, SetLike.mem_coe, MonoidHom.map_one]
#align map_roots_of_unity map_rootsOfUnity

@[norm_cast]
theorem rootsOfUnity.coe_pow [CommMonoid R] (ζ : rootsOfUnity k R) (m : ℕ) :
    ↑(ζ ^ m) = (ζ ^ m : R) := by
  change ↑(↑(ζ ^ m) : Rˣ) = ↑(ζ : Rˣ) ^ m
  rw [Subgroup.coe_pow, Units.val_pow_eq_pow_val]
#align roots_of_unity.coe_pow rootsOfUnity.coe_pow

section CommSemiring

variable [CommSemiring R] [CommSemiring S]

/-- Restrict a ring homomorphism to the nth roots of unity -/
def restrictRootsOfUnity [RingHomClass F R S] (σ : F) (n : ℕ+) :
    rootsOfUnity n R →* rootsOfUnity n S :=
  let h : ∀ ξ : rootsOfUnity n R, σ ξ ^ (n : ℕ) = 1 := fun ξ =>
    by
    change σ (ξ : Rˣ) ^ (n : ℕ) = 1
    rw [← map_pow, ← Units.val_pow_eq_pow_val, show (ξ : Rˣ) ^ (n : ℕ) = 1 from ξ.2, Units.val_one,
      map_one σ]
  { toFun := fun ξ =>
      ⟨@unitOfInvertible _ _ _ (invertibleOfPowEqOne _ _ (h ξ) n.NeZero),
        by
        ext
        rw [Units.val_pow_eq_pow_val]
        exact h ξ⟩
    map_one' := by
      ext
      exact map_one σ
    map_mul' := fun ξ₁ ξ₂ => by
      ext
      rw [Subgroup.coe_mul, Units.val_mul]
      exact map_mul σ _ _ }
#align restrict_roots_of_unity restrictRootsOfUnity

@[simp]
theorem restrictRootsOfUnity_coe_apply [RingHomClass F R S] (σ : F) (ζ : rootsOfUnity k R) :
    ↑(restrictRootsOfUnity σ k ζ) = σ ↑ζ :=
  rfl
#align restrict_roots_of_unity_coe_apply restrictRootsOfUnity_coe_apply

/-- Restrict a ring isomorphism to the nth roots of unity -/
def RingEquiv.restrictRootsOfUnity (σ : R ≃+* S) (n : ℕ+) : rootsOfUnity n R ≃* rootsOfUnity n S
    where
  toFun := restrictRootsOfUnity σ.toRingHom n
  invFun := restrictRootsOfUnity σ.symm.toRingHom n
  left_inv ξ := by
    ext
    exact σ.symm_apply_apply ξ
  right_inv ξ := by
    ext
    exact σ.apply_symm_apply ξ
  map_mul' := (restrictRootsOfUnity _ n).map_mul
#align ring_equiv.restrict_roots_of_unity RingEquiv.restrictRootsOfUnity

@[simp]
theorem RingEquiv.restrictRootsOfUnity_coe_apply (σ : R ≃+* S) (ζ : rootsOfUnity k R) :
    ↑(σ.restrictRootsOfUnity k ζ) = σ ↑ζ :=
  rfl
#align ring_equiv.restrict_roots_of_unity_coe_apply RingEquiv.restrictRootsOfUnity_coe_apply

@[simp]
theorem RingEquiv.restrictRootsOfUnity_symm (σ : R ≃+* S) :
    (σ.restrictRootsOfUnity k).symm = σ.symm.restrictRootsOfUnity k :=
  rfl
#align ring_equiv.restrict_roots_of_unity_symm RingEquiv.restrictRootsOfUnity_symm

end CommSemiring

section IsDomain

variable [CommRing R] [IsDomain R]

theorem mem_rootsOfUnity_iff_mem_nthRoots {ζ : Rˣ} :
    ζ ∈ rootsOfUnity k R ↔ (ζ : R) ∈ nthRoots k (1 : R) := by
  simp only [mem_rootsOfUnity, mem_nth_roots k.pos, Units.ext_iff, Units.val_one,
    Units.val_pow_eq_pow_val]
#align mem_roots_of_unity_iff_mem_nth_roots mem_rootsOfUnity_iff_mem_nthRoots

variable (k R)

/-- Equivalence between the `k`-th roots of unity in `R` and the `k`-th roots of `1`.

This is implemented as equivalence of subtypes,
because `roots_of_unity` is a subgroup of the group of units,
whereas `nth_roots` is a multiset. -/
def rootsOfUnityEquivNthRoots : rootsOfUnity k R ≃ { x // x ∈ nthRoots k (1 : R) } :=
  by
  refine'
    { toFun := fun x => ⟨x, mem_roots_of_unity_iff_mem_nth_roots.mp x.2⟩
      invFun := fun x => ⟨⟨x, x ^ (k - 1 : ℕ), _, _⟩, _⟩
      left_inv := _
      right_inv := _ }
  pick_goal 4;
  · rintro ⟨x, hx⟩
    ext
    rfl
  pick_goal 4;
  · rintro ⟨x, hx⟩
    ext
    rfl
  all_goals
    rcases x with ⟨x, hx⟩; rw [mem_nth_roots k.pos] at hx
    simp only [Subtype.coe_mk, ← pow_succ, ← pow_succ', hx,
      tsub_add_cancel_of_le (show 1 ≤ (k : ℕ) from k.one_le)]
  · show (_ : Rˣ) ^ (k : ℕ) = 1
    simp only [Units.ext_iff, hx, Units.val_mk, Units.val_one, Subtype.coe_mk,
      Units.val_pow_eq_pow_val]
#align roots_of_unity_equiv_nth_roots rootsOfUnityEquivNthRoots

variable {k R}

@[simp]
theorem rootsOfUnityEquivNthRoots_apply (x : rootsOfUnity k R) :
    (rootsOfUnityEquivNthRoots R k x : R) = x :=
  rfl
#align roots_of_unity_equiv_nth_roots_apply rootsOfUnityEquivNthRoots_apply

@[simp]
theorem rootsOfUnityEquivNthRoots_symm_apply (x : { x // x ∈ nthRoots k (1 : R) }) :
    ((rootsOfUnityEquivNthRoots R k).symm x : R) = x :=
  rfl
#align roots_of_unity_equiv_nth_roots_symm_apply rootsOfUnityEquivNthRoots_symm_apply

variable (k R)

instance rootsOfUnity.fintype : Fintype (rootsOfUnity k R) :=
  Fintype.ofEquiv { x // x ∈ nthRoots k (1 : R) } <| (rootsOfUnityEquivNthRoots R k).symm
#align roots_of_unity.fintype rootsOfUnity.fintype

instance rootsOfUnity.isCyclic : IsCyclic (rootsOfUnity k R) :=
  isCyclic_of_subgroup_isDomain ((Units.coeHom R).comp (rootsOfUnity k R).Subtype)
    (Units.ext.comp Subtype.val_injective)
#align roots_of_unity.is_cyclic rootsOfUnity.isCyclic

theorem card_rootsOfUnity : Fintype.card (rootsOfUnity k R) ≤ k :=
  calc
    Fintype.card (rootsOfUnity k R) = Fintype.card { x // x ∈ nthRoots k (1 : R) } :=
      Fintype.card_congr (rootsOfUnityEquivNthRoots R k)
    _ ≤ (nthRoots k (1 : R)).attach.card := (Multiset.card_le_of_le (Multiset.dedup_le _))
    _ = (nthRoots k (1 : R)).card := Multiset.card_attach
    _ ≤ k := card_nthRoots k 1

#align card_roots_of_unity card_rootsOfUnity

variable {k R}

theorem map_root_of_unity_eq_pow_self [RingHomClass F R R] (σ : F) (ζ : rootsOfUnity k R) :
    ∃ m : ℕ, σ ζ = ζ ^ m :=
  by
  obtain ⟨m, hm⟩ := MonoidHom.map_cyclic (restrictRootsOfUnity σ k)
  rw [← restrictRootsOfUnity_coe_apply, hm, zpow_eq_mod_orderOf, ←
    Int.toNat_of_nonneg
      (m.mod_nonneg (int.coe_nat_ne_zero.mpr (pos_iff_ne_zero.mp (orderOf_pos ζ)))),
    zpow_ofNat, rootsOfUnity.coe_pow]
  exact ⟨(m % orderOf ζ).toNat, rfl⟩
#align map_root_of_unity_eq_pow_self map_root_of_unity_eq_pow_self

end IsDomain

section Reduced

variable (R) [CommRing R] [IsReduced R]

@[simp]
theorem mem_rootsOfUnity_prime_pow_mul_iff (p k : ℕ) (m : ℕ+) [hp : Fact p.Prime] [CharP R p]
    {ζ : Rˣ} : ζ ∈ rootsOfUnity (⟨p, hp.1.Pos⟩ ^ k * m) R ↔ ζ ∈ rootsOfUnity m R := by
  simp [mem_roots_of_unity']
#align mem_roots_of_unity_prime_pow_mul_iff mem_rootsOfUnity_prime_pow_mul_iff

end Reduced

end rootsOfUnity

/-- An element `ζ` is a primitive `k`-th root of unity if `ζ ^ k = 1`,
and if `l` satisfies `ζ ^ l = 1` then `k ∣ l`. -/
structure IsPrimitiveRoot (ζ : M) (k : ℕ) : Prop where
  pow_eq_one : ζ ^ (k : ℕ) = 1
  dvd_of_pow_eq_one : ∀ l : ℕ, ζ ^ l = 1 → k ∣ l
#align is_primitive_root IsPrimitiveRoot

/-- Turn a primitive root μ into a member of the `roots_of_unity` subgroup. -/
@[simps]
def IsPrimitiveRoot.toRootsOfUnity {μ : M} {n : ℕ+} (h : IsPrimitiveRoot μ n) : rootsOfUnity n M :=
  rootsOfUnity.mkOfPowEq μ h.pow_eq_one
#align is_primitive_root.to_roots_of_unity IsPrimitiveRoot.toRootsOfUnity

section primitiveRoots

variable {k : ℕ}

/-- `primitive_roots k R` is the finset of primitive `k`-th roots of unity
in the integral domain `R`. -/
def primitiveRoots (k : ℕ) (R : Type _) [CommRing R] [IsDomain R] : Finset R :=
  (nthRoots k (1 : R)).toFinset.filterₓ fun ζ => IsPrimitiveRoot ζ k
#align primitive_roots primitiveRoots

variable [CommRing R] [IsDomain R]

@[simp]
theorem mem_primitiveRoots {ζ : R} (h0 : 0 < k) : ζ ∈ primitiveRoots k R ↔ IsPrimitiveRoot ζ k :=
  by
  rw [primitiveRoots, mem_filter, Multiset.mem_toFinset, mem_nth_roots h0, and_iff_right_iff_imp]
  exact IsPrimitiveRoot.pow_eq_one
#align mem_primitive_roots mem_primitiveRoots

@[simp]
theorem primitiveRoots_zero : primitiveRoots 0 R = ∅ := by
  rw [primitiveRoots, nth_roots_zero, Multiset.toFinset_zero, Finset.filter_empty]
#align primitive_roots_zero primitiveRoots_zero

theorem isPrimitiveRoot_of_mem_primitiveRoots {ζ : R} (h : ζ ∈ primitiveRoots k R) :
    IsPrimitiveRoot ζ k :=
  k.eq_zero_or_pos.elim (fun hk => False.elim <| by simpa [hk] using h) fun hk =>
    (mem_primitiveRoots hk).1 h
#align is_primitive_root_of_mem_primitive_roots isPrimitiveRoot_of_mem_primitiveRoots

end primitiveRoots

namespace IsPrimitiveRoot

variable {k l : ℕ}

theorem iff_def (ζ : M) (k : ℕ) : IsPrimitiveRoot ζ k ↔ ζ ^ k = 1 ∧ ∀ l : ℕ, ζ ^ l = 1 → k ∣ l :=
  ⟨fun ⟨h1, h2⟩ => ⟨h1, h2⟩, fun ⟨h1, h2⟩ => ⟨h1, h2⟩⟩
#align is_primitive_root.iff_def IsPrimitiveRoot.iff_def

theorem mk_of_lt (ζ : M) (hk : 0 < k) (h1 : ζ ^ k = 1) (h : ∀ l : ℕ, 0 < l → l < k → ζ ^ l ≠ 1) :
    IsPrimitiveRoot ζ k := by
  refine' ⟨h1, fun l hl => _⟩
  suffices k.gcd l = k by exact this ▸ k.gcd_dvd_right l
  rw [eq_iff_le_not_lt]
  refine' ⟨Nat.le_of_dvd hk (k.gcd_dvd_left l), _⟩
  intro h'
  apply h _ (Nat.gcd_pos_of_pos_left _ hk) h'
  exact pow_gcd_eq_one _ h1 hl
#align is_primitive_root.mk_of_lt IsPrimitiveRoot.mk_of_lt

section CommMonoid

variable {ζ : M} {f : F} (h : IsPrimitiveRoot ζ k)

@[nontriviality]
theorem of_subsingleton [Subsingleton M] (x : M) : IsPrimitiveRoot x 1 :=
  ⟨Subsingleton.elim _ _, fun _ _ => one_dvd _⟩
#align is_primitive_root.of_subsingleton IsPrimitiveRoot.of_subsingleton

theorem pow_eq_one_iff_dvd (l : ℕ) : ζ ^ l = 1 ↔ k ∣ l :=
  ⟨h.dvd_of_pow_eq_one l, by
    rintro ⟨i, rfl⟩
    simp only [pow_mul, h.pow_eq_one, one_pow, PNat.mul_coe]⟩
#align is_primitive_root.pow_eq_one_iff_dvd IsPrimitiveRoot.pow_eq_one_iff_dvd

theorem isUnit (h : IsPrimitiveRoot ζ k) (h0 : 0 < k) : IsUnit ζ :=
  by
  apply isUnit_of_mul_eq_one ζ (ζ ^ (k - 1))
  rw [← pow_succ, tsub_add_cancel_of_le h0.nat_succ_le, h.pow_eq_one]
#align is_primitive_root.is_unit IsPrimitiveRoot.isUnit

theorem pow_ne_one_of_pos_of_lt (h0 : 0 < l) (hl : l < k) : ζ ^ l ≠ 1 :=
  mt (Nat.le_of_dvd h0 ∘ h.dvd_of_pow_eq_one _) <| not_le_of_lt hl
#align is_primitive_root.pow_ne_one_of_pos_of_lt IsPrimitiveRoot.pow_ne_one_of_pos_of_lt

theorem ne_one (hk : 1 < k) : ζ ≠ 1 :=
  h.pow_ne_one_of_pos_of_lt zero_lt_one hk ∘ (pow_one ζ).trans
#align is_primitive_root.ne_one IsPrimitiveRoot.ne_one

theorem pow_inj (h : IsPrimitiveRoot ζ k) ⦃i j : ℕ⦄ (hi : i < k) (hj : j < k) (H : ζ ^ i = ζ ^ j) :
    i = j := by
  wlog hij : i ≤ j generalizing i j
  · exact (this hj hi H.symm (le_of_not_le hij)).symm
  apply le_antisymm hij
  rw [← tsub_eq_zero_iff_le]
  apply Nat.eq_zero_of_dvd_of_lt _ (lt_of_le_of_lt tsub_le_self hj)
  apply h.dvd_of_pow_eq_one
  rw [← ((h.is_unit (lt_of_le_of_lt (Nat.zero_le _) hi)).pow i).mul_left_inj, ← pow_add,
    tsub_add_cancel_of_le hij, H, one_mul]
#align is_primitive_root.pow_inj IsPrimitiveRoot.pow_inj

theorem one : IsPrimitiveRoot (1 : M) 1 :=
  { pow_eq_one := pow_one _
    dvd_of_pow_eq_one := fun l hl => one_dvd _ }
#align is_primitive_root.one IsPrimitiveRoot.one

@[simp]
theorem one_right_iff : IsPrimitiveRoot ζ 1 ↔ ζ = 1 :=
  by
  constructor
  · intro h
    rw [← pow_one ζ, h.pow_eq_one]
  · rintro rfl
    exact one
#align is_primitive_root.one_right_iff IsPrimitiveRoot.one_right_iff

@[simp]
theorem coe_submonoidClass_iff {M B : Type _} [CommMonoid M] [SetLike B M] [SubmonoidClass B M]
    {N : B} {ζ : N} : IsPrimitiveRoot (ζ : M) k ↔ IsPrimitiveRoot ζ k := by
  simp [iff_def, ← SubmonoidClass.coe_pow]
#align is_primitive_root.coe_submonoid_class_iff IsPrimitiveRoot.coe_submonoidClass_iff

@[simp]
theorem coe_units_iff {ζ : Mˣ} : IsPrimitiveRoot (ζ : M) k ↔ IsPrimitiveRoot ζ k := by
  simp only [iff_def, Units.ext_iff, Units.val_pow_eq_pow_val, Units.val_one]
#align is_primitive_root.coe_units_iff IsPrimitiveRoot.coe_units_iff

theorem pow_of_coprime (h : IsPrimitiveRoot ζ k) (i : ℕ) (hi : i.coprime k) :
    IsPrimitiveRoot (ζ ^ i) k := by
  by_cases h0 : k = 0
  · subst k
    simp_all only [pow_one, Nat.coprime_zero_right]
  rcases h.is_unit (Nat.pos_of_ne_zero h0) with ⟨ζ, rfl⟩
  rw [← Units.val_pow_eq_pow_val]
  rw [coe_units_iff] at h⊢
  refine'
    { pow_eq_one := by rw [← pow_mul', pow_mul, h.pow_eq_one, one_pow]
      dvd_of_pow_eq_one := _ }
  intro l hl
  apply h.dvd_of_pow_eq_one
  rw [← pow_one ζ, ← zpow_ofNat ζ, ← hi.gcd_eq_one, Nat.gcd_eq_gcd_ab, zpow_add, mul_pow, ←
    zpow_ofNat, ← zpow_mul, mul_right_comm]
  simp only [zpow_mul, hl, h.pow_eq_one, one_zpow, one_pow, one_mul, zpow_ofNat]
#align is_primitive_root.pow_of_coprime IsPrimitiveRoot.pow_of_coprime

theorem pow_of_prime (h : IsPrimitiveRoot ζ k) {p : ℕ} (hprime : Nat.Prime p) (hdiv : ¬p ∣ k) :
    IsPrimitiveRoot (ζ ^ p) k :=
  h.pow_of_coprime p (hprime.coprime_iff_not_dvd.2 hdiv)
#align is_primitive_root.pow_of_prime IsPrimitiveRoot.pow_of_prime

/- ./././Mathport/Syntax/Translate/Tactic/Lean3.lean:132:4: warning: unsupported: rw with cfg: { occs := occurrences.pos[occurrences.pos] «expr[ ,]»([1]) } -/
theorem pow_iff_coprime (h : IsPrimitiveRoot ζ k) (h0 : 0 < k) (i : ℕ) :
    IsPrimitiveRoot (ζ ^ i) k ↔ i.coprime k :=
  by
  refine' ⟨_, h.pow_of_coprime i⟩
  intro hi
  obtain ⟨a, ha⟩ := i.gcd_dvd_left k
  obtain ⟨b, hb⟩ := i.gcd_dvd_right k
  suffices b = k by rwa [this, ← one_mul k, mul_left_inj' h0.ne', eq_comm] at hb
  rw [ha] at hi
  rw [mul_comm] at hb
  apply Nat.dvd_antisymm ⟨i.gcd k, hb⟩ (hi.dvd_of_pow_eq_one b _)
  rw [← pow_mul', ← mul_assoc, ← hb, pow_mul, h.pow_eq_one, one_pow]
#align is_primitive_root.pow_iff_coprime IsPrimitiveRoot.pow_iff_coprime

protected theorem orderOf (ζ : M) : IsPrimitiveRoot ζ (orderOf ζ) :=
  ⟨pow_orderOf_eq_one ζ, fun l => orderOf_dvd_of_pow_eq_one⟩
#align is_primitive_root.order_of IsPrimitiveRoot.orderOf

theorem unique {ζ : M} (hk : IsPrimitiveRoot ζ k) (hl : IsPrimitiveRoot ζ l) : k = l :=
  Nat.dvd_antisymm (hk.2 _ hl.1) (hl.2 _ hk.1)
#align is_primitive_root.unique IsPrimitiveRoot.unique

theorem eq_orderOf : k = orderOf ζ :=
  h.unique (IsPrimitiveRoot.orderOf ζ)
#align is_primitive_root.eq_order_of IsPrimitiveRoot.eq_orderOf

protected theorem iff (hk : 0 < k) :
    IsPrimitiveRoot ζ k ↔ ζ ^ k = 1 ∧ ∀ l : ℕ, 0 < l → l < k → ζ ^ l ≠ 1 :=
  by
  refine'
    ⟨fun h => ⟨h.pow_eq_one, fun l hl' hl => _⟩, fun ⟨hζ, hl⟩ =>
      IsPrimitiveRoot.mk_of_lt ζ hk hζ hl⟩
  rw [h.eq_order_of] at hl
  exact pow_ne_one_of_lt_orderOf' hl'.ne' hl
#align is_primitive_root.iff IsPrimitiveRoot.iff

protected theorem not_iff : ¬IsPrimitiveRoot ζ k ↔ orderOf ζ ≠ k :=
  ⟨fun h hk => h <| hk ▸ IsPrimitiveRoot.orderOf ζ, fun h hk =>
    h.symm <| hk.unique <| IsPrimitiveRoot.orderOf ζ⟩
#align is_primitive_root.not_iff IsPrimitiveRoot.not_iff

theorem pow_of_dvd (h : IsPrimitiveRoot ζ k) {p : ℕ} (hp : p ≠ 0) (hdiv : p ∣ k) :
    IsPrimitiveRoot (ζ ^ p) (k / p) :=
  by
  suffices orderOf (ζ ^ p) = k / p by exact this ▸ IsPrimitiveRoot.orderOf (ζ ^ p)
  rw [orderOf_pow' _ hp, ← eq_order_of h, Nat.gcd_eq_right hdiv]
#align is_primitive_root.pow_of_dvd IsPrimitiveRoot.pow_of_dvd

protected theorem mem_rootsOfUnity {ζ : Mˣ} {n : ℕ+} (h : IsPrimitiveRoot ζ n) :
    ζ ∈ rootsOfUnity n M :=
  h.pow_eq_one
#align is_primitive_root.mem_roots_of_unity IsPrimitiveRoot.mem_rootsOfUnity

/-- If there is a `n`-th primitive root of unity in `R` and `b` divides `n`,
then there is a `b`-th primitive root of unity in `R`. -/
theorem pow {n : ℕ} {a b : ℕ} (hn : 0 < n) (h : IsPrimitiveRoot ζ n) (hprod : n = a * b) :
    IsPrimitiveRoot (ζ ^ a) b := by
  subst n
  simp only [iff_def, ← pow_mul, h.pow_eq_one, eq_self_iff_true, true_and_iff]
  intro l hl
  have ha0 : a ≠ 0 := by
    rintro rfl
    simpa only [Nat.not_lt_zero, MulZeroClass.zero_mul] using hn
  rwa [← mul_dvd_mul_iff_left ha0]
  exact h.dvd_of_pow_eq_one _ hl
#align is_primitive_root.pow IsPrimitiveRoot.pow

section Maps

open Function

theorem map_of_injective [MonoidHomClass F M N] (h : IsPrimitiveRoot ζ k) (hf : Injective f) :
    IsPrimitiveRoot (f ζ) k :=
  { pow_eq_one := by rw [← map_pow, h.pow_eq_one, _root_.map_one]
    dvd_of_pow_eq_one := by
      rw [h.eq_order_of]
      intro l hl
      rw [← map_pow, ← map_one f] at hl
      exact orderOf_dvd_of_pow_eq_one (hf hl) }
#align is_primitive_root.map_of_injective IsPrimitiveRoot.map_of_injective

theorem of_map_of_injective [MonoidHomClass F M N] (h : IsPrimitiveRoot (f ζ) k)
    (hf : Injective f) : IsPrimitiveRoot ζ k :=
  { pow_eq_one := by
      apply_fun f
      rw [map_pow, _root_.map_one, h.pow_eq_one]
    dvd_of_pow_eq_one := by
      rw [h.eq_order_of]
      intro l hl
      apply_fun f  at hl
      rw [map_pow, _root_.map_one] at hl
      exact orderOf_dvd_of_pow_eq_one hl }
#align is_primitive_root.of_map_of_injective IsPrimitiveRoot.of_map_of_injective

theorem map_iff_of_injective [MonoidHomClass F M N] (hf : Injective f) :
    IsPrimitiveRoot (f ζ) k ↔ IsPrimitiveRoot ζ k :=
  ⟨fun h => h.of_map_of_injective hf, fun h => h.map_of_injective hf⟩
#align is_primitive_root.map_iff_of_injective IsPrimitiveRoot.map_iff_of_injective

end Maps

end CommMonoid

section CommMonoidWithZero

variable {M₀ : Type _} [CommMonoidWithZero M₀]

theorem zero [Nontrivial M₀] : IsPrimitiveRoot (0 : M₀) 0 :=
  ⟨pow_zero 0, fun l hl => by
    simpa [zero_pow_eq, show ∀ p, ¬p → False ↔ p from @Classical.not_not] using hl⟩
#align is_primitive_root.zero IsPrimitiveRoot.zero

protected theorem ne_zero [Nontrivial M₀] {ζ : M₀} (h : IsPrimitiveRoot ζ k) : k ≠ 0 → ζ ≠ 0 :=
  mt fun hn => h.unique (hn.symm ▸ IsPrimitiveRoot.zero)
#align is_primitive_root.ne_zero IsPrimitiveRoot.ne_zero

end CommMonoidWithZero

section DivisionCommMonoid

variable {ζ : G}

theorem zpow_eq_one (h : IsPrimitiveRoot ζ k) : ζ ^ (k : ℤ) = 1 :=
  by
  rw [zpow_ofNat]
  exact h.pow_eq_one
#align is_primitive_root.zpow_eq_one IsPrimitiveRoot.zpow_eq_one

theorem zpow_eq_one_iff_dvd (h : IsPrimitiveRoot ζ k) (l : ℤ) : ζ ^ l = 1 ↔ (k : ℤ) ∣ l :=
  by
  by_cases h0 : 0 ≤ l
  · lift l to ℕ using h0
    rw [zpow_ofNat]
    norm_cast
    exact h.pow_eq_one_iff_dvd l
  · have : 0 ≤ -l := by
      simp only [not_le, neg_nonneg] at h0⊢
      exact le_of_lt h0
    lift -l to ℕ using this with l' hl'
    rw [← dvd_neg, ← hl']
    norm_cast
    rw [← h.pow_eq_one_iff_dvd, ← inv_inj, ← zpow_neg, ← hl', zpow_ofNat, inv_one]
#align is_primitive_root.zpow_eq_one_iff_dvd IsPrimitiveRoot.zpow_eq_one_iff_dvd

theorem inv (h : IsPrimitiveRoot ζ k) : IsPrimitiveRoot ζ⁻¹ k :=
  { pow_eq_one := by simp only [h.pow_eq_one, inv_one, eq_self_iff_true, inv_pow]
    dvd_of_pow_eq_one := by
      intro l hl
      apply h.dvd_of_pow_eq_one l
      rw [← inv_inj, ← inv_pow, hl, inv_one] }
#align is_primitive_root.inv IsPrimitiveRoot.inv

@[simp]
theorem inv_iff : IsPrimitiveRoot ζ⁻¹ k ↔ IsPrimitiveRoot ζ k :=
  by
  refine' ⟨_, fun h => inv h⟩
  intro h
  rw [← inv_inv ζ]
  exact inv h
#align is_primitive_root.inv_iff IsPrimitiveRoot.inv_iff

theorem zpow_of_gcd_eq_one (h : IsPrimitiveRoot ζ k) (i : ℤ) (hi : i.gcd k = 1) :
    IsPrimitiveRoot (ζ ^ i) k := by
  by_cases h0 : 0 ≤ i
  · lift i to ℕ using h0
    rw [zpow_ofNat]
    exact h.pow_of_coprime i hi
  have : 0 ≤ -i := by
    simp only [not_le, neg_nonneg] at h0⊢
    exact le_of_lt h0
  lift -i to ℕ using this with i' hi'
  rw [← inv_iff, ← zpow_neg, ← hi', zpow_ofNat]
  apply h.pow_of_coprime
  rw [Int.gcd, ← Int.natAbs_neg, ← hi'] at hi
  exact hi
#align is_primitive_root.zpow_of_gcd_eq_one IsPrimitiveRoot.zpow_of_gcd_eq_one

end DivisionCommMonoid

section IsDomain

variable {ζ : R}

variable [CommRing R] [IsDomain R]

@[simp]
theorem primitiveRoots_one : primitiveRoots 1 R = {(1 : R)} :=
  by
  apply Finset.eq_singleton_iff_unique_mem.2
  constructor
  · simp only [IsPrimitiveRoot.one_right_iff, mem_primitiveRoots zero_lt_one]
  · intro x hx
    rw [mem_primitiveRoots zero_lt_one, IsPrimitiveRoot.one_right_iff] at hx
    exact hx
#align is_primitive_root.primitive_roots_one IsPrimitiveRoot.primitiveRoots_one

theorem ne_zero' {n : ℕ+} (hζ : IsPrimitiveRoot ζ n) : NeZero ((n : ℕ) : R) :=
  by
  let p := ringChar R
  have hfin := multiplicity.finite_nat_iff.2 ⟨CharP.char_ne_one R p, n.pos⟩
  obtain ⟨m, hm⟩ := multiplicity.exists_eq_pow_mul_and_not_dvd hfin
  by_cases hp : p ∣ n
  · obtain ⟨k, hk⟩ := Nat.exists_eq_succ_of_ne_zero (multiplicity.pos_of_dvd hfin hp).ne'
    haveI : NeZero p := NeZero.of_pos (Nat.pos_of_dvd_of_pos hp n.pos)
    haveI hpri : Fact p.prime := CharP.char_is_prime_of_pos R p
    have := hζ.pow_eq_one
    rw [hm.1, hk, pow_succ, mul_assoc, pow_mul', ← frobenius_def, ← frobenius_one p] at this
    exfalso
    have hpos : 0 < p ^ k * m :=
      by
      refine' mul_pos (pow_pos hpri.1.Pos _) (Nat.pos_of_ne_zero fun h => _)
      have H := hm.1
      rw [h] at H
      simpa using H
    refine' hζ.pow_ne_one_of_pos_of_lt hpos _ (frobenius_inj R p this)
    · rw [hm.1, hk, pow_succ, mul_assoc, mul_comm p]
      exact lt_mul_of_one_lt_right hpos hpri.1.one_lt
  · exact NeZero.of_not_dvd R hp
#align is_primitive_root.ne_zero' IsPrimitiveRoot.ne_zero'

theorem mem_nthRootsFinset (hζ : IsPrimitiveRoot ζ k) (hk : 0 < k) : ζ ∈ nthRootsFinset k R :=
  (mem_nthRootsFinset hk).2 hζ.pow_eq_one
#align is_primitive_root.mem_nth_roots_finset IsPrimitiveRoot.mem_nthRootsFinset

end IsDomain

section IsDomain

variable [CommRing R]

variable {ζ : Rˣ} (h : IsPrimitiveRoot ζ k)

theorem eq_neg_one_of_two_right [NoZeroDivisors R] {ζ : R} (h : IsPrimitiveRoot ζ 2) : ζ = -1 :=
  by
  apply (eq_or_eq_neg_of_sq_eq_sq ζ 1 _).resolve_left
  · rw [← pow_one ζ]
    apply h.pow_ne_one_of_pos_of_lt <;> decide
  · simp only [h.pow_eq_one, one_pow]
#align is_primitive_root.eq_neg_one_of_two_right IsPrimitiveRoot.eq_neg_one_of_two_right

theorem neg_one (p : ℕ) [Nontrivial R] [h : CharP R p] (hp : p ≠ 2) : IsPrimitiveRoot (-1 : R) 2 :=
  by
  convert IsPrimitiveRoot.orderOf (-1 : R)
  rw [orderOf_neg_one, if_neg]
  rwa [ring_char.eq_iff.mpr h]
#align is_primitive_root.neg_one IsPrimitiveRoot.neg_one

/-- If `1 < k` then `(∑ i in range k, ζ ^ i) = 0`. -/
theorem geom_sum_eq_zero [IsDomain R] {ζ : R} (hζ : IsPrimitiveRoot ζ k) (hk : 1 < k) :
    (∑ i in range k, ζ ^ i) = 0 :=
  by
  refine' eq_zero_of_ne_zero_of_mul_left_eq_zero (sub_ne_zero_of_ne (hζ.ne_one hk).symm) _
  rw [mul_neg_geom_sum, hζ.pow_eq_one, sub_self]
#align is_primitive_root.geom_sum_eq_zero IsPrimitiveRoot.geom_sum_eq_zero

/-- If `1 < k`, then `ζ ^ k.pred = -(∑ i in range k.pred, ζ ^ i)`. -/
theorem pow_sub_one_eq [IsDomain R] {ζ : R} (hζ : IsPrimitiveRoot ζ k) (hk : 1 < k) :
    ζ ^ k.pred = -∑ i in range k.pred, ζ ^ i := by
  rw [eq_neg_iff_add_eq_zero, add_comm, ← sum_range_succ, ← Nat.succ_eq_add_one,
    Nat.succ_pred_eq_of_pos (pos_of_gt hk), hζ.geom_sum_eq_zero hk]
#align is_primitive_root.pow_sub_one_eq IsPrimitiveRoot.pow_sub_one_eq

/-- The (additive) monoid equivalence between `zmod k`
and the powers of a primitive root of unity `ζ`. -/
def zmodEquivZpowers (h : IsPrimitiveRoot ζ k) : ZMod k ≃+ Additive (Subgroup.zpowers ζ) :=
  AddEquiv.ofBijective
    (AddMonoidHom.liftOfRightInverse (Int.castAddHom <| ZMod k) _ ZMod.int_cast_rightInverse
      ⟨{  toFun := fun i => Additive.ofMul (⟨_, i, rfl⟩ : Subgroup.zpowers ζ)
          map_zero' := by
            simp only [zpow_zero]
            rfl
          map_add' := by
            intro i j
            simp only [zpow_add]
            rfl },
        fun i hi =>
        by
        simp only [AddMonoidHom.mem_ker, CharP.int_cast_eq_zero_iff (ZMod k) k, AddMonoidHom.coe_mk,
          Int.coe_castAddHom] at hi⊢
        obtain ⟨i, rfl⟩ := hi
        simp only [zpow_mul, h.pow_eq_one, one_zpow, zpow_ofNat]
        rfl⟩)
    (by
      constructor
      · rw [injective_iff_map_eq_zero]
        intro i hi
        rw [Subtype.ext_iff] at hi
        have := (h.zpow_eq_one_iff_dvd _).mp hi
        rw [← (CharP.int_cast_eq_zero_iff (ZMod k) k _).mpr this, eq_comm]
        exact ZMod.int_cast_rightInverse i
      · rintro ⟨ξ, i, rfl⟩
        refine' ⟨Int.castAddHom _ i, _⟩
        rw [AddMonoidHom.liftOfRightInverse_comp_apply]
        rfl)
#align is_primitive_root.zmod_equiv_zpowers IsPrimitiveRoot.zmodEquivZpowers

@[simp]
theorem zmodEquivZpowers_apply_coe_int (i : ℤ) :
    h.zmodEquivZpowers i = Additive.ofMul (⟨ζ ^ i, i, rfl⟩ : Subgroup.zpowers ζ) :=
  AddMonoidHom.liftOfRightInverse_comp_apply _ _ ZMod.int_cast_rightInverse _ _
#align is_primitive_root.zmod_equiv_zpowers_apply_coe_int IsPrimitiveRoot.zmodEquivZpowers_apply_coe_int

@[simp]
theorem zmodEquivZpowers_apply_coe_nat (i : ℕ) :
    h.zmodEquivZpowers i = Additive.ofMul (⟨ζ ^ i, i, rfl⟩ : Subgroup.zpowers ζ) :=
  by
  have : (i : ZMod k) = (i : ℤ) := by norm_cast
  simp only [this, zmod_equiv_zpowers_apply_coe_int, zpow_ofNat]
  rfl
#align is_primitive_root.zmod_equiv_zpowers_apply_coe_nat IsPrimitiveRoot.zmodEquivZpowers_apply_coe_nat

@[simp]
theorem zmodEquivZpowers_symm_apply_zpow (i : ℤ) :
    h.zmodEquivZpowers.symm (Additive.ofMul (⟨ζ ^ i, i, rfl⟩ : Subgroup.zpowers ζ)) = i := by
  rw [← h.zmod_equiv_zpowers.symm_apply_apply i, zmod_equiv_zpowers_apply_coe_int]
#align is_primitive_root.zmod_equiv_zpowers_symm_apply_zpow IsPrimitiveRoot.zmodEquivZpowers_symm_apply_zpow

@[simp]
theorem zmodEquivZpowers_symm_apply_zpow' (i : ℤ) : h.zmodEquivZpowers.symm ⟨ζ ^ i, i, rfl⟩ = i :=
  h.zmodEquivZpowers_symm_apply_zpow i
#align is_primitive_root.zmod_equiv_zpowers_symm_apply_zpow' IsPrimitiveRoot.zmodEquivZpowers_symm_apply_zpow'

@[simp]
theorem zmodEquivZpowers_symm_apply_pow (i : ℕ) :
    h.zmodEquivZpowers.symm (Additive.ofMul (⟨ζ ^ i, i, rfl⟩ : Subgroup.zpowers ζ)) = i := by
  rw [← h.zmod_equiv_zpowers.symm_apply_apply i, zmod_equiv_zpowers_apply_coe_nat]
#align is_primitive_root.zmod_equiv_zpowers_symm_apply_pow IsPrimitiveRoot.zmodEquivZpowers_symm_apply_pow

@[simp]
theorem zmodEquivZpowers_symm_apply_pow' (i : ℕ) : h.zmodEquivZpowers.symm ⟨ζ ^ i, i, rfl⟩ = i :=
  h.zmodEquivZpowers_symm_apply_pow i
#align is_primitive_root.zmod_equiv_zpowers_symm_apply_pow' IsPrimitiveRoot.zmodEquivZpowers_symm_apply_pow'

variable [IsDomain R]

theorem zpowers_eq {k : ℕ+} {ζ : Rˣ} (h : IsPrimitiveRoot ζ k) :
    Subgroup.zpowers ζ = rootsOfUnity k R :=
  by
  apply SetLike.coe_injective
  haveI F : Fintype (Subgroup.zpowers ζ) := Fintype.ofEquiv _ h.zmod_equiv_zpowers.toEquiv
  refine'
    @Set.eq_of_subset_of_card_le Rˣ (Subgroup.zpowers ζ) (rootsOfUnity k R) F
      (rootsOfUnity.fintype R k)
      (Subgroup.zpowers_subset <| show ζ ∈ rootsOfUnity k R from h.pow_eq_one) _
  calc
    Fintype.card (rootsOfUnity k R) ≤ k := card_rootsOfUnity R k
    _ = Fintype.card (ZMod k) := (ZMod.card k).symm
    _ = Fintype.card (Subgroup.zpowers ζ) := Fintype.card_congr h.zmod_equiv_zpowers.toEquiv

#align is_primitive_root.zpowers_eq IsPrimitiveRoot.zpowers_eq

theorem eq_pow_of_mem_rootsOfUnity {k : ℕ+} {ζ ξ : Rˣ} (h : IsPrimitiveRoot ζ k)
    (hξ : ξ ∈ rootsOfUnity k R) : ∃ (i : ℕ)(hi : i < k), ζ ^ i = ξ :=
  by
  obtain ⟨n, rfl⟩ : ∃ n : ℤ, ζ ^ n = ξ := by rwa [← h.zpowers_eq] at hξ
  have hk0 : (0 : ℤ) < k := by exact_mod_cast k.pos
  let i := n % k
  have hi0 : 0 ≤ i := Int.emod_nonneg _ (ne_of_gt hk0)
  lift i to ℕ using hi0 with i₀ hi₀
  refine' ⟨i₀, _, _⟩
  · zify
    rw [hi₀]
    exact Int.emod_lt_of_pos _ hk0
  · have aux := h.zpow_eq_one
    rw [← coe_coe] at aux
    rw [← zpow_ofNat, hi₀, ← Int.emod_add_ediv n k, zpow_add, zpow_mul, aux, one_zpow, mul_one]
#align is_primitive_root.eq_pow_of_mem_roots_of_unity IsPrimitiveRoot.eq_pow_of_mem_rootsOfUnity

theorem eq_pow_of_pow_eq_one {k : ℕ} {ζ ξ : R} (h : IsPrimitiveRoot ζ k) (hξ : ξ ^ k = 1)
    (h0 : 0 < k) : ∃ i < k, ζ ^ i = ξ :=
  by
  lift ζ to Rˣ using h.is_unit h0
  lift ξ to Rˣ using isUnit_ofPowEqOne hξ h0.ne'
  lift k to ℕ+ using h0
  simp only [← Units.val_pow_eq_pow_val, ← Units.ext_iff]
  rw [coe_units_iff] at h
  apply h.eq_pow_of_mem_roots_of_unity
  rw [mem_rootsOfUnity, Units.ext_iff, Units.val_pow_eq_pow_val, hξ, Units.val_one]
#align is_primitive_root.eq_pow_of_pow_eq_one IsPrimitiveRoot.eq_pow_of_pow_eq_one

theorem isPrimitiveRoot_iff' {k : ℕ+} {ζ ξ : Rˣ} (h : IsPrimitiveRoot ζ k) :
    IsPrimitiveRoot ξ k ↔ ∃ i < (k : ℕ), ∃ hi : i.coprime k, ζ ^ i = ξ :=
  by
  constructor
  · intro hξ
    obtain ⟨i, hik, rfl⟩ := h.eq_pow_of_mem_roots_of_unity hξ.pow_eq_one
    rw [h.pow_iff_coprime k.pos] at hξ
    exact ⟨i, hik, hξ, rfl⟩
  · rintro ⟨i, -, hi, rfl⟩
    exact h.pow_of_coprime i hi
#align is_primitive_root.is_primitive_root_iff' IsPrimitiveRoot.isPrimitiveRoot_iff'

theorem isPrimitiveRoot_iff {k : ℕ} {ζ ξ : R} (h : IsPrimitiveRoot ζ k) (h0 : 0 < k) :
    IsPrimitiveRoot ξ k ↔ ∃ i < k, ∃ hi : i.coprime k, ζ ^ i = ξ :=
  by
  constructor
  · intro hξ
    obtain ⟨i, hik, rfl⟩ := h.eq_pow_of_pow_eq_one hξ.pow_eq_one h0
    rw [h.pow_iff_coprime h0] at hξ
    exact ⟨i, hik, hξ, rfl⟩
  · rintro ⟨i, -, hi, rfl⟩
    exact h.pow_of_coprime i hi
#align is_primitive_root.is_primitive_root_iff IsPrimitiveRoot.isPrimitiveRoot_iff

theorem card_roots_of_unity' {n : ℕ+} (h : IsPrimitiveRoot ζ n) :
    Fintype.card (rootsOfUnity n R) = n :=
  by
  let e := h.zmod_equiv_zpowers
  haveI F : Fintype (Subgroup.zpowers ζ) := Fintype.ofEquiv _ e.to_equiv
  calc
    Fintype.card (rootsOfUnity n R) = Fintype.card (Subgroup.zpowers ζ) :=
      Fintype.card_congr <| by rw [h.zpowers_eq]
    _ = Fintype.card (ZMod n) := (Fintype.card_congr e.to_equiv.symm)
    _ = n := ZMod.card n

#align is_primitive_root.card_roots_of_unity' IsPrimitiveRoot.card_roots_of_unity'

theorem card_rootsOfUnity {ζ : R} {n : ℕ+} (h : IsPrimitiveRoot ζ n) :
    Fintype.card (rootsOfUnity n R) = n :=
  by
  obtain ⟨ζ, hζ⟩ := h.is_unit n.pos
  rw [← hζ, IsPrimitiveRoot.coe_units_iff] at h
  exact h.card_roots_of_unity'
#align is_primitive_root.card_roots_of_unity IsPrimitiveRoot.card_rootsOfUnity

/-- The cardinality of the multiset `nth_roots ↑n (1 : R)` is `n`
if there is a primitive root of unity in `R`. -/
theorem card_nthRoots {ζ : R} {n : ℕ} (h : IsPrimitiveRoot ζ n) : (nthRoots n (1 : R)).card = n :=
  by
  cases' Nat.eq_zero_or_pos n with hzero hpos
  · simp only [hzero, Multiset.card_zero, nth_roots_zero]
  rw [eq_iff_le_not_lt]
  use card_nth_roots n 1
  · rw [not_lt]
    have hcard :
      Fintype.card { x // x ∈ nth_roots n (1 : R) } ≤ (nth_roots n (1 : R)).attach.card :=
      Multiset.card_le_of_le (Multiset.dedup_le _)
    rw [Multiset.card_attach] at hcard
    rw [← PNat.toPNat'_coe hpos] at hcard h⊢
    set m := Nat.toPNat' n
    rw [← Fintype.card_congr (rootsOfUnityEquivNthRoots R m), card_rootsOfUnity h] at hcard
    exact hcard
#align is_primitive_root.card_nth_roots IsPrimitiveRoot.card_nthRoots

/-- The multiset `nth_roots ↑n (1 : R)` has no repeated elements
if there is a primitive root of unity in `R`. -/
theorem nthRoots_nodup {ζ : R} {n : ℕ} (h : IsPrimitiveRoot ζ n) : (nthRoots n (1 : R)).Nodup :=
  by
  cases' Nat.eq_zero_or_pos n with hzero hpos
  · simp only [hzero, Multiset.nodup_zero, nth_roots_zero]
  apply (@Multiset.dedup_eq_self R _ _).1
  rw [eq_iff_le_not_lt]
  constructor
  · exact Multiset.dedup_le (nth_roots n (1 : R))
  · by_contra ha
    replace ha := Multiset.card_lt_of_lt ha
    rw [card_nth_roots h] at ha
    have hrw : (nth_roots n (1 : R)).dedup.card = Fintype.card { x // x ∈ nth_roots n (1 : R) } :=
      by
      set fs := (⟨(nth_roots n (1 : R)).dedup, Multiset.nodup_dedup _⟩ : Finset R)
      rw [← Finset.card_mk, ← Fintype.card_of_subtype fs _]
      intro x
      simp only [Multiset.mem_dedup, Finset.mem_mk]
    rw [← PNat.toPNat'_coe hpos] at h hrw ha
    set m := Nat.toPNat' n
    rw [hrw, ← Fintype.card_congr (rootsOfUnityEquivNthRoots R m), card_rootsOfUnity h] at ha
    exact Nat.lt_asymm ha ha
#align is_primitive_root.nth_roots_nodup IsPrimitiveRoot.nthRoots_nodup

@[simp]
theorem card_nthRootsFinset {ζ : R} {n : ℕ} (h : IsPrimitiveRoot ζ n) :
    (nthRootsFinset n R).card = n := by
  rw [nth_roots_finset, ← Multiset.toFinset_eq (nth_roots_nodup h), card_mk, h.card_nth_roots]
#align is_primitive_root.card_nth_roots_finset IsPrimitiveRoot.card_nthRootsFinset

open Nat

/-- If an integral domain has a primitive `k`-th root of unity, then it has `φ k` of them. -/
theorem card_primitiveRoots {ζ : R} {k : ℕ} (h : IsPrimitiveRoot ζ k) :
    (primitiveRoots k R).card = φ k := by
  by_cases h0 : k = 0
  · simp [h0]
  symm
  refine' Finset.card_congr (fun i _ => ζ ^ i) _ _ _
  · simp only [true_and_iff, and_imp, mem_filter, mem_range, mem_univ]
    rintro i - hi
    rw [mem_primitiveRoots (Nat.pos_of_ne_zero h0)]
    exact h.pow_of_coprime i hi.symm
  · simp only [true_and_iff, and_imp, mem_filter, mem_range, mem_univ]
    rintro i j hi - hj - H
    exact h.pow_inj hi hj H
  · simp only [exists_prop, true_and_iff, mem_filter, mem_range, mem_univ]
    intro ξ hξ
    rw [mem_primitiveRoots (Nat.pos_of_ne_zero h0),
      h.is_primitive_root_iff (Nat.pos_of_ne_zero h0)] at hξ
    rcases hξ with ⟨i, hin, hi, H⟩
    exact ⟨i, ⟨hin, hi.symm⟩, H⟩
#align is_primitive_root.card_primitive_roots IsPrimitiveRoot.card_primitiveRoots

/-- The sets `primitive_roots k R` are pairwise disjoint. -/
theorem disjoint {k l : ℕ} (h : k ≠ l) : Disjoint (primitiveRoots k R) (primitiveRoots l R) :=
  Finset.disjoint_left.2 fun z hk hl =>
    h <|
      (isPrimitiveRoot_of_mem_primitiveRoots hk).unique <| isPrimitiveRoot_of_mem_primitiveRoots hl
#align is_primitive_root.disjoint IsPrimitiveRoot.disjoint

/-- `nth_roots n` as a `finset` is equal to the union of `primitive_roots i R` for `i ∣ n`
if there is a primitive root of unity in `R`.
This holds for any `nat`, not just `pnat`, see `nth_roots_one_eq_bUnion_primitive_roots`. -/
theorem nth_roots_one_eq_bunionᵢ_primitive_roots' {ζ : R} {n : ℕ+} (h : IsPrimitiveRoot ζ n) :
    nthRootsFinset n R = (Nat.divisors ↑n).bunionᵢ fun i => primitiveRoots i R :=
  by
  symm
  apply Finset.eq_of_subset_of_card_le
  · intro x
    simp only [nth_roots_finset, ← Multiset.toFinset_eq (nth_roots_nodup h), exists_prop,
      Finset.mem_bunionᵢ, Finset.mem_filter, Finset.mem_range, mem_nth_roots, Finset.mem_mk,
      Nat.mem_divisors, and_true_iff, Ne.def, PNat.ne_zero, PNat.pos, not_false_iff]
    rintro ⟨a, ⟨d, hd⟩, ha⟩
    have hazero : 0 < a := by
      contrapose! hd with ha0
      simp_all only [nonpos_iff_eq_zero, MulZeroClass.zero_mul]
      exact n.ne_zero
    rw [mem_primitiveRoots hazero] at ha
    rw [hd, pow_mul, ha.pow_eq_one, one_pow]
  · apply le_of_eq
    rw [h.card_nth_roots_finset, Finset.card_bunionᵢ]
    · nth_rw_lhs 1 [← Nat.sum_totient n]
      refine' sum_congr rfl _
      simp only [Nat.mem_divisors]
      rintro k ⟨⟨d, hd⟩, -⟩
      rw [mul_comm] at hd
      rw [(h.pow n.pos hd).card_primitiveRoots]
    · intro i hi j hj hdiff
      exact Disjoint hdiff
#align is_primitive_root.nth_roots_one_eq_bUnion_primitive_roots' IsPrimitiveRoot.nth_roots_one_eq_bunionᵢ_primitive_roots'

/-- `nth_roots n` as a `finset` is equal to the union of `primitive_roots i R` for `i ∣ n`
if there is a primitive root of unity in `R`. -/
theorem nth_roots_one_eq_bunionᵢ_primitiveRoots {ζ : R} {n : ℕ} (h : IsPrimitiveRoot ζ n) :
    nthRootsFinset n R = (Nat.divisors n).bunionᵢ fun i => primitiveRoots i R :=
  by
  by_cases hn : n = 0
  · simp [hn]
  exact @nth_roots_one_eq_bUnion_primitive_roots' _ _ _ _ ⟨n, Nat.pos_of_ne_zero hn⟩ h
#align is_primitive_root.nth_roots_one_eq_bUnion_primitive_roots IsPrimitiveRoot.nth_roots_one_eq_bunionᵢ_primitiveRoots

end IsDomain

section minpoly

open minpoly

section CommRing

variable {n : ℕ} {K : Type _} [CommRing K] {μ : K} (h : IsPrimitiveRoot μ n) (hpos : 0 < n)

include n μ h hpos

/-- `μ` is integral over `ℤ`. -/
theorem isIntegral : IsIntegral ℤ μ := by
  use X ^ n - 1
  constructor
  · exact monic_X_pow_sub_C 1 (ne_of_lt hpos).symm
  ·
    simp only [((IsPrimitiveRoot.iff_def μ n).mp h).left, eval₂_one, eval₂_X_pow, eval₂_sub,
      sub_self]
#align is_primitive_root.is_integral IsPrimitiveRoot.isIntegral

section IsDomain

variable [IsDomain K] [CharZero K]

omit hpos

/-- The minimal polynomial of a root of unity `μ` divides `X ^ n - 1`. -/
theorem minpoly_dvd_x_pow_sub_one : minpoly ℤ μ ∣ X ^ n - 1 :=
  by
  rcases n.eq_zero_or_pos with (rfl | hpos)
  · simp
  letI : IsIntegrallyClosed ℤ := GCDMonoid.toIsIntegrallyClosed
  apply minpoly.isIntegrallyClosed_dvd (IsIntegral h hpos)
  simp only [((IsPrimitiveRoot.iff_def μ n).mp h).left, aeval_X_pow, eq_intCast, Int.cast_one,
    aeval_one, AlgHom.map_sub, sub_self]
#align is_primitive_root.minpoly_dvd_X_pow_sub_one IsPrimitiveRoot.minpoly_dvd_x_pow_sub_one

/-- The reduction modulo `p` of the minimal polynomial of a root of unity `μ` is separable. -/
theorem separable_minpoly_mod {p : ℕ} [Fact p.Prime] (hdiv : ¬p ∣ n) :
    Separable (map (Int.castRingHom (ZMod p)) (minpoly ℤ μ)) :=
  by
  have hdvd : map (Int.castRingHom (ZMod p)) (minpoly ℤ μ) ∣ X ^ n - 1 := by
    simpa [Polynomial.map_pow, map_X, Polynomial.map_one, Polynomial.map_sub] using
      RingHom.map_dvd (map_ring_hom (Int.castRingHom (ZMod p))) (minpoly_dvd_X_pow_sub_one h)
  refine' separable.of_dvd (separable_X_pow_sub_C 1 _ one_ne_zero) hdvd
  by_contra hzero
  exact hdiv ((ZMod.nat_cast_zmod_eq_zero_iff_dvd n p).1 hzero)
#align is_primitive_root.separable_minpoly_mod IsPrimitiveRoot.separable_minpoly_mod

/-- The reduction modulo `p` of the minimal polynomial of a root of unity `μ` is squarefree. -/
theorem squarefree_minpoly_mod {p : ℕ} [Fact p.Prime] (hdiv : ¬p ∣ n) :
    Squarefree (map (Int.castRingHom (ZMod p)) (minpoly ℤ μ)) :=
  (separable_minpoly_mod h hdiv).Squarefree
#align is_primitive_root.squarefree_minpoly_mod IsPrimitiveRoot.squarefree_minpoly_mod

/- Let `P` be the minimal polynomial of a root of unity `μ` and `Q` be the minimal polynomial of
`μ ^ p`, where `p` is a natural number that does not divide `n`. Then `P` divides `expand ℤ p Q`. -/
theorem minpoly_dvd_expand {p : ℕ} (hdiv : ¬p ∣ n) : minpoly ℤ μ ∣ expand ℤ p (minpoly ℤ (μ ^ p)) :=
  by
  rcases n.eq_zero_or_pos with (rfl | hpos)
  · simp_all
  letI : IsIntegrallyClosed ℤ := GCDMonoid.toIsIntegrallyClosed
  refine' minpoly.isIntegrallyClosed_dvd (h.is_integral hpos) _
  · rw [aeval_def, coe_expand, ← comp, eval₂_eq_eval_map, map_comp, Polynomial.map_pow, map_X,
      eval_comp, eval_pow, eval_X, ← eval₂_eq_eval_map, ← aeval_def]
    exact minpoly.aeval _ _
#align is_primitive_root.minpoly_dvd_expand IsPrimitiveRoot.minpoly_dvd_expand

/- Let `P` be the minimal polynomial of a root of unity `μ` and `Q` be the minimal polynomial of
`μ ^ p`, where `p` is a prime that does not divide `n`. Then `P` divides `Q ^ p` modulo `p`. -/
theorem minpoly_dvd_pow_mod {p : ℕ} [hprime : Fact p.Prime] (hdiv : ¬p ∣ n) :
    map (Int.castRingHom (ZMod p)) (minpoly ℤ μ) ∣
      map (Int.castRingHom (ZMod p)) (minpoly ℤ (μ ^ p)) ^ p :=
  by
  set Q := minpoly ℤ (μ ^ p)
  have hfrob :
    map (Int.castRingHom (ZMod p)) Q ^ p = map (Int.castRingHom (ZMod p)) (expand ℤ p Q) := by
    rw [← ZMod.expand_card, map_expand]
  rw [hfrob]
  apply RingHom.map_dvd (map_ring_hom (Int.castRingHom (ZMod p)))
  exact minpoly_dvd_expand h hdiv
#align is_primitive_root.minpoly_dvd_pow_mod IsPrimitiveRoot.minpoly_dvd_pow_mod

/- Let `P` be the minimal polynomial of a root of unity `μ` and `Q` be the minimal polynomial of
`μ ^ p`, where `p` is a prime that does not divide `n`. Then `P` divides `Q` modulo `p`. -/
theorem minpoly_dvd_mod_p {p : ℕ} [hprime : Fact p.Prime] (hdiv : ¬p ∣ n) :
    map (Int.castRingHom (ZMod p)) (minpoly ℤ μ) ∣
      map (Int.castRingHom (ZMod p)) (minpoly ℤ (μ ^ p)) :=
  (UniqueFactorizationMonoid.dvd_pow_iff_dvd_of_squarefree (squarefree_minpoly_mod h hdiv)
        hprime.1.NeZero).1
    (minpoly_dvd_pow_mod h hdiv)
#align is_primitive_root.minpoly_dvd_mod_p IsPrimitiveRoot.minpoly_dvd_mod_p

/-- If `p` is a prime that does not divide `n`,
then the minimal polynomials of a primitive `n`-th root of unity `μ`
and of `μ ^ p` are the same. -/
theorem minpoly_eq_pow {p : ℕ} [hprime : Fact p.Prime] (hdiv : ¬p ∣ n) :
    minpoly ℤ μ = minpoly ℤ (μ ^ p) := by
  by_cases hn : n = 0
  · simp_all
  have hpos := Nat.pos_of_ne_zero hn
  by_contra hdiff
  set P := minpoly ℤ μ
  set Q := minpoly ℤ (μ ^ p)
  have Pmonic : P.monic := minpoly.monic (h.is_integral hpos)
  have Qmonic : Q.monic := minpoly.monic ((h.pow_of_prime hprime.1 hdiv).IsIntegral hpos)
  have Pirr : Irreducible P := minpoly.irreducible (h.is_integral hpos)
  have Qirr : Irreducible Q := minpoly.irreducible ((h.pow_of_prime hprime.1 hdiv).IsIntegral hpos)
  have PQprim : is_primitive (P * Q) := Pmonic.is_primitive.mul Qmonic.is_primitive
  have prod : P * Q ∣ X ^ n - 1 :=
    by
    rw [is_primitive.int.dvd_iff_map_cast_dvd_map_cast (P * Q) (X ^ n - 1) PQprim
        (monic_X_pow_sub_C (1 : ℤ) (ne_of_gt hpos)).IsPrimitive,
      Polynomial.map_mul]
    refine' IsCoprime.mul_dvd _ _ _
    · have aux := is_primitive.int.irreducible_iff_irreducible_map_cast Pmonic.is_primitive
      refine' (dvd_or_coprime _ _ (aux.1 Pirr)).resolve_left _
      rw [map_dvd_map (Int.castRingHom ℚ) Int.cast_injective Pmonic]
      intro hdiv
      refine' hdiff (eq_of_monic_of_associated Pmonic Qmonic _)
      exact associated_of_dvd_dvd hdiv (Pirr.dvd_symm Qirr hdiv)
    · apply (map_dvd_map (Int.castRingHom ℚ) Int.cast_injective Pmonic).2
      exact minpoly_dvd_X_pow_sub_one h
    · apply (map_dvd_map (Int.castRingHom ℚ) Int.cast_injective Qmonic).2
      exact minpoly_dvd_X_pow_sub_one (pow_of_prime h hprime.1 hdiv)
  replace prod := RingHom.map_dvd (map_ring_hom (Int.castRingHom (ZMod p))) Prod
  rw [coe_map_ring_hom, Polynomial.map_mul, Polynomial.map_sub, Polynomial.map_one,
    Polynomial.map_pow, map_X] at prod
  obtain ⟨R, hR⟩ := minpoly_dvd_mod_p h hdiv
  rw [hR, ← mul_assoc, ← Polynomial.map_mul, ← sq, Polynomial.map_pow] at prod
  have habs : map (Int.castRingHom (ZMod p)) P ^ 2 ∣ map (Int.castRingHom (ZMod p)) P ^ 2 * R := by
    use R
  replace habs :=
    lt_of_lt_of_le (PartENat.coe_lt_coe.2 one_lt_two)
      (multiplicity.le_multiplicity_of_pow_dvd (dvd_trans habs Prod))
  have hfree : Squarefree (X ^ n - 1 : (ZMod p)[X]) :=
    (separable_X_pow_sub_C 1 (fun h => hdiv <| (ZMod.nat_cast_zmod_eq_zero_iff_dvd n p).1 h)
        one_ne_zero).Squarefree
  cases'
    (multiplicity.squarefree_iff_multiplicity_le_one (X ^ n - 1)).1 hfree
      (map (Int.castRingHom (ZMod p)) P) with
    hle hunit
  · rw [Nat.cast_one] at habs
    exact hle.not_lt habs
  · replace hunit := degree_eq_zero_of_is_unit hunit
    rw [degree_map_eq_of_leading_coeff_ne_zero (Int.castRingHom (ZMod p)) _] at hunit
    · exact (minpoly.degree_pos (IsIntegral h hpos)).ne' hunit
    simp only [Pmonic, eq_intCast, monic.leading_coeff, Int.cast_one, Ne.def, not_false_iff,
      one_ne_zero]
#align is_primitive_root.minpoly_eq_pow IsPrimitiveRoot.minpoly_eq_pow

/-- If `m : ℕ` is coprime with `n`,
then the minimal polynomials of a primitive `n`-th root of unity `μ`
and of `μ ^ m` are the same. -/
theorem minpoly_eq_pow_coprime {m : ℕ} (hcop : Nat.coprime m n) : minpoly ℤ μ = minpoly ℤ (μ ^ m) :=
  by
  revert n hcop
  refine' UniqueFactorizationMonoid.induction_on_prime m _ _ _
  · intro n hn h
    congr
    simpa [(Nat.coprime_zero_left n).mp hn] using h
  · intro u hunit n hcop h
    congr
    simp [nat.is_unit_iff.mp hunit]
  · intro a p ha hprime hind n hcop h
    rw [hind (Nat.coprime.coprime_mul_left hcop) h]
    clear hind
    replace hprime := hprime.nat_prime
    have hdiv := (Nat.Prime.coprime_iff_not_dvd hprime).1 (Nat.coprime.coprime_mul_right hcop)
    haveI := Fact.mk hprime
    rw [minpoly_eq_pow (h.pow_of_coprime a (Nat.coprime.coprime_mul_left hcop)) hdiv]
    congr 1
    ring
#align is_primitive_root.minpoly_eq_pow_coprime IsPrimitiveRoot.minpoly_eq_pow_coprime

/-- If `m : ℕ` is coprime with `n`,
then the minimal polynomial of a primitive `n`-th root of unity `μ`
has `μ ^ m` as root. -/
theorem pow_isRoot_minpoly {m : ℕ} (hcop : Nat.coprime m n) :
    IsRoot (map (Int.castRingHom K) (minpoly ℤ μ)) (μ ^ m) := by
  simpa [minpoly_eq_pow_coprime h hcop, eval_map, aeval_def (μ ^ m) _] using minpoly.aeval ℤ (μ ^ m)
#align is_primitive_root.pow_is_root_minpoly IsPrimitiveRoot.pow_isRoot_minpoly

/-- `primitive_roots n K` is a subset of the roots of the minimal polynomial of a primitive
`n`-th root of unity `μ`. -/
theorem is_roots_of_minpoly :
    primitiveRoots n K ⊆ (map (Int.castRingHom K) (minpoly ℤ μ)).roots.toFinset :=
  by
  by_cases hn : n = 0; · simp_all
  have hpos := Nat.pos_of_ne_zero hn
  intro x hx
  obtain ⟨m, hle, hcop, rfl⟩ := (is_primitive_root_iff h hpos).1 ((mem_primitiveRoots hpos).1 hx)
  simpa [Multiset.mem_toFinset,
    mem_roots (map_monic_ne_zero <| minpoly.monic <| IsIntegral h hpos)] using
    pow_is_root_minpoly h hcop
#align is_primitive_root.is_roots_of_minpoly IsPrimitiveRoot.is_roots_of_minpoly

/-- The degree of the minimal polynomial of `μ` is at least `totient n`. -/
theorem totient_le_degree_minpoly : Nat.totient n ≤ (minpoly ℤ μ).natDegree :=
  let P : ℤ[X] := minpoly ℤ μ
  let
    P_K :-- minimal polynomial of `μ`
      K[X] :=
    map (Int.castRingHom K) P
  -- minimal polynomial of `μ` sent to `K[X]`
  calc
    n.totient = (primitiveRoots n K).card := h.card_primitiveRoots.symm
    _ ≤ P_K.roots.toFinset.card := (Finset.card_le_of_subset (is_roots_of_minpoly h))
    _ ≤ P_K.roots.card := (Multiset.toFinset_card_le _)
    _ ≤ P_K.natDegree := (card_roots' _)
    _ ≤ P.natDegree := natDegree_map_le _ _

#align is_primitive_root.totient_le_degree_minpoly IsPrimitiveRoot.totient_le_degree_minpoly

end IsDomain

end CommRing

end minpoly

section Automorphisms

variable {S} [CommRing S] [IsDomain S] {μ : S} {n : ℕ+} (hμ : IsPrimitiveRoot μ n) (R) [CommRing R]
  [Algebra R S]

/- ./././Mathport/Syntax/Translate/Tactic/Lean3.lean:132:4: warning: unsupported: rw with cfg: { occs := occurrences.pos[occurrences.pos] «expr[ ,]»([1]) } -/
/-- The `monoid_hom` that takes an automorphism to the power of μ that μ gets mapped to under it. -/
noncomputable def autToPow : (S ≃ₐ[R] S) →* (ZMod n)ˣ :=
  let μ' := hμ.toRootsOfUnity
  have ho : orderOf μ' = n := by
    rw [hμ.eq_order_of, ← hμ.coe_to_roots_of_unity_coe, orderOf_units, orderOf_subgroup]
  MonoidHom.toHomUnits
    { toFun := fun σ => (map_root_of_unity_eq_pow_self σ.toAlgHom μ').some
      map_one' := by
        generalize_proofs h1
        have h := h1.some_spec
        dsimp only [AlgEquiv.one_apply, AlgEquiv.toRingEquiv_eq_coe, RingEquiv.toRingHom_eq_coe,
          RingEquiv.coe_toRingHom, AlgEquiv.coe_ringEquiv] at *
        replace h : μ' = μ' ^ h1.some :=
          rootsOfUnity.coe_injective (by simpa only [rootsOfUnity.coe_pow] using h)
        rw [← pow_one μ'] at h
        rw [← @Nat.cast_one <| ZMod n, ZMod.nat_cast_eq_nat_cast_iff, ← ho, ←
          pow_eq_pow_iff_modEq μ', h]
      map_mul' := by
        generalize_proofs hxy' hx' hy'
        have hxy := hxy'.some_spec
        have hx := hx'.some_spec
        have hy := hy'.some_spec
        dsimp only [AlgEquiv.toRingEquiv_eq_coe, RingEquiv.toRingHom_eq_coe,
          RingEquiv.coe_toRingHom, AlgEquiv.coe_ringEquiv, AlgEquiv.mul_apply] at *
        replace hxy : x (↑μ' ^ hy'.some) = ↑μ' ^ hxy'.some := hy ▸ hxy
        rw [x.map_pow] at hxy
        replace hxy : ((μ' : S) ^ hx'.some) ^ hy'.some = μ' ^ hxy'.some := hx ▸ hxy
        rw [← pow_mul] at hxy
        replace hxy : μ' ^ (hx'.some * hy'.some) = μ' ^ hxy'.some :=
          rootsOfUnity.coe_injective (by simpa only [rootsOfUnity.coe_pow] using hxy)
        rw [← Nat.cast_mul, ZMod.nat_cast_eq_nat_cast_iff, ← ho, ← pow_eq_pow_iff_modEq μ', hxy] }
#align is_primitive_root.aut_to_pow IsPrimitiveRoot.autToPow

-- We are not using @[simps] in aut_to_pow to avoid a timeout.
theorem coe_autToPow_apply (f : S ≃ₐ[R] S) :
    (autToPow R hμ f : ZMod n) =
      ((map_root_of_unity_eq_pow_self f hμ.toRootsOfUnity).some : ZMod n) :=
  rfl
#align is_primitive_root.coe_aut_to_pow_apply IsPrimitiveRoot.coe_autToPow_apply

@[simp]
theorem autToPow_spec (f : S ≃ₐ[R] S) : μ ^ (hμ.autToPow R f : ZMod n).val = f μ :=
  by
  rw [IsPrimitiveRoot.coe_autToPow_apply]
  generalize_proofs h
  have := h.some_spec
  dsimp only [AlgEquiv.toAlgHom_eq_coe, AlgEquiv.coe_algHom] at this
  refine' (_ : ↑hμ.to_roots_of_unity ^ _ = _).trans this.symm
  rw [← rootsOfUnity.coe_pow, ← rootsOfUnity.coe_pow]
  congr 1
  rw [pow_eq_pow_iff_modEq, ← orderOf_subgroup, ← orderOf_units, hμ.coe_to_roots_of_unity_coe, ←
    hμ.eq_order_of, ZMod.val_nat_cast]
  exact Nat.mod_modEq _ _
#align is_primitive_root.aut_to_pow_spec IsPrimitiveRoot.autToPow_spec

end Automorphisms

end IsPrimitiveRoot


/-
Copyright (c) 2020. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Patrick Massot
-/
import tactic.ring
import tactic.doc_commands

/-!
# `group`

Normalizes expressions in the language of groups. The basic idea is to use the simplifier
to put everything into a product of group powers (`zpow` which takes a group element and an
integer), then simplify the exponents using the `ring` tactic. The process needs to be repeated
since `ring` can normalize an exponent to zero, leading to a factor that can be removed
before collecting exponents again. The simplifier step also uses some extra lemmas to avoid
some `ring` invocations.

## Tags

group_theory
-/

-- The next four lemmas are not general purpose lemmas, they are intended for use only by
-- the `group` tactic.

@[to_additive]
lemma tactic.group.zpow_trick {G : Type*} [group G] (a b : G) (n m : ℤ) : a*b^n*b^m = a*b^(n+m) :=
by rw [mul_assoc, ← zpow_add]

@[to_additive]
lemma tactic.group.zpow_trick_one {G : Type*} [group G] (a b : G) (m : ℤ) : a*b*b^m = a*b^(m+1) :=
by rw [mul_assoc, mul_self_zpow]

@[to_additive]
lemma tactic.group.zpow_trick_one' {G : Type*} [group G] (a b : G) (n : ℤ) : a*b^n*b = a*b^(n+1) :=
by rw [mul_assoc, mul_zpow_self]

@[to_additive]
lemma tactic.group.zpow_trick_sub {G : Type*} [group G] (a b : G) (n m : ℤ) :
  a*b^n*b^(-m) = a*b^(n-m) :=
by rw [mul_assoc, ← zpow_add] ; refl

namespace tactic

setup_tactic_parser

open tactic.simp_arg_type interactive tactic.group

/-- Auxiliary tactic for the `group` tactic. Calls the simplifier only. -/
meta def aux_group₁ (locat : loc) : tactic unit :=
  simp_core {} skip tt [
  expr ``(mul_one),
  expr ``(one_mul),
  expr ``(one_pow),
  expr ``(one_zpow),
  expr ``(sub_self),
  expr ``(add_neg_self),
  expr ``(neg_add_self),
  expr ``(neg_neg),
  expr ``(tsub_self),
  expr ``(int.coe_nat_add),
  expr ``(int.coe_nat_mul),
  expr ``(int.coe_nat_zero),
  expr ``(int.coe_nat_one),
  expr ``(int.coe_nat_bit0),
  expr ``(int.coe_nat_bit1),
  expr ``(int.mul_neg_eq_neg_mul_symm),
  expr ``(int.neg_mul_eq_neg_mul_symm),
  symm_expr ``(zpow_coe_nat),
  symm_expr ``(zpow_neg_one),
  symm_expr ``(zpow_mul),
  symm_expr ``(zpow_add_one),
  symm_expr ``(zpow_one_add),
  symm_expr ``(zpow_add),
  expr ``(mul_zpow_neg_one),
  expr ``(zpow_zero),
  expr ``(mul_zpow),
  symm_expr ``(mul_assoc),
  expr ``(zpow_trick),
  expr ``(zpow_trick_one),
  expr ``(zpow_trick_one'),
  expr ``(zpow_trick_sub),
  expr ``(tactic.ring.horner)]
  [] locat >> skip

/-- Auxiliary tactic for the `group` tactic. Calls `ring_nf` to normalize exponents. -/
meta def aux_group₂ (locat : loc) : tactic unit :=
ring_nf none tactic.ring.normalize_mode.raw locat
end tactic

namespace tactic.interactive
setup_tactic_parser
open tactic

/--
Tactic for normalizing expressions in multiplicative groups, without assuming
commutativity, using only the group axioms without any information about which group
is manipulated.

(For additive commutative groups, use the `abel` tactic instead.)

Example:
```lean
example {G : Type} [group G] (a b c d : G) (h : c = (a*b^2)*((b*b)⁻¹*a⁻¹)*d) : a*c*d⁻¹ = a :=
begin
  group at h, -- normalizes `h` which becomes `h : c = d`
  rw h,       -- the goal is now `a*d*d⁻¹ = a`
  group,      -- which then normalized and closed
end
```
-/
meta def group (locat : parse location) : tactic unit :=
do when locat.include_goal `[rw ← mul_inv_eq_one],
   try (aux_group₁ locat),
   repeat (aux_group₂ locat ; aux_group₁ locat)

end tactic.interactive

add_tactic_doc
{ name := "group",
  category := doc_category.tactic,
  decl_names := [`tactic.interactive.group],
  tags := ["decision procedure", "simplification"] }


import data.int.basic

/-
Canadian Mathematical Olympiad 1998, Problem 5

Let m be a positive integer. Define the sequence {aₙ} by a₀ = 0,
a₁ = m, and aₙ₊₁ = m²aₙ - aₙ₋₁ for n ≥ 1. Prove that an ordered pair
(a,b) of nonegative integers, with a ≤ b, is a solution of the equation

 (a² + b²) / (ab + 1) = m²

if an only if (a,b) = (aₙ,aₙ₊₁) for some n ≥ 0.
-/

def A (m : ℕ) (hm : 0 < m) : ℕ → ℤ
| 0 := 0
| 1 := (↑m)
| (n + 2) := m^2 * A (n + 1) - A n

theorem canada1998_q5 (m : ℕ) (hm : 0 < m) (a b : ℕ) (hab : a ≤ b) :
    a^2 + b^2 = m^2 * (a * b + 1) ↔
     ∃ n : ℕ, (a:ℤ) = A m hm n ∧ (b:ℤ) = A m hm (n + 1) :=
begin
  sorry
end