{-# OPTIONS --safe #-}

module Multiset.Relation.Base where

open import Multiset.Prelude
open import Multiset.Util.Relation public

open import Cubical.Foundations.Function using (_∘_ ; idfun)
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Structure using (TypeWithStr)
open import Cubical.Foundations.Structure using (⟨_⟩ ; str) public
open import Cubical.Reflection.RecordEquiv using (declareRecordIsoΣ)
open import Cubical.Relation.Binary using (Rel ; module BinaryRelation)
open import Cubical.Data.Sigma.Properties using (Σ≡Prop)

private
  variable
    ℓ ℓ′ ℓ≈ ℓ≈′ : Level

open BinaryRelation using (isRefl ; isSym ; isTrans ; isEquivRel ; isPropValued)

record IsRelation {A : Type ℓ} (_≈_ : Rel A A ℓ≈) : Type (ℓ-max ℓ ℓ≈) where
  no-eta-equality
  field
    is-set-carrier : isSet A
    is-prop-rel : isPropValued _≈_

record RelationStr (ℓ≈ : Level) (A : Type ℓ) : Type (ℓ-max ℓ (ℓ-suc ℓ≈)) where
  no-eta-equality
  field
    rel : Rel A A ℓ≈
    is-relation : IsRelation rel

  open IsRelation is-relation public

Relation : (ℓ ℓ≈ : Level) → Type _
Relation ℓ ℓ≈ = TypeWithStr ℓ (RelationStr ℓ≈)

RelOf : (S : Relation ℓ ℓ≈) → Rel ⟨ S ⟩ ⟨ S ⟩ ℓ≈
RelOf S = str S .RelationStr.rel

RelOf-syntax : (S : Relation ℓ ℓ≈) → Rel ⟨ S ⟩ ⟨ S ⟩ ℓ≈
RelOf-syntax = RelOf

syntax RelOf-syntax S a b = a ≈⟨ S ⟩ b

PathRelation : (A : Type ℓ) → isSet A → Relation ℓ ℓ
PathRelation A setA .fst = A
PathRelation A setA .snd .RelationStr.rel = Path A
PathRelation A setA .snd .RelationStr.is-relation .IsRelation.is-set-carrier = setA
PathRelation A setA .snd .RelationStr.is-relation .IsRelation.is-prop-rel = setA

module _ {ℓ ℓ≈ ℓ′ ℓ≈′ : Level} (S : Relation ℓ ℓ≈) (R : Relation ℓ′ ℓ≈′) where
  open IsRelation
  open RelationStr

  PreservesRelation : (f : ⟨ S ⟩ → ⟨ R ⟩) → Type (ℓ-max (ℓ-max ℓ ℓ≈) ℓ≈′)
  PreservesRelation f = ∀ {a b : ⟨ S ⟩} → a ≈⟨ S ⟩ b → f a ≈⟨ R ⟩ f b

  isPropPreservesRelation : {f : ⟨ S ⟩ → ⟨ R ⟩} → isProp (PreservesRelation f)
  isPropPreservesRelation = isPropImplicitΠ2 λ _ _ → isPropΠ λ _ → str R .is-prop-rel _ _

  record Rel[_⇒_] : Type (ℓ-max (ℓ-max ℓ ℓ′) (ℓ-max ℓ≈ ℓ≈′)) where
    constructor rel⇒
    field
      morphism : ⟨ S ⟩ → ⟨ R ⟩
      preserves-relation : PreservesRelation morphism

  open Rel[_⇒_]
  Rel⇒IsoΣ : Iso Rel[_⇒_] (Σ (⟨ S ⟩ → ⟨ R ⟩) PreservesRelation)
  Rel⇒IsoΣ .Iso.fun f .fst = f .morphism
  Rel⇒IsoΣ .Iso.fun f .snd = f .preserves-relation
  Rel⇒IsoΣ .Iso.inv p .morphism = p .fst
  Rel⇒IsoΣ .Iso.inv p .preserves-relation = p .snd
  Rel⇒IsoΣ .Iso.rightInv p = refl
  Rel⇒IsoΣ .Iso.leftInv f = refl

  isSetRel⇒ : isSet Rel[_⇒_]
  isSetRel⇒ = isOfHLevelRetractFromIso 2 Rel⇒IsoΣ (isSetΣSndProp (isSet→ (str R .is-set-carrier)) λ _ → isPropPreservesRelation)

module _ where
  open Rel[_⇒_]

  private
    variable
      ℓ₀ ℓ₁ ℓ₂ ℓ₃ : Level
      ℓR₀ ℓR₁ ℓR₂ ℓR₃ : Level

  Rel⇒Path : ∀ {R : Relation ℓ ℓ≈} {S : Relation ℓ′ ℓ≈′}
    → {f g : Rel[ R ⇒ S ]}
    → f .morphism ≡ g .morphism
    → f ≡ g
  Rel⇒Path p i .morphism = p i
  Rel⇒Path {R = R} {S} {f} {g} p i .preserves-relation =
    isProp→PathP (λ i → isPropPreservesRelation R S {f = p i}) (f .preserves-relation) (g .preserves-relation) i

  idRel⇒ : {S : Relation ℓ ℓ≈} → Rel[ S ⇒ S ]
  idRel⇒ {S = S} .morphism = idfun ⟨ S ⟩
  idRel⇒ {S = S} .preserves-relation {s₀} {s₁} = idfun (RelOf S s₀ s₁)

  module _
    {R₀ : Relation ℓ₀ ℓR₀}
    {R₁ : Relation ℓ₁ ℓR₁}
    {R₂ : Relation ℓ₂ ℓR₂}
    where

    compPreservesRelation : ∀ {f g}
      → PreservesRelation R₀ R₁ f
      → PreservesRelation R₁ R₂ g
      → PreservesRelation R₀ R₂ (g ∘ f)
    compPreservesRelation pres-f pres-g = pres-g ∘ pres-f

    compRel⇒ : Rel[ R₀ ⇒ R₁ ] → Rel[ R₁ ⇒ R₂ ] → Rel[ R₀ ⇒ R₂ ]
    compRel⇒ f g .morphism = g .morphism ∘ f .morphism
    compRel⇒ f g .preserves-relation = compPreservesRelation (f .preserves-relation) (g .preserves-relation)

    _⋆Rel⇒_ = compRel⇒

    infixl 9 _⋆Rel⇒_

  ⋆Rel⇒IdL : {R : Relation ℓ ℓ≈} {S : Relation ℓ′ ℓ≈′}
    (f : Rel[ R ⇒ S ]) → idRel⇒ ⋆Rel⇒ f ≡ f
  ⋆Rel⇒IdL _ = refl

  ⋆Rel⇒IdR : {R : Relation ℓ ℓ≈} {S : Relation ℓ′ ℓ≈′}
    (f : Rel[ R ⇒ S ]) → f ⋆Rel⇒ idRel⇒ ≡ f
  ⋆Rel⇒IdR _ = refl

  module _
    {R₀ : Relation ℓ₀ ℓR₀}
    {R₁ : Relation ℓ₁ ℓR₁}
    {R₂ : Relation ℓ₂ ℓR₂}
    {R₃ : Relation ℓ₃ ℓR₃} where

    ⋆Rel⇒Assoc :
      (f : Rel[ R₀ ⇒ R₁ ])
      (g : Rel[ R₁ ⇒ R₂ ])
      (h : Rel[ R₂ ⇒ R₃ ])
      → (f ⋆Rel⇒ g) ⋆Rel⇒ h ≡ f ⋆Rel⇒ (g ⋆Rel⇒ h)
    ⋆Rel⇒Assoc f g h = refl