import Mathlib.CategoryTheory.Action
import Mathlib.CategoryTheory.Category.Cat
import Mathlib.CategoryTheory.DiscreteCategory
import Mathlib.CategoryTheory.Functor.Category
import Mathlib.CategoryTheory.Functor.Hom
import Mathlib.CategoryTheory.Sigma.Basic
import Mathlib.CategoryTheory.Limits.Shapes.Types

open CategoryTheory CategoryTheory.Limits Equiv

universe u₁ u₂ u₃ u₄ v₁ v₂ v₃ v₄ w

def Prof (C : Type u₁) [Category.{v₁} C] (D : Type u₂) [Category.{v₂} D]
  := Dᵒᵖ × C ⥤ Type w
def Prof.Id (C : Type u₁) [Category.{v₁} C] : Prof C C := Functor.hom C

instance (C : Type u₁) [Category.{v₁} C] (D : Type u₂) [Category.{v₂} D]
  : Category (Prof C D) := Functor.category

variable {A : Type u₁} [Category.{v₁} A] {B : Type u₂} [Category.{v₂} B]
         {C : Type u₃} [Category.{v₃} C] {D : Type u₄} [Category.{v₄} D]

def Prof.precomp (F : A ⥤ C) (G : B ⥤ D) (H : Prof.{u₃, u₄, v₃, v₄, w} C D)
  : Prof A B := Functor.prod (Functor.op G) F ⋙ H

def square (F : A ⥤ C) (G : B ⥤ D)
  (H : Prof.{u₁, u₂, v₁, v₂, w} A B) (K : Prof.{u₃, u₄, v₃, v₄, w} C D)
  := H ⟶ K.precomp F G

def UniversalCover (G : Type u₁) [Monoid G] := ActionCategory G G

instance (G : Type u₁) [Monoid G] : Category (UniversalCover G) :=
  instCategoryActionCategory _ _
instance (G : Type u₁) [Monoid G] : CoeTC G (UniversalCover G) :=
  ActionCategory.instCoeTCActionCategory
instance (G : Type u₁) [Group G] : Groupoid (UniversalCover G) :=
  ActionCategory.instGroupoidActionCategoryToMonoidToDivInvMonoid

def UniversalCover.π (G : Type u₁) [Monoid G]
  : UniversalCover G ⥤ SingleObj G := ActionCategory.π G G

def IsThin (C : Type u₁) [Category.{v₁} C] := ∀ X Y : C, Subsingleton (X ⟶ Y)
lemma UniversalCover.IsThin (G : Type u₁) [RightCancelMonoid G] : IsThin (UniversalCover G)
| Sigma.mk _ g, Sigma.mk _ h => by
  dsimp at g h
  constructor
  intros p q
  obtain ⟨p, hyp1⟩ := p; obtain ⟨q, hyp2⟩ := q
  apply Subtype.ext
  exact mul_left_injective g (Eq.trans hyp1 hyp2.symm)

instance UniversalCover.HomUnique (G : Type u₁) [Group G]
  (x y : UniversalCover G) : Unique (x ⟶ y) :=
  @uniqueOfSubsingleton _ (UniversalCover.IsThin G x y)
                        ⟨y.back * x.back⁻¹, inv_mul_cancel_right y.back x.back⟩

instance UniversalCover.IsConnected (G : Type u₁) [Group G] : IsConnected (UniversalCover G) :=
  ActionCategory.instIsConnectedActionCategoryInstCategoryActionCategory

def SingleObj.OpIso (G : Type u₁) [Monoid G]
  : @CategoryTheory.Iso Cat _ (Bundled.of (SingleObj G)ᵒᵖ)
                              (Bundled.of (SingleObj Gᵐᵒᵖ))
  where
    hom := {
      obj := fun _ => SingleObj.star G
      map := fun g => MulOpposite.op (Opposite.unop g)
    }
    inv := {
      obj := fun _ => Opposite.op (SingleObj.star _)
      map := fun g => Opposite.op (MulOpposite.unop g)
    }

-- def Sigma.ToOp {J : Type w} {C : J → Type u₁} [(j : J) → Category.{v₁, u₁} (C j)]
--   : (Σ j, C j)ᵒᵖ ⥤ Σ j, (C j)ᵒᵖ
--   where
--     hom := CategoryTheory.Sigma.desc _
--     inv := sorry

def Sigma.OpIso {J : Type w} {C : J → Type u₁} [(j : J) → Category.{v₁, u₁} (C j)]
  : @CategoryTheory.Iso Cat _ (Bundled.of (Σ j, C j)ᵒᵖ)
                              (Bundled.of (Σ j, (C j)ᵒᵖ))
  where
    hom := (Sigma.desc (fun j => (@Sigma.incl _ (fun j' => (C j')ᵒᵖ) _ j).rightOp)).leftOp
    inv := Sigma.desc (fun j => Functor.leftOp (Sigma.incl j ⋙ opOp _))
    hom_inv_id := by
      apply Functor.hext
      . intros
        exact Eq.refl _
      . apply Opposite.rec'; intro X
        apply Opposite.rec'; intro Y
        apply Opposite.rec'; intro f
        change Y ⟶ X at f
        induction f
        exact HEq.refl _
    inv_hom_id := by
      apply Functor.hext
      . intros
        exact Eq.refl _
      . intros X Y f
        induction f
        exact HEq.refl _

def SingleObj.Inv (G : Type u₁) [Group G] : (SingleObj G)ᵒᵖ ⥤ SingleObj G where
  obj _        := SingleObj.star _
  map g        := g.unop⁻¹
  map_id _     := inv_one
  map_comp _ _ := mul_inv_rev _ _

def UniversalCover.Inv (G : Type u₁) [Group G] : UniversalCover G ⥤ UniversalCover G where
  obj g := (g.back⁻¹ : G)
  map {g h} _ := ⟨h.back⁻¹ * g.back, mul_inv_cancel_right h.back⁻¹ g.back⟩
  map_comp := by { intros; apply Subsingleton.elim }

variable {J : Type u₂} (G : J → Type u₁) [∀ j, Group (G j)]
abbrev Tot := Σ j, SingleObj (G j)
abbrev Cov := Σ j, UniversalCover (G j)

def Cov.Inv : Cov G ⥤ Cov G :=
  Sigma.Functor.sigma (fun j => UniversalCover.Inv (G j))

def Sigma.Functor.sigma' {I : Type w}
  {C : I → Type u₁} [(i : I) → Category.{v₁} (C i)]
  {D : I → Type u₂} [(i : I) → Category.{v₁} (D i)]
  (F : (i : I) → C i ⥤ D i) : (Σi, C i) ⥤ Σi, D i :=
  Sigma.desc (fun i => F i ⋙ Sigma.incl i)

def Cov.π : Cov G ⥤ Tot G := Sigma.Functor.sigma' (fun j => UniversalCover.π (G j))

def Pt (C : Type u₁) [Category.{v₂} C] : Prof C C := (Functor.const _).obj PUnit
def CofreeBiAct : Prof.{u₂, u₂, max u₁ u₂, max u₁ u₂} (Tot G) (Tot G) :=
  Functor.prod (Sigma.OpIso.hom ⋙ Sigma.desc (fun j => (SingleObj.OpIso (G j)).hom
                                                       ⋙ actionAsFunctor (G j)ᵐᵒᵖ (G j)))
               (Sigma.desc (fun j => actionAsFunctor (G j) (G j)))
  ⋙ uncurry.obj Types.binaryProductFunctor

def TheMap : square (Cov.π G) (Cov.π G) (Pt (Cov G)) (CofreeBiAct G) where
  app
  | ⟨⟨⟨i, ⟨_, s⟩⟩⟩, ⟨j, ⟨_, t⟩⟩⟩ => fun _ => (@Inv.inv (G i) _ s, t)
  naturality
  | ⟨⟨X⟩, Y⟩, ⟨⟨X'⟩, Y'⟩, ⟨⟨p⟩, q⟩ => by
    dsimp at p q
    induction p with | @mk j₁ s₁ t₁ p =>
    induction q with | @mk j₂ s₂ t₂ q =>
    obtain ⟨_, s₁⟩ := s₁; obtain ⟨_, t₁⟩ := t₁
    obtain ⟨_, s₂⟩ := s₂; obtain ⟨_, t₂⟩ := t₂
    obtain ⟨g, hyp1⟩ := p
    obtain ⟨h, hyp2⟩ := q
    ext
    refine Prod.mk.inj_iff.mpr ⟨?_, hyp2.symm⟩
    change (G j₁) at t₁
    change (G j₁) at s₁
    change s₁⁻¹ = t₁⁻¹ * g
    exact eq_inv_mul_of_mul_eq (mul_inv_eq_of_eq_mul hyp1.symm)

-- abbrev PermGrpd := Σ (n : ℕ), SingleObj (Perm (Fin n))
-- abbrev PermGrpdCover := Σ (n : ℕ), UniversalCover (Perm (Fin n))
-- def I : Discrete ℕ ⥤ PermGrpd := Discrete.functor (fun n => ⟨n, SingleObj.star _⟩)
-- def J : Discrete ℕ ⥤ PermGrpdCover := Discrete.functor (fun n => ⟨n, ActionCategory.objEquiv _ _ (Equiv.refl (Fin n))⟩)
-- def π : PermGrpdCover ⥤ PermGrpd := Sigma.Functor.sigma (fun _ => ActionCategory.π _ _)
-- def A : PermGrpdCover ⥤ PermGrpdCover := Sigma.Functor.sigma (fun _ => UniversalCover.Inv _)
-- def r : PermGrpd ⥤ Discrete ℕ := Sigma.desc (fun n => (Functor.const _).obj (Discrete.mk n))

-- lemma lem1 : I ⋙ r = 𝟭 (Discrete ℕ) := by
--   apply Functor.hext
--   . intro n; cases n
--     exact Eq.refl _
--   . intro n m f; cases n; cases m
--     obtain ⟨⟨h⟩⟩ := f
--     exact HEq.refl _

-- lemma lem2 : J ⋙ π = I := by
--   apply Functor.hext
--   . intro n; cases n
--     exact Eq.refl _
--   . intro n m f; cases n; cases m
--     obtain ⟨⟨h⟩⟩ := f; dsimp at h
--     subst h
--     exact HEq.refl _
