{-# OPTIONS --safe #-}

module Class.Computational where

open import Agda.Primitive using () renaming (Set to Type)
open import Level

open import Prelude.Init hiding (map)

open import Class.Applicative
open import Class.Bifunctor
open import Class.DecEq
open import Class.Functor renaming (fmap to map)
open import Class.Monad
open import Class.Semigroup
open import Class.Show.Core
open import Function
open import Relation.Binary.PropositionalEquality

private variable
  a : Level
  I O : Type
  i : I
  o o' : O
  Err Err₁ Err₂ : Type

data ComputationResult {a : Level} (Err : Type) (R : Type a) : Type a where
  success : R  ComputationResult Err R
  failure : Err  ComputationResult Err R

isFailure :  {A : Type a}  ComputationResult Err A  Type a
isFailure x = ∃[ e ] x  failure e

module _ {a b} {E : Type} {A : Type a} {B : Type b} where
  caseCR_∣_∣_ : (ma : ComputationResult E A)  (∀ {a}  ma  success a  B)  (isFailure ma  B)  B
  caseCR ma  f  g with ma
  ... | success _ = f refl
  ... | failure e = g (e , refl)

  caseCR-success :  {a} {ma : ComputationResult E A} {f :  {a}  ma  success a  B} {g : isFailure ma  B}
     (eq : ma  success a)
     caseCR ma  f  g  f eq
  caseCR-success refl = refl

  caseCR-failure : {ma : ComputationResult E A} {f :  {a}  ma  success a  B} {g : isFailure ma  B}
     (eq : isFailure ma)
     caseCR ma  f  g  g eq
  caseCR-failure (_ , refl) = refl

instance
  Bifunctor-ComputationResult :  {a : Level}  Bifunctor {_} {a} ComputationResult
  Bifunctor-ComputationResult .bimap _ f (success x) = success $ f x
  Bifunctor-ComputationResult .bimap f _ (failure x) = failure $ f x

  Functor-ComputationResult :  {E : Type}  Functor (ComputationResult E)
  Functor-ComputationResult ._<$>_ f (success x) = success $ f x
  Functor-ComputationResult ._<$>_ _ (failure x) = failure x

  Applicative-ComputationResult :  {E : Type}  Applicative (ComputationResult E)
  Applicative-ComputationResult .pure = success
  Applicative-ComputationResult ._<*>_ (success f) x = f <$> x
  Applicative-ComputationResult ._<*>_ (failure e) _ = failure e

  Monad-ComputationResult :  {E : Type}  Monad (ComputationResult E)
  Monad-ComputationResult .return = success
  Monad-ComputationResult ._>>=_ (success a) m = m a
  Monad-ComputationResult ._>>=_ (failure e) _ = failure e

  Show-ComputationResult :  {l} {E : Type} {A : Type l}   Show E    Show A   Show (ComputationResult E A)
  Show-ComputationResult .show (success x) = "success "  show x
  Show-ComputationResult .show (failure e) = "failure "  show e

map-failure :  {A B C : Type} {f : A  B} {x : C} {ma}  ma  failure x  map f ma  failure x
map-failure refl = refl

success-maybe :  {R : Type}  ComputationResult Err R  Maybe R
success-maybe (success x) = just x
success-maybe (failure _) = nothing

failure-maybe :  {R : Type}  ComputationResult Err R  Maybe Err
failure-maybe (success _) = nothing
failure-maybe (failure x) = just x

_≈ᶜʳ_ :  {A}  ComputationResult Err A  ComputationResult Err A  Type
x ≈ᶜʳ y = success-maybe x  success-maybe y

module _ (STS : I  O  Type) where

  ExtendedRel : I  ComputationResult Err O  Type
  ExtendedRel i (success o) = STS i o
  ExtendedRel i (failure _) =  o  ¬ STS i o

  record Computational Err : Type₁ where
    constructor MkComputational
    field
      computeProof : (i : I)  ComputationResult Err (∃[ o ] STS i o)

    compute : I  ComputationResult Err O
    compute i = map proj₁ $ computeProof i

    field
      completeness : (i : I)  (o : O)  STS i o  compute i  success o

    open ≡-Reasoning

    computeFail : I  Type
    computeFail i = isFailure $ compute i

    ≡-success⇔STS : compute i  success o  STS i o
    ≡-success⇔STS {i} {o} with computeProof i in eq
    ... | success (o' , h) = mk⇔  where refl  h) λ h' 
      begin success o' ≡˘⟨ completeness _ _ h 
            compute i  ≡⟨ completeness _ _ h' 
            success o  
    ... | failure y = mk⇔  ()) λ h 
      begin failure _ ≡˘⟨ map-failure eq 
            compute i ≡⟨ completeness _ _ h 
            success o 

    failure⇒∀¬STS : computeFail i   o  ¬ STS i o
    failure⇒∀¬STS comp≡nothing o h rewrite ≡-success⇔STS .Equivalence.from h =
      case comp≡nothing of λ ()

    ∀¬STS⇒failure : (∀ o  ¬ STS i o)  computeFail i
    ∀¬STS⇒failure {i = i} ¬sts with computeProof i
    ... | success (x , y) = ⊥-elim (¬sts x y)
    ... | failure y = (y , refl)

    failure⇔∀¬STS : computeFail i   o  ¬ STS i o
    failure⇔∀¬STS = mk⇔ failure⇒∀¬STS ∀¬STS⇒failure

    recomputeProof : STS i o  ComputationResult Err (∃[ o' ] STS i o')
    recomputeProof _ = computeProof _

module _ (STS : I  O  Type)  comp : Computational STS Err  where

  open Computational comp

  ExtendedRelSTS = ExtendedRel STS

  ExtendedRel-compute : ExtendedRelSTS i (compute i)
  ExtendedRel-compute {i} with compute i in eq
  ... | success o = Equivalence.to ≡-success⇔STS eq
  ... | failure _ = λ o h  case trans (sym $ Equivalence.from ≡-success⇔STS h) eq of λ ()

  open ≡-Reasoning

  ExtendedRel-rightUnique : ExtendedRelSTS i o  ExtendedRelSTS i o'  _≈ᶜʳ_ {Err = Err} o o'
  ExtendedRel-rightUnique {o = success x} {success x'} h h'
    with trans (sym $ Equivalence.from ≡-success⇔STS h) (Equivalence.from ≡-success⇔STS h')
  ... | refl = refl

  ExtendedRel-rightUnique {o = success x} {failure _}  h h' = ⊥-elim $ h' x h
  ExtendedRel-rightUnique {o = failure _} {success x'} h h' = ⊥-elim $ h x' h'
  ExtendedRel-rightUnique {o = failure a} {failure b}  h h' = refl

  computational⇒rightUnique : STS i o  STS i o'  o  o'
  computational⇒rightUnique h h' with ExtendedRel-rightUnique h h'
  ... | refl = refl

  Computational⇒Dec :  _ : DecEq O   Dec (STS i o)
  Computational⇒Dec {i} {o}
    with compute i | ExtendedRel-compute {i}
  ... | failure _ | ExSTS = no (ExSTS o)
  ... | success x | ExSTS with x  o
  ... | no ¬p     = no  λ h  ¬p $ sym $ computational⇒rightUnique h ExSTS
  ... | yes refl  = yes ExSTS

module _ {STS : I  O  Type} (comp comp' : Computational STS Err) where

  open Computational comp  renaming (compute to compute₁)
  open Computational comp' renaming (compute to compute₂)

  compute-ext≡ : compute₁ i ≈ᶜʳ compute₂ i
  compute-ext≡ = ExtendedRel-rightUnique _  comp 
    (ExtendedRel-compute _  comp ⦄) (ExtendedRel-compute _  comp' ⦄)

open Computational ⦃...⦄

Computational⇒Dec' :
  {STS : I  O  Type}  comp : Computational STS Err   Dec (∃[ o ] STS i o)
Computational⇒Dec' with computeProof _ in eq
... | success x = yes x
... | failure x = no λ (_ , h)  failure⇒∀¬STS (-, cong (map proj₁) eq) _ h

record InjectError Err₁ Err₂ : Type where
  field injectError : Err₁  Err₂

open InjectError

instance
  InjectError-⊥ : InjectError  Err
  InjectError-⊥ = λ where
    .injectError ()

  InjectError-Id : InjectError Err Err
  InjectError-Id = λ where
    .injectError  id