------------------------------------------------------------------------
-- The Agda standard library
--
-- Quotients for Heterogeneous equality
------------------------------------------------------------------------

{-# OPTIONS --with-K --safe #-}

module Relation.Binary.HeterogeneousEquality.Quotients where

open import Function.Base using (_$_; const; _∘_)
open import Level hiding (lift)
open import Relation.Binary.Bundles using (Setoid)
open import Relation.Binary.HeterogeneousEquality
open ≅-Reasoning

record Quotient {c } (S : Setoid c ) : Set (suc (c  )) where
  open Setoid S renaming (Carrier to A)
  field
    Q   : Set c
    abs : A  Q

  compat : (B : Q  Set c) (f :  a  B (abs a))  Set (c  )
  compat _ f = {a a′ : A}  a  a′  f a  f a′

  field
    compat-abs : compat _ abs
    lift       : (B : Q  Set c) (f :  a  B (abs a))  compat B f   q  B q
    lift-conv  : {B : Q  Set c} (f :  a  B (abs a)) (p : compat B f) 
                  a  lift B f p (abs a)  f a

Quotients :  c   Set (suc (c  ))
Quotients c  = (S : Setoid c )  Quotient S

module Properties {c } {S : Setoid c } (Qu : Quotient S) where

  open Setoid S renaming (Carrier to A) hiding (refl; sym; trans)
  open Quotient Qu

  module _ {B B′ : Q  Set c} {f :  a  B (abs a)} {p : compat B f} where

   lift-unique : {g :  q  B′ q}  (∀ a  g (abs a)  f a) 
                  x  lift B f p x  g x
   lift-unique {g} ext = lift _ liftf≅g liftf≅g-ext where

     liftf≅g :  a  lift B f p (abs a)  g (abs a)
     liftf≅g x = begin
       lift _ f p (abs x) ≅⟨ lift-conv f p x 
       f x                ≅⟨ sym (ext x) 
       g (abs x)          

     liftf≅g-ext :  {a a′}  a  a′  liftf≅g a  liftf≅g a′
     liftf≅g-ext eq = ≅-heterogeneous-irrelevantˡ _ _
                    $ cong (lift B f p) (compat-abs eq)

   lift-ext : {g :  a  B′ (abs a)} {p′ : compat B′ g}  (∀ x  f x  g x) 
               x  lift B f p x  lift B′ g p′ x
   lift-ext {g} {p′} h = lift-unique $ λ a  begin
       lift B′ g p′ (abs a) ≅⟨ lift-conv g p′ a 
       g a                  ≅⟨ sym (h a) 
       f a                  

  lift-conv-abs :  a  lift (const Q) abs compat-abs a  a
  lift-conv-abs = lift-unique  _  refl)

  lift-fold : {B : Q  Set c} (f :  q  B q) 
               a  lift B (f  abs) (cong f  compat-abs) a  f a
  lift-fold f = lift-unique  _  refl)

  abs-epimorphism : {B : Q  Set c} {f g :  q  B q} 
                    (∀ x  f (abs x)  g (abs x))   q  f q  g q
  abs-epimorphism {B} {f} {g} p q = begin
    f q                                      ≅⟨ sym (lift-fold f q) 
    lift B (f  abs) (cong f  compat-abs) q ≅⟨ lift-ext p q 
    lift B (g  abs) (cong g  compat-abs) q ≅⟨ lift-fold g q 
    g q                                      

------------------------------------------------------------------------
-- Properties provable with extensionality

module _ (ext :  {a b} {A : Set a} {B₁ B₂ : A  Set b} {f₁ :  a  B₁ a}
                {f₂ :  a  B₂ a}  (∀ a  f₁ a  f₂ a)  f₁  f₂) where

  module Properties₂
         {c } {S₁ S₂ : Setoid c } (Qu₁ : Quotient S₁) (Qu₂ : Quotient S₂)
         where

    module S₁  = Setoid S₁
    module S₂  = Setoid S₂
    module Qu₁ = Quotient Qu₁
    module Qu₂ = Quotient Qu₂

    module _  {B : _  _  Set c} (f :  s₁ s₂  B (Qu₁.abs s₁) (Qu₂.abs s₂)) where

     compat₂ : Set _
     compat₂ =  {a b a′ b′}  a S₁.≈ a′  b S₂.≈ b′  f a b  f a′ b′

     lift₂ : compat₂   q q′  B q q′
     lift₂ p = Qu₁.lift _ g (ext  g-ext) module Lift₂ where

       g :  a q  B (Qu₁.abs a) q
       g a = Qu₂.lift (B (Qu₁.abs a)) (f a) (p S₁.refl)

       g-ext :  {a a′}  a S₁.≈ a′   q  g a q  g a′ q
       g-ext a≈a′ = Properties.lift-ext Qu₂  _  p a≈a′ S₂.refl)

     lift₂-conv : (p : compat₂)   a a′  lift₂ p (Qu₁.abs a) (Qu₂.abs a′)  f a a′
     lift₂-conv p a a′ = begin
       lift₂ p (Qu₁.abs a) (Qu₂.abs a′)
          ≅⟨ cong (_$ (Qu₂.abs a′)) (Qu₁.lift-conv (Lift₂.g p) (ext  Lift₂.g-ext p) a) 
       Lift₂.g p a (Qu₂.abs a′)
          ≡⟨⟩
       Qu₂.lift (B (Qu₁.abs a)) (f a) (p S₁.refl) (Qu₂.abs a′)
          ≅⟨ Qu₂.lift-conv (f a) (p S₁.refl) a′ 
       f a a′
          

  module Properties₃ {c } {S₁ S₂ S₃ : Setoid c }
         (Qu₁ : Quotient S₁) (Qu₂ : Quotient S₂) (Qu₃ : Quotient S₃)
         where

    module S₁  = Setoid S₁
    module S₂  = Setoid S₂
    module S₃  = Setoid S₃
    module Qu₁ = Quotient Qu₁
    module Qu₂ = Quotient Qu₂
    module Qu₃ = Quotient Qu₃

    module _ {B : _  _  _  Set c}
             (f :  s₁ s₂ s₃  B (Qu₁.abs s₁) (Qu₂.abs s₂) (Qu₃.abs s₃)) where

     compat₃ : Set _
     compat₃ =  {a b c a′ b′ c′}  a S₁.≈ a′  b S₂.≈ b′  c S₃.≈ c′ 
               f a b c  f a′ b′ c′

     lift₃ : compat₃   q₁ q₂ q₃  B q₁ q₂ q₃
     lift₃ p = Qu₁.lift _ h (ext  h-ext) module Lift₃ where

       g :  a b q  B (Qu₁.abs a) (Qu₂.abs b) q
       g a b = Qu₃.lift (B (Qu₁.abs a) (Qu₂.abs b)) (f a b) (p S₁.refl S₂.refl)

       g-ext :  {a a′ b b′}  a S₁.≈ a′  b S₂.≈ b′   c  g a b c  g a′ b′ c
       g-ext a≈a′ b≈b′ = Properties.lift-ext Qu₃  _  p a≈a′ b≈b′ S₃.refl)

       h :  a q₂ q₃  B (Qu₁.abs a) q₂ q₃
       h a = Qu₂.lift  b   q  B (Qu₁.abs a) b q) (g a) (ext  g-ext S₁.refl)

       h-ext :  {a a′}  a S₁.≈ a′   b  h a b  h a′ b
       h-ext a≈a′ = Properties.lift-ext Qu₂ $ λ _  ext (g-ext a≈a′ S₂.refl)