/-
Copyright (c) 2018 Guy Leroy. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sangwoo Jo (aka Jason), Guy Leroy, Johannes Hölzl, Mario Carneiro

! This file was ported from Lean 3 source module data.int.gcd
! leanprover-community/mathlib commit c3291da49cfa65f0d43b094750541c0731edc932
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Nat.Gcd.Basic
import Mathbin.Tactic.NormNum

/-!
# Extended GCD and divisibility over ℤ

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

## Main definitions

* Given `x y : ℕ`, `xgcd x y` computes the pair of integers `(a, b)` such that
  `gcd x y = x * a + y * b`. `gcd_a x y` and `gcd_b x y` are defined to be `a` and `b`,
  respectively.

## Main statements

* `gcd_eq_gcd_ab`: Bézout's lemma, given `x y : ℕ`, `gcd x y = x * gcd_a x y + y * gcd_b x y`.

## Tags

Bézout's lemma, Bezout's lemma
-/

/-! ### Extended Euclidean algorithm -/

namespace Nat

#print Nat.xgcdAux /-
/-- Helper function for the extended GCD algorithm (`nat.xgcd`). -/
def xgcdAux : ℕ → ℤ → ℤ → ℕ → ℤ → ℤ → ℕ × ℤ × ℤ
  | 0, s, t, r', s', t' => (r', s', t')
  | r@(succ _), s, t, r', s', t' =>
    have : r' % r < r := mod_lt _ <| succ_pos _
    let q := r' / r
    xgcd_aux (r' % r) (s' - q * s) (t' - q * t) r s t
#align nat.xgcd_aux Nat.xgcdAux
-/

#print Nat.xgcd_zero_left /-
@[simp]
theorem xgcd_zero_left {s t r' s' t'} : xgcdAux 0 s t r' s' t' = (r', s', t') := by simp [xgcd_aux]
#align nat.xgcd_zero_left Nat.xgcd_zero_left
-/

#print Nat.xgcd_aux_rec /-
theorem xgcd_aux_rec {r s t r' s' t'} (h : 0 < r) :
    xgcdAux r s t r' s' t' = xgcdAux (r' % r) (s' - r' / r * s) (t' - r' / r * t) r s t := by
  cases r <;> [exact absurd h (lt_irrefl _),
    · simp only [xgcd_aux]
      rfl]
#align nat.xgcd_aux_rec Nat.xgcd_aux_rec
-/

#print Nat.xgcd /-
/-- Use the extended GCD algorithm to generate the `a` and `b` values
  satisfying `gcd x y = x * a + y * b`. -/
def xgcd (x y : ℕ) : ℤ × ℤ :=
  (xgcdAux x 1 0 y 0 1).2
#align nat.xgcd Nat.xgcd
-/

#print Nat.gcdA /-
/-- The extended GCD `a` value in the equation `gcd x y = x * a + y * b`. -/
def gcdA (x y : ℕ) : ℤ :=
  (xgcd x y).1
#align nat.gcd_a Nat.gcdA
-/

#print Nat.gcdB /-
/-- The extended GCD `b` value in the equation `gcd x y = x * a + y * b`. -/
def gcdB (x y : ℕ) : ℤ :=
  (xgcd x y).2
#align nat.gcd_b Nat.gcdB
-/

#print Nat.gcdA_zero_left /-
@[simp]
theorem gcdA_zero_left {s : ℕ} : gcdA 0 s = 0 :=
  by
  unfold gcd_a
  rw [xgcd, xgcd_zero_left]
#align nat.gcd_a_zero_left Nat.gcdA_zero_left
-/

#print Nat.gcdB_zero_left /-
@[simp]
theorem gcdB_zero_left {s : ℕ} : gcdB 0 s = 1 :=
  by
  unfold gcd_b
  rw [xgcd, xgcd_zero_left]
#align nat.gcd_b_zero_left Nat.gcdB_zero_left
-/

#print Nat.gcdA_zero_right /-
@[simp]
theorem gcdA_zero_right {s : ℕ} (h : s ≠ 0) : gcdA s 0 = 1 :=
  by
  unfold gcd_a xgcd
  induction s
  · exact absurd rfl h
  · simp [xgcd_aux]
#align nat.gcd_a_zero_right Nat.gcdA_zero_right
-/

#print Nat.gcdB_zero_right /-
@[simp]
theorem gcdB_zero_right {s : ℕ} (h : s ≠ 0) : gcdB s 0 = 0 :=
  by
  unfold gcd_b xgcd
  induction s
  · exact absurd rfl h
  · simp [xgcd_aux]
#align nat.gcd_b_zero_right Nat.gcdB_zero_right
-/

#print Nat.xgcd_aux_fst /-
@[simp]
theorem xgcd_aux_fst (x y) : ∀ s t s' t', (xgcdAux x s t y s' t').1 = gcd x y :=
  gcd.induction x y (by simp) fun x y h IH s t s' t' => by
    simp [xgcd_aux_rec, h, IH] <;> rw [← gcd_rec]
#align nat.xgcd_aux_fst Nat.xgcd_aux_fst
-/

#print Nat.xgcd_aux_val /-
theorem xgcd_aux_val (x y) : xgcdAux x 1 0 y 0 1 = (gcd x y, xgcd x y) := by
  rw [xgcd, ← xgcd_aux_fst x y 1 0 0 1] <;> cases xgcd_aux x 1 0 y 0 1 <;> rfl
#align nat.xgcd_aux_val Nat.xgcd_aux_val
-/

#print Nat.xgcd_val /-
theorem xgcd_val (x y) : xgcd x y = (gcdA x y, gcdB x y) := by
  unfold gcd_a gcd_b <;> cases xgcd x y <;> rfl
#align nat.xgcd_val Nat.xgcd_val
-/

section

parameter (x y : ℕ)

private def P : ℕ × ℤ × ℤ → Prop
  | (r, s, t) => (r : ℤ) = x * s + y * t
#align nat.P nat.P

#print Nat.xgcd_aux_P /-
theorem xgcd_aux_P {r r'} :
    ∀ {s t s' t'}, P (r, s, t) → P (r', s', t') → P (xgcdAux r s t r' s' t') :=
  gcd.induction r r' (by simp) fun a b h IH s t s' t' p p' =>
    by
    rw [xgcd_aux_rec h]; refine' IH _ p; dsimp [P] at *
    rw [Int.mod_def]; generalize (b / a : ℤ) = k
    rw [p, p']
    simp [mul_add, mul_comm, mul_left_comm, add_comm, add_left_comm, sub_eq_neg_add, mul_assoc]
#align nat.xgcd_aux_P Nat.xgcd_aux_P
-/

#print Nat.gcd_eq_gcd_ab /-
/-- **Bézout's lemma**: given `x y : ℕ`, `gcd x y = x * a + y * b`, where `a = gcd_a x y` and
`b = gcd_b x y` are computed by the extended Euclidean algorithm.
-/
theorem gcd_eq_gcd_ab : (gcd x y : ℤ) = x * gcdA x y + y * gcdB x y := by
  have := @xgcd_aux_P x y x y 1 0 0 1 (by simp [P]) (by simp [P]) <;>
    rwa [xgcd_aux_val, xgcd_val] at this
#align nat.gcd_eq_gcd_ab Nat.gcd_eq_gcd_ab
-/

end

#print Nat.exists_mul_emod_eq_gcd /-
theorem exists_mul_emod_eq_gcd {k n : ℕ} (hk : gcd n k < k) : ∃ m, n * m % k = gcd n k :=
  by
  have hk' := int.coe_nat_ne_zero.mpr (ne_of_gt (lt_of_le_of_lt (zero_le (gcd n k)) hk))
  have key := congr_arg (fun m => Int.natMod m k) (gcd_eq_gcd_ab n k)
  simp_rw [Int.natMod] at key
  rw [Int.add_mul_emod_self_left, ← Int.coe_nat_mod, Int.toNat_coe_nat, mod_eq_of_lt hk] at key
  refine' ⟨(n.gcd_a k % k).toNat, Eq.trans (Int.ofNat.inj _) key.symm⟩
  rw [Int.coe_nat_mod, Int.ofNat_mul, Int.toNat_of_nonneg (Int.emod_nonneg _ hk'),
    Int.toNat_of_nonneg (Int.emod_nonneg _ hk'), Int.mul_emod, Int.emod_emod, ← Int.mul_emod]
#align nat.exists_mul_mod_eq_gcd Nat.exists_mul_emod_eq_gcd
-/

#print Nat.exists_mul_emod_eq_one_of_coprime /-
theorem exists_mul_emod_eq_one_of_coprime {k n : ℕ} (hkn : coprime n k) (hk : 1 < k) :
    ∃ m, n * m % k = 1 :=
  Exists.cases_on (exists_mul_emod_eq_gcd (lt_of_le_of_lt (le_of_eq hkn) hk)) fun m hm =>
    ⟨m, hm.trans hkn⟩
#align nat.exists_mul_mod_eq_one_of_coprime Nat.exists_mul_emod_eq_one_of_coprime
-/

end Nat

/-! ### Divisibility over ℤ -/

namespace Int

#print Int.coe_nat_gcd /-
protected theorem coe_nat_gcd (m n : ℕ) : Int.gcd ↑m ↑n = Nat.gcd m n :=
  rfl
#align int.coe_nat_gcd Int.coe_nat_gcd
-/

#print Int.gcdA /-
/-- The extended GCD `a` value in the equation `gcd x y = x * a + y * b`. -/
def gcdA : ℤ → ℤ → ℤ
  | of_nat m, n => m.gcdA n.natAbs
  | -[m+1], n => -m.succ.gcdA n.natAbs
#align int.gcd_a Int.gcdA
-/

#print Int.gcdB /-
/-- The extended GCD `b` value in the equation `gcd x y = x * a + y * b`. -/
def gcdB : ℤ → ℤ → ℤ
  | m, of_nat n => m.natAbs.gcdB n
  | m, -[n+1] => -m.natAbs.gcdB n.succ
#align int.gcd_b Int.gcdB
-/

#print Int.gcd_eq_gcd_ab /-
/-- **Bézout's lemma** -/
theorem gcd_eq_gcd_ab : ∀ x y : ℤ, (gcd x y : ℤ) = x * gcdA x y + y * gcdB x y
  | (m : ℕ), (n : ℕ) => Nat.gcd_eq_gcd_ab _ _
  | (m : ℕ), -[n+1] =>
    show (_ : ℤ) = _ + -(n + 1) * -_ by rw [neg_mul_neg] <;> apply Nat.gcd_eq_gcd_ab
  | -[m+1], (n : ℕ) =>
    show (_ : ℤ) = -(m + 1) * -_ + _ by rw [neg_mul_neg] <;> apply Nat.gcd_eq_gcd_ab
  | -[m+1], -[n+1] =>
    show (_ : ℤ) = -(m + 1) * -_ + -(n + 1) * -_
      by
      rw [neg_mul_neg, neg_mul_neg]
      apply Nat.gcd_eq_gcd_ab
#align int.gcd_eq_gcd_ab Int.gcd_eq_gcd_ab
-/

/- warning: int.nat_abs_div -> Int.natAbs_ediv is a dubious translation:
lean 3 declaration is
  forall (a : Int) (b : Int), (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) b a) -> (Eq.{1} Nat (Int.natAbs (HDiv.hDiv.{0, 0, 0} Int Int Int (instHDiv.{0} Int Int.hasDiv) a b)) (HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.hasDiv) (Int.natAbs a) (Int.natAbs b)))
but is expected to have type
  forall (a : Int) (b : Int), (Dvd.dvd.{0} Int Int.instDvdInt b a) -> (Eq.{1} Nat (Int.natAbs (HDiv.hDiv.{0, 0, 0} Int Int Int (instHDiv.{0} Int Int.instDivInt_1) a b)) (HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.instDivNat) (Int.natAbs a) (Int.natAbs b)))
Case conversion may be inaccurate. Consider using '#align int.nat_abs_div Int.natAbs_edivₓ'. -/
theorem natAbs_ediv (a b : ℤ) (H : b ∣ a) : natAbs (a / b) = natAbs a / natAbs b :=
  by
  cases Nat.eq_zero_or_pos (nat_abs b)
  · rw [eq_zero_of_nat_abs_eq_zero h]
    simp [Int.div_zero]
  calc
    nat_abs (a / b) = nat_abs (a / b) * 1 := by rw [mul_one]
    _ = nat_abs (a / b) * (nat_abs b / nat_abs b) := by rw [Nat.div_self h]
    _ = nat_abs (a / b) * nat_abs b / nat_abs b := by rw [Nat.mul_div_assoc _ dvd_rfl]
    _ = nat_abs (a / b * b) / nat_abs b := by rw [nat_abs_mul (a / b) b]
    _ = nat_abs a / nat_abs b := by rw [Int.ediv_mul_cancel H]

#align int.nat_abs_div Int.natAbs_ediv

/- warning: int.dvd_of_mul_dvd_mul_left -> Int.dvd_of_mul_dvd_mul_left is a dubious translation:
lean 3 declaration is
  forall {i : Int} {j : Int} {k : Int}, (Ne.{1} Int k (OfNat.ofNat.{0} Int 0 (OfNat.mk.{0} Int 0 (Zero.zero.{0} Int Int.hasZero)))) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.hasMul) k i) (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.hasMul) k j)) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) i j)
but is expected to have type
  forall {i : Int} {j : Int} {k : Int}, (Ne.{1} Int k (OfNat.ofNat.{0} Int 0 (instOfNatInt 0))) -> (Dvd.dvd.{0} Int Int.instDvdInt (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.instMulInt) k i) (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.instMulInt) k j)) -> (Dvd.dvd.{0} Int Int.instDvdInt i j)
Case conversion may be inaccurate. Consider using '#align int.dvd_of_mul_dvd_mul_left Int.dvd_of_mul_dvd_mul_leftₓ'. -/
theorem dvd_of_mul_dvd_mul_left {i j k : ℤ} (k_non_zero : k ≠ 0) (H : k * i ∣ k * j) : i ∣ j :=
  Dvd.elim H fun l H1 => by rw [mul_assoc] at H1 <;> exact ⟨_, mul_left_cancel₀ k_non_zero H1⟩
#align int.dvd_of_mul_dvd_mul_left Int.dvd_of_mul_dvd_mul_left

/- warning: int.dvd_of_mul_dvd_mul_right -> Int.dvd_of_mul_dvd_mul_right is a dubious translation:
lean 3 declaration is
  forall {i : Int} {j : Int} {k : Int}, (Ne.{1} Int k (OfNat.ofNat.{0} Int 0 (OfNat.mk.{0} Int 0 (Zero.zero.{0} Int Int.hasZero)))) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.hasMul) i k) (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.hasMul) j k)) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) i j)
but is expected to have type
  forall {i : Int} {j : Int} {k : Int}, (Ne.{1} Int k (OfNat.ofNat.{0} Int 0 (instOfNatInt 0))) -> (Dvd.dvd.{0} Int Int.instDvdInt (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.instMulInt) i k) (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.instMulInt) j k)) -> (Dvd.dvd.{0} Int Int.instDvdInt i j)
Case conversion may be inaccurate. Consider using '#align int.dvd_of_mul_dvd_mul_right Int.dvd_of_mul_dvd_mul_rightₓ'. -/
theorem dvd_of_mul_dvd_mul_right {i j k : ℤ} (k_non_zero : k ≠ 0) (H : i * k ∣ j * k) : i ∣ j := by
  rw [mul_comm i k, mul_comm j k] at H <;> exact dvd_of_mul_dvd_mul_left k_non_zero H
#align int.dvd_of_mul_dvd_mul_right Int.dvd_of_mul_dvd_mul_right

#print Int.lcm /-
/-- ℤ specific version of least common multiple. -/
def lcm (i j : ℤ) : ℕ :=
  Nat.lcm (natAbs i) (natAbs j)
#align int.lcm Int.lcm
-/

#print Int.lcm_def /-
theorem lcm_def (i j : ℤ) : lcm i j = Nat.lcm (natAbs i) (natAbs j) :=
  rfl
#align int.lcm_def Int.lcm_def
-/

#print Int.coe_nat_lcm /-
protected theorem coe_nat_lcm (m n : ℕ) : Int.lcm ↑m ↑n = Nat.lcm m n :=
  rfl
#align int.coe_nat_lcm Int.coe_nat_lcm
-/

/- warning: int.gcd_dvd_left -> Int.gcd_dvd_left is a dubious translation:
lean 3 declaration is
  forall (i : Int) (j : Int), Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat Int (HasLiftT.mk.{1, 1} Nat Int (CoeTCₓ.coe.{1, 1} Nat Int (coeBase.{1, 1} Nat Int Int.hasCoe))) (Int.gcd i j)) i
but is expected to have type
  forall (i : Int) (j : Int), Dvd.dvd.{0} Int Int.instDvdInt (Nat.cast.{0} Int instNatCastInt (Int.gcd i j)) i
Case conversion may be inaccurate. Consider using '#align int.gcd_dvd_left Int.gcd_dvd_leftₓ'. -/
theorem gcd_dvd_left (i j : ℤ) : (gcd i j : ℤ) ∣ i :=
  dvd_natAbs.mp <| coe_nat_dvd.mpr <| Nat.gcd_dvd_left _ _
#align int.gcd_dvd_left Int.gcd_dvd_left

/- warning: int.gcd_dvd_right -> Int.gcd_dvd_right is a dubious translation:
lean 3 declaration is
  forall (i : Int) (j : Int), Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat Int (HasLiftT.mk.{1, 1} Nat Int (CoeTCₓ.coe.{1, 1} Nat Int (coeBase.{1, 1} Nat Int Int.hasCoe))) (Int.gcd i j)) j
but is expected to have type
  forall (i : Int) (j : Int), Dvd.dvd.{0} Int Int.instDvdInt (Nat.cast.{0} Int instNatCastInt (Int.gcd i j)) j
Case conversion may be inaccurate. Consider using '#align int.gcd_dvd_right Int.gcd_dvd_rightₓ'. -/
theorem gcd_dvd_right (i j : ℤ) : (gcd i j : ℤ) ∣ j :=
  dvd_natAbs.mp <| coe_nat_dvd.mpr <| Nat.gcd_dvd_right _ _
#align int.gcd_dvd_right Int.gcd_dvd_right

/- warning: int.dvd_gcd -> Int.dvd_gcd is a dubious translation:
lean 3 declaration is
  forall {i : Int} {j : Int} {k : Int}, (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) k i) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) k j) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) k ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat Int (HasLiftT.mk.{1, 1} Nat Int (CoeTCₓ.coe.{1, 1} Nat Int (coeBase.{1, 1} Nat Int Int.hasCoe))) (Int.gcd i j)))
but is expected to have type
  forall {i : Int} {j : Int} {k : Int}, (Dvd.dvd.{0} Int Int.instDvdInt k i) -> (Dvd.dvd.{0} Int Int.instDvdInt k j) -> (Dvd.dvd.{0} Int Int.instDvdInt k (Nat.cast.{0} Int instNatCastInt (Int.gcd i j)))
Case conversion may be inaccurate. Consider using '#align int.dvd_gcd Int.dvd_gcdₓ'. -/
theorem dvd_gcd {i j k : ℤ} (h1 : k ∣ i) (h2 : k ∣ j) : k ∣ gcd i j :=
  natAbs_dvd.1 <| coe_nat_dvd.2 <| Nat.dvd_gcd (natAbs_dvd_natAbs.2 h1) (natAbs_dvd_natAbs.2 h2)
#align int.dvd_gcd Int.dvd_gcd

#print Int.gcd_mul_lcm /-
theorem gcd_mul_lcm (i j : ℤ) : gcd i j * lcm i j = natAbs (i * j) := by
  rw [Int.gcd, Int.lcm, Nat.gcd_mul_lcm, nat_abs_mul]
#align int.gcd_mul_lcm Int.gcd_mul_lcm
-/

#print Int.gcd_comm /-
theorem gcd_comm (i j : ℤ) : gcd i j = gcd j i :=
  Nat.gcd_comm _ _
#align int.gcd_comm Int.gcd_comm
-/

#print Int.gcd_assoc /-
theorem gcd_assoc (i j k : ℤ) : gcd (gcd i j) k = gcd i (gcd j k) :=
  Nat.gcd_assoc _ _ _
#align int.gcd_assoc Int.gcd_assoc
-/

#print Int.gcd_self /-
@[simp]
theorem gcd_self (i : ℤ) : gcd i i = natAbs i := by simp [gcd]
#align int.gcd_self Int.gcd_self
-/

#print Int.gcd_zero_left /-
@[simp]
theorem gcd_zero_left (i : ℤ) : gcd 0 i = natAbs i := by simp [gcd]
#align int.gcd_zero_left Int.gcd_zero_left
-/

#print Int.gcd_zero_right /-
@[simp]
theorem gcd_zero_right (i : ℤ) : gcd i 0 = natAbs i := by simp [gcd]
#align int.gcd_zero_right Int.gcd_zero_right
-/

#print Int.gcd_one_left /-
@[simp]
theorem gcd_one_left (i : ℤ) : gcd 1 i = 1 :=
  Nat.gcd_one_left _
#align int.gcd_one_left Int.gcd_one_left
-/

#print Int.gcd_one_right /-
@[simp]
theorem gcd_one_right (i : ℤ) : gcd i 1 = 1 :=
  Nat.gcd_one_right _
#align int.gcd_one_right Int.gcd_one_right
-/

#print Int.gcd_neg_right /-
@[simp]
theorem gcd_neg_right {x y : ℤ} : gcd x (-y) = gcd x y := by rw [Int.gcd, Int.gcd, nat_abs_neg]
#align int.gcd_neg_right Int.gcd_neg_right
-/

#print Int.gcd_neg_left /-
@[simp]
theorem gcd_neg_left {x y : ℤ} : gcd (-x) y = gcd x y := by rw [Int.gcd, Int.gcd, nat_abs_neg]
#align int.gcd_neg_left Int.gcd_neg_left
-/

#print Int.gcd_mul_left /-
theorem gcd_mul_left (i j k : ℤ) : gcd (i * j) (i * k) = natAbs i * gcd j k :=
  by
  rw [Int.gcd, Int.gcd, nat_abs_mul, nat_abs_mul]
  apply Nat.gcd_mul_left
#align int.gcd_mul_left Int.gcd_mul_left
-/

#print Int.gcd_mul_right /-
theorem gcd_mul_right (i j k : ℤ) : gcd (i * j) (k * j) = gcd i k * natAbs j :=
  by
  rw [Int.gcd, Int.gcd, nat_abs_mul, nat_abs_mul]
  apply Nat.gcd_mul_right
#align int.gcd_mul_right Int.gcd_mul_right
-/

#print Int.gcd_pos_of_non_zero_left /-
theorem gcd_pos_of_non_zero_left {i : ℤ} (j : ℤ) (i_non_zero : i ≠ 0) : 0 < gcd i j :=
  Nat.gcd_pos_of_pos_left (natAbs j) (natAbs_pos_of_ne_zero i_non_zero)
#align int.gcd_pos_of_non_zero_left Int.gcd_pos_of_non_zero_left
-/

#print Int.gcd_pos_of_non_zero_right /-
theorem gcd_pos_of_non_zero_right (i : ℤ) {j : ℤ} (j_non_zero : j ≠ 0) : 0 < gcd i j :=
  Nat.gcd_pos_of_pos_right (natAbs i) (natAbs_pos_of_ne_zero j_non_zero)
#align int.gcd_pos_of_non_zero_right Int.gcd_pos_of_non_zero_right
-/

#print Int.gcd_eq_zero_iff /-
theorem gcd_eq_zero_iff {i j : ℤ} : gcd i j = 0 ↔ i = 0 ∧ j = 0 :=
  by
  rw [Int.gcd]
  constructor
  · intro h
    exact
      ⟨nat_abs_eq_zero.mp (Nat.eq_zero_of_gcd_eq_zero_left h),
        nat_abs_eq_zero.mp (Nat.eq_zero_of_gcd_eq_zero_right h)⟩
  · intro h
    rw [nat_abs_eq_zero.mpr h.left, nat_abs_eq_zero.mpr h.right]
    apply Nat.gcd_zero_left
#align int.gcd_eq_zero_iff Int.gcd_eq_zero_iff
-/

#print Int.gcd_pos_iff /-
theorem gcd_pos_iff {i j : ℤ} : 0 < gcd i j ↔ i ≠ 0 ∨ j ≠ 0 :=
  pos_iff_ne_zero.trans <| gcd_eq_zero_iff.Not.trans not_and_or
#align int.gcd_pos_iff Int.gcd_pos_iff
-/

/- warning: int.gcd_div -> Int.gcd_div is a dubious translation:
lean 3 declaration is
  forall {i : Int} {j : Int} {k : Int}, (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) k i) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) k j) -> (Eq.{1} Nat (Int.gcd (HDiv.hDiv.{0, 0, 0} Int Int Int (instHDiv.{0} Int Int.hasDiv) i k) (HDiv.hDiv.{0, 0, 0} Int Int Int (instHDiv.{0} Int Int.hasDiv) j k)) (HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.hasDiv) (Int.gcd i j) (Int.natAbs k)))
but is expected to have type
  forall {i : Int} {j : Int} {k : Int}, (Dvd.dvd.{0} Int Int.instDvdInt k i) -> (Dvd.dvd.{0} Int Int.instDvdInt k j) -> (Eq.{1} Nat (Int.gcd (HDiv.hDiv.{0, 0, 0} Int Int Int (instHDiv.{0} Int Int.instDivInt_1) i k) (HDiv.hDiv.{0, 0, 0} Int Int Int (instHDiv.{0} Int Int.instDivInt_1) j k)) (HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.instDivNat) (Int.gcd i j) (Int.natAbs k)))
Case conversion may be inaccurate. Consider using '#align int.gcd_div Int.gcd_divₓ'. -/
theorem gcd_div {i j k : ℤ} (H1 : k ∣ i) (H2 : k ∣ j) : gcd (i / k) (j / k) = gcd i j / natAbs k :=
  by
  rw [gcd, nat_abs_div i k H1, nat_abs_div j k H2] <;>
    exact Nat.gcd_div (nat_abs_dvd_iff_dvd.mpr H1) (nat_abs_dvd_iff_dvd.mpr H2)
#align int.gcd_div Int.gcd_div

#print Int.gcd_div_gcd_div_gcd /-
theorem gcd_div_gcd_div_gcd {i j : ℤ} (H : 0 < gcd i j) : gcd (i / gcd i j) (j / gcd i j) = 1 :=
  by
  rw [gcd_div (gcd_dvd_left i j) (gcd_dvd_right i j)]
  rw [nat_abs_of_nat, Nat.div_self H]
#align int.gcd_div_gcd_div_gcd Int.gcd_div_gcd_div_gcd
-/

/- warning: int.gcd_dvd_gcd_of_dvd_left -> Int.gcd_dvd_gcd_of_dvd_left is a dubious translation:
lean 3 declaration is
  forall {i : Int} {k : Int} (j : Int), (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) i k) -> (Dvd.Dvd.{0} Nat Nat.hasDvd (Int.gcd i j) (Int.gcd k j))
but is expected to have type
  forall {i : Int} {k : Int} (j : Int), (Dvd.dvd.{0} Int Int.instDvdInt i k) -> (Dvd.dvd.{0} Nat Nat.instDvdNat (Int.gcd i j) (Int.gcd k j))
Case conversion may be inaccurate. Consider using '#align int.gcd_dvd_gcd_of_dvd_left Int.gcd_dvd_gcd_of_dvd_leftₓ'. -/
theorem gcd_dvd_gcd_of_dvd_left {i k : ℤ} (j : ℤ) (H : i ∣ k) : gcd i j ∣ gcd k j :=
  Int.coe_nat_dvd.1 <| dvd_gcd ((gcd_dvd_left i j).trans H) (gcd_dvd_right i j)
#align int.gcd_dvd_gcd_of_dvd_left Int.gcd_dvd_gcd_of_dvd_left

/- warning: int.gcd_dvd_gcd_of_dvd_right -> Int.gcd_dvd_gcd_of_dvd_right is a dubious translation:
lean 3 declaration is
  forall {i : Int} {k : Int} (j : Int), (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) i k) -> (Dvd.Dvd.{0} Nat Nat.hasDvd (Int.gcd j i) (Int.gcd j k))
but is expected to have type
  forall {i : Int} {k : Int} (j : Int), (Dvd.dvd.{0} Int Int.instDvdInt i k) -> (Dvd.dvd.{0} Nat Nat.instDvdNat (Int.gcd j i) (Int.gcd j k))
Case conversion may be inaccurate. Consider using '#align int.gcd_dvd_gcd_of_dvd_right Int.gcd_dvd_gcd_of_dvd_rightₓ'. -/
theorem gcd_dvd_gcd_of_dvd_right {i k : ℤ} (j : ℤ) (H : i ∣ k) : gcd j i ∣ gcd j k :=
  Int.coe_nat_dvd.1 <| dvd_gcd (gcd_dvd_left j i) ((gcd_dvd_right j i).trans H)
#align int.gcd_dvd_gcd_of_dvd_right Int.gcd_dvd_gcd_of_dvd_right

#print Int.gcd_dvd_gcd_mul_left /-
theorem gcd_dvd_gcd_mul_left (i j k : ℤ) : gcd i j ∣ gcd (k * i) j :=
  gcd_dvd_gcd_of_dvd_left _ (dvd_mul_left _ _)
#align int.gcd_dvd_gcd_mul_left Int.gcd_dvd_gcd_mul_left
-/

#print Int.gcd_dvd_gcd_mul_right /-
theorem gcd_dvd_gcd_mul_right (i j k : ℤ) : gcd i j ∣ gcd (i * k) j :=
  gcd_dvd_gcd_of_dvd_left _ (dvd_mul_right _ _)
#align int.gcd_dvd_gcd_mul_right Int.gcd_dvd_gcd_mul_right
-/

#print Int.gcd_dvd_gcd_mul_left_right /-
theorem gcd_dvd_gcd_mul_left_right (i j k : ℤ) : gcd i j ∣ gcd i (k * j) :=
  gcd_dvd_gcd_of_dvd_right _ (dvd_mul_left _ _)
#align int.gcd_dvd_gcd_mul_left_right Int.gcd_dvd_gcd_mul_left_right
-/

#print Int.gcd_dvd_gcd_mul_right_right /-
theorem gcd_dvd_gcd_mul_right_right (i j k : ℤ) : gcd i j ∣ gcd i (j * k) :=
  gcd_dvd_gcd_of_dvd_right _ (dvd_mul_right _ _)
#align int.gcd_dvd_gcd_mul_right_right Int.gcd_dvd_gcd_mul_right_right
-/

/- warning: int.gcd_eq_left -> Int.gcd_eq_left is a dubious translation:
lean 3 declaration is
  forall {i : Int} {j : Int}, (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) i j) -> (Eq.{1} Nat (Int.gcd i j) (Int.natAbs i))
but is expected to have type
  forall {i : Int} {j : Int}, (Dvd.dvd.{0} Int Int.instDvdInt i j) -> (Eq.{1} Nat (Int.gcd i j) (Int.natAbs i))
Case conversion may be inaccurate. Consider using '#align int.gcd_eq_left Int.gcd_eq_leftₓ'. -/
theorem gcd_eq_left {i j : ℤ} (H : i ∣ j) : gcd i j = natAbs i :=
  Nat.dvd_antisymm (by unfold gcd <;> exact Nat.gcd_dvd_left _ _)
    (by unfold gcd <;> exact Nat.dvd_gcd dvd_rfl (nat_abs_dvd_iff_dvd.mpr H))
#align int.gcd_eq_left Int.gcd_eq_left

/- warning: int.gcd_eq_right -> Int.gcd_eq_right is a dubious translation:
lean 3 declaration is
  forall {i : Int} {j : Int}, (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) j i) -> (Eq.{1} Nat (Int.gcd i j) (Int.natAbs j))
but is expected to have type
  forall {i : Int} {j : Int}, (Dvd.dvd.{0} Int Int.instDvdInt j i) -> (Eq.{1} Nat (Int.gcd i j) (Int.natAbs j))
Case conversion may be inaccurate. Consider using '#align int.gcd_eq_right Int.gcd_eq_rightₓ'. -/
theorem gcd_eq_right {i j : ℤ} (H : j ∣ i) : gcd i j = natAbs j := by rw [gcd_comm, gcd_eq_left H]
#align int.gcd_eq_right Int.gcd_eq_right

#print Int.ne_zero_of_gcd /-
theorem ne_zero_of_gcd {x y : ℤ} (hc : gcd x y ≠ 0) : x ≠ 0 ∨ y ≠ 0 :=
  by
  contrapose! hc
  rw [hc.left, hc.right, gcd_zero_right, nat_abs_zero]
#align int.ne_zero_of_gcd Int.ne_zero_of_gcd
-/

#print Int.exists_gcd_one /-
theorem exists_gcd_one {m n : ℤ} (H : 0 < gcd m n) :
    ∃ m' n' : ℤ, gcd m' n' = 1 ∧ m = m' * gcd m n ∧ n = n' * gcd m n :=
  ⟨_, _, gcd_div_gcd_div_gcd H, (Int.ediv_mul_cancel (gcd_dvd_left m n)).symm,
    (Int.ediv_mul_cancel (gcd_dvd_right m n)).symm⟩
#align int.exists_gcd_one Int.exists_gcd_one
-/

#print Int.exists_gcd_one' /-
theorem exists_gcd_one' {m n : ℤ} (H : 0 < gcd m n) :
    ∃ (g : ℕ)(m' n' : ℤ), 0 < g ∧ gcd m' n' = 1 ∧ m = m' * g ∧ n = n' * g :=
  let ⟨m', n', h⟩ := exists_gcd_one H
  ⟨_, m', n', H, h⟩
#align int.exists_gcd_one' Int.exists_gcd_one'
-/

/- warning: int.pow_dvd_pow_iff -> Int.pow_dvd_pow_iff is a dubious translation:
lean 3 declaration is
  forall {m : Int} {n : Int} {k : Nat}, (LT.lt.{0} Nat Nat.hasLt (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero))) k) -> (Iff (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) (HPow.hPow.{0, 0, 0} Int Nat Int (instHPow.{0, 0} Int Nat (Monoid.Pow.{0} Int Int.monoid)) m k) (HPow.hPow.{0, 0, 0} Int Nat Int (instHPow.{0, 0} Int Nat (Monoid.Pow.{0} Int Int.monoid)) n k)) (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) m n))
but is expected to have type
  forall {m : Int} {n : Int} {k : Nat}, (LT.lt.{0} Nat instLTNat (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)) k) -> (Iff (Dvd.dvd.{0} Int Int.instDvdInt (HPow.hPow.{0, 0, 0} Int Nat Int Int.instHPowIntNat m k) (HPow.hPow.{0, 0, 0} Int Nat Int Int.instHPowIntNat n k)) (Dvd.dvd.{0} Int Int.instDvdInt m n))
Case conversion may be inaccurate. Consider using '#align int.pow_dvd_pow_iff Int.pow_dvd_pow_iffₓ'. -/
theorem pow_dvd_pow_iff {m n : ℤ} {k : ℕ} (k0 : 0 < k) : m ^ k ∣ n ^ k ↔ m ∣ n :=
  by
  refine' ⟨fun h => _, fun h => pow_dvd_pow_of_dvd h _⟩
  apply int.nat_abs_dvd_iff_dvd.mp
  apply (Nat.pow_dvd_pow_iff k0).mp
  rw [← Int.natAbs_pow, ← Int.natAbs_pow]
  exact int.nat_abs_dvd_iff_dvd.mpr h
#align int.pow_dvd_pow_iff Int.pow_dvd_pow_iff

#print Int.gcd_dvd_iff /-
theorem gcd_dvd_iff {a b : ℤ} {n : ℕ} : gcd a b ∣ n ↔ ∃ x y : ℤ, ↑n = a * x + b * y :=
  by
  constructor
  · intro h
    rw [← Nat.mul_div_cancel' h, Int.ofNat_mul, gcd_eq_gcd_ab, add_mul, mul_assoc, mul_assoc]
    refine' ⟨_, _, rfl⟩
  · rintro ⟨x, y, h⟩
    rw [← Int.coe_nat_dvd, h]
    exact
      dvd_add (dvd_mul_of_dvd_left (gcd_dvd_left a b) _) (dvd_mul_of_dvd_left (gcd_dvd_right a b) y)
#align int.gcd_dvd_iff Int.gcd_dvd_iff
-/

/- warning: int.gcd_greatest -> Int.gcd_greatest is a dubious translation:
lean 3 declaration is
  forall {a : Int} {b : Int} {d : Int}, (LE.le.{0} Int Int.hasLe (OfNat.ofNat.{0} Int 0 (OfNat.mk.{0} Int 0 (Zero.zero.{0} Int Int.hasZero))) d) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) d a) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) d b) -> (forall (e : Int), (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) e a) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) e b) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) e d)) -> (Eq.{1} Int d ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat Int (HasLiftT.mk.{1, 1} Nat Int (CoeTCₓ.coe.{1, 1} Nat Int (coeBase.{1, 1} Nat Int Int.hasCoe))) (Int.gcd a b)))
but is expected to have type
  forall {a : Int} {b : Int} {d : Int}, (LE.le.{0} Int Int.instLEInt (OfNat.ofNat.{0} Int 0 (instOfNatInt 0)) d) -> (Dvd.dvd.{0} Int Int.instDvdInt d a) -> (Dvd.dvd.{0} Int Int.instDvdInt d b) -> (forall (e : Int), (Dvd.dvd.{0} Int Int.instDvdInt e a) -> (Dvd.dvd.{0} Int Int.instDvdInt e b) -> (Dvd.dvd.{0} Int Int.instDvdInt e d)) -> (Eq.{1} Int d (Nat.cast.{0} Int instNatCastInt (Int.gcd a b)))
Case conversion may be inaccurate. Consider using '#align int.gcd_greatest Int.gcd_greatestₓ'. -/
theorem gcd_greatest {a b d : ℤ} (hd_pos : 0 ≤ d) (hda : d ∣ a) (hdb : d ∣ b)
    (hd : ∀ e : ℤ, e ∣ a → e ∣ b → e ∣ d) : d = gcd a b :=
  dvd_antisymm hd_pos (ofNat_zero_le (gcd a b)) (dvd_gcd hda hdb)
    (hd _ (gcd_dvd_left a b) (gcd_dvd_right a b))
#align int.gcd_greatest Int.gcd_greatest

/- warning: int.dvd_of_dvd_mul_left_of_gcd_one -> Int.dvd_of_dvd_mul_left_of_gcd_one is a dubious translation:
lean 3 declaration is
  forall {a : Int} {b : Int} {c : Int}, (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) a (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.hasMul) b c)) -> (Eq.{1} Nat (Int.gcd a c) (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) a b)
but is expected to have type
  forall {a : Int} {b : Int} {c : Int}, (Dvd.dvd.{0} Int Int.instDvdInt a (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.instMulInt) b c)) -> (Eq.{1} Nat (Int.gcd a c) (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) -> (Dvd.dvd.{0} Int Int.instDvdInt a b)
Case conversion may be inaccurate. Consider using '#align int.dvd_of_dvd_mul_left_of_gcd_one Int.dvd_of_dvd_mul_left_of_gcd_oneₓ'. -/
/-- Euclid's lemma: if `a ∣ b * c` and `gcd a c = 1` then `a ∣ b`.
Compare with `is_coprime.dvd_of_dvd_mul_left` and
`unique_factorization_monoid.dvd_of_dvd_mul_left_of_no_prime_factors` -/
theorem dvd_of_dvd_mul_left_of_gcd_one {a b c : ℤ} (habc : a ∣ b * c) (hab : gcd a c = 1) : a ∣ b :=
  by
  have := gcd_eq_gcd_ab a c
  simp only [hab, Int.ofNat_zero, Int.ofNat_succ, zero_add] at this
  have : b * a * gcd_a a c + b * c * gcd_b a c = b := by simp [mul_assoc, ← mul_add, ← this]
  rw [← this]
  exact dvd_add (dvd_mul_of_dvd_left (dvd_mul_left a b) _) (dvd_mul_of_dvd_left habc _)
#align int.dvd_of_dvd_mul_left_of_gcd_one Int.dvd_of_dvd_mul_left_of_gcd_one

/- warning: int.dvd_of_dvd_mul_right_of_gcd_one -> Int.dvd_of_dvd_mul_right_of_gcd_one is a dubious translation:
lean 3 declaration is
  forall {a : Int} {b : Int} {c : Int}, (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) a (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.hasMul) b c)) -> (Eq.{1} Nat (Int.gcd a b) (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) a c)
but is expected to have type
  forall {a : Int} {b : Int} {c : Int}, (Dvd.dvd.{0} Int Int.instDvdInt a (HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.instMulInt) b c)) -> (Eq.{1} Nat (Int.gcd a b) (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) -> (Dvd.dvd.{0} Int Int.instDvdInt a c)
Case conversion may be inaccurate. Consider using '#align int.dvd_of_dvd_mul_right_of_gcd_one Int.dvd_of_dvd_mul_right_of_gcd_oneₓ'. -/
/-- Euclid's lemma: if `a ∣ b * c` and `gcd a b = 1` then `a ∣ c`.
Compare with `is_coprime.dvd_of_dvd_mul_right` and
`unique_factorization_monoid.dvd_of_dvd_mul_right_of_no_prime_factors` -/
theorem dvd_of_dvd_mul_right_of_gcd_one {a b c : ℤ} (habc : a ∣ b * c) (hab : gcd a b = 1) :
    a ∣ c := by
  rw [mul_comm] at habc
  exact dvd_of_dvd_mul_left_of_gcd_one habc hab
#align int.dvd_of_dvd_mul_right_of_gcd_one Int.dvd_of_dvd_mul_right_of_gcd_one

#print Int.gcd_least_linear /-
/-- For nonzero integers `a` and `b`, `gcd a b` is the smallest positive natural number that can be
written in the form `a * x + b * y` for some pair of integers `x` and `y` -/
theorem gcd_least_linear {a b : ℤ} (ha : a ≠ 0) :
    IsLeast { n : ℕ | 0 < n ∧ ∃ x y : ℤ, ↑n = a * x + b * y } (a.gcd b) :=
  by
  simp_rw [← gcd_dvd_iff]
  constructor
  · simpa [and_true_iff, dvd_refl, Set.mem_setOf_eq] using gcd_pos_of_non_zero_left b ha
  · simp only [lowerBounds, and_imp, Set.mem_setOf_eq]
    exact fun n hn_pos hn => Nat.le_of_dvd hn_pos hn
#align int.gcd_least_linear Int.gcd_least_linear
-/

/-! ### lcm -/

#print Int.lcm_comm /-
theorem lcm_comm (i j : ℤ) : lcm i j = lcm j i :=
  by
  rw [Int.lcm, Int.lcm]
  exact Nat.lcm_comm _ _
#align int.lcm_comm Int.lcm_comm
-/

#print Int.lcm_assoc /-
theorem lcm_assoc (i j k : ℤ) : lcm (lcm i j) k = lcm i (lcm j k) :=
  by
  rw [Int.lcm, Int.lcm, Int.lcm, Int.lcm, nat_abs_of_nat, nat_abs_of_nat]
  apply Nat.lcm_assoc
#align int.lcm_assoc Int.lcm_assoc
-/

#print Int.lcm_zero_left /-
@[simp]
theorem lcm_zero_left (i : ℤ) : lcm 0 i = 0 :=
  by
  rw [Int.lcm]
  apply Nat.lcm_zero_left
#align int.lcm_zero_left Int.lcm_zero_left
-/

#print Int.lcm_zero_right /-
@[simp]
theorem lcm_zero_right (i : ℤ) : lcm i 0 = 0 :=
  by
  rw [Int.lcm]
  apply Nat.lcm_zero_right
#align int.lcm_zero_right Int.lcm_zero_right
-/

#print Int.lcm_one_left /-
@[simp]
theorem lcm_one_left (i : ℤ) : lcm 1 i = natAbs i :=
  by
  rw [Int.lcm]
  apply Nat.lcm_one_left
#align int.lcm_one_left Int.lcm_one_left
-/

#print Int.lcm_one_right /-
@[simp]
theorem lcm_one_right (i : ℤ) : lcm i 1 = natAbs i :=
  by
  rw [Int.lcm]
  apply Nat.lcm_one_right
#align int.lcm_one_right Int.lcm_one_right
-/

#print Int.lcm_self /-
@[simp]
theorem lcm_self (i : ℤ) : lcm i i = natAbs i :=
  by
  rw [Int.lcm]
  apply Nat.lcm_self
#align int.lcm_self Int.lcm_self
-/

/- warning: int.dvd_lcm_left -> Int.dvd_lcm_left is a dubious translation:
lean 3 declaration is
  forall (i : Int) (j : Int), Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) i ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat Int (HasLiftT.mk.{1, 1} Nat Int (CoeTCₓ.coe.{1, 1} Nat Int (coeBase.{1, 1} Nat Int Int.hasCoe))) (Int.lcm i j))
but is expected to have type
  forall (i : Int) (j : Int), Dvd.dvd.{0} Int Int.instDvdInt i (Nat.cast.{0} Int instNatCastInt (Int.lcm i j))
Case conversion may be inaccurate. Consider using '#align int.dvd_lcm_left Int.dvd_lcm_leftₓ'. -/
theorem dvd_lcm_left (i j : ℤ) : i ∣ lcm i j :=
  by
  rw [Int.lcm]
  apply coe_nat_dvd_right.mpr
  apply Nat.dvd_lcm_left
#align int.dvd_lcm_left Int.dvd_lcm_left

/- warning: int.dvd_lcm_right -> Int.dvd_lcm_right is a dubious translation:
lean 3 declaration is
  forall (i : Int) (j : Int), Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) j ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat Int (HasLiftT.mk.{1, 1} Nat Int (CoeTCₓ.coe.{1, 1} Nat Int (coeBase.{1, 1} Nat Int Int.hasCoe))) (Int.lcm i j))
but is expected to have type
  forall (i : Int) (j : Int), Dvd.dvd.{0} Int Int.instDvdInt j (Nat.cast.{0} Int instNatCastInt (Int.lcm i j))
Case conversion may be inaccurate. Consider using '#align int.dvd_lcm_right Int.dvd_lcm_rightₓ'. -/
theorem dvd_lcm_right (i j : ℤ) : j ∣ lcm i j :=
  by
  rw [Int.lcm]
  apply coe_nat_dvd_right.mpr
  apply Nat.dvd_lcm_right
#align int.dvd_lcm_right Int.dvd_lcm_right

/- warning: int.lcm_dvd -> Int.lcm_dvd is a dubious translation:
lean 3 declaration is
  forall {i : Int} {j : Int} {k : Int}, (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) i k) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) j k) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat Int (HasLiftT.mk.{1, 1} Nat Int (CoeTCₓ.coe.{1, 1} Nat Int (coeBase.{1, 1} Nat Int Int.hasCoe))) (Int.lcm i j)) k)
but is expected to have type
  forall {i : Int} {j : Int} {k : Int}, (Dvd.dvd.{0} Int Int.instDvdInt i k) -> (Dvd.dvd.{0} Int Int.instDvdInt j k) -> (Dvd.dvd.{0} Int Int.instDvdInt (Nat.cast.{0} Int instNatCastInt (Int.lcm i j)) k)
Case conversion may be inaccurate. Consider using '#align int.lcm_dvd Int.lcm_dvdₓ'. -/
theorem lcm_dvd {i j k : ℤ} : i ∣ k → j ∣ k → (lcm i j : ℤ) ∣ k :=
  by
  rw [Int.lcm]
  intro hi hj
  exact coe_nat_dvd_left.mpr (Nat.lcm_dvd (nat_abs_dvd_iff_dvd.mpr hi) (nat_abs_dvd_iff_dvd.mpr hj))
#align int.lcm_dvd Int.lcm_dvd

end Int

/- warning: pow_gcd_eq_one -> pow_gcd_eq_one is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} [_inst_1 : Monoid.{u1} M] (x : M) {m : Nat} {n : Nat}, (Eq.{succ u1} M (HPow.hPow.{u1, 0, u1} M Nat M (instHPow.{u1, 0} M Nat (Monoid.Pow.{u1} M _inst_1)) x m) (OfNat.ofNat.{u1} M 1 (OfNat.mk.{u1} M 1 (One.one.{u1} M (MulOneClass.toHasOne.{u1} M (Monoid.toMulOneClass.{u1} M _inst_1)))))) -> (Eq.{succ u1} M (HPow.hPow.{u1, 0, u1} M Nat M (instHPow.{u1, 0} M Nat (Monoid.Pow.{u1} M _inst_1)) x n) (OfNat.ofNat.{u1} M 1 (OfNat.mk.{u1} M 1 (One.one.{u1} M (MulOneClass.toHasOne.{u1} M (Monoid.toMulOneClass.{u1} M _inst_1)))))) -> (Eq.{succ u1} M (HPow.hPow.{u1, 0, u1} M Nat M (instHPow.{u1, 0} M Nat (Monoid.Pow.{u1} M _inst_1)) x (Nat.gcd m n)) (OfNat.ofNat.{u1} M 1 (OfNat.mk.{u1} M 1 (One.one.{u1} M (MulOneClass.toHasOne.{u1} M (Monoid.toMulOneClass.{u1} M _inst_1))))))
but is expected to have type
  forall {M : Type.{u1}} [_inst_1 : Monoid.{u1} M] (x : M) {m : Nat} {n : Nat}, (Eq.{succ u1} M (HPow.hPow.{u1, 0, u1} M Nat M (instHPow.{u1, 0} M Nat (Monoid.Pow.{u1} M _inst_1)) x m) (OfNat.ofNat.{u1} M 1 (One.toOfNat1.{u1} M (Monoid.toOne.{u1} M _inst_1)))) -> (Eq.{succ u1} M (HPow.hPow.{u1, 0, u1} M Nat M (instHPow.{u1, 0} M Nat (Monoid.Pow.{u1} M _inst_1)) x n) (OfNat.ofNat.{u1} M 1 (One.toOfNat1.{u1} M (Monoid.toOne.{u1} M _inst_1)))) -> (Eq.{succ u1} M (HPow.hPow.{u1, 0, u1} M Nat M (instHPow.{u1, 0} M Nat (Monoid.Pow.{u1} M _inst_1)) x (Nat.gcd m n)) (OfNat.ofNat.{u1} M 1 (One.toOfNat1.{u1} M (Monoid.toOne.{u1} M _inst_1))))
Case conversion may be inaccurate. Consider using '#align pow_gcd_eq_one pow_gcd_eq_oneₓ'. -/
theorem pow_gcd_eq_one {M : Type _} [Monoid M] (x : M) {m n : ℕ} (hm : x ^ m = 1) (hn : x ^ n = 1) :
    x ^ m.gcd n = 1 := by
  cases m; · simp only [hn, Nat.gcd_zero_left]
  lift x to Mˣ using isUnit_ofPowEqOne hm m.succ_ne_zero
  simp only [← Units.val_pow_eq_pow_val] at *
  rw [← Units.val_one, ← zpow_ofNat, ← Units.ext_iff] at *
  simp only [Nat.gcd_eq_gcd_ab, zpow_add, zpow_mul, hm, hn, one_zpow, one_mul]
#align pow_gcd_eq_one pow_gcd_eq_one

/- warning: gcd_nsmul_eq_zero -> gcd_nsmul_eq_zero is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} [_inst_1 : AddMonoid.{u1} M] (x : M) {m : Nat} {n : Nat}, (Eq.{succ u1} M (SMul.smul.{0, u1} Nat M (AddMonoid.SMul.{u1} M _inst_1) m x) (OfNat.ofNat.{u1} M 0 (OfNat.mk.{u1} M 0 (Zero.zero.{u1} M (AddZeroClass.toHasZero.{u1} M (AddMonoid.toAddZeroClass.{u1} M _inst_1)))))) -> (Eq.{succ u1} M (SMul.smul.{0, u1} Nat M (AddMonoid.SMul.{u1} M _inst_1) n x) (OfNat.ofNat.{u1} M 0 (OfNat.mk.{u1} M 0 (Zero.zero.{u1} M (AddZeroClass.toHasZero.{u1} M (AddMonoid.toAddZeroClass.{u1} M _inst_1)))))) -> (Eq.{succ u1} M (SMul.smul.{0, u1} Nat M (AddMonoid.SMul.{u1} M _inst_1) (Nat.gcd m n) x) (OfNat.ofNat.{u1} M 0 (OfNat.mk.{u1} M 0 (Zero.zero.{u1} M (AddZeroClass.toHasZero.{u1} M (AddMonoid.toAddZeroClass.{u1} M _inst_1))))))
but is expected to have type
  forall {M : Type.{u1}} [_inst_1 : AddMonoid.{u1} M] (x : M) {m : Nat} {n : Nat}, (Eq.{succ u1} M (HSMul.hSMul.{0, u1, u1} Nat M M (instHSMul.{0, u1} Nat M (AddMonoid.SMul.{u1} M _inst_1)) m x) (OfNat.ofNat.{u1} M 0 (Zero.toOfNat0.{u1} M (AddMonoid.toZero.{u1} M _inst_1)))) -> (Eq.{succ u1} M (HSMul.hSMul.{0, u1, u1} Nat M M (instHSMul.{0, u1} Nat M (AddMonoid.SMul.{u1} M _inst_1)) n x) (OfNat.ofNat.{u1} M 0 (Zero.toOfNat0.{u1} M (AddMonoid.toZero.{u1} M _inst_1)))) -> (Eq.{succ u1} M (HSMul.hSMul.{0, u1, u1} Nat M M (instHSMul.{0, u1} Nat M (AddMonoid.SMul.{u1} M _inst_1)) (Nat.gcd m n) x) (OfNat.ofNat.{u1} M 0 (Zero.toOfNat0.{u1} M (AddMonoid.toZero.{u1} M _inst_1))))
Case conversion may be inaccurate. Consider using '#align gcd_nsmul_eq_zero gcd_nsmul_eq_zeroₓ'. -/
theorem gcd_nsmul_eq_zero {M : Type _} [AddMonoid M] (x : M) {m n : ℕ} (hm : m • x = 0)
    (hn : n • x = 0) : m.gcd n • x = 0 :=
  by
  apply multiplicative.of_add.injective
  rw [ofAdd_nsmul, ofAdd_zero, pow_gcd_eq_one] <;>
    rwa [← ofAdd_nsmul, ← ofAdd_zero, Equiv.apply_eq_iff_eq]
#align gcd_nsmul_eq_zero gcd_nsmul_eq_zero

attribute [to_additive gcd_nsmul_eq_zero] pow_gcd_eq_one

/-! ### GCD prover -/

open NormNum

namespace Tactic

namespace NormNum

theorem int_gcd_helper' {d : ℕ} {x y a b : ℤ} (h₁ : (d : ℤ) ∣ x) (h₂ : (d : ℤ) ∣ y)
    (h₃ : x * a + y * b = d) : Int.gcd x y = d :=
  by
  refine' Nat.dvd_antisymm _ (Int.coe_nat_dvd.1 (Int.dvd_gcd h₁ h₂))
  rw [← Int.coe_nat_dvd, ← h₃]
  apply dvd_add
  · exact (Int.gcd_dvd_left _ _).mul_right _
  · exact (Int.gcd_dvd_right _ _).mul_right _
#align tactic.norm_num.int_gcd_helper' Tactic.NormNum.int_gcd_helper'

theorem nat_gcd_helper_dvd_left (x y a : ℕ) (h : x * a = y) : Nat.gcd x y = x :=
  Nat.gcd_eq_left ⟨a, h.symm⟩
#align tactic.norm_num.nat_gcd_helper_dvd_left Tactic.NormNum.nat_gcd_helper_dvd_left

theorem nat_gcd_helper_dvd_right (x y a : ℕ) (h : y * a = x) : Nat.gcd x y = y :=
  Nat.gcd_eq_right ⟨a, h.symm⟩
#align tactic.norm_num.nat_gcd_helper_dvd_right Tactic.NormNum.nat_gcd_helper_dvd_right

theorem nat_gcd_helper_2 (d x y a b u v tx ty : ℕ) (hu : d * u = x) (hv : d * v = y)
    (hx : x * a = tx) (hy : y * b = ty) (h : ty + d = tx) : Nat.gcd x y = d :=
  by
  rw [← Int.coe_nat_gcd];
  apply
    @int_gcd_helper' _ _ _ a (-b) (Int.coe_nat_dvd.2 ⟨_, hu.symm⟩) (Int.coe_nat_dvd.2 ⟨_, hv.symm⟩)
  rw [mul_neg, ← sub_eq_add_neg, sub_eq_iff_eq_add']
  norm_cast; rw [hx, hy, h]
#align tactic.norm_num.nat_gcd_helper_2 Tactic.NormNum.nat_gcd_helper_2

theorem nat_gcd_helper_1 (d x y a b u v tx ty : ℕ) (hu : d * u = x) (hv : d * v = y)
    (hx : x * a = tx) (hy : y * b = ty) (h : tx + d = ty) : Nat.gcd x y = d :=
  (Nat.gcd_comm _ _).trans <| nat_gcd_helper_2 _ _ _ _ _ _ _ _ _ hv hu hy hx h
#align tactic.norm_num.nat_gcd_helper_1 Tactic.NormNum.nat_gcd_helper_1

theorem nat_lcm_helper (x y d m n : ℕ) (hd : Nat.gcd x y = d) (d0 : 0 < d) (xy : x * y = n)
    (dm : d * m = n) : Nat.lcm x y = m :=
  mul_right_injective₀ d0.ne' <| by rw [dm, ← xy, ← hd, Nat.gcd_mul_lcm]
#align tactic.norm_num.nat_lcm_helper Tactic.NormNum.nat_lcm_helper

theorem nat_coprime_helper_zero_left (x : ℕ) (h : 1 < x) : ¬Nat.coprime 0 x :=
  mt (Nat.coprime_zero_left _).1 <| ne_of_gt h
#align tactic.norm_num.nat_coprime_helper_zero_left Tactic.NormNum.nat_coprime_helper_zero_left

theorem nat_coprime_helper_zero_right (x : ℕ) (h : 1 < x) : ¬Nat.coprime x 0 :=
  mt (Nat.coprime_zero_right _).1 <| ne_of_gt h
#align tactic.norm_num.nat_coprime_helper_zero_right Tactic.NormNum.nat_coprime_helper_zero_right

theorem nat_coprime_helper_1 (x y a b tx ty : ℕ) (hx : x * a = tx) (hy : y * b = ty)
    (h : tx + 1 = ty) : Nat.coprime x y :=
  nat_gcd_helper_1 _ _ _ _ _ _ _ _ _ (one_mul _) (one_mul _) hx hy h
#align tactic.norm_num.nat_coprime_helper_1 Tactic.NormNum.nat_coprime_helper_1

theorem nat_coprime_helper_2 (x y a b tx ty : ℕ) (hx : x * a = tx) (hy : y * b = ty)
    (h : ty + 1 = tx) : Nat.coprime x y :=
  nat_gcd_helper_2 _ _ _ _ _ _ _ _ _ (one_mul _) (one_mul _) hx hy h
#align tactic.norm_num.nat_coprime_helper_2 Tactic.NormNum.nat_coprime_helper_2

theorem nat_not_coprime_helper (d x y u v : ℕ) (hu : d * u = x) (hv : d * v = y) (h : 1 < d) :
    ¬Nat.coprime x y :=
  Nat.not_coprime_of_dvd_of_dvd h ⟨_, hu.symm⟩ ⟨_, hv.symm⟩
#align tactic.norm_num.nat_not_coprime_helper Tactic.NormNum.nat_not_coprime_helper

theorem int_gcd_helper (x y : ℤ) (nx ny d : ℕ) (hx : (nx : ℤ) = x) (hy : (ny : ℤ) = y)
    (h : Nat.gcd nx ny = d) : Int.gcd x y = d := by rwa [← hx, ← hy, Int.coe_nat_gcd]
#align tactic.norm_num.int_gcd_helper Tactic.NormNum.int_gcd_helper

theorem int_gcd_helper_neg_left (x y : ℤ) (d : ℕ) (h : Int.gcd x y = d) : Int.gcd (-x) y = d := by
  rw [Int.gcd] at h⊢ <;> rwa [Int.natAbs_neg]
#align tactic.norm_num.int_gcd_helper_neg_left Tactic.NormNum.int_gcd_helper_neg_left

theorem int_gcd_helper_neg_right (x y : ℤ) (d : ℕ) (h : Int.gcd x y = d) : Int.gcd x (-y) = d := by
  rw [Int.gcd] at h⊢ <;> rwa [Int.natAbs_neg]
#align tactic.norm_num.int_gcd_helper_neg_right Tactic.NormNum.int_gcd_helper_neg_right

theorem int_lcm_helper (x y : ℤ) (nx ny d : ℕ) (hx : (nx : ℤ) = x) (hy : (ny : ℤ) = y)
    (h : Nat.lcm nx ny = d) : Int.lcm x y = d := by rwa [← hx, ← hy, Int.coe_nat_lcm]
#align tactic.norm_num.int_lcm_helper Tactic.NormNum.int_lcm_helper

theorem int_lcm_helper_neg_left (x y : ℤ) (d : ℕ) (h : Int.lcm x y = d) : Int.lcm (-x) y = d := by
  rw [Int.lcm] at h⊢ <;> rwa [Int.natAbs_neg]
#align tactic.norm_num.int_lcm_helper_neg_left Tactic.NormNum.int_lcm_helper_neg_left

theorem int_lcm_helper_neg_right (x y : ℤ) (d : ℕ) (h : Int.lcm x y = d) : Int.lcm x (-y) = d := by
  rw [Int.lcm] at h⊢ <;> rwa [Int.natAbs_neg]
#align tactic.norm_num.int_lcm_helper_neg_right Tactic.NormNum.int_lcm_helper_neg_right

/-- Evaluates the `nat.gcd` function. -/
unsafe def prove_gcd_nat (c : instance_cache) (ex ey : expr) :
    tactic (instance_cache × expr × expr) := do
  let x ← ex.toNat
  let y ← ey.toNat
  match x, y with
    | 0, _ => pure (c, ey, q(Nat.gcd_zero_left).mk_app [ey])
    | _, 0 => pure (c, ex, q(Nat.gcd_zero_right).mk_app [ex])
    | 1, _ => pure (c, q((1 : ℕ)), q(Nat.gcd_one_left).mk_app [ey])
    | _, 1 => pure (c, q((1 : ℕ)), q(Nat.gcd_one_right).mk_app [ex])
    | _, _ => do
      let (d, a, b) := Nat.xgcdAux x 1 0 y 0 1
      if d = x then do
          let (c, ea) ← c (y / x)
          let (c, _, p) ← prove_mul_nat c ex ea
          pure (c, ex, q(nat_gcd_helper_dvd_left).mk_app [ex, ey, ea, p])
        else
          if d = y then do
            let (c, ea) ← c (x / y)
            let (c, _, p) ← prove_mul_nat c ey ea
            pure (c, ey, q(nat_gcd_helper_dvd_right).mk_app [ex, ey, ea, p])
          else do
            let (c, ed) ← c d
            let (c, ea) ← c a
            let (c, eb) ← c b
            let (c, eu) ← c (x / d)
            let (c, ev) ← c (y / d)
            let (c, _, pu) ← prove_mul_nat c ed eu
            let (c, _, pv) ← prove_mul_nat c ed ev
            let (c, etx, px) ← prove_mul_nat c ex ea
            let (c, ety, py) ← prove_mul_nat c ey eb
            let (c, p) ← if a ≥ 0 then prove_add_nat c ety ed etx else prove_add_nat c etx ed ety
            let pf : expr := if a ≥ 0 then q(nat_gcd_helper_2) else q(nat_gcd_helper_1)
            pure (c, ed, pf [ed, ex, ey, ea, eb, eu, ev, etx, ety, pu, pv, px, py, p])
#align tactic.norm_num.prove_gcd_nat tactic.norm_num.prove_gcd_nat

/-- Evaluates the `nat.lcm` function. -/
unsafe def prove_lcm_nat (c : instance_cache) (ex ey : expr) :
    tactic (instance_cache × expr × expr) := do
  let x ← ex.toNat
  let y ← ey.toNat
  match x, y with
    | 0, _ => pure (c, q((0 : ℕ)), q(Nat.lcm_zero_left).mk_app [ey])
    | _, 0 => pure (c, q((0 : ℕ)), q(Nat.lcm_zero_right).mk_app [ex])
    | 1, _ => pure (c, ey, q(Nat.lcm_one_left).mk_app [ey])
    | _, 1 => pure (c, ex, q(Nat.lcm_one_right).mk_app [ex])
    | _, _ => do
      let (c, ed, pd) ← prove_gcd_nat c ex ey
      let (c, p0) ← prove_pos c ed
      let (c, en, xy) ← prove_mul_nat c ex ey
      let d ← ed
      let (c, em) ← c (x * y / d)
      let (c, _, dm) ← prove_mul_nat c ed em
      pure (c, em, q(nat_lcm_helper).mk_app [ex, ey, ed, em, en, pd, p0, xy, dm])
#align tactic.norm_num.prove_lcm_nat tactic.norm_num.prove_lcm_nat

/-- Evaluates the `int.gcd` function. -/
unsafe def prove_gcd_int (zc nc : instance_cache) :
    expr → expr → tactic (instance_cache × instance_cache × expr × expr)
  | x, y =>
    match match_neg x with
    | some x => do
      let (zc, nc, d, p) ← prove_gcd_int x y
      pure (zc, nc, d, q(int_gcd_helper_neg_left).mk_app [x, y, d, p])
    | none =>
      match match_neg y with
      | some y => do
        let (zc, nc, d, p) ← prove_gcd_int x y
        pure (zc, nc, d, q(int_gcd_helper_neg_right).mk_app [x, y, d, p])
      | none => do
        let (zc, nc, nx, px) ← prove_nat_uncast zc nc x
        let (zc, nc, ny, py) ← prove_nat_uncast zc nc y
        let (nc, d, p) ← prove_gcd_nat nc nx ny
        pure (zc, nc, d, q(int_gcd_helper).mk_app [x, y, nx, ny, d, px, py, p])
#align tactic.norm_num.prove_gcd_int tactic.norm_num.prove_gcd_int

/-- Evaluates the `int.lcm` function. -/
unsafe def prove_lcm_int (zc nc : instance_cache) :
    expr → expr → tactic (instance_cache × instance_cache × expr × expr)
  | x, y =>
    match match_neg x with
    | some x => do
      let (zc, nc, d, p) ← prove_lcm_int x y
      pure (zc, nc, d, q(int_lcm_helper_neg_left).mk_app [x, y, d, p])
    | none =>
      match match_neg y with
      | some y => do
        let (zc, nc, d, p) ← prove_lcm_int x y
        pure (zc, nc, d, q(int_lcm_helper_neg_right).mk_app [x, y, d, p])
      | none => do
        let (zc, nc, nx, px) ← prove_nat_uncast zc nc x
        let (zc, nc, ny, py) ← prove_nat_uncast zc nc y
        let (nc, d, p) ← prove_lcm_nat nc nx ny
        pure (zc, nc, d, q(int_lcm_helper).mk_app [x, y, nx, ny, d, px, py, p])
#align tactic.norm_num.prove_lcm_int tactic.norm_num.prove_lcm_int

/-- Evaluates the `nat.coprime` function. -/
unsafe def prove_coprime_nat (c : instance_cache) (ex ey : expr) :
    tactic (instance_cache × Sum expr expr) := do
  let x ← ex.toNat
  let y ← ey.toNat
  match x, y with
    | 1, _ => pure (c, Sum.inl <| q(Nat.coprime_one_left).mk_app [ey])
    | _, 1 => pure (c, Sum.inl <| q(Nat.coprime_one_right).mk_app [ex])
    | 0, 0 => pure (c, Sum.inr q(Nat.not_coprime_zero_zero))
    | 0, _ => do
      let c ← mk_instance_cache q(ℕ)
      let (c, p) ← prove_lt_nat c q(1) ey
      pure (c, Sum.inr <| q(nat_coprime_helper_zero_left).mk_app [ey, p])
    | _, 0 => do
      let c ← mk_instance_cache q(ℕ)
      let (c, p) ← prove_lt_nat c q(1) ex
      pure (c, Sum.inr <| q(nat_coprime_helper_zero_right).mk_app [ex, p])
    | _, _ => do
      let c ← mk_instance_cache q(ℕ)
      let (d, a, b) := Nat.xgcdAux x 1 0 y 0 1
      if d = 1 then do
          let (c, ea) ← c a
          let (c, eb) ← c b
          let (c, etx, px) ← prove_mul_nat c ex ea
          let (c, ety, py) ← prove_mul_nat c ey eb
          let (c, p) ← if a ≥ 0 then prove_add_nat c ety q(1) etx else prove_add_nat c etx q(1) ety
          let pf : expr := if a ≥ 0 then q(nat_coprime_helper_2) else q(nat_coprime_helper_1)
          pure (c, Sum.inl <| pf [ex, ey, ea, eb, etx, ety, px, py, p])
        else do
          let (c, ed) ← c d
          let (c, eu) ← c (x / d)
          let (c, ev) ← c (y / d)
          let (c, _, pu) ← prove_mul_nat c ed eu
          let (c, _, pv) ← prove_mul_nat c ed ev
          let (c, p) ← prove_lt_nat c q(1) ed
          pure (c, Sum.inr <| q(nat_not_coprime_helper).mk_app [ed, ex, ey, eu, ev, pu, pv, p])
#align tactic.norm_num.prove_coprime_nat tactic.norm_num.prove_coprime_nat

/-- Evaluates the `gcd`, `lcm`, and `coprime` functions. -/
@[norm_num]
unsafe def eval_gcd : expr → tactic (expr × expr)
  | q(Nat.gcd $(ex) $(ey)) => do
    let c ← mk_instance_cache q(ℕ)
    Prod.snd <$> prove_gcd_nat c ex ey
  | q(Nat.lcm $(ex) $(ey)) => do
    let c ← mk_instance_cache q(ℕ)
    Prod.snd <$> prove_lcm_nat c ex ey
  | q(Nat.coprime $(ex) $(ey)) => do
    let c ← mk_instance_cache q(ℕ)
    prove_coprime_nat c ex ey >>= Sum.elim true_intro false_intro ∘ Prod.snd
  | q(Int.gcd $(ex) $(ey)) => do
    let zc ← mk_instance_cache q(ℤ)
    let nc ← mk_instance_cache q(ℕ)
    (Prod.snd ∘ Prod.snd) <$> prove_gcd_int zc nc ex ey
  | q(Int.lcm $(ex) $(ey)) => do
    let zc ← mk_instance_cache q(ℤ)
    let nc ← mk_instance_cache q(ℕ)
    (Prod.snd ∘ Prod.snd) <$> prove_lcm_int zc nc ex ey
  | _ => failed
#align tactic.norm_num.eval_gcd tactic.norm_num.eval_gcd

end NormNum

end Tactic


/-
Copyright (c) 2019 Zhouhang Zhou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Zhouhang Zhou
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebra.invertible
import Mathlib.tactic.linarith.default
import Mathlib.order.filter.at_top_bot
import Mathlib.PostPort

universes u_1

namespace Mathlib

/-!
# Quadratic discriminants and roots of a quadratic

This file defines the discriminant of a quadratic and gives the solution to a quadratic equation.

## Main definition

- `discrim a b c`: the discriminant of a quadratic `a * x * x + b * x + c` is `b * b - 4 * a * c`.

## Main statements

- `quadratic_eq_zero_iff`: roots of a quadratic can be written as
  `(-b + s) / (2 * a)` or `(-b - s) / (2 * a)`, where `s` is a square root of the discriminant.
- `quadratic_ne_zero_of_discrim_ne_square`: if the discriminant has no square root,
  then the corresponding quadratic has no root.
- `discrim_le_zero`: if a quadratic is always non-negative, then its discriminant is non-positive.

## Tags

polynomial, quadratic, discriminant, root
-/

/-- Discriminant of a quadratic -/
def discrim {R : Type u_1} [ring R] (a : R) (b : R) (c : R) : R :=
  b ^ bit0 1 - bit0 (bit0 1) * a * c

/--
A quadratic has roots if and only if its discriminant equals some square.
-/
theorem quadratic_eq_zero_iff_discrim_eq_square {R : Type u_1} [integral_domain R] {a : R} {b : R}
    {c : R} (h2 : bit0 1 ≠ 0) (ha : a ≠ 0) (x : R) :
    a * x * x + b * x + c = 0 ↔ discrim a b c = (bit0 1 * a * x + b) ^ bit0 1 :=
  sorry

/-- A quadratic has no root if its discriminant has no square root. -/
theorem quadratic_ne_zero_of_discrim_ne_square {R : Type u_1} [integral_domain R] {a : R} {b : R}
    {c : R} (h2 : bit0 1 ≠ 0) (ha : a ≠ 0) (h : ∀ (s : R), discrim a b c ≠ s * s) (x : R) :
    a * x * x + b * x + c ≠ 0 :=
  sorry

/-- Roots of a quadratic -/
theorem quadratic_eq_zero_iff {K : Type u_1} [field K] [invertible (bit0 1)] {a : K} {b : K} {c : K}
    (ha : a ≠ 0) {s : K} (h : discrim a b c = s * s) (x : K) :
    a * x * x + b * x + c = 0 ↔ x = (-b + s) / (bit0 1 * a) ∨ x = (-b - s) / (bit0 1 * a) :=
  sorry

/-- A quadratic has roots if its discriminant has square roots -/
theorem exists_quadratic_eq_zero {K : Type u_1} [field K] [invertible (bit0 1)] {a : K} {b : K}
    {c : K} (ha : a ≠ 0) (h : ∃ (s : K), discrim a b c = s * s) :
    ∃ (x : K), a * x * x + b * x + c = 0 :=
  sorry

/-- Root of a quadratic when its discriminant equals zero -/
theorem quadratic_eq_zero_iff_of_discrim_eq_zero {K : Type u_1} [field K] [invertible (bit0 1)]
    {a : K} {b : K} {c : K} (ha : a ≠ 0) (h : discrim a b c = 0) (x : K) :
    a * x * x + b * x + c = 0 ↔ x = -b / (bit0 1 * a) :=
  sorry

/-- If a polynomial of degree 2 is always nonnegative, then its discriminant is nonpositive -/
theorem discrim_le_zero {K : Type u_1} [linear_ordered_field K] {a : K} {b : K} {c : K}
    (h : ∀ (x : K), 0 ≤ a * x * x + b * x + c) : discrim a b c ≤ 0 :=
  sorry

/--
If a polynomial of degree 2 is always positive, then its discriminant is negative,
at least when the coefficient of the quadratic term is nonzero.
-/
theorem discrim_lt_zero {K : Type u_1} [linear_ordered_field K] {a : K} {b : K} {c : K} (ha : a ≠ 0)
    (h : ∀ (x : K), 0 < a * x * x + b * x + c) : discrim a b c < 0 :=
  sorry

end Mathlib


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

import data.nat.digits

/-!
# IMO 1962 Q1

Find the smallest natural number $n$ which has the following properties:

(a) Its decimal representation has 6 as the last digit.

(b) If the last digit 6 is erased and placed in front of the remaining digits,
the resulting number is four times as large as the original number $n$.

Since Lean does not explicitly express problems of the form "find the smallest number satisfying X",
we define the problem as a predicate, and then prove a particular number is the smallest member
of a set satisfying it.
-/

open nat

def problem_predicate (n : ℕ) : Prop :=
(digits 10 n).head = 6 ∧ of_digits 10 ((digits 10 n).tail.concat 6) = 4 * n

/-!
First, it's inconvenient to work with digits, so let's simplify them out of the problem.
-/

abbreviation problem_predicate' (c n : ℕ) : Prop :=
n = 10 * c + 6 ∧ 6 * 10 ^ (digits 10 c).length + c = 4 * n

lemma without_digits {n : ℕ} (h1 : problem_predicate n) :
∃ c : ℕ, problem_predicate' c n :=
begin
  use n / 10,
  cases n,
  { have h2 : ¬ problem_predicate 0, by norm_num [problem_predicate],
    contradiction },
  { rw [problem_predicate, digits_def' (dec_trivial : 2 ≤ 10) n.succ_pos,
        list.head, list.tail_cons, list.concat_eq_append] at h1,
    split,
    { rw [← h1.left, div_add_mod (n+1) 10], },
    { rw [← h1.right, of_digits_append, of_digits_digits,
          of_digits_singleton, add_comm, mul_comm], }, },
end

/-!
Now we can eliminate possibilities for `(digits 10 c).length` until we get to the one that works.
-/

lemma case_0_digit {c n : ℕ} (h1 : (digits 10 c).length = 0) : ¬ problem_predicate' c n :=
begin
  intro h2,
  have h3 : 6 * 10 ^ 0 + c = 6 * 10 ^ (digits 10 c).length + c, by rw h1,
  have h4 : 6 * 10 ^ 0 + c = 4 * (10 * c + 6), by rw [h3, h2.right, h2.left],
  linarith,
end

lemma case_1_digit {c n : ℕ} (h1 : (digits 10 c).length = 1) : ¬ problem_predicate' c n :=
begin
  intro h2,
  have h3 : 6 * 10 ^ 1 + c = 6 * 10 ^ (digits 10 c).length + c, by rw h1,
  have h4 : 6 * 10 ^ 1 + c = 4 * (10 * c + 6), by rw [h3, h2.right, h2.left],
  have h6 : c > 0, by linarith,
  linarith,
end

lemma case_2_digit {c n : ℕ} (h1 : (digits 10 c).length = 2) : ¬ problem_predicate' c n :=
begin
  intro h2,
  have h3 : 6 * 10 ^ 2 + c = 6 * 10 ^ (digits 10 c).length + c, by rw h1,
  have h4 : 6 * 10 ^ 2 + c = 4 * (10 * c + 6), by rw [h3, h2.right, h2.left],
  have h5 : c > 14, by linarith,
  linarith
end

lemma case_3_digit {c n : ℕ} (h1 : (digits 10 c).length = 3) : ¬ problem_predicate' c n :=
begin
  intro h2,
  have h3 : 6 * 10 ^ 3 + c = 6 * 10 ^ (digits 10 c).length + c, by rw h1,
  have h4 : 6 * 10 ^ 3 + c = 4 * (10 * c + 6), by rw [h3, h2.right, h2.left],
  have h5 : c > 153, by linarith,
  linarith
end

lemma case_4_digit {c n : ℕ} (h1 : (digits 10 c).length = 4) : ¬ problem_predicate' c n :=
begin
  intro h2,
  have h3 : 6 * 10 ^ 4 + c = 6 * 10 ^ (digits 10 c).length + c, by rw h1,
  have h4 : 6 * 10 ^ 4 + c = 4 * (10 * c + 6), by rw [h3, h2.right, h2.left],
  have h5 : c > 1537, by linarith,
  linarith
end

/-- Putting this inline causes a deep recursion error, so we separate it out. -/
lemma helper_5_digit {c : ℤ} (h : 6 * 10 ^ 5 + c = 4 * (10 * c + 6)) : c = 15384 := by linarith

lemma case_5_digit {c n : ℕ} (h1 : (digits 10 c).length = 5) (h2 : problem_predicate' c n) :
  c = 15384 :=
begin
  have h3 : 6 * 10 ^ 5 + c = 6 * 10 ^ (digits 10 c).length + c, by rw h1,
  have h4 : 6 * 10 ^ 5 + c = 4 * (10 * c + 6), by rw [h3, h2.right, h2.left],
  zify at *,
  exact helper_5_digit h4
end

/--
`linarith` fails on numbers this large, so this lemma spells out some of the arithmetic
that normally would be automated.
-/
lemma case_more_digits {c n : ℕ} (h1 : (digits 10 c).length ≥ 6) (h2 : problem_predicate' c n) :
  n ≥ 153846 :=
begin
  have h3 : c ≠ 0,
  { intro h4,
    have h5 : (digits 10 c).length = 0, by simp [h4],
    exact case_0_digit h5 h2 },
  have h6 : 2 ≤ 10, from dec_trivial,
  calc
    n ≥ 10 * c                    : le.intro h2.left.symm
  ... ≥ 10 ^ (digits 10 c).length : base_pow_length_digits_le 10 c h6 h3
  ... ≥ 10 ^ 6                    : (pow_le_iff_le_right h6).mpr h1
  ... ≥ 153846                    : by norm_num,
end

/-!
Now we combine these cases to show that 153846 is the smallest solution.
-/

lemma satisfied_by_153846 : problem_predicate 153846 :=
by norm_num [problem_predicate, digits_def', of_digits]

lemma no_smaller_solutions (n : ℕ) (h1 : problem_predicate n) : n ≥ 153846 :=
begin
  cases without_digits h1 with c h2,
  have h3 : (digits 10 c).length < 6 ∨ (digits 10 c).length ≥ 6, by apply lt_or_ge,
  cases h3,
  case or.inr
  { exact case_more_digits h3 h2, },
  case or.inl
  { interval_cases (digits 10 c).length with h,
    { exfalso, exact case_0_digit h h2 },
    { exfalso, exact case_1_digit h h2 },
    { exfalso, exact case_2_digit h h2 },
    { exfalso, exact case_3_digit h h2 },
    { exfalso, exact case_4_digit h h2 },
    { have h4 : c = 15384, from case_5_digit h h2,
      have h5 : n = 10 * 15384 + 6, from h4 ▸ h2.left,
      norm_num at h5,
      exact h5.ge, }, },
end

theorem imo1962_q1 : is_least {n | problem_predicate n} 153846 :=
⟨satisfied_by_153846, no_smaller_solutions⟩