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

import Mathlib.Init.Function
import Mathlib.Logic.Function.Basic
import Mathlib.Tactic.Basic
import Mathlib.Tactic.Coe
import Mathlib.Tactic.Ext

open Function

namespace Subtype
variable {α : Sort _} {β : Sort _} {γ : Sort _} {p : α → Prop} {q : α → Prop}

def simps.coe (x: Subtype p) : α := x

/-- A version of `x.property` or `x.2` where `p` is syntactically applied to the coercion of `x`
  instead of `x.1`. A similar result is `Subtype.mem` in `data.set.basic`. -/
lemma prop (x : Subtype p) : p x := x.2

@[simp] protected theorem «forall» {q : {a // p a} → Prop} :
  (∀ x, q x) ↔ (∀ a b, q ⟨a, b⟩) :=
⟨λ h a b => h ⟨a, b⟩, λ h ⟨a, b⟩ => h a b⟩

/-- An alternative version of `Subtype.forall`. This one is useful if Lean cannot figure out `q`
  when using `Subtype.forall` from right to left. -/
protected theorem forall' {q : ∀x, p x → Prop} :
  (∀ x h, q x h) ↔ (∀ x : {a // p a}, q x x.2) :=
(@Subtype.forall _ _ (λ x => q x.1 x.2)).symm

@[simp] protected theorem «exists» {q : {a // p a} → Prop} :
  (∃ x, q x) ↔ (∃ a b, q ⟨a, b⟩) :=
⟨λ ⟨⟨a, b⟩, h⟩ => ⟨a, b, h⟩, λ ⟨a, b, h⟩ => ⟨⟨a, b⟩, h⟩⟩

protected lemma ext : ∀ {a1 a2 : {x // p x}}, (a1 : α) = (a2 : α) → a1 = a2
| ⟨x, h1⟩, ⟨y, h2⟩, hz => by simp [hz]

lemma ext_iff {a1 a2 : {x // p x}} : a1 = a2 ↔ (a1 : α) = (a2 : α) :=
⟨congrArg _, Subtype.ext⟩

lemma heq_iff_coe_eq (h : ∀ x, p x ↔ q x) {a1 : {x // p x}} {a2 : {x // q x}} :
  HEq a1 a2 ↔ (a1 : α) = (a2 : α) :=
Eq.rec (motive := λ (pp: (α → Prop)) _ => ∀ a2' : {x // pp x}, HEq a1 a2' ↔ (a1 : α) = (a2' : α))
       (λ a2' => heq_iff_eq.trans ext_iff) (funext $ λ x => propext (h x)) a2

lemma heq_iff_coe_heq {α β : Sort _} {p : α → Prop} {q : β → Prop} {a : {x // p x}}
  {b : {y // q y}} (h : α = β) (h' : HEq p q) :
  HEq a b ↔ HEq (a : α) (b : β) :=
by subst h
   have hpq : p = q := heq_iff_eq.1 h'
   subst hpq
   rw [heq_iff_eq, heq_iff_eq, ext_iff]

lemma ext_val {a1 a2 : {x // p x}} : a1.1 = a2.1 → a1 = a2 :=
Subtype.ext

lemma ext_iff_val {a1 a2 : {x // p x}} : a1 = a2 ↔ a1.1 = a2.1 :=
ext_iff

@[simp] theorem coe_eta (a : {a // p a}) (h : p (a : α)) : mk (a : α) h = a := Subtype.ext rfl

theorem coe_mk (a h) : (@mk α p a h : α) = a := rfl

theorem mk_eq_mk {a h a' h'} : @mk α p a h = @mk α p a' h' ↔ a = a' :=
ext_iff

theorem coe_eq_iff {a : {a // p a}} {b : α} : ↑a = b ↔ ∃ h, a = ⟨b, h⟩ :=
⟨λ h => h ▸ ⟨a.2, (coe_eta _ _).symm⟩, λ ⟨hb, ha⟩ => ha.symm ▸ rfl⟩

theorem coe_injective : injective ((↑·) : Subtype p → α) :=
by intros a b hab
   exact Subtype.ext hab

theorem val_injective : injective (@val _ p) :=
coe_injective

/-- Restrict a (dependent) function to a subtype -/
def restrict {α} {β : α → Type _} (f : ∀x, β x) (p : α → Prop) (x : Subtype p) : β x.1 :=
f x

lemma restrict_apply {α} {β : α → Type _} (f : ∀x, β x) (p : α → Prop) (x : Subtype p) :
  restrict f p x = f x.1 := rfl

lemma restrict_def {α β} (f : α → β) (p : α → Prop) : restrict f p = f ∘ (↑) := rfl

lemma restrict_injective {α β} {f : α → β} (p : α → Prop) (h : injective f) :
  injective (restrict f p) :=
h.comp coe_injective

/-- Defining a map into a subtype, this can be seen as an "coinduction principle" of `Subtype`-/
def coind {α β} (f : α → β) {p : β → Prop} (h : ∀a, p (f a)) : α → Subtype p :=
λ a => ⟨f a, h a⟩

theorem coind_injective {α β} {f : α → β} {p : β → Prop} (h : ∀a, p (f a))
  (hf : injective f) : injective (coind f h) :=
by intros x y hxy
   refine hf ?_
   apply congrArg Subtype.val hxy

theorem coind_surjective {α β} {f : α → β} {p : β → Prop} (h : ∀a, p (f a))
  (hf : surjective f) : surjective (coind f h) :=
λ x => let ⟨a, ha⟩ := hf x
       ⟨a, coe_injective ha⟩

theorem coind_bijective {α β} {f : α → β} {p : β → Prop} (h : ∀a, p (f a))
  (hf : bijective f) : bijective (coind f h) :=
⟨coind_injective h hf.1, coind_surjective h hf.2⟩

/-- Restriction of a function to a function on subtypes. -/
def map {p : α → Prop} {q : β → Prop} (f : α → β) (h : ∀a, p a → q (f a)) :
  Subtype p → Subtype q :=
λ x => ⟨f x, h x x.prop⟩

theorem map_comp {p : α → Prop} {q : β → Prop} {r : γ → Prop} {x : Subtype p}
  (f : α → β) (h : ∀a, p a → q (f a)) (g : β → γ) (l : ∀a, q a → r (g a)) :
  map g l (map f h x) = map (g ∘ f) (λ a ha => l (f a) $ h a ha) x :=
rfl

theorem map_id {p : α → Prop} {h : ∀a, p a → p (id a)} : map (@id α) h = id :=
funext $ λ ⟨v, h⟩ => rfl

lemma map_injective {p : α → Prop} {q : β → Prop} {f : α → β} (h : ∀a, p a → q (f a))
  (hf : injective f) : injective (map f h) :=
coind_injective _ $ hf.comp coe_injective

lemma map_involutive {p : α → Prop} {f : α → α} (h : ∀a, p a → p (f a))
  (hf : involutive f) : involutive (map f h) :=
λ x => Subtype.ext (hf x)

instance [HasEquiv α] (p : α → Prop) : HasEquiv (Subtype p) :=
⟨λ s t => (s : α) ≈ (t : α)⟩

/-
  Port note: we need explicit universes here because in Lean 3 we have

    has_equiv.equiv : Π {α : Sort u} [c : has_equiv α], α → α → Prop

  but in Lean 4 `HasEquiv.Equiv` is additionally parameteric over the universe of
  the result. I.e

    HasEquiv.Equiv.{u, v} : {α : Sort u} → [self : HasEquiv α] → α → α → Sort v
-/
theorem equiv_iff [HasEquiv.{u_1, 0} α] {p : α → Prop} {s t : Subtype p} :
  HasEquiv.Equiv.{max 1 u_1, 0} s t ↔ HasEquiv.Equiv.{u_1, 0} (s : α) (t : α) :=
Iff.rfl

variable [Setoid α]

protected theorem refl (s : Subtype p) : s ≈ s :=
Setoid.refl (s : α)

protected theorem symm {s t : Subtype p} (h : s ≈ t) : t ≈ s :=
Setoid.symm h

protected theorem trans {s t u : Subtype p} (h₁ : s ≈ t) (h₂ : t ≈ u) : s ≈ u :=
Setoid.trans h₁ h₂

theorem equivalence (p : α → Prop) : Equivalence (@HasEquiv.Equiv (Subtype p) _) :=
⟨Subtype.refl, @Subtype.symm _ p _, @Subtype.trans _ p _⟩

instance (p : α → Prop) : Setoid (Subtype p) :=
Setoid.mk (·≈·) (equivalence p)

end Subtype

namespace Subtype
/-! Some facts about sets, which require that `α` is a type. -/
variable {α : Type _} {β : Type _} {γ : Type _} {p : α → Prop}

-- ∈-notation is reducible in Lean 4, so this won't trigger as a simp-lemma
lemma val_prop {S : Set α} (a : {a // a ∈ S}) : a.val ∈ S := a.property

end Subtype


/-
Copyright (c) 2017 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Stephen Morgan, Scott Morrison, Floris van Doorn
-/
import category_theory.eq_to_hom
import data.ulift

/-!
# Discrete categories

We define `discrete α` as a structure containing a term `a : α` for any type `α`,
and use this type alias to provide a `small_category` instance
whose only morphisms are the identities.

There is an annoying technical difficulty that it has turned out to be inconvenient
to allow categories with morphisms living in `Prop`,
so instead of defining `X ⟶ Y` in `discrete α` as `X = Y`,
one might define it as `plift (X = Y)`.
In fact, to allow `discrete α` to be a `small_category`
(i.e. with morphisms in the same universe as the objects),
we actually define the hom type `X ⟶ Y` as `ulift (plift (X = Y))`.

`discrete.functor` promotes a function `f : I → C` (for any category `C`) to a functor
`discrete.functor f : discrete I ⥤ C`.

Similarly, `discrete.nat_trans` and `discrete.nat_iso` promote `I`-indexed families of morphisms,
or `I`-indexed families of isomorphisms to natural transformations or natural isomorphism.

We show equivalences of types are the same as (categorical) equivalences of the corresponding
discrete categories.
-/

namespace category_theory

-- morphism levels before object levels. See note [category_theory universes].
universes v₁ v₂ v₃ u₁ u₁' u₂ u₃

/--
A wrapper for promoting any type to a category,
with the only morphisms being equalities.
-/
-- This is intentionally a structure rather than a type synonym
-- to enforce using `discrete_equiv` (or `discrete.mk` and `discrete.as`) to move between
-- `discrete α` and `α`. Otherwise there is too much API leakage.
@[ext] structure discrete (α : Type u₁) :=
(as : α)

@[simp] lemma discrete.mk_as {α : Type u₁} (X : discrete α) : discrete.mk X.as = X :=
by { ext, refl, }

/-- `discrete α` is equivalent to the original type `α`.-/
@[simps] def discrete_equiv {α : Type u₁} : discrete α ≃ α :=
{ to_fun := discrete.as,
  inv_fun := discrete.mk,
  left_inv := by tidy,
  right_inv := by tidy, }

instance {α : Type u₁} [decidable_eq α] : decidable_eq (discrete α) :=
discrete_equiv.decidable_eq

/--
The "discrete" category on a type, whose morphisms are equalities.

Because we do not allow morphisms in `Prop` (only in `Type`),
somewhat annoyingly we have to define `X ⟶ Y` as `ulift (plift (X = Y))`.

See <https://stacks.math.columbia.edu/tag/001A>
-/
instance discrete_category (α : Type u₁) : small_category (discrete α) :=
{ hom  := λ X Y, ulift (plift (X.as = Y.as)),
  id   := λ X, ulift.up (plift.up rfl),
  comp := λ X Y Z g f, by { cases X, cases Y, cases Z, rcases f with ⟨⟨⟨⟩⟩⟩, exact g } }

namespace discrete

variables {α : Type u₁}

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

instance [subsingleton α] : subsingleton (discrete α) :=
⟨by { intros, ext, apply subsingleton.elim, }⟩

/-- A simple tactic to run `cases` on any `discrete α` hypotheses. -/
meta def _root_.tactic.discrete_cases : tactic unit :=
`[cases_matching* [discrete _, (_ : discrete _) ⟶ (_ : discrete _), plift _]]

run_cmd add_interactive [``tactic.discrete_cases]

local attribute [tidy] tactic.discrete_cases

instance [unique α] : unique (discrete α) :=
unique.mk' (discrete α)

/-- Extract the equation from a morphism in a discrete category. -/
lemma eq_of_hom {X Y : discrete α} (i : X ⟶ Y) : X.as = Y.as := i.down.down

/-- Promote an equation between the wrapped terms in `X Y : discrete α` to a morphism `X ⟶ Y`
in the discrete category. -/
abbreviation eq_to_hom {X Y : discrete α} (h : X.as = Y.as) : X ⟶ Y :=
eq_to_hom (by { ext, exact h, })

/-- Promote an equation between the wrapped terms in `X Y : discrete α` to an isomorphism `X ≅ Y`
in the discrete category. -/
abbreviation eq_to_iso {X Y : discrete α} (h : X.as = Y.as) : X ≅ Y :=
eq_to_iso (by { ext, exact h, })

/-- A variant of `eq_to_hom` that lifts terms to the discrete category. -/
abbreviation eq_to_hom' {a b : α} (h : a = b) : discrete.mk a ⟶ discrete.mk b :=
eq_to_hom h

/-- A variant of `eq_to_iso` that lifts terms to the discrete category. -/
abbreviation eq_to_iso' {a b : α} (h : a = b) : discrete.mk a ≅ discrete.mk b :=
eq_to_iso h

@[simp] lemma id_def (X : discrete α) : ulift.up (plift.up (eq.refl X.as)) = 𝟙 X := rfl

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

instance {I : Type u₁} {i j : discrete I} (f : i ⟶ j) : is_iso f :=
⟨⟨eq_to_hom (eq_of_hom f).symm, by tidy⟩⟩

/--
Any function `I → C` gives a functor `discrete I ⥤ C`.
-/
def functor {I : Type u₁} (F : I → C) : discrete I ⥤ C :=
{ obj := F ∘ discrete.as,
  map := λ X Y f, by { discrete_cases, cases f, exact 𝟙 (F X), } }

@[simp] lemma functor_obj  {I : Type u₁} (F : I → C) (i : I) :
  (discrete.functor F).obj (discrete.mk i) = F i := rfl

lemma functor_map  {I : Type u₁} (F : I → C) {i : discrete I} (f : i ⟶ i) :
  (discrete.functor F).map f = 𝟙 (F i.as) :=
by tidy

/--
The discrete functor induced by a composition of maps can be written as a
composition of two discrete functors.
-/
@[simps]
def functor_comp {I : Type u₁} {J : Type u₁'} (f : J → C) (g : I → J) :
  discrete.functor (f ∘ g) ≅ discrete.functor (discrete.mk ∘ g) ⋙ discrete.functor f :=
nat_iso.of_components (λ X, iso.refl _) (by tidy)

/--
For functors out of a discrete category,
a natural transformation is just a collection of maps,
as the naturality squares are trivial.
-/
@[simps]
def nat_trans {I : Type u₁} {F G : discrete I ⥤ C}
  (f : Π i : discrete I, F.obj i ⟶ G.obj i) : F ⟶ G :=
{ app := f,
  naturality' := λ X Y g, by { discrete_cases, cases g, simp, } }

/--
For functors out of a discrete category,
a natural isomorphism is just a collection of isomorphisms,
as the naturality squares are trivial.
-/
@[simps]
def nat_iso {I : Type u₁} {F G : discrete I ⥤ C}
  (f : Π i : discrete I, F.obj i ≅ G.obj i) : F ≅ G :=
nat_iso.of_components f (λ X Y g, by { discrete_cases, cases g, simp, })

@[simp]
lemma nat_iso_app {I : Type u₁} {F G : discrete I ⥤ C}
  (f : Π i : discrete I, F.obj i ≅ G.obj i) (i : discrete I) :
  (discrete.nat_iso f).app i = f i :=
by tidy

/-- Every functor `F` from a discrete category is naturally isomorphic (actually, equal) to
  `discrete.functor (F.obj)`. -/
@[simp]
def nat_iso_functor {I : Type u₁} {F : discrete I ⥤ C} :
  F ≅ discrete.functor (F.obj ∘ discrete.mk) :=
nat_iso $ λ i, by { discrete_cases, refl, }

/-- Composing `discrete.functor F` with another functor `G` amounts to composing `F` with `G.obj` -/
@[simp]
def comp_nat_iso_discrete {I : Type u₁} {D : Type u₃} [category.{v₃} D]
 (F : I → C) (G : C ⥤ D) : discrete.functor F ⋙ G ≅ discrete.functor (G.obj ∘ F) :=
nat_iso $ λ i, iso.refl _

/--
We can promote a type-level `equiv` to
an equivalence between the corresponding `discrete` categories.
-/
@[simps]
def equivalence {I : Type u₁} {J : Type u₂} (e : I ≃ J) : discrete I ≌ discrete J :=
{ functor := discrete.functor (discrete.mk ∘ (e : I → J)),
  inverse := discrete.functor (discrete.mk ∘ (e.symm : J → I)),
  unit_iso := discrete.nat_iso (λ i, eq_to_iso (by { discrete_cases, simp })),
  counit_iso := discrete.nat_iso (λ j, eq_to_iso (by { discrete_cases, simp })), }

/-- We can convert an equivalence of `discrete` categories to a type-level `equiv`. -/
@[simps]
def equiv_of_equivalence {α : Type u₁} {β : Type u₂} (h : discrete α ≌ discrete β) : α ≃ β :=
{ to_fun := discrete.as ∘ h.functor.obj ∘ discrete.mk,
  inv_fun := discrete.as ∘ h.inverse.obj ∘ discrete.mk,
  left_inv := λ a, by simpa using eq_of_hom (h.unit_iso.app (discrete.mk a)).2,
  right_inv := λ a, by simpa using eq_of_hom (h.counit_iso.app (discrete.mk a)).1, }

end discrete

namespace discrete
variables {J : Type v₁}

open opposite

/-- A discrete category is equivalent to its opposite category. -/
@[simps functor_obj_as inverse_obj]
protected def opposite (α : Type u₁) : (discrete α)ᵒᵖ ≌ discrete α :=
let F : discrete α ⥤ (discrete α)ᵒᵖ := discrete.functor (λ x, op (discrete.mk x)) in
begin
  refine equivalence.mk (functor.left_op F) F _
    (discrete.nat_iso $ λ X, by { discrete_cases, simp [F] }),
  refine nat_iso.of_components (λ X, by { tactic.op_induction', discrete_cases, simp [F], }) _,
  tidy
end

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

@[simp] lemma functor_map_id
  (F : discrete J ⥤ C) {j : discrete J} (f : j ⟶ j) : F.map f = 𝟙 (F.obj j) :=
begin
  have h : f = 𝟙 j, { cases f, cases f, ext, },
  rw h,
  simp,
end

end discrete

end category_theory


/-
Copyright (c) 2018 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Mario Carneiro, Reid Barton, Andrew Yang

! This file was ported from Lean 3 source module topology.sheaves.presheaf
! leanprover-community/mathlib commit 8a318021995877a44630c898d0b2bc376fceef3b
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.CategoryTheory.Limits.KanExtension
import Mathbin.Topology.Category.Top.Opens
import Mathbin.CategoryTheory.Adjunction.Opposites

/-!
# Presheaves on a topological space

We define `presheaf C X` simply as `(opens X)ᵒᵖ ⥤ C`,
and inherit the category structure with natural transformations as morphisms.

We define
* `pushforward_obj {X Y : Top.{w}} (f : X ⟶ Y) (ℱ : X.presheaf C) : Y.presheaf C`
with notation `f _* ℱ`
and for `ℱ : X.presheaf C` provide the natural isomorphisms
* `pushforward.id : (𝟙 X) _* ℱ ≅ ℱ`
* `pushforward.comp : (f ≫ g) _* ℱ ≅ g _* (f _* ℱ)`
along with their `@[simp]` lemmas.

We also define the functors `pushforward` and `pullback` between the categories
`X.presheaf C` and `Y.presheaf C`, and provide their adjunction at
`pushforward_pullback_adjunction`.
-/

universe w v u

open CategoryTheory

open TopologicalSpace

open Opposite

variable (C : Type u) [Category.{v} C]

namespace TopCat

/-- The category of `C`-valued presheaves on a (bundled) topological space `X`. -/
@[nolint has_nonempty_instance]
def Presheaf (X : TopCat.{w}) : Type max u v w :=
  (Opens X)ᵒᵖ ⥤ C deriving Category
#align Top.presheaf TopCat.Presheaf

variable {C}

namespace Presheaf

attribute [local instance] concrete_category.has_coe_to_sort concrete_category.has_coe_to_fun

/-- Tag lemmas to use in `Top.presheaf.restrict_tac`.  -/
@[user_attribute]
unsafe def restrict_attr : user_attribute (tactic Unit → tactic Unit) Unit
    where
  Name := `sheaf_restrict
  descr := "tag lemmas to use in `Top.presheaf.restrict_tac`"
  cache_cfg :=
    { mk_cache := fun ns =>
        pure fun t => do
          let ctx ← tactic.local_context
          ctx (tactic.focus1 ∘ (tactic.apply' >=> fun _ => tactic.done) >=> fun _ => t) <|>
              ns
                (tactic.focus1 ∘
                    (tactic.resolve_name >=> tactic.to_expr >=> tactic.apply' >=> fun _ =>
                      tactic.done) >=>
                  fun _ => t)
      dependencies := [] }
#align Top.presheaf.restrict_attr Top.presheaf.restrict_attr

/- ./././Mathport/Syntax/Translate/Expr.lean:330:4: warning: unsupported (TODO): `[tacs] -/
/-- A tactic to discharge goals of type `U ≤ V` for `Top.presheaf.restrict_open` -/
unsafe def restrict_tac : ∀ n : ℕ, tactic Unit
  | 0 => tactic.fail "`restrict_tac` failed"
  | n + 1 => Monad.join (restrict_attr.get_cache <*> pure tactic.done) <|> sorry
#align Top.presheaf.restrict_tac Top.presheaf.restrict_tac

/-- A tactic to discharge goals of type `U ≤ V` for `Top.presheaf.restrict_open`.
Defaults to three iterations. -/
unsafe def restrict_tac' :=
  restrict_tac 3
#align Top.presheaf.restrict_tac' Top.presheaf.restrict_tac'

attribute [sheaf_restrict] bot_le le_top le_refl inf_le_left inf_le_right le_sup_left le_sup_right

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic Top.presheaf.restrict_tac' -/
example {X : TopCat} {v w x y z : Opens X} (h₀ : v ≤ x) (h₁ : x ≤ z ⊓ w) (h₂ : x ≤ y ⊓ z) : v ≤ y :=
  by
  run_tac
    restrict_tac'

/-- The restriction of a section along an inclusion of open sets.
For `x : F.obj (op V)`, we provide the notation `x |_ₕ i` (`h` stands for `hom`) for `i : U ⟶ V`,
and the notation `x |_ₗ U ⟪i⟫` (`l` stands for `le`) for `i : U ≤ V`.
-/
def restrict {X : TopCat} {C : Type _} [Category C] [ConcreteCategory C] {F : X.Presheaf C}
    {V : Opens X} (x : F.obj (op V)) {U : Opens X} (h : U ⟶ V) : F.obj (op U) :=
  F.map h.op x
#align Top.presheaf.restrict TopCat.Presheaf.restrict

-- mathport name: «expr |_ₕ »
scoped[AlgebraicGeometry] infixl:80 " |_ₕ " => TopCat.Presheaf.restrict

-- mathport name: «expr |_ₗ ⟪ ⟫»
scoped[AlgebraicGeometry]
  notation:80 x " |_ₗ " U " ⟪" e "⟫ " =>
    @TopCat.Presheaf.restrict _ _ _ _ _ _ x U (@homOfLE (Opens _) _ U _ e)

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic Top.presheaf.restrict_tac' -/
/-- The restriction of a section along an inclusion of open sets.
For `x : F.obj (op V)`, we provide the notation `x |_ U`, where the proof `U ≤ V` is inferred by
the tactic `Top.presheaf.restrict_tac'` -/
abbrev restrictOpen {X : TopCat} {C : Type _} [Category C] [ConcreteCategory C] {F : X.Presheaf C}
    {V : Opens X} (x : F.obj (op V)) (U : Opens X)
    (e : U ≤ V := by
      run_tac
        Top.presheaf.restrict_tac') :
    F.obj (op U) :=
  x |_ₗ U ⟪e⟫
#align Top.presheaf.restrict_open TopCat.Presheaf.restrictOpen

-- mathport name: «expr |_ »
scoped[AlgebraicGeometry] infixl:80 " |_ " => TopCat.Presheaf.restrictOpen

@[simp]
theorem restrict_restrict {X : TopCat} {C : Type _} [Category C] [ConcreteCategory C]
    {F : X.Presheaf C} {U V W : Opens X} (e₁ : U ≤ V) (e₂ : V ≤ W) (x : F.obj (op W)) :
    x |_ V |_ U = x |_ U := by
  delta restrict_open restrict
  rw [← comp_apply, ← functor.map_comp]
  rfl
#align Top.presheaf.restrict_restrict TopCat.Presheaf.restrict_restrict

@[simp]
theorem map_restrict {X : TopCat} {C : Type _} [Category C] [ConcreteCategory C]
    {F G : X.Presheaf C} (e : F ⟶ G) {U V : Opens X} (h : U ≤ V) (x : F.obj (op V)) :
    e.app _ (x |_ U) = e.app _ x |_ U :=
  by
  delta restrict_open restrict
  rw [← comp_apply, nat_trans.naturality, comp_apply]
#align Top.presheaf.map_restrict TopCat.Presheaf.map_restrict

/-- Pushforward a presheaf on `X` along a continuous map `f : X ⟶ Y`, obtaining a presheaf
on `Y`. -/
def pushforwardObj {X Y : TopCat.{w}} (f : X ⟶ Y) (ℱ : X.Presheaf C) : Y.Presheaf C :=
  (Opens.map f).op ⋙ ℱ
#align Top.presheaf.pushforward_obj TopCat.Presheaf.pushforwardObj

-- mathport name: «expr _* »
infixl:80 " _* " => pushforwardObj

@[simp]
theorem pushforwardObj_obj {X Y : TopCat.{w}} (f : X ⟶ Y) (ℱ : X.Presheaf C) (U : (Opens Y)ᵒᵖ) :
    (f _* ℱ).obj U = ℱ.obj ((Opens.map f).op.obj U) :=
  rfl
#align Top.presheaf.pushforward_obj_obj TopCat.Presheaf.pushforwardObj_obj

@[simp]
theorem pushforwardObj_map {X Y : TopCat.{w}} (f : X ⟶ Y) (ℱ : X.Presheaf C) {U V : (Opens Y)ᵒᵖ}
    (i : U ⟶ V) : (f _* ℱ).map i = ℱ.map ((Opens.map f).op.map i) :=
  rfl
#align Top.presheaf.pushforward_obj_map TopCat.Presheaf.pushforwardObj_map

/--
An equality of continuous maps induces a natural isomorphism between the pushforwards of a presheaf
along those maps.
-/
def pushforwardEq {X Y : TopCat.{w}} {f g : X ⟶ Y} (h : f = g) (ℱ : X.Presheaf C) :
    f _* ℱ ≅ g _* ℱ :=
  isoWhiskerRight (NatIso.op (Opens.mapIso f g h).symm) ℱ
#align Top.presheaf.pushforward_eq TopCat.Presheaf.pushforwardEq

theorem pushforward_eq' {X Y : TopCat.{w}} {f g : X ⟶ Y} (h : f = g) (ℱ : X.Presheaf C) :
    f _* ℱ = g _* ℱ := by rw [h]
#align Top.presheaf.pushforward_eq' TopCat.Presheaf.pushforward_eq'

@[simp]
theorem pushforwardEq_hom_app {X Y : TopCat.{w}} {f g : X ⟶ Y} (h : f = g) (ℱ : X.Presheaf C) (U) :
    (pushforwardEq h ℱ).Hom.app U =
      ℱ.map (by dsimp [functor.op]; apply Quiver.Hom.op; apply eq_to_hom; rw [h]) :=
  by simp [pushforward_eq]
#align Top.presheaf.pushforward_eq_hom_app TopCat.Presheaf.pushforwardEq_hom_app

theorem pushforward_eq'_hom_app {X Y : TopCat.{w}} {f g : X ⟶ Y} (h : f = g) (ℱ : X.Presheaf C)
    (U) : NatTrans.app (eqToHom (pushforward_eq' h ℱ)) U = ℱ.map (eqToHom (by rw [h])) := by
  simpa [eq_to_hom_map]
#align Top.presheaf.pushforward_eq'_hom_app TopCat.Presheaf.pushforward_eq'_hom_app

@[simp]
theorem pushforwardEq_rfl {X Y : TopCat.{w}} (f : X ⟶ Y) (ℱ : X.Presheaf C) (U) :
    (pushforwardEq (rfl : f = f) ℱ).Hom.app (op U) = 𝟙 _ :=
  by
  dsimp [pushforward_eq]
  simp
#align Top.presheaf.pushforward_eq_rfl TopCat.Presheaf.pushforwardEq_rfl

theorem pushforwardEq_eq {X Y : TopCat.{w}} {f g : X ⟶ Y} (h₁ h₂ : f = g) (ℱ : X.Presheaf C) :
    ℱ.pushforwardEq h₁ = ℱ.pushforwardEq h₂ :=
  rfl
#align Top.presheaf.pushforward_eq_eq TopCat.Presheaf.pushforwardEq_eq

namespace Pushforward

variable {X : TopCat.{w}} (ℱ : X.Presheaf C)

/-- The natural isomorphism between the pushforward of a presheaf along the identity continuous map
and the original presheaf. -/
def id : 𝟙 X _* ℱ ≅ ℱ :=
  isoWhiskerRight (NatIso.op (Opens.mapId X).symm) ℱ ≪≫ Functor.leftUnitor _
#align Top.presheaf.pushforward.id TopCat.Presheaf.Pushforward.id

theorem id_eq : 𝟙 X _* ℱ = ℱ := by
  unfold pushforward_obj
  rw [opens.map_id_eq]
  erw [functor.id_comp]
#align Top.presheaf.pushforward.id_eq TopCat.Presheaf.Pushforward.id_eq

@[simp]
theorem id_hom_app' (U) (p) : (id ℱ).Hom.app (op ⟨U, p⟩) = ℱ.map (𝟙 (op ⟨U, p⟩)) :=
  by
  dsimp [id]
  simp
#align Top.presheaf.pushforward.id_hom_app' TopCat.Presheaf.Pushforward.id_hom_app'

attribute [local tidy] tactic.op_induction'

@[simp]
theorem id_hom_app (U) : (id ℱ).Hom.app U = ℱ.map (eqToHom (Opens.op_map_id_obj U)) :=
  by
  -- was `tidy`
  induction U using Opposite.rec
  cases U
  rw [id_hom_app']
  congr
#align Top.presheaf.pushforward.id_hom_app TopCat.Presheaf.Pushforward.id_hom_app

@[simp]
theorem id_inv_app' (U) (p) : (id ℱ).inv.app (op ⟨U, p⟩) = ℱ.map (𝟙 (op ⟨U, p⟩)) :=
  by
  dsimp [id]
  simp
#align Top.presheaf.pushforward.id_inv_app' TopCat.Presheaf.Pushforward.id_inv_app'

/-- The natural isomorphism between
the pushforward of a presheaf along the composition of two continuous maps and
the corresponding pushforward of a pushforward. -/
def comp {Y Z : TopCat.{w}} (f : X ⟶ Y) (g : Y ⟶ Z) : (f ≫ g) _* ℱ ≅ g _* (f _* ℱ) :=
  isoWhiskerRight (NatIso.op (Opens.mapComp f g).symm) ℱ
#align Top.presheaf.pushforward.comp TopCat.Presheaf.Pushforward.comp

theorem comp_eq {Y Z : TopCat.{w}} (f : X ⟶ Y) (g : Y ⟶ Z) : (f ≫ g) _* ℱ = g _* (f _* ℱ) :=
  rfl
#align Top.presheaf.pushforward.comp_eq TopCat.Presheaf.Pushforward.comp_eq

@[simp]
theorem comp_hom_app {Y Z : TopCat.{w}} (f : X ⟶ Y) (g : Y ⟶ Z) (U) :
    (comp ℱ f g).Hom.app U = 𝟙 _ := by
  dsimp [comp]
  tidy
#align Top.presheaf.pushforward.comp_hom_app TopCat.Presheaf.Pushforward.comp_hom_app

@[simp]
theorem comp_inv_app {Y Z : TopCat.{w}} (f : X ⟶ Y) (g : Y ⟶ Z) (U) :
    (comp ℱ f g).inv.app U = 𝟙 _ := by
  dsimp [comp]
  tidy
#align Top.presheaf.pushforward.comp_inv_app TopCat.Presheaf.Pushforward.comp_inv_app

end Pushforward

/-- A morphism of presheaves gives rise to a morphisms of the pushforwards of those presheaves.
-/
@[simps]
def pushforwardMap {X Y : TopCat.{w}} (f : X ⟶ Y) {ℱ 𝒢 : X.Presheaf C} (α : ℱ ⟶ 𝒢) : f _* ℱ ⟶ f _* 𝒢
    where
  app U := α.app _
  naturality' U V i := by
    erw [α.naturality]
    rfl
#align Top.presheaf.pushforward_map TopCat.Presheaf.pushforwardMap

open CategoryTheory.Limits

section Pullback

variable [HasColimits C]

noncomputable section

/-- Pullback a presheaf on `Y` along a continuous map `f : X ⟶ Y`, obtaining a presheaf on `X`.

This is defined in terms of left Kan extensions, which is just a fancy way of saying
"take the colimits over the open sets whose preimage contains U".
-/
@[simps]
def pullbackObj {X Y : TopCat.{v}} (f : X ⟶ Y) (ℱ : Y.Presheaf C) : X.Presheaf C :=
  (lan (Opens.map f).op).obj ℱ
#align Top.presheaf.pullback_obj TopCat.Presheaf.pullbackObj

/-- Pulling back along continuous maps is functorial. -/
def pullbackMap {X Y : TopCat.{v}} (f : X ⟶ Y) {ℱ 𝒢 : Y.Presheaf C} (α : ℱ ⟶ 𝒢) :
    pullbackObj f ℱ ⟶ pullbackObj f 𝒢 :=
  (lan (Opens.map f).op).map α
#align Top.presheaf.pullback_map TopCat.Presheaf.pullbackMap

/-- If `f '' U` is open, then `f⁻¹ℱ U ≅ ℱ (f '' U)`.  -/
@[simps]
def pullbackObjObjOfImageOpen {X Y : TopCat.{v}} (f : X ⟶ Y) (ℱ : Y.Presheaf C) (U : Opens X)
    (H : IsOpen (f '' U)) : (pullbackObj f ℱ).obj (op U) ≅ ℱ.obj (op ⟨_, H⟩) :=
  by
  let x : costructured_arrow (opens.map f).op (op U) :=
    by
    refine' @costructured_arrow.mk _ _ _ _ _ (op (opens.mk (f '' U) H)) _ _
    exact (@hom_of_le _ _ _ ((opens.map f).obj ⟨_, H⟩) (set.image_preimage.le_u_l _)).op
  have hx : is_terminal x :=
    {
      lift := fun s => by
        fapply costructured_arrow.hom_mk
        change op (unop _) ⟶ op (⟨_, H⟩ : opens _)
        refine' (hom_of_le _).op
        exact
          (Set.image_subset f s.X.hom.unop.le).trans (set.image_preimage.l_u_le ↑(unop s.X.left))
        simp }
  exact
    is_colimit.cocone_point_unique_up_to_iso (colimit.is_colimit _)
      (colimit_of_diagram_terminal hx _)
#align Top.presheaf.pullback_obj_obj_of_image_open TopCat.Presheaf.pullbackObjObjOfImageOpen

namespace Pullback

variable {X Y : TopCat.{v}} (ℱ : Y.Presheaf C)

/-- The pullback along the identity is isomorphic to the original presheaf. -/
def id : pullbackObj (𝟙 _) ℱ ≅ ℱ :=
  NatIso.ofComponents
    (fun U =>
      pullbackObjObjOfImageOpen (𝟙 _) ℱ (unop U) (by simpa using U.unop.2) ≪≫
        ℱ.mapIso (eqToIso (by simp)))
    fun U V i => by
    ext; simp
    erw [colimit.pre_desc_assoc]
    erw [colimit.ι_desc_assoc]
    erw [colimit.ι_desc_assoc]
    dsimp; simp only [← ℱ.map_comp]; congr
#align Top.presheaf.pullback.id TopCat.Presheaf.Pullback.id

theorem id_inv_app (U : Opens Y) :
    (id ℱ).inv.app (op U) =
      colimit.ι (Lan.diagram (Opens.map (𝟙 Y)).op ℱ (op U))
        (@CostructuredArrow.mk _ _ _ _ _ (op U) _ (eqToHom (by simp))) :=
  by
  rw [← category.id_comp ((id ℱ).inv.app (op U)), ← nat_iso.app_inv, iso.comp_inv_eq]
  dsimp [id]
  rw [colimit.ι_desc_assoc]
  dsimp
  rw [← ℱ.map_comp, ← ℱ.map_id]; rfl
#align Top.presheaf.pullback.id_inv_app TopCat.Presheaf.Pullback.id_inv_app

end Pullback

end Pullback

variable (C)

/-- The pushforward functor.
-/
def pushforward {X Y : TopCat.{w}} (f : X ⟶ Y) : X.Presheaf C ⥤ Y.Presheaf C
    where
  obj := pushforwardObj f
  map := @pushforwardMap _ _ X Y f
#align Top.presheaf.pushforward TopCat.Presheaf.pushforward

@[simp]
theorem pushforward_map_app' {X Y : TopCat.{w}} (f : X ⟶ Y) {ℱ 𝒢 : X.Presheaf C} (α : ℱ ⟶ 𝒢)
    {U : (Opens Y)ᵒᵖ} : ((pushforward C f).map α).app U = α.app (op <| (Opens.map f).obj U.unop) :=
  rfl
#align Top.presheaf.pushforward_map_app' TopCat.Presheaf.pushforward_map_app'

theorem id_pushforward {X : TopCat.{w}} : pushforward C (𝟙 X) = 𝟭 (X.Presheaf C) :=
  by
  apply CategoryTheory.Functor.ext
  · intros
    ext U
    have h := f.congr
    erw [h (opens.op_map_id_obj U)]
    simpa [eq_to_hom_map]
  · intros
    apply pushforward.id_eq
#align Top.presheaf.id_pushforward TopCat.Presheaf.id_pushforward

section Iso

/-- A homeomorphism of spaces gives an equivalence of categories of presheaves. -/
@[simps]
def presheafEquivOfIso {X Y : TopCat} (H : X ≅ Y) : X.Presheaf C ≌ Y.Presheaf C :=
  Equivalence.congrLeft (Opens.mapMapIso H).symm.op
#align Top.presheaf.presheaf_equiv_of_iso TopCat.Presheaf.presheafEquivOfIso

variable {C}

/-- If `H : X ≅ Y` is a homeomorphism,
then given an `H _* ℱ ⟶ 𝒢`, we may obtain an `ℱ ⟶ H ⁻¹ _* 𝒢`.
-/
def toPushforwardOfIso {X Y : TopCat} (H : X ≅ Y) {ℱ : X.Presheaf C} {𝒢 : Y.Presheaf C}
    (α : H.Hom _* ℱ ⟶ 𝒢) : ℱ ⟶ H.inv _* 𝒢 :=
  (presheafEquivOfIso _ H).toAdjunction.homEquiv ℱ 𝒢 α
#align Top.presheaf.to_pushforward_of_iso TopCat.Presheaf.toPushforwardOfIso

@[simp]
theorem toPushforwardOfIso_app {X Y : TopCat} (H₁ : X ≅ Y) {ℱ : X.Presheaf C} {𝒢 : Y.Presheaf C}
    (H₂ : H₁.Hom _* ℱ ⟶ 𝒢) (U : (Opens X)ᵒᵖ) :
    (toPushforwardOfIso H₁ H₂).app U =
      ℱ.map (eqToHom (by simp [opens.map, Set.preimage_preimage])) ≫
        H₂.app (op ((Opens.map H₁.inv).obj (unop U))) :=
  by
  delta to_pushforward_of_iso
  simp only [Equiv.toFun_as_coe, nat_trans.comp_app, equivalence.equivalence_mk'_unit,
    eq_to_hom_map, eq_to_hom_op, eq_to_hom_trans, presheaf_equiv_of_iso_unit_iso_hom_app_app,
    equivalence.to_adjunction, equivalence.equivalence_mk'_counit,
    presheaf_equiv_of_iso_inverse_map_app, adjunction.mk_of_unit_counit_hom_equiv_apply]
  congr
#align Top.presheaf.to_pushforward_of_iso_app TopCat.Presheaf.toPushforwardOfIso_app

/-- If `H : X ≅ Y` is a homeomorphism,
then given an `H _* ℱ ⟶ 𝒢`, we may obtain an `ℱ ⟶ H ⁻¹ _* 𝒢`.
-/
def pushforwardToOfIso {X Y : TopCat} (H₁ : X ≅ Y) {ℱ : Y.Presheaf C} {𝒢 : X.Presheaf C}
    (H₂ : ℱ ⟶ H₁.Hom _* 𝒢) : H₁.inv _* ℱ ⟶ 𝒢 :=
  ((presheafEquivOfIso _ H₁.symm).toAdjunction.homEquiv ℱ 𝒢).symm H₂
#align Top.presheaf.pushforward_to_of_iso TopCat.Presheaf.pushforwardToOfIso

@[simp]
theorem pushforwardToOfIso_app {X Y : TopCat} (H₁ : X ≅ Y) {ℱ : Y.Presheaf C} {𝒢 : X.Presheaf C}
    (H₂ : ℱ ⟶ H₁.Hom _* 𝒢) (U : (Opens X)ᵒᵖ) :
    (pushforwardToOfIso H₁ H₂).app U =
      H₂.app (op ((Opens.map H₁.inv).obj (unop U))) ≫
        𝒢.map (eqToHom (by simp [opens.map, Set.preimage_preimage])) :=
  by simpa [pushforward_to_of_iso, equivalence.to_adjunction]
#align Top.presheaf.pushforward_to_of_iso_app TopCat.Presheaf.pushforwardToOfIso_app

end Iso

variable (C) [HasColimits C]

/-- Pullback a presheaf on `Y` along a continuous map `f : X ⟶ Y`, obtaining a presheaf
on `X`. -/
@[simps map_app]
def pullback {X Y : TopCat.{v}} (f : X ⟶ Y) : Y.Presheaf C ⥤ X.Presheaf C :=
  lan (Opens.map f).op
#align Top.presheaf.pullback TopCat.Presheaf.pullback

@[simp]
theorem pullbackObj_eq_pullbackObj {C} [Category C] [HasColimits C] {X Y : TopCat.{w}} (f : X ⟶ Y)
    (ℱ : Y.Presheaf C) : (pullback C f).obj ℱ = pullbackObj f ℱ :=
  rfl
#align Top.presheaf.pullback_obj_eq_pullback_obj TopCat.Presheaf.pullbackObj_eq_pullbackObj

/-- The pullback and pushforward along a continuous map are adjoint to each other. -/
@[simps unit_app_app counit_app_app]
def pushforwardPullbackAdjunction {X Y : TopCat.{v}} (f : X ⟶ Y) : pullback C f ⊣ pushforward C f :=
  Lan.adjunction _ _
#align Top.presheaf.pushforward_pullback_adjunction TopCat.Presheaf.pushforwardPullbackAdjunction

/-- Pulling back along a homeomorphism is the same as pushing forward along its inverse. -/
def pullbackHomIsoPushforwardInv {X Y : TopCat.{v}} (H : X ≅ Y) :
    pullback C H.Hom ≅ pushforward C H.inv :=
  Adjunction.leftAdjointUniq (pushforwardPullbackAdjunction C H.Hom)
    (presheafEquivOfIso C H.symm).toAdjunction
#align Top.presheaf.pullback_hom_iso_pushforward_inv TopCat.Presheaf.pullbackHomIsoPushforwardInv

/-- Pulling back along the inverse of a homeomorphism is the same as pushing forward along it. -/
def pullbackInvIsoPushforwardHom {X Y : TopCat.{v}} (H : X ≅ Y) :
    pullback C H.inv ≅ pushforward C H.Hom :=
  Adjunction.leftAdjointUniq (pushforwardPullbackAdjunction C H.inv)
    (presheafEquivOfIso C H).toAdjunction
#align Top.presheaf.pullback_inv_iso_pushforward_hom TopCat.Presheaf.pullbackInvIsoPushforwardHom

end Presheaf

end TopCat


import definitions

open Zalpha

local notation `α` := Zalpha.units.α
local notation `β` := Zalpha.units.β

theorem α_Fib (n : ℤ) : (↑(α^n) : ℤα) = ⟨Fib (n-1), Fib n⟩ :=
int.induction_on n rfl
  (λ m ih, ext (begin
    have ih2 : (↑(α^(m : ℤ)) : ℤα).r = Fib m,
      rw ih,
    simp only [gpow_add,
 ih,
 α_i,
 gpow_one,
 one_mul,
 units.α_coe,
 zero_mul,
 mul_i,
 add_comm,
 zero_add,
 neg_add_cancel_left,
 sub_eq_add_neg,
 α_r,
 units.coe_pow,
 units.coe_mul,
 gpow_coe_nat],
 convert ih2,
 simp,
  end) rfl)
  (λ m ih, ext (begin
    rw [sub_eq_add_neg, gpow_add, units.coe_mul, mul_i, ih],
    simp,
    apply sub_eq_of_eq_add,
    simp [-add_comm],
    convert Fib_add_two (-m - 2) using 2,
    simp, congr' 1, ring, congr' 1, ring end) rfl)

theorem β_Fib (n : ℤ) : (↑(β^n) : ℤα) = ⟨Fib (n+1), -Fib n⟩ :=
int.induction_on n rfl
  (λ m ih, ext
    ( begin rw [gpow_add, units.coe_mul, mul_i, ih, add_comm],
      convert (Fib_add_two m).symm;simp
      end
    )
    (begin simp [gpow_add] at *, simp [ih], end))
  (λ m ih, ext
    (begin simp [gpow_add] at *, simp [ih] end)
    (begin simp [gpow_add] at *, simp [ih],
      have := Fib_add_two (-m-1),
      rw [bit0, ← add_assoc, sub_add_cancel] at this,
      rw [add_comm (1 : ℤ), this],
      simp end))

theorem Fib_αβ (m : ℤ) : ↑(Fib m) * sqrt5 = ↑(α^m) - ↑(β^m) :=
ext (by simp [α_Fib, β_Fib];
  have := Fib_add_two (m-1);
  rw [bit0, ← add_assoc, sub_add_cancel] at this;
  simp [this])
(by simp [α_Fib, β_Fib, mul_two])

theorem Luc_αβ (m : ℤ) : (↑(Luc m) : ℤα) = ↑(α^m) + ↑(β^m) :=
ext (by simp [Luc]) (by simp [α_Fib, β_Fib])


example (P Q R : Type) : (P → (Q → R)) → ((P → Q) → (P → R)) :=
begin
    intro f,
    intro g,
    intro p,
    have q := f p,
    apply q,
    apply g,
    exact p,
end


import algebra.homology.homology
import category_theory.abelian.homology

namespace category_theory

open category_theory.limits

variables {A ι : Type*} [category A] [abelian A] {c : complex_shape ι}
  (X : homological_complex A c)

variables (i j k :  ι) (hij : c.rel i j)
  (hjk : c.rel j k)

section

include hjk

noncomputable theory

@[simps]
def kernel_d_from_iso :
  kernel (X.d_from j) ≅ kernel (X.d j k) :=
{ hom := kernel.lift _ (kernel.ι _) begin
    apply_fun (λ e, e ≫ (X.X_next_iso hjk).inv),
    swap,
    { intros u v h,
      apply_fun (λ e, e ≫ (X.X_next_iso hjk).hom) at h,
      simpa using h },
    dsimp,
    rw [category.assoc, ← X.d_from_eq hjk],
    simp,
  end,
  inv := kernel.lift _ (kernel.ι _) begin
    rw X.d_from_eq hjk,
    simp,
  end,
  hom_inv_id' := by { ext, simp },
  inv_hom_id' := by { ext, simp } }

include hij

@[reassoc]
lemma kernel_lift_comp_kernel_d_from_iso_hom :
  kernel.lift (X.d_from j) (X.d_to j) (by simp) ≫
  (kernel_d_from_iso X j k hjk).hom =
  kernel.lift _ (X.d_to _) (by simp [X.d_to_eq hij]) :=
by { ext, dsimp, simp }

lemma kernel_lift_eq :
  kernel.lift (X.d j k) (homological_complex.d_to X j)
    (by simp [X.d_to_eq hij]) =
  (X.X_prev_iso hij).hom ≫ kernel.lift _ _ (X.d_comp_d i j k) :=
begin
  ext,
  dsimp,
  simp [X.d_to_eq hij],
end

end

include hij hjk

-- TODO: replace this with the defn below it
noncomputable
def homology_iso :
  (homology_functor A _ j).obj X ≅ homology _ _ (X.d_comp_d i j k) :=
{ hom := homology.desc' _ _ _ ((kernel_d_from_iso _ _ k hjk).hom ≫
    homology.π' _ _ _)
  begin
    rw [kernel_lift_comp_kernel_d_from_iso_hom_assoc _ _ _ _ hij hjk,
      kernel_lift_eq _ _ _ _ hij hjk],
    simp only [category.assoc, homology.condition_π', comp_zero],
  end,
  inv := homology.desc' _ _ _
    ((kernel_d_from_iso _ _ k hjk).inv ≫ homology.π' _ _ _)
  begin
    have := kernel_lift_comp_kernel_d_from_iso_hom X i j k hij hjk,
    rw [← iso.eq_comp_inv, kernel_lift_eq _ _ _ _ hij hjk, category.assoc,
      ← iso.inv_comp_eq] at this,
    rw [← category.assoc, ← this],
    simp only [category.assoc, homology.condition_π', comp_zero],
  end,
  hom_inv_id' := by { ext, simp only [category.assoc, kernel_d_from_iso_inv, kernel_d_from_iso_hom,
    homology.π'_desc'_assoc, homology.π'_ι, kernel.lift_ι_assoc, category.id_comp] },
  inv_hom_id' := by { ext, simp only [category.assoc, kernel_d_from_iso_hom, kernel_d_from_iso_inv,
    homology.π'_desc'_assoc, homology.π'_ι, kernel.lift_ι_assoc, category.id_comp]} }
.

noncomputable
def homology_iso' :
  (homology_functor A _ j).obj X ≅ homology _ _ (X.d_comp_d i j k) :=
begin
  refine homology.map_iso _ _ _ _ _,
  { refine arrow.iso_mk (X.X_prev_iso hij) (iso.refl _) _,
    dsimp, simp only [X.d_to_eq hij, category.comp_id], },
  { refine arrow.iso_mk (iso.refl _) (X.X_next_iso hjk) _,
    dsimp, simp only [X.d_from_comp_X_next_iso hjk, category.id_comp], },
  { refl }
end

noncomputable
def homology_iso_map {X Y : homological_complex A c} (f : X ⟶ Y) :
  (homology_functor A _ j).map f =
  (homology_iso' X _ _ _ hij hjk).hom ≫
  homology.map _ _ ⟨f.f i, f.f j, f.comm _ _⟩ ⟨f.f j, f.f k, f.comm _ _⟩ rfl ≫
  (homology_iso' Y _ _ _ hij hjk).inv :=
begin
  simp only [homology_functor_map, homology_iso', homological_complex.hom.sq_from_left,
    homology.map_iso, homology.map_comp],
  congr' 1; ext,
  { simp only [homological_complex.hom.sq_to_left, comma.comp_left, arrow.iso_mk_hom_left,
      arrow.iso_mk_inv_left, f.prev_eq hij], },
  { simp only [homological_complex.hom.sq_to_right, comma.comp_right, arrow.iso_mk_hom_right,
      iso.refl_hom, arrow.iso_mk_inv_right, iso.refl_inv, category.id_comp],
    erw [category.comp_id] },
  { simp only [homological_complex.hom.sq_from_left, comma.comp_left, arrow.iso_mk_hom_left,
      iso.refl_hom, arrow.iso_mk_inv_left, iso.refl_inv, category.id_comp],
    erw [category.comp_id] },
  { simp only [homological_complex.hom.sq_from_right, comma.comp_right, arrow.iso_mk_hom_right,
      arrow.iso_mk_inv_right, f.next_eq hjk], }
end

noncomputable
def homology_iso_inv_homology_functor_map {X Y : homological_complex A c} (f : X ⟶ Y) :
  (homology_iso' X _ _ _ hij hjk).inv ≫
  (homology_functor A _ j).map f =
  homology.map _ _ ⟨f.f i, f.f j, f.comm _ _⟩ ⟨f.f j, f.f k, f.comm _ _⟩ rfl ≫
  (homology_iso' Y _ _ _ hij hjk).inv :=
by rw [iso.inv_comp_eq, homology_iso_map]

attribute [reassoc] cokernel.map_desc

end category_theory


/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Bhavik Mehta
-/
import category_theory.pempty
import category_theory.limits.has_limits

/-!
# Initial and terminal objects in a category.

## References
* [Stacks: Initial and final objects](https://stacks.math.columbia.edu/tag/002B)
-/

noncomputable theory

universes v u u₂

open category_theory

namespace category_theory.limits

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

/-- Construct a cone for the empty diagram given an object. -/
@[simps] def as_empty_cone (X : C) : cone (functor.empty C) := { X := X, π := by tidy }
/-- Construct a cocone for the empty diagram given an object. -/
@[simps] def as_empty_cocone (X : C) : cocone (functor.empty C) := { X := X, ι := by tidy }

/-- `X` is terminal if the cone it induces on the empty diagram is limiting. -/
abbreviation is_terminal (X : C) := is_limit (as_empty_cone X)
/-- `X` is initial if the cocone it induces on the empty diagram is colimiting. -/
abbreviation is_initial (X : C) := is_colimit (as_empty_cocone X)

/-- An object `Y` is terminal if for every `X` there is a unique morphism `X ⟶ Y`. -/
def is_terminal.of_unique (Y : C) [h : Π X : C, unique (X ⟶ Y)] : is_terminal Y :=
{ lift := λ s, (h s.X).default }

/-- Transport a term of type `is_terminal` across an isomorphism. -/
def is_terminal.of_iso {Y Z : C} (hY : is_terminal Y) (i : Y ≅ Z) : is_terminal Z :=
is_limit.of_iso_limit hY
{ hom := { hom := i.hom },
  inv := { hom := i.symm.hom } }

/-- An object `X` is initial if for every `Y` there is a unique morphism `X ⟶ Y`. -/
def is_initial.of_unique (X : C) [h : Π Y : C, unique (X ⟶ Y)] : is_initial X :=
{ desc := λ s, (h s.X).default }

/-- Transport a term of type `is_initial` across an isomorphism. -/
def is_initial.of_iso {X Y : C} (hX : is_initial X) (i : X ≅ Y) : is_initial Y :=
is_colimit.of_iso_colimit hX
{ hom := { hom := i.hom },
  inv := { hom := i.symm.hom } }

/-- Give the morphism to a terminal object from any other. -/
def is_terminal.from {X : C} (t : is_terminal X) (Y : C) : Y ⟶ X :=
t.lift (as_empty_cone Y)

/-- Any two morphisms to a terminal object are equal. -/
lemma is_terminal.hom_ext {X Y : C} (t : is_terminal X) (f g : Y ⟶ X) : f = g :=
t.hom_ext (by tidy)

@[simp] lemma is_terminal.comp_from {Z : C} (t : is_terminal Z) {X Y : C} (f : X ⟶ Y) :
  f ≫ t.from Y = t.from X :=
t.hom_ext _ _

@[simp] lemma is_terminal.from_self {X : C} (t : is_terminal X) : t.from X = 𝟙 X :=
t.hom_ext _ _

/-- Give the morphism from an initial object to any other. -/
def is_initial.to {X : C} (t : is_initial X) (Y : C) : X ⟶ Y :=
t.desc (as_empty_cocone Y)

/-- Any two morphisms from an initial object are equal. -/
lemma is_initial.hom_ext {X Y : C} (t : is_initial X) (f g : X ⟶ Y) : f = g :=
t.hom_ext (by tidy)

@[simp] lemma is_initial.to_comp {X : C} (t : is_initial X) {Y Z : C} (f : Y ⟶ Z) :
  t.to Y ≫ f = t.to Z :=
t.hom_ext _ _

@[simp] lemma is_initial.to_self {X : C} (t : is_initial X) : t.to X = 𝟙 X :=
t.hom_ext _ _

/-- Any morphism from a terminal object is mono. -/
lemma is_terminal.mono_from {X Y : C} (t : is_terminal X) (f : X ⟶ Y) : mono f :=
⟨λ Z g h eq, t.hom_ext _ _⟩

/-- Any morphism to an initial object is epi. -/
lemma is_initial.epi_to {X Y : C} (t : is_initial X) (f : Y ⟶ X) : epi f :=
⟨λ Z g h eq, t.hom_ext _ _⟩

variable (C)

/--
A category has a terminal object if it has a limit over the empty diagram.
Use `has_terminal_of_unique` to construct instances.
-/
abbreviation has_terminal := has_limits_of_shape (discrete pempty) C
/--
A category has an initial object if it has a colimit over the empty diagram.
Use `has_initial_of_unique` to construct instances.
-/
abbreviation has_initial := has_colimits_of_shape (discrete pempty) C

/--
An arbitrary choice of terminal object, if one exists.
You can use the notation `⊤_ C`.
This object is characterized by having a unique morphism from any object.
-/
abbreviation terminal [has_terminal C] : C := limit (functor.empty C)
/--
An arbitrary choice of initial object, if one exists.
You can use the notation `⊥_ C`.
This object is characterized by having a unique morphism to any object.
-/
abbreviation initial [has_initial C] : C := colimit (functor.empty C)

notation `⊤_` C:20 := terminal C
notation `⊥_` C:20 := initial C

section
variables {C}

/-- We can more explicitly show that a category has a terminal object by specifying the object,
and showing there is a unique morphism to it from any other object. -/
lemma has_terminal_of_unique (X : C) [h : Π Y : C, unique (Y ⟶ X)] : has_terminal C :=
{ has_limit := λ F, has_limit.mk
  { cone     := { X := X, π := { app := pempty.rec _ } },
    is_limit := { lift := λ s, (h s.X).default } } }

/-- We can more explicitly show that a category has an initial object by specifying the object,
and showing there is a unique morphism from it to any other object. -/
lemma has_initial_of_unique (X : C) [h : Π Y : C, unique (X ⟶ Y)] : has_initial C :=
{ has_colimit := λ F, has_colimit.mk
  { cocone     := { X := X, ι := { app := pempty.rec _ } },
    is_colimit := { desc := λ s, (h s.X).default } } }

/-- The map from an object to the terminal object. -/
abbreviation terminal.from [has_terminal C] (P : C) : P ⟶ ⊤_ C :=
limit.lift (functor.empty C) (as_empty_cone P)
/-- The map to an object from the initial object. -/
abbreviation initial.to [has_initial C] (P : C) : ⊥_ C ⟶ P :=
colimit.desc (functor.empty C) (as_empty_cocone P)

instance unique_to_terminal [has_terminal C] (P : C) : unique (P ⟶ ⊤_ C) :=
{ default := terminal.from P,
  uniq := λ m, by { apply limit.hom_ext, rintro ⟨⟩ } }

instance unique_from_initial [has_initial C] (P : C) : unique (⊥_ C ⟶ P) :=
{ default := initial.to P,
  uniq := λ m, by { apply colimit.hom_ext, rintro ⟨⟩ } }

@[simp] lemma terminal.comp_from [has_terminal C] {P Q : C} (f : P ⟶ Q) :
  f ≫ terminal.from Q = terminal.from P :=
by tidy
@[simp] lemma initial.to_comp [has_initial C] {P Q : C} (f : P ⟶ Q) :
  initial.to P ≫ f = initial.to Q :=
by tidy

/-- A terminal object is terminal. -/
def terminal_is_terminal [has_terminal C] : is_terminal (⊤_ C) :=
{ lift := λ s, terminal.from _ }

/-- An initial object is initial. -/
def initial_is_initial [has_initial C] : is_initial (⊥_ C) :=
{ desc := λ s, initial.to _ }

/-- Any morphism from a terminal object is mono. -/
instance terminal.mono_from {Y : C} [has_terminal C] (f : ⊤_ C ⟶ Y) : mono f :=
is_terminal.mono_from terminal_is_terminal _

/-- Any morphism to an initial object is epi. -/
instance initial.epi_to {Y : C} [has_initial C] (f : Y ⟶ ⊥_ C) : epi f :=
is_initial.epi_to initial_is_initial _

/-- An initial object is terminal in the opposite category. -/
def terminal_op_of_initial {X : C} (t : is_initial X) : is_terminal (opposite.op X) :=
{ lift := λ s, (t.to s.X.unop).op,
  uniq' := λ s m w, quiver.hom.unop_inj (t.hom_ext _ _) }

/-- An initial object in the opposite category is terminal in the original category. -/
def terminal_unop_of_initial {X : Cᵒᵖ} (t : is_initial X) : is_terminal X.unop :=
{ lift := λ s, (t.to (opposite.op s.X)).unop,
  uniq' := λ s m w, quiver.hom.op_inj (t.hom_ext _ _) }

/-- A terminal object is initial in the opposite category. -/
def initial_op_of_terminal {X : C} (t : is_terminal X) : is_initial (opposite.op X) :=
{ desc := λ s, (t.from s.X.unop).op,
  uniq' := λ s m w, quiver.hom.unop_inj (t.hom_ext _ _) }

/-- A terminal object in the opposite category is initial in the original category. -/
def initial_unop_of_terminal {X : Cᵒᵖ} (t : is_terminal X) : is_initial X.unop :=
{ desc := λ s, (t.from (opposite.op s.X)).unop,
  uniq' := λ s m w, quiver.hom.op_inj (t.hom_ext _ _) }

/-- From a functor `F : J ⥤ C`, given an initial object of `J`, construct a cone for `J`.
In `limit_of_diagram_initial` we show it is a limit cone. -/
@[simps]
def cone_of_diagram_initial {J : Type v} [small_category J]
  {X : J} (tX : is_initial X) (F : J ⥤ C) : cone F :=
{ X := F.obj X,
  π :=
  { app := λ j, F.map (tX.to j),
    naturality' := λ j j' k,
    begin
      dsimp,
      rw [← F.map_comp, category.id_comp, tX.hom_ext (tX.to j ≫ k) (tX.to j')],
    end } }

/-- From a functor `F : J ⥤ C`, given an initial object of `J`, show the cone
`cone_of_diagram_initial` is a limit. -/
def limit_of_diagram_initial {J : Type v} [small_category J]
  {X : J} (tX : is_initial X) (F : J ⥤ C) :
is_limit (cone_of_diagram_initial tX F) :=
{ lift := λ s, s.π.app X,
  uniq' := λ s m w,
    begin
      rw [← w X, cone_of_diagram_initial_π_app, tX.hom_ext (tX.to X) (𝟙 _)],
      dsimp, simp -- See note [dsimp, simp]
    end}

-- This is reducible to allow usage of lemmas about `cone_point_unique_up_to_iso`.
/-- For a functor `F : J ⥤ C`, if `J` has an initial object then the image of it is isomorphic
to the limit of `F`. -/
@[reducible]
def limit_of_initial {J : Type v} [small_category J] (F : J ⥤ C)
  [has_initial J] [has_limit F] :
limit F ≅ F.obj (⊥_ J) :=
is_limit.cone_point_unique_up_to_iso
  (limit.is_limit _)
  (limit_of_diagram_initial initial_is_initial F)

/-- From a functor `F : J ⥤ C`, given a terminal object of `J`, construct a cocone for `J`.
In `colimit_of_diagram_terminal` we show it is a colimit cocone. -/
@[simps]
def cocone_of_diagram_terminal {J : Type v} [small_category J]
  {X : J} (tX : is_terminal X) (F : J ⥤ C) : cocone F :=
{ X := F.obj X,
  ι :=
  { app := λ j, F.map (tX.from j),
    naturality' := λ j j' k,
    begin
      dsimp,
      rw [← F.map_comp, category.comp_id, tX.hom_ext (k ≫ tX.from j') (tX.from j)],
    end } }

/-- From a functor `F : J ⥤ C`, given a terminal object of `J`, show the cocone
`cocone_of_diagram_terminal` is a colimit. -/
def colimit_of_diagram_terminal {J : Type v} [small_category J]
  {X : J} (tX : is_terminal X) (F : J ⥤ C) :
is_colimit (cocone_of_diagram_terminal tX F) :=
{ desc := λ s, s.ι.app X,
  uniq' := λ s m w,
    by { rw [← w X, cocone_of_diagram_terminal_ι_app, tX.hom_ext (tX.from X) (𝟙 _)], simp } }

-- This is reducible to allow usage of lemmas about `cocone_point_unique_up_to_iso`.
/-- For a functor `F : J ⥤ C`, if `J` has a terminal object then the image of it is isomorphic
to the colimit of `F`. -/
@[reducible]
def colimit_of_terminal {J : Type v} [small_category J] (F : J ⥤ C)
  [has_terminal J] [has_colimit F] :
colimit F ≅ F.obj (⊤_ J) :=
is_colimit.cocone_point_unique_up_to_iso
  (colimit.is_colimit _)
  (colimit_of_diagram_terminal terminal_is_terminal F)

end

section comparison
variables {C} {D : Type u₂} [category.{v} D] (G : C ⥤ D)

/--
The comparison morphism from the image of a terminal object to the terminal object in the target
category.
-/
-- TODO: Show this is an isomorphism if and only if `G` preserves terminal objects.
def terminal_comparison [has_terminal C] [has_terminal D] :
  G.obj (⊤_ C) ⟶ ⊤_ D :=
terminal.from _

/--
The comparison morphism from the initial object in the target category to the image of the initial
object.
-/
-- TODO: Show this is an isomorphism if and only if `G` preserves initial objects.
def initial_comparison [has_initial C] [has_initial D] :
  ⊥_ D ⟶ G.obj (⊥_ C) :=
initial.to _

end comparison

variables {C} {J : Type v} [small_category J]

/--
If `j` is initial in the index category, then the map `limit.π F j` is an isomorphism.
-/
lemma is_iso_π_of_is_initial {j : J} (I : is_initial j) (F : J ⥤ C) [has_limit F] :
  is_iso (limit.π F j) :=
⟨⟨limit.lift _ (cone_of_diagram_initial I F), ⟨by { ext, simp }, by simp⟩⟩⟩

instance is_iso_π_initial [has_initial J] (F : J ⥤ C) [has_limit F] :
  is_iso (limit.π F (⊥_ J)) :=
is_iso_π_of_is_initial (initial_is_initial) F

/--
If `j` is terminal in the index category, then the map `colimit.ι F j` is an isomorphism.
-/
lemma is_iso_ι_of_is_terminal {j : J} (I : is_terminal j) (F : J ⥤ C) [has_colimit F] :
  is_iso (colimit.ι F j) :=
⟨⟨colimit.desc _ (cocone_of_diagram_terminal I F), ⟨by simp, by { ext, simp }⟩⟩⟩

instance is_iso_ι_terminal [has_terminal J] (F : J ⥤ C) [has_colimit F] :
  is_iso (colimit.ι F (⊤_ J)) :=
is_iso_ι_of_is_terminal (terminal_is_terminal) F

end category_theory.limits