import Mathlib.Init.Algebra.Order
import Mathlib.Init.Data.Nat.Notation
import Mathlib.Tactic.Linarith

set_option linter.unusedVariables false

class CatSystem (Cat : Type) :=
  [ decEq : DecidableEq Cat ]
  ( Func : Cat → Cat → Type )
  ( HasLAdj : ∀ {C D : Cat}, Func C D → Prop )
  [ decidableLAdj : ∀ (C D : Cat) (F : Func C D), Decidable (HasLAdj F) ]
  ( HasRAdj : ∀ {C D : Cat}, Func C D → Prop )
  [ decidableRAdj : ∀ (C D : Cat) (F : Func C D), Decidable (HasRAdj F) ]

/- We have a system of Categories -/
variable {Cat : Type} [CatSystem Cat]

attribute [instance] CatSystem.decEq CatSystem.decidableLAdj CatSystem.decidableRAdj

open CatSystem

mutual

inductive CoreprObj : Cat → Type
  | Coprod {C : Cat} (X Y : PreObj C) : CoreprObj C
  | LAdj {C D : Cat} (F : Func C D) : HasLAdj F → PreObj D → CoreprObj C
  | Bot : (C : Cat) → CoreprObj C

inductive ReprObj : Cat → Type
  | Prod {C : Cat} : PreObj C → PreObj C → ReprObj C
  | RAdj {C D : Cat} (F : Func C D) : HasRAdj F → PreObj D → ReprObj C
  | Top : (C : Cat) → ReprObj C

inductive PreObj : Cat → Type
  | Corepr : ∀ {C : Cat}, CoreprObj C → PreObj C
  | Var : (C : Cat) → ℕ → PreObj C
  | App' : ∀ {C D : Cat}, Func C D → PreObj C → PreObj D
  | Repr : ∀ {C : Cat}, ReprObj C → PreObj C

end

open PreObj

@[match_pattern, simp]
nonrec def PreObj.Coprod {C : Cat} (X Y : PreObj C) : PreObj C :=
  Corepr (CoreprObj.Coprod X Y)

@[match_pattern, simp]
nonrec def PreObj.Prod {C : Cat} (X Y : PreObj C) : PreObj C :=
  PreObj.Repr (ReprObj.Prod X Y)

@[match_pattern, simp]
nonrec def PreObj.LAdj {C D : Cat} (F : Func C D) (H : HasLAdj F) (X : PreObj D) : PreObj C :=
  Corepr (CoreprObj.LAdj F H X)

@[match_pattern, simp]
nonrec def PreObj.RAdj {C D : Cat} (F : Func C D) (H : HasRAdj F) (X : PreObj D) : PreObj C :=
  PreObj.Repr (ReprObj.RAdj F H X)

@[match_pattern]
nonrec def PreObj.Bot (C : Cat) : PreObj C :=
  Corepr (CoreprObj.Bot C)

@[match_pattern]
nonrec def PreObj.Top (C : Cat) : PreObj C :=
  PreObj.Repr (ReprObj.Top C)

@[simp]
def PreObj.App : ∀ {C D : Cat} (F : Func C D) (X : PreObj C), PreObj D
  | _, _, F, Coprod X Y =>
    if hR : HasRAdj F
    then Coprod (App F X) (App F Y)
    else App' F (Coprod X Y)
  | _, _, F, PreObj.Prod X Y =>
    if hL : HasLAdj F
    then Prod (App F X) (App F Y)
    else App' F (PreObj.Prod X Y)
  | _, _, F, PreObj.Bot _ =>
    if hR : HasRAdj F
    then PreObj.Bot _
    else App' F (PreObj.Bot _)
  | _, _, F, PreObj.Top _ =>
    if hL : HasLAdj F
    then PreObj.Top _
    else App' F (PreObj.Top _)
  | _, _, F, X => App' F X

@[simp]
def PreObj.size : ∀ {C : Cat} (X : PreObj C), ℕ
  | _, Corepr (CoreprObj.Coprod X Y) => 1 + max (size X) (size Y)
  | _, Corepr (CoreprObj.LAdj F H X) => 2 + size X
  | _, Corepr (CoreprObj.Bot C) => 1
  | _, Var C n => 1
  | _, App' F X => 1 + size X
  | _, Repr (ReprObj.Prod X Y) => 1 + max (size X) (size Y)
  | _, Repr (ReprObj.RAdj F H X) => 2 + size X
  | _, Repr (ReprObj.Top C) => 1

def CoreprObj.Valid : ∀ {C : Cat} (X : CoreprObj C), Prop := sorry

def ReprObj.Valid : ∀ {C : Cat} (X : ReprObj C), Prop := sorry

def PreObj.Valid : ∀ {C : Cat} (X : PreObj C), Prop := sorry

-- namespace Obj

-- def size {C : Cat} (X : Obj C) : ℕ := PreObj.size X.val

-- def Var {C : Cat} (n : ℕ) : Obj C := ⟨ PreObj.Var C n, Valid.Var n ⟩

-- def App {C D : Cat} (F : Func C D) (X : Obj C) : Obj D := ⟨ PreObj.App F X.val, Valid.App F X.val X.2 ⟩

-- def Coprod {C : Cat} (X Y : Obj C) : Obj C := ⟨ PreObj.Coprod X.val Y.val, Valid.Coprod X.val Y.val X.2 Y.2 ⟩

-- def Prod {C : Cat} (X Y : Obj C) : Obj C := ⟨ PreObj.Prod X.val Y.val, Valid.Prod X.val Y.val X.2 Y.2 ⟩

-- def Bot (C : Cat) : Obj C := ⟨ PreObj.Bot C, Valid.Bot ⟩

-- def Top (C : Cat) : Obj C := ⟨ PreObj.Top C, Valid.Top ⟩

-- def LAdj {C D : Cat} (F : Func C D) (H : HasLAdj F) (X : Obj D) : Obj C :=
--   ⟨ PreObj.LAdj F H X.val, Valid.LAdj F H X.val X.2 ⟩

-- def RAdj {C D : Cat} (F : Func C D) (H : HasRAdj F) (X : Obj D) : Obj C :=
--   ⟨ PreObj.RAdj F H X.val, Valid.RAdj F H X.val X.2 ⟩

-- theorem size_app : ∀ {C D : Cat} (F : Func C D) (X : Obj C), size (App F X) ≤ 1 + size X := sorry

-- end Obj

inductive HomVar (C : Cat) : (X Y : ℕ) → Type
  | id : ∀ (X : ℕ), HomVar C X X
  | varComp {X Y Z : ℕ} (n : ℕ) (f : HomVar C Y Z) : HomVar C X Z
  deriving DecidableEq

def HomVar.comp {C : Cat} : ∀ {X Y Z : ℕ} (f : HomVar C X Y) (g : HomVar C Y Z), HomVar C X Z
  | _, _, _, HomVar.id _, f => f
  | _, _, _, HomVar.varComp n f, g => HomVar.varComp n (HomVar.comp f g)

section PreObj

open PreObj

mutual

/- Currently have no way of writing certain homs.
-- map F (LAdj _ _ _) ; counit : App F (LAdj G _ _) -> App F (Radj F _ _)
-- map F (projComp (fst or snd)) ; counit : App F (X × _) -> App F (Radj F _ X) -/

inductive ProdProj : ∀ {C : Cat}, PreObj C → PreObj C → PreObj C → Type
  | fst : ∀ {X Y : PreObj C}, ProdProj X Y X
  | snd : ∀ {X Y : PreObj C}, ProdProj X Y Y

inductive CompCounit : ∀ {C D : Cat}, PreObj D → PreObj C → Type
  | counit : ∀ {C D : Cat} (F : Func D C) (H : HasRAdj F) (X : PreObj C),
      CompCounit (RAdj F H X) X
  | mapLAdjCompCounit : ∀ {C D E : Cat} (F : Func C D) (G : Func C E) (HF : HasLAdj F) (HG : HasRAdj G)
      {X : PreObj D} {Y : PreObj E} (f : PreHom X (App F (RAdj G HG Y))), CompCounit (LAdj F HF X) Y
  | mapProjComp : ∀ {C : Cat} {W X Y Z : PreObj C} (f : ProdProj W X Y) (g : CompCounit Y Z),
      CompCounit (Prod X Y) Z

inductive Proj : ∀ {C : Cat}, PreObj C → PreObj C → Type
  | prodProj : ∀ {X Y Z : PreObj C}, ProdProj X Y Z → Proj (Prod X Y) Z
  | compCounit : ∀ {C D : Cat} (F : Func D C) (H : HasRAdj F) {X : PreObj D} {Y : PreObj C}
      (f : CompCounit X Y), Proj (App' F X) Y

inductive HomCorepr : {C : Cat} → CoreprObj C → PreObj C → Type
  | coprod {C : Cat} {X Y Z : PreObj C} (f : PreHom X Z) (g : PreHom Y Z) : HomCorepr (CoreprObj.Coprod X Y) Z
  | ladj {C D : Cat} (F : Func C D) (H : HasLAdj F) {X : PreObj D} {Y : PreObj C}
      (f : PreHom X (App F Y)) : HomCorepr (CoreprObj.LAdj F H X) Y
  | botMk {C : Cat} {X : PreObj C} : HomCorepr (CoreprObj.Bot C) X

inductive CoprodEmb : ∀ {C : Cat}, PreObj C → PreObj C → PreObj C → Type
  | inl : ∀ {X Y : PreObj C}, CoprodEmb X X Y
  | inr : ∀ {X Y : PreObj C}, CoprodEmb Y X Y

/-- `UnitComp F H X Y` describes a morphism from `X` to `App F Y` -/
inductive UnitComp : ∀ {C D : Cat} (F : Func D C) (H : HasLAdj F), PreObj C → PreObj D → Type
  | unit : ∀ {C D : Cat} (F : Func D C) (H : HasLAdj F) (X : PreObj C),
      UnitComp F H X (LAdj F H X)
  --Looks wrong
  | unitCompMapRAdj : ∀ {C D E : Cat} (F : Func C D) (G : Func C E) (HF : HasLAdj F) (HG : HasRAdj G)
      {X : PreObj D} {Y : PreObj E} (f : PreHom (App G (LAdj F HF X)) Y), UnitComp F HF X (RAdj G HG Y)
  | compMapEmb : ∀ {C : Cat} {W X Y Z : PreObj D} (f : UnitComp F H W X) (g : CoprodEmb X Y Z),
      UnitComp F H W (Coprod Y Z)

inductive Emb : ∀ {C : Cat}, PreObj C → PreObj C → Type
  | coprodEmb : ∀ {X Y Z : PreObj C}, CoprodEmb X Y Z → Emb X (Coprod Y Z)
  | unitComp : ∀ {C D : Cat} (F : Func D C) (H : HasLAdj F) {X : PreObj C} {Y : PreObj D}
      (f : UnitComp F H X Y), Emb X (App' F Y)

inductive HomRepr : {C : Cat} → PreObj C → ReprObj C → Type
  | prod {C : Cat} {X : PreObj C} {Y Z : PreObj C}
    (f : PreHom X Y) (g : PreHom X Z) : HomRepr X (ReprObj.Prod Y Z)
  | radj {C D : Cat} (F : Func C D) (H : HasRAdj F) {X : PreObj C} {Y : PreObj D}
    (f : PreHom (App F X) Y) : HomRepr X (ReprObj.RAdj F H Y)
  | topMk {C : Cat} {X : PreObj C} : HomRepr X (ReprObj.Top C)

inductive PreHom : ∀ {C : Cat}, PreObj C → PreObj C → Type
  | projComp : ∀ {C : Cat} {X Y Z : PreObj C} (f : Proj X Y) (g : PreHom Y Z), PreHom X Z
  | compEmb : ∀ {C : Cat} {X : PreObj C} {Y Z : PreObj C} (f : PreHom X Y) (g : Emb Y Z), PreHom X Z
  | corepr : ∀ {C : Cat} {X : CoreprObj C} {Y : PreObj C} (f : HomCorepr X Y), PreHom (Corepr X) Y
  | repr : ∀ {C : Cat} {X : PreObj C} {Y : ReprObj C} (f : HomRepr X Y), PreHom X (Repr Y)
  | map' : ∀ {C D : Cat} {X Y : PreObj C} (F : Func C D) (f : PreHom X Y), PreHom (App' F X) (App' F Y)
  | var : ∀ {C : Cat} {X Y : ℕ}, HomVar C X Y → PreHom (Var C X) (Var C Y)

/- Provided LAdj is bigger than map, every Hom constructor make homs from homs between smaller objects.
By smaller I mean that -/

end

@[match_pattern]
def Proj.fst {C : Cat} {X Y : PreObj C} : Proj (Prod X Y) X :=
  prodProj ProdProj.fst

@[match_pattern]
def Proj.snd {C : Cat} {X Y : PreObj C} : Proj (Prod X Y) Y :=
  prodProj ProdProj.snd

@[match_pattern]
def Proj.counit' {C : Cat} {D : Cat} (F : Func D C) (H : HasRAdj F) {X : PreObj C} :
    Proj (App' F (RAdj F H X)) X :=
  compCounit F H (CompCounit.counit F H X)

def Proj.counit {C : Cat} {D : Cat} (F : Func D C) (H : HasRAdj F) {X : PreObj C} :
    Proj (App F (RAdj F H X)) X :=
  by simp only [App]; exact compCounit F H (CompCounit.counit F H X)

@[match_pattern]
def Emb.inl {C : Cat} {X Y : PreObj C} : Emb X (Coprod X Y) :=
  coprodEmb CoprodEmb.inl

@[match_pattern]
def Emb.inr {C : Cat} {X Y : PreObj C} : Emb Y (Coprod X Y) :=
  coprodEmb CoprodEmb.inr

@[match_pattern]
def Emb.unit' {C : Cat} {D : Cat} (F : Func D C) (H : HasLAdj F) {X : PreObj C} :
    Emb X (App' F (LAdj F H X)) :=
  unitComp F H (UnitComp.unit F H X)

def Emb.unit {C : Cat} {D : Cat} (F : Func D C) (H : HasLAdj F) {X : PreObj C} :
    Emb X (App F (LAdj F H X)) :=
  by simp only [App]; exact unitComp F H (UnitComp.unit F H X)

theorem size_lt_of_emb {C : Cat} {X Y : PreObj C} (f : Emb X Y) : PreObj.size X < PreObj.size Y := sorry

theorem size_lt_of_proj {C : Cat} {X Y : PreObj C} (f : Proj X Y) : PreObj.size Y < PreObj.size X := sorry

namespace PreHom

def id : ∀ {C : Cat} {X : PreObj C}, PreHom X X
  | _, PreObj.Var C X => PreHom.var (HomVar.id _)
  | _, PreObj.App' F X => PreHom.map' F PreHom.id
  | _, PreObj.Repr (ReprObj.Prod X Y) => repr (HomRepr.prod (PreHom.projComp Proj.fst PreHom.id) (PreHom.projComp Proj.snd PreHom.id))
  | _, PreObj.Repr (ReprObj.RAdj F H Y) => repr (HomRepr.radj F H (PreHom.projComp (Proj.counit F H) PreHom.id))
  | _, PreObj.Repr (ReprObj.Top C) => repr HomRepr.topMk
  | _, PreObj.Corepr (CoreprObj.Coprod X Y) => corepr (HomCorepr.coprod (PreHom.compEmb PreHom.id Emb.inl) (PreHom.compEmb PreHom.id Emb.inr))
  | _, PreObj.Corepr (CoreprObj.LAdj F H X) => corepr (HomCorepr.ladj F H (PreHom.compEmb PreHom.id (Emb.unit F H)))
  | _, PreObj.Corepr (CoreprObj.Bot C) => corepr HomCorepr.botMk

def ofEmb {C : Cat} {X Y : PreObj C} (f : Emb X Y) : PreHom X Y :=
  compEmb PreHom.id f

def ofProj {C : Cat} {X Y : PreObj C} (f : Proj X Y) : PreHom X Y :=
  projComp f PreHom.id

def inl {C : Cat} {X Y : PreObj C} : PreHom X (Coprod X Y) :=
  ofEmb Emb.inl

def inr {C : Cat} {X Y : PreObj C} : PreHom Y (Coprod X Y) :=
  ofEmb Emb.inr

def unit {C : Cat} {D : Cat} (F : Func C D) (H : HasLAdj F) {X : PreObj D} : PreHom X (App F (LAdj F H X)) :=
  ofEmb (Emb.unit F H)

def fst {C : Cat} {X Y : PreObj C} : PreHom (Prod X Y) X :=
  ofProj Proj.fst

def snd {C : Cat} {X Y : PreObj C} : PreHom (Prod X Y) Y :=
  ofProj Proj.snd

def counit {C : Cat} {D : Cat} (F : Func C D) (H : HasRAdj F) {X : PreObj D} : PreHom (App F (RAdj F H X)) X :=
  ofProj (Proj.counit F H)

@[match_pattern]
def botMk {C : Cat} {X : PreObj C} : PreHom (Bot C) X :=
  corepr HomCorepr.botMk

@[match_pattern]
def topMk {C : Cat} {X : PreObj C} : PreHom X (Top C) :=
  repr HomRepr.topMk

@[match_pattern]
def coprod {C : Cat} {X Y Z : PreObj C} (f : PreHom X Z) (g : PreHom Y Z) : PreHom (Coprod X Y) Z :=
  corepr (HomCorepr.coprod f g)

@[match_pattern]
def prod {C : Cat} {X Y Z : PreObj C} (f : PreHom X Y) (g : PreHom X Z) : PreHom X (Prod Y Z) :=
  repr (HomRepr.prod f g)

@[match_pattern]
def radj {C : Cat} {D : Cat} (F : Func C D) (H : HasRAdj F) {X : PreObj C} {Y : PreObj D}
    (f : PreHom (App F X) Y) : PreHom X (RAdj F H Y) :=
  repr (HomRepr.radj F H f)

@[match_pattern]
def ladj {C : Cat} {D : Cat} (F : Func C D) (H : HasLAdj F) {X : PreObj C} {Y : PreObj D}
    (f : PreHom Y (App F X)) : PreHom (LAdj F H Y) X :=
  corepr (HomCorepr.ladj F H f)

mutual

def map : ∀ {C D : Cat} (F : Func C D) {X Y : PreObj C}, PreHom X Y → PreHom (App F X) (App F Y)
  | _, _, _, _, _, _ => sorry

def coreprComp : ∀ {C : Cat} {X : CoreprObj C} {Y Z : PreObj C}, HomCorepr X Y → PreHom (Y : PreObj C) Z → HomCorepr X Z
  | _, _, _, _, HomCorepr.coprod f g, h => HomCorepr.coprod (comp f h) (comp g h)
  | _, _, _, _, HomCorepr.ladj F H f, g => HomCorepr.ladj _ _ (comp f (PreHom.map F g))
  | _, _, _, _, HomCorepr.botMk, _ => HomCorepr.botMk

def compRepr : ∀ {C : Cat} {X Y : PreObj C} {Z : ReprObj C}, PreHom (X : PreObj C) Y → HomRepr Y Z → HomRepr X Z
  | _, _, _, _, f, HomRepr.prod g h => HomRepr.prod (comp f g) (comp f h)
  | _, _, _, _, f, HomRepr.radj F H g => HomRepr.radj _ _ (comp (map F f) g)
  | _, _, _, _, _, HomRepr.topMk => HomRepr.topMk

def reprComp : ∀ {C : Cat} {X : PreObj C} {Y : ReprObj C} {Z : PreObj C},
    HomRepr X Y → PreHom (PreObj.Repr Y) Z → PreHom X Z
  | _, _, _, _, HomRepr.prod f g, projComp Proj.fst h => comp f h
  | _, _, _, _, HomRepr.prod f g, projComp Proj.snd h => comp g h
  | _, _, _, _, f, PreHom.repr g => repr (compRepr (repr f) g)
  | _, _, _, _, f, compEmb g h => compEmb (reprComp f g) h

def compCorepr : ∀ {C : Cat} {X : PreObj C} {Y : CoreprObj C} {Z : PreObj C},
    PreHom X (PreObj.Corepr Y) → HomCorepr Y Z → PreHom X Z
  | _, _, _, _, compEmb f Emb.inl, HomCorepr.coprod g h => comp f g
  | _, _, _, _, compEmb f Emb.inr, HomCorepr.coprod g h => comp f h
  | _, _, _, _, PreHom.corepr f, g => corepr (coreprComp f (corepr g))
  | _, _, _, _, projComp f g, h => projComp f (compCorepr g h)
--Think about cancellability
def comp : ∀ {C : Cat} {X Y Z : PreObj C} ,
    PreHom (X : PreObj C) Y → PreHom (Y : PreObj C) Z → PreHom (X : PreObj C) Z
  | _, _, _, _, PreHom.corepr f, g => corepr (coreprComp f g)
  | _, _, _, _, f, PreHom.repr g => repr (compRepr f g)
  | _, _, PreObj.Repr Y, _, PreHom.repr f, g => reprComp f g
  | _, _, PreObj.Corepr Y, _, f, PreHom.corepr g => compCorepr f g
  | _, W, Y, Z, PreHom.projComp (Y := X) f g, h =>
    have : size X + size Z < size W + size Z :=
      by linarith [size_lt_of_proj f]
    PreHom.projComp f (comp g h)
  | _, W, X, Z, f, PreHom.compEmb (Y := Y) g h =>
    have : size W + size Y < size W + size Z :=
      by linarith [size_lt_of_emb h]
    compEmb (f.comp g) h
  | _, _, _, _, var f, var g => var (HomVar.comp f g)
  | _, _, _, _, map' F f, map' _ g => map' _ (comp f g)
  | _, _, _, _, compEmb f (Emb.unitComp F H (UnitComp.unitCompMapRAdj _ G _ _ g)),
    projComp (Proj.compCounit _ HG (CompCounit.counit _ _ _)) k =>
    comp (compEmb f (Emb.unit _ _)) _
  -- | _, _, _, _, map' _ (projComp _ _), projComp (Proj.counit' _ _) _ =>
  --       sorry
  -- | _, App' _ X, _, _, map' F (PreHom.repr (HomRepr.radj _ _ f)), projComp (Proj.counit' _ _) g =>
  --   have : App' F X = App F X := sorry
  --   by rw [this]; exact f.comp g
  -- | _, _, _, _, map' _ (PreHom.corepr HomCorepr.botMk), projComp (Proj.counit' _ _) _ => sorry
  --Things must preserve coproducts in a stronger way.
  --| _, _, _, _, map' G (PreHom.corepr (HomCorepr.ladj F H f)), projComp (Proj.counit' _ _) g => sorry --New constructor
  | _, _, _, _, _, _ => sorry

end
termination_by comp C X Y Z f g => (size X + size Z, size Y, 1)
               coreprComp C X Y Z f g => (size (Corepr X) + size Z, size Y, 0)
               compRepr C X Y Z f g => (size X + size (Repr Z), size Y, 0)

end PreHom

end PreObj

open Obj

namespace Hom

def id : ∀ {C : Cat} {X : Obj C}, Hom X X := PreHom.id

def ofEmb {C : Cat} {X Y : Obj C} (f : Emb (X : PreObj C) Y) : Hom X Y := PreHom.ofEmb f

def ofProj {C : Cat} {X Y : Obj C} (f : Proj (X : PreObj C) Y) : Hom X Y := PreHom.ofProj f

def inl {C : Cat} {X Y : Obj C} : Hom X (Coprod X Y) := PreHom.inl

def inr {C : Cat} {X Y : Obj C} : Hom Y (Coprod X Y) := PreHom.inr

def unit {C : Cat} {D : Cat} (F : Func C D) (H : HasLAdj F) {X : Obj D} : Hom X (App F (LAdj F H X)) := PreHom.unit F H

def fst {C : Cat} {X Y : Obj C} : Hom (Prod X Y) X := PreHom.fst

def snd {C : Cat} {X Y : Obj C} : Hom (Prod X Y) Y := PreHom.snd

def counit {C : Cat} {D : Cat} (F : Func C D) (H : HasRAdj F) {X : Obj D} : Hom (App F (RAdj F H X)) X := PreHom.counit F H

def botMk {C : Cat} {X : Obj C} : Hom (Obj.Bot C) X := PreHom.botMk

def topMk {C : Cat} {X : Obj C} : Hom X (Top C) := PreHom.topMk

def coprod {C : Cat} {X Y Z : Obj C} (f : Hom X Z) (g : Hom Y Z) : Hom (Coprod X Y) Z := PreHom.coprod f g

def prod {C : Cat} {X Y Z : Obj C} (f : Hom X Y) (g : Hom X Z) : Hom X (Prod Y Z) := PreHom.prod f g

def radj {C : Cat} {D : Cat} (F : Func C D) (H : HasRAdj F) {X : Obj C} {Y : Obj D}
    (f : Hom (App F X) Y) : Hom X (RAdj F H Y) := PreHom.radj F H f

def ladj {C : Cat} {D : Cat} (F : Func C D) (H : HasLAdj F) {X : Obj C} {Y : Obj D}
    (f : Hom Y (App F X)) : Hom (LAdj F H Y) X := PreHom.ladj F H f

end Hom

def LAdjSymm {C D : Cat} (F : Func C D) (H : HasLAdj F) {X : Obj D} {Y : Obj C}
    (f : Hom (Obj.LAdj F H X) Y) : Hom X (App F Y) :=
  Hom.comp (Hom.compEmb Hom.id (Emb.unit _ H)) (map F f)

def RAdjSymm {C D : Cat} (F : Func C D) (H : HasRAdj F) {X : Obj C} {Y : Obj D}
    (f : Hom X (Obj.RAdj F H Y)) : Hom (App F X) Y :=
  Hom.comp (map F f) (Hom.projComp (Proj.counit _ H) Hom.id)

def ladjMap {C D : Cat} (F : Func C D) (H : HasLAdj F) {X Y : Obj D} (f : Hom X Y) :
    Hom (LAdj F H X) (LAdj F H Y) :=
  ladj _ _ (comp f (unit _ _))

def radjMap {C D : Cat} (F : Func C D) (H : HasRAdj F) {X Y : Obj D} (f : Hom X Y) :
    Hom (RAdj F H X) (RAdj F H Y) :=
  radj _ _ (comp (counit _ _) f)

def ladjPreserveBot {C D : Cat} (F : Func C D) (H : HasLAdj F) : Hom (LAdj F H (Obj.Bot _)) (Obj.Bot _) :=
  ladj _ _ botMk

def radjPreserveTop {C D : Cat} (F : Func C D) (H : HasRAdj F) : Hom (Obj.Top _) (RAdj F H (Obj.Top _)) :=
  radj _ _ topMk

def ladjPreserveCoprod {C D : Cat} (F : Func C D) (H : HasLAdj F) {X Y : Obj D} :
    Hom (LAdj F H (Obj.Coprod X Y)) (Obj.Coprod (LAdj F H X) (LAdj F H Y)) :=
  ladj _ _ (coprod (LAdjSymm _ H inl) (LAdjSymm _ H inr))

def ladjPreserveCoprodSymm {C D : Cat} (F : Func C D) (H : HasLAdj F) {X Y : Obj D} :
    Hom (Obj.Coprod (LAdj F H X) (LAdj F H Y)) (LAdj F H (Obj.Coprod X Y)) :=
  coprod (ladj _ _ (comp (unit F H) (map F (ladjMap _ _ inl))))
         (ladj _ _ (comp (unit F H) (map F (ladjMap _ _ inr))))

def radjPreserveProd {C D : Cat} (F : Func C D) (H : HasRAdj F) {X Y : Obj D} :
    Hom (Obj.Prod (RAdj F H X) (RAdj F H Y)) (RAdj F H (Obj.Prod X Y)) :=
  radj _ _ (prod (RAdjSymm _ H fst) (RAdjSymm _ H snd))

def radjPreserveProdSymm {C D : Cat} (F : Func C D) (H : HasRAdj F) {X Y : Obj D} :
    Hom (RAdj F H (Obj.Prod X Y)) (Obj.Prod (RAdj F H X) (RAdj F H Y)) :=
  prod (radj _ _ (comp (map F (radjMap _ _ fst)) (counit F H)))
       (radj _ _ (comp (map F (radjMap _ _ snd)) (counit F H)))

def preserveBotOfHasRAdj {C D : Cat} (F : Func C D) (H : HasRAdj F) :
    Hom (App F (Obj.Bot _)) (Obj.Bot _):=
  RAdjSymm _ H botMk

def preserveTopOfHasLAdj {C D : Cat} (F : Func C D) (H : HasLAdj F) :
    Hom (Obj.Top _) (App F (Obj.Top _)) :=
  LAdjSymm _ H topMk

def preserveCoprodOfHasRAdj {C D : Cat} (F : Func C D) (H : HasRAdj F) {X Y : Obj C} :
    Hom (App F (Obj.Coprod X Y)) (Obj.Coprod (App F X) (App F Y)) :=
  RAdjSymm _ H (coprod (radj _ _ inl) (radj _ _ inr))

def preserveCoprodOfHasRAdjSymm {C D : Cat} (F : Func C D) (H : HasRAdj F) {X Y : Obj C} :
    Hom (Obj.Coprod (App F X) (App F Y)) (App F (Obj.Coprod X Y)) :=
  coprod (map F inl) (map F inr)

def preserveProdOfHasLAdj {C D : Cat} (F : Func C D) (H : HasLAdj F) {X Y : Obj C} :
    Hom (Obj.Prod (App F X) (App F Y)) (App F (Obj.Prod X Y)) :=
  LAdjSymm _ H (prod (ladj _ _ fst) (ladj _ _ snd))

def preserveProdOfHasLAdjSymm {C D : Cat} (F : Func C D) (H : HasLAdj F) {X Y : Obj C} :
    Hom (App F (Obj.Prod X Y)) (Obj.Prod (App F X) (App F Y)) :=
  prod (map F fst) (map F snd)

section

def asCorepr : ∀ {C : Cat} {X : CoreprObj C} {Y : Obj C}, Hom (Corepr X) Y → HomCorepr X Y
  | _, _, _, Hom.corepr f => f
  | _, CoreprObj.Bot _, _, _ => HomCorepr.botMk
  | _, CoreprObj.Coprod X Y, _, f => HomCorepr.coprod (comp (ofEmb Emb.inl) f) (comp (ofEmb Emb.inr) f)
  | _, CoreprObj.LAdj F H X, _, f => HomCorepr.ladj _ _ (comp (ofEmb (Emb.unit F H)) (map F f))

def asRepr : ∀ {C : Cat} {X : Obj C} {Y : ReprObj C}, Hom X (Repr Y) → HomRepr X Y
  | _, _, _, Hom.repr f => f
  | _, _, ReprObj.Prod X Y, f => HomRepr.prod (comp f (ofProj Proj.fst)) (comp f (ofProj Proj.snd))
  | _, _, ReprObj.RAdj F H X, f => HomRepr.radj _ _ (comp (map F f) (ofProj (Proj.counit F H)))
  | _, _, ReprObj.Top _, _ => HomRepr.topMk

end

instance : ∀ C : Cat, DecidableEq (Obj C) := sorry
instance : ∀ C : Cat, DecidableEq (CoreprObj C) := sorry
instance : ∀ C : Cat, DecidableEq (ReprObj C) := sorry
instance : ∀ C : Cat, ∀ X Y : Obj C, DecidableEq (Hom X Y) := sorry
instance : ∀ C : Cat, ∀ X : CoreprObj C, ∀ Y : Obj C, DecidableEq (HomCorepr X Y) := sorry
instance : ∀ C : Cat, ∀ X : Obj C, ∀ Y : ReprObj C, DecidableEq (HomRepr X Y) := sorry

mutual

/-
Normal forms
- If it can be written as `f ; corepr g` then it is unless the first rule applies. What if there are two different ways of doing this?,
    Try to make sure `f` is not of that form
    Also `corepr g` should remain a subterm after `f` and `corepr g` are composed and cut eliminated.
- If it can be written as `repr_mk ; f` then it is unless the first rule applies.
- Not sure what else there is, just associativity of `projComp` and `compEmb`
-/

/-
Questions: What is shrinking? I have to make sure that everything splits into smaller homs.
I decided to do products before LAdj. Why? I don't think this applies if I insist on objects shrinking.

-/

open Hom

def getCompCorepr :
    ∀ {C : Cat} {X Y : Obj C} (f : Hom X Y),
      Option (Σ R : CoreprObj C, Hom X (Corepr R) × HomCorepr R Y × Bool)
      --Bool is true if the `Hom X (Corepr R)` is the identity
  | _, Corepr R, _, f => some ⟨_, Hom.id, asCorepr f, true⟩
  | _, _, _, var _ => none
  | _, _, _, topMk => none
  | _, _, _, Hom.repr (HomRepr.radj F H f) => none
  | _, _, _, map F f => none
  | _, _, _, projComp f g =>
    match getCompCorepr g with
    | none => none
    | some ⟨R, g, h, _⟩ => some ⟨R, projComp f g , h, false⟩
  | _, _, _, compEmb f h =>
    match getCompCorepr f with
    | none => none
    | some ⟨R, f, g, _⟩ => some ⟨R, f, coreprComp g (ofEmb h), false⟩
  | _, _, _, Hom.repr (HomRepr.prod f g) =>
    match getCompCorepr f, getCompCorepr g with
    | some ⟨R₁, f₁, f₂, _⟩, some ⟨R₂, g₁, g₂, _⟩ =>
        let nf := normalize f₁
        let ng := normalize g₁
        if hr : R₁ = R₂
          then if (hr ▸ nf) = ng
            then by
              subst R₁
              match R₂, nf, f₂, g₂ with
              | _, nf, HomCorepr.coprod f₂ f₃, HomCorepr.coprod g₂ g₃ =>
                exact some ⟨_, nf, HomCorepr.coprod (prod f₂ g₂) (prod f₃ g₃), false⟩
              | _, nf, HomCorepr.botMk, HomCorepr.botMk => exact some ⟨_, nf, HomCorepr.botMk, false⟩
              | _, nf, HomCorepr.ladj _ _ _, _ => exact none
            else none
        else none
    | _, _ => none

def getReprComp :
    ∀ {C : Cat} {X Y : Obj C} (f : Hom X Y), Option (Σ R : ReprObj C, HomRepr X R × Hom (Repr R) Y × Bool)
  | _, Corepr R, _, f => none
  | _, _, Obj.Repr R, f => some ⟨_, asRepr f, Hom.id, true⟩
  | _, _, _, var _ => none
  | _, _, _, map F f => none
  | _, _, _, projComp f g =>
    match getReprComp g with
    | none => none
    | some ⟨R, g, h, _⟩ => some ⟨R, compRepr (ofProj f) g, h, false⟩
  | _, _, _, compEmb f h =>
    match getReprComp f with
    | none => none
    | some ⟨R, f, g, _⟩ => some ⟨R, f, compEmb g h, false⟩

def normalizeCorepr : ∀ {C : Cat} {X : CoreprObj C} {Y : Obj C} (f : HomCorepr X Y),
    HomCorepr X Y
  | _, _, _, HomCorepr.coprod f g => HomCorepr.coprod (normalize f) (normalize g)
  | _, _, _, HomCorepr.botMk => HomCorepr.botMk
  | _, _, _, HomCorepr.ladj F H f => HomCorepr.ladj F H (normalize f)

def normalizeRepr : ∀ {C : Cat} {X : Obj C} {Y : ReprObj C} (f : HomRepr X Y),
    HomRepr X Y
  | _, _, _, HomRepr.radj F H f => HomRepr.radj F H (normalize f)
  | _, _, _, HomRepr.prod f g => HomRepr.prod (normalize f) (normalize g)
  | _, _, _, HomRepr.topMk => HomRepr.topMk

def normalize {C : Cat} {X Y : Obj C} (f : Hom X Y) : Hom X Y :=
  match getCompCorepr f with
  | none =>
    match getReprComp f with
    | none =>

end

end Hom


/-
Copyright (c) 2020 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison
-/
import category_theory.category.Cat
import category_theory.limits.types
import category_theory.limits.preserves.basic

/-!
# The category of small categories has all small limits.

An object in the limit consists of a family of objects,
which are carried to one another by the functors in the diagram.
A morphism between two such objects is a family of morphisms between the corresponding objects,
which are carried to one another by the action on morphisms of the functors in the diagram.

## Future work
Can the indexing category live in a lower universe?
-/

noncomputable theory

universes v u

open category_theory.limits

namespace category_theory

variables {J : Type v} [small_category J]

namespace Cat

namespace has_limits

instance category_objects {F : J ⥤ Cat.{u u}} {j} :
  small_category ((F ⋙ Cat.objects.{u u}).obj j) :=
(F.obj j).str

/-- Auxiliary definition:
the diagram whose limit gives the morphism space between two objects of the limit category. -/
@[simps]
def hom_diagram {F : J ⥤ Cat.{v v}} (X Y : limit (F ⋙ Cat.objects.{v v})) : J ⥤ Type v :=
{ obj := λ j, limit.π (F ⋙ Cat.objects) j X ⟶ limit.π (F ⋙ Cat.objects) j Y,
  map := λ j j' f g,
  begin
    refine eq_to_hom _ ≫ (F.map f).map g ≫ eq_to_hom _,
    exact (congr_fun (limit.w (F ⋙ Cat.objects) f) X).symm,
    exact (congr_fun (limit.w (F ⋙ Cat.objects) f) Y),
  end,
  map_id' := λ X, begin
    ext f, dsimp,
    simp [functor.congr_hom (F.map_id X) f],
  end,
  map_comp' := λ X Y Z f g, begin
    ext h, dsimp,
    simp [functor.congr_hom (F.map_comp f g) h],
    refl,
  end, }

@[simps]
instance (F : J ⥤ Cat.{v v}) : category (limit (F ⋙ Cat.objects)) :=
{ hom := λ X Y, limit (hom_diagram X Y),
  id := λ X, types.limit.mk (hom_diagram X X) (λ j, 𝟙 _) (λ j j' f, by simp),
  comp := λ X Y Z f g, types.limit.mk (hom_diagram X Z)
    (λ j, limit.π (hom_diagram X Y) j f ≫ limit.π (hom_diagram Y Z) j g)
    (λ j j' h, begin
      rw [←congr_fun (limit.w (hom_diagram X Y) h) f, ←congr_fun (limit.w (hom_diagram Y Z) h) g],
      dsimp,
      simp,
    end), }

/-- Auxiliary definition: the limit category. -/
@[simps]
def limit_cone_X (F : J ⥤ Cat.{v v}) : Cat.{v v} :=
{ α := limit (F ⋙ Cat.objects), }.

/-- Auxiliary definition: the cone over the limit category. -/
@[simps]
def limit_cone (F : J ⥤ Cat.{v v}) : cone F :=
{ X := limit_cone_X F,
  π :=
  { app := λ j,
    { obj := limit.π (F ⋙ Cat.objects) j,
      map := λ X Y, limit.π (hom_diagram X Y) j, },
    naturality' := λ j j' f, category_theory.functor.ext
      (λ X, (congr_fun (limit.w (F ⋙ Cat.objects) f) X).symm)
      (λ X Y h, (congr_fun (limit.w (hom_diagram X Y) f) h).symm), } }

/-- Auxiliary definition: the universal morphism to the proposed limit cone. -/
@[simps]
def limit_cone_lift (F : J ⥤ Cat.{v v}) (s : cone F) : s.X ⟶ limit_cone_X F :=
{ obj := limit.lift (F ⋙ Cat.objects)
  { X := s.X,
    π :=
    { app := λ j, (s.π.app j).obj,
      naturality' := λ j j' f, (congr_arg functor.obj (s.π.naturality f) : _), } },
  map := λ X Y f,
  begin
    fapply types.limit.mk,
    { intro j,
      refine eq_to_hom _ ≫ (s.π.app j).map f ≫ eq_to_hom _;
      simp, },
    { intros j j' h,
      dsimp,
      simp only [category.assoc, functor.map_comp,
        eq_to_hom_map, eq_to_hom_trans, eq_to_hom_trans_assoc],
      rw [←functor.comp_map],
      have := (s.π.naturality h).symm,
      conv at this { congr, skip, dsimp, simp, },
      erw [functor.congr_hom this f],
      dsimp, simp, },
  end, }

@[simp]
lemma limit_π_hom_diagram_eq_to_hom {F : J ⥤ Cat.{v v}}
  (X Y : limit (F ⋙ Cat.objects.{v v})) (j : J) (h : X = Y) :
  limit.π (hom_diagram X Y) j (eq_to_hom h) =
    eq_to_hom (congr_arg (limit.π (F ⋙ Cat.objects.{v v}) j) h) :=
by { subst h, simp, }

/-- Auxiliary definition: the proposed cone is a limit cone. -/
def limit_cone_is_limit (F : J ⥤ Cat.{v v}) : is_limit (limit_cone F) :=
{ lift := limit_cone_lift F,
  fac' := λ s j, category_theory.functor.ext (by tidy) (λ X Y f, types.limit.π_mk _ _ _ _),
  uniq' := λ s m w,
  begin
    symmetry,
    fapply category_theory.functor.ext,
    { intro X,
      ext,
      dsimp, simp only [types.limit.lift_π_apply, ←w j],
      refl, },
    { intros X Y f,
      dsimp, simp [(λ j, functor.congr_hom (w j).symm f)],
      congr, },
  end, }

end has_limits

/-- The category of small categories has all small limits. -/
instance : has_limits (Cat.{v v}) :=
{ has_limits_of_shape := λ J _, by exactI
  { has_limit := λ F, ⟨⟨⟨has_limits.limit_cone F, has_limits.limit_cone_is_limit F⟩⟩⟩, } }

instance : preserves_limits Cat.objects.{v v} :=
{ preserves_limits_of_shape := λ J _, by exactI
  { preserves_limit := λ F,
    preserves_limit_of_preserves_limit_cone (has_limits.limit_cone_is_limit F)
      (limits.is_limit.of_iso_limit (limit.is_limit (F ⋙ Cat.objects))
        (cones.ext (by refl) (by tidy))), }}

end Cat

end category_theory


/-
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.functor.const
import category_theory.discrete_category
import category_theory.yoneda
import category_theory.functor.reflects_isomorphisms

/-!
# Cones and cocones

We define `cone F`, a cone over a functor `F`,
and `F.cones : Cᵒᵖ ⥤ Type`, the functor associating to `X` the cones over `F` with cone point `X`.

A cone `c` is defined by specifying its cone point `c.X` and a natural transformation `c.π`
from the constant `c.X` valued functor to `F`.

We provide `c.w f : c.π.app j ≫ F.map f = c.π.app j'` for any `f : j ⟶ j'`
as a wrapper for `c.π.naturality f` avoiding unneeded identity morphisms.

We define `c.extend f`, where `c : cone F` and `f : Y ⟶ c.X` for some other `Y`,
which replaces the cone point by `Y` and inserts `f` into each of the components of the cone.
Similarly we have `c.whisker F` producing a `cone (E ⋙ F)`

We define morphisms of cones, and the category of cones.

We define `cone.postcompose α : cone F ⥤ cone G` for `α` a natural transformation `F ⟶ G`.

And, of course, we dualise all this to cocones as well.
-/

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

variables {J : Type u₁} [category.{v₁} J]
variables {K : Type u₂} [category.{v₂} K]
variables {C : Type u₃} [category.{v₃} C]
variables {D : Type u₄} [category.{v₄} D]

open category_theory
open category_theory.category
open category_theory.functor
open opposite

namespace category_theory

namespace functor
variables {J C} (F : J ⥤ C)

/--
`F.cones` is the functor assigning to an object `X` the type of
natural transformations from the constant functor with value `X` to `F`.
An object representing this functor is a limit of `F`.
-/
@[simps]
def cones : Cᵒᵖ ⥤ Type (max u₁ v₃) := (const J).op ⋙ yoneda.obj F

/--
`F.cocones` is the functor assigning to an object `X` the type of
natural transformations from `F` to the constant functor with value `X`.
An object corepresenting this functor is a colimit of `F`.
-/
@[simps]
def cocones : C ⥤ Type (max u₁ v₃) := const J ⋙ coyoneda.obj (op F)

end functor

section
variables (J C)

/--
Functorially associated to each functor `J ⥤ C`, we have the `C`-presheaf consisting of
cones with a given cone point.
-/
@[simps] def cones : (J ⥤ C) ⥤ (Cᵒᵖ ⥤ Type (max u₁ v₃)) :=
{ obj := functor.cones,
  map := λ F G f, whisker_left (const J).op (yoneda.map f) }

/--
Contravariantly associated to each functor `J ⥤ C`, we have the `C`-copresheaf consisting of
cocones with a given cocone point.
-/
@[simps] def cocones : (J ⥤ C)ᵒᵖ ⥤ (C ⥤ Type (max u₁ v₃)) :=
{ obj := λ F, functor.cocones (unop F),
  map := λ F G f, whisker_left (const J) (coyoneda.map f) }

end

namespace limits

section
local attribute [tidy] tactic.discrete_cases

/--
A `c : cone F` is:
* an object `c.X` and
* a natural transformation `c.π : c.X ⟶ F` from the constant `c.X` functor to `F`.

`cone F` is equivalent, via `cone.equiv` below, to `Σ X, F.cones.obj X`.
-/
structure cone (F : J ⥤ C) :=
(X : C)
(π : (const J).obj X ⟶ F)

instance inhabited_cone (F : discrete punit ⥤ C) : inhabited (cone F) :=
⟨{ X := F.obj ⟨⟨⟩⟩,
   π :=
   { app := λ ⟨⟨⟩⟩, 𝟙 _, }, }⟩

@[simp, reassoc] lemma cone.w {F : J ⥤ C} (c : cone F) {j j' : J} (f : j ⟶ j') :
  c.π.app j ≫ F.map f = c.π.app j' :=
by { rw ← c.π.naturality f, apply id_comp }

/--
A `c : cocone F` is
* an object `c.X` and
* a natural transformation `c.ι : F ⟶ c.X` from `F` to the constant `c.X` functor.

`cocone F` is equivalent, via `cone.equiv` below, to `Σ X, F.cocones.obj X`.
-/
structure cocone (F : J ⥤ C) :=
(X : C)
(ι : F ⟶ (const J).obj X)

instance inhabited_cocone (F : discrete punit ⥤ C) : inhabited (cocone F) :=
⟨{ X := F.obj ⟨⟨⟩⟩,
   ι :=
  { app := λ ⟨⟨⟩⟩, 𝟙 _, }, }⟩

@[simp, reassoc] lemma cocone.w {F : J ⥤ C} (c : cocone F) {j j' : J} (f : j ⟶ j') :
  F.map f ≫ c.ι.app j' = c.ι.app j :=
by { rw c.ι.naturality f, apply comp_id }

end

variables {F : J ⥤ C}

namespace cone

/-- The isomorphism between a cone on `F` and an element of the functor `F.cones`. -/
@[simps]
def equiv (F : J ⥤ C) : cone F ≅ Σ X, F.cones.obj X :=
{ hom := λ c, ⟨op c.X, c.π⟩,
  inv := λ c, { X := c.1.unop, π := c.2 },
  hom_inv_id' := by { ext1, cases x, refl },
  inv_hom_id' := by { ext1, cases x, refl } }

/-- A map to the vertex of a cone naturally induces a cone by composition. -/
@[simps] def extensions (c : cone F) :
  yoneda.obj c.X ⋙ ulift_functor.{u₁} ⟶ F.cones :=
{ app := λ X f, (const J).map f.down ≫ c.π }

/-- A map to the vertex of a cone induces a cone by composition. -/
@[simps] def extend (c : cone F) {X : C} (f : X ⟶ c.X) : cone F :=
{ X := X,
  π := c.extensions.app (op X) ⟨f⟩ }

/-- Whisker a cone by precomposition of a functor. -/
@[simps] def whisker (E : K ⥤ J) (c : cone F) : cone (E ⋙ F) :=
{ X := c.X,
  π := whisker_left E c.π }

end cone

namespace cocone

/-- The isomorphism between a cocone on `F` and an element of the functor `F.cocones`. -/
def equiv (F : J ⥤ C) : cocone F ≅ Σ X, F.cocones.obj X :=
{ hom := λ c, ⟨c.X, c.ι⟩,
  inv := λ c, { X := c.1, ι := c.2 },
  hom_inv_id' := by { ext1, cases x, refl },
  inv_hom_id' := by { ext1, cases x, refl } }

/-- A map from the vertex of a cocone naturally induces a cocone by composition. -/
@[simps] def extensions (c : cocone F) : coyoneda.obj (op c.X) ⋙ ulift_functor.{u₁} ⟶ F.cocones :=
{ app := λ X f, c.ι ≫ (const J).map f.down }

/-- A map from the vertex of a cocone induces a cocone by composition. -/
@[simps] def extend (c : cocone F) {X : C} (f : c.X ⟶ X) : cocone F :=
{ X := X,
  ι := c.extensions.app X ⟨f⟩ }

/--
Whisker a cocone by precomposition of a functor. See `whiskering` for a functorial
version.
-/
@[simps] def whisker (E : K ⥤ J) (c : cocone F) : cocone (E ⋙ F) :=
{ X := c.X,
  ι := whisker_left E c.ι }

end cocone

/-- A cone morphism between two cones for the same diagram is a morphism of the cone points which
commutes with the cone legs. -/
@[ext] structure cone_morphism (A B : cone F) :=
(hom : A.X ⟶ B.X)
(w'  : ∀ j : J, hom ≫ B.π.app j = A.π.app j . obviously)

restate_axiom cone_morphism.w'
attribute [simp, reassoc] cone_morphism.w

instance inhabited_cone_morphism (A : cone F) : inhabited (cone_morphism A A) :=
⟨{ hom := 𝟙 _ }⟩

/-- The category of cones on a given diagram. -/
@[simps] instance cone.category : category (cone F) :=
{ hom  := λ A B, cone_morphism A B,
  comp := λ X Y Z f g, { hom := f.hom ≫ g.hom },
  id   := λ B, { hom := 𝟙 B.X } }

namespace cones
/-- To give an isomorphism between cones, it suffices to give an
  isomorphism between their vertices which commutes with the cone
  maps. -/
@[ext, simps] def ext {c c' : cone F}
  (φ : c.X ≅ c'.X) (w : ∀ j, c.π.app j = φ.hom ≫ c'.π.app j) : c ≅ c' :=
{ hom := { hom := φ.hom },
  inv := { hom := φ.inv, w' := λ j, φ.inv_comp_eq.mpr (w j) } }

/--
Given a cone morphism whose object part is an isomorphism, produce an
isomorphism of cones.
-/
lemma cone_iso_of_hom_iso {K : J ⥤ C} {c d : cone K} (f : c ⟶ d) [i : is_iso f.hom] :
  is_iso f :=
⟨⟨{ hom := inv f.hom,
    w' := λ j, (as_iso f.hom).inv_comp_eq.2 (f.w j).symm }, by tidy⟩⟩

/--
Functorially postcompose a cone for `F` by a natural transformation `F ⟶ G` to give a cone for `G`.
-/
@[simps] def postcompose {G : J ⥤ C} (α : F ⟶ G) : cone F ⥤ cone G :=
{ obj := λ c, { X := c.X, π := c.π ≫ α },
  map := λ c₁ c₂ f, { hom := f.hom } }

/-- Postcomposing a cone by the composite natural transformation `α ≫ β` is the same as
postcomposing by `α` and then by `β`. -/
@[simps]
def postcompose_comp {G H : J ⥤ C} (α : F ⟶ G) (β : G ⟶ H) :
  postcompose (α ≫ β) ≅ postcompose α ⋙ postcompose β :=
nat_iso.of_components (λ s, cones.ext (iso.refl _) (by tidy)) (by tidy)

/-- Postcomposing by the identity does not change the cone up to isomorphism. -/
@[simps]
def postcompose_id : postcompose (𝟙 F) ≅ 𝟭 (cone F) :=
nat_iso.of_components (λ s, cones.ext (iso.refl _) (by tidy)) (by tidy)

/--
If `F` and `G` are naturally isomorphic functors, then they have equivalent categories of
cones.
-/
@[simps]
def postcompose_equivalence {G : J ⥤ C} (α : F ≅ G) : cone F ≌ cone G :=
{ functor := postcompose α.hom,
  inverse := postcompose α.inv,
  unit_iso := nat_iso.of_components (λ s, cones.ext (iso.refl _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ s, cones.ext (iso.refl _) (by tidy)) (by tidy) }

/--
Whiskering on the left by `E : K ⥤ J` gives a functor from `cone F` to `cone (E ⋙ F)`.
-/
@[simps]
def whiskering (E : K ⥤ J) : cone F ⥤ cone (E ⋙ F) :=
{ obj := λ c, c.whisker E,
  map := λ c c' f, { hom := f.hom } }

/--
Whiskering by an equivalence gives an equivalence between categories of cones.
-/
@[simps]
def whiskering_equivalence (e : K ≌ J) :
  cone F ≌ cone (e.functor ⋙ F) :=
{ functor := whiskering e.functor,
  inverse := whiskering e.inverse ⋙ postcompose (e.inv_fun_id_assoc F).hom,
  unit_iso := nat_iso.of_components (λ s, cones.ext (iso.refl _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ s, cones.ext (iso.refl _)
  (begin
    intro k,
    dsimp, -- See library note [dsimp, simp]
    simpa [e.counit_app_functor] using s.w (e.unit_inv.app k),
  end)) (by tidy), }

/--
The categories of cones over `F` and `G` are equivalent if `F` and `G` are naturally isomorphic
(possibly after changing the indexing category by an equivalence).
-/
@[simps functor inverse unit_iso counit_iso]
def equivalence_of_reindexing {G : K ⥤ C}
  (e : K ≌ J) (α : e.functor ⋙ F ≅ G) : cone F ≌ cone G :=
(whiskering_equivalence e).trans (postcompose_equivalence α)

section
variable (F)

/-- Forget the cone structure and obtain just the cone point. -/
@[simps]
def forget : cone F ⥤ C :=
{ obj := λ t, t.X, map := λ s t f, f.hom }

variables (G : C ⥤ D)

/-- A functor `G : C ⥤ D` sends cones over `F` to cones over `F ⋙ G` functorially. -/
@[simps] def functoriality : cone F ⥤ cone (F ⋙ G) :=
{ obj := λ A,
  { X := G.obj A.X,
    π := { app := λ j, G.map (A.π.app j), naturality' := by intros; erw ←G.map_comp; tidy } },
  map := λ X Y f,
  { hom := G.map f.hom,
    w' := λ j, by simp [-cone_morphism.w, ←f.w j] } }

instance functoriality_full [full G] [faithful G] : full (functoriality F G) :=
{ preimage := λ X Y t,
  { hom := G.preimage t.hom,
    w' := λ j, G.map_injective (by simpa using t.w j) } }

instance functoriality_faithful [faithful G] : faithful (cones.functoriality F G) :=
{ map_injective' := λ X Y f g e, by { ext1, injection e, apply G.map_injective h_1 } }

/--
If `e : C ≌ D` is an equivalence of categories, then `functoriality F e.functor` induces an
equivalence between cones over `F` and cones over `F ⋙ e.functor`.
-/
@[simps]
def functoriality_equivalence (e : C ≌ D) : cone F ≌ cone (F ⋙ e.functor) :=
let f : (F ⋙ e.functor) ⋙ e.inverse ≅ F :=
  functor.associator _ _ _ ≪≫ iso_whisker_left _ (e.unit_iso).symm ≪≫ functor.right_unitor _ in
{ functor := functoriality F e.functor,
  inverse := (functoriality (F ⋙ e.functor) e.inverse) ⋙
    (postcompose_equivalence f).functor,
  unit_iso := nat_iso.of_components (λ c, cones.ext (e.unit_iso.app _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ c, cones.ext (e.counit_iso.app _) (by tidy)) (by tidy), }

/--
If `F` reflects isomorphisms, then `cones.functoriality F` reflects isomorphisms
as well.
-/
instance reflects_cone_isomorphism (F : C ⥤ D) [reflects_isomorphisms F] (K : J ⥤ C) :
  reflects_isomorphisms (cones.functoriality K F) :=
begin
  constructor,
  introsI,
  haveI : is_iso (F.map f.hom) :=
    (cones.forget (K ⋙ F)).map_is_iso ((cones.functoriality K F).map f),
  haveI := reflects_isomorphisms.reflects F f.hom,
  apply cone_iso_of_hom_iso
end

end

end cones

/-- A cocone morphism between two cocones for the same diagram is a morphism of the cocone points
which commutes with the cocone legs. -/
@[ext] structure cocone_morphism (A B : cocone F) :=
(hom : A.X ⟶ B.X)
(w'  : ∀ j : J, A.ι.app j ≫ hom = B.ι.app j . obviously)

instance inhabited_cocone_morphism (A : cocone F) : inhabited (cocone_morphism A A) :=
⟨{ hom := 𝟙 _ }⟩

restate_axiom cocone_morphism.w'
attribute [simp, reassoc] cocone_morphism.w

@[simps] instance cocone.category : category (cocone F) :=
{ hom  := λ A B, cocone_morphism A B,
  comp := λ _ _ _ f g,
  { hom := f.hom ≫ g.hom },
  id   := λ B, { hom := 𝟙 B.X } }

namespace cocones
/-- To give an isomorphism between cocones, it suffices to give an
  isomorphism between their vertices which commutes with the cocone
  maps. -/
@[ext, simps] def ext {c c' : cocone F}
  (φ : c.X ≅ c'.X) (w : ∀ j, c.ι.app j ≫ φ.hom = c'.ι.app j) : c ≅ c' :=
{ hom := { hom := φ.hom },
  inv := { hom := φ.inv, w' := λ j, φ.comp_inv_eq.mpr (w j).symm } }

/--
Given a cocone morphism whose object part is an isomorphism, produce an
isomorphism of cocones.
-/
lemma cocone_iso_of_hom_iso {K : J ⥤ C} {c d : cocone K} (f : c ⟶ d) [i : is_iso f.hom] :
  is_iso f :=
⟨⟨{ hom := inv f.hom,
    w' := λ j, (as_iso f.hom).comp_inv_eq.2 (f.w j).symm }, by tidy⟩⟩

/-- Functorially precompose a cocone for `F` by a natural transformation `G ⟶ F` to give a cocone
for `G`. -/
@[simps] def precompose {G : J ⥤ C} (α : G ⟶ F) : cocone F ⥤ cocone G :=
{ obj := λ c, { X := c.X, ι := α ≫ c.ι },
  map := λ c₁ c₂ f, { hom := f.hom } }

/-- Precomposing a cocone by the composite natural transformation `α ≫ β` is the same as
precomposing by `β` and then by `α`. -/
def precompose_comp {G H : J ⥤ C} (α : F ⟶ G) (β : G ⟶ H) :
  precompose (α ≫ β) ≅ precompose β ⋙ precompose α :=
nat_iso.of_components (λ s, cocones.ext (iso.refl _) (by tidy)) (by tidy)

/-- Precomposing by the identity does not change the cocone up to isomorphism. -/
def precompose_id : precompose (𝟙 F) ≅ 𝟭 (cocone F) :=
nat_iso.of_components (λ s, cocones.ext (iso.refl _) (by tidy)) (by tidy)

/--
If `F` and `G` are naturally isomorphic functors, then they have equivalent categories of
cocones.
-/
@[simps]
def precompose_equivalence {G : J ⥤ C} (α : G ≅ F) : cocone F ≌ cocone G :=
{ functor := precompose α.hom,
  inverse := precompose α.inv,
  unit_iso := nat_iso.of_components (λ s, cocones.ext (iso.refl _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ s, cocones.ext (iso.refl _) (by tidy)) (by tidy) }

/--
Whiskering on the left by `E : K ⥤ J` gives a functor from `cocone F` to `cocone (E ⋙ F)`.
-/
@[simps]
def whiskering (E : K ⥤ J) : cocone F ⥤ cocone (E ⋙ F) :=
{ obj := λ c, c.whisker E,
  map := λ c c' f, { hom := f.hom, } }

/--
Whiskering by an equivalence gives an equivalence between categories of cones.
-/
@[simps]
def whiskering_equivalence (e : K ≌ J) :
  cocone F ≌ cocone (e.functor ⋙ F) :=
{ functor := whiskering e.functor,
  inverse := whiskering e.inverse ⋙
    precompose ((functor.left_unitor F).inv ≫ (whisker_right (e.counit_iso).inv F) ≫
      (functor.associator _ _ _).inv),
  unit_iso := nat_iso.of_components (λ s, cocones.ext (iso.refl _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ s, cocones.ext (iso.refl _)
  (begin
    intro k,
    dsimp,
    simpa [e.counit_inv_app_functor k] using s.w (e.unit.app k),
  end)) (by tidy), }

/--
The categories of cocones over `F` and `G` are equivalent if `F` and `G` are naturally isomorphic
(possibly after changing the indexing category by an equivalence).
-/
@[simps functor_obj]
def equivalence_of_reindexing {G : K ⥤ C}
  (e : K ≌ J) (α : e.functor ⋙ F ≅ G) : cocone F ≌ cocone G :=
(whiskering_equivalence e).trans (precompose_equivalence α.symm)

section
variable (F)

/-- Forget the cocone structure and obtain just the cocone point. -/
@[simps]
def forget : cocone F ⥤ C :=
{ obj := λ t, t.X, map := λ s t f, f.hom }

variables (G : C ⥤ D)

/-- A functor `G : C ⥤ D` sends cocones over `F` to cocones over `F ⋙ G` functorially. -/
@[simps] def functoriality : cocone F ⥤ cocone (F ⋙ G) :=
{ obj := λ A,
  { X := G.obj A.X,
    ι := { app := λ j, G.map (A.ι.app j), naturality' := by intros; erw ←G.map_comp; tidy } },
  map := λ _ _ f,
  { hom := G.map f.hom,
    w'  := by intros; rw [←functor.map_comp, cocone_morphism.w] } }

instance functoriality_full [full G] [faithful G] : full (functoriality F G) :=
{ preimage := λ X Y t,
  { hom := G.preimage t.hom,
    w' := λ j, G.map_injective (by simpa using t.w j) } }

instance functoriality_faithful [faithful G] : faithful (functoriality F G) :=
{ map_injective' := λ X Y f g e, by { ext1, injection e, apply G.map_injective h_1 } }

/--
If `e : C ≌ D` is an equivalence of categories, then `functoriality F e.functor` induces an
equivalence between cocones over `F` and cocones over `F ⋙ e.functor`.
-/
@[simps]
def functoriality_equivalence (e : C ≌ D) : cocone F ≌ cocone (F ⋙ e.functor) :=
let f : (F ⋙ e.functor) ⋙ e.inverse ≅ F :=
  functor.associator _ _ _ ≪≫ iso_whisker_left _ (e.unit_iso).symm ≪≫ functor.right_unitor _ in
{ functor := functoriality F e.functor,
  inverse := (functoriality (F ⋙ e.functor) e.inverse) ⋙
    (precompose_equivalence f.symm).functor,
  unit_iso := nat_iso.of_components (λ c, cocones.ext (e.unit_iso.app _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ c, cocones.ext (e.counit_iso.app _)
  begin
    -- Unfortunately this doesn't work by `tidy`.
    -- In this configuration `simp` reaches a dead-end and needs help.
    intros j,
    dsimp,
    simp only [←equivalence.counit_inv_app_functor, iso.inv_hom_id_app, map_comp,
      equivalence.fun_inv_map, assoc, id_comp, iso.inv_hom_id_app_assoc],
    dsimp, simp, -- See note [dsimp, simp].
  end)
  (λ c c' f, by { ext, dsimp, simp, dsimp, simp, }), }

/--
If `F` reflects isomorphisms, then `cocones.functoriality F` reflects isomorphisms
as well.
-/
instance reflects_cocone_isomorphism (F : C ⥤ D) [reflects_isomorphisms F] (K : J ⥤ C) :
  reflects_isomorphisms (cocones.functoriality K F) :=
begin
  constructor,
  introsI,
  haveI : is_iso (F.map f.hom) :=
    (cocones.forget (K ⋙ F)).map_is_iso ((cocones.functoriality K F).map f),
  haveI := reflects_isomorphisms.reflects F f.hom,
  apply cocone_iso_of_hom_iso
end

end
end cocones

end limits

namespace functor

variables {F : J ⥤ C} {G : J ⥤ C} (H : C ⥤ D)

open category_theory.limits

/-- The image of a cone in C under a functor G : C ⥤ D is a cone in D. -/
@[simps]
def map_cone   (c : cone F)   : cone (F ⋙ H)   := (cones.functoriality F H).obj c
/-- The image of a cocone in C under a functor G : C ⥤ D is a cocone in D. -/
@[simps]
def map_cocone (c : cocone F) : cocone (F ⋙ H) := (cocones.functoriality F H).obj c

/-- Given a cone morphism `c ⟶ c'`, construct a cone morphism on the mapped cones functorially.  -/
def map_cone_morphism   {c c' : cone F}   (f : c ⟶ c')   :
  H.map_cone c ⟶ H.map_cone c' := (cones.functoriality F H).map f

/-- Given a cocone morphism `c ⟶ c'`, construct a cocone morphism on the mapped cocones
functorially. -/
def map_cocone_morphism {c c' : cocone F} (f : c ⟶ c') :
  H.map_cocone c ⟶ H.map_cocone c' := (cocones.functoriality F H).map f

/-- If `H` is an equivalence, we invert `H.map_cone` and get a cone for `F` from a cone
for `F ⋙ H`.-/
def map_cone_inv [is_equivalence H]
  (c : cone (F ⋙ H)) : cone F :=
(limits.cones.functoriality_equivalence F (as_equivalence H)).inverse.obj c

/-- `map_cone` is the left inverse to `map_cone_inv`. -/
def map_cone_map_cone_inv {F : J ⥤ D} (H : D ⥤ C) [is_equivalence H] (c : cone (F ⋙ H)) :
  map_cone H (map_cone_inv H c) ≅ c :=
(limits.cones.functoriality_equivalence F (as_equivalence H)).counit_iso.app c

/-- `map_cone` is the right inverse to `map_cone_inv`. -/
def map_cone_inv_map_cone {F : J ⥤ D} (H : D ⥤ C) [is_equivalence H] (c : cone F) :
  map_cone_inv H (map_cone H c) ≅ c :=
(limits.cones.functoriality_equivalence F (as_equivalence H)).unit_iso.symm.app c
/-- If `H` is an equivalence, we invert `H.map_cone` and get a cone for `F` from a cone
for `F ⋙ H`.-/

def map_cocone_inv [is_equivalence H]
  (c : cocone (F ⋙ H)) : cocone F :=
(limits.cocones.functoriality_equivalence F (as_equivalence H)).inverse.obj c

/-- `map_cocone` is the left inverse to `map_cocone_inv`. -/
def map_cocone_map_cocone_inv {F : J ⥤ D} (H : D ⥤ C) [is_equivalence H] (c : cocone (F ⋙ H)) :
  map_cocone H (map_cocone_inv H c) ≅ c :=
(limits.cocones.functoriality_equivalence F (as_equivalence H)).counit_iso.app c

/-- `map_cocone` is the right inverse to `map_cocone_inv`. -/
def map_cocone_inv_map_cocone {F : J ⥤ D} (H : D ⥤ C) [is_equivalence H] (c : cocone F) :
  map_cocone_inv H (map_cocone H c) ≅ c :=
(limits.cocones.functoriality_equivalence F (as_equivalence H)).unit_iso.symm.app c

/-- `functoriality F _ ⋙ postcompose (whisker_left F _)` simplifies to `functoriality F _`. -/
@[simps]
def functoriality_comp_postcompose {H H' : C ⥤ D} (α : H ≅ H') :
  cones.functoriality F H ⋙ cones.postcompose (whisker_left F α.hom) ≅ cones.functoriality F H' :=
nat_iso.of_components (λ c, cones.ext (α.app _) (by tidy)) (by tidy)

/--
For `F : J ⥤ C`, given a cone `c : cone F`, and a natural isomorphism `α : H ≅ H'` for functors
`H H' : C ⥤ D`, the postcomposition of the cone `H.map_cone` using the isomorphism `α` is
isomorphic to the cone `H'.map_cone`.
-/
@[simps]
def postcompose_whisker_left_map_cone {H H' : C ⥤ D} (α : H ≅ H') (c : cone F) :
  (cones.postcompose (whisker_left F α.hom : _)).obj (H.map_cone c) ≅ H'.map_cone c :=
(functoriality_comp_postcompose α).app c

/--
`map_cone` commutes with `postcompose`. In particular, for `F : J ⥤ C`, given a cone `c : cone F`, a
natural transformation `α : F ⟶ G` and a functor `H : C ⥤ D`, we have two obvious ways of producing
a cone over `G ⋙ H`, and they are both isomorphic.
-/
@[simps]
def map_cone_postcompose {α : F ⟶ G} {c} :
  H.map_cone ((cones.postcompose α).obj c) ≅
  (cones.postcompose (whisker_right α H : _)).obj (H.map_cone c) :=
cones.ext (iso.refl _) (by tidy)

/--
`map_cone` commutes with `postcompose_equivalence`
-/
@[simps]
def map_cone_postcompose_equivalence_functor {α : F ≅ G} {c} :
  H.map_cone ((cones.postcompose_equivalence α).functor.obj c) ≅
    (cones.postcompose_equivalence (iso_whisker_right α H : _)).functor.obj (H.map_cone c) :=
cones.ext (iso.refl _) (by tidy)

/-- `functoriality F _ ⋙ precompose (whisker_left F _)` simplifies to `functoriality F _`. -/
@[simps]
def functoriality_comp_precompose {H H' : C ⥤ D} (α : H ≅ H') :
   cocones.functoriality F H ⋙ cocones.precompose (whisker_left F α.inv)
 ≅ cocones.functoriality F H' :=
nat_iso.of_components (λ c, cocones.ext (α.app _) (by tidy)) (by tidy)

/--
For `F : J ⥤ C`, given a cocone `c : cocone F`, and a natural isomorphism `α : H ≅ H'` for functors
`H H' : C ⥤ D`, the precomposition of the cocone `H.map_cocone` using the isomorphism `α` is
isomorphic to the cocone `H'.map_cocone`.
-/
@[simps]
def precompose_whisker_left_map_cocone {H H' : C ⥤ D} (α : H ≅ H') (c : cocone F) :
  (cocones.precompose (whisker_left F α.inv : _)).obj (H.map_cocone c) ≅ H'.map_cocone c :=
(functoriality_comp_precompose α).app c

/--
`map_cocone` commutes with `precompose`. In particular, for `F : J ⥤ C`, given a cocone
`c : cocone F`, a natural transformation `α : F ⟶ G` and a functor `H : C ⥤ D`, we have two obvious
ways of producing a cocone over `G ⋙ H`, and they are both isomorphic.
-/
@[simps]
def map_cocone_precompose {α : F ⟶ G} {c} :
  H.map_cocone ((cocones.precompose α).obj c) ≅
  (cocones.precompose (whisker_right α H : _)).obj (H.map_cocone c) :=
cocones.ext (iso.refl _) (by tidy)

/--
`map_cocone` commutes with `precompose_equivalence`
-/
@[simps]
def map_cocone_precompose_equivalence_functor {α : F ≅ G} {c} :
  H.map_cocone ((cocones.precompose_equivalence α).functor.obj c) ≅
    (cocones.precompose_equivalence (iso_whisker_right α H : _)).functor.obj (H.map_cocone c) :=
cocones.ext (iso.refl _) (by tidy)

/--
`map_cone` commutes with `whisker`
-/
@[simps]
def map_cone_whisker {E : K ⥤ J} {c : cone F} :
  H.map_cone (c.whisker E) ≅ (H.map_cone c).whisker E :=
cones.ext (iso.refl _) (by tidy)

/--
`map_cocone` commutes with `whisker`
-/
@[simps]
def map_cocone_whisker {E : K ⥤ J} {c : cocone F} :
  H.map_cocone (c.whisker E) ≅ (H.map_cocone c).whisker E :=
cocones.ext (iso.refl _) (by tidy)

end functor

end category_theory

namespace category_theory.limits

section
variables {F : J ⥤ C}

/-- Change a `cocone F` into a `cone F.op`. -/
@[simps] def cocone.op (c : cocone F) : cone F.op :=
{ X := op c.X,
  π := nat_trans.op c.ι }

/-- Change a `cone F` into a `cocone F.op`. -/
@[simps] def cone.op (c : cone F) : cocone F.op :=
{ X := op c.X,
  ι := nat_trans.op c.π }

/-- Change a `cocone F.op` into a `cone F`. -/
@[simps] def cocone.unop (c : cocone F.op) : cone F :=
{ X := unop c.X,
  π := nat_trans.remove_op c.ι }

/-- Change a `cone F.op` into a `cocone F`. -/
@[simps] def cone.unop (c : cone F.op) : cocone F :=
{ X := unop c.X,
  ι := nat_trans.remove_op c.π }

variables (F)

/--
The category of cocones on `F`
is equivalent to the opposite category of
the category of cones on the opposite of `F`.
-/
def cocone_equivalence_op_cone_op : cocone F ≌ (cone F.op)ᵒᵖ :=
{ functor :=
  { obj := λ c, op (cocone.op c),
    map := λ X Y f, quiver.hom.op
    { hom := f.hom.op,
      w' := λ j, by { apply quiver.hom.unop_inj, dsimp, simp, }, } },
  inverse :=
  { obj := λ c, cone.unop (unop c),
    map := λ X Y f,
    { hom := f.unop.hom.unop,
      w' := λ j, by { apply quiver.hom.op_inj, dsimp, simp, }, } },
  unit_iso := nat_iso.of_components (λ c, cocones.ext (iso.refl _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ c,
    by { induction c using opposite.rec,
         dsimp, apply iso.op, exact cones.ext (iso.refl _) (by tidy), })
    (λ X Y f, quiver.hom.unop_inj (cone_morphism.ext _ _ (by { dsimp, simp }))),
  functor_unit_iso_comp' := λ c, begin apply quiver.hom.unop_inj, ext, dsimp, simp, end }

attribute [simps] cocone_equivalence_op_cone_op

end

section
variables {F : J ⥤ Cᵒᵖ}

/-- Change a cocone on `F.left_op : Jᵒᵖ ⥤ C` to a cocone on `F : J ⥤ Cᵒᵖ`. -/
-- Here and below we only automatically generate the `@[simp]` lemma for the `X` field,
-- as we can write a simpler `rfl` lemma for the components of the natural transformation by hand.
@[simps {rhs_md := semireducible, simp_rhs := tt}]
def cone_of_cocone_left_op (c : cocone F.left_op) : cone F :=
{ X := op c.X,
  π := nat_trans.remove_left_op c.ι }

/-- Change a cone on `F : J ⥤ Cᵒᵖ` to a cocone on `F.left_op : Jᵒᵖ ⥤ C`. -/
@[simps {rhs_md := semireducible, simp_rhs := tt}]
def cocone_left_op_of_cone (c : cone F) : cocone (F.left_op) :=
{ X := unop c.X,
  ι := nat_trans.left_op c.π }

/-- Change a cone on `F.left_op : Jᵒᵖ ⥤ C` to a cocone on `F : J ⥤ Cᵒᵖ`. -/
/- When trying use `@[simps]` to generate the `ι_app` field of this definition, `@[simps]` tries to
  reduce the RHS using `expr.dsimp` and `expr.simp`, but for some reason the expression is not
  being simplified properly. -/
@[simps X]
def cocone_of_cone_left_op (c : cone F.left_op) : cocone F :=
{ X := op c.X,
  ι := nat_trans.remove_left_op c.π }

@[simp] lemma cocone_of_cone_left_op_ι_app (c : cone F.left_op) (j) :
  (cocone_of_cone_left_op c).ι.app j = (c.π.app (op j)).op :=
by { dsimp only [cocone_of_cone_left_op], simp }

/-- Change a cocone on `F : J ⥤ Cᵒᵖ` to a cone on `F.left_op : Jᵒᵖ ⥤ C`. -/
@[simps {rhs_md := semireducible, simp_rhs := tt}]
def cone_left_op_of_cocone (c : cocone F) : cone (F.left_op) :=
{ X := unop c.X,
  π := nat_trans.left_op c.ι }

end

section
variables {F : Jᵒᵖ ⥤ C}

/-- Change a cocone on `F.right_op : J ⥤ Cᵒᵖ` to a cone on `F : Jᵒᵖ ⥤ C`. -/
@[simps] def cone_of_cocone_right_op (c : cocone F.right_op) : cone F :=
{ X := unop c.X,
  π := nat_trans.remove_right_op c.ι }

/-- Change a cone on `F : Jᵒᵖ ⥤ C` to a cocone on `F.right_op : Jᵒᵖ ⥤ C`. -/
@[simps] def cocone_right_op_of_cone (c : cone F) : cocone (F.right_op) :=
{ X := op c.X,
  ι := nat_trans.right_op c.π }

/-- Change a cone on `F.right_op : J ⥤ Cᵒᵖ` to a cocone on `F : Jᵒᵖ ⥤ C`. -/
@[simps] def cocone_of_cone_right_op (c : cone F.right_op) : cocone F :=
{ X := unop c.X,
  ι := nat_trans.remove_right_op c.π }

/-- Change a cocone on `F : Jᵒᵖ ⥤ C` to a cone on `F.right_op : J ⥤ Cᵒᵖ`. -/
@[simps] def cone_right_op_of_cocone (c : cocone F) : cone (F.right_op) :=
{ X := op c.X,
  π := nat_trans.right_op c.ι }

end

section
variables {F : Jᵒᵖ ⥤ Cᵒᵖ}

/-- Change a cocone on `F.unop : J ⥤ C` into a cone on `F : Jᵒᵖ ⥤ Cᵒᵖ`. -/
@[simps] def cone_of_cocone_unop (c : cocone F.unop) : cone F :=
{ X := op c.X,
  π := nat_trans.remove_unop c.ι }

/-- Change a cone on `F : Jᵒᵖ ⥤ Cᵒᵖ` into a cocone on `F.unop : J ⥤ C`. -/
@[simps] def cocone_unop_of_cone (c : cone F) : cocone F.unop :=
{ X := unop c.X,
  ι := nat_trans.unop c.π }

/-- Change a cone on `F.unop : J ⥤ C` into a cocone on `F : Jᵒᵖ ⥤ Cᵒᵖ`. -/
@[simps] def cocone_of_cone_unop (c : cone F.unop) : cocone F :=
{ X := op c.X,
  ι := nat_trans.remove_unop c.π }

/-- Change a cocone on `F : Jᵒᵖ ⥤ Cᵒᵖ` into a cone on `F.unop : J ⥤ C`. -/
@[simps] def cone_unop_of_cocone (c : cocone F) : cone F.unop :=
{ X := unop c.X,
  π := nat_trans.unop c.ι }

end

end category_theory.limits

namespace category_theory.functor

open category_theory.limits

variables {F : J ⥤ C}

section
variables (G : C ⥤ D)

/-- The opposite cocone of the image of a cone is the image of the opposite cocone. -/
@[simps {rhs_md := semireducible}]
def map_cone_op (t : cone F) : (G.map_cone t).op ≅ (G.op.map_cocone t.op) :=
cocones.ext (iso.refl _) (by tidy)

/-- The opposite cone of the image of a cocone is the image of the opposite cone. -/
@[simps {rhs_md := semireducible}]
def map_cocone_op {t : cocone F} : (G.map_cocone t).op ≅ (G.op.map_cone t.op) :=
cones.ext (iso.refl _) (by tidy)

end

end category_theory.functor


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

/-!
# Factoring through subobjects

The predicate `h : P.factors f`, for `P : subobject Y` and `f : X ⟶ Y`
asserts the existence of some `P.factor_thru f : X ⟶ (P : C)` making the obvious diagram commute.

-/

universes v₁ v₂ u₁ u₂

noncomputable theory

open category_theory category_theory.category category_theory.limits

variables {C : Type u₁} [category.{v₁} C] {X Y Z : C}
variables {D : Type u₂} [category.{v₂} D]

namespace category_theory

namespace mono_over

/-- When `f : X ⟶ Y` and `P : mono_over Y`,
`P.factors f` expresses that there exists a factorisation of `f` through `P`.
Given `h : P.factors f`, you can recover the morphism as `P.factor_thru f h`.
-/
def factors {X Y : C} (P : mono_over Y) (f : X ⟶ Y) : Prop := ∃ g : X ⟶ P.val.left, g ≫ P.arrow = f

lemma factors_congr {X : C} {f g : mono_over X} {Y : C} (h : Y ⟶ X) (e : f ≅ g) :
  f.factors h ↔ g.factors h :=
⟨λ ⟨u, hu⟩, ⟨u ≫ (((mono_over.forget _).map e.hom)).left, by simp [hu]⟩,
 λ ⟨u, hu⟩, ⟨u ≫ (((mono_over.forget _).map e.inv)).left, by simp [hu]⟩⟩

/-- `P.factor_thru f h` provides a factorisation of `f : X ⟶ Y` through some `P : mono_over Y`,
given the evidence `h : P.factors f` that such a factorisation exists. -/
def factor_thru {X Y : C} (P : mono_over Y) (f : X ⟶ Y) (h : factors P f) : X ⟶ P.val.left :=
classical.some h

end mono_over

namespace subobject

/-- When `f : X ⟶ Y` and `P : subobject Y`,
`P.factors f` expresses that there exists a factorisation of `f` through `P`.
Given `h : P.factors f`, you can recover the morphism as `P.factor_thru f h`.
-/
def factors {X Y : C} (P : subobject Y) (f : X ⟶ Y) : Prop :=
quotient.lift_on' P (λ P, P.factors f)
begin
  rintros P Q ⟨h⟩,
  apply propext,
  split,
  { rintro ⟨i, w⟩,
    exact ⟨i ≫ h.hom.left, by erw [category.assoc, over.w h.hom, w]⟩, },
  { rintro ⟨i, w⟩,
    exact ⟨i ≫ h.inv.left, by erw [category.assoc, over.w h.inv, w]⟩, },
end

@[simp]

lemma factors_iff {X Y : C} (P : subobject Y) (f : X ⟶ Y) :
  P.factors f ↔ (representative.obj P).factors f :=
quot.induction_on P $ λ a, mono_over.factors_congr _ (representative_iso _).symm

lemma factors_self {X : C} (P : subobject X) : P.factors P.arrow :=
(factors_iff _ _).mpr ⟨𝟙 P, (by simp)⟩

lemma factors_comp_arrow {X Y : C} {P : subobject Y} (f : X ⟶ P) : P.factors (f ≫ P.arrow) :=
(factors_iff _ _).mpr ⟨f, rfl⟩

lemma factors_of_factors_right {X Y Z : C} {P : subobject Z} (f : X ⟶ Y) {g : Y ⟶ Z}
  (h : P.factors g) : P.factors (f ≫ g) :=
begin
  revert P,
  refine quotient.ind' _,
  intro P,
  rintro ⟨g, rfl⟩,
  exact ⟨f ≫ g, by simp⟩,
end

lemma factors_zero [has_zero_morphisms C] {X Y : C} {P : subobject Y} :
  P.factors (0 : X ⟶ Y) :=
(factors_iff _ _).mpr ⟨0, by simp⟩

lemma factors_of_le {Y Z : C} {P Q : subobject Y} (f : Z ⟶ Y) (h : P ≤ Q) :
  P.factors f → Q.factors f :=
by { simp only [factors_iff], exact λ ⟨u, hu⟩, ⟨u ≫ of_le _ _ h, by simp [←hu]⟩ }

/-- `P.factor_thru f h` provides a factorisation of `f : X ⟶ Y` through some `P : subobject Y`,
given the evidence `h : P.factors f` that such a factorisation exists. -/
def factor_thru {X Y : C} (P : subobject Y) (f : X ⟶ Y) (h : factors P f) : X ⟶ P :=
classical.some ((factors_iff _ _).mp h)

@[simp, reassoc] lemma factor_thru_arrow {X Y : C} (P : subobject Y) (f : X ⟶ Y) (h : factors P f) :
  P.factor_thru f h ≫ P.arrow = f :=
classical.some_spec ((factors_iff _ _).mp h)

@[simp] lemma factor_thru_self {X : C} (P : subobject X) (h) :
  P.factor_thru P.arrow h = 𝟙 P :=
by { ext, simp, }

@[simp] lemma factor_thru_comp_arrow {X Y : C} {P : subobject Y} (f : X ⟶ P) (h) :
  P.factor_thru (f ≫ P.arrow) h = f :=
by { ext, simp, }

@[simp] lemma factor_thru_eq_zero [has_zero_morphisms C]
  {X Y : C} {P : subobject Y} {f : X ⟶ Y} {h : factors P f} :
  P.factor_thru f h = 0 ↔ f = 0 :=
begin
  fsplit,
  { intro w,
    replace w := w =≫ P.arrow,
    simpa using w, },
  { rintro rfl,
    ext, simp, },
end

@[simp]
lemma factor_thru_right {X Y Z : C} {P : subobject Z} (f : X ⟶ Y) (g : Y ⟶ Z) (h : P.factors g) :
  f ≫ P.factor_thru g h = P.factor_thru (f ≫ g) (factors_of_factors_right f h) :=
begin
  apply (cancel_mono P.arrow).mp,
  simp,
end

@[simp]
lemma factor_thru_zero
  [has_zero_morphisms C] {X Y : C} {P : subobject Y} (h : P.factors (0 : X ⟶ Y)) :
  P.factor_thru 0 h = 0 :=
by simp

-- `h` is an explicit argument here so we can use
-- `rw ←factor_thru_le h`, obtaining a subgoal `P.factors f`.
@[simp]
lemma factor_thru_comp_of_le
  {Y Z : C} {P Q : subobject Y} {f : Z ⟶ Y} (h : P ≤ Q) (w : P.factors f) :
  P.factor_thru f w ≫ of_le P Q h = Q.factor_thru f (factors_of_le f h w) :=
by { ext, simp, }

section preadditive

variables [preadditive C]

lemma factors_add {X Y : C} {P : subobject Y} (f g : X ⟶ Y) (wf : P.factors f) (wg : P.factors g) :
  P.factors (f + g) :=
(factors_iff _ _).mpr ⟨P.factor_thru f wf + P.factor_thru g wg, by simp⟩

-- This can't be a `simp` lemma as `wf` and `wg` may not exist.
-- However you can `rw` by it to assert that `f` and `g` factor through `P` separately.
lemma factor_thru_add {X Y : C} {P : subobject Y} (f g : X ⟶ Y)
   (w : P.factors (f + g)) (wf : P.factors f) (wg : P.factors g) :
  P.factor_thru (f + g) w = P.factor_thru f wf + P.factor_thru g wg :=
by { ext, simp, }

lemma factors_left_of_factors_add {X Y : C} {P : subobject Y} (f g : X ⟶ Y)
  (w : P.factors (f + g)) (wg : P.factors g) : P.factors f :=
(factors_iff _ _).mpr ⟨P.factor_thru (f + g) w - P.factor_thru g wg, by simp⟩

@[simp]
lemma factor_thru_add_sub_factor_thru_right {X Y : C} {P : subobject Y} (f g : X ⟶ Y)
  (w : P.factors (f + g)) (wg : P.factors g) :
  P.factor_thru (f + g) w - P.factor_thru g wg =
    P.factor_thru f (factors_left_of_factors_add f g w wg) :=
by { ext, simp, }

lemma factors_right_of_factors_add {X Y : C} {P : subobject Y} (f g : X ⟶ Y)
  (w : P.factors (f + g)) (wf : P.factors f) : P.factors g :=
(factors_iff _ _).mpr ⟨P.factor_thru (f + g) w - P.factor_thru f wf, by simp⟩

@[simp]
lemma factor_thru_add_sub_factor_thru_left {X Y : C} {P : subobject Y} (f g : X ⟶ Y)
  (w : P.factors (f + g)) (wf : P.factors f) :
  P.factor_thru (f + g) w - P.factor_thru f wf =
    P.factor_thru g (factors_right_of_factors_add f g w wf) :=
by { ext, simp, }

end preadditive

end subobject

end category_theory


import Smt

theorem trans (p q r : Bool) : p == q → q == r → p == r := by
  smt
  cases p <;> cases q <;> cases r <;> simp_all


open classical
variables (α : Type) (p q : α → Prop)
variable r : Prop

example : α → ((∀ x : α, r) ↔ r) :=
begin
  intros,
  apply iff.intro,
    intro x,
    apply x,
    apply a,
  intros hr x,
  assumption
end
-- left to right requires classical logic
example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
begin
  apply iff.intro,
    intros h,
    apply by_cases,
      intro hr,
      right,
      assumption,
    intro hnr,
    left,
    intro,
    have : p x ∨ r,
        apply h,
    cases this,
      assumption,
    contradiction,
  intros,
  cases a,
    left,
    apply a,
  right,
  assumption
end
example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
begin
  apply iff.intro,
    intros,
    apply a,
    assumption,
  intros,
  apply a,
  assumption
end


/-
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.functor.const
import category_theory.discrete_category
import category_theory.yoneda
import category_theory.functor.reflects_isomorphisms

/-!
# Cones and cocones

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

We define `cone F`, a cone over a functor `F`,
and `F.cones : Cᵒᵖ ⥤ Type`, the functor associating to `X` the cones over `F` with cone point `X`.

A cone `c` is defined by specifying its cone point `c.X` and a natural transformation `c.π`
from the constant `c.X` valued functor to `F`.

We provide `c.w f : c.π.app j ≫ F.map f = c.π.app j'` for any `f : j ⟶ j'`
as a wrapper for `c.π.naturality f` avoiding unneeded identity morphisms.

We define `c.extend f`, where `c : cone F` and `f : Y ⟶ c.X` for some other `Y`,
which replaces the cone point by `Y` and inserts `f` into each of the components of the cone.
Similarly we have `c.whisker F` producing a `cone (E ⋙ F)`

We define morphisms of cones, and the category of cones.

We define `cone.postcompose α : cone F ⥤ cone G` for `α` a natural transformation `F ⟶ G`.

And, of course, we dualise all this to cocones as well.

For more results about the category of cones, see `cone_category.lean`.
-/

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

variables {J : Type u₁} [category.{v₁} J]
variables {K : Type u₂} [category.{v₂} K]
variables {C : Type u₃} [category.{v₃} C]
variables {D : Type u₄} [category.{v₄} D]

open category_theory
open category_theory.category
open category_theory.functor
open opposite

namespace category_theory

namespace functor
variables {J C} (F : J ⥤ C)

/--
`F.cones` is the functor assigning to an object `X` the type of
natural transformations from the constant functor with value `X` to `F`.
An object representing this functor is a limit of `F`.
-/
@[simps]
def cones : Cᵒᵖ ⥤ Type (max u₁ v₃) := (const J).op ⋙ yoneda.obj F

/--
`F.cocones` is the functor assigning to an object `X` the type of
natural transformations from `F` to the constant functor with value `X`.
An object corepresenting this functor is a colimit of `F`.
-/
@[simps]
def cocones : C ⥤ Type (max u₁ v₃) := const J ⋙ coyoneda.obj (op F)

end functor

section
variables (J C)

/--
Functorially associated to each functor `J ⥤ C`, we have the `C`-presheaf consisting of
cones with a given cone point.
-/
@[simps] def cones : (J ⥤ C) ⥤ (Cᵒᵖ ⥤ Type (max u₁ v₃)) :=
{ obj := functor.cones,
  map := λ F G f, whisker_left (const J).op (yoneda.map f) }

/--
Contravariantly associated to each functor `J ⥤ C`, we have the `C`-copresheaf consisting of
cocones with a given cocone point.
-/
@[simps] def cocones : (J ⥤ C)ᵒᵖ ⥤ (C ⥤ Type (max u₁ v₃)) :=
{ obj := λ F, functor.cocones (unop F),
  map := λ F G f, whisker_left (const J) (coyoneda.map f) }

end

namespace limits

section
local attribute [tidy] tactic.discrete_cases

/--
A `c : cone F` is:
* an object `c.X` and
* a natural transformation `c.π : c.X ⟶ F` from the constant `c.X` functor to `F`.

`cone F` is equivalent, via `cone.equiv` below, to `Σ X, F.cones.obj X`.
-/
structure cone (F : J ⥤ C) :=
(X : C)
(π : (const J).obj X ⟶ F)

instance inhabited_cone (F : discrete punit ⥤ C) : inhabited (cone F) :=
⟨{ X := F.obj ⟨⟨⟩⟩,
   π :=
   { app := λ ⟨⟨⟩⟩, 𝟙 _, }, }⟩

@[simp, reassoc] lemma cone.w {F : J ⥤ C} (c : cone F) {j j' : J} (f : j ⟶ j') :
  c.π.app j ≫ F.map f = c.π.app j' :=
by { rw ← c.π.naturality f, apply id_comp }

/--
A `c : cocone F` is
* an object `c.X` and
* a natural transformation `c.ι : F ⟶ c.X` from `F` to the constant `c.X` functor.

`cocone F` is equivalent, via `cone.equiv` below, to `Σ X, F.cocones.obj X`.
-/
structure cocone (F : J ⥤ C) :=
(X : C)
(ι : F ⟶ (const J).obj X)

instance inhabited_cocone (F : discrete punit ⥤ C) : inhabited (cocone F) :=
⟨{ X := F.obj ⟨⟨⟩⟩,
   ι :=
  { app := λ ⟨⟨⟩⟩, 𝟙 _, }, }⟩

@[simp, reassoc] lemma cocone.w {F : J ⥤ C} (c : cocone F) {j j' : J} (f : j ⟶ j') :
  F.map f ≫ c.ι.app j' = c.ι.app j :=
by { rw c.ι.naturality f, apply comp_id }

end

variables {F : J ⥤ C}

namespace cone

/-- The isomorphism between a cone on `F` and an element of the functor `F.cones`. -/
@[simps]
def equiv (F : J ⥤ C) : cone F ≅ Σ X, F.cones.obj X :=
{ hom := λ c, ⟨op c.X, c.π⟩,
  inv := λ c, { X := c.1.unop, π := c.2 },
  hom_inv_id' := by { ext1, cases x, refl },
  inv_hom_id' := by { ext1, cases x, refl } }

/-- A map to the vertex of a cone naturally induces a cone by composition. -/
@[simps] def extensions (c : cone F) :
  yoneda.obj c.X ⋙ ulift_functor.{u₁} ⟶ F.cones :=
{ app := λ X f, (const J).map f.down ≫ c.π }

/-- A map to the vertex of a cone induces a cone by composition. -/
@[simps] def extend (c : cone F) {X : C} (f : X ⟶ c.X) : cone F :=
{ X := X,
  π := c.extensions.app (op X) ⟨f⟩ }

/-- Whisker a cone by precomposition of a functor. -/
@[simps] def whisker (E : K ⥤ J) (c : cone F) : cone (E ⋙ F) :=
{ X := c.X,
  π := whisker_left E c.π }

end cone

namespace cocone

/-- The isomorphism between a cocone on `F` and an element of the functor `F.cocones`. -/
def equiv (F : J ⥤ C) : cocone F ≅ Σ X, F.cocones.obj X :=
{ hom := λ c, ⟨c.X, c.ι⟩,
  inv := λ c, { X := c.1, ι := c.2 },
  hom_inv_id' := by { ext1, cases x, refl },
  inv_hom_id' := by { ext1, cases x, refl } }

/-- A map from the vertex of a cocone naturally induces a cocone by composition. -/
@[simps] def extensions (c : cocone F) : coyoneda.obj (op c.X) ⋙ ulift_functor.{u₁} ⟶ F.cocones :=
{ app := λ X f, c.ι ≫ (const J).map f.down }

/-- A map from the vertex of a cocone induces a cocone by composition. -/
@[simps] def extend (c : cocone F) {X : C} (f : c.X ⟶ X) : cocone F :=
{ X := X,
  ι := c.extensions.app X ⟨f⟩ }

/--
Whisker a cocone by precomposition of a functor. See `whiskering` for a functorial
version.
-/
@[simps] def whisker (E : K ⥤ J) (c : cocone F) : cocone (E ⋙ F) :=
{ X := c.X,
  ι := whisker_left E c.ι }

end cocone

/-- A cone morphism between two cones for the same diagram is a morphism of the cone points which
commutes with the cone legs. -/
@[ext] structure cone_morphism (A B : cone F) :=
(hom : A.X ⟶ B.X)
(w'  : ∀ j : J, hom ≫ B.π.app j = A.π.app j . obviously)

restate_axiom cone_morphism.w'
attribute [simp, reassoc] cone_morphism.w

instance inhabited_cone_morphism (A : cone F) : inhabited (cone_morphism A A) :=
⟨{ hom := 𝟙 _ }⟩

/-- The category of cones on a given diagram. -/
@[simps] instance cone.category : category (cone F) :=
{ hom  := λ A B, cone_morphism A B,
  comp := λ X Y Z f g, { hom := f.hom ≫ g.hom },
  id   := λ B, { hom := 𝟙 B.X } }

namespace cones
/-- To give an isomorphism between cones, it suffices to give an
  isomorphism between their vertices which commutes with the cone
  maps. -/
@[ext, simps] def ext {c c' : cone F}
  (φ : c.X ≅ c'.X) (w : ∀ j, c.π.app j = φ.hom ≫ c'.π.app j) : c ≅ c' :=
{ hom := { hom := φ.hom },
  inv := { hom := φ.inv, w' := λ j, φ.inv_comp_eq.mpr (w j) } }

/-- Eta rule for cones. -/
@[simps] def eta (c : cone F) : c ≅ ⟨c.X, c.π⟩ :=
cones.ext (iso.refl _) (by tidy)

/--
Given a cone morphism whose object part is an isomorphism, produce an
isomorphism of cones.
-/
lemma cone_iso_of_hom_iso {K : J ⥤ C} {c d : cone K} (f : c ⟶ d) [i : is_iso f.hom] :
  is_iso f :=
⟨⟨{ hom := inv f.hom,
    w' := λ j, (as_iso f.hom).inv_comp_eq.2 (f.w j).symm }, by tidy⟩⟩

/--
Functorially postcompose a cone for `F` by a natural transformation `F ⟶ G` to give a cone for `G`.
-/
@[simps] def postcompose {G : J ⥤ C} (α : F ⟶ G) : cone F ⥤ cone G :=
{ obj := λ c, { X := c.X, π := c.π ≫ α },
  map := λ c₁ c₂ f, { hom := f.hom } }

/-- Postcomposing a cone by the composite natural transformation `α ≫ β` is the same as
postcomposing by `α` and then by `β`. -/
@[simps]
def postcompose_comp {G H : J ⥤ C} (α : F ⟶ G) (β : G ⟶ H) :
  postcompose (α ≫ β) ≅ postcompose α ⋙ postcompose β :=
nat_iso.of_components (λ s, cones.ext (iso.refl _) (by tidy)) (by tidy)

/-- Postcomposing by the identity does not change the cone up to isomorphism. -/
@[simps]
def postcompose_id : postcompose (𝟙 F) ≅ 𝟭 (cone F) :=
nat_iso.of_components (λ s, cones.ext (iso.refl _) (by tidy)) (by tidy)

/--
If `F` and `G` are naturally isomorphic functors, then they have equivalent categories of
cones.
-/
@[simps]
def postcompose_equivalence {G : J ⥤ C} (α : F ≅ G) : cone F ≌ cone G :=
{ functor := postcompose α.hom,
  inverse := postcompose α.inv,
  unit_iso := nat_iso.of_components (λ s, cones.ext (iso.refl _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ s, cones.ext (iso.refl _) (by tidy)) (by tidy) }

/--
Whiskering on the left by `E : K ⥤ J` gives a functor from `cone F` to `cone (E ⋙ F)`.
-/
@[simps]
def whiskering (E : K ⥤ J) : cone F ⥤ cone (E ⋙ F) :=
{ obj := λ c, c.whisker E,
  map := λ c c' f, { hom := f.hom } }

/--
Whiskering by an equivalence gives an equivalence between categories of cones.
-/
@[simps]
def whiskering_equivalence (e : K ≌ J) :
  cone F ≌ cone (e.functor ⋙ F) :=
{ functor := whiskering e.functor,
  inverse := whiskering e.inverse ⋙ postcompose (e.inv_fun_id_assoc F).hom,
  unit_iso := nat_iso.of_components (λ s, cones.ext (iso.refl _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ s, cones.ext (iso.refl _)
  (begin
    intro k,
    dsimp, -- See library note [dsimp, simp]
    simpa [e.counit_app_functor] using s.w (e.unit_inv.app k),
  end)) (by tidy), }

/--
The categories of cones over `F` and `G` are equivalent if `F` and `G` are naturally isomorphic
(possibly after changing the indexing category by an equivalence).
-/
@[simps functor inverse unit_iso counit_iso]
def equivalence_of_reindexing {G : K ⥤ C}
  (e : K ≌ J) (α : e.functor ⋙ F ≅ G) : cone F ≌ cone G :=
(whiskering_equivalence e).trans (postcompose_equivalence α)

section
variable (F)

/-- Forget the cone structure and obtain just the cone point. -/
@[simps]
def forget : cone F ⥤ C :=
{ obj := λ t, t.X, map := λ s t f, f.hom }

variables (G : C ⥤ D)

/-- A functor `G : C ⥤ D` sends cones over `F` to cones over `F ⋙ G` functorially. -/
@[simps] def functoriality : cone F ⥤ cone (F ⋙ G) :=
{ obj := λ A,
  { X := G.obj A.X,
    π := { app := λ j, G.map (A.π.app j), naturality' := by intros; erw ←G.map_comp; tidy } },
  map := λ X Y f,
  { hom := G.map f.hom,
    w' := λ j, by simp [-cone_morphism.w, ←f.w j] } }

instance functoriality_full [full G] [faithful G] : full (functoriality F G) :=
{ preimage := λ X Y t,
  { hom := G.preimage t.hom,
    w' := λ j, G.map_injective (by simpa using t.w j) } }

instance functoriality_faithful [faithful G] : faithful (cones.functoriality F G) :=
{ map_injective' := λ X Y f g e, by { ext1, injection e, apply G.map_injective h_1 } }

/--
If `e : C ≌ D` is an equivalence of categories, then `functoriality F e.functor` induces an
equivalence between cones over `F` and cones over `F ⋙ e.functor`.
-/
@[simps]
def functoriality_equivalence (e : C ≌ D) : cone F ≌ cone (F ⋙ e.functor) :=
let f : (F ⋙ e.functor) ⋙ e.inverse ≅ F :=
  functor.associator _ _ _ ≪≫ iso_whisker_left _ (e.unit_iso).symm ≪≫ functor.right_unitor _ in
{ functor := functoriality F e.functor,
  inverse := (functoriality (F ⋙ e.functor) e.inverse) ⋙
    (postcompose_equivalence f).functor,
  unit_iso := nat_iso.of_components (λ c, cones.ext (e.unit_iso.app _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ c, cones.ext (e.counit_iso.app _) (by tidy)) (by tidy), }

/--
If `F` reflects isomorphisms, then `cones.functoriality F` reflects isomorphisms
as well.
-/
instance reflects_cone_isomorphism (F : C ⥤ D) [reflects_isomorphisms F] (K : J ⥤ C) :
  reflects_isomorphisms (cones.functoriality K F) :=
begin
  constructor,
  introsI,
  haveI : is_iso (F.map f.hom) :=
    (cones.forget (K ⋙ F)).map_is_iso ((cones.functoriality K F).map f),
  haveI := reflects_isomorphisms.reflects F f.hom,
  apply cone_iso_of_hom_iso
end

end

end cones

/-- A cocone morphism between two cocones for the same diagram is a morphism of the cocone points
which commutes with the cocone legs. -/
@[ext] structure cocone_morphism (A B : cocone F) :=
(hom : A.X ⟶ B.X)
(w'  : ∀ j : J, A.ι.app j ≫ hom = B.ι.app j . obviously)

instance inhabited_cocone_morphism (A : cocone F) : inhabited (cocone_morphism A A) :=
⟨{ hom := 𝟙 _ }⟩

restate_axiom cocone_morphism.w'
attribute [simp, reassoc] cocone_morphism.w

@[simps] instance cocone.category : category (cocone F) :=
{ hom  := λ A B, cocone_morphism A B,
  comp := λ _ _ _ f g,
  { hom := f.hom ≫ g.hom },
  id   := λ B, { hom := 𝟙 B.X } }

namespace cocones
/-- To give an isomorphism between cocones, it suffices to give an
  isomorphism between their vertices which commutes with the cocone
  maps. -/
@[ext, simps] def ext {c c' : cocone F}
  (φ : c.X ≅ c'.X) (w : ∀ j, c.ι.app j ≫ φ.hom = c'.ι.app j) : c ≅ c' :=
{ hom := { hom := φ.hom },
  inv := { hom := φ.inv, w' := λ j, φ.comp_inv_eq.mpr (w j).symm } }

/-- Eta rule for cocones. -/
@[simps] def eta (c : cocone F) : c ≅ ⟨c.X, c.ι⟩ :=
cocones.ext (iso.refl _) (by tidy)

/--
Given a cocone morphism whose object part is an isomorphism, produce an
isomorphism of cocones.
-/
lemma cocone_iso_of_hom_iso {K : J ⥤ C} {c d : cocone K} (f : c ⟶ d) [i : is_iso f.hom] :
  is_iso f :=
⟨⟨{ hom := inv f.hom,
    w' := λ j, (as_iso f.hom).comp_inv_eq.2 (f.w j).symm }, by tidy⟩⟩

/-- Functorially precompose a cocone for `F` by a natural transformation `G ⟶ F` to give a cocone
for `G`. -/
@[simps] def precompose {G : J ⥤ C} (α : G ⟶ F) : cocone F ⥤ cocone G :=
{ obj := λ c, { X := c.X, ι := α ≫ c.ι },
  map := λ c₁ c₂ f, { hom := f.hom } }

/-- Precomposing a cocone by the composite natural transformation `α ≫ β` is the same as
precomposing by `β` and then by `α`. -/
def precompose_comp {G H : J ⥤ C} (α : F ⟶ G) (β : G ⟶ H) :
  precompose (α ≫ β) ≅ precompose β ⋙ precompose α :=
nat_iso.of_components (λ s, cocones.ext (iso.refl _) (by tidy)) (by tidy)

/-- Precomposing by the identity does not change the cocone up to isomorphism. -/
def precompose_id : precompose (𝟙 F) ≅ 𝟭 (cocone F) :=
nat_iso.of_components (λ s, cocones.ext (iso.refl _) (by tidy)) (by tidy)

/--
If `F` and `G` are naturally isomorphic functors, then they have equivalent categories of
cocones.
-/
@[simps]
def precompose_equivalence {G : J ⥤ C} (α : G ≅ F) : cocone F ≌ cocone G :=
{ functor := precompose α.hom,
  inverse := precompose α.inv,
  unit_iso := nat_iso.of_components (λ s, cocones.ext (iso.refl _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ s, cocones.ext (iso.refl _) (by tidy)) (by tidy) }

/--
Whiskering on the left by `E : K ⥤ J` gives a functor from `cocone F` to `cocone (E ⋙ F)`.
-/
@[simps]
def whiskering (E : K ⥤ J) : cocone F ⥤ cocone (E ⋙ F) :=
{ obj := λ c, c.whisker E,
  map := λ c c' f, { hom := f.hom, } }

/--
Whiskering by an equivalence gives an equivalence between categories of cones.
-/
@[simps]
def whiskering_equivalence (e : K ≌ J) :
  cocone F ≌ cocone (e.functor ⋙ F) :=
{ functor := whiskering e.functor,
  inverse := whiskering e.inverse ⋙
    precompose ((functor.left_unitor F).inv ≫ (whisker_right (e.counit_iso).inv F) ≫
      (functor.associator _ _ _).inv),
  unit_iso := nat_iso.of_components (λ s, cocones.ext (iso.refl _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ s, cocones.ext (iso.refl _)
  (begin
    intro k,
    dsimp,
    simpa [e.counit_inv_app_functor k] using s.w (e.unit.app k),
  end)) (by tidy), }

/--
The categories of cocones over `F` and `G` are equivalent if `F` and `G` are naturally isomorphic
(possibly after changing the indexing category by an equivalence).
-/
@[simps functor_obj]
def equivalence_of_reindexing {G : K ⥤ C}
  (e : K ≌ J) (α : e.functor ⋙ F ≅ G) : cocone F ≌ cocone G :=
(whiskering_equivalence e).trans (precompose_equivalence α.symm)

section
variable (F)

/-- Forget the cocone structure and obtain just the cocone point. -/
@[simps]
def forget : cocone F ⥤ C :=
{ obj := λ t, t.X, map := λ s t f, f.hom }

variables (G : C ⥤ D)

/-- A functor `G : C ⥤ D` sends cocones over `F` to cocones over `F ⋙ G` functorially. -/
@[simps] def functoriality : cocone F ⥤ cocone (F ⋙ G) :=
{ obj := λ A,
  { X := G.obj A.X,
    ι := { app := λ j, G.map (A.ι.app j), naturality' := by intros; erw ←G.map_comp; tidy } },
  map := λ _ _ f,
  { hom := G.map f.hom,
    w'  := by intros; rw [←functor.map_comp, cocone_morphism.w] } }

instance functoriality_full [full G] [faithful G] : full (functoriality F G) :=
{ preimage := λ X Y t,
  { hom := G.preimage t.hom,
    w' := λ j, G.map_injective (by simpa using t.w j) } }

instance functoriality_faithful [faithful G] : faithful (functoriality F G) :=
{ map_injective' := λ X Y f g e, by { ext1, injection e, apply G.map_injective h_1 } }

/--
If `e : C ≌ D` is an equivalence of categories, then `functoriality F e.functor` induces an
equivalence between cocones over `F` and cocones over `F ⋙ e.functor`.
-/
@[simps]
def functoriality_equivalence (e : C ≌ D) : cocone F ≌ cocone (F ⋙ e.functor) :=
let f : (F ⋙ e.functor) ⋙ e.inverse ≅ F :=
  functor.associator _ _ _ ≪≫ iso_whisker_left _ (e.unit_iso).symm ≪≫ functor.right_unitor _ in
{ functor := functoriality F e.functor,
  inverse := (functoriality (F ⋙ e.functor) e.inverse) ⋙
    (precompose_equivalence f.symm).functor,
  unit_iso := nat_iso.of_components (λ c, cocones.ext (e.unit_iso.app _) (by tidy)) (by tidy),
  counit_iso := nat_iso.of_components (λ c, cocones.ext (e.counit_iso.app _)
  begin
    -- Unfortunately this doesn't work by `tidy`.
    -- In this configuration `simp` reaches a dead-end and needs help.
    intros j,
    dsimp,
    simp only [←equivalence.counit_inv_app_functor, iso.inv_hom_id_app, map_comp,
      equivalence.fun_inv_map, assoc, id_comp, iso.inv_hom_id_app_assoc],
    dsimp, simp, -- See note [dsimp, simp].
  end)
  (λ c c' f, by { ext, dsimp, simp, dsimp, simp, }), }

/--
If `F` reflects isomorphisms, then `cocones.functoriality F` reflects isomorphisms
as well.
-/
instance reflects_cocone_isomorphism (F : C ⥤ D) [reflects_isomorphisms F] (K : J ⥤ C) :
  reflects_isomorphisms (cocones.functoriality K F) :=
begin
  constructor,
  introsI,
  haveI : is_iso (F.map f.hom) :=
    (cocones.forget (K ⋙ F)).map_is_iso ((cocones.functoriality K F).map f),
  haveI := reflects_isomorphisms.reflects F f.hom,
  apply cocone_iso_of_hom_iso
end

end
end cocones

end limits

namespace functor

variables {F : J ⥤ C} {G : J ⥤ C} (H : C ⥤ D)

open category_theory.limits

/-- The image of a cone in C under a functor G : C ⥤ D is a cone in D. -/
@[simps]
def map_cone   (c : cone F)   : cone (F ⋙ H)   := (cones.functoriality F H).obj c
/-- The image of a cocone in C under a functor G : C ⥤ D is a cocone in D. -/
@[simps]
def map_cocone (c : cocone F) : cocone (F ⋙ H) := (cocones.functoriality F H).obj c

/-- Given a cone morphism `c ⟶ c'`, construct a cone morphism on the mapped cones functorially.  -/
def map_cone_morphism   {c c' : cone F}   (f : c ⟶ c')   :
  H.map_cone c ⟶ H.map_cone c' := (cones.functoriality F H).map f

/-- Given a cocone morphism `c ⟶ c'`, construct a cocone morphism on the mapped cocones
functorially. -/
def map_cocone_morphism {c c' : cocone F} (f : c ⟶ c') :
  H.map_cocone c ⟶ H.map_cocone c' := (cocones.functoriality F H).map f

/-- If `H` is an equivalence, we invert `H.map_cone` and get a cone for `F` from a cone
for `F ⋙ H`.-/
def map_cone_inv [is_equivalence H]
  (c : cone (F ⋙ H)) : cone F :=
(limits.cones.functoriality_equivalence F (as_equivalence H)).inverse.obj c

/-- `map_cone` is the left inverse to `map_cone_inv`. -/
def map_cone_map_cone_inv {F : J ⥤ D} (H : D ⥤ C) [is_equivalence H] (c : cone (F ⋙ H)) :
  map_cone H (map_cone_inv H c) ≅ c :=
(limits.cones.functoriality_equivalence F (as_equivalence H)).counit_iso.app c

/-- `map_cone` is the right inverse to `map_cone_inv`. -/
def map_cone_inv_map_cone {F : J ⥤ D} (H : D ⥤ C) [is_equivalence H] (c : cone F) :
  map_cone_inv H (map_cone H c) ≅ c :=
(limits.cones.functoriality_equivalence F (as_equivalence H)).unit_iso.symm.app c
/-- If `H` is an equivalence, we invert `H.map_cone` and get a cone for `F` from a cone
for `F ⋙ H`.-/

def map_cocone_inv [is_equivalence H]
  (c : cocone (F ⋙ H)) : cocone F :=
(limits.cocones.functoriality_equivalence F (as_equivalence H)).inverse.obj c

/-- `map_cocone` is the left inverse to `map_cocone_inv`. -/
def map_cocone_map_cocone_inv {F : J ⥤ D} (H : D ⥤ C) [is_equivalence H] (c : cocone (F ⋙ H)) :
  map_cocone H (map_cocone_inv H c) ≅ c :=
(limits.cocones.functoriality_equivalence F (as_equivalence H)).counit_iso.app c

/-- `map_cocone` is the right inverse to `map_cocone_inv`. -/
def map_cocone_inv_map_cocone {F : J ⥤ D} (H : D ⥤ C) [is_equivalence H] (c : cocone F) :
  map_cocone_inv H (map_cocone H c) ≅ c :=
(limits.cocones.functoriality_equivalence F (as_equivalence H)).unit_iso.symm.app c

/-- `functoriality F _ ⋙ postcompose (whisker_left F _)` simplifies to `functoriality F _`. -/
@[simps]
def functoriality_comp_postcompose {H H' : C ⥤ D} (α : H ≅ H') :
  cones.functoriality F H ⋙ cones.postcompose (whisker_left F α.hom) ≅ cones.functoriality F H' :=
nat_iso.of_components (λ c, cones.ext (α.app _) (by tidy)) (by tidy)

/--
For `F : J ⥤ C`, given a cone `c : cone F`, and a natural isomorphism `α : H ≅ H'` for functors
`H H' : C ⥤ D`, the postcomposition of the cone `H.map_cone` using the isomorphism `α` is
isomorphic to the cone `H'.map_cone`.
-/
@[simps]
def postcompose_whisker_left_map_cone {H H' : C ⥤ D} (α : H ≅ H') (c : cone F) :
  (cones.postcompose (whisker_left F α.hom : _)).obj (H.map_cone c) ≅ H'.map_cone c :=
(functoriality_comp_postcompose α).app c

/--
`map_cone` commutes with `postcompose`. In particular, for `F : J ⥤ C`, given a cone `c : cone F`, a
natural transformation `α : F ⟶ G` and a functor `H : C ⥤ D`, we have two obvious ways of producing
a cone over `G ⋙ H`, and they are both isomorphic.
-/
@[simps]
def map_cone_postcompose {α : F ⟶ G} {c} :
  H.map_cone ((cones.postcompose α).obj c) ≅
  (cones.postcompose (whisker_right α H : _)).obj (H.map_cone c) :=
cones.ext (iso.refl _) (by tidy)

/--
`map_cone` commutes with `postcompose_equivalence`
-/
@[simps]
def map_cone_postcompose_equivalence_functor {α : F ≅ G} {c} :
  H.map_cone ((cones.postcompose_equivalence α).functor.obj c) ≅
    (cones.postcompose_equivalence (iso_whisker_right α H : _)).functor.obj (H.map_cone c) :=
cones.ext (iso.refl _) (by tidy)

/-- `functoriality F _ ⋙ precompose (whisker_left F _)` simplifies to `functoriality F _`. -/
@[simps]
def functoriality_comp_precompose {H H' : C ⥤ D} (α : H ≅ H') :
   cocones.functoriality F H ⋙ cocones.precompose (whisker_left F α.inv)
 ≅ cocones.functoriality F H' :=
nat_iso.of_components (λ c, cocones.ext (α.app _) (by tidy)) (by tidy)

/--
For `F : J ⥤ C`, given a cocone `c : cocone F`, and a natural isomorphism `α : H ≅ H'` for functors
`H H' : C ⥤ D`, the precomposition of the cocone `H.map_cocone` using the isomorphism `α` is
isomorphic to the cocone `H'.map_cocone`.
-/
@[simps]
def precompose_whisker_left_map_cocone {H H' : C ⥤ D} (α : H ≅ H') (c : cocone F) :
  (cocones.precompose (whisker_left F α.inv : _)).obj (H.map_cocone c) ≅ H'.map_cocone c :=
(functoriality_comp_precompose α).app c

/--
`map_cocone` commutes with `precompose`. In particular, for `F : J ⥤ C`, given a cocone
`c : cocone F`, a natural transformation `α : F ⟶ G` and a functor `H : C ⥤ D`, we have two obvious
ways of producing a cocone over `G ⋙ H`, and they are both isomorphic.
-/
@[simps]
def map_cocone_precompose {α : F ⟶ G} {c} :
  H.map_cocone ((cocones.precompose α).obj c) ≅
  (cocones.precompose (whisker_right α H : _)).obj (H.map_cocone c) :=
cocones.ext (iso.refl _) (by tidy)

/--
`map_cocone` commutes with `precompose_equivalence`
-/
@[simps]
def map_cocone_precompose_equivalence_functor {α : F ≅ G} {c} :
  H.map_cocone ((cocones.precompose_equivalence α).functor.obj c) ≅
    (cocones.precompose_equivalence (iso_whisker_right α H : _)).functor.obj (H.map_cocone c) :=
cocones.ext (iso.refl _) (by tidy)

/--
`map_cone` commutes with `whisker`
-/
@[simps]
def map_cone_whisker {E : K ⥤ J} {c : cone F} :
  H.map_cone (c.whisker E) ≅ (H.map_cone c).whisker E :=
cones.ext (iso.refl _) (by tidy)

/--
`map_cocone` commutes with `whisker`
-/
@[simps]
def map_cocone_whisker {E : K ⥤ J} {c : cocone F} :
  H.map_cocone (c.whisker E) ≅ (H.map_cocone c).whisker E :=
cocones.ext (iso.refl _) (by tidy)

end functor

end category_theory

namespace category_theory.limits

section
variables {F : J ⥤ C}

/-- Change a `cocone F` into a `cone F.op`. -/
@[simps] def cocone.op (c : cocone F) : cone F.op :=
{ X := op c.X,
  π := nat_trans.op c.ι }

/-- Change a `cone F` into a `cocone F.op`. -/
@[simps] def cone.op (c : cone F) : cocone F.op :=
{ X := op c.X,
  ι := nat_trans.op c.π }

/-- Change a `cocone F.op` into a `cone F`. -/
@[simps] def cocone.unop (c : cocone F.op) : cone F :=
{ X := unop c.X,
  π := nat_trans.remove_op c.ι }

/-- Change a `cone F.op` into a `cocone F`. -/
@[simps] def cone.unop (c : cone F.op) : cocone F :=
{ X := unop c.X,
  ι := nat_trans.remove_op c.π }

variables (F)

/--
The category of cocones on `F`
is equivalent to the opposite category of
the category of cones on the opposite of `F`.
-/
def cocone_equivalence_op_cone_op : cocone F ≌ (cone F.op)ᵒᵖ :=
{ functor :=
  { obj := λ c, op (cocone.op c),
    map := λ X Y f, quiver.hom.op
    { hom := f.hom.op,
      w' := λ j, by { apply quiver.hom.unop_inj, dsimp, apply cocone_morphism.w }, } },
  inverse :=
  { obj := λ c, cone.unop (unop c),
    map := λ X Y f,
    { hom := f.unop.hom.unop,
      w' := λ j, by { apply quiver.hom.op_inj, dsimp, apply cone_morphism.w }, } },
  unit_iso := nat_iso.of_components (λ c,
    cocones.ext (iso.refl _) (by { dsimp, simp })) (λ X Y f, by { ext, simp }),
  counit_iso := nat_iso.of_components (λ c,
    by { induction c using opposite.rec,
         dsimp, apply iso.op, exact cones.ext (iso.refl _) (by { dsimp, simp }), })
    (λ X Y f, quiver.hom.unop_inj (cone_morphism.ext _ _ (by { dsimp, simp }))),
  functor_unit_iso_comp' := (λ c,
    by { apply quiver.hom.unop_inj, ext, dsimp, apply comp_id })}

attribute [simps] cocone_equivalence_op_cone_op

end

section
variables {F : J ⥤ Cᵒᵖ}

/-- Change a cocone on `F.left_op : Jᵒᵖ ⥤ C` to a cocone on `F : J ⥤ Cᵒᵖ`. -/
-- Here and below we only automatically generate the `@[simp]` lemma for the `X` field,
-- as we can write a simpler `rfl` lemma for the components of the natural transformation by hand.
@[simps {rhs_md := semireducible, simp_rhs := tt}]
def cone_of_cocone_left_op (c : cocone F.left_op) : cone F :=
{ X := op c.X,
  π := nat_trans.remove_left_op c.ι }

/-- Change a cone on `F : J ⥤ Cᵒᵖ` to a cocone on `F.left_op : Jᵒᵖ ⥤ C`. -/
@[simps {rhs_md := semireducible, simp_rhs := tt}]
def cocone_left_op_of_cone (c : cone F) : cocone (F.left_op) :=
{ X := unop c.X,
  ι := nat_trans.left_op c.π }

/-- Change a cone on `F.left_op : Jᵒᵖ ⥤ C` to a cocone on `F : J ⥤ Cᵒᵖ`. -/
/- When trying use `@[simps]` to generate the `ι_app` field of this definition, `@[simps]` tries to
  reduce the RHS using `expr.dsimp` and `expr.simp`, but for some reason the expression is not
  being simplified properly. -/
@[simps X]
def cocone_of_cone_left_op (c : cone F.left_op) : cocone F :=
{ X := op c.X,
  ι := nat_trans.remove_left_op c.π }

@[simp] lemma cocone_of_cone_left_op_ι_app (c : cone F.left_op) (j) :
  (cocone_of_cone_left_op c).ι.app j = (c.π.app (op j)).op :=
by { dsimp only [cocone_of_cone_left_op], simp }

/-- Change a cocone on `F : J ⥤ Cᵒᵖ` to a cone on `F.left_op : Jᵒᵖ ⥤ C`. -/
@[simps {rhs_md := semireducible, simp_rhs := tt}]
def cone_left_op_of_cocone (c : cocone F) : cone (F.left_op) :=
{ X := unop c.X,
  π := nat_trans.left_op c.ι }

end

section
variables {F : Jᵒᵖ ⥤ C}

/-- Change a cocone on `F.right_op : J ⥤ Cᵒᵖ` to a cone on `F : Jᵒᵖ ⥤ C`. -/
@[simps] def cone_of_cocone_right_op (c : cocone F.right_op) : cone F :=
{ X := unop c.X,
  π := nat_trans.remove_right_op c.ι }

/-- Change a cone on `F : Jᵒᵖ ⥤ C` to a cocone on `F.right_op : Jᵒᵖ ⥤ C`. -/
@[simps] def cocone_right_op_of_cone (c : cone F) : cocone (F.right_op) :=
{ X := op c.X,
  ι := nat_trans.right_op c.π }

/-- Change a cone on `F.right_op : J ⥤ Cᵒᵖ` to a cocone on `F : Jᵒᵖ ⥤ C`. -/
@[simps] def cocone_of_cone_right_op (c : cone F.right_op) : cocone F :=
{ X := unop c.X,
  ι := nat_trans.remove_right_op c.π }

/-- Change a cocone on `F : Jᵒᵖ ⥤ C` to a cone on `F.right_op : J ⥤ Cᵒᵖ`. -/
@[simps] def cone_right_op_of_cocone (c : cocone F) : cone (F.right_op) :=
{ X := op c.X,
  π := nat_trans.right_op c.ι }

end

section
variables {F : Jᵒᵖ ⥤ Cᵒᵖ}

/-- Change a cocone on `F.unop : J ⥤ C` into a cone on `F : Jᵒᵖ ⥤ Cᵒᵖ`. -/
@[simps] def cone_of_cocone_unop (c : cocone F.unop) : cone F :=
{ X := op c.X,
  π := nat_trans.remove_unop c.ι }

/-- Change a cone on `F : Jᵒᵖ ⥤ Cᵒᵖ` into a cocone on `F.unop : J ⥤ C`. -/
@[simps] def cocone_unop_of_cone (c : cone F) : cocone F.unop :=
{ X := unop c.X,
  ι := nat_trans.unop c.π }

/-- Change a cone on `F.unop : J ⥤ C` into a cocone on `F : Jᵒᵖ ⥤ Cᵒᵖ`. -/
@[simps] def cocone_of_cone_unop (c : cone F.unop) : cocone F :=
{ X := op c.X,
  ι := nat_trans.remove_unop c.π }

/-- Change a cocone on `F : Jᵒᵖ ⥤ Cᵒᵖ` into a cone on `F.unop : J ⥤ C`. -/
@[simps] def cone_unop_of_cocone (c : cocone F) : cone F.unop :=
{ X := unop c.X,
  π := nat_trans.unop c.ι }

end

end category_theory.limits

namespace category_theory.functor

open category_theory.limits

variables {F : J ⥤ C}

section
variables (G : C ⥤ D)

/-- The opposite cocone of the image of a cone is the image of the opposite cocone. -/
@[simps {rhs_md := semireducible}]
def map_cone_op (t : cone F) : (G.map_cone t).op ≅ (G.op.map_cocone t.op) :=
cocones.ext (iso.refl _) (by tidy)

/-- The opposite cone of the image of a cocone is the image of the opposite cone. -/
@[simps {rhs_md := semireducible}]
def map_cocone_op {t : cocone F} : (G.map_cocone t).op ≅ (G.op.map_cone t.op) :=
cones.ext (iso.refl _) (by tidy)

end

end category_theory.functor