{-# OPTIONS --without-K --safe #-}

module Categories.Category.Instance.Properties.Setoids.Extensive where

open import Level using (Level)
open import Data.Product using (_,_)
open import Data.Sum.Base as Sum using (_⊎_; inj₁; inj₂)
open import Data.Sum.Relation.Binary.Pointwise using (inj₁; inj₂; _⊎ₛ_; drop-inj₁; drop-inj₂)
open import Data.Unit.Polymorphic using (tt)
open import Function.Bundles using (Func; _⟨$⟩_)
open import Function.Construct.Composition using (function)
open import Function.Construct.Setoid using () renaming (setoid to _⇨_)
open import Relation.Binary using (Setoid)
import  Relation.Binary.Reasoning.Setoid as SetoidR

open import Categories.Category using (Category; _[_,_])
open import Categories.Category.Instance.Setoids using (Setoids)
open import Categories.Category.Extensive using (Extensive)
open import Categories.Category.Cocartesian using (Cocartesian)
open import Categories.Diagram.Pullback using (Pullback)
open import Categories.Category.Instance.Properties.Setoids.Limits.Canonical using (pullback; FiberProduct)
open import Categories.Category.Monoidal.Instance.Setoids using (Setoids-Cocartesian)

open Pullback
open Func

-- Note the Setoids is extensive if the two levels coincide.  Whether it happens more generally
-- is unknown at this point.

Setoids-Extensive : ( : Level)  Extensive (Setoids  )
Setoids-Extensive  = record
   { cocartesian = record { initial = initial ; coproducts = coproducts }
   ; pullback₁ = λ f  pullback   f i₁
   ; pullback₂ = λ f  pullback   f i₂
   ; pullback-of-cp-is-cp = λ f  record
        { [_,_] = λ g h  copair f g h
        ; inject₁ = λ {X g h z}  copair-inject₁ f g h z
        ; inject₂ = λ {X g h z}  copair-inject₂ f g h z
        ; unique = λ {X u g h} feq₁ feq₂ {z}  copair-unique f g h u z feq₁ feq₂
        }
   ; pullback₁-is-mono = λ _ _ eq  drop-inj₁ eq
   ; pullback₂-is-mono = λ _ _ eq  drop-inj₂ eq
   ; disjoint = λ {A B}  record
        { commute = λ { {()}}
        ; universal = λ {C f g} eq  record
           { to = λ z  conflict A B (eq {z})
           ; cong = λ z  tt
           }
        ; unique = λ _ _  tt
        ; p₁∘universal≈h₁ = λ {_ _ _ eq x}  conflict A B (eq {x})
        ; p₂∘universal≈h₂ = λ {_ _ _ eq y}  conflict A B (eq {y})
        }
   }
     where
       infixr 9 _∙_
       _∙_ : {a₁ a₂ b₁ b₂ c₁ c₂ : Level} {A : Setoid a₁ a₂} {B : Setoid b₁ b₂}
         {C : Setoid c₁ c₂}  Func B C  Func A B  Func A C
       f  g = function g f

       open Cocartesian (Setoids-Cocartesian {} {})
       open Relation.Binary.IsEquivalence
       open Setoid renaming (_≈_ to [_][_≈_]; Carrier to ∣_∣) using (isEquivalence)

       -- must be in the standard library. Maybe it is?
       conflict :  { ℓ' ℓ''} (X Y : Setoid  ℓ') {Z : Set ℓ''}
         {x :  X } {y :  Y }  [ X ⊎ₛ Y ][ inj₁ x  inj₂ y ]  Z
       conflict X Y ()

       module Diagram {A B C X : Setoid  } (f : Func C (A ⊎ₛ B))
         (g : Func (P (pullback   f i₁)) X) (h : Func (P (pullback   f i₂)) X) where

         private
           module A = Setoid A
           module B = Setoid B
           module C = Setoid C
           module X = Setoid X
           A⊎B = A ⊎ₛ B
           module A⊎B = Setoid (A ⊎ₛ B)

           A′ = P (pullback   f i₁)
           B′ = P (pullback   f i₂)
           A⊎B′ = A′ ⊎ₛ B′
           module A⊎B′ = Setoid A⊎B′
           open SetoidR A⊎B′

         _⊎⟶_ : {o₁ o₂ o₃ ℓ₁ ℓ₂ ℓ₃ : Level} {X : Setoid o₁ ℓ₁} {Y : Setoid o₂ ℓ₂} {Z : Setoid o₃ ℓ₃} 
           (Func X Z)  (Func Y Z)  Func (X ⊎ₛ Y) Z
         f₁ ⊎⟶ f₂ = record
           { to = Sum.[ f₁ ⟨$⟩_ , f₂ ⟨$⟩_ ]
           ; cong = λ { (inj₁ x)  cong f₁ x ; (inj₂ x)  cong f₂ x}
           }

         to-⊎ₛ : (z :  C )  (w :  A ⊎ₛ B )  (eq : [ A⊎B ][ f ⟨$⟩ z  w ])   A′ ⊎ₛ B′ 
         to-⊎ₛ z (inj₁ x) eq = inj₁ (record { elem₁ = z ; elem₂ = x ; commute = eq })
         to-⊎ₛ z (inj₂ y) eq = inj₂ (record { elem₁ = z ; elem₂ = y ; commute = eq })

         to-⊎ₛ-cong : {z :  C } {w w′ :  A ⊎ₛ B  }  [ A⊎B ][ w  w′ ] 
           {eq : [ A⊎B ][ f ⟨$⟩ z  w ]}  {eq′ : [ A⊎B ][ f ⟨$⟩ z  w′ ]}  [ A⊎B′ ][ to-⊎ₛ z w eq  to-⊎ₛ z w′ eq′ ]
         to-⊎ₛ-cong (inj₁ x) = inj₁ (C.refl , x)
         to-⊎ₛ-cong (inj₂ x) = inj₂ (C.refl , x)

         f⟨$⟩→ : (z :  C )   A′ ⊎ₛ B′ 
         f⟨$⟩→ z = to-⊎ₛ z (f ⟨$⟩ z) A⊎B.refl

         f⟨$⟩-cong′ : {z z′ :  C }  (z≈z′ : [ C ][ z  z′ ])  (w w′ :  A ⊎ₛ B ) 
           [ A⊎B ][ f ⟨$⟩ z  w ]  [ A⊎B ][ f ⟨$⟩ z′  w′ ]  [ A′ ⊎ₛ B′ ][ f⟨$⟩→ z  f⟨$⟩→ z′ ]
         f⟨$⟩-cong′ {z} {z′} z≈z′ (inj₁ x) (inj₁ x₁) fz≈w fz′≈w′ = begin
           f⟨$⟩→ z                       ≈⟨ A⊎B′.refl 
           to-⊎ₛ z (f ⟨$⟩ z) A⊎B.refl   ≈⟨ to-⊎ₛ-cong fz≈w 
           to-⊎ₛ z (inj₁ x) fz≈w       ≈⟨ inj₁ (z≈z′ , (drop-inj₁ (A⊎B.trans (A⊎B.sym fz≈w) (A⊎B.trans (cong f z≈z′) fz′≈w′)))) 
           to-⊎ₛ z′ (inj₁ x₁) fz′≈w′   ≈⟨ to-⊎ₛ-cong (A⊎B.sym fz′≈w′) 
           to-⊎ₛ z′ (f ⟨$⟩ z′) A⊎B.refl ≈⟨ A⊎B′.refl 
           f⟨$⟩→ z′ 
         f⟨$⟩-cong′ z≈z′ (inj₁ x) (inj₂ y) fz≈w fz′≈w′ = conflict A B (A⊎B.trans (A⊎B.sym fz≈w) (A⊎B.trans (cong f z≈z′) fz′≈w′))
         f⟨$⟩-cong′ z≈z′ (inj₂ y) (inj₁ x) fz≈w fz′≈w′ = conflict A B (A⊎B.trans (A⊎B.sym fz′≈w′) (A⊎B.trans (cong f (C.sym z≈z′)) fz≈w))
         f⟨$⟩-cong′ {z} {z′} z≈z′ (inj₂ y) (inj₂ y₁) fz≈w fz′≈w′ = begin
           to-⊎ₛ z (f ⟨$⟩ z) A⊎B.refl   ≈⟨ to-⊎ₛ-cong fz≈w 
           to-⊎ₛ z (inj₂ y) fz≈w       ≈⟨ inj₂ (z≈z′ , (drop-inj₂ (A⊎B.trans (A⊎B.sym fz≈w) (A⊎B.trans (cong f z≈z′) fz′≈w′)))) 
           to-⊎ₛ z′ (inj₂ y₁) fz′≈w′   ≈⟨ to-⊎ₛ-cong (A⊎B.sym fz′≈w′) 
           to-⊎ₛ z′ (f ⟨$⟩ z′) A⊎B.refl 

         f⟨$⟩-cong : {i j :  C }  i C.≈ j  [ A′ ⊎ₛ B′ ][ f⟨$⟩→ i  f⟨$⟩→ j ]
         f⟨$⟩-cong {i} {j} i≈j = f⟨$⟩-cong′ i≈j (f ⟨$⟩ i) (f ⟨$⟩ j) A⊎B.refl A⊎B.refl

         f⟨$⟩ : Func C (A′ ⊎ₛ B′)
         f⟨$⟩ = record { to = f⟨$⟩→ ; cong = f⟨$⟩-cong }

         -- copairing of g : A′ → X and h : B′ → X, resulting in C → X
         copair : Func C X
         copair = (g ⊎⟶ h)  f⟨$⟩

         copair-inject₁ : (z : FiberProduct f i₁)  [ X ][ copair ⟨$⟩ (FiberProduct.elem₁ z)  g ⟨$⟩ z ]
         copair-inject₁ record { elem₁ = z ; elem₂ = x ; commute = eq } = cong (g ⊎⟶ h) (to-⊎ₛ-cong eq)

         copair-inject₂ : (z : FiberProduct f i₂)  [ X ][ copair ⟨$⟩ (FiberProduct.elem₁ z)  h ⟨$⟩ z ]
         copair-inject₂ record { elem₁ = z ; elem₂ = y ; commute = eq } = cong (g ⊎⟶ h) (to-⊎ₛ-cong eq)

         copair-unique′ : (u : Func C X) (z :  C ) 
           [ A′  X ][ u  p₁ (pullback   f i₁)  g ] 
           [ B′  X ][ u  p₁ (pullback   f i₂)  h ] 
           (w :  A⊎B )  [ A⊎B ][ f ⟨$⟩ z  w ] 
           [ X ][ copair ⟨$⟩ z  u ⟨$⟩ z ]
         copair-unique′ u z feq₁ feq₂ (inj₁ x) fz≈w = XR.begin
           copair ⟨$⟩ z                                XR.≈⟨ X.refl 
           g ⊎⟶ h ⟨$⟩ (to-⊎ₛ z (f ⟨$⟩ z) A⊎B.refl)     XR.≈⟨ cong (g ⊎⟶ h) (to-⊎ₛ-cong fz≈w) 
           g ⊎⟶ h ⟨$⟩ (to-⊎ₛ z (inj₁ x) fz≈w)         XR.≈⟨ X.refl 
           g ⟨$⟩ fb                                    XR.≈⟨ X.sym (feq₁ {x = fb}) 
           u ⟨$⟩ z                                     XR.∎

           where
             module XR = SetoidR X
             fb = record {elem₁ = z ; elem₂ = x; commute = fz≈w }
         copair-unique′ u z feq₁ feq₂ (inj₂ y) fz≈w = XR.begin
           copair ⟨$⟩ z                                XR.≈⟨ X.refl 
           g ⊎⟶ h ⟨$⟩ (to-⊎ₛ z (f ⟨$⟩ z) A⊎B.refl)     XR.≈⟨ cong (g ⊎⟶ h) (to-⊎ₛ-cong fz≈w) 
           g ⊎⟶ h ⟨$⟩ (to-⊎ₛ z (inj₂ y) fz≈w)         XR.≈⟨ X.refl 
           h ⟨$⟩ fb                                    XR.≈⟨ X.sym feq₂ 
           u ⟨$⟩ z                                     XR.∎
           where
             module XR = SetoidR X
             fb = record {elem₁ = z ; elem₂ = y; commute = fz≈w }

         copair-unique : (u : Func C X) (z :  C ) 
           [ A′  X ][ u  p₁ (pullback   f i₁)  g ] 
           [ B′  X ][ u  p₁ (pullback   f i₂)  h ] 
           [ X ][ copair ⟨$⟩ z  u ⟨$⟩ z ]
         copair-unique u z feq₁ feq₂ = copair-unique′ u z feq₁ feq₂ (f ⟨$⟩ z) A⊎B.refl

       open Diagram