open import Cubical.Foundations.Prelude
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Structure

module Calf.Computation where

open import Calf.Core.Abstract
open import Calf.Value
open import Calf.Core.Cost
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Function
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.Univalence using (ua; ua→; ua-gluePath)

record 𝒞 : 𝒱₁ where
  field
    U : 𝒱
    is-set : isSet U

  field
    charge :   U  U
    charge/0 :  {a}  charge 0ℂ a  a
    charge/+ :  {a c₁ c₂}  charge (c₁ +ℂ c₂) a  charge c₁ (charge c₂ a)

  opaque
    charge/comm :  {c} {c'} {a}
       charge c (charge c' a)  charge c' (charge c a)
    charge/comm = sym charge/+  cong (flip charge _) (+ℂ-comm _ _)  charge/+
open 𝒞 public

variable
  A B C : 𝒞

infix 1 _⊸_
record _⊸_ (A B : 𝒞) : 𝒱 where
  field
    U : U A  U B
    charge :  c a  U (A .charge c a)  B .charge c (U a)
open _⊸_ public

isEquivᶜ : (A  B)  𝒱
isEquivᶜ f = isEquiv (U f)

_≃ᶜ_ : 𝒞  𝒞  𝒱
A ≃ᶜ B = Σ (A  B) isEquivᶜ

idᶜ : A  A
idᶜ .U a = a
idᶜ .charge _ _ = refl

infixl 9 _⨾ᶜ_
_⨾ᶜ_ : (A  B)  (B  C)  (A  C)
(f ⨾ᶜ g) .U = g .U  f .U
(f ⨾ᶜ g) .charge c a = cong (g .U) (f .charge c a)  g .charge c (f .U a)

CHARGE :   A  A
CHARGE {A} c .U = charge A c
CHARGE {A} c .charge c' a = charge/comm A {c} {c'} {a}

opaque
  isPropCharge/0
    : {U : 𝒱} {isSetU : isSet U} (charge :   U  U)
     isProp (∀ {a}  charge 0ℂ a  a)
  isPropCharge/0 {U} {isSetU} charge =
    isPropImplicitΠ λ a  isSetU (charge 0ℂ a) a

opaque
  isPropCharge/+
    : {U : 𝒱} {isSetU : isSet U} (charge :   U  U)
     isProp (∀ {a c₁ c₂}  charge (c₁ +ℂ c₂) a  charge c₁ (charge c₂ a))
  isPropCharge/+ {U} {isSetU} charge =
    isPropImplicitΠ3 λ a c₁ c₂ 
      isSetU (charge (c₁ +ℂ c₂) a) (charge c₁ (charge c₂ a))

𝒞-path
  : {A B : 𝒞}
   (U-path : A .U  B .U)
   PathP
       i    U-path i  U-path i)
      (charge A)
      (charge B)
   A  B
𝒞-path {A} {B} U-path charge-path i =
  record
    { U = U-path i
    ; is-set = isSetUi i
    ; charge = charge-path i
    ; charge/0 =
        isProp→PathP
           i  isPropCharge/0 {U = U-path i} {isSetUi i} (charge-path i))
          (A .charge/0)
          (B .charge/0)
          i
    ; charge/+ =
        isProp→PathP
           i  isPropCharge/+ {U = U-path i} {isSetUi i} (charge-path i))
          (A .charge/+)
          (B .charge/+)
          i
    }
  where
    opaque
      isSetUi : PathP  i  isSet (U-path i)) (A .is-set) (B .is-set)
      isSetUi =
        isProp→PathP
           i  isPropIsSet {A = U-path i})
          (A .is-set)
          (B .is-set)

opaque
  isProp⊸charge
    : (A B : 𝒞) (f : U A  U B)
     isProp ((c : ) (a : U A)  f (A .charge c a)  B .charge c (f a))
  isProp⊸charge A B f =
    isPropΠ2 λ c a  B .is-set (f (A .charge c a)) (B .charge c (f a))

opaque
  ⊸-path
    : {A₀ A₁ B₀ B₁ : 𝒞}
     (A-path : A₀  A₁)
     (B-path : B₀  B₁)
     {f₀ : A₀  B₀}
     {f₁ : A₁  B₁}
     PathP  i  U (A-path i)  U (B-path i)) (f₀ .U) (f₁ .U)
     PathP  i  A-path i  B-path i) f₀ f₁
  ⊸-path A-path B-path {f₀ = f₀} {f₁ = f₁} U-path i .U = U-path i
  ⊸-path A-path B-path {f₀ = f₀} {f₁ = f₁} U-path i .charge =
    isProp→PathP
       i  isProp⊸charge (A-path i) (B-path i) (U-path i))
      (f₀ .charge)
      (f₁ .charge)
      i

⊸-Σ-Iso
  : Iso (A  B)
      (Σ[ h  (U A  U B) ]
        ((c : ) (a : U A)  h (A .charge c a)  B .charge c (h a)))
⊸-Σ-Iso = iso to from  _  refl)  _  refl)
  where
    to : (A  B) 
      (Σ[ h  (U A  U B) ]
        ((c : ) (a : U A)  h (A .charge c a)  B .charge c (h a)))
    to f = f .U , f .charge

    from :
      (Σ[ h  (U A  U B) ]
        ((c : ) (a : U A)  h (A .charge c a)  B .charge c (h a)))
       (A  B)
    from (h , ch) .U = h
    from (h , ch) .charge = ch

opaque
  isSet⊸ : isSet (A  B)
  isSet⊸ {A} {B} =
    isOfHLevelRetractFromIso 2 ⊸-Σ-Iso
      (isSetΣ (isSet→ (B .is-set)) λ h  isProp→isSet (isProp⊸charge A B h))

CHARGE-commute
  :  c (e : A  B)
   CHARGE c ⨾ᶜ e  e ⨾ᶜ CHARGE c
CHARGE-commute c e =
  ⊸-path refl refl (funExt λ a  e .charge c a)

CHARGE-comm :  c₁ c₂  CHARGE {A} c₁ ⨾ᶜ CHARGE c₂  CHARGE c₂ ⨾ᶜ CHARGE c₁
CHARGE-comm c₁ c₂ = CHARGE-commute c₁ (CHARGE c₂)

CHARGE-0 : CHARGE {A} 0ℂ  idᶜ
CHARGE-0 {A = A} =
  ⊸-path refl refl (funExt λ a  A .charge/0)

CHARGE-+ :  c₁ c₂  CHARGE {A} (c₁ +ℂ c₂)  CHARGE c₂ ⨾ᶜ CHARGE c₁
CHARGE-+ {A = A} c₁ c₂ =
  ⊸-path refl refl (funExt λ a  A .charge/+)

idᶜ⨾ᶜf≡f : (f : A  B)  idᶜ ⨾ᶜ f  f
idᶜ⨾ᶜf≡f f = ⊸-path refl refl refl

f⨾ᶜidᶜ≡f : (f : A  B)  f ⨾ᶜ idᶜ  f
f⨾ᶜidᶜ≡f f = ⊸-path refl refl (funExt  x  refl))

charge-path-inv
  : {X Y : 𝒱}
   (e : X  Y)
   (chargeX :   X  X)
   (chargeY :   Y  Y)
   ((c : ) (y : Y)  invEq e (chargeY c y)  chargeX c (invEq e y))
   PathP
       i    ua (invEquiv e) i  ua (invEquiv e) i)
      chargeY
      chargeX
charge-path-inv e chargeX chargeY h =
  funExt λ c  ua→ λ y  ua-gluePath (invEquiv e) (h c y)

opaque
  charge-path
    : {X Y : 𝒱}
     (e : X  Y)
     (chargeX :   X  X)
     (chargeY :   Y  Y)
     ((c : ) (x : X)  e .fst (chargeX c x)  chargeY c (e .fst x))
     PathP
         i    ua e i  ua e i)
        chargeX
        chargeY
  charge-path e chargeX chargeY h =
    funExt λ c  ua→ λ x  ua-gluePath e (h c x)

conservativity :
  (f : A  B)
   isEquivᶜ f
   A  B
conservativity {A} {B} f f-equiv =
  𝒞-path
    (ua (f .U , f-equiv))
    (charge-path (f .U , f-equiv) (A .charge) (B .charge) (f .charge))

uaᶜ : A ≃ᶜ B  A  B
uaᶜ = uncurry conservativity

invEquivᶜ : (f : A  B)  isEquivᶜ f  B  A
invEquivᶜ {A} {B} f fe .U = invEq (f .U , fe)
invEquivᶜ {A} {B} f fe .charge c b =
    cong (invEq e) (cong (B .charge c) (sym (secEq e b)))
   cong (invEq e) (sym (f .charge c (invEq e b)))
   retEq e (A .charge c (invEq e b))
  where
    e = (f .U , fe)

infixr 30 _∙ₑᶜ_
_∙ₑᶜ_ : A ≃ᶜ B  B ≃ᶜ C  A ≃ᶜ C
(e ∙ₑᶜ f) .fst = e .fst ⨾ᶜ f .fst
(e ∙ₑᶜ f) .snd = ((e .fst .U , e .snd) ∙ₑ (f .fst .U , f .snd)) .snd

⊸-inv : (e : B  C)  isEquivᶜ e  C  B
⊸-inv e h .U = invIsEq h
⊸-inv {B = B} {C = C} e h .charge c x =
    sym (cong (invIsEq h)
      (e .charge c (invIsEq h x)  cong (C .charge c) (secIsEq h x)))
   retIsEq h (B .charge c (invIsEq h x))

⊸-postcomp-isEquiv : {A B C : 𝒞} (e : B  C)  isEquivᶜ e
   isEquiv  (f : A  B)  f ⨾ᶜ e)
⊸-postcomp-isEquiv e h =
  isoToIsEquiv (iso (_⨾ᶜ e) (_⨾ᶜ ⊸-inv e h)
     g  ⊸-path refl refl (funExt λ a  secIsEq h (g .U a)))
     f  ⊸-path refl refl (funExt λ a  retIsEq h (f .U a))))

⊸-postcomp-≃ : {A B C : 𝒞} (e : B  C)  isEquivᶜ e  (A  B)  (A  C)
⊸-postcomp-≃ e h = (_⨾ᶜ e) , ⊸-postcomp-isEquiv e h

𝒞WithStr : (B : 𝒞  𝒱)  𝒱₁
𝒞WithStr B = Σ[ A  𝒞 ] B A

⟨_⟩ᶜ :  {B}  𝒞WithStr B  𝒞
⟨_⟩ᶜ = fst

strᶜ :  {B}  (A : 𝒞WithStr B)  B  A ⟩ᶜ
strᶜ = snd