{-# OPTIONS --without-K --safe #-}
module Function.Bundles where
import Function.Definitions as FunctionDefinitions
import Function.Structures as FunctionStructures
open import Level using (Level; _⊔_; suc)
open import Data.Product using (_,_; proj₁; proj₂)
open import Relation.Binary hiding (_⇔_)
open import Relation.Binary.PropositionalEquality as ≡
  using (_≡_)
open Setoid using (isEquivalence)
private
  variable
    a b ℓ₁ ℓ₂ : Level
module _ (From : Setoid a ℓ₁) (To : Setoid b ℓ₂) where
  open Setoid From using () renaming (Carrier to A; _≈_ to _≈₁_)
  open Setoid To   using () renaming (Carrier to B; _≈_ to _≈₂_)
  open FunctionDefinitions _≈₁_ _≈₂_
  open FunctionStructures  _≈₁_ _≈₂_
  record Injection : Set (a ⊔ b ⊔ ℓ₁ ⊔ ℓ₂) where
    field
      f           : A → B
      cong        : f Preserves _≈₁_ ⟶ _≈₂_
      injective   : Injective f
    isCongruent : IsCongruent f
    isCongruent = record
      { cong           = cong
      ; isEquivalence₁ = isEquivalence From
      ; isEquivalence₂ = isEquivalence To
      }
    open IsCongruent isCongruent public using (module Eq₁; module Eq₂)
    isInjection : IsInjection f
    isInjection = record
      { isCongruent = isCongruent
      ; injective   = injective
      }
  record Surjection : Set (a ⊔ b ⊔ ℓ₁ ⊔ ℓ₂) where
    field
      f          : A → B
      cong       : f Preserves _≈₁_ ⟶ _≈₂_
      surjective : Surjective f
    isCongruent : IsCongruent f
    isCongruent = record
      { cong           = cong
      ; isEquivalence₁ = isEquivalence From
      ; isEquivalence₂ = isEquivalence To
      }
    open IsCongruent isCongruent public using (module Eq₁; module Eq₂)
    isSurjection : IsSurjection f
    isSurjection = record
      { isCongruent = isCongruent
      ; surjective  = surjective
      }
  record Bijection : Set (a ⊔ b ⊔ ℓ₁ ⊔ ℓ₂) where
    field
      f         : A → B
      cong      : f Preserves _≈₁_ ⟶ _≈₂_
      bijective : Bijective f
    injective : Injective f
    injective = proj₁ bijective
    surjective : Surjective f
    surjective = proj₂ bijective
    injection : Injection
    injection = record
      { cong      = cong
      ; injective = injective
      }
    surjection : Surjection
    surjection = record
      { cong       = cong
      ; surjective = surjective
      }
    open Injection  injection  public using (isInjection)
    open Surjection surjection public using (isSurjection)
    isBijection : IsBijection f
    isBijection = record
      { isInjection = isInjection
      ; surjective  = surjective
      }
    open IsBijection isBijection public using (module Eq₁; module Eq₂)
  record Equivalence : Set (a ⊔ b ⊔ ℓ₁ ⊔ ℓ₂) where
    field
      f     : A → B
      g     : B → A
      cong₁ : f Preserves _≈₁_ ⟶ _≈₂_
      cong₂ : g Preserves _≈₂_ ⟶ _≈₁_
  record LeftInverse : Set (a ⊔ b ⊔ ℓ₁ ⊔ ℓ₂) where
    field
      f         : A → B
      g         : B → A
      cong₁     : f Preserves _≈₁_ ⟶ _≈₂_
      cong₂     : g Preserves _≈₂_ ⟶ _≈₁_
      inverseˡ  : Inverseˡ f g
    isCongruent : IsCongruent f
    isCongruent = record
      { cong           = cong₁
      ; isEquivalence₁ = isEquivalence From
      ; isEquivalence₂ = isEquivalence To
      }
    open IsCongruent isCongruent public using (module Eq₁; module Eq₂)
    isLeftInverse : IsLeftInverse f g
    isLeftInverse = record
      { isCongruent = isCongruent
      ; cong₂       = cong₂
      ; inverseˡ    = inverseˡ
      }
    equivalence : Equivalence
    equivalence = record
      { cong₁ = cong₁
      ; cong₂ = cong₂
      }
  record RightInverse : Set (a ⊔ b ⊔ ℓ₁ ⊔ ℓ₂) where
    field
      f         : A → B
      g         : B → A
      cong₁     : f Preserves _≈₁_ ⟶ _≈₂_
      cong₂     : g Preserves _≈₂_ ⟶ _≈₁_
      inverseʳ  : Inverseʳ f g
    isCongruent : IsCongruent f
    isCongruent = record
      { cong           = cong₁
      ; isEquivalence₁ = isEquivalence From
      ; isEquivalence₂ = isEquivalence To
      }
    isRightInverse : IsRightInverse f g
    isRightInverse = record
      { isCongruent = isCongruent
      ; cong₂       = cong₂
      ; inverseʳ    = inverseʳ
      }
    equivalence : Equivalence
    equivalence = record
      { cong₁ = cong₁
      ; cong₂ = cong₂
      }
  record BiEquivalence : Set (a ⊔ b ⊔ ℓ₁ ⊔ ℓ₂) where
    field
      f     : A → B
      g₁    : B → A
      g₂    : B → A
      cong₁ : f Preserves _≈₁_ ⟶ _≈₂_
      cong₂ : g₁ Preserves _≈₂_ ⟶ _≈₁_
      cong₃ : g₂ Preserves _≈₂_ ⟶ _≈₁_
  record BiInverse : Set (a ⊔ b ⊔ ℓ₁ ⊔ ℓ₂) where
    field
      f         : A → B
      g₁        : B → A
      g₂        : B → A
      cong₁     : f Preserves _≈₁_ ⟶ _≈₂_
      cong₂     : g₁ Preserves _≈₂_ ⟶ _≈₁_
      cong₃     : g₂ Preserves _≈₂_ ⟶ _≈₁_
      inverseˡ  : Inverseˡ f g₁
      inverseʳ  : Inverseʳ f g₂
    f-isCongruent : IsCongruent f
    f-isCongruent = record
      { cong           = cong₁
      ; isEquivalence₁ = isEquivalence From
      ; isEquivalence₂ = isEquivalence To
      }
    isBiInverse : IsBiInverse f g₁ g₂
    isBiInverse = record
      { f-isCongruent = f-isCongruent
      ; cong₂         = cong₂
      ; inverseˡ      = inverseˡ
      ; cong₃         = cong₃
      ; inverseʳ      = inverseʳ
      }
    biEquivalence : BiEquivalence
    biEquivalence = record
      { cong₁ = cong₁
      ; cong₂ = cong₂
      ; cong₃ = cong₃
      }
  record Inverse : Set (a ⊔ b ⊔ ℓ₁ ⊔ ℓ₂) where
    field
      f         : A → B
      f⁻¹       : B → A
      cong₁     : f Preserves _≈₁_ ⟶ _≈₂_
      cong₂     : f⁻¹ Preserves _≈₂_ ⟶ _≈₁_
      inverse   : Inverseᵇ f f⁻¹
    inverseˡ : Inverseˡ f f⁻¹
    inverseˡ = proj₁ inverse
    inverseʳ : Inverseʳ f f⁻¹
    inverseʳ = proj₂ inverse
    leftInverse : LeftInverse
    leftInverse = record
      { cong₁    = cong₁
      ; cong₂    = cong₂
      ; inverseˡ = inverseˡ
      }
    rightInverse : RightInverse
    rightInverse = record
      { cong₁    = cong₁
      ; cong₂    = cong₂
      ; inverseʳ = inverseʳ
      }
    open LeftInverse leftInverse   public using (isLeftInverse)
    open RightInverse rightInverse public using (isRightInverse)
    isInverse : IsInverse f f⁻¹
    isInverse = record
      { isLeftInverse = isLeftInverse
      ; inverseʳ      = inverseʳ
      }
    open IsInverse isInverse public using (module Eq₁; module Eq₂)
infix 3 _↣_ _↠_ _⤖_ _⇔_ _↩_ _↪_ _↩↪_ _↔_
_↣_ : Set a → Set b → Set _
A ↣ B = Injection (≡.setoid A) (≡.setoid B)
_↠_ : Set a → Set b → Set _
A ↠ B = Surjection (≡.setoid A) (≡.setoid B)
_⤖_ : Set a → Set b → Set _
A ⤖ B = Bijection (≡.setoid A) (≡.setoid B)
_⇔_ : Set a → Set b → Set _
A ⇔ B = Equivalence (≡.setoid A) (≡.setoid B)
_↩_ : Set a → Set b → Set _
A ↩ B = LeftInverse (≡.setoid A) (≡.setoid B)
_↪_ : Set a → Set b → Set _
A ↪ B = RightInverse (≡.setoid A) (≡.setoid B)
_↩↪_ : Set a → Set b → Set _
A ↩↪ B = BiInverse (≡.setoid A) (≡.setoid B)
_↔_ : Set a → Set b → Set _
A ↔ B = Inverse (≡.setoid A) (≡.setoid B)
module _ {A : Set a} {B : Set b} where
  open FunctionDefinitions {A = A} {B} _≡_ _≡_
  mk↣ : ∀ {f : A → B} → Injective f → A ↣ B
  mk↣ {f} inj = record
    { f         = f
    ; cong      = ≡.cong f
    ; injective = inj
    }
  mk↠ : ∀ {f : A → B} → Surjective f → A ↠ B
  mk↠ {f} surj = record
    { f          = f
    ; cong       = ≡.cong f
    ; surjective = surj
    }
  mk⤖ : ∀ {f : A → B} → Bijective f → A ⤖ B
  mk⤖ {f} bij = record
    { f         = f
    ; cong      = ≡.cong f
    ; bijective = bij
    }
  mk⇔ : ∀ (f : A → B) (g : B → A) → A ⇔ B
  mk⇔ f g = record
    { f     = f
    ; g     = g
    ; cong₁ = ≡.cong f
    ; cong₂ = ≡.cong g
    }
  mk↩ : ∀ {f : A → B} {g : B → A} → Inverseˡ f g → A ↩ B
  mk↩ {f} {g} invˡ = record
    { f        = f
    ; g        = g
    ; cong₁    = ≡.cong f
    ; cong₂    = ≡.cong g
    ; inverseˡ = invˡ
    }
  mk↪ : ∀ {f : A → B} {g : B → A} → Inverseʳ f g → A ↪ B
  mk↪ {f} {g} invʳ = record
    { f        = f
    ; g        = g
    ; cong₁    = ≡.cong f
    ; cong₂    = ≡.cong g
    ; inverseʳ = invʳ
    }
  mk↩↪ : ∀ {f : A → B} {g₁ : B → A} {g₂ : B → A} →
         Inverseˡ f g₁ → Inverseʳ f g₂ → A ↩↪ B
  mk↩↪ {f} {g₁} {g₂} invˡ invʳ = record
    { f        = f
    ; g₁       = g₁
    ; g₂       = g₂
    ; cong₁    = ≡.cong f
    ; cong₂    = ≡.cong g₁
    ; cong₃    = ≡.cong g₂
    ; inverseˡ = invˡ
    ; inverseʳ = invʳ
    }
  mk↔ : ∀ {f : A → B} {f⁻¹ : B → A} → Inverseᵇ f f⁻¹ → A ↔ B
  mk↔ {f} {f⁻¹} inv = record
    { f       = f
    ; f⁻¹     = f⁻¹
    ; cong₁   = ≡.cong f
    ; cong₂   = ≡.cong f⁻¹
    ; inverse = inv
    }
  
  mk↔′ : ∀ (f : A → B) (f⁻¹ : B → A) → Inverseˡ f f⁻¹ → Inverseʳ f f⁻¹ → A ↔ B
  mk↔′ f f⁻¹ invˡ invʳ = mk↔ {f = f} {f⁻¹ = f⁻¹} (invˡ , invʳ)