/-
Copyright (c) 2022 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Markus Himmel
-/
module

prelude
import Init.Data.Int.Basic
import Init.Data.Nat.Gcd
import Init.Data.Nat.Lcm
import Init.Data.Int.DivMod.Lemmas
import Init.Data.Int.Pow

/-!
Definition and lemmas for gcd and lcm over Int

-/
namespace Int

/-! ## gcd -/

/--
Computes the greatest common divisor of two integers as a natural number. The GCD of two integers is
the largest natural number that evenly divides both. However, the GCD of a number and `0` is the
number's absolute value.

This implementation uses `Nat.gcd`, which is overridden in both the kernel and the compiler to
efficiently evaluate using arbitrary-precision arithmetic.

Examples:
* `Int.gcd 10 15 = 5`
* `Int.gcd 10 (-15) = 5`
* `Int.gcd (-6) (-9) = 3`
* `Int.gcd 0 5 = 5`
* `Int.gcd (-7) 0 = 7`
-/
def gcd (m n : Int) : Nat := m.natAbs.gcd n.natAbs

theorem gcd_eq_natAbs_gcd_natAbs (m n : Int) : gcd m n = Nat.gcd m.natAbs n.natAbs := rfl

theorem gcd_dvd_natAbs_left (a b : Int) : gcd a b ∣ a.natAbs := Nat.gcd_dvd_left ..
theorem gcd_dvd_natAbs_right (a b : Int) : gcd a b ∣ b.natAbs := Nat.gcd_dvd_right ..

theorem gcd_dvd_left (a b : Int) : (gcd a b : Int) ∣ a := by
  simp [ofNat_dvd_left, gcd_dvd_natAbs_left]

theorem gcd_dvd_right (a b : Int) : (gcd a b : Int) ∣ b := by
  simp [ofNat_dvd_left, gcd_dvd_natAbs_right]

@[simp] theorem one_gcd {a : Int} : gcd 1 a = 1 := by simp [gcd_eq_natAbs_gcd_natAbs]
@[simp] theorem gcd_one {a : Int} : gcd a 1 = 1 := by simp [gcd_eq_natAbs_gcd_natAbs]

@[simp] theorem zero_gcd {a : Int} : gcd 0 a = a.natAbs := by simp [gcd_eq_natAbs_gcd_natAbs]
@[simp] theorem gcd_zero {a : Int} : gcd a 0 = a.natAbs := by simp [gcd_eq_natAbs_gcd_natAbs]

theorem gcd_one_left (a : Int) : gcd 1 a = 1 := by simp
theorem gcd_one_right (a : Int) : gcd a 1 = 1 := by simp
theorem gcd_zero_left (a : Int) : gcd 0 a = a.natAbs := by simp
theorem gcd_zero_right (a : Int) : gcd a 0 = a.natAbs := by simp

@[simp] theorem gcd_self {a : Int} : gcd a a = a.natAbs := by simp [gcd_eq_natAbs_gcd_natAbs]

@[simp] theorem neg_gcd {a b : Int} : gcd (-a) b = gcd a b := by simp [gcd_eq_natAbs_gcd_natAbs]
@[simp] theorem gcd_neg {a b : Int} : gcd a (-b) = gcd a b := by simp [gcd_eq_natAbs_gcd_natAbs]

@[simp, norm_cast] protected theorem gcd_natCast_natCast (a b : Nat) :
    gcd (a : Int) (b : Int) = Nat.gcd a b := by
  simp [gcd_eq_natAbs_gcd_natAbs]

theorem gcd_le_natAbs_left (b : Int) (ha : a ≠ 0) : gcd a b ≤ a.natAbs :=
  Nat.gcd_le_left b.natAbs (natAbs_pos.2 ha)

theorem gcd_le_natAbs_right (a : Int) (hb : b ≠ 0) : gcd a b ≤ b.natAbs :=
  Nat.gcd_le_right a.natAbs (natAbs_pos.2 hb)

theorem gcd_le_left (b : Int) (ha : 0 < a) : (gcd a b : Int) ≤ a := by
  rw (occs := [2]) [(by omega : a = (a.natAbs : Int))]
  simp [gcd_le_natAbs_left b (by omega : a ≠ 0)]

theorem gcd_le_right (a : Int) (hb : 0 < b) : (gcd a b : Int) ≤ b := by
  rw (occs := [2]) [(by omega : b = (b.natAbs : Int))]
  simp [gcd_le_natAbs_right a (by omega : b ≠ 0)]

theorem dvd_coe_gcd {a b c : Int} (ha : c ∣ a) (hb : c ∣ b) : c ∣ (gcd a b : Int) :=
  ofNat_dvd_right.2 (Nat.dvd_gcd (natAbs_dvd_natAbs.2 ha) (natAbs_dvd_natAbs.2 hb))

theorem dvd_gcd {a b : Int} {c : Nat} (ha : (c : Int) ∣ a) (hb : (c : Int) ∣ b) : c ∣ gcd a b :=
  ofNat_dvd.1 (dvd_coe_gcd ha hb)

theorem dvd_coe_gcd_iff {a b c : Int} : c ∣ (gcd a b : Int) ↔ c ∣ a ∧ c ∣ b :=
  ⟨fun h => ⟨Int.dvd_trans h (gcd_dvd_left _ _), Int.dvd_trans h (gcd_dvd_right _ _)⟩,
   fun ⟨ha, hb⟩ => dvd_coe_gcd ha hb⟩

theorem dvd_gcd_iff {a b : Int} {c : Nat} : c ∣ gcd a b ↔ (c : Int) ∣ a ∧ (c : Int) ∣ b := by
  rw [← ofNat_dvd, dvd_coe_gcd_iff]

theorem gcd_comm (a b : Int) : gcd a b = gcd b a := Nat.gcd_comm ..

theorem gcd_eq_natAbs_left_iff_dvd : gcd a b = a.natAbs ↔ a ∣ b := by
  simp [gcd_eq_natAbs_gcd_natAbs, Nat.gcd_eq_left_iff_dvd]

theorem gcd_eq_natAbs_right_iff_dvd : gcd a b = b.natAbs ↔ b ∣ a := by
  simp [gcd_eq_natAbs_gcd_natAbs, Nat.gcd_eq_right_iff_dvd]

theorem gcd_eq_left_iff_dvd (ha : 0 ≤ a) : gcd a b = a ↔ a ∣ b := by
  rw (occs := [2]) [eq_natAbs_of_nonneg ha]
  simp [ofNat_inj, gcd_eq_natAbs_left_iff_dvd]

theorem gcd_eq_right_iff_dvd (hb : 0 ≤ b) : gcd a b = b ↔ b ∣ a := by
  rw [gcd_comm, gcd_eq_left_iff_dvd hb]

theorem gcd_assoc (a b c : Int) : gcd (gcd a b) c = gcd a (gcd b c) := Nat.gcd_assoc ..

theorem gcd_mul_left (m n k : Int) : gcd (m * n) (m * k) = m.natAbs * gcd n k := by
  simp [gcd_eq_natAbs_gcd_natAbs, Nat.gcd_mul_left, natAbs_mul]

theorem gcd_mul_right (m n k : Int) : gcd (m * n) (k * n) = gcd m k * n.natAbs := by
  simp [gcd_eq_natAbs_gcd_natAbs, Nat.gcd_mul_right, natAbs_mul]

theorem gcd_pos_of_ne_zero_left {a : Int} (b : Int) (ha : a ≠ 0) : 0 < gcd a b :=
  Nat.gcd_pos_of_pos_left _ (natAbs_pos.2 ha)

theorem gcd_pos_of_ne_zero_right (a : Int) {b : Int} (hb : b ≠ 0) : 0 < gcd a b :=
  Nat.gcd_pos_of_pos_right _ (natAbs_pos.2 hb)

theorem gcd_ne_zero_left (ha : a ≠ 0) : gcd a b ≠ 0 := by
  simp [← Nat.pos_iff_ne_zero, gcd_pos_of_ne_zero_left _ ha]

theorem gcd_ne_zero_right (hb : b ≠ 0) : gcd a b ≠ 0 := by
  simp [← Nat.pos_iff_ne_zero, gcd_pos_of_ne_zero_right _ hb]

theorem natAbs_div_gcd_pos_of_ne_zero_left (b : Int) (h : a ≠ 0) : 0 < a.natAbs / gcd a b :=
  Nat.div_gcd_pos_of_pos_left _ (natAbs_pos.2 h)

theorem natAbs_div_gcd_pos_of_ne_zero_right (a : Int) (h : b ≠ 0) : 0 < b.natAbs / gcd a b :=
  Nat.div_gcd_pos_of_pos_right _ (natAbs_pos.2 h)

theorem ediv_gcd_ne_zero_of_ne_zero_left (b : Int) (h : a ≠ 0) : a / gcd a b ≠ 0 := by
  rw [← natAbs_pos, natAbs_ediv_of_dvd (gcd_dvd_left _ _), natAbs_natCast]
  exact natAbs_div_gcd_pos_of_ne_zero_left _ h

theorem ediv_gcd_ne_zero_if_ne_zero_right (a : Int) (h : b ≠ 0) : b / gcd a b ≠ 0 := by
  simpa [gcd_comm] using ediv_gcd_ne_zero_of_ne_zero_left a h

theorem eq_zero_of_gcd_eq_zero_left (h : gcd a b = 0) : a = 0 :=
  natAbs_eq_zero.1 (Nat.eq_zero_of_gcd_eq_zero_left h)

theorem eq_zero_of_gcd_eq_zero_right (h : gcd a b = 0) : b = 0 :=
  natAbs_eq_zero.1 (Nat.eq_zero_of_gcd_eq_zero_right h)

theorem gcd_ediv {a b c : Int} (ha : c ∣ a) (hb : c ∣ b) :
    gcd (a / c) (b / c) = gcd a b / c.natAbs := by
  rw [gcd_eq_natAbs_gcd_natAbs, natAbs_ediv_of_dvd ha, natAbs_ediv_of_dvd hb,
    Nat.gcd_div (by simpa) (by simpa), gcd_eq_natAbs_gcd_natAbs]

theorem gcd_dvd_gcd_of_dvd_left {a b : Int} (c : Int) (h : a ∣ b) : gcd a c ∣ gcd b c :=
  Nat.gcd_dvd_gcd_of_dvd_left _ (by simpa)

theorem gcd_dvd_gcd_of_dvd_right {a b : Int} (c : Int) (h : a ∣ b) : gcd c a ∣ gcd c b :=
  Nat.gcd_dvd_gcd_of_dvd_right _ (by simpa)

theorem gcd_dvd_gcd_mul_left_left (a b c : Int) : gcd a b ∣ gcd (c * a) b := by
  simpa [gcd_eq_natAbs_gcd_natAbs, natAbs_mul] using Nat.gcd_dvd_gcd_mul_left_left ..

theorem gcd_dvd_gcd_mul_right_left (a b c : Int) : gcd a b ∣ gcd (a * c) b := by
  simpa [gcd_eq_natAbs_gcd_natAbs, natAbs_mul] using Nat.gcd_dvd_gcd_mul_right_left ..

theorem gcd_dvd_gcd_mul_left_right (a b c : Int) : gcd a b ∣ gcd a (c * b) := by
  simpa [gcd_eq_natAbs_gcd_natAbs, natAbs_mul] using Nat.gcd_dvd_gcd_mul_left_right ..

theorem gcd_dvd_gcd_mul_right_right (a b c : Int) : gcd a b ∣ gcd a (b * c) := by
  simpa [gcd_eq_natAbs_gcd_natAbs, natAbs_mul] using Nat.gcd_dvd_gcd_mul_right_right ..

theorem gcd_eq_natAbs_left (h : a ∣ b) : gcd a b = a.natAbs :=
  gcd_eq_natAbs_left_iff_dvd.2 h

theorem gcd_eq_natAbs_right (h : b ∣ a) : gcd a b = b.natAbs :=
  gcd_eq_natAbs_right_iff_dvd.2 h

theorem gcd_eq_left (ha : 0 ≤ a) (h : a ∣ b) : gcd a b = a :=
  (gcd_eq_left_iff_dvd ha).2 h

theorem gcd_eq_right (hb : 0 ≤ b) (h : b ∣ a) : gcd a b = b :=
  (gcd_eq_right_iff_dvd hb).2 h

theorem gcd_right_eq_iff {a b b' : Int} : gcd a b = gcd a b' ↔ ∀ c, c ∣ a → (c ∣ b ↔ c ∣ b') := by
  simp only [gcd_eq_natAbs_gcd_natAbs, Nat.gcd_right_eq_iff]
  refine ⟨fun hk c hc => ?_, fun hc k hk => ?_⟩
  · simpa using hk c.natAbs (by simpa)
  · simpa [ofNat_dvd_left] using hc k (by simpa [ofNat_dvd_left])

theorem gcd_left_eq_iff {a a' b : Int} : gcd a b = gcd a' b ↔ ∀ c, c ∣ b → (c ∣ a ↔ c ∣ a') := by
  rw [gcd_comm a b, gcd_comm a' b, gcd_right_eq_iff]

@[simp] theorem gcd_mul_left_left (a b : Int) : gcd (a * b) b = b.natAbs := by
  simp [gcd_eq_natAbs_gcd_natAbs, natAbs_mul]

@[simp] theorem gcd_mul_left_right (a b : Int) : gcd a (b * a) = a.natAbs := by
  simp [gcd_eq_natAbs_gcd_natAbs, natAbs_mul]

@[simp] theorem gcd_mul_right_left (a b : Int) : gcd (b * a) b = b.natAbs := by
  simp [gcd_eq_natAbs_gcd_natAbs, natAbs_mul]

@[simp] theorem gcd_mul_right_right (a b : Int) : gcd a (a * b) = a.natAbs := by
  simp [gcd_eq_natAbs_gcd_natAbs, natAbs_mul]

@[simp] theorem gcd_gcd_self_right_left (m n : Int) : gcd m (gcd m n) = gcd m n :=
  Nat.gcd_gcd_self_right_left ..

@[simp] theorem gcd_gcd_self_right_right (m n : Int) : gcd m (gcd n m) = gcd n m :=
  Nat.gcd_gcd_self_right_right ..

@[simp] theorem gcd_gcd_self_left_right (m n : Int) : gcd (gcd n m) m = gcd n m :=
  Nat.gcd_gcd_self_left_right ..

@[simp] theorem gcd_gcd_self_left_left (m n : Int) : gcd (gcd m n) m = gcd m n :=
  Nat.gcd_gcd_self_left_left ..

@[simp] theorem gcd_add_mul_right_right (m n k : Int) : gcd m (n + k * m) = gcd m n :=
  gcd_right_eq_iff.2 (by rintro c ⟨l, rfl⟩; rw [Int.mul_comm, Int.mul_assoc, Int.dvd_add_self_mul])

@[simp] theorem gcd_add_mul_left_right (m n k : Int) : gcd m (n + m * k) = gcd m n := by
  rw [Int.mul_comm, gcd_add_mul_right_right]

@[simp] theorem gcd_mul_right_add_right (m n k : Int) : gcd m (k * m + n) = gcd m n := by
  rw [Int.add_comm, gcd_add_mul_right_right]

@[simp] theorem gcd_mul_left_add_right (m n k : Int) : gcd m (m * k + n) = gcd m n := by
  rw [Int.add_comm, gcd_add_mul_left_right]

@[simp] theorem gcd_add_mul_right_left (m n k : Int) : gcd (n + k * m) m = gcd n m := by
  rw [gcd_comm, gcd_add_mul_right_right, gcd_comm]

@[simp] theorem gcd_add_mul_left_left (m n k : Int) : gcd (n + m * k) m = gcd n m := by
  rw [Int.mul_comm, gcd_add_mul_right_left]

@[simp] theorem gcd_mul_right_add_left (m n k : Int) : gcd (k * m + n) m = gcd n m := by
  rw [Int.add_comm, gcd_add_mul_right_left]

@[simp] theorem gcd_mul_left_add_left (m n k : Int) : gcd (m * k + n) m = gcd n m := by
  rw [Int.add_comm, gcd_add_mul_left_left]

@[simp] theorem gcd_add_self_right (m n : Int) : gcd m (n + m) = gcd m n := by
  simpa using gcd_add_mul_right_right _ _ 1

@[simp] theorem gcd_self_add_right (m n : Int) : gcd m (m + n) = gcd m n := by
  simpa using gcd_mul_right_add_right _ _ 1

@[simp] theorem gcd_add_self_left (m n : Int) : gcd (n + m) m = gcd n m := by
  simpa using gcd_add_mul_right_left _ _ 1

@[simp] theorem gcd_self_add_left (m n : Int) : gcd (m + n) m = gcd n m := by
  simpa using gcd_mul_right_add_left _ _ 1

@[simp] theorem gcd_add_left_left_of_dvd {m k : Int} (n : Int) :
    m ∣ k → gcd (k + n) m = gcd n m := by
  rintro ⟨l, rfl⟩; exact gcd_mul_left_add_left m n l

@[simp] theorem gcd_add_right_left_of_dvd {m k : Int} (n : Int) :
    m ∣ k → gcd (n + k) m = gcd n m := by
  rintro ⟨l, rfl⟩; exact gcd_add_mul_left_left m n l

@[simp] theorem gcd_add_left_right_of_dvd {n k : Int} (m : Int) :
    n ∣ k → gcd n (k + m) = gcd n m := by
  rintro ⟨l, rfl⟩; exact gcd_mul_left_add_right n m l

@[simp] theorem gcd_add_right_right_of_dvd {n k : Int} (m : Int) :
    n ∣ k → gcd n (m + k) = gcd n m := by
  rintro ⟨l, rfl⟩; exact gcd_add_mul_left_right n m l

@[simp] theorem gcd_sub_mul_right_right (m n k : Int) : gcd m (n - k * m) = gcd m n := by
  simp [Int.sub_eq_add_neg, ← Int.neg_mul]

@[simp] theorem gcd_sub_mul_left_right (m n k : Int) : gcd m (n - m * k) = gcd m n := by
  rw [Int.mul_comm, gcd_sub_mul_right_right]

@[simp] theorem gcd_mul_right_sub_right (m n k : Int) : gcd m (k * m - n) = gcd m n := by
  simp [Int.sub_eq_add_neg]

@[simp] theorem gcd_mul_left_sub_right (m n k : Int) : gcd m (m * k - n) = gcd m n := by
  simp [Int.sub_eq_add_neg]

@[simp] theorem gcd_sub_mul_right_left (m n k : Int) : gcd (n - k * m) m = gcd n m := by
  rw [gcd_comm, gcd_sub_mul_right_right, gcd_comm]

@[simp] theorem gcd_sub_mul_left_left (m n k : Int) : gcd (n - m * k) m = gcd n m := by
  rw [Int.mul_comm, gcd_sub_mul_right_left]

@[simp] theorem gcd_mul_right_sub_left (m n k : Int) : gcd (k * m - n) m = gcd n m := by
  simp [Int.sub_eq_add_neg]

@[simp] theorem gcd_mul_left_sub_left (m n k : Int) : gcd (m * k - n) m = gcd n m := by
  simp [Int.sub_eq_add_neg]

@[simp] theorem gcd_sub_self_right (m n : Int) : gcd m (n - m) = gcd m n := by
  simpa using gcd_sub_mul_right_right _ _ 1

@[simp] theorem gcd_self_sub_right (m n : Int) : gcd m (m - n) = gcd m n := by
  simpa using gcd_mul_right_sub_right _ _ 1

@[simp] theorem gcd_sub_self_left (m n : Int) : gcd (n - m) m = gcd n m := by
  simpa using gcd_sub_mul_right_left _ _ 1

@[simp] theorem gcd_self_sub_left (m n : Int) : gcd (m - n) m = gcd n m := by
  simpa using gcd_mul_right_sub_left _ _ 1

@[simp] theorem gcd_sub_left_left_of_dvd {m k : Int} (n : Int) :
    m ∣ k → gcd (k - n) m = gcd n m := by
  rintro ⟨l, rfl⟩; exact gcd_mul_left_sub_left m n l

@[simp] theorem gcd_sub_right_left_of_dvd {m k : Int} (n : Int) :
    m ∣ k → gcd (n - k) m = gcd n m := by
  rintro ⟨l, rfl⟩; exact gcd_sub_mul_left_left m n l

@[simp] theorem gcd_sub_left_right_of_dvd {n k : Int} (m : Int) :
    n ∣ k → gcd n (k - m) = gcd n m := by
  rintro ⟨l, rfl⟩; exact gcd_mul_left_sub_right n m l

@[simp] theorem gcd_sub_right_right_of_dvd {n k : Int} (m : Int) :
    n ∣ k → gcd n (m - k) = gcd n m := by
  rintro ⟨l, rfl⟩; exact gcd_sub_mul_left_right n m l

@[simp] theorem gcd_eq_zero_iff {a b : Int} : gcd a b = 0 ↔ a = 0 ∧ b = 0 := by
  simp [gcd_eq_natAbs_gcd_natAbs]

@[simp] theorem gcd_pos_iff {a b : Int} : 0 < gcd a b ↔ a ≠ 0 ∨ b ≠ 0 := by
  simp only [Nat.pos_iff_ne_zero, gcd_eq_zero_iff, ne_eq, Decidable.not_and_iff_not_or_not]

theorem gcd_eq_iff {a b : Int} {g : Nat} :
    gcd a b = g ↔ (g : Int) ∣ a ∧ (g : Int) ∣ b ∧ (∀ c, c ∣ a → c ∣ b → c ∣ g) := by
  refine ⟨?_, fun ⟨ha, hb, hc⟩ => Nat.dvd_antisymm ?_ (dvd_gcd ha hb)⟩
  · rintro rfl
    exact ⟨gcd_dvd_left _ _, gcd_dvd_right _ _, fun _ => Int.dvd_coe_gcd⟩
  · exact Int.ofNat_dvd.1 (hc _ (gcd_dvd_left _ _) (gcd_dvd_right _ _))

/-- Represent a divisor of `m * n` as a product of a divisor of `m` and a divisor of `n`. -/
def dvdProdDvdOfDvdProd {k m n : Int} (h : k ∣ m * n) :
    { d : { m' // m' ∣ m } × { n' // n' ∣ n } // k = d.1.val * d.2.val } :=
  let d₀ := Nat.dvdProdDvdOfDvdProd (natAbs_mul _ _ ▸ natAbs_dvd_natAbs.2 h)
  if hk : 0 ≤ k then
    ⟨⟨⟨d₀.val.1.val, ofNat_dvd_left.2 d₀.val.1.property⟩,
      ⟨d₀.val.2.val, ofNat_dvd_left.2 d₀.val.2.property⟩⟩,
     (eq_natAbs_of_nonneg hk).trans (by simp [d₀.property])⟩
  else
    ⟨⟨⟨-d₀.val.1.val, by simpa using ofNat_dvd_left.2 d₀.val.1.property⟩,
      ⟨d₀.val.2.val, ofNat_dvd_left.2 d₀.val.2.property⟩⟩,
     (eq_neg_natAbs_of_nonpos (Int.le_of_not_le hk)).trans (by simp [d₀.property, Int.neg_mul])⟩

protected theorem dvd_mul {a b c : Int} : c ∣ a * b ↔ ∃ c₁ c₂, c₁ ∣ a ∧ c₂ ∣ b ∧ c₁ * c₂ = c := by
  refine ⟨fun h => ?_, ?_⟩
  · obtain ⟨⟨⟨k₁, hk₁⟩, ⟨k₂, hk₂⟩⟩, rfl⟩ := dvdProdDvdOfDvdProd h
    exact ⟨k₁, k₂, hk₁, hk₂, rfl⟩
  · rintro ⟨k₁, k₂, hk₁, hk₂, rfl⟩
    exact Int.mul_dvd_mul hk₁ hk₂

theorem gcd_mul_right_dvd_mul_gcd (k m n : Int) : gcd k (m * n) ∣ gcd k m * gcd k n := by
  simp [gcd_eq_natAbs_gcd_natAbs, natAbs_mul, Nat.gcd_mul_right_dvd_mul_gcd]

theorem gcd_mul_left_dvd_mul_gcd (k m n : Int) : gcd (m * n) k ∣ gcd m k * gcd n k := by
  simp [gcd_eq_natAbs_gcd_natAbs, natAbs_mul, Nat.gcd_mul_left_dvd_mul_gcd]

theorem dvd_gcd_mul_iff_dvd_mul {k n m : Int} : k ∣ gcd k n * m ↔ k ∣ n * m := by
  simp [← natAbs_dvd_natAbs, natAbs_mul, Nat.dvd_gcd_mul_iff_dvd_mul, gcd_eq_natAbs_gcd_natAbs]

theorem dvd_mul_gcd_iff_dvd_mul {k n m : Int} : k ∣ n * gcd k m ↔ k ∣ n * m := by
  simp [← natAbs_dvd_natAbs, natAbs_mul, Nat.dvd_mul_gcd_iff_dvd_mul, gcd_eq_natAbs_gcd_natAbs]

theorem dvd_gcd_mul_gcd_iff_dvd_mul {k n m : Int} : k ∣ gcd k n * gcd k m ↔ k ∣ n * m := by
  simp [← natAbs_dvd_natAbs, natAbs_mul, Nat.dvd_gcd_mul_gcd_iff_dvd_mul, gcd_eq_natAbs_gcd_natAbs]

theorem gcd_eq_one_iff {m n : Int} : gcd m n = 1 ↔ ∀ c, c ∣ m → c ∣ n → c ∣ 1 := by
  simp [gcd_eq_iff]

theorem gcd_mul_right_right_of_gcd_eq_one {n m k : Int} : gcd n m = 1 → gcd n (m * k) = gcd n k := by
  simpa [gcd_eq_natAbs_gcd_natAbs, natAbs_mul] using Nat.gcd_mul_right_right_of_gcd_eq_one

theorem gcd_mul_left_right_of_gcd_eq_one {n m k : Int} : gcd n m = 1 → gcd n (k * m) = gcd n k := by
  simpa [gcd_eq_natAbs_gcd_natAbs, natAbs_mul] using Nat.gcd_mul_left_right_of_gcd_eq_one

theorem gcd_mul_right_left_of_gcd_eq_one {n m k : Int} : gcd n m = 1 → gcd (n * k) m = gcd k m := by
  simpa [gcd_eq_natAbs_gcd_natAbs, natAbs_mul] using Nat.gcd_mul_right_left_of_gcd_eq_one

theorem gcd_mul_left_left_of_gcd_eq_one {n m k : Int} : gcd n m = 1 → gcd (k * n) m = gcd k m := by
  simpa [gcd_eq_natAbs_gcd_natAbs, natAbs_mul] using Nat.gcd_mul_left_left_of_gcd_eq_one

theorem gcd_pow_left_of_gcd_eq_one {n m : Int} {k : Nat} : gcd n m = 1 → gcd (n ^ k) m = 1 := by
  simpa [gcd_eq_natAbs_gcd_natAbs, natAbs_pow] using Nat.gcd_pow_left_of_gcd_eq_one

theorem gcd_pow_right_of_gcd_eq_one {n m : Int} {k : Nat} (h : gcd n m = 1) : gcd n (m ^ k) = 1 := by
  rw [gcd_comm, gcd_pow_left_of_gcd_eq_one (gcd_comm _ _ ▸ h)]

theorem pow_gcd_pow_of_gcd_eq_one {n m : Int} {k l : Nat} (h : gcd n m = 1) : gcd (n ^ k) (m ^ l) = 1 :=
  gcd_pow_left_of_gcd_eq_one (gcd_pow_right_of_gcd_eq_one h)

theorem gcd_ediv_gcd_ediv_gcd_of_ne_zero_left {n m : Int} (h : n ≠ 0) :
    gcd (n / gcd n m) (m / gcd n m) = 1 := by
  rw [gcd_ediv (gcd_dvd_left _ _) (gcd_dvd_right _ _), natAbs_natCast,
    Nat.div_self (gcd_pos_of_ne_zero_left _ h)]

theorem gcd_ediv_gcd_ediv_gcd_of_ne_zero_right {n m : Int} (h : m ≠ 0) :
    gcd (n / gcd n m) (m / gcd n m) = 1 := by
  rw [gcd_ediv (gcd_dvd_left _ _) (gcd_dvd_right _ _), natAbs_natCast,
    Nat.div_self (gcd_pos_of_ne_zero_right _ h)]

theorem gcd_ediv_gcd_ediv_gcd {i j : Int} (h : 0 < gcd i j) : gcd (i / gcd i j) (j / gcd i j) = 1 :=
  match gcd_pos_iff.1 h with
  | Or.inl h => gcd_ediv_gcd_ediv_gcd_of_ne_zero_left h
  | Or.inr h => gcd_ediv_gcd_ediv_gcd_of_ne_zero_right h

theorem pow_gcd_pow {n m : Int} {k : Nat} : gcd (n ^ k) (m ^ k) = (gcd n m) ^ k := by
  simpa [gcd_eq_natAbs_gcd_natAbs, natAbs_pow] using Nat.pow_gcd_pow

theorem pow_dvd_pow_iff {a b : Int} {n : Nat} (h : n ≠ 0) : a ^ n ∣ b ^ n ↔ a ∣ b := by
  simp [← natAbs_dvd_natAbs, natAbs_pow, Nat.pow_dvd_pow_iff h]

/-! ## lcm -/

/--
Computes the least common multiple of two integers as a natural number. The LCM of two integers is
the smallest natural number that's evenly divisible by the absolute values of both.

Examples:
 * `Int.lcm 9 6 = 18`
 * `Int.lcm 9 (-6) = 18`
 * `Int.lcm 9 3 = 9`
 * `Int.lcm 9 (-3) = 9`
 * `Int.lcm 0 3 = 0`
 * `Int.lcm (-3) 0 = 0`
-/
def lcm (m n : Int) : Nat := m.natAbs.lcm n.natAbs

theorem lcm_eq_natAbs_lcm_natAbs (m n : Int) : lcm m n = Nat.lcm m.natAbs n.natAbs := rfl

theorem lcm_eq_mul_div (m n : Int) : lcm m n = m.natAbs * n.natAbs / gcd m n := by
  simp [lcm_eq_natAbs_lcm_natAbs, Nat.lcm_eq_mul_div, gcd_eq_natAbs_gcd_natAbs]

@[simp] theorem gcd_mul_lcm (m n : Int) : gcd m n * lcm m n = m.natAbs * n.natAbs := by
  simp [gcd_eq_natAbs_gcd_natAbs, lcm_eq_natAbs_lcm_natAbs]

@[simp] theorem lcm_mul_gcd (m n : Int) : lcm m n * gcd m n = m.natAbs * n.natAbs := by
  simp [gcd_eq_natAbs_gcd_natAbs, lcm_eq_natAbs_lcm_natAbs]

@[simp] theorem lcm_dvd_natAbs_mul (m n : Int) : lcm m n ∣ m.natAbs * n.natAbs := ⟨gcd m n, by simp⟩
@[simp] theorem gcd_dvd_natAbs_mul (m n : Int) : gcd m n ∣ m.natAbs * n.natAbs := ⟨lcm m n, by simp⟩

@[simp] theorem lcm_dvd_mul (m n : Int) : (lcm m n : Int) ∣ m * n := by
  simp [ofNat_dvd_left, natAbs_mul]

@[simp] theorem gcd_dvd_mul (m n : Int) : (gcd m n : Int) ∣ m * n := by
  simp [ofNat_dvd_left, natAbs_mul]

theorem natAbs_dvd_lcm_left (a b : Int) : a.natAbs ∣ lcm a b := Nat.dvd_lcm_left ..
theorem natAbs_dvd_lcm_right (a b : Int) : b.natAbs ∣ lcm a b := Nat.dvd_lcm_right ..

theorem dvd_lcm_left (a b : Int) : a ∣ lcm a b := by
  simp [ofNat_dvd_right, natAbs_dvd_lcm_left]

theorem dvd_lcm_right (a b : Int) : b ∣ lcm a b := by
  simp [ofNat_dvd_right, natAbs_dvd_lcm_right]

theorem lcm_le_natAbs_mul {a b : Int} (ha : a ≠ 0) (hb : b ≠ 0) : lcm a b ≤ a.natAbs * b.natAbs :=
  Nat.lcm_le_mul (natAbs_pos.2 ha) (natAbs_pos.2 hb)

theorem gcd_le_natAbs_mul {a b : Int} (ha : a ≠ 0) (hb : b ≠ 0) : gcd a b ≤ a.natAbs * b.natAbs :=
  Nat.gcd_le_mul (natAbs_pos.2 ha) (natAbs_pos.2 hb)

theorem lcm_comm (a b : Int) : lcm a b = lcm b a := Nat.lcm_comm ..

@[simp] theorem one_lcm {a : Int} : lcm 1 a = a.natAbs := by simp [lcm_eq_natAbs_lcm_natAbs]
@[simp] theorem lcm_one {a : Int} : lcm a 1 = a.natAbs := by simp [lcm_eq_natAbs_lcm_natAbs]

@[simp] theorem zero_lcm {a : Int} : lcm 0 a = 0 := by simp [lcm_eq_natAbs_lcm_natAbs]
@[simp] theorem lcm_zero {a : Int} : lcm a 0 = 0 := by simp [lcm_eq_natAbs_lcm_natAbs]

theorem lcm_one_left (a : Int) : lcm 1 a = a.natAbs := by simp
theorem lcm_one_right (a : Int) : lcm a 1 = a.natAbs := by simp

theorem lcm_zero_left (a : Int) : lcm 0 a = 0 := by simp
theorem lcm_zero_right (a : Int) : lcm a 0 = 0 := by simp

@[simp] theorem lcm_self {a : Int} : lcm a a = a.natAbs := Nat.lcm_self _

@[simp] theorem neg_lcm {a b : Int} : lcm (-a) b = lcm a b := by simp [lcm_eq_natAbs_lcm_natAbs]
@[simp] theorem lcm_neg {a b : Int} : lcm a (-b) = lcm a b := by simp [lcm_eq_natAbs_lcm_natAbs]

@[simp, norm_cast] protected theorem lcm_natCast_natCast (a b : Nat) :
    lcm (a : Int) (b : Int) = Nat.lcm a b := by
  simp [lcm_eq_natAbs_lcm_natAbs]

theorem natAbs_le_lcm_left (a : Int) (hb : b ≠ 0) : a.natAbs ≤ lcm a b :=
  Nat.le_lcm_left a.natAbs (natAbs_pos.2 hb)

theorem natAbs_le_lcm_right (b : Int) (ha : a ≠ 0) : b.natAbs ≤ lcm a b :=
  Nat.le_lcm_right b.natAbs (natAbs_pos.2 ha)

theorem le_lcm_left (a : Int) (hb : b ≠ 0) : a ≤ (lcm a b : Int) :=
  Int.le_trans le_natAbs (ofNat_le.2 (natAbs_le_lcm_left a hb))

theorem le_lcm_right (b : Int) (ha : a ≠ 0) : b ≤ (lcm a b : Int) :=
  Int.le_trans le_natAbs (ofNat_le.2 (natAbs_le_lcm_right b ha))

theorem coe_lcm_dvd {a b c : Int} (ha : a ∣ c) (hb : b ∣ c) : (lcm a b : Int) ∣ c :=
  ofNat_dvd_left.2 (Nat.lcm_dvd (natAbs_dvd_natAbs.2 ha) (natAbs_dvd_natAbs.2 hb))

theorem lcm_dvd {a b : Int} {c : Nat} (ha : a ∣ (c : Int)) (hb : b ∣ (c : Int)) : lcm a b ∣ c :=
  ofNat_dvd.1 (coe_lcm_dvd ha hb)

theorem coe_lcm_dvd_iff {a b c : Int} : (lcm a b : Int) ∣ c ↔ a ∣ c ∧ b ∣ c :=
  ⟨fun h => ⟨Int.dvd_trans (dvd_lcm_left _ _) h, Int.dvd_trans (dvd_lcm_right _ _ ) h⟩,
   fun ⟨ha, hb⟩ => coe_lcm_dvd ha hb⟩

theorem lcm_dvd_iff {a b : Int} {c : Nat} : lcm a b ∣ c ↔ a ∣ (c : Int) ∧ b ∣ (c : Int) := by
  rw [← ofNat_dvd, coe_lcm_dvd_iff]

theorem lcm_eq_natAbs_left_iff_dvd : lcm a b = a.natAbs ↔ b ∣ a := by
  simp [lcm_eq_natAbs_lcm_natAbs, Nat.lcm_eq_left_iff_dvd]

theorem lcm_eq_natAbs_right_iff_dvd : lcm a b = b.natAbs ↔ a ∣ b := by
  simp [lcm_eq_natAbs_lcm_natAbs, Nat.lcm_eq_right_iff_dvd]

theorem lcm_eq_left_iff_dvd (ha : 0 ≤ a) : lcm a b = a ↔ b ∣ a := by
  rw (occs := [2]) [eq_natAbs_of_nonneg ha]
  simp [ofNat_inj, lcm_eq_natAbs_left_iff_dvd]

theorem lcm_eq_right_iff_dvd (hb : 0 ≤ b) : lcm a b = b ↔ a ∣ b := by
  rw [lcm_comm, lcm_eq_left_iff_dvd hb]

theorem lcm_assoc (a b c : Int) : lcm (lcm a b) c = lcm a (lcm b c) := Nat.lcm_assoc ..

theorem lcm_mul_left (m n k : Int) : lcm (m * n) (m * k) = m.natAbs * lcm n k := by
  simp [lcm_eq_natAbs_lcm_natAbs, Nat.lcm_mul_left, natAbs_mul]

theorem lcm_mul_right (m n k : Int) : lcm (m * n) (k * n) = lcm m k * n.natAbs := by
  simp [lcm_eq_natAbs_lcm_natAbs, Nat.lcm_mul_right, natAbs_mul]

theorem lcm_ne_zero (hm : m ≠ 0) (hn : n ≠ 0) : lcm m n ≠ 0 := by
  apply Nat.lcm_ne_zero <;> simpa

theorem lcm_pos : m ≠ 0 → n ≠ 0 → 0 < lcm m n := by
  simpa [← Nat.pos_iff_ne_zero] using lcm_ne_zero

theorem eq_zero_of_lcm_eq_zero (h : lcm m n = 0) : m = 0 ∨ n = 0 := by
  have := lcm_ne_zero (m := m) (n := n); omega

@[simp] theorem lcm_eq_zero_iff : lcm m n = 0 ↔ m = 0 ∨ n = 0 :=
  ⟨eq_zero_of_lcm_eq_zero, by rintro (rfl|rfl) <;> simp⟩

@[simp] theorem lcm_pos_iff : 0 < lcm m n ↔ m ≠ 0 ∧ n ≠ 0 := by
  simp only [Nat.pos_iff_ne_zero, ne_eq, lcm_eq_zero_iff, not_or]

theorem lcm_eq_iff {n m : Int} {l : Nat} :
    lcm n m = l ↔ n ∣ (l : Int) ∧ m ∣ (l : Int) ∧ (∀ c, n ∣ c → m ∣ c → (l : Int) ∣ c) := by
  refine ⟨?_, fun ⟨hn, hm, hl⟩ => Nat.dvd_antisymm (lcm_dvd hn hm) ?_⟩
  · rintro rfl
    exact ⟨dvd_lcm_left _ _, dvd_lcm_right _ _, fun _ => coe_lcm_dvd⟩
  · exact Int.ofNat_dvd.1 (hl _ (dvd_lcm_left _ _) (dvd_lcm_right _ _))

theorem lcm_ediv {a b c : Int} (ha : c ∣ a) (hb : c ∣ b) :
    lcm (a / c) (b / c) = lcm a b / c.natAbs := by
  rw [lcm_eq_natAbs_lcm_natAbs, natAbs_ediv_of_dvd ha, natAbs_ediv_of_dvd hb,
    Nat.lcm_div (by simpa) (by simpa), lcm_eq_natAbs_lcm_natAbs]

theorem lcm_dvd_lcm_of_dvd_left {a b : Int} (c : Int) (h : a ∣ b) : lcm a c ∣ lcm b c :=
  Nat.lcm_dvd_lcm_of_dvd_left _ (by simpa)

theorem lcm_dvd_lcm_of_dvd_right {a b : Int} (c : Int) (h : a ∣ b) : lcm c a ∣ lcm c b :=
  Nat.lcm_dvd_lcm_of_dvd_right _ (by simpa)

theorem lcm_dvd_lcm_mul_left_left (a b c : Int) : lcm a b ∣ lcm (c * a) b := by
  simpa [lcm_eq_natAbs_lcm_natAbs, natAbs_mul] using Nat.lcm_dvd_lcm_mul_left_left ..

theorem lcm_dvd_lcm_mul_right_left (a b c : Int) : lcm a b ∣ lcm (a * c) b := by
  simpa [lcm_eq_natAbs_lcm_natAbs, natAbs_mul] using Nat.lcm_dvd_lcm_mul_right_left ..

theorem lcm_dvd_lcm_mul_left_right (a b c : Int) : lcm a b ∣ lcm a (c * b) := by
  simpa [lcm_eq_natAbs_lcm_natAbs, natAbs_mul] using Nat.lcm_dvd_lcm_mul_left_right ..

theorem lcm_dvd_lcm_mul_right_right (a b c : Int) : lcm a b ∣ lcm a (b * c) := by
  simpa [lcm_eq_natAbs_lcm_natAbs, natAbs_mul] using Nat.lcm_dvd_lcm_mul_right_right ..

theorem lcm_eq_natAbs_left (h : b ∣ a) : lcm a b = a.natAbs :=
  lcm_eq_natAbs_left_iff_dvd.2 h

theorem lcm_eq_natAbs_right (h : a ∣ b) : lcm a b = b.natAbs :=
  lcm_eq_natAbs_right_iff_dvd.2 h

theorem lcm_eq_left (ha : 0 ≤ a) (h : b ∣ a) : lcm a b = a :=
  (lcm_eq_left_iff_dvd ha).2 h

theorem lcm_eq_right (hb : 0 ≤ b) (h : a ∣ b) : lcm a b = b :=
  (lcm_eq_right_iff_dvd hb).2 h

@[simp] theorem lcm_mul_left_left (a b : Int) : lcm (a * b) b = a.natAbs * b.natAbs := by
  simp [lcm_eq_natAbs_lcm_natAbs, natAbs_mul]

@[simp] theorem lcm_mul_left_right (a b : Int) : lcm a (b * a) = b.natAbs * a.natAbs := by
  simp [lcm_eq_natAbs_lcm_natAbs, natAbs_mul]

@[simp] theorem lcm_mul_right_left (a b : Int) : lcm (b * a) b = b.natAbs * a.natAbs := by
  simp [lcm_eq_natAbs_lcm_natAbs, natAbs_mul]

@[simp] theorem lcm_mul_right_right (a b : Int) : lcm a (a * b) = a.natAbs * b.natAbs := by
  simp [lcm_eq_natAbs_lcm_natAbs, natAbs_mul]

@[simp] theorem lcm_lcm_self_right_left (m n : Int) : lcm m (lcm m n) = lcm m n :=
  Nat.lcm_lcm_self_right_left ..

@[simp] theorem lcm_lcm_self_right_right (m n : Int) : lcm m (lcm n m) = lcm m n :=
  Nat.lcm_lcm_self_right_right ..

@[simp] theorem lcm_lcm_self_left_right (m n : Int) : lcm (lcm n m) m = lcm n m :=
  Nat.lcm_lcm_self_left_right ..

@[simp] theorem lcm_lcm_self_left_left (m n : Int) : lcm (lcm m n) m = lcm n m :=
  Nat.lcm_lcm_self_left_left ..

theorem lcm_eq_mul_iff {m n : Int} : lcm m n = m.natAbs * n.natAbs ↔ m = 0 ∨ n = 0 ∨ gcd m n = 1 := by
  simp [lcm_eq_natAbs_lcm_natAbs, Nat.lcm_eq_mul_iff, gcd_eq_natAbs_gcd_natAbs]

@[simp] theorem lcm_eq_one_iff {m n : Int} : lcm m n = 1 ↔ m ∣ 1 ∧ n ∣ 1 := by
  refine ⟨fun h => ⟨?_, ?_⟩, fun ⟨ha, hb⟩ => Nat.eq_one_of_dvd_one (lcm_dvd ha hb)⟩ <;>
    simp [← natAbs_dvd_natAbs, ← h, natAbs_dvd_lcm_left, natAbs_dvd_lcm_right]

theorem lcm_mul_right_dvd_mul_lcm (k m n : Nat) : lcm k (m * n) ∣ lcm k m * lcm k n := by
  simp [lcm_eq_natAbs_lcm_natAbs, natAbs_mul, Nat.lcm_mul_right_dvd_mul_lcm]

theorem lcm_mul_left_dvd_mul_lcm (k m n : Nat) : lcm (m * n) k ∣ lcm m k * lcm n k := by
  simpa [lcm_comm, Nat.mul_comm] using lcm_mul_right_dvd_mul_lcm _ _ _

theorem lcm_dvd_mul_self_left_iff_dvd_mul {k n m : Nat} : lcm k n ∣ k * m ↔ n ∣ k * m := by
  simp [← natAbs_dvd_natAbs, natAbs_mul, Nat.lcm_dvd_mul_self_left_iff_dvd_mul,
    lcm_eq_natAbs_lcm_natAbs]

theorem lcm_dvd_mul_self_right_iff_dvd_mul {k m n : Nat} : lcm n k ∣ m * k ↔ n ∣ m * k := by
  rw [lcm_comm, Nat.mul_comm m, lcm_dvd_mul_self_left_iff_dvd_mul]

theorem lcm_mul_right_right_eq_mul_of_lcm_eq_mul {n m k : Int} (h : lcm n m = n.natAbs * m.natAbs) :
    lcm n (m * k) = lcm n k * m.natAbs := by
  simp [lcm_eq_natAbs_lcm_natAbs, natAbs_mul, Nat.lcm_mul_right_right_eq_mul_of_lcm_eq_mul h]

theorem lcm_mul_left_right_eq_mul_of_lcm_eq_mul {n m k} (h : lcm n m = n.natAbs * m.natAbs) :
    lcm n (k * m) = lcm n k * m.natAbs := by
  simp [lcm_eq_natAbs_lcm_natAbs, natAbs_mul, Nat.lcm_mul_left_right_eq_mul_of_lcm_eq_mul h]

theorem lcm_mul_right_left_eq_mul_of_lcm_eq_mul {n m k} (h : lcm n m = n.natAbs * m.natAbs) :
    lcm (n * k) m = n.natAbs * lcm k m := by
  simp [lcm_eq_natAbs_lcm_natAbs, natAbs_mul, Nat.lcm_mul_right_left_eq_mul_of_lcm_eq_mul h]

theorem lcm_mul_left_left_eq_mul_of_lcm_eq_mul {n m k} (h : lcm n m = n.natAbs * m.natAbs) :
    lcm (k * n) m = n.natAbs * lcm k m := by
  simp [lcm_eq_natAbs_lcm_natAbs, natAbs_mul, Nat.lcm_mul_left_left_eq_mul_of_lcm_eq_mul h]

theorem pow_lcm_pow {n m : Int} {k : Nat} : lcm (n ^ k) (m ^ k) = (lcm n m) ^ k := by
  simpa [lcm_eq_natAbs_lcm_natAbs, natAbs_pow] using Nat.pow_lcm_pow

end Int
