{-# OPTIONS --cubical --no-import-sorts #-}

module MoreLogic.Definitions where -- hProp logic

open import Cubical.Foundations.Everything renaming (_⁻¹ to _⁻¹ᵖ; assoc to ∙-assoc)
open import Cubical.Data.Sum.Base renaming (_⊎_ to infixr 4 _⊎_)
open import Cubical.Relation.Nullary.Base renaming (¬_ to ¬ᵗ_)
open import Cubical.Foundations.Logic renaming (inl to inlᵖ; inr to inrᵖ)
open import Cubical.Data.Empty renaming (elim to ⊥-elim) renaming ( to ⊥⊥) -- `⊥` and `elim`

import Cubical.Data.Empty as Empty
open import Cubical.Data.Unit.Base

open import MoreLogic.Reasoning
open import Utils

-- lifted versions of ⊥ and ⊤

hPropRel :  {} (A B : Type ) (ℓ' : Level)  Type (ℓ-max  (ℓ-suc ℓ'))
hPropRel A B ℓ' = A  B  hProp ℓ'

isSetIsProp : ∀{} {A : Type }  isProp (isSet A)
isSetIsProp is-set₀ is-set₁ = funExt  x  funExt λ y  isPropIsProp (is-set₀ x y) (is-set₁ x y))

isSetᵖ : ∀{} (A : Type )  hProp 
isSetᵖ A = isSet A , isSetIsProp

-- hProp-syntax for Σ types of hProps to omit propositional truncation
-- this should be equivalent to ∃! from the standard library but ∃! is not an hProp
-- proof sketch:
--   `∃! A B = isContr (Σ A B) = Σ[ x ∈ Σ A B ] ∀(  y : Σ A B) → x ≡ y`
--   `Σᵖ[]-syntax A B          ≈ Σ[ c ∈ Σ A B ] ∀(x y : Σ A B) → x ≡ y`
-- NOTE: we also have isProp→Iso in `Cubical.Foundations.Isomorphism`

Σᵖ[]-syntax : ∀{ ℓ'}  {A : hProp ℓ'}  ([ A ]  hProp )  hProp _
Σᵖ[]-syntax {A = A} P = Σ [ A ] ([_]  P) , isPropΣ (isProp[] A) (isProp[]  P)

syntax  Σᵖ[]-syntax  x  P) = Σᵖ[ x ] P
infix 2 Σᵖ[]-syntax

Σᵖ[∶]-syntax : ∀{ ℓ'}  {A : hProp ℓ'}  ([ A ]  hProp )  hProp _
Σᵖ[∶]-syntax {A = A} P = Σ [ A ] ([_]  P) , isPropΣ (isProp[] A) (isProp[]  P)

syntax  Σᵖ[∶]-syntax {A = A}  x  P) = Σᵖ[ x  A ] P
infix 2 Σᵖ[∶]-syntax

isProp⊎ˡ : ∀{ ℓ'} {A : Type } {B : Type ℓ'}  isProp A  isProp B  (A  ¬ᵗ B)  isProp (A  B)
isProp⊎ˡ pA pB A⇒¬B (inl x) (inl y) = cong inl (pA x y)
isProp⊎ˡ pA pB A⇒¬B (inr x) (inr y) = cong inr (pB x y)
isProp⊎ˡ pA pB A⇒¬B (inl x) (inr y) = ⊥-elim (A⇒¬B x y)
isProp⊎ˡ pA pB A⇒¬B (inr x) (inl y) = ⊥-elim (A⇒¬B y x)

-- hProp-syntax for disjoint unions to omit propositional truncation
⊎ᵖ-syntax : ∀{ ℓ'} (P : hProp ) (Q : hProp ℓ')  {[ P ]  ¬ᵗ [ Q ]}  hProp _
⊎ᵖ-syntax P Q {P⇒¬Q} = ([ P ]  [ Q ]) , isProp⊎ˡ (isProp[] P) (isProp[] Q) P⇒¬Q

syntax ⊎ᵖ-syntax P Q {P⇒¬Q} = [ P⇒¬Q ] P ⊎ᵖ Q

{-# DISPLAY ⊎ᵖ-syntax a b = a  b #-}

-- hProp-syntax for equality on sets to omit propositional truncation
≡ˢ-syntax : { : Level} {A : Type }  A  A  {isset : isSet A}  hProp 
≡ˢ-syntax a b {isset} = (a  b) , isset a b

syntax ≡ˢ-syntax a b {isset} = [ isset ] a ≡ˢ b

infix 1 ⊎ᵖ-syntax
infix 1 ≡ˢ-syntax

{-# DISPLAY ≡ˢ-syntax a b = a  b #-}

-- pretty print ∀-syntax from cubical standard library
{-# DISPLAY ∀[]-syntax {A = A} P = P #-}
-- {-# DISPLAY ∀[]-syntax {A = A} P = ∃ P #-}
-- {-# DISPLAY ∀[]-syntax (λ a → P) = ∀[ a ] P #-}
-- {-# DISPLAY ∃[]-syntax (λ x → P) = ∃[ x ] P #-}

-- ∀-syntax to quantify over hProps (without needing to `[_]` them)
∀ᵖ[∶]-syntax : ∀{ ℓ'} {A : hProp ℓ'}  ([ A ]  hProp )  hProp (ℓ-max  ℓ')
∀ᵖ[∶]-syntax {A = A} P = (∀ x  [ P x ]) , isPropΠ (isProp[]  P)

syntax ∀ᵖ[∶]-syntax {A = A}  a  P) = ∀ᵖ[ a  A ] P
infix 2 ∀ᵖ[∶]-syntax

{-# DISPLAY ∀ᵖ[∶]-syntax {A = A} P = P #-}

-- isPropΠ for a function with an instance argument
isPropΠⁱ : ∀{ ℓ'} {A : Type } {B : {{p : A}}  Type ℓ'} (h : (x : A)  isProp (B {{x}}))  isProp ({{x : A}}  B {{x}})
isPropΠⁱ h f g i {{x}} = (h x) (f {{x}}) (g {{x}}) i

-- ∀-syntax to quantify over hProps (without needing to `[_]` them) which produces an intance argument
∀ᵖ〚∶〛-syntax : ∀{ ℓ'} {A : hProp ℓ'}  ([ A ]  hProp )  hProp (ℓ-max  ℓ')
∀ᵖ〚∶〛-syntax {A = A} P = (∀ {{x}}  [ P x ]) , isPropΠⁱ (isProp[]  P)

syntax ∀ᵖ〚∶〛-syntax {A = A}  a  P) = ∀ᵖ〚 a  A  P
infix 2 ∀ᵖ〚∶〛-syntax

!isProp : ∀{} {P : Type }  isProp P  isProp (! P)
!isProp is-prop (!! x) (!! y) = subst  z  (!! x)  (!! z)) (is-prop x y) refl

∀ᵖ!〚∶〛-syntax : ∀{ ℓ'} {A : hProp ℓ'}  (! [ A ]  hProp )  hProp (ℓ-max  ℓ')
∀ᵖ!〚∶〛-syntax {A = A} P = (∀ {{x}}  [ P x ]) , isPropΠⁱ  p  isProp[] (P (p))) -- isPropΠⁱ (isProp[] ∘ P) -- isPropΠⁱ (!isProp ∘ isProp[] ∘ P) --  --

syntax ∀ᵖ!〚∶〛-syntax {A = A}  a  P) = ∀ᵖ!〚 a  A  P
infix 2 ∀ᵖ!〚∶〛-syntax

{-# DISPLAY ∀ᵖ[∶]-syntax {A = A} P = P #-}

-- ∀-syntax which produces an intance argument
∀〚∶〛-syntax : ∀{ ℓ'} {A : Type ℓ'}  (A  hProp )  hProp _
∀〚∶〛-syntax {A = A} P = (∀ {{x}}  [ P x ]) , isPropΠⁱ (isProp[]  P)

syntax ∀〚∶〛-syntax {A = A}  a  P) = ∀〚 a  A  P
infix 2 ∀〚∶〛-syntax

{-# DISPLAY ∀〚∶〛-syntax {A = A} P = P #-}

⊎⇒⊔ :  { ℓ'} (P : hProp ) (Q : hProp ℓ')  [ P ]  [ Q ]  [ P  Q ]
⊎⇒⊔ P Q (inl x) = inlᵖ x
⊎⇒⊔ P Q (inr x) = inrᵖ x

-- pretty `⊔-elim`
case[⊔]-syntaxᵈ :  { ℓ' ℓ''} (P : hProp ) (Q : hProp ℓ')
                   (z : [ P  Q ])
                   (R : [ P  Q ]  hProp ℓ'')
                   (S : (x : [ P ]  [ Q ])  [ R (⊎⇒⊔ P Q x) ] )
                   [ R z ]
case[⊔]-syntaxᵈ P Q z R S = ⊔-elim P Q R  p  S (inl p))  q  S (inr q)) z

syntax case[⊔]-syntaxᵈ P Q z  x  R) S = case z as x  P  Q  R of S

case[⊔]-syntax :  { ℓ' ℓ''} (P : hProp ) (Q : hProp ℓ')
                   (z : [ P  Q ])
                   (R : hProp ℓ'')
                   (S : (x : [ P ]  [ Q ])  [ R ] )
                   [ R ]
case[⊔]-syntax P Q z R S = ⊔-elim P Q  _  R)  p  S (inl p))  q  S (inr q)) z

syntax case[⊔]-syntax P Q z R S = case z as P  Q  R of S

-- {-# DISPLAY case[⊔]-syntax P Q z R S = case z of S #-}

-- for a function, to be an hProp, it suffices that the result is an hProp
-- so in principle we might inject any non-hProps as arguments with `_ᵗ⇒_`
_ᵗ⇒_ : ∀{ ℓ'} (A : Type )  (B : hProp ℓ')  hProp _
A ᵗ⇒ B = (A  [ B ]) , isPropΠ λ _  isProp[] B

infixr 6 _ᵗ⇒_

-- lifting of hProps to create "universe-homogeneous" pathes
--   this is not necessary when using _⇔_ which is universe-inhomogeneous
--   because `_⇔_` can "cross" universes, where `PathP` needs to stay in the same universe

Liftᵖ : ∀{i j : Level}  hProp i  hProp (ℓ-max i j)
Liftᵖ {i} {j} P = Lift {i} {j} [ P ] , λ{ (lift p) (lift q)  λ i  lift (isProp[] P p q i) }

liftᵖ : ∀{i j : Level}  (P : hProp i)  [ P ]  [ Liftᵖ {i} {j} P ]
liftᵖ P p = lift p

unliftᵖ : ∀{i j : Level}  (P : hProp i)  [ Liftᵖ {i} {j} P ]  [ P ]
unliftᵖ P (lift p) = p

⊥↑ : ∀{}  hProp 
⊥↑ = Lift Empty.⊥ , λ ()

⊤↑ : ∀{}  hProp 
⊤↑ = Lift Unit , isOfHLevelLift 1  _ _ _  tt)

infix 10 ¬↑_
¬↑_ : ∀{}  hProp   hProp 
¬↑_ {} A = ([ A ]  Lift {j = } Empty.⊥) , isPropΠ λ _  isOfHLevelLift 1 Empty.isProp⊥

-- negation with an instance argument
¬ⁱ_ : ∀{}  hProp   hProp 
¬ⁱ A = ({{p : [ A ]}}  ⊥⊥) , isPropΠⁱ {A = [ A ]} λ _  isProp⊥

¬'_ : ∀{}  Type   hProp 
¬' A = (A  ⊥⊥) , isPropΠ λ _  isProp⊥

¬-≡-¬ⁱ : ∀{} (P : hProp )  ¬ P  ¬ⁱ P
¬-≡-¬ⁱ P =
  ⇒∶  f {{p}}  f   p  )
  ⇐∶  f   p    f {{p}})