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

! This file was ported from Lean 3 source module number_theory.divisors
! leanprover-community/mathlib commit f7fc89d5d5ff1db2d1242c7bb0e9062ce47ef47c
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.BigOperators.Order
import Mathlib.Data.Nat.Interval
import Mathlib.Data.Nat.Factors

/-!
# Divisor Finsets

This file defines sets of divisors of a natural number. This is particularly useful as background
for defining Dirichlet convolution.

## Main Definitions
Let `n : ℕ`. All of the following definitions are in the `Nat` namespace:
 * `divisors n` is the `Finset` of natural numbers that divide `n`.
 * `properDivisors n` is the `Finset` of natural numbers that divide `n`, other than `n`.
 * `divisorsAntidiagonal n` is the `Finset` of pairs `(x,y)` such that `x * y = n`.
 * `perfect n` is true when `n` is positive and the sum of `properDivisors n` is `n`.

## Implementation details
 * `divisors 0`, `properDivisors 0`, and `divisorsAntidiagonal 0` are defined to be `∅`.

## Tags
divisors, perfect numbers

-/

open BigOperators Classical Finset

namespace Nat

variable (n : ℕ)

/-- `divisors n` is the `Finset` of divisors of `n`. As a special case, `divisors 0 = ∅`. -/
def divisors : Finset ℕ :=
  Finset.filter (fun x : ℕ => x ∣ n) (Finset.Ico 1 (n + 1))
#align nat.divisors Nat.divisors

/-- `properDivisors n` is the `Finset` of divisors of `n`, other than `n`.
  As a special case, `properDivisors 0 = ∅`. -/
def properDivisors : Finset ℕ :=
  Finset.filter (fun x : ℕ => x ∣ n) (Finset.Ico 1 n)
#align nat.proper_divisors Nat.properDivisors

/-- `divisorsAntidiagonal n` is the `Finset` of pairs `(x,y)` such that `x * y = n`.
  As a special case, `divisorsAntidiagonal 0 = ∅`. -/
def divisorsAntidiagonal : Finset (ℕ × ℕ) :=
  Finset.filter (fun x => x.fst * x.snd = n) (Ico 1 (n + 1) ×ᶠ Ico 1 (n + 1))
#align nat.divisors_antidiagonal Nat.divisorsAntidiagonal

variable {n}

@[simp]
theorem filter_dvd_eq_divisors (h : n ≠ 0) : (Finset.range n.succ).filter (· ∣ n) = n.divisors := by
  ext
  simp only [divisors, mem_filter, mem_range, mem_Ico, and_congr_left_iff, iff_and_self]
  exact fun ha _ => succ_le_iff.mpr (pos_of_dvd_of_pos ha h.bot_lt)
#align nat.filter_dvd_eq_divisors Nat.filter_dvd_eq_divisors

@[simp]
theorem filter_dvd_eq_properDivisors (h : n ≠ 0) :
    (Finset.range n).filter (· ∣ n) = n.properDivisors := by
  ext
  simp only [properDivisors, mem_filter, mem_range, mem_Ico, and_congr_left_iff, iff_and_self]
  exact fun ha _ => succ_le_iff.mpr (pos_of_dvd_of_pos ha h.bot_lt)
#align nat.filter_dvd_eq_proper_divisors Nat.filter_dvd_eq_properDivisors

theorem properDivisors.not_self_mem : ¬n ∈ properDivisors n := by simp [properDivisors]
#align nat.proper_divisors.not_self_mem Nat.properDivisors.not_self_mem

@[simp]
theorem mem_properDivisors {m : ℕ} : n ∈ properDivisors m ↔ n ∣ m ∧ n < m := by
  rcases eq_or_ne m 0 with (rfl | hm); · simp [properDivisors]
  simp only [and_comm, ← filter_dvd_eq_properDivisors hm, mem_filter, mem_range]
#align nat.mem_proper_divisors Nat.mem_properDivisors

theorem insert_self_properDivisors (h : n ≠ 0) : insert n (properDivisors n) = divisors n := by
  rw [divisors, properDivisors, Ico_succ_right_eq_insert_Ico (one_le_iff_ne_zero.2 h),
    Finset.filter_insert, if_pos (dvd_refl n)]
#align nat.insert_self_proper_divisors Nat.insert_self_properDivisors

theorem cons_self_properDivisors (h : n ≠ 0) :
    cons n (properDivisors n) properDivisors.not_self_mem = divisors n := by
  rw [cons_eq_insert, insert_self_properDivisors h]
#align nat.cons_self_proper_divisors Nat.cons_self_properDivisors

@[simp]
theorem mem_divisors {m : ℕ} : n ∈ divisors m ↔ n ∣ m ∧ m ≠ 0 := by
  rcases eq_or_ne m 0 with (rfl | hm); · simp [divisors]
  simp only [hm, Ne.def, not_false_iff, and_true_iff, ← filter_dvd_eq_divisors hm, mem_filter,
    mem_range, and_iff_right_iff_imp, lt_succ_iff]
  exact le_of_dvd hm.bot_lt
#align nat.mem_divisors Nat.mem_divisors

theorem one_mem_divisors : 1 ∈ divisors n ↔ n ≠ 0 := by simp
#align nat.one_mem_divisors Nat.one_mem_divisors

theorem mem_divisors_self (n : ℕ) (h : n ≠ 0) : n ∈ n.divisors :=
  mem_divisors.2 ⟨dvd_rfl, h⟩
#align nat.mem_divisors_self Nat.mem_divisors_self

theorem dvd_of_mem_divisors {m : ℕ} (h : n ∈ divisors m) : n ∣ m := by
  cases m
  · apply dvd_zero
  · simp [mem_divisors.1 h]
#align nat.dvd_of_mem_divisors Nat.dvd_of_mem_divisors

@[simp]
theorem mem_divisorsAntidiagonal {x : ℕ × ℕ} :
    x ∈ divisorsAntidiagonal n ↔ x.fst * x.snd = n ∧ n ≠ 0 := by
  simp only [divisorsAntidiagonal, Finset.mem_Ico, Ne.def, Finset.mem_filter, Finset.mem_product]
  rw [and_comm]
  apply and_congr_right
  rintro rfl
  constructor <;> intro h
  · contrapose! h
    simp [h]
  · rw [Nat.lt_add_one_iff, Nat.lt_add_one_iff]
    rw [mul_eq_zero, not_or] at h
    simp only [succ_le_of_lt (Nat.pos_of_ne_zero h.1), succ_le_of_lt (Nat.pos_of_ne_zero h.2),
      true_and_iff]
    exact
      ⟨le_mul_of_pos_right (Nat.pos_of_ne_zero h.2), le_mul_of_pos_left (Nat.pos_of_ne_zero h.1)⟩
#align nat.mem_divisors_antidiagonal Nat.mem_divisorsAntidiagonal

-- Porting note: Redundant binder annotation update
-- variable {n}

theorem divisor_le {m : ℕ} : n ∈ divisors m → n ≤ m := by
  cases' m with m
  · simp
  · simp only [mem_divisors, Nat.succ_ne_zero m, and_true_iff, Ne.def, not_false_iff]
    exact Nat.le_of_dvd (Nat.succ_pos m)
#align nat.divisor_le Nat.divisor_le

theorem divisors_subset_of_dvd {m : ℕ} (hzero : n ≠ 0) (h : m ∣ n) : divisors m ⊆ divisors n :=
  Finset.subset_iff.2 fun _x hx => Nat.mem_divisors.mpr ⟨(Nat.mem_divisors.mp hx).1.trans h, hzero⟩
#align nat.divisors_subset_of_dvd Nat.divisors_subset_of_dvd

theorem divisors_subset_properDivisors {m : ℕ} (hzero : n ≠ 0) (h : m ∣ n) (hdiff : m ≠ n) :
    divisors m ⊆ properDivisors n := by
  apply Finset.subset_iff.2
  intro x hx
  exact
    Nat.mem_properDivisors.2
      ⟨(Nat.mem_divisors.1 hx).1.trans h,
        lt_of_le_of_lt (divisor_le hx)
          (lt_of_le_of_ne (divisor_le (Nat.mem_divisors.2 ⟨h, hzero⟩)) hdiff)⟩
#align nat.divisors_subset_proper_divisors Nat.divisors_subset_properDivisors

@[simp]
theorem divisors_zero : divisors 0 = ∅ := by
  ext
  simp
#align nat.divisors_zero Nat.divisors_zero

@[simp]
theorem properDivisors_zero : properDivisors 0 = ∅ := by
  ext
  simp
#align nat.proper_divisors_zero Nat.properDivisors_zero

theorem properDivisors_subset_divisors : properDivisors n ⊆ divisors n :=
  filter_subset_filter _ <| Ico_subset_Ico_right n.le_succ
#align nat.proper_divisors_subset_divisors Nat.properDivisors_subset_divisors

@[simp]
theorem divisors_one : divisors 1 = {1} := by
  ext
  simp
#align nat.divisors_one Nat.divisors_one

@[simp]

theorem pos_of_mem_divisors {m : ℕ} (h : m ∈ n.divisors) : 0 < m := by
  cases m
  · rw [mem_divisors, zero_eq, zero_dvd_iff (a := n)] at h
    cases h.2 h.1
  apply Nat.succ_pos
#align nat.pos_of_mem_divisors Nat.pos_of_mem_divisors

theorem pos_of_mem_properDivisors {m : ℕ} (h : m ∈ n.properDivisors) : 0 < m :=
  pos_of_mem_divisors (properDivisors_subset_divisors h)
#align nat.pos_of_mem_proper_divisors Nat.pos_of_mem_properDivisors

theorem one_mem_properDivisors_iff_one_lt : 1 ∈ n.properDivisors ↔ 1 < n := by
  rw [mem_properDivisors, and_iff_right (one_dvd _)]
#align nat.one_mem_proper_divisors_iff_one_lt Nat.one_mem_properDivisors_iff_one_lt

@[simp]
theorem divisorsAntidiagonal_zero : divisorsAntidiagonal 0 = ∅ := by
  ext
  simp
#align nat.divisors_antidiagonal_zero Nat.divisorsAntidiagonal_zero

@[simp]
theorem divisorsAntidiagonal_one : divisorsAntidiagonal 1 = {(1, 1)} := by
  ext
  simp [Nat.mul_eq_one_iff, Prod.ext_iff]
#align nat.divisors_antidiagonal_one Nat.divisorsAntidiagonal_one

/- Porting note: simpnf linter; added aux lemma below
Left-hand side simplifies from
  Prod.swap x ∈ Nat.divisorsAntidiagonal n
to
  x.snd * x.fst = n ∧ ¬n = 0-/
-- @[simp]
theorem swap_mem_divisorsAntidiagonal {x : ℕ × ℕ} :
    x.swap ∈ divisorsAntidiagonal n ↔ x ∈ divisorsAntidiagonal n := by
  rw [mem_divisorsAntidiagonal, mem_divisorsAntidiagonal, mul_comm, Prod.swap]
#align nat.swap_mem_divisors_antidiagonal Nat.swap_mem_divisorsAntidiagonal

-- Porting note: added below thm to replace the simp from the previous thm
@[simp]
theorem swap_mem_divisorsAntidiagonal_aux {x : ℕ × ℕ} :
    x.snd * x.fst = n ∧ ¬n = 0 ↔ x ∈ divisorsAntidiagonal n := by
  rw [mem_divisorsAntidiagonal, mul_comm]

theorem fst_mem_divisors_of_mem_antidiagonal {x : ℕ × ℕ} (h : x ∈ divisorsAntidiagonal n) :
    x.fst ∈ divisors n := by
  rw [mem_divisorsAntidiagonal] at h
  simp [Dvd.intro _ h.1, h.2]
#align nat.fst_mem_divisors_of_mem_antidiagonal Nat.fst_mem_divisors_of_mem_antidiagonal

theorem snd_mem_divisors_of_mem_antidiagonal {x : ℕ × ℕ} (h : x ∈ divisorsAntidiagonal n) :
    x.snd ∈ divisors n := by
  rw [mem_divisorsAntidiagonal] at h
  simp [Dvd.intro_left _ h.1, h.2]
#align nat.snd_mem_divisors_of_mem_antidiagonal Nat.snd_mem_divisors_of_mem_antidiagonal

@[simp]
theorem map_swap_divisorsAntidiagonal :
    (divisorsAntidiagonal n).map (Equiv.prodComm _ _).toEmbedding = divisorsAntidiagonal n := by
  rw [← coe_inj, coe_map, Equiv.coe_toEmbedding, Equiv.coe_prodComm,
    Set.image_swap_eq_preimage_swap]
  ext
  exact swap_mem_divisorsAntidiagonal
#align nat.map_swap_divisors_antidiagonal Nat.map_swap_divisorsAntidiagonal

@[simp]
theorem image_fst_divisorsAntidiagonal : (divisorsAntidiagonal n).image Prod.fst = divisors n := by
  ext
  simp [Dvd.dvd, @eq_comm _ n (_ * _)]
#align nat.image_fst_divisors_antidiagonal Nat.image_fst_divisorsAntidiagonal

@[simp]
theorem image_snd_divisorsAntidiagonal : (divisorsAntidiagonal n).image Prod.snd = divisors n := by
  rw [← map_swap_divisorsAntidiagonal, map_eq_image, image_image]
  exact image_fst_divisorsAntidiagonal
#align nat.image_snd_divisors_antidiagonal Nat.image_snd_divisorsAntidiagonal

theorem map_div_right_divisors :
    n.divisors.map ⟨fun d => (d, n / d), fun p₁ p₂ => congr_arg Prod.fst⟩ =
      n.divisorsAntidiagonal := by
  ext ⟨d, nd⟩
  simp only [mem_map, mem_divisorsAntidiagonal, Function.Embedding.coeFn_mk, mem_divisors,
    Prod.ext_iff, exists_prop, and_left_comm, exists_eq_left]
  constructor
  · rintro ⟨⟨⟨k, rfl⟩, hn⟩, rfl⟩
    rw [Nat.mul_div_cancel_left _ (left_ne_zero_of_mul hn).bot_lt]
    exact ⟨rfl, hn⟩
  · rintro ⟨rfl, hn⟩
    exact ⟨⟨dvd_mul_right _ _, hn⟩, Nat.mul_div_cancel_left _ (left_ne_zero_of_mul hn).bot_lt⟩
#align nat.map_div_right_divisors Nat.map_div_right_divisors

theorem map_div_left_divisors :
    n.divisors.map ⟨fun d => (n / d, d), fun p₁ p₂ => congr_arg Prod.snd⟩ =
      n.divisorsAntidiagonal := by
  apply Finset.map_injective (Equiv.prodComm _ _).toEmbedding
  rw [map_swap_divisorsAntidiagonal, ← map_div_right_divisors, Finset.map_map]
  rfl
#align nat.map_div_left_divisors Nat.map_div_left_divisors

theorem sum_divisors_eq_sum_properDivisors_add_self :
    (∑ i in divisors n, i) = (∑ i in properDivisors n, i) + n := by
  rcases Decidable.eq_or_ne n 0 with (rfl | hn)
  · simp
  · rw [← cons_self_properDivisors hn, Finset.sum_cons, add_comm]
#align
  nat.sum_divisors_eq_sum_proper_divisors_add_self
  Nat.sum_divisors_eq_sum_properDivisors_add_self

/-- `n : ℕ` is perfect if and only the sum of the proper divisors of `n` is `n` and `n`
  is positive. -/
def Perfect (n : ℕ) : Prop :=
  (∑ i in properDivisors n, i) = n ∧ 0 < n
#align nat.perfect Nat.Perfect

theorem perfect_iff_sum_properDivisors (h : 0 < n) : Perfect n ↔ (∑ i in properDivisors n, i) = n :=
  and_iff_left h
#align nat.perfect_iff_sum_proper_divisors Nat.perfect_iff_sum_properDivisors

theorem perfect_iff_sum_divisors_eq_two_mul (h : 0 < n) :
    Perfect n ↔ (∑ i in divisors n, i) = 2 * n := by
  rw [perfect_iff_sum_properDivisors h, sum_divisors_eq_sum_properDivisors_add_self, two_mul]
  constructor <;> intro h
  · rw [h]
  · apply add_right_cancel h
#align nat.perfect_iff_sum_divisors_eq_two_mul Nat.perfect_iff_sum_divisors_eq_two_mul

theorem mem_divisors_prime_pow {p : ℕ} (pp : p.Prime) (k : ℕ) {x : ℕ} :
    x ∈ divisors (p ^ k) ↔ ∃ (j : ℕ) (_ : j ≤ k), x = p ^ j := by
  rw [mem_divisors, Nat.dvd_prime_pow pp, and_iff_left (ne_of_gt (pow_pos pp.pos k))]
  simp
#align nat.mem_divisors_prime_pow Nat.mem_divisors_prime_pow

theorem Prime.divisors {p : ℕ} (pp : p.Prime) : divisors p = {1, p} := by
  ext
  rw [mem_divisors, dvd_prime pp, and_iff_left pp.ne_zero, Finset.mem_insert, Finset.mem_singleton]
#align nat.prime.divisors Nat.Prime.divisors

theorem Prime.properDivisors {p : ℕ} (pp : p.Prime) : properDivisors p = {1} := by
  rw [← erase_insert properDivisors.not_self_mem, insert_self_properDivisors pp.ne_zero,
    pp.divisors, pair_comm, erase_insert fun con => pp.ne_one (mem_singleton.1 con)]
#align nat.prime.proper_divisors Nat.Prime.properDivisors

-- Porting note: Specified pow to Nat.pow
theorem divisors_prime_pow {p : ℕ} (pp : p.Prime) (k : ℕ) :
    divisors (p ^ k) = (Finset.range (k + 1)).map ⟨Nat.pow p, pow_right_injective pp.two_le⟩ := by
  ext a
  simp only [mem_divisors, mem_map, mem_range, lt_succ_iff, Function.Embedding.coeFn_mk, Nat.pow_eq,
    mem_divisors_prime_pow pp k]
  have := mem_divisors_prime_pow pp k (x := a)
  rw [mem_divisors] at this
  rw [this]
  refine ⟨?_, ?_⟩
  · intro h; rcases h with ⟨x, hx, hap⟩; use x; tauto
  · tauto
#align nat.divisors_prime_pow Nat.divisors_prime_pow

theorem eq_properDivisors_of_subset_of_sum_eq_sum {s : Finset ℕ} (hsub : s ⊆ n.properDivisors) :
    ((∑ x in s, x) = ∑ x in n.properDivisors, x) → s = n.properDivisors := by
  cases n
  · rw [properDivisors_zero, subset_empty] at hsub
    simp [hsub]
  classical
    rw [← sum_sdiff hsub]
    intro h
    apply Subset.antisymm hsub
    rw [← sdiff_eq_empty_iff_subset]
    contrapose h
    rw [← Ne.def, ← nonempty_iff_ne_empty] at h
    apply ne_of_lt
    rw [← zero_add (∑ x in s, x), ← add_assoc, add_zero]
    apply add_lt_add_right
    have hlt :=
      sum_lt_sum_of_nonempty h fun x hx => pos_of_mem_properDivisors (sdiff_subset _ _ hx)
    simp only [sum_const_zero] at hlt
    apply hlt
#align nat.eq_proper_divisors_of_subset_of_sum_eq_sum Nat.eq_properDivisors_of_subset_of_sum_eq_sum

theorem sum_properDivisors_dvd (h : (∑ x in n.properDivisors, x) ∣ n) :
    (∑ x in n.properDivisors, x) = 1 ∨ (∑ x in n.properDivisors, x) = n := by
  cases' n with n
  · simp
  · cases' n with n
    · contrapose! h
      simp
    · rw [or_iff_not_imp_right]
      intro ne_n
      have hlt : (∑ x in n.succ.succ.properDivisors, x) < n.succ.succ :=
        lt_of_le_of_ne (Nat.le_of_dvd (Nat.succ_pos _) h) ne_n
      symm
      rw [← mem_singleton,
        eq_properDivisors_of_subset_of_sum_eq_sum
          (singleton_subset_iff.2 (mem_properDivisors.2 ⟨h, hlt⟩)) sum_singleton,
        mem_properDivisors]
      refine' ⟨one_dvd _, Nat.succ_lt_succ (Nat.succ_pos _)⟩
#align nat.sum_proper_divisors_dvd Nat.sum_properDivisors_dvd

@[to_additive (attr := simp)]
theorem Prime.prod_properDivisors {α : Type _} [CommMonoid α] {p : ℕ} {f : ℕ → α} (h : p.Prime) :
    (∏ x in p.properDivisors, f x) = f 1 := by simp [h.properDivisors]
#align nat.prime.prod_proper_divisors Nat.Prime.prod_properDivisors
#align nat.prime.sum_proper_divisors Nat.Prime.sum_properDivisors

@[to_additive (attr := simp)]
theorem Prime.prod_divisors {α : Type _} [CommMonoid α] {p : ℕ} {f : ℕ → α} (h : p.Prime) :
    (∏ x in p.divisors, f x) = f p * f 1 := by
  rw [← cons_self_properDivisors h.ne_zero, prod_cons, h.prod_properDivisors]
#align nat.prime.prod_divisors Nat.Prime.prod_divisors
#align nat.prime.sum_divisors Nat.Prime.sum_divisors

theorem properDivisors_eq_singleton_one_iff_prime : n.properDivisors = {1} ↔ n.Prime := by
  refine ⟨?_, ?_⟩
  · intro h
    refine' Nat.prime_def_lt''.mpr ⟨_, fun m hdvd => _⟩
    · match n with
      | 0 => contradiction
      | 1 => contradiction
      | Nat.succ (Nat.succ n) => simp [succ_le_succ]
    · rw [← mem_singleton, ← h, mem_properDivisors]
      have := Nat.le_of_dvd ?_ hdvd
      · simp [hdvd, this]
        exact (le_iff_eq_or_lt.mp this).symm
      · by_contra'
        simp [nonpos_iff_eq_zero.mp this, this] at h
  · exact fun h => Prime.properDivisors h
#align nat.proper_divisors_eq_singleton_one_iff_prime Nat.properDivisors_eq_singleton_one_iff_prime

theorem sum_properDivisors_eq_one_iff_prime : (∑ x in n.properDivisors, x) = 1 ↔ n.Prime := by
  cases' n with n
  · simp [Nat.not_prime_zero]
  · cases n
    · simp [Nat.not_prime_one]
    · rw [← properDivisors_eq_singleton_one_iff_prime]
      refine' ⟨fun h => _, fun h => h.symm ▸ sum_singleton⟩
      rw [@eq_comm (Finset ℕ) _ _]
      apply
        eq_properDivisors_of_subset_of_sum_eq_sum
          (singleton_subset_iff.2
            (one_mem_properDivisors_iff_one_lt.2 (succ_lt_succ (Nat.succ_pos _))))
          (Eq.trans sum_singleton h.symm)
#align nat.sum_proper_divisors_eq_one_iff_prime Nat.sum_properDivisors_eq_one_iff_prime

theorem mem_properDivisors_prime_pow {p : ℕ} (pp : p.Prime) (k : ℕ) {x : ℕ} :
    x ∈ properDivisors (p ^ k) ↔ ∃ (j : ℕ) (_ : j < k), x = p ^ j := by
  rw [mem_properDivisors, Nat.dvd_prime_pow pp, ← exists_and_right]
  simp only [exists_prop, and_assoc]
  apply exists_congr
  intro a
  constructor <;> intro h
  · rcases h with ⟨_h_left, rfl, h_right⟩
    rw [pow_lt_pow_iff pp.one_lt] at h_right
    exact ⟨h_right, by rfl⟩
  · rcases h with ⟨h_left, rfl⟩
    rw [pow_lt_pow_iff pp.one_lt]
    simp [h_left, le_of_lt]
#align nat.mem_proper_divisors_prime_pow Nat.mem_properDivisors_prime_pow

-- Porting note: Specified pow to Nat.pow
theorem properDivisors_prime_pow {p : ℕ} (pp : p.Prime) (k : ℕ) :
    properDivisors (p ^ k) = (Finset.range k).map ⟨Nat.pow p, pow_right_injective pp.two_le⟩ := by
  ext a
  simp only [mem_properDivisors, Nat.isUnit_iff, mem_map, mem_range, Function.Embedding.coeFn_mk,
    pow_eq]
  have := mem_properDivisors_prime_pow pp k (x := a)
  rw [mem_properDivisors] at this
  rw [this]
  refine ⟨?_, ?_⟩
  · intro h; rcases h with ⟨j, hj, hap⟩; use j; tauto
  · tauto
#align nat.proper_divisors_prime_pow Nat.properDivisors_prime_pow

@[to_additive (attr := simp)]
theorem prod_properDivisors_prime_pow {α : Type _} [CommMonoid α] {k p : ℕ} {f : ℕ → α}
    (h : p.Prime) : (∏ x in (p ^ k).properDivisors, f x) = ∏ x in range k, f (p ^ x) := by
  simp [h, properDivisors_prime_pow]
#align nat.prod_proper_divisors_prime_pow Nat.prod_properDivisors_prime_pow
#align nat.sum_proper_divisors_prime_nsmul Nat.sum_properDivisors_prime_nsmul

@[to_additive (attr := simp) sum_divisors_prime_pow]
theorem prod_divisors_prime_pow {α : Type _} [CommMonoid α] {k p : ℕ} {f : ℕ → α} (h : p.Prime) :
    (∏ x in (p ^ k).divisors, f x) = ∏ x in range (k + 1), f (p ^ x) := by
  simp [h, divisors_prime_pow]
#align nat.prod_divisors_prime_pow Nat.prod_divisors_prime_pow
#align nat.sum_divisors_prime_pow Nat.sum_divisors_prime_pow

@[to_additive]
theorem prod_divisorsAntidiagonal {M : Type _} [CommMonoid M] (f : ℕ → ℕ → M) {n : ℕ} :
    (∏ i in n.divisorsAntidiagonal, f i.1 i.2) = ∏ i in n.divisors, f i (n / i) := by
  rw [← map_div_right_divisors, Finset.prod_map]
  rfl
#align nat.prod_divisors_antidiagonal Nat.prod_divisorsAntidiagonal
#align nat.sum_divisors_antidiagonal Nat.sum_divisorsAntidiagonal

@[to_additive]
theorem prod_divisorsAntidiagonal' {M : Type _} [CommMonoid M] (f : ℕ → ℕ → M) {n : ℕ} :
    (∏ i in n.divisorsAntidiagonal, f i.1 i.2) = ∏ i in n.divisors, f (n / i) i := by
  rw [← map_swap_divisorsAntidiagonal, Finset.prod_map]
  exact prod_divisorsAntidiagonal fun i j => f j i
#align nat.prod_divisors_antidiagonal' Nat.prod_divisorsAntidiagonal'
#align nat.sum_divisors_antidiagonal' Nat.sum_divisorsAntidiagonal'

/-- The factors of `n` are the prime divisors -/
theorem prime_divisors_eq_to_filter_divisors_prime (n : ℕ) :
    n.factors.toFinset = (divisors n).filter Prime := by
  rcases n.eq_zero_or_pos with (rfl | hn)
  · simp
  · ext q
    simpa [hn, hn.ne', mem_factors] using and_comm
#align nat.prime_divisors_eq_to_filter_divisors_prime Nat.prime_divisors_eq_to_filter_divisors_prime

@[simp]
theorem image_div_divisors_eq_divisors (n : ℕ) :
    image (fun x : ℕ => n / x) n.divisors = n.divisors := by
  by_cases hn : n = 0
  · simp [hn]
  ext a
  constructor
  · rw [mem_image]
    rintro ⟨x, hx1, hx2⟩
    rw [mem_divisors] at *
    refine' ⟨_, hn⟩
    rw [← hx2]
    exact div_dvd_of_dvd hx1.1
  · rw [mem_divisors, mem_image]
    rintro ⟨h1, -⟩
    exact ⟨n / a, mem_divisors.mpr ⟨div_dvd_of_dvd h1, hn⟩, Nat.div_div_self h1 hn⟩
#align nat.image_div_divisors_eq_divisors Nat.image_div_divisors_eq_divisors

/- Porting note: Removed simp; simp_nf linter:
Left-hand side does not simplify, when using the simp lemma on itself.
This usually means that it will never apply. -/
@[to_additive sum_div_divisors]
theorem prod_div_divisors {α : Type _} [CommMonoid α] (n : ℕ) (f : ℕ → α) :
    (∏ d in n.divisors, f (n / d)) = n.divisors.prod f := by
  by_cases hn : n = 0; · simp [hn]
  rw [← prod_image]
  · exact prod_congr (image_div_divisors_eq_divisors n) (by simp)
  · intro x hx y hy h
    rw [mem_divisors] at hx hy
    exact (div_eq_iff_eq_of_dvd_dvd hn hx.1 hy.1).mp h
#align nat.prod_div_divisors Nat.prod_div_divisors
#align nat.sum_div_divisors Nat.sum_div_divisors

end Nat


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

! This file was ported from Lean 3 source module algebra.regular.basic
! leanprover-community/mathlib commit 5cd3c25312f210fec96ba1edb2aebfb2ccf2010f
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.Group.Commute
import Mathlib.Algebra.Order.Monoid.Lemmas
import Mathlib.Algebra.GroupWithZero.Basic

/-!
# Regular elements

We introduce left-regular, right-regular and regular elements, along with their `to_additive`
analogues add-left-regular, add-right-regular and add-regular elements.

By definition, a regular element in a commutative ring is a non-zero divisor.
Lemma `isRegular_of_ne_zero` implies that every non-zero element of an integral domain is regular.
Since it assumes that the ring is a `CancelMonoidWithZero` it applies also, for instance, to `ℕ`.

The lemmas in Section `MulZeroClass` show that the `0` element is (left/right-)regular if and
only if the `MulZeroClass` is trivial.  This is useful when figuring out stopping conditions for
regular sequences: if `0` is ever an element of a regular sequence, then we can extend the sequence
by adding one further `0`.

The final goal is to develop part of the API to prove, eventually, results about non-zero-divisors.
-/

variable {R : Type _}

section Mul

variable [Mul R]

/-- A left-regular element is an element `c` such that multiplication on the left by `c`
is injective. -/
@[to_additive "An add-left-regular element is an element `c` such that addition
    on the left by `c` is injective."]
def IsLeftRegular (c : R) :=
  (c * ·).Injective
#align is_left_regular IsLeftRegular
#align is_add_left_regular IsAddLeftRegular

/-- A right-regular element is an element `c` such that multiplication on the right by `c`
is injective. -/
@[to_additive "An add-right-regular element is an element `c` such that addition
    on the right by `c` is injective."]
def IsRightRegular (c : R) :=
  (· * c).Injective
#align is_right_regular IsRightRegular
#align is_add_right_regular IsAddRightRegular

/-- An add-regular element is an element `c` such that addition by `c` both on the left and
on the right is injective. -/
structure IsAddRegular {R : Type _} [Add R] (c : R) : Prop where
  /-- An add-regular element `c` is left-regular -/
  left : IsAddLeftRegular c -- Porting note: It seems like to_additive is misbehaving
  /-- An add-regular element `c` is right-regular -/
  right : IsAddRightRegular c
#align is_add_regular IsAddRegular

/-- A regular element is an element `c` such that multiplication by `c` both on the left and
on the right is injective. -/
structure IsRegular (c : R) : Prop where
  /-- A regular element `c` is left-regular -/
  left : IsLeftRegular c
  /-- A regular element `c` is right-regular -/
  right : IsRightRegular c
#align is_regular IsRegular

attribute [to_additive] IsRegular

@[to_additive]
protected theorem MulLECancellable.isLeftRegular [PartialOrder R] {a : R}
    (ha : MulLECancellable a) : IsLeftRegular a :=
  ha.Injective
#align mul_le_cancellable.is_left_regular MulLECancellable.isLeftRegular
#align add_le_cancellable.is_add_left_regular AddLECancellable.isAddLeftRegular

theorem IsLeftRegular.right_of_commute {a : R}
    (ca : ∀ b, Commute a b) (h : IsLeftRegular a) : IsRightRegular a :=
  fun x y xy => h <| (ca x).trans <| xy.trans <| (ca y).symm
#align is_left_regular.right_of_commute IsLeftRegular.right_of_commute

theorem Commute.isRegular_iff {a : R} (ca : ∀ b, Commute a b) : IsRegular a ↔ IsLeftRegular a :=
  ⟨fun h => h.left, fun h => ⟨h, h.right_of_commute ca⟩⟩
#align commute.is_regular_iff Commute.isRegular_iff

end Mul

section Semigroup

variable [Semigroup R] {a b : R}

/-- In a semigroup, the product of left-regular elements is left-regular. -/
@[to_additive "In an additive semigroup, the sum of add-left-regular elements is add-left.regular."]
theorem IsLeftRegular.mul (lra : IsLeftRegular a) (lrb : IsLeftRegular b) : IsLeftRegular (a * b) :=
  show Function.Injective (((a * b) * ·)) from comp_mul_left a b ▸ lra.comp lrb
#align is_left_regular.mul IsLeftRegular.mul
#align is_add_left_regular.add IsAddLeftRegular.add

/-- In a semigroup, the product of right-regular elements is right-regular. -/
@[to_additive "In an additive semigroup, the sum of add-right-regular elements is
add-right-regular."]
theorem IsRightRegular.mul (rra : IsRightRegular a) (rrb : IsRightRegular b) :
    IsRightRegular (a * b) :=
  show Function.Injective (· * (a * b)) from comp_mul_right b a ▸ rrb.comp rra
#align is_right_regular.mul IsRightRegular.mul
#align is_add_right_regular.add IsAddRightRegular.add

/-- If an element `b` becomes left-regular after multiplying it on the left by a left-regular
element, then `b` is left-regular. -/
@[to_additive "If an element `b` becomes add-left-regular after adding to it on the left
a add-left-regular element, then `b` is add-left-regular."]
theorem IsLeftRegular.of_mul (ab : IsLeftRegular (a * b)) : IsLeftRegular b :=
  Function.Injective.of_comp (by rwa [comp_mul_left a b])
#align is_left_regular.of_mul IsLeftRegular.of_mul
#align is_add_left_regular.of_add IsAddLeftRegular.of_add

/-- An element is left-regular if and only if multiplying it on the left by a left-regular element
is left-regular. -/
@[to_additive (attr := simp) "An element is add-left-regular if and only if adding to it on the left
a add-left-regular element is add-left-regular."]
theorem mul_isLeftRegular_iff (b : R) (ha : IsLeftRegular a) :
    IsLeftRegular (a * b) ↔ IsLeftRegular b :=
  ⟨fun ab => IsLeftRegular.of_mul ab, fun ab => IsLeftRegular.mul ha ab⟩
#align mul_is_left_regular_iff mul_isLeftRegular_iff
#align add_is_add_left_regular_iff add_isAddLeftRegular_iff

/-- If an element `b` becomes right-regular after multiplying it on the right by a right-regular
element, then `b` is right-regular. -/
@[to_additive "If an element `b` becomes add-right-regular after adding to it on the right
a add-right-regular element, then `b` is add-right-regular."]
theorem IsRightRegular.of_mul (ab : IsRightRegular (b * a)) : IsRightRegular b := by
  refine' fun x y xy => ab (_ : x * (b * a) = y * (b * a))
  rw [← mul_assoc, ← mul_assoc]
  exact congr_fun (congr_arg (· * ·) xy) a
#align is_right_regular.of_mul IsRightRegular.of_mul
#align is_add_right_regular.of_add IsAddRightRegular.of_add

/-- An element is right-regular if and only if multiplying it on the right with a right-regular
element is right-regular. -/
@[to_additive (attr := simp)
"An element is add-right-regular if and only if adding it on the right to
a add-right-regular element is add-right-regular."]
theorem mul_isRightRegular_iff (b : R) (ha : IsRightRegular a) :
    IsRightRegular (b * a) ↔ IsRightRegular b :=
  ⟨fun ab => IsRightRegular.of_mul ab, fun ab => IsRightRegular.mul ab ha⟩
#align mul_is_right_regular_iff mul_isRightRegular_iff
#align add_is_add_right_regular_iff add_isAddRightRegular_iff

/-- Two elements `a` and `b` are regular if and only if both products `a * b` and `b * a`
are regular. -/
@[to_additive "Two elements `a` and `b` are add-regular if and only if both sums `a + b` and
`b + a` are add-regular."]
theorem isRegular_mul_and_mul_iff :
    IsRegular (a * b) ∧ IsRegular (b * a) ↔ IsRegular a ∧ IsRegular b := by
  refine' ⟨_, _⟩
  · rintro ⟨ab, ba⟩
    exact
      ⟨⟨IsLeftRegular.of_mul ba.left, IsRightRegular.of_mul ab.right⟩,
        ⟨IsLeftRegular.of_mul ab.left, IsRightRegular.of_mul ba.right⟩⟩
  · rintro ⟨ha, hb⟩
    exact
      ⟨⟨(mul_isLeftRegular_iff _ ha.left).mpr hb.left,
          (mul_isRightRegular_iff _ hb.right).mpr ha.right⟩,
        ⟨(mul_isLeftRegular_iff _ hb.left).mpr ha.left,
          (mul_isRightRegular_iff _ ha.right).mpr hb.right⟩⟩
#align is_regular_mul_and_mul_iff isRegular_mul_and_mul_iff
#align is_add_regular_add_and_add_iff isAddRegular_add_and_add_iff

/-- The "most used" implication of `mul_and_mul_iff`, with split hypotheses, instead of `∧`. -/
@[to_additive "The \"most used\" implication of `add_and_add_iff`, with split
hypotheses, instead of `∧`."]
theorem IsRegular.and_of_mul_of_mul (ab : IsRegular (a * b)) (ba : IsRegular (b * a)) :
    IsRegular a ∧ IsRegular b :=
  isRegular_mul_and_mul_iff.mp ⟨ab, ba⟩
#align is_regular.and_of_mul_of_mul IsRegular.and_of_mul_of_mul
#align is_add_regular.and_of_add_of_add IsAddRegular.and_of_add_of_add

end Semigroup

section MulZeroClass

variable [MulZeroClass R] {a b : R}

/-- The element `0` is left-regular if and only if `R` is trivial. -/
theorem IsLeftRegular.subsingleton (h : IsLeftRegular (0 : R)) : Subsingleton R :=
  ⟨fun a b => h <| Eq.trans (zero_mul a) (zero_mul b).symm⟩
#align is_left_regular.subsingleton IsLeftRegular.subsingleton

/-- The element `0` is right-regular if and only if `R` is trivial. -/
theorem IsRightRegular.subsingleton (h : IsRightRegular (0 : R)) : Subsingleton R :=
  ⟨fun a b => h <| Eq.trans (mul_zero a) (mul_zero b).symm⟩
#align is_right_regular.subsingleton IsRightRegular.subsingleton

/-- The element `0` is regular if and only if `R` is trivial. -/
theorem IsRegular.subsingleton (h : IsRegular (0 : R)) : Subsingleton R :=
  h.left.subsingleton
#align is_regular.subsingleton IsRegular.subsingleton

/-- The element `0` is left-regular if and only if `R` is trivial. -/
theorem isLeftRegular_zero_iff_subsingleton : IsLeftRegular (0 : R) ↔ Subsingleton R :=
  ⟨fun h => h.subsingleton, fun H a b _ => @Subsingleton.elim _ H a b⟩
#align is_left_regular_zero_iff_subsingleton isLeftRegular_zero_iff_subsingleton

/-- In a non-trivial `MulZeroClass`, the `0` element is not left-regular. -/
theorem not_isLeftRegular_zero_iff : ¬IsLeftRegular (0 : R) ↔ Nontrivial R := by
  rw [nontrivial_iff, not_iff_comm, isLeftRegular_zero_iff_subsingleton, subsingleton_iff]
  push_neg
  exact Iff.rfl
#align not_is_left_regular_zero_iff not_isLeftRegular_zero_iff

/-- The element `0` is right-regular if and only if `R` is trivial. -/
theorem isRightRegular_zero_iff_subsingleton : IsRightRegular (0 : R) ↔ Subsingleton R :=
  ⟨fun h => h.subsingleton, fun H a b _ => @Subsingleton.elim _ H a b⟩
#align is_right_regular_zero_iff_subsingleton isRightRegular_zero_iff_subsingleton

/-- In a non-trivial `MulZeroClass`, the `0` element is not right-regular. -/
theorem not_isRightRegular_zero_iff : ¬IsRightRegular (0 : R) ↔ Nontrivial R := by
  rw [nontrivial_iff, not_iff_comm, isRightRegular_zero_iff_subsingleton, subsingleton_iff]
  push_neg
  exact Iff.rfl
#align not_is_right_regular_zero_iff not_isRightRegular_zero_iff

/-- The element `0` is regular if and only if `R` is trivial. -/
theorem isRegular_iff_subsingleton : IsRegular (0 : R) ↔ Subsingleton R :=
  ⟨fun h => h.left.subsingleton, fun h =>
    ⟨isLeftRegular_zero_iff_subsingleton.mpr h, isRightRegular_zero_iff_subsingleton.mpr h⟩⟩
#align is_regular_iff_subsingleton isRegular_iff_subsingleton

/-- A left-regular element of a `Nontrivial` `MulZeroClass` is non-zero. -/
theorem IsLeftRegular.ne_zero [Nontrivial R] (la : IsLeftRegular a) : a ≠ 0 := by
  rintro rfl
  rcases exists_pair_ne R with ⟨x, y, xy⟩
  refine' xy (la (_ : 0 * x = 0 * y)) -- Porting note: lean4 seems to need the type signature
  rw [zero_mul, zero_mul]
#align is_left_regular.ne_zero IsLeftRegular.ne_zero

/-- A right-regular element of a `Nontrivial` `MulZeroClass` is non-zero. -/
theorem IsRightRegular.ne_zero [Nontrivial R] (ra : IsRightRegular a) : a ≠ 0 := by
  rintro rfl
  rcases exists_pair_ne R with ⟨x, y, xy⟩
  refine' xy (ra (_ : x * 0 = y * 0))
  rw [mul_zero, mul_zero]
#align is_right_regular.ne_zero IsRightRegular.ne_zero

/-- A regular element of a `Nontrivial` `MulZeroClass` is non-zero. -/
theorem IsRegular.ne_zero [Nontrivial R] (la : IsRegular a) : a ≠ 0 :=
  la.left.ne_zero
#align is_regular.ne_zero IsRegular.ne_zero

/-- In a non-trivial ring, the element `0` is not left-regular -- with typeclasses. -/
theorem not_isLeftRegular_zero [nR : Nontrivial R] : ¬IsLeftRegular (0 : R) :=
  not_isLeftRegular_zero_iff.mpr nR
#align not_is_left_regular_zero not_isLeftRegular_zero

/-- In a non-trivial ring, the element `0` is not right-regular -- with typeclasses. -/
theorem not_isRightRegular_zero [nR : Nontrivial R] : ¬IsRightRegular (0 : R) :=
  not_isRightRegular_zero_iff.mpr nR
#align not_is_right_regular_zero not_isRightRegular_zero

/-- In a non-trivial ring, the element `0` is not regular -- with typeclasses. -/
theorem not_isRegular_zero [Nontrivial R] : ¬IsRegular (0 : R) := fun h => IsRegular.ne_zero h rfl
#align not_is_regular_zero not_isRegular_zero

end MulZeroClass

section MulOneClass

variable [MulOneClass R]

/-- If multiplying by `1` on either side is the identity, `1` is regular. -/
@[to_additive "If adding `0` on either side is the identity, `0` is regular."]
theorem isRegular_one : IsRegular (1 : R) :=
  ⟨fun a b ab => (one_mul a).symm.trans (Eq.trans ab (one_mul b)), fun a b ab =>
    (mul_one a).symm.trans (Eq.trans ab (mul_one b))⟩
#align is_regular_one isRegular_one
#align is_add_regular_zero isAddRegular_zero

end MulOneClass

section CommSemigroup

variable [CommSemigroup R] {a b : R}

/-- A product is regular if and only if the factors are. -/
@[to_additive "A sum is add-regular if and only if the summands are."]
theorem isRegular_mul_iff : IsRegular (a * b) ↔ IsRegular a ∧ IsRegular b := by
  refine' Iff.trans _ isRegular_mul_and_mul_iff
  refine' ⟨fun ab => ⟨ab, by rwa [mul_comm]⟩, fun rab => rab.1⟩
#align is_regular_mul_iff isRegular_mul_iff
#align is_add_regular_add_iff isAddRegular_add_iff

end CommSemigroup

section Monoid

variable [Monoid R] {a b : R}

/-- An element admitting a left inverse is left-regular. -/
@[to_additive "An element admitting a left additive opposite is add-left-regular."]
theorem isLeftRegular_of_mul_eq_one (h : b * a = 1) : IsLeftRegular a :=
  @IsLeftRegular.of_mul R _ _ _ (by rw [h]; exact isRegular_one.left)
#align is_left_regular_of_mul_eq_one isLeftRegular_of_mul_eq_one
#align is_add_left_regular_of_add_eq_zero isAddLeftRegular_of_add_eq_zero

/-- An element admitting a right inverse is right-regular. -/
@[to_additive "An element admitting a right additive opposite is add-right-regular."]
theorem isRightRegular_of_mul_eq_one (h : a * b = 1) : IsRightRegular a :=
  IsRightRegular.of_mul (by rw [h]; exact isRegular_one.right)
#align is_right_regular_of_mul_eq_one isRightRegular_of_mul_eq_one
#align is_add_right_regular_of_add_eq_zero isAddRightRegular_of_add_eq_zero

/-- If `R` is a monoid, an element in `Rˣ` is regular. -/
@[to_additive "If `R` is an additive monoid, an element in `add_units R` is add-regular."]
theorem Units.isRegular (a : Rˣ) : IsRegular (a : R) :=
  ⟨isLeftRegular_of_mul_eq_one a.inv_mul, isRightRegular_of_mul_eq_one a.mul_inv⟩
#align units.is_regular Units.isRegular
#align add_units.is_add_regular AddUnits.isAddRegular

/-- A unit in a monoid is regular. -/
@[to_additive "An additive unit in an additive monoid is add-regular."]
theorem IsUnit.isRegular (ua : IsUnit a) : IsRegular a := by
  rcases ua with ⟨a, rfl⟩
  exact Units.isRegular a
#align is_unit.is_regular IsUnit.isRegular
#align is_add_unit.is_add_regular IsAddUnit.isAddRegular

end Monoid

/-- Elements of a left cancel semigroup are left regular. -/
@[to_additive "Elements of an add left cancel semigroup are add-left-regular."]
theorem isLeftRegular_of_leftCancelSemigroup [LeftCancelSemigroup R]
    (g : R) : IsLeftRegular g :=
  mul_right_injective g
#align is_left_regular_of_left_cancel_semigroup isLeftRegular_of_leftCancelSemigroup
#align is_add_left_regular_of_left_cancel_add_semigroup isAddLeftRegular_of_addLeftCancelSemigroup

/-- Elements of a right cancel semigroup are right regular. -/
@[to_additive "Elements of an add right cancel semigroup are add-right-regular"]
theorem isRightRegular_of_rightCancelSemigroup [RightCancelSemigroup R]
    (g : R) : IsRightRegular g :=
  mul_left_injective g
#align is_right_regular_of_right_cancel_semigroup isRightRegular_of_rightCancelSemigroup
#align is_add_right_regular_of_right_cancel_add_semigroup   isAddRightRegular_of_addRightCancelSemigroup

section CancelMonoid

variable [CancelMonoid R]

/-- Elements of a cancel monoid are regular.  Cancel semigroups do not appear to exist. -/
@[to_additive "Elements of an add cancel monoid are regular.
Add cancel semigroups do not appear to exist."]
theorem isRegular_of_cancelMonoid (g : R) : IsRegular g :=
  ⟨mul_right_injective g, mul_left_injective g⟩
#align is_regular_of_cancel_monoid isRegular_of_cancelMonoid
#align is_add_regular_of_cancel_add_monoid isAddRegular_of_addCancelMonoid

end CancelMonoid

section CancelMonoidWithZero

variable [CancelMonoidWithZero R] {a : R}

/-- Non-zero elements of an integral domain are regular. -/
theorem isRegular_of_ne_zero (a0 : a ≠ 0) : IsRegular a :=
  ⟨fun _ _ => (mul_right_inj' a0).mp, fun _ _ => (mul_left_inj' a0).mp⟩
#align is_regular_of_ne_zero isRegular_of_ne_zero

/-- In a non-trivial integral domain, an element is regular iff it is non-zero. -/
theorem isRegular_iff_ne_zero [Nontrivial R] : IsRegular a ↔ a ≠ 0 :=
  ⟨IsRegular.ne_zero, isRegular_of_ne_zero⟩
#align is_regular_iff_ne_zero isRegular_iff_ne_zero

end CancelMonoidWithZero


/-
Copyright (c) 2014 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.nat.basic
import Mathlib.PostPort

namespace Mathlib

/-!
# Definitions and properties of `gcd`, `lcm`, and `coprime`

-/

namespace nat

/-! ### `gcd` -/

theorem gcd_dvd (m : ℕ) (n : ℕ) : gcd m n ∣ m ∧ gcd m n ∣ n := sorry

theorem gcd_dvd_left (m : ℕ) (n : ℕ) : gcd m n ∣ m := and.left (gcd_dvd m n)

theorem gcd_dvd_right (m : ℕ) (n : ℕ) : gcd m n ∣ n := and.right (gcd_dvd m n)

theorem gcd_le_left {m : ℕ} (n : ℕ) (h : 0 < m) : gcd m n ≤ m := le_of_dvd h (gcd_dvd_left m n)

theorem gcd_le_right (m : ℕ) {n : ℕ} (h : 0 < n) : gcd m n ≤ n := le_of_dvd h (gcd_dvd_right m n)

theorem dvd_gcd {m : ℕ} {n : ℕ} {k : ℕ} : k ∣ m → k ∣ n → k ∣ gcd m n := sorry

theorem dvd_gcd_iff {m : ℕ} {n : ℕ} {k : ℕ} : k ∣ gcd m n ↔ k ∣ m ∧ k ∣ n := sorry

theorem gcd_comm (m : ℕ) (n : ℕ) : gcd m n = gcd n m :=
  dvd_antisymm (dvd_gcd (gcd_dvd_right m n) (gcd_dvd_left m n))
    (dvd_gcd (gcd_dvd_right n m) (gcd_dvd_left n m))

theorem gcd_eq_left_iff_dvd {m : ℕ} {n : ℕ} : m ∣ n ↔ gcd m n = m := sorry

theorem gcd_eq_right_iff_dvd {m : ℕ} {n : ℕ} : m ∣ n ↔ gcd n m = m :=
  eq.mpr (id (Eq._oldrec (Eq.refl (m ∣ n ↔ gcd n m = m)) (gcd_comm n m))) gcd_eq_left_iff_dvd

theorem gcd_assoc (m : ℕ) (n : ℕ) (k : ℕ) : gcd (gcd m n) k = gcd m (gcd n k) := sorry

@[simp] theorem gcd_one_right (n : ℕ) : gcd n 1 = 1 := Eq.trans (gcd_comm n 1) (gcd_one_left n)

theorem gcd_mul_left (m : ℕ) (n : ℕ) (k : ℕ) : gcd (m * n) (m * k) = m * gcd n k := sorry

theorem gcd_mul_right (m : ℕ) (n : ℕ) (k : ℕ) : gcd (m * n) (k * n) = gcd m k * n := sorry

theorem gcd_pos_of_pos_left {m : ℕ} (n : ℕ) (mpos : 0 < m) : 0 < gcd m n :=
  pos_of_dvd_of_pos (gcd_dvd_left m n) mpos

theorem gcd_pos_of_pos_right (m : ℕ) {n : ℕ} (npos : 0 < n) : 0 < gcd m n :=
  pos_of_dvd_of_pos (gcd_dvd_right m n) npos

theorem eq_zero_of_gcd_eq_zero_left {m : ℕ} {n : ℕ} (H : gcd m n = 0) : m = 0 :=
  or.elim (eq_zero_or_pos m) id
    fun (H1 : 0 < m) => absurd (Eq.symm H) (ne_of_lt (gcd_pos_of_pos_left n H1))

theorem eq_zero_of_gcd_eq_zero_right {m : ℕ} {n : ℕ} (H : gcd m n = 0) : n = 0 :=
  eq_zero_of_gcd_eq_zero_left (eq.mp (Eq._oldrec (Eq.refl (gcd m n = 0)) (gcd_comm m n)) H)

theorem gcd_div {m : ℕ} {n : ℕ} {k : ℕ} (H1 : k ∣ m) (H2 : k ∣ n) :
    gcd (m / k) (n / k) = gcd m n / k :=
  sorry

theorem gcd_dvd_gcd_of_dvd_left {m : ℕ} {k : ℕ} (n : ℕ) (H : m ∣ k) : gcd m n ∣ gcd k n :=
  dvd_gcd (dvd.trans (gcd_dvd_left m n) H) (gcd_dvd_right m n)

theorem gcd_dvd_gcd_of_dvd_right {m : ℕ} {k : ℕ} (n : ℕ) (H : m ∣ k) : gcd n m ∣ gcd n k :=
  dvd_gcd (gcd_dvd_left n m) (dvd.trans (gcd_dvd_right n m) H)

theorem gcd_dvd_gcd_mul_left (m : ℕ) (n : ℕ) (k : ℕ) : gcd m n ∣ gcd (k * m) n :=
  gcd_dvd_gcd_of_dvd_left n (dvd_mul_left m k)

theorem gcd_dvd_gcd_mul_right (m : ℕ) (n : ℕ) (k : ℕ) : gcd m n ∣ gcd (m * k) n :=
  gcd_dvd_gcd_of_dvd_left n (dvd_mul_right m k)

theorem gcd_dvd_gcd_mul_left_right (m : ℕ) (n : ℕ) (k : ℕ) : gcd m n ∣ gcd m (k * n) :=
  gcd_dvd_gcd_of_dvd_right m (dvd_mul_left n k)

theorem gcd_dvd_gcd_mul_right_right (m : ℕ) (n : ℕ) (k : ℕ) : gcd m n ∣ gcd m (n * k) :=
  gcd_dvd_gcd_of_dvd_right m (dvd_mul_right n k)

theorem gcd_eq_left {m : ℕ} {n : ℕ} (H : m ∣ n) : gcd m n = m :=
  dvd_antisymm (gcd_dvd_left m n) (dvd_gcd (dvd_refl m) H)

theorem gcd_eq_right {m : ℕ} {n : ℕ} (H : n ∣ m) : gcd m n = n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (gcd m n = n)) (gcd_comm m n)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (gcd n m = n)) (gcd_eq_left H))) (Eq.refl n))

@[simp] theorem gcd_mul_left_left (m : ℕ) (n : ℕ) : gcd (m * n) n = n :=
  dvd_antisymm (gcd_dvd_right (m * n) n) (dvd_gcd (dvd_mul_left n m) (dvd_refl n))

@[simp] theorem gcd_mul_left_right (m : ℕ) (n : ℕ) : gcd n (m * n) = n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (gcd n (m * n) = n)) (gcd_comm n (m * n))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (gcd (m * n) n = n)) (gcd_mul_left_left m n))) (Eq.refl n))

@[simp] theorem gcd_mul_right_left (m : ℕ) (n : ℕ) : gcd (n * m) n = n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (gcd (n * m) n = n)) (mul_comm n m)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (gcd (m * n) n = n)) (gcd_mul_left_left m n))) (Eq.refl n))

@[simp] theorem gcd_mul_right_right (m : ℕ) (n : ℕ) : gcd n (n * m) = n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (gcd n (n * m) = n)) (gcd_comm n (n * m))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (gcd (n * m) n = n)) (gcd_mul_right_left m n))) (Eq.refl n))

@[simp] theorem gcd_gcd_self_right_left (m : ℕ) (n : ℕ) : gcd m (gcd m n) = gcd m n :=
  dvd_antisymm (gcd_dvd_right m (gcd m n)) (dvd_gcd (gcd_dvd_left m n) (dvd_refl (gcd m n)))

@[simp] theorem gcd_gcd_self_right_right (m : ℕ) (n : ℕ) : gcd m (gcd n m) = gcd n m :=
  eq.mpr (id (Eq._oldrec (Eq.refl (gcd m (gcd n m) = gcd n m)) (gcd_comm n m)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (gcd m (gcd m n) = gcd m n)) (gcd_gcd_self_right_left m n)))
      (Eq.refl (gcd m n)))

@[simp] theorem gcd_gcd_self_left_right (m : ℕ) (n : ℕ) : gcd (gcd n m) m = gcd n m :=
  eq.mpr (id (Eq._oldrec (Eq.refl (gcd (gcd n m) m = gcd n m)) (gcd_comm (gcd n m) m)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (gcd m (gcd n m) = gcd n m)) (gcd_gcd_self_right_right m n)))
      (Eq.refl (gcd n m)))

@[simp] theorem gcd_gcd_self_left_left (m : ℕ) (n : ℕ) : gcd (gcd m n) m = gcd m n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (gcd (gcd m n) m = gcd m n)) (gcd_comm m n)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (gcd (gcd n m) m = gcd n m)) (gcd_gcd_self_left_right m n)))
      (Eq.refl (gcd n m)))

theorem gcd_add_mul_self (m : ℕ) (n : ℕ) (k : ℕ) : gcd m (n + k * m) = gcd m n := sorry

theorem gcd_eq_zero_iff {i : ℕ} {j : ℕ} : gcd i j = 0 ↔ i = 0 ∧ j = 0 := sorry

/-! ### `lcm` -/

theorem lcm_comm (m : ℕ) (n : ℕ) : lcm m n = lcm n m :=
  id
    (eq.mpr (id (Eq._oldrec (Eq.refl (m * n / gcd m n = n * m / gcd n m)) (mul_comm m n)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (n * m / gcd m n = n * m / gcd n m)) (gcd_comm m n)))
        (Eq.refl (n * m / gcd n m))))

@[simp] theorem lcm_zero_left (m : ℕ) : lcm 0 m = 0 :=
  id
    (eq.mpr (id (Eq._oldrec (Eq.refl (0 * m / gcd 0 m = 0)) (zero_mul m)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (0 / gcd 0 m = 0)) (nat.zero_div (gcd 0 m)))) (Eq.refl 0)))

@[simp] theorem lcm_zero_right (m : ℕ) : lcm m 0 = 0 := lcm_comm 0 m ▸ lcm_zero_left m

@[simp] theorem lcm_one_left (m : ℕ) : lcm 1 m = m := sorry

@[simp] theorem lcm_one_right (m : ℕ) : lcm m 1 = m := lcm_comm 1 m ▸ lcm_one_left m

@[simp] theorem lcm_self (m : ℕ) : lcm m m = m := sorry

theorem dvd_lcm_left (m : ℕ) (n : ℕ) : m ∣ lcm m n :=
  dvd.intro (n / gcd m n) (Eq.symm (nat.mul_div_assoc m (gcd_dvd_right m n)))

theorem dvd_lcm_right (m : ℕ) (n : ℕ) : n ∣ lcm m n := lcm_comm n m ▸ dvd_lcm_left n m

theorem gcd_mul_lcm (m : ℕ) (n : ℕ) : gcd m n * lcm m n = m * n := sorry

theorem lcm_dvd {m : ℕ} {n : ℕ} {k : ℕ} (H1 : m ∣ k) (H2 : n ∣ k) : lcm m n ∣ k := sorry

theorem lcm_assoc (m : ℕ) (n : ℕ) (k : ℕ) : lcm (lcm m n) k = lcm m (lcm n k) := sorry

theorem lcm_ne_zero {m : ℕ} {n : ℕ} (hm : m ≠ 0) (hn : n ≠ 0) : lcm m n ≠ 0 := sorry

/-!
### `coprime`

See also `nat.coprime_of_dvd` and `nat.coprime_of_dvd'` to prove `nat.coprime m n`.
-/

protected instance coprime.decidable (m : ℕ) (n : ℕ) : Decidable (coprime m n) :=
  eq.mpr sorry (nat.decidable_eq (gcd m n) 1)

theorem coprime.gcd_eq_one {m : ℕ} {n : ℕ} : coprime m n → gcd m n = 1 := id

theorem coprime.symm {m : ℕ} {n : ℕ} : coprime n m → coprime m n := Eq.trans (gcd_comm m n)

theorem coprime.dvd_of_dvd_mul_right {m : ℕ} {n : ℕ} {k : ℕ} (H1 : coprime k n) (H2 : k ∣ m * n) :
    k ∣ m :=
  sorry

theorem coprime.dvd_of_dvd_mul_left {m : ℕ} {n : ℕ} {k : ℕ} (H1 : coprime k m) (H2 : k ∣ m * n) :
    k ∣ n :=
  coprime.dvd_of_dvd_mul_right H1 (eq.mp (Eq._oldrec (Eq.refl (k ∣ m * n)) (mul_comm m n)) H2)

theorem coprime.gcd_mul_left_cancel {k : ℕ} (m : ℕ) {n : ℕ} (H : coprime k n) :
    gcd (k * m) n = gcd m n :=
  sorry

theorem coprime.gcd_mul_right_cancel (m : ℕ) {k : ℕ} {n : ℕ} (H : coprime k n) :
    gcd (m * k) n = gcd m n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (gcd (m * k) n = gcd m n)) (mul_comm m k)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (gcd (k * m) n = gcd m n)) (coprime.gcd_mul_left_cancel m H)))
      (Eq.refl (gcd m n)))

theorem coprime.gcd_mul_left_cancel_right {k : ℕ} {m : ℕ} (n : ℕ) (H : coprime k m) :
    gcd m (k * n) = gcd m n :=
  sorry

theorem coprime.gcd_mul_right_cancel_right {k : ℕ} {m : ℕ} (n : ℕ) (H : coprime k m) :
    gcd m (n * k) = gcd m n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (gcd m (n * k) = gcd m n)) (mul_comm n k)))
    (eq.mpr
      (id (Eq._oldrec (Eq.refl (gcd m (k * n) = gcd m n)) (coprime.gcd_mul_left_cancel_right n H)))
      (Eq.refl (gcd m n)))

theorem coprime_div_gcd_div_gcd {m : ℕ} {n : ℕ} (H : 0 < gcd m n) :
    coprime (m / gcd m n) (n / gcd m n) :=
  sorry

theorem not_coprime_of_dvd_of_dvd {m : ℕ} {n : ℕ} {d : ℕ} (dgt1 : 1 < d) (Hm : d ∣ m) (Hn : d ∣ n) :
    ¬coprime m n :=
  fun (co : gcd m n = 1) =>
    not_lt_of_ge
      (le_of_dvd zero_lt_one
        (eq.mpr (id (Eq._oldrec (Eq.refl (d ∣ 1)) (Eq.symm co))) (dvd_gcd Hm Hn)))
      dgt1

theorem exists_coprime {m : ℕ} {n : ℕ} (H : 0 < gcd m n) :
    ∃ (m' : ℕ), ∃ (n' : ℕ), coprime m' n' ∧ m = m' * gcd m n ∧ n = n' * gcd m n :=
  sorry

theorem exists_coprime' {m : ℕ} {n : ℕ} (H : 0 < gcd m n) :
    ∃ (g : ℕ), ∃ (m' : ℕ), ∃ (n' : ℕ), 0 < g ∧ coprime m' n' ∧ m = m' * g ∧ n = n' * g :=
  sorry

theorem coprime.mul {m : ℕ} {n : ℕ} {k : ℕ} (H1 : coprime m k) (H2 : coprime n k) :
    coprime (m * n) k :=
  Eq.trans (coprime.gcd_mul_left_cancel n H1) H2

theorem coprime.mul_right {k : ℕ} {m : ℕ} {n : ℕ} (H1 : coprime k m) (H2 : coprime k n) :
    coprime k (m * n) :=
  coprime.symm (coprime.mul (coprime.symm H1) (coprime.symm H2))

theorem coprime.coprime_dvd_left {m : ℕ} {k : ℕ} {n : ℕ} (H1 : m ∣ k) (H2 : coprime k n) :
    coprime m n :=
  sorry

theorem coprime.coprime_dvd_right {m : ℕ} {k : ℕ} {n : ℕ} (H1 : n ∣ m) (H2 : coprime k m) :
    coprime k n :=
  coprime.symm (coprime.coprime_dvd_left H1 (coprime.symm H2))

theorem coprime.coprime_mul_left {k : ℕ} {m : ℕ} {n : ℕ} (H : coprime (k * m) n) : coprime m n :=
  coprime.coprime_dvd_left (dvd_mul_left m k) H

theorem coprime.coprime_mul_right {k : ℕ} {m : ℕ} {n : ℕ} (H : coprime (m * k) n) : coprime m n :=
  coprime.coprime_dvd_left (dvd_mul_right m k) H

theorem coprime.coprime_mul_left_right {k : ℕ} {m : ℕ} {n : ℕ} (H : coprime m (k * n)) :
    coprime m n :=
  coprime.coprime_dvd_right (dvd_mul_left n k) H

theorem coprime.coprime_mul_right_right {k : ℕ} {m : ℕ} {n : ℕ} (H : coprime m (n * k)) :
    coprime m n :=
  coprime.coprime_dvd_right (dvd_mul_right n k) H

theorem coprime.coprime_div_left {m : ℕ} {n : ℕ} {a : ℕ} (cmn : coprime m n) (dvd : a ∣ m) :
    coprime (m / a) n :=
  sorry

theorem coprime.coprime_div_right {m : ℕ} {n : ℕ} {a : ℕ} (cmn : coprime m n) (dvd : a ∣ n) :
    coprime m (n / a) :=
  coprime.symm (coprime.coprime_div_left (coprime.symm cmn) dvd)

theorem coprime_mul_iff_left {k : ℕ} {m : ℕ} {n : ℕ} :
    coprime (m * n) k ↔ coprime m k ∧ coprime n k :=
  sorry

theorem coprime_mul_iff_right {k : ℕ} {m : ℕ} {n : ℕ} :
    coprime k (m * n) ↔ coprime k m ∧ coprime k n :=
  sorry

theorem coprime.gcd_left (k : ℕ) {m : ℕ} {n : ℕ} (hmn : coprime m n) : coprime (gcd k m) n :=
  coprime.coprime_dvd_left (gcd_dvd_right k m) hmn

theorem coprime.gcd_right (k : ℕ) {m : ℕ} {n : ℕ} (hmn : coprime m n) : coprime m (gcd k n) :=
  coprime.coprime_dvd_right (gcd_dvd_right k n) hmn

theorem coprime.gcd_both (k : ℕ) (l : ℕ) {m : ℕ} {n : ℕ} (hmn : coprime m n) :
    coprime (gcd k m) (gcd l n) :=
  coprime.gcd_right l (coprime.gcd_left k hmn)

theorem coprime.mul_dvd_of_dvd_of_dvd {a : ℕ} {n : ℕ} {m : ℕ} (hmn : coprime m n) (hm : m ∣ a)
    (hn : n ∣ a) : m * n ∣ a :=
  sorry

theorem coprime_one_left (n : ℕ) : coprime 1 n := gcd_one_left

theorem coprime_one_right (n : ℕ) : coprime n 1 := gcd_one_right

theorem coprime.pow_left {m : ℕ} {k : ℕ} (n : ℕ) (H1 : coprime m k) : coprime (m ^ n) k :=
  nat.rec_on n (coprime_one_left k) fun (n : ℕ) (IH : coprime (m ^ n) k) => coprime.mul H1 IH

theorem coprime.pow_right {m : ℕ} {k : ℕ} (n : ℕ) (H1 : coprime k m) : coprime k (m ^ n) :=
  coprime.symm (coprime.pow_left n (coprime.symm H1))

theorem coprime.pow {k : ℕ} {l : ℕ} (m : ℕ) (n : ℕ) (H1 : coprime k l) : coprime (k ^ m) (l ^ n) :=
  coprime.pow_right n (coprime.pow_left m H1)

theorem coprime.eq_one_of_dvd {k : ℕ} {m : ℕ} (H : coprime k m) (d : k ∣ m) : k = 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (k = 1)) (Eq.symm (coprime.gcd_eq_one H))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (k = gcd k m)) (gcd_eq_left d))) (Eq.refl k))

@[simp] theorem coprime_zero_left (n : ℕ) : coprime 0 n ↔ n = 1 := sorry

@[simp] theorem coprime_zero_right (n : ℕ) : coprime n 0 ↔ n = 1 := sorry

@[simp] theorem coprime_one_left_iff (n : ℕ) : coprime 1 n ↔ True := sorry

@[simp] theorem coprime_one_right_iff (n : ℕ) : coprime n 1 ↔ True := sorry

@[simp] theorem coprime_self (n : ℕ) : coprime n n ↔ n = 1 := sorry

/-- Represent a divisor of `m * n` as a product of a divisor of `m` and a divisor of `n`. -/
def prod_dvd_and_dvd_of_dvd_prod {m : ℕ} {n : ℕ} {k : ℕ} (H : k ∣ m * n) :
    Subtype
        fun (d : (Subtype fun (m' : ℕ) => m' ∣ m) × Subtype fun (n' : ℕ) => n' ∣ n) =>
          k = ↑(prod.fst d) * ↑(prod.snd d) :=
  (fun (_x : ℕ) (h0 : gcd k m = _x) =>
      nat.cases_on _x
        (fun (h0 : gcd k m = 0) =>
          Eq._oldrec
            (fun (H : 0 ∣ m * n) (h0 : gcd 0 m = 0) =>
              Eq._oldrec
                (fun (H : 0 ∣ 0 * n) (h0 : gcd 0 0 = 0) =>
                  { val := ({ val := 0, property := sorry }, { val := n, property := dvd_refl n }),
                    property := sorry })
                sorry H h0)
            sorry H h0)
        (fun (n_1 : ℕ) (h0 : gcd k m = Nat.succ n_1) =>
          (fun (h0 : gcd k m = Nat.succ n_1) =>
              { val :=
                  ({ val := gcd k m, property := gcd_dvd_right k m },
                  { val := k / gcd k m, property := sorry }),
                property := sorry })
            h0)
        h0)
    (gcd k m) sorry

theorem gcd_mul_dvd_mul_gcd (k : ℕ) (m : ℕ) (n : ℕ) : gcd k (m * n) ∣ gcd k m * gcd k n := sorry

theorem coprime.gcd_mul (k : ℕ) {m : ℕ} {n : ℕ} (h : coprime m n) :
    gcd k (m * n) = gcd k m * gcd k n :=
  dvd_antisymm (gcd_mul_dvd_mul_gcd k m n)
    (coprime.mul_dvd_of_dvd_of_dvd (coprime.gcd_both k k h) (gcd_dvd_gcd_mul_right_right k m n)
      (gcd_dvd_gcd_mul_left_right k n m))

theorem pow_dvd_pow_iff {a : ℕ} {b : ℕ} {n : ℕ} (n0 : 0 < n) : a ^ n ∣ b ^ n ↔ a ∣ b := sorry

theorem gcd_mul_gcd_of_coprime_of_mul_eq_mul {a : ℕ} {b : ℕ} {c : ℕ} {d : ℕ} (cop : coprime c d)
    (h : a * b = c * d) : gcd a c * gcd b c = c :=
  sorry

end Mathlib