import data.bitvec
import data.stream
import util.data.list
import util.data.fin
import util.meta.tactic

import tactic.norm_num

import algebra.group_power
import test.slim_check.liftable
import system.io
import system.random

open list io applicative

universes u v w

-- instance {g : Type v} [random_gen g] : random_gen (ulift.{u} g) :=
-- by refine_struct { .. }; have_field; rintro ⟨ g ⟩;
--      apply prod.map _ _ (field g);
--      { apply id <|> apply ulift.up }

-- structure generator :=
--   (seed1 : unsigned)
--   (seed2 : unsigned)

-- instance : has_to_string generator :=
-- ⟨ λ ⟨x,y⟩, to_string (x,y) ⟩

namespace generator
/- ported from
    https://hackage.haskell.org/package/random
    -/

-- def from_nat32 (s : unsigned) : generator :=
-- let s₀ := s.to_nat,
--     q  := s₀ / 2147483562,
--     s₁ : unsigned := fin.of_nat $ s₀ % 2147483562,
--     s₂ : unsigned := fin.of_nat $ q % 2147483398 in
-- ⟨ s₁ + 1, s₂ + 1 ⟩

-- def next : generator → unsigned × generator
--   | ⟨ ⟨ s1, _⟩, ⟨ s2, _ ⟩ ⟩ :=
-- let k : ℤ := s1 / 53668,
--     s1'  := 40014 * ((s1 : ℤ) - k * 53668) - k * 12211,
--     s1'' := if s1' < 0 then s1' + 2147483563 else s1',

--     k' : ℤ := s2 / 52774,
--     s2'  := 40692 * ((s2 : ℤ) - k' * 52774) - k' * 3791,
--     s2'' := if s2' < 0 then s2' + 2147483399 else s2',
--     z    := s1'' - s2'',
--     z'   := if z < 1 then z + 2147483562 else z
-- in
-- ⟨ fin.of_nat z'.to_nat, fin.of_nat s1''.to_nat, fin.of_nat s2''.to_nat ⟩

-- def split : generator → generator × generator
--  | ⟨ s1, s2 ⟩ :=
-- let    nx := (next ⟨s1,s2⟩).2,
--        ⟨t1,t2⟩ := nx,
--        new_s1 := if s1 = 2147483562
--                  then  1
--                  else s1 + 1,

--        new_s2 := if s2 = 1
--                  then 2147483398
--                  else s2 - 1,

--        -- no statistical foundation for this!
--        left  : generator := ⟨new_s1, t2⟩,
--        right : generator := ⟨t1, new_s2⟩
-- in ⟨ left, right ⟩

end generator

@[reducible]
def rand_g (g : Type) := state (ulift g)
@[reducible]
def rand := rand_g std_gen

instance (g : Type) : liftable (rand_g.{u} g) (rand_g.{v} g) :=
@state_t.liftable' _ _ _ _ _ _ _ _ _  (equiv.ulift.trans.{u u u u u} equiv.ulift.symm)

-- def rand (α : Type u) := generator → α × generator

-- namespace rand

-- variables {α β γ : Type u}

-- protected def pure (x : α) : rand α :=
-- λ g, (x,g)

-- protected def bind (x : rand α) (f : α → rand β) : rand β :=
-- λ g,
-- let r := x g in
-- f r.1 r.2

-- instance : has_bind rand :=
-- ⟨ @rand.bind ⟩

-- instance : has_pure rand :=
-- ⟨ @rand.pure ⟩

-- lemma bind_assoc (x : rand α) (f : α → rand β) (g : β → rand γ)
-- : x >>= f >>= g = x >>= (λ i, f i >>= g) :=
-- begin
--   funext sz,
--   simp [has_bind.bind,rand.bind],
-- end

-- lemma pure_bind (x : α) (f : α → rand β)
-- : pure x >>= f = f x :=
-- by refl

-- lemma id_map (x : rand α)
-- : x >>= pure ∘ id = x :=
-- begin
--   funext i,
--   simp [function.comp,has_bind.bind,rand.bind],
--   cases x i, refl
-- end

-- end rand

-- instance : monad rand :=
-- { pure := @rand.pure
-- , bind := @rand.bind
-- , bind_assoc := @rand.bind_assoc
-- , pure_bind  := @rand.pure_bind
-- , id_map := @rand.id_map }
-- #check @random_gen.next

open ulift

def random.next {gen : Type} [random_gen gen] : rand_g gen ℕ :=
⟨ prod.map id up ∘ random_gen.next ∘ down ⟩

def range {α : Type u} [has_le α] (i j : α) :=
{ x : α // i ≤ x ∧ x ≤ j }

infix ` .. `:41 := range

open stream

class random (α : Type u) extends has_le α :=
(random : Π (g : Type) [random_gen g], rand_g g α)
(random_r : Π g [random_gen g] (x y : α),
              x ≤ y →
              rand_g g (x .. y))
(random_series : Π (gen : Type) [random_gen gen], gen → stream α :=
by { introsI,
     exact corec prod.fst ((random gen).run ∘ prod.snd) ( (random gen).run ⟨ a ⟩ ) } )
(random_series_r : Π (g : Type) [random_gen g] (x y : α)
                        (h : x ≤ y),
                        g →
                        stream (x .. y) :=
by { introsI,
     exact corec prod.fst ((random_r g x y h).run ∘ prod.snd) ((random_r g x y h).run ⟨ a ⟩) } )

namespace tactic.interactive

meta def time_in_nanos : tactic ℕ :=
do time ← tactic.unsafe_run_io (@io.cmd { cmd := "gdate", args := [ "+%s%N" ] } ),
   pure time.to_nat

meta def check_range : tactic unit :=
assumption <|> do
`[apply of_as_true, trivial]

end tactic.interactive

export tactic.interactive (check_range)

namespace io

-- variable [io.interface]

def read_dev_random (n : ℕ) : io (array n char) := do
fh ← mk_file_handle "/dev/random" mode.read tt,
buf ← fs.read fh n,
fs.close fh,
if h : buf.size = n
then return (cast (by rw h) buf.to_array)
else io.fail "wrong number of bytes read from /dev/random"

def accum_char (w : ℕ) (c : char) : ℕ :=
c.to_nat + 256 * w

def mk_generator : io std_gen := do
x ← io.read_dev_random 8,
return $ mk_std_gen (foldl accum_char 0 $ x.to_list : ℕ)

variables {α : Type}

def run_rand (cmd : _root_.rand α) : io α :=
do g ← io.mk_generator,
   return $ (cmd.run ⟨g⟩).1

variable [random α]

def random : io α :=
io.run_rand (random.random α _)

def random_r (x y : α) (p : x ≤ y . check_range) : io (x .. y) :=
io.run_rand (random.random_r _ x y p)

def random_series : io (stream α) := do
g ← io.mk_generator,
return $ random.random_series _ _ g

def random_series_r (x y : α) (h : x ≤ y . check_range) : io (stream $ x .. y) := do
g ← io.mk_generator,
return $ random.random_series_r _ x y h g

end io

namespace tactic.interactive

meta def mk_generator : tactic std_gen := do
tactic.unsafe_run_io @io.mk_generator

meta def tactic' (α : Type u) : Type (max u 1) :=
Π (β : Type), (α → tactic β) → tactic β

meta def run_rand' {α : Type u} (cmd : rand α) (β : Type) (tac : α → tactic β)
: tactic β := do
g ← mk_generator,
tac (cmd.run ⟨g⟩).1

section random'

variables {α : Type u}
variable [random α]

meta def random' : tactic' α :=
run_rand' (random.random _ _)

meta def random_r' (x y : α) (p : x ≤ y . check_range) : tactic' (x .. y) :=
run_rand' (random.random_r _ x y p)

meta def random_series' : tactic' (stream α)
 | β cmd := do
g ← mk_generator,
cmd $ random.random_series _ std_gen g

meta def random_series_r' (x y : α) (h : x ≤ y . check_range) : tactic' (stream $ x .. y)
 | β cmd := do
g ← mk_generator,
cmd $ random.random_series_r std_gen x y h g

end random'

section random

variable {α : Type}
variable [random α]

meta def random : tactic α :=
random' _ return

meta def random_r (x y : α) (p : x ≤ y . check_range) : tactic (x .. y) :=
random_r' _ _ p _ return

meta def random_series : tactic (stream α) :=
random_series' _ return

meta def random_series_r (x y : α) (h : x ≤ y . check_range) : tactic (stream $ x .. y) :=
random_series_r' _ _ h _ return

end random

end tactic.interactive

instance : preorder bool :=
{ le := λ p q, p → q
, le_refl := by { introv h, apply h }
, le_trans := by { introv ha hb h, apply hb, apply ha h } }

namespace bool

def coerce (x y : bool) (p : x ≤ y) (i : bool) : x .. y := do
  if hx : x ≤ i ∧ i ≤ y
  then ⟨ i, hx ⟩
  else ⟨ x , le_refl x , p ⟩
open ulift
variables {gen : Type} [random_gen gen]
protected def get_random : rand_g gen bool :=
⟨ prod.map id up ∘ @rand_bool gen _ ∘ down ⟩

structure bool_generator (g : Type) :=
  (next : bool)
  (queue : unsigned × ℕ)
  (gen : g)

protected def first (g : gen) : bool_generator gen  :=
let (r,g') := random_gen.next g in
{ next := r % 2 = 1
, queue := (r / 2,30)
, gen := g' }

protected def next : bool_generator gen → bool_generator gen
 | ⟨_,(_,0),g⟩ := bool.first g
 | ⟨_,(n,k),g⟩ := ⟨(n%2 = 1),(n/2,k-1),g⟩

def random_series' (g : gen) : stream (bool_generator gen) :=
stream.iterate bool.next (bool.first g)

def random_series (g : gen) : stream bool :=
stream.map bool.bool_generator.next $ random_series' g

end bool

instance : random bool :=
{ to_has_le := by apply_instance
, random   := λ g, @bool.get_random _
, random_r := λ g _inst x y p, bool.coerce _ _ p <$> (@bool.get_random g _inst)
, random_series   := @bool.random_series
, random_series_r := λ gen _inst x y p g, stream.map (bool.coerce _ _ p) $ @bool.random_series _ _inst g }

instance (n : ℕ) : preorder (bitvec n) :=
{ le := λ x y, x.to_nat ≤ y.to_nat
, le_refl  := by { introv, apply nat.le_refl }
, le_trans := by { introv ha hb, apply nat.le_trans ha hb } }

lemma bitvec.le_def {n : ℕ} (x y : bitvec n)
: x ≤ y ↔ x.to_nat ≤ y.to_nat :=
by refl

open nat (succ one_add mod_eq_of_lt zero_lt_succ add_one succ_le_succ)

namespace stream

variable {α : Type u}

open list (length) stream (approx)

lemma length_approx
: ∀ (s : stream α) (n : ℕ), length (approx n s) = n
 | s 0 := rfl
 | s (succ n) := by simp [approx,length,one_add,length_approx]

end stream

variables {gen : Type} [random_gen gen]

def bitvec.random (n : ℕ) : rand_g gen (bitvec n) :=
⟨ λ ⟨ g ⟩,
let r := bool.random_series' g,
    v := map bool.bool_generator.next $ stream.approx n r in
have Hv : length v = n,
     by { simp [stream.length_approx _ _], },
⟨ ⟨ v, Hv ⟩ , ⟨ (r.nth $ succ n).gen ⟩ ⟩ ⟩

section coerce

parameters {i' n : ℕ}
parameters {x y : bitvec n}

parameters P' : x.to_nat ≤ y.to_nat
include P'

local infix ^ := nat.pow

lemma bitvec.interval_fits_in_word_size
: x.to_nat + i' % (1 + (y.to_nat - x.to_nat)) < 2^n :=
begin
  let x' := x.to_nat,
  let y' := y.to_nat,
  apply @lt_of_lt_of_le _ _ _ (x' + (y' - x' + 1)),
  { apply add_lt_add_left, simp,
    apply nat.mod_lt,
    rw one_add, apply zero_lt_succ },
  { rw [← add_assoc,← nat.add_sub_assoc P',nat.add_sub_cancel_left,add_one],
    clear P' i',
    cases y with y Hy,
    unfold bitvec.to_nat vector.to_list subtype.val bitvec.bits_to_nat,
    rw [← reverse_reverse y, foldl_reverse,← Hy,← length_reverse],
    generalize : reverse y = z,
    clear x' x y' Hy y n,
    induction z with x xs,
    { rw [list.length,list.foldr,nat.pow] },
    { simp [foldr,length,one_add,pow_succ,flip,bitvec.add_lsb],
      transitivity succ (1 +
       (foldr (λ (b : bool) (a : ℕ), a + (a + cond b 1 0)) 0 xs +
          foldr (λ (b : bool) (a : ℕ), a + (a + cond b 1 0)) 0 xs)),
      { apply succ_le_succ, apply add_le_add_right,
        cases x, apply nat.zero_le, refl, },
      { simp!,
        rw [← nat.add_succ,← nat.add_succ,one_add,← nat.succ_add,mul_comm,← two_mul],
        apply nat.mul_le_mul_left,
        simp [flip,bitvec.add_lsb] at z_ih,
        apply z_ih } }, },
end
end coerce

open nat

def bitvec.coerce {n : ℕ} (x y : bitvec n) (P : x ≤ y)
  (i : bitvec n)
: (x .. y) :=
let x' := x.to_nat,
    y' := y.to_nat,
    i' := i.to_nat,
    r := i' % (y' - x' + 1) + x' in
have Hx : x ≤ bitvec.of_nat n r,
  begin
    unfold_local r,
    simp [bitvec.le_def,bitvec.to_nat_of_nat],
    rw [mod_eq_of_lt],
    { apply nat.le_add_right },
    unfold_locals x' y' i',
    apply bitvec.interval_fits_in_word_size,
    apply P
  end,
have Hy : bitvec.of_nat n r ≤ y,
  begin
    unfold_local r,
    rw [bitvec.le_def,bitvec.to_nat_of_nat,mod_eq_of_lt],
    transitivity (y' - x') + x',
    { apply add_le_add_right,
      apply le_of_lt_succ,
      rw ← add_one,
      apply mod_lt,
      rw add_one, apply zero_lt_succ },
    { transitivity x' + (y' - x'),
      apply le_of_eq, ac_refl,
      rw [← nat.add_sub_assoc P,nat.add_sub_cancel_left], },
    simp, apply bitvec.interval_fits_in_word_size P,
  end,
⟨ bitvec.of_nat _ r , Hx , Hy ⟩

def bitvec.random_series (n : ℕ) (g : gen) : stream (bitvec n) :=
stream.corec
(λ s, ⟨ stream.approx n s, stream.length_approx _ _ ⟩)
(stream.drop n)
(@random.random_series bool _ gen _ g)

instance random_bitvec (n : ℕ) : random (bitvec n) :=
{ to_has_le := by apply_instance
, random := λ _ inst, @bitvec.random _ inst n
, random_r := λ _ inst x y p, bitvec.coerce _ _ p <$> @bitvec.random _ inst n
, random_series := λ _ inst, @bitvec.random_series _ inst n
, random_series_r := λ _ inst x y p g, bitvec.coerce _ _ p ∘ @bitvec.random_series _ inst n g }

-- example : true :=
-- begin
-- tactic.trace "\n\n",
-- (do x ← (tactic.interactive.random : tactic (bitvec 16)),
--     tactic.trace (x : bitvec 16).to_nat),
-- (do x ← (tactic.interactive.random_series),
--     tactic.trace $ map bitvec.to_nat (stream.approx 10 x : list (bitvec 16))),
-- (do x ← (tactic.interactive.random_series_r (25 : bitvec 15) 100),
--     tactic.trace $ map (bitvec.to_nat ∘ subtype.val) (stream.approx 10 x)),
-- trivial
-- end

-- meta def main [io.interface] : io unit := do
-- print_ln "\n\n",
-- x ← (io.random : io (bitvec 16)),
-- print_ln (x : bitvec 16).to_nat,
-- x ← io.random_series,
-- print_ln $ map bitvec.to_nat (stream.approx 10 x : list (bitvec 16)),
-- x ← (io.random_series_r (25 : bitvec 15) 100),
-- print_ln $ map (bitvec.to_nat ∘ subtype.val) (stream.approx 10 x)

-- run_cmd tactic.run_io @main

open nat

lemma div_two_round_up
  (x : ℕ)
  (h₀ : 1 < x)
: (x + 1) / 2 < x :=
begin
  rw [div_lt_iff_lt_mul,norm_num.mul_bit0,mul_one,bit0],
  apply add_lt_add_left, apply h₀,
  apply of_as_true, trivial
end

def word_size : ℕ → ℕ
 | x :=
if h : 1 < x then
  have (x + 1) / 2 < x,
    from div_two_round_up _ h,
  succ $ word_size ((x + 1) / 2)
else 0

local infix ^ := nat.pow

lemma word_size_le_two_pow (n : ℕ)
: n ≤ 2^word_size n :=
begin
  apply nat.strong_induction_on n,
  clear n, intros n IH,
  by_cases h : 1 < n,
  { rw [word_size,if_pos h,nat.pow],
    cases n with n, { cases not_lt_zero _ h },
    change n < _,
    rw ← @div_lt_iff_lt_mul _ _ 2 dec_trivial,
    have h' := div_two_round_up (succ n) h,
    specialize IH ((succ n + 1) / 2) h', clear h h',
    rw [succ_add,← add_succ] at *,
    simp only [-succ_pos', add_zero] at *,
    have h : (n + 2 * 1) / 2 = n / 2 + 1 :=
       add_mul_div_left _ _ dec_trivial,
    rw [mul_one] at h,
    rw h at IH ⊢,
    apply IH },
  { rw [word_size,if_neg h,nat.pow],
    apply le_of_not_gt h }
end

namespace fin
section fin
parameter {n : ℕ}

def shift_31l : ℕ :=
by apply_normed 2^31

protected def random_aux : ℕ → ℕ → rand_g gen (fin (succ n))
 | 0 k := return $ fin.of_nat k
 | (succ n) k :=
do x ← random.next,
   random_aux n $ x + (k * shift_31l)

protected def random : rand_g gen (fin (succ n)) :=
let m := word_size n / 31 + 1 in
random_aux m 0

section coerce

parameters {i' r k : ℕ}
parameters {y : fin k}

-- def x' := x.val
-- def y' := y.val
parameters {x' : ℕ}

parameters P' : x' ≤ y.val
include P'

lemma fin.interval_fits_in_word_size
: x' + i' % (1 + (y.val - x')) < k :=
begin
  apply @lt_of_lt_of_le _ _ _ (x' + (y.val - x' + 1)),
  { apply add_lt_add_left, simp,
    apply nat.mod_lt,
    rw one_add, apply zero_lt_succ },
  { rw [← add_assoc,← nat.add_sub_assoc P',nat.add_sub_cancel_left,add_one],
    apply y.is_lt }
end
end coerce

protected def coerce {n : ℕ} (x y : fin (succ n)) (P : x ≤ y)
  (i : fin (succ n))
: (x .. y) :=
let x' := x.val,
    i' := i.val,
    r := i' % (y.val - x' + 1) + x' in
have P' : x.val ≤ y.val,
  by { rw ← le_def, apply P },
have Hx : x ≤ fin.of_nat r,
  begin
    unfold_local r,
    simp [fin.le_def,fin.val_of_nat_eq_mod],
    rw [mod_eq_of_lt],
    { apply nat.le_add_right },
    apply fin.interval_fits_in_word_size,
    unfold_locals x',
    rw ← fin.le_def, apply P
  end,
have Hy : fin.of_nat r ≤ y,
  begin
    unfold_local r,
    rw [fin.le_def,fin.val_of_nat_eq_mod,mod_eq_of_lt],
    transitivity (y.val - x') + x',
    { apply add_le_add_right,
      apply le_of_lt_succ,
      rw add_one,
      apply mod_lt,
      apply zero_lt_succ },
    { transitivity x' + (y.val - x'),
      apply le_of_eq, ac_refl,
      rw [← nat.add_sub_assoc P',nat.add_sub_cancel_left], },
    simp, apply fin.interval_fits_in_word_size P',
  end,
⟨ fin.of_nat r , Hx , Hy ⟩

protected def random_r (x y : fin (succ n)) (p : x ≤ y) : rand_g gen (x .. y) :=
fin.coerce _ _ p <$> fin.random

end fin
end fin

instance fin_random (n : ℕ) : random (fin (succ n)) :=
{ to_has_le := by apply_instance
, random := λ g, @fin.random _ g
, random_r := λ x y p, @fin.random_r n x y p }

open nat

def random_fin_of_pos : ∀ (n : ℕ) (h : 0 < n), random (fin n)
 | (succ n) _ := fin_random _
 | 0 h := false.elim (not_lt_zero _ h)

section
open stream

def try_bitvec_random : io unit := do
put_str_ln "",
y ← io.mk_generator,
let w := succ $ 31,
let i : bitvec w := 2,
let j : bitvec w := 10000,
print_ln (repr y),
x ← (io.random : io (bitvec w)), return x.to_nat,
x ← (io.random_r (i) j : io _), return (x.val.to_nat),
x ← (io.random_series : io (stream (bitvec w))),
    return $ map bitvec.to_nat $ approx 10 x,
x ← (io.random_series_r i j),
    return $ map (bitvec.to_nat ∘ subtype.val) $ approx 10 x,
return ()

def try_fin_random : io unit := do
put_str_ln "",
let n := by { apply_normed 2^31 },
let i : fin (succ n) := fin.of_nat 2,
let j : fin (succ n) := fin.of_nat 100000,
have dp : i ≤ j,
by { unfold_locals i j n,
     rw [fin.le_def,fin.val_of_nat,fin.val_of_nat]
     ; [ skip, apply succ_le_succ, apply succ_le_succ ]
     ; norm_num ; trivial, }, do
x ← (io.random : io (fin 10)), print_ln x.val,
x ← (io.random : io (fin (succ n))), return x,
x ← (io.random_r i j : io _), print_ln (x.val),
x ← (io.random_series : io (stream (fin (succ n)))), return $ approx 10 x,
x ← (io.random_series_r i j), return $ map subtype.val $ approx 10 x,
return ()

end

-- run_cmd do
--   tactic.timetac "fin_random:    " (tactic.unsafe_run_io @try_fin_random ),
--   tactic.timetac "bitvec_random: " (tactic.unsafe_run_io @try_bitvec_random),
--   tactic.trace "do"

-- fin_random:     46.1ms
-- bitvec_random:  785ms


/-
Copyright (c) 2022 Joël Riou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joël Riou
-/

import algebra.homology.homological_complex
import for_mathlib.algebra.homology.homological_complex_biprod
import for_mathlib.algebra.homology.trunc_le

open category_theory
open category_theory.category

variables {C : Type*} [category C]

namespace category_theory

namespace limits

lemma is_zero.iff_of_biprod [preadditive C] (A B : C) [has_binary_biproduct A B] :
  is_zero (A ⊞ B) ↔ is_zero A ∧ is_zero B :=
begin
  simp only [is_zero.iff_id_eq_zero],
  split,
  { intro h,
    split,
    { suffices : 𝟙 A = biprod.inl ≫ (𝟙 (A ⊞ B)) ≫ biprod.fst,
      { rw [this, h, zero_comp, comp_zero], },
      rw [id_comp, biprod.inl_fst], },
    { suffices : 𝟙 B = biprod.inr ≫ (𝟙 (A ⊞ B)) ≫ biprod.snd,
      { rw [this, h, zero_comp, comp_zero], },
      rw [id_comp, biprod.inr_snd], }, },
  { intro h,
    ext,
    { simpa only [comp_id, biprod.inl_fst, comp_zero, zero_comp] using h.left, },
    { simp only [comp_id, biprod.inl_snd, comp_zero, zero_comp], },
    { simp only [comp_id, biprod.inr_fst, comp_zero, zero_comp], },
    { simpa only [comp_id, biprod.inr_snd, comp_zero, zero_comp] using h.right, } },
end

end limits

end category_theory

open category_theory.limits

namespace cochain_complex

def is_bounded_above [has_zero_morphisms C] (K : cochain_complex C ℤ) : Prop :=
∃ (r : ℤ), ∀ (i : ℤ) (hi : r < i), is_zero (K.X i)

namespace is_bounded_above

lemma of_biprod [preadditive C] (K L : cochain_complex C ℤ)
  (hK : K.is_bounded_above) (hL : L.is_bounded_above)
  [∀ i, has_binary_biproduct (K.X i) (L.X i)] :
  is_bounded_above (homological_complex.biprod K L) :=
begin
  cases hK with k hk,
  cases hL with l hl,
  use max k l,
  intros i hi,
  dsimp,
  rw is_zero.iff_of_biprod,
  split,
  { apply hk,
    exact lt_of_le_of_lt (le_max_left _ _) hi, },
  { apply hl,
    exact lt_of_le_of_lt (le_max_right _ _) hi, },
end

lemma of_is_strictly_le [abelian C] (K : cochain_complex C ℤ) (n : ℤ)
  [K.is_strictly_le n] : is_bounded_above K :=
⟨n, λ i hi, is_strictly_le.is_zero K n i hi⟩

end is_bounded_above

end cochain_complex


import tactic
import category.functor
import category.applicative

universes u v

namespace monad

@[simp]
lemma bind_pure_star {m} [monad m] [is_lawful_monad m] (x : m punit) :
  x >>= (λ (_x : punit), pure punit.star : punit → m punit) = x :=
by { transitivity,
     { apply congr_arg, ext z, cases z, refl },
     { simp } }

variables {α β γ : Type u}
variables {m : Type u → Type v} [monad m]

@[reducible]
def pipe (a : α → m β) (b : β → m γ) : α → m γ :=
λ x, a x >>= b

infixr ` >=> `:55 := pipe

@[functor_norm]
lemma map_bind_eq_bind_comp {α β γ} {m} [monad m] [is_lawful_monad m]
  (f : α → β) (cmd : m α) (g : β → m γ) :
  (f <$> cmd) >>= g = cmd >>= g ∘ f :=
by rw [← bind_pure_comp_eq_map,bind_assoc,(∘)]; simp

@[functor_norm]
lemma bind_map {α β γ} {m} [monad m] [is_lawful_monad m]
  (f : α → γ → β) (cmd : m α) (g : α → m γ) :
  cmd >>= (λ x, f x <$> g x) = do { x ← cmd, y ← g x, pure $ f x y }  :=
by congr; ext; rw [← bind_pure (g x),map_bind]; simp

@[functor_norm]
lemma bind_seq {α β γ : Type u} {m} [monad m] [is_lawful_monad m]
  (f : α → m (γ → β)) (cmd : m α) (g : α → m γ) :
  cmd >>= (λ x, f x <*> g x) = do { x ← cmd, h ← f x, y ← g x, pure $ h y }  :=
by congr; ext; simp [seq_eq_bind_map] with functor_norm

end monad

attribute [functor_norm] bind_assoc has_bind.and_then map_bind seq_left_eq seq_right_eq

namespace sum

variables {e : Type v} {α β : Type u}

protected def seq : Π (x : sum e (α → β)) (f : sum e α), sum e β
| (sum.inl e) _ := sum.inl e
| (sum.inr f) x := f <$> x

instance : applicative (sum e) :=
{ seq := @sum.seq e,
  pure := @sum.inr e }

instance : is_lawful_applicative (sum e) :=
by constructor; intros;
   casesm* _ ⊕ _; simp [(<*>),sum.seq,pure,(<$>)];
   refl

end sum


import tactic

universe variables u v

notation `ε` binder `, ` r:(scoped p, classical.epsilon p) := r

namespace classical

variables {α : Sort u}
variables {β : Sort v}
variables {p q : α → Prop}
variables Hpq : ∀ x, p x → q x
variables Hex : ∃ x, p x

include Hpq Hex

lemma some_spec'
: q (some Hex) :=
begin
  apply Hpq,
  apply some_spec
end

variable [nonempty α]

lemma epsilon_spec'
: q (epsilon p) :=
begin
  apply Hpq,
  apply epsilon_spec Hex,
end

end classical

open tactic interactive interactive.types lean.parser
open tactic.interactive (tauto)

meta def apply_some_spec (id : parse $ optional (tk "with" *> ident_)) : tactic unit :=
do t ← target,
   (l,_) ← solve_aux t (do
     e ← to_expr ``(@classical.some _ _ _),
     v ← mk_fresh_name,
     generalize e v,
     (expr.pi v bi t e) ← target,
     return (expr.lam v bi t e)),
   refine ``(@classical.some_spec' _ _ %%l _ _),
   `[simp only],
   interactive.intro id,
   `[intros h, apply h] <|>
      tauto (some ()) <|>
      smt_tactic.execute smt_tactic.eblast <|>
      return ()

meta def apply_epsilon_spec (ex : parse $ optional texpr)
  (id : parse $ optional (tk "with" *> ident_)) : tactic unit :=
focus1 $
do t ← target,
   (l,_) ← solve_aux t (do
     e ← to_expr ``(classical.epsilon _),
     v ← mk_fresh_name,
     generalize e v,
     (expr.pi v bi t e) ← target,
     return (expr.lam v bi t e)),
   refine ``(@classical.epsilon_spec' _ _ %%l _ _ _),
   `[simp only],
   interactive.intro id,
   try `[intros h, apply h],
   all_goals (solve_by_elim <|> ↑ex >>= tactic.refine <|>
              tauto (some ()) <|>
              smt_tactic.execute (smt_tactic.intros >> smt_tactic.eblast) <|>
              return ())

run_cmd add_interactive [`apply_some_spec,`apply_epsilon_spec]


/-
Copyright (c) 2022 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison
-/
import category_theory.limits.shapes.pullbacks
import category_theory.limits.shapes.zero_morphisms
import category_theory.limits.constructions.binary_products

/-!
# Limits involving zero objects

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

Binary products and coproducts with a zero object always exist,
and pullbacks/pushouts over a zero object are products/coproducts.
-/

noncomputable theory

open category_theory

variables {C : Type*} [category C]

namespace category_theory.limits

variables [has_zero_object C] [has_zero_morphisms C]
open_locale zero_object

/-- The limit cone for the product with a zero object. -/
def binary_fan_zero_left (X : C) : binary_fan (0 : C) X :=
binary_fan.mk 0 (𝟙 X)

/-- The limit cone for the product with a zero object is limiting. -/
def binary_fan_zero_left_is_limit (X : C) : is_limit (binary_fan_zero_left X) :=
binary_fan.is_limit_mk (λ s, binary_fan.snd s) (by tidy) (by tidy) (by tidy)

instance has_binary_product_zero_left (X : C) : has_binary_product (0 : C) X :=
has_limit.mk ⟨_, binary_fan_zero_left_is_limit X⟩

/-- A zero object is a left unit for categorical product. -/
def zero_prod_iso (X : C) : (0 : C) ⨯ X ≅ X :=
limit.iso_limit_cone ⟨_, binary_fan_zero_left_is_limit X⟩

@[simp] lemma zero_prod_iso_hom (X : C) : (zero_prod_iso X).hom = prod.snd :=
rfl
@[simp] lemma zero_prod_iso_inv_snd (X : C) : (zero_prod_iso X).inv ≫ prod.snd = 𝟙 X :=
by { dsimp [zero_prod_iso, binary_fan_zero_left], simp, }

/-- The limit cone for the product with a zero object. -/
def binary_fan_zero_right (X : C) : binary_fan X (0 : C) :=
binary_fan.mk (𝟙 X) 0

/-- The limit cone for the product with a zero object is limiting. -/
def binary_fan_zero_right_is_limit (X : C) : is_limit (binary_fan_zero_right X) :=
binary_fan.is_limit_mk (λ s, binary_fan.fst s) (by tidy) (by tidy) (by tidy)

instance has_binary_product_zero_right (X : C) : has_binary_product X (0 : C) :=
has_limit.mk ⟨_, binary_fan_zero_right_is_limit X⟩

/-- A zero object is a right unit for categorical product. -/
def prod_zero_iso (X : C) : X ⨯ (0 : C) ≅ X :=
limit.iso_limit_cone ⟨_, binary_fan_zero_right_is_limit X⟩

@[simp] lemma prod_zero_iso_hom (X : C) : (prod_zero_iso X).hom = prod.fst :=
rfl
@[simp] lemma prod_zero_iso_iso_inv_snd (X : C) : (prod_zero_iso X).inv ≫ prod.fst = 𝟙 X :=
by { dsimp [prod_zero_iso, binary_fan_zero_right], simp, }

/-- The colimit cocone for the coproduct with a zero object. -/
def binary_cofan_zero_left (X : C) : binary_cofan (0 : C) X :=
binary_cofan.mk 0 (𝟙 X)

/-- The colimit cocone for the coproduct with a zero object is colimiting. -/
def binary_cofan_zero_left_is_colimit (X : C) : is_colimit (binary_cofan_zero_left X) :=
binary_cofan.is_colimit_mk (λ s, binary_cofan.inr s) (by tidy) (by tidy) (by tidy)

instance has_binary_coproduct_zero_left (X : C) : has_binary_coproduct (0 : C) X :=
has_colimit.mk ⟨_, binary_cofan_zero_left_is_colimit X⟩

/-- A zero object is a left unit for categorical coproduct. -/
def zero_coprod_iso (X : C) : (0 : C) ⨿ X ≅ X :=
colimit.iso_colimit_cocone ⟨_, binary_cofan_zero_left_is_colimit X⟩

@[simp] lemma inr_zero_coprod_iso_hom (X : C) : coprod.inr ≫ (zero_coprod_iso X).hom = 𝟙 X :=
by { dsimp [zero_coprod_iso, binary_cofan_zero_left], simp, }
@[simp] lemma zero_coprod_iso_inv (X : C) : (zero_coprod_iso X).inv = coprod.inr :=
rfl

/-- The colimit cocone for the coproduct with a zero object. -/
def binary_cofan_zero_right (X : C) : binary_cofan X (0 : C) :=
binary_cofan.mk (𝟙 X) 0

/-- The colimit cocone for the coproduct with a zero object is colimiting. -/
def binary_cofan_zero_right_is_colimit (X : C) : is_colimit (binary_cofan_zero_right X) :=
binary_cofan.is_colimit_mk (λ s, binary_cofan.inl s) (by tidy) (by tidy) (by tidy)

instance has_binary_coproduct_zero_right (X : C) : has_binary_coproduct X (0 : C) :=
has_colimit.mk ⟨_, binary_cofan_zero_right_is_colimit X⟩

/-- A zero object is a right unit for categorical coproduct. -/
def coprod_zero_iso (X : C) : X ⨿ (0 : C) ≅ X :=
colimit.iso_colimit_cocone ⟨_, binary_cofan_zero_right_is_colimit X⟩

@[simp] lemma inr_coprod_zeroiso_hom (X : C) : coprod.inl ≫ (coprod_zero_iso X).hom = 𝟙 X :=
by { dsimp [coprod_zero_iso, binary_cofan_zero_right], simp, }
@[simp] lemma coprod_zero_iso_inv (X : C) : (coprod_zero_iso X).inv = coprod.inl :=
rfl

instance has_pullback_over_zero
  (X Y : C) [has_binary_product X Y] : has_pullback (0 : X ⟶ 0) (0 : Y ⟶ 0) :=
has_limit.mk ⟨_, is_pullback_of_is_terminal_is_product _ _ _ _
  has_zero_object.zero_is_terminal (prod_is_prod X Y)⟩

/-- The pullback over the zeron object is the product. -/
def pullback_zero_zero_iso (X Y : C) [has_binary_product X Y] :
  pullback (0 : X ⟶ 0) (0 : Y ⟶ 0) ≅ X ⨯ Y :=
limit.iso_limit_cone ⟨_, is_pullback_of_is_terminal_is_product _ _ _ _
  has_zero_object.zero_is_terminal (prod_is_prod X Y)⟩

@[simp] lemma pullback_zero_zero_iso_inv_fst (X Y : C) [has_binary_product X Y] :
  (pullback_zero_zero_iso X Y).inv ≫ pullback.fst = prod.fst :=
by { dsimp [pullback_zero_zero_iso], simp, }
@[simp] lemma pullback_zero_zero_iso_inv_snd (X Y : C) [has_binary_product X Y] :
  (pullback_zero_zero_iso X Y).inv ≫ pullback.snd = prod.snd :=
by { dsimp [pullback_zero_zero_iso], simp, }
@[simp] lemma pullback_zero_zero_iso_hom_fst (X Y : C) [has_binary_product X Y] :
  (pullback_zero_zero_iso X Y).hom ≫ prod.fst = pullback.fst :=
by { simp [←iso.eq_inv_comp], }
@[simp] lemma pullback_zero_zero_iso_hom_snd (X Y : C) [has_binary_product X Y] :
  (pullback_zero_zero_iso X Y).hom ≫ prod.snd = pullback.snd :=
by { simp [←iso.eq_inv_comp], }

instance has_pushout_over_zero
  (X Y : C) [has_binary_coproduct X Y] : has_pushout (0 : 0 ⟶ X) (0 : 0 ⟶ Y) :=
has_colimit.mk ⟨_, is_pushout_of_is_initial_is_coproduct _ _ _ _
  has_zero_object.zero_is_initial (coprod_is_coprod X Y)⟩

/-- The pushout over the zero object is the coproduct. -/
def pushout_zero_zero_iso
  (X Y : C) [has_binary_coproduct X Y] : pushout (0 : 0 ⟶ X) (0 : 0 ⟶ Y) ≅ X ⨿ Y :=
colimit.iso_colimit_cocone ⟨_, is_pushout_of_is_initial_is_coproduct _ _ _ _
  has_zero_object.zero_is_initial (coprod_is_coprod X Y)⟩

@[simp] lemma inl_pushout_zero_zero_iso_hom (X Y : C) [has_binary_coproduct X Y] :
  pushout.inl ≫ (pushout_zero_zero_iso X Y).hom = coprod.inl :=
by { dsimp [pushout_zero_zero_iso], simp, }
@[simp] lemma inr_pushout_zero_zero_iso_hom (X Y : C) [has_binary_coproduct X Y] :
  pushout.inr ≫ (pushout_zero_zero_iso X Y).hom = coprod.inr :=
by { dsimp [pushout_zero_zero_iso], simp, }
@[simp] lemma inl_pushout_zero_zero_iso_inv (X Y : C) [has_binary_coproduct X Y] :
  coprod.inl ≫ (pushout_zero_zero_iso X Y).inv = pushout.inl :=
by { simp [iso.comp_inv_eq], }
@[simp] lemma inr_pushout_zero_zero_iso_inv (X Y : C) [has_binary_coproduct X Y] :
  coprod.inr ≫ (pushout_zero_zero_iso X Y).inv = pushout.inr :=
by { simp [iso.comp_inv_eq], }

end category_theory.limits


/-
Copyright (c) 2022 Markus Himmel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel
-/
import category_theory.balanced
import category_theory.limits.essentially_small
import category_theory.limits.opposites
import category_theory.limits.shapes.zero_morphisms
import category_theory.subobject.lattice
import category_theory.subobject.well_powered
import data.set.opposite

/-!
# Separating and detecting sets

There are several non-equivalent notions of a generator of a category. Here, we consider two of
them:

* We say that `𝒢` is a separating set if the functors `C(G, -)` for `G ∈ 𝒢` are collectively
    faithful, i.e., if `h ≫ f = h ≫ g` for all `h` with domain in `𝒢` implies `f = g`.
* We say that `𝒢` is a detecting set if the functors `C(G, -)` collectively reflect isomorphisms,
    i.e., if any `h` with domain in `𝒢` uniquely factors through `f`, then `f` is an isomorphism.

There are, of course, also the dual notions of coseparating and codetecting sets.

## Main results

We
* define predicates `is_separating`, `is_coseparating`, `is_detecting` and `is_codetecting` on
  sets of objects;
* show that separating and coseparating are dual notions;
* show that detecting and codetecting are dual notions;
* show that if `C` has equalizers, then detecting implies separating;
* show that if `C` has coequalizers, then codetecting implies separating;
* show that if `C` is balanced, then separating implies detecting and coseparating implies
  codetecting;
* show that `∅` is separating if and only if `∅` is coseparating if and only if `C` is thin;
* show that `∅` is detecting if and only if `∅` is codetecting if and only if `C` is a groupoid;
* define predicates `is_separator`, `is_coseparator`, `is_detector` and `is_codetector` as the
  singleton counterparts to the definitions for sets above and restate the above results in this
  situation;
* show that `G` is a separator if and only if `coyoneda.obj (op G)` is faithful (and the dual);
* show that `G` is a detector if and only if `coyoneda.obj (op G)` reflects isomorphisms (and the
  dual).

## Future work

* We currently don't have any examples yet.
* We will want typeclasses `has_separator C` and similar.

-/

universes w v₁ v₂ u₁ u₂

open category_theory.limits opposite

namespace category_theory
variables {C : Type u₁} [category.{v₁} C] {D : Type u₂} [category.{v₂} D]

/-- We say that `𝒢` is a separating set if the functors `C(G, -)` for `G ∈ 𝒢` are collectively
    faithful, i.e., if `h ≫ f = h ≫ g` for all `h` with domain in `𝒢` implies `f = g`. -/
def is_separating (𝒢 : set C) : Prop :=
∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ (G ∈ 𝒢) (h : G ⟶ X), h ≫ f = h ≫ g) → f = g

/-- We say that `𝒢` is a coseparating set if the functors `C(-, G)` for `G ∈ 𝒢` are collectively
    faithful, i.e., if `f ≫ h = g ≫ h` for all `h` with codomain in `𝒢` implies `f = g`. -/
def is_coseparating (𝒢 : set C) : Prop :=
∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ (G ∈ 𝒢) (h : Y ⟶ G), f ≫ h = g ≫ h) → f = g

/-- We say that `𝒢` is a detecting set if the functors `C(G, -)` collectively reflect isomorphisms,
    i.e., if any `h` with domain in `𝒢` uniquely factors through `f`, then `f` is an isomorphism. -/
def is_detecting (𝒢 : set C) : Prop :=
∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ (G ∈ 𝒢) (h : G ⟶ Y), ∃! (h' : G ⟶ X), h' ≫ f = h) → is_iso f

/-- We say that `𝒢` is a codetecting set if the functors `C(-, G)` collectively reflect
    isomorphisms, i.e., if any `h` with codomain in `G` uniquely factors through `f`, then `f` is
    an isomorphism. -/
def is_codetecting (𝒢 : set C) : Prop :=
∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ (G ∈ 𝒢) (h : X ⟶ G), ∃! (h' : Y ⟶ G), f ≫ h' = h) → is_iso f

section dual

lemma is_separating_op_iff (𝒢 : set C) : is_separating 𝒢.op ↔ is_coseparating 𝒢 :=
begin
  refine ⟨λ h𝒢 X Y f g hfg, _, λ h𝒢 X Y f g hfg, _⟩,
  { refine quiver.hom.op_inj (h𝒢 _ _ (λ G hG h, quiver.hom.unop_inj _)),
    simpa only [unop_comp, quiver.hom.unop_op] using hfg _ (set.mem_op.1 hG) _ },
  { refine quiver.hom.unop_inj (h𝒢 _ _ (λ G hG h, quiver.hom.op_inj _)),
    simpa only [op_comp, quiver.hom.op_unop] using hfg _ (set.op_mem_op.2 hG) _ }
end

lemma is_coseparating_op_iff (𝒢 : set C) : is_coseparating 𝒢.op ↔ is_separating 𝒢 :=
begin
  refine ⟨λ h𝒢 X Y f g hfg, _, λ h𝒢 X Y f g hfg, _⟩,
  { refine quiver.hom.op_inj (h𝒢 _ _ (λ G hG h, quiver.hom.unop_inj _)),
    simpa only [unop_comp, quiver.hom.unop_op] using hfg _ (set.mem_op.1 hG) _ },
  { refine quiver.hom.unop_inj (h𝒢 _ _ (λ G hG h, quiver.hom.op_inj _)),
    simpa only [op_comp, quiver.hom.op_unop] using hfg _ (set.op_mem_op.2 hG) _ }
end

lemma is_coseparating_unop_iff (𝒢 : set Cᵒᵖ) : is_coseparating 𝒢.unop ↔ is_separating 𝒢 :=
by rw [← is_separating_op_iff, set.unop_op]

lemma is_separating_unop_iff (𝒢 : set Cᵒᵖ) : is_separating 𝒢.unop ↔ is_coseparating 𝒢 :=
by rw [← is_coseparating_op_iff, set.unop_op]

lemma is_detecting_op_iff (𝒢 : set C) : is_detecting 𝒢.op ↔ is_codetecting 𝒢 :=
begin
  refine ⟨λ h𝒢 X Y f hf, _, λ h𝒢 X Y f hf, _⟩,
  { refine (is_iso_op_iff _).1 (h𝒢 _ (λ G hG h, _)),
    obtain ⟨t, ht, ht'⟩ := hf (unop G) (set.mem_op.1 hG) h.unop,
    exact ⟨t.op, quiver.hom.unop_inj ht, λ y hy,
      quiver.hom.unop_inj (ht' _ (quiver.hom.op_inj hy))⟩ },
  { refine (is_iso_unop_iff _).1 (h𝒢 _ (λ G hG h, _)),
    obtain ⟨t, ht, ht'⟩ := hf (op G) (set.op_mem_op.2 hG) h.op,
    refine ⟨t.unop, quiver.hom.op_inj ht, λ y hy, quiver.hom.op_inj (ht' _ _)⟩,
    exact quiver.hom.unop_inj (by simpa only using hy) }
end

lemma is_codetecting_op_iff (𝒢 : set C) : is_codetecting 𝒢.op ↔ is_detecting 𝒢 :=
begin
  refine ⟨λ h𝒢 X Y f hf, _, λ h𝒢 X Y f hf, _⟩,
  { refine (is_iso_op_iff _).1 (h𝒢 _ (λ G hG h, _)),
    obtain ⟨t, ht, ht'⟩ := hf (unop G) (set.mem_op.1 hG) h.unop,
    exact ⟨t.op, quiver.hom.unop_inj ht, λ y hy,
      quiver.hom.unop_inj (ht' _ (quiver.hom.op_inj hy))⟩ },
  { refine (is_iso_unop_iff _).1 (h𝒢 _ (λ G hG h, _)),
    obtain ⟨t, ht, ht'⟩ := hf (op G) (set.op_mem_op.2 hG) h.op,
    refine ⟨t.unop, quiver.hom.op_inj ht, λ y hy, quiver.hom.op_inj (ht' _ _)⟩,
    exact quiver.hom.unop_inj (by simpa only using hy) }
end

lemma is_detecting_unop_iff (𝒢 : set Cᵒᵖ) : is_detecting 𝒢.unop ↔ is_codetecting 𝒢 :=
by rw [← is_codetecting_op_iff, set.unop_op]

lemma is_codetecting_unop_iff {𝒢 : set Cᵒᵖ} : is_codetecting 𝒢.unop ↔ is_detecting 𝒢 :=
by rw [← is_detecting_op_iff, set.unop_op]

end dual

lemma is_detecting.is_separating [has_equalizers C] {𝒢 : set C} (h𝒢 : is_detecting 𝒢) :
  is_separating 𝒢 :=
λ X Y f g hfg,
  have is_iso (equalizer.ι f g), from h𝒢 _ (λ G hG h, equalizer.exists_unique _ (hfg _ hG _)),
  by exactI eq_of_epi_equalizer

section

lemma is_codetecting.is_coseparating [has_coequalizers C] {𝒢 : set C} :
  is_codetecting 𝒢 → is_coseparating 𝒢 :=
by simpa only [← is_separating_op_iff, ← is_detecting_op_iff] using is_detecting.is_separating

end

lemma is_separating.is_detecting [balanced C] {𝒢 : set C} (h𝒢 : is_separating 𝒢) :
  is_detecting 𝒢 :=
begin
  intros X Y f hf,
  refine (is_iso_iff_mono_and_epi _).2 ⟨⟨λ Z g h hgh, h𝒢 _ _ (λ G hG i, _)⟩, ⟨λ Z g h hgh, _⟩⟩,
  { obtain ⟨t, -, ht⟩ := hf G hG (i ≫ g ≫ f),
    rw [ht (i ≫ g) (category.assoc _ _ _), ht (i ≫ h) (hgh.symm ▸ category.assoc _ _ _)] },
  { refine h𝒢 _ _ (λ G hG i, _),
    obtain ⟨t, rfl, -⟩ := hf G hG i,
    rw [category.assoc, hgh, category.assoc] }
end

section
local attribute [instance] balanced_opposite

lemma is_coseparating.is_codetecting [balanced C] {𝒢 : set C} :
  is_coseparating 𝒢 → is_codetecting 𝒢 :=
by simpa only [← is_detecting_op_iff, ← is_separating_op_iff] using is_separating.is_detecting

end

lemma is_detecting_iff_is_separating [has_equalizers C] [balanced C] (𝒢 : set C) :
  is_detecting 𝒢 ↔ is_separating 𝒢 :=
⟨is_detecting.is_separating, is_separating.is_detecting⟩

lemma is_codetecting_iff_is_coseparating [has_coequalizers C] [balanced C] {𝒢 : set C} :
  is_codetecting 𝒢 ↔ is_coseparating 𝒢 :=
⟨is_codetecting.is_coseparating, is_coseparating.is_codetecting⟩

section mono

lemma is_separating.mono {𝒢 : set C} (h𝒢 : is_separating 𝒢) {ℋ : set C} (h𝒢ℋ : 𝒢 ⊆ ℋ) :
  is_separating ℋ :=
λ X Y f g hfg, h𝒢 _ _ $ λ G hG h, hfg _ (h𝒢ℋ hG) _

lemma is_coseparating.mono {𝒢 : set C} (h𝒢 : is_coseparating 𝒢) {ℋ : set C} (h𝒢ℋ : 𝒢 ⊆ ℋ) :
  is_coseparating ℋ :=
λ X Y f g hfg, h𝒢 _ _ $ λ G hG h, hfg _ (h𝒢ℋ hG) _

lemma is_detecting.mono {𝒢 : set C} (h𝒢 : is_detecting 𝒢) {ℋ : set C} (h𝒢ℋ : 𝒢 ⊆ ℋ) :
  is_detecting ℋ :=
λ X Y f hf, h𝒢 _ $ λ G hG h, hf _ (h𝒢ℋ hG) _

lemma is_codetecting.mono {𝒢 : set C} (h𝒢 : is_codetecting 𝒢) {ℋ : set C} (h𝒢ℋ : 𝒢 ⊆ ℋ) :
  is_codetecting ℋ :=
λ X Y f hf, h𝒢 _ $ λ G hG h, hf _ (h𝒢ℋ hG) _

end mono

section empty

lemma thin_of_is_separating_empty (h : is_separating (∅ : set C)) : quiver.is_thin C :=
λ _ _, ⟨λ f g, h _ _ $ λ G, false.elim⟩

lemma is_separating_empty_of_thin [quiver.is_thin C] : is_separating (∅ : set C) :=
λ X Y f g hfg, subsingleton.elim _ _

lemma is_coseparating_empty_of_thin [quiver.is_thin C] :
  is_coseparating (∅ : set C) :=
λ X Y f g hfg, subsingleton.elim _ _

lemma groupoid_of_is_detecting_empty (h : is_detecting (∅ : set C)) {X Y : C} (f : X ⟶ Y) :
  is_iso f :=
h _ $ λ G, false.elim

lemma is_detecting_empty_of_groupoid [∀ {X Y : C} (f : X ⟶ Y), is_iso f] :
  is_detecting (∅ : set C) :=
λ X Y f hf, infer_instance

lemma groupoid_of_is_codetecting_empty (h : is_codetecting (∅ : set C)) {X Y : C} (f : X ⟶ Y) :
  is_iso f :=
h _ $ λ G, false.elim

lemma is_codetecting_empty_of_groupoid [∀ {X Y : C} (f : X ⟶ Y), is_iso f] :
  is_codetecting (∅ : set C) :=
λ X Y f hf, infer_instance

end empty

lemma is_separating_iff_epi (𝒢 : set C)
  [Π (A : C), has_coproduct (λ f : Σ G : 𝒢, (G : C) ⟶ A, (f.1 : C))] :
  is_separating 𝒢 ↔ ∀ A : C, epi (sigma.desc (@sigma.snd 𝒢 (λ G, (G : C) ⟶ A))) :=
begin
  refine ⟨λ h A, ⟨λ Z u v huv, h _ _ (λ G hG f, _)⟩, λ h X Y f g hh, _⟩,
  { simpa using (sigma.ι (λ f : Σ G : 𝒢, (G : C) ⟶ A, (f.1 : C)) ⟨⟨G, hG⟩, f⟩) ≫= huv },
  { haveI := h X,
    refine (cancel_epi (sigma.desc (@sigma.snd 𝒢 (λ G, (G : C) ⟶ X)))).1 (colimit.hom_ext (λ j, _)),
    simpa using hh j.as.1.1 j.as.1.2 j.as.2 }
end

lemma is_coseparating_iff_mono (𝒢 : set C)
  [Π (A : C), has_product (λ f : Σ G : 𝒢, A ⟶ (G : C), (f.1 : C))] :
  is_coseparating 𝒢 ↔ ∀ A : C, mono (pi.lift (@sigma.snd 𝒢 (λ G, A ⟶ (G : C)))) :=
begin
  refine ⟨λ h A, ⟨λ Z u v huv, h _ _ (λ G hG f, _)⟩, λ h X Y f g hh, _⟩,
  { simpa using huv =≫ (pi.π (λ f : Σ G : 𝒢, A ⟶ (G : C), (f.1 : C)) ⟨⟨G, hG⟩, f⟩) },
  { haveI := h Y,
    refine (cancel_mono (pi.lift (@sigma.snd 𝒢 (λ G, Y ⟶ (G : C))))).1 (limit.hom_ext (λ j, _)),
    simpa using hh j.as.1.1 j.as.1.2 j.as.2 }
end

/-- An ingredient of the proof of the Special Adjoint Functor Theorem: a complete well-powered
    category with a small coseparating set has an initial object.

    In fact, it follows from the Special Adjoint Functor Theorem that `C` is already cocomplete,
    see `has_colimits_of_has_limits_of_is_coseparating`. -/
lemma has_initial_of_is_coseparating [well_powered C] [has_limits C] {𝒢 : set C} [small.{v₁} 𝒢]
  (h𝒢 : is_coseparating 𝒢) : has_initial C :=
begin
  haveI := has_products_of_shape_of_small C 𝒢,
  haveI := λ A, has_products_of_shape_of_small.{v₁} C (Σ G : 𝒢, A ⟶ (G : C)),
  letI := complete_lattice_of_complete_semilattice_Inf (subobject (pi_obj (coe : 𝒢 → C))),
  suffices : ∀ A : C, unique (((⊥ : subobject (pi_obj (coe : 𝒢 → C))) : C) ⟶ A),
  { exactI has_initial_of_unique ((⊥ : subobject (pi_obj (coe : 𝒢 → C))) : C) },
  refine λ A, ⟨⟨_⟩, λ f, _⟩,
  { let s := pi.lift (λ f : Σ G : 𝒢, A ⟶ (G : C), id (pi.π (coe : 𝒢 → C)) f.1),
    let t := pi.lift (@sigma.snd 𝒢 (λ G, A ⟶ (G : C))),
    haveI : mono t := (is_coseparating_iff_mono 𝒢).1 h𝒢 A,
    exact subobject.of_le_mk _ (pullback.fst : pullback s t ⟶ _) bot_le ≫ pullback.snd },
  { generalize : default = g,
    suffices : is_split_epi (equalizer.ι f g),
    { exactI eq_of_epi_equalizer },
    exact is_split_epi.mk' ⟨subobject.of_le_mk _ (equalizer.ι f g ≫ subobject.arrow _)
      bot_le, by { ext, simp }⟩ }
end

/-- An ingredient of the proof of the Special Adjoint Functor Theorem: a cocomplete well-copowered
    category with a small separating set has a terminal object.

    In fact, it follows from the Special Adjoint Functor Theorem that `C` is already complete, see
    `has_limits_of_has_colimits_of_is_separating`. -/
lemma has_terminal_of_is_separating [well_powered Cᵒᵖ] [has_colimits C] {𝒢 : set C} [small.{v₁} 𝒢]
  (h𝒢 : is_separating 𝒢) : has_terminal C :=
begin
  haveI : small.{v₁} 𝒢.op := small_of_injective (set.op_equiv_self 𝒢).injective,
  haveI : has_initial Cᵒᵖ := has_initial_of_is_coseparating ((is_coseparating_op_iff _).2 h𝒢),
  exact has_terminal_of_has_initial_op
end

section well_powered

namespace subobject

lemma eq_of_le_of_is_detecting {𝒢 : set C} (h𝒢 : is_detecting 𝒢) {X : C} (P Q : subobject X)
  (h₁ : P ≤ Q) (h₂ : ∀ (G ∈ 𝒢) {f : G ⟶ X}, Q.factors f → P.factors f) : P = Q :=
begin
  suffices : is_iso (of_le _ _ h₁),
  { exactI le_antisymm h₁ (le_of_comm (inv (of_le _ _ h₁)) (by simp)) },
  refine h𝒢 _ (λ G hG f, _),
  have : P.factors (f ≫ Q.arrow) := h₂ _ hG ((factors_iff _ _).2 ⟨_, rfl⟩),
  refine ⟨factor_thru _ _ this, _, λ g (hg : g ≫ _ = f), _⟩,
  { simp only [← cancel_mono Q.arrow, category.assoc, of_le_arrow, factor_thru_arrow] },
  { simp only [← cancel_mono (subobject.of_le _ _ h₁), ← cancel_mono Q.arrow, hg,
      category.assoc, of_le_arrow, factor_thru_arrow] }
end

lemma inf_eq_of_is_detecting [has_pullbacks C] {𝒢 : set C} (h𝒢 : is_detecting 𝒢) {X : C}
  (P Q : subobject X) (h : ∀ (G ∈ 𝒢) {f : G ⟶ X}, P.factors f → Q.factors f) : P ⊓ Q = P :=
eq_of_le_of_is_detecting h𝒢 _ _ _root_.inf_le_left (λ G hG f hf, (inf_factors _).2 ⟨hf, h _ hG hf⟩)

lemma eq_of_is_detecting [has_pullbacks C] {𝒢 : set C} (h𝒢 : is_detecting 𝒢) {X : C}
  (P Q : subobject X) (h : ∀ (G ∈ 𝒢) {f : G ⟶ X}, P.factors f ↔ Q.factors f) : P = Q :=
calc P = P ⊓ Q : eq.symm $ inf_eq_of_is_detecting h𝒢 _ _ $ λ G hG f hf, (h G hG).1 hf
   ... = Q ⊓ P : inf_comm
   ... = Q     : inf_eq_of_is_detecting h𝒢 _ _ $ λ G hG f hf, (h G hG).2 hf

end subobject

/-- A category with pullbacks and a small detecting set is well-powered. -/
lemma well_powered_of_is_detecting [has_pullbacks C] {𝒢 : set C} [small.{v₁} 𝒢]
  (h𝒢 : is_detecting 𝒢) : well_powered C :=
⟨λ X, @small_of_injective _ _ _ (λ P : subobject X, { f : Σ G : 𝒢, G.1 ⟶ X | P.factors f.2 }) $
  λ P Q h, subobject.eq_of_is_detecting h𝒢 _ _ (by simpa [set.ext_iff] using h)⟩

end well_powered

namespace structured_arrow
variables (S : D) (T : C ⥤ D)

lemma is_coseparating_proj_preimage {𝒢 : set C} (h𝒢 : is_coseparating 𝒢) :
  is_coseparating ((proj S T).obj ⁻¹' 𝒢) :=
begin
  refine λ X Y f g hfg, ext _ _ (h𝒢 _ _ (λ G hG h, _)),
  exact congr_arg comma_morphism.right (hfg (mk (Y.hom ≫ T.map h)) hG (hom_mk h rfl))
end

end structured_arrow

namespace costructured_arrow
variables (S : C ⥤ D) (T : D)

lemma is_separating_proj_preimage {𝒢 : set C} (h𝒢 : is_separating 𝒢) :
  is_separating ((proj S T).obj ⁻¹' 𝒢) :=
begin
  refine λ X Y f g hfg, ext _ _ (h𝒢 _ _ (λ G hG h, _)),
  convert congr_arg comma_morphism.left (hfg (mk (S.map h ≫ X.hom)) hG (hom_mk h rfl))
end

end costructured_arrow

/-- We say that `G` is a separator if the functor `C(G, -)` is faithful. -/
def is_separator (G : C) : Prop :=
is_separating ({G} : set C)

/-- We say that `G` is a coseparator if the functor `C(-, G)` is faithful. -/
def is_coseparator (G : C) : Prop :=
is_coseparating ({G} : set C)

/-- We say that `G` is a detector if the functor `C(G, -)` reflects isomorphisms. -/
def is_detector (G : C) : Prop :=
is_detecting ({G} : set C)

/-- We say that `G` is a codetector if the functor `C(-, G)` reflects isomorphisms. -/
def is_codetector (G : C) : Prop :=
is_codetecting ({G} : set C)

section dual

lemma is_separator_op_iff (G : C) : is_separator (op G) ↔ is_coseparator G :=
by rw [is_separator, is_coseparator, ← is_separating_op_iff, set.singleton_op]

lemma is_coseparator_op_iff (G : C) : is_coseparator (op G) ↔ is_separator G :=
by rw [is_separator, is_coseparator, ← is_coseparating_op_iff, set.singleton_op]

lemma is_coseparator_unop_iff (G : Cᵒᵖ) : is_coseparator (unop G) ↔ is_separator G :=
by rw [is_separator, is_coseparator, ← is_coseparating_unop_iff, set.singleton_unop]

lemma is_separator_unop_iff (G : Cᵒᵖ) : is_separator (unop G) ↔ is_coseparator G :=
by rw [is_separator, is_coseparator, ← is_separating_unop_iff, set.singleton_unop]

lemma is_detector_op_iff (G : C) : is_detector (op G) ↔ is_codetector G :=
by rw [is_detector, is_codetector, ← is_detecting_op_iff, set.singleton_op]

lemma is_codetector_op_iff (G : C) : is_codetector (op G) ↔ is_detector G :=
by rw [is_detector, is_codetector, ← is_codetecting_op_iff, set.singleton_op]

lemma is_codetector_unop_iff (G : Cᵒᵖ) : is_codetector (unop G) ↔ is_detector G :=
by rw [is_detector, is_codetector, ← is_codetecting_unop_iff, set.singleton_unop]

lemma is_detector_unop_iff (G : Cᵒᵖ) : is_detector (unop G) ↔ is_codetector G :=
by rw [is_detector, is_codetector, ← is_detecting_unop_iff, set.singleton_unop]

end dual

lemma is_detector.is_separator [has_equalizers C] {G : C} : is_detector G → is_separator G :=
is_detecting.is_separating

lemma is_codetector.is_coseparator [has_coequalizers C] {G : C} :
  is_codetector G → is_coseparator G :=
is_codetecting.is_coseparating

lemma is_separator.is_detector [balanced C] {G : C} : is_separator G → is_detector G :=
is_separating.is_detecting

lemma is_cospearator.is_codetector [balanced C] {G : C} : is_coseparator G → is_codetector G :=
is_coseparating.is_codetecting

lemma is_separator_def (G : C) :
  is_separator G ↔ ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ h : G ⟶ X, h ≫ f = h ≫ g) → f = g :=
⟨λ hG X Y f g hfg, hG _ _ $ λ H hH h, by { obtain rfl := set.mem_singleton_iff.1 hH, exact hfg h },
 λ hG X Y f g hfg, hG _ _ $ λ h, hfg _ (set.mem_singleton _) _⟩

lemma is_separator.def {G : C} :
  is_separator G → ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ h : G ⟶ X, h ≫ f = h ≫ g) → f = g :=
(is_separator_def _).1

lemma is_coseparator_def (G : C) :
  is_coseparator G ↔ ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ h : Y ⟶ G, f ≫ h = g ≫ h) → f = g :=
⟨λ hG X Y f g hfg, hG _ _ $ λ H hH h, by { obtain rfl := set.mem_singleton_iff.1 hH, exact hfg h },
 λ hG X Y f g hfg, hG _ _ $ λ h, hfg _ (set.mem_singleton _) _⟩

lemma is_coseparator.def {G : C} :
  is_coseparator G → ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ h : Y ⟶ G, f ≫ h = g ≫ h) → f = g :=
(is_coseparator_def _).1

lemma is_detector_def (G : C) :
  is_detector G ↔ ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ h : G ⟶ Y, ∃! h', h' ≫ f = h) → is_iso f :=
⟨λ hG X Y f hf, hG _ $ λ H hH h, by { obtain rfl := set.mem_singleton_iff.1 hH, exact hf h },
 λ hG X Y f hf, hG _ $ λ h, hf _ (set.mem_singleton _) _⟩

lemma is_detector.def {G : C} :
  is_detector G → ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ h : G ⟶ Y, ∃! h', h' ≫ f = h) → is_iso f :=
(is_detector_def _).1

lemma is_codetector_def (G : C) :
  is_codetector G ↔ ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ h : X ⟶ G, ∃! h', f ≫ h' = h) → is_iso f :=
⟨λ hG X Y f hf, hG _ $ λ H hH h, by { obtain rfl := set.mem_singleton_iff.1 hH, exact hf h },
 λ hG X Y f hf, hG _ $ λ h, hf _ (set.mem_singleton _) _⟩

lemma is_codetector.def {G : C} :
  is_codetector G → ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ h : X ⟶ G, ∃! h', f ≫ h' = h) → is_iso f :=
(is_codetector_def _).1

lemma is_separator_iff_faithful_coyoneda_obj (G : C) :
  is_separator G ↔ faithful (coyoneda.obj (op G)) :=
⟨λ hG, ⟨λ X Y f g hfg, hG.def _ _ (congr_fun hfg)⟩,
 λ h, (is_separator_def _).2 $ λ X Y f g hfg,
  by exactI (coyoneda.obj (op G)).map_injective (funext hfg)⟩

lemma is_coseparator_iff_faithful_yoneda_obj (G : C) :
  is_coseparator G ↔ faithful (yoneda.obj G) :=
⟨λ hG, ⟨λ X Y f g hfg, quiver.hom.unop_inj (hG.def _ _ (congr_fun hfg))⟩,
 λ h, (is_coseparator_def _).2 $ λ X Y f g hfg, quiver.hom.op_inj $
  by exactI (yoneda.obj G).map_injective (funext hfg)⟩

lemma is_separator_iff_epi (G : C) [Π A : C, has_coproduct (λ (f : G ⟶ A), G)] :
  is_separator G ↔ ∀ (A : C), epi (sigma.desc (λ (f : G ⟶ A), f)) :=
begin
  rw is_separator_def,
  refine ⟨λ h A, ⟨λ Z u v huv, h _ _ (λ i, _)⟩, λ h X Y f g hh, _⟩,
  { simpa using (sigma.ι _ i) ≫= huv },
  { haveI := h X,
    refine (cancel_epi (sigma.desc (λ (f : G ⟶ X), f))).1 (colimit.hom_ext (λ j, _)),
    simpa using hh j.as }
end

lemma is_coseparator_iff_mono (G : C) [Π A : C, has_product (λ (f : A ⟶ G), G)] :
  is_coseparator G ↔ ∀ (A : C), mono (pi.lift (λ (f : A ⟶ G), f)) :=
begin
  rw is_coseparator_def,
  refine ⟨λ h A, ⟨λ Z u v huv, h _ _ (λ i, _)⟩, λ h X Y f g hh, _⟩,
  { simpa using huv =≫ (pi.π _ i) },
  { haveI := h Y,
    refine (cancel_mono (pi.lift (λ (f : Y ⟶ G), f))).1 (limit.hom_ext (λ j, _)),
    simpa using hh j.as }
end

section zero_morphisms
variables [has_zero_morphisms C]

lemma is_separator_coprod (G H : C) [has_binary_coproduct G H] :
  is_separator (G ⨿ H) ↔ is_separating ({G, H} : set C) :=
begin
  refine ⟨λ h X Y u v huv, _, λ h, (is_separator_def _).2 (λ X Y u v huv, h _ _ (λ Z hZ g, _))⟩,
  { refine h.def _ _ (λ g, coprod.hom_ext _ _),
    { simpa using huv G (by simp) (coprod.inl ≫ g) },
    { simpa using huv H (by simp) (coprod.inr ≫ g) } },
  { simp only [set.mem_insert_iff, set.mem_singleton_iff] at hZ,
    unfreezingI { rcases hZ with rfl|rfl },
    { simpa using coprod.inl ≫= huv (coprod.desc g 0) },
    { simpa using coprod.inr ≫= huv (coprod.desc 0 g) } }
end

lemma is_separator_coprod_of_is_separator_left (G H : C) [has_binary_coproduct G H]
  (hG : is_separator G) : is_separator (G ⨿ H) :=
(is_separator_coprod _ _).2 $ is_separating.mono hG $ by simp

lemma is_separator_coprod_of_is_separator_right (G H : C) [has_binary_coproduct G H]
  (hH : is_separator H) : is_separator (G ⨿ H) :=
(is_separator_coprod _ _).2 $ is_separating.mono hH $ by simp

lemma is_separator_sigma {β : Type w} (f : β → C) [has_coproduct f] :
  is_separator (∐ f) ↔ is_separating (set.range f) :=
begin
  refine ⟨λ h X Y u v huv, _, λ h, (is_separator_def _).2 (λ X Y u v huv, h _ _ (λ Z hZ g, _))⟩,
  { refine h.def _ _ (λ g, colimit.hom_ext (λ b, _)),
    simpa using huv (f b.as) (by simp) (colimit.ι (discrete.functor f) _ ≫ g) },
  { obtain ⟨b, rfl⟩ := set.mem_range.1 hZ,
    classical,
    simpa using sigma.ι f b ≫= huv (sigma.desc (pi.single b g)) }
end

lemma is_separator_sigma_of_is_separator {β : Type w} (f : β → C) [has_coproduct f]
  (b : β) (hb : is_separator (f b)) : is_separator (∐ f) :=
(is_separator_sigma _).2 $ is_separating.mono hb $ by simp

lemma is_coseparator_prod (G H : C) [has_binary_product G H] :
  is_coseparator (G ⨯ H) ↔ is_coseparating ({G, H} : set C) :=
begin
  refine ⟨λ h X Y u v huv, _, λ h, (is_coseparator_def _).2 (λ X Y u v huv, h _ _ (λ Z hZ g, _))⟩,
  { refine h.def _ _ (λ g, prod.hom_ext _ _),
    { simpa using huv G (by simp) (g ≫ limits.prod.fst) },
    { simpa using huv H (by simp) (g ≫ limits.prod.snd) } },
  { simp only [set.mem_insert_iff, set.mem_singleton_iff] at hZ,
    unfreezingI { rcases hZ with rfl|rfl },
    { simpa using huv (prod.lift g 0) =≫ limits.prod.fst },
    { simpa using huv (prod.lift 0 g) =≫ limits.prod.snd } }
end

lemma is_coseparator_prod_of_is_coseparator_left (G H : C) [has_binary_product G H]
  (hG : is_coseparator G) : is_coseparator (G ⨯ H) :=
(is_coseparator_prod _ _).2 $ is_coseparating.mono hG $ by simp

lemma is_coseparator_prod_of_is_coseparator_right (G H : C) [has_binary_product G H]
  (hH : is_coseparator H) : is_coseparator (G ⨯ H) :=
(is_coseparator_prod _ _).2 $ is_coseparating.mono hH $ by simp

lemma is_coseparator_pi {β : Type w} (f : β → C) [has_product f] :
  is_coseparator (∏ f) ↔ is_coseparating (set.range f) :=
begin
  refine ⟨λ h X Y u v huv, _, λ h, (is_coseparator_def _).2 (λ X Y u v huv, h _ _ (λ Z hZ g, _))⟩,
  { refine h.def _ _ (λ g, limit.hom_ext (λ b, _)),
    simpa using huv (f b.as) (by simp) (g ≫ limit.π (discrete.functor f) _ ) },
  { obtain ⟨b, rfl⟩ := set.mem_range.1 hZ,
    classical,
    simpa using huv (pi.lift (pi.single b g)) =≫ pi.π f b }
end

lemma is_coseparator_pi_of_is_coseparator {β : Type w} (f : β → C) [has_product f]
  (b : β) (hb : is_coseparator (f b)) : is_coseparator (∏ f) :=
(is_coseparator_pi _).2 $ is_coseparating.mono hb $ by simp

end zero_morphisms

lemma is_detector_iff_reflects_isomorphisms_coyoneda_obj (G : C) :
  is_detector G ↔ reflects_isomorphisms (coyoneda.obj (op G)) :=
begin
  refine ⟨λ hG, ⟨λ X Y f hf, hG.def _ (λ h, _)⟩, λ h, (is_detector_def _).2 (λ X Y f hf, _)⟩,
  { rw [is_iso_iff_bijective, function.bijective_iff_exists_unique] at hf,
    exact hf h },
  { suffices : is_iso ((coyoneda.obj (op G)).map f),
    { exactI @is_iso_of_reflects_iso _ _ _ _ _ _ _ (coyoneda.obj (op G)) _ h },
    rwa [is_iso_iff_bijective, function.bijective_iff_exists_unique] }
end

lemma is_codetector_iff_reflects_isomorphisms_yoneda_obj (G : C) :
  is_codetector G ↔ reflects_isomorphisms (yoneda.obj G) :=
begin
  refine ⟨λ hG, ⟨λ X Y f hf, _ ⟩, λ h, (is_codetector_def _).2 (λ X Y f hf, _)⟩,
  { refine (is_iso_unop_iff _).1 (hG.def _ _),
    rwa [is_iso_iff_bijective, function.bijective_iff_exists_unique] at hf },
  { rw ← is_iso_op_iff,
    suffices : is_iso ((yoneda.obj G).map f.op),
    { exactI @is_iso_of_reflects_iso _ _ _ _ _ _ _ (yoneda.obj G) _ h },
    rwa [is_iso_iff_bijective, function.bijective_iff_exists_unique] }
end

lemma well_powered_of_is_detector [has_pullbacks C] (G : C) (hG : is_detector G) :
  well_powered C :=
well_powered_of_is_detecting hG

end category_theory