{-# OPTIONS --safe #-}
module Cubical.Functions.FunExtEquiv where

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.CartesianKanOps
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Function
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.Univalence

open import Cubical.Data.Vec.Base
open import Cubical.Data.Vec.NAry
open import Cubical.Data.Nat

open import Cubical.Reflection.StrictEquiv

private
  variable
     ℓ₁ ℓ₂ ℓ₃ : Level

-- Function extensionality is an equivalence
module _ {A : Type } {B : A  I  Type ℓ₁}
  {f : (x : A)  B x i0} {g : (x : A)  B x i1} where

  funExtEquiv : (∀ x  PathP (B x) (f x) (g x))  PathP  i   x  B x i) f g
  unquoteDef funExtEquiv = defStrictEquiv funExtEquiv funExt funExt⁻

  funExtPath : (∀ x  PathP (B x) (f x) (g x))  PathP  i   x  B x i) f g
  funExtPath = ua funExtEquiv

  funExtIso : Iso (∀ x  PathP (B x) (f x) (g x)) (PathP  i   x  B x i) f g)
  funExtIso = iso funExt funExt⁻  x  refl {x = x})  x  refl {x = x})

-- Function extensionality for binary functions
funExt₂ : {A : Type } {B : A  Type ℓ₁} {C : (x : A)  B x  I  Type ℓ₂}
          {f : (x : A)  (y : B x)  C x y i0}
          {g : (x : A)  (y : B x)  C x y i1}
           ((x : A) (y : B x)  PathP (C x y) (f x y) (g x y))
           PathP  i   x y  C x y i) f g
funExt₂ p i x y = p x y i

-- Function extensionality for binary functions is an equivalence
module _ {A : Type } {B : A  Type ℓ₁} {C : (x : A)  B x  I  Type ℓ₂}
         {f : (x : A)  (y : B x)  C x y i0}
         {g : (x : A)  (y : B x)  C x y i1} where
  private
    appl₂ : PathP  i   x y  C x y i) f g   x y  PathP (C x y) (f x y) (g x y)
    appl₂ eq x y i = eq i x y

  funExt₂Equiv : (∀ x y  PathP (C x y) (f x y) (g x y))  (PathP  i   x y  C x y i) f g)
  unquoteDef funExt₂Equiv = defStrictEquiv funExt₂Equiv funExt₂ appl₂

  funExt₂Path : (∀ x y  PathP (C x y) (f x y) (g x y))  (PathP  i   x y  C x y i) f g)
  funExt₂Path = ua funExt₂Equiv


-- Function extensionality for ternary functions
funExt₃ : {A : Type } {B : A  Type ℓ₁} {C : (x : A)  B x  Type ℓ₂}
          {D : (x : A)  (y : B x)  C x y  I  Type ℓ₃}
          {f : (x : A)  (y : B x)  (z : C x y)  D x y z i0}
          {g : (x : A)  (y : B x)  (z : C x y)  D x y z i1}
         ((x : A) (y : B x) (z : C x y)  PathP (D x y z) (f x y z) (g x y z))
         PathP  i   x y z  D x y z i) f g
funExt₃ p i x y z = p x y z i

-- Function extensionality for ternary functions is an equivalence
module _ {A : Type } {B : A  Type ℓ₁} {C : (x : A)  B x  Type ℓ₂}
         {D : (x : A)  (y : B x)  C x y  I  Type ℓ₃}
         {f : (x : A)  (y : B x)  (z : C x y)  D x y z i0}
         {g : (x : A)  (y : B x)  (z : C x y)  D x y z i1} where
  private
    appl₃ : PathP  i   x y z  D x y z i) f g   x y z  PathP (D x y z) (f x y z) (g x y z)
    appl₃ eq x y z i = eq i x y z

  funExt₃Equiv : (∀ x y z  PathP (D x y z) (f x y z) (g x y z))  (PathP  i   x y z  D x y z i) f g)
  unquoteDef funExt₃Equiv = defStrictEquiv funExt₃Equiv funExt₃ appl₃

  funExt₃Path : (∀ x y z  PathP (D x y z) (f x y z) (g x y z))  (PathP  i   x y z  D x y z i) f g)
  funExt₃Path = ua funExt₃Equiv


-- n-ary non-dependent funext
nAryFunExt : {X : Type } {Y : I  Type ℓ₁} (n : ) (fX : nAryOp n X (Y i0)) (fY : nAryOp n X (Y i1))
            ((xs : Vec X n)  PathP Y (fX $ⁿ xs) (fY $ⁿ map  x  x) xs))
            PathP  i  nAryOp n X (Y i)) fX fY
nAryFunExt zero fX fY p        = p []
nAryFunExt (suc n) fX fY p i x = nAryFunExt n (fX x) (fY x)  xs  p (x  xs)) i

-- n-ary funext⁻
nAryFunExt⁻ : (n : ) {X : Type } {Y : I  Type ℓ₁} (fX : nAryOp n X (Y i0)) (fY : nAryOp n X (Y i1))
            PathP  i  nAryOp n X (Y i)) fX fY
            ((xs : Vec X n)  PathP Y (fX $ⁿ xs) (fY $ⁿ map  x  x) xs))
nAryFunExt⁻ zero fX fY p [] = p
nAryFunExt⁻ (suc n) fX fY p (x  xs) = nAryFunExt⁻ n (fX x) (fY x)  i  p i x) xs

nAryFunExtEquiv : (n : ) {X : Type } {Y : I  Type ℓ₁} (fX : nAryOp n X (Y i0)) (fY : nAryOp n X (Y i1))
   ((xs : Vec X n)  PathP Y (fX $ⁿ xs) (fY $ⁿ map  x  x) xs))  PathP  i  nAryOp n X (Y i)) fX fY
nAryFunExtEquiv n {X} {Y} fX fY = isoToEquiv (iso (nAryFunExt n fX fY) (nAryFunExt⁻ n fX fY)
                                              (linv n fX fY) (rinv n fX fY))
  where
  linv : (n : ) (fX : nAryOp n X (Y i0)) (fY : nAryOp n X (Y i1))
    (p : PathP  i  nAryOp n X (Y i)) fX fY)
     nAryFunExt n fX fY (nAryFunExt⁻ n fX fY p)  p
  linv zero fX fY p          = refl
  linv (suc n) fX fY p i j x = linv n (fX x) (fY x)  k  p k x) i j

  rinv : (n : ) (fX : nAryOp n X (Y i0)) (fY : nAryOp n X (Y i1))
         (p : (xs : Vec X n)  PathP Y (fX $ⁿ xs) (fY $ⁿ map  x  x) xs))
        nAryFunExt⁻ n fX fY (nAryFunExt n fX fY p)  p
  rinv zero fX fY p i []          = p []
  rinv (suc n) fX fY p i (x  xs) = rinv n (fX x) (fY x)  ys i  p (x  ys) i) i xs

-- Funext when the domain also depends on the interval

funExtDep : {A : I  Type } {B : (i : I)  A i  Type ℓ₁}
  {f : (x : A i0)  B i0 x} {g : (x : A i1)  B i1 x}
   ({x₀ : A i0} {x₁ : A i1} (p : PathP A x₀ x₁)  PathP  i  B i (p i)) (f x₀) (g x₁))
   PathP  i  (x : A i)  B i x) f g
funExtDep {A = A} {B} {f} {g} h i x =
  transp  k  B i (coei→i A i x k)) (i  ~ i) (h  j  coei→j A i j x) i)

funExtDep⁻ : {A : I  Type } {B : (i : I)  A i  Type ℓ₁}
  {f : (x : A i0)  B i0 x} {g : (x : A i1)  B i1 x}
   PathP  i  (x : A i)  B i x) f g
   ({x₀ : A i0} {x₁ : A i1} (p : PathP A x₀ x₁)  PathP  i  B i (p i)) (f x₀) (g x₁))
funExtDep⁻ q p i = q i (p i)

funExtDepEquiv : {A : I  Type } {B : (i : I)  A i  Type ℓ₁}
  {f : (x : A i0)  B i0 x} {g : (x : A i1)  B i1 x}
   ({x₀ : A i0} {x₁ : A i1} (p : PathP A x₀ x₁)  PathP  i  B i (p i)) (f x₀) (g x₁))
   PathP  i  (x : A i)  B i x) f g
funExtDepEquiv {A = A} {B} {f} {g} = isoToEquiv isom
  where
  open Iso
  isom : Iso _ _
  isom .fun = funExtDep
  isom .inv = funExtDep⁻
  isom .rightInv q m i x =
    transp
       k  B i (coei→i A i x (k  m)))
      (m  i  ~ i)
      (q i (coei→i A i x m))
  isom .leftInv h m p i =
    transp
       k  B i (lemi→i m k))
      (m  i  ~ i)
      (h  j  lemi→j j m) i)
    where
    lemi→j :  j  coei→j A i j (p i)  p j
    lemi→j j k = coePath A  i  p i) i j k

    lemi→i : PathP  m  lemi→j i m  p i) (coei→i A i (p i)) refl
    lemi→i m k = coei→i A i (p i) (m  k)

-- Funext for non-dependent functions but where both domain and
-- codomain depend on the interval. In this case we can omit the
-- outer transp in funExtDep.

funExtNonDep : {A : I  Type } {B : I  Type ℓ₁}
  {f : A i0  B i0} {g : A i1  B i1}
   ({x₀ : A i0} {x₁ : A i1}  PathP A x₀ x₁  PathP B (f x₀) (g x₁))
   PathP  i  A i  B i) f g
funExtNonDep {A = A} h i x = h  j  coei→j A i j x) i

funExtNonDep⁻ : {A : I  Type } {B : I  Type ℓ₁}
  {f : A i0  B i0} {g : A i1  B i1}
   PathP  i  A i  B i) f g
   ({x₀ : A i0} {x₁ : A i1}  PathP A x₀ x₁  PathP B (f x₀) (g x₁))
funExtNonDep⁻ = funExtDep⁻

funExtNonDepEquiv : {A : I  Type } {B : I  Type ℓ₁}
  {f : A i0  B i0} {g : A i1  B i1}
   ({x₀ : A i0} {x₁ : A i1}  PathP A x₀ x₁  PathP B (f x₀) (g x₁))
   PathP  i  A i  B i) f g
funExtNonDepEquiv {A = A} = isoToEquiv isom
  where
  open Iso
  isom : Iso _ _
  isom .fun = funExtNonDep
  isom .inv = funExtNonDep⁻
  isom .rightInv q m i x = q i (coei→i A i x m)
  isom .leftInv h m p i = h  j  lemi→j j m) i
    where
    lemi→j :  j  coei→j A i j (p i)  p j
    lemi→j j k = coePath A  i  p i) i j k

heteroHomotopy≃Homotopy : {A : I  Type } {B : (i : I)  Type ℓ₁}
  {f : A i0  B i0} {g : A i1  B i1}
   ({x₀ : A i0} {x₁ : A i1}  PathP A x₀ x₁  PathP B (f x₀) (g x₁))
   ((x₀ : A i0)  PathP B (f x₀) (g (transport  i  A i) x₀)))
heteroHomotopy≃Homotopy {A = A} {B} {f} {g} = isoToEquiv isom
  where
  open Iso
  isom : Iso _ _
  isom .fun h x₀ = h (isContrSinglP A x₀ .fst .snd)
  isom .inv k {x₀} {x₁} p =
    subst  fib  PathP B (f x₀) (g (fib .fst))) (isContrSinglP A x₀ .snd (x₁ , p)) (k x₀)
  isom .rightInv k = funExt λ x₀ 
    cong  α  subst  fib  PathP B (f x₀) (g (fib .fst))) α (k x₀))
      (isProp→isSet isPropSinglP (isContrSinglP A x₀ .fst) _
        (isContrSinglP A x₀ .snd (isContrSinglP A x₀ .fst))
        refl)
     transportRefl (k x₀)
  isom .leftInv h j {x₀} {x₁} p =
    transp
       i  PathP B (f x₀) (g (isContrSinglP A x₀ .snd (x₁ , p) (i  j) .fst)))
      j
      (h (isContrSinglP A x₀ .snd (x₁ , p) j .snd))