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

open import Fragment.Algebra.Signature

module Fragment.Algebra.Quotient (Σ : Signature) where

open import Fragment.Algebra.Algebra Σ
open import Fragment.Algebra.Homomorphism Σ
open import Fragment.Setoid.Morphism using (_↝_)

open import Level using (Level; Setω; _⊔_; suc)
open import Data.Vec.Properties using (map-id)
open import Relation.Binary using (Setoid; IsEquivalence; Rel)
import Relation.Binary.PropositionalEquality as PE

private
  variable
    a b ℓ₁ ℓ₂ ℓ₃ ℓ₄ : Level

module _
  (A : Algebra {a} {ℓ₁})
  (_≈_ : Rel  A  ℓ₂)
  where

  private
    setoid' : IsEquivalence _≈_  Setoid _ _
    setoid' isEquivalence =
      record { Carrier       =  A 
             ; _≈_           = _≈_
             ; isEquivalence = isEquivalence
             }

  record IsDenominator : Set (a  suc ℓ₁  suc ℓ₂) where
    field
      isEquivalence : IsEquivalence _≈_
      isCoarser     :  {x y}  x =[ A ] y  x  y
      isCompatible  : Congruence (setoid' isEquivalence) (A ⟦_⟧_)

module _
  (A : Algebra {a} {ℓ₁})
  (_≈_ : Rel  A  ℓ₄)
  {{isDenom : IsDenominator A _≈_}}
  where

  record IsQuotient (A/≈ : Algebra {b} {ℓ₂}) : Setω where
    field
      inc : A  A/≈

      factor :  {c ℓ₃} {X : Algebra {c} {ℓ₃}}
                (f : A  X)
                (Congruent _≈_ ≈[ X ]  f )
                A/≈  X

      commute :  {c ℓ₃} {X : Algebra {c} {ℓ₃}}
                (f : A  X)
                (cong : Congruent _≈_ ≈[ X ]  f )
                factor f cong  inc  f

      universal :  {c ℓ₃} {X : Algebra {c} {ℓ₃}}
                   (f : A  X)
                   (cong : Congruent _≈_ ≈[ X ]  f )
                   {h : A/≈  X}
                   h  inc  f
                   factor f cong  h

  open IsDenominator isDenom

  ∥_∥/_ : Setoid _ _
  ∥_∥/_ = record { Carrier       =  A 
                 ; _≈_           = _≈_
                 ; isEquivalence = isEquivalence
                 }

  _/_-isAlgebra : IsAlgebra ∥_∥/_
  _/_-isAlgebra = record { ⟦_⟧     = A ⟦_⟧_
                         ; ⟦⟧-cong = isCompatible
                         }

  _/_ : Algebra
  _/_ = record { ∥_∥/≈           = ∥_∥/_
               ; ∥_∥/≈-isAlgebra = _/_-isAlgebra
               }

  private

    ∣inc∣⃗ :  A ∥/≈  ∥_∥/_
    ∣inc∣⃗ = record { ∣_∣      = λ x  x
                    ; ∣_∣-cong = isCoarser
                    }

    ∣inc∣-hom : Homomorphic A _/_  x  x)
    ∣inc∣-hom f xs =
      Setoid.reflexive ∥_∥/_ (PE.cong (A  f ⟧_) (map-id xs))

  inc : A  _/_
  inc = record { ∣_∣⃗    = ∣inc∣⃗
               ; ∣_∣-hom = ∣inc∣-hom
               }

  module _ {c ℓ₃}
    {X : Algebra {c} {ℓ₃}}
    (f : A  X)
    (cong : Congruent _≈_ ≈[ X ]  f )
    where

    private

      ∣factor∣⃗ : ∥_∥/_   X ∥/≈
      ∣factor∣⃗ = record { ∣_∣      =  f 
                         ; ∣_∣-cong = cong
                         }

    factor : _/_  X
    factor = record { ∣_∣⃗    = ∣factor∣⃗
                    ; ∣_∣-hom =  f ∣-hom
                    }

    factor-commute : factor  inc  f
    factor-commute = Setoid.refl  X ∥/≈

    factor-universal :  {h : _/_  X}  h  inc  f  factor  h
    factor-universal p {x} = sym (p {x})
      where open Setoid  X ∥/≈

  _/_-isQuotient : IsQuotient (_/_)
  _/_-isQuotient = record { inc       = inc
                          ; factor    = factor
                          ; commute   = factor-commute
                          ; universal = factor-universal
                          }