/-
Copyright (c) 2022 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Coe
import Init.Classical
import Init.SimpLemmas
import Init.Data.Nat.Basic
import Init.Data.List.Basic
import Init.Data.Prod

namespace Nat.Linear

/-!
  Helper definitions and theorems for constructing linear arithmetic proofs.
-/

abbrev Var := Nat

abbrev Context := List Nat

/--
  When encoding polynomials. We use `fixedVar` for encoding numerals.
  The denotation of `fixedVar` is always `1`. -/
def fixedVar := 100000000 -- Any big number should work here

def Var.denote (ctx : Context) (v : Var) : Nat :=
  bif v == fixedVar then 1 else go ctx v
where
  go : List Nat → Nat → Nat
   | [],    _   => 0
   | a::_,  0   => a
   | _::as, i+1 => go as i

inductive Expr where
  | num  (v : Nat)
  | var  (i : Var)
  | add  (a b : Expr)
  | mulL (k : Nat) (a : Expr)
  | mulR (a : Expr) (k : Nat)
  deriving Inhabited

def Expr.denote (ctx : Context) : Expr → Nat
  | Expr.add a b  => Nat.add (denote ctx a) (denote ctx b)
  | Expr.num k    => k
  | Expr.var v    => v.denote ctx
  | Expr.mulL k e => Nat.mul k (denote ctx e)
  | Expr.mulR e k => Nat.mul (denote ctx e) k

abbrev Poly := List (Nat × Var)

def Poly.denote (ctx : Context) (p : Poly) : Nat :=
  match p with
  | [] => 0
  | (k, v) :: p => Nat.add (Nat.mul k (v.denote ctx)) (denote ctx p)

def Poly.insertSorted (k : Nat) (v : Var) (p : Poly) : Poly :=
  match p with
  | [] => [(k, v)]
  | (k', v') :: p => bif Nat.blt v v' then (k, v) :: (k', v') :: p else (k', v') :: insertSorted k v p

def Poly.sort (p : Poly) : Poly :=
  let rec go (p : Poly) (r : Poly) : Poly :=
    match p with
    | [] => r
    | (k, v) :: p => go p (r.insertSorted k v)
  go p []

def Poly.fuse (p : Poly) : Poly :=
  match p with
  | []  => []
  | (k, v) :: p =>
    match fuse p with
    | [] => [(k, v)]
    | (k', v') :: p' => bif v == v' then (Nat.add k k', v)::p' else (k, v) :: (k', v') :: p'

def Poly.mul (k : Nat) (p : Poly) : Poly :=
  bif k == 0 then
    []
  else bif k == 1 then
    p
  else
    go p
where
  go : Poly → Poly
  | [] => []
  | (k', v) :: p => (Nat.mul k k', v) :: go p

def Poly.cancelAux (fuel : Nat) (m₁ m₂ r₁ r₂ : Poly) : Poly × Poly :=
  match fuel with
  | 0 => (r₁.reverse ++ m₁, r₂.reverse ++ m₂)
  | fuel + 1 =>
    match m₁, m₂ with
    | m₁, [] => (r₁.reverse ++ m₁, r₂.reverse)
    | [], m₂ => (r₁.reverse, r₂.reverse ++ m₂)
    | (k₁, v₁) :: m₁, (k₂, v₂) :: m₂ =>
      bif Nat.blt v₁ v₂ then
        cancelAux fuel m₁ ((k₂, v₂) :: m₂) ((k₁, v₁) :: r₁) r₂
      else bif Nat.blt v₂ v₁ then
        cancelAux fuel ((k₁, v₁) :: m₁) m₂ r₁ ((k₂, v₂) :: r₂)
      else bif Nat.blt k₁ k₂ then
        cancelAux fuel m₁ m₂ r₁ ((Nat.sub k₂ k₁, v₁) :: r₂)
      else bif Nat.blt k₂ k₁ then
        cancelAux fuel m₁ m₂ ((Nat.sub k₁ k₂, v₁) :: r₁) r₂
      else
        cancelAux fuel m₁ m₂ r₁ r₂

def hugeFuel := 1000000 -- any big number should work

def Poly.cancel (p₁ p₂ : Poly) : Poly × Poly :=
  cancelAux hugeFuel p₁ p₂ [] []

def Poly.isNum? (p : Poly) : Option Nat :=
  match p with
  | [] => some 0
  | [(k, v)] => bif v == fixedVar then some k else none
  | _ => none

def Poly.isZero (p : Poly) : Bool :=
  match p with
  | [] => true
  | _  => false

def Poly.isNonZero (p : Poly) : Bool :=
  match p with
  | [] => false
  | (k, v) :: p => bif v == fixedVar then k > 0 else isNonZero p

def Poly.denote_eq (ctx : Context) (mp : Poly × Poly) : Prop := mp.1.denote ctx = mp.2.denote ctx

def Poly.denote_le (ctx : Context) (mp : Poly × Poly) : Prop := mp.1.denote ctx ≤ mp.2.denote ctx

def Poly.combineAux (fuel : Nat) (p₁ p₂ : Poly) : Poly :=
  match fuel with
  | 0 => p₁ ++ p₂
  | fuel + 1 =>
    match p₁, p₂ with
    | p₁, [] => p₁
    | [], p₂ => p₂
    | (k₁, v₁) :: p₁, (k₂, v₂) :: p₂ =>
      bif Nat.blt v₁ v₂ then
        (k₁, v₁) :: combineAux fuel p₁ ((k₂, v₂) :: p₂)
      else bif Nat.blt v₂ v₁ then
        (k₂, v₂) :: combineAux fuel ((k₁, v₁) :: p₁) p₂
      else
        (Nat.add k₁ k₂, v₁) :: combineAux fuel p₁ p₂

def Poly.combine (p₁ p₂ : Poly) : Poly :=
  combineAux hugeFuel p₁ p₂

def Expr.toPoly : Expr → Poly
  | Expr.num k    => bif k == 0 then [] else [ (k, fixedVar) ]
  | Expr.var i    => [(1, i)]
  | Expr.add a b  => a.toPoly ++ b.toPoly
  | Expr.mulL k a => a.toPoly.mul k
  | Expr.mulR a k => a.toPoly.mul k

def Poly.norm (p : Poly) : Poly :=
  p.sort.fuse

def Expr.toNormPoly (e : Expr) : Poly :=
  e.toPoly.norm

def Expr.inc (e : Expr) : Expr :=
   Expr.add e (Expr.num 1)

structure PolyCnstr  where
  eq  : Bool
  lhs : Poly
  rhs : Poly
  deriving BEq

-- TODO: implement LawfulBEq generator companion for BEq
instance : LawfulBEq PolyCnstr where
  eq_of_beq {a b} h := by
    cases a; rename_i eq₁ lhs₁ rhs₁
    cases b; rename_i eq₂ lhs₂ rhs₂
    have h : eq₁ == eq₂ && lhs₁ == lhs₂ && rhs₁ == rhs₂ := h
    simp at h
    have ⟨⟨h₁, h₂⟩, h₃⟩ := h
    rw [h₁, h₂, h₃]
  rfl {a} := by
    cases a; rename_i eq lhs rhs
    show (eq == eq && lhs == lhs && rhs == rhs) = true
    simp [LawfulBEq.rfl]

def PolyCnstr.mul (k : Nat) (c : PolyCnstr) : PolyCnstr :=
  { c with lhs := c.lhs.mul k, rhs := c.rhs.mul k }

def PolyCnstr.combine (c₁ c₂ : PolyCnstr) : PolyCnstr :=
  let (lhs, rhs) := Poly.cancel (c₁.lhs.combine c₂.lhs) (c₁.rhs.combine c₂.rhs)
  { eq := c₁.eq && c₂.eq, lhs, rhs }

structure ExprCnstr where
  eq  : Bool
  lhs : Expr
  rhs : Expr

def PolyCnstr.denote (ctx : Context) (c : PolyCnstr) : Prop :=
  bif c.eq then
    Poly.denote_eq ctx (c.lhs, c.rhs)
  else
    Poly.denote_le ctx (c.lhs, c.rhs)

def PolyCnstr.norm (c : PolyCnstr) : PolyCnstr :=
  let (lhs, rhs) := Poly.cancel c.lhs.sort.fuse c.rhs.sort.fuse
  { eq := c.eq, lhs, rhs }

def PolyCnstr.isUnsat (c : PolyCnstr) : Bool :=
  bif c.eq then
    (c.lhs.isZero && c.rhs.isNonZero) || (c.lhs.isNonZero && c.rhs.isZero)
  else
    c.lhs.isNonZero && c.rhs.isZero

def PolyCnstr.isValid (c : PolyCnstr) : Bool :=
  bif c.eq then
    c.lhs.isZero && c.rhs.isZero
  else
    c.lhs.isZero

def ExprCnstr.denote (ctx : Context) (c : ExprCnstr) : Prop :=
  bif c.eq then
    c.lhs.denote ctx = c.rhs.denote ctx
  else
    c.lhs.denote ctx ≤ c.rhs.denote ctx

def ExprCnstr.toPoly (c : ExprCnstr) : PolyCnstr :=
  { c with lhs := c.lhs.toPoly, rhs := c.rhs.toPoly }

def ExprCnstr.toNormPoly (c : ExprCnstr) : PolyCnstr :=
  let (lhs, rhs) := Poly.cancel c.lhs.toNormPoly c.rhs.toNormPoly
  { c with lhs, rhs }

abbrev Certificate := List (Nat × ExprCnstr)

def Certificate.combineHyps (c : PolyCnstr) (hs : Certificate) : PolyCnstr :=
  match hs with
  | [] => c
  | (k, c') :: hs => combineHyps (PolyCnstr.combine c (c'.toNormPoly.mul (Nat.add k 1))) hs

def Certificate.combine (hs : Certificate) : PolyCnstr :=
  match hs with
  | [] => { eq := true, lhs := [], rhs := [] }
  | (k, c) :: hs => combineHyps (c.toNormPoly.mul (Nat.add k 1)) hs

def Certificate.denote (ctx : Context) (c : Certificate) : Prop :=
  match c with
  | [] => False
  | (_, c)::hs => c.denote ctx → denote ctx hs

def monomialToExpr (k : Nat) (v : Var) : Expr :=
  bif v == fixedVar then
    Expr.num k
  else bif k == 1 then
    Expr.var v
  else
    Expr.mulL k (Expr.var v)

def Poly.toExpr (p : Poly) : Expr :=
  match p with
  | [] => Expr.num 0
  | (k, v) :: p => go (monomialToExpr k v) p
where
  go (e : Expr) (p : Poly) : Expr :=
    match p with
    | [] => e
    | (k, v) :: p => go (Expr.add e (monomialToExpr k v)) p

def PolyCnstr.toExpr (c : PolyCnstr) : ExprCnstr :=
  { c with lhs := c.lhs.toExpr, rhs := c.rhs.toExpr }

attribute [local simp] Nat.add_comm Nat.add_assoc Nat.add_left_comm Nat.right_distrib Nat.left_distrib Nat.mul_assoc Nat.mul_comm
attribute [local simp] Poly.denote Expr.denote Poly.insertSorted Poly.sort Poly.sort.go Poly.fuse Poly.cancelAux
attribute [local simp] Poly.mul Poly.mul.go

theorem Poly.denote_insertSorted (ctx : Context) (k : Nat) (v : Var) (p : Poly) : (p.insertSorted k v).denote ctx = p.denote ctx + k * v.denote ctx := by
  match p with
  | [] => simp
  | (k', v') :: p => by_cases h : Nat.blt v v' <;> simp [h, denote_insertSorted]

attribute [local simp] Poly.denote_insertSorted

theorem Poly.denote_sort_go (ctx : Context) (p : Poly) (r : Poly) : (sort.go p r).denote ctx = p.denote ctx + r.denote ctx := by
  match p with
  | [] => simp
  | (k, v):: p => simp [denote_sort_go]

attribute [local simp] Poly.denote_sort_go

theorem Poly.denote_sort (ctx : Context) (m : Poly) : m.sort.denote ctx = m.denote ctx := by
  simp

attribute [local simp] Poly.denote_sort

theorem Poly.denote_append (ctx : Context) (p q : Poly) : (p ++ q).denote ctx = p.denote ctx + q.denote ctx := by
  match p with
  | []  => simp
  | (k, v) :: p => simp [denote_append]

attribute [local simp] Poly.denote_append

theorem Poly.denote_cons (ctx : Context) (k : Nat) (v : Var) (p : Poly) : denote ctx ((k, v) :: p) = k * v.denote ctx + p.denote ctx := by
  match p with
  | []     => simp
  | _ :: m => simp [denote_cons]

attribute [local simp] Poly.denote_cons

theorem Poly.denote_reverseAux (ctx : Context) (p q : Poly) : denote ctx (List.reverseAux p q) = denote ctx (p ++ q) := by
  match p with
  | [] => simp [List.reverseAux]
  | (k, v) :: p => simp [List.reverseAux, denote_reverseAux]

attribute [local simp] Poly.denote_reverseAux

theorem Poly.denote_reverse (ctx : Context) (p : Poly) : denote ctx (List.reverse p) = denote ctx p := by
  simp [List.reverse]

attribute [local simp] Poly.denote_reverse

theorem Poly.denote_fuse (ctx : Context) (p : Poly) : p.fuse.denote ctx = p.denote ctx := by
  match p with
  | [] => rfl
  | (k, v) :: p =>
    have ih := denote_fuse ctx p
    simp
    split
    case _ h => simp [← ih, h]
    case _ k' v' p' h => by_cases he : v == v' <;> simp [he, ← ih, h]; rw [eq_of_beq he]

attribute [local simp] Poly.denote_fuse

theorem Poly.denote_mul (ctx : Context) (k : Nat) (p : Poly) : (p.mul k).denote ctx = k * p.denote ctx := by
  simp
  by_cases h : k == 0 <;> simp [h]; simp [eq_of_beq h]
  by_cases h : k == 1 <;> simp [h]; simp [eq_of_beq h]
  induction p with
  | nil  => simp
  | cons kv m ih => cases kv with | _ k' v => simp [ih]

private theorem eq_of_not_blt_eq_true (h₁ : ¬ (Nat.blt x y = true)) (h₂ : ¬ (Nat.blt y x = true)) : x = y :=
  have h₁ : ¬ x < y := fun h => h₁ (Nat.blt_eq.mpr h)
  have h₂ : ¬ y < x := fun h => h₂ (Nat.blt_eq.mpr h)
  Nat.le_antisymm (Nat.ge_of_not_lt h₂) (Nat.ge_of_not_lt h₁)

attribute [local simp] Poly.denote_mul

theorem Poly.denote_eq_cancelAux (ctx : Context) (fuel : Nat) (m₁ m₂ r₁ r₂ : Poly)
    (h : denote_eq ctx (r₁.reverse ++ m₁, r₂.reverse ++ m₂)) : denote_eq ctx (cancelAux fuel m₁ m₂ r₁ r₂) := by
  induction fuel generalizing m₁ m₂ r₁ r₂ with
  | zero => assumption
  | succ fuel ih =>
    simp
    split <;> simp at h <;> try assumption
    rename_i k₁ v₁ m₁ k₂ v₂ m₂
    by_cases hltv : Nat.blt v₁ v₂ <;> simp [hltv]
    · apply ih; simp [denote_eq] at h |-; assumption
    · by_cases hgtv : Nat.blt v₂ v₁ <;> simp [hgtv]
      · apply ih; simp [denote_eq] at h |-; assumption
      · have heqv : v₁ = v₂ := eq_of_not_blt_eq_true hltv hgtv; subst heqv
        by_cases hltk : Nat.blt k₁ k₂ <;> simp [hltk]
        · apply ih
          simp [denote_eq] at h |-
          have haux : k₁ * Var.denote ctx v₁ ≤ k₂ * Var.denote ctx v₁ := Nat.mul_le_mul_right _ (Nat.le_of_lt (Nat.blt_eq.mp hltk))
          rw [Nat.mul_sub_right_distrib, ← Nat.add_assoc, ← Nat.add_sub_assoc haux]
          apply Eq.symm
          apply Nat.sub_eq_of_eq_add
          simp [h]
        · by_cases hgtk : Nat.blt k₂ k₁ <;> simp [hgtk]
          · apply ih
            simp [denote_eq] at h |-
            have haux : k₂ * Var.denote ctx v₁ ≤ k₁ * Var.denote ctx v₁ := Nat.mul_le_mul_right _ (Nat.le_of_lt (Nat.blt_eq.mp hgtk))
            rw [Nat.mul_sub_right_distrib, ← Nat.add_assoc, ← Nat.add_sub_assoc haux]
            apply Nat.sub_eq_of_eq_add
            simp [h]
          · have heqk : k₁ = k₂ := eq_of_not_blt_eq_true hltk hgtk; subst heqk
            apply ih
            simp [denote_eq] at h |-
            rw [← Nat.add_assoc, ← Nat.add_assoc] at h
            exact Nat.add_right_cancel h

theorem Poly.of_denote_eq_cancelAux (ctx : Context) (fuel : Nat) (m₁ m₂ r₁ r₂ : Poly)
    (h : denote_eq ctx (cancelAux fuel m₁ m₂ r₁ r₂)) : denote_eq ctx (r₁.reverse ++ m₁, r₂.reverse ++ m₂) := by
  induction fuel generalizing m₁ m₂ r₁ r₂ with
  | zero => assumption
  | succ fuel ih =>
    simp at h
    split at h <;> simp <;> try assumption
    rename_i k₁ v₁ m₁ k₂ v₂ m₂
    by_cases hltv : Nat.blt v₁ v₂ <;> simp [hltv] at h
    · have ih := ih (h := h); simp [denote_eq] at ih ⊢; assumption
    · by_cases hgtv : Nat.blt v₂ v₁ <;> simp [hgtv] at h
      · have ih := ih (h := h); simp [denote_eq] at ih ⊢; assumption
      · have heqv : v₁ = v₂ := eq_of_not_blt_eq_true hltv hgtv; subst heqv
        by_cases hltk : Nat.blt k₁ k₂ <;> simp [hltk] at h
        · have ih := ih (h := h); simp [denote_eq] at ih ⊢
          have haux : k₁ * Var.denote ctx v₁ ≤ k₂ * Var.denote ctx v₁ := Nat.mul_le_mul_right _ (Nat.le_of_lt (Nat.blt_eq.mp hltk))
          rw [Nat.mul_sub_right_distrib, ← Nat.add_assoc, ← Nat.add_sub_assoc haux] at ih
          have ih := Nat.eq_add_of_sub_eq (Nat.le_trans haux (Nat.le_add_left ..)) ih.symm
          simp at ih
          rw [ih]
        · by_cases hgtk : Nat.blt k₂ k₁ <;> simp [hgtk] at h
          · have ih := ih (h := h); simp [denote_eq] at ih ⊢
            have haux : k₂ * Var.denote ctx v₁ ≤ k₁ * Var.denote ctx v₁ := Nat.mul_le_mul_right _ (Nat.le_of_lt (Nat.blt_eq.mp hgtk))
            rw [Nat.mul_sub_right_distrib, ← Nat.add_assoc, ← Nat.add_sub_assoc haux] at ih
            have ih := Nat.eq_add_of_sub_eq (Nat.le_trans haux (Nat.le_add_left ..)) ih
            simp at ih
            rw [ih]
          · have heqk : k₁ = k₂ := eq_of_not_blt_eq_true hltk hgtk; subst heqk
            have ih := ih (h := h); simp [denote_eq] at ih ⊢
            rw [← Nat.add_assoc, ih, Nat.add_assoc]

theorem Poly.denote_eq_cancel {ctx : Context} {m₁ m₂ : Poly} (h : denote_eq ctx (m₁, m₂)) : denote_eq ctx (cancel m₁ m₂) := by
  simp; apply denote_eq_cancelAux; simp [h]

theorem Poly.of_denote_eq_cancel {ctx : Context} {m₁ m₂ : Poly} (h : denote_eq ctx (cancel m₁ m₂)) : denote_eq ctx (m₁, m₂) := by
  simp at h
  have := Poly.of_denote_eq_cancelAux (h := h)
  simp at this
  assumption

theorem Poly.denote_eq_cancel_eq (ctx : Context) (m₁ m₂ : Poly) : denote_eq ctx (cancel m₁ m₂) = denote_eq ctx (m₁, m₂) :=
  propext <| Iff.intro (fun h => of_denote_eq_cancel h) (fun h => denote_eq_cancel h)

attribute [local simp] Poly.denote_eq_cancel_eq

theorem Poly.denote_le_cancelAux (ctx : Context) (fuel : Nat) (m₁ m₂ r₁ r₂ : Poly)
    (h : denote_le ctx (r₁.reverse ++ m₁, r₂.reverse ++ m₂)) : denote_le ctx (cancelAux fuel m₁ m₂ r₁ r₂) := by
  induction fuel generalizing m₁ m₂ r₁ r₂ with
  | zero => assumption
  | succ fuel ih =>
    simp
    split <;> simp at h <;> try assumption
    rename_i k₁ v₁ m₁ k₂ v₂ m₂
    by_cases hltv : Nat.blt v₁ v₂ <;> simp [hltv]
    · apply ih; simp [denote_le] at h |-; assumption
    · by_cases hgtv : Nat.blt v₂ v₁ <;> simp [hgtv]
      · apply ih; simp [denote_le] at h |-; assumption
      · have heqv : v₁ = v₂ := eq_of_not_blt_eq_true hltv hgtv; subst heqv
        by_cases hltk : Nat.blt k₁ k₂ <;> simp [hltk]
        · apply ih
          simp [denote_le] at h |-
          have haux : k₁ * Var.denote ctx v₁ ≤ k₂ * Var.denote ctx v₁ := Nat.mul_le_mul_right _ (Nat.le_of_lt (Nat.blt_eq.mp hltk))
          rw [Nat.mul_sub_right_distrib, ← Nat.add_assoc, ← Nat.add_sub_assoc haux]
          apply Nat.le_sub_of_add_le
          simp [h]
        · by_cases hgtk : Nat.blt k₂ k₁ <;> simp [hgtk]
          · apply ih
            simp [denote_le] at h |-
            have haux : k₂ * Var.denote ctx v₁ ≤ k₁ * Var.denote ctx v₁ := Nat.mul_le_mul_right _ (Nat.le_of_lt (Nat.blt_eq.mp hgtk))
            rw [Nat.mul_sub_right_distrib, ← Nat.add_assoc, ← Nat.add_sub_assoc haux]
            apply Nat.sub_le_of_le_add
            simp [h]
          · have heqk : k₁ = k₂ := eq_of_not_blt_eq_true hltk hgtk; subst heqk
            apply ih
            simp [denote_le] at h |-
            rw [← Nat.add_assoc, ← Nat.add_assoc] at h
            apply Nat.le_of_add_le_add_right h
    done

theorem Poly.of_denote_le_cancelAux (ctx : Context) (fuel : Nat) (m₁ m₂ r₁ r₂ : Poly)
    (h : denote_le ctx (cancelAux fuel m₁ m₂ r₁ r₂)) : denote_le ctx (r₁.reverse ++ m₁, r₂.reverse ++ m₂) := by
  induction fuel generalizing m₁ m₂ r₁ r₂ with
  | zero => assumption
  | succ fuel ih =>
    simp at h
    split at h <;> simp <;> try assumption
    rename_i k₁ v₁ m₁ k₂ v₂ m₂
    by_cases hltv : Nat.blt v₁ v₂ <;> simp [hltv] at h
    · have ih := ih (h := h); simp [denote_le] at ih ⊢; assumption
    · by_cases hgtv : Nat.blt v₂ v₁ <;> simp [hgtv] at h
      · have ih := ih (h := h); simp [denote_le] at ih ⊢; assumption
      · have heqv : v₁ = v₂ := eq_of_not_blt_eq_true hltv hgtv; subst heqv
        by_cases hltk : Nat.blt k₁ k₂ <;> simp [hltk] at h
        · have ih := ih (h := h); simp [denote_le] at ih ⊢
          have haux : k₁ * Var.denote ctx v₁ ≤ k₂ * Var.denote ctx v₁ := Nat.mul_le_mul_right _ (Nat.le_of_lt (Nat.blt_eq.mp hltk))
          rw [Nat.mul_sub_right_distrib, ← Nat.add_assoc, ← Nat.add_sub_assoc haux] at ih
          have := Nat.add_le_of_le_sub (Nat.le_trans haux (Nat.le_add_left ..)) ih
          simp at this
          exact this
        · by_cases hgtk : Nat.blt k₂ k₁ <;> simp [hgtk] at h
          · have ih := ih (h := h); simp [denote_le] at ih ⊢
            have haux : k₂ * Var.denote ctx v₁ ≤ k₁ * Var.denote ctx v₁ := Nat.mul_le_mul_right _ (Nat.le_of_lt (Nat.blt_eq.mp hgtk))
            rw [Nat.mul_sub_right_distrib, ← Nat.add_assoc, ← Nat.add_sub_assoc haux] at ih
            have := Nat.le_add_of_sub_le ih
            simp at this
            exact this
          · have heqk : k₁ = k₂ := eq_of_not_blt_eq_true hltk hgtk; subst heqk
            have ih := ih (h := h); simp [denote_le] at ih ⊢
            have := Nat.add_le_add_right ih (k₁ * Var.denote ctx v₁)
            simp at this
            exact this

theorem Poly.denote_le_cancel {ctx : Context} {m₁ m₂ : Poly} (h : denote_le ctx (m₁, m₂)) : denote_le ctx (cancel m₁ m₂) := by
  simp; apply denote_le_cancelAux; simp [h]

theorem Poly.of_denote_le_cancel {ctx : Context} {m₁ m₂ : Poly} (h : denote_le ctx (cancel m₁ m₂)) : denote_le ctx (m₁, m₂) := by
  simp at h
  have := Poly.of_denote_le_cancelAux (h := h)
  simp at this
  assumption

theorem Poly.denote_le_cancel_eq (ctx : Context) (m₁ m₂ : Poly) : denote_le ctx (cancel m₁ m₂) = denote_le ctx (m₁, m₂) :=
  propext <| Iff.intro (fun h => of_denote_le_cancel h) (fun h => denote_le_cancel h)

attribute [local simp] Poly.denote_le_cancel_eq

theorem Poly.denote_combineAux (ctx : Context) (fuel : Nat) (p₁ p₂ : Poly) : (p₁.combineAux fuel p₂).denote ctx = p₁.denote ctx + p₂.denote ctx := by
  induction fuel generalizing p₁ p₂ with simp [combineAux]
  | succ fuel ih =>
    split <;> simp
    rename_i k₁ v₁ p₁ k₂ v₂ p₂
    by_cases hltv : Nat.blt v₁ v₂ <;> simp [hltv, ih]
    by_cases hgtv : Nat.blt v₂ v₁ <;> simp [hgtv, ih]
    have heqv : v₁ = v₂ := eq_of_not_blt_eq_true hltv hgtv
    simp [heqv]

theorem Poly.denote_combine (ctx : Context) (p₁ p₂ : Poly) : (p₁.combine p₂).denote ctx = p₁.denote ctx + p₂.denote ctx := by
  simp [combine, denote_combineAux]

attribute [local simp] Poly.denote_combine

theorem Expr.denote_toPoly (ctx : Context) (e : Expr) : e.toPoly.denote ctx = e.denote ctx := by
  induction e with
  | num k => by_cases h : k == 0 <;> simp [toPoly, h, Var.denote]; simp [eq_of_beq h]
  | var i => simp [toPoly]
  | add a b iha ihb => simp [toPoly, iha, ihb]
  | mulL k a ih => simp [toPoly, ih, -Poly.mul]
  | mulR k a ih => simp [toPoly, ih, -Poly.mul]

attribute [local simp] Expr.denote_toPoly

theorem Expr.eq_of_toNormPoly (ctx : Context) (a b : Expr) (h : a.toNormPoly = b.toNormPoly) : a.denote ctx = b.denote ctx := by
  simp [toNormPoly, Poly.norm] at h
  have h := congrArg (Poly.denote ctx) h
  simp at h
  assumption

theorem Expr.of_cancel_eq (ctx : Context) (a b c d : Expr) (h : Poly.cancel a.toNormPoly b.toNormPoly = (c.toPoly, d.toPoly)) : (a.denote ctx = b.denote ctx) = (c.denote ctx = d.denote ctx) := by
  have := Poly.denote_eq_cancel_eq ctx a.toNormPoly b.toNormPoly
  rw [h] at this
  simp [toNormPoly, Poly.norm, Poly.denote_eq] at this
  exact this.symm

theorem Expr.of_cancel_le (ctx : Context) (a b c d : Expr) (h : Poly.cancel a.toNormPoly b.toNormPoly = (c.toPoly, d.toPoly)) : (a.denote ctx ≤ b.denote ctx) = (c.denote ctx ≤ d.denote ctx) := by
  have := Poly.denote_le_cancel_eq ctx a.toNormPoly b.toNormPoly
  rw [h] at this
  simp [toNormPoly, Poly.norm,Poly.denote_le] at this
  exact this.symm

theorem Expr.of_cancel_lt (ctx : Context) (a b c d : Expr) (h : Poly.cancel a.inc.toNormPoly b.toNormPoly = (c.inc.toPoly, d.toPoly)) : (a.denote ctx < b.denote ctx) = (c.denote ctx < d.denote ctx) :=
  of_cancel_le ctx a.inc b c.inc d h

theorem ExprCnstr.toPoly_norm_eq (c : ExprCnstr) : c.toPoly.norm = c.toNormPoly :=
  rfl

theorem ExprCnstr.denote_toPoly (ctx : Context) (c : ExprCnstr) : c.toPoly.denote ctx = c.denote ctx := by
  cases c; rename_i eq lhs rhs
  simp [ExprCnstr.denote, PolyCnstr.denote, ExprCnstr.toPoly];
  by_cases h : eq = true <;> simp [h]
  · simp [Poly.denote_eq, Expr.toPoly]
  · simp [Poly.denote_le, Expr.toPoly]

attribute [local simp] ExprCnstr.denote_toPoly

theorem ExprCnstr.denote_toNormPoly (ctx : Context) (c : ExprCnstr) : c.toNormPoly.denote ctx = c.denote ctx := by
  cases c; rename_i eq lhs rhs
  simp [ExprCnstr.denote, PolyCnstr.denote, ExprCnstr.toNormPoly]
  by_cases h : eq = true <;> simp [h]
  · rw [Poly.denote_eq_cancel_eq]; simp [Poly.denote_eq, Expr.toNormPoly, Poly.norm]
  · rw [Poly.denote_le_cancel_eq]; simp [Poly.denote_le, Expr.toNormPoly, Poly.norm]

attribute [local simp] ExprCnstr.denote_toNormPoly

theorem Poly.mul.go_denote (ctx : Context) (k : Nat) (p : Poly) : (Poly.mul.go k p).denote ctx = k * p.denote ctx := by
  match p with
  | [] => rfl
  | (k', v) :: p => simp [Poly.mul.go, go_denote]

attribute [local simp] Poly.mul.go_denote

section
attribute [-simp] Nat.right_distrib Nat.left_distrib

theorem PolyCnstr.denote_mul (ctx : Context) (k : Nat) (c : PolyCnstr) : (c.mul (k+1)).denote ctx = c.denote ctx := by
  cases c; rename_i eq lhs rhs
  have : k ≠ 0 → k + 1 ≠ 1 := by intro h; match k with | 0 => contradiction | k+1 => simp; apply Nat.succ_ne_zero
  have : ¬ (k == 0) → (k + 1 == 1) = false := fun h => beq_false_of_ne (this (ne_of_beq_false (Bool.of_not_eq_true h)))
  have : ¬ ((k + 1 == 0) = true)  := fun h => absurd (eq_of_beq h) (Nat.succ_ne_zero k)
  have : (1 == (0 : Nat)) = false := rfl
  have : (1 == (1 : Nat)) = true  := rfl
  by_cases he : eq = true <;> simp [he, PolyCnstr.mul, PolyCnstr.denote, Poly.denote_le, Poly.denote_eq]
     <;> by_cases hk : k == 0 <;> (try simp [eq_of_beq hk]) <;> simp [*] <;> apply propext <;> apply Iff.intro <;> intro h
  · exact Nat.eq_of_mul_eq_mul_left (Nat.zero_lt_succ _) h
  · rw [h]
  · exact Nat.le_of_mul_le_mul_left h (Nat.zero_lt_succ _)
  · apply Nat.mul_le_mul_left _ h

end

attribute [local simp] PolyCnstr.denote_mul

theorem PolyCnstr.denote_combine {ctx : Context} {c₁ c₂ : PolyCnstr} (h₁ : c₁.denote ctx) (h₂ : c₂.denote ctx) : (c₁.combine c₂).denote ctx := by
  cases c₁; cases c₂; rename_i eq₁ lhs₁ rhs₁ eq₂ lhs₂ rhs₂
  simp [denote] at h₁ h₂
  simp [PolyCnstr.combine, denote]
  by_cases he₁ : eq₁ = true <;> by_cases he₂ : eq₂ = true <;> simp [he₁, he₂] at h₁ h₂ |-
  · rw [Poly.denote_eq_cancel_eq]; simp [Poly.denote_eq] at h₁ h₂ |-; simp [h₁, h₂]
  · rw [Poly.denote_le_cancel_eq]; simp [Poly.denote_eq, Poly.denote_le] at h₁ h₂ |-; rw [h₁]; apply Nat.add_le_add_left h₂
  · rw [Poly.denote_le_cancel_eq]; simp [Poly.denote_eq, Poly.denote_le] at h₁ h₂ |-; rw [h₂]; apply Nat.add_le_add_right h₁
  · rw [Poly.denote_le_cancel_eq]; simp [Poly.denote_eq, Poly.denote_le] at h₁ h₂ |-; apply Nat.add_le_add h₁ h₂

attribute [local simp] PolyCnstr.denote_combine

theorem Poly.isNum?_eq_some (ctx : Context) {p : Poly} {k : Nat} : p.isNum? = some k → p.denote ctx = k := by
  simp [isNum?]
  split
  next => intro h; injection h
  next k v => by_cases h : v == fixedVar <;> simp [h]; intros; simp [Var.denote, eq_of_beq h]; assumption
  next => intros; contradiction

theorem Poly.of_isZero (ctx : Context) {p : Poly} (h : isZero p = true) : p.denote ctx = 0 := by
  simp [isZero] at h
  split at h
  · simp
  · contradiction

theorem Poly.of_isNonZero (ctx : Context) {p : Poly} (h : isNonZero p = true) : p.denote ctx > 0 := by
  match p with
  | [] => contradiction
  | (k, v) :: p =>
    by_cases he : v == fixedVar <;> simp [he, isNonZero] at h ⊢
    · simp [eq_of_beq he, Var.denote]; apply Nat.lt_of_succ_le; exact Nat.le_trans h (Nat.le_add_right ..)
    · have ih := of_isNonZero ctx h
      exact Nat.le_trans ih (Nat.le_add_right ..)

theorem PolyCnstr.eq_false_of_isUnsat (ctx : Context) {c : PolyCnstr} : c.isUnsat → c.denote ctx = False := by
  cases c; rename_i eq lhs rhs
  simp [isUnsat]
  by_cases he : eq = true <;> simp [he, denote, Poly.denote_eq, Poly.denote_le]
  · intro
      | Or.inl ⟨h₁, h₂⟩ => simp [Poly.of_isZero, h₁]; have := Nat.not_eq_zero_of_lt (Poly.of_isNonZero ctx h₂); simp [this.symm]
      | Or.inr ⟨h₁, h₂⟩ => simp [Poly.of_isZero, h₂]; have := Nat.not_eq_zero_of_lt (Poly.of_isNonZero ctx h₁); simp [this]
  · intro ⟨h₁, h₂⟩
    simp [Poly.of_isZero, h₂]
    have := Nat.not_eq_zero_of_lt (Poly.of_isNonZero ctx h₁)
    simp [this]
    done

theorem PolyCnstr.eq_true_of_isValid (ctx : Context) {c : PolyCnstr} : c.isValid → c.denote ctx = True := by
  cases c; rename_i eq lhs rhs
  simp [isValid]
  by_cases he : eq = true <;> simp [he, denote, Poly.denote_eq, Poly.denote_le]
  · intro ⟨h₁, h₂⟩
    simp [Poly.of_isZero, h₁, h₂]
  · intro h
    simp [Poly.of_isZero, h]

theorem ExprCnstr.eq_false_of_isUnsat (ctx : Context) (c : ExprCnstr) (h : c.toNormPoly.isUnsat) : c.denote ctx = False := by
  have := PolyCnstr.eq_false_of_isUnsat ctx h
  simp at this
  assumption

theorem ExprCnstr.eq_true_of_isValid (ctx : Context) (c : ExprCnstr) (h : c.toNormPoly.isValid) : c.denote ctx = True := by
  have := PolyCnstr.eq_true_of_isValid ctx h
  simp at this
  assumption

theorem Certificate.of_combineHyps (ctx : Context) (c : PolyCnstr) (cs : Certificate) (h : (combineHyps c cs).denote ctx → False) : c.denote ctx → cs.denote ctx := by
  match cs with
  | [] => simp [combineHyps, denote] at *; exact h
  | (k, c')::cs =>
    intro h₁ h₂
    have := PolyCnstr.denote_combine (ctx := ctx) (c₂ := PolyCnstr.mul (k + 1) (ExprCnstr.toNormPoly c')) h₁
    simp at this
    have := this h₂
    have ih := of_combineHyps ctx (PolyCnstr.combine c (PolyCnstr.mul (k + 1) (ExprCnstr.toNormPoly c'))) cs
    exact ih h this

theorem Certificate.of_combine (ctx : Context) (cs : Certificate) (h : cs.combine.denote ctx → False) : cs.denote ctx := by
  match cs with
  | [] => simp [combine, PolyCnstr.denote, Poly.denote_eq] at h
  | (k, c)::cs =>
    simp [denote, combine] at *
    intro h'
    apply of_combineHyps (h := h)
    simp [h']

theorem Certificate.of_combine_isUnsat (ctx : Context) (cs : Certificate) (h : cs.combine.isUnsat) : cs.denote ctx :=
  have h := PolyCnstr.eq_false_of_isUnsat ctx h
  of_combine ctx cs (fun h' => Eq.mp h h')

theorem denote_monomialToExpr (ctx : Context) (k : Nat) (v : Var) : (monomialToExpr k v).denote ctx = k * v.denote ctx := by
  simp [monomialToExpr]
  by_cases h : v == fixedVar <;> simp [h, Expr.denote]
  · simp [eq_of_beq h, Var.denote]
  · by_cases h : k == 1 <;> simp [h, Expr.denote]; simp [eq_of_beq h]

attribute [local simp] denote_monomialToExpr

theorem Poly.denote_toExpr_go (ctx : Context) (e : Expr) (p : Poly) : (toExpr.go e p).denote ctx = e.denote ctx + p.denote ctx := by
  induction p generalizing e with
  | nil => simp [toExpr.go, Poly.denote]
  | cons kv p ih => cases kv; simp [toExpr.go, ih, Expr.denote, Poly.denote]

attribute [local simp] Poly.denote_toExpr_go

theorem Poly.denote_toExpr (ctx : Context) (p : Poly) : p.toExpr.denote ctx = p.denote ctx := by
  match p with
  | [] => simp [toExpr, Expr.denote, Poly.denote]
  | (k, v) :: p => simp [toExpr, Expr.denote, Poly.denote]

theorem ExprCnstr.eq_of_toNormPoly_eq (ctx : Context) (c d : ExprCnstr) (h : c.toNormPoly == d.toPoly) : c.denote ctx = d.denote ctx := by
  have h := congrArg (PolyCnstr.denote ctx) (eq_of_beq h)
  simp at h
  assumption

theorem Expr.eq_of_toNormPoly_eq (ctx : Context) (e e' : Expr) (h : e.toNormPoly == e'.toPoly) : e.denote ctx = e'.denote ctx := by
  have h := congrArg (Poly.denote ctx) (eq_of_beq h)
  simp [Expr.toNormPoly, Poly.norm] at h
  assumption

end Nat.Linear


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

! This file was ported from Lean 3 source module data.finsupp.fin
! leanprover-community/mathlib commit 68d1483e8a718ec63219f0e227ca3f0140361086
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Finsupp.Defs

/-!
# `cons` and `tail` for maps `fin n →₀ M`

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

We interpret maps `fin n →₀ M` as `n`-tuples of elements of `M`,
We define the following operations:
* `finsupp.tail` : the tail of a map `fin (n + 1) →₀ M`, i.e., its last `n` entries;
* `finsupp.cons` : adding an element at the beginning of an `n`-tuple, to get an `n + 1`-tuple;

In this context, we prove some usual properties of `tail` and `cons`, analogous to those of
`data.fin.tuple.basic`.
-/

noncomputable section

namespace Finsupp

variable {n : ℕ} (i : Fin n) {M : Type _} [Zero M] (y : M) (t : Fin (n + 1) →₀ M) (s : Fin n →₀ M)

#print Finsupp.tail /-
/-- `tail` for maps `fin (n + 1) →₀ M`. See `fin.tail` for more details. -/
def tail (s : Fin (n + 1) →₀ M) : Fin n →₀ M :=
  Finsupp.equivFunOnFinite.symm (Fin.tail s)
#align finsupp.tail Finsupp.tail
-/

#print Finsupp.cons /-
/-- `cons` for maps `fin n →₀ M`. See `fin.cons` for more details. -/
def cons (y : M) (s : Fin n →₀ M) : Fin (n + 1) →₀ M :=
  Finsupp.equivFunOnFinite.symm (Fin.cons y s : Fin (n + 1) → M)
#align finsupp.cons Finsupp.cons
-/

#print Finsupp.tail_apply /-
theorem tail_apply : tail t i = t i.succ :=
  rfl
#align finsupp.tail_apply Finsupp.tail_apply
-/

/- warning: finsupp.cons_zero -> Finsupp.cons_zero is a dubious translation:
lean 3 declaration is
  forall {n : Nat} {M : Type.{u1}} [_inst_1 : Zero.{u1} M] (y : M) (s : Finsupp.{0, u1} (Fin n) M _inst_1), Eq.{succ u1} M (coeFn.{succ u1, succ u1} (Finsupp.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) M _inst_1) (fun (_x : Finsupp.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) M _inst_1) => (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) -> M) (Finsupp.coeFun.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) M _inst_1) (Finsupp.cons.{u1} n M _inst_1 y s) (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (OfNat.mk.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (Zero.zero.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) (Fin.hasZeroOfNeZero (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne)) (NeZero.succ n)))))) y
but is expected to have type
  forall {n : Nat} {M : Type.{u1}} [_inst_1 : Zero.{u1} M] (y : M) (s : Finsupp.{0, u1} (Fin n) M _inst_1), Eq.{succ u1} ((fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => M) (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) 0 (Fin.instOfNatFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) 0 (NeZero.succ n)))) (FunLike.coe.{succ u1, 1, succ u1} (Finsupp.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) M _inst_1) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (_x : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => M) _x) (Finsupp.funLike.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) M _inst_1) (Finsupp.cons.{u1} n M _inst_1 y s) (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) 0 (Fin.instOfNatFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) 0 (NeZero.succ n)))) y
Case conversion may be inaccurate. Consider using '#align finsupp.cons_zero Finsupp.cons_zeroₓ'. -/
@[simp]
theorem cons_zero : cons y s 0 = y :=
  rfl
#align finsupp.cons_zero Finsupp.cons_zero

#print Finsupp.cons_succ /-
@[simp]
theorem cons_succ : cons y s i.succ = s i :=
  Fin.cons_succ _ _ _
#align finsupp.cons_succ Finsupp.cons_succ
-/

#print Finsupp.tail_cons /-
@[simp]
theorem tail_cons : tail (cons y s) = s :=
  ext fun k => by simp only [tail_apply, cons_succ]
#align finsupp.tail_cons Finsupp.tail_cons
-/

/- warning: finsupp.cons_tail -> Finsupp.cons_tail is a dubious translation:
lean 3 declaration is
  forall {n : Nat} {M : Type.{u1}} [_inst_1 : Zero.{u1} M] (t : Finsupp.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) M _inst_1), Eq.{succ u1} (Finsupp.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) M _inst_1) (Finsupp.cons.{u1} n M _inst_1 (coeFn.{succ u1, succ u1} (Finsupp.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) M _inst_1) (fun (_x : Finsupp.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) M _inst_1) => (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) -> M) (Finsupp.coeFun.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) M _inst_1) t (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (OfNat.mk.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (Zero.zero.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) (Fin.hasZeroOfNeZero (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne)) (NeZero.succ n)))))) (Finsupp.tail.{u1} n M _inst_1 t)) t
but is expected to have type
  forall {n : Nat} {M : Type.{u1}} [_inst_1 : Zero.{u1} M] (t : Finsupp.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) M _inst_1), Eq.{succ u1} (Finsupp.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) ((fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => M) (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) 0 (Fin.instOfNatFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) 0 (NeZero.succ n)))) _inst_1) (Finsupp.cons.{u1} n ((fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => M) (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) 0 (Fin.instOfNatFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) 0 (NeZero.succ n)))) _inst_1 (FunLike.coe.{succ u1, 1, succ u1} (Finsupp.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) M _inst_1) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (_x : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => M) _x) (Finsupp.funLike.{0, u1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) M _inst_1) t (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) 0 (Fin.instOfNatFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) 0 (NeZero.succ n)))) (Finsupp.tail.{u1} n ((fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => M) (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) 0 (Fin.instOfNatFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) 0 (NeZero.succ n)))) _inst_1 t)) t
Case conversion may be inaccurate. Consider using '#align finsupp.cons_tail Finsupp.cons_tailₓ'. -/
@[simp]
theorem cons_tail : cons (t 0) (tail t) = t := by
  ext
  by_cases c_a : a = 0
  · rw [c_a, cons_zero]
  · rw [← Fin.succ_pred a c_a, cons_succ, ← tail_apply]
#align finsupp.cons_tail Finsupp.cons_tail

#print Finsupp.cons_zero_zero /-
@[simp]
theorem cons_zero_zero : cons 0 (0 : Fin n →₀ M) = 0 :=
  by
  ext
  by_cases c : a = 0
  · simp [c]
  · rw [← Fin.succ_pred a c, cons_succ]
    simp
#align finsupp.cons_zero_zero Finsupp.cons_zero_zero
-/

variable {s} {y}

#print Finsupp.cons_ne_zero_of_left /-
theorem cons_ne_zero_of_left (h : y ≠ 0) : cons y s ≠ 0 :=
  by
  contrapose! h with c
  rw [← cons_zero y s, c, Finsupp.coe_zero, Pi.zero_apply]
#align finsupp.cons_ne_zero_of_left Finsupp.cons_ne_zero_of_left
-/

#print Finsupp.cons_ne_zero_of_right /-
theorem cons_ne_zero_of_right (h : s ≠ 0) : cons y s ≠ 0 :=
  by
  contrapose! h with c
  ext
  simp [← cons_succ a y s, c]
#align finsupp.cons_ne_zero_of_right Finsupp.cons_ne_zero_of_right
-/

#print Finsupp.cons_ne_zero_iff /-
theorem cons_ne_zero_iff : cons y s ≠ 0 ↔ y ≠ 0 ∨ s ≠ 0 :=
  by
  refine' ⟨fun h => _, fun h => h.casesOn cons_ne_zero_of_left cons_ne_zero_of_right⟩
  refine' imp_iff_not_or.1 fun h' c => h _
  rw [h', c, Finsupp.cons_zero_zero]
#align finsupp.cons_ne_zero_iff Finsupp.cons_ne_zero_iff
-/

end Finsupp


/-
Copyright (c) 2020 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau, Ken Lee, Chris Hughes
-/
import tactic.ring
import algebra.big_operators.basic
import data.fintype.basic
import data.int.gcd
import data.set.disjointed

/-!
# Coprime elements of a ring

## Main definitions

* `is_coprime x y`: that `x` and `y` are coprime, defined to be the existence of `a` and `b` such
that `a * x + b * y = 1`. Note that elements with no common divisors are not necessarily coprime,
e.g., the multivariate polynomials `x₁` and `x₂` are not coprime.

-/

open_locale classical big_operators

universes u v

section comm_semiring

variables {R : Type u} [comm_semiring R] (x y z : R)

/-- The proposition that `x` and `y` are coprime, defined to be the existence of `a` and `b` such
that `a * x + b * y = 1`. Note that elements with no common divisors are not necessarily coprime,
e.g., the multivariate polynomials `x₁` and `x₂` are not coprime. -/
@[simp] def is_coprime : Prop :=
∃ a b, a * x + b * y = 1

theorem nat.is_coprime_iff_coprime {m n : ℕ} : is_coprime (m : ℤ) n ↔ nat.coprime m n :=
⟨λ ⟨a, b, H⟩, nat.eq_one_of_dvd_one $ int.coe_nat_dvd.1 $ by { rw [int.coe_nat_one, ← H],
  exact dvd_add (dvd_mul_of_dvd_right (int.coe_nat_dvd.2 $ nat.gcd_dvd_left m n) _)
    (dvd_mul_of_dvd_right (int.coe_nat_dvd.2 $ nat.gcd_dvd_right m n) _) },
λ H, ⟨nat.gcd_a m n, nat.gcd_b m n, by rw [mul_comm _ (m : ℤ), mul_comm _ (n : ℤ),
    ← nat.gcd_eq_gcd_ab, show _ = _, from H, int.coe_nat_one]⟩⟩

variables {x y z}

theorem is_coprime.symm (H : is_coprime x y) : is_coprime y x :=
let ⟨a, b, H⟩ := H in ⟨b, a, by rw [add_comm, H]⟩

theorem is_coprime_comm : is_coprime x y ↔ is_coprime y x :=
⟨is_coprime.symm, is_coprime.symm⟩

theorem is_coprime_self : is_coprime x x ↔ is_unit x :=
⟨λ ⟨a, b, h⟩, is_unit_of_mul_eq_one x (a + b) $ by rwa [mul_comm, add_mul],
λ h, let ⟨b, hb⟩ := is_unit_iff_exists_inv'.1 h in ⟨b, 0, by rwa [zero_mul, add_zero]⟩⟩

theorem is_coprime_zero_left : is_coprime 0 x ↔ is_unit x :=
⟨λ ⟨a, b, H⟩, is_unit_of_mul_eq_one x b $ by rwa [mul_zero, zero_add, mul_comm] at H,
λ H, let ⟨b, hb⟩ := is_unit_iff_exists_inv'.1 H in ⟨1, b, by rwa [one_mul, zero_add]⟩⟩

theorem is_coprime_zero_right : is_coprime x 0 ↔ is_unit x :=
is_coprime_comm.trans is_coprime_zero_left

theorem is_coprime_one_left : is_coprime 1 x :=
⟨1, 0, by rw [one_mul, zero_mul, add_zero]⟩

theorem is_coprime_one_right : is_coprime x 1 :=
⟨0, 1, by rw [one_mul, zero_mul, zero_add]⟩

theorem is_coprime.dvd_of_dvd_mul_right (H1 : is_coprime x z) (H2 : x ∣ y * z) : x ∣ y :=
let ⟨a, b, H⟩ := H1 in by { rw [← mul_one y, ← H, mul_add, ← mul_assoc, mul_left_comm],
exact dvd_add (dvd_mul_left _ _) (dvd_mul_of_dvd_right H2 _) }

theorem is_coprime.dvd_of_dvd_mul_left (H1 : is_coprime x y) (H2 : x ∣ y * z) : x ∣ z :=
let ⟨a, b, H⟩ := H1 in by { rw [← one_mul z, ← H, add_mul, mul_right_comm, mul_assoc b],
exact dvd_add (dvd_mul_left _ _) (dvd_mul_of_dvd_right H2 _) }

theorem is_coprime.mul_left (H1 : is_coprime x z) (H2 : is_coprime y z) : is_coprime (x * y) z :=
let ⟨a, b, h1⟩ := H1, ⟨c, d, h2⟩ := H2 in
⟨a * c, a * x * d + b * c * y + b * d * z,
calc  a * c * (x * y) + (a * x * d + b * c * y + b * d * z) * z
    = (a * x + b * z) * (c * y + d * z) : by ring
... = 1 : by rw [h1, h2, mul_one]⟩

theorem is_coprime.mul_right (H1 : is_coprime x y) (H2 : is_coprime x z) : is_coprime x (y * z) :=
by { rw is_coprime_comm at H1 H2 ⊢, exact H1.mul_left H2 }

variables {I : Type v} {s : I → R} {t : finset I}

theorem is_coprime.prod_left : (∀ i ∈ t, is_coprime (s i) x) → is_coprime (∏ i in t, s i) x :=
finset.induction_on t (λ _, is_coprime_one_left) $ λ b t hbt ih H,
by { rw finset.prod_insert hbt, rw finset.forall_mem_insert at H, exact H.1.mul_left (ih H.2) }

theorem is_coprime.prod_right : (∀ i ∈ t, is_coprime x (s i)) → is_coprime x (∏ i in t, s i) :=
by simpa only [is_coprime_comm] using is_coprime.prod_left

theorem is_coprime.mul_dvd (H : is_coprime x y) (H1 : x ∣ z) (H2 : y ∣ z) : x * y ∣ z :=
begin
  obtain ⟨a, b, h⟩ := H,
  rw [← mul_one z, ← h, mul_add],
  apply dvd_add,
  { rw [mul_comm z, mul_assoc],
    exact dvd_mul_of_dvd_right (mul_dvd_mul_left _ H2) _ },
  { rw [mul_comm b, ← mul_assoc],
    exact dvd_mul_of_dvd_left (mul_dvd_mul_right H1 _) _ }
end

theorem finset.prod_dvd_of_coprime :
  ∀ (Hs : set.pairwise_on (↑t : set I) (is_coprime on s)) (Hs1 : ∀ i ∈ t, s i ∣ z),
  ∏ x in t, s x ∣ z :=
finset.induction_on t (λ _ _, one_dvd z)
begin
  intros a r har ih Hs Hs1,
  rw finset.prod_insert har,
  have aux1 : a ∈ (↑(insert a r) : set I) := finset.mem_insert_self a r,
  refine (is_coprime.prod_right $ λ i hir, Hs a aux1 i _ (by { rintro rfl, exact har hir })).mul_dvd
    (Hs1 a aux1) (ih (Hs.mono _) $ λ i hi, Hs1 i (finset.mem_insert_of_mem hi)),
  { exact finset.mem_insert_of_mem hir },
  { simp only [finset.coe_insert, set.subset_insert] }
end

theorem fintype.prod_dvd_of_coprime [fintype I] (Hs : pairwise (is_coprime on s))
  (Hs1 : ∀ i, s i ∣ z) : ∏ x, s x ∣ z :=
finset.prod_dvd_of_coprime (Hs.pairwise_on _) (λ i _, Hs1 i)

theorem is_coprime.of_mul_left_left (H : is_coprime (x * y) z) : is_coprime x z :=
let ⟨a, b, h⟩ := H in ⟨a * y, b, by rwa [mul_right_comm, mul_assoc]⟩

theorem is_coprime.of_mul_left_right (H : is_coprime (x * y) z) : is_coprime y z :=
by { rw mul_comm at H, exact H.of_mul_left_left }

theorem is_coprime.of_mul_right_left (H : is_coprime x (y * z)) : is_coprime x y :=
by { rw is_coprime_comm at H ⊢, exact H.of_mul_left_left }

theorem is_coprime.of_mul_right_right (H : is_coprime x (y * z)) : is_coprime x z :=
by { rw mul_comm at H, exact H.of_mul_right_left }

theorem is_coprime.mul_left_iff : is_coprime (x * y) z ↔ is_coprime x z ∧ is_coprime y z :=
⟨λ H, ⟨H.of_mul_left_left, H.of_mul_left_right⟩, λ ⟨H1, H2⟩, H1.mul_left H2⟩

theorem is_coprime.mul_right_iff : is_coprime x (y * z) ↔ is_coprime x y ∧ is_coprime x z :=
by rw [is_coprime_comm, is_coprime.mul_left_iff, is_coprime_comm, @is_coprime_comm _ _ z]

theorem is_coprime.prod_left_iff : is_coprime (∏ i in t, s i) x ↔ ∀ i ∈ t, is_coprime (s i) x :=
finset.induction_on t (iff_of_true is_coprime_one_left $ λ _, false.elim) $ λ b t hbt ih,
by rw [finset.prod_insert hbt, is_coprime.mul_left_iff, ih, finset.forall_mem_insert]

theorem is_coprime.prod_right_iff : is_coprime x (∏ i in t, s i) ↔ ∀ i ∈ t, is_coprime x (s i) :=
by simpa only [is_coprime_comm] using is_coprime.prod_left_iff

theorem is_coprime.of_prod_left (H1 : is_coprime (∏ i in t, s i) x) (i : I) (hit : i ∈ t) :
  is_coprime (s i) x :=
is_coprime.prod_left_iff.1 H1 i hit

theorem is_coprime.of_prod_right (H1 : is_coprime x (∏ i in t, s i)) (i : I) (hit : i ∈ t) :
  is_coprime x (s i) :=
is_coprime.prod_right_iff.1 H1 i hit

variables {m n : ℕ}

theorem is_coprime.pow_left (H : is_coprime x y) : is_coprime (x ^ m) y :=
by { rw [← finset.card_range m, ← finset.prod_const], exact is_coprime.prod_left (λ _ _, H) }

theorem is_coprime.pow_right (H : is_coprime x y) : is_coprime x (y ^ n) :=
by { rw [← finset.card_range n, ← finset.prod_const], exact is_coprime.prod_right (λ _ _, H) }

theorem is_coprime.pow (H : is_coprime x y) : is_coprime (x ^ m) (y ^ n) :=
H.pow_left.pow_right

theorem is_coprime.is_unit_of_dvd (H : is_coprime x y) (d : x ∣ y) : is_unit x :=
let ⟨k, hk⟩ := d in is_coprime_self.1 $ is_coprime.of_mul_right_left $
show is_coprime x (x * k), from hk ▸ H

theorem is_coprime.map (H : is_coprime x y) {S : Type v} [comm_semiring S] (f : R →+* S) :
  is_coprime (f x) (f y) :=
let ⟨a, b, h⟩ := H in ⟨f a, f b, by rw [← f.map_mul, ← f.map_mul, ← f.map_add, h, f.map_one]⟩

variables {x y z}

lemma is_coprime.of_add_mul_left_left (h : is_coprime (x + y * z) y) : is_coprime x y :=
let ⟨a, b, H⟩ := h in ⟨a, a * z + b, by simpa only [add_mul, mul_add,
    add_assoc, add_comm, add_left_comm, mul_assoc, mul_comm, mul_left_comm] using H⟩

lemma is_coprime.of_add_mul_right_left (h : is_coprime (x + z * y) y) : is_coprime x y :=
by { rw mul_comm at h, exact h.of_add_mul_left_left }

lemma is_coprime.of_add_mul_left_right (h : is_coprime x (y + x * z)) : is_coprime x y :=
by { rw is_coprime_comm at h ⊢, exact h.of_add_mul_left_left }

lemma is_coprime.of_add_mul_right_right (h : is_coprime x (y + z * x)) : is_coprime x y :=
by { rw mul_comm at h, exact h.of_add_mul_left_right }

lemma is_coprime.of_mul_add_left_left (h : is_coprime (y * z + x) y) : is_coprime x y :=
by { rw add_comm at h, exact h.of_add_mul_left_left }

lemma is_coprime.of_mul_add_right_left (h : is_coprime (z * y + x) y) : is_coprime x y :=
by { rw add_comm at h, exact h.of_add_mul_right_left }

lemma is_coprime.of_mul_add_left_right (h : is_coprime x (x * z + y)) : is_coprime x y :=
by { rw add_comm at h, exact h.of_add_mul_left_right }

lemma is_coprime.of_mul_add_right_right (h : is_coprime x (z * x + y)) : is_coprime x y :=
by { rw add_comm at h, exact h.of_add_mul_right_right }

end comm_semiring

namespace is_coprime

section comm_ring

variables {R : Type u} [comm_ring R]

lemma add_mul_left_left {x y : R} (h : is_coprime x y) (z : R) : is_coprime (x + y * z) y :=
@of_add_mul_left_left R _ _ _ (-z) $
by simpa only [mul_neg_eq_neg_mul_symm, add_neg_cancel_right] using h

lemma add_mul_right_left {x y : R} (h : is_coprime x y) (z : R) : is_coprime (x + z * y) y :=
by { rw mul_comm, exact h.add_mul_left_left z }

lemma add_mul_left_right {x y : R} (h : is_coprime x y) (z : R) : is_coprime x (y + x * z) :=
by { rw is_coprime_comm, exact h.symm.add_mul_left_left z }

lemma add_mul_right_right {x y : R} (h : is_coprime x y) (z : R) : is_coprime x (y + z * x) :=
by { rw is_coprime_comm, exact h.symm.add_mul_right_left z }

lemma mul_add_left_left {x y : R} (h : is_coprime x y) (z : R) : is_coprime (y * z + x) y :=
by { rw add_comm, exact h.add_mul_left_left z }

lemma mul_add_right_left {x y : R} (h : is_coprime x y) (z : R) : is_coprime (z * y + x) y :=
by { rw add_comm, exact h.add_mul_right_left z }

lemma mul_add_left_right {x y : R} (h : is_coprime x y) (z : R) : is_coprime x (x * z + y) :=
by { rw add_comm, exact h.add_mul_left_right z }

lemma mul_add_right_right {x y : R} (h : is_coprime x y) (z : R) : is_coprime x (z * x + y) :=
by { rw add_comm, exact h.add_mul_right_right z }

lemma add_mul_left_left_iff {x y z : R} : is_coprime (x + y * z) y ↔ is_coprime x y :=
⟨of_add_mul_left_left, λ h, h.add_mul_left_left z⟩

lemma add_mul_right_left_iff {x y z : R} : is_coprime (x + z * y) y ↔ is_coprime x y :=
⟨of_add_mul_right_left, λ h, h.add_mul_right_left z⟩

lemma add_mul_left_right_iff {x y z : R} : is_coprime x (y + x * z) ↔ is_coprime x y :=
⟨of_add_mul_left_right, λ h, h.add_mul_left_right z⟩

lemma add_mul_right_right_iff {x y z : R} : is_coprime x (y + z * x) ↔ is_coprime x y :=
⟨of_add_mul_right_right, λ h, h.add_mul_right_right z⟩

lemma mul_add_left_left_iff {x y z : R} : is_coprime (y * z + x) y ↔ is_coprime x y :=
⟨of_mul_add_left_left, λ h, h.mul_add_left_left z⟩

lemma mul_add_right_left_iff {x y z : R} : is_coprime (z * y + x) y ↔ is_coprime x y :=
⟨of_mul_add_right_left, λ h, h.mul_add_right_left z⟩

lemma mul_add_left_right_iff {x y z : R} : is_coprime x (x * z + y) ↔ is_coprime x y :=
⟨of_mul_add_left_right, λ h, h.mul_add_left_right z⟩

lemma mul_add_right_right_iff {x y z : R} : is_coprime x (z * x + y) ↔ is_coprime x y :=
⟨of_mul_add_right_right, λ h, h.mul_add_right_right z⟩

end comm_ring

end is_coprime


/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Scott Morrison

Case bashing on variables in finite intervals.

In particular, `interval_cases n`
1) inspects hypotheses looking for lower and upper bounds of the form `a ≤ n` and `n < b`
   (although in `ℕ`, `ℤ`, and `ℕ+` bounds of the form `a < n` and `n ≤ b` are also allowed),
   and also makes use of lower and upper bounds found via `le_top` and `bot_le`
   (so for example if `n : ℕ`, then the bound `0 ≤ n` is found automatically), then
2) calls `fin_cases` on the synthesised hypothesis `n ∈ set.Ico a b`,
   assuming an appropriate `fintype` instance can be found for the type of `n`.

The variable `n` can belong to any type `α`, with the following restrictions:
* only bounds on which `expr.to_rat` succeeds will be considered "explicit" (TODO: generalise this?)
* an instance of `decidable_eq α` is available,
* an explicit lower bound can be found amongst the hypotheses, or from `bot_le n`,
* an explicit upper bound can be found amongst the hypotheses, or from `le_top n`,
* if multiple bounds are located, an instance of `linear_order α` is available, and
* an instance of `fintype set.Ico l u` is available for the relevant bounds.

You can also explicitly specify a lower and upper bound to use, as `interval_cases using hl hu`.
The hypotheses should be in the form `hl : a ≤ n` and `hu : n < b`,
in which case `interval_cases` calls `fin_cases` on the resulting fact `n ∈ set.Ico a b`.

-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.tactic.fin_cases
import Mathlib.data.fintype.intervals
import Mathlib.PostPort

universes u_1

namespace Mathlib

namespace tactic

namespace interval_cases

/--
If `e` easily implies `(%%n < %%b)`
for some explicit `b`,
return that proof.
-/
-- We use `expr.to_rat` merely to decide if an `expr` is an explicit number.

-- It would be more natural to use `expr.to_int`, but that hasn't been implemented.

/--
If `e` easily implies `(%%n ≥ %%b)`
for some explicit `b`,
return that proof.
-/
/-- Combine two upper bounds. -/
/-- Combine two lower bounds. -/
/-- Inspect a given expression, using it to update a set of upper and lower bounds on `n`. -/
/--
Attempt to find a lower bound for the variable `n`, by evaluating `bot_le n`.
-/
/--
Attempt to find an upper bound for the variable `n`, by evaluating `le_top n`.
-/
/-- Inspect the local hypotheses for upper and lower bounds on a variable `n`. -/
/-- The finset of elements of a set `s` for which we have `fintype s`. -/
def set_elems {α : Type u_1} [DecidableEq α] (s : set α) [fintype ↥s] : finset α :=
  finset.image subtype.val (fintype.elems ↥s)

/-- Each element of `s` is a member of `set_elems s`. -/
theorem mem_set_elems {α : Type u_1} [DecidableEq α] (s : set α) [fintype ↥s] {a : α} (h : a ∈ s) : a ∈ set_elems s :=
  iff.mpr finset.mem_image
    (Exists.intro { val := a, property := h } (Exists.intro (fintype.complete { val := a, property := h }) rfl))

end interval_cases

/-- Call `fin_cases` on membership of the finset built from
an `Ico` interval corresponding to a lower and an upper bound.

Here `hl` should be an expression of the form `a ≤ n`, for some explicit `a`, and
`hu` should be of the form `n < b`, for some explicit `b`.

By default `interval_cases_using` automatically generates a name for the new hypothesis. The name can be specified via the optional argument `n`.
-/
namespace interactive

/--
`interval_cases n` searches for upper and lower bounds on a variable `n`,
and if bounds are found,
splits into separate cases for each possible value of `n`.

As an example, in
```
example (n : ℕ) (w₁ : n ≥ 3) (w₂ : n < 5) : n = 3 ∨ n = 4 :=
begin
  interval_cases n,
  all_goals {simp}
end
```
after `interval_cases n`, the goals are `3 = 3 ∨ 3 = 4` and `4 = 3 ∨ 4 = 4`.

You can also explicitly specify a lower and upper bound to use,
as `interval_cases using hl hu`.
The hypotheses should be in the form `hl : a ≤ n` and `hu : n < b`,
in which case `interval_cases` calls `fin_cases` on the resulting fact `n ∈ set.Ico a b`.

You can specify a name `h` for the new hypothesis,
as `interval_cases n with h` or `interval_cases n using hl hu with h`.
-/
/--
`interval_cases n` searches for upper and lower bounds on a variable `n`,


import analysis.special_functions.exp_log

namespace real

lemma one_of_log_zero_of_pos : ∀ (r : ℝ), 0 < r → real.log(r) = 0 → r = 1 :=
begin
  intros r hr1 hr2,
  calc r = exp(log(r))  : by exact (exp_log hr1).symm
  ... = exp(0)          : by rw hr2
  ... = 1               : by rw exp_zero,
end

lemma zero_or_one_of_log_zero_if_nonneg : ∀ (r : ℝ), 0 ≤ r → log(r) = 0 → r = 0 ∨ r = 1 :=
begin
  intros r hr logr,
  replace hr := le_iff_lt_or_eq.mp hr,
  cases hr with r1 r0,
  right,
  exact one_of_log_zero_of_pos r r1 logr,
  left,
  linarith,
end

lemma log_inv_nonneg : ∀ (r : ℝ), 0 ≤ r → r ≤ 1 → 0 ≤ log(r⁻¹) :=
begin
  intros r hr,
  norm_num at *,
  apply log_nonpos hr,
end

lemma r_neq_zero_one : ∀ (r : ℝ), r ≠ 0 ∧ r ≠ 1 → r < 0 ∨ (0 < r ∧ r < 1) ∨ 1 < r :=
begin
  intros r hr,
  by_contradiction,
  push_neg at h,
  cases h with r0 hright,
  cases hright with r01 r1,
  finish,
end

lemma zero_or_pm_one_of_log_zero : ∀ (r : ℝ), log(r) = 0 → r = -1 ∨ r = 0 ∨ r = 1 :=
begin
  intros r log0,
  -- case r < 0
  have r_sign : r < 0 ∨ 0 ≤ r, {by exact lt_or_ge r 0,},
  cases r_sign with r_neg r_nonneg,
  rw ← log_neg_eq_log at log0,
  have r_sign' : 0 ≤ -r, {by linarith,},
  have h' : -r = 0 ∨ -r = 1, {by exact zero_or_one_of_log_zero_if_nonneg (-r) r_sign' log0,},
  left,
  cases h' with r0 r1,
  exfalso,
  linarith,
  linarith,
  -- case r ≥ 0, proved by zero_or_one_of_log_zero_if_nonneg
  right,
  exact zero_or_one_of_log_zero_if_nonneg r r_nonneg log0,
end

end real


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Johannes Hölzl, Scott Morrison, Jens Wagemaker

! This file was ported from Lean 3 source module data.polynomial.integral_normalization
! leanprover-community/mathlib commit 10bf4f825ad729c5653adc039dafa3622e7f93c9
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Polynomial.AlgebraMap
import Mathbin.Data.Polynomial.Degree.Lemmas
import Mathbin.Data.Polynomial.Monic

/-!
# Theory of monic polynomials

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

We define `integral_normalization`, which relate arbitrary polynomials to monic ones.
-/

open BigOperators Polynomial

namespace Polynomial

universe u v y

variable {R : Type u} {S : Type v} {a b : R} {m n : ℕ} {ι : Type y}

section IntegralNormalization

section Semiring

variable [Semiring R]

#print Polynomial.integralNormalization /-
/-- If `f : R[X]` is a nonzero polynomial with root `z`, `integral_normalization f` is
a monic polynomial with root `leading_coeff f * z`.

Moreover, `integral_normalization 0 = 0`.
-/
noncomputable def integralNormalization (f : R[X]) : R[X] :=
  ∑ i in f.support,
    monomial i (if f.degree = i then 1 else coeff f i * f.leadingCoeff ^ (f.natDegree - 1 - i))
#align polynomial.integral_normalization Polynomial.integralNormalization
-/

#print Polynomial.integralNormalization_zero /-
@[simp]
theorem integralNormalization_zero : integralNormalization (0 : R[X]) = 0 := by
  simp [integral_normalization]
#align polynomial.integral_normalization_zero Polynomial.integralNormalization_zero
-/

/- warning: polynomial.integral_normalization_coeff -> Polynomial.integralNormalization_coeff is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : Semiring.{u1} R] {f : Polynomial.{u1} R _inst_1} {i : Nat}, Eq.{succ u1} R (Polynomial.coeff.{u1} R _inst_1 (Polynomial.integralNormalization.{u1} R _inst_1 f) i) (ite.{succ u1} R (Eq.{1} (WithBot.{0} Nat) (Polynomial.degree.{u1} R _inst_1 f) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat (WithBot.{0} Nat) (HasLiftT.mk.{1, 1} Nat (WithBot.{0} Nat) (CoeTCₓ.coe.{1, 1} Nat (WithBot.{0} Nat) (WithBot.hasCoeT.{0} Nat))) i)) (Option.decidableEq.{0} Nat (fun (a : Nat) (b : Nat) => Nat.decidableEq a b) (Polynomial.degree.{u1} R _inst_1 f) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat (WithBot.{0} Nat) (HasLiftT.mk.{1, 1} Nat (WithBot.{0} Nat) (CoeTCₓ.coe.{1, 1} Nat (WithBot.{0} Nat) (WithBot.hasCoeT.{0} Nat))) i)) (OfNat.ofNat.{u1} R 1 (OfNat.mk.{u1} R 1 (One.one.{u1} R (AddMonoidWithOne.toOne.{u1} R (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} R (NonAssocSemiring.toAddCommMonoidWithOne.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))))) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))) (Polynomial.coeff.{u1} R _inst_1 f i) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) (Polynomial.leadingCoeff.{u1} R _inst_1 f) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) (Polynomial.natDegree.{u1} R _inst_1 f) (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))) i))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : Semiring.{u1} R] {f : Polynomial.{u1} R _inst_1} {i : Nat}, Eq.{succ u1} R (Polynomial.coeff.{u1} R _inst_1 (Polynomial.integralNormalization.{u1} R _inst_1 f) i) (ite.{succ u1} R (Eq.{1} (WithBot.{0} Nat) (Polynomial.degree.{u1} R _inst_1 f) (Nat.cast.{0} (WithBot.{0} Nat) (Semiring.toNatCast.{0} (WithBot.{0} Nat) (OrderedSemiring.toSemiring.{0} (WithBot.{0} Nat) (OrderedCommSemiring.toOrderedSemiring.{0} (WithBot.{0} Nat) (WithBot.orderedCommSemiring.{0} Nat (fun (a : Nat) (b : Nat) => instDecidableEqNat a b) Nat.canonicallyOrderedCommSemiring Nat.nontrivial)))) i)) (WithBot.instDecidableEqWithBot.{0} Nat (fun (a : Nat) (b : Nat) => instDecidableEqNat a b) (Polynomial.degree.{u1} R _inst_1 f) (Nat.cast.{0} (WithBot.{0} Nat) (Semiring.toNatCast.{0} (WithBot.{0} Nat) (OrderedSemiring.toSemiring.{0} (WithBot.{0} Nat) (OrderedCommSemiring.toOrderedSemiring.{0} (WithBot.{0} Nat) (WithBot.orderedCommSemiring.{0} Nat (fun (a : Nat) (b : Nat) => instDecidableEqNat a b) Nat.canonicallyOrderedCommSemiring Nat.nontrivial)))) i)) (OfNat.ofNat.{u1} R 1 (One.toOfNat1.{u1} R (Semiring.toOne.{u1} R _inst_1))) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1)))) (Polynomial.coeff.{u1} R _inst_1 f i) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) (Polynomial.leadingCoeff.{u1} R _inst_1 f) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) (Polynomial.natDegree.{u1} R _inst_1 f) (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) i))))
Case conversion may be inaccurate. Consider using '#align polynomial.integral_normalization_coeff Polynomial.integralNormalization_coeffₓ'. -/
theorem integralNormalization_coeff {f : R[X]} {i : ℕ} :
    (integralNormalization f).coeff i =
      if f.degree = i then 1 else coeff f i * f.leadingCoeff ^ (f.natDegree - 1 - i) :=
  by
  have : f.coeff i = 0 → f.degree ≠ i := fun hc hd => coeff_ne_zero_of_eq_degree hd hc
  simp (config := { contextual := true }) [integral_normalization, coeff_monomial, this,
    mem_support_iff]
#align polynomial.integral_normalization_coeff Polynomial.integralNormalization_coeff

#print Polynomial.integralNormalization_support /-
theorem integralNormalization_support {f : R[X]} : (integralNormalization f).support ⊆ f.support :=
  by
  intro
  simp (config := { contextual := true }) [integral_normalization, coeff_monomial, mem_support_iff]
#align polynomial.integral_normalization_support Polynomial.integralNormalization_support
-/

#print Polynomial.integralNormalization_coeff_degree /-
theorem integralNormalization_coeff_degree {f : R[X]} {i : ℕ} (hi : f.degree = i) :
    (integralNormalization f).coeff i = 1 := by rw [integral_normalization_coeff, if_pos hi]
#align polynomial.integral_normalization_coeff_degree Polynomial.integralNormalization_coeff_degree
-/

#print Polynomial.integralNormalization_coeff_natDegree /-
theorem integralNormalization_coeff_natDegree {f : R[X]} (hf : f ≠ 0) :
    (integralNormalization f).coeff (natDegree f) = 1 :=
  integralNormalization_coeff_degree (degree_eq_natDegree hf)
#align polynomial.integral_normalization_coeff_nat_degree Polynomial.integralNormalization_coeff_natDegree
-/

/- warning: polynomial.integral_normalization_coeff_ne_degree -> Polynomial.integralNormalization_coeff_ne_degree is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : Semiring.{u1} R] {f : Polynomial.{u1} R _inst_1} {i : Nat}, (Ne.{1} (WithBot.{0} Nat) (Polynomial.degree.{u1} R _inst_1 f) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat (WithBot.{0} Nat) (HasLiftT.mk.{1, 1} Nat (WithBot.{0} Nat) (CoeTCₓ.coe.{1, 1} Nat (WithBot.{0} Nat) (WithBot.hasCoeT.{0} Nat))) i)) -> (Eq.{succ u1} R (Polynomial.coeff.{u1} R _inst_1 (Polynomial.integralNormalization.{u1} R _inst_1 f) i) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))) (Polynomial.coeff.{u1} R _inst_1 f i) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) (Polynomial.leadingCoeff.{u1} R _inst_1 f) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) (Polynomial.natDegree.{u1} R _inst_1 f) (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))) i))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : Semiring.{u1} R] {f : Polynomial.{u1} R _inst_1} {i : Nat}, (Ne.{1} (WithBot.{0} Nat) (Polynomial.degree.{u1} R _inst_1 f) (Nat.cast.{0} (WithBot.{0} Nat) (Semiring.toNatCast.{0} (WithBot.{0} Nat) (OrderedSemiring.toSemiring.{0} (WithBot.{0} Nat) (OrderedCommSemiring.toOrderedSemiring.{0} (WithBot.{0} Nat) (WithBot.orderedCommSemiring.{0} Nat (fun (a : Nat) (b : Nat) => instDecidableEqNat a b) Nat.canonicallyOrderedCommSemiring Nat.nontrivial)))) i)) -> (Eq.{succ u1} R (Polynomial.coeff.{u1} R _inst_1 (Polynomial.integralNormalization.{u1} R _inst_1 f) i) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1)))) (Polynomial.coeff.{u1} R _inst_1 f i) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) (Polynomial.leadingCoeff.{u1} R _inst_1 f) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) (Polynomial.natDegree.{u1} R _inst_1 f) (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) i))))
Case conversion may be inaccurate. Consider using '#align polynomial.integral_normalization_coeff_ne_degree Polynomial.integralNormalization_coeff_ne_degreeₓ'. -/
theorem integralNormalization_coeff_ne_degree {f : R[X]} {i : ℕ} (hi : f.degree ≠ i) :
    coeff (integralNormalization f) i = coeff f i * f.leadingCoeff ^ (f.natDegree - 1 - i) := by
  rw [integral_normalization_coeff, if_neg hi]
#align polynomial.integral_normalization_coeff_ne_degree Polynomial.integralNormalization_coeff_ne_degree

/- warning: polynomial.integral_normalization_coeff_ne_nat_degree -> Polynomial.integralNormalization_coeff_ne_natDegree is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : Semiring.{u1} R] {f : Polynomial.{u1} R _inst_1} {i : Nat}, (Ne.{1} Nat i (Polynomial.natDegree.{u1} R _inst_1 f)) -> (Eq.{succ u1} R (Polynomial.coeff.{u1} R _inst_1 (Polynomial.integralNormalization.{u1} R _inst_1 f) i) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))) (Polynomial.coeff.{u1} R _inst_1 f i) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) (Polynomial.leadingCoeff.{u1} R _inst_1 f) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) (Polynomial.natDegree.{u1} R _inst_1 f) (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))) i))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : Semiring.{u1} R] {f : Polynomial.{u1} R _inst_1} {i : Nat}, (Ne.{1} Nat i (Polynomial.natDegree.{u1} R _inst_1 f)) -> (Eq.{succ u1} R (Polynomial.coeff.{u1} R _inst_1 (Polynomial.integralNormalization.{u1} R _inst_1 f) i) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1)))) (Polynomial.coeff.{u1} R _inst_1 f i) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) (Polynomial.leadingCoeff.{u1} R _inst_1 f) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) (Polynomial.natDegree.{u1} R _inst_1 f) (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) i))))
Case conversion may be inaccurate. Consider using '#align polynomial.integral_normalization_coeff_ne_nat_degree Polynomial.integralNormalization_coeff_ne_natDegreeₓ'. -/
theorem integralNormalization_coeff_ne_natDegree {f : R[X]} {i : ℕ} (hi : i ≠ natDegree f) :
    coeff (integralNormalization f) i = coeff f i * f.leadingCoeff ^ (f.natDegree - 1 - i) :=
  integralNormalization_coeff_ne_degree (degree_ne_of_natDegree_ne hi.symm)
#align polynomial.integral_normalization_coeff_ne_nat_degree Polynomial.integralNormalization_coeff_ne_natDegree

#print Polynomial.monic_integralNormalization /-
theorem monic_integralNormalization {f : R[X]} (hf : f ≠ 0) : Monic (integralNormalization f) :=
  monic_of_degree_le f.natDegree
    (Finset.sup_le fun i h =>
      WithBot.coe_le_coe.2 <| le_natDegree_of_mem_supp i <| integralNormalization_support h)
    (integralNormalization_coeff_natDegree hf)
#align polynomial.monic_integral_normalization Polynomial.monic_integralNormalization
-/

end Semiring

section IsDomain

variable [Ring R] [IsDomain R]

#print Polynomial.support_integralNormalization /-
@[simp]
theorem support_integralNormalization {f : R[X]} : (integralNormalization f).support = f.support :=
  by
  by_cases hf : f = 0; · simp [hf]
  ext i
  refine' ⟨fun h => integral_normalization_support h, _⟩
  simp only [integral_normalization_coeff, mem_support_iff]
  intro hfi
  split_ifs with hi <;> simp [hfi, hi, pow_ne_zero _ (leading_coeff_ne_zero.mpr hf)]
#align polynomial.support_integral_normalization Polynomial.support_integralNormalization
-/

end IsDomain

section IsDomain

variable [CommRing R] [IsDomain R]

variable [CommSemiring S]

/- warning: polynomial.integral_normalization_eval₂_eq_zero -> Polynomial.integralNormalization_eval₂_eq_zero is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} {S : Type.{u2}} [_inst_1 : CommRing.{u1} R] [_inst_2 : IsDomain.{u1} R (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1))] [_inst_3 : CommSemiring.{u2} S] {p : Polynomial.{u1} R (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1))} (f : RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) {z : S}, (Eq.{succ u2} S (Polynomial.eval₂.{u1, u2} R S (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1)) (CommSemiring.toSemiring.{u2} S _inst_3) f z p) (OfNat.ofNat.{u2} S 0 (OfNat.mk.{u2} S 0 (Zero.zero.{u2} S (MulZeroClass.toHasZero.{u2} S (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} S (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))))))))) -> (forall (x : R), (Eq.{succ u2} S (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) (fun (_x : RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) => R -> S) (RingHom.hasCoeToFun.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) f x) (OfNat.ofNat.{u2} S 0 (OfNat.mk.{u2} S 0 (Zero.zero.{u2} S (MulZeroClass.toHasZero.{u2} S (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} S (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))))))))) -> (Eq.{succ u1} R x (OfNat.ofNat.{u1} R 0 (OfNat.mk.{u1} R 0 (Zero.zero.{u1} R (MulZeroClass.toHasZero.{u1} R (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))))))))))) -> (Eq.{succ u2} S (Polynomial.eval₂.{u1, u2} R S (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1)) (CommSemiring.toSemiring.{u2} S _inst_3) f (HMul.hMul.{u2, u2, u2} S S S (instHMul.{u2} S (Distrib.toHasMul.{u2} S (NonUnitalNonAssocSemiring.toDistrib.{u2} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} S (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3)))))) z (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) (fun (_x : RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) => R -> S) (RingHom.hasCoeToFun.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) f (Polynomial.leadingCoeff.{u1} R (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1)) p))) (Polynomial.integralNormalization.{u1} R (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1)) p)) (OfNat.ofNat.{u2} S 0 (OfNat.mk.{u2} S 0 (Zero.zero.{u2} S (MulZeroClass.toHasZero.{u2} S (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} S (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3)))))))))
but is expected to have type
  forall {R : Type.{u1}} {S : Type.{u2}} [_inst_1 : CommRing.{u1} R] [_inst_2 : IsDomain.{u1} R (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1))] [_inst_3 : CommSemiring.{u2} S] {p : Polynomial.{u1} R (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1))} (f : RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) {z : S}, (Eq.{succ u2} S (Polynomial.eval₂.{u1, u2} R S (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1)) (CommSemiring.toSemiring.{u2} S _inst_3) f z p) (OfNat.ofNat.{u2} S 0 (Zero.toOfNat0.{u2} S (CommMonoidWithZero.toZero.{u2} S (CommSemiring.toCommMonoidWithZero.{u2} S _inst_3))))) -> (forall (x : R), (Eq.{succ u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : R) => S) x) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) R (fun (_x : R) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : R) => S) _x) (MulHomClass.toFunLike.{max u1 u2, u1, u2} (RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) R S (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))))) (NonUnitalNonAssocSemiring.toMul.{u2} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} S (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3)))) (NonUnitalRingHomClass.toMulHomClass.{max u1 u2, u1, u2} (RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) R S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1)))) (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} S (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) (RingHomClass.toNonUnitalRingHomClass.{max u1 u2, u1, u2} (RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3)) (RingHom.instRingHomClassRingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3)))))) f x) (OfNat.ofNat.{u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : R) => S) x) 0 (Zero.toOfNat0.{u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : R) => S) x) (CommMonoidWithZero.toZero.{u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : R) => S) x) (CommSemiring.toCommMonoidWithZero.{u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : R) => S) x) _inst_3))))) -> (Eq.{succ u1} R x (OfNat.ofNat.{u1} R 0 (Zero.toOfNat0.{u1} R (CommMonoidWithZero.toZero.{u1} R (CancelCommMonoidWithZero.toCommMonoidWithZero.{u1} R (IsDomain.toCancelCommMonoidWithZero.{u1} R (CommRing.toCommSemiring.{u1} R _inst_1) _inst_2))))))) -> (Eq.{succ u2} S (Polynomial.eval₂.{u1, u2} R S (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1)) (CommSemiring.toSemiring.{u2} S _inst_3) f (HMul.hMul.{u2, u2, u2} S ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : R) => S) (Polynomial.leadingCoeff.{u1} R (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1)) p)) S (instHMul.{u2} S (NonUnitalNonAssocSemiring.toMul.{u2} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} S (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))))) z (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) R (fun (_x : R) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : R) => S) _x) (MulHomClass.toFunLike.{max u1 u2, u1, u2} (RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) R S (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))))) (NonUnitalNonAssocSemiring.toMul.{u2} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} S (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3)))) (NonUnitalRingHomClass.toMulHomClass.{max u1 u2, u1, u2} (RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) R S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1)))) (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} S (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) (RingHomClass.toNonUnitalRingHomClass.{max u1 u2, u1, u2} (RingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3))) R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3)) (RingHom.instRingHomClassRingHom.{u1, u2} R S (NonAssocRing.toNonAssocSemiring.{u1} R (Ring.toNonAssocRing.{u1} R (CommRing.toRing.{u1} R _inst_1))) (Semiring.toNonAssocSemiring.{u2} S (CommSemiring.toSemiring.{u2} S _inst_3)))))) f (Polynomial.leadingCoeff.{u1} R (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1)) p))) (Polynomial.integralNormalization.{u1} R (Ring.toSemiring.{u1} R (CommRing.toRing.{u1} R _inst_1)) p)) (OfNat.ofNat.{u2} S 0 (Zero.toOfNat0.{u2} S (CommMonoidWithZero.toZero.{u2} S (CommSemiring.toCommMonoidWithZero.{u2} S _inst_3)))))
Case conversion may be inaccurate. Consider using '#align polynomial.integral_normalization_eval₂_eq_zero Polynomial.integralNormalization_eval₂_eq_zeroₓ'. -/
theorem integralNormalization_eval₂_eq_zero {p : R[X]} (f : R →+* S) {z : S} (hz : eval₂ f z p = 0)
    (inj : ∀ x : R, f x = 0 → x = 0) :
    eval₂ f (z * f p.leadingCoeff) (integralNormalization p) = 0 :=
  calc
    eval₂ f (z * f p.leadingCoeff) (integralNormalization p) =
        p.support.attach.Sum fun i =>
          f (coeff (integralNormalization p) i.1 * p.leadingCoeff ^ i.1) * z ^ i.1 :=
      by
      rw [eval₂, sum_def, support_integral_normalization]
      simp only [mul_comm z, mul_pow, mul_assoc, RingHom.map_pow, RingHom.map_mul]
      exact finset.sum_attach.symm
    _ =
        p.support.attach.Sum fun i =>
          f (coeff p i.1 * p.leadingCoeff ^ (natDegree p - 1)) * z ^ i.1 :=
      by
      by_cases hp : p = 0; · simp [hp]
      have one_le_deg : 1 ≤ nat_degree p :=
        Nat.succ_le_of_lt (nat_degree_pos_of_eval₂_root hp f hz inj)
      congr with i
      congr 2
      by_cases hi : i.1 = nat_degree p
      · rw [hi, integral_normalization_coeff_degree, one_mul, leading_coeff, ← pow_succ,
          tsub_add_cancel_of_le one_le_deg]
        exact degree_eq_nat_degree hp
      · have : i.1 ≤ p.nat_degree - 1 :=
          Nat.le_pred_of_lt (lt_of_le_of_ne (le_nat_degree_of_ne_zero (mem_support_iff.mp i.2)) hi)
        rw [integral_normalization_coeff_ne_nat_degree hi, mul_assoc, ← pow_add,
          tsub_add_cancel_of_le this]
    _ = f p.leadingCoeff ^ (natDegree p - 1) * eval₂ f z p :=
      by
      simp_rw [eval₂, sum_def, fun i => mul_comm (coeff p i), RingHom.map_mul, RingHom.map_pow,
        mul_assoc, ← Finset.mul_sum]
      congr 1
      exact @Finset.sum_attach _ _ p.support _ fun i => f (p.coeff i) * z ^ i
    _ = 0 := by rw [hz, _root_.mul_zero]

#align polynomial.integral_normalization_eval₂_eq_zero Polynomial.integralNormalization_eval₂_eq_zero

#print Polynomial.integralNormalization_aeval_eq_zero /-
theorem integralNormalization_aeval_eq_zero [Algebra R S] {f : R[X]} {z : S} (hz : aeval z f = 0)
    (inj : ∀ x : R, algebraMap R S x = 0 → x = 0) :
    aeval (z * algebraMap R S f.leadingCoeff) (integralNormalization f) = 0 :=
  integralNormalization_eval₂_eq_zero (algebraMap R S) hz inj
#align polynomial.integral_normalization_aeval_eq_zero Polynomial.integralNormalization_aeval_eq_zero
-/

end IsDomain

end IntegralNormalization

end Polynomial


import data.nat.modeq data.nat.basic data.nat.prime data.int.basic tactic.norm_num tactic.ring
def int.prime : ℤ → Prop := λ x,nat.prime (int.nat_abs x)

theorem prime_dvd_mul {p m n : ℤ} (pp : int.prime p) : p ∣ m * n ↔ p ∣ m ∨ p ∣ n :=begin
    have H:int.nat_abs p ∣int.nat_abs m * int.nat_abs n ↔ int.nat_abs p ∣ int.nat_abs m ∨ int.nat_abs p ∣int.nat_abs n:=begin
        unfold int.prime at pp,revert pp, exact nat.prime.dvd_mul,end,
    rw [←int.coe_nat_dvd,←int.coe_nat_dvd,←int.coe_nat_dvd,int.coe_nat_mul,int.nat_abs_dvd,int.nat_abs_dvd,int.nat_abs_dvd,
    ←int.abs_eq_nat_abs,←int.abs_eq_nat_abs] at H,cases lt_trichotomy m 0 with Hm Hm,
    cases lt_trichotomy n 0 with Hn Hn,rwa[abs_of_neg Hm,abs_of_neg Hn,dvd_neg,dvd_neg,neg_mul_neg] at H,
    cases Hn with Hn Hn,rw Hn,norm_num,rwa[abs_of_neg Hm,abs_of_pos Hn,dvd_neg,←neg_mul_eq_neg_mul,dvd_neg] at H,
    cases Hm with Hm Hm,rw Hm,simp,cases lt_trichotomy n 0 with Hn Hn,rwa[abs_of_neg Hn,abs_of_pos Hm,dvd_neg,mul_neg_eq_neg_mul_symm,dvd_neg] at H,
    cases Hn with Hn Hn,rw Hn,simp,rwa[abs_of_pos Hn,abs_of_pos Hm] at H,
end
theorem dioph_identity:∀ a b p q:ℤ, (a*a+b*b)*(p*p+q*q)=(a*p+b*q)*(a*p+b*q)+(a*q-b*p)*(a*q-b*p):=begin
    intros,rw[mul_add,mul_add,add_mul,add_mul,add_mul,add_mul,mul_sub,sub_mul,sub_mul],ring,
end
theorem lemma_1 : ∀ a b p q : ℤ,∃ x y:ℤ, x*x+y*y=(a*a+b*b)*(p*p+q*q):=begin
    intros,existsi [(a*p+b*q),(a*q-b*p)],simp[mul_add,add_mul],ring
end
theorem lemma_1_nat :∀ a b p q : ℕ,∃ x y:ℕ, x*x+y*y=(a*a+b*b)*(p*p+q*q):=begin
    assume a b p q,cases lemma_1 ↑a ↑b ↑p ↑q with x hxy,cases hxy with y,existsi [int.nat_abs x,int.nat_abs y],
    rw[←int.coe_nat_eq_coe_nat_iff,int.coe_nat_add,int.coe_nat_mul,int.coe_nat_mul,int.coe_nat_mul,int.coe_nat_add,int.coe_nat_add,int.coe_nat_mul,int.coe_nat_mul,int.coe_nat_mul,int.coe_nat_mul],
    rwa[←int.abs_eq_nat_abs,←int.abs_eq_nat_abs,abs_mul_abs_self,abs_mul_abs_self],
end

theorem lemma_2 {a b p q :ℤ}:int.prime (p*p+q*q)→ (p*p+q*q) ∣ (a*a + b*b) → ∃ x y:ℤ,(x*x+y*y)*(p*p+q*q)=(a*a+b*b):=begin
    intros  H1 H2,
    have H3:(p*p+q*q) ∣ (b*p-a*q)*(b*p+a*q):=begin
        rw[mul_add,sub_mul,sub_mul,add_sub],
        have H3:a * q * (b * p) =b * p * (a * q):=by rw mul_comm (a*q),
        rw H3,rw sub_add_cancel,clear H3,
        have H3:b * p * (b * p) - a * q * (a * q)=(p*p)*(a*a+b*b)-(a*a)*(p*p+q*q):=by rw[mul_add,mul_add];admit,
        rw H3,exact dvd_sub (dvd_mul_of_dvd_right H2 _) (dvd_mul_left _ _),
    end,
    rw prime_dvd_mul H1 at H3,revert a b p q H1 H2 H3,
    have H:∀ a b p q:ℤ,int.prime (p * p + q * q)→p * p + q * q ∣ a * a + b * b→ p * p + q * q ∣ b * p - a * q→∃ (x y : ℤ), (x * x + y * y) * (p * p + q * q) = a * a + b * b:=begin
        assume a b p q H1 H2 H3,
        have H4:=dioph_identity b a q p,
        have H5:=dvd_mul_left (p * p + q * q) (b * b + a * a),rw [add_comm (p*p),H4,add_comm (q*q)] at H5,
        have H6:=iff.elim_right (dvd_add_iff_left (dvd_mul_of_dvd_right H3 (b * p - a * q))) H5,clear  H5,
        have H7:=mul_dvd_mul H3 H3, have H8:=exists_eq_mul_left_of_dvd H3,cases H8 with c Hc,
        cases exists_eq_mul_left_of_dvd H2 with d Hd,
        have H10:(b * b + a * a) * (q * q + p * p) = d*  (q * q + p * p) * (q * q + p * p):=begin rw[add_comm(b*b),Hd],simp,end,rw H10 at H4,clear H10,
        have H10:=dvd_mul_left ((q * q + p * p) * (q * q + p * p) ) d,
        rw[←mul_assoc,H4,add_comm (q*q),←dvd_add_iff_left H7] at H10,have H11:= dvd_of_mul_left_dvd H10,rw prime_dvd_mul at H11,simp at H11,
        cases exists_eq_mul_left_of_dvd H11 with e He,rw [add_comm(q*q),←Hd] at H4,
        existsi [c,e],clear H10 H2 H6 H3 H7 Hd d,
        suffices :(c * c + e * e) * (p * p + q * q) * (p * p + q * q) = (a * a + b * b) * (p * p + q * q),
        have H3:(p*p + q*q)≠0:=begin intro,unfold int.prime at H1,rw a_1 at H1,revert H1,simp [int.nat_abs_zero,nat.prime,(dec_trivial:¬0≥2)],end,
        have H2:(c * c + e * e) * (p * p + q * q) * (p * p + q * q) / (p * p + q * q)= (a * a + b * b) * (p * p + q * q) / (p * p + q * q):=by rw this,
        rwa[int.mul_div_cancel ((c * c + e * e) * (p * p + q * q)) H3,int.mul_div_cancel (a * a + b * b) H3] at H2,rw H4,
        have H12:(c * c + e * e) * (p * p + q * q) * (p * p + q * q) = (c * (p * p + q * q))*(c * (p * p + q * q))+ (e * (p * p + q * q))*(e * (p * p + q * q)):=begin
            rw [add_mul _ _ (p * p + q * q),add_mul _ _ (p * p + q * q)],norm_num,end,
        rw [H12,←Hc,←He],norm_num,assumption,
    end,intros a b p q H1 H2 H3,cases H3 with H3 H3,exact H a b p q H1 H2 H3,
    have H4:p * p + q * q ∣ -a * -a + b * b:=by rwa neg_mul_neg,
    have H5:p * p + q * q ∣ b * p - -a * q:=by revert H3;simp;assumption,
    have H6:=H (-a) b p q H1 H4 H5,rwa neg_mul_neg at H6,
end
theorem lemma_2_nat  {a b p q :ℕ}:nat.prime (p*p+q*q)→ (p*p+q*q) ∣ (a*a + b*b) → ∃ x y:ℕ,(x*x+y*y)*(p*p+q*q)=(a*a+b*b):=begin
    assume hp h,rw [←int.coe_nat_dvd,int.coe_nat_add,int.coe_nat_add,int.coe_nat_mul,int.coe_nat_mul,int.coe_nat_mul,int.coe_nat_mul] at h,
    have:int.prime ↑(p * p + q * q),unfold int.prime,rwa int.nat_abs_of_nat,
    rw [int.coe_nat_add,int.coe_nat_mul,int.coe_nat_mul] at this,have:=lemma_2 this h,cases this with x hx, cases hx with y hxy,
    existsi [int.nat_abs x,int.nat_abs y],rw [←int.coe_nat_eq_coe_nat_iff,int.coe_nat_mul,int.coe_nat_add,int.coe_nat_add,int.coe_nat_mul,int.coe_nat_mul,int.coe_nat_mul,int.coe_nat_add,int.coe_nat_mul,int.coe_nat_mul,int.coe_nat_mul],
    rwa[←int.abs_eq_nat_abs,←int.abs_eq_nat_abs,abs_mul_abs_self,abs_mul_abs_self],
end

open list
open nat
theorem prime_induction (n:ℕ) {q : ℕ → Prop} : n > 0 → q 1 → (∀ m p:ℕ, prime p → q m → q (m * p)) → q n:=begin
    revert n,have:∀ y n:ℕ, n≤y→ n > 0 → q 1 → (∀ m p:ℕ, prime p → q m → q (m * p)) → q n,
    assume y n hny hn q1 hmp,cases n,simp[(dec_trivial:¬0>0)] at hn,contradiction,revert n,induction y with y1 hi,assume n hny hn,simp[(dec_trivial:¬succ n≤0)] at hny,contradiction,
    assume n hny hn,cases n,assumption,cases exists_prime_and_dvd (dec_trivial:succ(succ n)≥2) with p hp,
    cases exists_eq_mul_left_of_dvd hp.right with c hc,cases c,simp [(dec_trivial:¬succ (succ n) = 0)]at hc,contradiction,
    have:=iff.elim_right (lt_mul_iff_one_lt_right (dec_trivial:succ c > 0)) (prime.gt_one hp.left),rw ←hc at this,have:=le_of_succ_le_succ (le_trans (succ_le_of_lt this) hny),
    have :=hmp (succ c) p hp.left (hi c this dec_trivial),rwa hc,exact λ n,this n n (le_refl n),
end
theorem dvd_prime {x y p:ℕ}:prime p→x ∣ p * y → p ∣ x ∨ x ∣ y:=begin
    cases x,simp,have hy:succ x>0:=dec_trivial,generalize hu :succ x = u,revert y p,
    apply prime_induction u,rwa hu at hy,simp,assume m p1 hp1 hi y p2 hp2 hd,
    have:=dvd_of_mul_right_dvd hd,cases hi hp2 this,simp [dvd_mul_of_dvd_left h p1],

end

theorem lemma_3:∀ n x a b:ℕ,x*n=a*a+b*b→(∀x₁ x₂,¬x=x₁ *x₁+x₂ * x₂)→∃ p:ℕ,p∣n∧(∀p₁ p₂:ℕ,¬p=p₁*p₁+p₂*p₂):=begin
    have: ∀ (L:list ℕ) (x a b:ℕ),x * prod L = a*a+b*b →(∀ p:ℕ,p∈L→nat.prime p)→(∀ x₁ x₂:ℕ, x≠x₁ *x₁+x₂ * x₂)→∃ p:ℕ,p∣prod L∧(∀ p₁ p₂:ℕ,p≠p₁*p₁+p₂*p₂):=begin
        assume L,induction L with p1 L hi,simp,intros,have:=a_2 a b a_1,contradiction,simp,
        assume x a b hab hp hL hxs,cases classical.em (∃ p₁ p₂:ℕ, p₁*p₁ +p₂*p₂=p1),
        cases h with p₁ hp12, cases hp12 with p₂ hp12,rw[mul_comm p1,←mul_assoc] at hab,
        have h1:=dvd_mul_left p1 (x*prod L),rw hab at h1,rw ← hp12 at hp h1,have h2:=lemma_2_nat hp h1,
        cases h2 with ai hai, cases hai with bi habi,
        rw [hp12,←hab] at habi,have h2:((ai * ai + bi * bi) * p1)/p1=(x * prod L * p1)/p1,rw habi,rw hp12 at hp,rw [nat.mul_div_cancel _ (prime.pos hp),nat.mul_div_cancel _ (prime.pos hp)] at h2,
        rw eq_comm at h2,have:= hi x ai bi h2 hL hxs,cases this with k hk,existsi k,
        simp[hk.right, dvd_mul_of_dvd_right hk.left p1],existsi p1,simp,simp at h,intros d e,
        have:= h d e,rwa eq_comm,
    end,
    assume n x a b hnx hx,
    admit,
end
theorem lemma_4 {a b:ℕ} (f:ℕ):coprime a b→f∣(a*a+b*b)→∃x y:ℕ,x*x+y*y = f:=begin
    assume hco hf,apply classical.by_contradiction,simp,assume hxx,have h1:=exists_eq_mul_left_of_dvd hf,
    cases h1 with c hc,
end


/-
  Reading list:
  - https://leanprover.github.io/logic_and_proof/index.html
    - Especially https://leanprover.github.io/logic_and_proof/sets.html
-/
prelude
import topology.order topology.subset_properties
import data.set data.set.basic
import set_theory.zfc

open set
open topological_space

universes u
variables {α β: Type u} [topological_space α] {A B C : set α}

section discrete

def is_discrete (s : set α) : Prop :=
∀ t ⊆ s, is_open t

class discrete_space (α : Type u) [topological_space α] : Prop :=
(is_discrete_univ : is_discrete (univ : set α))

lemma comp_intersect : A ∩ (B \ A) = ∅ :=
begin
   apply set.eq_empty_of_subset_empty,
   assume x,
   assume h : x ∈ A ∩ (B \ A),
   have a : x ∈ A, from and.left h,
   have : x ∈ B \ A, from and.right h,
   have c : x ∉ A, from and.right this,
   show x ∈ ∅, from absurd a c
end

theorem is_totally_separated_of_is_discrete {s : set α}
  (H : is_discrete s) : is_totally_separated s :=
begin
  rewrite is_totally_separated,
  intros,
  fapply exists.intro,
  exact singleton(x),
  fapply exists.intro,
  exact (s \ {x}),
  split,
  -- {x} is open
  apply H,
  rewrite set.singleton_subset_iff,
  assumption,
  split,
  -- s \ {x} is open
  apply H,
  simp,
  split,
  -- x ∈ {x}
  simp,
  split,
  -- y ∈ s \ {x}
  simp,
  split,
  assumption,
  show ¬y = x,
    finish,
    split,
  show s ⊆ {x} ∪ s \ {x},
    intros,
    rewrite set.union_diff_cancel,
  show {x} ⊆ s,
    rw set.singleton_subset_iff,
    assumption,
  show {x} ∩ (s \ {x}) = ∅,
    exact comp_intersect
end

instance discrete_space.totally_separated_space (α : Type u) [topological_space α]
  [discrete_space α] : totally_separated_space α :=
⟨is_totally_separated_of_is_discrete $ discrete_space.is_discrete_univ _⟩

/-
  TODO:
  * is_totally_separated A -> |A| > 1 -> ¬ is_connected A
  * is_discrete -> is_compact
-/

end discrete

section indiscrete

def is_indiscrete (s : set α) : Prop :=
  is_open s -> s = ∅ ∨ s = univ

class indiscrete_space (α : Type u) [topological_space α] : Prop :=
(is_indiscrete_univ : is_indiscrete (univ : set α))

variable (s : set α)
#check is_indiscrete s

lemma nontrivial_nonempty [indiscrete_space α] (s : set α)
  (o : is_open s) (i : ∃ x, x ∈ s) : s = univ :=
begin
  cases i with x xs,
  have : s = ∅ ∨ s = univ, from sorry,
  cases this with empty full,
    have : x ∉ ∅, from set.not_mem_empty x,
    rewrite empty at xs,
    contradiction,
    assumption
end

instance indiscrete_space.irreducible_space (α : Type u) [topological_space α]
  [indiscrete_space α] : irreducible_space α := ⟨
    begin
      rewrite is_irreducible,
      assume u v : set α,
      assume ou : is_open u,
      assume ov : is_open v,
      assume : ∃ x, x ∈ univ ∩ u,
      cases this with x iu,
      have : x ∈ u, from iu.right,
      have au : u = univ, from nontrivial_nonempty u ou ⟨x, this⟩,
      rewrite au,
      assume : ∃ y, y ∈ univ ∩ v,
      cases this with y iv,
      have : y ∈ v, from iv.right,
      have av : v = univ, from nontrivial_nonempty v ov ⟨y, this⟩,
      rewrite av,
      exact ⟨x, by simp⟩
    end
  ⟩

theorem is_compact_of_is_indiscrete
  (H : is_indiscrete (univ : set α)) : compact (univ : set α) :=
begin
  rw compact_iff_finite_subcover,
  intros,
  suffices : ∃ x ∈ univ, sorry,
  cases this with x xu,
  have : x ∈ ⋃₀ c, from sorry,
  have : ∃ t ∈ c, x ∈ t, from sorry,
  cases this with t tu,
  have : t = univ, from sorry,
  exact ⟨singleton(t), sorry⟩
end

instance indiscrete_space.compact_space (α : Type u) [topological_space α]
  [indiscrete_space α] : compact_space α :=
⟨is_compact_of_is_indiscrete $ indiscrete_space.is_indiscrete_univ _⟩

end indiscrete


import data.nat.choose

-- The Binomial Theorem

open finset
open nat

theorem t044 {α : Type*} [comm_semiring α] :
∀ (x y : α) (n : ℕ), (x + y) ^ n = (range (succ n)).sum (λ m, x ^ m * y ^ (n - m) * choose n m)
:= add_pow


import .defs
import .sequences

open espace_metrique
open_locale classical

section suites

variables {X:Type} [espace_metrique X]
variables {Y:Type} [espace_metrique Y]
variables {Z:Type} [espace_metrique Z]

-- Définir la continuité séquentielle --/

-- f est continue en L si pour tout (x_n) ∈ ℝ^ℕ convergente de limite L, (f(x_n))_n est convergente de limite f(L).
def seq_continue_en_l (f: X → Y) (L: X) := ∀ (x: ℕ → X), converge x L → converge (suite_image f x) (f L)
def seq_continue (f: X → Y) := ∀ L : X, seq_continue_en_l f L

/-- On démontre que la continuité séquentielle est stable par composition --/

-- On démontre la composition des suites images.
theorem comp_suite_image (f: X → Y) (g: Y → Z) (x: ℕ → X): suite_image g (suite_image f x) = suite_image (g ∘ f) x :=
begin
ext,
repeat {rw suite_image},
end

-- On se ramène à la continuité séquentielle en un point.
theorem comp_seq_continue_ponctuel (f : X → Y) (g : Y → Z) (l: X):
  seq_continue_en_l f l ∧ seq_continue_en_l g (f l)
  → seq_continue_en_l (g ∘ f) l := begin
  intros H x x_cv,
  cases H with Hf Hg,
  have := Hg (suite_image f x) (Hf x x_cv),
  conv at this {
    congr,
    rw comp_suite_image f g x,
    skip,
    skip,
  },
  exact this,
end

theorem comp_seq_continue (f:X → Y) (g:Y → Z):
 seq_continue f ∧ seq_continue g → seq_continue (g ∘ f):=
 begin
 intro H,
 intro l,
 apply comp_seq_continue_ponctuel,
 cases H with Hf Hg,
 split,
 exact Hf l,
 exact Hg (f l),
 end

end suites


/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura
-/
import data.equiv data.int.basic data.encodable data.countable
open equiv bool sum

namespace int
definition int_equiv_bool_nat : int ≃ (bool × nat) :=
equiv.mk
  (λ i, match i with of_nat a := (tt, a) | neg_succ_of_nat a := (ff, a) end)
  (λ p, match p with (tt, a) := of_nat a | (ff, a) := neg_succ_of_nat a end)
  (λ i, begin cases i, repeat reflexivity end)
  (λ p, begin cases p with b a, cases b, repeat reflexivity end)

definition int_equiv_nat : int ≃ nat :=
calc int ≃ (bool × nat)                : int_equiv_bool_nat
     ... ≃ ((unit + unit) × nat)       : prod_congr bool_equiv_unit_sum_unit !_root_.equiv.refl
     ... ≃ (unit × nat) + (unit × nat) : sum_prod_distrib
     ... ≃ nat + nat                   : sum_congr !prod_unit_left !prod_unit_left
     ... ≃ nat                         : nat_sum_nat_equiv_nat

definition encodable_int [instance] : encodable int :=
encodable_of_equiv (_root_.equiv.symm int_equiv_nat)

lemma countable_int : countable int :=
countable_of_encodable encodable_int

end int