------------------------------------------------------------------------
-- Pointwise lifting of binary relations to sigma types
------------------------------------------------------------------------

{-# OPTIONS --universe-polymorphism #-}

module Relation.Binary.Sigma.Pointwise where

open import Data.Product as Prod
open import Level
open import Function
open import Function.Equality as F using (_⟶_; _⟨$⟩_)
  renaming (_∘_ to _⊚_)
open import Function.Equivalence as Eq
  using (Equivalent; _⇔_; module Equivalent)
  renaming (_∘_ to _⟨∘⟩_)
open import Function.Inverse as Inv
  using (Inverse; _⇿_; module Inverse; Isomorphism)
  renaming (_∘_ to _⟪∘⟫_)
open import Function.LeftInverse
  using (_LeftInverseOf_; _RightInverseOf_)
open import Function.Surjection using (_↠_; module Surjection)
import Relation.Binary as B
open import Relation.Binary.Indexed as I using (_at_)
import Relation.Binary.HeterogeneousEquality as H
import Relation.Binary.PropositionalEquality as P

------------------------------------------------------------------------
-- Pointwise lifting

infixr 4 _,_

data REL {a₁ a₂ b₁ b₂ ℓ₁ ℓ₂}
         {A₁ : Set a₁} (B₁ : A₁  Set b₁)
         {A₂ : Set a₂} (B₂ : A₂  Set b₂)
         (_R₁_ : B.REL A₁ A₂ ℓ₁) (_R₂_ : I.REL B₁ B₂ ℓ₂) :
         B.REL (Σ A₁ B₁) (Σ A₂ B₂) (a₁  a₂  b₁  b₂  ℓ₁  ℓ₂) where
  _,_ : {x₁ : A₁} {y₁ : B₁ x₁} {x₂ : A₂} {y₂ : B₂ x₂}
        (x₁Rx₂ : x₁ R₁ x₂) (y₁Ry₂ : y₁ R₂ y₂) 
        REL B₁ B₂ _R₁_ _R₂_ (x₁ , y₁) (x₂ , y₂)

Rel :  {a b ℓ₁ ℓ₂} {A : Set a} (B : A  Set b)
      (_R₁_ : B.Rel A ℓ₁) (_R₂_ : I.Rel B ℓ₂)  B.Rel (Σ A B) _
Rel B = REL B B

------------------------------------------------------------------------
-- Rel preserves many properties

private
 module Dummy {a b ℓ₁ ℓ₂} {A : Set a} {B : A  Set b}
              {R₁ : B.Rel A ℓ₁} {R₂ : I.Rel B ℓ₂} where

  refl : B.Reflexive R₁  I.Reflexive B R₂ 
         B.Reflexive (Rel B R₁ R₂)
  refl refl₁ refl₂ {x = (x , y)} = (refl₁ , refl₂)

  symmetric : B.Symmetric R₁  I.Symmetric B R₂ 
              B.Symmetric (Rel B R₁ R₂)
  symmetric sym₁ sym₂ (x₁Rx₂ , y₁Ry₂) = (sym₁ x₁Rx₂ , sym₂ y₁Ry₂)

  transitive : B.Transitive R₁  I.Transitive B R₂ 
               B.Transitive (Rel B R₁ R₂)
  transitive trans₁ trans₂ (x₁Rx₂ , y₁Ry₂) (x₂Rx₃ , y₂Ry₃) =
    (trans₁ x₁Rx₂ x₂Rx₃ , trans₂ y₁Ry₂ y₂Ry₃)

  isEquivalence : B.IsEquivalence R₁  I.IsEquivalence B R₂ 
                  B.IsEquivalence (Rel B R₁ R₂)
  isEquivalence eq₁ eq₂ = record
    { refl  = refl (B.IsEquivalence.refl eq₁)
                   (I.IsEquivalence.refl eq₂)
    ; sym   = symmetric (B.IsEquivalence.sym eq₁)
                        (I.IsEquivalence.sym eq₂)
    ; trans = transitive (B.IsEquivalence.trans eq₁)
                         (I.IsEquivalence.trans eq₂)
    }

open Dummy public

setoid :  {b₁ b₂ i₁ i₂} 
         (A : B.Setoid b₁ b₂)  I.Setoid (B.Setoid.Carrier A) i₁ i₂ 
         B.Setoid _ _
setoid s₁ s₂ = record
  { isEquivalence = isEquivalence (B.Setoid.isEquivalence s₁)
                                  (I.Setoid.isEquivalence s₂)
  }

------------------------------------------------------------------------
-- The propositional equality setoid over sigma types can be
-- decomposed using Rel

Rel⇿≡ :  {a b} {A : Set a} {B : A  Set b} 
        Inverse (setoid (P.setoid A) (H.indexedSetoid B))
                (P.setoid (Σ A B))
Rel⇿≡ {a} {b} {A} {B} = record
  { to         = record { _⟨$⟩_ = id; cong = to-cong   }
  ; from       = record { _⟨$⟩_ = id; cong = from-cong }
  ; inverse-of = record
    { left-inverse-of  = uncurry  _ _  (P.refl , H.refl))
    ; right-inverse-of = λ _  P.refl
    }
  }
  where
  open I using (_=[_]⇒_)

  to-cong : Rel B P._≡_  x y  H._≅_ x y) =[ id {a = a  b} ]⇒ P._≡_
  to-cong (P.refl , H.refl) = P.refl

  from-cong : P._≡_ =[ id {a = a  b} ]⇒ Rel B P._≡_  x y  H._≅_ x y)
  from-cong {i = (x , y)} P.refl = (P.refl , H.refl)

------------------------------------------------------------------------
-- Equivalences and inverses are also preserved

equivalent :
   {a₁ a₂ b₁ b₁′ b₂ b₂′}
    {A₁ : Set a₁} {A₂ : Set a₂}
    {B₁ : I.Setoid A₁ b₁ b₁′} {B₂ : I.Setoid A₂ b₂ b₂′}
  (A₁⇔A₂ : A₁  A₂) 
  (∀ {x}  B₁ at x  B₂ at (Equivalent.to   A₁⇔A₂ ⟨$⟩ x)) 
  (∀ {y}  B₂ at y  B₁ at (Equivalent.from A₁⇔A₂ ⟨$⟩ y)) 
  Equivalent (setoid (P.setoid A₁) B₁) (setoid (P.setoid A₂) B₂)
equivalent {A₁ = A₁} {A₂} {B₁} {B₂} A₁⇔A₂ B-to B-from = record
  { to   = record { _⟨$⟩_ = to;   cong = to-cong   }
  ; from = record { _⟨$⟩_ = from; cong = from-cong }
  }
  where
  open B.Setoid (setoid (P.setoid A₁) B₁)
    using () renaming (_≈_ to _≈₁_)
  open B.Setoid (setoid (P.setoid A₂) B₂)
    using () renaming (_≈_ to _≈₂_)
  open B using (_=[_]⇒_)

  to = Prod.map (_⟨$⟩_ (Equivalent.to A₁⇔A₂)) (_⟨$⟩_ B-to)

  to-cong : _≈₁_ =[ to ]⇒ _≈₂_
  to-cong (P.refl , ) = (P.refl , F.cong B-to )

  from = Prod.map (_⟨$⟩_ (Equivalent.from A₁⇔A₂)) (_⟨$⟩_ B-from)

  from-cong : _≈₂_ =[ from ]⇒ _≈₁_
  from-cong (P.refl , ) = (P.refl , F.cong B-from )

equivalent′ :
   {a₁ a₂ b₁ b₁′ b₂ b₂′}
    {A₁ : Set a₁} {A₂ : Set a₂}
    {B₁ : I.Setoid A₁ b₁ b₁′} (B₂ : I.Setoid A₂ b₂ b₂′)
  (A₁↠A₂ : A₁  A₂) 
  (∀ {x}  Equivalent (B₁ at x) (B₂ at (Surjection.to A₁↠A₂ ⟨$⟩ x))) 
  Equivalent (setoid (P.setoid A₁) B₁) (setoid (P.setoid A₂) B₂)
equivalent′ {B₁ = B₁} B₂ A₁↠A₂ B₁⇔B₂ =
  equivalent (Surjection.equivalent A₁↠A₂) B-to B-from
  where
  B-to :  {x}  B₁ at x  B₂ at (Surjection.to A₁↠A₂ ⟨$⟩ x)
  B-to = Equivalent.to B₁⇔B₂

  subst-cong :  {i a p} {I : Set i} {A : I  Set a}
               (P :  {i}  A i  A i  Set p) {i i′} {x y : A i}
               (i≡i′ : P._≡_ i i′) 
               P x y  P (P.subst A i≡i′ x) (P.subst A i≡i′ y)
  subst-cong P P.refl p = p

  B-from :  {y}  B₂ at y  B₁ at (Surjection.from A₁↠A₂ ⟨$⟩ y)
  B-from = record
    { _⟨$⟩_ = λ x  Equivalent.from B₁⇔B₂ ⟨$⟩
                      P.subst (I.Setoid.Carrier B₂)
                         (P.sym $ Surjection.right-inverse-of A₁↠A₂ _)
                         x
    ; cong  = F.cong (Equivalent.from B₁⇔B₂) 
              subst-cong  {x}  I.Setoid._≈_ B₂ {x} {x})
                         (P.sym (Surjection.right-inverse-of A₁↠A₂ _))
    }

 :  {a₁ a₂ b₁ b₂}
      {A₁ : Set a₁} {A₂ : Set a₂}
      {B₁ : A₁  Set b₁} {B₂ : A₂  Set b₂}
    (A₁⇔A₂ : A₁  A₂) 
    (∀ {x}  B₁ x  B₂ (Equivalent.to   A₁⇔A₂ ⟨$⟩ x)) 
    (∀ {y}  B₂ y  B₁ (Equivalent.from A₁⇔A₂ ⟨$⟩ y)) 
    Σ A₁ B₁  Σ A₂ B₂
 {B₁ = B₁} {B₂} A₁⇔A₂ B-to B-from =
  Inverse.equivalent (Rel⇿≡ {B = B₂}) ⟨∘⟩
  equivalent A₁⇔A₂
    (Inverse.to (H.≡⇿≅ B₂)  P.→-to-⟶ B-to  Inverse.from (H.≡⇿≅ B₁))
    (Inverse.to (H.≡⇿≅ B₁)  P.→-to-⟶ B-from  Inverse.from (H.≡⇿≅ B₂))
    ⟨∘⟩
  Eq.sym (Inverse.equivalent (Rel⇿≡ {B = B₁}))

⇔′ :  {a₁ a₂ b₁ b₂}
       {A₁ : Set a₁} {A₂ : Set a₂}
       {B₁ : A₁  Set b₁} {B₂ : A₂  Set b₂}
     (A₁↠A₂ : A₁  A₂) 
     (∀ {x}  _⇔_ (B₁ x) (B₂ (Surjection.to A₁↠A₂ ⟨$⟩ x))) 
     _⇔_ (Σ A₁ B₁) (Σ A₂ B₂)
⇔′ {B₁ = B₁} {B₂} A₁↠A₂ B₁⇔B₂ =
  Inverse.equivalent (Rel⇿≡ {B = B₂}) ⟨∘⟩
  equivalent′ (H.indexedSetoid B₂) A₁↠A₂
     {x}  Inverse.equivalent (H.≡⇿≅ B₂) ⟨∘⟩
             B₁⇔B₂ {x} ⟨∘⟩
             Inverse.equivalent (Inv.sym (H.≡⇿≅ B₁))) ⟨∘⟩
  Eq.sym (Inverse.equivalent (Rel⇿≡ {B = B₁}))

inverse :
   {a₁ a₂ b₁ b₁′ b₂ b₂′}
    {A₁ : Set a₁} {A₂ : Set a₂}
    {B₁ : I.Setoid A₁ b₁ b₁′} (B₂ : I.Setoid A₂ b₂ b₂′) 
  (A₁⇿A₂ : A₁  A₂) 
  (∀ {x}  Inverse (B₁ at x) (B₂ at (Inverse.to A₁⇿A₂ ⟨$⟩ x))) 
  Inverse (setoid (P.setoid A₁) B₁) (setoid (P.setoid A₂) B₂)
inverse {A₁ = A₁} {A₂} {B₁} B₂ A₁⇿A₂ B₁⇿B₂ = record
  { to         = Equivalent.to   eq
  ; from       = Equivalent.from eq
  ; inverse-of = record
    { left-inverse-of  = left
    ; right-inverse-of = right
    }
  }
  where
  eq = equivalent′ B₂ (Inverse.surjection A₁⇿A₂)
                      (Inverse.equivalent B₁⇿B₂)

  left : Equivalent.from eq LeftInverseOf Equivalent.to eq
  left (x , y) =
    Inverse.left-inverse-of A₁⇿A₂ x ,
    I.Setoid.trans B₁
      (lemma (P.sym (Inverse.left-inverse-of A₁⇿A₂ x))
             (P.sym (Inverse.right-inverse-of A₁⇿A₂
                       (Inverse.to A₁⇿A₂ ⟨$⟩ x))))
      (Inverse.left-inverse-of B₁⇿B₂ y)
    where
    lemma :
       {x x′ y}  P._≡_ x x′ 
      (eq : P._≡_ (Inverse.to A₁⇿A₂ ⟨$⟩ x) (Inverse.to A₁⇿A₂ ⟨$⟩ x′)) 
      I.Setoid._≈_ B₁
        (Inverse.from B₁⇿B₂ ⟨$⟩ P.subst (I.Setoid.Carrier B₂) eq y)
        (Inverse.from B₁⇿B₂ ⟨$⟩ y)
    lemma P.refl P.refl = I.Setoid.refl B₁

  right : Equivalent.from eq RightInverseOf Equivalent.to eq
  right (x , y) =
    Inverse.right-inverse-of A₁⇿A₂ x ,
    I.Setoid.trans B₂
      (Inverse.right-inverse-of B₁⇿B₂
         (P.subst (I.Setoid.Carrier B₂) p y))
      (lemma p)
    where
    p = P.sym $ Inverse.right-inverse-of A₁⇿A₂ x

    lemma :  {x x′ y} (eq : P._≡_ x x′) 
            I.Setoid._≈_ B₂
              (P.subst (I.Setoid.Carrier B₂) eq y)
              y
    lemma P.refl = I.Setoid.refl B₂

 :  {a₁ a₂ b₁ b₂}
      {A₁ : Set a₁} {A₂ : Set a₂}
      {B₁ : A₁  Set b₁} {B₂ : A₂  Set b₂}
    (A₁⇿A₂ : A₁  A₂) 
    (∀ {x}  B₁ x  B₂ (Inverse.to A₁⇿A₂ ⟨$⟩ x)) 
    Σ A₁ B₁  Σ A₂ B₂
 {B₁ = B₁} {B₂} A₁⇿A₂ B₁⇿B₂ =
  Rel⇿≡ {B = B₂} ⟪∘⟫
  inverse (H.indexedSetoid B₂) A₁⇿A₂
     {x}  H.≡⇿≅ B₂ ⟪∘⟫ B₁⇿B₂ {x} ⟪∘⟫ Inv.sym (H.≡⇿≅ B₁)) ⟪∘⟫
  Inv.sym (Rel⇿≡ {B = B₁})

cong :  {k a₁ a₂ b₁ b₂}
         {A₁ : Set a₁} {A₂ : Set a₂}
         {B₁ : A₁  Set b₁} {B₂ : A₂  Set b₂}
       (A₁⇿A₂ : _⇿_ A₁ A₂) 
       (∀ {x}  Isomorphism k (B₁ x) (B₂ (Inverse.to A₁⇿A₂ ⟨$⟩ x))) 
       Isomorphism k (Σ A₁ B₁) (Σ A₂ B₂)
cong {k = Inv.equivalent} = ⇔′  Inverse.surjection
cong {k = Inv.inverse}    =