/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Johannes Hölzl

Probability mass function -- discrete probability measures
-/
import topology.instances.nnreal topology.instances.ennreal topology.algebra.infinite_sum
noncomputable theory
variables {α : Type*} {β : Type*} {γ : Type*}
local attribute [instance] classical.prop_decidable

/-- Probability mass functions, i.e. discrete probability measures -/
def {u} pmf (α : Type u) : Type u := { f : α → nnreal // is_sum f 1 }

namespace pmf

instance : has_coe_to_fun (pmf α) := ⟨λp, α → nnreal, λp a, p.1 a⟩

@[extensionality] protected lemma ext : ∀{p q : pmf α}, (∀a, p a = q a) → p = q
| ⟨f, hf⟩ ⟨g, hg⟩ eq :=  subtype.eq $ funext eq

lemma is_sum_coe_one (p : pmf α) : is_sum p 1 := p.2

lemma has_sum_coe (p : pmf α) : has_sum p := has_sum_spec p.is_sum_coe_one

@[simp] lemma tsum_coe (p : pmf α) : (∑a, p a) = 1 := tsum_eq_is_sum p.is_sum_coe_one

def support (p : pmf α) : set α := {a | p.1 a ≠ 0}

def pure (a : α) : pmf α := ⟨λa', if a' = a then 1 else 0, is_sum_ite_eq _ _⟩

@[simp] lemma pure_apply (a a' : α) : pure a a' = (if a' = a then 1 else 0) := rfl

instance [inhabited α] : inhabited (pmf α) := ⟨pure (default α)⟩

lemma coe_le_one (p : pmf α) (a : α) : p a ≤ 1 :=
is_sum_le (by intro b; split_ifs; simp [h]; exact le_refl _) (is_sum_ite_eq a (p a)) p.2

protected lemma bind.has_sum (p : pmf α) (f : α → pmf β) (b : β) : has_sum (λa:α, p a * f a b) :=
begin
  refine nnreal.has_sum_of_le (assume a, _) p.has_sum_coe,
  suffices : p a * f a b ≤ p a * 1, { simpa },
  exact mul_le_mul_of_nonneg_left ((f a).coe_le_one _) (p a).2
end

def bind (p : pmf α) (f : α → pmf β) : pmf β :=
⟨λb, (∑a, p a * f a b),
  begin
    simp [ennreal.is_sum_coe.symm, (ennreal.tsum_coe (bind.has_sum p f _)).symm],
    rw [is_sum_iff_of_has_sum ennreal.has_sum, ennreal.tsum_comm],
    simp [ennreal.mul_tsum, (ennreal.tsum_coe (f _).has_sum_coe),
      ennreal.tsum_coe p.has_sum_coe]
  end⟩

@[simp] lemma bind_apply (p : pmf α) (f : α → pmf β) (b : β) : p.bind f b = (∑a, p a * f a b) := rfl

lemma coe_bind_apply (p : pmf α) (f : α → pmf β) (b : β) :
  (p.bind f b : ennreal) = (∑a, p a * f a b) :=
eq.trans (ennreal.tsum_coe $ bind.has_sum p f b).symm $ by simp

@[simp] lemma pure_bind (a : α) (f : α → pmf β) : (pure a).bind f = f a :=
have ∀b a', ite (a' = a) 1 0 * f a' b = ite (a' = a) (f a b) 0, from
  assume b a', by split_ifs; simp; subst h; simp,
by ext b; simp [this]

@[simp] lemma bind_pure (p : pmf α) : p.bind pure = p :=
have ∀a a', (p a * ite (a' = a) 1 0) = ite (a = a') (p a') 0, from
  assume a a', begin split_ifs; try { subst a }; try { subst a' }; simp * at * end,
by ext b; simp [this]

@[simp] lemma bind_bind (p : pmf α) (f : α → pmf β) (g : β → pmf γ) :
  (p.bind f).bind g = p.bind (λa, (f a).bind g) :=
begin
  ext b,
  simp only [ennreal.coe_eq_coe.symm, coe_bind_apply, ennreal.mul_tsum.symm, ennreal.tsum_mul.symm],
  rw [ennreal.tsum_comm],
  simp [mul_assoc, mul_left_comm, mul_comm]
end

lemma bind_comm (p : pmf α) (q : pmf β) (f : α → β → pmf γ) :
  p.bind (λa, q.bind (f a)) = q.bind (λb, p.bind (λa, f a b)) :=
begin
  ext b,
  simp only [ennreal.coe_eq_coe.symm, coe_bind_apply, ennreal.mul_tsum.symm, ennreal.tsum_mul.symm],
  rw [ennreal.tsum_comm],
  simp [mul_assoc, mul_left_comm, mul_comm]
end

def map (f : α → β) (p : pmf α) : pmf β := bind p (pure ∘ f)

lemma bind_pure_comp (f : α → β) (p : pmf α) : bind p (pure ∘ f) = map f p := rfl

lemma map_id (p : pmf α) : map id p = p := by simp [map]

lemma map_comp (p : pmf α) (f : α → β) (g : β → γ) : (p.map f).map g = p.map (g ∘ f) :=
by simp [map]

lemma pure_map (a : α) (f : α → β) : (pure a).map f = pure (f a) :=
by simp [map]

def seq (f : pmf (α → β)) (p : pmf α) : pmf β := f.bind (λm, p.bind $ λa, pure (m a))

def of_multiset (s : multiset α) (hs : s ≠ 0) : pmf α :=
⟨λa, s.count a / s.card,
  have s.to_finset.sum (λa, (s.count a : ℝ) / s.card) = 1,
    by simp [div_eq_inv_mul, finset.mul_sum.symm, (finset.sum_nat_cast _ _).symm, hs],
  have s.to_finset.sum (λa, (s.count a : nnreal) / s.card) = 1,
    by rw [← nnreal.eq_iff, nnreal.coe_one, ← this, nnreal.sum_coe]; simp,
  begin
    rw ← this,
    apply is_sum_sum_of_ne_finset_zero,
    simp {contextual := tt},
  end⟩

def of_fintype [fintype α] (f : α → nnreal) (h : finset.univ.sum f = 1) : pmf α :=
⟨f, h ▸ is_sum_sum_of_ne_finset_zero (by simp)⟩

def bernoulli (p : nnreal) (h : p ≤ 1) : pmf bool :=
of_fintype (λb, cond b p (1 - p)) (nnreal.eq $ by simp [h])

end pmf


/-
Copyright (c) 2021 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import analysis.inner_product_space.calculus
import analysis.inner_product_space.pi_L2

/-!
# Euclidean distance on a finite dimensional space

When we define a smooth bump function on a normed space, it is useful to have a smooth distance on
the space. Since the default distance is not guaranteed to be smooth, we define `to_euclidean` to be
an equivalence between a finite dimensional normed space and the standard Euclidean space of the
same dimension. Then we define `euclidean.dist x y = dist (to_euclidean x) (to_euclidean y)` and
provide some definitions (`euclidean.ball`, `euclidean.closed_ball`) and simple lemmas about this
distance. This way we hide the usage of `to_euclidean` behind an API.
-/

open_locale topology
open set

variables {E : Type*} [normed_add_comm_group E] [normed_space ℝ E] [finite_dimensional ℝ E]

noncomputable theory

/-- If `E` is a finite dimensional space over `ℝ`, then `to_euclidean` is a continuous `ℝ`-linear
equivalence between `E` and the Euclidean space of the same dimension. -/
def to_euclidean : E ≃L[ℝ] euclidean_space ℝ (fin $ finite_dimensional.finrank ℝ E) :=
continuous_linear_equiv.of_finrank_eq finrank_euclidean_space_fin.symm

namespace euclidean

/-- If `x` and `y` are two points in a finite dimensional space over `ℝ`, then `euclidean.dist x y`
is the distance between these points in the metric defined by some inner product space structure on
`E`. -/
def dist (x y : E) : ℝ := dist (to_euclidean x) (to_euclidean y)

/-- Closed ball w.r.t. the euclidean distance. -/
def closed_ball (x : E) (r : ℝ) : set E := {y | dist y x ≤ r}

/-- Open ball w.r.t. the euclidean distance. -/
def ball (x : E) (r : ℝ) : set E := {y | dist y x < r}

lemma ball_eq_preimage (x : E) (r : ℝ) :
  ball x r = to_euclidean ⁻¹' (metric.ball (to_euclidean x) r) :=
rfl

lemma closed_ball_eq_preimage (x : E) (r : ℝ) :
  closed_ball x r = to_euclidean ⁻¹' (metric.closed_ball (to_euclidean x) r) :=
rfl

lemma ball_subset_closed_ball {x : E} {r : ℝ} : ball x r ⊆ closed_ball x r :=
λ y (hy : _ < _), le_of_lt hy

lemma is_open_ball {x : E} {r : ℝ} : is_open (ball x r) :=
metric.is_open_ball.preimage to_euclidean.continuous

lemma mem_ball_self {x : E} {r : ℝ} (hr : 0 < r) : x ∈ ball x r := metric.mem_ball_self hr

lemma closed_ball_eq_image (x : E) (r : ℝ) :
  closed_ball x r = to_euclidean.symm '' metric.closed_ball (to_euclidean x) r :=
by rw [to_euclidean.image_symm_eq_preimage, closed_ball_eq_preimage]

lemma is_compact_closed_ball {x : E} {r : ℝ} : is_compact (closed_ball x r) :=
begin
  rw closed_ball_eq_image,
  exact (is_compact_closed_ball _ _).image to_euclidean.symm.continuous
end

lemma is_closed_closed_ball {x : E} {r : ℝ} : is_closed (closed_ball x r) :=
is_compact_closed_ball.is_closed

lemma closure_ball (x : E) {r : ℝ} (h : r ≠ 0) : closure (ball x r) = closed_ball x r :=
by rw [ball_eq_preimage, ← to_euclidean.preimage_closure, closure_ball (to_euclidean x) h,
  closed_ball_eq_preimage]

lemma exists_pos_lt_subset_ball {R : ℝ} {s : set E} {x : E}
  (hR : 0 < R) (hs : is_closed s) (h : s ⊆ ball x R) :
  ∃ r ∈ Ioo 0 R, s ⊆ ball x r :=
begin
  rw [ball_eq_preimage, ← image_subset_iff] at h,
  rcases exists_pos_lt_subset_ball hR (to_euclidean.is_closed_image.2 hs) h with ⟨r, hr, hsr⟩,
  exact ⟨r, hr, image_subset_iff.1 hsr⟩
end

lemma nhds_basis_closed_ball {x : E} :
  (𝓝 x).has_basis (λ r : ℝ, 0 < r) (closed_ball x) :=
begin
  rw [to_euclidean.to_homeomorph.nhds_eq_comap],
  exact metric.nhds_basis_closed_ball.comap _
end

lemma closed_ball_mem_nhds {x : E} {r : ℝ} (hr : 0 < r) : closed_ball x r ∈ 𝓝 x :=
nhds_basis_closed_ball.mem_of_mem hr

lemma nhds_basis_ball {x : E} :
  (𝓝 x).has_basis (λ r : ℝ, 0 < r) (ball x) :=
begin
  rw [to_euclidean.to_homeomorph.nhds_eq_comap],
  exact metric.nhds_basis_ball.comap _
end

lemma ball_mem_nhds {x : E} {r : ℝ} (hr : 0 < r) : ball x r ∈ 𝓝 x :=
nhds_basis_ball.mem_of_mem hr

end euclidean

variables {F : Type*} [normed_add_comm_group F] [normed_space ℝ F] {f g : F → E} {n : ℕ∞}

lemma cont_diff.euclidean_dist (hf : cont_diff ℝ n f) (hg : cont_diff ℝ n g)
  (h : ∀ x, f x ≠ g x) :
  cont_diff ℝ n (λ x, euclidean.dist (f x) (g x)) :=
begin
  simp only [euclidean.dist],
  apply @cont_diff.dist ℝ,
  exacts [(@to_euclidean E _ _ _).cont_diff.comp hf,
    (@to_euclidean E _ _ _).cont_diff.comp hg, λ x, to_euclidean.injective.ne (h x)]
end


/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Markus Himmel
-/
import category_theory.limits.preserves.shapes.zero

/-!
# Kernels and cokernels

In a category with zero morphisms, the kernel of a morphism `f : X ⟶ Y` is
the equalizer of `f` and `0 : X ⟶ Y`. (Similarly the cokernel is the coequalizer.)

The basic definitions are
* `kernel : (X ⟶ Y) → C`

* `kernel.ι : kernel f ⟶ X`
* `kernel.condition : kernel.ι f ≫ f = 0` and
* `kernel.lift (k : W ⟶ X) (h : k ≫ f = 0) : W ⟶ kernel f` (as well as the dual versions)

## Main statements

Besides the definition and lifts, we prove
* `kernel.ι_zero_is_iso`: a kernel map of a zero morphism is an isomorphism
* `kernel.eq_zero_of_epi_kernel`: if `kernel.ι f` is an epimorphism, then `f = 0`
* `kernel.of_mono`: the kernel of a monomorphism is the zero object
* `kernel.lift_mono`: the lift of a monomorphism `k : W ⟶ X` such that `k ≫ f = 0`
  is still a monomorphism
* `kernel.is_limit_cone_zero_cone`: if our category has a zero object, then the map from the zero
  obect is a kernel map of any monomorphism
* `kernel.ι_of_zero`: `kernel.ι (0 : X ⟶ Y)` is an isomorphism

and the corresponding dual statements.

## Future work
* TODO: connect this with existing working in the group theory and ring theory libraries.

## Implementation notes
As with the other special shapes in the limits library, all the definitions here are given as
`abbreviation`s of the general statements for limits, so all the `simp` lemmas and theorems about
general limits can be used.

## References

* [F. Borceux, *Handbook of Categorical Algebra 2*][borceux-vol2]
-/

noncomputable theory

universes v u u' u₂

open category_theory
open category_theory.limits.walking_parallel_pair

namespace category_theory.limits

variables {C : Type u} [category.{v} C]
variables [has_zero_morphisms C]

/-- A morphism `f` has a kernel if the functor `parallel_pair f 0` has a limit. -/
abbreviation has_kernel {X Y : C} (f : X ⟶ Y) : Prop := has_limit (parallel_pair f 0)
/-- A morphism `f` has a cokernel if the functor `parallel_pair f 0` has a colimit. -/
abbreviation has_cokernel {X Y : C} (f : X ⟶ Y) : Prop := has_colimit (parallel_pair f 0)

variables {X Y : C} (f : X ⟶ Y)

section

/-- A kernel fork is just a fork where the second morphism is a zero morphism. -/
abbreviation kernel_fork := fork f 0

variables {f}

@[simp, reassoc] lemma kernel_fork.condition (s : kernel_fork f) : fork.ι s ≫ f = 0 :=
by erw [fork.condition, has_zero_morphisms.comp_zero]

@[simp] lemma kernel_fork.app_one (s : kernel_fork f) : s.π.app one = 0 :=
by simp [fork.app_one_eq_ι_comp_right]

/-- A morphism `ι` satisfying `ι ≫ f = 0` determines a kernel fork over `f`. -/
abbreviation kernel_fork.of_ι {Z : C} (ι : Z ⟶ X) (w : ι ≫ f = 0) : kernel_fork f :=
fork.of_ι ι $ by rw [w, has_zero_morphisms.comp_zero]

@[simp] lemma kernel_fork.ι_of_ι {X Y P : C} (f : X ⟶ Y) (ι : P ⟶ X) (w : ι ≫ f = 0) :
  fork.ι (kernel_fork.of_ι ι w) = ι := rfl

section
local attribute [tidy] tactic.case_bash

/-- Every kernel fork `s` is isomorphic (actually, equal) to `fork.of_ι (fork.ι s) _`. -/
def iso_of_ι (s : fork f 0) : s ≅ fork.of_ι (fork.ι s) (fork.condition s) :=
cones.ext (iso.refl _) $ by tidy

/-- If `ι = ι'`, then `fork.of_ι ι _` and `fork.of_ι ι' _` are isomorphic. -/
def of_ι_congr {P : C} {ι ι' : P ⟶ X} {w : ι ≫ f = 0} (h : ι = ι') :
  kernel_fork.of_ι ι w ≅ kernel_fork.of_ι ι' (by rw [←h, w]) :=
cones.ext (iso.refl _) $ by tidy

/-- If `F` is an equivalence, then applying `F` to a diagram indexing a (co)kernel of `f` yields
    the diagram indexing the (co)kernel of `F.map f`. -/
def comp_nat_iso {D : Type u'} [category.{v} D] [has_zero_morphisms D] (F : C ⥤ D)
  [is_equivalence F] : parallel_pair f 0 ⋙ F ≅ parallel_pair (F.map f) 0 :=
nat_iso.of_components (λ j, match j with
  | zero := iso.refl _
  | one := iso.refl _
  end) $ by tidy

end

/-- If `s` is a limit kernel fork and `k : W ⟶ X` satisfies ``k ≫ f = 0`, then there is some
    `l : W ⟶ s.X` such that `l ≫ fork.ι s = k`. -/
def kernel_fork.is_limit.lift' {s : kernel_fork f} (hs : is_limit s) {W : C} (k : W ⟶ X)
  (h : k ≫ f = 0) : {l : W ⟶ s.X // l ≫ fork.ι s = k} :=
⟨hs.lift $ kernel_fork.of_ι _ h, hs.fac _ _⟩

/-- This is a slightly more convenient method to verify that a kernel fork is a limit cone. It
    only asks for a proof of facts that carry any mathematical content -/
def is_limit_aux (t : kernel_fork f)
  (lift : Π (s : kernel_fork f), s.X ⟶ t.X)
  (fac : ∀ (s : kernel_fork f), lift s ≫ t.ι = s.ι)
  (uniq : ∀ (s : kernel_fork f) (m : s.X ⟶ t.X) (w : m ≫ t.ι = s.ι), m = lift s) :
  is_limit t :=
{ lift := lift,
  fac' := λ s j, by { cases j, { exact fac s, }, { simp, }, },
  uniq' := λ s m w, uniq s m (w limits.walking_parallel_pair.zero), }

/--
This is a more convenient formulation to show that a `kernel_fork` constructed using
`kernel_fork.of_ι` is a limit cone.
-/
def is_limit.of_ι {W : C} (g : W ⟶ X) (eq : g ≫ f = 0)
  (lift : Π {W' : C} (g' : W' ⟶ X) (eq' : g' ≫ f = 0), W' ⟶ W)
  (fac : ∀ {W' : C} (g' : W' ⟶ X) (eq' : g' ≫ f = 0), lift g' eq' ≫ g = g')
  (uniq :
    ∀ {W' : C} (g' : W' ⟶ X) (eq' : g' ≫ f = 0) (m : W' ⟶ W) (w : m ≫ g = g'), m = lift g' eq') :
  is_limit (kernel_fork.of_ι g eq) :=
is_limit_aux _ (λ s, lift s.ι s.condition) (λ s, fac s.ι s.condition) (λ s, uniq s.ι s.condition)

/-- Every kernel of `f` induces a kernel of `f ≫ g` if `g` is mono. -/
def is_kernel_comp_mono {c : kernel_fork f} (i : is_limit c) {Z} (g : Y ⟶ Z) [hg : mono g]
  {h : X ⟶ Z} (hh : h = f ≫ g) :
  is_limit (kernel_fork.of_ι c.ι (by simp [hh]) : kernel_fork h) :=
fork.is_limit.mk' _ $ λ s,
  let s' : kernel_fork f := fork.of_ι s.ι (by rw [←cancel_mono g]; simp [←hh, s.condition]) in
  let l := kernel_fork.is_limit.lift' i s'.ι s'.condition in
  ⟨l.1, l.2, λ m hm, by apply fork.is_limit.hom_ext i; rw fork.ι_of_ι at hm; rw hm; exact l.2.symm⟩

lemma is_kernel_comp_mono_lift {c : kernel_fork f} (i : is_limit c) {Z} (g : Y ⟶ Z) [hg : mono g]
  {h : X ⟶ Z} (hh : h = f ≫ g) (s : kernel_fork h) :
  (is_kernel_comp_mono i g hh).lift s
  = i.lift (fork.of_ι s.ι (by { rw [←cancel_mono g, category.assoc, ←hh], simp })) := rfl

/-- Every kernel of `f ≫ g` is also a kernel of `f`, as long as `c.ι ≫ f` vanishes. -/
def is_kernel_of_comp {W : C} (g : Y ⟶ W) (h : X ⟶ W) {c : kernel_fork h} (i : is_limit c)
  (hf : c.ι ≫ f = 0) (hfg : f ≫ g = h) : is_limit (kernel_fork.of_ι c.ι hf) :=
fork.is_limit.mk _
  (λ s, i.lift (kernel_fork.of_ι s.ι (by simp [← hfg])))
  (λ s, by simp only [kernel_fork.ι_of_ι, fork.is_limit.lift_ι])
  (λ s m h, by { apply fork.is_limit.hom_ext i, simpa using h })

end

section
variables [has_kernel f]

/-- The kernel of a morphism, expressed as the equalizer with the 0 morphism. -/
abbreviation kernel : C := equalizer f 0

/-- The map from `kernel f` into the source of `f`. -/
abbreviation kernel.ι : kernel f ⟶ X := equalizer.ι f 0

@[simp] lemma equalizer_as_kernel : equalizer.ι f 0 = kernel.ι f := rfl

@[simp, reassoc] lemma kernel.condition : kernel.ι f ≫ f = 0 :=
kernel_fork.condition _

/-- The kernel built from `kernel.ι f` is limiting. -/
def kernel_is_kernel :
  is_limit (fork.of_ι (kernel.ι f) ((kernel.condition f).trans (comp_zero.symm))) :=
is_limit.of_iso_limit (limit.is_limit _) (fork.ext (iso.refl _) (by tidy))

/-- Given any morphism `k : W ⟶ X` satisfying `k ≫ f = 0`, `k` factors through `kernel.ι f`
    via `kernel.lift : W ⟶ kernel f`. -/
abbreviation kernel.lift {W : C} (k : W ⟶ X) (h : k ≫ f = 0) : W ⟶ kernel f :=
limit.lift (parallel_pair f 0) (kernel_fork.of_ι k h)

@[simp, reassoc]
lemma kernel.lift_ι {W : C} (k : W ⟶ X) (h : k ≫ f = 0) : kernel.lift f k h ≫ kernel.ι f = k :=
limit.lift_π _ _

@[simp]
lemma kernel.lift_zero {W : C} {h} : kernel.lift f (0 : W ⟶ X) h = 0 :=
by { ext, simp, }

instance kernel.lift_mono {W : C} (k : W ⟶ X) (h : k ≫ f = 0) [mono k] : mono (kernel.lift f k h) :=
⟨λ Z g g' w,
begin
  replace w := w =≫ kernel.ι f,
  simp only [category.assoc, kernel.lift_ι] at w,
  exact (cancel_mono k).1 w,
end⟩

/-- Any morphism `k : W ⟶ X` satisfying `k ≫ f = 0` induces a morphism `l : W ⟶ kernel f` such that
    `l ≫ kernel.ι f = k`. -/
def kernel.lift' {W : C} (k : W ⟶ X) (h : k ≫ f = 0) : {l : W ⟶ kernel f // l ≫ kernel.ι f = k} :=
⟨kernel.lift f k h, kernel.lift_ι _ _ _⟩

/-- A commuting square induces a morphism of kernels. -/
abbreviation kernel.map {X' Y' : C} (f' : X' ⟶ Y') [has_kernel f']
  (p : X ⟶ X') (q : Y ⟶ Y') (w : f ≫ q = p ≫ f') : kernel f ⟶ kernel f' :=
kernel.lift f' (kernel.ι f ≫ p) (by simp [←w])

/--
Given a commutative diagram
    X --f--> Y --g--> Z
    |        |        |
    |        |        |
    v        v        v
    X' -f'-> Y' -g'-> Z'
with horizontal arrows composing to zero,
then we obtain a commutative square
   X ---> kernel g
   |         |
   |         | kernel.map
   |         |
   v         v
   X' --> kernel g'
-/
lemma kernel.lift_map {X Y Z X' Y' Z' : C}
  (f : X ⟶ Y) (g : Y ⟶ Z) [has_kernel g] (w : f ≫ g = 0)
  (f' : X' ⟶ Y') (g' : Y' ⟶ Z') [has_kernel g'] (w' : f' ≫ g' = 0)
  (p : X ⟶ X') (q : Y ⟶ Y') (r : Z ⟶ Z') (h₁ : f ≫ q = p ≫ f') (h₂ : g ≫ r = q ≫ g') :
  kernel.lift g f w ≫ kernel.map g g' q r h₂ = p ≫ kernel.lift g' f' w' :=
by { ext, simp [h₁], }

/-- A commuting square of isomorphisms induces an isomorphism of kernels. -/
@[simps]
def kernel.map_iso {X' Y' : C} (f' : X' ⟶ Y') [has_kernel f']
  (p : X ≅ X') (q : Y ≅ Y') (w : f ≫ q.hom = p.hom ≫ f') : kernel f ≅ kernel f' :=
{ hom := kernel.map f f' p.hom q.hom w,
  inv := kernel.map f' f p.inv q.inv (by { refine (cancel_mono q.hom).1 _, simp [w], }), }

/-- Every kernel of the zero morphism is an isomorphism -/
instance kernel.ι_zero_is_iso : is_iso (kernel.ι (0 : X ⟶ Y)) :=
equalizer.ι_of_self _

lemma eq_zero_of_epi_kernel [epi (kernel.ι f)] : f = 0 :=
(cancel_epi (kernel.ι f)).1 (by simp)

/-- The kernel of a zero morphism is isomorphic to the source. -/
def kernel_zero_iso_source : kernel (0 : X ⟶ Y) ≅ X :=
equalizer.iso_source_of_self 0

@[simp] lemma kernel_zero_iso_source_hom :
  kernel_zero_iso_source.hom = kernel.ι (0 : X ⟶ Y) := rfl

@[simp] lemma kernel_zero_iso_source_inv :
  kernel_zero_iso_source.inv = kernel.lift (0 : X ⟶ Y) (𝟙 X) (by simp) :=
by { ext, simp [kernel_zero_iso_source], }

/-- If two morphisms are known to be equal, then their kernels are isomorphic. -/
def kernel_iso_of_eq {f g : X ⟶ Y} [has_kernel f] [has_kernel g] (h : f = g) :
  kernel f ≅ kernel g :=
has_limit.iso_of_nat_iso (by simp[h])

@[simp]
lemma kernel_iso_of_eq_refl {h : f = f} : kernel_iso_of_eq h = iso.refl (kernel f) :=
by { ext, simp [kernel_iso_of_eq], }

@[simp, reassoc]
lemma kernel_iso_of_eq_hom_comp_ι {f g : X ⟶ Y} [has_kernel f] [has_kernel g] (h : f = g) :
  (kernel_iso_of_eq h).hom ≫ kernel.ι _ = kernel.ι _ :=
by { unfreezingI { induction h, simp } }

@[simp, reassoc]
lemma kernel_iso_of_eq_inv_comp_ι {f g : X ⟶ Y} [has_kernel f] [has_kernel g] (h : f = g) :
  (kernel_iso_of_eq h).inv ≫ kernel.ι _ = kernel.ι _ :=
by { unfreezingI { induction h, simp } }

@[simp, reassoc]
lemma lift_comp_kernel_iso_of_eq_hom {Z} {f g : X ⟶ Y} [has_kernel f] [has_kernel g]
  (h : f = g) (e : Z ⟶ X) (he) :
  kernel.lift _ e he ≫ (kernel_iso_of_eq h).hom = kernel.lift _ e (by simp [← h, he]) :=
by { unfreezingI { induction h, simp } }

@[simp, reassoc]
lemma lift_comp_kernel_iso_of_eq_inv {Z} {f g : X ⟶ Y} [has_kernel f] [has_kernel g]
  (h : f = g) (e : Z ⟶ X) (he) :
  kernel.lift _ e he ≫ (kernel_iso_of_eq h).inv = kernel.lift _ e (by simp [h, he]) :=
by { unfreezingI { induction h, simp } }

@[simp]
lemma kernel_iso_of_eq_trans {f g h : X ⟶ Y} [has_kernel f] [has_kernel g] [has_kernel h]
  (w₁ : f = g) (w₂ : g = h) :
  kernel_iso_of_eq w₁ ≪≫ kernel_iso_of_eq w₂ = kernel_iso_of_eq (w₁.trans w₂) :=
by { unfreezingI { induction w₁, induction w₂, }, ext, simp [kernel_iso_of_eq], }

variables {f}

lemma kernel_not_epi_of_nonzero (w : f ≠ 0) : ¬epi (kernel.ι f) :=
λ I, by exactI w (eq_zero_of_epi_kernel f)

lemma kernel_not_iso_of_nonzero (w : f ≠ 0) : (is_iso (kernel.ι f)) → false :=
λ I, kernel_not_epi_of_nonzero w $ by { resetI, apply_instance }

instance has_kernel_comp_mono {X Y Z : C} (f : X ⟶ Y) [has_kernel f] (g : Y ⟶ Z) [mono g] :
  has_kernel (f ≫ g) :=
⟨⟨{ cone := _, is_limit := is_kernel_comp_mono (limit.is_limit _) g rfl }⟩⟩

/--
When `g` is a monomorphism, the kernel of `f ≫ g` is isomorphic to the kernel of `f`.
-/
@[simps]
def kernel_comp_mono {X Y Z : C} (f : X ⟶ Y) (g : Y ⟶ Z) [has_kernel f] [mono g] :
  kernel (f ≫ g) ≅ kernel f :=
{ hom := kernel.lift _ (kernel.ι _) (by { rw [←cancel_mono g], simp, }),
  inv := kernel.lift _ (kernel.ι _) (by simp), }

instance has_kernel_iso_comp {X Y Z : C} (f : X ⟶ Y) (g : Y ⟶ Z) [is_iso f] [has_kernel g] :
  has_kernel (f ≫ g) :=
{ exists_limit :=
  ⟨{ cone := kernel_fork.of_ι (kernel.ι g ≫ inv f) (by simp),
     is_limit := is_limit_aux _ (λ s, kernel.lift _ (s.ι ≫ f) (by tidy)) (by tidy)
       (λ s m w, by { simp_rw [←w], ext, simp, }), }⟩ }

/--
When `f` is an isomorphism, the kernel of `f ≫ g` is isomorphic to the kernel of `g`.
-/
@[simps]
def kernel_is_iso_comp {X Y Z : C} (f : X ⟶ Y) (g : Y ⟶ Z) [is_iso f] [has_kernel g] :
  kernel (f ≫ g) ≅ kernel g :=
{ hom := kernel.lift _ (kernel.ι _ ≫ f) (by simp),
  inv := kernel.lift _ (kernel.ι _ ≫ inv f) (by simp), }

end

section has_zero_object
variables [has_zero_object C]

open_locale zero_object

/-- The morphism from the zero object determines a cone on a kernel diagram -/
def kernel.zero_kernel_fork : kernel_fork f :=
{ X := 0,
  π := { app := λ j, 0 }}

/-- The map from the zero object is a kernel of a monomorphism -/
def kernel.is_limit_cone_zero_cone [mono f] : is_limit (kernel.zero_kernel_fork f) :=
fork.is_limit.mk _ (λ s, 0)
  (λ s, by { erw zero_comp,
    convert (zero_of_comp_mono f _).symm,
    exact kernel_fork.condition _ })
  (λ _ _ _, zero_of_to_zero _)

/-- The kernel of a monomorphism is isomorphic to the zero object -/
def kernel.of_mono [has_kernel f] [mono f] : kernel f ≅ 0 :=
functor.map_iso (cones.forget _) $ is_limit.unique_up_to_iso
  (limit.is_limit (parallel_pair f 0)) (kernel.is_limit_cone_zero_cone f)

/-- The kernel morphism of a monomorphism is a zero morphism -/
lemma kernel.ι_of_mono [has_kernel f] [mono f] : kernel.ι f = 0 :=
zero_of_source_iso_zero _ (kernel.of_mono f)

/-- If `g ≫ f = 0` implies `g = 0` for all `g`, then `0 : 0 ⟶ X` is a kernel of `f`. -/
def zero_kernel_of_cancel_zero {X Y : C} (f : X ⟶ Y)
  (hf : ∀ (Z : C) (g : Z ⟶ X) (hgf : g ≫ f = 0), g = 0) :
    is_limit (kernel_fork.of_ι (0 : 0 ⟶ X) (show 0 ≫ f = 0, by simp)) :=
fork.is_limit.mk _ (λ s, 0)
  (λ s, by rw [hf _ _ (kernel_fork.condition s), zero_comp])
  (λ s m h, by ext)

end has_zero_object

section transport

/-- If `i` is an isomorphism such that `l ≫ i.hom = f`, then any kernel of `f` is a kernel of `l`.-/
def is_kernel.of_comp_iso {Z : C} (l : X ⟶ Z) (i : Z ≅ Y) (h : l ≫ i.hom = f)
  {s : kernel_fork f} (hs : is_limit s) : is_limit (kernel_fork.of_ι (fork.ι s) $
    show fork.ι s ≫ l = 0, by simp [←i.comp_inv_eq.2 h.symm]) :=
fork.is_limit.mk _
  (λ s, hs.lift $ kernel_fork.of_ι (fork.ι s) $ by simp [←h])
  (λ s, by simp)
  (λ s m h, by { apply fork.is_limit.hom_ext hs, simpa using h })

/-- If `i` is an isomorphism such that `l ≫ i.hom = f`, then the kernel of `f` is a kernel of `l`.-/
def kernel.of_comp_iso [has_kernel f]
  {Z : C} (l : X ⟶ Z) (i : Z ≅ Y) (h : l ≫ i.hom = f) :
  is_limit (kernel_fork.of_ι (kernel.ι f) $
    show kernel.ι f ≫ l = 0, by simp [←i.comp_inv_eq.2 h.symm]) :=
is_kernel.of_comp_iso f l i h $ limit.is_limit _

/-- If `s` is any limit kernel cone over `f` and if  `i` is an isomorphism such that
    `i.hom ≫ s.ι  = l`, then `l` is a kernel of `f`. -/
def is_kernel.iso_kernel {Z : C} (l : Z ⟶ X) {s : kernel_fork f} (hs : is_limit s)
  (i : Z ≅ s.X) (h : i.hom ≫ fork.ι s = l) : is_limit (kernel_fork.of_ι l $
    show l ≫ f = 0, by simp [←h]) :=
is_limit.of_iso_limit hs $ cones.ext i.symm $ λ j,
  by { cases j, { exact (iso.eq_inv_comp i).2 h }, { simp } }

/-- If `i` is an isomorphism such that `i.hom ≫ kernel.ι f = l`, then `l` is a kernel of `f`. -/
def kernel.iso_kernel [has_kernel f]
  {Z : C} (l : Z ⟶ X) (i : Z ≅ kernel f) (h : i.hom ≫ kernel.ι f = l) :
  is_limit (kernel_fork.of_ι l $ by simp [←h]) :=
is_kernel.iso_kernel f l (limit.is_limit _) i h

end transport

section
variables (X Y)

/-- The kernel morphism of a zero morphism is an isomorphism -/
lemma kernel.ι_of_zero : is_iso (kernel.ι (0 : X ⟶ Y)) :=
equalizer.ι_of_self _

end

section

/-- A cokernel cofork is just a cofork where the second morphism is a zero morphism. -/
abbreviation cokernel_cofork := cofork f 0

variables {f}

@[simp, reassoc] lemma cokernel_cofork.condition (s : cokernel_cofork f) : f ≫ s.π = 0 :=
by rw [cofork.condition, zero_comp]

@[simp] lemma cokernel_cofork.π_eq_zero (s : cokernel_cofork f) : s.ι.app zero = 0 :=
by simp [cofork.app_zero_eq_comp_π_right]

/-- A morphism `π` satisfying `f ≫ π = 0` determines a cokernel cofork on `f`. -/
abbreviation cokernel_cofork.of_π {Z : C} (π : Y ⟶ Z) (w : f ≫ π = 0) : cokernel_cofork f :=
cofork.of_π π $ by rw [w, zero_comp]

@[simp] lemma cokernel_cofork.π_of_π {X Y P : C} (f : X ⟶ Y) (π : Y ⟶ P) (w : f ≫ π = 0) :
  cofork.π (cokernel_cofork.of_π π w) = π := rfl

/-- Every cokernel cofork `s` is isomorphic (actually, equal) to `cofork.of_π (cofork.π s) _`. -/
def iso_of_π (s : cofork f 0) : s ≅ cofork.of_π (cofork.π s) (cofork.condition s) :=
cocones.ext (iso.refl _) $ λ j, by cases j; tidy

/-- If `π = π'`, then `cokernel_cofork.of_π π _` and `cokernel_cofork.of_π π' _` are isomorphic. -/
def of_π_congr {P : C} {π π' : Y ⟶ P} {w : f ≫ π = 0} (h : π = π') :
  cokernel_cofork.of_π π w ≅ cokernel_cofork.of_π π' (by rw [←h, w]) :=
cocones.ext (iso.refl _) $ λ j, by cases j; tidy

/-- If `s` is a colimit cokernel cofork, then every `k : Y ⟶ W` satisfying `f ≫ k = 0` induces
    `l : s.X ⟶ W` such that `cofork.π s ≫ l = k`. -/
def cokernel_cofork.is_colimit.desc' {s : cokernel_cofork f} (hs : is_colimit s) {W : C} (k : Y ⟶ W)
  (h : f ≫ k = 0) : {l : s.X ⟶ W // cofork.π s ≫ l = k} :=
⟨hs.desc $ cokernel_cofork.of_π _ h, hs.fac _ _⟩

/--
This is a slightly more convenient method to verify that a cokernel cofork is a colimit cocone.
It only asks for a proof of facts that carry any mathematical content -/
def is_colimit_aux (t : cokernel_cofork f)
  (desc : Π (s : cokernel_cofork f), t.X ⟶ s.X)
  (fac : ∀ (s : cokernel_cofork f), t.π ≫ desc s = s.π)
  (uniq : ∀ (s : cokernel_cofork f) (m : t.X ⟶ s.X) (w : t.π ≫ m = s.π), m = desc s) :
  is_colimit t :=
{ desc := desc,
  fac' := λ s j, by { cases j, { simp, }, { exact fac s, }, },
  uniq' := λ s m w, uniq s m (w limits.walking_parallel_pair.one), }

/--
This is a more convenient formulation to show that a `cokernel_cofork` constructed using
`cokernel_cofork.of_π` is a limit cone.
-/
def is_colimit.of_π {Z : C} (g : Y ⟶ Z) (eq : f ≫ g = 0)
  (desc : Π {Z' : C} (g' : Y ⟶ Z') (eq' : f ≫ g' = 0), Z ⟶ Z')
  (fac : ∀ {Z' : C} (g' : Y ⟶ Z') (eq' : f ≫ g' = 0), g ≫ desc g' eq' = g')
  (uniq :
    ∀ {Z' : C} (g' : Y ⟶ Z') (eq' : f ≫ g' = 0) (m : Z ⟶ Z') (w : g ≫ m = g'), m = desc g' eq') :
  is_colimit (cokernel_cofork.of_π g eq) :=
is_colimit_aux _ (λ s, desc s.π s.condition) (λ s, fac s.π s.condition) (λ s, uniq s.π s.condition)

/-- Every cokernel of `f` induces a cokernel of `g ≫ f` if `g` is epi. -/
def is_cokernel_epi_comp  {c : cokernel_cofork f} (i : is_colimit c) {W} (g : W ⟶ X) [hg : epi g]
  {h : W ⟶ Y} (hh : h = g ≫ f) :
  is_colimit (cokernel_cofork.of_π c.π (by rw [hh]; simp) : cokernel_cofork h) :=
cofork.is_colimit.mk' _ $ λ s,
  let s' : cokernel_cofork f := cofork.of_π s.π
    (by { apply hg.left_cancellation, rw [←category.assoc, ←hh, s.condition], simp }) in
  let l := cokernel_cofork.is_colimit.desc' i s'.π s'.condition in
  ⟨l.1, l.2,
    λ m hm, by apply cofork.is_colimit.hom_ext i; rw cofork.π_of_π at hm; rw hm; exact l.2.symm⟩

@[simp]
lemma is_cokernel_epi_comp_desc {c : cokernel_cofork f} (i : is_colimit c) {W}
  (g : W ⟶ X) [hg : epi g] {h : W ⟶ Y} (hh : h = g ≫ f) (s : cokernel_cofork h) :
  (is_cokernel_epi_comp i g hh).desc s
  = i.desc (cofork.of_π s.π (by { rw [←cancel_epi g, ←category.assoc, ←hh], simp })) := rfl

/-- Every cokernel of `g ≫ f` is also a cokernel of `f`, as long as `f ≫ c.π` vanishes. -/
def is_cokernel_of_comp {W : C} (g : W ⟶ X) (h : W ⟶ Y) {c : cokernel_cofork h} (i : is_colimit c)
  (hf : f ≫ c.π = 0) (hfg : g ≫ f = h) : is_colimit (cokernel_cofork.of_π c.π hf) :=
cofork.is_colimit.mk _
  (λ s, i.desc (cokernel_cofork.of_π s.π (by simp [← hfg])))
  (λ s, by simp only [cokernel_cofork.π_of_π, cofork.is_colimit.π_desc])
  (λ s m h, by { apply cofork.is_colimit.hom_ext i, simpa using h })

end

section
variables [has_cokernel f]

/-- The cokernel of a morphism, expressed as the coequalizer with the 0 morphism. -/
abbreviation cokernel : C := coequalizer f 0

/-- The map from the target of `f` to `cokernel f`. -/
abbreviation cokernel.π : Y ⟶ cokernel f := coequalizer.π f 0

@[simp] lemma coequalizer_as_cokernel : coequalizer.π f 0 = cokernel.π f := rfl

@[simp, reassoc] lemma cokernel.condition : f ≫ cokernel.π f = 0 :=
cokernel_cofork.condition _

/-- The cokernel built from `cokernel.π f` is colimiting. -/
def cokernel_is_cokernel :
  is_colimit (cofork.of_π (cokernel.π f) ((cokernel.condition f).trans (zero_comp.symm))) :=
is_colimit.of_iso_colimit (colimit.is_colimit _) (cofork.ext (iso.refl _) (by tidy))

/-- Given any morphism `k : Y ⟶ W` such that `f ≫ k = 0`, `k` factors through `cokernel.π f`
    via `cokernel.desc : cokernel f ⟶ W`. -/
abbreviation cokernel.desc {W : C} (k : Y ⟶ W) (h : f ≫ k = 0) : cokernel f ⟶ W :=
colimit.desc (parallel_pair f 0) (cokernel_cofork.of_π k h)

@[simp, reassoc]
lemma cokernel.π_desc {W : C} (k : Y ⟶ W) (h : f ≫ k = 0) :
  cokernel.π f ≫ cokernel.desc f k h = k :=
colimit.ι_desc _ _

@[simp]
lemma cokernel.desc_zero {W : C} {h} : cokernel.desc f (0 : Y ⟶ W) h = 0 :=
by { ext, simp, }

instance cokernel.desc_epi
  {W : C} (k : Y ⟶ W) (h : f ≫ k = 0) [epi k] : epi (cokernel.desc f k h) :=
⟨λ Z g g' w,
begin
  replace w := cokernel.π f ≫= w,
  simp only [cokernel.π_desc_assoc] at w,
  exact (cancel_epi k).1 w,
end⟩

/-- Any morphism `k : Y ⟶ W` satisfying `f ≫ k = 0` induces `l : cokernel f ⟶ W` such that
    `cokernel.π f ≫ l = k`. -/
def cokernel.desc' {W : C} (k : Y ⟶ W) (h : f ≫ k = 0) :
  {l : cokernel f ⟶ W // cokernel.π f ≫ l = k} :=
⟨cokernel.desc f k h, cokernel.π_desc _ _ _⟩

/-- A commuting square induces a morphism of cokernels. -/
abbreviation cokernel.map {X' Y' : C} (f' : X' ⟶ Y') [has_cokernel f']
  (p : X ⟶ X') (q : Y ⟶ Y') (w : f ≫ q = p ≫ f') : cokernel f ⟶ cokernel f' :=
cokernel.desc f (q ≫ cokernel.π f') (by simp [reassoc_of w])

/--
Given a commutative diagram
    X --f--> Y --g--> Z
    |        |        |
    |        |        |
    v        v        v
    X' -f'-> Y' -g'-> Z'
with horizontal arrows composing to zero,
then we obtain a commutative square
   cokernel f ---> Z
   |               |
   | cokernel.map  |
   |               |
   v               v
   cokernel f' --> Z'
-/
lemma cokernel.map_desc {X Y Z X' Y' Z' : C}
  (f : X ⟶ Y) [has_cokernel f] (g : Y ⟶ Z) (w : f ≫ g = 0)
  (f' : X' ⟶ Y') [has_cokernel f'] (g' : Y' ⟶ Z') (w' : f' ≫ g' = 0)
  (p : X ⟶ X') (q : Y ⟶ Y') (r : Z ⟶ Z') (h₁ : f ≫ q = p ≫ f') (h₂ : g ≫ r = q ≫ g') :
  cokernel.map f f' p q h₁ ≫ cokernel.desc f' g' w' = cokernel.desc f g w ≫ r :=
by { ext, simp [h₂], }

/-- A commuting square of isomorphisms induces an isomorphism of cokernels. -/
@[simps]
def cokernel.map_iso {X' Y' : C} (f' : X' ⟶ Y') [has_cokernel f']
  (p : X ≅ X') (q : Y ≅ Y') (w : f ≫ q.hom = p.hom ≫ f') : cokernel f ≅ cokernel f' :=
{ hom := cokernel.map f f' p.hom q.hom w,
  inv := cokernel.map f' f p.inv q.inv (by { refine (cancel_mono q.hom).1 _, simp [w], }), }

/-- The cokernel of the zero morphism is an isomorphism -/
instance cokernel.π_zero_is_iso :
  is_iso (cokernel.π (0 : X ⟶ Y)) :=
coequalizer.π_of_self _

lemma eq_zero_of_mono_cokernel [mono (cokernel.π f)] : f = 0 :=
(cancel_mono (cokernel.π f)).1 (by simp)

/-- The cokernel of a zero morphism is isomorphic to the target. -/
def cokernel_zero_iso_target : cokernel (0 : X ⟶ Y) ≅ Y :=
coequalizer.iso_target_of_self 0

@[simp] lemma cokernel_zero_iso_target_hom :
  cokernel_zero_iso_target.hom = cokernel.desc (0 : X ⟶ Y) (𝟙 Y) (by simp) :=
by { ext, simp [cokernel_zero_iso_target], }

@[simp] lemma cokernel_zero_iso_target_inv :
  cokernel_zero_iso_target.inv = cokernel.π (0 : X ⟶ Y) := rfl

/-- If two morphisms are known to be equal, then their cokernels are isomorphic. -/
def cokernel_iso_of_eq {f g : X ⟶ Y} [has_cokernel f] [has_cokernel g] (h : f = g) :
  cokernel f ≅ cokernel g :=
has_colimit.iso_of_nat_iso (by simp[h])

@[simp]
lemma cokernel_iso_of_eq_refl {h : f = f} : cokernel_iso_of_eq h = iso.refl (cokernel f) :=
by { ext, simp [cokernel_iso_of_eq], }

@[simp, reassoc]
lemma π_comp_cokernel_iso_of_eq_hom {f g : X ⟶ Y} [has_cokernel f] [has_cokernel g] (h : f = g) :
  cokernel.π _ ≫ (cokernel_iso_of_eq h).hom = cokernel.π _ :=
by { unfreezingI { induction h, simp } }

@[simp, reassoc]

@[simp, reassoc]
lemma cokernel_iso_of_eq_hom_comp_desc {Z} {f g : X ⟶ Y} [has_cokernel f] [has_cokernel g]
  (h : f = g) (e : Y ⟶ Z) (he) :
  (cokernel_iso_of_eq h).hom ≫ cokernel.desc _ e he = cokernel.desc _ e (by simp [h, he]) :=
by { unfreezingI { induction h, simp } }

@[simp, reassoc]
lemma cokernel_iso_of_eq_inv_comp_desc {Z} {f g : X ⟶ Y} [has_cokernel f] [has_cokernel g]
  (h : f = g) (e : Y ⟶ Z) (he) :
  (cokernel_iso_of_eq h).inv ≫ cokernel.desc _ e he = cokernel.desc _ e (by simp [← h, he]) :=
by { unfreezingI { induction h, simp } }

@[simp]
lemma cokernel_iso_of_eq_trans {f g h : X ⟶ Y} [has_cokernel f] [has_cokernel g] [has_cokernel h]
  (w₁ : f = g) (w₂ : g = h) :
  cokernel_iso_of_eq w₁ ≪≫ cokernel_iso_of_eq w₂ = cokernel_iso_of_eq (w₁.trans w₂) :=
by { unfreezingI { induction w₁, induction w₂, }, ext, simp [cokernel_iso_of_eq], }

variables {f}

lemma cokernel_not_mono_of_nonzero (w : f ≠ 0) : ¬mono (cokernel.π f) :=
λ I, by exactI w (eq_zero_of_mono_cokernel f)

lemma cokernel_not_iso_of_nonzero (w : f ≠ 0) : (is_iso (cokernel.π f)) → false :=
λ I, cokernel_not_mono_of_nonzero w $ by { resetI, apply_instance }

-- TODO the remainder of this section has obvious generalizations to `has_coequalizer f g`.

instance has_cokernel_comp_iso {X Y Z : C} (f : X ⟶ Y) (g : Y ⟶ Z) [has_cokernel f] [is_iso g] :
  has_cokernel (f ≫ g) :=
{ exists_colimit :=
  ⟨{ cocone := cokernel_cofork.of_π (inv g ≫ cokernel.π f) (by simp),
     is_colimit := is_colimit_aux _ (λ s, cokernel.desc _ (g ≫ s.π)
       (by { rw [←category.assoc, cokernel_cofork.condition], }))
       (by tidy)
       (λ s m w, by { simp_rw [←w], ext, simp, }), }⟩ }

/--
When `g` is an isomorphism, the cokernel of `f ≫ g` is isomorphic to the cokernel of `f`.
-/
@[simps]
def cokernel_comp_is_iso {X Y Z : C} (f : X ⟶ Y) (g : Y ⟶ Z) [has_cokernel f] [is_iso g] :
  cokernel (f ≫ g) ≅ cokernel f :=
{ hom := cokernel.desc _ (inv g ≫ cokernel.π f) (by simp),
  inv := cokernel.desc _ (g ≫ cokernel.π (f ≫ g)) (by rw [←category.assoc, cokernel.condition]), }

instance has_cokernel_epi_comp {X Y : C} (f : X ⟶ Y) [has_cokernel f] {W} (g : W ⟶ X) [epi g] :
  has_cokernel (g ≫ f) :=
⟨⟨{ cocone := _, is_colimit := is_cokernel_epi_comp (colimit.is_colimit _) g rfl }⟩⟩

/--
When `f` is an epimorphism, the cokernel of `f ≫ g` is isomorphic to the cokernel of `g`.
-/
@[simps]
def cokernel_epi_comp {X Y Z : C} (f : X ⟶ Y) (g : Y ⟶ Z) [epi f] [has_cokernel g] :
  cokernel (f ≫ g) ≅ cokernel g :=
{ hom := cokernel.desc _ (cokernel.π g) (by simp),
  inv := cokernel.desc _ (cokernel.π (f ≫ g)) (by { rw [←cancel_epi f, ←category.assoc], simp, }), }

end

section has_zero_object
variables [has_zero_object C]

open_locale zero_object

/-- The morphism to the zero object determines a cocone on a cokernel diagram -/
def cokernel.zero_cokernel_cofork : cokernel_cofork f :=
{ X := 0,
  ι := { app := λ j, 0 } }

/-- The morphism to the zero object is a cokernel of an epimorphism -/
def cokernel.is_colimit_cocone_zero_cocone [epi f] :
  is_colimit (cokernel.zero_cokernel_cofork f) :=
cofork.is_colimit.mk _ (λ s, 0)
  (λ s, by { erw zero_comp,
    convert (zero_of_epi_comp f _).symm,
    exact cokernel_cofork.condition _ })
  (λ _ _ _, zero_of_from_zero _)

/-- The cokernel of an epimorphism is isomorphic to the zero object -/
def cokernel.of_epi [has_cokernel f] [epi f] : cokernel f ≅ 0 :=
functor.map_iso (cocones.forget _) $ is_colimit.unique_up_to_iso
  (colimit.is_colimit (parallel_pair f 0)) (cokernel.is_colimit_cocone_zero_cocone f)

/-- The cokernel morphism of an epimorphism is a zero morphism -/
lemma cokernel.π_of_epi [has_cokernel f] [epi f] : cokernel.π f = 0 :=
zero_of_target_iso_zero _ (cokernel.of_epi f)

end has_zero_object

section mono_factorisation
variables {f}

@[simp] lemma mono_factorisation.kernel_ι_comp [has_kernel f] (F : mono_factorisation f) :
  kernel.ι f ≫ F.e = 0 :=
by rw [← cancel_mono F.m, zero_comp, category.assoc, F.fac, kernel.condition]

end mono_factorisation

section has_image

/--
The cokernel of the image inclusion of a morphism `f` is isomorphic to the cokernel of `f`.

(This result requires that the factorisation through the image is an epimorphism.
This holds in any category with equalizers.)
-/
@[simps]
def cokernel_image_ι {X Y : C} (f : X ⟶ Y)
  [has_image f] [has_cokernel (image.ι f)] [has_cokernel f] [epi (factor_thru_image f)] :
  cokernel (image.ι f) ≅ cokernel f :=
{ hom := cokernel.desc _ (cokernel.π f)
  begin
    have w := cokernel.condition f,
    conv at w { to_lhs, congr, rw ←image.fac f, },
    rw [←has_zero_morphisms.comp_zero (limits.factor_thru_image f), category.assoc, cancel_epi]
      at w,
    exact w,
  end,
  inv := cokernel.desc _ (cokernel.π _)
  begin
    conv { to_lhs, congr, rw ←image.fac f, },
    rw [category.assoc, cokernel.condition, has_zero_morphisms.comp_zero],
  end, }

end has_image

section
variables (X Y)

/-- The cokernel of a zero morphism is an isomorphism -/
lemma cokernel.π_of_zero :
  is_iso (cokernel.π (0 : X ⟶ Y)) :=
coequalizer.π_of_self _

end

section has_zero_object
variables [has_zero_object C]

open_locale zero_object

/-- The kernel of the cokernel of an epimorphism is an isomorphism -/
instance kernel.of_cokernel_of_epi [has_cokernel f]
  [has_kernel (cokernel.π f)] [epi f] : is_iso (kernel.ι (cokernel.π f)) :=
equalizer.ι_of_eq $ cokernel.π_of_epi f

/-- The cokernel of the kernel of a monomorphism is an isomorphism -/
instance cokernel.of_kernel_of_mono [has_kernel f]
  [has_cokernel (kernel.ι f)] [mono f] : is_iso (cokernel.π (kernel.ι f)) :=
coequalizer.π_of_eq $ kernel.ι_of_mono f

/-- If `f ≫ g = 0` implies `g = 0` for all `g`, then `0 : Y ⟶ 0` is a cokernel of `f`. -/
def zero_cokernel_of_zero_cancel {X Y : C} (f : X ⟶ Y)
  (hf : ∀ (Z : C) (g : Y ⟶ Z) (hgf : f ≫ g = 0), g = 0) :
    is_colimit (cokernel_cofork.of_π (0 : Y ⟶ 0) (show f ≫ 0 = 0, by simp)) :=
cofork.is_colimit.mk _ (λ s, 0)
  (λ s, by rw [hf _ _ (cokernel_cofork.condition s), comp_zero])
  (λ s m h, by ext)

end has_zero_object

section transport

/-- If `i` is an isomorphism such that `i.hom ≫ l = f`, then any cokernel of `f` is a cokernel of
    `l`. -/
def is_cokernel.of_iso_comp {Z : C} (l : Z ⟶ Y) (i : X ≅ Z) (h : i.hom ≫ l = f)
  {s : cokernel_cofork f} (hs : is_colimit s) : is_colimit (cokernel_cofork.of_π (cofork.π s) $
    show l ≫ cofork.π s = 0, by simp [i.eq_inv_comp.2 h]) :=
cofork.is_colimit.mk _
  (λ s, hs.desc $ cokernel_cofork.of_π (cofork.π s) $ by simp [←h])
  (λ s, by simp)
  (λ s m h, by { apply cofork.is_colimit.hom_ext hs, simpa using h })

/-- If `i` is an isomorphism such that `i.hom ≫ l = f`, then the cokernel of `f` is a cokernel of
    `l`. -/
def cokernel.of_iso_comp [has_cokernel f]
  {Z : C} (l : Z ⟶ Y) (i : X ≅ Z) (h : i.hom ≫ l = f) :
  is_colimit (cokernel_cofork.of_π (cokernel.π f) $
    show l ≫ cokernel.π f = 0, by simp [i.eq_inv_comp.2 h]) :=
is_cokernel.of_iso_comp f l i h $ colimit.is_colimit _

/-- If `s` is any colimit cokernel cocone over `f` and `i` is an isomorphism such that
    `s.π ≫ i.hom = l`, then `l` is a cokernel of `f`. -/
def is_cokernel.cokernel_iso {Z : C} (l : Y ⟶ Z) {s : cokernel_cofork f} (hs : is_colimit s)
  (i : s.X ≅ Z) (h : cofork.π s ≫ i.hom = l) : is_colimit (cokernel_cofork.of_π l $
    show f ≫ l = 0, by simp [←h]) :=
is_colimit.of_iso_colimit hs $ cocones.ext i $ λ j, by { cases j, { simp }, { exact h } }

/-- If `i` is an isomorphism such that `cokernel.π f ≫ i.hom = l`, then `l` is a cokernel of `f`. -/
def cokernel.cokernel_iso [has_cokernel f]
  {Z : C} (l : Y ⟶ Z) (i : cokernel f ≅ Z) (h : cokernel.π f ≫ i.hom = l) :
  is_colimit (cokernel_cofork.of_π l $ by simp [←h]) :=
is_cokernel.cokernel_iso f l (colimit.is_colimit _) i h

end transport

section comparison

variables {D : Type u₂} [category.{v} D] [has_zero_morphisms D]
variables (G : C ⥤ D) [functor.preserves_zero_morphisms G]

/--
The comparison morphism for the kernel of `f`.
This is an isomorphism iff `G` preserves the kernel of `f`; see
`category_theory/limits/preserves/shapes/kernels.lean`
-/
def kernel_comparison [has_kernel f] [has_kernel (G.map f)] :
  G.obj (kernel f) ⟶ kernel (G.map f) :=
kernel.lift _ (G.map (kernel.ι f)) (by simp only [←G.map_comp, kernel.condition, functor.map_zero])

@[simp, reassoc]
lemma kernel_comparison_comp_ι [has_kernel f] [has_kernel (G.map f)] :
  kernel_comparison f G ≫ kernel.ι (G.map f) = G.map (kernel.ι f) :=
kernel.lift_ι _ _ _

@[simp, reassoc]
lemma map_lift_kernel_comparison [has_kernel f] [has_kernel (G.map f)]
  {Z : C} {h : Z ⟶ X} (w : h ≫ f = 0) :
    G.map (kernel.lift _ h w) ≫ kernel_comparison f G =
      kernel.lift _ (G.map h) (by simp only [←G.map_comp, w, functor.map_zero]) :=
by { ext, simp [← G.map_comp] }

/-- The comparison morphism for the cokernel of `f`. -/
def cokernel_comparison [has_cokernel f] [has_cokernel (G.map f)] :
  cokernel (G.map f) ⟶ G.obj (cokernel f) :=
cokernel.desc _ (G.map (coequalizer.π _ _))
  (by simp only [←G.map_comp, cokernel.condition, functor.map_zero])

@[simp, reassoc]
lemma π_comp_cokernel_comparison [has_cokernel f] [has_cokernel (G.map f)] :
  cokernel.π (G.map f) ≫ cokernel_comparison f G = G.map (cokernel.π _) :=
cokernel.π_desc _ _ _

@[simp, reassoc]
lemma cokernel_comparison_map_desc [has_cokernel f] [has_cokernel (G.map f)]
  {Z : C} {h : Y ⟶ Z} (w : f ≫ h = 0) :
  cokernel_comparison f G ≫ G.map (cokernel.desc _ h w) =
    cokernel.desc _ (G.map h) (by simp only [←G.map_comp, w, functor.map_zero]) :=
by { ext, simp [← G.map_comp] }

end comparison

end category_theory.limits

namespace category_theory.limits
variables (C : Type u) [category.{v} C]

variables [has_zero_morphisms C]

/-- `has_kernels` represents the existence of kernels for every morphism. -/
class has_kernels : Prop :=
(has_limit : Π {X Y : C} (f : X ⟶ Y), has_kernel f . tactic.apply_instance)

/-- `has_cokernels` represents the existence of cokernels for every morphism. -/
class has_cokernels : Prop :=
(has_colimit : Π {X Y : C} (f : X ⟶ Y), has_cokernel f . tactic.apply_instance)

attribute [instance, priority 100] has_kernels.has_limit has_cokernels.has_colimit

@[priority 100]
instance has_kernels_of_has_equalizers [has_equalizers C] : has_kernels C :=
{}

@[priority 100]
instance has_cokernels_of_has_coequalizers [has_coequalizers C] : has_cokernels C :=
{}

end category_theory.limits


import algebra.big_operators.ring
import data.real.basic
import algebra.indicator_function
import algebra.algebra.basic
import algebra.order.nonneg

/-!
# Nonnegative rationals
-/

noncomputable theory

open_locale classical big_operators

/-- Nonnegative rational numbers. -/
@[derive [canonically_ordered_comm_semiring, canonically_linear_ordered_add_monoid,
  linear_ordered_comm_group_with_zero, linear_ordered_semiring, has_sub, has_ordered_sub,
  densely_ordered, archimedean, inhabited]]
def nnrat := {q : ℚ // 0 ≤ q}
localized "notation ` ℚ≥0 ` := nnrat" in nnreal

namespace nnrat

instance : has_coe ℚ≥0 ℚ := ⟨subtype.val⟩

/- Simp lemma to put back `n.val` into the normal form given by the coercion. -/
@[simp] lemma val_eq_coe (n : ℚ≥0) : n.val = n := rfl

instance : can_lift ℚ ℚ≥0 :=
{ coe := coe,
  cond := λ r, 0 ≤ r,
  prf := λ x hx, ⟨⟨x, hx⟩, rfl⟩ }

protected lemma eq {n m : ℚ≥0} : (n : ℚ) = (m : ℚ) → n = m := subtype.eq

protected lemma eq_iff {n m : ℚ≥0} : (n : ℚ) = (m : ℚ) ↔ n = m :=
iff.intro nnrat.eq (congr_arg coe)

lemma ne_iff {x y : ℚ≥0} : (x : ℚ) ≠ (y : ℚ) ↔ x ≠ y :=
not_iff_not_of_iff $ nnrat.eq_iff

/-- Reinterpret a rational number `q` as a non-negative rational number. Returns `0` if `q < 0`. -/
protected def of_rat (q : ℚ) : ℚ≥0 := ⟨max q 0, le_max_right _ _⟩

lemma coe_of_rat (q : ℚ) (hr : 0 ≤ q) : (nnrat.of_rat q : ℚ) = q :=
max_eq_left hr

lemma le_coe_of_rat (r : ℚ) : r ≤ nnrat.of_rat r :=
le_max_left r 0

lemma coe_nonneg (r : ℚ≥0) : (0 : ℚ) ≤ r := r.2
@[norm_cast]
theorem coe_mk (a : ℚ) (ha) : ((⟨a, ha⟩ : ℚ≥0) : ℚ) = a := rfl

protected lemma coe_injective : function.injective (coe : ℚ≥0 → ℚ) := subtype.coe_injective
@[simp, norm_cast] protected lemma coe_eq {r₁ r₂ : ℚ≥0} : (r₁ : ℚ) = r₂ ↔ r₁ = r₂ :=
nnrat.coe_injective.eq_iff
protected lemma coe_zero : ((0 : ℚ≥0) : ℚ) = 0 := rfl
protected lemma coe_one  : ((1 : ℚ≥0) : ℚ) = 1 := rfl
protected lemma coe_add (r₁ r₂ : ℚ≥0) : ((r₁ + r₂ : ℚ≥0) : ℚ) = r₁ + r₂ := rfl
protected lemma coe_mul (r₁ r₂ : ℚ≥0) : ((r₁ * r₂ : ℚ≥0) : ℚ) = r₁ * r₂ := rfl
protected lemma coe_inv (r : ℚ≥0) : ((r⁻¹ : ℚ≥0) : ℚ) = r⁻¹ := rfl
protected lemma coe_div (r₁ r₂ : ℚ≥0) : ((r₁ / r₂ : ℚ≥0) : ℚ) = r₁ / r₂ := rfl
@[simp, norm_cast] protected lemma coe_bit0 (r : ℚ≥0) : ((bit0 r : ℚ≥0) : ℚ) = bit0 r := rfl
@[simp, norm_cast] protected lemma coe_bit1 (r : ℚ≥0) : ((bit1 r : ℚ≥0) : ℚ) = bit1 r := rfl

@[simp, norm_cast] protected lemma coe_sub {r₁ r₂ : ℚ≥0} (h : r₂ ≤ r₁) :
  ((r₁ - r₂ : ℚ≥0) : ℚ) = r₁ - r₂ :=
max_eq_left $ le_sub.2 $ by simp [show (r₂ : ℚ) ≤ r₁, from h]

-- TODO: setup semifield!
@[simp] protected lemma coe_eq_zero (r : ℚ≥0) : ↑r = (0 : ℚ) ↔ r = 0 := by norm_cast
lemma coe_ne_zero {r : ℚ≥0} : (r : ℚ) ≠ 0 ↔ r ≠ 0 := by norm_cast

/-- Coercion `ℚ≥0 → ℚ` as a `ring_hom`. -/
def to_rational_hom : ℚ≥0 →+* ℚ :=
⟨coe, nnrat.coe_one, nnrat.coe_mul, nnrat.coe_zero, nnrat.coe_add⟩

/-- The rational numbers are an algebra over the non-negative rationals. -/
instance : algebra ℚ≥0 ℚ := to_rational_hom.to_algebra

/-- A `module` over `ℚ` restricts to a `module` over `ℚ≥0`. -/
instance {M : Type*} [add_comm_monoid M] [module ℚ M] : module ℚ≥0 M :=
module.comp_hom M to_rational_hom

@[simp] lemma coe_to_rational_hom : ⇑to_rational_hom = coe := rfl

@[simp, norm_cast] lemma coe_indicator {α} (s : set α) (f : α → ℚ≥0) (a : α) :
  ((s.indicator f a : ℚ≥0) : ℚ) = s.indicator (λ x, f x) a :=
(to_rational_hom : ℚ≥0 →+ ℚ).map_indicator _ _ _

@[simp, norm_cast] lemma coe_pow (r : ℚ≥0) (n : ℕ) : ((r^n : ℚ≥0) : ℚ) = r^n :=
to_rational_hom.map_pow r n

@[norm_cast] lemma coe_list_sum (l : list ℚ≥0) :
  ((l.sum : ℚ≥0) : ℚ) = (l.map coe).sum :=
to_rational_hom.map_list_sum l

@[norm_cast] lemma coe_list_prod (l : list ℚ≥0) :
  ((l.prod : ℚ≥0) : ℚ) = (l.map coe).prod :=
to_rational_hom.map_list_prod l

@[norm_cast] lemma coe_multiset_sum (s : multiset ℚ≥0) :
  ((s.sum : ℚ≥0) : ℚ) = (s.map coe).sum :=
to_rational_hom.map_multiset_sum s

@[norm_cast] lemma coe_multiset_prod (s : multiset ℚ≥0) :
  ((s.prod : ℚ≥0) : ℚ) = (s.map coe).prod :=
to_rational_hom.map_multiset_prod s

@[norm_cast] lemma coe_sum {α} {s : finset α} {f : α → ℚ≥0} :
  ↑(∑ a in s, f a) = ∑ a in s, (f a : ℚ) :=
to_rational_hom.map_sum _ _

lemma of_rat_sum_of_nonneg {α} {s : finset α} {f : α → ℚ} (hf : ∀ a, a ∈ s → 0 ≤ f a) :
  nnrat.of_rat (∑ a in s, f a) = ∑ a in s, nnrat.of_rat (f a) :=
begin
  rw [←nnrat.coe_eq, nnrat.coe_sum, nnrat.coe_of_rat _ (finset.sum_nonneg hf)],
  exact finset.sum_congr rfl (λ x hxs, by rw nnrat.coe_of_rat _ (hf x hxs)),
end

@[norm_cast] lemma coe_prod {α} {s : finset α} {f : α → ℚ≥0} :
  ↑(∏ a in s, f a) = ∏ a in s, (f a : ℚ) :=
to_rational_hom.map_prod _ _

lemma of_rat_prod_of_nonneg {α} {s : finset α} {f : α → ℚ} (hf : ∀ a, a ∈ s → 0 ≤ f a) :
  nnrat.of_rat (∏ a in s, f a) = ∏ a in s, nnrat.of_rat (f a) :=
begin
  rw [←nnrat.coe_eq, nnrat.coe_prod, nnrat.coe_of_rat _ (finset.prod_nonneg hf)],
  exact finset.prod_congr rfl (λ x hxs, by rw nnrat.coe_of_rat _ (hf x hxs)),
end

@[norm_cast] lemma nsmul_coe (r : ℚ≥0) (n : ℕ) : ↑(n • r) = n • (r:ℚ) :=
to_rational_hom.to_add_monoid_hom.map_nsmul _ _

@[simp, norm_cast] protected lemma coe_nat_cast (n : ℕ) : (↑(↑n : ℚ≥0) : ℚ) = n :=
map_nat_cast to_rational_hom n

@[simp, norm_cast] protected lemma coe_le_coe {r₁ r₂ : ℚ≥0} : (r₁ : ℚ) ≤ r₂ ↔ r₁ ≤ r₂ := iff.rfl
@[simp, norm_cast] protected lemma coe_lt_coe {r₁ r₂ : ℚ≥0} : (r₁ : ℚ) < r₂ ↔ r₁ < r₂ := iff.rfl
@[simp, norm_cast] protected lemma coe_pos {r : ℚ≥0} : (0 : ℚ) < r ↔ 0 < r := iff.rfl

protected lemma coe_mono : monotone (coe : ℚ≥0 → ℚ) := λ _ _, nnrat.coe_le_coe.2

protected lemma of_rat_mono : monotone nnrat.of_rat :=
λ x y h, max_le_max h (le_refl 0)

@[simp] lemma of_rat_coe {r : ℚ≥0} : nnrat.of_rat r = r :=
nnrat.eq $ max_eq_left r.2

@[simp] lemma mk_coe_nat (n : ℕ) : @eq ℚ≥0 (⟨(n : ℚ), n.cast_nonneg⟩ : ℚ≥0) n :=
nnrat.eq (nnrat.coe_nat_cast n).symm

@[simp] lemma of_rat_coe_nat (n : ℕ) : nnrat.of_rat n = n :=
nnrat.eq $ by simp [coe_of_rat]

/-- `nnrat.of_rat` and `coe : ℚ≥0 → ℚ` form a Galois insertion. -/
protected def gi : galois_insertion nnrat.of_rat coe :=
galois_insertion.monotone_intro nnrat.coe_mono nnrat.of_rat_mono
  le_coe_of_rat (λ _, of_rat_coe)

-- TODO: why are these three instances necessary? why aren't they inferred? (same for `ℝ≥0`)
instance covariant_add : covariant_class ℚ≥0 ℚ≥0 (+) (≤) :=
ordered_add_comm_monoid.to_covariant_class_left ℚ≥0

instance contravariant_add : contravariant_class ℚ≥0 ℚ≥0 (+) (<) :=
ordered_cancel_add_comm_monoid.to_contravariant_class_left ℚ≥0

instance covariant_mul : covariant_class ℚ≥0 ℚ≥0 (*) (≤) :=
ordered_comm_monoid.to_covariant_class_left ℚ≥0

lemma bdd_above_coe {s : set ℚ≥0} : bdd_above ((coe : ℚ≥0 → ℚ) '' s) ↔ bdd_above s :=
iff.intro
  (assume ⟨b, hb⟩, ⟨nnrat.of_rat b, assume ⟨y, hy⟩ hys, show y ≤ max b 0, from
    le_trans (hb $ set.mem_image_of_mem _ hys) (le_max_left _ _)⟩)
  (assume ⟨b, hb⟩, ⟨b, assume y ⟨x, hx, eq⟩, eq ▸ hb hx⟩)

lemma bdd_below_coe (s : set ℚ≥0) : bdd_below ((coe : ℚ≥0 → ℚ) '' s) :=
⟨0, assume r ⟨q, _, eq⟩, eq ▸ q.2⟩

lemma le_of_forall_pos_le_add {a b : ℚ≥0} (h : ∀ε, 0 < ε → a ≤ b + ε) : a ≤ b :=
le_of_forall_le_of_dense $ assume x hxb,
begin
  rcases le_iff_exists_add.1 (le_of_lt hxb) with ⟨ε, rfl⟩,
  exact h _ ((lt_add_iff_pos_right b).1 hxb)
end

-- TODO: generalize to some ordered add_monoids, based on #6145
lemma le_of_add_le_left {a b c : ℚ≥0} (h : a + b ≤ c) : a ≤ c :=
by { refine le_trans _ h, simp }

lemma le_of_add_le_right {a b c : ℚ≥0} (h : a + b ≤ c) : b ≤ c :=
by { refine le_trans _ h, simp }

lemma bot_eq_zero : (⊥ : ℚ≥0) = 0 := rfl

lemma mul_sup (a b c : ℚ≥0) : a * (b ⊔ c) = (a * b) ⊔ (a * c) :=
begin
  cases le_total b c with h h,
  { simp [sup_eq_max, max_eq_right h, max_eq_right (mul_le_mul_of_nonneg_left h (zero_le a))] },
  { simp [sup_eq_max, max_eq_left h, max_eq_left (mul_le_mul_of_nonneg_left h (zero_le a))] },
end

lemma mul_finset_sup {α} {f : α → ℚ≥0} {s : finset α} (r : ℚ≥0) :
  r * s.sup f = s.sup (λa, r * f a) :=
begin
  refine s.induction_on _ _,
  { simp [bot_eq_zero] },
  { assume a s has ih, simp [has, ih, mul_sup], }
end

@[simp, norm_cast] lemma coe_max (x y : ℚ≥0) :
  ((max x y : ℚ≥0) : ℚ) = max (x : ℚ) (y : ℚ) :=
nnrat.coe_mono.map_max

@[simp, norm_cast] lemma coe_min (x y : ℚ≥0) :
  ((min x y : ℚ≥0) : ℚ) = min (x : ℚ) (y : ℚ) :=
nnrat.coe_mono.map_min

section of_rat

@[simp] lemma zero_le_coe {q : ℚ≥0} : 0 ≤ (q : ℚ) := q.2

@[simp] lemma of_rat_zero : nnrat.of_rat 0 = 0 :=
by simp [nnrat.of_rat]; refl

@[simp] lemma of_rat_one : nnrat.of_rat 1 = 1 :=
by simp [nnrat.of_rat, max_eq_left (zero_le_one : (0 :ℚ) ≤ 1)]; refl

@[simp] lemma of_rat_pos {r : ℚ} : 0 < nnrat.of_rat r ↔ 0 < r :=
by simp [nnrat.of_rat, nnrat.coe_lt_coe.symm, lt_irrefl]

@[simp] lemma of_rat_eq_zero {r : ℚ} : nnrat.of_rat r = 0 ↔ r ≤ 0 :=
by simpa [-of_rat_pos] using (not_iff_not.2 (@of_rat_pos r))

lemma of_rat_of_nonpos {r : ℚ} : r ≤ 0 → nnrat.of_rat r = 0 :=
of_rat_eq_zero.2

@[simp] lemma of_rat_le_of_rat_iff {r p : ℚ} (hp : 0 ≤ p) :
  nnrat.of_rat r ≤ nnrat.of_rat p ↔ r ≤ p :=
by simp [nnrat.coe_le_coe.symm, nnrat.of_rat, hp]

@[simp] lemma of_rat_lt_of_rat_iff' {r p : ℚ} :
  nnrat.of_rat r < nnrat.of_rat p ↔ r < p ∧ 0 < p :=
by { simp [nnrat.coe_lt_coe.symm, nnrat.of_rat, lt_irrefl], intros h0p hr0, exact hr0.trans h0p }

lemma of_rat_lt_of_rat_iff {r p : ℚ} (h : 0 < p) :
  nnrat.of_rat r < nnrat.of_rat p ↔ r < p :=
of_rat_lt_of_rat_iff'.trans (and_iff_left h)

lemma of_rat_lt_of_rat_iff_of_nonneg {r p : ℚ} (hr : 0 ≤ r) :
  nnrat.of_rat r < nnrat.of_rat p ↔ r < p :=
of_rat_lt_of_rat_iff'.trans ⟨and.left, λ h, ⟨h, lt_of_le_of_lt hr h⟩⟩

@[simp] lemma of_rat_add {r p : ℚ} (hr : 0 ≤ r) (hp : 0 ≤ p) :
  nnrat.of_rat (r + p) = nnrat.of_rat r + nnrat.of_rat p :=
nnrat.eq $ by simp [nnrat.of_rat, hr, hp, add_nonneg]

lemma of_rat_add_of_rat {r p : ℚ} (hr : 0 ≤ r) (hp : 0 ≤ p) :
  nnrat.of_rat r + nnrat.of_rat p = nnrat.of_rat (r + p) :=
(of_rat_add hr hp).symm

lemma of_rat_le_of_rat {r p : ℚ} (h : r ≤ p) : nnrat.of_rat r ≤ nnrat.of_rat p :=
nnrat.of_rat_mono h

lemma of_rat_add_le {r p : ℚ} : nnrat.of_rat (r + p) ≤ nnrat.of_rat r + nnrat.of_rat p :=
nnrat.coe_le_coe.1 $ max_le (add_le_add (le_max_left _ _) (le_max_left _ _)) nnrat.zero_le_coe

lemma of_rat_le_iff_le_coe {r : ℚ} {p : ℚ≥0} : nnrat.of_rat r ≤ p ↔ r ≤ ↑p :=
nnrat.gi.gc r p

lemma le_of_rat_iff_coe_le {r : ℚ≥0} {p : ℚ} (hp : 0 ≤ p) : r ≤ nnrat.of_rat p ↔ ↑r ≤ p :=
by rw [← nnrat.coe_le_coe, nnrat.coe_of_rat p hp]

lemma le_of_rat_iff_coe_le' {r : ℚ≥0} {p : ℚ} (hr : 0 < r) : r ≤ nnrat.of_rat p ↔ ↑r ≤ p :=
(le_or_lt 0 p).elim le_of_rat_iff_coe_le $ λ hp,
  by simp only [(hp.trans_le r.coe_nonneg).not_le, of_rat_eq_zero.2 hp.le, hr.not_le]

lemma of_rat_lt_iff_lt_coe {r : ℚ} {p : ℚ≥0} (ha : 0 ≤ r) : nnrat.of_rat r < p ↔ r < ↑p :=
by rw [← nnrat.coe_lt_coe, nnrat.coe_of_rat r ha]

lemma lt_of_rat_iff_coe_lt {r : ℚ≥0} {p : ℚ} : r < nnrat.of_rat p ↔ ↑r < p :=
begin
  cases le_total 0 p,
  { rw [← nnrat.coe_lt_coe, nnrat.coe_of_rat p h] },
  { rw [of_rat_eq_zero.2 h], split,
    intro, have := not_lt_of_le (zero_le r), contradiction,
    intro rp, have : ¬(p ≤ 0) := not_le_of_lt (lt_of_le_of_lt (coe_nonneg _) rp), contradiction }
end

@[simp] lemma of_rat_bit0 {r : ℚ} (hr : 0 ≤ r) :
  nnrat.of_rat (bit0 r) = bit0 (nnrat.of_rat r) :=
of_rat_add hr hr

@[simp] lemma of_rat_bit1 {r : ℚ} (hr : 0 ≤ r) :
  nnrat.of_rat (bit1 r) = bit1 (nnrat.of_rat r) :=
(of_rat_add (by simp [hr]) zero_le_one).trans (by simp [of_rat_one, bit1, hr])

end of_rat

section mul

lemma mul_eq_mul_left {a b c : ℚ≥0} (h : a ≠ 0) : (a * b = a * c ↔ b = c) :=
begin
  rw [← nnrat.eq_iff, ← nnrat.eq_iff, nnrat.coe_mul, nnrat.coe_mul], split,
  { exact mul_left_cancel₀ (mt (@nnrat.eq_iff a 0).1 h) },
  { assume h, rw [h] }
end

lemma of_rat_mul {p q : ℚ} (hp : 0 ≤ p) :
  nnrat.of_rat (p * q) = nnrat.of_rat p * nnrat.of_rat q :=
begin
  cases le_total 0 q with hq hq,
  { apply nnrat.eq,
    simp [nnrat.of_rat, hp, hq, max_eq_left, mul_nonneg] },
  { have hpq := mul_nonpos_of_nonneg_of_nonpos hp hq,
    rw [of_rat_eq_zero.2 hq, of_rat_eq_zero.2 hpq, mul_zero] }
end

end mul

section sub

lemma sub_def {r p : ℚ≥0} : r - p = nnrat.of_rat (r - p) := rfl

lemma sub_eq_zero {r p : ℚ≥0} (h : r ≤ p) : r - p = 0 :=
nnrat.eq $ max_eq_right $ sub_le_iff_le_add.2 $ by simpa [nnrat.coe_le_coe] using h

@[simp] lemma sub_self {r : ℚ≥0} : r - r = 0 := sub_eq_zero $ le_refl r

@[simp] lemma sub_zero {r : ℚ≥0} : r - 0 = r :=
by rw [sub_def, nnrat.coe_zero, sub_zero, nnrat.of_rat_coe]

lemma sub_pos {r p : ℚ≥0} : 0 < r - p ↔ p < r :=
of_rat_pos.trans $ sub_pos.trans $ nnrat.coe_lt_coe

protected lemma sub_lt_self {r p : ℚ≥0} : 0 < r → 0 < p → r - p < r :=
assume hr hp,
begin
  cases le_total r p,
  { rwa [sub_eq_zero h] },
  { rw [← nnrat.coe_lt_coe, nnrat.coe_sub h], exact sub_lt_self _ hp }
end

@[simp] lemma sub_le_iff_le_add {r p q : ℚ≥0} : r - p ≤ q ↔ r ≤ q + p :=
match le_total p r with
| or.inl h := by rw [← nnrat.coe_le_coe, ← nnrat.coe_le_coe, nnrat.coe_sub h, nnrat.coe_add,
    sub_le_iff_le_add]
| or.inr h :=
  have r ≤ p + q, from le_add_right h,
  by simpa [nnrat.coe_le_coe, nnrat.coe_le_coe, sub_eq_zero h, add_comm]
end

@[simp] lemma sub_le_self {r p : ℚ≥0} : r - p ≤ r :=
sub_le_iff_le_add.2 $ le_add_right $ le_refl r

lemma add_sub_cancel {r p : ℚ≥0} : (p + r) - r = p :=
nnrat.eq $ by rw [nnrat.coe_sub, nnrat.coe_add, add_sub_cancel]; exact le_add_left (le_refl _)

lemma add_sub_cancel' {r p : ℚ≥0} : (r + p) - r = p :=
by rw [add_comm, add_sub_cancel]

lemma sub_add_eq_max {r p : ℚ≥0} : (r - p) + p = max r p :=
nnrat.eq $ by rw [sub_def, nnrat.coe_add, coe_max, nnrat.of_rat, coe_mk,
  ← max_add_add_right, zero_add, sub_add_cancel]

lemma add_sub_eq_max {r p : ℚ≥0} : p + (r - p) = max p r :=
by rw [add_comm, sub_add_eq_max, max_comm]

@[simp] lemma sub_add_cancel_of_le {a b : ℚ≥0} (h : b ≤ a) : (a - b) + b = a :=
by rw [sub_add_eq_max, max_eq_left h]

lemma sub_sub_cancel_of_le {r p : ℚ≥0} (h : r ≤ p) : p - (p - r) = r :=
by rw [nnrat.sub_def, nnrat.sub_def, nnrat.coe_of_rat _ $ sub_nonneg.2 h,
  sub_sub_cancel, nnrat.of_rat_coe]

lemma lt_sub_iff_add_lt {p q r : ℚ≥0} : p < q - r ↔ p + r < q :=
begin
  split,
  { assume H,
    have : (((q - r) : ℚ≥0) : ℚ) = (q : ℚ) - (r : ℚ) :=
      nnrat.coe_sub (le_of_lt (sub_pos.1 (lt_of_le_of_lt (zero_le _) H))),
    rwa [← nnrat.coe_lt_coe, this, lt_sub_iff_add_lt, ← nnrat.coe_add] at H },
  { assume H,
    have : r ≤ q := le_trans (le_add_left (le_refl _)) (le_of_lt H),
    rwa [← nnrat.coe_lt_coe, nnrat.coe_sub this, lt_sub_iff_add_lt, ← nnrat.coe_add] }
end

lemma sub_lt_iff_lt_add {a b c : ℚ≥0} (h : b ≤ a) : a - b < c ↔ a < b + c :=
by simp only [←nnrat.coe_lt_coe, nnrat.coe_sub h, nnrat.coe_add, sub_lt_iff_lt_add']

lemma sub_eq_iff_eq_add {a b c : ℚ≥0} (h : b ≤ a) : a - b = c ↔ a = c + b :=
by rw [←nnrat.eq_iff, nnrat.coe_sub h, ←nnrat.eq_iff, nnrat.coe_add, sub_eq_iff_eq_add]

end sub

section inv

lemma sum_div {ι} (s : finset ι) (f : ι → ℚ≥0) (b : ℚ≥0) :
  (∑ i in s, f i) / b = ∑ i in s, (f i / b) :=
by simp only [div_eq_mul_inv, finset.sum_mul]

@[simp] lemma inv_pos {r : ℚ≥0} : 0 < r⁻¹ ↔ 0 < r :=
by simp [pos_iff_ne_zero]

lemma div_pos {r p : ℚ≥0} (hr : 0 < r) (hp : 0 < p) : 0 < r / p :=
by simpa only [div_eq_mul_inv] using mul_pos hr (inv_pos.2 hp)

protected lemma mul_inv {r p : ℚ≥0} : (r * p)⁻¹ = p⁻¹ * r⁻¹ := nnrat.eq $ mul_inv_rev _ _

lemma div_self_le (r : ℚ≥0) : r / r ≤ 1 :=
if h : r = 0 then by simp [h] else by rw [div_self h]

@[simp] lemma inv_le {r p : ℚ≥0} (h : r ≠ 0) : r⁻¹ ≤ p ↔ 1 ≤ r * p :=
by rw [← mul_le_mul_left (pos_iff_ne_zero.2 h), mul_inv_cancel h]

lemma inv_le_of_le_mul {r p : ℚ≥0} (h : 1 ≤ r * p) : r⁻¹ ≤ p :=
by by_cases r = 0; simp [*, inv_le]

@[simp] lemma le_inv_iff_mul_le {r p : ℚ≥0} (h : p ≠ 0) : (r ≤ p⁻¹ ↔ r * p ≤ 1) :=
by rw [← mul_le_mul_left (pos_iff_ne_zero.2 h), mul_inv_cancel h, mul_comm]

@[simp] lemma lt_inv_iff_mul_lt {r p : ℚ≥0} (h : p ≠ 0) : (r < p⁻¹ ↔ r * p < 1) :=
by rw [← mul_lt_mul_left (pos_iff_ne_zero.2 h), mul_inv_cancel h, mul_comm]

lemma mul_le_iff_le_inv {a b r : ℚ≥0} (hr : r ≠ 0) : r * a ≤ b ↔ a ≤ r⁻¹ * b :=
have 0 < r, from lt_of_le_of_ne (zero_le r) hr.symm,
by rw [← @mul_le_mul_left _ _ a _ r this, ← mul_assoc, mul_inv_cancel hr, one_mul]

lemma le_div_iff_mul_le {a b r : ℚ≥0} (hr : r ≠ 0) : a ≤ b / r ↔ a * r ≤ b :=
by rw [div_eq_inv_mul, ← mul_le_iff_le_inv hr, mul_comm]

lemma div_le_iff {a b r : ℚ≥0} (hr : r ≠ 0) : a / r ≤ b ↔ a ≤ b * r :=
@div_le_iff ℚ _ a r b $ pos_iff_ne_zero.2 hr

lemma lt_div_iff {a b r : ℚ≥0} (hr : r ≠ 0) : a < b / r ↔ a * r < b :=
lt_iff_lt_of_le_iff_le (div_le_iff hr)

lemma mul_lt_of_lt_div {a b r : ℚ≥0} (h : a < b / r) : a * r < b :=
begin
  refine (lt_div_iff $ λ hr, false.elim _).1 h,
  subst r,
  simpa using h
end

lemma le_of_forall_lt_one_mul_le {x y : ℚ≥0} (h : ∀a<1, a * x ≤ y) : x ≤ y :=
le_of_forall_ge_of_dense $ assume a ha,
  have hx : x ≠ 0 := pos_iff_ne_zero.1 (lt_of_le_of_lt (zero_le _) ha),
  have hx' : x⁻¹ ≠ 0, by rwa [(≠), inv_eq_zero],
  have a * x⁻¹ < 1, by rwa [← lt_inv_iff_mul_lt hx', inv_inv],
  have (a * x⁻¹) * x ≤ y, from h _ this,
  by rwa [mul_assoc, inv_mul_cancel hx, mul_one] at this

lemma div_add_div_same (a b c : ℚ≥0) : a / c + b / c = (a + b) / c :=
eq.symm $ right_distrib a b (c⁻¹)

lemma half_pos {a : ℚ≥0} (h : 0 < a) : 0 < a / 2 := div_pos h zero_lt_two

lemma add_halves (a : ℚ≥0) : a / 2 + a / 2 = a := nnrat.eq (add_halves a)

lemma half_lt_self {a : ℚ≥0} (h : a ≠ 0) : a / 2 < a :=
by rw [← nnrat.coe_lt_coe, nnrat.coe_div]; exact
half_lt_self (bot_lt_iff_ne_bot.2 h)

lemma two_inv_lt_one : (2⁻¹:ℚ≥0) < 1 :=
by simpa using half_lt_self zero_ne_one.symm

lemma div_lt_iff {a b c : ℚ≥0} (hc : c ≠ 0) : b / c < a ↔ b < a * c :=
lt_iff_lt_of_le_iff_le $ nnrat.le_div_iff_mul_le hc

lemma div_lt_one_of_lt {a b : ℚ≥0} (h : a < b) : a / b < 1 :=
begin
  rwa [div_lt_iff, one_mul],
  exact ne_of_gt (lt_of_le_of_lt (zero_le _) h)
end

@[field_simps] lemma div_add_div (a : ℚ≥0) {b : ℚ≥0} (c : ℚ≥0) {d : ℚ≥0}
  (hb : b ≠ 0) (hd : d ≠ 0) : a / b + c / d = (a * d + b * c) / (b * d) :=
begin
  rw ← nnrat.eq_iff,
  simp only [nnrat.coe_add, nnrat.coe_div, nnrat.coe_mul],
  exact div_add_div _ _ (coe_ne_zero.2 hb) (coe_ne_zero.2 hd)
end

@[field_simps] lemma add_div' (a b c : ℚ≥0) (hc : c ≠ 0) :
  b + a / c = (b * c + a) / c :=
by simpa using div_add_div b a one_ne_zero hc

@[field_simps] lemma div_add' (a b c : ℚ≥0) (hc : c ≠ 0) :
  a / c + b = (a + b * c) / c :=
by rwa [add_comm, add_div', add_comm]

lemma of_rat_inv {x : ℚ} :
  nnrat.of_rat x⁻¹ = (nnrat.of_rat x)⁻¹ :=
begin
  by_cases hx : 0 ≤ x,
  { nth_rewrite 0 ← coe_of_rat x hx,
    rw [←nnrat.coe_inv, of_rat_coe], },
  { have hx' := le_of_not_ge hx,
    rw [of_rat_eq_zero.mpr hx', inv_zero, of_rat_eq_zero.mpr (inv_nonpos.mpr hx')], },
end

lemma of_rat_div {x y : ℚ} (hx : 0 ≤ x) :
  nnrat.of_rat (x / y) = nnrat.of_rat x / nnrat.of_rat y :=
by rw [div_eq_mul_inv, div_eq_mul_inv, ←of_rat_inv, ←of_rat_mul hx]

lemma of_rat_div' {x y : ℚ} (hy : 0 ≤ y) :
  nnrat.of_rat (x / y) = nnrat.of_rat x / nnrat.of_rat y :=
by rw [div_eq_inv_mul, div_eq_inv_mul, of_rat_mul (inv_nonneg.2 hy), of_rat_inv]

end inv

@[simp] lemma abs_eq (x : ℚ≥0) : abs (x : ℚ) = x :=
abs_of_nonneg x.property

end nnrat

/-- The absolute value on `ℚ` as a map to `ℚ≥0`. -/
@[pp_nodot] def rational.nnabs (x : ℚ) : ℚ≥0 := ⟨abs x, abs_nonneg x⟩

@[norm_cast, simp] lemma nnrat.coe_nnabs (x : ℚ) : (rational.nnabs x : ℚ) = abs x :=
by simp [rational.nnabs]


/-
Copyright (c) 2016 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad

The order relation on the integers.
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.data.int.basic
import Mathlib.Lean3Lib.init.data.ordering.basic

namespace Mathlib

namespace int

def nonneg (a : ℤ) :=
  int.cases_on a (fun (n : ℕ) => True) fun (n : ℕ) => False

protected def le (a : ℤ) (b : ℤ) :=
  nonneg (b - a)

protected instance has_le : HasLessEq ℤ :=
  { LessEq := int.le }

protected def lt (a : ℤ) (b : ℤ) :=
  a + 1 ≤ b

protected instance has_lt : HasLess ℤ :=
  { Less := int.lt }

def decidable_nonneg (a : ℤ) : Decidable (nonneg a) :=
  int.cases_on a (fun (a : ℕ) => decidable.true) fun (a : ℕ) => decidable.false

protected instance decidable_le (a : ℤ) (b : ℤ) : Decidable (a ≤ b) :=
  decidable_nonneg (b - a)

protected instance decidable_lt (a : ℤ) (b : ℤ) : Decidable (a < b) :=
  decidable_nonneg (b - (a + 1))

theorem lt_iff_add_one_le (a : ℤ) (b : ℤ) : a < b ↔ a + 1 ≤ b :=
  iff.refl (a < b)

theorem nonneg.elim {a : ℤ} : nonneg a → ∃ (n : ℕ), a = ↑n :=
  int.cases_on a (fun (n : ℕ) (H : nonneg (Int.ofNat n)) => exists.intro n rfl) fun (n' : ℕ) => false.elim

theorem nonneg_or_nonneg_neg (a : ℤ) : nonneg a ∨ nonneg (-a) :=
  int.cases_on a (fun (n : ℕ) => Or.inl trivial) fun (n : ℕ) => Or.inr trivial

theorem le.intro_sub {a : ℤ} {b : ℤ} {n : ℕ} (h : b - a = ↑n) : a ≤ b :=
  (fun (this : nonneg (b - a)) => this) (eq.mpr (id (Eq._oldrec (Eq.refl (nonneg (b - a))) h)) trivial)

theorem le.intro {a : ℤ} {b : ℤ} {n : ℕ} (h : a + ↑n = b) : a ≤ b := sorry

theorem le.dest_sub {a : ℤ} {b : ℤ} (h : a ≤ b) : ∃ (n : ℕ), b - a = ↑n :=
  nonneg.elim h

theorem le.dest {a : ℤ} {b : ℤ} (h : a ≤ b) : ∃ (n : ℕ), a + ↑n = b := sorry

theorem le.elim {a : ℤ} {b : ℤ} (h : a ≤ b) {P : Prop} (h' : ∀ (n : ℕ), a + ↑n = b → P) : P :=
  exists.elim (le.dest h) h'

protected theorem le_total (a : ℤ) (b : ℤ) : a ≤ b ∨ b ≤ a := sorry

theorem coe_nat_le_coe_nat_of_le {m : ℕ} {n : ℕ} (h : m ≤ n) : ↑m ≤ ↑n := sorry

theorem le_of_coe_nat_le_coe_nat {m : ℕ} {n : ℕ} (h : ↑m ≤ ↑n) : m ≤ n :=
  le.elim h
    fun (k : ℕ) (hk : ↑m + ↑k = ↑n) =>
      (fun (this : m + k = n) => nat.le.intro this) (int.coe_nat_inj (Eq.trans (int.coe_nat_add m k) hk))

theorem coe_nat_le_coe_nat_iff (m : ℕ) (n : ℕ) : ↑m ≤ ↑n ↔ m ≤ n :=
  { mp := le_of_coe_nat_le_coe_nat, mpr := coe_nat_le_coe_nat_of_le }

theorem coe_zero_le (n : ℕ) : 0 ≤ ↑n :=
  coe_nat_le_coe_nat_of_le (nat.zero_le n)

theorem eq_coe_of_zero_le {a : ℤ} (h : 0 ≤ a) : ∃ (n : ℕ), a = ↑n := sorry

theorem eq_succ_of_zero_lt {a : ℤ} (h : 0 < a) : ∃ (n : ℕ), a = ↑(Nat.succ n) := sorry

theorem lt_add_succ (a : ℤ) (n : ℕ) : a < a + ↑(Nat.succ n) := sorry

theorem lt.intro {a : ℤ} {b : ℤ} {n : ℕ} (h : a + ↑(Nat.succ n) = b) : a < b :=
  h ▸ lt_add_succ a n

theorem lt.dest {a : ℤ} {b : ℤ} (h : a < b) : ∃ (n : ℕ), a + ↑(Nat.succ n) = b := sorry

theorem lt.elim {a : ℤ} {b : ℤ} (h : a < b) {P : Prop} (h' : ∀ (n : ℕ), a + ↑(Nat.succ n) = b → P) : P :=
  exists.elim (lt.dest h) h'

theorem coe_nat_lt_coe_nat_iff (n : ℕ) (m : ℕ) : ↑n < ↑m ↔ n < m := sorry

theorem lt_of_coe_nat_lt_coe_nat {m : ℕ} {n : ℕ} (h : ↑m < ↑n) : m < n :=
  iff.mp (coe_nat_lt_coe_nat_iff m n) h

theorem coe_nat_lt_coe_nat_of_lt {m : ℕ} {n : ℕ} (h : m < n) : ↑m < ↑n :=
  iff.mpr (coe_nat_lt_coe_nat_iff m n) h

/- show that the integers form an ordered additive group -/

protected theorem le_refl (a : ℤ) : a ≤ a :=
  le.intro (int.add_zero a)

protected theorem le_trans {a : ℤ} {b : ℤ} {c : ℤ} (h₁ : a ≤ b) (h₂ : b ≤ c) : a ≤ c := sorry

protected theorem le_antisymm {a : ℤ} {b : ℤ} (h₁ : a ≤ b) (h₂ : b ≤ a) : a = b := sorry

protected theorem lt_irrefl (a : ℤ) : ¬a < a := sorry

protected theorem ne_of_lt {a : ℤ} {b : ℤ} (h : a < b) : a ≠ b :=
  fun (this : a = b) => absurd (eq.mp (Eq._oldrec (Eq.refl (a < b)) this) h) (int.lt_irrefl b)

theorem le_of_lt {a : ℤ} {b : ℤ} (h : a < b) : a ≤ b :=
  lt.elim h fun (n : ℕ) (hn : a + ↑(Nat.succ n) = b) => le.intro hn

protected theorem lt_iff_le_and_ne (a : ℤ) (b : ℤ) : a < b ↔ a ≤ b ∧ a ≠ b := sorry

theorem lt_succ (a : ℤ) : a < a + 1 :=
  int.le_refl (a + 1)

protected theorem add_le_add_left {a : ℤ} {b : ℤ} (h : a ≤ b) (c : ℤ) : c + a ≤ c + b := sorry

protected theorem add_lt_add_left {a : ℤ} {b : ℤ} (h : a < b) (c : ℤ) : c + a < c + b := sorry

protected theorem mul_nonneg {a : ℤ} {b : ℤ} (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a * b := sorry

protected theorem mul_pos {a : ℤ} {b : ℤ} (ha : 0 < a) (hb : 0 < b) : 0 < a * b := sorry

protected theorem zero_lt_one : 0 < 1 :=
  trivial

protected theorem lt_iff_le_not_le {a : ℤ} {b : ℤ} : a < b ↔ a ≤ b ∧ ¬b ≤ a := sorry

protected instance linear_order : linear_order ℤ :=
  linear_order.mk int.le int.lt int.le_refl int.le_trans int.le_antisymm int.le_total int.decidable_le int.decidable_eq
    int.decidable_lt

theorem eq_nat_abs_of_zero_le {a : ℤ} (h : 0 ≤ a) : a = ↑(nat_abs a) := sorry

theorem le_nat_abs {a : ℤ} : a ≤ ↑(nat_abs a) := sorry

theorem neg_succ_lt_zero (n : ℕ) : Int.negSucc n < 0 := sorry

theorem eq_neg_succ_of_lt_zero {a : ℤ} : a < 0 → ∃ (n : ℕ), a = Int.negSucc n := sorry

/- int is an ordered add comm group -/

protected theorem eq_neg_of_eq_neg {a : ℤ} {b : ℤ} (h : a = -b) : b = -a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (b = -a)) h)) (eq.mpr (id (Eq._oldrec (Eq.refl (b = --b)) (int.neg_neg b))) (Eq.refl b))

protected theorem neg_add_cancel_left (a : ℤ) (b : ℤ) : -a + (a + b) = b :=
  eq.mpr (id (Eq._oldrec (Eq.refl (-a + (a + b) = b)) (Eq.symm (int.add_assoc (-a) a b))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (-a + a + b = b)) (int.add_left_neg a)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (0 + b = b)) (int.zero_add b))) (Eq.refl b)))

protected theorem add_neg_cancel_left (a : ℤ) (b : ℤ) : a + (-a + b) = b :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a + (-a + b) = b)) (Eq.symm (int.add_assoc a (-a) b))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a + -a + b = b)) (int.add_right_neg a)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (0 + b = b)) (int.zero_add b))) (Eq.refl b)))

protected theorem add_neg_cancel_right (a : ℤ) (b : ℤ) : a + b + -b = a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a + b + -b = a)) (int.add_assoc a b (-b))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a + (b + -b) = a)) (int.add_right_neg b)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (a + 0 = a)) (int.add_zero a))) (Eq.refl a)))

protected theorem neg_add_cancel_right (a : ℤ) (b : ℤ) : a + -b + b = a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a + -b + b = a)) (int.add_assoc a (-b) b)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a + (-b + b) = a)) (int.add_left_neg b)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (a + 0 = a)) (int.add_zero a))) (Eq.refl a)))

protected theorem sub_self (a : ℤ) : a - a = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a - a = 0)) int.sub_eq_add_neg))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a + -a = 0)) (int.add_right_neg a))) (Eq.refl 0))

protected theorem sub_eq_zero_of_eq {a : ℤ} {b : ℤ} (h : a = b) : a - b = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a - b = 0)) h))
    (eq.mpr (id (Eq._oldrec (Eq.refl (b - b = 0)) (int.sub_self b))) (Eq.refl 0))

protected theorem eq_of_sub_eq_zero {a : ℤ} {b : ℤ} (h : a - b = 0) : a = b := sorry

protected theorem sub_eq_zero_iff_eq {a : ℤ} {b : ℤ} : a - b = 0 ↔ a = b :=
  { mp := int.eq_of_sub_eq_zero, mpr := int.sub_eq_zero_of_eq }

@[simp] protected theorem neg_eq_of_add_eq_zero {a : ℤ} {b : ℤ} (h : a + b = 0) : -a = b := sorry

protected theorem neg_mul_eq_neg_mul (a : ℤ) (b : ℤ) : -(a * b) = -a * b := sorry

protected theorem neg_mul_eq_mul_neg (a : ℤ) (b : ℤ) : -(a * b) = a * -b := sorry

@[simp] theorem neg_mul_eq_neg_mul_symm (a : ℤ) (b : ℤ) : -a * b = -(a * b) :=
  Eq.symm (int.neg_mul_eq_neg_mul a b)

@[simp] theorem mul_neg_eq_neg_mul_symm (a : ℤ) (b : ℤ) : a * -b = -(a * b) :=
  Eq.symm (int.neg_mul_eq_mul_neg a b)

protected theorem neg_mul_neg (a : ℤ) (b : ℤ) : -a * -b = a * b := sorry

protected theorem neg_mul_comm (a : ℤ) (b : ℤ) : -a * b = a * -b := sorry

protected theorem mul_sub (a : ℤ) (b : ℤ) (c : ℤ) : a * (b - c) = a * b - a * c := sorry

protected theorem sub_mul (a : ℤ) (b : ℤ) (c : ℤ) : (a - b) * c = a * c - b * c := sorry

protected theorem le_of_add_le_add_left {a : ℤ} {b : ℤ} {c : ℤ} (h : a + b ≤ a + c) : b ≤ c := sorry

protected theorem lt_of_add_lt_add_left {a : ℤ} {b : ℤ} {c : ℤ} (h : a + b < a + c) : b < c := sorry

protected theorem add_le_add_right {a : ℤ} {b : ℤ} (h : a ≤ b) (c : ℤ) : a + c ≤ b + c :=
  int.add_comm c a ▸ int.add_comm c b ▸ int.add_le_add_left h c

protected theorem add_lt_add_right {a : ℤ} {b : ℤ} (h : a < b) (c : ℤ) : a + c < b + c :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a + c < b + c)) (int.add_comm a c)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (c + a < b + c)) (int.add_comm b c))) (int.add_lt_add_left h c))

protected theorem add_le_add {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} (h₁ : a ≤ b) (h₂ : c ≤ d) : a + c ≤ b + d :=
  le_trans (int.add_le_add_right h₁ c) (int.add_le_add_left h₂ b)

protected theorem le_add_of_nonneg_right {a : ℤ} {b : ℤ} (h : b ≥ 0) : a ≤ a + b :=
  (fun (this : a + b ≥ a + 0) => eq.mp (Eq._oldrec (Eq.refl (a + b ≥ a + 0)) (int.add_zero a)) this)
    (int.add_le_add_left h a)

protected theorem le_add_of_nonneg_left {a : ℤ} {b : ℤ} (h : b ≥ 0) : a ≤ b + a :=
  (fun (this : 0 + a ≤ b + a) => eq.mp (Eq._oldrec (Eq.refl (0 + a ≤ b + a)) (int.zero_add a)) this)
    (int.add_le_add_right h a)

protected theorem add_lt_add {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} (h₁ : a < b) (h₂ : c < d) : a + c < b + d :=
  lt_trans (int.add_lt_add_right h₁ c) (int.add_lt_add_left h₂ b)

protected theorem add_lt_add_of_le_of_lt {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} (h₁ : a ≤ b) (h₂ : c < d) : a + c < b + d :=
  lt_of_le_of_lt (int.add_le_add_right h₁ c) (int.add_lt_add_left h₂ b)

protected theorem add_lt_add_of_lt_of_le {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} (h₁ : a < b) (h₂ : c ≤ d) : a + c < b + d :=
  lt_of_lt_of_le (int.add_lt_add_right h₁ c) (int.add_le_add_left h₂ b)

protected theorem lt_add_of_pos_right (a : ℤ) {b : ℤ} (h : b > 0) : a < a + b :=
  (fun (this : a + 0 < a + b) => eq.mp (Eq._oldrec (Eq.refl (a + 0 < a + b)) (int.add_zero a)) this)
    (int.add_lt_add_left h a)

protected theorem lt_add_of_pos_left (a : ℤ) {b : ℤ} (h : b > 0) : a < b + a :=
  (fun (this : 0 + a < b + a) => eq.mp (Eq._oldrec (Eq.refl (0 + a < b + a)) (int.zero_add a)) this)
    (int.add_lt_add_right h a)

protected theorem le_of_add_le_add_right {a : ℤ} {b : ℤ} {c : ℤ} (h : a + b ≤ c + b) : a ≤ c := sorry

protected theorem lt_of_add_lt_add_right {a : ℤ} {b : ℤ} {c : ℤ} (h : a + b < c + b) : a < c := sorry

-- here we start using properties of zero.

protected theorem add_nonneg {a : ℤ} {b : ℤ} (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b :=
  int.zero_add 0 ▸ int.add_le_add ha hb

protected theorem add_pos {a : ℤ} {b : ℤ} (ha : 0 < a) (hb : 0 < b) : 0 < a + b :=
  int.zero_add 0 ▸ int.add_lt_add ha hb

protected theorem add_pos_of_pos_of_nonneg {a : ℤ} {b : ℤ} (ha : 0 < a) (hb : 0 ≤ b) : 0 < a + b :=
  int.zero_add 0 ▸ int.add_lt_add_of_lt_of_le ha hb

protected theorem add_pos_of_nonneg_of_pos {a : ℤ} {b : ℤ} (ha : 0 ≤ a) (hb : 0 < b) : 0 < a + b :=
  int.zero_add 0 ▸ int.add_lt_add_of_le_of_lt ha hb

protected theorem add_nonpos {a : ℤ} {b : ℤ} (ha : a ≤ 0) (hb : b ≤ 0) : a + b ≤ 0 :=
  int.zero_add 0 ▸ int.add_le_add ha hb

protected theorem add_neg {a : ℤ} {b : ℤ} (ha : a < 0) (hb : b < 0) : a + b < 0 :=
  int.zero_add 0 ▸ int.add_lt_add ha hb

protected theorem add_neg_of_neg_of_nonpos {a : ℤ} {b : ℤ} (ha : a < 0) (hb : b ≤ 0) : a + b < 0 :=
  int.zero_add 0 ▸ int.add_lt_add_of_lt_of_le ha hb

protected theorem add_neg_of_nonpos_of_neg {a : ℤ} {b : ℤ} (ha : a ≤ 0) (hb : b < 0) : a + b < 0 :=
  int.zero_add 0 ▸ int.add_lt_add_of_le_of_lt ha hb

protected theorem lt_add_of_le_of_pos {a : ℤ} {b : ℤ} {c : ℤ} (hbc : b ≤ c) (ha : 0 < a) : b < c + a :=
  int.add_zero b ▸ int.add_lt_add_of_le_of_lt hbc ha

protected theorem sub_add_cancel (a : ℤ) (b : ℤ) : a - b + b = a :=
  int.neg_add_cancel_right a b

protected theorem add_sub_cancel (a : ℤ) (b : ℤ) : a + b - b = a :=
  int.add_neg_cancel_right a b

protected theorem add_sub_assoc (a : ℤ) (b : ℤ) (c : ℤ) : a + b - c = a + (b - c) := sorry

protected theorem neg_le_neg {a : ℤ} {b : ℤ} (h : a ≤ b) : -b ≤ -a := sorry

protected theorem le_of_neg_le_neg {a : ℤ} {b : ℤ} (h : -b ≤ -a) : a ≤ b := sorry

protected theorem nonneg_of_neg_nonpos {a : ℤ} (h : -a ≤ 0) : 0 ≤ a :=
  (fun (this : -a ≤ -0) => int.le_of_neg_le_neg this) (eq.mpr (id (Eq._oldrec (Eq.refl (-a ≤ -0)) int.neg_zero)) h)

protected theorem neg_nonpos_of_nonneg {a : ℤ} (h : 0 ≤ a) : -a ≤ 0 :=
  (fun (this : -a ≤ -0) => eq.mp (Eq._oldrec (Eq.refl (-a ≤ -0)) int.neg_zero) this) (int.neg_le_neg h)

protected theorem nonpos_of_neg_nonneg {a : ℤ} (h : 0 ≤ -a) : a ≤ 0 :=
  (fun (this : -0 ≤ -a) => int.le_of_neg_le_neg this) (eq.mpr (id (Eq._oldrec (Eq.refl (-0 ≤ -a)) int.neg_zero)) h)

protected theorem neg_nonneg_of_nonpos {a : ℤ} (h : a ≤ 0) : 0 ≤ -a :=
  (fun (this : -0 ≤ -a) => eq.mp (Eq._oldrec (Eq.refl (-0 ≤ -a)) int.neg_zero) this) (int.neg_le_neg h)

protected theorem neg_lt_neg {a : ℤ} {b : ℤ} (h : a < b) : -b < -a := sorry

protected theorem lt_of_neg_lt_neg {a : ℤ} {b : ℤ} (h : -b < -a) : a < b :=
  int.neg_neg a ▸ int.neg_neg b ▸ int.neg_lt_neg h

protected theorem pos_of_neg_neg {a : ℤ} (h : -a < 0) : 0 < a :=
  (fun (this : -a < -0) => int.lt_of_neg_lt_neg this) (eq.mpr (id (Eq._oldrec (Eq.refl (-a < -0)) int.neg_zero)) h)

protected theorem neg_neg_of_pos {a : ℤ} (h : 0 < a) : -a < 0 :=
  (fun (this : -a < -0) => eq.mp (Eq._oldrec (Eq.refl (-a < -0)) int.neg_zero) this) (int.neg_lt_neg h)

protected theorem neg_of_neg_pos {a : ℤ} (h : 0 < -a) : a < 0 :=
  (fun (this : -0 < -a) => int.lt_of_neg_lt_neg this) (eq.mpr (id (Eq._oldrec (Eq.refl (-0 < -a)) int.neg_zero)) h)

protected theorem neg_pos_of_neg {a : ℤ} (h : a < 0) : 0 < -a :=
  (fun (this : -0 < -a) => eq.mp (Eq._oldrec (Eq.refl (-0 < -a)) int.neg_zero) this) (int.neg_lt_neg h)

protected theorem le_neg_of_le_neg {a : ℤ} {b : ℤ} (h : a ≤ -b) : b ≤ -a :=
  eq.mp (Eq._oldrec (Eq.refl ( --b ≤ -a)) (int.neg_neg b)) (int.neg_le_neg h)

protected theorem neg_le_of_neg_le {a : ℤ} {b : ℤ} (h : -a ≤ b) : -b ≤ a :=
  eq.mp (Eq._oldrec (Eq.refl (-b ≤ --a)) (int.neg_neg a)) (int.neg_le_neg h)

protected theorem lt_neg_of_lt_neg {a : ℤ} {b : ℤ} (h : a < -b) : b < -a :=
  eq.mp (Eq._oldrec (Eq.refl ( --b < -a)) (int.neg_neg b)) (int.neg_lt_neg h)

protected theorem neg_lt_of_neg_lt {a : ℤ} {b : ℤ} (h : -a < b) : -b < a :=
  eq.mp (Eq._oldrec (Eq.refl (-b < --a)) (int.neg_neg a)) (int.neg_lt_neg h)

protected theorem sub_nonneg_of_le {a : ℤ} {b : ℤ} (h : b ≤ a) : 0 ≤ a - b :=
  eq.mp (Eq._oldrec (Eq.refl (b + -b ≤ a + -b)) (int.add_right_neg b)) (int.add_le_add_right h (-b))

protected theorem le_of_sub_nonneg {a : ℤ} {b : ℤ} (h : 0 ≤ a - b) : b ≤ a :=
  eq.mp (Eq._oldrec (Eq.refl (0 + b ≤ a)) (int.zero_add b))
    (eq.mp (Eq._oldrec (Eq.refl (0 + b ≤ a - b + b)) (int.sub_add_cancel a b)) (int.add_le_add_right h b))

protected theorem sub_nonpos_of_le {a : ℤ} {b : ℤ} (h : a ≤ b) : a - b ≤ 0 :=
  eq.mp (Eq._oldrec (Eq.refl (a + -b ≤ b + -b)) (int.add_right_neg b)) (int.add_le_add_right h (-b))

protected theorem le_of_sub_nonpos {a : ℤ} {b : ℤ} (h : a - b ≤ 0) : a ≤ b :=
  eq.mp (Eq._oldrec (Eq.refl (a ≤ 0 + b)) (int.zero_add b))
    (eq.mp (Eq._oldrec (Eq.refl (a - b + b ≤ 0 + b)) (int.sub_add_cancel a b)) (int.add_le_add_right h b))

protected theorem sub_pos_of_lt {a : ℤ} {b : ℤ} (h : b < a) : 0 < a - b :=
  eq.mp (Eq._oldrec (Eq.refl (b + -b < a + -b)) (int.add_right_neg b)) (int.add_lt_add_right h (-b))

protected theorem lt_of_sub_pos {a : ℤ} {b : ℤ} (h : 0 < a - b) : b < a :=
  eq.mp (Eq._oldrec (Eq.refl (0 + b < a)) (int.zero_add b))
    (eq.mp (Eq._oldrec (Eq.refl (0 + b < a - b + b)) (int.sub_add_cancel a b)) (int.add_lt_add_right h b))

protected theorem sub_neg_of_lt {a : ℤ} {b : ℤ} (h : a < b) : a - b < 0 :=
  eq.mp (Eq._oldrec (Eq.refl (a + -b < b + -b)) (int.add_right_neg b)) (int.add_lt_add_right h (-b))

protected theorem lt_of_sub_neg {a : ℤ} {b : ℤ} (h : a - b < 0) : a < b :=
  eq.mp (Eq._oldrec (Eq.refl (a < 0 + b)) (int.zero_add b))
    (eq.mp (Eq._oldrec (Eq.refl (a - b + b < 0 + b)) (int.sub_add_cancel a b)) (int.add_lt_add_right h b))

protected theorem add_le_of_le_neg_add {a : ℤ} {b : ℤ} {c : ℤ} (h : b ≤ -a + c) : a + b ≤ c :=
  eq.mp (Eq._oldrec (Eq.refl (a + b ≤ a + (-a + c))) (int.add_neg_cancel_left a c)) (int.add_le_add_left h a)

protected theorem le_neg_add_of_add_le {a : ℤ} {b : ℤ} {c : ℤ} (h : a + b ≤ c) : b ≤ -a + c :=
  eq.mp (Eq._oldrec (Eq.refl (-a + (a + b) ≤ -a + c)) (int.neg_add_cancel_left a b)) (int.add_le_add_left h (-a))

protected theorem add_le_of_le_sub_left {a : ℤ} {b : ℤ} {c : ℤ} (h : b ≤ c - a) : a + b ≤ c :=
  eq.mp (Eq._oldrec (Eq.refl (a + b ≤ c + a - a)) (int.add_sub_cancel c a))
    (eq.mp (Eq._oldrec (Eq.refl (a + b ≤ a + c - a)) (int.add_comm a c))
      (eq.mp (Eq._oldrec (Eq.refl (a + b ≤ a + (c - a))) (Eq.symm (int.add_sub_assoc a c a))) (int.add_le_add_left h a)))

protected theorem le_sub_left_of_add_le {a : ℤ} {b : ℤ} {c : ℤ} (h : a + b ≤ c) : b ≤ c - a :=
  eq.mp (Eq._oldrec (Eq.refl (b + a + -a ≤ c + -a)) (int.add_neg_cancel_right b a))
    (eq.mp (Eq._oldrec (Eq.refl (a + b + -a ≤ c + -a)) (int.add_comm a b)) (int.add_le_add_right h (-a)))

protected theorem add_le_of_le_sub_right {a : ℤ} {b : ℤ} {c : ℤ} (h : a ≤ c - b) : a + b ≤ c :=
  eq.mp (Eq._oldrec (Eq.refl (a + b ≤ c - b + b)) (int.sub_add_cancel c b)) (int.add_le_add_right h b)

protected theorem le_sub_right_of_add_le {a : ℤ} {b : ℤ} {c : ℤ} (h : a + b ≤ c) : a ≤ c - b :=
  eq.mp (Eq._oldrec (Eq.refl (a + b + -b ≤ c + -b)) (int.add_neg_cancel_right a b)) (int.add_le_add_right h (-b))

protected theorem le_add_of_neg_add_le {a : ℤ} {b : ℤ} {c : ℤ} (h : -b + a ≤ c) : a ≤ b + c :=
  eq.mp (Eq._oldrec (Eq.refl (b + (-b + a) ≤ b + c)) (int.add_neg_cancel_left b a)) (int.add_le_add_left h b)

protected theorem neg_add_le_of_le_add {a : ℤ} {b : ℤ} {c : ℤ} (h : a ≤ b + c) : -b + a ≤ c :=
  eq.mp (Eq._oldrec (Eq.refl (-b + a ≤ -b + (b + c))) (int.neg_add_cancel_left b c)) (int.add_le_add_left h (-b))

protected theorem le_add_of_sub_left_le {a : ℤ} {b : ℤ} {c : ℤ} (h : a - b ≤ c) : a ≤ b + c :=
  eq.mp (Eq._oldrec (Eq.refl (a ≤ c + b)) (int.add_comm c b))
    (eq.mp (Eq._oldrec (Eq.refl (a - b + b ≤ c + b)) (int.sub_add_cancel a b)) (int.add_le_add_right h b))

protected theorem sub_left_le_of_le_add {a : ℤ} {b : ℤ} {c : ℤ} (h : a ≤ b + c) : a - b ≤ c :=
  eq.mp (Eq._oldrec (Eq.refl (a + -b ≤ c + b + -b)) (int.add_neg_cancel_right c b))
    (eq.mp (Eq._oldrec (Eq.refl (a + -b ≤ b + c + -b)) (int.add_comm b c)) (int.add_le_add_right h (-b)))

protected theorem le_add_of_sub_right_le {a : ℤ} {b : ℤ} {c : ℤ} (h : a - c ≤ b) : a ≤ b + c :=
  eq.mp (Eq._oldrec (Eq.refl (a - c + c ≤ b + c)) (int.sub_add_cancel a c)) (int.add_le_add_right h c)

protected theorem sub_right_le_of_le_add {a : ℤ} {b : ℤ} {c : ℤ} (h : a ≤ b + c) : a - c ≤ b :=
  eq.mp (Eq._oldrec (Eq.refl (a + -c ≤ b + c + -c)) (int.add_neg_cancel_right b c)) (int.add_le_add_right h (-c))

protected theorem le_add_of_neg_add_le_left {a : ℤ} {b : ℤ} {c : ℤ} (h : -b + a ≤ c) : a ≤ b + c :=
  int.le_add_of_sub_left_le (eq.mp (Eq._oldrec (Eq.refl (-b + a ≤ c)) (int.add_comm (-b) a)) h)

protected theorem neg_add_le_left_of_le_add {a : ℤ} {b : ℤ} {c : ℤ} (h : a ≤ b + c) : -b + a ≤ c :=
  eq.mpr (id (Eq._oldrec (Eq.refl (-b + a ≤ c)) (int.add_comm (-b) a))) (int.sub_left_le_of_le_add h)

protected theorem le_add_of_neg_add_le_right {a : ℤ} {b : ℤ} {c : ℤ} (h : -c + a ≤ b) : a ≤ b + c :=
  int.le_add_of_sub_right_le (eq.mp (Eq._oldrec (Eq.refl (-c + a ≤ b)) (int.add_comm (-c) a)) h)

protected theorem neg_add_le_right_of_le_add {a : ℤ} {b : ℤ} {c : ℤ} (h : a ≤ b + c) : -c + a ≤ b :=
  int.neg_add_le_left_of_le_add (eq.mp (Eq._oldrec (Eq.refl (a ≤ b + c)) (int.add_comm b c)) h)

protected theorem le_add_of_neg_le_sub_left {a : ℤ} {b : ℤ} {c : ℤ} (h : -a ≤ b - c) : c ≤ a + b :=
  int.le_add_of_neg_add_le_left (int.add_le_of_le_sub_right h)

protected theorem neg_le_sub_left_of_le_add {a : ℤ} {b : ℤ} {c : ℤ} (h : c ≤ a + b) : -a ≤ b - c :=
  eq.mp (Eq._oldrec (Eq.refl (-a ≤ -c + b)) (int.add_comm (-c) b))
    (int.le_neg_add_of_add_le (int.sub_left_le_of_le_add h))

protected theorem le_add_of_neg_le_sub_right {a : ℤ} {b : ℤ} {c : ℤ} (h : -b ≤ a - c) : c ≤ a + b :=
  int.le_add_of_sub_right_le (int.add_le_of_le_sub_left h)

protected theorem neg_le_sub_right_of_le_add {a : ℤ} {b : ℤ} {c : ℤ} (h : c ≤ a + b) : -b ≤ a - c :=
  int.le_sub_left_of_add_le (int.sub_right_le_of_le_add h)

protected theorem sub_le_of_sub_le {a : ℤ} {b : ℤ} {c : ℤ} (h : a - b ≤ c) : a - c ≤ b :=
  int.sub_left_le_of_le_add (int.le_add_of_sub_right_le h)

protected theorem sub_le_sub_left {a : ℤ} {b : ℤ} (h : a ≤ b) (c : ℤ) : c - b ≤ c - a :=
  int.add_le_add_left (int.neg_le_neg h) c

protected theorem sub_le_sub_right {a : ℤ} {b : ℤ} (h : a ≤ b) (c : ℤ) : a - c ≤ b - c :=
  int.add_le_add_right h (-c)

protected theorem sub_le_sub {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} (hab : a ≤ b) (hcd : c ≤ d) : a - d ≤ b - c :=
  int.add_le_add hab (int.neg_le_neg hcd)

protected theorem add_lt_of_lt_neg_add {a : ℤ} {b : ℤ} {c : ℤ} (h : b < -a + c) : a + b < c :=
  eq.mp (Eq._oldrec (Eq.refl (a + b < a + (-a + c))) (int.add_neg_cancel_left a c)) (int.add_lt_add_left h a)

protected theorem lt_neg_add_of_add_lt {a : ℤ} {b : ℤ} {c : ℤ} (h : a + b < c) : b < -a + c :=
  eq.mp (Eq._oldrec (Eq.refl (-a + (a + b) < -a + c)) (int.neg_add_cancel_left a b)) (int.add_lt_add_left h (-a))

protected theorem add_lt_of_lt_sub_left {a : ℤ} {b : ℤ} {c : ℤ} (h : b < c - a) : a + b < c :=
  eq.mp (Eq._oldrec (Eq.refl (a + b < c + a - a)) (int.add_sub_cancel c a))
    (eq.mp (Eq._oldrec (Eq.refl (a + b < a + c - a)) (int.add_comm a c))
      (eq.mp (Eq._oldrec (Eq.refl (a + b < a + (c - a))) (Eq.symm (int.add_sub_assoc a c a))) (int.add_lt_add_left h a)))

protected theorem lt_sub_left_of_add_lt {a : ℤ} {b : ℤ} {c : ℤ} (h : a + b < c) : b < c - a :=
  eq.mp (Eq._oldrec (Eq.refl (b + a + -a < c + -a)) (int.add_neg_cancel_right b a))
    (eq.mp (Eq._oldrec (Eq.refl (a + b + -a < c + -a)) (int.add_comm a b)) (int.add_lt_add_right h (-a)))

protected theorem add_lt_of_lt_sub_right {a : ℤ} {b : ℤ} {c : ℤ} (h : a < c - b) : a + b < c :=
  eq.mp (Eq._oldrec (Eq.refl (a + b < c - b + b)) (int.sub_add_cancel c b)) (int.add_lt_add_right h b)

protected theorem lt_sub_right_of_add_lt {a : ℤ} {b : ℤ} {c : ℤ} (h : a + b < c) : a < c - b :=
  eq.mp (Eq._oldrec (Eq.refl (a + b + -b < c + -b)) (int.add_neg_cancel_right a b)) (int.add_lt_add_right h (-b))

protected theorem lt_add_of_neg_add_lt {a : ℤ} {b : ℤ} {c : ℤ} (h : -b + a < c) : a < b + c :=
  eq.mp (Eq._oldrec (Eq.refl (b + (-b + a) < b + c)) (int.add_neg_cancel_left b a)) (int.add_lt_add_left h b)

protected theorem neg_add_lt_of_lt_add {a : ℤ} {b : ℤ} {c : ℤ} (h : a < b + c) : -b + a < c :=
  eq.mp (Eq._oldrec (Eq.refl (-b + a < -b + (b + c))) (int.neg_add_cancel_left b c)) (int.add_lt_add_left h (-b))

protected theorem lt_add_of_sub_left_lt {a : ℤ} {b : ℤ} {c : ℤ} (h : a - b < c) : a < b + c :=
  eq.mp (Eq._oldrec (Eq.refl (a < c + b)) (int.add_comm c b))
    (eq.mp (Eq._oldrec (Eq.refl (a - b + b < c + b)) (int.sub_add_cancel a b)) (int.add_lt_add_right h b))

protected theorem sub_left_lt_of_lt_add {a : ℤ} {b : ℤ} {c : ℤ} (h : a < b + c) : a - b < c :=
  eq.mp (Eq._oldrec (Eq.refl (a + -b < c + b + -b)) (int.add_neg_cancel_right c b))
    (eq.mp (Eq._oldrec (Eq.refl (a + -b < b + c + -b)) (int.add_comm b c)) (int.add_lt_add_right h (-b)))

protected theorem lt_add_of_sub_right_lt {a : ℤ} {b : ℤ} {c : ℤ} (h : a - c < b) : a < b + c :=
  eq.mp (Eq._oldrec (Eq.refl (a - c + c < b + c)) (int.sub_add_cancel a c)) (int.add_lt_add_right h c)

protected theorem sub_right_lt_of_lt_add {a : ℤ} {b : ℤ} {c : ℤ} (h : a < b + c) : a - c < b :=
  eq.mp (Eq._oldrec (Eq.refl (a + -c < b + c + -c)) (int.add_neg_cancel_right b c)) (int.add_lt_add_right h (-c))

protected theorem lt_add_of_neg_add_lt_left {a : ℤ} {b : ℤ} {c : ℤ} (h : -b + a < c) : a < b + c :=
  int.lt_add_of_sub_left_lt (eq.mp (Eq._oldrec (Eq.refl (-b + a < c)) (int.add_comm (-b) a)) h)

protected theorem neg_add_lt_left_of_lt_add {a : ℤ} {b : ℤ} {c : ℤ} (h : a < b + c) : -b + a < c :=
  eq.mpr (id (Eq._oldrec (Eq.refl (-b + a < c)) (int.add_comm (-b) a))) (int.sub_left_lt_of_lt_add h)

protected theorem lt_add_of_neg_add_lt_right {a : ℤ} {b : ℤ} {c : ℤ} (h : -c + a < b) : a < b + c :=
  int.lt_add_of_sub_right_lt (eq.mp (Eq._oldrec (Eq.refl (-c + a < b)) (int.add_comm (-c) a)) h)

protected theorem neg_add_lt_right_of_lt_add {a : ℤ} {b : ℤ} {c : ℤ} (h : a < b + c) : -c + a < b :=
  int.neg_add_lt_left_of_lt_add (eq.mp (Eq._oldrec (Eq.refl (a < b + c)) (int.add_comm b c)) h)

protected theorem lt_add_of_neg_lt_sub_left {a : ℤ} {b : ℤ} {c : ℤ} (h : -a < b - c) : c < a + b :=
  int.lt_add_of_neg_add_lt_left (int.add_lt_of_lt_sub_right h)

protected theorem neg_lt_sub_left_of_lt_add {a : ℤ} {b : ℤ} {c : ℤ} (h : c < a + b) : -a < b - c :=
  eq.mp (Eq._oldrec (Eq.refl (-a < -c + b)) (int.add_comm (-c) b))
    (int.lt_neg_add_of_add_lt (int.sub_left_lt_of_lt_add h))

protected theorem lt_add_of_neg_lt_sub_right {a : ℤ} {b : ℤ} {c : ℤ} (h : -b < a - c) : c < a + b :=
  int.lt_add_of_sub_right_lt (int.add_lt_of_lt_sub_left h)

protected theorem neg_lt_sub_right_of_lt_add {a : ℤ} {b : ℤ} {c : ℤ} (h : c < a + b) : -b < a - c :=
  int.lt_sub_left_of_add_lt (int.sub_right_lt_of_lt_add h)

protected theorem sub_lt_of_sub_lt {a : ℤ} {b : ℤ} {c : ℤ} (h : a - b < c) : a - c < b :=
  int.sub_left_lt_of_lt_add (int.lt_add_of_sub_right_lt h)

protected theorem sub_lt_sub_left {a : ℤ} {b : ℤ} (h : a < b) (c : ℤ) : c - b < c - a :=
  int.add_lt_add_left (int.neg_lt_neg h) c

protected theorem sub_lt_sub_right {a : ℤ} {b : ℤ} (h : a < b) (c : ℤ) : a - c < b - c :=
  int.add_lt_add_right h (-c)

protected theorem sub_lt_sub {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} (hab : a < b) (hcd : c < d) : a - d < b - c :=
  int.add_lt_add hab (int.neg_lt_neg hcd)

protected theorem sub_lt_sub_of_le_of_lt {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} (hab : a ≤ b) (hcd : c < d) : a - d < b - c :=
  int.add_lt_add_of_le_of_lt hab (int.neg_lt_neg hcd)

protected theorem sub_lt_sub_of_lt_of_le {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} (hab : a < b) (hcd : c ≤ d) : a - d < b - c :=
  int.add_lt_add_of_lt_of_le hab (int.neg_le_neg hcd)

protected theorem sub_le_self (a : ℤ) {b : ℤ} (h : b ≥ 0) : a - b ≤ a :=
  trans_rel_left LessEq (trans_rel_right LessEq rfl (int.add_le_add_left (int.neg_nonpos_of_nonneg h) a))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a + 0 = a)) (int.add_zero a))) (Eq.refl a))

protected theorem sub_lt_self (a : ℤ) {b : ℤ} (h : b > 0) : a - b < a :=
  trans_rel_left Less (trans_rel_right Less rfl (int.add_lt_add_left (int.neg_neg_of_pos h) a))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a + 0 = a)) (int.add_zero a))) (Eq.refl a))

protected theorem add_le_add_three {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} {e : ℤ} {f : ℤ} (h₁ : a ≤ d) (h₂ : b ≤ e) (h₃ : c ≤ f) : a + b + c ≤ d + e + f :=
  le_trans (int.add_le_add (int.add_le_add h₁ h₂) h₃) (le_refl (d + e + f))

/- missing facts -/

protected theorem mul_lt_mul_of_pos_left {a : ℤ} {b : ℤ} {c : ℤ} (h₁ : a < b) (h₂ : 0 < c) : c * a < c * b := sorry

protected theorem mul_lt_mul_of_pos_right {a : ℤ} {b : ℤ} {c : ℤ} (h₁ : a < b) (h₂ : 0 < c) : a * c < b * c := sorry

protected theorem mul_le_mul_of_nonneg_left {a : ℤ} {b : ℤ} {c : ℤ} (h₁ : a ≤ b) (h₂ : 0 ≤ c) : c * a ≤ c * b := sorry

protected theorem mul_le_mul_of_nonneg_right {a : ℤ} {b : ℤ} {c : ℤ} (h₁ : a ≤ b) (h₂ : 0 ≤ c) : a * c ≤ b * c := sorry

-- TODO: there are four variations, depending on which variables we assume to be nonneg

protected theorem mul_le_mul {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} (hac : a ≤ c) (hbd : b ≤ d) (nn_b : 0 ≤ b) (nn_c : 0 ≤ c) : a * b ≤ c * d :=
  le_trans (int.mul_le_mul_of_nonneg_right hac nn_b) (int.mul_le_mul_of_nonneg_left hbd nn_c)

protected theorem mul_nonpos_of_nonneg_of_nonpos {a : ℤ} {b : ℤ} (ha : a ≥ 0) (hb : b ≤ 0) : a * b ≤ 0 :=
  (fun (h : a * b ≤ a * 0) => eq.mp (Eq._oldrec (Eq.refl (a * b ≤ a * 0)) (int.mul_zero a)) h)
    (int.mul_le_mul_of_nonneg_left hb ha)

protected theorem mul_nonpos_of_nonpos_of_nonneg {a : ℤ} {b : ℤ} (ha : a ≤ 0) (hb : b ≥ 0) : a * b ≤ 0 :=
  (fun (h : a * b ≤ 0 * b) => eq.mp (Eq._oldrec (Eq.refl (a * b ≤ 0 * b)) (int.zero_mul b)) h)
    (int.mul_le_mul_of_nonneg_right ha hb)

protected theorem mul_lt_mul {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} (hac : a < c) (hbd : b ≤ d) (pos_b : 0 < b) (nn_c : 0 ≤ c) : a * b < c * d :=
  lt_of_lt_of_le (int.mul_lt_mul_of_pos_right hac pos_b) (int.mul_le_mul_of_nonneg_left hbd nn_c)

protected theorem mul_lt_mul' {a : ℤ} {b : ℤ} {c : ℤ} {d : ℤ} (h1 : a ≤ c) (h2 : b < d) (h3 : b ≥ 0) (h4 : c > 0) : a * b < c * d :=
  lt_of_le_of_lt (int.mul_le_mul_of_nonneg_right h1 h3) (int.mul_lt_mul_of_pos_left h2 h4)

protected theorem mul_neg_of_pos_of_neg {a : ℤ} {b : ℤ} (ha : a > 0) (hb : b < 0) : a * b < 0 :=
  (fun (h : a * b < a * 0) => eq.mp (Eq._oldrec (Eq.refl (a * b < a * 0)) (int.mul_zero a)) h)
    (int.mul_lt_mul_of_pos_left hb ha)

protected theorem mul_neg_of_neg_of_pos {a : ℤ} {b : ℤ} (ha : a < 0) (hb : b > 0) : a * b < 0 :=
  (fun (h : a * b < 0 * b) => eq.mp (Eq._oldrec (Eq.refl (a * b < 0 * b)) (int.zero_mul b)) h)
    (int.mul_lt_mul_of_pos_right ha hb)

protected theorem mul_le_mul_of_nonpos_right {a : ℤ} {b : ℤ} {c : ℤ} (h : b ≤ a) (hc : c ≤ 0) : a * c ≤ b * c := sorry

protected theorem mul_nonneg_of_nonpos_of_nonpos {a : ℤ} {b : ℤ} (ha : a ≤ 0) (hb : b ≤ 0) : 0 ≤ a * b :=
  (fun (this : 0 * b ≤ a * b) => eq.mp (Eq._oldrec (Eq.refl (0 * b ≤ a * b)) (int.zero_mul b)) this)
    (int.mul_le_mul_of_nonpos_right ha hb)

protected theorem mul_lt_mul_of_neg_left {a : ℤ} {b : ℤ} {c : ℤ} (h : b < a) (hc : c < 0) : c * a < c * b := sorry

protected theorem mul_lt_mul_of_neg_right {a : ℤ} {b : ℤ} {c : ℤ} (h : b < a) (hc : c < 0) : a * c < b * c := sorry

protected theorem mul_pos_of_neg_of_neg {a : ℤ} {b : ℤ} (ha : a < 0) (hb : b < 0) : 0 < a * b :=
  (fun (this : 0 * b < a * b) => eq.mp (Eq._oldrec (Eq.refl (0 * b < a * b)) (int.zero_mul b)) this)
    (int.mul_lt_mul_of_neg_right ha hb)

protected theorem mul_self_le_mul_self {a : ℤ} {b : ℤ} (h1 : 0 ≤ a) (h2 : a ≤ b) : a * a ≤ b * b :=
  int.mul_le_mul h2 h2 h1 (le_trans h1 h2)

protected theorem mul_self_lt_mul_self {a : ℤ} {b : ℤ} (h1 : 0 ≤ a) (h2 : a < b) : a * a < b * b :=
  int.mul_lt_mul' (le_of_lt h2) h2 h1 (lt_of_le_of_lt h1 h2)

/- more facts specific to int -/

theorem of_nat_nonneg (n : ℕ) : 0 ≤ Int.ofNat n :=
  trivial

theorem coe_succ_pos (n : ℕ) : ↑(Nat.succ n) > 0 :=
  coe_nat_lt_coe_nat_of_lt (nat.succ_pos n)

theorem exists_eq_neg_of_nat {a : ℤ} (H : a ≤ 0) : ∃ (n : ℕ), a = -↑n := sorry

theorem nat_abs_of_nonneg {a : ℤ} (H : a ≥ 0) : ↑(nat_abs a) = a := sorry

theorem of_nat_nat_abs_of_nonpos {a : ℤ} (H : a ≤ 0) : ↑(nat_abs a) = -a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (↑(nat_abs a) = -a)) (Eq.symm (nat_abs_neg a))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (↑(nat_abs (-a)) = -a)) (nat_abs_of_nonneg (int.neg_nonneg_of_nonpos H))))
      (Eq.refl (-a)))

theorem lt_of_add_one_le {a : ℤ} {b : ℤ} (H : a + 1 ≤ b) : a < b :=
  H

theorem add_one_le_of_lt {a : ℤ} {b : ℤ} (H : a < b) : a + 1 ≤ b :=
  H

theorem lt_add_one_of_le {a : ℤ} {b : ℤ} (H : a ≤ b) : a < b + 1 :=
  int.add_le_add_right H 1

theorem le_of_lt_add_one {a : ℤ} {b : ℤ} (H : a < b + 1) : a ≤ b :=
  int.le_of_add_le_add_right H

theorem sub_one_le_of_lt {a : ℤ} {b : ℤ} (H : a ≤ b) : a - 1 < b :=
  int.sub_right_lt_of_lt_add (lt_add_one_of_le H)

theorem lt_of_sub_one_le {a : ℤ} {b : ℤ} (H : a - 1 < b) : a ≤ b :=
  le_of_lt_add_one (int.lt_add_of_sub_right_lt H)

theorem le_sub_one_of_lt {a : ℤ} {b : ℤ} (H : a < b) : a ≤ b - 1 :=
  int.le_sub_right_of_add_le H

theorem lt_of_le_sub_one {a : ℤ} {b : ℤ} (H : a ≤ b - 1) : a < b :=
  int.add_le_of_le_sub_right H

theorem sign_of_succ (n : ℕ) : sign ↑(Nat.succ n) = 1 :=
  rfl

theorem sign_eq_one_of_pos {a : ℤ} (h : 0 < a) : sign a = 1 := sorry

theorem sign_eq_neg_one_of_neg {a : ℤ} (h : a < 0) : sign a = -1 := sorry

theorem eq_zero_of_sign_eq_zero {a : ℤ} : sign a = 0 → a = 0 := sorry

theorem pos_of_sign_eq_one {a : ℤ} : sign a = 1 → 0 < a := sorry

theorem neg_of_sign_eq_neg_one {a : ℤ} : sign a = -1 → a < 0 := sorry

theorem sign_eq_one_iff_pos (a : ℤ) : sign a = 1 ↔ 0 < a :=
  { mp := pos_of_sign_eq_one, mpr := sign_eq_one_of_pos }

theorem sign_eq_neg_one_iff_neg (a : ℤ) : sign a = -1 ↔ a < 0 :=
  { mp := neg_of_sign_eq_neg_one, mpr := sign_eq_neg_one_of_neg }

theorem sign_eq_zero_iff_zero (a : ℤ) : sign a = 0 ↔ a = 0 := sorry

protected theorem eq_zero_or_eq_zero_of_mul_eq_zero {a : ℤ} {b : ℤ} (h : a * b = 0) : a = 0 ∨ b = 0 := sorry

protected theorem eq_of_mul_eq_mul_right {a : ℤ} {b : ℤ} {c : ℤ} (ha : a ≠ 0) (h : b * a = c * a) : b = c := sorry

protected theorem eq_of_mul_eq_mul_left {a : ℤ} {b : ℤ} {c : ℤ} (ha : a ≠ 0) (h : a * b = a * c) : b = c := sorry

theorem eq_one_of_mul_eq_self_left {a : ℤ} {b : ℤ} (Hpos : a ≠ 0) (H : b * a = a) : b = 1 :=
  int.eq_of_mul_eq_mul_right Hpos
    (eq.mpr (id (Eq._oldrec (Eq.refl (b * a = 1 * a)) (int.one_mul a)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (b * a = a)) H)) (Eq.refl a)))

theorem eq_one_of_mul_eq_self_right {a : ℤ} {b : ℤ} (Hpos : b ≠ 0) (H : b * a = b) : a = 1 :=
  int.eq_of_mul_eq_mul_left Hpos
    (eq.mpr (id (Eq._oldrec (Eq.refl (b * a = b * 1)) (int.mul_one b)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (b * a = b)) H)) (Eq.refl b)))