{-# OPTIONS --without-K --rewriting --overlapping-instances #-}

open import lib.Basics
open import lib.types.Bool
open import lib.types.Empty
open import lib.types.Lift
open import lib.types.Sigma
open import lib.types.Pi

module lib.types.Coproduct where

module _ {i j} {A : Type i} {B : Type j} where

  Coprod-elim :  {k} {C : A  B  Type k}
     ((a : A)  C (inl a))  ((b : B)  C (inr b))
     Π (A  B) C
  Coprod-elim f g (inl a) = f a
  Coprod-elim f g (inr b) = g b

  ⊔-elim = Coprod-elim

  Coprod-rec :  {k} {C : Type k}
     (A  C)  (B  C)  (A  B  C)
  Coprod-rec {C = C} = Coprod-elim {C = λ _  C}

  ⊔-rec = Coprod-rec

  Coprod= : Coprod A B  Coprod A B  Type (lmax i j)
  Coprod= (inl a₁) (inl a₂) = Lift {j = (lmax i j)} $ a₁ == a₂
  Coprod= (inl a₁) (inr b₂) = Lift Empty
  Coprod= (inr b₁) (inl a₂) = Lift Empty
  Coprod= (inr b₁) (inr b₂) = Lift {j = (lmax i j)} $ b₁ == b₂

  Coprod=-in : {x y : Coprod A B}  (x == y)  Coprod= x y
  Coprod=-in {inl _} idp = lift idp
  Coprod=-in {inr _} idp = lift idp

  Coprod=-out : {x y : Coprod A B}  Coprod= x y  (x == y)
  Coprod=-out {inl _} {inl _} c = ap inl $ lower c
  Coprod=-out {inl _} {inr _} c = Empty-rec $ lower c
  Coprod=-out {inr _} {inl _} c = Empty-rec $ lower c
  Coprod=-out {inr _} {inr _} c = ap inr (lower c)

  Coprod=-in-equiv : (x y : Coprod A B)  (x == y)  Coprod= x y
  Coprod=-in-equiv x y = equiv Coprod=-in Coprod=-out (f-g x y) (g-f x y)
    where f-g :  x' y'   c  Coprod=-in (Coprod=-out {x'} {y'} c) == c
          f-g (inl a₁) (inl .a₁) (lift idp) = idp
          f-g (inl a₁) (inr b₂) b = Empty-rec $ lower b
          f-g (inr b₁) (inl a₂) b = Empty-rec $ lower b
          f-g (inr b₁) (inr .b₁) (lift idp) = idp

          g-f :  x' y'   p  Coprod=-out (Coprod=-in {x'} {y'} p) == p
          g-f (inl _) .(inl _) idp = idp
          g-f (inr _) .(inr _) idp = idp

  inl=inl-equiv : (a₁ a₂ : A)  (inl a₁ == inl a₂)  (a₁ == a₂)
  inl=inl-equiv a₁ a₂ = lower-equiv ∘e Coprod=-in-equiv (inl a₁) (inl a₂)

  inr=inr-equiv : (b₁ b₂ : B)  (inr b₁ == inr b₂)  (b₁ == b₂)
  inr=inr-equiv b₁ b₂ = lower-equiv ∘e Coprod=-in-equiv (inr b₁) (inr b₂)

  inl≠inr : (a₁ : A) (b₂ : B)  (inl a₁  inr b₂)
  inl≠inr a₁ b₂ p = lower $ Coprod=-in p

  inr≠inl : (b₁ : B) (a₂ : A)  (inr b₁  inl a₂)
  inr≠inl a₁ b₂ p = lower $ Coprod=-in p

  ⊔-level :  {n}  has-level (S (S n)) A  has-level (S (S n)) B
             has-level (S (S n)) (Coprod A B)
  ⊔-level {n} pA pB = has-level-in (⊔-level-aux pA pB) where
      
    instance _ = pA; _ = pB

    ⊔-level-aux : has-level (S (S n)) A  has-level (S (S n)) B
       has-level-aux (S (S n)) (Coprod A B)
    ⊔-level-aux _ _ (inl a₁) (inl a₂) = equiv-preserves-level (inl=inl-equiv a₁ a₂ ⁻¹)
    ⊔-level-aux _ _ (inl a₁) (inr b₂) = has-level-in  p  Empty-rec (inl≠inr a₁ b₂ p))
    ⊔-level-aux _ _ (inr b₁) (inl a₂) = has-level-in  p  Empty-rec (inr≠inl b₁ a₂ p))
    ⊔-level-aux _ _ (inr b₁) (inr b₂) = equiv-preserves-level ((inr=inr-equiv b₁ b₂)⁻¹)

  instance
    ⊔-level-instance :  {n}  {{has-level (S (S n)) A}}  {{has-level (S (S n)) B}}
             has-level (S (S n)) (Coprod A B)
    ⊔-level-instance = ⊔-level ⟨⟩ ⟨⟩

  Coprod-level = ⊔-level

module _ {i j} {A : Type i} {B : Type j} where

  Coprod-rec-post∘ :  {k l} {C : Type k} {D : Type l}
     (h : C  D) (f : A  C) (g : B  C)
     h  Coprod-rec f g  Coprod-rec (h  f) (h  g)
  Coprod-rec-post∘ h f g (inl _) = idp
  Coprod-rec-post∘ h f g (inr _) = idp

  ⊔-rec-post∘ = Coprod-rec-post∘

module _ {i i' j j'} {A : Type i} {A' : Type i'} {B : Type j} {B' : Type j'}
  (f : A  A') (g : B  B') where

  ⊔-fmap : A  B  A'  B'
  ⊔-fmap (inl a) = inl (f a)
  ⊔-fmap (inr b) = inr (g b)

infix 80 _⊙⊔_
_⊙⊔_ :  {i j}  Ptd i  Ptd j  Ptd (lmax i j)
X ⊙⊔ Y = ⊙[ Coprod (de⊙ X) (de⊙ Y) , inl (pt X) ]

_⊔⊙_ :  {i j}  Ptd i  Ptd j  Ptd (lmax i j)
X ⊔⊙ Y = ⊙[ Coprod (de⊙ X) (de⊙ Y) , inr (pt Y) ]

module _ {i j k} {X : Ptd i} {Y : Ptd j} {Z : Ptd k}
  (f : X ⊙→ Z) (g : Y ⊙→ Z) where

  ⊙⊔-rec : X ⊙⊔ Y ⊙→ Z
  ⊙⊔-rec = Coprod-rec (fst f) (fst g) , snd f

  ⊔⊙-rec : X ⊔⊙ Y ⊙→ Z
  ⊔⊙-rec = Coprod-rec (fst f) (fst g) , snd g

module _ {i i' j j'} {X : Ptd i} {X' : Ptd i'} {Y : Ptd j} {Y' : Ptd j'}
  (f : X ⊙→ X') (g : Y ⊙→ Y') where

  ⊙⊔-fmap : X ⊙⊔ Y ⊙→ X' ⊙⊔ Y'
  ⊙⊔-fmap = ⊔-fmap (fst f) (fst g) , ap inl (snd f)

  ⊔⊙-fmap : X ⊔⊙ Y ⊙→ X' ⊔⊙ Y'
  ⊔⊙-fmap = ⊔-fmap (fst f) (fst g) , ap inr (snd g)

codiag :  {i} {A : Type i}  A  A  A
codiag = Coprod-rec (idf _) (idf _)

⊙codiag :  {i} {X : Ptd i}  X ⊙⊔ X ⊙→ X
⊙codiag = (codiag , idp)

-- A binary sigma is a coproduct
ΣBool-equiv-⊔ :  {i} (Pick : Bool  Type i)
   Σ Bool Pick  Pick true  Pick false
ΣBool-equiv-⊔ Pick = equiv into out into-out out-into
  where
  into : Σ _ Pick  Pick true  Pick false
  into (true , a) = inl a
  into (false , b) = inr b

  out : (Pick true  Pick false)  Σ _ Pick
  out (inl a) = (true , a)
  out (inr b) = (false , b)

  abstract
    into-out :  c  into (out c) == c
    into-out (inl a) = idp
    into-out (inr b) = idp

    out-into :  s  out (into s) == s
    out-into (true , a) = idp
    out-into (false , b) = idp

⊔₁-Empty :  {i} (A : Type i)  Empty  A  A
⊔₁-Empty A = equiv (λ{(inl ()); (inr a)  a}) inr  _  idp) (λ{(inl ()); (inr _)  idp})

module _ {i j k} {A : Type i} {B : Type j} (P : A  B  Type k) where
  Π₁-⊔-equiv-× : Π (A  B) P  Π A (P  inl) × Π B (P  inr)
  Π₁-⊔-equiv-× = equiv to from to-from from-to
    where
    to : Π (A  B) P  Π A (P  inl) × Π B (P  inr)
    to f =  a  f (inl a)) ,  b  f (inr b))

    from : Π A (P  inl) × Π B (P  inr)  Π (A  B) P
    from (f , g) (inl a) = f a
    from (f , g) (inr b) = g b

    abstract
      to-from :  fg  to (from fg) == fg
      to-from _ = idp

      from-to :  fg  from (to fg) == fg
      from-to fg = λ= λ where (inl _)  idp
                              (inr _)  idp

-- the empty type is a unit for the coproduct
Coprod-unit-l :  {i} (A : Type i)    A  A
Coprod-unit-l {i} (A) = equiv to from to-from from-to
  where
    to :   A  A
    to (inl ())
    to (inr a) = a

    from : A    A
    from a = inr a

    to-from : (a : A)  to (from a) == a
    to-from a = idp

    from-to : (x :   A)  from (to x) == x
    from-to (inl ())
    from-to (inr a) = idp