open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Function
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.Univalence using (ua)
open import Cubical.HITs.SetTruncation using (∣_∣₂; rec)

module Calf.Computation.Lolli where

open import Calf.Core.Cost
open import Calf.Value
open import Calf.Computation
open import Calf.Computation.Tensor

infix 1 _⊸ᶜ_

_⊸ᶜ_ : 𝒞 → 𝒞 → 𝒞
(A ⊸ᶜ B) .U = A ⊸ B
(A ⊸ᶜ B) .is-set =
  isSetRetract
    (λ f → f .U , f .charge)
    (λ (U , charge) → record { U = U ; charge = charge })
    (λ _ → refl)
    (isSetΣSndProp
      (isSetΠ λ _ → B .is-set)
      (isProp⊸charge A B))
(A ⊸ᶜ B) .charge c f .U a = B .charge c (f .U a)
(A ⊸ᶜ B) .charge c f .charge c' a =
  cong (B .charge c) (f .charge c' a)
  ∙ cong ((_$ f .U a) ∘ U) (CHARGE-comm {B} c' c)
(A ⊸ᶜ B) .charge/0 = ⊸-path refl refl (funExt λ a → B .charge/0)
(A ⊸ᶜ B) .charge/+ = ⊸-path refl refl (funExt λ a → B .charge/+)

lolli-currying : (A ⊗ B ⊸ C) ≡ (A ⊸ (B ⊸ᶜ C))
lolli-currying {A} {B} {C} =
  ua (isoToEquiv (iso curryᶜ uncurryᶜ curryᶜ-uncurryᶜ uncurryᶜ-curryᶜ))
  where
    curryᶜ : (A ⊗ B ⊸ C) → (A ⊸ (B ⊸ᶜ C))
    curryᶜ f .U a .U b = f .U (a ∥ b)
    curryᶜ f .U a .charge c b =
        f .U (a ∥ (B .charge c b))
      ≡⟨ cong (f .U) (sym (cong ∣_∣₂ (law c a b))) ⟩
        f .U ((A .charge c a) ∥ b)
      ≡⟨ f .charge c (a ∥ b) ⟩
        C .charge c (f .U (a ∥ b))
      ∎
    curryᶜ f .charge c a =
      ⊸-path refl refl (funExt λ b → f .charge c (a ∥ b))

    uncurryᶜ-U : (A ⊸ (B ⊸ᶜ C)) → A ⊛ B → U C
    uncurryᶜ-U f (inj a b) = f .U a .U b
    uncurryᶜ-U f (law c a b i) =
      ( cong (λ g → g .U b) (f .charge c a)
      ∙ sym (f .U a .charge c b) ) i

    uncurryᶜ : (A ⊸ (B ⊸ᶜ C)) → (A ⊗ B ⊸ C)
    uncurryᶜ f .U = rec (C .is-set) (uncurryᶜ-U f)
    uncurryᶜ f .charge c =
      ⊛-≡ (C .is-set)
        (λ x → uncurryᶜ f .U ((A ⊗ B) .charge c x))
        (λ x → C .charge c (uncurryᶜ f .U x))
        (λ a b → cong (λ g → g .U b) (f .charge c a))

    curryᶜ-uncurryᶜ : (f : A ⊸ (B ⊸ᶜ C)) → curryᶜ (uncurryᶜ f) ≡ f
    curryᶜ-uncurryᶜ f =
      ⊸-path refl refl (funExt λ a → ⊸-path refl refl refl)

    uncurryᶜ-curryᶜ : (f : A ⊗ B ⊸ C) → uncurryᶜ (curryᶜ f) ≡ f
    uncurryᶜ-curryᶜ f =
      ⊸-path refl refl
        (funExt
          (⊛-≡ (C .is-set)
            (λ x → uncurryᶜ (curryᶜ f) .U x)
            (f .U)
            (λ a b → refl)))